앞으로 3개월 동안 오프라인에서 만나 JS, React를 공부하여 프로젝트를 하는 것을 목표로 세 명이서 노력하기로 했다. 😼 서로 다른 분야의 세 명의 개발자(라고 부르기엔 나만 초짜...)가 모여 시작해 앞으로 재미있는 시간이 될 것 같다.
JS는 우선 드림 코딩 자바스크립트 기초 (ES5+) by 엘리
라는 유튜브 무료 강의를 수강하고, 코딩 테스트를 대비하기 위해 알고리즘 문제를 2~3문제씩 풀면서 서로 공유하기로 했다.
드림 코딩 자바스크립트 기초 (ES5+) by 엘리
강의가 너무 좋아서 개인적으로 혼자 JS를 공부하고 싶은 사람에게도 추천한다. 그런데, JS 문법을 자세히하기 보다는 예제 코드 조금과 이론 설명 위주로 되어 있으므로 기초 문법을 잘 모르는 사람들은 기초 문법을 먼저 공부하고 수강하면 좋을 것 같다.
공부한 코드는 github에 주석과 함께 정리를 해놓고 있다. 링크
강의를 수강하면서 배운 내용은 velog에 각자 정리하기로 했다. 한 달 단위로 쪼개 계획을 정리해보았는데... 우선은 JS를 이번 달 안에 정리하고, React를 다음 달에 같이 공부해보기로 했다. 계획이 꼭 성공하길... 😺
scope는 "범위"라는 뜻을 가진 단어이다. 다른 언어와 마찬가지로 JS에도 local과 global 개념이 있는데, 강의에서는 local을 block이라고 설명했다. JS에서는 local보다는 block이란 단어를 더 많이 쓰는 것 같았다. (아마도)
block scope와 global scope는 서로 반대되는 개념이다. 이 두 가지의 scope는 아주 간단한 이론 하나로 정리할 수 있는데, 바로 "밖에서는 안을 볼 수 없지만 안에서는 밖을 볼 수 있다!" 는 것이다.
block scope는 블록 안에서 선언된 변수를 블록 안에서만 접근할 수 있다는 뜻이고, global scope는 블록 밖에서 선언된 변수는 어디에서든 접근할 수 있다는 뜻이다. 변수가 아닌 함수로 생각해보면, 함수가 생성될 때마다 새로운 scope가 생긴다고 하니... 함수 또한 block이라고 생각하면 쉬운 것 같다.
참고로, scope는 함수를 호출한 시기가 아니라 "어디에 함수를 선언했는지"에 따라 결정된다. 이를 렉시컬 스코핑(Lexical Scoping)이라고 한다.
강의 안에서는 hoisting을 "어디에 선언했든 가장 위에 선언한 것처럼 끌어올리는 현상" 이라고 설명했다. (내가 이해한대로) 쉽게 말하면, 변수나 함수가 global해지는 것이다.
자바스크립트 함수는 실행되기 전에 Parser가 함수 실행 전 해당 함수를 한 번 훑은 다음, 함수 안에 필요한 변수값들을 모아서 유효 범위(scope)의 최상단에 선언한다. 즉, 함수 내에서 필요한 것들을 골라 최상단에 선언한 것처럼 끌어올리는 것이다. 그러나, 실제로 코드가 끌어올려지는 건 아니며, 자바스크립트 Parser 내부적으로 끌어올려서 처리하는 것이다. 그래서 실제 메모리에서는 변화가 없다.
🔼 위의 설명은 링크를 참고하여 작성했다.
hoisting은 var 변수와 함수 선언식(function declarations)에 적용된다.
var은 ES6 이전의 변수 선언형으로 block scope가 불가능한, 호이스팅의 대상이었다. 그래서 아래 같은 말도 안 되는 코드가 말이 된다... (2017년도에 학교 수업에서 배웠을 때 아주 화가 났던 부분... 이래서 그때는 프론트에 관심이 그닥 없었다. 만약 그때 ES6를 알았다면...?)
{
age = 4; // 변수 선언 전에도 사용 가능
var age;
}
console.log(age); // 블럭 밖임에도 접근 가능
위의 코드는 호이스팅에 의해서,
var age;
{
age = 4; // 변수 선언 전에도 사용 가능
}
console.log(age); // 블럭 밖임에도 접근 가능
처럼 해석된다. 그래서 아주 다양한 혼란을 야기하고 아주 작은 실수로 인한 에러를 강령술마냥 불러 일으키기 때문에~ ES6에서는 let과 const가 추가되었다.
let은 block scope가 가능한 변수 선언형으로 위와 같은 문제가 일절! 차단된다. const는 마찬가지로 block scope 변수 선언형이지만, 상수이다. 이 const를 잘 활용하면 보안성과 thread safety를 지키고, 실수를 줄일 수 있는 좋은 코드가 될 수 있다. const는 반드시! 초기값이 필요하다.
이 둘은 예제로 비교하는 게 빠르다.
function 함수 이름() {
함수 로직;
}
🔼 위는 함수 선언식(function declarations)이다.
함수 선언식은 전술했듯, 호이스팅의 대상이다. 오류를 부르지 않기 위해서는 의무적으로 코드의 맨 위쪽에 함수를 선언하는 게 좋다.
let 함수 이름 = function() {
함수 로직;
}
🔼 위는 함수 표현식(function expressions)이다.
함수 표현식은 호이스팅에 영향을 받지 않는다. 그러나, 변수가 let이 아닌 var이라면 호이스팅의 대상이 된다. 함수 표현식의 장점은 1. 클로저(Closer)로 사용이 가능하다는 것이며, 2. callback으로도 활용할 수 있다는 것이다.
참고로, JS에서 함수는 object형으로 분리된다. (=first-class function) 그러므로, 함수를 다른 함수에 매개변수로 제공하거나, 함수가 함수를 반환할 수 있으며, 변수에도 할당할 수 있다. (재귀적 프로그래밍 또한 가능) MDN 참조
클로저는 JS 뿐만 아니라 함수를 일급 객체(전술한 first-class function)로 취급하는 함수형 프로그래밍 언어(Functional Programming language: 얼랭(Erlnag), 스칼라(Scala), 하스켈(Haskell), 리스프(Lisp)…) 에서도 사용되는 특성이다. 클로저는 이해하기가 쉽지 않은 개념인데... 내가 봤던 책에서는 "클로저는 함수와 그 함수가 선언될 때의 환경으로 이뤄진다" 라고 한다.
MDN에서는 "클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다." 라고 정의하는데, 이 렉시컬 환경이라는 말마저 난해하다... 그래서 자료를 좀 더 찾아보았다. 참고
function outerFunc() {
let x = 10;
let innerFunc = function () { console.log(x); };
innerFunc();
}
outerFunc(); // 10
이 코드 안에서 innerFunc는 outerFunc 내부에 선언된 x를 참조할 수 있다. 또, innerFunc는 함수 outerFunc의 내부에서 선언되었기 때문에 함수 innerFunc의 상위 스코프는 함수 outerFunc이다.
따라서, innerFunc는 자신이 속한 렉시컬 스코프(함수 outerFunc, 자신의 스코프)를 참조할 수 있다. 설명이 더 남아있지만 나머지는 링크를 통해 확인하는 게 좋을 것 같다. 다른 책에서 봤던 예제를 보면,
function makeCounterFunction(initVal) {
let count = initVal;
function Increase() {
count++;
console.log(count);
}
return Increase;
}
let counter1 = makeCounterFunction(0); // 1
let counter2 = makeCounterFunction(10); // 11
counter1();
counter2();
counter1이 호출될 때와 counter2가 호출될 때의 count 변수는 같은 count 변수이지만 값이 다르다. 즉, counter1과 counter2의 클로저가 다르기 때문이다.
클로저를 이용하면 다른 객체 지향 언어(Object-Oriented Programming, OOP)에 있는 private나 public 같은 개념을 구현할 수 있다.
쓰면서도 내가 맞게 잘 썼나 의심된다... 다음은... 변수 타입과 함수 어쩌고로 돌아오겠다... 아자아자! 😇