자바스크립트에서 this는 실행 컨텍스트가 생성될 때 함께 결정됨
실행 컨텍스트는 함수를 호출할 때 생성되므로, this는 함수를 호출할 때 결정됨
함수를 어떤 방식으로 호출하느냐에 따라 값이 달라짐
: 전역 객체 가리킴
왜? 전역 컨텍스트를 생성하는 주체가 전역 객체
전역 객체
windowconsole.log(this); // { alert: f(), atob: f(), blur: f(), btoa: f(), ... }
console.log(window); // { alert: f(), atob: f(), blur: f(), btoa: f(), ... }
console.log(this === window); // trueglobalconsole.log(this); // { process: {title: 'node', version: 'v01.13.0', ... } }
console.log(window); // { process: {title: 'node', version: 'v01.13.0', ... } }
console.log(this === global); // true전역공간에서 선언한 a, window.a, this.a
var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1
// 스코프체인에서 a검색 ->
// 가장 마지막에 도달하는 전역 스코프의 L.E === 전역객체의 프로퍼티에서 a 발견 ->
// 1반환
전역변수 선언이랑 window 프로퍼티에 직접 할당한 거랑 같은 거 아냐?
대부분의 경우엔 맞음
var a = 1;
window.b = 2;
console.log(a, window.a, this.a); // 1 1 1
console.log(b, window.b, this.b); // 2 2 2
window.a = 3;
b = 4;
console.log(a, window.a, this.a); // 3 3 3
console.log(b, window.b, this.b); // 4 4 4
전혀 다른 경우 : 삭제 명령
전역변수 선언 시 삭제 명령해도 삭제 안됨
-> 전역객체의 프로퍼티로 할당
-> 전역객체의 프로퍼티의 configurable 속성 (변경 및 삭제 가능성) false로 정의
(사용자의 의도치 않은 삭제 방지 차원)
var a = 1;
delete window.a; // false
console.log(a, window.a, this.a); // 1 1 1
var b = 1;
delete b; // false (window.b)
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
window.d = 4;
delete c; // true (window.c)
console.log(c, window.c, this.c); // Uncaught ReferenceError: c is not defined
함수 vs. 메서드
var func = function (x) { // func라는 변수에 익명함수 할당
console.log(this, x);
};
func(1); // 함수 호출 // Window { ... } 1 // 전역객체 Window 출력
var obj = { // obj라는 변수에 객체 할당
method: func // method 프로퍼티에 func 함수 할당
};
obj.method(2); // 메서드 호출 // { method: f } 2 // obj 출력
메서드로서 호출 - 점 표기법, 대괄호 표기법
함수 이름 앞에 객체가 명시돼 있는 경우 메서드로 호출, 아닌 경우 함수로 호출
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: functioin () { console.log(this); }
}
};
obj.methodA(); // { methodA: f, inner: {...} } ( === obj)
obj['methodA'](); // { methodA: f, inner: {...} } ( === obj)
obj.inner.methodB(); // { methodB: f } ( === obj.inner)
obj.ineer['method'](); // { methodB: f } ( === obj.inner)
obj['inner'].methodB(); // { methodB: f } ( === obj.inner)
obj['inner']['methodB'](); // { methodB: f } ( === obj.inner)
메서드의 내부함수에서의 this
.or[]) -> 호출 구문 앞 객체 바인딩var obj1 = { // (1) 객체 생성, 객체 내부에 outer라는 프로퍼티에 익명함수 연결
outer: function () { // (3) obj1.outer 함수의 실행 컨텍스트가 생성되면서 호이스팅하고, 스코프 체인 정보 수집하고, this 바인딩 => this에 obj1 바인딩
console.log(this); // (4) obj1 객체 정보 출력
var innerFunc = function () { // (5) 호이스팅된 변수 innerFunc는 outer 스코프 내에서만 접근 가능한 지역변수, 이 지역변수에 익명함수 할당
// (7) innerFunc 함수의 실행 컨텍스트가 생성되면서 호이스팅, 스코프 체인 수집, this 바인딩 => 함수로서 호출되어 this 지정 X => 자동으로 스코프체인상 최상위 객체인 전역객체 Window 바인딩
console.log(this); // (8) Window 객체 정보 출력 // (12) obj2 객체 정보 출력
}
innerFunc(); // 전역객체 Window // (6) innerFunc 함수로서 호출
var obj2 = { // (9) 호이스팅된 변수 obj2는 outer 스코프 내애서만 접근 가능한 지역변수, 이 지역변수에는 다시 객체 할당, 그 객체에는 innerMethod라는 프로퍼티 있으며, 변수 innerFunc와 연결된 익명함수 연결
// (11) innerMethod 함수의 실행 컨텍스트 생성 => 메서드로서 호출되어 객체 obj2 바인딩
innerMethod: innerFunc // (12)
};
obj2.innerMethod(); // obj2 // (10) obj2.innerMethod 메서드로서 호출
}
};
obj1.outer(); // obj1 // (2) obj1.outer 메서드로서 호출
메서드의 내부 함수에서의 this를 우회하는 방법
1 ) 상위 스코프 this 저장해서 내부 함수에서 활용
var obj = {
outer: function () {
console.log(this); // (1) { outer: f }
var innerFunc1 = function () {
console.log(this); // (2) Window { ... } 전역객체
};
innerFunc1();
var self = this; // outer 스코프에서 this 저장 (self 변수 사용 우회)
var innerFunc2 = function () {
console.log(self); // (3) { outer: f } // 객체 obj
};
innerFunc2();
}
};
obj.outer();
2) this를 바인딩하지 않는 함수 (화살표 함수)
var obj = {
outer: function () {
console.log(this); // (1) { outer: f }
var innerFunc = () => {
console.log(this); // (2) { outer: f }
};
innerFunc();
}
};
obj.outer();
3) call, apply 메서드 활용해 명시적으로 this 지정 (뒤에서 계속...)
제어권을 가지는 함수(메서드)가 콜백 함수에서의 this 결졍
정의하지 않은 경우 전역객체 참조
콜백 함수 : 함수 A의 제어권을 다른 함수 B(또는 메서드 B)에게 넘겨주는 경우 함수 A
함수 A는 함수 B의 내부 로직에 따라 실행됨
함수 A의 this도 함수 B의 내부 로직에서 정한 규칙 따라 결정됨
콜백 함수도 함수라 this가 전역객체 참조하지만,
제어권을 받은 함수에서 콜백 함수에 별도로 this 될 대상을 지정한 경우 그 대상 참조
setTimeout(function () { console.log(this); }, 300); // 0.3초 뒤 전역객체 출력
[1, 2, 3, 4, 5].forEach(function (x) {
console.log(this, x); // 콜백 함수의 첫번째 인자로 삼아 함수 실행 // 전역객체와 배열 각 요소 총 5회 출력
});
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
.addEventListener('click', function (e) { // addEventListner 메서드는 콜백 함수를 호출할 때 자신의 this 상속하도록 정의 // . 앞부분이 this
console.log(this.e); // 클릭 발생마다 그 이벤트 정보를 콜백 함수의 첫 번째 인자로 삼아 함수 실행 // 클릭 시 지정한 엘리먼트와 클릭 이벤트 정보 담긴 객체 출력
});
객체지향 언어에서는
__proto__ 라는 프로퍼티가 있는 객체(인스턴스)를 만들고var Cat = function (name, age) { // Cat이란 변수에 익명 함수 할당
this.bark = '야옹'; // this에 접근해서 bark, name, age 프로퍼티에 각각 값 대입
this.name = name;
this.age = age;
};
var choco = new Cat('초코', 7); // new 명령어와 함께 Cat 함수 호출하여 변수 choco, nabi에 각각 할당
// 생성자 함수 내부에서의 this는 choco 인스턴스를 가리킴
var nabi = new Cat('나비', 5);
// 셍상지 힘스 내부에의 this는 nabi 인스턴스를 가리킴
console.log(choco, nabi); // 각각 Cat 클래스의 인스턴스 객체 출력
/*
Cat { bark: '야옹', name: '초코', age: 7 }
Cat { bark: '야옹', name: '나비', age: 5 }
*/
규칙을 깨고 this에 별도의 대상을 바인딩하는 방법
: 메서드의 호출 주체인 함수를 즉시 실행 명령
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
첫 번째 인자: this로 바인딩, 나머지 인자들 : 호출할 함수의 매개변수
임의의 객체를 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
: 메서드의 호출 주체인 함수를 즉시 실행 명령 (call 메서드와 기능 동일)
Function.prototype.apply(thisArg[, argsArray])
첫 번째 인자: this로 바인딩, 두번째 인자 배열: 호출할 함수의 매개변수
임의의 객체를 this로 지정 가능
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 프로퍼티의 값이 () 또는 양의 정수인 객체
즉, 배열의 유사한 객체의 경우 (유사배열객체)
call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있음
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
Array.prototype.push.call(obj, 'd'); // 배열 메서드인 push를 객체 obj에 적용해 프로퍼티 3에 'd' 추가
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }
var arr = Array.prototype.slice.call(obj); // slice 메서드를 적용해 객체를 배열로 전환
console.log(arr); // { 'a', 'b', 'c', 'd' }
slice(시작 인덱스값, 마지막 인덱스값)
: 시작값부터 마지막값의 앞부분까지 배열 요소를 추출하는 배열 메서드
매개변수 아무것도 넘기지 않을 경우 원본 배열의 얕은 복사본 반환
함수 내부에서 접근 가능한 arguments 객체도 유사배열객체이므로 배열로 전환해서 활용 가능
function a () {
var argv = Array.prototype.slice.all(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 프로퍼티를 지니는 문자열도 모든 배열 메서드를 적용 가능
단, 문자열의 경우 length 프로퍼티가 읽기 전용이기 때문에
원본 문자열에 변경을 가하는 메서드 (push, pop, shift, unshift, splice 등)는 에러를 던지며
대상이 반드시 배열이어야 하는 경우 (concat)는 에러는 나지 않지만 제대로 된 결과 얻을 수 없음
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를 원하는 값으로 지정해서 호출한다'는 본래의 메서드 의도와 다름..
slice 메서드는 배열 형태로 복사 하기 위해 차용됐을 뿐 의도 파악하기 어려움
=> Array.from 메서드 : ES6 유사배열객체 또는 순회 가능한 모든 종류의 데이터 타입을 배열로 전환
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: e
};
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', '구골');
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.max, Math.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); // 45 3
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min); // 45 3
call/apply 메서드
: 명시적으로 별도의 this를 바인딩하면서 함수 또는 메서드를 실행하는 방법
this 예측하기 어려워 코드 해석 방해
ES5이하에서는 대안 없기에 광범위하게 활용
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
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
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를 그대로 바라보게 하기 위한 방법
callvar obj = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.log(this);
};
innerFunc.call(this);
}
};
obj.outer();
bindvar 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(); // Window { ... }
obj.logThisLater2(); // obj { logThis: f, ... }
ES6 새롭게 도입된 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩 과정 제외
화살표 함수에는 this가 아예 없다!
접근할 시 스코프체인상 가장 가까운 this에 접근
var obj = {
outer: function () {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
}
};
obj.outer();
콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this로 지정할 객체(thisArg)를 인자로 지정할 수 있는 경우 있음
thisArg 값 지정하면 콜백 함수 내부에서 this 값을 원하는 대로 변경할 수 있음
여러 내부 요소에 대해 같은 동작을 반속 수행해야 하는 배열 메서드에 많이 포진
ES6에서 새로 등장한 Set, Map 메서드에도 일부 존재
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
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(arrayLike[, callback[, thisArg]])
Set.prototype.forEach(callback[, thisArg])
Map.prototype.forEach(callback[, thisArg])