ECMAScript 5 Strict Mode, JSON, and More

이글은 John Resig의 'ECMAScript 5 Strict Mode, JSON, and More'을 번역한 것이다.

ECMAScript 5 Strict Mode, JSON, and More

ES5에는 관심을 가져야 하는 기능과 API들이 많다. 그 중에서 Strict Mode와 native JSON 지원을 주목해야 한다.

Strict Mode

Script Mode는 ES5에 새로 추가됐다. 'Strict Mode'를 켜면 프로그램이나 함수는 Strict Context에서 동작한다. Strict Context에서는 저지를만한 실수를 미연에 방지해주고 좀 더 예외가 던저진다. 즉 사용자에게 좀 더 자세한 정보를 제공해서 사용자는 꼼꼼한 코딩 실무를 익힐 수 있다.

ES5는 ES3와 호환되지만 ES3의 기능중에서 Deprecated된 것들은 'Strict Mode'에서는 사용할 수 없다.

'Strict Mode'에서는:

  • 일반적인 코딩 실수를 발견하면 예외가 발생한다.
  • Global 객체에 접근하는 것 같이 덜 안전한 행위에 대해 에러를 던진다.
  • 불명확하고 미숙했던 기능들은 제거된다.

'Strict Mode'에 대한 정보의 대부분은 ES5 표준 #223 페이지에 나와 있다.

ES5의 'Strict Mode'와 Firefox의 'Strict Mode'는 다르다. Firefox에서는 about:config 페이지에서 javascript.options.strict 설정으로 'Strict Mode'를 끄고 켤수 있다. ES5의 것은 에러를 발생시킬 것 같은 것들을 지적해 주지만 Firefox의 것은 나쁜 습관인 것은 모두 지적 해준다.

How do you enable strict mode?

전역에 적용하고 싶으면 맨 첫 줄에 다음과 같이 넣으면 된다:

"use strict";

함수같이 특별한 context에서만 켜고 싶으면 다음과 같이 넣는다:

function imStrict(){
  "use strict";
  // ... your code ...
}

Strict Mode를 켜는데 사용한 문법을 자세히 보자. "use strict"는 단순히 스트링일 뿐이고 이를 위해 새로운 문법을 고안하지 않았다. 이 점은 굉징히 훌륭하고 나는 정말 이게 마음에 든다. 예전 브라우저에서 문제가 생길 것을 걱정할 필요없이 'Strict Mode'를 마음껏 사용할 수 있다.

다른 글에서도 누차 강조했었지만 ES5에서 문법 자체는 건드리지 않았다. 전혀 하위 호환성을 걱정할 필요가 없고 ES4를 사용하는 프로그램은 있을 수 없다. 즉, 'Strict Mode'를 당장 사용하는 것이 좋다.

함수에만 'Strict Mode'를 적용할 수 있기 때문에 다른 코드에 대한 걱정없이 JavaScript 라이브러리에 사용할 수 있다.

// Non-strict code...
(function(){
  "use strict";

  // Define your library strictly...
})();

// Non-strict code...

많은 라이브러리들은 코드 전체를 함수 하나로 감싸는 이 방식으로 구현했기 때문에 'Strict Mode'를 쉽게 적용할 수 있다.

'Strict Mode'를 켜면 많은 것이 달라진다.

Variables and Properties

foo = "bar";같은 표현은 불가능해진다. 기존에는 window.foo에 값을 할당하는 표현식이 였지만 이제는 예외가 던져진다. 기존의 표현식은 분명 까다로운 버그를 만들어 낸다.

다른 글에서 설명한 것처럼 writable 속성이 false인 프로퍼티의 값은 변경 할 수 없고, configurable 속성이 false인 프로퍼티를 삭제할 수 없고, extensible 속성이 false인 객체에 프로퍼티를 추가할 수 없다. 이런 걸 시도하면 에러가 발생한다. 과거에는 그냥 에러없이 조용히 넘어갔지만 이제는 에러가 발생한다.

변수, 함수, 인자를 해제(delete)하면 에러가 난다:

var foo = "test";
function test(){}
delete foo; // Error
delete test; // Error

function test2(arg) {
  delete arg; // Error
}

객체에 같은 프로퍼티를 여러개 정의하면 에러가 난다:

// Error
{ foo: true, foo: false }

eval

'eval'이라는 이름으로 무언가 만들 수 없다:

// All generate errors...
obj.eval = ...
obj.foo = eval;
var eval = ...;
for ( var eval in ... ) {}
function eval(){}
function test(eval){}
function(eval){}
new Function("eval")

그리고 eval() 함수로 변수를 정의하는 것도 안된다.

eval("var a = false;");
print( typeof a ); // undefined

Functions

arguments 객체에 뭔가 할당할 수 없다:

arguments = [...]; // not allowed

function( foo, foo ) {}처럼 동일한 인자를 두 개 만들 수 없다:

arguments.caller와 arguments.callee에 접근하면 에러가 발생한다. 그래서 anonymous 함수를 재사용 하고 싶으면 named 함수로 정의해야 한다:

setTimeout(function later(){
  // do stuff...
  setTimeout( later, 1000 );
}, 1000 );

특정 함수의 arguments와 caller 프로퍼티에 접근할 수 없다. 이 프로퍼티를 만들지 못 하도록 금지됐다:

function test(){
  function inner(){
    // Don't exist, either
    test.arguments = ...; // Error
    inner.caller = ...; // Error
  }
}

마지막으로 그토록 우리를 괴롭혔던 버그가 해결됐다. null이나 undefined가 global 객체 처럼 사용되면 예외가 난다. 'Strict Mode'에서는 이렇게 사용하지 못한다:

(function(){ ... }).call( null ); // Exception

with(){}

'Strict Mode'에서는 with(){} 구문을 사용할 수 없다. 확실히 오남용되고 있다고 하는데 나는 잘 모르겠다. 근거가 충분하다고 확신할 수 없다.

ES5의 Strict Mode는 많이 다르다. with를 제거하는 것처럼 스타일을 강제하는 것부터 literal 객체의 프로퍼티 조차 재정의할 수 있었던 것 같은 문제까지도 바로 잡았다. 사람들이 어떻게 사용할지 Javascript 개발을 어떻게 바꿔놓을지 살펴보는 것이 매우 재미 있을 것이다.

jQuery도 ES5의 Strict Mode를 따를 것이다. Strict Mode가 구현되고 충분히 검증되면 Strict Mode에 맞게 jQuery를 수정하는 일이 매우 즐거울 것 같다.

JSON

JSON을 navive로 지원하는 것이 ES5에서 두번째로 주목할만 하다.

나는 진작부터 이런 주장을 해왔기 때문에 표준에 추가된 것이 정말 기쁘다.

지금 당장 Crokford의 json2.js로 바꾸기라도 해야 한다. json2.js는 ES5와 호화되고 native JSON이 구현되면 쉽게 바로 적용할 수 있다. native는 정말 빠르다.

실제로 나는 어제 JSON.parse가 구현있으면 쓰도록 jQuery를 수정했다.

JSON을 다루는 메소드는 두 개다. 하나는 JSON 스트링을 JavasScript 객체로 바꾸는 메소드인 JSON.parse이고 다른 하나는 JavaScript 객체를 JSON 스트링으로 바꿔주는 JSON.stringify이다.

JSON.parse( text )

JSON 스트링을 JavaScript 객체로 바꿔준다:

var obj = JSON.parse('{"name":"John"}');
// Prints 'John'
print( obj.name );

JSON.parse( text, translate )

변환할 때 적당히 가공할 수 있다:

function translate(key, value) {
  if ( key === "name" ) {
    return value + " Resig";
  }
}
var obj = JSON.parse('{"name":"John","last":"Resig"}', translate);
// Prints 'John Resig'
print( obj.name );

// Undefined
print( obj.last );

JSON.stringify( obj )

객체를 JSON 스트링으로 바꾼다:

var str = JSON.stringify({ name: "John" });
// Prints {"name":"John"}
print( str );

JSON.stringify( obj, ["white", "list"])

바꿀 프로퍼티를 고를 수 있다:

var list = ["name"];
var str = JSON.stringify({name: "John", last: "Resig"}, list);
// Prints {"name":"John"}
print( str );

JSON.stringify( obj, translate )

prase처럼 translate 함수를 사용할 수 있다:

function translate(key, value) {
  if ( key === "name" ) {
    return value + " Resig";
  }
}
var str = JSON.stringify({"name":"John","last":"Resig"}, translate);
// Prints {"name":"John Resig"}
print( str );

JSON.stringify( obj, null, 2 )

결과물이 예쁘도록 space 문자를 넣을 수 있다:

var str = JSON.stringify({ name: "John" }, null, 2);
// Prints:
// {
//   "name": "John"
// }
print( str );

JSON.stringify( obj, null, "\t" )

space 문자 대신 그냥 원하는 스트링을 사용할 수 있다:

var str = JSON.stringify({ name: "John" }, null, "\t");
// Prints:
// {\n\t"name": "John"\n}
print( str );

기본 객체들에 몇 가지 추가된 메소드들도 있는데 별로 새롭진 않다. String, Boolean, Number의 .valueOf()와 같고 Date는 .toISOString()과 같다.

// Yawn...
String.prototype.toJSON
Boolean.prototype.toJSON
Number.prototype.toJSON
Date.prototype.toJSON

.bind()

함수의 context를 고정시키는 .bind() 메소드가 드디어 들어 왔다. Prototype 라이브러리의 .bind()와 본질적으로 동일하다.

Function.prototype.bind(thisArg, arg1, arg2...)

function context를 가리키는 this에 특정 객체를 고정하고 필요한 인자를 채워서 호출하면 된다:

var obj = {
  method: function(name){
    this.name = name;
  }
};
setTimeout( obj.method.bind(obj, "John"), 100 );

이 함수는 계속 기다려 왔던이기 때문에 정말 기쁘다.

Date

Date 객체를 ISO 형식의 String으로 상호 변환할 수 있게 됐다. 신이시여 정말 감사합니다. 짜잔~~

Date 생성자에 ISO 형식의 날자가 입력되면 파싱하여 그 날자로 객체를 초기화한다. 그리고 .toISOString()이라는 메소드도 생겨서 date 객체를 ISO 형식의 스트링으로 변환할 수 있다:

var date = new Date("2009-05-21T16:06:05.000Z");
// Prints 2009-05-21T16:06:05.000Z
print( date.toISOString() );

.trim()

이제 native로 구현됐다. 다른 라이브러리들과 완전히 동일하지만 더 빠르다.

Steven Levithan이 trim 메소드에 대해 깊게 고민했다.

Array

indexOf, lastIndexOf, every, some, forEach, map, filter, reduce, reduceRight 등등의 Array의 메소드들(번역)도 드디어 표준에 추가됐다.

그리고 isArray 메소드도 추가 됐다:

Array.isArray = function( array ) {
  return Object.prototype.toString.call( array ) === "[object Array]";
};

나는 ES5에 잘 만들어 졌다고 생각한다. ES4가 꿈꾸던 그런 것은 아니고 단지 버그를 수정하고 언어를 더 안전하고 빠르게 만든 것 뿐이지만 많은 이들이 ES5를 구현할 것이라고 생각한다.