New in JavaScript 1.7
Mozilla의 New In JavaScript 1.7를 정리한 것이다. 이 글의 내용은 아직 논쟁중이고 보편적이지 않아서 당장은 별 도움되지 않을 듯...
Generators and Iterators
개인적으로 closure가 있는데 Generator나 Iterator가 유용한지 조금 회이적이다.
Iterators
Iterator는 Iterator() 함수로 만들고:
var lang = { name: 'JavaScript', birthYear: 1995 };
var it = Iterator(lang);
next()를 호출하면 해당 key, value가 반환된다:
var pair = it.next(); // Pair is ["name", "JavaScript"]
pair = it.next(); // Pair is ["birthYear", 1995]
pair = it.next(); // A StopIteration exception is thrown
next()대신 for..in
으로도 사용할 수 있다. 여기에서도 StopIteration 예외가 발생하면 loop이 중지된다:
var it = Iterator(lang);
for (var pair in it)
print(pair); // prints each [key, value] pair in turn
key, value pair가 아니라 단순히 key만 iteration하고 싶으면 Interator()의 두번째 인자에 true를 넘긴다:
var it = Iterator(lang, true);
for (var key in it)
print(key); // prints each key in turn
Iterator()는 Array에도 OK:
var langs = ['JavaScript', 'Python', 'C++'];
var it = Iterator(langs);
for (var pair in it)
print(pair); // prints each [index, language] pair in turn
Array에서 Iterator()의 두번째 인자로 true를 주면 index를 iteration한다.
var langs = ['JavaScript', 'Python', 'C++'];
var it = Iterator(langs, true);
for (var i in it)
print(i); // prints 0, then 1, then 2
let 키워드로 block scoped 변수를 만들어 할당할 수 있다:
var langs = ['JavaScript', 'Python', 'C++'];
var it = Iterator(langs);
for (let [i, lang] in it)
print(i + ': ' + lang); // prints "0: JavaScript" etc.
Defining custom iterators
특정 범위의 수를 Iteration하는 Interator를 만들어 보자.
먼저 Range 객체를 만들고:
function Range(low, high){
this.low = low;
this.high = high;
}
low, high사이의 값을 순서대로 반환하고 끝나면 StopInteration 예외를 던지는 RangeIterator를 만든다:
function RangeIterator(range){
this.range = range;
this.current = this.range.low;
}
RangeIterator.prototype.next = function(){
if (this.current > this.range.high)
throw StopIteration;
else
return this.current++;
};
Range에 RangeIterator를 반환하는 메소드를 추가한다. iterator는 이를 위한 특별한 프로퍼티다.
Range.prototype.__iterator__ = function(){
return new RangeIterator(this);
};
만든 RangeIterator를 써보자:
var range = new Range(3, 5);
for (var i in range)
print(i); // prints 3, then 4, then 5 in sequence
Generators: a better way to build Iterators
Custom Iterator와 같은 것을 보다 쉽고 간결하게 작성할 수 있다. Generator는 Iterator의 Factory라고 볼 수 있는데 함수에 새로 추가된 yield 키워드를 사용하면 그 함수가 Generator가 된다.
yeild 키워드를 사용하려면 Script Block을 다음과 같이 선언해야 한다:
<script type="application/JavaScript;version=1.7"/>
Generator()를 호출하면 그 함수가 바로 실행되는 것이 아니라 generator-iterator 객체를 반환한다. 그리고 반환된 generator-iterator 객체의 next() 메소드를 호출할 때마다 다음 yield까지 실행한다. 함수가 끝나거나 return되면 StopIteration 예외를 던진다.
간단한 예제를 보자:
function simpleGenerator(){
yield "first";
yield "second";
yield "third";
for (var i = 0; i < 3; i++)
yield i;
}
var g = simpleGenerator();
print(g.next()); // prints "first"
print(g.next()); // prints "second"
print(g.next()); // prints "third"
print(g.next()); // prints 0
print(g.next()); // prints 1
print(g.next()); // prints 2
print(g.next()); // StopIteration is thrown
위에서 살펴봤던 RangeIterator의 구현을 Range의 prototype의 iterator 프로퍼트를 바로 구현하여 더 간단하게 구현할 수 있다:
function Range(low, high){
this.low = low;
this.high = high;
}
Range.prototype.__iterator__ = function(){
for (var i = this.low; i <= this.high; i++)
yield i;
};
var range = new Range(3, 5);
for (var i in range)
print(i); // prints 3, then 4, then 5 in sequence
이전 두 수의 합을 구하는 피보나치 수열을 Generator로 구현해보자:
function fibonacci(){
var fn1 = 1;
var fn2 = 1;
while (1){
var current = fn2;
fn2 = fn1;
fn1 = fn1 + current;
yield current;
}
}
var sequence = fibonacci();
print(sequence.next()); // 1
print(sequence.next()); // 1
print(sequence.next()); // 2
print(sequence.next()); // 3
print(sequence.next()); // 5
print(sequence.next()); // 8
print(sequence.next()); // 13
무한 수열을 구하는 것을 만들었지만 수열의 끝나도록 끝을 명시할 수 있다. Generator는 인자를 받을 수 있기 때문에 fibonacci()에 limit 인자를 추가한다:
function fibonacci(limit){
var fn1 = 1;
var fn2 = 1;
while (1){
var current = fn2;
fn2 = fn1;
fn1 = fn1 + current;
if (limit && current > limit)
return;
yield current;
}
위에서도 설명했지만 return하면 결국 StopIteration 예외를 던지는 것이다.
Advanced generators
next()함수를 적어도 한번 호출해서 Iteration을 시작하면 send()를 이용해서 특정 시점으로 강제로 지정할 수 있습니다. send(whatever)라고 호출하는 것은 이 전의 yield한 값으로 whatever로 사용하는 next()와 같습니다.
피보나치 Generator에 send()를 사용해보자:
function fibonacci(){
var fn1 = 1;
var fn2 = 1;
while (1){
var current = fn2;
fn2 = fn1;
fn1 = fn1 + current;
var reset = yield current;
if (reset){
fn1 = 1;
fn2 = 1;
}
}
}
var sequence = fibonacci();
print(sequence.next()); // 1
print(sequence.next()); // 1
print(sequence.next()); // 2
print(sequence.next()); // 3
print(sequence.next()); // 5
print(sequence.next()); // 8
print(sequence.next()); // 13
print(sequence.send(true)); // 1
print(sequence.next()); // 1
print(sequence.next()); // 2
print(sequence.next()); // 3
send(undefined)는 next()와 완전히 동일하고 지금 막 만든 Generator에서는(아직 next()를 한번도 호출하지 않은) 엄한 값이 사용되면 TypeError 예외를 던진다.
Generator를 종료할 때에는 close()를 사용한다:
var gen = generator();
function driveGenerator() {
if (gen.next()) {
window.setTimeout(driveGenerator, 0);
} else {
gen.close();
}
}
Array comprehensions
Array Comprehension은 다른 객체를 애용해 새로운 Array를 만들 때 사용할 수 있는 새로운 문법이다. 보통 map()과 filter()를 사용하는 코드대신 사용된다.
다음과 같은 코드를:
var numbers = [1, 2, 3, 4];
var doubled = numbers.map(function(i) { return i * 2; });
alert(doubled); // Alerts 2,4,6,8
Array Comprehension으로 바꿀 수 있다:
var numbers = [1, 2, 3, 4];
var doubled = [i * 2 for each (i in numbers)];
alert(doubled); // Alerts 2,4,6,8
filter()의 경우에도:
var numbers = [1, 2, 3, 21, 22, 30];
var evens = numbers.filter(function(i) { return i % 2 == 0; });
alert(evens); // Alerts 2,22,30
Array Comprehension을 사용할 수 있다:
var numbers = [1, 2, 3, 21, 22, 30];
var evens = [i for each (i in numbers) if (i % 2 == 0)];
alert(evens); // Alerts 2,22,30
map()과 filter()가 결합된 코드도 작성할 수 있다:
var numbers = [1, 2, 3, 21, 22, 30];
var doubledEvens = [i * 2 for each (i in numbers) if (i % 2 == 0)];
alert(doubledEvens); // Alerts 4,44,60
Array comprehension을 위한 [] 블럭은 scoping 블럭이다. let 키워드를 사용하여 변수를 정의한 것 처럼 [] 블럭 밖에서는 사용할 수 없다.
Array comprehension을 위해 사용하는 입력 데이터에 Iterator나 Generator도 사용할 수 있다.
String도 Array처럼 취급할 수 있기 때문에 사용할 수 있다:
var str = 'abcdef';
var consonantsOnlyStr = [c for each (c in str) if (!(/[aeiouAEIOU]/).test(c)) ].join(''); // 'bcdf'
var interpolatedZeros = [c+'0' for each (c in str) ].join(''); // 'a0b0c0d0e0f0'
Array comprehension의 결과는 Array이기 때문에 join()으로 다시 합쳤다.
Block scope with let
let으로 선언한 변수의 lifecycle은 해당 블록이다. var는 Global 변수를 선언하거나 함수의 Local 변수를 선언하는 것이다.
예제를 보자:
var a = 5;
var b = 10;
if (a === 5) {
let a = 4; // The scope is inside the if-block
var b = 1; // The scope is inside the function
console.log(a); // 4
console.log(b); // 1
}
console.log(a); // 5
console.log(b); // 1
Destructuring assignment
Destructuring assignment는 Array 같은 Collection을 통째로 할당하는 것을 말한다.
Destructuring assignment를 사용하면 임시 변수는 만들지 않고도 swap할 수 있다:
var a = 1;
var b = 3;
[a, b] = [b, a];
다음과 같이 변수를 할당하는 것도 가능하다:
var i = 0, j = 1;
[i, j] = [j, i + j];
리턴값을 여러개 넘길 수 있다:
function f() {
return [1, 2];
}
var a, b;
[a, b] = f();
document.write ("A is " + a + " B is " + b + "<br>\n");
리턴값중에서 몇개는 무시할 수도 있다.
function f() {
return [1, 2, 3];
}
var [a, , b] = f();
document.write ("A is " + a + " B is " + b + "<br>\n");
결과를 Array로도 받을 수 있다.
var a = f();
document.write ("A is " + a);
for loop에서도 사용할 수 있다:
let obj = { width: 3, length: 1.5, color: "orange" };
for (let [name, value] in Iterator(obj)) {
document.write ("Name: " + name + ", Value: " + value + "<br>\n");
}
Array안에 있는 객체의 프로퍼티에 직접 접근할 수도 있다.
var people = [
{
name: "Mike Smith",
family: {
mother: "Jane Smith",
father: "Harry Smith",
sister: "Samantha Smith"
},
age: 35
},
{
name: "Tom Jones",
family: {
mother: "Norah Jones",
father: "Richard Jones",
brother: "Howard Jones"
},
age: 25
}
];
for each (let {name: n, family: { father: f } } in people) {
document.write ("Name: " + n + ", Father: " + f + "<br>\n");
}
함수 인자에서도 사용할 수 있다:
function userId({id}) {
return id;
}
function whois({displayName: displayName, fullName: {firstName: name}})
console.log(displayName + " is " + name);
}
var user = {id: 42, displayName: "jdoe", fullName: {firstName: "John", lastName: "Doe"}};
console.log("userId: " + userId(user));
whois(user);
정규표현식에도 사용할 수 있다:
var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
if (!parsedURL)
return null;
var [, protocol, fullhost, fullpath] = parsedURL;
JavaScript 1.7 compatibility
브라우저 호환 테이블은 여기