As i wish

[You don't know JS] - this가 이런 거로군! - 1 본문

JavaScript

[You don't know JS] - this가 이런 거로군! - 1

어면태 2019. 5. 9. 01:18

앞서 포스팅했던 것에 이어서 두 번째 장인 'this가 이런 거로군!'에 대하여 포스팅해보겠습니다.

앞서 포스팅에서 this가 알고 있었던 것과는 달리 호출부에서 함수를 호출할 때 바인딩된다고 포스팅했었습니다.

this라나 뭐라나

 

[You don't know JS] - this라나 뭐라나

기본은 모르고 자꾸 개발만 하다 보니 이상하게 제 지식이 역삼각형 형태가 되는 것 같아서. 기본적인 책을 사서 읽어보고 정리를 하려고 합니다. 책은 한빛미디어에서 나온 "You don't know JS - 카일 심슨"의 책..

eomtttttt-develop.tistory.com

참고해주세요!

일단 먼저 this바인딩에 대한 규칙에 대하여 정리해보겠습니다.

1. 기본 바인딩

2. 암시적 바인딩

3. 명시적 바인딩

4. new 바인딩

 

1. 기본 바인딩

첫째로는 가장 평범한 함수 호출인 '단독 함수 실행'에 관한 규칙으로 나머지 규칙에 해당하지 않을 경우 적용되는 this의 가장 기본 규칙입니다.

function foo() {
    console.log(this.a);
}

var a = 2;
foo(); // 2

var a =2처럼 전역 스코프에 변수를 선언하면 변수 명과 같은 이름의 전역 객체 프로퍼티가 생성됩니다.

그리고 foo() 함수 호출 시 this.a는 전역 객체 a입니다. 기본 바인딩이 적용되어 this는 전역 객체를 참조하죠.

2. 암시적 바인딩  

두 번째 규칙은 호출부에 콘텍스트 객체가 있는지를 확인해 봅니다.

function foo() {
    console.log(this.a);
}

var obj = {
    a: 2,
    foo: foo
}

obj.foo() // 2

앞에서 선언한 foo() 함수를 obj에서 프로퍼티로 참조하고 있죠. 호출부는 obj 콘텍스트로 foo()를 참조합니다.

foo() 호출 시점에 이미 obj 객체 레퍼런스는 준비된 상태입니다. 함수 레퍼런스에 대한 컨텍스트 객체가 존재할 때 암시적 바인딩 규칙에 따르면 바로 이 콘텍스트 객체가 함수 호출 시 this에 바인딩됩니다.

즉, foo() 호출 시 obj는 this이니 this.a 는 obj.a가 됩니다.

* 참고: 실행 콘텍스트:js 동작원리  

- 암시적 소실

 암시적으로 바인딩된 함수에서 바인딩이 소실되는 경우가 있는데요, this 바인딩이 뜻밖에 헷갈리기 쉬운 경우입니다.

function foo() {
    console.log(this.a);
}

var obj = {
    a: 2,
    foo: foo
};

var bar = obj.foo // 함수 레퍼런스
var a = '엥 전역이네!'; // 'a' 역시 전역 객체의 프로퍼티
bar() // '엥 전역이네!'

위처럼 bar는 obj의 foo를 참조하는 변수처럼 보이지만 실은 foo를 직접 가리키는 또 다른 레퍼런스입니다. 게다가 호출부에서 그냥 평범하게 bar()를 호출하므로 1. 기본 바인딩이 적용됩니다.

3. 명시적 바인딩

암시적 바인딩에선 함수 레퍼런스를 객체에 넣기 위해 객체 자신을 변형하고, 함수 레퍼런스 프로퍼티를 이용하여 this를 간접적(암시적)으로 바인딩했습니다. 그러나 함수 레퍼런스 프로퍼티를 객체에 더하지 않고 어떤 객체를 this 바인딩에 이용하는 방법이 있죠.

바로 call(), apply() 메서드입니다. 

function foo() {
    console.log(this.a);
}

var obj = {
    a: 2
};

foo.call(obj) // 2

foo.call()에서 명시적으로 바인딩하여 함수를 호출하므로 this는 반드시 obj가 됩니다.

- 하드 바인딩

function foo() {
    console.log(this.a);
}

var obj1 = {
    a: 2
};

var obj2 = {
    a: 3
};

var bar = function() {
    foo.call(obj1);
};

bar();  // 2
bar.call(obj2);  // 2
bar();  // 2

함수 bar()는 내부에서 foo.call(obj1)으로 foo를 호출하면서 obj1를 this에 강제 바인딩하도록 하드 코딩합니다. 따라서 bar를 어떻게 호출하든 이 함수는 항상 obj1를 바인딩하여 foo를 실행합니다. 이런 바인딩은 명시적이고 강력해서 '하드 바인딩'이라고 합니다.

function foo(something) {
    console.log(this.a, something);
    return this.a + something;
}

var obj = {
    a: 2
};

var bar = foo.bind(obj, 3); // 2 3
var b = bar();
console.log(b) // 5;

위의 예제는 bind()를 써서 this를 바인딩한 코드입니다.

4. new 바인딩

먼저 책에서 new바인딩 규칙을 설명하기 전에 오해를 하나 바로 잡았습니다. 이해가 쉽도록 책 문구를 그대로 인용하겠습니다.

전통적인 클래스 지향 언어의 생성자는 클래스에 붙은 특별한 메서드로, 다음과 같이 클래스 인스턴스 생성 시 new 연산자로 호출된다.

"something = new Myclass();" 

자바스크립트도 new 연산자가 있고 사용 방법은 겉보기에 다른 클래스 지향 언어와 별 차이가 없어 보여서 그 내부 체계 또한 이와 비슷할 거라 쉽게 단정하는 사람이 많다. 그러나 사실 자바스크립트에서 new는 의미상 클래스 지향적인 기능과 아무 상관이 없다.

먼저 자바스크립트에서 '생성자'의 정의를 내려보자. 자바스크립트 생성자는 앞에 new 연산자가 있을 때 호출되는 일반 함수에 불과하다. 클래스에 붙은 것도 아니고 클래스 인스턴스화 기능도 없다. 심지어 특별한 형태의 함수도 아니다. 단지 new를 사용하여 호출할 때 자동으로 붙들려 실행되는 그저 평범한 함수이다. 예를

들어, 생성자 Number() 함수에 대한 설명이다.
"new 표현식의 일부로 호출 시 Number는 생성자이며 새로 만들어진 객체를 초기화한다."따라서
Number() 같은 부류의 내장 객체 함수는 물론이고 상당수의 옛 함수는 앞에 new를 붙여 호출할 수 있고 이는 결국 '생성자 호출(Constructor Call)'이나 다름없다. 미묘한 차이를 잘 구분하는 것이 중요한데 '생성자 함수
(Constructor Call)'가
아니라 '함수를 생성하는 호출(Construction Calls Of Functions)'이라고 해야 옳다.  

즉, 간단히 정리를 하면 new 표현식은 일반 객체지향 언어들과 달리 '함수를 생성하는 호출(Construction Calls Of Functions) (객체를 생성 (영어로 보면 훨씬 이해가 빠릅니다))'이라고 보면 될 것 같습니다.

*참고: [JavaScript] 생성자와 new

함수 앞에 new를 붙여 생성자 호출을 하면 다음과 같은 일들이 저절로 일어납니다.

1. 새 객체가 툭 만들어진다.

2. 새로 생성된 객체의 [[Prototype]]이 연결된다.

3. 새로 생성된 객체는 해당 함수 호출 시 this로 바인딩된다.

4. 이 함수가 자신의 또 다를 객체를 반환하지 않는 한 new와 함께 호출된 함수는 자동으로 새로 생성된 객체를 반환한다.

function foo(a) {
    this.a = a;
}

var bar = new foo(2);

console.log(bar); // foo {a: 2}
console.log(bar.a); // 2

앞에 new를 붙여 foo()를 호출했고 새로 생성된 객체는 foo() 호출 시 this에 바인딩됩니다. 따라서 결국 new는 함수 호출 시 this를 새 객체와 바인딩하는 방법이며 이것이 4.new 바인딩입니다.

Comments