이거뭐야
this
는 위 그림에서 보이듯이 execute context의this
를 말한다.
Javascript의 Excution context 내부를 살펴보면 this
라는 Value의 자리가 존재한다. 흔히 타 언어에서 사용하는 self
와 this
는 객체, 자기자신 을 의미하기 때문에 이해하기가 수월하였다. 하지만 JS의 this
는 개념이 조금 다르기 때문에 이해하는 것이 쉽지않다.
뭐 그래도 타 언어와 동일하게 this는 객체를 참조하는 것이니만큼 일단 선행되어야할 기본지식은 Object - Prototype 에 대한 이해이다.
그럼 먼저 Object - Prototype 에 대해 짚고 넘어가자.
자바스크립트는 prototype
기반의 객체지향적 언어
일반적인 객체지향언어는 class
기반이지만, 자바스크립트는 기본적으로 class
가 없다. (ES6부터 문법만 추가)
때문에 상속
도 없다. -> (bind
를 통해 상속을 구현할 수는 있다.)
이러한 특성을 prototype
을 통해 구현해낸다.
자바스크립트는 function()
과 new
연산자를 통해 클래스를 비슷하게 흉내낼 수 있음.
function Person() {
this.eyes = 2;
this.nose = 1;
}
var kim = new Person();
var park = new Person();
console.log(kim.eyes); // => 2
console.log(kim.nose); // => 1
console.log(park.eyes); // => 2
console.log(park.nose); // => 1
kim
과 park
는 eyes
, nose
2개의 프로퍼티를 가지고 있음. 객체를 100개 만들면 200개 변수 메모리가 할당됨.
prototype
은 이러한 비효율성을 해결해 줌.
// 클래스 선언
function Person() {}
Person.prototype.eyes = 2;
Person.prototype.nose = 1;
var kim = new Person();
var park = new Person();
console.log(kim.eyes); // -> 2
console.log(park.eyes); // -> 2
이제부터 생성자로서 사용될 함수를 선언하였을 때, 해당 함수를 통해 객체를 생성하였을 때 일어나는 일들을 살펴볼 것이다.
__proto__
를 통한 prototype Link
와 prototype property
가 가리키는 prototype Object
가 존재한다. 이 둘을 통칭하여 prototype
이라고 한다. function A() {}; // 빈 객체를 생성하는 함수 선언
var A = new A(); // new 연산자를 통한 객체 생성
console.log(A);
이제부터 저 위 그림을 파헤쳐보자!
객체는 언제나 함수에서 시작된다! 따라서 다음 과정도 함수로부터 시작되는 것이다.(literal을 통한 객체 생성.)
var obj = {};
var obj = new Object();
즉, Object()
는 자바스크립트에서 기본적으로 제공하는 함수
이다.
함수가 정의되는 순간 발생하는 일 2가지는 다음과 같다.
생성자로 사용될 함수 뿐 만 아니라 모든 함수가 이러한 과정을 거치며, 이를 이해함으로써 prototype에 대한 이해가 가능할 것이다.
Constructor
자격이 부여되면 new
연산자를 통해 인스턴스를 생성할 수 있게 됨.함수 뿐만 아니라 prototype Object
도 함께 생성된다.
위 그림과 동일하게 함수는 arguments
, name
, caller
, [[scope]]
등과 함께 function Object(왼쪽)의 prototype
프로퍼티를 통해 prototype Object
을 참조할 수 있다. prototype Object
는 일반적인 객체와 같고, 기본적인 프로퍼티로 constructor
와 __proto__
를 가지고 있다.
함수 객체만 유일하게 prototype
property를 가지며, 이 property는 해당 함수로 새롭게 생성되는 객체가 가질 틀
과 같다.
함수 객체가 생성하는 prototype Obect
만이 오로지 constructor
라는 property를 가지며, 생성자로 사용된 function Object를 가리킨다.
__proto__
는 prototype Link 이다. (아래에서 설명)
다음 코드를 보고 다시 이해해보도록 하자.
function Person() {};
Person.prototype.eyes = 2;
Person.prototype.nose = 1;
var kim = Person();
var park = Person();
이제 왜 Person.prototype을 사용하는지 알 수 있을 것이다.
Person
함수가 생성되며 함께 생성된 prototype Object
에 eyes
와 nose
프로퍼티를 추가하여 Person()
함수로 생성된 객체는 prototype Object
의 eyes
, nose
프로퍼티를 참조할 수 있다.
prototype Link ?
kim
의 eyes
프로퍼티에 접근 했을 때, kim
에게는 eyes
라는 프로퍼티가 없는데도, 2라는 값을 반환하는 것을 볼 수 있다.__proto__
property생성자의 prototype Obect
의 property가 kim
이라는 객체의 property가 되는 것이 아닌 것을 볼 수 있다.
그럼에도 불구하고 가능한 것은 kim
의 __proto__
프로퍼티에 의해서 가능하다.
**__proto__
** 는 인스턴스가 생성될 때 조상이었던 함수의 prototype Object
를 가리킨다.
__proto__
의 형태를 보자.
객체가 생성될 때 가지는 __proto__
property에 의해 단계적으로 상위 생성자 함수의 prototype Obect
의 property와 method가 참조 가능하게 하는 reference chain을 의미한다.
Scope Chain이라는 array로 구현되어 있다면, Prototype Chain은 말그대로 reference(참조)의 연속으로 구현 되어있다.
함수 또한 하나의 객체임을 기억하며, 간단한 예제로 지금까지 공부했던 것 살펴보자.
요약하자면, function Person() {}
를 생성하면서, Person prototype Object
가 함께 생성되고, 거기에는 constructor
, __proto__
라는 프로퍼티가 존재한다.
Person()
을 통해 Person
의 인스턴스 kim
을 생성했을 때, 그 kim
에게는 __proto__
프로퍼티가 기본적으로 존재하고, 이는 Person prototype Object
의 참조를 담고있다.
kim
인스턴스가 eyes
나 nose
프로퍼티를 직접 가지고 있지 않기 때문에 eyes
, nose
프로퍼티를 찾을 때 까지 상위 프로토타입을 탐색하고, 최상위 프로토타입인 Object의 Prototype Object
까지 탐색했는데도 못찾은 경우 undefined
를 리턴한다.
이렇게 __proto__를 통해 상위 프로토타입과 연결되어 있는 형태를 프로토타입 체인(prototype chain) 이라고 한다.
이제 하나의 예제를 더 살펴보면서 옵져버 패턴의 의미를 살펴보자.
var A = function () { };
A.prototype.x = function () {
console.log('hello');
};
var B = new A();
var C = new A();
B.x();
> hello
C.x();
> hello
A.prototype.x = function () {
console.log('world');
};
B.x();
> world
C.x();
> world
prototype chain을 통해 상속되는 x
method는 각 객체마다 할당되는 것이 아니라 Prototype Object
의 method를 Prototype Chain
을 통해 참조하는 것이다.
즉, method를 공유하게 되는데, 위의 예제처럼 객체간의 관계가 1:N의 형태로 정의되는 것, 상태를 가지고 있는 객체와 상태의 변경을 관리하는 객체가 구분되어있는 객체 간의 관계를 Observer pattern
라고한다.
옵저버 패턴 말고도 Javascipt에서는 다양한 패턴이 존재한다.
지금까지 Prototype 기반의 객체지향적 언어인 Javscript에 대해 공부해보았다. 객체의 생성과 호출에 대해 이해가 되었다면 이제 본격적으로
this
에 대해 알아보자.
this
에 바인딩되는 객체는 객체의 생성시점과 호출방식 에 따라 달라진다.
window
에, Server-side는 global
에 바인딩된다.chrome의 console
nodejs의 console
함수의 내부함수일 때,
function foo() {
console.log("foo's this: ", this); // window
function bar() {
console.log("bar's this: ", this); // window
}
bar();
}
foo();
method의 내부함수일 때,
method 또한 하나의 함수이므로 위의 예제와 동일한 가정을 의미한다.
var value = 1;
var obj = {
value: 100,
foo: function() {
console.log("foo's this: ", this); // obj
console.log("foo's this.value: ", this.value); // 100
function bar() {
console.log("bar's this: ", this); // window
console.log("bar's this.value: ", this.value); // 1
}
bar();
}
};
obj.foo();
var obj1 = {
name: 'Lee',
sayName: function() {
console.log(this.name);
}
}
var obj2 = {
name: 'Kim'
}
obj2.sayName = obj1.sayName;
obj1.sayName(); //Lee
obj2.sayName(); //Kim
Prototype Object
의 method를 호출할 때에도 호출한 객체에 바인딩되며, Prototype Object
또한 하나의 객체로 바인딩 될 수 있다.function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
var me = new Person('Lee');
console.log(me.getName());
//prototype 또한 하나의 객체를 의미한다.
Person.prototype.name = 'Kim';
console.log(Person.prototype.getName());
이러한 의미는 생성자로서의 함수가 아니더라도 new 연산자를 사용할 수 있다는 뜻이다.
new 연산자를 사용하지 않으면, 객체를 생성하지 않으므로 의미가 없다.
이 것은 new 연산자가 호출 되었을 때의 과정을 살펴보면서 이해할 수 있다.
예제를 보면서 이해하자.
function Person(name) {
// 생성자 함수 코드 실행 전 -------- 1
this.name = name; // --------- 2
// 생성된 함수 반환 -------------- 3
}
var me = new Person('Lee');
console.log(me.name);
빈 객체 생성 및 this 바인딩
new
연산자에 의해 Object prototype object
의 빈 객체가 생성되고, 현재 execute context
의 this
에 이 빈 객체를 바인딩한다.
Prototype Chain 연결
이 후, 빈 객체의 __proto__
를 생성자 함수의 prototype object
에 link시켜 chain을 완성시킨다.
this
를 통한 property / method 생성.
바인딩되어 있는 빈 객체에 생성자 함수에 명시된 property와 method를 추가시킨다.
새롭게 생성된 객체를 return
생성자 함수를 new를 사용하지 않고 호출한다면?
위에서 보다시피 함수 내부에서도 전역객체에 할당된다고 하였다. new를 사용하지 않았을 때 일반적인 함수와 동일하게 동작함을 볼 수 있다.
function Person(name) {
this.name = name;
};
var me = Person('Lee');
console.log(me); // undefined
console.log(window.name); // Lee
이 후의 Chapter에서 여러가지 고차함수를 다뤄볼 예정이나, 일단 함수의 호출에 사용되는 고차함수들을 먼저 살펴보자.
func.apply(thisArg, [argsArray])
func - apply 함수를 호출할 함수 객체명.
thisArg - 해당함수 호출시, this에 바인딩할 객체명.
argsArray -해당함수 호출시, 필요한 인자값.
주요기능이 함수를 호출하는 것에 있다.
Function.prototype.call()
함수객체의 최상위 계층의 Function.prototype의 method이므로 모든 함수객체가 가지고 있는 method이다.
예제로 살펴보자.
function hello(says){
this.name = 'chanwoo';
this.sayHello = function(){
console.log(this.name + says);
}
}
var sub = {
name : 'sub'
}
hello.apply(sub, [' hi']);
sub.sayHello();
// output
// chanwoohi
apply함수를 통하여 hello라는 생성자 함수를 호출하되, new를 사용하지 않고 기존 객체에 this를 바인딩시켜 동작하게하는 모습이다.
function Person(name) {
this.name = name;
}
Person.prototype.doSomething = function(callback) {
if(typeof callback == 'function') {
// --------- 1
callback();
}
};
function foo() {
console.log(this.name); // --------- 2
}
var p = new Person('Lee');
p.doSomething(foo); // undefined
이전에도 설명하였듯, 객체의 메소드이더라도 내부함수의 경우, this가 전역객체에 바인딩된다고 이야기하였다. callback함수에도 마찬가지이므로 일반호출이 아닌, 현재의 객체까지 call로 넘겨주는 것이다.
function Person(name) {
this.name = name;
}
Person.prototype.doSomething = function (callback) {
if (typeof callback == 'function') {
callback.call(this);
}
};
function foo() {
console.log(this.name);
}
var p = new Person('Lee');
p.doSomething(foo); // 'Lee'
apply와 동일하지만, arguments들을 각각으로 넣어주는 경우 사용한다.
기존 new 생성자는 빈 객체를 생성하여 __proto__
를 연결하는 방식이나 빈 객체를 생성하는 것이 아닌, 기존 객체에 바인딩하여 method, property를 정의하는 방식이 될 것.
예제 추가예정
JS에서 this
는 객체, 자기 자신 이다. 여기까지만 본다면 다른 언어의 this
와 동일하지만 Object 와 Prototype 의 개념이 들어가면서 JS만의 특별한 this
가 완성되었다. 콜백, 이벤트등 this
에 바인딩되는 객체와 시점이 애매모호할 때 매우 필요한 학습이라고 생각하기에 작성하게 되었다.
더 필요한 학습