this에 대해 배울거에요. 다른 객체지향 언어에서는 this는 곳 클래스로 생성한 인스턴스를 말합니다. 그러나 자바스크립트에서는 this가 어디에서나 사용될 수 있어요. this가 상황별로 어떻게 달라지는지, 이유는 뭔지, this를 추적하는 방법 등을 알아 볼 예정입니다.
상황에 따라 달라지는 this
this는 실행 컨텍스트가 생성될 때 결정(this binding) === this는 함수를 호출할 때 결정
전역 공간에서 this는 전역 객체를 가리킴
window(브라우저 환경), global(node 환경)
console.log(this);
console.log(window);
console.log(this === window); //true
메서드로서 호출할 때 그 메서드 내부에서의 this
함수 vs 메서드
함수와 메서드가 호출될 때, this는 각각 다르게 할당
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
var obj = {
methodA: function () { console.log(this) },
inner: {
methodB: function() { console.log(this) },
}
};
obj.methodA(); // this === obj
obj['methodA'](); // this === obj
obj.inner.methodB(); // this === obj.inner
obj.inner['methodB'](); // this === obj.inner
obj['inner'].methodB(); // this === obj.inner
obj['inner']['methodB'](); // this === obj.inner
함수로서 호출할 때 그 함수 내부에서의 this
메서드의 내부라고 해도, 함수로서 호출한다면 this는 전역 객체
var obj1 = {
outer: function() {
console.log(this);
var innerFunc = function() {
console.log(this);
}
innerFunc();
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod();
}
};
obj1.outer();
this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지가 관건임!
하지만 그럼에도 불구하고 doesn’t make sense!
변수 활용
var obj1 = {
outer: function() {
console.log(this);
var innerFunc1 = function() {
console.log(this);
}
innerFunc1();
var self = this;
var innerFunc2 = function() {
console.log(self);
};
innerFunc2();
}
};
obj1.outer();
화살표 함수
ES6에서 처음 도입된 화살표 함수는, 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 없음(따라서, this는 이전의 값-상위값-이 유지됨)
var obj = {
outer: function() {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
}
}
obj.outer();
콜백 함수 호출 시 그 함수 내부에서의 this
setTimeout(function () { console.log(this) }, 300);
[1, 2, 3, 4, 5].forEach(function(x) {
console.log(this, x);
});
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
console.log(this, e);
});
생성자 함수 내부에서의 this
생성자 : 구체적인 인스턴스를 만들기 위한 일종의 틀
공통 속성들이 이미 준비돼 있음
var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
};
var choco = new Cat('초코', 7); //this : choco
var nabi = new Cat('나비', 5); //this : nabi
명시적 this 바인딩(자동으로 부여되는 상황별 this의 규칙을 깨고 this에 별도의 값을 저장하는 방법)
call 메서드
호출 주체인 함수를 즉시 실행하는 명령어
예시
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
apply 메서드
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
call / apply 메서드 활용
유사배열객체(array-like-object)에 배열 메서드를 적용
(출처 : https://kamang-it.tistory.com/entry/JavaScript15유사배열-객체Arraylike-Objects)
(출처 : https://www.daleseo.com/js-array-slice-splice/)
//객체에는 배열 메서드를 직접 적용할 수 없어요.
//유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있어요.
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' ]
arguments, NodeList에 배열 메서드를 적용(VSCode)
Array.from 메서드(ES6)
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
var arr = Array.from(obj);
console.log(arr);
생성자 내부에서 다른 생성자를 호출(공통된 내용의 반복 제거)
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 wj = new Student('원장', 'male', '서울대');
var tj = new Employee('투장', 'female', '삼성');
여러 인수를 묶어 하나의 배열로 전달할 때 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);
//효율
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);
//Spread Operation(ES6)
//https://paperblock.tistory.com/62
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max min);
bind 메서드
call과 비슷하지만, 즉시 호출하지는 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 메서드
목적
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // window객체
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 프로퍼티에 ‘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를 내부함수나 콜백 함수에 전달하기
내부함수
var obj = {
outer: function() {
console.log(this);
var innerFunc = function () {
console.log(this);
};
innerFunc.call(this);
}
};
obj.outer();
var obj = {
outer: function() {
console.log(this);
var innerFunc = function () {
console.log(this);
}.bind(this);
innerFunc();
}
};
obj.outer();
콜백함수
var obj = {
logThis: function () {
console.log(this);
},
logThisLater1: function () {
setTimeout(this.logThis, 500);
},
logThisLater2: function () {
setTimeout(this.logThis.bind(this), 1000);
}
};
obj.logThisLater1();
obj.logThisLater2();
화살표 함수의 예외사항
var obj = {
outer: function () {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
};
};
obj.outer();
별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)
// forEach 예시
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());
// 콜백 함수와 함께 thisArg를 인자로 받는 메서드
// forEach, map, filter, some, every, find, findIndex, flatMap, from, forEach(Set, Map)