How JavaScript Works

더글러스 크락포드, How JavaScript Works(2020) 한글 번역판

https://purelledhand.github.io/how-javascript-works/

목차

  1. 이름
  2. 숫자
  3. 배열

이름

자바스크립트는 변수 이름의 길이에 제한을 두지 않습니다. 가능하다면 이름만 보고도 무엇을 하는 것인지 짐작할 수 있게 만들어야 합니다.

모든 이름은 문자로 시작해서 문자로 끝나도록 하세요

자바스크립트는 _, $로 이름을 시작할 수 있으며 _, $, 숫자로 끝낼 수 있습니다. 이 외에도 해선 안되는 여러 문자를 허용하고 있습니다. _로 시작하거나 끝나는 이름들은 public 속성이나 전역 변수를 의미하는데, 프로그램을 잘 작성했다면 이런 변수들은 전부 private 이었을 것입니다. $ 기호는 코드 제너레이터, 트렌스파일러, 매크로 처리기에서 사용할 목적으로 추가된 기호입니다. 이들은 달러 기호를 사용함으로써 일반 목적으로 사용되는 이름과 겹치지 않는 이름을 사용할 수 있다고 보장받습니다. 그렇기 때문에 코드 제너레이터와 같은 프로그램이 아닌 한 달러 기호는 사용하지 않는 것이 좋습니다.

서수형, 기수형을 의미하는 변수의 네이밍

자바스크립트 내 모든 이름은 반드시 소문자로 시작해야 합니다.

이는 자바스크립트의 new 연산자 때문입니다. 함수 호출문의 new로 시작하면 해당 함수는 생성자로서 호출되고, 그렇지 않으면 함수로 호출됩니다. 생성자와 함수의 기능은 상당히 다르기 때문에 생성자를 잘못된 방식으로 호출하면 에러가 발생할 수 있습니다. 그래서 new를 써야 하는데 사용하지 않거나 잘못 사용한 경우 발생하는 문제를 자동으로 감지할 방법이 없습니다. 그래서 정한 약속이 모든 생성자 함수의 이름은 대문자로 시작되어야 하며, 그렇지 않은 모든 경우에는 소문자로 시작되어야 한다는 것입니다.

예약어

arguments await break case catch class const continue debugger default delete do
else enum eval export extends false finally for function if implements import in Infinity instanceof
interface let NaN new null package private protected public return static super switch
this throw true try typeof undefined var void while with yield

⬆ back to top

숫자

자바스크립트의 number는 실수(real number)에서 영감을 받았지만, 진짜 실수는 아닙니다. 수학에 대한 이해나 직관이 자바스크립트의 number에 완벽하게 적용되지는 않습니다.

자바스크립트는 하나의 숫자형 number 를 가지고 있는데 이 숫자형은 인텔 iAPX-432 프로세서를 위해 처음 개발된 IEEE 부동소수점 연산 표준(IEEE 754)을 차용했습니다. 자바스크립트는 IEEE 754 표준 전체를 사용하지는 않으며 자바가 사용하는 일부분 중의 일부분을 사용합니다. 자바스크립트의 number는 64비트 2진 부동소수점 타입이라는 점에서 자바의 double과 아주 밀접합니다.

부동소수점의 아이디어는 두 개의 수로 하나의 숫자를 표현하는 것입니다.

두 번째 수인 지수는 부호와 유효 숫자 사이의 공간을 차지합니다.

부호 * 유효 숫자 * (2**지수)

지수는 부호가 있는 정수로 표현되어서 숫자를 마치 64비트 정수인 것처럼 만들어 다른 숫자와 비교할 수 있습니다. 그래서 큰 성능상의 이득을 볼 수 있습니다. 지수는 또한 NaN, Infinity 그리고 아주 작은 수영(0)을 표현할 수 있습니다.

영(0)

자바스크립트에는 영(0)으로 표현되지만 영(0)이 아닌 값이 있습니다. IEEE 754 표준에는 0-0이라는 두 개의 0이 있습니다. 자바스크립트는 이 이상한 현상을 숨기기 위해 열심히 노력해왔습니다. 아래와 같은 경우를 제외하면 -0이 존재한다는 사실을 몰라도 됩니다.

(1 / 0) === (1 / -0) // false
Object.is(0, -0) //false

숫자 리터럴

자바스크립트에는 18437736874454810627개의 불변 숫자 객체가 내장되어 있는데, 이들은 각각의 고유한 숫자를 나타냅니다. 숫자 리터럴은 각 리터럴의 값과 가장 잘 맞는 숫자 객체에 대한 참조를 생성합니다.

정수에 대한 숫자 리터럴은 간단하게 연속한 10진수 숫자들이라고 할 수 있습니다. 하지만 기수 접두사를 써서 다른 밑수를 사용할 수도 있습니다. 아래 모든 리터럴들은 2018이라는 숫자 참조를 생성합니다.

0b11111100010 // 2진수
0o3742 // 8진수
2018.00 // 10진수
0x7E2 // 16진수

Infinity는 자바스크립트에서 표현하기에는 너무 큰 모든 숫자를 의미합니다.

NaN은 ‘Not a Number’를 뜻하는 특별한 값이지만, typeof 연산자는 NaN을 “number”로 표시하기 때문에 매우 헷갈립니다. NaN은 문자열을 숫자로 변환하려고 했으나 실패했을 때 결과값으로 반환될 수 있습니다. 변환에 실패한 경우 오류가 발생하는 대신 NaN이 반환됩니다. 산술 연산자 역시 입력 중에 NaN이 있으면 그 결과로 NaN을 반환합니다.

NaN과 NaN을 동등 연산자로 비교해 보면 false를 표시합니다. 이는 자바스크립트가 숨기지 않은 IEEE 754의 끔찍한 부분입니다. NaN에 대한 테스트는 다른 모든 숫자 값에 대한 equal 테스트와 다릅니다. 테스트 코드를 작성 할 때 문제가 될 수 있죠. 만약 테스트의 기댓값이 NaN이라면 실제 값이 NaN이라고 해도 항상 실패합니다.

값이 NaN인지 아닌지를 테스트하려면 Number.isNaN(value)를 사용하세요. Number.isFinite(value)는 값이 NaN, Infinity, 또는 -Infinity인 경우 false를 반환합니다.

Number

Number(number와 다릅니다)는 숫자를 만드는 함수입니다. 자바스크립트의 수는 불변 객체입니다. 수에 대한 typeof 연산자는 “number”를 반환합니다. Number 함수에는 new를 사용하면 안 됩니다.

const good_example = Number("432")
const bad_example = new Number("432")
typeof good_example // "number"
typeof bad_example // "object"
good_example === bad_example // false

Number가 포함하는 상수들을 통해 수가 어떻게 동작하는지 살펴볼 수 있습니다.

Number.EPSILON

1에 더했을 때 1보다 큰 수를 만들어 낼 수 있는 가장 작은 양수입니다. Number.EPSILON보다 작은 수를 1을 더해도 그 수는 1과 같습니다. 0이 아닌 양수를 1에 더했는데도 1이라는 사실은 IEEE 754를 포함한 모든 고정 크기 부동소수점 시스템이 가지고 있는 특징입니다. 숫자 표현의 크기를 고정함으로써 생기는 장단점이라고 볼 수 있습니다.

Number.MAX_SAFE_INTEGER

약 9천조입니다. 자바스크립트의 숫자형은 Number.MAX_SAFE_INTEGER까지의 모든 정수형을 표현할 수 있으므로 다른 정수형 타입이 필요 없습니다. 자바스크립트의 숫자형은 부호를 포함한 54비트를 사용합니다.

Number.MAX_SAFE_INTEGER보다 큰 수에 1을 더하는 것은 0을 더하는 것과 같습니다. 자바스크립트는 값이나 연산 결과, 그리고 중간 연산 값들이 전부 -Number.MAX_SAFE_INTEGER과 Number.MAX_SAFE_INTEGER 사이의 정수 값인 경우에만 올바른 정수 연산을 할 수 있습니다. 이를 벗어난다면 분배법칙, 결합법칙과 같은 수학적 연산이 보장되지 않아 숫자를 더하는 순서에 따라 그 합이 바뀌는 등의 연산 오류가 발생할 수 있습니다.

숫자가 안전한 범위 내에 있는 경우 true를 반환하는 Number.isSafeInteger(number)를 통해 숫자의 안정성을 확인할 수 있습니다. Number.isInteger(number)는 숫자가 안전한 범위 밖에 있어도 true를 반환합니다.

Number.MAX_VALUE

자바스크립트가 표현할 수 있는 가장 큰 숫자를 의미합니다. 값은 Number.MAX_SAFE_INTEGER * 2 ** 971 입니다.

Number.MAX_VALUE에 안전한 범위 안에 있는 어떤 양의 정수를 더해도 그 값은 여전히 Number.MAX_VALUE입니다. 계산 결과로 Number.MAX_VALUE를 만들어내는 프로그램은 뭔가 잘못되었을 가능성이 있습니다. Number.MAX_SAFE_INTEGER를 넘는 모든 결과가 미심쩍습니다. IEEE 754 표준은 넓은 범위를 보장해주지만, 실수로 이어지기 쉽습니다.

Number.MIN_VALUE

자바스크립트가 표현할 수 있는 영(0) 보다 큰 수 중 가장 작은 수를 의미합니다. 값은 2 ** -1074 입니다. Number.MIN_VALUE보다 작은 양수는 영(0)과 구별이 불가능합니다.

Math 객체

Math 객체는 Number에 내장되어 있어야 할 중요한 여러 함수를 포함하고 있습니다. 자바의 나쁜 영향 중 한 가지입니다. 삼각 함수, 대수 함수 외에도 연산자로 제공되었어야 할 여러 유용한 함수들이 있습니다.

Math.floor(-2.5) // -3
Math.trunc(-2.5) // -2

숫자 속의 괴물

자바스크립트는 10진 소수 값, 특히 화폐 단위의 처리가 안좋은 것으로 유명합니다. 0.1 또는 그 외 10진 소수 값을 프로그램에 입력하면 자바스크립트는 그 값을 제대로 처리하지 못합니다. 그래서 그 값을 정확히 표현할 수 있는 alias를 사용합니다.

프로그램에 10진수 소수 값을 입력하거나 10진수 소수 값이 포함된 데이터를 읽어들이는 것은 프로그램에 오류를 집어넣는 것과 같습니다. 0.3을 부동소수점의 구성 요소로 분해해보면 0.1 + 0.2와 다른 결과를 얻습니다. 또한 100 / 3은 33.333333333333336이라는 결과를 내놓습니다.

그 어떤 유한 시스템도 실수를 정확하게 표현할 수는 없지만 10진 소수점으로 이루어진 값을 통해 사람들이 필요로 하는 수의 값을 정확하게 표현하는 것은 가능합니다. 자바스크립트를 대체할 다음 세대 언어에서 10진 소수점 값을 정확하게 표현할 수 있는 단일 숫자 시스템을 지원하기 전까지는 최대한 안전한 정수 범위 내에서 작업하세요. 모든 화폐 값을 센트 (1/100 달러) 단위로 변환해서 처리하면 정확한 정수 값으로 처리할 수 있습니다. 비슷한 크기의 숫자끼리 더하면 다른 크기의 숫자를 더하는 경우에 비해 오류가 덜 발생합니다. 그렇기 때문에 부분의 합을 더하는 것이 개별 값을 전부 더하는 것보다 정확한 것입니다.

⬆ back to top

배열

자바스크립트의 배열은 객체이지만 현재 자바스크립트에서의 배열은 아래 네 가지 관점에서 객체와 다릅니다.

typeof 연산자는 배열에 대해서 “object”를 반환하는데, 잘못되었습니다. 그렇기 때문에 배열인지 확인하고자 한다면 Array.isArray(value)를 사용해야 합니다.

const what_is_it = new Array(1000);
typeof what_is_it // "object"
Array.isArray(what_is_it) // true

초기화

새로운 배열은 두 가지 방법으로 만들 수 있습니다.

let my_little_array = new Array(10).fill(0); // my_little_array is [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let same_thing = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
my_little_array === same_thing // false

my_little_arraysame_thing은 값이 같습니다. 하지만 객체와 마찬가지로 배열은 실제로 같은 배열인 경우에만 같다고 봅니다.

스택과 큐

shift 메서드는 pop 메서드와 비슷하지만, 배열의 마지막 요소 대신 0번째 요소를 제거하고 리턴합니다. unshift 메서드는 push와는 달리 배열의 가장 앞에 새로운 요소를 추가합니다. shiftunshift 메서드는 pop, push에 비해 많이 느린데, 이는 배열이 커질수록 심해집니다. shiftpush를 써서 배열의 가장 뒤에 새로운 아이템을 추가하고 가장 앞에서 아이템을 꺼내 쓰는 큐를 구현할 수 있습니다.

검색

indexOf 메서드는 인자로 전달받은 값을 배열의 0번째 요소부터 비교하면서 찾습니다. 일치하는 값을 가지는 배열의 요소를 찾으면 검색을 중단하고 해당 요소의 인덱스 값을 리턴합니다. 배열에 찾는 값이 없다면 -1을 리턴합니다.

lastIndexOf 함수는 indexOf와 비슷하지만 배열의 마지막 인덱스부터 검색합니다. indexOf와 마찬가지로 값을 찾지 못하면 -1을 리턴합니다.

includes 함수는 indexOf 함수와는 다르게 값이 있다면 true, 없다면 false를 리턴합니다.

reduce

reduce 메서드는 배열을 하나의 값으로 축약합니다. 두 개의 매개변수를 받는 함수를 인자로 받으며, 배열의 요소가 하나의 값이 될 때까지 전달받은 함수에 두 인자를 전달하여 계속 호출합니다.

reduce 메서드는 두 가지 방식으로 설계할 수 있습니다. 첫 번째 방식은 전달한 함수를 배열의 모든 요소에 대해 호출하도록 하는 것입니다. 이 경우 초기 값이 지정되어야 합니다.

function add(reduction, element) {
  return reduction + element;
}

let my_little_array = [3, 5, 7, 11];
let total = my_little_array.reduce(add, 0); // total is 26

add 함수가 리턴하는 값은 다음 add 함수 호출 시 reduction의 인자로 전달됩니다.

초기 값이 꼭 0 이어야 하는 것은 아닙니다. 곱셈 함수가 reduction이라면 초기 값은 1이어야 하고, Math.max 함수를 reduce에 전달한다면 초기 값은 -Infinity여야 합니다.

두 번째 방식으로 reduce를 사용하면 초기 값을 지정하지 않아도 되며 reduction 함수가 한 번 덜 호출됩니다. reduction 함수가 처음 호출 될 때 0번째와 1번째 요소를 인자로 받습니다. 0번째 요소가 초기 값이 되는 것입니다.

total = my_little_array.reduce(add); // 26

이 방식은 add 함수가 한 번 덜 호출되고, 초기화 값을 잘못 설정하여 문제가 생기는 일도 없습니다.

초기 reduce 값을 전달하면 전달한 함수는 배열의 모든 요소마다 호출하지만, 초기 reduce 값을 전달하지 않으면 0번째 배열 요소가 초기 축약 값으로 사용됩니다.

reduceRight 함수는 배열의 끝에서 시작한다는 것을 제외하면 reduce 함수와 동일합니다.