《 실행 컨텍스트 》에 이어 작성하는 글입니다.
실행 컨텍스트 복습!
전역 공간에서의 this
console.log(this);
console.log(window);
console.log(this === window);
node 환경에서의 this 확인
globle이라는 객체가 나온다.
➡️ node 환경에서의 this라는 전역 변수는 globle 객체를 의미한다는 것을 알 수 있다.
console.log(this);
console.log(global);
console.log(this === global);
함수 vs 메서드
함수명();
객체.메서드명();
➡️ 즉, 함수는 호출의 주체를 명시할 수 없기 때문에 this가 전역 객체이지만, 메서드는 this가 호출의 주체가 된다!!
this의 할당
var func = function (x) {
console.log(this, x);
};
func(1); // Window { ... } 1
// 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 obj1 = {
outer: function() {
console.log(this); // (1)
var innerFunc = function() {
console.log(this); // (2), (3)
}
innerFunc();
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod();
}
};
obj1.outer();
➡️ 실행 결과 : (1) : obj1, (2) : 전역객체, (3) : obj2
메서드의 내부 함수에서의 this 우회
var obj1 = {
outer: function() {
console.log(this); // (1) outer
// AS-IS (this 유실)
var innerFunc1 = function() {
console.log(this); // (2) 전역객체
}
innerFunc1();
// TO-BE (this 보존)
var self = this; // this 저장
var innerFunc2 = function() {
console.log(self); // (3) outer
};
innerFunc2();
}
};
// 메서드 호출 부분
obj1.outer();
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명령어를 사용하여, 첫 번째 매개변수에 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);
}
};
// method 함수 안의 this는 항상 obj를 가리킨다.
obj.method(2, 3); // 1 2 3
// 명시적 binding
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
유사 배열 객체(array-like-object)에 배열 메서드를 적용
중요한 개념이므로 따로 포스팅하였다.
자세한 내용은《 유사 배열 객체 》을 참고하자.
Array.from 메서드(ES6)
// 유사배열
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', '삼성');
여러 인수를 묶어 하나의 배열로 전달할 때 apply 사용할 수 있다.
apply를 통해 비효율적인 예시를 효율적인 예시로 바꿔보자
➡️ (apply ->({},[])
첫 번째 인자로 this로 엮을 객체, 두 번째 인자로 배열을 받는 것을 활용)
//비효율
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);
- this를 바인딩 하는 메서드
- 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
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를 내부함수나 콜백 함수에 전달하기
var 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();
《 콜백 함수 》
var obj = {
logThis: function () {
console.log(this);
},
logThisLater1: function () {
// 0.5초를 기다렸다가 출력 (정상동작 X)
// 콜백함수도 함수이기 때문에 this를 bind해주지 않아서 잃어버림 (유실)
setTimeout(this.logThis, 500);
},
logThisLater2: function () {
// 1초를 기다렸다가 출력 (정상동작)
// 콜백함수에 this를 bind 해주었기 때문
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();
- 출력의 결과를 제출해주세요, 그리고 그 이유를 최대한 상세히 설명해주세요
- 주의사항 : 브라우저에서 테스트해주세요(Chrome → 개발자 도구 → 콘솔에 입력하여 실행)
var fullname = "Ciryl Gane";
var fighter = {
fullname: "John Jones",
opponent: {
fullname: "Francis Ngannou",
getFullname: function () {
return this.fullname;
},
},
getName: function () {
return this.fullname;
},
getFirstName: () => {
return this.fullname.split(" ")[0];
},
getLastName: (function () {
return this.fullname.split(" ")[1];
})(),
};
console.log("Not", fighter.opponent.getFullname(), "VS", fighter.getName());
console.log(
"It is",
fighter.getName(),
"VS",
fighter.getFirstName(),
fighter.getLastName
);
위 코드를 브라우저 상에서 출력하면 아래와 같은 결과를 얻을 수 있다.
Not Francis Ngannou VS John Jones
VM62:27 It is John Jones VS Ciryl Gane
이 결과는 JavaScript의 this 바인딩 방식과 화살표 함수의 특성 때문이다.
fighter.opponent.getFullname
fighter.getName
fighter.getFirstName
⭐fighter.getLastName
var fullname = "Ciryl Gane";
var fighter = {
fullname: "John Jones",
opponent: {
fullname: "Francis Ngannou",
getFullname: function () {
// 1. 객체 this 바인딩 : 프란시스 은가누
return this.fullname;
},
},
getName: function () {
// 2. 객체 this 바인딩 : 존 존스
return this.fullname;
},
getFirstName: () => {
// 3. 함수 this 바인딩 : 시릴
return this.fullname.split(" ")[0];
},
getLastName: (function () {
// 3. 함수 this 바인딩 : 시릴
return this.fullname.split(" ")[1];
})(),
};
console.log("Not", fighter.opponent.getFullname(), "VS", fighter.getName());
console.log(
"It is",
fighter.getName(),
"VS",
fighter.getFirstName(),
fighter.getLastName
);