자바스크립트의 함수는 호출될 때 this를 암묵적으로 전달받는다.
함수 호출 방식에 따라 this에 바인딩될 객체가 동적으로 결정된다.
<함수 호출방식>
기본적으로 this는 전역 객체에 바인딩된다. 전역 객체는 모든 객체의 유일한 최상위 객체를 의미한다. Browser-side에서는
window
, Server-side에서는global
객체를 의미한다.
function foo() {
console.log("foo's this: ", this); // window
function bar() {
console.log("bar's this: ", this); // window
}
bar();
}
foo();
내부함수도 일반함수, 메소드, 콜백 함수 어디에 선언되었든 관계없이 this는 전역 객체에 바인딩된다.
let value = 100;
let myObject = {
value: 1,
func1: function() {
this.value += 1;
console.log(`this value of func1(): ${this.value}`); // 2
func2() = function() {
this.value += 1;
console.log(`this value of func2(): ${this.value}`); // 101
func3() = function() {
this.value += 1;
console.log(`this value of func3(): ${this.value}`); // 102
}
func3();
}
func2();
};
myObject.func1();
아래 메소드 호출에서 보겠지만 func1는 myObject 객체의 메소드이므로 func1를 호출한 객체에 바인딩 된다. 따라서 func1의 this.value는 2이다.
그러나 func1의 내부함수 func2, func2의 내부함수 func3은 myObject에 바인딩되지 않고 전역객체 window에 바인딩되어 결과가 101, 102로 나온다.
이런 결과가 나온 이유는 자바스크립트에서는 내부 함수 호출 패턴을 정의해놓지 않기 때문이다. 내부함수도 결국 함수이므로 메소드 호출이 아니라 함수 호출로 취급하여 전역 객체에 바인딩 되는 것이다.
내부함수에서도 부모함수의 this에 접근 가능하게 하려면, func1의 this를 다른 변수에 저장해서 사용하는 방법이 있다.
함수가 객체의 프로퍼티 값이면 메소드로서 호출된다.
이 때 메소드 내부의 this는 해당 메소드를 호출한 객체에 바인딩된다.
prototype 객체도 메소드를 가질 수 있다.
prototype 객체 메소드 내부에서 사용된 this도 해당 메소드를 호출한 객체에 바인딩된다.
자바스크립트의 생성자 함수는 객체를 생성하는 역할을 한다.
빈 객체 생성 및 this 바인딩
생성자 함수 내의 코드가 실행되기 전에 빈 객체가 생성됨.
이후 생성자 함수 내에서 사용되는 this는 이 빈 객체를 가리키게 됨.
이 빈 객체는 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다.
💡 이게 무슨 소리냐...?
모든 함수는 객체로서, prototype 프로퍼티를 가지고 있다.
여기서 함수 객체의 prototype 프로퍼티
와 모든 객체의 부모를 나타내는 내부 프로퍼티인 [[Prototype]]
는 다른 것이다.
두 프로퍼티 모두 프로토타입 객체를 가리킨다는 공통점이 있지만 관점이 다르다. 모든 객체에 존재하는 내부 프로퍼티인 [[Prototype]]은 객체 자신의 부모 역할을 하는 프로토타입 객체를 가리키고, 함수 객체가 가지는 prototype 프로퍼티는 이 함수가 생성자로 사용될 때, 이 함수로 생성된 객체의 부모 역할을 하는 프로토타입 객체를 가리킨다.
prototype 프로퍼티는 함수가 생성될 때 만들어지고, 아래 그림처럼 constructor 프로퍼티 하나만 있는 객체를 가리킨다. 그리고 constructor 프로퍼티는 자신과 연결된 함수를 가리킨다.
즉, 자바스크립트에서는 함수를 생성할 때, 함수 자신과 연결된 프로토타입 객체를 동시에 생성하고, 이 둘은 서로를 참조하게 된다.
this를 통한 프로퍼티 생성
생성된 빈 객체에 this를 이용하여 동적으로 프로퍼티나 메소드를 생성할 수 있다.
this는 새로 생긴 객체를 가리키므로 this를 통해 생성된 프로퍼티와 메소드는 새로 생긴 객체에 추가된다.
생성된 객체 반환
function Person(name) {
// 생성자 함수 코드 실행 전 -------- 1
this.name = name; // --------- 2
// 생성된 함수 반환 -------------- 3
}
var me = new Person('Lee');
console.log(me.name);
일반함수를 호출하면 this는 전역객체에 바인딩되고, 생성자 함수를 호출하면 this는 새로 생성된 객체에 바인딩 된다.
따라서, 생성자 함수를 new 없이 호출하면 this는 전역 객체에 바인딩 되어 window
를 가리키게 된다.
this를 특정 객체에 명시적으로 바인딩하는 방식
func.apply(thisArg, [argsArray])
// thisArg: 함수 내부의 this에 바인딩할 객체
// argsArray: 함수에 전달할 argument의 배열
var Person = function (name) {
this.name = name;
};
var foo = {};
// apply 메소드는 생성자함수 Person을 호출한다. 이때 this에 객체 foo를 바인딩한다.
Person.apply(foo, ['Jane']);
console.log(foo); // { name: 'Jane' }
call
apply와 비슷하지만, call은 apply의 두번째 인자에서 배열로 넘긴 arguments를 각각 하나의 인자로 넘긴다.
bind
bind 메소드는 apply, call과 비슷하지만 this만 바꾸고 호출은 하지 않는다.
함수가 호출될 때 전달되는 arguments는 배열의 형태이지만 진짜 배열은 아닌 유사배열이다. 그래서 배열의 메서드를 사용할 수 없는데, apply 메서드를 사용하면 배열의 메서드를 사용할 수 있다.
function myFunction() {
console.log(arguments);
// arguments.shift(); -> 에러 발생
let args = Array.prototype.slice.apply(arguments);
console.dir(args);
}
myFunction(1,2,3);
Array.prototype.slice.appy(arguments);
'Array.prototoype.slice() 메서드를 호출하라. 이 때 this는 arguments에 바인딩하라.'
마치 Array.prototype.slice()메서드를 arguments의 메서드인 것처럼 호출하라는 말이다.