코드를 만들어서 자바스크립트 엔진에 실행을 하면 그 즉시 memory
와 execution context
를 엔진이 객체로 생성해 만든다.
실행을 하기 위해서는 여러가지 정보가 필요한데 다음과 같은 정보를 구분하고 가지고 있기 위해 execution context
를 객체 형태로 관리한다.
함수가 호출되면 scope
에 따라 다음과 같은 정보가 execution context
가 만들어 져 call stack
에 push
된다. 함수를 벗어나면 call stack
에서 pop
된다.
scope
내 변수: 지역변수, 매개변수, 전역변수, 객체의 프로퍼티- 호출의 근원 (
caller
)- 전달의 인지 (
arguments
)this
그러므로 변수가 담긴 함수를 선언하면 local memory
와 local execution context
가 생성된다. 자바스크립트는 lexical scope
이기 때문에 어디서 선언 되었는지가 중요하다.
this
는 일반적으로 메서드를 호출한 객체가 저장되어 있는 속성이다. 하지만 this
속성은 메서드를 호출할 때뿐 아니라 일반 함수를 호출할 때도 만들어지며 이벤트 리스너가 호출될 때에도 this
속성이 만들어진다. 문제는 this
속성에 저장되는 값이 동일한 값이 아니라 각각 다르다는 점이다.
this
키워드는 모든 함수 scope
에서 자동으로 설정되는 특수한 식별자이다. execution context
를 이루는 요소 중 하나로 사용할 수 있다.
this
가 상황에 따라 무엇인지 식별하는데, this
는 5가지 각기 다른 상황에 따라 무엇을 지칭하는지 알 수 있다.
가장 일반적인 규칙이다. 거의 80% 정도가 this가 무엇을 참조하는지 이 규칙을 통해 알 수 있다.
const user = {
name: "Tyler",
age: 27,
greet() {
alert(`Hello, my name is ${this.name}`)
},
mother: {
name: "Stacey",
greet() {
alert(`Hello, my name is ${this.name}`)
}
}
}
user.greet() // Hello, my name is Tyler
user.mother.greet() // Hello, my name is Stacey
look to the left of the dot when the function is invoked
아래와 같이 greet 함수가 user object에 속한 메서드가 아니라 단독 함수라면 this 가 user obejct를 참조하도록 하면서 greet함수를 호출하려면 어떻게 해야할까?
call
함수, Function.prototype.call()
를 통해 가능하다.
.call
은 모든 함수에 있는 메소드로, 함수가 호출될 컨텍스트를 지정하여 함수를 호출할 수 있게 한다. 이것이 바로 명시적 바인딩이다. this
키워드가 무엇을 참조할 지 명시적으로 알려주는 방법이다.
만약 파라미터가 필요한 함수라면 .call
함수의 첫번째 인자로 컨텍스트를 넣은 다음, 차례대로 인자를 넣어주면 된다.
하지만 이렇게 일일이 인자를 넣어주는 게 귀찮다면, .apply
함수, Function.prototype.apply()
를 사용해서 인자를 한번에 배열로 넣어주면 된다.
마지막으로 .bind
, Function.prototype.bind()
를 살펴보면, .call
함수와 동일한데 즉시 함수를 호출하는 게 아니라 바인딩된 새로운 함수를 리턴하는 것이 다르다.
function greet(l1, l2, l3) {
alert(`Hello, my name is ${this.name} and I know ${l1}, ${l2}, and ${l3}`)
}
const user = {
name: "Tyler",
age: 27
}
const languages = ["JavaScript", "Ruby", "Python"]
// .call
greet.call(user, languages[0], languages[1], languages[2])
// .apply
greet.apply(user, languages)
// .bind
const newFn = greet.bind(user, languages[0], languages[1], languages[2])
명시적인 바인딩은 묵시적 바인딩보다 우위를 갖는다.
new 키워드로 함수를 호출하면 자바스크립트 인터프리터는 새로운 object를 리턴하는데 this는 그 새로운 object를 참조한다.
function User(name, age) {
this.name = name
this.age = age
}
const me = new User("Tyler", 27)
하드 바인딩은 bind() (ES5)으로 가능하다. bind() 메소드는 우리가 지정한 this
컨텍스트를 가진 기존 함수를 불러오기 위해 하드코딩된 새로운 함수를 반환한다.
var myMethod = function () {
console.log(this);
};
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
myMethod = myMethod.bind(obj1); // 2
myMethod.call( obj2 ); // 2 명시적 바인딩은 obj2이나, obj1로 하드바인딩 되어있음
하드바인딩은 명시적 바인딩보다 우위를 갖는다
const user = {
name: "Tyler",
age: 27,
languages: ["JavaScript", "Ruby", "Python"],
greet() {
const hello = `Hello, my name is ${this.name} and I know`
const langs = this.languages.reduce(function(str, lang, i) {
if (i === this.languages.length - 1) {
return `${str} and ${lang}.`
}
return `${str} ${lang},`
}, "")
alert(hello + langs)
}
}
user.greet() // Uncaught TypeError: Cannot read property 'length' of undefined
this.languages
가 undefined
여서 Cannot read property ‘length’ of undefined
에러가 발생한다.
원래 바라는 바는 this
가 user object
를 참조하는 것인데, 어디서 잘못됐는지 차근차근 확인해보자.
어디서 함수가 호출되었나? → language
문자열을 합치는 함수는 reduce
함수에 전달되었다. reduce
함수내에서 어떻게 함수가 호출되는지 정확히 알 수 없다.
이 함수가 user
컨텍스트에서 호출될 수 있도록 명시적으로 알려줘야 한다. bind
를 이용해서 user
를 참조하는 this
를 바인딩해주면 된다.
greet() {
const hello = `Hello, my name is ${this.name} and I know`
const langs = this.languages.reduce(function (str, lang, i) {
if (i === this.languages.length - 1) {
return `${str} and ${lang}.`
}
return `${str} ${lang},`
}.bind(this), "")
alert(hello + langs)
}
하지만 좀 보기 복잡한 면이 있다.
ES6
부터 도입된 arrow function
은 일반 function
과 다르게 함수 자체의 this
를 가지고 있지 않다. 대신 lexical
하게 this
를 결정짓는다. (lexical scope
: 바깥에서 선언한 변수는 안쪽에서 접근 가능하다)
arrow function
을 이용하면 this
를 명시적으로 바인딩해주지 않아도 부모 스코프의 this
를 참조한다.
Arrow functions don’t have their own this.
const langs = this.languages.reduce((str, lang, i) => {
if (i === this.languages.length - 1) {
return `${str} and ${lang}.`
}
return `${str} ${lang},`
}, "")
자바스크립트는 기본적으로 this 키워드는 window object를 참조하고 있다. (Node.js에서는 global object)
window.age = 27
function sayAge() {
console.log(`My age is ${this.age}`)
}
sayAge() // My age is 27
참고로 ES5부터 “엄격 모드”를 활성화하면 JavaScript가 올바른 작업을 수행하며 창 객체를 기본값으로 사용하는 대신 “this”를 정의되지 않은 상태로 유지한다
"use strict"
window.age = 27
function sayAge() {
console.log(`My age is ${this.age}`)
}
sayAge() // TypeError: Cannot read property 'age' of undefined
다시 한번 정리해보자면, this 키워드가 무엇을 참조하는지는 아래의 순서에 따라 판단하면 된다.
함수가 어디에서 호출됐는지 확인
. 왼쪽에 object가 있나? 그 object가 this 키워드가 참조하는 거다. 만약 그렇지 않다면 #3 규칙으로
함수가 call, apply, bind를 이용해서 호출됐나? 그렇다면 call, apply, bind의 첫번째 인자가 바로 this 키워드가 참조하고 있는 것이다. 아니면 #4 규칙으로
함수가 new 키워드로 호출됐나? 그렇다면 this 키워드는 새롭게 생성된 object를 참조하고 있다. 아니면 #5로
화살표함수 안에 this가 있나? 그렇다면 부모 스코프에서 this 가 무엇을 참조하는지 찾을 수 있을거다. 아니면 #6으로
strict mode인가? 그렇다면 this 는 undefined다. 아니면 #7로
위의 모든 케이스에 해당되지 않는다면, this는 window object를 참조하고 있다!
this
(window 객체)this
(window 객체)this
(이벤트를 발생시킨 객체)this
(메서드를 호출한 객체)this
(window 객체)일반 함수 내부에서 this
는 전역 객체인 window
가 저장된다.
var data = 10;
function thisFn() {
this.data = 20;
console.log("this.data = 20; 실행한 후 ===== ===== =====");
console.log("1.data : " + data);
console.log("2.this.data : " + this.data);
console.log("3.window.data : " + window.data);
data = 30;
console.log("data = 30; 실행한 후 ===== ===== =====");
console.log("1.data : " + data);
console.log("2.this.data : " + this.data);
console.log("3.window.data : " + window.data);
}
thisFn();
출력결과
this.data = 20; 실행한 후 ===== ===== =====
1.data : 20
2.this.data : 20
3.window.data : 20
data = 30; 실행한 후 ===== ===== =====
1.data : 30
2.this.data : 30
3.window.data : 30
미리 선언해준 전역 변수 data
에 새값을 할당해도 this.data
는 오염되지 않고 유지된다.
thisInnerFn()
함수는 thisOuterFn()
함수에 만들어져 있기 때문에 thisOuterFn()
내부에서만 사용할 수 있는 전형적인 중첩 함수다. 따라서 일반 중첩 함수에서 this
역시 window
가 된다.
var data = 10;
function thisOuterFn() {
function thisInnerFn() {
this.data = 20;
data = 30;
console.log("1.data : " + data);
console.log("2.this.data : " + this.data);
console.log("3.window.data : " + window.data);
}
thisInnerFn();
}
thisOuterFn();
출력결과
1.data : 30
2.this.data : 30
3.window.data : 30
이벤트 리스너에서 this
는 이벤트를 발생시킨 객체가 된다.
var data = 10;
$(document).ready(function () {
$("#thisButton").click(function () {
this.data = 20;
console.log("this.data = 20; 실행한 후 ===== ===== =====");
console.log("1.data : " + data);
console.log("2.this.data : " + this.data);
console.log("3.window.data : " + window.data);
data = 30;
console.log("data = 30; 실행한 후 ===== ===== =====");
console.log("1.data : " + data);
console.log("2.this.data : " + this.data);
console.log("3.window.data : " + window.data);
});
});
출력결과
this.data = 20; 실행한 후 ===== ===== =====
1.data : 10
2.this.data : 20
3.window.data : 10
data = 30; 실행한 후 ===== ===== =====
1.data : 30
2.this.data : 20
3.window.data : 30
메서드에서 this
는 객체 자신이 저장된다.
var data = 10;
function thisFn() {
this.data = 0;
}
thisFn.prototype.thisMethod = function () {
this.data = 20;
console.log("this.data = 20; 실행한 후 ===== ===== =====");
console.log("1.data : " + data);
console.log("2.this.data : " + this.data);
console.log("3.window.data : " + window.data);
data = 30;
console.log("data = 30; 실행한 후 ===== ===== =====");
console.log("1.data : " + data);
console.log("2.this.data : " + this.data);
console.log("3.window.data : " + window.data);
};
var thisFn = new thisFn();
thisFn.thisMethod();
출력결과
this.data = 20; 실행한 후 ===== ===== =====
1.data : 10
2.this.data : 20
3.window.data : 10
data = 30; 실행한 후 ===== ===== =====
1.data : 30
2.this.data : 20
3.window.data : 30
객체의 메서드 내부에 만들어지는 중첩 함수에서 this
는 객체가 아닌 window
가 된다.
var data = 10;
function thisOuterFn() {
this.data = 0;
}
thisOuterFn.prototype.thisMethod = function () {
function thisInnerFn() {
this.data = 20;
console.log("this.data = 20; 실행한 후 ===== ===== =====");
console.log("1.data : " + data);
console.log("2.this.data : " + this.data);
console.log("3.window.data : " + window.data);
data = 30;
console.log("data = 30; 실행한 후 ===== ===== =====");
console.log("1.data : " + data);
console.log("2.this.data : " + this.data);
console.log("3.window.data : " + window.data);
}
thisInnerFn();
};
var thisOuterFn = new thisOuterFn();
thisOuterFn.thisMethod();
출력결과
this.data = 20; 실행한 후 ===== ===== =====
1.data : 20
2.this.data : 20
3.window.data : 20
data = 30; 실행한 후 ===== ===== =====
1.data : 30
2.this.data : 30
3.window.data : 30
Arrow function
은 자신을 포함하는 외부 scope
에서 this
를 계승받는다.
즉, Arrow function
은 자신만의 this
를 생성하지 않는다. (Lexical this)
React Class
형 컴포넌트를 예로 들어보자.
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
data: 'www.javatpoint.com'
}
this.handleEvent = this.handleEvent.bind(this);
}
handleEvent(){
console.log(this.props);
}
render() {
return (
<div className="App">
<h2>React Constructor Example</h2>
<input type ="text" value={this.state.data} />
<button onClick={this.handleEvent}>Please Click</button>
</div>
);
}
}
export default App;
일반적으로 함수는 호출한 자신을 this
로 암묵적으로 지정한다.
하지만 여기서 handleEvent()
에서 this
는 컴포넌트 class
인 App
을 가르킨다.
이렇게 되기 위해서 constructor
부분에 this.handleEvent = this.handleEvent.bind(this)
구문을 통해 호출한 handleEvent()
가 아닌 class
의 App
을 명시적으로 잡아주었다.
만약 이를 Arrow function
으로 변환해보자.
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
data: 'www.javatpoint.com'
}
}
handleEvent = () => {
console.log(this.props);
}
render() {
return (
<div className="App">
<h2>React Constructor Example</h2>
<input type ="text" value={this.state.data} />
<button onClick={this.handleEvent}>Please Click</button>
</div>
);
}
}
export default App;
위와 같이 변환할 수 있다.
따로 this
를 constructor
에서 .bind()
해주지 않아도 arrow function
의 경우 호출한 함수자체에 대한 this
를 만들지 않고 호출한 함수자체를 포함한 상위의 외부 this
를 계승받아 암묵적으로 상위 class
인 App
을 this
로 인식한다.
때때로, 우리는 라이브러리나 헬퍼오브젝트를 사용한다. (Ajax, event handling, etc.) 그리고 전달된 콜백을 호출한다. 이러한 경우에는, this의 동작을 주의해야 한다.
myObject = {
myMethod: function () {
helperObject.doSomethingCool('superCool',
this.onSomethingCoolDone);
},
onSomethingCoolDone: function () {
/// Only god knows what is "this" here
}
};
우리가 "this.onSomethingCoolDone"을 콜백으로 넘겼기 때문에, 스코프가 그 메소드를 참조하고 있다고 생각할 수도 있다.
이 부분을 고치기 위해, 몇가지 방법이 있습니다
myObject = {
myMethod: function () {
helperObject.doSomethingCool('superCool', this.onSomethingCoolDone, this);
},
onSomethingCoolDone: function () {
/// Now everybody know that "this" === myObject
}
};
myObject = {
myMethod: function () {
helperObject.doSomethingCool('superCool', this.onSomethingCoolDone.bind(this));
},
onSomethingCoolDone: function () {
/// Now everybody know that "this" === myObject
}
};
다음의 글을 참고하였습니다.