Skip to main content

Mocha에서 SpiderMonkey까지

1995년 전체와 1996년의 대부분 기간 동안, Brendan Eich는 Netscape에서 Javascript 엔진g1을 담당하는 유일한 풀타임 개발자였다. 1996년 8월 Netscape 3.0의 프로덕션 릴리즈에 포함된 Javascript 1.1은 여전히 주로 1995년 5월의 열흘간 만들어진 프로토타입에서 나온 코드로 구성되어 있었다. 이 릴리즈 이후 Eich는 엔진g의 기술 부채2를 청산하고 Javascript를 "더 깔끔한 언어"로 만드는 작업을 시작할 때라고 느꼈다. Netscape 경영진은 그가 언어 명세 작업을 하기를 원했다. 그들은 Javascript에 명세가 없다는 Microsoft의 비판에 민감했고 곧 시작될 표준화 작업 활동의 시작이 명세를 요구할 것으로 예상했다. Eich는 이에 저항했다. 그는 Mocha를 재구현하는 것부터 시작하고자 했다. 명세를 작성하려면 Mocha 구현을 신중하게 검토해야 했다. 그는 검토하면서 Mocha를 다시 작성하는 것이 가장 효율적일 것이라고 생각했다. 이는 또한 원본의 설계 실수를 명세에 넣어버리기 전에 고칠 수 있게 해줄 것이었다.

이 논쟁에 좌절한 Eich는 사무실을 떠나 2주 동안 집에서 일하면서 Javascript 엔진의 핵심부를 재설계하고 재구현했다. 그 결과는 더 빠르고, 더 신뢰할 수 있으며, 더 유연한 실행 엔진이었다. 그는 원래 Javascript의 값들을 discriminated uniong으로 표현했는데 이를 폐기하고 대신 원시값이 직접적으로 포함된 tagged pointer를 사용했다. 그리고 원래 엔진에는 포함되지 않았던 중첩 함수, 함수 표현식, switch문 등의 기능을 구현했다. 레퍼런스 카운팅 형식의 메모리 관리자는 마크 앤 스윕 가비지 컬렉터로 대체되었다.

Eich가 사무실로 돌아왔을 때, 새로운 엔진은 Mocha를 대체했다. Netscape의 원래 개발자 중 한 명인 Chris Houck가 Eich와 함께 Javascript 팀의 두 번째 풀타임 멤버로 합류했다. Houck는 새 엔진을 "“SpiderMonkeyg"로 명명했다. 이는 영화 Beavis and Butt-Head Do America에 나오는 외설적인 대사에서 따온 거였다[Judge et al. 1996]. Clayton Lewis가 팀에 매니저로 합류했고 Norris Boyd를 고용했다. 테크니컬 라이터 Rand McKinny가 Eich의 명세 작성을 돕기 위해 배정되었다.

Brendan Eich는 Netscape 4.0 릴리즈에 들어갈 Javascript 1.2를 계속 개선했다.그 첫번째 베타 릴리즈는 1996년 12월이었다. 정규 표현식은 1997년 4월 베타에 추가되었다. 다양한 플랫폼을 지원하는 Netscape 4의 프로덕션 릴리즈는 6월에 시작되어 1997년 하반기에 걸쳐 이루어졌다.

  • do
  • 문장 레이블과 레이블로의 breakcontinue
  • switch
  • 중첩 함수 선언(렉시컬 스코핑)
  • 함수 표현식(람다 표현식)
  • == 연산자의 자동 형변환 규칙 삭제
  • 실제로 객체 프로퍼티를 삭제하는 프로퍼티 삭제 연산자
  • 객체 리터럴
  • 배열 리터럴
  • 정규 표현식 리터럴
  • 정규 표현식 매칭을 위한 메서드를 가진 RegExp 객체
  • 모든 객체가 가지고 있는 __proto__ 의사 프로퍼티
  • 새로운 배열 메서드 push, pop, shift, unshift, splice, concat, slice
  • 새로운 문자열 메서드 charCodeAt
  • RegExp를 이용하는 fromCharCode(ISO latin-1), match, replace, search, substr, split
  • 함수의 arity 프로퍼티
  • 함수와 그 arguments가 서로 다른 객체가 되는 것
  • 함수의 형식 매개변수와 지역 스코프 선언은 함수의 arguments 객체를 통해 접근 가능
  • arguments 객체에 callee 속성 추가
  • watch/unwatch 함수
  • import/export 문과 signed script

그림 10. Javascript 1.2의 새 기능들

SpiderMonkey에 의해 구현된 Javascript 1.2 언어와 내장 라이브러리는 Javascript 1.0/1.1에 비해 상당한 개선을 이루었다. 그림 10은 Javascript 1.2의 주요한 새 기능들을 나열한 것이다[Netscape 1997c]. Javascript 1.2의 라이브러리 추가 사항 대부분은 다른 인기 있는 언어들의 기능에서 영향을 받은 것이다. 배열의 concat, slice 메소드는 Python의 시퀀스 연산에서 영감을 받았다. 배열의 push, pop, shift, unshift, splice는 비슷한 이름을 가진 Perl의 배열 함수를 직접적으로 따왔다. 문자열의 concat, slice, search 메소드는 Python에서 영향을 받았고, 문자열 match, replace, substr은 Perl에서 왔다. Java는 charCodeAt에 영향을 주었다. 정규 표현식과 문자열 매칭의 문법과 의미는 Perl에서 차용되었다.

문장 수준의 추가 사항들은 C 계열 언어에 익숙한 프로그래머들이 기대했지만 이전에는 빠져 있던 문장들을 제공했다. do 문은 C의 do 문의 문법과 유사한 의미를 직접 복제해왔다. Javascript 1.0에는 해당 기능이 빠져 있었다. 레이블 문과 break, continue에 레이블을 붙이는 것은 Java의 같은 기능을 모델로 삼았다. 해당 기능은 중첩된 반복문과 switch문에서 여러 중첩 레벨을 탈출하는 것을 가능하게 하고 비반복적인 코드 블럭에서 조기 탈출도 가능하게 한다. Javascript 1.2의 switch문은 C와 Java처럼 case 선택자 표현식의 컴파일 타임 평가를 포함하고 있었다[Eich et al. 1998, jsemit.c lines 757-776].

Javascript 1.0/1.1에서는 함수가 스크립트의 최상위 스코프에서만 전역 선언을 통해 정의될 수 있었다. Javascript 1.2는 다른 함수 내부에서 지역 선언을 사용해서 내부 함수를 정의할 수 있게 허용했다. 이런 내부 함수는 무제한으로 중첩될 수 있다. 내부 함수에는 렉시컬 스코프가 지정되며 내부 함수의 지역 선언은 외부 스코프에서 동일한 이름으로 선언된 것을 덮어쓴다. Javascript 1.0/1.1에서는 최상위 레벨의 varfunction 선언이 스크립트의 최상단으로 "끌어올려졌다(hoisted)". 그리고 함수의 지역적인 var 선언도 함수 본문의 시작 부분으로 끌어올려졌다. 그래서 변수와 함수가 선언되기 전에 참조하는 것이 가능했다. Javascript 1.2에서는 중첩된 function 선언도 해당 함수를 포함하는 외부 함수 본문의 시작 부분으로 끌어올려지게 되었다. 같은 이름을 가지는 function 선언이 2개 이상 있을 경우 이를 감싸는 함수 스코프의 소스코드 상에서 맨 마지막에 나오는 것이 이름에 바인딩된다.

Javascript 1.2는 함수 정의가 표현식 형태로 나타나는 것을 허용함으로써 람다 표현식을 제공했다. 이들은 "함수 표현식"이라고 불렸다. 그리고 함수 이름이 선택 사항인 것을 제외하고는 함수 선언과 문법적으로 동일하다. 만약 이름이 있으면 함수 표현식은 바인딩 목적으로 스코프 최상단으로 끌어올려진 function 함수 선언으로 취급된다. 함수 이름이 없는 함수 표현식은 익명 함수를 정의한다. 어느 경우에든, 함수 표현식의 런타임 평가는 새로운 클로저를 생성한다. arguments 객체에 callee 속성을 추가함으로써 이러한 클로저들이 자기 자신을 재귀적으로 참조할 수 있게 되었다.

배열 리터럴과 객체 리터럴3은 Python 언어의 유사한 기능에서 영감을 받았다. 배열 리터럴은 배열 객체의 요소를 생성하고 초기화하기 위한 간결한 문법을 제공한다. Javascript 프로그래머는 배열 리터럴을 사용하면 이렇게 작성할 수 있다.

var p2 = [1, 2, 4, 8, 16, 32, 64];

Javascript 1.2에서

원래 Javascript 1.1에서는 이렇게 해야 했다.

var p2 = new Array();
p2[0] = 1;
p2[1] = 2;
p2[2] = 4;
// etc.

Javascript 1.1에서

비슷하게 객체 리터럴은 객체를 생성하고 속성을 만들기 위한 간결한 문법을 제공했다. 객체 리터럴은 이렇게 작성할 수 있다.

var origin = { x: 0, y: 0 };

Javascript 1.2에서

원래 Javascript 1.0에서는 이렇게 해야 했다.

var origin = new Object();
origin.x = 0;
origin.y = 0;

Javascript 1.0에서

객체 리터럴과 함수 표현식의 조합은 클래스 없이도 메서드를 가지는 객체를 생성하는 것을 쉽게 만들었다. 예시로 다음과 같은 코드를 들 수 있다.

function Point(x, y) {
return {
x: x,
y: y,
distance: function (another) {
return Math.sqrt(
Math.pow(this.x - another.x, 2) + Math.pow(this.y - another.y, 2)
);
},
};
}

var origin = new Point(0, 0);
alert(origin.distance(new Point(5, 5)));

Javascript 1.2에서

객체 리터럴과 함수 표현식을 결합하면 프로토타입 객체를 더 편리하게 정의할 수도 있다. 또한 Javascript 프로그램이 객체의 프로토타입 객체 참조를 동적으로 접근하고 수정할 수 있도록 해주는 __proto__ 의사 속성(pseudo-property)를 추가했다. 이 속성을 사용하면 각 객체가 상속된 속성에 접근하기 위해서 사용하는 내부 참조를 동적으로 접근하고 수정할 수 있다4. __proto__를 사용하면 프로그램은 임의의 깊이의 상속 계층을 동적으로 구성하고 객체가 상속된 속성을 가져오는 출처도 동적으로 변경할 수 있다.

Javascript 1.2의 일부 변경 사항은 결국 좋지 못한 결정이었던 게 드러났다. importexport 문은 Netscape 4에서 제공된, Java와 호환 가능한 스크립트 서명 메커니즘(script signing mechanism)[Netscape 1997a]과 같이 사용하기 위해 만들어졌다. 서명된 스크립트에서 정의된 전역 변수들은 export 문을 사용해 명시적으로 내보낸 함수들을 제외하고 다른 스크립트에서는 접근할 수 없었다. (번역자: 지금의 private와 비슷한 기능으로 보인다) 이 기능은 Netscape 계열이 아닌 브라우저에서는 전혀 도입되지 않았다.

Javascript 1.0/1.1의 ==연산자의 자동 형변환 규칙은 사용자들의 요구사항이었음에도 불구하고, 일부 사용자들은 그 동작이 놀랍고 혼란스럽다는 것을 깨닫고 있었다. 브랜든 아이크는 Javascript 1.2에서 ==연산자의 자동 형변환 규칙 대부분을 제거함으로써 == 연산자를 고치기로 결정했다 [Netscape 1997d; Rein 1997]. 두 피연산자가 동일한 원시 타입(숫자, 문자열, 불리언, 객체)이 아니라면 ==false를 반환하도록 했다.

Javascript 1.2에서는 여러 변경사항들이 있었는데, 이렇게 Javascript 1.0, 1.1로부터의 변화들은 <script> 태그의 version 속성을 통해 다루어질 수 있을 것으로 희망되었다. 하지만 Javascript 1.2 프로덕션 릴리즈 시점까지 이 형태의 버전 관리는 웹 개발자들에게 이미 관리하기 어려운 것이 되어 버렸다[Rein 1997]. 자체적인 Javascript 구현을 가진 비-Netscape 계열 브라우저들에서도 작동해야 하는 웹 페이지들에게는 특히 더 그러했다.

Footnotes

  1. Javascript 커뮤니티에서 "엔진"이라는 용어는 Javascript의 언어 구현을 가리킨다. Javascript 엔진은 일반적으로 파서, 가상 머신이나 비슷한 런타임 지원, 가비지 컬렉터, 표준 라이브러리 구현과 다른 구성 요소들로 구성된다.

  2. "기술 부채"는 Brandan Eich가 회고하면서 사용한 용어다. Eich가 미뤄졌던 유지보수를 해야 했던 필요성을 나타내기 위해 1996년에 썼던 용어가 "기술 부채"인 건 아니다.

  3. Javascript 1.2 문서와 ES3 명세는 이를 "배열 이니셜라이저"와 "객체 이니셜라이저"라고 불렀다. 하지만 "리터럴"이라는 용어가 Javascript 프로그래머들에게 널리 퍼져 있고 여러 아티클과 책에서도 일반적으로 "리터럴"이라는 용어를 사용한다.

  4. __proto__ 의사 프로퍼티는 Self 언어의 parent 슬롯과 비슷하다.