
👉 ‘코어 자바스크립트’의 ‘03 this’을 읽으며 기억할 내용들과 새로 알게 된 내용을 정리하였습니다.
this: 클래스로 생성한 인스턴스 객체this: 어디서든 사용 가능하다. 상황에 따라 대상이 다름. 이유를 파악하기 힘든 경우, 예상과 다른 경우 등.. 어려움.this는 실질적으로 구분하는 거의 유일한 기능.this기본적으로 this는 실행 컨텍스트가 생성될 때 함께 결정: this는 함수를 호출할 때 결정.
어떤 방식으로 호출하냐에 따라 값이 달라짐.
3-1-1 전역 공간에서의 this
: 전역 객체를 가리킴. 개념상 전역 컨텍스트를 생성하는 주체가 전역 객체.
windowglobal💡 전역변수 선언 시, 전역 객체의 property로도 할당:
JS의 모든 변수는 사실 특정 객체(실행 컨텍스트의 LexicalEnvironment)의 property로 동작
var a = 1;
delete window.a; // false
console.log(a, window.a, this.a); // 1 1 1
var b = 2;
delete b; // false
console.log(b, window.b, this.b); // 2 2 2
window.c = 3;
delete window.c; // true
console.log(c, window.c, this.c); // Uncaught ReferenceError: c is not defined
3-1-2 메서드로서 호출할 때 그 메서드 내부에서의 this
함수 vs 메서드
독립성의 차이: 함수는 그 자체로 독립적, 메서드는 호출한 대상 객체에 대한 동작.var func = function (x) {
console.log(this, x);
}
func(1); // Window { ... } 1
var obj = {
method: func
};
obj.method(2); // { method: f } 2
var obj = {
method: function (x) { console.log(this, x); }
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2
메서드 내부에서의 this
: 호출한 주체에 대한 정보, 마지막 점 앞에 명시된 객체.
3-1-3 함수로서 호출할 때 그 함수 내부에서의 this
함수 내부에서의 this
: this가 지정되지 않음. 호출 주체를 알 수 없기 때문. this가 지정되지 않으면 this는 전역 객체를 가리키게 됨.
메서드의 내부함수에서의 this
this라는 단어 자체가 주는 느낌적 느낌 그대로 생각하면 예상과 다른 결과가 나온다.var obj1 = {
outer: function () {
console.log(this); // (1)
var innerFunc = function () {
console.log(this); // (2) innnerFunc (3) obj2.innerMethod
}
innerFunc();
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod();
}
};
obj1.outer();
this의 대상이 서로 다르다.this의 바인딩은 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지 등)은 중요하지 않고, 호출하는 구문에 . 혹은 [] 표기가 있는지 없는지가 중요.메서드 내부 함수에서의 this를 우회하는 방법
this를 그대로 상속받아 사용할 수 있는 방법이 없는가?var obj = {
outer: function () {
console.log(this); // (1) { outer: f }
var innerFunc1 = function () {
console.log(this); // (2) Window { ... }
}
innerFunc1();
var self = this;
var innerFunc2 = function () {
console.log(self); // (3) { outer: f }
};
innerFunc2();
}
};
obj1.outer();
this를 바인딩하지 않는 함수
this가 전역 객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수(arrow function)을 새로 도입.this 바인딩 과정 자체가 없어, 상위 스코프의 this를 그대로 활용 가능.var obj = {
outer: function () {
console.log(this); // (1) { outer: f }
var innerFunc = () => {
console.log(this); // (2) { outer: f }
}
innerFunc();
}
};
obj.outer();
3-1-4 콜백 함수 호출 시 그 함수 내부에서의 this
콜백 함수: 함수의 제어권을 다른 함수 or 메서드에 넘겨주는 함수.this가 전역 객체 참조.this의 대상을 지정한 경우 그 대상을 참조.setTimeout(function () { console.log(this); }, 300); // (1) 전역 객체(Window)
[1, 2, 3, 4, 5].forEach(function (x) { // (2) 전역 객체(Window)
console.log(this, x);
});
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
.addEventListener('click', function (e) { // (3) <button id="a">클릭</button>
console.log(this, e);
});
3-1-5 생성자 함수 내부에서의 this
생성자(constructor): 구체적인 인스턴스를 만들기 위한 일종의 틀.
this는 곧 새로 만들 구체적인 인스턴스 자신.prototype 프로퍼티를 참조하는 __proto__라는 프로퍼티가 있는 객체(인스턴스)를 만들고, 미리 준비된 공통 속성 및 개성을 해당 객체(this)에 부여.var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
};
var choco = new Cat('초코', 7);
var nabi = new Cat('나비', 5);
console.log(choco, nabi);
/* 결과
Cat { bark: '야옹', name: '초코', age: 7 }
Cat { bark: '야옹', name: '나비', age: 5 }
*/
this를 바인딩하는 방법this에 별도의 대상을 바인딩하는 방법.
3-2-1 call 메서드
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
call 메서드: 호출 주체인 함수를 즉시 실행하도록 하는 명령.
this로 바인딩, 이후의 인자들은 호출할 함수의 매개변수.this는 전역 객체를 참조하지만, call 메서드 사용 시 임의의 객체를 this로 지정 가능.var func = function (a, b, c) {
console.log(this, a, b, c);
};
func(1, 2, 3); // Window { ... } 1 2 3
func.call({ x: 1 }, 4, 5, 6); // { x: 1 } 4 5 6
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6
3-2-2 apply 메서드
Function.prototype.apply(thisArg[, argsArray])
기능적으로 call과 완전히 동일.
var func = function(a, b, c) {
console.log(this, a, b, c);
};
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6
3-2-3 call / apply 메서드의 활용
유사배열객체(Array-like object)에 배열 메서드 적용
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
Array.prototype.push.call(obj, 'd');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }
var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]
객체에는 배열 메서드 직접 적용 X.
키가 0 or 양의 정수 프로퍼티 and length 프로퍼티 값이 0 or 양의 정수인 경우(유사배열객체) call or apply를 통해 Array의 프로토타입을 이용해서 배열 메서드를 차용 가능.
call 메서드를 이용해 원본인 유사배열객체의 얕은 복사 수행, slice가 배열 메서드이므로 배열로 반환.
function a () {
var argv = Array.prototype.slice.call(arguments);
argv.forEach(function (arg) {
console.log(arg);
});
}
a(1, 2, 3);
document.body.innerHTML = '<div>a</div><div>b</div><div>c</div>';
var nodeList = document.querySelectorAll('div');
var nodeArr = Array.prototype.slice.call(nodeList);
nodeArr.forEach(function (node) {
console.log(node);
});
모든 배열 메서드 적용 가능. 문자열도 마찬가지(문자열도 인덱스와 length가 존재하기 때문).
var str = 'abc def';
Array.prototype.push.call(str, ', pushed string');
// Error: Cannot assign to read only property 'length' of object [object String]
Array.prototype.concat.call(str, 'string'); // [String {"abc def"}, "string"]
Array.prototype.every.call(str, function(char) { return char !== ' '; }); // false
Array.prototype.some.call(str, function(char) { return char === ' '; }); // true
var newArr = Array.prototype.map.call(str, function(char) { return char + '!'; });
console.log(newArr); // ['a!', 'b!', 'c!', ' !', 'd!', 'e!', 'f!']
var newStr = Array.prototype.reduce.apply(str, [
function(string, char, i) { return string + char + i; },
''
]);
console.log(newStr); // "a0b1c2 3d4e5f6"
call / apply를 이용해 형변환은 ‘this를 원하는 값으로 지정해 호출한다’는 본래의 의도와 다른 활용법. ⇒ 유사배열객체 or 순회 가능 모든 종류 데이터 타입 배열 전환: Array.from 도입.
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
var arr = Array.from(obj);
console.log(arr); // ['a', 'b', 'c']
생성자 내부에서 다른 생성자 호출
생성자 내부에 다른 생성자와 공통된 내용이 있는 경우 call / apply를 이용해 다른 생성자를 호출하여 간단히 반복 간소화 가능.
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
function Student(name, gender, school) {
Person.call(this, name, gender);
this.school = school;
}
function Employee(name, gender, company) {
Person.apply(this, [name, gender]);
this.company = company;
}
var by = new Student('보영', 'female', '단국대');
var jn = new Employee('재난', 'male', '구골');
여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 - apply 활용
메서드의 인수의 수가 가변적인 경우 apply를 사용하면 좋다.
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];
numbers.forEach(function(number) {
if (number > max) {
max = number;
}
if (number < min) {
min = number;
}
});
console.log(max, min); // 45 3
Math.min / Math.max에 apply 적용 시.
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min); // 45 3
펼치기 연산자(스프레드 연산자(spread operator) 이용.
var numbers = [10, 20, 3, 16, 45];
var max = Math.max(...numbers);
var min = Math.min(...numbers);
console.log(max, min); // 45 3
call / apply 메서드는 명시적으로 별도의 this를 바인딩하면서 함수 또는 메서드를 실행하는 훌륭한 방법이지만, 오히려 이로 인해 this를 에측하기 어렵게 만들어 코드 해석을 방해한다는 단점.
그럼에도 불구하고 ES5 이하 환경에서는 마땅한 대안이 없어 실무에서도 광범위하게 활용.
3-2-4 bind 메서드
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
bind: ES5에서 추가된 기능. call 비슷하지만, 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드.
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // Window{ ... } 1 2 3 4
var bindFunc1 = func.bind({ x: 1 });
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8
var bindFunc2 = func.bind({ x: 1 }, 4, 5);
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9
name 프로퍼티
bind로 생성한 함수는 name 프로퍼티에 bind의 수동태인 bound라는 접두어가 붙음.
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x: 1 }, 4, 5);
console.log(func.name); // func
console.log(bindFunc.name); // bound func
상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달하기
self를 활용한 우회법 대신 call / apply 또는 bind를 이용하면 더 깔끔.
var obj1 = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.log(this);
};
innerFunc.call(this);
}
};
obj1.outer();
var obj2 = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.log(this);
}.bind(this);
innerFunc();
}
};
obj2.outer();
콜백을 인자로 받거나 기본적으로 콜백 내의 this에 관여하는 함수 or 메서드에 대해서도 bind를 이용하여 this 변경 가능.
var obj = {
logThis: function () {
console.log(this);
},
logThisLater1: function () {
setTimeout(this.logThis, 500);
},
logthisLater2: function () {
setTimeout(this.logThis.bind(this), 1000);
}
};
obj.logThisLater1(); // Window { ... }
obj.logThisLater2(); // obj { logThis: f, ... }
3-2-5 화살표 함수의 예외사항
화살표 함수: 실행 컨텍스트 생성 시 this 바인딩 X. 스코프 체인 상 가장 가까운 this 참조.
var obj = {
outer: function () {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
}
};
obj.outer();
더욱 간결 and 편리.
3-2-6 별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)
콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this로 지정할 객체(thisArg)를 인자로 지정 가능.
var report = {
sum: 0,
count: 0,
add: function () {
var args = Array.prototype.slice.call(arguments);
args.forEach(function (entry) {
this.sum += entry;
++this.count;
}, this);
},
average: function () {
return this.sum / this.count;
}
};
report.add(60, 85, 95);
console.log(report.sum, report.count,report.average()); // 240 3 80
add 메서드가 arguments를 배열로 변환해서 args 변수에 담고, 배열을 순회하며 콜백 실행.
이 콜백 함수 내부에서의 this는 forEach 함수의 2번째 인자로 전달해 준 this가 바인딩.
60, 85, 95를 인자로 add를 호출하면, 이를 배열로 만들어 forEach 메서드 실행.
콜백 함수 내부에서의 this는 add 메서드에서의 this가 전달된 상태이므로 add 메서드의 this(report)를 그대로 참조.
순회하면서 report.sum, report.count 값이 차례로 바뀌고, 순회가 끝나면 240, 3이 담김.
기타 thisArg 인자 메서드
Array.prototype.forEach(callback[, thisArg])
Array.prototype.map(callback[, thisArg])
Array.prototype.filter(callback[, thisArg])
Array.prototype.some(callback[, thisArg])
Array.prototype.every(callback[, thisArg])
Array.prototype.find(callback[, thisArg])
Array.prototype.findIndex(callback[, thisArg])
Array.prototype.flatMap(callback[, thisArg])
Array.prototype.from(callback[, thisArg])
Set.prototype.forEach(callback[, thisArg])
Map.prototype.forEach(callback[, thisArg])
명시적 this 바인딩이 없는 한 늘 성립하는 규칙.
명시적 this 바인딩 규칙.