모든 'function'이라는 키워드로 생성된 함수는 this
를 쓸 수 있다.
화살표 함수의 경우에 this
는 항상 상위 scope가 된다.
this
에 관해 알아야 할 가장 중요한 특징은,
this
의 값은 this
가 사용된 함수가 어떤 방식으로 "실행"되었는지에 따라 달라진다는 점이다.
즉, 선언된 부분만으로는 this
가 무엇을 가리키는 지 알 수 없다는 뜻이다.
this
를 가진 함수가 실행될 수 있는 방식은 다음과 같이 4가지로 나누어볼 수 있다:
1. Regular Function Call
2. Dot Notation
3. Call, Appply, Bind
4. "new" Keyword
그렇다면 지금부터 this
값을 정확히 구분하는 방법에 대해 차례대로 정리해보자❗️
첫번째는, '일반함수실행(Regular Function Call)' 방식이다.
일반적으로 함수를 호출할 때 함수명();
와 같은 방법을 사용한다.
이처럼 함수명만을 이용해 함수를 실행하는 방식을 '일반함수실행' 방식이라고 명명하기로 한다.
일반함수실행 방식에서의 this
는 Strict Mode를 기준으로, 다시 2가지로 나뉘어진다.
this
를 포함하고 있는 함수가 실행된 방식이 일반함수실행 방식이라면,
this
는 global object(= 브라우저에서는 window)이다.
var name = "pepper";
var pet = {
name: "salt",
printName: function() {
who(); //`this`값을 판별하는 데에 보아야할 부분은 who()가 실행된 이 부분!!
}
};
function who() {
console.log(this.name);
}
pet.printName(); //pepper
위 예제에서 this
값을 판별하는 데에 중요한 정보는 주석으로 표시된 부분,this
를 포함한 함수인 who()
가 실행되는 부분이다.
비록 pet.printName();
이 실행됨으로 인해 who()
함수가 실행되고 있지만, 달라지는 것은 없다.
즉, this
를 포함한 함수가 또 다른 함수 내에 포함되어 있다 하더라도 확인해야 하는 부분은 실제로 this
를 갖고 있는 함수가 실행되는 방식인 것이다.
+PLUS: 만일 var name = "pepper";
를 var
가 아닌 let
으로 변수를 선언했다면, 전역스코프(global scope)에 선언했다 할지라도 let
이나 const
로 선언한 변수는 window 객체에 추가되지 않으므로 "pepper"
가 아닌 undefined
를 반환한다.
게시글 <var, let, const 의 차이점>의 2. window 객체 요소 추가 부분 참고
"use strict";
를 입력하면 Strict Mode를 활성화된다.
Strict Mode를 활성화하면 엄격한 코드실행모드가 켜지면서 this
의 값도 달라진다.
이 때 this
는 undefined가 된다.
'use strict';
var name = 'Hannah';
function call() {
console.log(this.name);
}
call(); //Error: Cannot read property 'name' of undefined
Strict Mode를 키고 this
값을 가진 일반함수를 실행한다면 this
는 무조건 undefined
가 된다!
왜냐하면 undefine.name
은 error
이기 때문!
만약 Strict Mode가 아닌 일반함수실행 모드였다면 this
는 window를 가리키게 되고,
그러면 console.log(this.name)
은 name
이라는 속성이 없다해도 아래와 같이 error
가 아닌 undefined
를 반환해서 자연스럽게 넘어가게 된다.
//non-strict mode
console.log(window.name); //undefined
//strict mode
'use strict';
console.log(undefined.name); //Error: Cannot read property 'name' of undefined
객체에 없는 속성을 불러오려고 하면 undefined
를 반환하기 때문이다.
사실 실무에서 window를 가리키는 목적으로 this
를 사용하는 경우는 없다고 한다.
this = window
의 목적으로 사용되었다면 bug일 수 있고 오히려 혼선만 생길 수 있기 때문에,
이런 this
의 특징을 잘 활용하면 debugging 을 보다 쉽게 할 수 있다는 장점이 있다 😃
두번째는, 실행문의 dot앞에 있는 객체가 this가 되는 방식이다.
객체명.함수명(); 으로 실행되는 함수에서 this
는 dot(.) 앞에 위치한 객체가 된다.
var color = "yellow";
function printColor () {
console.log(this.color);
}
var salt = {
color: "white",
printColor: printColor
};
var hannah = {
color: "skyblue",
printColor: salt.printColor
};
salt.printColor(); //white
hannah.printColor(); //skyblue
위 예제에서 dot(.)앞에 위치한 객체를 기준으로 this
값을 판별해보면, salt.printColor();
의 this
는 salt
객체가 되고,
hannah.printColor();
의 this
는 hannah
객체가 된다.
this
를 가진 함수가 객체명.함수명();의 형식으로 실행이 되었다면, this
값을 찾기위해 오롯이 확인해야 하는 것은 실행문에서 dot(.) 앞의 객체가 무엇이냐는 것이다.
여기서 주의할 점은,
hannah
객체의 printColor
속성값은 salt.printColor
이기 때문에 hannah.printColor();
를 실행 시 salt
객체가 this
가 될 것이라 생각할 수 있으나, 실제 this
를 가진 printColor
함수를 실행시키는 부분은 hannah.printColor();
이므로 여기서의 this
는 hannah
객체가 된다.
세번째는, Call
, Apply
, Bind
메소드를 이용해서 함수를 실행하는 방식이다.
함수도 객체이기 때문에 메소드를 사용할 수가 있다. 언제, 어디에서든 위 메소드들을 사용해서 함수를 실행시킬 수 있는데, 이 때 this
의 값도 바뀐다.
메소드는 this
의 값을 명확하게 지정하기 때문에 "Explicit Binding" 이라고 명명할 수 있다.
함수명.call(this값, 매개변수1, 매개변수2, ...)
기본적으로 call은 앞에 있는 함수를 호출해주는 기능을 한다.
그렇지만 this
를 사용한 함수에서는 this
값을 정해주는 역할을 한다.
function family() {
console.log(this.number);
}
const sister = {
number: 2
};
family.call(sister); //2
위 예제의 경우, this
값은 sister
객체가 된다.
call 메소드를 사용할 때에는 괄호 안의 첫번째 인자가 this
값이 된다.
즉, call 메소드는
1. 함수를 실행함과 동시에
2. this
값을 지정하고 싶을 때
사용할 수 있다.
만약 매개변수를 가지는 함수를 실행함과 동시에 this
값을 설정해주고 싶다면....?
function yeah (a, b, c) {
console.log(this.name);
console.log(a*b*c);
}
var she = {
name: "Rachael"
};
yeah.call(she, 1, 2, 3); //Rachael
// 6
위 예제처럼 call은 첫번째 인자인 she
는 this
값으로 설정을 하고, 나머지 1, 2, 3
은 매개변수로 함수 yeah
에게 인자로 전달한다.
call메소드는 받을 수 있는 인자의 갯수 제한이 없다.
var status = "😎";
setTimeout(() => {
const status = "😍";
const data = {
status: "🥕",
getStatus: function () {
return this.status;
}
};
console.log(data.getStatus.call(this); //여기서 this는 무엇?
}, 0);
위 예제에서 call
은 this
를 가지고 getStatus()
를 실행시킨다.
여기에서의 this
를 찾기 위해서는 this
를 가지고 있는 function을 찾아야한다.
왜냐하면, call
,apply
,bind
가 this
라는 키워드를 this
로 지정해서 함수를 실행시킬 때는 this
를 가지고 있는 함수가 this
그 자체가 되기 때문이다.
화살표함수인 setTimeout()
은 this
를 가지고 있지 않기 때문에 상위스코프에서 찾는다.
따라서 위 예제에서의 this
는 최상위스코프인 전역스코프가 되어 this.status
는 "😎 "가 된다.
+PLUS: 만약에 call을 사용한 함수에서 this
값을 null
을 할당하는 경우(ex.foo.call(null, 1, 2, 3);
)도 있는데, strict mode가 아니라면, null
및 undefined
는 전역객체로 대체되어 실행된다(error가 나지 않는다). 즉, this
값을 null
을 할당한다고 해서 실제로 this
값이 null
이 되지는 않는다.
함수명.apply(this값, [매개변수로 전달되는 배열])
apply도 call 메소드와 유사한 역할을 한다.
함수를 실행함과 동시에 this
값을 지정할 수 있다.
function yeah (a, b, c) {
console.log(this.name);
console.log(a*b*c);
}
var she = {
name: "Rachael"
};
yeah.apply(she, [1, 2, 3]); //Rachael
// 6
하지만 apply 메소드는,
1. 2개의 인자만을 전달해야하고, (call
과의 차이점 #1)
2. 첫번째 인자는 this
값이 되고,
3. 두번째 인자는 반드시 배열 (call
과의 차이점 #2)
이어야만 한다.
함수명.bind(this값, 매개변수1, 매개변수2, ...)
bind 메소드는
1. 원본 함수를 복제한 "새로운 함수"를 반환해줄뿐 함수를 실행하지 않으며,
2. 반환된 함수를 실행해야 원본함수가 실행된다.
3. 첫번째 인자는 this
값이 되고,
4. 전달할 수 있는 인자의 갯수 제한이 없다.
function hello (a, b, c) {
console.log(this.name);
}
var her = { name: "Kemaya" };
var him = hello.bind(her, 1, 2); //this=her, a=1, b=2 가 된다.
him(3); //c=3 이 된다. 복사본 함수에게 주어지는 인자도 순차적으로 원본함수에게 전달되기 때문.
//Kemaya
+PLUS: bind가 일반함수호출방식보다 우선순위가 높기 때문에, 만약 일반함수실행방식(ex. foo();)으로 실행이 되었는데 this
값이 window나 undefined
가 아니라면, 분명 앞전 단계에서 bind 로 this
를 지정해 준 내역이 있다고 추측해볼 수 있다.
+PLUS: 이벤트핸들러에서 bind메소드를 다음과 같이 활용할 수 있다.
function dog(a, b, c) {
a + b + c
}
button.addEventListener("click", function(event) {
dog(a, b, c);
});
위 예제는 bind메소드를 사용해 아래의 코드로 대체할 수 있다.
function dog(a, b, c) {
a + b + c
}
button.addEventListener("click", dog.bind(null, a, b, c));
foo.bind(null, a, b, c)
로 실행한 값을 이벤트핸들러로 주는 것!
이것은 또 아래와 같은 방법으로도 기재할 수 있다.
function dog(a, b, c) {
a + b + c
}
var pet = dog.bind(null, a, b, c);
button.addEventListener("click", pet);
❗️ 항상 메소드를 이용할 때는 이 메소드에 어떤 매개변수를 넣어줘야 하는지, 그리고 어떤 반환값이 나오는지를 깊이 생각해 보아야 한다. ❗️
네번째는, new
키워드로 함수를 실행하는 방식이다.
new
라는 키워드를 이용해 함수를 호출 시, 새롭게 생겨난 빈 객체를 반환한다.
따라서 this = {} 와 같다고 볼 수 있으며, new
를 이용해 함수를 실행하면 함수 안에서의 this
값은 빈 객체이다.
function Pet() {
this.age = 3;
this.logBreed = function () {
console.log("papillion")
}
console.log(this);
}
const family = new Pet(); //{age: 3, logBreed: ƒ}
위 예제는 new
키워드로 생성된 빈 객체에 age = 3
라는 속성과 logBreed
함수를 할당해준다.
여기서 new
키워드가 사용된 원본 함수Pet
을 "constructor function(생성자함수)" 이라고 부른다.
생성자함수에 속성을 추가하면, 새로운 객체에도 같은 속성이 추가된다.
일반적으로 생성자함수는
1) 함수명이 명사이며,
2) 첫글자를 대문자로 표기한다.
그리고 생성자 함수가 반환해주는 빈 객체는 "instance(인스턴스)" 라고 부른다.
생성자함수는 만드는 단계에서부터 인스턴스를 생성할 목적으로 만들어지기 때문에 일반함수와 구분되어 사용된다.
함수 실행 시 new
라는 키워드가 사용되면 return
값이 명시되지 않아도 특정 객체를 반환해 family
라는 변수에 담기게 된다. = 생성자 함수의 기본 반환값이 this
이기 때문!
function People () {
this.play = function () {
console.log("YAY PLAY!");
};
}
function Person (name) {
People.call(this);
this.name = name;
}
var hannah = new Person("hannah");
hannah.play(); //YAY PLAY!
위 예제에서 Person
이라는 함수 안에 this
가 무엇인지를 파악하기 위해서는 이 함수가 "어떻게" 실행되었는지를 봐야한다.
new Person();
이 Person
을 실행시키고 있으므로, Person
함수 중괄호 내 this
는 모두 새로 만들어진 빈 객체, 인스턴스 hannah
가 된다.
그리고 Person
함수 내 People.call(this)
는 this
를 새로 생성된 Person
의 인스턴스, hannah
으로 지정해줌과 동시에 People
함수를 실행시킨다.
본 포스팅은 (바닐라코딩의 수장이신)Ken님의 this 이론을 기반으로 하였습니다. 🙂
학습단계로 잘못된 정보가 있을 수 있습니다. 잘못된 부분에 대해 알려주시면 곧바로 정정하도록 하겠습니다.
The casual style of writing this blog content was enjoyable for me. moto x3m