this


What is ‘this’??

자바를 사용한 경험이 있는 프로그래머에게 this란 매우 친숙한 키워드이다. this는 인스턴스 자신을 참조할 때 사용하는 참조변수로, this를 통해서 객체 내 멤버변수와 메서드를 참조해 사용할 수 있었다.

하지만 자바스크립트의 this는, 바인딩되는 객체는 한가지가 아니라 해당 함수 호출 방식에 따라 this에 바인딩되는 객체가 달라진다.

함수호출방식은 다음과 같다.

1. 함수 호출
2. 메소드 호출
3. 생성자 함수 호출
4. apply/call/bind 호출

    var foo = function() { 
        console.dir(this);
    };


    // 1. 함수 호출
    foo(); // window
    // window.foo();

    // 2. 메소드 호출
    var obj = { foo: foo };
    obj.foo(); // obj

    // 3. 생성자 함수 호출
    var instance = new foo(); // instance

    // 4. apply/call/bind 호출
    var bar = { name : 'bar' };
    foo.call(bar);
    foo.apply(bar);
    foo.bind(bar)();

1. 함수 호출

전역객체(Global Object)는 모든 객체의 유일한 최상위 객체를 의미하며, Browser에선 window, Server에선 global 객체를 의미한다.

    const eoo = 'Global Variable';

    console.log(eoo); // Global Variable
    console.log(window.eoo); // Global Variable

    function foo() {
        console.log('ss');
    }
    foo(); // ss
    window.foo(); // ss

전역객체는 전역 스코프를 갖는 전역변수를 프로퍼티로 소유한다. 글로벌 영역에 선언한 함수는 전역객체의 프로퍼티로 접근할 수 있는 전역변수의 메소드이다.

    function foo() {
        console.log('foo' , this); // window
        function bar() {
            console.log('bar', this); // window
        }
        bar();
    }
    foo();

기본적으로 this는 전역객체에 바인딩한다. 전역함수는 물론이고 내부함수의 경우도 this는 외부함수가 아닌 전역객체에 바인딩한다.

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    console.log("foo's this: ",  this);  // obj
    console.log("foo's this.value: ",  this.value); // 100
    function bar() {
      console.log("bar's this: ",  this); // window
      console.log("bar's this.value: ", this.value); // 1
    }
    bar();
  }
};

obj.foo();

객체 내에 존재하는 메소드의 내부함수일때도 this 전역객체에 바인딩한다. 내부함수는 일반 함수, 메소드, 콜백 함수 어디에서 선언되었듯 관계없이 this 전역객체를 바인딩한다. 내부함수의 this가 전역객체를 참조하는 것의 회피방법은 다음과 같다.

    var obj = {
        foo: function() {
            var that = this;
            console.log(this); // obj
            function bar() {
                console.log(this); // window
                console.log(that); // obj
            }
        }
    }

함수가 객체의 프로퍼티 값이면 메소드로서 호출되며, 이 때 메소드 내부의 this는 해당 메소드를 소유한 객체에 바인딩된다. 이를 사용해 미리 메소드를 선언할 때 객체에 바인딩된 this를 변수로 선언해둘 수 있다. 혹은, this를 명시적으로 바인딩할 수 있는 apply, call, bind 메소드를 제공한다.


2. 메소드 호출

메소드 내부의 this는 해당 메소드를 소유한 객체, 해당 메소드를 호출한 객체에 바인딩된다. 이는 프로토타입 객체 메소드 내부에서 사용된 this도 일반 메소드 방식과 마찬가지로 해당 메소드를 호출한 객체에 바인딩된다.

var obj1 = {
  name: 'Lee',
  sayName: function() {
    console.log(this.name);
  }
}

var obj2 = {
  name: 'Kim'
}

obj2.sayName = obj1.sayName;

obj1.sayName(); // Lee
obj2.sayName(); // Kim

3. 생성자 함수 호출

자바스크립트의 생성자 함수도 자바의 생성자 함수처럼 객체를 생성하는 역할을 한다. 자바의 경우 클래스 내에 생성자 함수를 작성하였지만, 자바스크립트는 형식이 정해져 있는 것이 아니라 기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.

즉, 일반 함수에 new 연산자를 붙여 호출하면 생성자 함수처럼 동작할 수 있다. 일반적으로 생성자 함수명은 첫문자를 대문자로 기술하여 혼란을 방지하려는 노력을 한다.

    function Person(name) {
        this.name = name;
    }

    var me = new Person('Lee');
    console.log(me); // Personship; {name : "Lee"}
    // new 연산자와 함꼐 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않는다.
    var you = new Person('Kim');
    console.log(you); // undefined

new 연산자와 함께 생성자 함수를 호출하면 this 바인딩이 메소드나 함수 호출 떄와는 다르게 동작한다.

생성자 함수 동작방식

  1. 빈 객체 생성 및 this 바인딩
    생성된 함수의 코드가 실행되기 전 빈 객체를 생성. 이후, 생성자 함수 내에서 사용되는 this는 이 빈 객체를 가리킨다.
  2. this를 통한 프로퍼티 생성
    생성된 빈 객체의 this를 통해 동적으로 프로퍼티나 메소드를 생성할 수 있다.
  3. 생성된 객체 반환
    반환문이 없으면, this에 바인딩된 새로 생성된 객체가 반환된다. 따라서 생성자 함수는 반환문을 사용하지 않는다.

객체 리터럴 방식과 생성자 함수 방식의 차이

// 객체 리터럴 방식
var foo = {
  name: 'foo',
  gender: 'male'
}

console.dir(foo); // { name: 'foo', gender: 'male' }

// 생성자 함수 방식
function Person(name, gender) {
  this.name = name;
  this.gender = gender;
}

var me  = new Person('Lee', 'male'); // Person { name: 'Lee', gender: 'male' }
console.dir(me);

var you = new Person('Kim', 'female'); // Person { name: 'Kim', gender: 'female' }
console.dir(you);
  • 객체 리터럴 방식의 경우, 생성된 객체의 프로토타입 객체는 Object.prototype이다.
  • 생성자 함수 방식의 경우, 생성된 객체의 프로토타입 객체는 Person.prototype이다.

생성자 함수에 new 연산자를 붙이지 않고 호출할 경우

function Person(name) {
  // new없이 호출하는 경우, 전역객체에 name 프로퍼티를 추가
  this.name = name;
};

// 일반 함수로서 호출되었기 때문에 객체를 암묵적으로 생성하여 반환하지 않는다.
// 일반 함수의 this는 전역객체를 가리킨다.
var me = Person('Lee');

console.log(me); // undefined
console.log(window.name); // Lee

생성자 함수를 new 없이 호출한 경우, 생성자 함수 내부의 this는 전역객체를 가리키므로 name은 전역변수(window)에 바인딩된다. 또한 new와 함께 생성자 함수를 호출하는 경우에 암묵적으로 반환하던 this도 반환하지 않으며, 반환문이 없으므로 undefined를 반환하게 된다.


4. apply/call/bind 호출

this에 바인딩 될 객체는 함수 호출 패턴에 의해 결정된다. 이는 자바스크립트 엔진에 의해 수행된다. 하지만 this를 특정 객체에 명시적으로 바인딩하는 방법도 제공되는데, 이것이 apply와 call 메소드이다.

이 메소드들은 모든 함수 객체의 프로토타입인 Function.prototype의 메소드이다.

apply와 call 메소드 모두 주어진 this 값과 제공되는 arguments로 함수를 호출한다.
이 둘의 차이는 apply는 인수들의 단일 배열을 받고, call은 함수에 전달될 인수 리스트를 받는다는 점이다.

var Person = function (name) {
  this.name = name;
};

var foo = {};

// apply 메소드는 생성자함수 Person을 호출한다. 이때 this에 객체 foo를 바인딩한다.
Person.apply(foo, ['name']);
// Person을 호출하면서 arguments로 name을 제공 
// call 메소드 호출 방식은 다음과 같다.
// Person.call(foo, 'name');

console.log(foo); // { name: 'name' }

bind 메소드는 apply와 call 메소드와 다르게 함수를 호출하지 않고 this를 수정한다.

let user = {
  firstName: "John"
};

function func() {
  alert(this.firstName);
}

let funcUser = func.bind(user);
funcUser(); // John

func.bind(user)는 func의 this를 user로 바인딩한 변형이다. funcUser는 바인딩된 함수가 되어 단독으로 호출할 수 있다. 실행시키면 인수는 그대로 전달되고, bind에 의해 this가 고정된 것을 확인할 수 있다.


Reference

https://poiemaweb.com/js-this

You might also enjoy