From 40437de61a6382a0b7338ba010ede6dbdb738ff6 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 9 Feb 2016 09:23:29 +0800 Subject: [PATCH] doc: add ES7 stage 3 features --- docs/async.md | 36 ++++++--- docs/object.md | 198 ++++++++++++++++++++++++++++++++++++++++++++++ docs/proxy.md | 45 +++++++++-- docs/reference.md | 2 + docs/simd.md | 147 +++++++++++++++++++++++++++++++++- docs/string.md | 7 ++ 6 files changed, 420 insertions(+), 15 deletions(-) diff --git a/docs/async.md b/docs/async.md index efd0a50..054c22f 100644 --- a/docs/async.md +++ b/docs/async.md @@ -691,7 +691,7 @@ function* somethingAsync(x) { } ``` -上面的代码允许并发三个somethingAsync异步操作,等到它们全部完成,才会进行下一步。 +上面的代码允许并发三个`somethingAsync`异步操作,等到它们全部完成,才会进行下一步。 ## async函数 @@ -721,7 +721,7 @@ var gen = function* (){ }; ``` -写成 async 函数,就是下面这样。 +写成`async`函数,就是下面这样。 ```javascript var asyncReadFile = async function (){ @@ -734,7 +734,7 @@ var asyncReadFile = async function (){ 一比较就会发现,`async`函数就是将Generator函数的星号(`*`)替换成`async`,将`yield`替换成`await`,仅此而已。 -`async`函数对 Generator 函数的改进,体现在以下三点。 +`async`函数对 Generator 函数的改进,体现在以下四点。 (1)内置执行器。Generator函数的执行必须靠执行器,所以才有了`co`模块,而`async`函数自带执行器。也就是说,`async`函数的执行,与普通函数一模一样,只要一行。 @@ -752,6 +752,8 @@ var result = asyncReadFile(); 进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而`await`命令就是内部`then`命令的语法糖。 +正常情况下,`await`命令后面是一个Promise对象,否则会被转成Promise。 + ### async函数的实现 async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。 @@ -770,9 +772,9 @@ function fn(args){ } ``` -所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。 +所有的`async`函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。 -下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。 +下面给出`spawn`函数的实现,基本就是前文自动执行器的翻版。 ```javascript function spawn(genF) { @@ -798,11 +800,11 @@ function spawn(genF) { } ``` -async 函数是非常新的语法功能,新到都不属于 ES6,而是属于 ES7。目前,它仍处于提案阶段,但是转码器 Babel 和 regenerator 都已经支持,转码后就能使用。 +`async`函数是非常新的语法功能,新到都不属于 ES6,而是属于 ES7。目前,它仍处于提案阶段,但是转码器`Babel`和`regenerator`都已经支持,转码后就能使用。 ### async 函数的用法 -同Generator函数一样,async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。 +同Generator函数一样,`async`函数返回一个Promise对象,可以使用`then`方法添加回调函数。当函数执行的时候,一旦遇到`await`就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。 下面是一个例子。 @@ -813,12 +815,12 @@ async function getStockPriceByName(name) { return stockPrice; } -getStockPriceByName('goog').then(function (result){ +getStockPriceByName('goog').then(function (result) { console.log(result); }); ``` -上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。 +上面代码是一个获取股票报价的函数,函数前面的`async`关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个`Promise`对象。 下面的例子,指定多少毫秒后输出一个值。 @@ -839,6 +841,22 @@ asyncPrint('hello world', 50); 上面代码指定50毫秒以后,输出"hello world"。 +Async函数有多种使用形式。 + +```javascript +// 函数声明 +async function foo() {} + +// 函数表达式 +const foo = async function () {}; + +// 对象的方法 +let obj = { async foo() {} } + +// 箭头函数 +const foo = async () => {}; +``` + ### 注意点 第一点,`await`命令后面的Promise对象,运行结果可能是rejected,所以最好把`await`命令放在`try...catch`代码块中。 diff --git a/docs/object.md b/docs/object.md index 57a84a3..b002af3 100644 --- a/docs/object.md +++ b/docs/object.md @@ -729,6 +729,13 @@ Object.values(obj) // [] 上面代码中,`Object.create`方法的第二个参数添加的对象属性(属性`p`),如果不显式声明,默认是不可遍历的。`Object.values`不会返回这个属性。 +`Object.values`会过滤属性名为Symbol值的属性。 + +```javascript +Object.entries({ [Symbol()]: 123, foo: 'abc' }); +// ['abc'] +``` + 如果`Object.values`方法的参数是一个字符串,会返回各个字符组成的一个数组。 ```javascript @@ -757,6 +764,26 @@ Object.entries(obj) 除了返回值不一样,该方法的行为与`Object.values`基本一致。 +如果原对象的属性名是一个Symbol值,该属性会被省略。 + +```javascript +Object.entries({ [Symbol()]: 123, foo: 'abc' }); +// [ [ 'foo', 'abc' ] ] +``` + +上面代码中,原对象有两个属性,`Object.entries`只输出属性名非Symbol值的属性。将来可能会有`Reflect.ownEntries()`方法,返回对象自身的所有属性。 + +`Object.entries`的基本用途是遍历对象的属性。 + +```javascript +let obj = { one: 1, two: 2 }; +for (let [k, v] of Object.entries(obj)) { + console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`); +} +// "one": 1 +// "two": 2 +``` + `Object.entries`方法的一个用处是,将对象转为真正的`Map`结构。 ```javascript @@ -896,3 +923,174 @@ let runtimeError = { ```javascript let emptyObject = { ...null, ...undefined }; // 不报错 ``` + +## Object.getOwnPropertyDescriptors() + +ES5有一个`Object.getOwnPropertyDescriptor`方法,返回某个对象属性的描述对象(descriptor)。 + +```javascript +var obj = { p: 'a' }; + +Object.getOwnPropertyDescriptor(obj, 'p') +// Object { value: "a", +// writable: true, +// enumerable: true, +// configurable: true +// } +``` + +ES7有一个提案,提出了`Object.getOwnPropertyDescriptors`方法,返回指定对象所有自身属性(非继承属性)的描述对象。 + +```javascript +const obj = { + foo: 123, + get bar() { return 'abc' }, +}; + +Object.getOwnPropertyDescriptors(obj) +// { foo: +// { value: 123, +// writable: true, +// enumerable: true, +// configurable: true }, +// bar: +// { get: [Function: bar], +// set: undefined, +// enumerable: true, +// configurable: true } } +``` + +`Object.getOwnPropertyDescriptors`方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。 + +该方法的实现非常容易。 + +```javascript +function getOwnPropertyDescriptors(obj) { + const result = {}; + for (let key of Reflect.ownKeys(obj)) { + result[key] = Object.getOwnPropertyDescriptor(obj, key); + } + return result; +} +``` + +该方法的提出目的,主要是为了解决`Object.assign()`无法正确拷贝`get`属性和`set`属性的问题。 + +```javascript +const source = { + set foo(value) { + console.log(value); + } +}; + +const target1 = {}; +Object.assign(target1, source); + +Object.getOwnPropertyDescriptor(target1, 'foo') +// { value: undefined, +// writable: true, +// enumerable: true, +// configurable: true } +``` + +上面代码中,`source`对象的`foo`属性的值是一个赋值函数,`Object.assign`方法将这个属性拷贝给`target1`对象,结果该属性的值变成了`undefined`。这是因为`Object.assign`方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。 + +这时,`Object.getOwnPropertyDescriptors`方法配合`Object.defineProperties`方法,就可以实现正确拷贝。 + +```javascript +const source = { + set foo(value) { + console.log(value); + } +}; + +const target2 = {}; +Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source)); +Object.getOwnPropertyDescriptor(target2, 'foo') +// { get: undefined, +// set: [Function: foo], +// enumerable: true, +// configurable: true } +``` + +上面代码中,将两个对象合并的逻辑提炼出来,就是下面这样。 + +```javascript +const shallowMerge = (target, source) => Object.defineProperties( + target, + Object.getOwnPropertyDescriptors(source) +); +``` + +`Object.getOwnPropertyDescriptors`方法的另一个用处,是配合`Object.create`方法,将对象属性克隆到一个新对象。这属于浅拷贝。 + +```javascript +const clone = Object.create(Object.getPrototypeOf(obj), + Object.getOwnPropertyDescriptors(obj)); + +// 或者 + +const shallowClone = (obj) => Object.create( + Object.getPrototypeOf(obj), + Object.getOwnPropertyDescriptors(obj) +); +``` + +上面代码会克隆对象`obj`。 + +另外,`Object.getOwnPropertyDescriptors`方法可以实现,一个对象继承另一个对象。以前,继承另一个对象,常常写成下面这样。 + +```javascript +const obj = { + __proto__: prot, + foo: 123, +}; +``` + +ES6规定`__proto__`只有浏览器要部署,其他环境不用部署。如果去除`__proto__`,上面代码就要改成下面这样。 + +```javascript +const obj = Object.create(prot); +obj.foo = 123; + +// 或者 + +const obj = Object.assign( + Object.create(prot), + { + foo: 123, + } +); +``` + +有了`Object.getOwnPropertyDescriptors`,我们就有了另一种写法。 + +```javascript +const obj = Object.create( + prot, + Object.getOwnPropertyDescriptors({ + foo: 123, + }) +); +``` + +`Object.getOwnPropertyDescriptors`也可以用来实现Mixin(混入)模式。 + +```javascript +let mix = (object) => ({ + with: (...mixins) => mixins.reduce( + (c, mixin) => Object.create( + c, Object.getOwnPropertyDescriptors(mixin) + ), object) +}); + +// multiple mixins example +let a = {a: 'a'}; +let b = {b: 'b'}; +let c = {c: 'c'}; +let d = mix(c).with(a, b); +``` + +上面代码中,对象`a`和`b`被混入了对象`c`。 + +出于完整性的考虑,`Object.getOwnPropertyDescriptors`进入标准以后,还会有`Reflect.getOwnPropertyDescriptors`方法。 diff --git a/docs/proxy.md b/docs/proxy.md index 018f0ec..31d03df 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -245,7 +245,7 @@ arr[-1] // c 上面代码中,数组的位置参数是`-1`,就会输出数组的倒数最后一个成员。 -利用proxy,可以将读取属性的操作(`get`),转变为执行某个函数,从而实现属性的链式操作。 +利用Proxy,可以将读取属性的操作(`get`),转变为执行某个函数,从而实现属性的链式操作。 ```javascript var pipe = (function () { @@ -276,11 +276,46 @@ pipe(3).double.pow.reverseInt.get 上面代码设置Proxy以后,达到了将函数名链式使用的效果。 +下面的例子则是利用`get`拦截,实现一个生成各种DOM节点的通用函数`dom`。 + +```javascript +const el = dom.div({}, + 'Hello, my name is ', + dom.a({href: '//example.com'}, 'Mark'), + '. I like:', + dom.ul({}, + dom.li({}, 'The web'), + dom.li({}, 'Food'), + dom.li({}, '…actually that\'s it') + ) +); + +document.body.appendChild(el); + +const dom = new Proxy({}, { + get(target, property) { + return function(attrs = {}, ...children) { + const el = document.createElement(property); + for (let prop of Object.keys(attrs)) { + el.setAttribute(prop, attrs[prop]); + } + for (let child of children) { + if (typeof child === 'string') { + child = document.createTextNode(child); + } + el.appendChild(child); + } + return el; + } + } +}); +``` + ### set() `set`方法用来拦截某个属性的赋值操作。 -假定Person对象有一个`age`属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证`age`的属性值符合要求。 +假定`Person`对象有一个`age`属性,该属性应该是一个不大于200的整数,那么可以使用`Proxy`保证`age`的属性值符合要求。 ```javascript let validator = { @@ -364,8 +399,8 @@ var handler = { var p = new Proxy(target, handler); -p() === 'I am the proxy'; -// true +p() +// "I am the proxy" ``` 上面代码中,变量`p`是Proxy的实例,当它作为函数调用时(`p()`),就会被`apply`方法拦截,返回一个字符串。 @@ -387,7 +422,7 @@ proxy.call(null, 5, 6) // 22 proxy.apply(null, [7, 8]) // 30 ``` -上面代码中,每当执行`proxy`函数,就会被`apply`方法拦截。 +上面代码中,每当执行`proxy`函数(直接调用或`call`和`apply`调用),就会被`apply`方法拦截。 另外,直接调用`Reflect.apply`方法,也会被拦截。 diff --git a/docs/reference.md b/docs/reference.md index 20801f6..5e3d0e1 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -82,6 +82,8 @@ - Sella Rafaeli, [Native JavaScript Data-Binding](http://www.sellarafaeli.com/blog/native_javascript_data_binding): 如何使用Object.observe方法,实现数据对象与DOM对象的双向绑定 - Axel Rauschmayer, [`__proto__` in ECMAScript 6](http://www.2ality.com/2015/09/proto-es6.html) - Axel Rauschmayer, [Enumerability in ECMAScript 6](http://www.2ality.com/2015/10/enumerability-es6.html) +- Axel Rauschmayer, [ES proposal: Object.getOwnPropertyDescriptors()](http://www.2ality.com/2016/02/object-getownpropertydescriptors.html) +- TC39, [Object.getOwnPropertyDescriptors Proposal](https://github.com/tc39/proposal-object-getownpropertydescriptors) ## Proxy和Reflect diff --git a/docs/simd.md b/docs/simd.md index e36e23f..e3ce170 100644 --- a/docs/simd.md +++ b/docs/simd.md @@ -1,5 +1,7 @@ # SIMD 的用法 +## 概述 + SIMD是“Single Instruction/Multiple Data”的缩写,意为“单指令,多数据”。它是JavaScript操作CPU对应指令的接口,你可以看做这是一种不同的运算执行模式。与它相对的是SISD(“Single Instruction/Single Data”),即“单指令,单数据”。 SIMD的含义是使用一个指令,完成多个数据的运算;SISD的含义是使用一个指令,完成单个数据的运算,这是JavaScript的默认运算模式。显而易见,SIMD的执行效率要高于SISD,所以被广泛用于3D图形运算、物理模拟等运算量超大的项目之中。 @@ -25,9 +27,152 @@ c; // Array[6, 8, 10, 12] ```javascript var a = SIMD.Float32x4(1, 2, 3, 4); var b = SIMD.Float32x4(5, 6, 7, 8); -var c = SIMD.Float32x4.add(a,b); // Float32x4[6, 8, 10, 12] +var c = SIMD.Float32x4.add(a, b); // Float32x4[6, 8, 10, 12] ``` 上面代码之中,数组`a`和`b`的四个成员的各自相加,只用一条指令就完成了。因此,速度比上一种写法提高了4倍。 一次SIMD运算,可以处理多个数据,这些数据被称为“通道”(lane)。上面代码中,一次运算了四个数据,因此就是四个通道。 + +SIMD通常用于矢量运算。 + +```javascript +v + w = 〈v1, …, vn〉+ 〈w1, …, wn〉 + = 〈v1+w1, …, vn+wn〉 +``` + +上面代码中,`v`和`w`是两个多元矢量。它们的加运算,在SIMD下是一个指令、而不是n个指令完成的,这就大大提高了效率。这对于3D动画、图像处理、信号处理、数值处理、加密等运算是非常重要的。 + +总得来说,SIMD是数据并行处理(parallelism)的一种手段。 + +## 数据类型 + +SIMD提供多种数据类型。 + +- Float32x4:四个32位浮点数 +- Float64x2:两个64位浮点数 +- Int32x4:四个32位整数 +- Int16x8:八个16位整数 +- Int8x16:十六个8位整数 +- Uint32x4:四个无符号的32位整数 +- Uint16x8:八个无符号的16位整数 +- Uint8x16:十六个无符号的8位整数 +- Bool32x4:四个32位布尔值 +- Bool16x8:八个16位布尔值 +- Bool8x16:十六个8位布尔值 +- Bool64x2:两个64位布尔值 + +每种数据类型都是一个方法,可以传入参数,生成对应的值。 + +```javascript +var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0); +``` + +上面代码中,变量`a`就是一个128位、包含四个32位浮点数的值。 + +注意,这些数据类型方法都不是构造函数,前面不能加`new`,否则会报错。 + +```javascript +var v = new SIMD.Float32x4(0,1,2,3); +// TypeError: SIMD.Float32x4 is not a constructor +``` + +每种数据类型都有一系列运算符,下面是其中的一些。 + +- float32x4.abs(v):返回`v`的绝对值 +- float32x4.neg(v):返回`v`的绝对值的负值 +- float32x4.sqrt(v):返回`v`的平方根 +- float32x4.add(v, w):`v`和`w`对应项的相加 +- float32x4.mul(v, w):`v`和`w`对应项的相乘 +- float32x4.equal(v, w):比较`v`和`w`对应项是否相等,返回的布尔值组成一个`uint32x4`的值 + +下面是一个`add`运算符的例子。 + +```javascript +c[i] = SIMD.float32x4.add(a[i], b[i]); + +// 或者 + +var tmp0 = a[i]; +var tmp1 = b[i]; +var tmp2 = SIMD.float32x4.add(tmp0, tmp1); +c[i] = tmp2; +``` + +此外,每种数据类型还有操作方法。 + +`getAt`方法返回指定位置的值。 + +```javascript +var a = SIMD.float32x4(1.0, 2.0, 3.0, 4.0); +var b = a.getAt(0); // 1.0 +``` + +`zero`方法可以将SIMD值清零。 + +```javascript +var b = SIMD.float32x4.zero(); +``` + +上面代码中,变量`b`包含的四个32位浮点数全部是0.0。 + +`shuffle`方法根据指定模式,重新生成一个SIMD值。 + +```javascript +var a = SIMD.float32x4(1.0, 2.0, 3.0, 4.0); +var b = SIMD.float32x4.shuffle(a, SIMD.float32x4.XXYY); +// [1.0, 1.0, 2.0, 2.0] + +var c = SIMD.float32x4.shuffle(a, SIMD.float32x4.WWWW); +// [4.0, 4.0, 4.0, 4.0] + +var d = SIMD.float32x4.shuffle(a, SIMD.float32x4.WZYX); +// [4.0, 3.0, 2.0, 1.0] +``` + +下面是一个求平均值的例子。 + +```javascript +function average(f32x4list) { + var n = f32x4list.length; + var sum = SIMD.float32x4.zero(); + for (int i = 0; i < n; i++) { + sum = SIMD.float32x4.add(sum, f32x4list.getAt(i)); + } + var total = sum.x + sum.y + sum.z + sum.w; + return total / (n * 4); +} +``` + +## 二进制数组 + +SIMD可以与二进制数组结合,生成数组实例。 + +```javascript +var _f64x2 = new Float64Array(_f32x4.buffer); +var _i32x4 = new Int32Array(_f32x4.buffer); +var _i16x8 = new Int16Array(_f32x4.buffer); +var _i8x16 = new Int8Array(_f32x4.buffer); +var _ui32x4 = new Uint32Array(_f32x4.buffer); +var _ui16x8 = new Uint16Array(_f32x4.buffer); +var _ui8x16 = new Uint8Array(_f32x4.buffer); +``` + +下面是一些应用的例子。 + +```javascript +// a 和 b 是float32x4数组实例 +function addArrays(a, b) { + var c = new Float32x4Array(a.length); + for (var i = 0; i < a.length; i++) { + c[i] = SIMD.float32x4.add(a[i], b[i]); + } + return c; +} +``` + +## 参考链接 + +- MDN, [SIMD](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SIMD) +- TC39, [ECMAScript SIMD](https://github.com/tc39/ecmascript_simd) +- Axel Rauschmayer, [JavaScript gains support for SIMD](http://www.2ality.com/2013/12/simd-js.html) diff --git a/docs/string.md b/docs/string.md index 31cc34a..5108cee 100644 --- a/docs/string.md +++ b/docs/string.md @@ -318,6 +318,13 @@ ES7推出了字符串补全长度的功能。如果某个字符串不够指定 'xxx'.padEnd(2, 'ab') // 'xxx' ``` +如果用来补全的字符串与原字符串,两者的长度之和超过了制定的最小长度,则会截去超出位数的补全字符串。 + +```javascript +'abc'.padStart(10, '0123456789') +// '0123456abc' +``` + 如果省略第二个参数,则会用空格补全长度。 ```javascript