this
는 call-site(어떻게 함수가 호출되는지) 에 기반을 둔 각 함수 호출로 만들어진 binding이다.
this
가 무엇을 참조하는지 알기 위해서 call-site를 먼저 봐야한다.
여기서 중요한 것은 Call-stack, 현재 실행 중인 각 함수의 스택이다.
call-site는 현재 실행 중인 함수 전의 invocation 안에 있다.
대부분의 현대 데스크톱 브라우저들은 JS 디버거를 포함한 개발 툴이 있는데 이용하여 call-stack을 확인할 수 있다.
조사하고 싶은 함수 첫 줄에 breakpoint를 설정하거나, 'debugger;'를 추가한다.(해당 위치에서 멈춰 그곳에 도달하기까지의 함수리스트를 보여준다.(콜스택) 위에서 두번째 항목이 바로 call-site)
call-site가 함수가 실행하는 동안 this
가 가리키고 있는 곳을 결정하는 방법을 알아보자.
4가지 규칙이 있고 우선순위가 있다.
standalone function invocation. 별다른 규칙이 없을 경우.
function foo() {
//"use strict";
console.log( this.a );
}
var a = 2;
foo(); // 2
this에 대한 defulat binding이 function call에 적용하여 전역객체를 가리킨다.
그런데 foo()함수의 contents(foo의 내부 외 무관)가 strict mode인 경우, this는 전역객체가 아닌 "TypeError"를 띄우며 undefined라고 한다. 즉, 전역 객체의 경우엔 조건이 붙는다.
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
함수가 obj에 처음으로 초기화되었든, 후에 참조로 추가되든 상관없이 obj가 실제로 함수를 소유한다고 할 수는 없지만,
callsite는 obj context를 사용하여 함수를 참조하기 때문에, 호출시 함수 참조를 소유한다고 할 수 있다.
함수 참조에 context object가 있다면 implicit binding 규칙에 의해 그 객체가 function call의 'this' binding으로 사용된다.
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
call-site는 객체 속성 참조 체인의 상위단계만 중요하다.
implicitly bound function이 해당 binding을 잃을 때가 있다. 즉, strict mode에 따라 default binding으로 돌아가게 된다는 말.
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // function reference/alias!//그냥 foo참조
var a = "oops, global"; // `a` also property on global object
bar(); // "oops, global"//그리고 call-site를 보면..
callback
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// `fn` is just another reference to `foo`
fn(); // <-- call-site!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a` also property on global object
doFoo( obj.foo ); // "oops, global"
모든 함수는 그들의 Prototype을 통한 call(), apply() method가 있어서 explicit binding을 할 수 있다.
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
call에 다른 원시값을 넣으면 object-form(new String())으로 둘러싸이게 되는데 이를 boxing이라고 한다.
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// `bar` hard binds `foo`'s `this` to `obj`
// so that it cannot be overriden
bar.call( window ); // 2
이런 hard binding은 어떤 argument가 들어오든 반환값을 받든 성공적이다.
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
// simple `bind` helper
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}
var obj = {
a: 2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
ES5부터 Function.prototype.bind를 이용할 수도. bind는 새로운 함수를 반환한다.
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a: 2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
그리고 ES6부터 bind에 name property가 새로 생겼고 bar.name하면 'bound foo'라고 나옴 ㅋㅋ
function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
};
// use `obj` as `this` for `foo(..)` calls
[1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome
new
BindingJS에서 constructor는 그냥 new 함수에 의해 호출된 함수다. 즉, 다른 함수도 마찬가지로 앞에 new를 붙여서 함수를 호출할 수 있고 constructor call이라고 하는데 이는 다음을 거치며 만들어진다.
function foo(a) {
this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2
function foo(p1,p2) {
this.val = p1 + p2;
}
// using `null` here because we don't care about
// the `this` hard-binding in this scenario, and
// it will be overridden by the `new` call anyway!
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" );
baz.val; // p1p2
this
call, apply, bind에 null 이나 undefined를 넘기면, default.
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// spreading out array as parameters
foo.apply( null, [2, 3] ); // a:2, b:3
// currying with `bind(..)`
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3
DMZ 객체. 전역객체 부작용 없앰.
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// our DMZ empty object
var ø = Object.create( null );
// spreading out array as parameters
foo.apply( ø, [2, 3] ); // a:2, b:3
// currying with `bind(..)`
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3
Inderect references를 함수에 만들 수 있다.
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2
hard binding은 후의 binding을 허용하지 않는다.
this
화살표 함수에서는 this binding를 scope에서 채택한다.
function foo() {
// return an arrow function
return (a) => {
// `this` here is lexically adopted from `foo()`
console.log( this.a );
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, not 3! can not be overridden. even with new!
function foo() {
setTimeout(() => {
// `this` here is lexically adopted from `foo()`
console.log( this.a );
},100);
}
var obj = {
a: 2
};
foo.call( obj ); // 2
self=this도 쓰지말고 lexical this도 쓰지 말자. bind를 사용하든가..