var a = 10;
var obj = {
a: 20,
b: this.a + 10,
c: function () {
return this.a + 20;
},
};
console.log(obj.b); // ?
console.log(obj.c()); // ?
var btn1 = document.querySelector("#btn1");
btn1.addEventListener("click", function () {
console.log(this); // ?
});
var btn2 = document.querySelector("#btn2");
btn2.addEventListener("click", () => {
console.log(this); // ?
});
const curry = (fn) => (fn2) => (a) => (b) => fn(a, b) + fn2(a, b);
const add = (a, b) => a + b;
const mul = (a, b) => a * b;
const addMul = curry(add)(mul);
console.log(addMul(3)(2)); // 11
자기 참조 변수
다.var foo = function () {
console.dir(this);
};
// 1. 함수 호출
foo(); // window
// window.foo();
// 2. 메소드 호출
var obj = { foo: foo };
obj.foo(); // obj
// 3. 생성자 함수 호출
var instance = new foo(); // instance
// 4. apply/call/bind 호출
var bar = { name: "bar" };
foo.call(bar); // bar
foo.apply(bar); // bar
foo.bind(bar)(); // bar
기본적으로 this에는 전역 객체(global object, window(browser) or global(node.js))가 바인딩 된다.
내부함수는 일반함수, 메소드, 콜백함수 어디에서 선언되었든 관계 없이 this는 전역객체를 바인딩한다.
자바스크립트 개발자 중 하나인 더글라스 크락포드는 “이것은 설계 단계의 결함으로 메소드가 내부함수를 사용하여 자신의 작업을 돕게 할 수 없다는 것을 의미한다”라고 말했다.
다만 내부함수의 this가 전역객체를 참조하는 것의 회피방법도 존재한다.
새로운 변수에 현재 실행 문맥의 this를 담아 실행
var value = 1;
var obj = {
value: 100,
foo: function () {
var that = this; // Workaround : this === obj
console.log("foo's this: ", this); // obj
console.log("foo's this.value: ", this.value); // 100
function bar() {
console.log("bar's this: ", this); // window
console.log("bar's this.value: ", this.value); // 1
console.log("bar's that: ", that); // obj
console.log("bar's that.value: ", that.value); // 100
}
bar();
},
};
obj.foo();
apply, call, bind 메소드
var value = 1;
var obj = {
value: 100,
foo: function () {
console.log("foo's this: ", this); // obj
console.log("foo's this.value: ", this.value); // 100
function bar(a, b) {
console.log("bar's this: ", this); // obj
console.log("bar's this.value: ", this.value); // 100
console.log("bar's arguments: ", arguments);
}
bar.apply(obj, [1, 2]);
bar.call(obj, 1, 2);
bar.bind(obj)(1, 2);
},
};
obj.foo();
화살표 함수
var value = 1;
const obj = {
value: 100,
foo() {
setTimeout(() => console.log(this.value), 100); // 100
},
};
obj.foo();
메소드 내부의 this에는 메소드를 호출한 객체, 즉 메소드를 호출할 때 메소드 이름 앞의 마침표 연산자 앞에 기술한 객체가 바인딩된다.
var obj1 = {
name: "Lee",
sayName: function () {
console.log(this.name);
},
};
var obj2 = {
name: "Kim",
};
obj2.sayName = obj1.sayName;
obj1.sayName(); // Lee
obj2.sayName(); // Kim
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
var me = new Person("Lee");
console.log(me.getName());
Person.prototype.name = "Kim";
console.log(Person.prototype.getName());
생성자 함수 내부의 this에는 생성자 함수가 생성할 인스턴스가 바인딩된다.
하지만 생성자 함수 역시 일반 함수로 호출 될 수 있으므로(new 키워드 없이 호출 되었을 경우) 이 경우에도 전역 객체가 this 바인딩 된다.
function Person(name) {
// new없이 호출하는 경우, 전역객체에 name 프로퍼티를 추가
this.name = name;
}
// 일반 함수로서 호출되었기 때문에 객체를 암묵적으로 생성하여 반환하지 않는다.
// 일반 함수의 this는 전역객체를 가리킨다.
var me = Person("Lee");
console.log(me); // undefined
console.log(window.name); // Lee
// Scope-Safe Constructor Pattern
function A(arg) {
// 생성자 함수가 new 연산자와 함께 호출되면
// 함수의 선두에서 빈객체를 생성하고 this에 바인딩한다.
/*
this가 호출된 함수(arguments.callee, 본 예제의 경우 A)의 인스턴스가 아니면
new 연산자를 사용하지 않은 것이므로
이 경우 new와 함께 생성자 함수를 호출하여 인스턴스를 반환한다.
arguments.callee는 호출된 함수(현재 실행중인 함수)의 이름을 나타낸다.
이 예제의 경우 A로 표기하여도 문제없이 동작하지만
특정함수의 이름과 의존성을 없애기 위해서 arguments.callee를 사용하는 것이 좋다.
*/
if (!(this instanceof arguments.callee)) {
return new arguments.callee(arg);
}
// 프로퍼티 생성과 값의 할당
this.value = arg ? arg : 0;
}
var a = new A(100);
var b = A(10);
console.log(a.value);
console.log(b.value);
apply() 메소드
Function.prototype.apply(thisArg, [argsArray]);
// thisArg: 함수 내부의 this에 바인딩할 객체
// argsArray: 함수에 전달할 argument의 배열
// -----
var Person = function (name) {
this.name = name;
};
var foo = {};
/* apply 메소드는 생성자함수 Person을 호출한다. 이때 this에 객체 foo를 바인딩한다.
Person 함수는 this의 name 프로퍼티에 매개변수 name에 할당된 인수(argsArray)를 할당하는데
this에 바인딩된 foo 객체에는 name프로퍼티가 없으므로 동적 추가되고 값이 할당된다.
*/
Person.apply(foo, ["name"]);
console.log(foo); // { name: 'name' }
// apply 메소드의 대표적인 용도
function convertArgsToArray() {
console.log(arguments);
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// arguments 객체를 배열로 변환
// slice: 배열의 특정 부분에 대한 복사본을 생성한다.
var arr = Array.prototype.slice.apply(arguments); // arguments.slice
// var arr = [].slice.apply(arguments);
console.log(arr);
// [1, 2, 3]
return arr;
}
convertArgsToArray(1, 2, 3);
call() 메소드 : apply와 기능은 같지만 apply의 두번째 인자인 argsArray가 아닌 각각의 인자로 바뀌었다.
Person.apply(foo, [1, 2, 3]);
Person.call(foo, 1, 2, 3);
// call메소드의 사용법
function Person(name) {
this.name = name;
}
Person.prototype.doSomething = function (callback) {
if (typeof callback == "function") {
// callback()
callback.call(this);
}
};
function foo() {
console.log(this.name);
}
var p = new Person("Lee");
p.doSomething(foo); // 'Lee', prototype에서 그대로 callback을 호출한다면 undefined
bind() 메소드: bind함수에 인자로 전달한 this가 바인딩된 새로운 함수를 리턴한다. 함수를 실행하지않고 반환하기 때문에 명시적으로 호출할 필요가 있다.
function Person(name) {
this.name = name;
}
Person.prototype.doSomething = function (callback) {
if (typeof callback == "function") {
// callback.call(this);
// this가 바인딩된 새로운 함수를 호출
callback.bind(this)();
}
};
function foo() {
console.log("#", this.name);
}
var p = new Person("Lee");
p.doSomething(foo); // 'Lee'
<button id="btn">Click</button>
<script>
var btn = document.getElementById("btn");
btn.onclick = function () {
console.log(this); // <button id="btn">Click</button>
};
</script>
클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. - MDN
function outer() {
let x = 1;
function inner() {
x++;
}
return inner;
}
const innerFunc = outer();
innerFunc(); // 1
innerFunc(); // 2
function add(a, b) {
return a + b;
}
function add(a) {
return function (b) {
return a + b;
};
}
add(1)(2); // 3
function multiply(x) {
return function (y) {
return function (z) {
return x * y * z;
};
};
}
let multiply3 = multiply(3); // 3이 고정됨
let multiply3And5 = multiply3(5); // 3과 5가 고정됨
let multiply3And2 = multiply3(2); // 3과 2가 고정됨
console.log(multiply3And5(7));
console.log(multiply3And5(8));
console.log(multiply3And2(1));