[JavaScript] This, Closure
this, closure
this
this란?
자바스크립트에서의 this는 함수 호출 방식에 따라서 결정됨
함수의 호출방식
- 메소드로서의 호출
메소드로 호출 시(ex: obj1.addNums) 자신을 호출한 대상이 this
- 함수로서의 호출
함수를 독립적으로 호출 시 호출주체를 알 수 없으므로 this 지정 x -> 때문에 this는 전역 객체를 가리킴
또한 메소드의 내부라고 해도 함수로서 호출하면 this는 전역객체를 의미함
- 콜백함수로 호출될 때 그 함수 내부에서의 this
콜백함수도 함수이기 때문에 this는 전역객체를 참조하지만, 콜백함수를 넘겨받은 함수에서 별도로 this를 지정한 경우 예외적으로 그 대상을 참조함
- 생성자 함수 호출
this는 인스턴스를 가리킴
- apply, call, bind 호출
apply, call, bind는 원하는 this를 바인딩할 수 있는 메소드임
- apply
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
- call
var func = function(a,b,c) {
console.log(this, a,b,c,)
}
func(1,2,3) // window{…} 1 2 3
func.call({x: 1}, 2,3,4) // {x:1} 2 3 4
- bind
call이나 apply랑 비슷해보이지만 call과 다르게 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 리턴하는 메소드
var func = function (a,b,c,d) {
console.log(this, a,b,c,d)
}
func(1,2,3,4) // this는 window 객체 가리킴
var bindFunc = func.bind({x:1})
bindFunc(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
Closure
Closure란?
외부함수의 생명주기가 이미 종료되었음에도 내부함수가 외부 함수의 컨텍스트에 접근할 수 있는 것을 말함 → 이미 종료된 외부함수의 변수 등을 여전의 참조(외부함수의 렉시컬 환경을 여전히 참조)할 수 있음
어떻게 접근할 수 있냐면 가비지 컬렉터가 보고 외부함수의 렉시컬환경을 아직 참조하는 곳이 있어 냅두기 때문임
예시
const x = 1;
function outer() {
const x = 10;
const inner = function () {
console.log(x); // 10
};
return inner;
}
const innerFunc = outer();
innerFunc();
const innerFunc = outer() <- 이 부분에서 outer 함수를 호출하고 그 결과인 inner 함수를 innerFunc에 할당함
outer 함수는 inner 함수를 반환한 후 콜스택에서 사라짐. 하지만 outer 함수의 렉시컬 환경은 inner 함수가 여전히 참조하고 있으므로 사라지지 않음
innerFunc() <- inner 함수를 호출하면 이 함수는 자신이 선언됐던 렉시컬 환경인 outer 함수의 환경을 참조하여, outer 함수 내부에 선언된 x 변수를 사용할 수 있음. 이렇게 함수가 자신이 선언된 환경을 기억하는 특성이 바로 클로저임
inner 함수는 console.log(x)를 실행한 후 콜스택에서 사라짐
따라서 함수가 생성될 때 렉시컬 환경을 기억하고, 이후에도 그 환경에 접근할 수 있는 이런 특성이 클로저임. 이를 통해 함수 외부에서 접근할 수 없는 private한 변수를 만들 수 있음
그러면 클로저는 왜 사용하는 걸까?
- 상태유지
현재 상태를 기억하고 변경된 최신 상태를 유지하는 것
예시
function makeCounter() {
let count = 0;
return function() {
return count++;
}
}
let counter1 = makeCounter();
console.log(counter1()); // 0
console.log(counter1()); // 1
- 전역 변수 사용 억제
자신이 생성되었을 때의 lexical 환경을 기억하기 때문
예시
let count = 0;
function makeCounter() {
return function () {
return ++count;
};
}
let counter1 = makeCounter();
console.log(counter1(), "before"); // 0
count = 1000;
console.log(counter1(), "after"); // 1001
- 정보의 은닉
예시
// 생성자 함수
function Person(name, age) {
this.name = name; //public
let _age = age; //private
this.sayHi = function () {
console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
};
}
const me = new Person("Choi", 33);
me.sayHi(); // Hi!, My name is Choi. I am 33.
console.log(me.name); // Choi
console.log(me._age); // undefined
const you = new Person("Lee", 30);
you.sayHi(); // Hi! My name is Lee. I am 30.
console.log(you.name); // Lee
console.log(you.age); // undefined
위 코드에서 me._age나 you.age에 접근하면 undefined가 나오는 이유
_age는 Person 함수의 내부에서만 사용하는 변수라서 외부에서 접근할 수 없음
변수 _age는 Person 함수 내부에서만 접근가능하고(은닉화), 함수가 종료되면 콜스택에서 사라지지만 클로저로 인해 메소드 SayHi 함수에서 계속 접근이 가능함
me.name이 되는 이유는 Person 객체의 인스턴스의 name 속성을 나타내고, 이 속성은 어디서든 접근가능하고 변경할 수 있는 public 속성이기 때문임. 따라서 me.name이나 you.name이라고 하면 그 속성값이 나옴