<aside> 💡 - 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
</aside>
함수 vs 메서드
함수와 메서드, 상당히 비슷해 보이지만 엄연한 차이가 존재한다. 기준은 독립성으로 함수는 그 자체로 독립적인 기능을 수행한다.
함수명();
그러나 메서드는 자신을 호출한 대상 객체에 대한 동작을 수행한다.
객체.메서드명();
this의 할당
// CASE1 : 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미해요.
var func = function (x) {
console.log(this, x);
};
func(1); // Window { ... } 1
// CASE2 : 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미해요.
// obj는 곧 { method: f }를 의미하죠?
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
위의 내용에서 보았듯, 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
독립적으로
호출할 때는 this는 항상 전역객체를 가리킨다
는 것을 주의하자.메서드의 내부함수에서의 this
예외는 없다. 메서드의 내부라고 해도, 함수로서 호출한다면 this는 전역 객체를 의미한다.
var obj1 = {
outer: function() {
console.log(this); // obj1
var innerFunc = function() {
console.log(this); // (2), (3)
}
innerFunc(); // 여기서 출력한 this (2) -> window
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod(); // 여기서 출력한 this (3) -> obj2
}
};
obj1.outer();
<aside>
💡 this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경 (메서드 내부인지, 함수 내부인지)는 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지
가 관건이라는 것을 알 수 있다.
</aside>
메서드의 내부 함수에서의 this 우회
this는 상당히 애매모호하고 받아들이기가 어려운 점이 많다. 그렇기에 this를 우회할 수 있는 방법을 찾아볼 수 있다.
변수를 활용하는 방법
내부 스코프에 이미 존재하는 this를 별도의 변수 (ex: self)
에 할당하는 방법.
var obj1 = {
outer: function() {
console.log(this); // (1) outer
// AS-IS
var innerFunc1 = function() {
console.log(this); // (2) 전역객체
}
innerFunc1();
// TO-BE
var self = this;
var innerFunc2 = function() {
console.log(self); // (3) outer
};
innerFunc2();
}
};
// 메서드 호출 부분
obj1.outer();
화살표 함수 (=this를 바인딩하지 않는 함수)
ES6에서 처음 도입된 화살표 함수는, 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 없다. 따라서 this는 이전의 값 - 상위 값 - 이 유지된다. / ES6에서는 함수 내부에서 this가 전역 객체를 바라보는 문제 때문에 화살표 함수를 도입했다.)
일반 함수와 화살표 함수의 가장 큰 차이점은 무엇일까?
→ 바로 this binding 여부가 가장 적절한 답변.
var obj = {
outer: function() {
console.log(this); // (1) obj
var innerFunc = () => {
console.log(this); // (2) obj
};
innerFunc();
}
}
obj.outer();
콜백 함수 호출 시 그 함수 내부에서의 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);
});
생성자 함수 내부에서의 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
call 메서드
호출 주체인 함수를 즉시 실행하는 명령어.
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
apply 메서드
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
call / apply 메서드 활용
this binding을 위해 call, apply 메서드를 활용하기도 하지만 더 유용한 측면이 있다.
유사 배열 객체 (array-like-object)에 배열 메서드를 적용
유사 배열의 조건
- 반드시 length가 필요해야한다. 이 조건은 필수이며 없으면 유사배열이라고 인식하지 않는다.
- index번호가 0번부터 시작해서 1씩 증가해야한다. 안그래도 되긴 하는데 예상치 못한 결과가 생긴다.
slice() 함수
slice() 함수는 배열로부터 특정 범위를 복사한 값들을 담고 있는 새로운 배열을 만드는데 사용한다.
첫번째 인자로 시작 인덱스, 두번째 인자로 종료 인덱스를 받으며, 시작 인덱스부터 종료 인덱스까지 값을 복사하여 반환한다.
//객체에는 배열 메서드를 직접 적용할 수 없어요.
//유사배열객체에는 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' ]
Array.from 메서드 (ES6)
사실 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);
생성자 내부에서 다른 생성사를 호출 (공통된 내용의 반복 제거)
Student
, Employee
모두 Person
이다. name
과 gender
속성 모두 필요하다. 그러니 Student
와 Employee
인스턴스를 만들 때 마다 세 가지 속성을 모두 각 생성자 함수에 넣기보다는 Person
이라는 생성자 함수를 별도로 빼는게 구조화에 도움이 더된다.
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', '삼성');
여러 인수를 묶어 하나의 배열로 전달할 때 apply
를 사용할 수 있다.
apply
를 통해 비효율적인 예시를 효율적인 예시로 바꿔보자.
//비효율
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];
numbers.forEach(function(number) {
// 현재 돌아가는 숫자가 max값 보다 큰 경우
if (number > max) {
// max 값을 교체
max = number;
}
// 현재 돌아가는 숫자가 min값 보다 작은 경우
if (number < min) {
// min 값을 교체
min = number;
}
});
console.log(max, min);
어떤가? 코드가 너무 길고 가독성이 떨어진다. 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);
// 펼치기 연산자(Spread Operation)를 통하면 더 간편하게 해결도 가능해요
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max min);
bind 메서드
call
과 비슷하다. 하지만 즉시, call
과는 다르게 즉시 호출하지는 않고 넘겨받은 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
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);
// func와 bindFunc의 name 프로퍼티의 차이를 살펴보세요!
console.log(func.name); // func
console.log(bindFunc.name); // bound func
상위 컨텍스트 this를 내부함수나 콜백 함수에 전달하기
내부 함수
(1). 메서드의 내부 함수에서 메서드의 this를 그대로 사용하기 위한 방법이다. (이전에는 내부함수에 this를 전달하기 위해 self를 썼었다.)
var self = this;
var innerFunc2 = function() {
console.log(self); // (3) outer
};
innerFunc2();
(2). self 등의 변수를 활용한 우회법보다 call, apply, bind를 사용하면 깔끔하게 처리 가능하기 때문에 이렇게 이용하는게 더 좋다.
var obj = {
outer: function() {
console.log(this); // obj
var innerFunc = function () {
console.log(this);
};
// call을 이용해서 즉시실행하면서 this를 넘겨주었습니다
innerFunc.call(this); // obj
}
};
obj.outer();
이번엔 call이 아니라 bind를 이용해보자.
var obj = {
outer: function() {
console.log(this);
var innerFunc = function () {
console.log(this);
}.bind(this); // innerFunc에 this를 결합한 새로운 함수를 할당
innerFunc();
}
};
obj.outer();
콜백 함수
화살표 함수의 예외사항