this에 관한 탐구
this를 사용하면서 생각날 수 있는 방식에 대해 탐구한다
this
실행
과정에서 호출될 때 결정(바인딩
)되는 키워드이다.- this에 관한 여러가지가 적힌 stackoverflow링크
암시적인 바인딩
- 함수 리턴의 객체 프로퍼티의 this
function makeUser(){
return {
name: "John",
ref: this,
}
}
let user = makeUser();//(global에 있는) makeUser();
console.log(user.ref.name); //Undefined (or Exception)
user
의 ref(this)
는 무엇인가? -> 없음
. 따라서 global
this
는 호출
될 때 이미 결정되었다. user
는 global
에 있는 makeUser
를 호출하였고, this
는 global
이 된다.
다음을 참고하면,
const userFactory = {
name: "userFactory",
makeUser: function(){
return {
name: "John",
ref: this,
}
}
}
let user = userFactory.makeUser();
//userFactory 안에 있는 makeUser();
console.log(user.ref.name); //userFactory
this
의 대상을 찾아보았지만 대상이 없을 때 global(node.js)
이나 window(browser)
로 표시한다. use strict
할 경우 undefined
이다.
"use strict"
console.log((()=>{
console.log(this);
})());
//{}
//(처음의 console.log는 IIFE로부터 아무것도 리턴 안하는 객체를 받아
//비어있는 객체가 된다. 의미 없는 출력)
//undefined
//(strict한 상태에서 this가 대상을 찾지 못하면
//window나 global이 아니라 undefined가 된다.
user
는 userFactory
에서 호출한 makeUser()
의 리턴을 받는다. this
는 호출될 때 이미 결정되었다. userFactory
에서 makeUser
를 호출하였고, this
는 userFactory
가 된다.
- IIFE의 this
IIFE는 외부로부터 격리되는
let user = (
return {
name: "John",
ref: this
}
})();
console.log(user.ref.name);
//Undefined (or Exception)
console.log(user.indata);
//{internal_name : "My Internal Name"}
user
는 function makeUser()
의 결과물인 객체의 return
이다. makeUser IIFE의 결과인 user의 경우, this는 전역에 의해 호출된다. 따라서 global
이나 window
를 리턴한다(찾을 수 없음).
그 외에도, IIFE는 this바인딩 함수(call, apply, bind 등...)로 바인드하는것이 아니면, this는 항상 window
나 global
이나 undefined
가 된다.
const something = {
name: "Something",
makeUser:(function(){
return {
name:"John",
ref:this
}
})()
}
console.log(something.makeUser.ref);
//undefined (or Exception)
something
의 makeUser
의 this
는 없다
. 따라서, window
나 global
을 표시한다. strict
의 경우 undefined
.
IIFE에서의 this관련 Stackoverflow 링크
- 화살표 함수의 this
상기 링크에서 다음과 같이 기재되어 있다.
화살표 함수가 나오기 전까지는, 모든 새로운 함수는, 어떻게 그 함수가 호출되는지에 따라 자신의 this 값을 정의했습니다:
이 함수가 생성자인 경우는 새로운 객체
엄격 모드 함수 호출에서는 undefined
함수가 "객체 메서드"로서 호출된 경우 문맥 객체 등등
이는 객체 지향 스타일로 프로그래밍할 때 별로 좋지않습니다.
화살표 함수는 자신의 this가 없습니다.
대신 화살표 함수를 둘러싸는 렉시컬 범위(lexical scope)의 this가 사용됩니다;
화살표 함수는 일반 변수 조회 규칙(normal variable lookup rules)을 따릅니다.
때문에 현재 범위에서 존재하지 않는 this를 찾을 때, 화살표 함수는 바로 바깥 범위에서 this를 찾는것으로 검색을 끝내게 됩니다.
상기 기재된 렉시컬 범위
는 컴파일레이션
과정 중 렉싱 타임
에 렉서
가 코드를 처리할 때 확정적으로 정의되는 스코프이다. 기존 함수의 this가 호출될 때 정해진다면, 화살표 함수의 this는 컴파일레이션 단계에서 정해진 렉시컬 범위를 참조하므로, 컴파일레이션 단계에서 결정되는거나 다름없다고 볼 수도 있다.
let obj = { // does not create a new scope
i: 10,
arrow: () => console.log(this.i, this),
expression: function() {
console.log( this.i, this)
}
}
obj.arrow(); // prints undefined, Window {...} (or the global object)
obj.expression(); // prints 10, Object {...}
obj
는 화살표 함수의 대상이 아니다. arrow함수의 렉시컬 범위의 바로 바깥은global
/window
이다. 그러므로arrow
에서 표현하는this
는없다
.obj
의expression
에서 함수의 호출부는 obj이다. 따라서this
는 호출부인obj
가 된다.
class OBJ{
constructor(){
this.i = 10;
this.arrow = () => console.log(this.i, this);
this.expression = function() {
console.log( this.i, this);
};
}
};
let obj = new OBJ();
obj.arrow();// prints 10, Object {...}
obj.expression();// prints 10, Object {...}
obj
는class
(사실, class는 객체지향프로그래밍 기준 언어의 Class추상화 패턴을 JS환경에서 어떻게든 비슷하게 사용할 수 있게만 만들어준 것이다.)를 구현한 객체이며 이곳의 this를 호출하면 this가 객체가 된 obj를 지칭하게 되며,this
가 정확히obj
가 됨을 알 수 있다.
그 외의 방법으로 Babel
트랜스파일러와 파이어폭스 브라우저
에서 분석한 소스의 결과를 비교해보는 것으로 차이점을 확인해본다.
let arrow = () => {
console.log("arrow");
}
let expression = function(){
console.log("expression");
}
다음 코드를 분석한 일부는 다음과 같다.
var arrow = function arrow() {
console.log("arrow");
};
var expression = function expression() {
console.log("expression");
};
this
가 없어서 그런지 차이를 보이지 않는다. this
가 없으면 Babel에서는 화살표함수나 무명함수나 거기서 거기로 판단한다.
하지만, this
가 존재하는 다음 코드를 분석하면 차이가 발생하게 된다.
let arrow = () => {
console.log(this);
}
let expression = function(){
console.log(this);
}
분석한 결과는 다음과 같다.
var _this = this;
var arrow = function arrow() {
console.log(_this);
};
var expression = function expression() {
console.log(this);
};
화살표 함수는 자신을 감싸는 렉시컬 스코프의 this
를 _this
변수를 통해 가져오며, 무명함수는 있는 그대로의 this
를 사용함을 알 수 있다.
명시적인 바인딩
명시적인 바인딩은 암시적인 바인딩보다 우선순위가 높다.
우선순위가 높아서, this를 덮어쓸 수 있다.
화살표 함수에서는 쓸 수 없다.
call
: 0번째 인자는 bind할 object, 그 다음 인자는 순서대로 함수의 인자로 하면서 수행한결과
를 리턴한다.
apply
: 0번째 인자는 bind할 object, 그 다음 인자는 Array이어야 하며, 함수의 인자로 하면서 수행한결과
를 리턴한다.
function say_hello(){
console.log("hello!", this.target," and ", arguments);
}
const GreetingFactory = {
target: "GF",
hello:say_hello
}
const bar1 = {
target: "BAR1"
}
const bar2 = {
target: "BAR2"
}
const bar3 = {
target: "BAR3"
}
// this를 결정하면서, 함수를 수행한다.
say_hello.call(bar1, "bar1", "bar2", "bar3")
//hello! BAR1 and [Arguments] { '0': 'bar1', '1': 'bar2', '2': 'bar3' }
// this를 결정하면서, 함수를 수행한다.
say_hello.apply(bar2, ["bar1", "bar2", "bar3"]);
//hello! BAR2 and [Arguments] { '0': 'bar1', '1': 'bar2', '2': 'bar3' }
//GreetingFactory의 암시적인 바인딩을 수행하였으나,
//명시적인 바인딩으로 this를 바꾸어버렸다.
GreetingFactory.hello.call(bar3);
//hello! BAR3 and [Arguments] {}
명시적인 바인딩 - 하드 바인딩
bind
: this
로 할 객체와 argument
를 직접 바인딩
한다. 바인딩
한 함수
를 리턴한다.
화살표 함수
에서는 쓸 수 없다.
function say_hello(){
console.log("hello!", this.target," and ", arguments);
}
const bar1 = {
target: "BAR1"
}
const bar2 = {
target: "BAR2"
}
const GreetingFactory = {
target: "GF",
//bind는 this를 bar1로 `하드 바인딩`한 결과 함수를 리턴한다.
hello:say_hello.bind(bar1, "foo1", "foo2")
}
//bar2를 call하면서
GreetingFactory.hello.call(bar2);
// hello! BAR1 and [Arguments] { '0': 'baz1', '1': 'baz2' }
bind
함수는 다음과 같이 사용할 수 있다.
function add_a_and_b (a,b){
return a + b;
}
//a를 2로 정해놓은 함수가 된다.
const add_2_and_b = add_a_and_b.bind(null, 2);
console.log(add_2_and_b(3));
Author And Source
이 문제에 관하여(this에 관한 탐구), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@fgprjs/this에-관한-탐구저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)