this는 Javascript의 키워드다.
우리는 this를 이용해서 내/외부 Scope를 이외에 새로운 Scope를 가질 수 있다. (정확히는 this는 객체를 참조한다.)
즉, 함수에게 내/외부 Scope 이외에 "변수를 얻을 수 있는 새로운 원천"을 this를 통해 제공해 줄 수 있다.는 것이다.
예를 들면, Person.getAge()
의 경우
getAge() 함수 내부에서 this
를 사용하여 Person의 Scope(변수나 함수)를 사용할 수 있다.
이것이 가능한 이유는 실행 컨텍스트(Execution Context)의 this binding component 덕분이다.
this 키워드를 사용하여 함수를 호출한 오브젝트에 접근할 수 있도록, 함수의 실행 컨텍스트안에서 this를 참조하고 있어야 한다. EC에서 오브젝트를 참조하기 위한 공간이 바로 TBC(This Binding Component)다.
우리는 외부 렉시컬 환경 참조를 통해 함수 바로 바깥에 있는 것들에 접근할 수 있으며, 선언적 환경 레코드를 통해 함수 내부에 있는 것들에 접근할 수 있다. 함수 내외부 뿐만아니라 TBC 덕분에, this를 통해 다른 객체에도 접근이 가능하다.
this로 참조할 객체는 호출 시점에 결정된다. 호출 시 함수 앞에 적힌 Object가 this로 참조할 객체가 된다.
뿐만 아니라, call()
, apply()
, bind()
등의 메소드를 통해, 호출 때 마다 this로 참조할 객체를 변경할 수도 있다.
즉 this는 동적으로 결정된다. 이것이 가능한 이유는 TBC만큼은 동적으로 바인딩되기 때문이다.
obj.func()
형태로 함수(메소드)를 호출하면, 함수 func에서 this
로 오브젝트 obj를 참조할 수 있다.
function test(){ return this }
console.log(this === window) //=> true
// 전역 함수의 this(Global Object)와 window가 동일
let value = 100; // 전역변수
console.log(this.value) //=> 100
// 글로벌 Scope에서 this로 자기 자신을 참조
// this가 글로벌 오브젝트를 참조하므로, this.value 형태로 글로벌 변수 사용이 가능하다.
var value = 100;
console.log(window.value) //=> 100
// window가 글로벌 오브젝트를 참조하므로, `window.value`형태로 글로벌 변수 사용이 가능하다.
this.value = 100;
console.log(window.value) //=> 100
// 현재 상태에서 this는 **Global Object**를 참조하므로 value는 Global Object에 설정된다.
// window가 **Global Object**를 참조하므로 value를 사용할 수 있다.
Host Object
window 오브젝트처럼, 다른 오브젝트를 마치 내것처럼 사용하는 개념을 Host Object라고 한다.
"DOM 오브젝트" 또한 Host Object다.
window와 전역 컨텍스트에서 참조하는 this가 똑같기 때문에, 종종 Global Object가 Window라고 하곤 한다. 그러나 실체는 다르다는 것을 알고 있어야 한다.
window.onload = function(){
console.log(this === window) //=> true
}
onload()
가 비록 이벤트를 처리하는 핸들러 함수라도, function Object이다.onload
앞에 작성된 window를 this 바인딩 컴포넌트에 바인딩하게 된다.window.onload = function(){
var value = 100
console.log(this.value) //=> undefined
}
// 변수 `value`는 핸들러 함수의 지역변수다.
// this는 window object를 참조하게 되므로, this.value로 지역변수에 접근할 수 없다.
var book = {
point : 100,
member : {
point : 200,
get(){
console.log(this.point) //200
console.log(this === book.member)
//=> true
//this는 호출한 오브젝트 그 자체를 참조한다.
}
}
}
book.member.get()
book.member.get()
에서 this로 참조하는 것은 member
라는 오브젝트다. book은 그저 member를 찾아가는 경로에 불과하다.var point = 999;
var obj = {
point: 111,
get() {
var point = 555;
console.log(this === window);
console.log(this === obj);
console.log(this.point);
},
};
var func = obj.get;
func(); //=> true; false; 999;
obj.get(); //=> false; true; 111;
func()
에 대한 결과func()
는 obj의 get과 같은 function Object를 받았다. 그러나 호출할 때 오브젝트를 명시하지 않았다.obj.get()
에 대한 결과obj.get()
호출할 때 오브젝트는 obj다.get()
의 실행 컨텍스트의 this 바인딩 컴포넌트는 obj를 참조하게 되며 이때, this는 obj다.우리는 오브젝트.함수이름()
형태로 함수를 호출하지만 글로벌 오브젝트의 경우, 오브젝트의 이름이 없으므로 함수 이름만 작성하여 호출한다.
__proto__
접근의 목적var book = {};
book.Point = function (point) { this.point = point; };
book.Point.prototype.getPoint = function () { console.log(this.point); };
var obj = new book.Point(100);
obj.getPoint(); //=> 100
var obj2 = new book.Point(200);
obj2.getPoint(); //=> 200
//메소드 변경
book.Point.prototype.getPoint = function () { console.log("changed"); };
obj.getPoint(); //=> changed
obj2.getPoint(); //=> changed
book.Point = function (point){}
로 book이라는 객체에 Point라는 생성자 함수를 선언한다. (생성자 함수는 첫글자가 대문자라는 관례가 있다.)
생성자 함수가 있으므로 new
키워드로 book.Point라는 인스턴스를 생성할 수 있다.
생성자 함수는 this.point
로 반환할 객체에 point라는 변수에 인자로 들어온 point 값을 할당한 후, 객체를 반환한다. 이 point는 인스턴스마다 독립적인 변수가 된다.
book.Point.prototype.getPoint
로 Point
의 Prototype Object에 메소드를 추가한다. (클래스 원형에 메소드가 추가되었다고 생각하면 된다.)
이를 통해 book.Point 인스턴스이 getPoint라는 함수를 호출하게 되면 IR(식별자 해결)시 Scope Chain을 타고 올라가 Prototyp Object의 메소드를 사용하게 된다.
this로 참조할 오브젝트를 변경할 수 있는 것이 call의 특징이다.
call
메소드는 getTotal.call()
와 같이 함수.call 형태로 사용한다.getTotal.call(this, 10, 20)
과 같이 파라미터를 넘길 수 있다.var value = 100;
function get(param) {
console.log(this);
return param + this.value;
}
var result = get.call(this, 20);
console.log(result);
get
함수의 console.log(this)는 window를 반환하게 된다.Object를 바인딩하게 만들면 아래와 같다.
var obj = { value: 80 };
var result = get.call(obj, 20);
console.log(result); //=> 100
obj
로 바꾸면, get함수는 obj를 this로 참조하게 된다. 따라서 100이 반환된다.primitive 타입을 바인딩하는 call
function get() {
return this.valueOf();
}
var result = get.call(123);
console.log(result);
this.valueOf
로 primitive 값을 꺼낼 수 있다.getTotal.apply(this,[10,20])
과 같이 사용법은 call메소드와 다르지 않다.var obj = { 0: 10, 1: 20, 2: 30 };
var data = [4, 5, 6];
function get() {
for (i = 0; i < arguments.length; i++) {
console.log(arguments[i], this[i]);
}
}
get.apply(obj, data);
get
메소드는 파라미터 수가 유동적이므로 따로 파라미터를 작성하지 않고, argument 프로퍼티를 이용해서 받는다.map()
, forEach()
메소드 처럼 콜백함수가 있는 메소드는 두 번째 파라미터에 this로 참조할 오브젝트를 작성할 수 있다.function get(data, obj) {
return data.map(function (elem, idx, data) {
return elem + this.base;
}, obj); //this 바인딩
}
let result = get([5, 6, 7], {base:100} );
console.log(result); //[105,106,107]
result = get([5,6,7], {base : 10})
console.log(result); //[15,16,17]
this는 연산을 하는 메소드에서 베이스가 되는 값을 동적으로 바꿔주는데 용이하다.
1을 더하는 메소드였지만, 코드 실행 과정 중에, 100을 더하는 메소드로 바꾸고 싶다면, this 바인딩을 통해 바꿔줄 수도 있다.
var book = {
point: 123,
get() {
return this.point;
},
};
var obj = book.get.bind(book);
console.log(typeof obj); //새로운 function 반환
var result = obj();
console.log(result);
var obj = book.get.bind(book)
bind
메소드를 사용했다.get
)function Object의 내부 프로퍼티 [[BountTargetFunction]]
에 "새로 생성한 function Object"를 설정한다.[[BoundThis]]
에 bind 메소드의 첫번째 파라미터(book
)를 설정한다.obj
에 할당한다.var result = obj()
book.get()
함수가 호출된다.return this.point
[[BountThis]]
에 담긴 것을 참조한다.[[BoundThis]]
는 book
오브젝트를 참조하고 있었다.var title = "글로벌";
function getTitle(param) {
console.log(param, this.title);
}
var newObj = { title: "새 오브젝트" };
var bindedFunc = getTitle.bind(newObj, 99);
bindedFunc(); //=> 99 "새 오브젝트" //=> 매개변수까지 미리 바인딩 되어 있다.
getTitle(); //=> undefined "글로벌"
<script src="point.js" defer></script>
<button id="point">값 출력</button>
bind()
를 이용해서 해결할 수 있다.var book = {
myPoint: 100,
setEvent(node) {
node.onclick = this.show.bind(book, node); //핸들러 함수 설정
},
show(node, event) { //핸들러 함수
console.log(node.textContent);
console.log(this.myPoint);
},
};
book.setEvent(document.getElementById("point"));