코어 자바스크립트를 읽고 스터디 세션에서 공유를 위해 정리한 글입니다.
- 다른 대부분의 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미하고 클래스에서만 사용 가능하다. 그러나
JS는 어디서든 사용할 수 있다.
- 함수와 객체(메서드)의 구분이 느슨한 JS에서 this는 실질적으로 이 둘을 구분하는 거의 유일한 기능이다.
- this에는 호출한 주체에 대한 정보가 담긴다.
var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // this === window, 1
var a = 1;
window.b = 2;
console.log(a, this.a, window.a) // 1 1 1
console.log(b, this.b, window.b) // 2 2 2
var a = 1;
delete window.a;
console.log(a); // 1
var b = 2;
delete b;
console.log(b) // 2
window.c = 3;
delete c;
console.log(c) // Uncaught RefereceError: c is not defiend
window.d = 3;
delete window.d;
console.log(d) // Uncaught RefereceError: d is not defiend
전역변수를 선언하면 자동으로 전역객체의 프로퍼티로 할당하면서 추가적으로 해당 프로퍼티의 configurable 속성(변경 및 삭제 가능성)을 false로 정의한다.
// 위에서 설명하는 전역변수 할당 시 JS엔진의 동작을 코드로 나타내 보면...
// var a = 1;
Object.defineProperty(window, "a", { value: "1", configurable: false });
delete window.a;
console.log(a); // 1
💡 추가: 위 동작에 대한 나의 생각
MDN의 delete 연산자의 정의에 따르면…delete 연산자는 객체의 프로퍼티를 제거하기 위한 연산자이다
. 엄밀히 말하면 전역변수는 전역객체의 프로퍼티가 맞지만, JS의 동작을 위해 존재하는 전역객체를 사용자로 부터 은닉하고 전역변수로 기대되는 동작을 보장하기 위해 변수 자체로 추상화 하는 것이라고 생각하면 delete 연산자로 삭제할 수 없음을 이해할 수 있을 것이다.
Delete
Thedelete
operator removes a property from an object. If the property's value is an object and there are no more references to the object, the object held by that property is eventually released automatically.
delete - JavaScript | MDN
함수: 독립적으로 기능 수행
메서드: 자신을 호출한 대상 객체에 관한 동작 수행
→ JS는 상황별로 this 키워드에 다른 값을 부여함으로써 이를 구현
흔히 메서드를 ‘객체의 프로퍼티에 할당된 함수’로 이해한다. 반은 맞고 반은 틀리다. 어떤 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로 무조건 메서드가 되는 것이 아니라
객체의 메소드로서 호출할 경우에만 메서드로 동작한다.
var func = function(x){
console.log(this, x);
}
func(1) // Window { ... } 1
// 함술로서 호출
var obj = {
method: func
}
obj.method(2) // { method: f } 2
obj['method']
// 메서드로서 호출
// (점 표기법이든 대괄호 표기법이든, 어떤 함수를 호출할 때
// 그 함수 이름 앞에 객체가 명시되어 있는 경우에는 메서드로서 호출.)
var obj = {
methodA: function() {
console.log(this);
},
inner: {
methodB: function() {
console.log(this);
}
}
}
obj.methodA();
obj['methodA'](); // { methodA: f, inner: {...} } (=== obj)
obj.inner.methodB();
obj['inner'].methodB(); // { methodB: f } (=== obj.inner)
→ 우리는 이미 함수로써 호출한 함수의 this가 무엇을 가리키는지 알고 있다.
내부 함수 역시 함수로서 호출했는지 메서드로서 호출했는지 파악하면 this의 값을 맞출 수 있다.
아래 코드의 결과를 예상해보자.
var obj = {
outer: function () {
console.log(this); // (1)
var innerFunc = function () {
console.log(this); // (2) (3)
}
innerFunc(); // **함수** | 메서드 호출!
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod(); // 함수 | **메서드** 호출!
}
};
obj.outer(); // 함수 | **메서드** 호출!
this 바인딩에 관해서는 함수를 실행하는 당시의 주변환경(메서드 내외부인지 등)은 중요하지 않고, 오직 해당 함수가 메서드로써, 함수로써 호출 되는지 여부(== 함수 호출 구문 앞 저 혹은 대괄호 표기가 있는지)가 관건이다.
var obj = {
outer: function () {
console.log(this); // (1) { outer: f } === obj
var innerFunc1 = function () {
console.log(this); // (2) Window { ... }
}
innerFunc1();
var self = this;
var innerFunc2 = function () {
console.log(self); // (3) { outer: f }
};
innerFunc2();
}
};
obj.outer();
화살표 함수 도입
var obj = {
outer: function () {
console.log(this); // (1) { outer: f }
var innerFunc = () => {
console.log(this); // (2) { outer: f }
}
innerFunc();
}
}
obj.outer();
콜백 함수의 this는 콜백함수를 제어하는 함수인 **함수 B**의 내부 구현에 따라 값이 결정된다.
setTimeout(function () {
console.log(this); // Window { ... }
}, 300);
[1, 2, 3, 4, 5].forEach(function (ele) {
console.log(this, ele); // Window { ... }
})
document.body.innerHTML += "<button id='btn'>클릭</button>";
**document.body.querySelector('#btn')**.addEventListener('click', function (e) {
console.log(this, e); // 버튼 엘리먼트 객체
})
결과
addEventListener는 내부적으로 콜백함수의 this를 지정하도록 정의되어있다.
var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
}
var choco = new Cat('초코', 5);
var nabi = new Cat('나비', 5);
console.log(choco, nabi);
// Cat { bark: '야옹', name: '초코', age: 5 },
// Cat { bark: '야옹', name: '나비', age: 5 }
call, apply, bind
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: 111 }, 4, 5); // 111 4 5
Function.prototype.apply(**thisArg**[, argsArray])
obj.method.apply({ a: 111 }, [222, 333]); // 111 222 333
var obb = {
0: "1",
1: "2",
2: "3",
length: 3,
};
Array.prototype.push.call(obb, "33");
console.log(obb); // { 0: '1', 1: '2', 2: '3', 3: '33', length: 4 }
var arr = Array.prototype.slice.call(obb);
console.log(obb); // [ 'a', 'b', 'c', 'd']
function a() {
var argv = Array.prototype.slice.call(**arguments**);
// 유사배열객체인 arguments를 배열로 전환해 forEach 메서드 사용.
argv.forEach((ele) => {
console.log(ele);
});
}
a(1, 2, 3, 4)
The arguments object - JavaScript | MDN
Array.From
slice를 배열을 복사하기 위한 방법으로 사용하는 것은 경험을 통해 숨은 뜻을 알고 있는 사람이 아닌한 의도파악이 어렵다.
→ ES6에서는 유사 배열객체 또는 순회 가능한 종류의 데이터 타입을 배열로 전환하는 Array.from 메서드 도입
var arr = Array.from(obj);
console.log(arr); // => Array
생성자 내부에서 다른 생성자를 호출
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
function Student(name, gender, school) {
Person.call(this, name, gender);
// Person의 this를 Student의 인스턴스를 바꿔 상속 구현
this.school = school;
}
var jin = new Student("otter", 13, "river");
console.log(jin); // { name: 'otter', gender: 13, school: 'river' }
여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 - apply 활용
var numbers = [1, 2, 3, 4, 5];
var max = Math.max.apply(null, numbers);
console.log(max) // 5
여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 - ES6 spread operator 사용
var numbers = [1, 2, 3, 4, 5];
var max = Math.max(...numbers);
console.log(max) // 5
ES5에서 추가된 기능으로 call과 비슷하지만 즉시 호출하는 것이 아니라 새로운 함수를 반환한다.
var sum = function (a, b, c, d) {
console.log(this, a + b + c + d);
};
sum(1, 2, 3, 4); // Window{ ... } 10
var sumWithTen = sum.bind({ten:10}, 10);
sumWithTen(1, 2, 3); // [ { ten: 10 }, 16 ] at this index.js:26:3
name 프로퍼티
console.log(sum.name, sumWithTen.name); // [ 'sum', 'bound sum' ]
상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달하기
var obj = {
outer: function() {
var innerFunc = function() {
console.log(this) // 기본은 전역객체, call을 이용해 실행해 obj를 가리킨다.
}
innerFunc.call(this);
}
}
obj.outer();
var obj = {
logThis: function () {
console.log(this);
},
logThisLater: function () {
setTimeout(this.logThis.bind(this), 300);
},
};
화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩 하는 과정이 제외되었다. 함수 내부에는 this가 아예 없으며, 접근하고자 하면 스코프체인상 가장 가까운 this에 접근
(+ this에는 argument property, prototype에 대한 정보가 없다.)
var obj = {
outer: function () {
var innerFunc = () => {
console.log(this);
};
var innerFunc1 = function () {
console.log(this);
};
innerFunc(); // obj { outer }
innerFunc1(); // Window { ... }
},
};
obj.outer();
Array.prototype.forEach(callback[, thisArg]);