this는 실행 컨텍스트가 생성될 때 함께 결정되는데
실행 컨텍스트는 함수를 호출할 때 생성되므로
this는 함수를 호출할 때 결정된다.
this : 전역 객체
(전역 컨텍스트를 생성하는 주체가 바로 전역 객체이기 때문)
전역 객체
1) 브라우저 환경 : window
console.log(this === window); // true
2) Node.js 환경
console.log(this === global); // true
전역변수를 선언하면 자바스크립트 엔진은
이를 전역객체의 프로퍼티로도 할당한다.
변수이면서 객체의 프로퍼티이다.
var a; // 변수
window.a // 객체의 프로퍼티
자바스크립트의 모든 변수는 실은 특정 객체(LexicalEnvironment)의 프로퍼티로서 동작한다.
실행 컨텍스트는 변수를 수집해서
LexicalEnvironment의 프로퍼티로 저장 후
어떤 변수를 호출하면 LexicalEnvironment를 조회해서 일치하는 프로퍼티가 있을 경우 그 값을 반환한다.
전역 컨텍스트의 경우 LexicalEnvironment는
전역객체를 그대로 참조한다.
var a = 1; // 전역변수
console.log(a); // 1 : 변수, a === window.a
console.log(window.a); // 1 : 전역객체의 프로퍼티
console.log(this.a); // 1 : 전역객체의 프로퍼티
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
전역변수로 선언한 경우 : 삭제 x
처음부터 전역객체의 프로퍼티로 할당한 경우 : 삭제
// 1. 전역변수로 선언한 경우
var a = 1;
delete window.a; // false
console.log(a, window.a, this.a) // 1 1 1
// 2. 처음부터 전역객체의 프로퍼티로 할당한 경우
window.b = 2
delete window.b; // true
delete d; // true
console.log(b, window.b, this.b); // Uncaught ReferenceError: b is not defined
전역변수와 전역객체의 프로퍼티 차이
1) 호이스팅 여부
2) configurable 여부
함수를 실행하는 방법
1) 함수로서 호출하는 경우
a();
2) 메서드로서 호출하는 경우
obj.a();
차이점 : 독립성
1) 함수 : 그 자체로 독립적인 수행
2) 메서드 : 자신을 호출한 대상 객체와 관한 동작 수행
어떤 함수를 객체의 프로퍼티에 할당한다고 해서
그 자체로서 무조건 메서드가 되는 것이 아니라
객체의 메서드로서 호출할 경우에만 메서드로 동작하고,
그렇지 않으면 함수로 동작한다.
구분 : 함수 앞에 점(.)이나 대괄호([]) 표기 여부
var func = function (x) {
console.log(this, x);
};
func(1); // Window { ...} 1
var obj = {
method: func
};
obj.method(2); // { method: f } 2
obj['method'](2); // { method: f } 2
어떤 함수를 메서드로 호출하는 경우
호출 주제는 바로 함수명(프로퍼티명) 앞의 객체이다.
var obj = {
methodA: function () { console.log(this); },
inner: {
methodB: function () { console.log(this); }
}
};
obj.methodA(); // { methodA: f, inner: {...} } === obj
obj.inner.methodB(); // { methodB: f } === obj.inner
this : 전역 객체
var obj1 = {
outer: function() {
console.log(this); // obj1
var innerFunc = function () {
console.log(this); // 전역객체(Window)
}
innerFunc();
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod(); // obj2
}
};
obj1.outer();
변수를 검색하면 우선 가장 가까운 스코프의 LexicalEnvironment를 찾고 없으면
상위 스코프를 탐색하듯이,
this 역시 현재 컨텍스트에 바인딩된 대상이 없으면
직전 컨텍스트의 this를 바라보도록 하는게 이상적이다.
ES5 : 변수를 활용하는 방법
var obj = {
outer: function() {
console.log(this); // { outer: f }
var innerFunc1 = function () {
console.log(this); // Window { ... }
};
innerFunc1();
var self = this; // 상위 스코프의 this를 저장해서 내부함수에서 활용
var innerFunc2 = function () {
console.log(self); // { outer: f }
};
innerFunc2();
}
};
obj.outer();
ES6 : 화살표 함수
실행 컨텍스트를 생성할 때
this 바인딩 과정 자체가 빠지게 되어,
상위 스코프의 this를 그대로 활용할 수 있다.
var obj = {
outer: function() {
console.log(this); // { outer: f }
var innerFunc = () => {
console.log(this); // { outer: f }
}
innerFunc();
}
};
obj.outer();
함수 A의 제어권을 다른 함수(또는 메서드)
B에게 넘겨주는 경우
함수 A를 콜백 함수라 하며
B의 내부로직에 따라 실행된다.
1) 기본적으로 콜백함수도 함수이기 때문에
this : 전역 객체
2) 제어권을 받은 함수에 따라 this가 달라짐
// 1. this : 전역객체
setTimeout(function () { console.log(this); }, 300);
// 2. this : 전역객체
[1, 2, 3, 4, 5].forEach(function (x) {
console.log(this, x);
});
// 3. this : <button id="a">클릭</button>
// Mouse Event { isTrusted: true, ... }
// addEventListener 메서드는 자신의 this를 상속하도록 되어있다.
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
.addEventListener('click', function (e) {
console.log(this, e);
});
어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수
1) 클래스 :구체적인 인스턴스를 만들기 위한 일종의 틀)
2) 인스턴스 : 클래스를 통해 만든 객체
new 명령어와 함께 함수를 호출하면
해당 함수가 생성자로 동작하게 되며
어떤 함수가 생성자 함수로서 호출된 경우
내부에서의 this는
곧 새로 만들 구체적인 인스턴스 자신이 된다.
new 명령어와 함께 함수를 호출하면
1) 생성자의 prototype 프로퍼티를 참조하는
__proto__
라는 프로퍼티가 있는
객체(인스턴스)를 만든다.
2) 미리 준비된 공통 속성 및 개성을
해당 객체(this)에 부여
3) 구체적인 인스턴스가 만들어진다.
var Dog = function (name) {
this.name = name;
}
var cappy = new Dog('Cappy'); // this : cappy 인스턴스
console.log(cappy) // Dog { name: 'Cappy' }
this에 어떤 값이 바인딩 되는지를 바꾸는 방법
call(this, 인자들)
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
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(this, 인자들의 배열)
Function.prototype.apply(thisArg[, argsArray])
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
1) 유사배열객체에 배열 메서드를 적용
객체에는 배열 메서드를 직접 적용할 수 없으나,
키가 0 또는 양의 정수인 프로퍼티가 존재하고,
legnth 프로퍼티 값이 0 또는 양의 정수인 객체,
즉, 배열의 구조와 유사한 객체의 경우(유사배열객체)
call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있다.
var obj = {
0: 'a',
1: 'b',
2: 'C',
length: 3
};
// 배열메서드인 push를 객체 obj에 적용
Array.prototype.push.call(obj, 'd');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4}
// slice 메서드를 통해 객체를 배열로 전환
var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]
2) arguments, NodeList에 배열 메서드를 적용
함수 내부에서 접근할 수 있는 arguments 객체도 유사배열객체므로
위의 방법으로 배열로 전환해서 활용할 수 있다.
function a () {
var argv = Array.prototype.slice.call(arguments);
argv.forEach(function (arg) {
console.log(arg);
});
};
a(1, 2, 3); // 1 2 3
Node 선택자(querySelectorAll, getElementsByClassName 등)로
선택한 결과인 NodeList도 마찬가지이다.
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); // <div>a</div><div>b</div><div>c</div>
});
3) 문자열에 배열 메서드 적용
유사배열객체에는 call/apply 메서드를 이용해 모든 배열 메서드를 적용할 수 있다.
배열처럼 인덱스와 length 프로퍼티를 지니는 문자열에도 가능하지만
문자열의 경우 length 프로퍼티가 읽기 전용이기 때문에
원본 문자열 변경이 가능한 메서드(push, pop, shift, unshift, splice 등)은
에러를 던지며,
concat처럼 대상이 반드시 배열이어야 하는 경우에는
에러는 나지 않지만 제대로된 결과를 얻을 수 없다.
var str = 'abc def';
// 1. push/call
Array.prototype.push.call(str, ',pushed string');
// Uncaught TypeError: Cannot assign to read only property 'length' of object '[object String]
// 2. concat/call
Array.prototype.concat.call(str, 'string');
// [String {"abc def"}, "string"]
// 3. every/call
Array.prototype.every.call(str, function(char) {
return char !== ' ';
}); // false
// 4. some/call
Array.prototype.some.call(str, function(char) {
return char === ' ';
}); // true
// 5. map/call
var newArr = Array.prototype.map.call(str, function(char) {
return char + '!';
});
console.log(newArr); // ["a!", "b!", "c!", " !", "d!", "e!", "f!"]
// 6. reduce/apply
var newArr = Array.prototype.reduce.apply(str, [
function(string, char, i) {
return string + char + i; }, ''
]);
console.log(newArr); // "a0b1c2 3d4e5f"
4) ES6의 Array.from 메서드
var obj = {
0: 'a',
1: 'b',
2: 'C',
length: 3
};
var arr = Array.from(obj);
console.log(arr); // [ 'a', 'b', 'c']
생성자 내부에 다른 생성자와 공통된 내용이 있을 경우
call 또는 apply를 다른 생성자를 호출하면 간단한게 반복을 줄일 수 있다.
Student, Employee 생성자 함수 내부에서
Person 생성자 함수를 호출해서
인스턴스의 속성을 정의하도록 구현
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 = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min); // 45 3
ES6 Spread 연산자 이용
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min); // 45 3
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])