Chapter 2: this All Makes Sense Now! - hochan222/Everything-in-JavaScript GitHub Wiki
์ด์ ์ฐ๋ฆฌ๋ this๊ฐ ์ ์ ์ผ๋ก call-site์ ๊ธฐ๋ฐํด์ ๋ฐ์ธ๋ฉ ๋๋ค๋ ์ฌ์ค์ ์์๋ค.
Call-site
this์ ๋ฐ์ธ๋ฉ์ ์ดํดํ๋ ค๋ฉด call-site(ํจ์๊ฐ ํธ์ถ๋๋ ์ฝ๋์ ์์น)๋ฅผ ์ดํดํด์ผํ๋ค. call-site๋ this๊ฐ ๋ฌด์์ ์ฐธ์กฐํ๊ณ ์๋๊ฐ์ ๋ํ ๋ต์ ํด์ค ์ ์๋ค. ๊ทธ๋ฌ๋ ์๋ฐ์คํฌ๋ฆฝํธ์์ ํน์ ํธ์ถ ํจํด์ด call-site๋ฅผ ์จ๊ธธ ์ ์๊ธฐ ๋๋ฌธ์ ์ ์ฌํ ๋ด์ผํ๋ค.
function baz() {
// call-stack is: `baz`
// so, our call-site is in the global scope
console.log( "baz" );
bar(); // <-- call-site for `bar`
}
function bar() {
// call-stack is: `baz` -> `bar`
// so, our call-site is in `baz`
console.log( "bar" );
foo(); // <-- call-site for `foo`
}
function foo() {
// call-stack is: `baz` -> `bar` -> `foo`
// so, our call-site is in `bar`
console.log( "foo" );
}
baz(); // <-- call-site for `baz`
Nothing But Rules
call-site๊ฐ ํจ์ ์คํ์ค์ this๊ฐ ์ด๋๋ฅผ ๊ฐ๋ฅดํค๋๊ฐ๋ฅผ ์ด๋ป๊ฒ ๊ฒฐ์ ํ๋๊ฐ๋ฅผ ์์๋ณด์.
4๊ฐ์ง ๊ท์น๊ณผ ๊ฐ๊ฐ ๊ฒฝ์ฐ์ ์ ์ฉ๋๋ ์ฐ์ ์์๊ฐ ์๋ค.
Default Binding
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
์ฐ์ var a = 2;
๊ฐ์ด ์ ์ญ์ ์ธ๋๊ฒ์ global-object properties์ ๊ฐ์ ์ด๋ฆ์ผ๋ก ์กด์ฌํ๋ ๊ฒ๊ณผ ์ ํํ ๋์น์ด๋ค. ๊ฐ๊ฐ์ ๋ณต์ฌ๋ณธ์ด์๋๊ณ ํ๋๋ค.
๊ธฐ๋ณธ ๋ฐ์ธ๋ฉ์ด ํจ์ ํธ์ถ์ ์ ์ฉ๋๋ฏ๋ก foo()
ํจ์ ์์ this๋ (global-object) ์ ์ญ ๊ฐ์ฒด๋ฅผ ๊ฐ๋ฅดํจ๋ค.
strict mode
์ผ ๊ฒฝ์ฐ ์ ์ญ ๊ฐ์ฒด๋ ์ ํฉํ์ง ์์ undefined
๋ก ์ค์ ๋๋ค.
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // TypeError: `this` is `undefined`
Implicit Binding
callsite๊ฐ context object๋ฅผ ๊ฐ์ง๊ณ ์์ ๋๋ค.(object์ ๊ด๋ จ๋ ๋)
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
call-site๋ obj context๋ฅผ ํจ์ ์ฐธ์กฐ๋ฅผ ๊ฒฐ์ ํ๋๋ฐ ์ฌ์ฉํ๋ค. ๊ทธ๋ฆฌ๊ณ call-site๋ ์ค์ง ๊ฐ์ฅ ์๋จ/๋ง์ง๋ง์์๋ ๊ฐ์ฒด ์์ฑ์ฐธ์กฐ ์ฒด์ธ๋ง ์ค์ํ๋ค.
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
Implicitly Lost
๊ฐ์ฅ ํผ๋์ค๋ฌ์ธ ๋๋ implicitly ๋ฐ์ธ๋ฉ์ด ๋ฐ์ธ๋ฉ์ ์์ ๊ฒฝ์ฐ์ด๋ค.
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // function reference/alias!
var a = "oops, global"; // `a` also property on global object
bar(); // "oops, global"
์ฌ์ค bar
๊ฐ obj.foo
๋ฅผ ์ฐธ์กฐํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง, ๊ทธ์ foo
์ ๋ค๋ฅธ ์ฐธ์กฐ์ผ ๋ฟ์ด๋ค. ๋ํ, call-site๊ฐ bar()์ด๊ธฐ ๋๋ฌธ์ ๊ธฐ๋ณธ ๋ฐ์ธ๋ฉ์ด ์ ์ฉ๋๋ค.
์ด๋ฐ ์์ธ๋ ์ฝ๋ฐฑํจ์๋ฅผ ์ ๋ฌํ ๋ ๋ํ ๋ฐ์ํ๋ค.
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"
์ฝ๋ฐฑ์ผ๋ก ์ ๋ฌํ๋ ํจ์๊ฐ builtin ์ด๋ผ๊ณ ํด๋ ๊ฒฐ๊ณผ๋ ๋๊ฐ๋ค.
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a` also property on global object
setTimeout( obj.foo, 100 ); // "oops, global"
๋ณดํต ์๋ฐ์คํฌ๋ฆฝํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ์ด ์ฝ๋ฐฑ์ ๋ฐ์ธ๋ฉ์ ๊ฐ์ ํ๋ ๊ฒ์ ์ข์ํ๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ์ ํผ๋์ ๊ฐ์ค๋๋ค.
Explicit Binding
์ฐ๋ฆฌ๋ ๋ํ ๋ช
์์ ์ผ๋ก call()
๊ณผ apply()
๋ฑ์ ํตํด ๋ฐ์ธ๋ฉ ํ ์ ์๋ค.
๋ช๋ช ์๋ฐ์คํฌ๋ฆฝํธ ํ๊ฒฝ์์๋ ์ ๊ณต๋์ง ์์ ์ ์๋๋ฐ ๋๋ถ๋ถ ์ ๊ณต๋๋ค.
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
๋ง์ฝ call()์์ primitive value๊ฐ ์ค๋ฉด ๊ทธ๊ฒ์ object-form(new String(), new Boolean() ...)๋ฑ์ ๋ฐ์ธ๋ฉํ๋ค. ์ฐ๋ฆฌ๋ ์ด๊ฑธ ํํ "boxing"์ด๋ผ๊ณ ํ๋ค.
์์ฝ๊ฒ๋ ๋ช ์์ ๋ฐ์ธ๋ฉ์ด ์์ ๋ฌธ์ ๋ค์ ํด๊ฒฐํด์ฃผ์ง๋ ๋ชปํ๋ค.
Hard Binding
์ฐ๋ฆฌ๋ ๋ช๊ฐ์ง ํธ๋ฆญ์ผ๋ก ์์ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ ์ ์๋ค.
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
๋ค๋ฅธ ์์
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a: 2
};
var bar = function() {
return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5
์ฌ์ฌ์ฉ ๊ฐ๋ฅ ํจ์๋ฅผ ๋ง๋ค์ด์ ์กฐ๊ธ ๋ ๊น๋ํ๊ฒ ์ง๋ณด์.
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
๋ก ์ ๊ณต๋๋ค.
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()ํจ์์ stack ์ถ์ ์ ์ํด bar.name ์์ฑ์ ์ถ๊ฐ๋ก ๊ฐ๋๋ค.
API Call "Contexts"
๋ง์ ์๋ฐ์คํฌ๋ฆฝํธ ๋ด์ฅ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด "context" ๋ผ๋ ์ ํ์ ๋งค๊ฐ๋ณ์๋ฅผ ์ ๊ณตํ๋ค.
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 Binding
๋ค๋ฒ์งธ์ด์ ๋ง์ง๋ง this
๋ฐ์ธ๋ฉ ๊ท์น์ ํจ์์ ๊ฐ์ฒด๋ค์ ๋ํ ์คํด๋ฅผ ๋ค์ ์๊ฐํ๊ฒ ๋ง๋ ๋ค.
์ ํต์ ์ธ ํด๋์ค ์งํฅ ์ธ์ด์์ ์์ฑ์๋ ํด๋์ค์ ์ฐ๊ฒฐ๋ ํน์ ๋ฉ์๋๋ก, ํด๋์ค๊ฐ ์ ์ฐ์ฐ์๋ก ์ธ์คํด์คํ ๋ ๋ ํด๋น ํด๋์ค์ ์์ฑ์๊ฐ ํธ์ถ๋๋ค.
something = new MyClass(..);
์๋ฐ์คํฌ๋ฆฝํธ์ new๋ฅผ ๊ฐ์ ๋์์ผ๋ก ์คํดํ๋๋ฐ ์๋ฐ์คํฌ๋ฆฝํธ์์ ์ด์๋ํ class ์งํฅ์ ๊ธฐ๋ฅ์ ์๋ค.
์๋ฐ์คํฌ๋ฆฝํธ์์ ์์ฑ์๋ new ์ฐ์ฐ์๋ฅผ ์์๋๊ณ ํธ์ถ๋๋ ํจ์์ผ ๋ฟ์ด๋ค. ์๋ฌด ์ญํ ๋ ์๋ค.
์๋ฅผ๋ค์ด Number()์ ์์ฑ์ํจ์๋ ES5.1 spec์ ์ธ์ฉํ๋ฉด
15.7.2 The Number Constructor
When Number is called as part of a new expression it is a constructor: it initialises the newly created object.
์๋ฐ์คํฌ๋ฆฝํธ์์ ์์ฑ์๋ ๊ทธ์ ๋จผ์ ๋ถ๋ ค์ง๋ ํจ์์ผ ๋ฟ์ด๋ค.
new ์ฐ์ฐ์ด ์ด๋ค์ง๋ฉด ๋ค์ ๊ณผ์ ์ด ์๋์ ์ผ๋ก ์คํ๋๋ค.
- ์๋ก ๊ตฌ์ฑ๋ ๊ฐ์ฒด๊ฐ ์์ฑ๋๋ค.
- ์๋ก ๊ตฌ์ฑ๋ ๊ฐ์ฒด์ธ Prototype์ ๋งํฌ๋๋ค.
- ์๋ก ๊ตฌ์ฑ๋ ๊ฐ์ฒด๊ฐ ํจ์ ํธ์ถ์์
this
๋ฐ์ธ๋ฉ์ ์ค์ ํ๋ค. - ๋ฐํ๋ ๋ค๋ฅธ ๋์ฒด ๊ฐ์ฒด๊ฐ์์ผ๋ฉด ์๋ก ๊ตฌ์ฑ๋ ๊ฐ์ฒด๊ฐ ๋ฐํ๋๋ค.
2๋จ๊ณ๋ ๋์ค์ 5 Chapter์์ ๋ค๋ฃฐ๊ฒ์ด๋ค.
function foo(a) {
this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2
Everything In Order
์ด์ ์ฐ์ ์์์ ๋ํด ์์๋ณด์. ์ฐจ๋ก๋๋ก default binding์ ์ฐ์ ์์๊ฐ ๊ฐ์ฅ ๋ฎ๋ค.
๋จผ์ ๋ช ์์ ๋ฐ์ธ๋ฉ์ด ์์์ ๋ฐ์ธ๋ฉ๋ณด๋ค ์ฐ์ ์์๊ฐ ๋๋ค.
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
์ ์ฒด๋ฅผ ๋น๊ตํด๋ณด์.
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar( 3 );
console.log( obj1.a ); // 2
console.log( baz.a ); // 3
์ฐ๋ฆฌ๊ฐ ์๊น ์์ ๋ง๋ ๊ฐ์ง ๋ฐ์ธ๋ฉ์ผ๋ก ๋์๊ฐ๋ฉด ๋๋ผ์ด ๊นจ๋ฌ์์ ์ป์ ์ ์๋ค.
function bind(fn, obj) {
return function() {
fn.apply( obj, arguments );
};
}
์ฐ๋ฆฌ๊ฐ ๋ฐฉ๊ธ ์์์ํ๊ฒ์ฒ๋ผ ํ๋๋ฐ์ธ๋ฉ์ ์ฌ์ ์ํ ๋ฐฉ๋ฒ์ด์๋ค. ์ฌ์ค ES5์ bind()๋ ์๊ฐ๋ณด๋ค ๋งค์ฐ ๋ณต์กํ๋ค. ์๋๋ bind()์ pollyfill์ด๋ค.
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError( "Function.prototype.bind - what " +
"is trying to be bound is not callable"
);
}
var aArgs = Array.prototype.slice.call( arguments, 1 ),
fToBind = this,
fNOP = function(){},
fBound = function(){
return fToBind.apply(
(
this instanceof fNOP &&
oThis ? this : oThis
),
aArgs.concat( Array.prototype.slice.call( arguments ) )
);
}
;
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
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
Determining this
- new ๋ก ์๋ก์ด ๊ฐ์ฒด๊ฐ ์์ฑ๋๋์ง?
- call, apply๋ก ๋ช ์์ ๋ฐ์ธ๋ฉ์ด ๋๋์ง?
- ์์์ ๋ฐ์ธ๋ฉ์ด ๋๋์ง?
- default binding ๊ณ ๋ ค (strict mode)์ธ์ง ์๋์ง.
Binding Exceptions
Ignored this
apply, call, bind์ this ๋ฐ์ธ๋ฉ ๋งค๊ฐ๋ณ์์ null์ด๋ undefined๋ฅผ ์ ๋ฌํ๋ฉด default binding์ด ๋๋ค.
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
๋ํ, curry๋ฅผ ์ฌ์ฉํ๋ฉด ๋งค์ฐ ์ ์ฉํ ์์๋ค.
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
Safer this
Object.create(null)
๋ {}์ ๋น์ทํ๊ฒ ๋น ๊ฐ์ฒด๋ฅผ ์์ฑํ์ง๋ง, Object.prototype
์ ๋ํ ์์(delegation)์ด ์์ผ๋ฏ๋ก ๋ ๋น์ด์๋ค๊ณ ํ ์ ์๋ค.
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
Indirection
์ฃผ์ํด์ผํ ๋ ๋ค๋ฅธ ์ฌํญ์ ํจ์์๋ํ ๊ฐ์ ์ฐธ์กฐ๋ฅผ ๋ง๋๋ ๊ฒฝ์ฐ์ด๋ค. ์ด๋ฐ ๊ฒฝ์ฐ ํจ์๊ฐ ํธ์ถ๋๋ฉด default binding์ด ์ ์ฉ๋๋ค.
๊ฐ์ ์ฐธ์กฐ๋ ์ผ๋ฐ์ ์ผ๋ก ํ ๋น์์ ๋น๋ฒํ ๋ฐ์ํ๋ค.
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
Lexical this
์ผ๋ฐ์ ์ธ ํจ์๋ ์์์ ์ธ๊ธํ 4๊ฐ์ง ๊ท์น์ด ์ ์ฉ๋๋ค. ํ์ง๋ง ES6์์ ์๊ฐํ๋ ํ์ดํํจ์๋ ์ด๋ฐ ๊ท์น์ ์ฌ์ฉํ์ง ์๋๋ค.
ํ์ดํํจ์๋ ๋๋ฌ์ธ๋ ๋ฒ์์์ this๋ฅผ ๋ฐ์ธ๋ฉํ๋ค.
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!
์ผ๋ฐ์ ์ฌ๋ก๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ ์ฝ๋ฐฑ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์ด๋ค.
function foo() {
setTimeout(() => {
// `this` here is lexically adopted from `foo()`
console.log( this.a );
},100);
}
var obj = {
a: 2
};
foo.call( obj ); // 2
๋ ์๋์ ๋์น
function foo() {
var self = this; // lexical capture of `this`
setTimeout( function(){
console.log( self.a );
}, 100 );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
Review (TL;DR)
๋ฐ์ธ๋ฉ ๊ท์น 4๊ฐ์ง.
์์ ํ๊ฒ this binding์ํ๋ ค๋ฉด รธ = Object.create(null)
๋ก ์์ฑํ์.
ES6์ ํ์ดํํจ์๋ ์ด์ ๋ ๊ฑฐ์ ์ฝ๋์ ํ์คํ ๋์ฒด๋ฌผ์ด๋ค.