자바스크립트는 무엇으로 구성되어있을까?


원문: https://overreacted.io/what-is-javascript-made-of/

처음 몇 년 동안 자바스크립트를 사용하면서, 나는 사기꾼처럼 느껴졌었다. 프레임워크를 이용해 웹사이트를 만들 수 있었지만, 뭔가 빠진 느낌이었다. 기초에 대한 확실한 이해가 없었기 때문에 자바스크립트 구직 면접을 두려워했었다.

몇 년 동안, 나에게 자신감을 주는 자바스크립트의 멘탈 모델을 만들었다. 여기, 매우 압축된 버전의 멘탈 모델을 공유하려고 한다. 단어를 하나 제시하고, 각 주제에 맞게 몇 문장을 설명하는 방식으로 구성했다.

글을 읽으면서, 각 토픽에 대해 얼마나 자신감을 느끼는지 마음속으로 점수를 매겨봐라. 당신이 토픽 중 몇 가지를 놓쳤는지 판단하지 않을 것이다! 이 글의 끝에는 몇 가지 도움이 될만한 것을 작성했다.


  • 값(Value): 값의 개념은 약간 추상적이다. "것(thing)"이다. 자바스크립트에서 값은 수학의 숫자, 기하학의 점이다. 프로그램이 실행될 때 프로그램은 값으로 가득차 있다. 1, 2, 420 같은 숫자로 된 값 외에, "소는 음매" 같은 문장도 포함되어 있다. 하지만, 모든 것이 값은 아니다. 숫자는 값이지만 if문은 아니다. 아래에서 몇 가지 다른 값을 살펴보자.

    • 값의 타입: 값에는 몇 가지 "타입"이 존재한다. 예를 들어, 420 같은 숫자나,"소는 음매" 같은 문자열, 객체, 그리고 몇 가지 다른 타입이 존재한다. typeof를 값의 앞에 두면 값의 타입에 대해 알 수 있다. 예를 들어, console.log(typeof 2)를 실행한다면 "number"가 출력될 것이다.
    • 원시값(Primitive Values): 몇몇 값들의 타입은 "원시(Primitive)"다. 원시값은 숫자, 문자열, 그리고 몇 가지 타입을 포함한다. 원시값의 한 가지 독특한 점은 새롭게 원시값을 만들거나, 원시값을 변경하는 것이 불가능하다는 것이다. 예를 들어, 당신이 2를 쓸 때마다 항상 같은 2 값을 얻을 것이다. 당신의 프로그램에서 새로운 2를 "생성"할 수 없고, 23이 "되는" 것은 불가능하다. 문자열 또한 동일하다.
    • nullundefined: 두 가지 특별한 값이 있다. 이 두 값은 많은 것을 할 수 없게 만들며, 종종 에러의 원인이 된다. 일반적으로 null은 의도적으로 값이 없는 것을 의미하며, undefined는 의도하지 않게 값이 없는 것을 의미한다. 그러나, 언제 어떻게 사용될지는 프로그래머에게 맡겨진다. 두 값은 종종 누락된 값을 처리하는 것보다 연산이 실패하는 것이 괜찮기 때문에 존재한다.
  • 같음(Equality): "값" 처럼, "같음"은 자바스크립트의 기본 개념이다. 두 값이 같다고 말한다면(나는 절대 그렇게 말하지 않겠지만), 같은 값(value)이라는 것을 의미한다. 서로 다른 두 값(value)은 존재할 수 없다. 하나일 뿐이다! 예를 들어, "소는 음매"==="소는 음매"2===2인 것은 22이기 때문이다. JavaScript는 이 개념을 나타내기 위해 등호 세개를 사용한다.

    • 엄격한(strict) 같음: 위와 동일
    • 참조(Referential) 같음: 위와 동일
    • 느슨한(Loose) 같음: 이건 다르다! 느슨한 같음은 두 개의 등호(==)를 사용한다. 보기에는 비슷해 보이지만 다른 값을 참조하더라도 느슨하게 동등한 것으로 간주할 수 있다.(2와 "2"처럼) 일찍이 자바스크립트에 편의를 위해 추가되었지만, 이후로 끊임없는 혼란을 불러왔다. 이 개념은 기본 개념은 아니지만, 일반적인 실수의 원인이 된다. 난관에 부딪혔을 때 상황을 해결해 줄 수 있지만, 일반적으로는 사용을 지양한다.
  • 리터럴(Literal): 리터럴은 문자를 그대로 적음으로써 값을 나타내는 것이다. 2는 숫자 리터럴이고 "바나나"는 문자열 리터럴이다.
  • 변수: 변수를 통해 이름을 사용해 값에 참조할 수 있다. 예를 들어, let message = "소는 음매"로 작성하면 코드상에서 반복해서 문장을 적지 않고 message로 작성할 수 있게 된다. 또한, 이후에 message = "난 바다코끼리다"처럼 message가 다른 값을 가리키도록 바꿀 수 있다. 이것이 의미하는 것은 값 자체를 바꾸는 것이 아니다. "와이어"처럼 message가 어디 있는지 가리키는 위치만 변경된다. 변수는 "소는 음매"를 가리키다, 지금은 "나는 바다코끼리다"를 가리키고 있다.

    • 스코프: 프로그램 전체에서 하나의 message 변수를 사용할 수 있다면 별로일 것이다. 변수를 정의하면, 프로그램의 한 부분에서 사용할 수 있게 된다. 스코프가 어떻게 동작하는지 작동 방식에 대한 규칙이 있지만, 일반적으로 변수를 정의한 위치 근처의 {} 중괄호를 확인하면 찾을 수 있다. 이 코드 "블록"이 변수의 스코프다.
    • 할당: message = "나는 바다코끼리다"로 작성할 때, message변수가 가리키는 값이 "나는 바다코끼리다"로 바뀐다. 이것을 할당, 쓰기, 혹은 변수 설정이라 한다.
    • let vs const vs var: 보통은 let을 사용하고 싶을 것이다. 만약 변수에 값 할당을 금지하려면 const를 사용하면 된다.(몇몇 코드베이스와 동료들은 한 번만 할당 할 경우 const를 사용하는 약속을 만들기도 한다.) 가능하다면 var 사용은 피하라. var는 스코프를 더럽힌다.
  • 객체: 객체는 자바스크립트에서 특별한 종류의 값이다. 객체의 멋진 점은 다른 값들과 연결을 가질 수 있다는 점이다. 예를 들어 {flavor: "vanilla"} 객체는 "vanilla"라는 값을 가리키는 flavor 속성이 존재한다. 객체를 "와이어"로 "자신의" 값을 갖는 값으로 생각하라.

    • 속성: 속성은 객체에서 어떤 값을 가리키는 "와이어" 역할을 한다. 앞에서 언급했던 변수가 떠오를 수 있는데(flavor같은 이름을 갖고 "vanilla"라는 값을 가리키는 것이), 변수와 다르게 요소는 코드(스코프)가 아닌 객체 내에 "존재"하게 된다. 요소는 객체의 일부로 간주하지만, 가리키는 값은 아니다.
    • 객체 리터럴: 객체 리터럴은 {}{flavor: "vanilla"}처럼 문자 그대로 작성해서 객체를 만드는 방법이다. {} 안에는 쉼표로 구분된 여러개의 {proverty: value} 쌍을 가질 수 있다. 이를 통해 객체의 속성이 어디에 "와이어"되는지 할당할 수 있다.
    • 객체 항등(Identity): 우리는 앞서 22와 같다(다시 말하면, 2 === 2)고 했다. 왜냐하면 2를 작성하는 어디에서나 항상 같은 값이 "소환"되기 때문이다. 하지만 {}를 작성한다면, 항상 다른 값이 될 것이다! 따라서 {}는 다른 {}와 다르다. 콘솔 창에 {} === {}를 실행해봐라.(false가 반환될 것이다) 컴퓨터가 코드에서 2를 만나면 항상 같은 2를 반환할 것이다. 하지만, 객체 리터럴은 다르다. 컴퓨터가 {}를 만나면 항상 새로운 객체를 생성한다. 그럼 객체 항등(identity)은 무엇일까? 그것은 여전히 동일하거나, 값의 동일성을 나타내는 또 다른 용어이다. 우리가 "ab가 항등이다"라고 말하는 것은, "ab가 같은 값을 가리키고 있다"는 것을 의미한다.(a === b) 만약 "ab가 항등이 아니다"는 것은 "ab가 다른 값을 가리킨다"(a !== b)는 것을 의미한다.
    • 점 표기법: 객체의 속성에 접근하거나 할당하고 싶을때 점(.) 표기법을 사용할 수 있다. 예를 들어 만약 변수 iceCreamflavor요소가 가리키는 값이 "chocolate"이라면, iceCream.flavor로 작성할 경우 "chocolate"가 반환된다.
    • 괄호 표기법: 접근하고 싶은 프로퍼티의 이름을 미리 알지 못하는 경우가 있다. 예를 들어 때로는 iceCream.flavor에 접근하고 싶을 수도 있고 iceCream.taste에 접근하고 싶을 수도 있다. 대괄호([]) 표기법은 변수를 이용해서 요소에 접근이 가능하게 한다. 예를 들어 let ourProperty = 'flavor'라고 선언해보자. iceCream[ourProperty]로 작성하면 "chocolate"를 반환한다. 신기하게도 { [ourProperty]: "vanilla" }처럼 객체를 생성할 때도 사용할 수 있다.
    • 뮤테이션(Mutation): 우리는 객체의 요소가 가리키는 부분이 달라졌을 때 변이(mutate) 되었다고 한다. 예를 들어 let iceCream = {flavor: "vanilla}라고 선언했을 때, iceCream.flavor = "chocolate"로 작성해 값을 변이 할 수 있다.비록 consticeCream을 선언하더라도 iceCream.flavor를 통해 변이할 수 있다. 왜냐하면 consticeCream 변수 그 자체만의 할당만을 막을 수 있기 때문에, 객체의 요소(flavor)가 가리키는 값을 바꿀 수 있다. 이런 오해의 소지 때문에 몇몇 사람들은 const를 맹목적으로 잘못 사용하기도 한다.
    • 배열: 배열은 무엇인가 나열 할 때 사용하는 객체이다. 배열 리터럴로 ["banana", "chocolate", "vanilla"]로 작성한다면, 본질적으로 0 요소가 "banana"에, 1 요소가 "chocolate"를, 2 요소가 "vanilla"를 가리키는 객체를 생성한다. {0: ..., 1: ..., 2: ...}처럼 작성하는 것은 매우 귀찮을 것이다. 또한 map, filter, reduce처럼 배열을 다루는 여러가지 메서드또한 제공된다. reduce가 혼란스럽다고 절망하지 마라 - 다른 사람들도 마찬가지로 혼란스러워하는 개념이다.
    • 프로토타입: 만약 존재하지 않는 요소에 접근하려 하면 어떤 일이 벌어질까? 예를 들어 iceCream.taste처럼 말이다.(우리가 선언한 요소는 flavor뿐이다.) 질문에 대한 단순한 답은 undefined이다. 좀 더 구체적으로 설명해보면, 대부분의 자바스크립트 객체는 "프로토타입"을 갖는다. 당신은 프로토타입을 모든 객체에서 "다음에 어디를 봐야할 지" 결정하는 "숨겨진"요소로 생각할 수 있다. 따라서 iceCream객체에 taste라는 요소가 없다면, 자바스크립트는 객체의 프로토타입을 따라 taste요소를 찾아 거슬러 올라갈 것이다. 그리고 "프로토타입 체인"을 모두 찾았지만 .taste를 찾지 못한 경우에 undefined를 반환한다. 당신은 이 메커니즘을 직접적으로 사용할 일은 별로 없겠지만, iceCream객체에 우리가 정의하지 않은 toString 메서드가 존재하는 것을 설명할 수 있다. - 이 메서드는 프로토타입에서 왔다.
  • 함수: 함수는 프로그램에서 일부 코드를 나타내는 목적을 가진 특별한 값이다. 함수는 중복 코드를 여러번 작성하지 않으려는 경우 편리하게 쓸 수 있다. sayHi()같은 함수를 "호출"하면 컴퓨터에서 해당 코드를 내부적으로 실행한 뒤, 프로그램의 원래 위치로 돌아간다.

    • 매개 변수: sayHi("Amelie")처럼 매개변수를 이용하면 함수로 몇 가지 정보를 전달할 수 있다. 함수 내에서 매개변수는 변수처럼 동작한다. 이 값들은 "인자" 혹은 "매개변수"로 불린다.(함수를 정의하는 부분인지 호출하는 부분인지에 따라). 그러나 엄격하게 구분할 필요는 없고, 두 용어는 같이 사용될 수 있다.
    • 함수 표현식: 이전에 우리는 문자열 값을 let message = "나는 바다코끼리다"형태로 지정했었다. let sayHi = function { }처럼 함수를 변수에 할당하는 것 또한 가능하다. = 이후에 오는 함수를 함수 표현식이라 한다. 함수 표현식은 코드의 한 부분을 나타내는 특별한 값(함수)을 나타내기 때문에, 이후에 원하면 호출할 수 있다.
    • 함수 선언식: 함수를 let sayHi = function() {}처럼 매번 함수 표현식으로 선언하는 것은 귀찮은 일이다. 그래서 function sayHi() {} 같이 함수 표현식보다 짧은 형식을 사용한다. 이것을 함수 선언식이라 한다. 왼쪽에 변수 명을 지정하는 것 대신 function 키워드 뒤에 지정한다. 함수 표현식과 함수 선언식은 대부분 호환된다.
    • 함수 호이스팅: 일반적으로, let이나 const로 먼저 선언이 되어있어야 뒤에서 사용을 할 수 있다. 하지만 이런 특징 때문에 함수가 서로를 호출할 때, 어떤 함수가 다른 함수를 사용할 때, 어떤 함수가 우선적으로 선언되어야 하는지를 추적하는 것은 매우 성가시다. 이런 부분을 편리하게 하기 위해, 함수 선언 구문을 사용할 때만, "호이스팅"되어 순서에 상관없이 사용 가능해진다. 호이스팅은 개념적으로 스코프의 가장 위로 이동된다는 것을 의미한다. 이 경우에는 함수를 호출할 때, 이미 모두 정의되어 있다.
    • this: this는 아마 자바스크립트 개념 중 가장 오해받고 있는 함수의 특별한 인수다. 함수를 사용할 때 직접 this를 넘기지 않는다. 대신, 함수 호출 방식에 따라 자바스크립트 자체가 this를 넘겨줄 것이다. 예를 들어, 점 표기법 .을 사용하여 호출할 경우(iceCream.eat() 처럼) this.앞에 있는 값일 것이다. (예시에서는 iceCream) this는 함수가 어디서 정의되었는지가 아니라, 어디서 호출되었는지에 따라 달라진다. .bind, .call, .apply 같은 헬퍼를 이용하면 this를 좀 더 효과적으로 제어할 수 있을 것이다.
    • 화살표 함수: 화살표 함수는 함수 표현식과 비슷하다. let sayHi = () => { } 같이 선언할 수 있다. 화살표 함수들은 간결하고 한 줄로 표현될 수 있다. 화살표 함수는 일반 함수보다 제한적인 기능을 갖는다. 예를 들어, 화살표 함수는 this의 개념이 없다. 만약 화살표 함수에서 this를 사용한다면, 가장 가까운 "일반" 함수의 this를 사용한다. 이것은 매개변수나 함수에 존재하는 변수만 사용하는 것과 비슷하다. 실제로, 사람들이 화살표 함수를 사용하는 것은 코드상에서 둘러싸고 있는 것을 그대로 "보고" 싶을 때 사용한다는 것을 의미한다.
    • 함수 바인딩: 일반적으로 함수 fthis 값과 인수에 바인딩 하는 것은 사전 정의된 값으로 f를 호출하는 새로운 함수를 만드는 것을 의미한다 .자바스크립트는 .bind라는 빌트인 헬퍼가 존재하지만, 함수를 이용하지 않고 직접 바인딩을 할 수도 있다. 바인딩은 중첩된 함수가 외부 함수와 동일한 this를 "보게"하는 일반적인 방법이었다. 하지만 이런 부분들은 화살표 함수에서 처리되므로, 바인딩은 자주 사용되지 않는다.
    • 콜 스택: 함수를 호출하는 것은 방에 들어가는 것과 같다. 함수를 호출할 때마다, 함수 내부에 있는 변수들을 매번 초기화 한다. 따라서, 함수를 호출하는 것은 코드를 사용해 새로운 "방"을 구성하고 들어가는 것과 같다. 함수의 변수들은 방에 "살아 있게" 된다. 함수가 종료되면, "방"과 모든 변수는 사라진다. 당신은 이런 방을 수직 스택으로 시각화 할 수 있을 것이다. 이것이 콜 스택이다. 함수가 종료되면, 콜 스택의 "아래" 함수로 돌아가게 된다.
    • 재귀: 재귀는 함수가 내부에서 자기 자신을 호출하는 것을 의미한다. 재귀는 같은 동작을 다른 인수로 함수 안에서 다시 반복해야할 때 매우 유용하다. 예를 들어, 웹을 크롤링하는 검색 엔진을 만든다면, collectLinks(url) 함수는 먼저 페이지에서 방문 가능한 링크를 수집한 뒤, 모든 페이지를 방문할 때까지 자체적으로 함수를 호출할 것이다. 재귀의 함정은 계속 자기 자신을 호출하기 때문에 끝나지 않는 코드를 작성하기 쉽다는 것이다. 이 경우 자바스크립트는 "스택 오버플로우"로 불리는 에러와 함께 멈출 것이다. 이 에러가 스택 오버플로우라 불리는 이유는 콜스택에 호출해야 하는 함수가 너무 많이 쌓여 말그대로 넘쳤을 때 발생하기 때문이다.
    • 고차함수(Higher-Order Function): 고차 함수는 다른 함수를 인수로 사용하거나 그것을 반환하여 처리하는 함수이다. 처음에는 이상해 보일지 몰라도, 함수 또한 숫자, 문자열, 객체 처럼 전달 될 수 있는 값이다. 이 스타일은 과용될 수 있지만, 적당히 사용하면 매우 좋다.
    • 콜백: 콜백은 자바스크립트 용어는 아니다. 그것은 패턴에 가깝다. 콜백은 다른 함수의 매개변수로 함수를 넘겼을 때, 먼저 호출된 함수가 종료된 뒤에 넘긴 함수가 호출되는 것을 기대할 것이다. "뒤이어 호출" 될 것을 예상할 것이다. 예를 들어, setTimeout콜백함수를 갖는데.. 시간이 지나면 이 함수를 뒤이어 실행 시켜 준다. 그러나, 콜백 함수는 특별할 것이 없다. 일반적인 함수이며, "콜백" 함수에 대해서는 어떻게 동작할지에 대한 예상만 이야기한다.
    • 클로저: 일반적으로, 함수가 종료되면 모든 변수는 "사라진다". 왜냐하면 더는 선언된 변수가 필요가 없어지기 때문이다. 그러나 함수 내부에 함수를 선언한다면 어떻게 될까? 내부에 있는 함수는 이후에 여전히 호출될 수 있고, 외부함수의 변수에 접근할 수 있게 된다. 실제로 클로저는 매우 유용하다! 하지만 클로저가 동작하기 위해서는, 외부 함수의 변수는 어딘가 "그대로 놓여" 있어야 한다. 따라서 이때는, 자바스크립트는 변수를 "잊어버리지" 않고 "변수를 살려둔 채"로 유지하게 된다. 이것을 "클로저"라 부른다. 클로저는 종종 오해의 소지가 있는 자바스크립트의 기능으로 간주하지만, 당신은 아마 그것을 깨닫지 못한 채 자주 사용할 것이다!

자바스크립트는 이런 여러가지 개념들로 구성된다. 나는 정확한 멘탈 모델을 만들때까지 자바스크립트 지식이 매우 불안했고, 다음 세대 개발자들이 이 격차를 빠르게 따라잡기 위해 돕고 싶다.

이 각각의 주제들에 좀 더 깊게 공부하고 싶다면, 이 사이트를 살펴봐라. Just Javascript는 어떻게 자바스크립트가 동작하는지에 대한 나의 정제된 멘탈 모델이며, Maggie Appleton의 놀라운 시각적인 자료들과 함께한다. 이 글과 다르게 페이스가 느리기 때문에 모든 세부적인 내용을 살필 수 있을 것이다.

Just Javascript는 아직 매우 초기 단계이므로 가공하지 않거나, 수정중인 초안 정도의 시리즈들을 이메일로 받을 수 있다. 이 프로젝트가 흥미롭게 보인다면, 이메일로 무료 초안들을 받아봐라. 당신의 피드백에 매우 감사할 것이다. 땡큐!

한정2020.02.19
Back to list