├── README-zh.markdown
└── README.md
/README-zh.markdown:
--------------------------------------------------------------------------------
1 | ##ECMAScript 6 in Node.JS
2 |
3 |
4 |
5 | 此文主要用一些简单的例子来介绍ECMAScript6(以下用ES6代替)的一些可以在Node中应用的特性。不需要转译器或[shim](http://www.cnblogs.com/ziyunfei/archive/2012/09/17/2688829.html)就可以运行这些例子。
6 |
7 |
8 | ES6的基本方案已确定。但是在正式发布前一些具体的语法会有改动,实验性的ES6在Node不一定会遵循最近的[草案](http://people.mozilla.org/~jorendorff/es6-draft.html)
9 |
10 |
11 | 假定不稳定版本分支0.11.x比稳定分支0.10.x有更好的ES6支持。为了简单的切换Node版本, 建议使用TJ的[n](https://github.com/visionmedia/n)去做版本管理。
12 |
13 |
14 |
15 | 只加标志`--harmony`可以使用大部分的ES6实验特性。至于v0.11.13,为了使用block scoping等例子,也需要加`--use_strict`标志。
16 |
17 |
18 | > 译者注:
19 | **在使用版本v0.11.9, 使用let语法时,必须使用`node --harmony --use_strict test.js`, 否则会报警`SyntaxError: Illegal let declaration outside extended mode`**
20 |
21 | 欢迎Pull requests,Enjoy~
22 |
23 |
24 | ## 內容目錄
25 | 1. [塊作用域](#block-scoping)
26 | 2. [遍歷器](#generators)
27 | 3. [代理](#proxies)
28 | 4. [Maps and Sets數據結構](#maps-and-sets)
29 | 5. [Weak Maps](#weak-maps)
30 | 6. [Symbols](#symbols)
31 |
32 |
33 | ##Block scoping(块作用域)
34 |
35 |
36 | 先看看`let`。你可以把`let`理解为`var`的块作用域变量,功能和var一样,是声明变量,只是`let`声明的变量只在它所在的代码块里有效。
37 |
38 | ```
39 | {
40 | let a = 'I am declared inside an anonymous block';
41 | console.log(a); // ReferenceError: a is not defined
42 | }
43 | ```
44 |
45 |
46 | ES6之前,Javascript 只有函数作用域。这其实是开发者hack的做法。下面将用两个例子来看看ES6带来的改进。
47 |
48 |
49 | 第一个例子是关于私有变量。
50 |
51 | ```
52 | // ES5: 复杂的函数闭包
53 | var login = (function ES5() {
54 | var privateKey = Math.random();
55 |
56 | return function(password) {
57 | return password === privateKey;
58 | };
59 | }())
60 | ```
61 | ```
62 | // ES6: 简单的块
63 | {
64 | let privateKey = Math.random();
65 | var login = function(password) {
66 | return password === privateKey;
67 | };
68 | }
69 | ```
70 |
71 | 第二个例子
72 |
73 | ```
74 | //ES5: DEfensive declarations at the top to avoid hoisting surprise
75 |
76 | function fibonacci(n) {
77 | var previous = 0;
78 | var current = 1;
79 | var i;
80 | var temp;
81 |
82 | for(i = 0; i < n; i += 1){
83 | temp = previous;
84 | previous = current;
85 | current = temp + current;
86 | }
87 | return current;
88 | }
89 | ```
90 |
91 |
92 | ```
93 | // ES6: variables are concdaled within the apporpriate block scope
94 | function fibonacci(n) {
95 | let previous = 0;
96 | let current = 1;
97 |
98 | for(let i = 0; i < n; i += 1) { // 在loop初始使用块作用域
99 | let temp = previous;
100 | previous = current;
101 | current = temp + current;
102 | }
103 |
104 | return current;
105 | }
106 | ```
107 |
108 |
109 | 第三个例子, 嵌套循环
110 |
111 | ```javascript
112 | //ES5: 一个变量名不能重用
113 | var counter = 0;
114 | for (var i = 0; i < 3; i+= 1) {
115 | for (var i = 0; i < 3; i += 1) {
116 | counter += 1;
117 | }
118 | }
119 |
120 | console.log(counter); // 输出 "3"
121 | ```
122 |
123 |
124 | ```javascript
125 | // ES6: 一个变量名可以重用
126 | var counter = 0;
127 | for (let i = 0; i < 3; i += 1) {
128 | for (let i = 0; i < 3; i += 1) {
129 | counter += 1;
130 | }
131 | }
132 |
133 | console.log(counter); 输出 "9"
134 | ```
135 |
136 |
137 | ES6的设计者也为`let`生了个妹妹。关键词`const`声明块作用域内的*静态*变量。
138 |
139 | ```
140 | const a = 'You shal remain constant!';
141 |
142 | a = 'I wanna be free'; // SyntaxError: Assignment to constant variable;
143 | ```
144 |
145 |
146 | 最后,ES6通过块作用域函数定义解决了一个疑难问题。 下面的代码在ES5中不能很好的定义。
147 |
148 |
149 | ```
150 | function f() { console.log('I am outside'); }
151 | (function () {
152 | if (false) {
153 | // 重新声明会怎么样?
154 | function f() { console.log('I am inside '); }
155 | }
156 | f();
157 | }());
158 | ```
159 |
160 | `f`二次声明被销毁?`if`块没有执行导致其被忽略?其作用域在`if`块里? 不同的浏览器有不同的表现。在ES6函数声明是块作用域,所以上面的代码输出`I am outside!`。
161 |
162 | 最后, 不能用`let`在同一个块作用域内对相同的变量声明多次,否则会抛出语法错误。在相同的函数域内使用`var`对一个变量多次声明不会抛出错误。
163 |
164 | ```
165 | {
166 | let a;
167 | let a; // 语法错误: Variable 'a' has already been declared
168 | }
169 | ```
170 |
171 |
172 |
173 | ##Generators(遍历器)
174 |
175 | 有了Generators,函数可以被"分片"执行。在每片的结尾暂停,在下一片开始恢复。Generators的语法与函数类似但是`function`被替代为`function*`.`yield`语句是控制程序顺序的。
176 |
177 |
178 | ```
179 | function* argumentsGenerator() {
180 | for (let i = 0; i < arguments.length; i += 1) {
181 | yield arguments[i];
182 | }
183 | }
184 | ```
185 |
186 |
187 | (注意虽然ES5中`yield`不是保留字符, 新的`function*`语法保证在ES6中非ES5函数使用“yield”作为变量将会出错)
188 |
189 |
190 | Generators可以返回迭代器。遍历器即带有`next`方法的对象,按顺序执行generators的主体。`next`方法, 被重复调用时, 其实是部分执行对应的产生器, 最终执行整个产生器主直到遇到`yield`关键字。
191 |
192 |
193 | ```
194 | var argumentsIterator = argumentsGenerator('a', 'b', 'c');
195 |
196 | // 输出 "a b c"
197 | console.log(
198 | argumentsIterator.next().value,
199 | argumentsIterator.next().value,
200 | argumnetsIterator.next().value
201 | );
202 | ```
203 |
204 | 只要对象的产生器主体还未`return`,迭代器的`next`方法返回一个带有`value`属性和`done`属性的对象。`value`属性就是被返回的值(或者说产生的值)。产生器主体未`return`前,`done`属性为`false`。产生器`return`时, `done`为`true`。如果`done`为`true`, `next`方法被调用,将会抛出错误。
205 |
206 |
207 | ES6有一些语法糖在遍历器上。
208 |
209 | ```
210 | // 输出 "a","b","c"
211 | for(let value of argumentsIterator) {
212 | console.log(value);
213 | }
214 | ```
215 |
216 |
217 | 有了Generators可以定义不能确定长度的队列....
218 |
219 |
220 | ```
221 | function* fibonacci() {
222 | let a = 0; b = 1;
223 | while(true) {
224 | yield a;
225 | [a, b] = [b, a + b];
226 | }
227 | }
228 | ```
229 |
230 |
231 | 可以优雅的遍历其值。
232 |
233 | ```
234 | // 遍历Fibonacci数字
235 | for(let value of fibonacci(){
236 | console.log(value);
237 | })
238 | ```
239 |
240 |
241 | Generators可以用来解决传统的嵌套式的控制流带来的烦恼,避免多重回调。 两个库, [task.js](https://github.com/mozilla/task.js)和[gen-run](https://github.com/creationix/gen-run),可以帮助你用同步的风格来写异步Javascript。
242 |
243 |
244 | ```
245 | // task.js example
246 | spawn(function*(){
247 | var data = yield $.ajax(url);
248 | $('#result').html(data);
249 | var status = $('#status').html('Download complete');
250 | yield status.fadeIn().promise();
251 | yield sleep(2000);
252 | status.fadeOut();
253 | });
254 | ```
255 |
256 | 顺序控制流可以用`try`-`catch`语句, 可以预见在ES6通过回调去错误处理会慢慢减少。
257 |
258 |
259 | 使用`yield*`,可以让产生器`yield`一个遍历器。
260 |
261 |
262 | ```
263 | let delegatedIterator = (function*(){
264 | yield 'Hello!';
265 | yield 'Bye';
266 | }());
267 |
268 | let delegatingIterator = (function* (){
269 | yield 'Greetings!';
270 | yield* delegatedIterator;
271 | yield 'OK, bye';
272 | }());
273 |
274 | // 输出 "Greetings!", "Hello!", "Bye!", "Ok, bye."
275 | for(let value of delegatingIterator) {
276 | console.log(value);
277 | }
278 | ```
279 |
280 |
281 | ##Proxies
282 |
283 |
284 | Proxy可以理解为一个园编程对象, 将原生的对象行为用函数调用来代替。包裹里的方法是与其相关的处理对象。
285 |
286 | ```
287 | var random = Proxy.create({
288 | get: function () {
289 | return Math.random();
290 | }
291 | })
292 | ```
293 |
294 |
295 | 函数`Proxy.create`创建一个'代理',其处理对象传给第一个参数。这里我们用`get`函数包裹去改写读属性。每次`random`,会有一个新的随机值。
296 |
297 | ```
298 | // 输出3个随机数
299 |
300 | console.log(random.value, random.value, random.value);
301 | ```
302 |
303 |
304 | 相似的, 赋值属性可以再创建`set`函数包裹
305 |
306 |
307 | ```
308 | var time = Proxy.create({
309 | get: function () {
310 | return Date.now();
311 | },
312 | set: function () {
313 | throw 'Time travel error1';
314 | }
315 | })
316 | ```
317 |
318 |
319 | 继续阐述对象怎样表现天然属性,我们创建一个数组,其可以像Python一样可以通过负索引取值。
320 |
321 |
322 | ```
323 | function pythonArray(array){
324 | var dummy = array;
325 | return Proxy.create({
326 | set: function (receiver, index, value) {
327 | dummy[index] = value;
328 | },
329 | get: function (receiver, index) {
330 | index = parseInt(index);
331 | return index < 0 ? dummy[dummy.length + index] : dummy[index];
332 | }
333 | })
334 | }
335 | ```
336 |
337 |
338 | 现在索引`-1`引用数组最后一个元素, `-2`为倒数第二个数,等等
339 |
340 |
341 | 注意`set`有3个元素;`receiver`指代理, `index`为属性名, `value`为属性值。
342 |
343 | ```
344 | // 输出 "gamma"
345 | console.log(pythonArray(['alpha', 'beta', 'gamma'])[-1]);
346 | ```
347 |
348 |
349 | 代理也可以用来清理数据绑定。用Backbon.JS 模型, 例如数据绑定结束的标志是必须使用`model.get`和`model.set`方法。这种句法结构在proxies是不必要的。
350 |
351 |
352 | 下面用一个复杂的安全例子来结束proxies。
353 |
354 |
355 | 假设一个函数`f`想要共享一个对象`o`给另外一个函数`g`。
356 |
357 | TODO(understand the example)
358 |
359 |
360 | ##Maps和sets数据结构
361 |
362 | map可以把它看作一个对象,其与普通对象的不同之处在于, 它的键值可以为任意的对象。在ES5中,`toString`隐式调用属性键值在属性获得值之前, 考虑到`({}.toString())`结果为`[Object Object]`, 可以将属性键值给为对象键值
363 |
364 |
365 | 下面举例来说
366 |
367 | ```
368 | const gods = [
369 | { name: 'Brendan Eich' },
370 | { name: 'Guido van Rossum' },
371 | { name: 'Raffaele Esposito' }
372 | ];
373 |
374 |
375 | let miracles = new Map();
376 |
377 | miracles.set(gods[0], 'Javascript');
378 | miracles.set(gods[1], 'Python');
379 | miracles.set(gods[2], 'Pizza Margherita');
380 |
381 | // 输出 "Javascript"
382 | console.log(miracles.get(gods[0]));
383 | ```
384 |
385 |
386 | set是一种包含有限元素集合的数据结构。每个值只能出现一次。构造函数是`Set`, api很简单
387 |
388 |
389 | ```
390 | // 输出 ['constructor', 'size', 'add', 'has', 'delete', 'clear']
391 | console.log(Object.getOwnPropertyNames(Set.prototype));
392 | ```
393 |
394 |
395 | 为了举例验证, 我们调查了6个人生活中最快乐的事。
396 |
397 | ```
398 | let surveyAnswers = ['sex', 'sleep', 'sex', 'sun', 'sex', 'cinema'];
399 | let pleasures = new Set();
400 | surveyAnswers.forEach(function(pleasure) {
401 | pleasures.add(pleasure);
402 | });
403 | // 输出pleasures的数目 4, 排除了重复值
404 | console.log(pleasures.size);
405 | ```
406 |
407 |
408 | 不幸的是,目前只支持array和set间的转换,set 遍历还未支持。
409 |
410 | maps和sets本该是两种正常使用的数据结构, 但是一直是用对象来代替的。下面将讨论weak maps,这种数据结构是不能用ES5去实现的。
411 |
412 |
413 | ##Weak maps
414 |
415 |
416 | Weak maps 相对于maps和sets已经是两个不同的概念了,因为其从根本来上来说已经不仅仅是语法糖了,Weak maps 像maps但是与垃圾回收紧密相连,它提供了一个工具可以让写出来的代码不会内存泄漏.
417 |
418 |
419 | Node的Javascript虚拟机, V8定期释放不在作用域里的对象。 如果当前作用域没有对其的引用, 可以说这个对象不再在作用域内。如果这个对象在作用域内, 但是不再使用, 就认为是内存泄漏。这样的内存泄漏周期性的重复时, V8不断地给其分配内存, 最终会让程序崩溃。
420 |
421 |
422 | 在weak map里设定一对键值,属性键值并没有引用。键名是对象的弱引用,意味着从v8垃圾回收的角度考虑, 他们是被忽略的。表现出来的特征, 键名是不可枚举的。weak maps也没有`size`属性,而其与maps很类似, 说明这些特性与v8垃圾回收忽略有关。
423 |
424 |
425 | ```
426 | let weakMap = new WeakMap();
427 |
428 | // 这句实际上是个空指令, 键值会立即被回收
429 | weakMap.set({}, 'noise');
430 | ```
431 |
432 |
433 | WeakMap的应用在于, 键所对应的对象被回收后, WeakMap自动移除对应的键值对。
434 |
435 |
436 | ```
437 | let userMetadata = new WeakMap();
438 |
439 | server.on('userConnect', function(user){
440 | userMetadata.set(user, { connectionTime: Date.now() });
441 | server.on('userDisconnect', function() {
442 | 使用user对象, 然后忽略它, `metadata`自动被抛弃
443 | });
444 | });
445 | ```
446 |
447 |
448 | 因为字面量直接赋值,所以字面量是不可以作为weak map的键名。
449 |
450 | ```
451 | let weakMap = new WeakMap();
452 |
453 | // TypeError: Invalid value used as weak map key
454 | weakMap.set('example string literal', 0);
455 | ```
456 | Javascript代码,没有方法确定对象是否被回收。要么选择相信虚拟机是对的, 或者, 虚拟机可以用debugger工具调试, [node-inspector](https://github.com/node-inspector/node-inspector) 。
457 |
458 |
459 | ##Symbols
460 |
461 |
462 | Symbols 扩展了键名值的范围, 例如 对象属性标志符。在ES5中, 键名值明确对应了字符窜集合。
463 |
464 | ```
465 | let a = {};
466 | let debugSymbol = Symbol();
467 | a[debugSymbol] = 'This property value is indentified by a symbol';
468 | ```
469 |
470 | > 译者注: 上例可以看出,Symbols是和String并列的, 是一种原始属性。下例验证
471 |
472 |
473 | ```
474 | a = Symbol();
475 | // 输出 symbol
476 | console.log(typeof(a));
477 | ```
478 |
479 |
480 | Symbols构造函数决定了它是独一无二的, 这样也避免了命名冲突。唯一的意思可以理解为: 同时创建的两个symbol绝不会引用相同的对象属性。
481 |
482 |
483 | 在ES5中, 命名冲突只能靠不用相同的命名来解决。下面的代码来自Jquery源码也说明了这点
484 |
485 | ```
486 | jQuery.extend({
487 | // 页面里每次的jQuery的复制都是独一无二的。
488 | // 非数字被移走为了匹配
489 | expando: "jQuery" + (core_version + Math.random()).replace(/\D/g, "");
490 | });
491 | ```
492 |
493 |
494 | 唯一性得到了些有趣的结果。
495 |
496 |
497 | ```
498 | let a = Map();
499 | a.set(Symbol(), 'Noise');
500 | // 输出 1, 虽然直到有一个元素, 但是这个元素不可能取得出来
501 | console.log(a.size);
502 | ```
503 |
504 |
505 | 为了清楚的说明symbol, 给symbol赋一个不变的名字。
506 |
507 | ```
508 | let testSymbol = Symbol('This is a test');
509 | // 输出 "This is a test"
510 | console.log(testSymbol.name);
511 | ```
512 |
513 |
514 | TODO: 讨论私有symbols的应用
515 |
516 |
517 | ##Object observation (对象)
518 |
519 |
520 | TODO
521 |
522 |
523 |
524 | ##Typed arrays and array buffers(数组)
525 |
526 |
527 | TODO
528 |
529 |
530 |
531 | ##总结
532 |
533 |
534 | 直到v0.11.9, 很多ES6特性已经可以用了,包括 symbols , weakmaps, proxies。 我们也期望即将到来的如 rest, spread操作符, 重组和类可以被加入Nodejs...
535 |
536 |
537 |
538 |
539 |
540 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ECMAScript 6 in Node.JS
2 | ===
3 |
4 | This text introduces and illustrates, with simple examples, ECMAScript 6 (ES6 for short) features natively available in Node. No transpiler or shim is required to run the code snippets. We hope the reader finds the subset of ES6 presented here interesting.
5 |
6 | The underlying philosophy and broad direction of ES6 has mostly been agreed upon. However, implementation details are being polished until the final specification is published. Experimental ES6 in Node may not comply with the latest draft specification, available [here](http://people.mozilla.org/~jorendorff/es6-draft.html).
7 |
8 | We assume the unstable 0.11.x branch, which has greater ES6 support than the stable 0.10.x branch. For version control, we recommend [n](https://github.com/visionmedia/n). To list the flags enabling experimental ES6 in Node, use ```node --v8-options | grep harmony```.
9 |
10 | The single `--harmony` flag enables most of the ES6 experimental features. As of v0.11.13, you need the `--use_strict` flag for the block scoping examples.
11 |
12 | Pull requests are welcome. Enjoy.
13 |
14 | ## Table of Contents
15 | 1. [Block Scoping](#block-scoping)
16 | 2. [Generators](#generators)
17 | 3. [Proxies](#proxies)
18 | 4. [Maps and Sets](#maps-and-sets)
19 | 5. [Weak Maps](#weak-maps)
20 | 6. [Symbols](#symbols)
21 |
22 | Block scoping
23 | ---
24 | Let us start with `let`. Think of `let` as a block-scoped variation of `var` for variable declaration.
25 |
26 | ```javascript
27 | { let a = 'I am declared inside an anonymous block'; }
28 | console.log(a); // ReferenceError: a is not defined
29 | ```
30 |
31 | Up until ES6, JavaScript only had function scoping, largely considered a design flaw. We illustrate improvements brought by ES6 with three examples.
32 |
33 | The first example is about private variables.
34 |
35 | ```javascript
36 | // ES5: A convoluted function closure
37 | var login = (function ES5() {
38 | var privateKey = Math.random();
39 |
40 | return function (password) {
41 | return password === privateKey;
42 | };
43 | }());
44 | ```
45 | ```javascript
46 | // ES6: A simple block
47 | {
48 | let privateKey = Math.random();
49 |
50 | var login = function (password) {
51 | return password === privateKey;
52 | };
53 | }
54 | ```
55 |
56 | The second example has to do with variable hoisting.
57 |
58 | ```javascript
59 | // ES5: Defensive declarations at the top to avoid hoisting surprises
60 | function fibonacci(n) {
61 | var previous = 0;
62 | var current = 1;
63 | var i;
64 | var temp;
65 |
66 | for(i = 0; i < n; i += 1) {
67 | temp = previous;
68 | previous = current;
69 | current = temp + current;
70 | }
71 |
72 | return current;
73 | }
74 | ```
75 | ```javascript
76 | // ES6: Variables are concealed within the appropriate block scope
77 | function fibonacci(n) {
78 | let previous = 0;
79 | let current = 1;
80 |
81 | for(let i = 0; i < n; i += 1) { // Implicit block scope for the loop header
82 | let temp = previous;
83 | previous = current;
84 | current = temp + current;
85 | }
86 |
87 | return current;
88 | }
89 | ```
90 |
91 | The third example is regarding nested for loops.
92 |
93 | ```javascript
94 | // ES5: Reusing the same loop variable name is bad
95 | var counter = 0;
96 | for(var i = 0; i < 3; i += 1) {
97 | for(var i = 0; i < 3; i += 1) {
98 | counter += 1;
99 | }
100 | }
101 |
102 | console.log(counter); // Oops, prints "3"
103 | ```
104 | ```javascript
105 | // ES6: Reusing the same loop variable name is OK
106 | var counter = 0;
107 | for(let i = 0; i < 3; i += 1) {
108 | for(let i = 0; i < 3; i += 1) {
109 | counter += 1;
110 | }
111 | }
112 |
113 | console.log(counter); // Prints "9"
114 | ```
115 |
116 | The designers of ES6 conceived of a baby sister for `let`. The keyword `const` declares block-scoped *constant* variables.
117 |
118 | ```javascript
119 | const a = 'You shall remain constant!';
120 |
121 | a = 'I wanna be free!'; // SyntaxError: Assignment to constant variable
122 | ```
123 |
124 | On a more esoteric note, ES6 is fixing a long standing issue with block scope function definitions. The following code is not well defined in the ES5 specification.
125 |
126 | ```javascript
127 | function f() { console.log('I am outside!'); }
128 | (function () {
129 | if(false) {
130 | // What should happen with this redeclaration?
131 | function f() { console.log('I am inside!'); }
132 | }
133 |
134 | f();
135 | }());
136 | ```
137 |
138 | Should the redeclaration of `f` be hoisted? Should it be ignored because the `if` block is not executed? Should it be scoped to the `if` block? Different browsers handle things differently. In ES6 function declarations are block-scoped, so the above snippet will print `I am outside!`.
139 |
140 | Finally, ES6 throws a syntax error when multiple `let` declarations of the same variable occur in the same block. No analogous error is thrown for `var` redeclarations within the same scope.
141 |
142 | ```javascript
143 | {
144 | let a;
145 | let a; // SyntaxError: Variable 'a' has already been declared
146 | }
147 | ```
148 |
149 | Generators
150 | ---
151 | Generators allow for function-like behaviour where execution is segmented into "pieces". Execution is paused at the end of each piece and can be resumed at the start of the next piece. The syntax for generators is similar to that of functions but the `function` keyword is replaced by `function*`. Flow control is dictated with `yield` statements.
152 |
153 | ```javascript
154 | function* argumentsGenerator() {
155 | for (let i = 0; i < arguments.length; i += 1) {
156 | yield arguments[i];
157 | }
158 | }
159 | ```
160 |
161 | (Note that although the `yield` keyword is *not* a reserved keyword in ES5, the new `function*` syntax guarantees no ES5 function using "yield" as a variable name will break in ES6.)
162 |
163 | Generators are useful because they return (i.e. create) iterators. In turn, an iterator, an object with a `next` method, actually executes the body of generators. The `next` method, when repeatedly called, partially executes the corresponding generator, gradually advancing through the body until a `yield` keyword is hit.
164 |
165 | ```javascript
166 | var argumentsIterator = argumentsGenerator('a', 'b', 'c');
167 |
168 | // Prints "a b c"
169 | console.log(
170 | argumentsIterator.next().value,
171 | argumentsIterator.next().value,
172 | argumentsIterator.next().value
173 | );
174 | ```
175 |
176 | The `next` method of an iterator returns an object with a `value` property and a `done` property, as long as the body of the corresponding generator has not `return`ed. The `value` property refers the value `yield`ed or `return`ed. The `done` property is `false` up until the generator body `return`s, at which point it is `true`. If the `next` method is called after `done` is `true`, an error is thrown.
177 |
178 | Alongside generators and iterators, ES6 has syntactic sugar for iteration.
179 |
180 | ```javascript
181 | // Prints "a", "b", "c"
182 | for(let value of argumentsIterator) {
183 | console.log(value);
184 | }
185 | ```
186 |
187 | Generators are ideal for defining sequences of undetermined lengths...
188 |
189 | ```javascript
190 | function* fibonacci() {
191 | let a = 0, b = 1;
192 |
193 | while(true) {
194 | yield a;
195 | [a, b] = [b, a + b];
196 | }
197 | }
198 | ```
199 |
200 | ...which are elegantly enumerated.
201 |
202 | ```javascript
203 | // Enumerates the Fibonacci numbers
204 | for(let value of fibonacci()) {
205 | console.log(value);
206 | }
207 | ```
208 |
209 | Generators can be used to provide an alternative to the traditional nested callback flow control, thereby avoiding callback "pyramids" and "hell". Two libraries, [task.js](https://github.com/mozilla/task.js) and [gen-run](https://github.com/creationix/gen-run), aim to help write asynchronous JavaScript in a sequential style.
210 |
211 | ```javascript
212 | // task.js example
213 | spawn(function*() {
214 | var data = yield $.ajax(url);
215 | $('#result').html(data);
216 | var status = $('#status').html('Download complete.');
217 | yield status.fadeIn().promise();
218 | yield sleep(2000);
219 | status.fadeOut();
220 | });
221 | ```
222 |
223 | The sequential flow control also allows for meaningful `try`-`catch` statements, so the burden of explicitly passing errors through the callback chain, commonly found in Node libraries, can be alleviated in ES6.
224 |
225 | To conclude, it is possible for a generator to `yield` to an iterator using a "delegated `yield`" with the syntax `yield*`.
226 |
227 | ```javascript
228 | let delegatedIterator = (function* () {
229 | yield 'Hello!';
230 | yield 'Bye!';
231 | }());
232 |
233 | let delegatingIterator = (function* () {
234 | yield 'Greetings!';
235 | yield* delegatedIterator;
236 | yield 'Ok, bye.';
237 | }());
238 |
239 | // Prints "Greetings!", "Hello!", "Bye!", "Ok, bye."
240 | for(let value of delegatingIterator) {
241 | console.log(value);
242 | }
243 | ```
244 |
245 | Proxies
246 | ---
247 |
248 | A proxy is a meta-programming object for which some primitive object behaviours are replaced by function calls, called traps. The traps are methods in an associated handler object.
249 |
250 | ```javascript
251 | var random = Proxy.create({
252 | get: function () {
253 | return Math.random();
254 | }
255 | });
256 | ```
257 |
258 | The function `Proxy.create` creates a proxy whose handler object is passed as the first argument. Here, we have a single `get` trap which overrides property reads. For each read of `random`, a new random value is computed at run time.
259 |
260 | ```javascript
261 | // Prints three random numbers
262 | console.log(random.value, random.value, random.value);
263 | ```
264 |
265 | Similarly, property writes can be trapped by the `set` trap.
266 |
267 | ```javascript
268 | var time = Proxy.create({
269 | get: function () {
270 | return Date.now();
271 | },
272 | set: function () {
273 | throw 'Time travel error!';
274 | }
275 | });
276 | ```
277 |
278 | The continue with the theme of dictating how objects should behave at the "native" level, we build arrays for which negative indexes behave as in Python.
279 |
280 | ```javascript
281 | function pythonArray(array) {
282 | var dummy = array;
283 |
284 | return Proxy.create({
285 | set: function (receiver, index, value) {
286 | dummy[index] = value;
287 | },
288 | get: function (receiver, index) {
289 | index = parseInt(index);
290 | return index < 0 ? dummy[dummy.length + index] : dummy[index];
291 | }
292 | });
293 | }
294 | ```
295 |
296 | Now the index `-1` references the last array element, `-2` the penultimate, etc.
297 |
298 | Notice `set` has three arguments; `receiver` refers to the proxy, `index` to the property name and `value` to the property value.
299 |
300 | ```javascript
301 | // Prints "gamma"
302 | console.log(pythonArray(['alpha', 'beta', 'gamma'])[-1]);
303 | ```
304 |
305 | Proxies can also be used for clean data binding. With Backbone.JS models, for example, data binding is done at the expense of having to use the `model.get` and `model.set` methods. This syntactic indirection is unnecessary with proxies.
306 |
307 | Let's conclude proxies with a somewhat sophisticated security example. Please put your abstraction hat on.
308 |
309 | Suppose a function `f` wants to share an object `o` to another function `g` and later revoke access to `o`. Well, `f` can give `g` a proxy `p` to `o`. The traps of `p` grant or deny access based on a key `k` private to `f`. Actually, a proxy `q` on the handler object `h` of `p` can implement the access mechanism for all the traps of `p` simultaneously with a single `get` trap.
310 |
311 | To recap, `g` interfaces through `p` to access `o`. The relevant trap of `p` in `h` is called triggering the `get` trap of `q` in charge of access control based on `k`.
312 |
313 | TODO: Add examples which are not `get` or `set`.
314 |
315 | Maps and sets
316 | ---
317 |
318 | A map can be thought of as a object for which the keys can be arbitrary objects. In ES5, the method `toString` is implicitly called on property keys before a property access, which is less than helpful for object keys given that `({}.toString())` is `[object Object]`.
319 |
320 | Let's illustrate...
321 |
322 | ```javascript
323 | const gods = [
324 | {name: 'Douglas Crockford'},
325 | {name: 'Guido van Rossum'},
326 | {name: 'Raffaele Esposito'}
327 | ];
328 |
329 | let miracles = new Map();
330 |
331 | miracles.set(gods[0], 'JavaScript');
332 | miracles.set(gods[1], 'Python');
333 | miracles.set(gods[2], 'Pizza Margherita');
334 |
335 | // Prints "JavaScript"
336 | console.log(miracles.get(gods[0]));
337 | ```
338 |
339 | A set is a data structure containing a finite set of elements, each occurring exactly once. The constructor is `Set`, and the API is simple.
340 |
341 | ```javascript
342 | // Prints [ 'constructor', 'size', 'add', 'has', 'delete', 'clear' ]
343 | console.log(Object.getOwnPropertyNames(Set.prototype));
344 | ```
345 |
346 | To illustrate, we have surveyed six people regarding their greatest pleasure in life...
347 |
348 | ```javascript
349 | let surveyAnswers = ['sex', 'sleep', 'sex', 'sun', 'sex', 'cinema'];
350 |
351 | let pleasures = new Set();
352 | surveyAnswers.forEach(function (pleasure) {
353 | pleasures.add(pleasure);
354 | });
355 |
356 | // Prints the number of pleasures in the survey, not counting duplicates
357 | console.log(pleasures.size);
358 | ```
359 |
360 | Unfortunately, support for array to set conversion, as well as set iteration is not yet supported in Node.JS.
361 |
362 | Both maps and sets are naturally occurring data structures which can and have been implemented through object abuse. We now discuss weak maps, which are data structures which *cannot* be emulated with ES5.
363 |
364 | Weak maps
365 | ---
366 |
367 | Weak maps fit in a somewhat different category to that of maps and sets because weak maps fundamentally provide more than just syntax sugar. Weak maps are like maps but are intimately linked to garbage collection, providing a tool to help writing code that does not leak memory.
368 |
369 | The JavaScript virtual machine, V8 in the case of Node, periodically frees memory allocated to objects no longer in scope. An object is no longer in scope if there is no chain of references from the current scope leading to it. If an object is held in scope, but never gets used, we say there is a memory leak. Such leaks are especially problematic when they occur periodically, as the memory allocated by the virtual machine increases over time, eventually breaking things.
370 |
371 | By setting a key-value pair in a weak map, no reference to the property *key* is created. Instead, references to keys which are internal to weak maps can be thought as "weak", meaning that from the point of view of the garbage collector they are ignored. In particular, property keys of a weak map cannot be enumerated. Also, weak maps do not have a `size` property analogous to maps as this would expose garbage collector behaviour which should be kept hidden.
372 |
373 | ```javascript
374 | let weakMap = WeakMap();
375 |
376 | // This is effectively a noop, the key-value pair can immediately be garbage collected
377 | weakMap.set({}, 'noise');
378 | ```
379 |
380 | Weak maps allows for "meta-data" to be associated to an object without obstructing garbage collection would the object otherwise escape the current scope.
381 |
382 | ```javascript
383 | // Expose user metadata to the rest of the program
384 | let userMetadata = WeakMap();
385 |
386 | server.on('userConnect', function (user) {
387 | userMetadata.set(user, { connectionTime: Date.now() });
388 |
389 | server.on('userDisconnect', function () {
390 | // Do stuff with `user` and discard of it, automatically discarding `userMetadata`
391 | });
392 | });
393 | ```
394 |
395 | Because literals in JavaScript are passed by *value* (instead of by *reference*), literals cannot be weak map keys.
396 |
397 | ```javascript
398 | let weakMap = WeakMap();
399 |
400 | // TypeError: Invalid value used as weak map key
401 | weakMap.set('example string literal', 0);
402 | ```
403 |
404 | From the point of view of JavaScript code, there is no way to confirm that objects have been garbage collected. One has to have faith that the virtual machine is doing its job properly. Alternatively, the guts of the virtual machine can be inspected with a debugger, such as [node-inspector](https://github.com/dannycoates/node-inspector).
405 |
406 | Symbols
407 | ---
408 |
409 | Symbols extend the set of key values, i.e. object property identifiers. In ES5 the set of key values corresponds precisely to the set of strings.
410 |
411 | ```javascript
412 | let a = {};
413 | let debugSymbol = Symbol();
414 |
415 | a[debugSymbol] = 'This property value is identified by a symbol';
416 | ```
417 |
418 | Symbols are unique by construction which make them useful for avoiding name collisions. By unique, we mean that two symbols created at different points in time will never refer to the same property of an object.
419 |
420 | In ES5, name collisions avoidance is hacked by using names which are *unlikely* to collide. The following snippet from jQuery's source illustrates this.
421 |
422 | ```javascript
423 | jQuery.extend({
424 | // Unique for each copy of jQuery on the page
425 | // Non-digits removed to match rinlinejQuery
426 | expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
427 | ```
428 |
429 | Uniqueness has interesting consequences.
430 |
431 | ```javascript
432 | let a = Map();
433 | a.set(Symbol(), 'Noise');
434 |
435 | // Prints "1" although the one element cannot be accessed!
436 | console.log(a.size);
437 | ```
438 |
439 | For informational or debugging purposes, an immutable name can be given when instantiating a symbol.
440 |
441 | ```javascript
442 | let testSymbol = Symbol('This is a test');
443 |
444 | // Prints "This is a test"
445 | console.log(testSymbol.name);
446 | ```
447 |
448 | TODO: Discuss private symbols when the get implemented.
449 |
450 | Object observation
451 | ---
452 |
453 | TODO
454 |
455 | Typed arrays and array buffers
456 | ---
457 |
458 | TODO
459 |
460 | Concluding note
461 | ---
462 |
463 | As of Node v0.11.3, a lot of the ES6 features providing new semantics to the language are available. As we saw, these include proxies, symbols and weak maps. We look forward for tasty syntax sugar yet to come, such as rest and spread operators, destructuring and classes. Yum yum.
464 |
--------------------------------------------------------------------------------