This
자바스크립트에서 This는 함수의 호출 방식에 따라 객체가 동적으로 결정된다.
전역객체 This
function thisBrowser() {
console.log(this === window);
}
thisBrowser(); // true
function thisNodeJs() {
console.log(this === global);
}
thisNodeJs(); // true
전역객체와 let, var
var money = 100
console.log(this.money) // 100
let cash = 200
console.log(this.cash) // undefined
var 변수는 전역객체의 속성을 정의할 수 있지만, let 변수는 전역객체의 속성을 정의할 수 없다.
자바스크립트에서 일반적인 함수의 호출시 This 는 전역객체(window or global)로 바인딩 된다.
var money = 100
function test(){
console.log(this.money) // 100
var money = 200
function innerTest(){
console.log(this.money) // 100
}
innerTest()
}
test()
test 함수의 경우 this가 전역을 가리키는 것으로 100이라는 값이 당연하다.
하지만, innerTest 함수인 함수의 내부함수 조차도
this는 전역객체(window 혹은 global)로 바인딩 되는 것을 알 수 있다.
var money = 100;
function test(){
var money = 200;
printMoney()
}
function printMoney(){
var money = 300;
console.log(this.money) // 100
}
test()
위와 같이 일반 함수를 외부에서 선언하고 test 함수에서 호출해도 결국 전역객체가 바인딩된다.
함수가 객체의 프로퍼티로 선언되고 메소드로서 호출되면, this는 메소드의 소유 객체로 바인딩된다.
var money = 100;
const obj = {
money: 200,
test(){
console.log(this.money) // 200
}
}
console.log(this.money) // 100
obj.test()
위의 예시에서 obj 객체에 test는 메소드로서 호출 위치가 obj 객체이기 때문에 this는 더이상 전역객체가 아니다.
var money = 100;
function printMoney(){
console.log(this.money);
}
const obj1 = {
money: 200,
test: printMoney
}
const obj2 = {
money: 300,
test: printMoney
}
printMoney(); // 100
obj1.test(); // 200
obj2.test(); // 300
위에서는 printMoney 함수를 선언하고 각 오브젝트에 메소드로 넘겼다.
obj1의 메소드 호출시 this는 obj1,
obj2의 메소드 호출시 this는 obj2 라는 것을 알 수 있고,
결국 함수의 선언과는 상관없이 호출되는 곳이 this 값을 결정하는 것을 명확히 알 수 있다.
var money = 100;
const obj1 = {
money: 200,
printMoney(){
console.log(this.money)
}
}
const obj2 = {
money: 300,
printMoney: obj1.printMoney
}
obj1.printMoney() // 200, this -> obj1
obj2.printMoney() // 300, this -> obj2
조금더 복잡한? 예시를 확인할 수 있다.
arguments ?
명시적 바인딩 전에 먼저 arguments에 대해 짚고 넘어간다.
함수 선언식으로 생성된 모든 함수는 arguments 객체를 갖는다.
함수 실행컨텍스트 생성시 LexicalEnvironment 컴포넌트안의 EnvironmentRecord를 확인하면,
Arguments 객체를 받고, 0번부터 시작하는 index: value 로 매핑 되어있고 length 또한 속성으로 갖고 있다.
ex) Arguments: {0: 100, 1: 200, length: 2}
함수안에서 arguments를 호출하여 인자로 들어온 값들을 확인 할 수 있다.
뒤에 정의하게될 This 객체를 명시적으로 바인딩하는 apply와 call함수는 arguments를 받는 부분에서만 차이가 있다.
This를 apply, call, bind 메소드를 통해 원하는 객체로 바인딩하여 실행할 수 있다.
var money = 100;
function test () {
console.log(this.money) // 999
console.log(arguments) // {0:200, 1:300}
}
const obj = {
money: 999
}
test.apply(obj, [200, 300])
// 첫번째 인자가 this 바인딩 되고, 두번째 인자(배열)이 arguments로 들어감
apply 메소드는 첫번째 인자로 함수의 this를 바인딩하고 두번째 인자를 배열로 받아 arguments를 전달하고 실행한다.
var money = 100;
function test () {
console.log(this.money) // 999
console.log(arguments) // {0:200, 1:300}
}
const obj = {
money: 999
}
test.call(obj, 200, 300)
// 첫번째 인자가 this 바인딩 되고, 나머지가 arguments 로 순서대로 들어감
call 메소드는 첫번째 인자로 함수의 this를 바인딩하고 두번째 인자부터 순서대로 arguments에 전달후 실행한다. (apply 와의 차이점은 arguments를 받는 부분)
var money = 100;
function test () {
console.log(this.money) // 999
console.log(arguments) // {0:200, 1:300}
}
const obj = {
money: 999
}
test.bind(obj)(200, 300)
bind 메소드의 경우 apply, call 과 비슷하게 동작하지만 함수를 실행시키진 않기 때문에 직접 실행해 주어야 한다.
new 키워드를 사용한 생성자 함수는 this 바인딩이 전역 객체를 가리키지 않는다.
function Money(){
console.log(this === window) // false
}
const test = new Money();
console.log(test) // {} -> this는 Money라는 빈 객체
위의 예시에서console.log(test)
를 한 결과가 빈객체로 나오는 것을 알 수 있는데
이는 New 키워드로 생성한 함수가 다음과 같은 절차를 따르기 때문이다.
New 연산자로 생성한 함수의 특징
1. This를 새로운 빈객체로 생성
2. This에 속성을 할당할 수 있음
3. 리턴값이 없으면 This를 자동 리턴
결국 새로운 this={}를 만들어 내고, 속성을 정의할 수 있으며 return 까지 해준다는 것!
function Olympic (country, medal){
this.country = country;
this.medal = medal;
}
const test1 = new Olympic('korea', 'gold');
const test2 = new Olympic('japan', 'silver');
console.log(test1) // {country: 'korea', medal: 'gold'}
console.log(test2) // {country: 'japan', medal: 'silver'}
Arrow function 에서 this는 상위스코프의 this를 가리킨다.
const obj = {
nomal(){
console.log(this === window) // false
},
arrow: () => {
console.log(this === window) // true
}
}
obj.nomal()
obj.arrow()
obj 라는 객체를 통해 호출한 두 함수에서 차이를 알 수 있는데,
nomal 함수의 this는 obj의 메소드로 호출한 일반 함수라서 obj가 this
를 의미한다.
반면 arrow 함수는 obj의 메소드로 호출하였지만,
arrow function 이므로 함수가 정의된 obj객체의 상위스코프,
즉 global 객체를 참조하게 되고, window 가 this로 바인딩 된다.
var money = 999
const obj = {
money: 100,
nomal(){
console.log(this.money) // 100
},
arrow: () => {
console.log(this.money) // 999
}
}
obj.nomal()
obj.arrow()
위 예시를 통해 일반함수와 Arrow함수 사이의
명확히 바인딩 되는 this에 대해 확인할 수 있다.
위 예시를 조금더 확장해 보면,
var money = 999
const obj = {
money: 100,
nomal(){
console.log(this.money)
},
arrow: () => {
console.log(this.money)
},
inner(){
const innerArrow = () => {
console.log(this.money)
console.log(this === obj) // true
}
return innerArrow
}
}
obj.nomal() // 100
obj.arrow() // 999
obj.inner()() // 100
새로 추가된 inner 함수의 innerArrow 함수를 정의 했는데,
위 arrow function의 경우 this가 window를 바인딩 했지만
innerArrow 의 경우 상위스코프인 inner 함수를 참조하여 this는 obj 를 가리킨다.
arrow function의 arguments
const arrow = () => {
console.log(arguments)
}
arrow() // error
function nomal(){
console.log(arguments)
}
nomal() // {}
arrow 함수는 arguments 속성을 갖고 있지 않다.