ThisBindings는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체(실행 컨텍스트) 중 하나입니다.
this는 함수를 호출할 때 결정됩니다.
런타임 환경
프로그램이 구동중인 환경을 말합니다. node 파일이름.js로 vscode상에서 구동한다면 node 환경, html 파일 안에 숨겨놓아서 크롬브라우저 등에서 연다고 한다면 브라우저 환경이라고 할 수 있습니다.
console.log(this);
console.log(window);
console.log(this === window); // true
console.log(this);
console.log(global);
console.log(this === global); // true
메서드화 함수는 상당히 비슷해 보이지만 엄연한 차이가 존재합니다. 기준은 독립성입니다.
메서드는 자신을 호출한 대상 객체에 대한 동작을 수행합니다.
함수는 그 자체로 독립적인 기능을 수행합니다.
// 메서드
객체.메서드명();
// 함수
함수명();
호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미합니다.
obj는 곧 { method: f }를 의미하죠?
var obj = {
method: func,
};
obj.method(2); // { method: f } 2
호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미합니다.
var func = function (x) {
console.log(this, x);
};
func(1); // Window { ... } 1
메서드로서의 호출 구분 : . []
var obj = {
method: function (x) {
console.log(this, x);
},
};
obj.method(1); // { method: f } 1
obj["method"](2); // { method: f } 2
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는 전역 객체를 의미합니다.
함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지가 관건이라는 것을 알 수 있습니다.
var obj1 = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.log(this);
};
innerFunc(); // 함수로서의 호출 - 전역객체(global)
var obj2 = {
innerMethod: innerFunc,
};
obj2.innerMethod(); // 메서드로서의 호출 - obj2
},
};
obj1.outer(); // 메서드로서의 호출 - obj1
내부 스코프에 이미 존재하는 this를 별도의 변수(ex : self)에 할당하는 방법이 있습니다.
var obj1 = {
outer: function () {
console.log(this); // outer
// AS-IS(기존)
var innerFunc1 = function () {
console.log(this); // 전역객체
};
innerFunc1();
// TO-BE(이후)
var self = this;
var innerFunc2 = function () {
console.log(self); // outer
};
innerFunc2();
},
};
obj1.outer(); // 메서드 호출 부분
ES6에서 처음 도입된 화살표 함수는, 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 없습니다. 따라서, this는 이전의 값(스코프 체인상 가장 가까운 this)이 유지됩니다. ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제 때문에 화살표함수를 도입했습니다.
일반 함수와 화살표 함수의 가장 큰 차이점은 this binding 여부입니다.
바인딩(Binding)
데이터를 특정 위치에 동적으로 연결하는 것을 의미합니다.
var obj = {
outer: function () {
console.log(this); // obj
var innerFunc = () => {
console.log(this); // obj
};
innerFunc();
},
};
obj.outer();
함수, 메서드의 인자(매개변수)로 넘겨주는 함수를 콜백함수라고 합니다.
이 때, 콜백함수 내부의 this는 해당 콜백함수를 넘겨받은 함수(메서드)가 정한 규칙에 따라 값이 결정됩니다.
콜백 함수도 함수기 때문에 this는 전역 객체를 참조하지만, 콜백함수를 넘겨받은 함수에서 콜백 함수에 별도로 this를 지정한 경우는 예외적으로 그 대상을 참조하게 되어있습니다.
// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);
// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
console.log(this, x);
});
// addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
// 따라서 this는 button을 의미함
document.body.innerHTML += "<button id="a">클릭</button>";
document.body.querySelector("#a").addEventListener("click", function(e) {
console.log(this, e);
});
구체적인 인스턴스(객체)를 만들기 위한 일종의 틀
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에 별도의 값을 저장하는 방법입니다.
호출 주체인 함수를 즉시 실행하는 명령어입니다.
call명령어를 사용하여, 첫 번째 매개변수에 this로 binding할 객체를 넣어주면 명시적으로 binding할 수 있습니다.
var func = function (a, b, c) {
console.log(this, a, b, c);
};
// no binding
func(1, 2, 3); // Window{ ... } 1 2 3
// 명시적 binding
// func 안에 this에는 {x: 1}이 binding
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 - method 함수 안의 this는 obj
obj.method.call({ a: 4 }, 5, 6); // 4 5 6
call 메서드와 완전히 동일합니다. 다만, this에 binding할 객체는 똑같이 넣어주고 나머지 부분만 배열 형태로 넘겨줍니다.
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
유사배열객체
배열과 유사한 형태의 객체입니다. 반드시 length가 필요하고 index번호가 0부터 시작해서 1씩 증가해야합니다.
객체에는 배열 메서드를 직접 적용할 수 없습니다. 예시) obj.push(X), obj.slice(X)
유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있습니다.
this의 자리에 유사배열객체를 넣어줌으로 기능을 수행하도록 합니다.
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" ]
call, apply는 this binding만 하는 것이 아니라 객체 → 배열로 형 변환을 위해서도 쓸 수 있습니다. 그러나 원래 의도와는 거리가 먼 방법입니다.
따라서, ES6에서는 Array.from이라는 방법을 제시했습니다.
// 유사배열객체
var obj = {
0: "a",
1: "b",
2: "c",
length: 3
};
// 객체 → 배열
var arr = Array.from(obj);
console.log(arr); // ["a", "b", "c"]
공통된 내용 반복을 제거하는 방법
function Student(name, gender, school) {
this.name = name;
this.gender = gender;
this.school = school;
}
function Employee(name, gender, company) {
this.name = name;
this.gender = gender;
this.company = company;
}
var kd = new Student("길동", "male", "서울대");
var ks = new Employee("길순", "female", "삼성");
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
function Student(name, gender, school) {
Person.call(this, name, gender); // this는 student 인스턴스
this.school = school;
}
function Employee(name, gender, company) {
Person.apply(this, [name, gender]); // this는 employee 인스턴스
this.company = company;
}
var kd = new Student("길동", "male", "서울대");
var ks = new Employee("길순", "female", "삼성");
Student, Employee는 name과 gender 속성 모두 필요합니다.
그러니 Student와 Employee 인스턴스를 만들 때 마다 세 가지 속성을 모두 각 생성자 함수에 넣기 보다는
Person이라는 생성자 함수를 별도로 빼는게 구조화에 도움이 됩니다.
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0]; // 10
numbers.forEach(function (number) {
// 현재 돌아가는 숫자가 max값 보다 큰 경우
if (number > max) {
// max 값을 교체
max = number;
}
// 현재 돌아가는 숫자가 min값 보다 작은 경우
if (number < min) {
// 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)를 통하면 더 간편하게 해결 가능
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max min);
call, apply와는 다르게 즉시 호출하지는 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 메서드입니다.
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // window객체
// 함수에 this 미리 적용
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); // 4와 5를 미리 적용
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9
bind 메서드를 적용해서 새로 만든 함수는 name 프로퍼티에 bound(bind의 수동태. bind가 되었다) 라는 접두어가 붙습니다.
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를 사용하면 깔끔하게 처리 가능합니다.
vvar obj = {
outer: function () {
console.log(this); // obj
var innerFunc = function () {
console.log(this);
};
// call을 이용해서 즉시실행하면서 this를 넘겨줌
innerFunc.call(this); // obj
},
};
obj.outer();
var obj = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.log(this);
}.bind(this); // innerFunc에 this를 결합한 새로운 함수를 할당
innerFunc();
},
};
obj.outer();
콜백함수도 함수이기 때문에, 함수가 인자로 전달될 때는 함수 자체로 전달합니다.(this가 유실)
var obj = {
logThis: function () {
console.log(this);
},
logThisLater1: function () {
// 0.5초를 기다렸다가 출력. 정상동작하지 않음.
// 콜백함수도 함수이기 때문에 this를 bind해주지 않아서 유실
setTimeout(this.logThis, 500);
},
logThisLater2: function () {
// 1초를 기다렸다가 출력. 정상동작.
// 콜백함수에 this를 bind 해주었기 때문에 정상작동.
setTimeout(this.logThis.bind(this), 1000);
},
};
obj.logThisLater1();
obj.logThisLater2();
화살표 함수는 실행 컨텍스트 생성 시, this를 바인딩하는 과정이 제외합니다.
이 함수 내부에는 this의 할당과정(바인딩 과정)이 없으며, 접근하고자 하면 스코프체인상 가장 가까운 this에 접근하게 됩니다.
this우회, call, apply, bind보다 편리한 방법입니다.
var obj = {
outer: function () {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc(); // outer
},
};
obj.outer(); // outer