이 시리즈는 You Don't Know JS Yet (1판인 You Don't Know JS의 개정판) 을 가지고 스터디를 진행하면서 요약 정리한 글입니다. 내용에 대한 퀴즈도 깃헙 리포지토리에 올리고 있으니 참고하시면 더 도움이 되실 것 같습니다. 내용에 대해 오류가 있거나 궁금하신 점이 있다면 댓글 달아주세요!
프로그램을 만드는 가장 큰 이유는 데이터를 처리하기 위함인데, 데이터를 통째로 처리하는 것이 이해하기 쉽겠지만 데이터가 엄청 클 경우에는 통째로 처리하는것은 어렵습니다. 결국에는 데이터 묶음을 하나하나 처리해야 하는데 이를 처리하는 방식이 iteration 기법입니다. 이때 iteration에 넣는 데이터 묶음을 iterable라고 합니다.
doSomething( ...it );
var vals = [ ...it ];
다른 언어에서 사용하는것 처럼 for..of
를 사용해 iterable를 하나하나 처리할 수 있습니다. 특히나 JS에서의 특수한 문법은 바로 Spread인데, iterable를 말 그대로 '뿌려서' 사용할 수 있습니다. 다양하게 활용할 수 있는데 위 코드와 같이 함수에 여러 매개변수를 넣을 때 iterable를 하나하나 나눠서 넣는 것과 같은 작업을 할 수 있고, iterable를 deep copy할때에도 Spread를 사용해 복사할 수 있습니다.
var buttonNames = new Map();
buttonNames.set(btn1,"Button 1");
buttonNames.set(btn2,"Button 2");
for (let [btn,btnName] of buttonNames) {
btn.addEventListener("click", function(){
console.log(`Clicked ${ btnName }`);
});
}
Iterable에는 배열, 문자열, 맵등이 있습니다. 특히 맵을 iterator를 통해 처리될 땐 key와 value가 둘다 가지고 있는 새로운 entry로 데이터를 처리합니다. 위 코드에서 buttonNames
에는 DOM Element인 btn
을 키로, 이름 문자열을 값으로 가진 맵인데 이를 for..of
로 사용하기 위해 대괄호 안에 btn
과 btnName
을 넣어 entry내 값을 두 값에 할당합니다. 이를 'array destructuring'라고 하고, 주로 iterator에서 많이 활용되는 기법중 하나입니다.
Closure을 책에서 이렇게 정의하고 있습니다.
Closure is when a function remembers and continues to access variables from outside its scope, even when the function is executed in a different scope.
정리하자면, 스코프 밖으로 나가도 일부 값들이 남아있어 밖에서도 값들을 사용할 수 있는 방식을 클로저라고 합니다.
function counter(step = 1) {
var count = 0;
return function increaseCount(){
count = count + step;
return count;
};
}
var incBy1 = counter();
var incBy3 = counter(3);
incBy1(); // 1
incBy1(); // 2
incBy3(); // 3
incBy3(); // 6
또한 클로저는 만들 때 마다 새로운 스코프를 가집니다. 위 코드에서 같은 counter()
함수를 사용해도 그 안 step
이 다른 것을 저장해 사용되는 것을 확인할 수 있습니다. 또 다른 예시로는 위 map iterator를 보았을 때 for 루프를 통해 addEventListener
에 값을 넣어놨는데, 이때 bntName
값은 각각 다르고 이 코드가 작동되는 걸 확인할 수 있습니다.
스코프에 대한 더욱 구체적인 이야기는 YDNJSY 2권 Scope & Closure를 통해 배울 예정입니다.
JS에서 가장 햇갈리는 키워드가 바로 this
입니다. this
를 호출할 때 마다 용도가 달라지고 내용이 달라지기 때문에 햇갈리기 마련입니다. 여기서 this
의 핵심은 코드에서 어떻게 this
가 호출되는지에 따라 달라진다는 점입니다. 이와 관련해서는 실행 컨텍스트와 관련이 있는데, 이에 대해서 깊게 다루진 않고 간단하게 this
의 원리를 다뤄볼까 합니다.
function classroom(teacher) {
return function study() {
console.log(
`${ teacher } says to study ${ this.topic }`
);
};
}
var assignment = classroom("Kyle");
assignment();
// Kyle says to study undefined
var homework = {
topic: "JS",
assignment: assignment
};
homework.assignment();
// Kyle says to study JS
var otherHomework = {
topic: "Math"
};
assignment.call(otherHomework);
// Kyle says to study Math
위 코드가 this
를 가장 잘 이해할 수 있는 코드인 것 같습니다. 핵심은 this의 위치가 아니라 코드가 실행되는 위치를 유심히 보시길 바랍니다. classroom()
을 호출해 만든 assignment
변수는 study()
함수를 받게 되는데 이때 함수 안에 this
가 있습니다. 이 this
가 코드상으로는 같은 위치에 있지만 실행 위치에 따라 값이 달라집니다. 기본적으로 this
는 전역 변수 (브라우저의 경우 window
, nodejs의 경우 global
)을 가지기 때문에 아무련 연관이 없게 된다면 이 전역변수를 가지게 됩니다. assignment
를 처음으로 실행할 때엔 아무런 연관이 없기 때문에 window
나 global
에 topic
이 없기 때문에 undefined
가 나오게 되는 것입니다.
다만 아래 두 예시와 같이 실행할 때 연관을 짓게 된다면 this
가 변경하게 되는데, homework
의 경우에는 homework.assignment()
을 실행할 때 앞에 있는 homework
가 연관이 있음으로 이때 this
는 homework
가 할당되게 됩니다. 그래서 JS가 출력되는 것입니다. 다른 연관을 짓는 방법으로는 call()
을 사용하는 것입니다. 함수 매개변수에 this
에 연관시키고 싶은 객체를 넣어놓는다면 다음과 같이 otherHomework
가 this
에 할당되게 됩니다.
this
가 함수에서의 특성이라면 객체에서의 대표적인 특성은 Prototype입니다. Prototype는 객체 간 관계를 연결하는 특성이라고 생각하시면 될 것 같습니다. 기본적으로 모든 객체는 object.prototype
을 Prototype로 가지게 됩니다. 다만 새롭게 프로토타입을 연결하고 싶다면 Object.create()
를 통해 관계를 만들 수 있습니다.
var homework = {
topic: "JS"
};
var otherHomework = Object.create(homework);
otherHomework.topic; // "JS"
otherHomework.topic = "Math";
otherHomework.topic; // "Math"
homework.topic; // "JS"
위 코드와 사진을 보면, otherHomework
를 homework
를 통해 만들게 되면서 사진과 같은 Prototype 관계를 가지게 되는 것입니다. 이때 otherHomework
의 값을 바꿔도 homework
의 값은 바뀌지 않습니다. 이는 직접적으로 참조하는 관계가 아니라 'Prototype'으로 관련되어있기 때문입니다.