
코어 자바스크립트 책을 읽고 배운 내용을 바탕으로 작성되었다.
함수와 그 함수가 선언되었을 때의 렉시컬 환경(
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);
};