📒 인사이드 자바스크립트 중 메모해야할 부분만 적었습니다.
0. 클래스 기반의 언어 - 프로토타입기반의 언어 클래스 기반의 언어
클래스로 객체의 기본적인 형태와 기능을 정의하고, 생성자로 인스턴스를 만들어서 사용할 수 있다.
런타임에 바꿀 수 없다.
정확성, 안전성, 예측성등의 관점에서는 프로토타입기반의 언어보다 좀더 나은 결과를 보장.
JAVA, C++
프로토타입 기반의 언어
객체의 자료구조, 메서드 등을 동적으로 바꿀 수 있다.
자바스크립트
1. 클래스, 생성자, 메서드 1 2 3 4 5 6 7 8 9 10 11 12 13 function Person (arg ) { this .name = arg; this .getName = function ( ) { return this .name ; } this .setName = function (value ):void { this .name = value; } } const me = new Person ("Kim" );console .log (me.getName ()); me.setName ("Joy" ); console .log (me.getName ())
1 2 const you = new Person ("Gray" );const him = new Person ("Lee" );
공통적으로 사용될 수 있는 setName과 getName 메서드가 중복으로 메모리에 올려놓게 된다.
setName과 getName을 재사용하쟈!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function Person (arg ){ this .name = arg; } Person .prototype .getName = function ( ) { return this .name ; } Person .prototype .setName = function (value ) { this .name = value; } const you = new Person ("Gray" );const him = new Person ("Lee" );console .log (you.getName ()) console .log (him.getName ())
프로토타입 메서드를 만드는 루틴을 함수체인의 더 상위인 Function 프로토타입에 method라는 이름으로 만들어놓고 재사용하는 방법도 있다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Function .prototype .method = function (name, function ) { if (!this .prototpye [name]) { this .prototype [name] = function ; } } function Person (arg ) { this .name = arg; } Person .method ('setName' , function (value ){ this .name = value; }) Person .method ('getName' , function ( ) { return this .name ; }) const me = new Person ("me" );const you = new Person ("you" );console .log (me.getName ());console .log (you.getName ());
2. 상속
자바스크립트는 클래스를 기반으로 하는 전통적인 상속을 지원하지 않는다.
자바스크립트 특성 중 객체 프로토타입 체인 을 이용하여 상속을 구현해낼 수 있다.
상속 구현방법
클래스 기반 전통적인 상속 방식을 흉내냄 (컨텍스트 자체를 상속받음)
클래스 개념 없이 객체의 프로토타입으로 상속을 구현하는 방식 => Prototypeal inheritance
2-1. 프로토타입을 이용한 상속 1 2 3 4 5 function create_object (o ) { function F ( ) {} F.prototype = o; return new F (); }
인자로 들어온 객체(o)를 부모로 하는 자식 객체(F)를 생성하여 반환한다. => 프로토타입의 특성을 활용하여 상속을 구현하는것 = 프로토타입 기반의 상속
예시
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function create_object (o ) { function F ( ) {} F.prototype = o; return new F (); } var person = { name : "Joy" getName : function ( ) { return this .name ; } setName : function (arg ){ this .name = arg; } } var me = create_object (person)me.name me.getName () me.setName ("Kim" ) me.name me.getName ()
클래스에 해당하는 생성자 함수를 만들지도 않았고,
그 클래스의 인스턴스를 따로 생성하지도 않았다.
person 객체와 이 객체를 프로토타입 체인으로 참조할 수 있는 자식 객체 me를 만들어서 사용함.
1 2 me.setAge = function (age ) {...} me.getAge = function ( ) {...}
위의 방식으로 확장할 수 있지만, 코드가 지저분해질 수 있다. **extend() **라는 이름의 함수로 객체에 자신이 원하는 객체 혹은 함수를 추가시킨다.
__jQuery의 extend 함수
1 2 3 4 5 6 7 8 9 10 jQuery.extend = jQuery.fn .extend = function (obj : 자식, prop : 부모 ) { if (!prop) { prop = obj; obj = this ; } for ( var i in prop ) { ob[i] = prop[i]; } return obj; }
jQuery.fn
은 jQuery의 프로토타입
$.extend()나 var elem = new jQuery(...); elem.extend()
형태로 호출가능
ob[i] = prop[i];
은 얕은 복사 (shallow copy) => 참조값을 복사하는 경우 영향이 생긴다. 이를 방지하기 위해 깊은 복사를 해야함.
깊은 복사를 위해서빈 객체를 만들어서 extend 함수를 재귀적으로 호출
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ... for (; i < length; i++){ if ( (options = arguments [i]) != null ) { for (name in options){ src = target[name]; copy = options[name]; if (target === copy){ continue ; } if (deep && copy && (jQuery.isPlainObject (copy) || (copyIsArray = jQuery.isArray (copy))) ){ ) if ( copyIsArray ) { copyIsArray = false ; clone = src && jQuery.isArray (src) ? src : []; } else { clone = src && jQuery.isPlainObject (src) ? src : {}; } target[name] = jQuery.extend (deep, clone, copy); } else if (copy !== undefined ) { target[name] = copy; } } } return target; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const person = { name : 'joy' , getName : function ( ){ return this .name ; }, setName : function (arg ){this .name = arg;} } function create_object (o ) { function F ( ) {}; F.prototype = o; return new F (); } function extend (obj, prop ) { if (!prop) {prop = obj; obj = this ;} for (let i in prop) obj[i] = prop[i]; return obj; } const student = create_object (person);const added = { setAge : function (age ){ this .age = age; } getAge : function ( ) {return this .age ;} } extend (student, added); student.setAge (25 ); console .log (student.getAge ());
person객체를 갖고있는 프로토타입을 갖고있는 student 인스턴스(컨텍스트)가 added를 상속받는다. (deep copy함)
student 인스턴스에는 added 객체가 복사된다.
2-2. 클래스 기반의 상속 1번은 객체의 상속이었고, 지금은 클래스의 역할을 하는 함수를 상속하는 것을 설명한다. (컨텍스트 상속)
1 2 3 4 5 6 7 8 9 10 11 12 function Person (arg ) { this .name = arg;}Person .prototype .setName = function (value ) { this .name = value; };Person .prototype .getName = function ( ) { return this .name ; }function Student (arg ) {}const you = new Person ('JoyKim' ); Student .prototype = you;const me = new Student ('NaYoung' );me.setName ('Kim' ); console .log (me.getName ());
you는 Person의 인스턴스 (name: JoyKim)
Student의 프로토타입은 you를 가리킨다.
me는 Student의 인스턴스
Student에는 인자를 받을 name이 없으므로 ‘NaYoung’을 넣어줘도 아무런 적용 안됨
이를 위해서 인스턴스가 생성될때 부모함수가 바로 실행될 수 있도록 Student에 실행코드를 넣는다.1 2 3 function Student (arg ){ Person .apply (this , arg) }
me객체에서 setName을 호출하면 프로토타입체이닝에 의해서 Person까지올라간다.
이 로직의 단점은 me의 prototype이 Student.prototype이고, 이는 곳 you를 가리킨다는 것인데, 이렇게 되면 me가 you의 자식 개념이 되면서 잘못된 설계가 된다.
me와 you의 독립성을 위해서 중간 역할을 해주는 프로토콜 빈 함수 F()를 추가한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Person (arg ) { this .name = arg;}Function .prototype .method = function (name, func ) { this .prototype [name] = func };Person .method ('setName' , function (value ) { this .name = value; };)Person .method ('getName' , function ( ) { return this .name ; })function Student (arg ) {}function F ( ){};F.prototype = Person .prototype ; Student .prototype = new F ();Student .prototype .constructor = Student ;Student .super = Person .prototype ; const me = new Student ('NaYoung' );const you = new Person ('YoungRan' );me.setName ('Kim' );
Function 프로토타입에 프로토타입 함수를 만들어주는, 재사용할 수 있는 method라는 메서드를 추가한다. (아오 네이밍 예시 헷갈)
F의 프로토타입을 Person의 프로토타입을 참조하게 함으로써 중간역할을 하게함
그 중간역할을 하는 F의 인스턴스를 Student의 프로토타입이 참조하도록 함. (현재 Student => F => Person)
프로토타입 체인을 위해서 Student 프로토타입의 생성자를 Student로 할당.
Student가 Person.prototype에도 접근하기 위해서 super라는 메서드 생성
me는 Person을 상속받은 Student의 인스턴스이고 you는 Person의 인스턴스.
위 로직을 모듈화 시키면..(by.스토얀 스테파노프[JavaScript Pattersn])
1 2 3 4 5 6 7 8 9 const inherit = function (Parent, Child ){ const F = function ( ){}; return function (Paretn, Child ){ F.prototype = Parent .prototype ; Child .prototype = new F (); Child .prototype .constructor = Child ; Child .super = Parent .prototype ; } }
클로저는 F()함수를 지속적으로 참조한다. F()는 가비지 컬렉션의 대상이 되지 않고 계속 남아 있다. 이를 이용해 함수 F()의 생성은 단 한 번 이루어지고 inherit함수가 계속해서 호출되어도 함수 F()의 생성을 새로 할 필요가 없다.
3. 캡슐화 (feat. 클로저)
정보 공개의 여부. 정보 은닉 개념 (Typescript에서는 public
, private
, protected
멤버를 선언함으로써 해당 정보를 외부로 노출시킬지 여부를 결정)
자바스크립트 es6에서는 get, set 키워드로 외부에서 해당 클래스 혹은 함수 내부에 접근 할 수 있다. (get은 readonly)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const Person = function (arg ) { let name = arg? arg : "joy" ; this .getName = function ( ) { return name; } this .setName = function (arg ) { name = arg; } } const me = new Person ();console .log (me.getName ());me.setName ('NaYoung' ); console .log (me.getName ());console .log (me.name );
Person의 내부 public 함수들은 **클로저 **역할을 하면서 name에 접근하고 있다.
1 2 3 4 5 6 7 8 9 10 11 const Person = function (arg ){ const name = arg? arg : 'joy' ; return { getName : function ( ) { return name;}, setName : fucntion (arg ){ name = arg;} } } const me = Person ()console .log (me.getName ())
접근하는 private 멤버가 객체나 배열 이면(레퍼런스) 얕은 복사로 참조만을 반환 하므로 사용자가 이후 이를 쉽게 변경할 수 있다. (Deep copy, Shallow copy)
객체만을 반환하기 때문에 Person 함수의 프로토타입에 접근할 수 없다.1 2 3 4 5 6 7 8 9 const Person3 = function (arg ){ const name = arg? arg : 'joy' ; var Func = function ( ){}; Func .prototype = { getName : function ( ) { return name; }, setName : function (arg ) {name = arg;} } return Func ; }();
4. 객체지향 프로그래밍 응용 예제
함수의 프로토타입 체인 extend 함수 인스턴스를 생성할 때 생성자 호출을 이용해서 자바스크립트로 클래스 기능을 하는 함수 만들기
4-1-1. subClass 함수 구조 subClass함수는 변수 및 메서드가 담긴 객체를 인자로 받은 부모 함수를 상속받는 자식 클래스를 만든다.
부모함수는 subClass() 함수를 호출할 때 this 객체를 의미한다.
1 2 const SuperClass = subClass (obj); const SubClass = SuperClass .subClass (obj);
이처럼 SuperClass를 상속받는 subClass를 만들고자 할 때, SuperClass.subClass()의 형식으로 호출하게 구현한다. 참고로 최상위 클래스인 SuperClass는 자바스크립트의 Fucntion을 상속받게 한다.
1 2 3 4 5 6 7 8 9 function subClass (obj ){ }
4-1-2. 자식 클래스 생성 및 상속 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function subClass (obj ) { ... const parent = this ; const F = function ( ){}; const child = function ( ){}; F.prototype = parent.prototype ; child.prototype = new F (); child.prototype .constructor = child; child.parent = parent.prototype ; child.parent_constructor = parent; ... return child; }
자식 클래스는 child 라는 이름의 함수 객체를 생성함으로써 만들어졌다.
4-1-3. 자식 클래스 확장 1 2 3 4 5 for (let i in obj){ if (obj.hasOwnProperty (i)){ child.prototype [i] = obj[i]; } }
hasOwnProperty
인자로 넘기는 이름에 해당하는 프로퍼티가 객체 내에 있는지를 판다.
프로토타입 체인을 타고 올라가지 않고 해당객체 내에서만 찾는다는 것에 유의 1 2 3 4 5 o = new Object (); o.prop = 'exists' ; o.hasOwnProperty ('prop' ); o.hasOwnProperty ('toString' ); o.hasOwnProperty ('hasOwnProperty' );
4-1-4. 생성자 호출 클래스의 인스턴스가 생성될 때, 클래스 내에 정의된 생성자가 호출돼야하다. 부모 클래스의 생성자 역시 호출되어야한다. (초기화를 위해서)
1 2 3 4 const child = function ( ) { if (parent.hasOwnProperty ('_init' )){ parent._init .apply (this , arguments ); } if (child.prototype .hasOwnProperty ('_init' )){child.prototype ._init .apply (this , arguments );} }
1 2 3 4 5 const SuperClass = subClass ();const SubClass = SuperClass .subClass ();const Sub _SubClass = SubClass .subClass ();const instance = new Sub _SubClass();
instance 생성시 SuperClass 생성자가 호출되지 않는다. => 부모클래스의 생성자를 호출하는 코드를 재귀적으로 구현하여 해결한다.
1 2 3 4 5 const child = function ( ) { const _parent = child.parent_constructor ; if (_parent && _parent !== Function ){ _parent.apply (this , arguments ); } if (child.prototype .hasOwnProperty ('_init' )){child.prototype ._init .apply (this , arguments );} }
현재 클래스의 부모 생성자가 있으면, 그 함수를 호출하다. 다만 부모가 Function이 경우는 최상위 클래스에 도달했으므로 실행하지 않는다.
최종
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function subClass (obj ){ const parent = this === window ? Function : this ; const F = function ( ){} const child = function ( ) { const _parent = child.parent ; if (_parent && _parent !== Function ){ parent.apply (this , arguments ); } if (child.prototype ._init )){ child.prototype ._init .apply (this , arguments ); } } F.prototype = parent.prototype ; child.prototype = new F (); child.prototype .constructor = child; child.parent = parent; child.subClass = arguments .callee ; for (let i in obj){ if (obj.hasOwnProperty (i)){ child.prototype [i] = obj[i]; } } return child }
By Joy.
내부함수를 선언할때와 this로 바인딩된 함수를 선언하는 것의 차이점? 기준?
프로토타입 체이닝을 만든 이유 객체지향 프로그래밍을 지원하기 위해 부모객체를 가리키는 참조링크 형태로 숨겨진 프로퍼티.
(Naming) 복사시 복사할 대상을 copy, 복사의 결과물을 clone
shallow copy와 다르게 deep copy는 재귀적으로 호출해야함.(다시 정리)