ECMAScript5 또는 ES5는 2009년에 처음 발행되었고, 2011년에 명세상의 모호한 표현들을 수정한 ES5.1이 발행되었습니다. 이 글에서는, ES5가 가져온 변화들 중 주요한 기능들에 대해서 요약해 보았습니다.

(ECMAScript가 무엇인가에 대해서는 지난 포스팅에서 읽을 수 있습니다.)

ES5는 모든 모던 브라우저에서 지원되며, IE9는 strict mode 이외의 모든 기능을, I E10 이상은 모든 기능을 지원합니다.

use strict

엄격 모드를 활성화하는 키워드 use strict가 추가되었습니다. 엄격 모드를 적용하면 다음과 같은 제한이 적용됩니다.

  • var 키워드를 사용하지 않은 전역 변수 선언 금지 — flat = true;
  • 읽기 전용 프로퍼티의 값 바꾸기 금지 — var false = true;
  • 삭제할 수 없는 프로퍼티 삭제 금지 — delete Array.prototype
  • 객체에 중복된 프로퍼티 선언 금지 — var obj = { a: 1, a: 2 };
  • 함수에 중복된 파라미터를 사용 — function open(target, target)
  • 원시 값에 프로퍼티를 추가 — 'content'.size = 1;
  • with 키워드 사용 금지
  • eval 이 전역 변수를 만들지 않음 — eval('"use strict"; var x = true;')
  • eval, arguments 키워드가 오용되는 몇 가지 용례 금지 (MDN)
  • 미래에 ES 표준이 될 수 있는 키워드 사용 금지 — implements, interface, let, package, private, protected, public, static, yield

이들은 버그를 일으킬 수 있는 몇몇 패턴을 사전에 방지하며, JavaScript 엔진이 코드를 쉽게 최적화할 수 있도록 도와주는 역할을 합니다.

객체 (Object)

Object.create

프로토타입과 프로퍼티를 사용자 정의해서 새 객체를 만듭니다.

var obj1 = {};
var obj2 = Object.create(null);
console.log(obj1.__proto__); // { constructor: f, __defineGetter__: f, ... }
console.log(obj2.__proto__); // undefined

Object.defineProperty, Object.defineProperties

객체의 프로퍼티나 프로퍼티 설명자를 재정의합니다. 1개의 프로퍼티는 defineProperty를, 복수의 프로퍼티는 defineProperties를 사용합니다.

var obj = { name: 'students' };
Object.defineProperty(obj, 'count', { value: 10, enumerable: false });
for (var key in obj) console.log(key); // name

Object.getPrototypeOf

객체가 가진 프로토타입을 객체 형태로 반환합니다.

var obj = Object.create(Array.prototype);
console.log( Object.getPrototypeOf(x) ); // { constructor: f, concat: f, ... }

Object.getOwnPropertyDescriptor

객체의 프로퍼티 설명자를 반환합니다.

var obj = { name: 'rabbit' };
console.log( Object.getOwnPropertyDescriptor(obj, 'name') ); // { value: 'rabbit', writable: true ... }

Object.getOwnPropertyNames

객체의 모든 프로퍼티의 이름을 배열 형태로 반환합니다.

var obj = { name: 'fish', origin: 'Korea' };
Object.defineProperty(obj, 'count', { value: 10, enumerable: false });
console.log( Object.getOwnPropertyNames(obj) ); // ['fish', 'origin', 'count'];

Object.keys

객체의 열거 가능한(enumerable) 모든 프로퍼티의 이름을 배열 형태로 반환합니다.

var obj = { name: 'fish', origin: 'Korea' };
Object.defineProperty(obj, 'count', { value: 10, enumerable: false });
console.log( Object.keys(obj) ); // ['fish', 'origin'];

Object.freeze, Object.seal

객체의 프로퍼티와 값을 변경할 수 없는 상태로 지정합니다. 공통적으로 다음과 같은 효과를 냅니다.

  • 프로퍼티를 추가할 수 없습니다.
  • 프로퍼티를 삭제할 수 없습니다.
  • 프로퍼티의 값을 변경할 수 없습니다.
  • 프로토타입을 변경할 수 없습니다.
  • 프로퍼티의 유형을 데이터 프로퍼티와 접근자 프로퍼티(getter, setter) 사이에서 서로 변경할 수 없습니다.
  • 엄격 모드에서 위와 같은 시도를 할 경우 에러를 발생시킵니다.

두 메소드 사이엔 다음과 같은 차이점이 있습니다.

  • freeze는 프로퍼티의 값을 변경할 수 없지만 seal은 가능합니다.
  • freeze는 configurable, writable 설명자가 false로 설정되는 반면, seal은 true 상태를 유지합니다.
  • freeze된 객체는 Object.isSealed에 대해 true를 반환하지만, seal된 객체는 Object.isFrozen에 대해 false를 반환합니다.

양쪽 모두 종속되어 있는 하위 객체에는 (프로퍼티의 값이 객체 리터럴인 경우) 영향을 미치지 않습니다.

var source = {
    repository: {
        name: 'React',
        type: 'git',
    }
};
Object.freeze(source);
source.repository.name = 'Reactive'; // OK

MDN 문서에는 하위 객체까지 깊은 동결(deep freezing)을 하기 위한 제안 코드가 포함돼 있습니다.

function deepFreeze(object) {
  var propNames = Object.getOwnPropertyNames(object);

  for (let name of propNames) {
    let value = object[name];

    object[name] = value && typeof value === "object" ?
      deepFreeze(value) : value;
  }

  return Object.freeze(object);
}

Object.preventExtensions

객체에 새로운 프로퍼티를 추가할 수 없게 하지만, 기존 프로퍼티의 값을 변경하거나 제거할 수 있도록 합니다.

Object.isFrozen, Object.isSealed, Object.isExtensible

객체가 각각 freeze, seal, preventExtensions된 상태인지 여부를 Boolean 형태로 반환합니다.

배열 (Array)

Array.isArray

매개변수로 받은 값이 배열인지 여부를 불리언 형태로 반환합니다.

var numbers = [5, 0, -1];
var map = { 1: true, 2: false };
console.log( Array.isArray(numbers) ); // true
console.log( Array.isArray(map) ); // false

indexOf, lastIndexOf

매개변수로 받은 값이 배열 안에서 몇 번째 인덱스 위치에 존재하는지를 반환합니다.

var words = ['no', 'melon', 'no', 'lemon'];
console.log( words.indexOf('no') ); // 0
console.log( words.lastIndexOf('no') ); // 2
console.log( words.indexOf('apple') ); // -1

every

배열의 원소가 매개변수로 받은 조건 함수를 만족하는지를 Boolean 형태로 반환합니다.

var numbers = [5, 10, 15, 20];
var arePositive = numbers.every(function(n) {
    return n >= 0;
});
console.log(arePositive); // true

some

배열의 원소가 하나라도 매개변수로 받은 조건 함수를 만족하는지를 불리언 형태로 반환합니다.

var numbers = [5, 10, 15, -20];
var includesNegative = numbers.some(function(n) {
    return n < 0;
});
console.log(includesNegative); // true

forEach

매개변수로 받은 함수를 배열의 원소들에 대해 순서대로 실행합니다.

var numbers = [5, 10, 15];
numbers.forEach(function(n) {
    console.log(n); // 5, 10, 15
});

map

매개변수로 받은 함수를 배열의 원소들에 대해 순서대로 실행하고, 각 실행시마다 반환된 값들을 새로운 배열로 반홥합니다.

var numbers = [5, 10, 15];
var numbers10x = numbers.map(function(n) {
	return n * 10;
});
console.log(numbers10x); // [50, 100, 150];

filter

배열의 원소들 중 매개변수로 받은 조건 함수를 만족하는 원소들을 모아 새로운 배열로 반환합니다.

var numbers = [5, 10, 15, 20];
var greaterThan10 = numbers.filter(function(n) {
    return n < 10;
});
console.log(greaterThan10); // [15, 20]

reduce, reduceRight

배열의 원소들에 대해 매개변수로 받은 누적 함수를 실행하고 누적된 값을 반환합니다.

var words = ['you', 'and', 'me'];
var forewards = words.reduce(function(sentence, word) {
    return sentence + ' ' + word;
}, 'story of');
var backwords = words.reduceRight(function(sentence, word) {
    return sentence + word;
}, 'story of');
console.log(forewards); // 'story of you and me'
console.log(backwards); // 'story of me and you'

문자열 (String)

String 문자 접근

문자열을 객체처럼 인덱스값을 사용해 접근할 수 있습니다. 'word'[0] 구문을 사용해서 'word'.charAt(0)와 같은 값을 얻을 수 있습니다.

split

문자열을 매개변수로 받은 문자열 또는 정규식을 구분자로 해서 여러 개의 문자열로 나누고, 배열의 형태로 반환합니다.

var sentence = 'I love you.';
console.log( sentence.split(' ') ); // ['I', 'love', 'you.']
console.log( sentence.split(' love ') ); // ['I', 'you.']

trim

문자열의 양 끝에서 공백을 모두 제거합니다.

var input = ' Ludwig van Beethoven        ';
console.log( input.trim() ); // 'Ludwig van Beethoven'

Date 객체

Date.now

현재 UNIX 시간을 밀리세컨드 단위의 숫자로 반환합니다. new Date().getTime() 또는 +new Date 등과 같은 역할을 합니다.

var now = Date.now();
console.log(now); // 1613728800000

toJSON, toISOString

Date 객체를 문자열로 변환합니다.

  • toJSON은 내부에서 toISOString을 사용하므로 역할상의 차이는 없습니다.
  • 잘못된 날짜로 생성된 Date 객체일 경우 toJSONnull을, toISOString은 환경에 따라 에러를 발생시키거나 Invalid Date 문자열을 반환하는 등 다양한 반응을 일으킵니다.
var date = new Date();
var dateJSON = date.toJSON();
var dateISO = date.toISOString();
console.log(date); // Fri Feb 19 2021 19:00:00 GMT+0900 (대한민국 표준시)
console.log(dateJSON); // "2021-02-19T10:00:00.000Z"
console.log(dateISO); // "2021-02-19T10:00:00.000Z"

함수 (Function)

bind

함수의 컨텍스트를 나타내는 this를 재설정해 새로운 함수를 생성합니다.

var counter = {
    value: 0,
    print: function() {
        console.log(this.value);
    }
};

var printCount = counter.print;
var printCountOfCounter = counter.print.bind(counter);

console.log( counter.print() ); // 0
console.log( printCount() ); // undefined (window 컨텍스트에서 실행됨)
console.log( printCountOfCounter() ); // 0

JSON 객체

JSON 입력을 핸들링할 수 있는 JSON 객체가 도입되었습니다.

JSON.parse, JSON.stringify

JSON 문자열을 JavaScript 객체로 변환하거나, 반대로 JavaScript 객체를 JSON 문자열로 변환할 수 있습니다.

var json = '{ "name": "student", "count": 10 }';

var parsed = JSON.parse(json);
console.log(parsed); // { name: 'student', count: 10 }

var stringified = JSON.stringify(parsed);
console.log(stringified); // '{ "name": "student", "count": 10 }'

이러한 성질을 이용해, 시리얼화 가능한 객체의 깊은 복사를 구현할 수 있습니다.

var source = { name: 'ids', values: [0, 5, 7, 8, 11] };
var copy = JSON.parse(JSON.stringify(source));

console.log(source === copy); // false
console.log(source.name === copy.name); // true
console.log(
	copy.values.every(function(n) {
		return source.values.indexOf(n) !== -1;
	})
); // true