코어 자바스크립트 책을 읽고 배운 내용을 바탕으로 작성되었다.
함수와 그 함수가 선언되었을 때의 렉시컬 환경(
Lexical Environment
)의 조합이다.
- 선언될 당시의
lexical environment
는 실행 컨텍스트의 구성 요소 중outerEnvironmentReference
에 해당한다.
const outer = function(){
const a = 1;
const inner = function(){
console.log(++a);
};
inner();
}
outer(); // 2
inner
함수의 environementRecord
에서 변수 a를 찾지 못했기에 outerEnvironmentReference
를 통해 변수 a를 찾는다.inner
함수의 outerEnvironmentReference
는 outer
함수의 Lexical Environment
를 참조하고 여기에서 변수 a를 찾는다.outer
함수의 실행 컨텍스트가 종료되면 Lexical Environment
에 저장된 식별자들 (a, inner)에 대한 참조를 지운다.const outer = function(){
const a = 1;
const inner = function(){
return ++a;
};
return inner();
}
const outer2 = outer();
console.log(outer2); // 2
inner
함수의 실행 결과 반환const outer = function(){
const a = 1;
const inner = function(){
return ++a;
};
return inner;
}
const outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3
inner
함수의 실행 결과가 아닌 함수 자체 반환outer2
변수는 outer
의 실행 결과인 inner
함수를 참조하게 된다.inner
함수의 실행 컨텍스트의 environmentRecord
에는 수집할 정보가 없고, outerEnvironmentReference
에는 inner
함수가 선언된 위치의 Lexical Environment
가 참조 복사된다. (즉, outer
함수의 Lexical Environment
)inner
함수의 실행 시점에는 outer
함수는 이미 실행이 종료된 상태인데 outer
의 Lexical Environment
를 어떻게 접근할 수 있을까?outer
함수는 inner
함수를 반환하고 실행 컨텍스트가 종료되었지만, outer2
함수로 인해 inner
함수의 실행 컨텍스트가 활성화될 수 있으므로 inner
함수의 실행 컨텍스트의 구성 요소 중 outerEnvironmentReference
즉, outer
함수의 Lexical Environment
는 GC의 수집 대상에서 제외된다.Lexical Environment
전부를 GC
하지 않도록 되어 있으나, 2019년 기준으로 크롬이나 Node.js 등에서 사용중인 V8
엔진의 경우 내부함수에서 실제로 사용하는 변수만 남겨두고 나머지는 GC하도록 최적화되어 있다.(function(){
const a = 0;
const intervalId = null;
const inner = function(){
if (++a >= 10){
clearInterval(intervalId);
}
console.log(a);
};
intervalId = setInterval(inner, 1000);
}();
(function(){
const count = 0;
const button = document.createElement("button");
button.innterText = "click";
button.addEventListener("click", function(){
console.log(++count, "times clicked");
}
document.body.appendChild(button);
}
💡 자신이 선언될 당시의
Lexical Environment
을 기억하는 함수
💡 자신이 선언될 당시의 스코프 체인에서 알 수 있었던 변수들 중 자신이 실행될 때 사용할 변수들만을 기억하여 유지시키는 함수
Lexical environment
를 기억해야 하므로 메모리 차원에서 손해를 볼 수 있다. 그래서 개발자의 의도적인 메모리 관리가 필요하다.GC(Garbage Collector)
의 수거 대상이 되므로 개발자 의도적으로 참조 카운트가 0이 되도록 설계해야 한다.null
이나 undefined
)를 할당하면 된다.const outer = (function(){
const a = 1;
const inner = function(){
return ++a;
}
return inner;
}();
console.log(outer());
outer = null;
outer
함수에 null
을 할당함으로써 outer
식별자의 inner
함수 참조를 끊었다.(function(){
const a = 0;
const intervalId = null;
const inner = function(){
if (++a >= 10){
clearInterval(intervalId);
inner = null;
}
console.log(a);
};
intervalId = setInterval(inner, 1000);
}();
inner
함수를 더 이상 호출하지 않으므로 inner
식별자의 함수 참조를 끊었다.(function(){
const count = 0;
const button = document.createElement("button");
button.innterText = "click";
const clickHandler = function(){
console.log(++count, "times clicked");
if (count >= 10){
button.removeEventListener("click", clickHandler);
clickHandler = null; // clickHandler 식별자의 함수 참조를 끊음.
}
};
button.addEventListener("click", clickHandler);
document.body.appendChild(button);
}
const fruits = ["apple", "banana", "peach"];
const ul = document.createElement("ul");
fruits.forEach(function(fruit){ // (A)
const li = document.createElement("li");
li.innerText = fruit;
li.addEventListener("click", function(){ // (B)
alert('your choice is ' + fruit);
});
ul.appendChild(li);
});
document.body.appendChild(ul);
outerEnvironmentReference
가 (A)의 LexicalEnvironment
를 참조하게 된다.GC
대상에서 제외되어 계속 참조할 수 있다.const fruits = ["apple", "banana", "peach"];
const ul = document.createElement("ul");
const alertFruit = function(fruit){ // (B)
alert('your choice is ' + fruit);
}
fruits.forEach(function(fruit){ // (A)
const li = document.createElement("li");
li.innerText = fruit;
li.addEventListener("click", alertFruit);
ul.appendChild(li);
});
document.body.appendChild(ul);
alertFruit
)의 인자에 대한 제어권은 addEventLister
에게 있으며, addEventListener
가 콜백함수를 호출할 때 콜백함수의 인자로 이벤트 객체를 전달한다.const fruits = ["apple", "banana", "peach"];
const ul = document.createElement("ul");
const alertFruit = function(fruit){ // (B)
alert('your choice is ' + fruit);
}
fruits.forEach(function(fruit){ // (A)
const li = document.createElement("li");
li.innerText = fruit;
li.addEventListener("click", alertFruit.bind(null, fruit));
ul.appendChild(li);
});
document.body.appendChild(ul);
새로 바인딩할 this
인데 이 값을 생략할 수 없기에 일반적으로 원래의 this를 유지할도록 할 수 없는 경우가 많다const fruits = ["apple", "banana", "peach"];
const ul = document.createElement("ul");
const alertFruitBuilder = function(fruit){ // (B)
return function(){ // (C)
alert('your choice is ' + fruit);
}
}
fruits.forEach(function(fruit){ // (A)
const li = document.createElement("li");
li.innerText = fruit;
li.addEventListener("click", alertFruitBuilder(fruit));
ul.appendChild(li);
});
document.body.appendChild(ul);
💡 alertFruitBuilder
가 리턴한 함수가 콜백함수가 되고 그 함수는 자신이 선언될 당시의 Lexical Environment
를 통해 알 수 있었던 변수들 중 자신이 실행될 때 참조할 변수들을 기억하는 closure
이다.
outerEnvironmentReference
를 통해 (B) 함수의 인자로 넘어온 fruit를 참조할 수 있다.GC
대상에서 제외되어 (B) 함수가 종료되어도 (C) 함수에서는 변수 fruit를 계속 참조할 수 있다.public
, private
, protected
가 있다.public
은 외부에서 접근 가능한 것을 말하고, private
은 내부에서만 사용하며 외부에 노출되지 않는 것을 말한다.💡 클로저를 이용하면 함수 차원에서 public한 변수와 private한 변수를 구분하는 것이 가능하다.
💡 외부에 제공하고자 하는 대상들을 모아서 객체, 배열, 함수 형태로return
하고, 내부에서만 사용할 정보들은 return
하지 않는 것으로 접근 권한 제어가 가능하다.
const createCar = function(){
const fuel = Math.ceil(Math.random() * 10 + 10);
const power = Math.ceil(Math.random() * 3 + 2);
let moved = 0;
return{
get moved(){
return moved;
},
run: function(){ // closure
const km = Math.ceil(Math.random() * 6);
const wasteFuel = km / power;
if (fuel < wasteFuel){
console.log("이동불가");
return;
}
fuel -= wasteFuel;
moved += km;
console.log(`${km} km 이동 (총 ${moved} km), 남은 연료: ${fuel}`);
}
}
}
const car = createCar();
car.run();
getter
만을 부여함으로써 읽기 전용 속성을 부여함.어뷰징
이 가능한 상태이다.Object.freeze()
적용하여 변경할 수 없도록 함.partially applied function
const add = function(){
const result = 0;
for (let i = 0; i < arguments.length; i++){
result += arguments[i];
}
return result;
}
const addPartial = add.bind(null, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10)); // 55
this
를 변경할 수 밖에 없다.const partial = function(){
const originalPartialArgs = arguments;
const func = originalPartialArgs[0];
if (typeof func !== "function"){
throw new Error("첫 번째 인자가 함수가 아닙니다.");
}
return function(){
const partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
const restArgs = Array.prototype.slice.call(arguments);
return func.apply(this, partialArgs.concat(restArgs));
};
};
const add = function(){
const result = 0;
for (let i = 0; i < arguments.length; i++){
result += arguments[i];
}
return result;
}
const addPartial = partial(add, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10)); // 55
const dog = {
name: "강아지",
greet: partial(function(prefix, suffix){
return prefix + this.name + suffix;
}, "왈왈, ");
};
dog.greet("입니다!");
concat
) 원본 함수를 호출 (apply
)한다.arguments
는 함수에 전달된 인수에 해당하는 Array 형태의 객체이다. (유사배열객체)this
를 그대로 반영함으로써 this
에는 아무런 영향을 주지 않는다.Object.defineProperty(window, '_', {
value: 'EMPTY_SPACE',
writable: false,
configurable: false,
enumerable: false,
});
const partial = function(){
const originalPartialArgs = arguments;
const func = originalPartialArgs[0];
if (typeof func !== "function"){
throw new Error("첫 번째 인자가 함수가 아닙니다.");
}
return function(){
const partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
const restArgs = Array.prototype.slice.call(arguments);
for (let i = 0; i < partialArgs.length; i++){
if (partialArgs[i] === _){
partialArgs[i] = restArgs.shift();
}
}
return func.apply(this, partialArgs.concat(restArgs));
};
};
💡 부분 적용 함수으로 구현할 원본 함수와 그 함수의 일부 인자를 넘겨 원본함수와 그 인자들을 기억하는 함수(closure
)를 리턴한다. 클로저를 활용하여 이후 리턴한 함수를 호출할 때 나머지 인자들을 넘겨 기억하고 있던 인자들까지 함께 원본함수를 실행하게 한다.
debounce
const debounce = function(eventName, func, wait){
const timeoutId = null;
return function(event){
const self = this;
console.log(`${eventName} event 발생`);
clearTimeout(timeoutId); // 콜백함수 호출 대기 초기화
timeoutId = setTimeout(func.bind(self, event), wait);
};
};
const moveHandler = function(e){
console.log("move event 처리");
};
const wheelHandler = function(e){
console.log("wheel event 처리");
};
document.body.addEventListener("mousemove", debounce('move', moveHandler, 500));
document.body.addEventListener("mousewheel", debounce('wheel', wheelHandler, 700));
debounce
함수가 리턴한 함수는 클로저로 eventName
, func
, wait
, timeoutId
변수를 기억한다.currying function
const curry3 = function(func){
return function(a){
return function(b){
return func(a, b);
};
};
};
const getMaxWith10 = curry(Math.max)(10);
console.log(geMaxWith10(8)); // 10
console.log(getMaxWith10(25)); // 25
const getMinWith10 = curry(Math.min)(10);
console.log(geMinWith10(8)); // 8
console.log(getMinWith10(25)); // 10
const curry5 = function(func){
return function(a){
return function(b){
return function(c){
return function(d){
return function(e){
func(a, b, c, d, e);
};
};
};
};
};
};
const getMax = curry5(Math.max);
console.log(getMax(1)(2)(3)(4)(5));
const curry5 = func => a => b => c => d => e => func(a, b, c, d, e);
💡 각 단계에서 받은 인자들은 모두 마지막에 원본 함수가 호출될 때 참조되므로 GC
의 수거 대상이 되지 않았다가, 마지막 호출이 되서야 실행 컨텍스트가 종료된 후에야 비로소 한꺼번에 GC
의 수거 대상이 된다.
lazy execution
const getInformation = baseUrl => path => id => fetch(`${baseUrl}${path}/${id}`);
baseUrl
부터 전부 기입해주기보다는 공통적인 요소는 먼저 기억시켜두고 특정한 값(1id
)만으로 서버 요청을 수행하는 함수를 만들어두는 것이 개발 효율성이나 가독성 측면에서 더 좋을 것이다.Redux
의 middleware
// Redux Middleward 'Logger'
const logger = store => next => action => {
console.log('dispatching', action);
console.log('next state', store.getStore());
return next(action);
};
// Redux Middleward 'thunk'
const thunk = store => next => action => {
return typeof action === 'function'
? action(dispatch, store.getState)
: next(action);
};