[ydkjs]this & Object Prototypes-2-this All Makes Sense Now!

p*0*q·2021년 1월 27일
0

YDKJS

목록 보기
15/16

thiscall-site(어떻게 함수가 호출되는지) 에 기반을 둔 각 함수 호출로 만들어진 binding이다.

Call-site

this가 무엇을 참조하는지 알기 위해서 call-site를 먼저 봐야한다.
여기서 중요한 것은 Call-stack, 현재 실행 중인 각 함수의 스택이다.
call-site는 현재 실행 중인 함수 전의 invocation 에 있다.

대부분의 현대 데스크톱 브라우저들은 JS 디버거를 포함한 개발 툴이 있는데 이용하여 call-stack을 확인할 수 있다.
조사하고 싶은 함수 첫 줄에 breakpoint를 설정하거나, 'debugger;'를 추가한다.(해당 위치에서 멈춰 그곳에 도달하기까지의 함수리스트를 보여준다.(콜스택) 위에서 두번째 항목이 바로 call-site)

Nothing But Rules

call-site가 함수가 실행하는 동안 this가 가리키고 있는 곳을 결정하는 방법을 알아보자.
4가지 규칙이 있고 우선순위가 있다.

Default Binding

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라고 한다. 즉, 전역 객체의 경우엔 조건이 붙는다.

Implicit Binding

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 Lost

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"

Explicit Binding

모든 함수는 그들의 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이라고 한다.

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

이런 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'라고 나옴 ㅋㅋ

API Call "Contexts"

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

JS에서 constructor는 그냥 new 함수에 의해 호출된 함수다. 즉, 다른 함수도 마찬가지로 앞에 new를 붙여서 함수를 호출할 수 있고 constructor call이라고 하는데 이는 다음을 거치며 만들어진다.

  1. 새로운 객체가 만들어진다.
  2. 새롭게 구성된 객체가 prototype-linked
  3. 해당 함수 호출에 this binding.
  4. 만약에 함수가 자체적으로 다른 객체를 반환하지 않으면 자동으로 새로 구성된 객체 반환.
function foo(a) {
	this.a = a;
}

var bar = new foo( 2 );
console.log( bar.a ); // 2

Everything In Order

  1. new
  2. explicit(call, apply), hard(bind)
  3. implicit
  4. default
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

Binding Exceptions

Ignored 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

Safer this

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

Indirection

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

Softening Binding

hard binding은 후의 binding을 허용하지 않는다.

Lexical 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를 사용하든가..

0개의 댓글

관련 채용 정보