From 64a77470f80d30ae5d58ca2b2c9107a35ce3e710 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 7 Sep 2015 20:49:25 +0800 Subject: [PATCH 0001/1221] add string/template example --- docs/string.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/docs/string.md b/docs/string.md index b1e575aeb..ebbeede1c 100644 --- a/docs/string.md +++ b/docs/string.md @@ -318,13 +318,117 @@ var msg = `Hello, ${place}`; // 报错 ``` -由于模板字符串的大括号内部,就是执行JavaScript代码,因此如果表达式放在引号之中,将会原样输出。 +由于模板字符串的大括号内部,就是执行JavaScript代码,因此如果大括号内部是一个字符串,将会原样输出。 ```javascript `Hello ${'World'}` // "Hello World" ``` +## 实例:模板编译 + +下面,我们来看一个通过模板字符串,生成正式模板的实例。 + +```javascript +var template = ` + +`; +``` + +上面代码在模板字符串之中,放置了一个常规模板。该模板使用`<%...%>`放置JavaScript代码,使用`<%= ... %>`输出JavaScript表达式。 + +怎么编译这个模板字符串呢? + +一种思路是将其转换为JavaScript表达式字符串。 + +```javascript +echo(''); +``` + +这个转换使用正则表达式就行了。 + +```javascript +var evalExpr = /<%=(.+?)%>/g; +var expr = /<%([\s\S]+?)%>/g; + +template = template + .replace(evalExpr, '`); \n echo( $1 ); \n echo(`') + .replace(expr, '`); \n $1 \n echo(`'); + +template = 'echo(`' + template + '`);'; +``` + +然后,将`template`封装在一个函数里面返回,就可以了。 + +```javascript +var script = +`(function parse(data){ + var output = ""; + + function echo(html){ + output += html; + } + + ${ template } + + return output; +})`; + +return script; +``` + +将上面的内容拼装成一个模板编译函数`compile`。 + +```javascript +function compile(template){ + var evalExpr = /<%=(.+?)%>/g; + var expr = /<%([\s\S]+?)%>/g; + + template = template + .replace(evalExpr, '`); \n echo( $1 ); \n echo(`') + .replace(expr, '`); \n $1 \n echo(`'); + + template = 'echo(`' + template + '`);'; + + var script = + `(function parse(data){ + var output = ""; + + function echo(html){ + output += html; + } + + ${ template } + + return output; + })`; + + return script; +} +``` + +`compile`函数的用法如下。 + +```javascript +var parse = eval(compile(template)); +div.innerHTML = parse({ supplies: [ "broom", "mop", "cleaner" ] }); +// +``` + ## 标签模板 模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。 From 79b1d30404543d75cbc48a9ba7a8d40b30114eeb Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 9 Sep 2015 09:59:14 +0800 Subject: [PATCH 0002/1221] edit Generator --- docs/async.md | 4 +- docs/generator.md | 289 ++++++++++++++++++++++++++++++++++++++-------- docs/reference.md | 2 + sidebar.md | 2 +- 4 files changed, 244 insertions(+), 53 deletions(-) diff --git a/docs/async.md b/docs/async.md index a7c9c8b66..b34b0f177 100644 --- a/docs/async.md +++ b/docs/async.md @@ -1,4 +1,4 @@ -# 异步操作 +# 异步操作和Async函数 异步编程对JavaScript语言太重要。JavaScript只有一根线程,如果没有异步编程,根本没法用,非卡死不可。 @@ -9,7 +9,7 @@ ES6诞生以前,异步编程的方法,大概有下面四种。 - 发布/订阅 - Promise 对象 -ES6将JavaScript异步编程带入了一个全新的阶段。 +ES6将JavaScript异步编程带入了一个全新的阶段,ES7的`Async`函数更是提出了异步编程的终极解决方案。 ## 基本概念 diff --git a/docs/generator.md b/docs/generator.md index 63d4e868f..f25e90979 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -22,7 +22,7 @@ function* helloWorldGenerator() { var hw = helloWorldGenerator(); ``` -上面代码定义了一个Generator函数`helloWorldGenerator`,它内部有两个yield语句“hello”和“world”,即该函数有三个状态:hello,world和return语句(结束执行)。 +上面代码定义了一个Generator函数`helloWorldGenerator`,它内部有两个`yield`语句“hello”和“world”,即该函数有三个状态:hello,world和return语句(结束执行)。 然后,Generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。 @@ -105,7 +105,7 @@ setTimeout(function () { // SyntaxError: Unexpected number ``` -上面代码在一个普通函数中使用yield语句,结果产生一个句法错误。 +上面代码在一个普通函数中使用`yield`语句,结果产生一个句法错误。 下面是另外一个例子。 @@ -127,7 +127,7 @@ for (var f of flat(arr)){ } ``` -上面代码也会产生句法错误,因为forEach方法的参数是一个普通函数,但是在里面使用了yield语句。一种修改方法是改用for循环。 +上面代码也会产生句法错误,因为`forEach`方法的参数是一个普通函数,但是在里面使用了`yield`语句。一种修改方法是改用`for`循环。 ```javascript var arr = [1, [[2, 3], 4], [5, 6]]; @@ -150,11 +150,28 @@ for (var f of flat(arr)){ // 1, 2, 3, 4, 5, 6 ``` -### 与Iterator的关系 +另外,`yield`语句如果用在一个表达式之中,必须放在圆括号里面。 -上一章说过,任意一个对象的`Symbol.iterator`方法,等于该对象的遍历器函数,调用该函数会返回该对象的一个遍历器。 +```javascript +console.log('Hello' + yield); // SyntaxError +console.log('Hello' + yield 123); // SyntaxError + +console.log('Hello' + (yield)); // OK +console.log('Hello' + (yield 123)); // OK +``` -遍历器本身也是一个对象,它的`Symbol.iterator`方法执行后,返回自身。 +`yield`语句用作函数参数或赋值表达式的右边,可以不加括号。 + +```javascript +foo(yield 'a', yield 'b'); // OK +let input = yield; // OK +``` + +### 与Iterator接口的关系 + +上一章说过,任意一个对象的`Symbol.iterator`方法,等于该对象的遍历器对象生成函数,调用该函数会返回该对象的一个遍历器对象。 + +遍历器对象本身也有`Symbol.iterator`方法,执行后返回自身。 ```javascript function* gen(){ @@ -167,11 +184,11 @@ g[Symbol.iterator]() === g // true ``` -上面代码中,gen是一个Generator函数,调用它会生成一个遍历器g。遍历器g的Symbol.iterator属性是一个遍历器函数,执行后返回它自己。 +上面代码中,`gen`是一个Generator函数,调用它会生成一个遍历器对象`g`。它的`Symbol.iterator`属性,也是一个遍历器对象生成函数,执行后返回它自己。 ## next方法的参数 -yield语句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。 +`yield`句本身没有返回值,或者说总是返回`undefined`。`next`方法可以带一个参数,该参数就会被当作上一个`yield`语句的返回值。 ```javascript function* f() { @@ -188,9 +205,9 @@ g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false } ``` -上面代码先定义了一个可以无限运行的Generator函数f,如果next方法没有参数,每次运行到yield语句,变量reset的值总是undefined。当next方法带一个参数true时,当前的变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。 +上面代码先定义了一个可以无限运行的Generator函数`f`,如果`next`方法没有参数,每次运行到`yield`语句,变量`reset`的值总是`undefined`。当`next`方法带一个参数`true`时,当前的变量`reset`就被重置为这个参数(即`true`),因此`i`会等于-1,下一轮循环就会从-1开始递增。 -这个功能有很重要的语法意义。Generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。 +这个功能有很重要的语法意义。Generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过`next`方法的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。 再看一个例子。 @@ -208,9 +225,9 @@ a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:false} ``` -上面代码中,第二次运行next方法的时候不带参数,导致y的值等于`2 * undefined`(即NaN),除以3以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于`5 + NaN + undefined`,即NaN。 +上面代码中,第二次运行`next`方法的时候不带参数,导致y的值等于`2 * undefined`(即`NaN`),除以3以后还是`NaN`,因此返回对象的`value`属性也等于`NaN`。第三次运行`Next`方法的时候不带参数,所以`z`等于`undefined`,返回对象的`value`属性等于`5 + NaN + undefined`,即`NaN`。 -如果向next方法提供参数,返回结果就完全不一样了。 +如果向`next`方法提供参数,返回结果就完全不一样了。 ```javascript function* foo(x) { @@ -229,13 +246,35 @@ it.next(13) // { value:42, done:true } ``` -上面代码第一次调用next方法时,返回`x+1`的值6;第二次调用next方法,将上一次yield语句的值设为12,因此y等于24,返回`y / 3`的值8;第三次调用next方法,将上一次yield语句的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。 +上面代码第一次调用next方法时,返回`x+1`的值6;第二次调用`next`方法,将上一次`yield`语句的值设为12,因此`y`等于24,返回`y / 3`的值8;第三次调用`next`方法,将上一次`yield`语句的值设为13,因此`z`等于13,这时`x`等于5,`y`等于24,所以`return`语句的值等于42。 + +注意,由于`next`方法的参数表示上一个`yield`语句的返回值,所以第一次使用`next`方法时,不能带有参数。V8引擎直接忽略第一次使用`next`方法时的参数,只有从第二次使用`next`方法开始,参数才是有效的。 -注意,由于next方法的参数表示上一个yield语句的返回值,所以第一次使用next方法时,不能带有参数。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。 +如果想要第一次调用`next`方法时,就能够输入值,可以在Generator函数外面再包一层。 + +```javascript +function wrapper(generatorFunction) { + return function (...args) { + let generatorObject = generatorFunction(...args); + generatorObject.next(); + return generatorObject; + }; +} + +const wrapped = wrapper(function* () { + console.log(`First input: ${yield}`); + return 'DONE'; +}); + +wrapped().next('hello!') +// First input: hello! +``` + +上面代码中,Generator函数如果不用`wrapper`先包一层,是无法第一次调用`next`方法,就输入参数的。 ## for...of循环 -for...of循环可以自动遍历Generator函数,且此时不再需要调用next方法。 +`for...of`循环可以自动遍历Generator函数,且此时不再需要调用`next`方法。 ```javascript function *foo() { @@ -253,9 +292,9 @@ for (let v of foo()) { // 1 2 3 4 5 ``` -上面代码使用for...of循环,依次显示5个yield语句的值。这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。 +上面代码使用`for...of`循环,依次显示5个`yield`语句的值。这里需要注意,一旦`next`方法的返回对象的`done`属性为`true`,`for...of`循环就会中止,且不包含该返回对象,所以上面代码的`return`语句返回的6,不包括在`for...of`循环之中。 -下面是一个利用generator函数和for...of循环,实现斐波那契数列的例子。 +下面是一个利用Generator函数和`for...of`循环,实现斐波那契数列的例子。 ```javascript function* fibonacci() { @@ -272,11 +311,55 @@ for (let n of fibonacci()) { } ``` -从上面代码可见,使用for...of语句时不需要使用next方法。 +从上面代码可见,使用`for...of`语句时不需要使用next方法。 + +前面章节曾经介绍过,`for...of`循环、扩展运算符(...)、解构赋值和`Array.from`方法内部调用的,都是遍历器接口。这意味着,它们可以将Generator函数返回的Iterator对象,作为参数。 -## throw方法 +```javascript +function* numbers () { + yield 1 + yield 2 + return 3 + yield 4 +} + +[...numbers()] // [1, 2] -Generator函数还有一个特点,它可以在函数体外抛出错误,然后在函数体内捕获。 +Array.from(numbers()) // [1, 2] + +let [x, y] = numbers(); +x // 1 +y // 2 + +for (let n of numbers()) { + console.log(n) +} +// 1 +// 2 +``` + +利用`for...of`循环,可以写出遍历任意对象的方法。原生的JavaScript对象没有遍历接口,无法使用`for...of`循环,通过Generator函数为它加上这个接口,就可以用了。 + +```javascript +function* objectEntries(obj) { + let propKeys = Reflect.ownKeys(obj); + + for (let propKey of propKeys) { + yield [propKey, obj[propKey]]; + } +} + +let jane = { first: 'Jane', last: 'Doe' }; +for (let [key,value] of objectEntries(jane)) { + console.log(`${key}: ${value}`); +} +// first: Jane +// last: Doe +``` + +## Generator.prototype.throw() + +Generator函数返回的遍历器对象,都有一个`throw`方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。 ```javascript var g = function* () { @@ -303,9 +386,9 @@ try { // 外部捕获 b ``` -上面代码中,遍历器i连续抛出两个错误。第一个错误被Generator函数体内的catch捕获,然后Generator函数执行完成,于是第二个错误被函数体外的catch捕获。 +上面代码中,遍历器对象`i`连续抛出两个错误。第一个错误被Generator函数体内的`catch`语句捕获,然后Generator函数执行完成,于是第二个错误被函数体外的`catch`语句捕获。 -注意,上面代码的错误,是用遍历器的throw方法抛出的,而不是用throw命令抛出的。后者只能被函数体外的catch语句捕获。 +注意,不要混淆遍历器对象的`throw`方法和全局的`throw`命令。上面代码的错误,是用遍历器对象的`throw`方法抛出的,而不是用`throw`命令抛出的。后者只能被函数体外的`catch`语句捕获。 ```javascript var g = function* () { @@ -396,7 +479,7 @@ try { // world ``` -上面代码中,throw命令抛出的错误不会影响到遍历器的状态,所以两次执行next方法,都取到了正确的操作。 +上面代码中,`throw`命令抛出的错误不会影响到遍历器的状态,所以两次执行`next`方法,都取到了正确的操作。 这种函数体内捕获错误的机制,大大方便了对错误的处理。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数写一个错误处理语句。 @@ -438,7 +521,7 @@ function* g(){ } ``` -反过来,Generator函数内抛出的错误,也可以被函数体外的catch捕获。 +反过来,Generator函数内抛出的错误,也可以被函数体外的`catch`捕获。 ```javascript function *foo() { @@ -458,9 +541,9 @@ try { } ``` -上面代码中,第二个next方法向函数体内传入一个参数42,数值是没有toUpperCase方法的,所以会抛出一个TypeError错误,被函数体外的catch捕获。 +上面代码中,第二个`next`方法向函数体内传入一个参数42,数值是没有`toUpperCase`方法的,所以会抛出一个TypeError错误,被函数体外的`catch`捕获。 -一旦Generator执行过程中抛出错误,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefined、done属性等于true的对象,即JavaScript引擎认为这个Generator已经运行结束了。 +一旦Generator执行过程中抛出错误,就不会再执行下去了。如果此后还调用next方法,将返回一个`value`属性等于`undefined`、`done`属性等于`true`的对象,即JavaScript引擎认为这个Generator已经运行结束了。 ```javascript function* g() { @@ -475,7 +558,7 @@ function log(generator) { var v; console.log('starting generator'); try { - v = generator.next(); + g.next(); // { value: undefined, done: true } v = generator.next(); console.log('第一次运行next方法', v); } catch (err) { console.log('捕捉错误', v); @@ -506,9 +589,109 @@ log(g()); 上面代码一共三次运行next方法,第二次运行的时候会抛出错误,然后第三次运行的时候,Generator函数就已经结束了,不再执行下去了。 +## Generator.prototype.return() + +Generator函数返回的遍历器对象,还有一个`return`方法,可以返回给定的值,并且终结遍历Generator函数。 + +```javascript +function* gen() { + yield 1; + yield 2; + yield 3; +} + +var g = gen(); + +g.next() // { value: 1, done: false } +g.return("foo") // { value: "foo", done: true } +g.next() // { value: undefined, done: true } +``` + +上面代码中,遍历器对象`g`调用`return`方法后,返回值的`value`属性就是`return`方法的参数`foo`。并且,Generator函数的遍历就终止了,返回值的`done`属性为`true`,以后再调用`next`方法,`done`属性总是返回`true`。 + +如果`return`方法调用时,不提供参数,则返回值的`vaule`属性为`undefined`。 + +```javascript +function* gen() { + yield 1; + yield 2; + yield 3; +} + +var g = gen(); + +g.next() // { value: 1, done: false } +g.return() // { value: undefined, done: true } +``` + +如果Generator函数内部有`try...finally`代码块,那么`return`方法会推迟到`finally`代码块执行完再执行。 + +```javascript +function* numbers () { + yield 1; + try { + yield 2; + yield 3; + } finally { + yield 4; + yield 5; + } + yield 6; +} +var g = numbers() +g.next() // { done: false, value: 1 } +g.next() // { done: false, value: 2 } +g.return(7) // { done: false, value: 4 } +g.next() // { done: false, value: 5 } +g.next() // { done: true, value: 7 } +``` + +上面代码中,调用`return`方法后,就开始执行`finally`代码块,然后等到`finally`代码块执行完,再执行`return`方法。 + ## yield*语句 -如果yield命令后面跟的是一个遍历器,需要在yield命令后面加上星号,表明它返回的是一个遍历器。这被称为yield*语句。 +如果在Generater函数内部,调用另一个Generator函数,默认情况下是没有效果的。 + +```javascript +function* foo() { + yield 'a'; + yield 'b'; +} + +function* bar() { + yield 'x'; + foo(); + yield 'y'; +} + +for (let v of bar()){ + console.log(v); +} +// "x" +// "y" +``` + +上面代码中,`foo`和`bar`都是Generator函数,在`bar`里面调用`foo`,是不会有效果的。 + +这个就需要用到`yield*`语句,用来在一个Generator函数里面执行另一个Generator函数。 + +```javascript +function* bar() { + yield 'x'; + yield* foo(); + yield 'y'; +} + +for (let v of bar()){ + console.log(v); +} +// "x" +// "a" +// "b" +// "y" +``` + +从另一个角度看,如果`yield`命令后面跟的是一个遍历器对象,需要在`yield`命令后面加上星号,表明它返回的是一个遍历器对象。这被称为`yield*`语句。 ```javascript let delegatedIterator = (function* () { @@ -531,7 +714,7 @@ for(let value of delegatingIterator) { // "Ok, bye." ``` -上面代码中,delegatingIterator是代理者,delegatedIterator是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Generator函数,有递归的效果。 +上面代码中,`delegatingIterator`是代理者,`delegatedIterator`是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Generator函数,有递归的效果。 yield*语句等同于在Generator函数内部,部署一个for...of循环。 @@ -553,25 +736,25 @@ function* concat(iter1, iter2) { } ``` -上面代码说明,yield*不过是for...of的一种简写形式,完全可以用后者替代前者。 +上面代码说明,`yield*`不过是`for...of`的一种简写形式,完全可以用后者替代前者。 再来看一个对比的例子。 ```javascript function* inner() { - yield 'hello!' + yield 'hello!'; } function* outer1() { - yield 'open' - yield inner() - yield 'close' + yield 'open'; + yield inner(); + yield 'close'; } var gen = outer1() -gen.next() // -> 'open' -gen.next() // -> a generator -gen.next() // -> 'close' +gen.next().value // "open" +gen.next().value // 返回一个遍历器对象 +gen.next().value // "close" function* outer2() { yield 'open' @@ -580,12 +763,12 @@ function* outer2() { } var gen = outer2() -gen.next() // -> 'open' -gen.next() // -> 'hello!' -gen.next() // -> 'close' +gen.next().value // "open" +gen.next().value // "hello!" +gen.next().value // "close" ``` -上面例子中,outer2使用了`yield*`,outer1没使用。结果就是,outer1返回一个遍历器,outer2返回该遍历器的内部值。 +上面例子中,`outer2`使用了`yield*`,`outer1`没使用。结果就是,`outer1`返回一个遍历器对象,`outer2`返回该遍历器对象的内部值。 如果`yield*`后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。 @@ -597,9 +780,9 @@ function* gen(){ gen().next() // { value:"a", done:false } ``` -上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器。 +上面代码中,`yield`命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。 -如果被代理的Generator函数有return语句,那么就可以向代理它的Generator函数返回数据。 +如果被代理的Generator函数有`return`语句,那么就可以向代理它的Generator函数返回数据。 ```javascript function *foo() { @@ -617,14 +800,20 @@ function *bar() { var it = bar(); -it.next(); // -it.next(); // -it.next(); // -it.next(); // "v: foo" -it.next(); // +it.next() +// {value: 1, done: false} +it.next() +// {value: 2, done: false} +it.next() +// {value: 3, done: false} +it.next(); +// "v: foo" +// {value: 4, done: false} +it.next() +// {value: undefined, done: true} ``` -上面代码在第四次调用next方法的时候,屏幕上会有输出,这是因为函数foo的return语句,向函数bar提供了返回值。 +上面代码在第四次调用`next`方法的时候,屏幕上会有输出,这是因为函数`foo`的`return`语句,向函数`bar`提供了返回值。 `yield*`命令可以很方便地取出嵌套数组的所有成员。 @@ -703,7 +892,7 @@ let obj = { }; ``` -上面代码中,myGeneratorMethod属性前面有一个星号,表示这个属性是一个Generator函数。 +上面代码中,`myGeneratorMethod`属性前面有一个星号,表示这个属性是一个Generator函数。 它的完整形式如下,与上面的写法是等价的。 diff --git a/docs/reference.md b/docs/reference.md index a3acd823f..d845c7623 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -43,6 +43,7 @@ - Mozilla Developer Network, [Template strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings) - Addy Osmani, [Getting Literal With ES6 Template Strings](http://updates.html5rocks.com/2015/01/ES6-Template-Strings): 模板字符串的介绍 - Blake Winton, [ES6 Templates](https://weblog.latte.ca/blake/tech/firefox/templates.html): 模板字符串的介绍 +- Peter Jaszkowiak, [How to write a template compiler in JavaScript](https://medium.com/@PitaJ/how-to-write-a-template-compiler-in-javascript-f174df6f32f): 使用模板字符串,编写一个模板编译函数 ## 正则 @@ -115,6 +116,7 @@ - Ruslan Ismagilov, [learn-generators](https://github.com/isRuslan/learn-generators): 编程练习,共6道题 - Steven Sanderson, [Experiments with Koa and JavaScript Generators](http://blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators/): Generator入门介绍,以Koa框架为例 - Mahdi Dibaiee, [ES7 Array and Generator comprehensions](http://dibaiee.ir/es7-array-generator-comprehensions/):ES7的Generator推导 +- Nicolas Bevacqua, [ES6 Generators in Depth](http://ponyfoo.com/articles/es6-generators-in-depth) ## Promise对象 diff --git a/sidebar.md b/sidebar.md index c5d00decc..d1ea5c3eb 100644 --- a/sidebar.md +++ b/sidebar.md @@ -21,7 +21,7 @@ 1. [Iterator和for...of循环](#docs/iterator) 1. [Generator函数](#docs/generator) 1. [Promise对象](#docs/promise) -1. [异步操作](#docs/async) +1. [异步操作和Async函数](#docs/async) 1. [Class](#docs/class) 1. [Decorator](#docs/decorator) 1. [Module](#docs/module) From 3150ae90fe299d5bd8a2d84e9ee5b325627697c4 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 10 Sep 2015 08:51:40 +0800 Subject: [PATCH 0003/1221] edit symbol --- docs/reference.md | 1 + docs/symbol.md | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index d845c7623..1935b890a 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -130,6 +130,7 @@ - Luke Hoban, [Async Functions for ECMAScript](https://github.com/lukehoban/ecmascript-asyncawait): Async函数的设计思想,与Promise、Gernerator函数的关系 - Jafar Husain, [Asynchronous Generators for ES7](https://github.com/jhusain/asyncgenerator): Async函数的深入讨论 - Nolan Lawson, [Taming the asynchronous beast with ES7](http://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html): async函数通俗的实例讲解 +- Axel Rauschmayer, [ES6 generators in depth](http://www.2ality.com/2015/03/es6-generators.html): Generator规格的详尽讲解 ## Class diff --git a/docs/symbol.md b/docs/symbol.md index a03826e21..a27dbf2c9 100644 --- a/docs/symbol.md +++ b/docs/symbol.md @@ -6,7 +6,7 @@ ES5的对象属性名都是字符串,这容易造成属性名的冲突。比 ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。 -Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 +Symbol值通过`Symbol`函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 ```javascript let s = Symbol(); @@ -15,9 +15,9 @@ typeof s // "symbol" ``` -上面代码中,变量s就是一个独一无二的值。typeof运算符的结果,表明变量s是Symbol数据类型,而不是字符串之类的其他类型。 +上面代码中,变量`s`就是一个独一无二的值。`typeof`运算符的结果,表明变量`s`是Symbol数据类型,而不是字符串之类的其他类型。 -注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。 +注意,Symbol函数前不能使用`new`命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。 Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。 @@ -32,7 +32,7 @@ s1.toString() // "Symbol(foo)" s2.toString() // "Symbol(bar)" ``` -上面代码中,s1和s2是两个Symbol值。如果不加参数,它们在控制台的输出都是`Symbol()`,不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。 +上面代码中,`s1`和`s2`是两个Symbol值。如果不加参数,它们在控制台的输出都是`Symbol()`,不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。 注意,Symbol函数的参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的。 @@ -50,7 +50,7 @@ var s2 = Symbol("foo"); s1 === s2 // false ``` -上面代码中,s1和s2都是Symbol函数的返回值,而且参数相同,但是它们是不相等的。 +上面代码中,`s1`和`s2`都是Symbol函数的返回值,而且参数相同,但是它们是不相等的。 Symbol值不能与其他类型的值进行运算,会报错。 @@ -109,7 +109,7 @@ a[mySymbol] // undefined a['mySymbol'] // "Hello!" ``` -上面代码中,因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个Symbol值。 +上面代码中,因为点运算符后面总是字符串,所以不会读取`mySymbol`作为标识名所指代的那个值,导致`a`的属性名实际上是一个字符串,而不是一个Symbol值。 同理,在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。 @@ -123,9 +123,9 @@ let obj = { obj[s](123); ``` -上面代码中,如果s不放在方括号中,该属性的键名就是字符串s,而不是s所代表的那个Symbol值。 +上面代码中,如果`s`不放在方括号中,该属性的键名就是字符串`s`,而不是`s`所代表的那个Symbol值。 -采用增强的对象写法,上面代码的obj对象可以写得更简洁一些。 +采用增强的对象写法,上面代码的`obj`对象可以写得更简洁一些。 ```javascript let obj = { @@ -149,9 +149,9 @@ log(log.levels.INFO, 'info message'); ## 属性名的遍历 -Symbol作为属性名,该属性不会出现在for...in、for...of循环中,也不会被`Object.keys()`、`Object.getOwnPropertyNames()`返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。 +Symbol作为属性名,该属性不会出现在`for...in`、`for...of`循环中,也不会被`Object.keys()`、`Object.getOwnPropertyNames()`返回。但是,它也不是私有属性,有一个`Object.getOwnPropertySymbols`方法,可以获取指定对象的所有Symbol属性名。 -Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。 +`Object.getOwnPropertySymbols`方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。 ```javascript var obj = {}; @@ -167,7 +167,7 @@ objectSymbols // [Symbol(a), Symbol(b)] ``` -下面是另一个例子,Object.getOwnPropertySymbols方法与for...in循环、Object.getOwnPropertyNames方法进行对比的例子。 +下面是另一个例子,`Object.getOwnPropertySymbols`方法与`for...in`循环、`Object.getOwnPropertyNames`方法进行对比的例子。 ```javascript var obj = {}; @@ -189,9 +189,9 @@ Object.getOwnPropertySymbols(obj) // [Symbol(foo)] ``` -上面代码中,使用Object.getOwnPropertyNames方法得不到Symbol属性名,需要使用Object.getOwnPropertySymbols方法。 +上面代码中,使用`Object.getOwnPropertyNames`方法得不到`Symbol`属性名,需要使用`Object.getOwnPropertySymbols`方法。 -另一个新的API,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和Symbol键名。 +另一个新的API,`Reflect.ownKeys`方法可以返回所有类型的键名,包括常规键名和Symbol键名。 ```javascript let obj = { From f5365acb2aec5eebe5437a94d8e4d837b55cd3be Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 13 Sep 2015 08:48:49 +0800 Subject: [PATCH 0004/1221] edit Promise --- docs/promise.md | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index 4895158cc..72c9deb79 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -2,21 +2,21 @@ ## Promise的含义 -Promise在JavaScript语言早有实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。 +`Promise`在JavaScript语言早有实现,ES6将其写进了语言标准,统一了用法,原生提供了`Promise`对象。 -所谓Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的API,可供进一步处理。 +所谓`Promise`,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的API,可供进一步处理。 -Promise对象有以下两个特点。 +`Promise`对象有以下两个特点。 -(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 +(1)对象的状态不受外界影响。`Promise`对象代表一个异步操作,有三种状态:`Pending`(进行中)、`Resolved`(已完成,又称Fulfilled)和`Rejected`(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是`Promise`这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 -(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 +(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。`Promise`对象的状态改变,只有两种可能:从`Pending`变为`Resolved`和从`Pending`变为`Rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对`Promise`对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 -有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。 +有了`Promise`对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,`Promise`对象提供统一的接口,使得控制异步操作更加容易。 -Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 +`Promise`也有一些缺点。首先,无法取消`Promise`,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,`Promise`内部抛出的错误,不会反应到外部。第三,当处于`Pending`状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 -如果某些事件不断地反复发生,一般来说,使用stream模式是比部署Promise更好的选择。 +如果某些事件不断地反复发生,一般来说,使用stream模式是比部署`Promise`更好的选择。 ## 基本用法 @@ -443,9 +443,26 @@ p.then(null, function (s){ 上面代码生成一个Promise对象的实例p,状态为rejected,回调函数会立即执行。 -## Generator函数与Promise的结合 +## 应用 -使用Generator函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。 +### 加载图片 + +我们可以将图片的加载写成一个`Promise`,一旦加载完成,`Promise`的状态就发生变化。 + +```javascript +conset preloadImage = function (path) { + return new Promise(function (resolve, reject) { + var image = new Image(); + image.onload = resolve; + image.onerror = reject; + image.src = path; + }); +}; +``` + +### Generator函数与Promise的结合 + +使用Generator函数管理流程,遇到异步操作的时候,通常返回一个`Promise`对象。 ```javascript function getFoo () { @@ -482,7 +499,7 @@ function run (generator) { run(g); ``` -上面代码的Generator函数g之中,有一个异步操作getFoo,它返回的就是一个Promise对象。函数run用来处理这个Promise对象,并调用下一个next方法。 +上面代码的Generator函数`g`之中,有一个异步操作`getFoo`,它返回的就是一个`Promise`对象。函数`run`用来处理这个`Promise`对象,并调用下一个`next`方法。 ## async函数 From 1f9f4914bddd2142f1f1d35432a1d230787b9c2f Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 13 Sep 2015 12:26:01 +0800 Subject: [PATCH 0005/1221] edit Set/Map --- docs/set-map.md | 173 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 133 insertions(+), 40 deletions(-) diff --git a/docs/set-map.md b/docs/set-map.md index 126fbf05d..bf159d867 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -17,16 +17,31 @@ for (i of s) {console.log(i)} // 2 3 5 4 ``` -上面代码通过add方法向Set结构加入成员,结果表明Set结构不会添加重复的值。 +上面代码通过`add`方法向Set结构加入成员,结果表明Set结构不会添加重复的值。 -Set函数可以接受一个数组作为参数,用来初始化。 +Set函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。 ```javascript -var items = new Set([1,2,3,4,5,5,5,5]); +var set = new Set([1, 2, 3, 4, 4]) +[...set] +// [1, 2, 3, 4] + +var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); items.size // 5 + +function divs () { + return [...document.querySelectorAll('div')] +} + +var set = new Set(divs()) +set.size // 56 + +// 类似于 +divs().forEach(div => set.add(div)) +set.size // 56 ``` -向Set加入值的时候,不会发生类型转换,所以5和“5”是两个不同的值。Set内部判断两个值是否不同,使用的算法类似于精确相等运算符(===),这意味着,两个对象总是不相等的。唯一的例外是NaN等于自身(精确相等运算符认为NaN不等于自身)。 +向Set加入值的时候,不会发生类型转换,所以`5`和`"5"`是两个不同的值。Set内部判断两个值是否不同,使用的算法类似于精确相等运算符(`===`),这意味着,两个对象总是不相等的。唯一的例外是`NaN`等于自身(精确相等运算符认为`NaN`不等于自身)。 ```javascript let set = new Set(); @@ -120,7 +135,7 @@ Set结构的实例有四个遍历方法,可以用于遍历成员。 - entries():返回一个键值对的遍历器 - forEach():使用回调函数遍历每个成员 -key方法、value方法、entries方法返回的都是遍历器(详见《Iterator对象》一章)。由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以key方法和value方法的行为完全一致。 +`key`方法、`value`方法、`entries`方法返回的都是遍历器对象(详见《Iterator对象》一章)。由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以`key`方法和`value`方法的行为完全一致。 ```javascript let set = new Set(['red', 'green', 'blue']); @@ -147,16 +162,16 @@ for ( let item of set.entries() ){ // ["blue", "blue"] ``` -上面代码中,entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。 +上面代码中,`entries`方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。 -Set结构的实例默认可遍历,它的默认遍历器就是它的values方法。 +Set结构的实例默认可遍历,它的默认遍历器生成函数就是它的`values`方法。 ```javascript Set.prototype[Symbol.iterator] === Set.prototype.values // true ``` -这意味着,可以省略values方法,直接用for...of循环遍历Set。 +这意味着,可以省略`values`方法,直接用`for...of`循环遍历Set。 ```javascript let set = new Set(['red', 'green', 'blue']); @@ -169,7 +184,7 @@ for (let x of set) { // blue ``` -由于扩展运算符(...)内部使用for...of循环,所以也可以用于Set结构。 +由于扩展运算符(...)内部使用`for...of`循环,所以也可以用于Set结构。 ```javascript let set = new Set(['red', 'green', 'blue']); @@ -185,7 +200,7 @@ let unique = [...new Set(arr)]; // [3, 5, 2] ``` -而且,数组的map和filter方法也可以用于Set了。 +而且,数组的`map`和`filter`方法也可以用于Set了。 ```javascript let set = new Set([1, 2, 3]); @@ -220,7 +235,7 @@ set.forEach((value, key) => console.log(value * 2) ) // 6 ``` -上面代码说明,forEach方法的参数就是一个处理函数。该函数的参数依次为键值、键名、集合本身(上例省略了该参数)。另外,forEach方法还可以有第二个参数,表示绑定的this对象。 +上面代码说明,`forEach`方法的参数就是一个处理函数。该函数的参数依次为键值、键名、集合本身(上例省略了该参数)。另外,`forEach`方法还可以有第二个参数,表示绑定的this对象。 如果想在遍历操作中,同步改变原来的Set结构,目前没有直接的方法,但有两种变通方法。一种是利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构;另一种是利用Array.from方法。 @@ -250,17 +265,19 @@ WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set var ws = new WeakSet(); ws.add(1) // TypeError: Invalid value used in weak set +ws.add(Symbol()) +// TypeError: invalid value used in weak set ``` -上面代码试图向WeakSet添加一个数值,结果报错。 +上面代码试图向WeakSet添加一个数值和`Symbol`值,结果报错。 -WeakSet是一个构造函数,可以使用new命令,创建WeakSet数据结构。 +WeakSet是一个构造函数,可以使用`new`命令,创建WeakSet数据结构。 ```javascript var ws = new WeakSet(); ``` -作为构造函数,WeakSet可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有iterable接口的对象,都可以作为WeakSet的对象。)该数组的所有成员,都会自动成为WeakSet实例对象的成员。 +作为构造函数,WeakSet可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有iterable接口的对象,都可以作为WeakSet的参数。)该数组的所有成员,都会自动成为WeakSet实例对象的成员。 ```javascript var a = [[1,2], [3,4]]; @@ -306,6 +323,24 @@ ws.forEach(function(item){ console.log('WeakSet has ' + item)}) WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保存成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。 +下面是WeakMap的另一个例子。 + +```javascript +const foos = new WeakSet() +class Foo { + constructor() { + foos.add(this) + } + method () { + if (!foos.has(this)) { + throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!') + } + } +} +``` + +上面代码保证了`Foo`的实例方法,只能在`Foo`的实例上调用。这里使用WeakSet的好处是,数组`foos`对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑`foos`,也不会出现内存泄漏。 + ## Map ### Map结构的目的和基本用法 @@ -341,7 +376,7 @@ m.has(o) // false 作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。 ```javascript -var map = new Map([ ["name", "张三"], ["title", "Author"]]); +var map = new Map([["name", "张三"], ["title", "Author"]]); map.size // 2 map.has("name") // true @@ -352,6 +387,17 @@ map.get("title") // "Author" 上面代码在新建Map实例时,就指定了两个键name和title。 +Map构造函数接受数组作为参数,实际上执行的是下面的算法。 + +```javascript +var items = [ + ["name", "张三"], + ["title", "Author"] +]; +var map = new Map(); +items.forEach(([key, value]) => map.set(key, value)); +``` + 如果对同一个键多次赋值,后面的值将覆盖前面的值。 ```javascript @@ -416,23 +462,21 @@ map.get(+0) // 123 Map结构的实例有以下属性和操作方法。 -- size:返回成员总数。 -- set(key, value):设置key所对应的键值,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。 -- get(key):读取key对应的键值,如果找不到key,返回undefined。 -- has(key):返回一个布尔值,表示某个键是否在Map数据结构中。 -- delete(key):删除某个键,返回true。如果删除失败,返回false。 -- clear():清除所有成员,没有返回值。 +**(1)size属性** -set()方法返回的是Map本身,因此可以采用链式写法。 +`size`属性返回Map结构的成员总数。 ```javascript -let map = new Map() - .set(1, 'a') - .set(2, 'b') - .set(3, 'c'); +let map = new Map(); +map.set('foo', true); +map.set('bar', false); + +map.size // 2 ``` -下面是has()和delete()的例子。 +**(2)set(key, value)** + +`set`方法设置`key`所对应的键值,然后返回整个Map结构。如果`key`已经有值,则键值会被更新,否则就新生成该键。 ```javascript var m = new Map(); @@ -440,24 +484,62 @@ var m = new Map(); m.set("edition", 6) // 键是字符串 m.set(262, "standard") // 键是数值 m.set(undefined, "nah") // 键是undefined +``` + +`set`方法返回的是Map本身,因此可以采用链式写法。 + +```javascript +let map = new Map() + .set(1, 'a') + .set(2, 'b') + .set(3, 'c'); +``` + +**(3)get(key)** + +`get`方法读取`key`对应的键值,如果找不到`key`,返回`undefined`。 + +```javascript +var m = new Map(); var hello = function() {console.log("hello");} m.set(hello, "Hello ES6!") // 键是函数 +m.get(hello) // Hello ES6! +``` + +**(4)has(key)** + +`has`方法返回一个布尔值,表示某个键是否在Map数据结构中。 + +```javascript +var m = new Map(); + +m.set("edition", 6); +m.set(262, "standard"); +m.set(undefined, "nah"); + m.has("edition") // true m.has("years") // false m.has(262) // true m.has(undefined) // true -m.has(hello) // true +``` + +**(5)delete(key)** + +`delete`方法删除某个键,返回true。如果删除失败,返回false。 + +```javascript +var m = new Map(); +m.set(undefined, "nah"); +m.has(undefined) // true m.delete(undefined) m.has(undefined) // false - -m.get(hello) // Hello ES6! -m.get("edition") // 6 ``` +**(6)clear()** -下面是size属性和clear方法的例子。 +`clear`方法清除所有成员,没有返回值。 ```javascript let map = new Map(); @@ -471,11 +553,12 @@ map.size // 0 ### 遍历方法 -Map原生提供三个遍历器。 +Map原生提供三个遍历器生成函数和一个遍历方法。 - keys():返回键名的遍历器。 - values():返回键值的遍历器。 - entries():返回所有成员的遍历器。 +- forEach():遍历Map的所有成员。 下面是使用实例。 @@ -514,7 +597,7 @@ for (let [key, value] of map) { } ``` -上面代码最后的那个例子,表示Map结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。 +上面代码最后的那个例子,表示Map结构的默认遍历器接口(`Symbol.iterator`属性),就是`entries`方法。 ```javascript map[Symbol.iterator] === map.entries @@ -584,7 +667,7 @@ map.forEach(function(value, key, map) { }, reporter); ``` -上面代码中,forEach方法的回调函数的this,就指向reporter。 +上面代码中,`forEach`方法的回调函数的`this`,就指向`reporter`。 ### 与其他数据结构的互相转换 @@ -692,11 +775,21 @@ jsonToMap('[[true,7],[{"foo":3},["abc"]]]') ## WeakMap -WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受原始类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。 +`WeakMap`结构与`Map`结构基本类似,唯一的区别是它只接受对象作为键名(`null`除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。 + +```javascript +var map = new WeakMap() +map.set(1, 2) +// TypeError: 1 is not an object! +map.set(Symbol(), 2) +// TypeError: Invalid value used as weak map key +``` + +上面代码中,如果将1和`Symbol`作为WeakMap的键名,都会报错。 -WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,WeakMap自动移除对应的键值对。典型应用是,一个对应DOM元素的WeakMap结构,当某个DOM元素被清除,其所对应的WeakMap记录就会自动被移除。基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。 +`WeakMap`的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,`WeakMap`自动移除对应的键值对。典型应用是,一个对应DOM元素的`WeakMap`结构,当某个DOM元素被清除,其所对应的`WeakMap`记录就会自动被移除。基本上,`WeakMap`的专用场合就是,它的键所对应的对象,可能会在将来消失。`WeakMap`结构有助于防止内存泄漏。 -下面是WeakMap结构的一个例子,可以看到用法上与Map几乎一样。 +下面是`WeakMap`结构的一个例子,可以看到用法上与`Map`几乎一样。 ```javascript var wm = new WeakMap(); @@ -710,9 +803,9 @@ element = null; wm.get(element) // undefined ``` -上面代码中,变量wm是一个WeakMap实例,我们将一个DOM节点element作为键名,然后销毁这个节点,element对应的键就自动消失了,再引用这个键名就返回undefined。 +上面代码中,变量`wm`是一个`WeakMap`实例,我们将一个`DOM`节点`element`作为键名,然后销毁这个节点,`element`对应的键就自动消失了,再引用这个键名就返回`undefined`。 -WeakMap与Map在API上的区别主要是两个,一是没有遍历操作(即没有key()、values()和entries()方法),也没有size属性;二是无法清空,即不支持clear方法。这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。 +WeakMap与Map在API上的区别主要是两个,一是没有遍历操作(即没有`key()`、`values()`和`entries()`方法),也没有`size`属性;二是无法清空,即不支持`clear`方法。这与`WeakMap`的键不被计入引用、被垃圾回收机制忽略有关。因此,`WeakMap`只有四个方法可用:`get()`、`set()`、`has()`、`delete()`。 ```javascript var wm = new WeakMap(); From c6846370994bdb7a4ff2cc0f2e466b5f2df99de4 Mon Sep 17 00:00:00 2001 From: LeeY Date: Sun, 13 Sep 2015 18:24:31 +0800 Subject: [PATCH 0006/1221] update let.md --- docs/let.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/let.md b/docs/let.md index de2c96e58..dd9ad5eef 100644 --- a/docs/let.md +++ b/docs/let.md @@ -205,10 +205,10 @@ function f(){ } } -f() // 没有输出 +f() // undefined ``` -上面代码中,函数f执行后没有任何输出,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。 +上面代码中,函数f执行后,输出结果为`undefined`,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。 第二种场景,用来计数的循环变量泄露为全局变量。 From 24548c3f3f0b823b64100c70d87078c2d51d34ca Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 13 Sep 2015 21:17:33 +0800 Subject: [PATCH 0007/1221] add function's name property --- docs/function.md | 54 +++++++++++++++++++++++++++++++++++++++++++---- docs/module.md | 28 ++++++++++-------------- docs/object.md | 21 +++++++++++++++--- docs/reference.md | 1 + 4 files changed, 80 insertions(+), 24 deletions(-) diff --git a/docs/function.md b/docs/function.md index 26ef1f5a3..e87ade1b4 100644 --- a/docs/function.md +++ b/docs/function.md @@ -280,7 +280,7 @@ var args = [0, 1, 2]; f(...args); ``` -下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法。 +下面是扩展运算符取代`apply`方法的一个实际的例子,应用`Math.max`方法,简化求出一个数组最大元素的写法。 ```javascript // ES5的写法 @@ -295,7 +295,7 @@ Math.max(14, 3, 77); 上面代码表示,由于JavaScript不提供求数组最大元素的函数,所以只能套用`Math.max`函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用`Math.max`了。 -另一个例子是通过push函数,将一个数组添加到另一个数组的尾部。 +另一个例子是通过`push`函数,将一个数组添加到另一个数组的尾部。 ```javascript // ES5的写法 @@ -309,7 +309,7 @@ var arr2 = [3, 4, 5]; arr1.push(...arr2); ``` -上面代码的ES5写法中,push方法的参数不能是数组,所以只好通过apply方法变通使用push方法。有了扩展运算符,就可以直接将数组传入push方法。 +上面代码的ES5写法中,`push`方法的参数不能是数组,所以只好通过`apply`方法变通使用`push`方法。有了扩展运算符,就可以直接将数组传入`push`方法。 扩展运算符与正常的函数参数可以结合使用,非常灵活。 @@ -426,7 +426,53 @@ var go = function*(){ [...go()] // [1, 2, 3] ``` -上面代码中,变量go是一个Generator函数,执行后返回的是一个遍历器,对这个遍历器执行扩展运算符,就会将内部遍历得到的值,转为一个数组。 +上面代码中,变量go是一个Generator函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。 + +## name属性 + +函数的`name`属性,返回该函数的函数名。 + +```javascript +function foo() {} +foo.name // "foo" +``` + +这个属性早就被浏览器广泛支持,但是直到ES6,才将其写入了标准。 + +需要注意的是,ES6对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5的`name`属性,会返回空字符串,而ES6的`name`属性会返回实际的函数名。 + +```javascript +var func1 = function () {}; + +// ES5 +func1.name // "" + +// ES6 +func1.name // "func1" +``` + +上面代码中,变量`func1`等于一个匿名函数,ES5和ES6的`name`属性返回的值不一样。 + +如果将一个具名函数赋值给一个变量,则ES5和ES6的`name`属性都返回这个具名函数原本的名字。 + +```javascript +const bar = function baz() {}; + +// ES5 +bar.name // "baz" + +// ES6 +bar.name // "baz" +``` + +只有具名函数才有`name`这个属性,匿名函数是没有的。 + +```javascript +'name' in (function () {}) +// false +'name' in (() => {}) +// false +``` ## 箭头函数 diff --git a/docs/module.md b/docs/module.md index 6deacfb43..fb46a3643 100644 --- a/docs/module.md +++ b/docs/module.md @@ -77,11 +77,11 @@ export { 上面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次。 -最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下面的import命令也是如此。 +最后,`export`命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下面的`import`命令也是如此。 ## import命令 -使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件)。 +使用`export`命令定义了模块的对外接口以后,其他JS文件就可以通过`import`命令加载这个模块(文件)。 ```javascript // main.js @@ -93,7 +93,7 @@ function setName(element) { } ``` -上面代码的import命令,就用于加载profile.js文件,并从中输入变量。import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。 +上面代码的`import`命令,就用于加载`profile.js`文件,并从中输入变量。`import`命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(`profile.js`)对外接口的名称相同。 如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。 @@ -127,7 +127,7 @@ import { es6 } from './someModule'; export default es6; ``` -上面代码中,export和import语句可以结合在一起,写成一行。但是从可读性考虑,不建议采用这种写法,h应该采用标准写法。 +上面代码中,`export`和`import`语句可以结合在一起,写成一行。但是从可读性考虑,不建议采用这种写法,而应该采用标准写法。 ## 模块的整体输入 @@ -182,7 +182,7 @@ module命令后面跟一个变量,表示输入的模块定义在该变量上 ## export default命令 -从前面的例子可以看出,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。 +从前面的例子可以看出,使用`import`命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。 为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到`export default`命令,为模块指定默认输出。 @@ -203,9 +203,9 @@ import customName from './export-default'; customName(); // 'foo' ``` -上面代码的import命令,可以用任意名称指向`export-default.js`输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号。 +上面代码的import命令,可以用任意名称指向`export-default.js`输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时`import`命令后面,不使用大括号。 -export default命令用在非匿名函数前,也是可以的。 +`export default`命令用在非匿名函数前,也是可以的。 ```javascript // export-default.js @@ -222,7 +222,7 @@ function foo() { export default foo; ``` -上面代码中,foo函数的函数名foo,在模块外部是无效的。加载的时候,视同匿名函数加载。 +上面代码中,`foo`函数的函数名`foo`,在模块外部是无效的。加载的时候,视同匿名函数加载。 下面比较一下默认输出和正常输出。 @@ -236,20 +236,14 @@ import { crc32 } from 'crc32'; export function crc32(){}; ``` -上面代码的两组写法,第一组是使用`export default`时,对应的import语句不需要使用大括号;第二组是不使用`export default`时,对应的import语句需要使用大括号。 +上面代码的两组写法,第一组是使用`export default`时,对应的`import`语句不需要使用大括号;第二组是不使用`export default`时,对应的`import`语句需要使用大括号。 -`export default`命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此`export deault`命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能对应一个方法。 +`export default`命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此`export deault`命令只能使用一次。所以,`import`命令后面才不用加大括号,因为只可能对应一个方法。 -本质上,`export default`就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。 +本质上,`export default`就是输出一个叫做`default`的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。 ```javascript // modules.js -export default function (x, y) { - return x * y; -}; - -// 或者 - function add(x, y) { return x * y; }; diff --git a/docs/object.md b/docs/object.md index 487f8cdd1..6f0fe00a3 100644 --- a/docs/object.md +++ b/docs/object.md @@ -144,7 +144,7 @@ obj.hello() // hi ## 方法的name属性 -函数的name属性,返回函数名。ES6为对象方法也添加了name属性。 +函数的`name`属性,返回函数名。对象方法也是函数,因此也有`name`属性。 ```javascript var person = { @@ -160,9 +160,9 @@ person.sayName.name // "sayName" person.firstName.name // "get firstName" ``` -上面代码中,方法的name属性返回函数名(即方法名)。如果使用了取值函数,则会在方法名前加上get。如果是存值函数,方法名的前面会加上set。 +上面代码中,方法的`name`属性返回函数名(即方法名)。如果使用了取值函数,则会在方法名前加上`get`。如果是存值函数,方法名的前面会加上`set`。 -有两种特殊情况:bind方法创造的函数,name属性返回“bound”加上原函数的名字;Function构造函数创造的函数,name属性返回“anonymous”。 +有两种特殊情况:`bind`方法创造的函数,`name`属性返回“bound”加上原函数的名字;`Function`构造函数创造的函数,`name`属性返回“anonymous”。 ```javascript (new Function()).name // "anonymous" @@ -173,6 +173,21 @@ var doSomething = function() { doSomething.bind().name // "bound doSomething" ``` +如果对象的方法是一个Symbol值,那么`name`属性返回的是这个Symbol值的描述。 + +```javascript +const key1 = Symbol('description'); +const key2 = Symbol(); +let obj = { + [key1]() {}, + [key2]() {}, +}; +obj[key1].name // "[description]" +obj[key2].name // "" +``` + +上面代码中,`key1`对应的Symbol值有描述,`key2`没有。 + ## Object.is() Object.is()用来比较两个值是否严格相等。它与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身。 diff --git a/docs/reference.md b/docs/reference.md index 1935b890a..a9578d4c1 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -62,6 +62,7 @@ - Axel Rauschmayer, [Handling required parameters in ECMAScript 6](http://www.2ality.com/2014/04/required-parameters-es6.html) - Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值 - Ragan Wald, [Destructuring and Recursion in ES6](http://raganwald.com/2015/02/02/destructuring.html): rest参数和扩展运算符的详细介绍 +- Axel Rauschmayer, [The names of functions in ES6](http://www.2ality.com/2015/09/function-names-es6.html): 函数的name属性的详细介绍 ## 对象 From 8ac0c18bd724815ea2d4bfbc65328cf9b657efdf Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 17 Sep 2015 11:49:48 +0800 Subject: [PATCH 0008/1221] edit class/no static property --- docs/class.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/class.md b/docs/class.md index bcca32970..588670014 100644 --- a/docs/class.md +++ b/docs/class.md @@ -416,7 +416,7 @@ class ColorPoint extends Point { } ``` -上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。 +上面代码中,子类的`constructor`方法没有调用`super`之前,就使用`this`关键字,结果报错,而放在`super`方法之后就是正确的。 下面是生成子类实例的代码。 @@ -814,7 +814,7 @@ for (let x of new Foo('hello', 'world')) { ## Class的静态方法 -类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。 +类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上`static`关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。 ```javascript class Foo { @@ -830,7 +830,23 @@ foo.classMethod() // TypeError: undefined is not a function ``` -上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(`Foo.classMethod()`),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。 +上面代码中,`Foo`类的`classMethod`方法前有`static`关键字,表明该方法是一个静态方法,可以直接在`Foo`类上调用(`Foo.classMethod()`),而不是在`Foo`类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。 + +需要注意的是,类只有静态方法,没有静态属性,像`Class.propname`这样的用法不存在。 + +```javascript +// 以下两种写法都无效, +// 但不会报错 +class Foo { + // 写法一 + prop: 2 + + // 写法二 + static prop: 2 +} + +Foo.prop // undefined +``` 父类的静态方法,可以被子类继承。 @@ -849,7 +865,7 @@ Bar.classMethod(); // 'hello' 上面代码中,父类Foo有一个静态方法,子类Bar可以调用这个方法。 -静态方法也是可以从super对象上调用的。 +静态方法也是可以从`super`对象上调用的。 ```javascript class Foo { From 97f6f6d98e3d75e51e810f850ff4a91497d1d43f Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 18 Sep 2015 08:06:09 +0800 Subject: [PATCH 0009/1221] add ES7 features --- docs/array.md | 18 ++++++-- docs/class.md | 70 +++++++++++++++++++++---------- docs/function.md | 102 +++++++++++++++++++++++++++++++++++++++------- docs/intro.md | 104 ++++++++++++++++++++++++++++++++++++++++------- docs/module.md | 28 +++++++++---- 5 files changed, 261 insertions(+), 61 deletions(-) diff --git a/docs/array.md b/docs/array.md index ce4d5ef22..939d85cdf 100644 --- a/docs/array.md +++ b/docs/array.md @@ -317,9 +317,9 @@ var a2 = [for (i of a1) i * 2]; a2 // [2, 4, 6, 8] ``` -上面代码表示,通过for...of结构,数组a2直接在a1的基础上生成。 +上面代码表示,通过`for...of`结构,数组`a2`直接在`a1`的基础上生成。 -注意,数组推导中,for...of结构总是写在最前面,返回的表达式写在最后面。 +注意,数组推导中,`for...of`结构总是写在最前面,返回的表达式写在最后面。 for...of后面还可以附加if语句,用来设定循环的限制条件。 @@ -386,8 +386,20 @@ var a3 = ["x3", "y3"]; 数组推导需要注意的地方是,新数组会立即在内存中生成。这时,如果原数组是一个很大的数组,将会非常耗费内存。 +推导的用法不限于数组,还可以直接使用。 + +```javascript +var results = ( + for (c of customers) + if (c.city == "Seattle") + { name: c.name, age: c.age } +) +``` + ## Array.observe(),Array.unobserve() 这两个方法用于监听(取消监听)数组的变化,指定回调函数。 -它们的用法与Object.observe和Object.unobserve方法完全一致,也属于ES7的一部分,请参阅《对象的扩展》一章。唯一的区别是,对象可监听的变化一共有六种,而数组只有四种:add、update、delete、splice(数组的length属性发生变化)。 +它们的用法与`Object.observe`和`Object.unobserve`方法完全一致,也属于ES7的一部分,请参阅《对象的扩展》一章。 + +唯一的区别是,对象可监听的变化一共有六种,而数组只有四种:`add`、`update`、`delete`、`splice`(数组的`length`属性发生变化)。 diff --git a/docs/class.md b/docs/class.md index 588670014..9dfb662f3 100644 --- a/docs/class.md +++ b/docs/class.md @@ -37,9 +37,9 @@ class Point { } ``` -上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5的构造函数Point,对应ES6的Point类的构造方法。 +上面代码定义了一个“类”,可以看到里面有一个`constructor`方法,这就是构造方法,而`this`关键字则代表实例对象。也就是说,ES5的构造函数Point,对应ES6的Point类的构造方法。 -Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个保留字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。 +Point类除了构造方法,还定义了一个`toString`方法。注意,定义“类”的方法的时候,前面不需要加上`function`这个保留字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。 ES6的类,完全可以看作构造函数的另一种写法。 @@ -240,9 +240,9 @@ p1.__proto__ === p2.__proto__ //true ``` -上面代码中,p1和p2都是Point的实例,它们的原型都是Point,所以\_\_proto\_\_属性是相等的。 +上面代码中,`p1`和`p2`都是Point的实例,它们的原型都是Point,所以`__proto__`属性是相等的。 -这也意味着,可以通过实例的\_\_proto\_\_属性为Class添加方法。 +这也意味着,可以通过实例的`__proto__`属性为Class添加方法。 ```javascript var p1 = new Point(2,3); @@ -257,7 +257,7 @@ var p3 = new Point(4,2); p3.printName() // "Oops" ``` -上面代码在p1的原型上添加了一个printName方法,由于p1的原型就是p2的原型,因此p2也可以调用这个方法。而且,此后新建的实例p3也可以调用这个方法。这意味着,使用实例的\_\_proto\_\_属性改写原型,必须相当谨慎,不推荐使用,因为这会改变Class的原始定义,影响到所有实例。 +上面代码在`p1`的原型上添加了一个`printName`方法,由于`p1`的原型就是`p2`的原型,因此`p2`也可以调用这个方法。而且,此后新建的实例`p3`也可以调用这个方法。这意味着,使用实例的`__proto__`属性改写原型,必须相当谨慎,不推荐使用,因为这会改变Class的原始定义,影响到所有实例。 **(4)name属性** @@ -832,22 +832,6 @@ foo.classMethod() 上面代码中,`Foo`类的`classMethod`方法前有`static`关键字,表明该方法是一个静态方法,可以直接在`Foo`类上调用(`Foo.classMethod()`),而不是在`Foo`类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。 -需要注意的是,类只有静态方法,没有静态属性,像`Class.propname`这样的用法不存在。 - -```javascript -// 以下两种写法都无效, -// 但不会报错 -class Foo { - // 写法一 - prop: 2 - - // 写法二 - static prop: 2 -} - -Foo.prop // undefined -``` - 父类的静态方法,可以被子类继承。 ```javascript @@ -883,6 +867,50 @@ class Bar extends Foo { Bar.classMethod(); ``` +## Class的静态属性 + +静态属性指的是Class本身的属性,即`Class.propname`,而不是定义在实例对象(`this`)上的属性。 + +ES6明确规定,类只有静态方法,没有静态属性,即像`Class.propname`这样的用法不存在。 + +```javascript +// 以下两种写法都无效, +// 但不会报错 +class Foo { + // 写法一 + prop: 2 + + // 写法二 + static prop: 2 +} + +Foo.prop // undefined +``` + +ES7有一个静态属性的[提案](https://github.com/jeffmo/es-class-properties),目前Babel转码器支持。 + +这个提案对实例属性和静态属性,都规定了新的写法。 + +```javascript +// 实例属性的新写法 +class MyClass { + myProp = 42; + + constructor() { + console.log(this.myProp); // 42 + } +} + +// 静态属性的新写法 +class MyClass { + static myStaticProp = 42; + + constructor() { + console.log(MyClass.myProp); // 42 + } +} +``` + ## new.target属性 new是从构造函数生成实例的命令。ES6为new命令引入了一个`new.target`属性,(在构造函数中)返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,`new.target`会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。 diff --git a/docs/function.md b/docs/function.md index e87ade1b4..aa68021d2 100644 --- a/docs/function.md +++ b/docs/function.md @@ -400,7 +400,7 @@ var nodeList = document.querySelectorAll('div'); var array = [...nodeList]; ``` -上面代码中,querySelectorAll方法返回的是一个nodeList对象,扩展运算符可以将其转为真正的数组。 +上面代码中,`querySelectorAll`方法返回的是一个`nodeList`对象,扩展运算符可以将其转为真正的数组。 扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。 @@ -426,7 +426,7 @@ var go = function*(){ [...go()] // [1, 2, 3] ``` -上面代码中,变量go是一个Generator函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。 +上面代码中,变量`go`是一个Generator函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。 ## name属性 @@ -580,15 +580,25 @@ headAndTail(1, 2, 3, 4, 5) 箭头函数有几个使用注意点。 -(1)函数体内的this对象,绑定定义时所在的对象,而不是使用时所在的对象。 +(1)函数体内的`this`对象,绑定定义时所在的对象,而不是使用时所在的对象。 -(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 +(2)不可以当作构造函数,也就是说,不可以使用`new`命令,否则会抛出一个错误。 -(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。 +(3)不可以使用`arguments`对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。 -(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。 +(4)不可以使用`yield`命令,因此箭头函数不能用作Generator函数。 -上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。下面的代码是一个例子,将this对象绑定定义时所在的对象。 +上面四点中,第一点尤其值得注意。`this`对象的指向是可变的,但是在箭头函数中,它是固定的。 + +```javascript +[1, 2, 3].map(n => n * 2); + +// 等同于 + +[1, 2, 3].map(function(n) { return n * 2; }, this); +``` + +下面的代码是一个例子,将this对象绑定定义时所在的对象。 ```javascript var handler = { @@ -605,7 +615,7 @@ var handler = { }; ``` -上面代码的init方法中,使用了箭头函数,这导致this绑定handler对象,否则回调函数运行时,`this.doSomething`这一行会报错,因为此时this指向全局对象。 +上面代码的`init`方法中,使用了箭头函数,这导致`this`绑定`handler`对象,否则回调函数运行时,`this.doSomething`这一行会报错,因为此时`this`指向全局对象。 ```javascript function Timer () { @@ -690,15 +700,11 @@ var fix = f => (x => f(v => x(x)(v))) ## 函数绑定 -箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以ES7提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。虽然该语法还是ES7的一个提案,但是Babel转码器已经支持。 +箭头函数可以绑定`this`对象,大大减少了显式绑定`this`对象的写法(`call`、`apply`、`bind`)。但是,箭头函数并不适用于所有场合,所以ES7提出了“函数绑定”(function bind)运算符,用来取代`call`、`apply`、`bind`调用。虽然该语法还是ES7的一个[提案](https://github.com/zenparsing/es-function-bind),但是Babel转码器已经支持。 -函数绑定运算符是并排的两个双引号(::),双引号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。 +函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。 ```javascript -let log = ::console.log; -// 等同于 -var log = console.log.bind(console); - foo::bar; // 等同于 bar.call(foo); @@ -706,6 +712,42 @@ bar.call(foo); foo::bar(...arguments); i// 等同于 bar.apply(foo, arguments); + +const hasOwnProperty = Object.prototype.hasOwnProperty; +function hasOwn(obj, key) { + return obj::hasOwnProperty(key); +} +``` + +如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。 + +```javascript +var method = obj::obj.foo; +// 等同于 +var method = ::obj.foo; + +let log = ::console.log; +// 等同于 +var log = console.log.bind(console); +``` + +由于双冒号运算符返回的还是原对象,因此可以采用链式写法。 + +```javascript +// 例一 +import { map, takeWhile, forEach } from "iterlib"; + +getPlayers() +::map(x => x.character()) +::takeWhile(x => x.strength > 100) +::forEach(x => console.log(x)); + +// 例二 +let { find, html } = jake; + +document.querySelectorAll("div.myClass") +::find("p") +::html("hahaha"); ``` ## 尾调用优化 @@ -897,3 +939,35 @@ factorial(5) // 120 上面代码中,参数 total 有默认值1,所以调用时不用提供这个值。 总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。 + +## 函数参数的尾逗号 + +ES7有一个[提案](https://github.com/jeffmo/es-trailing-function-commas),允许函数的最后一个参数有尾逗号(trailing comma)。 + +目前,函数定义和调用时,都不允许有参数的尾逗号。 + +```javascript +function clownsEverywhere( + param1, + param2 +) { /* ... */ } + +clownsEverywhere( + 'foo', + 'bar' +); +``` + +如果以后要在函数的定义之中添加参数,就势必还要添加一个逗号。这对版本管理系统来说,就会显示,添加逗号的那一行也发生了变动。这看上去有点冗余,因此新提案允许定义和调用时,尾部直接有一个逗号。 + +```javascript +function clownsEverywhere( + param1, + param2, +) { /* ... */ } + +clownsEverywhere( + 'foo', + 'bar', +); +``` diff --git a/docs/intro.md b/docs/intro.md index f0d91e829..33189927b 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -52,9 +52,9 @@ Node.js和io.js(一个部署新功能更快的Node分支)是JavaScript语言 $ curl -o- https://raw.githubusercontent.com/creationix/nvm//install.sh | bash ``` -上面命令的version number处,需要用版本号替换。本书写作时的版本号是v0.25.4。 +上面命令的`version number`处,需要用版本号替换。本节写作时的版本号是`v0.25.4`。 -该命令运行后,nvm会默认安装在用户主目录的`.nvm`子目录。然后,激活nvm。 +该命令运行后,`nvm`会默认安装在用户主目录的`.nvm`子目录。然后,激活`nvm`。 ```bash $ source ~/.nvm/nvm.sh @@ -78,7 +78,7 @@ $ nvm use node $ nvm use iojs ``` -需要注意的是,Node.js对ES6的支持,需要打开harmony参数,iojs不需要。 +需要注意的是,Node.js对ES6的支持,需要打开`harmony`参数,iojs不需要。 ``` $ node --harmony @@ -139,7 +139,9 @@ input.map(function (item) { 上面的原始代码用了箭头函数,这个特性还没有得到广泛支持,Babel将其转为普通函数,就能在现有的JavaScript环境执行了。 -它的安装命令如下。 +### 命令行环境 + +命令行下,Babel的安装命令如下。 ```bash $ npm install --global babel @@ -195,7 +197,17 @@ $ babel -d build-dir source-dir $ babel -d build-dir source-dir -s ``` -Babel也可以用于浏览器。 +### 浏览器环境 + +Babel也可以用于浏览器。它的浏览器版本,可以通过安装`babel-core`模块获取。 + +```bash +$ npm install babel-core +``` + +运行上面的命令以后,就可以在当前目录的`node_modules/babel-core/`子目录里面,找到`babel`的浏览器版本`browser.js`(未精简)和`browser.min.js`(已精简)。 + +然后,将下面的代码插入网页。 ```html @@ -204,14 +216,18 @@ Babel也可以用于浏览器。 ``` -上面代码中,`browser.js`是Babel提供的转换器脚本,可以在浏览器运行。用户的ES6脚本放在script标签之中,但是要注明`type="text/babel"`。 +上面代码中,`browser.js`是Babel提供的转换器脚本,可以在浏览器运行。用户的ES6脚本放在`script`标签之中,但是要注明`type="text/babel"`。 + +这种写法是实时将ES6代码转为ES5,对网页性能会有影响。生产环境需要加载已经转码完成的脚本。 -Babel配合Browserify一起使用,可以生成浏览器能够直接加载的脚本。 +`Babel`配合`Browserify`一起使用,可以生成浏览器能够直接加载的脚本。 ```bash $ browserify script.js -t babelify --outfile bundle.js ``` +上面代码将ES6脚本`script.js`,转为`bundle.js`。浏览器直接加载后者就可以了,不用再加载`browser.js`。 + 在`package.json`设置下面的代码,就不用每次命令行都输入参数了。 ```javascript @@ -225,6 +241,38 @@ $ browserify script.js -t babelify --outfile bundle.js } ``` +### Node环境 + +Node脚本之中,需要转换ES6脚本,可以像下面这样写。 + +先安装`babel-core`。 + +```javascript +$ npm install --save-dev babel-core +``` + +然后在脚本中,调用`babel-core`的`transform`方法。 + +```javascript +require("babel-core").transform("code", options); +``` + +上面代码中,`transform`方法的第一个参数是一个字符串,表示ES6代码。 + +Node脚本还有一种特殊的`babel`用法,即把`babel`加载为`require`命令的一个钩子。先将`babel`全局安装。 + +```bash +$ npm install -g babel +``` + +然后,在你的应用的入口脚本(entry script)头部,加入下面的语句。 + +```javascript +require("babel/register"); +``` + +有了上面这行语句,后面所有通过`require`命令加载的后缀名为`.es6`、`.es`、`.jsx`和`.js`的脚本,都会先通过`babel`转码后再加载。 + ## Traceur转码器 Google公司的[Traceur](https://github.com/google/traceur-compiler)转码器,也可以将ES6代码转为ES5代码。 @@ -342,7 +390,7 @@ $ traceur --script calc.es6.js --out calc.es5.js --experimental 命令行下转换的文件,就可以放到浏览器中运行。 -### Node.js环境的用法 +### Node.js环境的用法 Traceur的Node.js用法如下(假定已安装traceur模块)。 @@ -373,16 +421,42 @@ fs.writeFileSync('out.js.map', result.sourceMap); 2013年3月,ES6的草案封闭,不再接受新功能了。新的功能将被加入ES7。 -ES7可能包括的功能有: +任何人都可以向ES7提案,从提案到变成正式标准,需要经历五个阶段。每个阶段的变动都需要由TC39委员会批准。 + +- Stage 0 - Strawman(展示阶段) +- Stage 1 - Proposal(征求意见阶段) +- Stage 2 - Draft(草案阶段) +- Stage 3 - Candidate(候选人阶段) +- Stage 4 - Finished(定案阶段) -(1)**Object.observe**:用来监听对象(以及数组)的变化。一旦监听对象发生变化,就会触发回调函数。 +一个提案只要能进入Stage 2,就差不多等于肯定会包括在ES7里面。 -(2)**Async函数**:在Promise和Generator函数基础上,提出的异步操作解决方案。 +本书的写作目标之一,是跟踪ECMAScript语言的最新进展。对于那些明确的、或者很有希望列入ES7的功能,尤其是那些Babel已经支持的功能,都将予以介绍。 -(3)**Multi-Threading**:多线程支持。目前,Intel和Mozilla有一个共同的研究项目RiverTrail,致力于让JavaScript多线程运行。预计这个项目的研究成果会被纳入ECMAScript标准。 +本书介绍的ES7功能清单如下。 -(4)**Traits**:它将是“类”功能(class)的一个替代。通过它,不同的对象可以分享同样的特性。 +**Stage 0**: -其他可能包括的功能还有:更精确的数值计算、改善的内存回收、增强的跨站点安全、类型化的更贴近硬件的低级别操作、国际化支持(Internationalization Support)、更多的数据结构等等。 +- es7.comprehensions:数组推导 +- es7.classProperties:类的属性 +- es7.functionBind:函数的绑定运算符 + +**Stage 1**: + +- es7.decorators:修饰器 +- es7.exportExtensions:export的扩展写法 +- es7.trailingFunctionCommas:函数参数的尾逗号 + +**Stage 2**: + +- es7.exponentiationOperator:指数运算符 +- es7.asyncFunctions:ansyc函数 +- es7.objectRestSpread:对象的Rest参数和扩展运算符 + +Babel转码器对Stage 2及以上阶段的功能,是默认支持的。对于那些默认没有打开的功能,需要手动打开。 + +```bash +$ babel --stage 0 +$ babel --optional es7.decorators +``` -本书的写作目标之一,是跟踪ECMAScript语言的最新进展。对于那些明确的、或者很有希望列入ES7的功能,尤其是那些Babel已经支持的功能,都将予以介绍。 diff --git a/docs/module.md b/docs/module.md index fb46a3643..767fb3682 100644 --- a/docs/module.md +++ b/docs/module.md @@ -2,7 +2,7 @@ ES6的Class只是面向对象编程的语法糖,升级了ES5的构造函数的原型链继承的写法,并没有解决模块化问题。Module功能就是为了解决这个问题而提出的。 -历史上,JavaScript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如Ruby的require、Python的import,甚至就连CSS都有@import,但是JavaScript任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。 +历史上,JavaScript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如Ruby的`require`、Python的`import`,甚至就连CSS都有`@import`,但是JavaScript任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。 在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。 @@ -95,7 +95,7 @@ function setName(element) { 上面代码的`import`命令,就用于加载`profile.js`文件,并从中输入变量。`import`命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(`profile.js`)对外接口的名称相同。 -如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。 +如果想为输入的变量重新取一个名字,import命令要使用`as`关键字,将输入的变量重命名。 ```javascript import { lastName as surname } from './profile'; @@ -115,9 +115,9 @@ class Car extends Vehicle { export { Car } ``` -上面的模块先加载Vehicle模块,然后在其基础上添加了move方法,再作为一个新模块输出。 +上面的模块先加载`Vehicle`模块,然后在其基础上添加了`move`方法,再作为一个新模块输出。 -如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。 +如果在一个模块之中,先输入后输出同一个模块,`import`语句可以与`export`语句写在一起。 ```javascript export { es6 as default } from './someModule'; @@ -129,9 +129,21 @@ export default es6; 上面代码中,`export`和`import`语句可以结合在一起,写成一行。但是从可读性考虑,不建议采用这种写法,而应该采用标准写法。 -## 模块的整体输入 +另外,ES7有一个[提案](https://github.com/leebyron/ecmascript-more-export-from),简化先输入后输出的写法,拿掉输出时的大括号。 -下面是一个circle.js文件,它输出两个方法area和circumference。 +```javascript +// 提案的写法 +export v from "mod"; + +// 现行的写法 +export {v} from "mod"; +``` + +## 模块的整体加载 + +除了指定加载某个输出值,还可以使用整体加载,即用星号(`*`)指定一个对象,所有输出值都加载在这个对象上面。 + +下面是一个`circle.js`文件,它输出两个方法`area`和`circumference`。 ```javascript // circle.js @@ -145,7 +157,7 @@ export function circumference(radius) { } ``` -然后,main.js文件输入circle.js模块。 +现在,加载这个模块。 ```javascript // main.js @@ -156,7 +168,7 @@ console.log("圆面积:" + area(4)); console.log("圆周长:" + circumference(14)); ``` -上面写法是逐一指定要输入的方法。另一种写法是整体输入。 +上面写法是逐一指定要加载的方法,整体加载的写法如下。 ```javascript import * as circle from './circle'; From 981ef3119594970544d07db019363d52adefc84e Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 18 Sep 2015 08:14:03 +0800 Subject: [PATCH 0010/1221] edit number/safe integer --- docs/number.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/number.md b/docs/number.md index 4225cc110..d7742f6f0 100644 --- a/docs/number.md +++ b/docs/number.md @@ -53,18 +53,15 @@ ES5通过下面的代码,部署Number.isFinite方法。 Number.isNaN()用来检查一个值是否为NaN。 ```javascript - Number.isNaN(NaN); // true Number.isNaN(15); // false Number.isNaN("15"); // false Number.isNaN(true); // false - ``` ES5通过下面的代码,部署Number.isNaN()。 ```javascript - (function (global) { var global_isNaN = global.isNaN; @@ -77,13 +74,11 @@ ES5通过下面的代码,部署Number.isNaN()。 writable: true }); })(this); - ``` 它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,非数值一律返回false。 ```javascript - isFinite(25) // true isFinite("25") // true Number.isFinite(25) // true @@ -93,7 +88,6 @@ isNaN(NaN) // true isNaN("NaN") // true Number.isNaN(NaN) // true Number.isNaN("NaN") // false - ``` ## Number.parseInt(), Number.parseFloat() @@ -112,18 +106,16 @@ Number.parseFloat('123.45#') // 123.45 这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。 -## Number.isInteger()和安全整数 +## Number.isInteger() Number.isInteger()用来判断一个值是否为整数。需要注意的是,在JavaScript内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值。 ```javascript - Number.isInteger(25) // true Number.isInteger(25.0) // true Number.isInteger(25.1) // false Number.isInteger("15") // false Number.isInteger(true) // false - ``` ES5通过下面的代码,部署Number.isInteger()。 @@ -146,7 +138,9 @@ ES5通过下面的代码,部署Number.isInteger()。 })(this); ``` -JavaScript能够准确表示的整数范围在-2ˆ53 and 2ˆ53之间。ES6引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。 +## 安全整数和Number.isSafeInteger() + +JavaScript能够准确表示的整数范围在`-2^53`到`2^53`之间。ES6引入了`Number.MAX_SAFE_INTEGER`和`Number.MIN_SAFE_INTEGER`这两个常量,用来表示这个范围的上下限。`Number.isSafeInteger()`则是用来判断一个整数是否落在这个范围之内。 ```javascript var inside = Number.MAX_SAFE_INTEGER; From 84de781145cbdddb29cb2c03cdd2400cd2cdca32 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 19 Sep 2015 09:09:26 +0800 Subject: [PATCH 0011/1221] edit proxy --- docs/proxy.md | 254 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 241 insertions(+), 13 deletions(-) diff --git a/docs/proxy.md b/docs/proxy.md index fe28f0caa..5ee09fe10 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -19,7 +19,7 @@ var obj = new Proxy({}, { }); ``` -上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写它的属性,就会得到下面的结果。 +上面代码对一个空对象架设了一层拦截,重定义了属性的读取(`get`)和设置(`set`)行为。这里暂时先不解释具体的语法,只看运行结果。对设置了拦截行为的对象`obj`,去读写它的属性,就会得到下面的结果。 ```javascript obj.count = 1 @@ -38,7 +38,7 @@ ES6原生提供Proxy构造函数,用来生成Proxy实例。 var proxy = new Proxy(target, handler) ``` -Proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,`new Proxy()`表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。 +Proxy对象的所有用法,都是上面这种形式,不同的只是`handler`参数的写法。其中,`new Proxy()`表示生成一个Proxy实例,target参数表示所要拦截的目标对象,`handler`参数也是一个对象,用来定制拦截行为。 下面是另一个拦截读取属性行为的例子。 @@ -54,11 +54,23 @@ proxy.name // 35 proxy.title // 35 ``` -上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。 +上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个`get`方法,用来拦截对目标对象属性的访问请求。`get`方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回`35`,所以访问任何属性都得到`35`。 注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。 -一个技巧是将Proxy对象,设置到`object.proxy`属性,从而可以在object对象上调用。 +如果`handler`没有设置任何拦截,那就等同于直接通向原对象。 + +```javascript +var target = {}; +var handler = {}; +var proxy = new Proxy(target, handler); +proxy.a = 'b'; +target.a // "b" +``` + +上面代码中,`handler`是一个空对象,没有任何拦截效果,访问`handeler`就等同于访问`target`。 + +一个技巧是将Proxy对象,设置到`object.proxy`属性,从而可以在`object`对象上调用。 ```javascript var object = { proxy: new Proxy(target, handler) } @@ -77,7 +89,7 @@ let obj = Object.create(proxy); obj.time // 35 ``` -上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。 +上面代码中,`proxy`对象是`obj`对象的原型,`obj`对象本身并没有`time`属性,所以根据原型链,会在`proxy`对象上读取该属性,导致被拦截。 同一个拦截器函数,可以设置拦截多个操作。 @@ -229,10 +241,11 @@ pipe(3) . double . pow . reverseInt . get ### set() -set方法用来拦截某个属性的赋值操作。假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证age的属性值符合要求。 +`set`方法用来拦截某个属性的赋值操作。 -```javascript +假定Person对象有一个`age`属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证`age`的属性值符合要求。 +```javascript let validator = { set: function(obj, prop, value) { if (prop === 'age') { @@ -256,20 +269,58 @@ person.age = 100; person.age // 100 person.age = 'young' // 报错 person.age = 300 // 报错 +``` +上面代码中,由于设置了存值函数`set`,任何不符合要求的`age`属性赋值,都会抛出一个错误。利用`set`方法,还可以数据绑定,即每当对象发生变化时,会自动更新DOM。 + +有时,我们会在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合`get`和`set`方法,就可以做到防止这些内部属性被外部读写。 + +```javascript +var handler = { + get (target, key) { + invariant(key, 'get'); + return target[key]; + }, + set (target, key, value) { + invariant(key, 'set'); + return true; + } +} +function invariant (key, action) { + if (key[0] === '_') { + throw new Error(`Invalid attempt to ${action} private "${key}" property`); + } +} +var target = {}; +var proxy = new Proxy(target, handler); +proxy._prop +// Error: Invalid attempt to get private "_prop" property +proxy._prop = 'c' +// Error: Invalid attempt to set private "_prop" property ``` -上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新DOM。 +上面代码中,只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的。 ### apply() -apply方法拦截函数的调用、call和apply操作。 +`apply`方法拦截函数的调用、call和apply操作。 ```javascript +var handler = { + apply (target, ctx, args) { + return Reflect.apply(...arguments); + } +} +``` + +`apply`方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(`this`)和目标对象的参数数组。 +下面是一个例子。 + +```javascript var target = function () { return 'I am the target'; }; var handler = { - apply: function (receiver, ...args) { + apply: function () { return 'I am the proxy'; } }; @@ -281,11 +332,166 @@ p() === 'I am the proxy'; ``` -上面代码中,变量p是Proxy的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。 +上面代码中,变量p是Proxy的实例,当它作为函数调用时(`p()`),就会被apply方法拦截,返回一个字符串。 + +下面是另外一个例子。 + +```javascript +var twice = { + apply (target, ctx, args) { + return Reflect.apply(...arguments) * 2; + } +} +function sum (left, right) { + return left + right; +} +var proxy = new Proxy(sum, twice); +proxy(1, 2) // 6 +proxy.call(null, 5, 6) // 22 +proxy.apply(null, [7, 8]) // 30 +``` + +上面代码中,每当执行`proxy`函数,就会被`apply`方法拦截。 + +另外,直接调用`Reflect.apply`方法,也会被拦截。 + +```javascript +Reflect.apply(proxy, null, [9, 10]) // 38 +``` + +### has() + +`has`方法可以隐藏某些属性,不被`in`操作符发现。 + +```javascript +var handler = { + has (target, key) { + if (key[0] === '_') { + return false; + } + return key in target; + } +} +var target = { _prop: 'foo', prop: 'foo' }; +'_prop' in proxy +// false +``` + +上面代码中,如果原对象的属性名的第一个字符是下划线,`proxy.has`就会返回`false`,从而不会被`in`运算符发现。 + +如果原对象不可配置或者禁止扩展,这时`has`拦截会报错。 + +```javascript +var obj = { a: 10 }; +Object.preventExtensions(obj); +var p = new Proxy(obj, { + has: function(target, prop) { + return false; + } +}); + +"a" in p; // TypeError is thrown +``` + +上面代码中,`obj`对象禁止扩展,结果使用`has`拦截就会报错。 + +### deleteProperty() + +`deleteProperty`方法用于拦截`delete`操作,如果这个方法抛出错误或者返回`false`,当前属性就无法被`delete`命令删除。 + +```javascript +var handler = { + deleteProperty (target, key) { + invariant(key, 'delete'); + return true; + } +} +function invariant (key, action) { + if (key[0] === '_') { + throw new Error(`Invalid attempt to ${action} private "${key}" property`); + } +} + +var target = { _prop: 'foo' } +var proxy = new Proxy(target, handler) +delete proxy._prop +// Error: Invalid attempt to delete private "_prop" property +``` + +上面代码中,`deleteProperty`方法拦截了`delete`操作符,删除第一个字符为下划线的属性会报错。 + +### defineProperty() + +`defineProperty`方法拦截了`Object.defineProperty`操作。 + +```javascript +var handler = { + defineProperty (target, key, descriptor) { + return false + } +} +var target = {} +var proxy = new Proxy(target, handler) +proxy.foo = 'bar' +// TypeError: proxy defineProperty handler returned false for property '"foo"' +``` + +上面代码中,`defineProperty`方法返回`false`,导致添加新属性会抛出错误。 + +### enumerate() + +`enumerate`方法用来拦截`for...in`循环。注意与Proxy对象的`has`方法区分,后者用来拦截`in`操作符,对`for...in`循环无效。 + +```javascript +var handler = { + enumerate (target) { + return Object.keys(target).filter(key => key[0] !== '_')[Symbol.iterator](); + } +} +var target = { prop: 'foo', _bar: 'baz', _prop: 'foo' } +var proxy = new Proxy(target, handler) +for (let key in proxy) { + console.log(key); + // "prop" +} +``` + +上面代码中,`enumerate`方法取出原对象的所有属性名,将其中第一个字符等于下划线的都过滤掉,然后返回这些符合条件的属性名的一个遍历器对象,供`for...in`循环消费。 + +下面是另一个例子。 + +```javascript +var p = new Proxy({}, { + enumerate(target) { + console.log("called"); + return ["a", "b", "c"][Symbol.iterator](); + } +}); + +for (var x in p) { + console.log(x); +} +// "called" +// "a" +// "b" +// "c" +``` + +如果`enumerate`方法返回的不是一个对象,就会报错。 + +```javascript +var p = new Proxy({}, { + enumerate(target) { + return 1; + } +}); + +for (var x in p) {} // 报错 +``` ### ownKeys() -ownKeys方法用来拦截Object.keys()操作。 +`ownKeys`方法用来拦截`Object.keys()`操作。 ```javascript let target = {}; @@ -302,7 +508,29 @@ Object.keys(proxy) // [ 'hello', 'world' ] ``` -上面代码拦截了对于target对象的Object.keys()操作,返回预先设定的数组。 +上面代码拦截了对于`target`对象的`Object.keys()`操作,返回预先设定的数组。 + +下面的例子是拦截第一个字符为下划线的属性名。 + +```javascript +var target = { + _bar: 'foo', + _prop: 'bar', + prop: 'baz' +}; + +var handler = { + ownKeys (target) { + return Reflect.ownKeys(target).filter(key => key[0] !== '_'); + } +}; + +var proxy = new Proxy(target, handler); +for (let key of Object.keys(proxy)) { + console.log(key) +} +// "baz" +``` ## Proxy.revocable() From 8843f242917f352ec6232c7b265964f1816b4b70 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 19 Sep 2015 12:07:45 +0800 Subject: [PATCH 0012/1221] edit proxy --- docs/proxy.md | 180 ++++++++++++++++++++++++++++++++++++++++++++++ docs/reference.md | 3 + 2 files changed, 183 insertions(+) diff --git a/docs/proxy.md b/docs/proxy.md index 5ee09fe10..ecc39c7ac 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -395,6 +395,45 @@ var p = new Proxy(obj, { 上面代码中,`obj`对象禁止扩展,结果使用`has`拦截就会报错。 +### construct() + +`construct`方法用于拦截`new`命令。 + +```javascript +var handler = { + construct (target, args) { + return new target(...args); + } +} +``` + +下面是一个例子。 + +```javascript +var p = new Proxy(function() {}, { + construct: function(target, args) { + console.log('called: ' + args.join(', ')); + return { value: args[0] * 10 }; + } +}); + +new p(1).value +// "called: 1" +// 10 +``` + +如果`construct`方法返回的不是对象,就会抛出错误。 + +```javascript +var p = new Proxy(function() {}, { + construct: function(target, argumentsList) { + return 1; + } +}); + +new p() // 报错 +``` + ### deleteProperty() `deleteProperty`方法用于拦截`delete`操作,如果这个方法抛出错误或者返回`false`,当前属性就无法被`delete`命令删除。 @@ -489,6 +528,92 @@ var p = new Proxy({}, { for (var x in p) {} // 报错 ``` +### getOwnPropertyDescriptor() + +`getOwnPropertyDescriptor`方法拦截`Object.getOwnPropertyDescriptor`,返回一个属性描述对象或者`undefined`。 + +```javascript +var handler = { + getOwnPropertyDescriptor (target, key) { + if (key[0] === '_') { + return + } + return Object.getOwnPropertyDescriptor(target, key) + } +} +var target = { _foo: 'bar', baz: 'tar' }; +var proxy = new Proxy(target, handler); +Object.getOwnPropertyDescriptor(proxy, 'wat') +// undefined +Object.getOwnPropertyDescriptor(proxy, '_foo') +// undefined +Object.getOwnPropertyDescriptor(proxy, 'baz') +// { value: 'tar', writable: true, enumerable: true, configurable: true } +``` + +上面代码中,`handler.getOwnPropertyDescriptor`方法对于第一个字符为下划线的属性名会返回`undefined`。 + +### getPrototypeOf() + +`getPrototypeOf`方法主要用来拦截`Object.getPrototypeOf()`运算符,以及其他一些操作。 + +- `Object.prototype.__proto__` +- `Object.prototype.isPrototypeOf()` +- `Object.getPrototypeOf()` +- `Reflect.getPrototypeOf()` +- `instanceof`运算符 + +下面是一个例子。 + +```javascript +var proto = {}; +var p = new Proxy({}, { + getPrototypeOf(target) { + return proto; + } +}); +Object.getPrototypeOf(p) === proto // true +``` + +上面代码中,`getPrototypeOf`方法拦截`Object.getPrototypeOf()`,返回`proto`对象。 + +### isExtensible() + +`isExtensible`方法拦截`Object.isExtensible`操作。 + +```javascript +var p = new Proxy({}, { + isExtensible: function(target) { + console.log("called"); + return true; + } +}); + +Object.isExtensible(p) +// "called" +// true +``` + +上面代码设置了`isExtensible`方法,在调用`Object.isExtensible`时会输出`called`。 + +这个方法有一个强限制,如果不能满足下面的条件,就会抛出错误。 + +```javascript +Object.isExtensible(proxy) === Object.isExtensible(target) +``` + +下面是一个例子。 + +```javascript +var p = new Proxy({}, { + isExtensible: function(target) { + return false; + } +}); + +Object.isExtensible(p); // 报错 +``` + ### ownKeys() `ownKeys`方法用来拦截`Object.keys()`操作。 @@ -532,6 +657,61 @@ for (let key of Object.keys(proxy)) { // "baz" ``` +### preventExtensions() + +`preventExtensions`方法拦截`Object.preventExtensions()`。该方法必须返回一个布尔值。 + +这个方法有一个限制,只有当`Object.isExtensible(proxy)`为`false`(即不可扩展)时,`proxy.preventExtensions`才能返回true,否则会报错。 + +```javascript +var p = new Proxy({}, { + preventExtensions: function(target) { + return true; + } +}); + +Object.preventExtensions(p); // 报错 +``` + +上面代码中,`proxy.preventExtensions`方法返回`true`,但这时`Object.isExtensible(proxy)`会返回`true`,因此报错。 + +为了防止出现这个问题,通常要在`proxy.preventExtensions`方法里面,调用一次`Object.preventExtensions`。 + +```javascript +var p = new Proxy({}, { + preventExtensions: function(target) { + console.log("called"); + Object.preventExtensions(target); + return true; + } +}); + +Object.preventExtensions(p) +// "called" +// true +``` + +### setPrototypeOf() + +`setPrototypeOf`方法主要用来拦截`Object.setPrototypeOf`方法。 + +下面是一个例子。 + +```javascript +var handler = { + setPrototypeOf (target, proto) { + throw new Error('Changing the prototype is forbidden'); + } +} +var proto = {}; +var target = function () {}; +var proxy = new Proxy(target, handler); +proxy.setPrototypeOf(proxy, proto); +// Error: Changing the prototype is forbidden +``` + +上面代码中,只要修改`target`的原型对象,就会报错。 + ## Proxy.revocable() Proxy.revocable方法返回一个可取消的Proxy实例。 diff --git a/docs/reference.md b/docs/reference.md index a9578d4c1..99434a6a1 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -78,6 +78,9 @@ - Tom Van Cutsem, [Proxy Traps](https://github.com/tvcutsem/harmony-reflect/blob/master/doc/traps.md):Proxy拦截操作一览 - Tom Van Cutsem, [Reflect API](https://github.com/tvcutsem/harmony-reflect/blob/master/doc/api.md) - Tom Van Cutsem, [Proxy Handler API](https://github.com/tvcutsem/harmony-reflect/blob/master/doc/handler_api.md) +- Nicolas Bevacqua, [ES6 Proxies in Depth](http://ponyfoo.com/articles/es6-proxies-in-depth) +- Nicolas Bevacqua, [ES6 Proxy Traps in Depth](http://ponyfoo.com/articles/es6-proxy-traps-in-depth) +- Nicolas Bevacqua, [More ES6 Proxy Traps in Depth](http://ponyfoo.com/articles/more-es6-proxy-traps-in-depth) ## Symbol From 9971d0692608024ad23e61a28b029b1822c9bf4b Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 20 Sep 2015 08:41:12 +0800 Subject: [PATCH 0013/1221] edit Number --- docs/number.md | 164 ++++++++++++++++++++++++++++++++++++++-------- docs/object.md | 2 + docs/proxy.md | 15 ++--- docs/reference.md | 4 ++ 4 files changed, 151 insertions(+), 34 deletions(-) diff --git a/docs/number.md b/docs/number.md index d7742f6f0..5b563297c 100644 --- a/docs/number.md +++ b/docs/number.md @@ -2,14 +2,14 @@ ## 二进制和八进制表示法 -ES6提供了二进制和八进制数值的新的写法,分别用前缀0b和0o表示。 +ES6提供了二进制和八进制数值的新的写法,分别用前缀`0b`和`0o`表示。 ```javascript 0b111110111 === 503 // true 0o767 === 503 // true ``` -八进制不再允许使用前缀0表示,而改为使用前缀0o。 +八进制不再允许使用前缀0表示,而改为使用前缀`0o`。 ```javascript 011 === 9 // 不正确 @@ -18,9 +18,9 @@ ES6提供了二进制和八进制数值的新的写法,分别用前缀0b和0o ## Number.isFinite(), Number.isNaN() -ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法,用来检查Infinite和NaN这两个特殊值。 +ES6在Number对象上,新提供了`Number.isFinite()`和`Number.isNaN()`两个方法,用来检查`Infinite`和`NaN`这两个特殊值。 -Number.isFinite()用来检查一个数值是否非无穷(infinity)。 +`Number.isFinite()`用来检查一个数值是否非无穷(infinity)。 ```javascript Number.isFinite(15); // true @@ -28,12 +28,12 @@ Number.isFinite(0.8); // true Number.isFinite(NaN); // false Number.isFinite(Infinity); // false Number.isFinite(-Infinity); // false -Number.isFinite("foo"); // false -Number.isFinite("15"); // false +Number.isFinite('foo'); // false +Number.isFinite('15'); // false Number.isFinite(true); // false ``` -ES5通过下面的代码,部署Number.isFinite方法。 +ES5可以通过下面的代码,部署`Number.isFinite`方法。 ```javascript (function (global) { @@ -50,16 +50,19 @@ ES5通过下面的代码,部署Number.isFinite方法。 })(this); ``` -Number.isNaN()用来检查一个值是否为NaN。 +`Number.isNaN()`用来检查一个值是否为NaN。 ```javascript -Number.isNaN(NaN); // true -Number.isNaN(15); // false -Number.isNaN("15"); // false -Number.isNaN(true); // false +Number.isNaN(NaN) // true +Number.isNaN(15) // false +Number.isNaN('15') // false +Number.isNaN(true) // false +Number.isNaN(9/NaN) // true +Number.isNaN('true'/0) // true +Number.isNaN('true'/'true') // true ``` -ES5通过下面的代码,部署Number.isNaN()。 +ES5通过下面的代码,部署`Number.isNaN()`。 ```javascript (function (global) { @@ -76,7 +79,7 @@ ES5通过下面的代码,部署Number.isNaN()。 })(this); ``` -它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,非数值一律返回false。 +它们与传统的全局方法`isFinite()`和`isNaN()`的区别在于,传统方法先调用`Number()`将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,非数值一律返回`false`。 ```javascript isFinite(25) // true @@ -92,23 +95,28 @@ Number.isNaN("NaN") // false ## Number.parseInt(), Number.parseFloat() -ES6将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。 +ES6将全局方法`parseInt()`和`parseFloat()`,移植到Number对象上面,行为完全保持不变。 ```javascript // ES5的写法 -parseInt("12.34") // 12 +parseInt('12.34') // 12 parseFloat('123.45#') // 123.45 // ES6的写法 -Number.parseInt("12.34") // 12 +Number.parseInt('12.34') // 12 Number.parseFloat('123.45#') // 123.45 ``` 这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。 +```javascript +Number.parseInt === parseInt // true +Number.parseFloat === parseFloat // true +``` + ## Number.isInteger() -Number.isInteger()用来判断一个值是否为整数。需要注意的是,在JavaScript内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值。 +`Number.isInteger()`用来判断一个值是否为整数。需要注意的是,在JavaScript内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值。 ```javascript Number.isInteger(25) // true @@ -118,7 +126,7 @@ Number.isInteger("15") // false Number.isInteger(true) // false ``` -ES5通过下面的代码,部署Number.isInteger()。 +ES5可以通过下面的代码,部署Number.isInteger()。 ```javascript (function (global) { @@ -138,19 +146,123 @@ ES5通过下面的代码,部署Number.isInteger()。 })(this); ``` +## Number.EPSILON + +ES6在Number对象上面,新增一个极小的常量`Number.EPSILON`。 + +```javascript +Number.EPSILON +// 2.220446049250313e-16 +Number.EPSILON.toFixed(20) +// '0.00000000000000022204' +``` + +引入一个这么小的量的目的,在于为浮点数计算,设置一个误差范围。我们知道浮点数计算是不精确的。 + +```javascript +0.1 + 0.2 +// 0.30000000000000004 + +0.1 + 0.2 - 0.3 +// 5.551115123125783e-17 + +5.551115123125783e-17.toFixed(20) +// '0.00000000000000005551' +``` + +但是如果这个误差能够小于`Number.EPSILON`,我们就可以认为得到了正确结果。 + +```javascript +5.551115123125783e-17 < Number.EPSILON +// true +``` + +因此,`Number.EPSILON`的实质是一个可以接受的误差范围。 + +```javascript +function withinErrorMargin (left, right) { + return Math.abs(left - right) < Number.EPSILON +} +withinErrorMargin(0.1 + 0.2, 0.3) +// true +withinErrorMargin(0.2 + 0.2, 0.3) +// false +``` + +上面的代码部署了一个误差检查函数。 + ## 安全整数和Number.isSafeInteger() -JavaScript能够准确表示的整数范围在`-2^53`到`2^53`之间。ES6引入了`Number.MAX_SAFE_INTEGER`和`Number.MIN_SAFE_INTEGER`这两个常量,用来表示这个范围的上下限。`Number.isSafeInteger()`则是用来判断一个整数是否落在这个范围之内。 +JavaScript能够准确表示的整数范围在`-2^53`到`2^53`之间(不含两个端点)。ES6引入了`Number.MAX_SAFE_INTEGER`和`Number.MIN_SAFE_INTEGER`这两个常量,用来表示这个范围的上下限。 + +```javascript +Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 +// true +Number.MAX_SAFE_INTEGER === 9007199254740991 +// true + +Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER +// true +Number.MIN_SAFE_INTEGER === -9007199254740991 +// true +``` + +`Number.isSafeInteger()`则是用来判断一个整数是否落在这个范围之内。 + +```javascript +Number.isSafeInteger('a') // false +Number.isSafeInteger(null) // false +Number.isSafeInteger(NaN) // false +Number.isSafeInteger(Infinity) // false +Number.isSafeInteger(-Infinity) // false +Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false +Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true +Number.isSafeInteger(1) // true +Number.isSafeInteger(1.2) // false +Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true +Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false +``` + +注意,验证运算结果是否落在安全整数的范围时,不要只验证运算结果,而要同时验证参与运算的每个值。 + +```javascript +Number.isSafeInteger(9007199254740993) +// false +Number.isSafeInteger(990) +// true +Number.isSafeInteger(9007199254740993 - 990) +// true +9007199254740993 - 990 +// 返回结果 9007199254740002 +// 正确答案应该是 9007199254740003 +``` + +上面代码中,`9007199254740993`不是一个安全整数,但是`Number.isSafeInteger`会返回结果,显示计算结果是安全的。这是因为,这个数超出了精度范围,导致在计算机内部,以`9007199254740992`的形式储存。 ```javascript -var inside = Number.MAX_SAFE_INTEGER; -var outside = inside + 1; +9007199254740993 === 9007199254740992 +// true +``` + +所以,如果只验证运算结果是否为安全整数,很可能得到错误结果。下面的函数可以同时验证两个运算数和运算结果。 + +```javascript +function trusty (left, right, result) { + if ( + Number.isSafeInteger(left) && + Number.isSafeInteger(right) && + Number.isSafeInteger(result) + ) { + return result + } + throw new RangeError('Operation cannot be trusted!') +} -Number.isInteger(inside) // true -Number.isSafeInteger(inside) // true +trusty(9007199254740993, 990, 9007199254740993 - 990) +// RangeError: Operation cannot be trusted! -Number.isInteger(outside) // true -Number.isSafeInteger(outside) // false +trusty(1, 2, 3) +// 3 ``` ## Math对象的扩展 diff --git a/docs/object.md b/docs/object.md index 6f0fe00a3..2d79f144c 100644 --- a/docs/object.md +++ b/docs/object.md @@ -353,6 +353,8 @@ obj.method = function() { ... } 有了这个属性,实际上已经不需要通过Object.create()来生成新对象了。 +由于`__proto__`前后的双引号,看上去很像内部属性,而不像一个正式的对外的API,所以从语义角度考虑,最好不要使用这个属性,而是下面的`Object.setPrototypeOf()`和`Object.getPrototypeOf()`代替。 + **(2)Object.setPrototypeOf()** Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象。它是ES6正式推荐的设置原型对象的方法。 diff --git a/docs/proxy.md b/docs/proxy.md index ecc39c7ac..30a753e24 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -329,7 +329,6 @@ var p = new Proxy(target, handler); p() === 'I am the proxy'; // true - ``` 上面代码中,变量p是Proxy的实例,当它作为函数调用时(`p()`),就会被apply方法拦截,返回一个字符串。 @@ -741,21 +740,21 @@ Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新AP (3) 让Object操作都变成函数行为。某些Object操作是命令式,比如`name in obj`和`delete obj[name]`,而`Reflect.has(obj, name)`和`Reflect.deleteProperty(obj, name)`让它们变成了函数行为。 -(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。 +(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。 ```javascript Proxy(target, { set: function(target, name, value, receiver) { var success = Reflect.set(target,name, value, receiver); if (success) { - log('property '+name+' on '+target+' set to '+value); + log('property ' + name + ' on ' + target + ' set to ' + value); } return success; } }); ``` -上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,然后再部署额外的功能。 +上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用`Reflect.set`方法将值赋值给对象的属性,然后再部署额外的功能。 下面是get方法的例子。 @@ -824,7 +823,7 @@ Reflect.get(obj, "foo", wrapper); 等同于`delete obj[name]`。 -**(5)Reflect.construct(target, args)** +**(5)Refl2ect.construct(target, args)** 等同于`new target(...args)`,这提供了一种不使用new,来调用构造函数的方法。 @@ -838,9 +837,9 @@ Reflect.get(obj, "foo", wrapper); **(8)Reflect.apply(fun,thisArg,args)** -等同于`Function.prototype.apply.call(fun,thisArg,args)`。一般来说,如果要绑定一个函数的this对象,可以这样写`fn.apply(obj, args)`,但是如果函数定义了自己的apply方法,就只能写成`Function.prototype.apply.call(fn, obj, args)`,采用Reflect对象可以简化这种操作。 +等同于`Function.prototype.apply.call(fun,thisArg,args)`。一般来说,如果要绑定一个函数的this对象,可以这样写`fn.apply(obj, args)`,但是如果函数定义了自己的`apply`方法,就只能写成`Function.prototype.apply.call(fn, obj, args)`,采用Reflect对象可以简化这种操作。 -另外,需要注意的是,Reflect.set()、Reflect.defineProperty()、Reflect.freeze()、Reflect.seal()和Reflect.preventExtensions()返回一个布尔值,表示操作是否成功。它们对应的Object方法,失败时都会抛出错误。 +另外,需要注意的是,`Reflect.set()`、`Reflect.defineProperty()`、`Reflect.freeze()`、`Reflect.seal()`和`Reflect.preventExtensions()`返回一个布尔值,表示操作是否成功。它们对应的Object方法,失败时都会抛出错误。 ```javascript // 失败时抛出错误 @@ -849,4 +848,4 @@ Object.defineProperty(obj, name, desc); Reflect.defineProperty(obj, name, desc); ``` -上面代码中,Reflect.defineProperty方法的作用与Object.defineProperty是一样的,都是为对象定义一个属性。但是,Reflect.defineProperty方法失败时,不会抛出错误,只会返回false。 +上面代码中,`Reflect.defineProperty`方法的作用与`Object.defineProperty`是一样的,都是为对象定义一个属性。但是,`Reflect.defineProperty`方法失败时,不会抛出错误,只会返回`false`。 diff --git a/docs/reference.md b/docs/reference.md index 99434a6a1..d01f9729e 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -50,6 +50,10 @@ - Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符 - Axel Rauschmayer, [New regular expression features in ECMAScript 6](http://www.2ality.com/2015/07/regexp-es6.html):ES6正则特性的详细介绍 +## 数值 + +- Nicolas Bevacqua, [ES6 Number Improvements in Depth](http://ponyfoo.com/articles/es6-number-improvements-in-depth) + ## 数组 - Axel Rauschmayer, [ECMAScript 6’s new array methods](http://www.2ality.com/2014/05/es6-array-methods.html): 对ES6新增的数组方法的全面介绍 From e509bec9f24b840bd3aa174f67b099de81f48987 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 20 Sep 2015 13:11:46 +0800 Subject: [PATCH 0014/1221] edit object/__proto__ --- docs/object.md | 44 ++++++++++++++++++++++++++++++++++++++------ docs/reference.md | 1 + 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/docs/object.md b/docs/object.md index 2d79f144c..f38fdd110 100644 --- a/docs/object.md +++ b/docs/object.md @@ -335,25 +335,57 @@ function processContent(options) { **(1)__proto__属性** -__proto__属性,用来读取或设置当前对象的prototype对象。该属性一度被正式写入ES6草案,但后来又被移除。目前,所有浏览器(包括IE11)都部署了这个属性。 +`__proto__`属性(前后各两个下划线),用来读取或设置当前对象的`prototype`对象。目前,所有浏览器(包括IE11)都部署了这个属性。 ```javascript // es6的写法 - var obj = { - __proto__: someOtherObj, method: function() { ... } } +obj.__proto__ = someOtherObj; // es5的写法 - var obj = Object.create(someOtherObj); obj.method = function() { ... } ``` -有了这个属性,实际上已经不需要通过Object.create()来生成新对象了。 +该属性没有写入ES6的正文,而是写入了附录,原因是`__proto__`前后的双引号,说明它本质上是一个内部属性,而不是一个正式的对外的API,只是由于浏览器广泛支持,才被加入了ES6。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的`Object.setPrototypeOf()`(写操作)、`Object.getPrototypeOf()`(读操作)、`Object.create()`(生成操作)代替。 + +在实现上,`__proto__`调用的是`Object.prototype.__proto__`,具体实现如下。 -由于`__proto__`前后的双引号,看上去很像内部属性,而不像一个正式的对外的API,所以从语义角度考虑,最好不要使用这个属性,而是下面的`Object.setPrototypeOf()`和`Object.getPrototypeOf()`代替。 +```javascript +Object.defineProperty(Object.prototype, '__proto__', { + get() { + let _thisObj = Object(this); + return Object.getPrototypeOf(_thisObj); + }, + set(proto) { + if (this === undefined || this === null) { + throw new TypeError(); + } + if (!isObject(this)) { + return undefined; + } + if (!isObject(proto)) { + return undefined; + } + let status = Reflect.setPrototypeOf(this, proto); + if (! status) { + throw new TypeError(); + } + }, +}); +function isObject(value) { + return Object(value) === value; +} +``` + +如果一个对象本身部署了`__proto__`属性,则该属性的值就是对象的原型。 + +```javascript +Object.getPrototypeOf({ __proto__: null }) +// null +``` **(2)Object.setPrototypeOf()** diff --git a/docs/reference.md b/docs/reference.md index d01f9729e..a0818d4b3 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -72,6 +72,7 @@ - Addy Osmani, [Data-binding Revolutions with Object.observe()](http://www.html5rocks.com/en/tutorials/es7/observe/): 介绍Object.observe()的概念 - 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) ## Proxy和Reflect From 24bc58626629a1272567958ca74afb0cb9cf991c Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 20 Sep 2015 13:15:32 +0800 Subject: [PATCH 0015/1221] edit object/__proto__ --- docs/object.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/object.md b/docs/object.md index f38fdd110..a9206f048 100644 --- a/docs/object.md +++ b/docs/object.md @@ -331,9 +331,9 @@ function processContent(options) { 上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。 -## __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf() +## `__proto__`属性,Object.setPrototypeOf(),Object.getPrototypeOf() -**(1)__proto__属性** +**(1)`__proto__`属性** `__proto__`属性(前后各两个下划线),用来读取或设置当前对象的`prototype`对象。目前,所有浏览器(包括IE11)都部署了这个属性。 From 7ce0931bbaddd98a73a9209bddc79a184d01f076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E8=BE=BE?= Date: Mon, 21 Sep 2015 01:28:13 +0800 Subject: [PATCH 0016/1221] Update let.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 不应该是没有输出,只是输出的是“undefined” --- docs/let.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/let.md b/docs/let.md index de2c96e58..22c331a71 100644 --- a/docs/let.md +++ b/docs/let.md @@ -205,7 +205,7 @@ function f(){ } } -f() // 没有输出 +f() // undefined ``` 上面代码中,函数f执行后没有任何输出,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。 From 048f1e6a1818ffdd68aa27a6ff89139cd8af43d0 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 22 Sep 2015 08:04:26 +0800 Subject: [PATCH 0017/1221] edit iterator --- css/app.css | 3 ++- docs/iterator.md | 65 ++++++++++++++++++++++++------------------------ 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/css/app.css b/css/app.css index 95f696eb6..84fcfd579 100644 --- a/css/app.css +++ b/css/app.css @@ -153,7 +153,8 @@ body { border-radius: 2px; } -#content p>code { +#content p>code, +#content li>code { color: #c7254e; background: #f9f2f4; } diff --git a/docs/iterator.md b/docs/iterator.md index b179258cc..61f3b2bd2 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -109,13 +109,13 @@ iter.next() // { value: 'c', done: false } iter.next() // { value: undefined, done: true } ``` -上面代码中,变量arr是一个数组,原生就具有遍历器接口,部署在arr的Symbol.iterator属性上面。所以,调用这个属性,就得到遍历器对象。 +上面代码中,变量`arr`是一个数组,原生就具有遍历器接口,部署在`arr`的`Symbol.iterator`属性上面。所以,调用这个属性,就得到遍历器对象。 -上面提到,原生就部署iterator接口的数据结构有三类,对于这三类数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的Iterator接口,都需要自己在Symbol.iterator属性上面部署,这样才会被`for...of`循环遍历。 +上面提到,原生就部署Iterator接口的数据结构有三类,对于这三类数据结构,不用自己写遍历器生成函数,`for...of`循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的Iterator接口,都需要自己在`Symbol.iterator`属性上面部署,这样才会被`for...of`循环遍历。 对象(Object)之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作Map结构使用,ES5没有Map结构,而ES6原生提供了。 -一个对象如果要有可被`for...of`循环调用的Iterator接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。 +一个对象如果要有可被`for...of`循环调用的Iterator接口,就必须在`Symbol.iterator`的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。 ```javascript class RangeIterator { @@ -146,7 +146,7 @@ for (var value of range(0, 3)) { } ``` -上面代码是一个类部署Iterator接口的写法。Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器对象。 +上面代码是一个类部署Iterator接口的写法。`Symbol.iterator`属性对应一个函数,执行后返回当前对象的遍历器对象。 下面是通过遍历器实现指针结构的例子。 @@ -197,7 +197,7 @@ for (var i of one){ // 3 ``` -上面代码首先在构造函数的原型链上部署Symbol.iterator方法,调用该方法会返回遍历器对象iterator,调用该对象的next方法,在返回一个值的同时,自动将内部指针移到下一个实例。 +上面代码首先在构造函数的原型链上部署`Symbol.iterator`方法,调用该方法会返回遍历器对象`iterator`,调用该对象的`next`方法,在返回一个值的同时,自动将内部指针移到下一个实例。 下面是另一个为对象添加Iterator接口的例子。 @@ -233,7 +233,22 @@ NodeList.prototype[Symbol.iterator] = [][Symbol.iterator]; [...document.querySelectorAll('div')] // 可以执行了 ``` -如果Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。 +下面是对象调用数组的`Symbol.iterator`方法的例子。 + +```javascript +let iterable = { + 0: 'a', + 1: 'b', + 2: 'c', + length: 3, + [Symbol.iterator]: Array.prototype[Symbol.iterator] +}; +for (let item of iterable) { + console.log(item); // 'a', 'b', 'c' +} +``` + +如果`Symbol.iterator`方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。 ```javascript var obj = {}; @@ -245,7 +260,7 @@ obj[Symbol.iterator] = () => 1; 上面代码中,变量obj的Symbol.iterator方法对应的不是遍历器生成函数,因此报错。 -有了遍历器接口,数据结构就可以用`for...of`循环遍历(详见下文),也可以使用while循环遍历。 +有了遍历器接口,数据结构就可以用`for...of`循环遍历(详见下文),也可以使用`while`循环遍历。 ```javascript var $iterator = ITERABLE[Symbol.iterator](); @@ -257,11 +272,11 @@ while (!$result.done) { } ``` -上面代码中,ITERABLE代表某种可遍历的数据结构,$iterator是它的遍历器对象。遍历器对象每次移动指针(next方法),都检查一下返回值的done属性,如果遍历还没结束,就移动遍历器对象的指针到下一步(next方法),不断循环。 +上面代码中,`ITERABLE`代表某种可遍历的数据结构,`$iterator`是它的遍历器对象。遍历器对象每次移动指针(`next`方法),都检查一下返回值的`done`属性,如果遍历还没结束,就移动遍历器对象的指针到下一步(`next`方法),不断循环。 -## 调用默认Iterator接口的场合 +## 调用Iterator接口的场合 -有一些场合会默认调用iterator接口(即Symbol.iterator方法),除了下文会介绍的for...of循环,还有几个别的场合。 +有一些场合会默认调用Iterator接口(即`Symbol.iterator`方法),除了下文会介绍的`for...of`循环,还有几个别的场合。 **(1)解构赋值** @@ -292,9 +307,9 @@ let arr = ['b', 'c']; // ['a', 'b', 'c', 'd'] ``` -上面代码的扩展运算符内部就调用iterator接口。 +上面代码的扩展运算符内部就调用Iterator接口。 -实际上,这提供了一种简便机制,可以将任何部署了iterator接口的数据结构,转为数组。也就是说,只要某个数据结构部署了iterator接口,就可以对它使用扩展运算符,将其转为数组。 +实际上,这提供了一种简便机制,可以将任何部署了Iterator接口的数据结构,转为数组。也就是说,只要某个数据结构部署了Iterator接口,就可以对它使用扩展运算符,将其转为数组。 ```javascript let arr = [...iterable]; @@ -325,27 +340,13 @@ iterator.next() // { value: undefined, done: true } 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。 +- for...of - Array.from() - Map(), Set(), WeakMap(), WeakSet()(比如`new Map([['a',1],['b',2]])`) - Promise.all() - Promise.race() -## 原生具备Iterator接口的数据结构 - -《数组的扩展》一章中提到,ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器对象。 - -```javascript -var arr = [1, 5, 7]; -var arrEntries = arr.entries(); - -arrEntries.toString() -// "[object Array Iterator]" - -arrEntries === arrEntries[Symbol.iterator]() -// true -``` - -上面代码中,entries方法返回的是一个遍历器对象(iterator),本质上就是调用了`Symbol.iterator`方法。 +## 字符串的Iterator接口 字符串是一个类似数组的对象,也原生具有Iterator接口。 @@ -388,7 +389,7 @@ str[Symbol.iterator] = function() { str // "hi" ``` -上面代码中,字符串str的`Symbol.iterator`方法被修改了,所以扩展运算符(...)返回的值变成了bye,而字符串本身还是hi。 +上面代码中,字符串str的`Symbol.iterator`方法被修改了,所以扩展运算符(`...`)返回的值变成了`bye`,而字符串本身还是`hi`。 ## Iterator接口与Generator函数 @@ -424,11 +425,11 @@ for (let x of obj) { ## 遍历器对象的return(),throw() -遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。 +遍历器对象除了具有`next`方法,还可以具有`return`方法和`throw`方法。如果你自己写遍历器生成函数,那么`next`方法是必须部署的,`return`方法和`throw`方法是否部署是可选的。 -return方法的使用场合是,如果`for...of`循环提前退出(通常是因为出错,或者有break语句或continue语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。 +`return`方法的使用场合是,如果`for...of`循环提前退出(通常是因为出错,或者有`break`语句或`continue`语句),就会调用`return`方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署`return`方法。 -throw方法主要是配合Generator函数使用,一般的遍历器对象用不到这个方法。请参阅《Generator函数》一章。 +`throw`方法主要是配合Generator函数使用,一般的遍历器对象用不到这个方法。请参阅《Generator函数》一章。 ## for...of循环 From 1ed5dfa1f3aa7c8a2fb881848283862a11087233 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 24 Sep 2015 09:55:14 +0800 Subject: [PATCH 0018/1221] edit object & array & math --- css/app.css | 8 +++-- docs/array.md | 89 ++++++++++++++++++++++++++++++++++++-------------- docs/number.md | 80 ++++++++++++++++++++++++++++++--------------- docs/object.md | 62 ++++++++++++++++++++++++++++++++--- 4 files changed, 180 insertions(+), 59 deletions(-) diff --git a/css/app.css b/css/app.css index 84fcfd579..841ee8eb5 100644 --- a/css/app.css +++ b/css/app.css @@ -154,9 +154,11 @@ body { } #content p>code, -#content li>code { - color: #c7254e; - background: #f9f2f4; +#content li>code, +#content h2>code, +#content h3>code{ + color: #c7254e; + background: #f9f2f4; } #content h2 { diff --git a/docs/array.md b/docs/array.md index 939d85cdf..57b2d4b8b 100644 --- a/docs/array.md +++ b/docs/array.md @@ -2,7 +2,7 @@ ## Array.from() -Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。 +`Array.from`方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。 ```javascript Array.from('hello') @@ -20,26 +20,40 @@ Array.from(ps).forEach(function (p) { }); ``` -上面代码中,querySelectorAll方法返回的是一个类似数组的对象,只有将这个对象转为真正的数组,才能使用forEach方法。 +上面代码中,`querySelectorAll`方法返回的是一个类似数组的对象,只有将这个对象转为真正的数组,才能使用forEach方法。 -Array.from方法可以将函数的arguments对象,转为数组。 +`Array.from`方法可以将函数的`arguments`对象,转为数组。 ```javascript function foo() { - var args = Array.from( arguments ); + var args = Array.from(arguments); } -foo( "a", "b", "c" ); +foo('a', 'b', 'c'); ``` -任何有length属性的对象,都可以通过Array.from方法转为数组。 +值得提醒的是,扩展运算符(`...`)也可以将某些数据结构转为数组。 + +```javascript +// arguments对象 +function foo() { + var args = [...arguments]; +} + +// NodeList对象 +[...document.querySelectorAll('div')] +``` + +扩展运算符背后调用的是遍历器接口(`Symbol.iterator`),如果一个对象没有部署这个接口,就无法转换。`Array.from`方法就不存在这个问题,比如下面的这个例子,扩展运算符就无法转换。 + +任何有`length`属性的对象,都可以通过`Array.from`方法转为数组。 ```javascript Array.from({ 0: "a", 1: "b", 2: "c", length: 3 }); // [ "a", "b" , "c" ] ``` -对于还没有部署该方法的浏览器,可以用Array.prototype.slice方法替代。 +对于还没有部署该方法的浏览器,可以用`Array.prototype.slice`方法替代。 ```javascript const toArray = (() => @@ -47,7 +61,7 @@ const toArray = (() => )(); ``` -Array.from()还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理。 +`Array.from`还可以接受第二个参数,作用类似于数组的`map`方法,用来对每个元素进行处理。 ```JavaScript Array.from(arrayLike, x => x * x); @@ -58,14 +72,26 @@ Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9] ``` -下面的例子将数组中布尔值为false的成员转为0。 +下面的例子将数组中布尔值为`false`的成员转为`0`。 ```javascript Array.from([1, , 2, , 3], (n) => n || 0) // [1, 0, 2, 0, 3] ``` -`Array.from()`可以将各种值转为真正的数组,并且还提供map功能。这实际上意味着,你可以在数组里造出任何想要的值。 +另一个例子是返回各种数据的类型。 + +```javascript +function typesOf () { + return Array.from(arguments, value => typeof value) +} +typesOf(null, [], NaN) +// ['object', 'object', 'number'] +``` + +如果`map`函数里面用到了`this`关键字,还可以传入`Array.from`的第三个参数,用来绑定`this`。 + +`Array.from()`可以将各种值转为真正的数组,并且还提供`map`功能。这实际上意味着,你可以在数组里造出任何想要的值。 ```javascript Array.from({ length: 2 }, () => 'jack') @@ -74,7 +100,7 @@ Array.from({ length: 2 }, () => 'jack') 上面代码中,`Array.from`的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。 -`Array.from()`的另一个应用是,将字符串转为数组,然后返回字符串的长度。这样可以避免JavaScript将大于`\uFFFF`的Unicode字符,算作两个字符的bug。 +`Array.from()`的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种Unicode字符,可以避免JavaScript将大于`\uFFFF`的Unicode字符,算作两个字符的bug。 ```javascript function countSymbols(string) { @@ -84,7 +110,7 @@ function countSymbols(string) { ## Array.of() -Array.of方法用于将一组值,转换为数组。 +`Array.of`方法用于将一组值,转换为数组。 ```javaScript Array.of(3, 11, 8) // [3,11,8] @@ -92,17 +118,26 @@ Array.of(3) // [3] Array.of(3).length // 1 ``` -这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。 +这个方法的主要目的,是弥补数组构造函数`Array()`的不足。因为参数个数的不同,会导致`Array()`的行为有差异。 ```javascript Array() // [] Array(3) // [undefined, undefined, undefined] -Array(3,11,8) // [3, 11, 8] +Array(3, 11, 8) // [3, 11, 8] ``` -上面代码说明,只有当参数个数不少于2个,Array()才会返回由参数组成的新数组。 +上面代码说明,只有当参数个数不少于2个,`Array()`才会返回由参数组成的新数组。 + +`Array.of`基本上可以用来替代`new Array()`,并且不存在`new Array(length)`导致的重载。它的行为非常统一。 + +```javascript +Array.of() // [] +Array.of(undefined) // [undefined] +Array.of(1) // [1] +Array.of(1, 2) // [1, 2] +``` -Array.of方法可以用下面的代码模拟实现。 +`Array.of`方法可以用下面的代码模拟实现。 ```javascript function ArrayOf(){ @@ -112,12 +147,16 @@ function ArrayOf(){ ## 数组实例的copyWithin() -数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。 +数组实例的`copyWithin`方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。 + +```javascript +Array.prototype.copyWithin(target, start = 0, end = this.length) +``` 它接受三个参数。 -- target(必需):从该位置开始复制数据。 -- start(必需):从该位置开始读取数据。如果为负值,表示倒数。 +- target(必需):从该位置开始替换数据。 +- start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。 - end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。 这三个参数都应该是数值,如果不是,会自动转为数值。 @@ -157,7 +196,7 @@ i32a.copyWithin(0, 2); ## 数组实例的find()和findIndex() -数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。 +数组实例的`find`方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为`true`的成员,然后返回该成员。如果没有符合条件的成员,则返回`undefined`。 ```javascript [1, 4, -5, 10].find((n) => n < 0) @@ -172,9 +211,9 @@ i32a.copyWithin(0, 2); }) // 10 ``` -上面代码中,find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。 +上面代码中,`find`方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。 -数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。 +数组实例的`findIndex`方法的用法与`find`方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回`-1`。 ```javascript [1, 5, 10, 15].findIndex(function(value, index, arr) { @@ -198,7 +237,7 @@ i32a.copyWithin(0, 2); ## 数组实例的fill() -fill()使用给定值,填充一个数组。 +`fill`方法使用给定值,填充一个数组。 ```javascript ['a', 'b', 'c'].fill(7) @@ -208,9 +247,9 @@ new Array(3).fill(7) // [7, 7, 7] ``` -上面代码表明,fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。 +上面代码表明,`fill`方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。 -fill()还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。 +`fill`方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。 ```javascript ['a', 'b', 'c'].fill(7, 1, 2) diff --git a/docs/number.md b/docs/number.md index 5b563297c..23fe543e7 100644 --- a/docs/number.md +++ b/docs/number.md @@ -271,13 +271,21 @@ ES6在Math对象上新增了17个与数学相关的方法。所有这些方法 ### Math.trunc() -Math.trunc方法用于去除一个数的小数部分,返回整数部分。 +`Math.trunc`方法用于去除一个数的小数部分,返回整数部分。 ```javascript Math.trunc(4.1) // 4 Math.trunc(4.9) // 4 Math.trunc(-4.1) // -4 Math.trunc(-4.9) // -4 +Math.trunc(-0.1234) // -0 +``` + +对于非数值,`Math.trunc`内部使用`Number`方法将其先转为数值。 + +```javascript +Math.trunc('123.456') +// 123 ``` 对于空值和无法截取整数的值,返回NaN。 @@ -298,7 +306,7 @@ Math.trunc = Math.trunc || function(x) { ### Math.sign() -Math.sign方法用来判断一个数到底是正数、负数、还是零。 +`Math.sign`方法用来判断一个数到底是正数、负数、还是零。 它会返回五种值。 @@ -332,13 +340,20 @@ Math.sign = Math.sign || function(x) { ### Math.cbrt() -Math.cbrt方法用于计算一个数的立方根。 +`Math.cbrt`方法用于计算一个数的立方根。 + +```javascript +Math.cbrt(-1) // -1 +Math.cbrt(0) // 0 +Math.cbrt(1) // 1 +Math.cbrt(2) // 1.2599210498948734 +``` + +对于非数值,`Math.cbrt`方法内部也是先使用`Number`方法将其转为数值。 ```javascript -Math.cbrt(-1); // -1 -Math.cbrt(0); // 0 -Math.cbrt(1); // 1 -Math.cbrt(2); // 1.2599210498948734 +Math.cbrt('8') // 2 +Math.cbrt('hello') // NaN ``` 对于没有部署这个方法的环境,可以用下面的代码模拟。 @@ -352,7 +367,7 @@ Math.cbrt = Math.cbrt || function(x) { ### Math.clz32() -JavaScript的整数使用32位二进制形式表示,Math.clz32方法返回一个数的32位无符号整数形式有多少个前导0。 +JavaScript的整数使用32位二进制形式表示,`Math.clz32`方法返回一个数的32位无符号整数形式有多少个前导0。 ```javascript Math.clz32(0) // 32 @@ -360,16 +375,28 @@ Math.clz32(1) // 31 Math.clz32(1000) // 22 ``` -上面代码中,0的二进制形式全为0,所以有32个前导0;1的二进制形式是0b1,只占1位,所以32位之中有31个前导0;1000的二进制形式是0b1111101000,一共有10位,所以32位之中有22个前导0。 +上面代码中,0的二进制形式全为0,所以有32个前导0;1的二进制形式是`0b1`,只占1位,所以32位之中有31个前导0;1000的二进制形式是`0b1111101000`,一共有10位,所以32位之中有22个前导0。 + +`clz32`这个函数名就来自”count leading zero bits in 32-bit binary representations of a number“(计算32位整数的前导0)的缩写。 + +左移运算符(`<<`)与`Math.clz32`方法直接相关。 + +```javascript +Math.clz32(0) // 32 +Math.clz32(1) // 31 +Math.clz32(1 << 1) // 30 +Math.clz32(1 << 2) // 29 +Math.clz32(1 << 29) // 2 +``` -对于小数,Math.clz32方法只考虑整数部分。 +对于小数,`Math.clz32`方法只考虑整数部分。 ```javascript Math.clz32(3.2) // 30 Math.clz32(3.9) // 30 ``` -对于空值或其他类型的值,Math.clz32方法会将它们先转为数值,然后再计算。 +对于空值或其他类型的值,`Math.clz32`方法会将它们先转为数值,然后再计算。 ```javascript Math.clz32() // 32 @@ -384,7 +411,7 @@ Math.clz32(true) // 31 ### Math.imul() -Math.imul方法返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号整数。 +`Math.imul`方法返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号整数。 ```javascript Math.imul(2, 4); // 8 @@ -392,13 +419,13 @@ Math.imul(-1, 8); // -8 Math.imul(-2, -2); // 4 ``` -如果只考虑最后32位(含第一个整数位),大多数情况下,`Math.imul(a, b)`与`a * b`的结果是相同的,即该方法等同于`(a * b)|0`的效果。之所以需要部署这个方法,是因为JavaScript有精度限制,超过2的53次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,Math.imul方法可以返回正确的低位数值。 +如果只考虑最后32位(含第一个整数位),大多数情况下,`Math.imul(a, b)`与`a * b`的结果是相同的,即该方法等同于`(a * b)|0`的效果(超过32位的部分溢出)。之所以需要部署这个方法,是因为JavaScript有精度限制,超过2的53次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,`Math.imul`方法可以返回正确的低位数值。 ```javascript (0x7fffffff * 0x7fffffff)|0 // 0 ``` -上面这个乘法算式,返回结果为0。但是由于这两个数的个位数都是1,所以这个结果肯定是不正确的。这个错误就是因为它们的乘积超过了2的53次方,JavaScript无法保存额外的精度,就把低位的值都变成了0。Math.imul方法可以返回正确的值1。 +上面这个乘法算式,返回结果为0。但是由于这两个二进制数的最低位都是1,所以这个结果肯定是不正确的,因为根据二进制乘法,计算结果的二进制最低位应该也是1。这个错误就是因为它们的乘积超过了2的53次方,JavaScript无法保存额外的精度,就把低位的值都变成了0。`Math.imul`方法可以返回正确的值1。 ```javascript Math.imul(0x7fffffff, 0x7fffffff) // 1 @@ -428,7 +455,7 @@ Math.fround = Math.fround || function(x) { ### Math.hypot() -Math.hypot方法返回所有参数的平方和的平方根。 +`Math.hypot`方法返回所有参数的平方和的平方根。 ```javascript Math.hypot(3, 4); // 5 @@ -448,7 +475,7 @@ Math.hypot(-3); // 3 ES6新增了4个对数相关方法。 -(1) Math.expm1() +**(1) Math.expm1()** `Math.expm1(x)`返回ex - 1。 @@ -466,9 +493,9 @@ Math.expm1 = Math.expm1 || function(x) { }; ``` -(2)Math.log1p() +**(2)Math.log1p()** -`Math.log1p(x)`方法返回1 + x的自然对数。如果x小于-1,返回NaN。 +`Math.log1p(x)`方法返回`1 + x`的自然对数。如果`x`小于-1,返回`NaN`。 ```javascript Math.log1p(1); // 0.6931471805599453 @@ -485,7 +512,7 @@ Math.log1p = Math.log1p || function(x) { }; ``` -(3)Math.log10() +**(3)Math.log10()** `Math.log10(x)`返回以10为底的x的对数。如果x小于0,则返回NaN。 @@ -505,17 +532,18 @@ Math.log10 = Math.log10 || function(x) { }; ``` -(4)Math.log2() +**(4)Math.log2()** `Math.log2(x)`返回以2为底的x的对数。如果x小于0,则返回NaN。 ```javascript -Math.log2(3); // 1.584962500721156 -Math.log2(2); // 1 -Math.log2(1); // 0 -Math.log2(0); // -Infinity -Math.log2(-2); // NaN -Math.log2(1024); // 10 +Math.log2(3) // 1.584962500721156 +Math.log2(2) // 1 +Math.log2(1) // 0 +Math.log2(0) // -Infinity +Math.log2(-2) // NaN +Math.log2(1024) // 10 +Math.log2(1 << 29) // 29 ``` 对于没有部署这个方法的环境,可以用下面的代码模拟。 diff --git a/docs/object.md b/docs/object.md index a9206f048..25d6704aa 100644 --- a/docs/object.md +++ b/docs/object.md @@ -190,7 +190,16 @@ obj[key2].name // "" ## Object.is() -Object.is()用来比较两个值是否严格相等。它与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身。 +`Object.is`用来比较两个值是否严格相等。它与严格比较运算符(===)的行为基本一致。 + +```javascript +Object.is('foo', 'foo') +// true +Object.is({}, {}) +// false +``` + +不同之处只有两个:一是`+0`不等于`-0`,二是`NaN`等于自身。 ```javascript +0 === -0 //true @@ -200,7 +209,7 @@ Object.is(+0, -0) // false Object.is(NaN, NaN) // true ``` -ES5可以通过下面的代码,部署Object.is()。 +ES5可以通过下面的代码,部署`Object.is`。 ```javascript Object.defineProperty(Object, 'is', { @@ -220,7 +229,7 @@ Object.defineProperty(Object, 'is', { ## Object.assign() -Object.assign方法用来将源对象(source)的所有可枚举属性,复制到目标对象(target)。它至少需要两个对象作为参数,第一个参数是目标对象,后面的参数都是源对象。只要有一个参数不是对象,就会抛出TypeError错误。 +`Object.assign`方法用来将源对象(source)的所有可枚举属性,复制到目标对象(target)。它至少需要两个对象作为参数,第一个参数是目标对象,后面的参数都是源对象。只要有一个参数不是对象,就会抛出TypeError错误。 ```javascript var target = { a: 1 }; @@ -244,7 +253,48 @@ Object.assign(target, source1, source2); target // {a:1, b:2, c:3} ``` -assign方法有很多用处。 +`Object.assign`只拷贝自身属性,不可枚举的属性(`enumerable`为false)和继承的属性不会被拷贝。 + +```javascript +Object.assign({b: 'c'}, + Object.defineProperty({}, 'invisible', { + enumerable: false, + value: 'hello' + }) +) +// { b: 'c' } +``` + +上面代码中,`Object.assign`要拷贝的对象只有一个不可枚举属性`invisible`,这个属性并没有被拷贝进去。 + +属性名为Symbol值的属性,也会被`Object.assign`拷贝。 + +```javascript +Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) +// { a: 'b', Symbol(c): 'd' } +``` + +对于嵌套的对象,`Object.assign`的处理方法是替换,而不是添加。 + +```javascript +var target = { a: { b: 'c', d: 'e' } } +var source = { a: { b: 'hello' } } +Object.assign(target, source) +// { a: { b: 'hello' } } +``` + +上面代码中,`target`对象的`a`属性被`source`对象的`a`属性整个替换掉了,而不会得到`{ a: { b: 'hello', d: 'e' } }`的结果。这通常不是开发者想要的,需要特别小心。有一些函数库提供`Object.assign`的定制版本(比如Lodash的`_.defaultsDeep`方法),可以解决深拷贝的问题。 + +注意,`Object.assign`可以用来处理数组,但是会把数组视为对象。 + +```javascript +Object.assign([1, 2, 3], [4, 5]) +// [4, 5, 3] +``` + +上面代码中,`Object.assign`把数组视为属性名为0、1、2的对象,因此目标数组的0号属性`4`覆盖了原数组的0号属性`1`。 + +`Object.assign`方法有很多用处。 **(1)为对象添加属性** @@ -329,7 +379,9 @@ function processContent(options) { } ``` -上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。 +上面代码中,`DEFAULTS`对象是默认值,`options`对象是用户提供的参数。`Object.assign`方法将`DEFAULTS`和`options`合并成一个新对象,如果两者有同名属性,则`option`的属性值会覆盖`DEFAULTS`的属性值。 + +注意,由于存在深拷贝的问题,`DEFAULTS`对象和`options`对象的所有属性的值,都只能是简单类型,而不能指向另一个对象。否则,将导致`DEFAULTS`对象的该属性不起作用。 ## `__proto__`属性,Object.setPrototypeOf(),Object.getPrototypeOf() From 3ee70ea290b031c718cdcc5600a683e7bca72ffb Mon Sep 17 00:00:00 2001 From: Zoey Young Date: Fri, 25 Sep 2015 17:04:02 +0800 Subject: [PATCH 0019/1221] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/decorator.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/decorator.md b/docs/decorator.md index c54ba3ba1..2c8b88869 100644 --- a/docs/decorator.md +++ b/docs/decorator.md @@ -12,7 +12,7 @@ function testable(target) { } @testable -class MyTestableClass () {} +class MyTestableClass {} console.log(MyTestableClass.isTestable) // true ``` @@ -59,9 +59,9 @@ function testable(target) { } @testable -class MyTestableClass () {} +class MyTestableClass {} -let obj = new MyClass(); +let obj = new MyTestableClass(); console.log(obj.isTestable) // true ``` From 078a0a5f407059e1ecee3f8c2c6596c0e2e18d88 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 26 Sep 2015 08:08:53 +0800 Subject: [PATCH 0020/1221] edit decorator --- docs/decorator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/decorator.md b/docs/decorator.md index 2c8b88869..2e157dc91 100644 --- a/docs/decorator.md +++ b/docs/decorator.md @@ -42,10 +42,10 @@ function testable(isTestable) { } } -@testable(true) class MyTestableClass () {} +@testable(true) class MyTestableClass {} console.log(MyTestableClass.isTestable) // true -@testable(false) class MyClass () {} +@testable(false) class MyClass {} console.log(MyClass.isTestable) // false ``` From 3ff90589e641a225eff3c3b7fd674fb25b8c1de2 Mon Sep 17 00:00:00 2001 From: "r.4ntix Blue" Date: Tue, 29 Sep 2015 23:32:31 +0800 Subject: [PATCH 0021/1221] fix Array.prototype.includes() typo --- docs/array.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/array.md b/docs/array.md index 57b2d4b8b..f6ab0c93f 100644 --- a/docs/array.md +++ b/docs/array.md @@ -292,7 +292,7 @@ console.log(entries.next().value); // [2, 'c'] ## 数组实例的includes() -`Array.protypeto.includes`方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的`includes`方法类似。该方法属于ES7,但Babel转码器已经支持。 +`Array.prototype.includes`方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的`includes`方法类似。该方法属于ES7,但Babel转码器已经支持。 ```javascript [1, 2, 3].includes(2); // true From 40b791a8dcbea11fe24feaa7efce5575b9c7b110 Mon Sep 17 00:00:00 2001 From: Nfer zhuang Date: Tue, 29 Sep 2015 23:43:36 +0800 Subject: [PATCH 0022/1221] =?UTF-8?q?=E5=A4=96=E5=B1=82=E4=BD=9C=E7=94=A8?= =?UTF-8?q?=E5=9F=9F=E6=97=A0=E6=B3=95=E8=AF=BB=E5=8F=96=E5=86=85=E5=B1=82?= =?UTF-8?q?=E4=BD=9C=E7=94=A8=E5=9F=9F=E7=9A=84=E5=8F=98=E9=87=8F=EF=BC=9B?= =?UTF-8?q?=20const=E5=8F=98=E9=87=8F=E5=90=8C=E6=A0=B7=E4=B8=8D=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E9=87=8D=E5=A4=8D=E5=A3=B0=E6=98=8E;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/let.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/let.md b/docs/let.md index dd9ad5eef..505e140ea 100644 --- a/docs/let.md +++ b/docs/let.md @@ -244,7 +244,6 @@ ES6允许块级作用域的任意嵌套。 ```javascript {{{{{let insane = 'Hello World'}}}}}; -insane // "Hello World" ``` 上面代码使用了一个五层的块级作用域。外层作用域无法读取内层作用域的变量。 @@ -332,9 +331,6 @@ PI // 3.1415 PI = 3; PI // 3.1415 - -const PI = 3.1; -PI // 3.1415 ``` 上面代码表明改变常量的值是不起作用的。需要注意的是,对常量重新赋值不会报错,只会默默地失败。 From 9a66ab3d9e08a06be1711a5b69aeb85c1f2dab68 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 30 Sep 2015 08:41:56 +0800 Subject: [PATCH 0023/1221] edit let --- docs/let.md | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/let.md b/docs/let.md index dd9ad5eef..fc1590f47 100644 --- a/docs/let.md +++ b/docs/let.md @@ -4,7 +4,7 @@ ### 基本用法 -ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。 +ES6新增了`let`命令,用来声明变量。它的用法类似于`var`,但是所声明的变量,只在`let`命令所在的代码块内有效。 ```javascript { @@ -16,9 +16,9 @@ a // ReferenceError: a is not defined. b // 1 ``` -上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。 +上面代码在代码块之中,分别用`let`和`var`声明了两个变量。然后在代码块之外调用这两个变量,结果`let`声明的变量报错,`var`声明的变量返回了正确的值。这表明,`let`声明的变量只在它所在的代码块有效。 -for循环的计数器,就很合适使用let命令。 +`for`循环的计数器,就很合适使用let命令。 ```javascript for(let i = 0; i < arr.length; i++){} @@ -27,9 +27,9 @@ console.log(i) //ReferenceError: i is not defined ``` -上面代码的计数器i,只在for循环体内有效。 +上面代码的计数器`i`,只在`for`循环体内有效。 -下面的代码如果使用var,最后输出的是10。 +下面的代码如果使用`var`,最后输出的是10。 ```javascript var a = []; @@ -41,7 +41,7 @@ for (var i = 0; i < 10; i++) { a[6](); // 10 ``` -如果使用let,声明的变量仅在块级作用域内有效,最后输出的是6。 +如果使用`let`,声明的变量仅在块级作用域内有效,最后输出的是6。 ```javascript var a = []; @@ -58,10 +58,8 @@ a[6](); // 6 `let`不像`var`那样,会发生“变量提升”现象。 ```javascript -function do_something() { - console.log(foo); // ReferenceError - let foo = 2; -} +console.log(foo); // ReferenceError +let foo = 2; ``` 上面代码在声明`foo`之前,就使用这个变量,结果会抛出一个错误。 @@ -69,10 +67,8 @@ function do_something() { 这也意味着`typeof`不再是一个百分之百安全的操作。 ```javascript -if (1) { - typeof x; // ReferenceError - let x; -} +typeof x; // ReferenceError +let x; ``` 上面代码中,由于块级作用域内`typeof`运行时,`x`还没有值,所以会抛出一个`ReferenceError`。 From b6dc8ebe08c749ad4e48bf837a2c262e6e9c866d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 30 Sep 2015 16:49:17 +0800 Subject: [PATCH 0024/1221] edit let --- docs/let.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/let.md b/docs/let.md index 24ee38d48..93398bae9 100644 --- a/docs/let.md +++ b/docs/let.md @@ -433,13 +433,13 @@ console.log(B); // 3 ## 全局对象的属性 -全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。在JavaScript语言中,所有全局变量都是全局对象的属性。 +全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。在JavaScript语言中,所有全局变量都是全局对象的属性。(对于模块,指模块的顶层对象。) ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。 ```javascript var a = 1; -// 如果在node环境,可以写成global.a +// 如果在Node的REPL环境,可以写成global.a // 或者采用通用方法,写成this.a window.a // 1 @@ -447,4 +447,4 @@ let b = 1; window.b // undefined ``` -上面代码中,全局变量a由var命令声明,所以它是全局对象的属性;全局变量b由let命令声明,所以它不是全局对象的属性,返回undefined。 +上面代码中,全局变量`a`由var命令声明,所以它是全局对象的属性;全局变量`b`由let命令声明,所以它不是全局对象的属性,返回`undefined`。 From 38de94a7cef9954cb40a7a78149f45ef8740762e Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 30 Sep 2015 22:20:18 +0800 Subject: [PATCH 0025/1221] edit let --- docs/let.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/let.md b/docs/let.md index 93398bae9..96b766a62 100644 --- a/docs/let.md +++ b/docs/let.md @@ -433,7 +433,7 @@ console.log(B); // 3 ## 全局对象的属性 -全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。在JavaScript语言中,所有全局变量都是全局对象的属性。(对于模块,指模块的顶层对象。) +全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。在JavaScript语言中,所有全局变量都是全局对象的属性。(Node的情况比较特殊,这一条只对REPL环境适用,模块环境必须显式声明成`global`的属性。) ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。 @@ -447,4 +447,4 @@ let b = 1; window.b // undefined ``` -上面代码中,全局变量`a`由var命令声明,所以它是全局对象的属性;全局变量`b`由let命令声明,所以它不是全局对象的属性,返回`undefined`。 +上面代码中,全局变量`a`由`var`命令声明,所以它是全局对象的属性;全局变量`b`由`let`命令声明,所以它不是全局对象的属性,返回`undefined`。 From e47cb1018808947eb1586bff667731bbfe5ffd69 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 1 Oct 2015 21:26:03 +0800 Subject: [PATCH 0026/1221] add arraybuffer --- docs/arraybuffer.md | 911 ++++++++++++++++++++++++++++++++++++++++++++ docs/reference.md | 8 + sidebar.md | 1 + 3 files changed, 920 insertions(+) create mode 100644 docs/arraybuffer.md diff --git a/docs/arraybuffer.md b/docs/arraybuffer.md new file mode 100644 index 000000000..8a8b5b396 --- /dev/null +++ b/docs/arraybuffer.md @@ -0,0 +1,911 @@ +ArrayBuffer对象、TypedArray对象、DataView对象是JavaScript操作二进制数据的一个接口。这些对象早就存在,属于独立的规格,ES6将它们纳入了ECMAScript规格,并且增加了新的方法。 + +这些对象原始的设计目的,要从WebGL项目的诞生说起。所谓WebGL,就是指浏览器与显卡之间的通信接口,为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个32位整数,两端的JavaScript脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像C语言那样,直接操作字节,将4个字节的32位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。 + +`ArrayBuffer`等对象就是在这种背景下诞生的。它很像C语言的数组,允许开发者以数组下标的形式,直接操作内存。它们大大增强了JavaScript处理二进制数据的能力,使得开发者有可能通过JavaScript与操作系统的原生接口进行二进制通信。 + +ArrayBuffer对象与TypedArray对象、DataView对象的区别是:前者代表底层数据,不区分类型;后两者代表上层视图,具有类型。 + +- ArrayBuffer对象:代表内存的一段二进制数据,可以通过“视图”进行操作。各种“视图”具有数组接口,可以用数组的方法操作内存。 +- TypedArray对象:提供多个构造函数,用来生成不同类型的视图,比如`Uint8Array`(无符号8位整数)数组视图, `Int16Array`(16位整数)数组视图, `Float32Array`(32位浮点数)数组视图等等。 +- DataView对象:可以自定义字节序,解读同一段内存,比如Uint8(无符号8位整数)、Int16(16位整数)、Float32(32位浮点数)等等。 + +ArrayBuffer对象的视图支持以下数据类型。 + +数据类型|字节长度|含义|对应的C语言类型 +--------|--------|----|--------------- +Int8|1|8位带符号整数|signed char +Uint8|1|8位不带符号整数|unsigned char +Uint8C|1|8位不带符号整数(自动过滤溢出)|unsigned char +Int16|2|16位带符号整数|short +Uint16|2|16位不带符号整数|unsigned short +Int32|4|32位带符号整数|int +Uint32|4|32位不带符号的整数|unsigned int +Float32|4|32位浮点数|float +Float64|8|64位浮点数|double + +很多浏览器的API使用ArrayBuffer对象,操作二进制数据,下面是其中的几个。 + +- File API +- XMLHttpRequest +- Fetch API +- Canvas +- WebSockets + +## ArrayBuffe对象 + +### 概述 + +`ArrayBuffer`对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(`TypedArray`视图和`DataView`视图)来读写。视图的作用是以指定格式解读二进制数据。 + +`ArrayBuffer`也是一个构造函数,可以分配一段可以存放数据的连续内存区域。 + +```javascript +var buf = new ArrayBuffer(32); +``` + +上面代码生成了一段32字节的内存区域,每个字节的值默认都是0。可以看到,`ArrayBuffer`构造函数的参数是所需要的内存大小(单位字节)。 + +为了读写这段内容,需要为它指定视图。`DataView`视图的创建,需要提供`ArrayBuffer`对象实例作为参数。 + +```javascript +var buf = new ArrayBuffer(32); +var dataView = new DataView(buf); +dataView.getUint8(0) // 0 +``` + +上面代码对一段32字节的内存,建立`DataView`视图,然后以不带符号的8位整数格式,读取第一个元素,结果得到0,因为原始内存的ArrayBuffer对象,默认所有位都是0。 + +另一种`TypedArray`视图,与`DataView`视图的一个区别是,它不是一个构造函数,而是一组构造函数,代表不同的数据格式。 + +```javascript +var buffer = new ArrayBuffer(12); + +var x1 = new Int32Array(buffer); +x1[0] = 1; +var x2 = new Uint8Array(buffer); +x2[0] = 2; + +x1[0] // 2 +``` + +上面代码对同一段内存,分别建立两种视图:32位带符号整数(Int32Array构造函数)和8位不带符号整数(Uint8Array构造函数)。由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。 + +TypedArray视图的构造函数,除了接受`ArrayBuffer`实例作为参数,还可以接受正常数组作为参数,直接分配内存生成底层的ArrayBuffer实例,并同时完成对这段内存的赋值。 + +```javascript +var typedArray = new Uint8Array([0,1,2]); +typedArray.length // 3 + +typedArray[0] = 5; +typedArray // [5, 1, 2] +``` + +上面代码使用`TypedArray`视图的`Uint8Array`构造函数,新建一个不带符号的8位整数视图。可以看到,`Uint8Array`直接使用正常数组作为参数,对底层内存的赋值同时完成。 + +### ArrayBuffer.prototype.byteLength + +`ArrayBuffer`实例的`byteLength`属性,返回所分配的内存区域的字节长度。 + +```javascript +var buffer = new ArrayBuffer(32); +buffer.byteLength +// 32 +``` + +如果要分配的内存区域很大,有可能分配失败(因为没有那么多的连续空余内存),所以有必要检查是否分配成功。 + +```javascript +if (buffer.byteLength === n) { + // 成功 +} else { + // 失败 +} +``` + +### ArrayBuffer.prototype.slice() + +`ArrayBuffer`实例有一个`slice`方法,允许将内存区域的一部分,拷贝生成一个新的`ArrayBuffer`对象。 + +```javascript +var buffer = new ArrayBuffer(8); +var newBuffer = buffer.slice(0, 3); +``` + +上面代码拷贝`buffer`对象的前3个字节(从0开始,到第3个字节前面结束),生成一个新的`ArrayBuffer`对象。`slice`方法其实包含两步,第一步是先分配一段新内存,第二步是将原来那个`ArrayBuffer`对象拷贝过去。 + +`slice`方法接受两个参数,第一个参数表示拷贝开始的字节序号(含该字节),第二个参数表示拷贝截止的字节序号(不含该字节)。如果省略第二个参数,则默认到原`ArrayBuffer`对象的结尾。 + +除了`slice`方法,`ArrayBuffer`对象不提供任何直接读写内存的方法,只允许在其上方建立视图,然后通过视图读写。 + +### ArrayBuffer.isView() + +`ArrayBuffer`有一个静态方法`isView`,返回一个布尔值,表示参数是否为`ArrayBuffer`的视图实例。这个方法大致相当于判断参数,是否为TypedArray实例或DataView实例。 + +```javascript +var buffer = new ArrayBuffer(8); +ArrayBuffer.isView(buffer) // false + +var v = new Int32Array(buffer); +ArrayBuffer.isView(v) // true +``` + +## TypedArray对象 + +### 概述 + +`ArrayBuffer`对象作为内存区域,可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)。`ArrayBuffer`有两种视图,一种是TypedArray视图,另一种是DataView视图,两者的区别主要是字节序,前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。 + +目前,TypedArray对象一共提供9种类型的视图,每一种视图都是一种构造函数。 + +- **Int8Array**:8位有符号整数,长度1个字节。 +- **Uint8Array**:8位无符号整数,长度1个字节。 +- **Uint8ClampedArray**:8位无符号整数,长度1个字节,溢出处理不同。 +- **Int16Array**:16位有符号整数,长度2个字节。 +- **Uint16Array**:16位无符号整数,长度2个字节。 +- **Int32Array**:32位有符号整数,长度4个字节。 +- **Uint32Array**:32位无符号整数,长度4个字节。 +- **Float32Array**:32位浮点数,长度4个字节。 +- **Float64Array**:64位浮点数,长度8个字节。 + +这9个构造函数生成的对象,统称为TypedArray对象。它们很像正常数组,都有`length`属性,都能用方括号运算符(`[]`)获取单个元素,所有数组的方法,在类型化数组上面都能使用。两者的差异主要在以下方面。 + +- TypedArray数组的所有成员,都是同一种类型和格式。 +- TypedArray数组的成员是连续的,不会有空位。 +- Typed化数组成员的默认值为0。比如,`new Array(10)`返回一个正常数组,里面没有任何成员,只是10个空位;`new Uint8Array(10)`返回一个类型化数组,里面10个成员都是0。 +- TypedArray数组只是一层视图,本身不储存数据,它的数据都储存在底层的`ArrayBuffer`对象之中,要获取底层对象必须使用`buffer`属性。 + +### 构造函数 + +TypedArray数组提供9种构造函数,用来生成相应类型的数组实例。 + +构造函数有多种用法。 + +**(1)TypedArray(buffer, byteOffset=0, length?)** + +同一个`ArrayBuffer`对象之上,可以根据不同的数据类型,建立多个视图。 + +```javascript +// 创建一个8字节的ArrayBuffer +var b = new ArrayBuffer(8); + +// 创建一个指向b的Int32视图,开始于字节0,直到缓冲区的末尾 +var v1 = new Int32Array(b); + +// 创建一个指向b的Uint8视图,开始于字节2,直到缓冲区的末尾 +var v2 = new Uint8Array(b, 2); + +// 创建一个指向b的Int16视图,开始于字节2,长度为2 +var v3 = new Int16Array(b, 2, 2); +``` + +上面代码在一段长度为8个字节的内存(`b`)之上,生成了三个视图:`v1`、`v2`和`v3`。 + +视图的构造函数可以接受三个参数: + +- 第一个参数(必需):视图对应的底层`ArrayBuffer`对象。 +- 第二个参数(可选):视图开始的字节序号,默认从0开始。 +- 第三个参数(可选):视图包含的数据个数,默认直到本段内存区域结束。 + +因此,`v1`、`v2`和`v3`是重叠的:`v1[0]`是一个32位整数,指向字节0~字节3;`v2[0]`是一个8位无符号整数,指向字节2;`v3[0]`是一个16位整数,指向字节2~字节3。只要任何一个视图对内存有所修改,就会在另外两个视图上反应出来。 + +注意,`byteOffset`必须与所要建立的数据类型一致,否则会报错。 + +```javascript +var buffer = new ArrayBuffer(8); +var i16 = new Int16Array(buffer, 1); +// Uncaught RangeError: start offset of Int16Array should be a multiple of 2 +``` + +上面代码中,新生成一个8个字节的`ArrayBuffer`对象,然后在这个对象的第一个字节,建立带符号的16位整数视图,结果报错。因为,带符号的16位整数需要两个字节,所以`byteOffset`参数必须能够被2整除。 + +如果想从任意字节开始解读`ArrayBuffer`对象,必须使用`DataView`视图,因为`TypedArray`视图只提供9种固定的解读格式。 + +**(2)TypedArray(length)** + +视图还可以不通过`ArrayBuffer`对象,直接分配内存而生成。 + +```javascript +var f64a = new Float64Array(8); +f64a[0] = 10; +f64a[1] = 20; +f64a[2] = f64a[0] + f64a[1]; +``` + +上面代码生成一个8个成员的`Float64Array`数组(共64字节),然后依次对每个成员赋值。这时,视图构造函数的参数就是成员的个数。可以看到,视图数组的赋值操作与普通数组的操作毫无两样。 + +**(3)TypedArray(typedArray)** + +类型化数组的构造函数,可以接受另一个视图实例作为参数。 + +```javascript +var typedArray = new Int8Array(new Uint8Array(4)); +``` + +上面代码中,`Int8Array`构造函数接受一个`Uint8Array`实例作为参数。 + +注意,此时生成的新数组,只是复制了参数数组的值,对应的底层内存是不一样的。新数组会开辟一段新的内存储存数据,不会在原数组的内存之上建立视图。 + +```javascript +var x = new Int8Array([1, 1]); +var y = new Int8Array(x); +x[0] // 1 +y[0] // 1 + +x[0] = 2; +y[0] // 1 +``` + +上面代码中,数组`y`是以数组`x`为模板而生成的,当`x`变动的时候,`y`并没有变动。 + +如果想基于同一段内存,构造不同的视图,可以采用下面的写法。 + +```javascript +var x = new Int8Array([1, 1]); +var y = new Int8Array(x.buffer); +x[0] // 1 +y[0] // 1 + +x[0] = 2; +y[0] // 2 +``` + +**(4)TypedArray(arrayLikeObject)** + +构造函数的参数也可以是一个普通数组,然后直接生成TypedArray实例。 + +```javascript +var typedArray = new Uint8Array([1, 2, 3, 4]); +``` + +注意,这时TypedArray视图会重新开辟内存,不会在原数组的内存上建立视图。 + +上面代码从一个普通的数组,生成一个8位无符号整数的`TypedArray`实例。 + +`TypedArray`数组也可以转换回普通数组。 + +```javascript +var normalArray = Array.prototype.slice.call(typedArray); +``` + +### 数组方法 + +普通数组的操作方法和属性,对TypedArray数组完全适用。 + +- `TypedArray.prototype.copyWithin(target, start[, end = this.length])` +- `TypedArray.prototype.entries()` +- `TypedArray.prototype.every(callbackfn, thisArg?)` +- `TypedArray.prototype.fill(value, start=0, end=this.length)` +- `TypedArray.prototype.filter(callbackfn, thisArg?)` +- `TypedArray.prototype.find(predicate, thisArg?)` +- `TypedArray.prototype.findIndex(predicate, thisArg?)` +- `TypedArray.prototype.forEach(callbackfn, thisArg?)` +- `TypedArray.prototype.indexOf(searchElement, fromIndex=0)` +- `TypedArray.prototype.join(separator)` +- `TypedArray.prototype.keys()` +- `TypedArray.prototype.lastIndexOf(searchElement, fromIndex?)` +- `TypedArray.prototype.map(callbackfn, thisArg?)` +- `TypedArray.prototype.reduce(callbackfn, initialValue?)` +- `TypedArray.prototype.reduceRight(callbackfn, initialValue?)` +- `TypedArray.prototype.reverse()` +- `TypedArray.prototype.slice(start=0, end=this.length)` +- `TypedArray.prototype.some(callbackfn, thisArg?)` +- `TypedArray.prototype.sort(comparefn)` +- `TypedArray.prototype.toLocaleString(reserved1?, reserved2?)` +- `TypedArray.prototype.toString()` +- `TypedArray.prototype.values()` + +上面所有方法的用法,请参阅数组方法的介绍,这里不再重复了。 + +另外,`TypedArray`数组与普通数组一样,部署了Iterator接口,所以可以被遍历。 + +```javascript +let ui8 = Uint8Array.of(0, 1, 2); +for (let byte of ui8) { + console.log(byte); +} +// 0 +// 1 +// 2 +``` + +### 字节序 + +字节序指的是数值在内存中的表示方式。 + +```javascript +var buffer = new ArrayBuffer(16); +var int32View = new Int32Array(buffer); + +for (var i = 0; i < int32View.length; i++) { + int32View[i] = i * 2; +} +``` + +上面代码生成一个16字节的`ArrayBuffer`对象,然后在它的基础上,建立了一个32位整数的视图。由于每个32位整数占据4个字节,所以一共可以写入4个整数,依次为0,2,4,6。 + +如果在这段数据上接着建立一个16位整数的视图,则可以读出完全不一样的结果。 + +```javascript +var int16View = new Int16Array(buffer); + +for (var i = 0; i < int16View.length; i++) { + console.log("Entry " + i + ": " + int16View[i]); +} +// Entry 0: 0 +// Entry 1: 0 +// Entry 2: 2 +// Entry 3: 0 +// Entry 4: 4 +// Entry 5: 0 +// Entry 6: 6 +// Entry 7: 0 +``` + +由于每个16位整数占据2个字节,所以整个ArrayBuffer对象现在分成8段。然后,由于x86体系的计算机都采用小端字节序(little endian),相对重要的字节排在后面的内存地址,相对不重要字节排在前面的内存地址,所以就得到了上面的结果。 + +比如,一个占据四个字节的16进制数`0x12345678`,决定其大小的最重要的字节是“12”,最不重要的是“78”。小端字节序将最不重要的字节排在前面,储存顺序就是`78563412`;大端字节序则完全相反,将最重要的字节排在前面,储存顺序就是`12345678`。目前,所有个人电脑几乎都是小端字节序,所以TypedArray数组内部也采用小端字节序读写数据,或者更准确的说,按照本机操作系统设定的字节序读写数据。 + +这并不意味大端字节序不重要,事实上,很多网络设备和特定的操作系统采用的是大端字节序。这就带来一个严重的问题:如果一段数据是大端字节序,TypedArray数组将无法正确解析,因为它只能处理小端字节序!为了解决这个问题,JavaScript引入`DataView`对象,可以设定字节序,下文会详细介绍。 + +下面是另一个例子。 + +```javascript +// 假定某段buffer包含如下字节 [0x02, 0x01, 0x03, 0x07] +var buffer = new ArrayBuffer(4); +var v1 = new Uint8Array(buffer); +v1[0] = 2; +v1[1] = 1; +v1[2] = 3; +v1[3] = 7; + +var uInt16View = new Uint16Array(buffer); + +// 计算机采用小端字节序 +// 所以头两个字节等于258 +if (uInt16View[0] === 258) { + console.log('OK'); // "OK" +} + +// 赋值运算 +uInt16View[0] = 255; // 字节变为[0xFF, 0x00, 0x03, 0x07] +uInt16View[0] = 0xff05; // 字节变为[0x05, 0xFF, 0x03, 0x07] +uInt16View[1] = 0x0210; // 字节变为[0x05, 0xFF, 0x10, 0x02] +``` + +下面的函数可以用来判断,当前视图是小端字节序,还是大端字节序。 + +```javascript +const BIG_ENDIAN = Symbol('BIG_ENDIAN'); +const LITTLE_ENDIAN = Symbol('LITTLE_ENDIAN'); + +function getPlatformEndianness() { + let arr32 = Uint32Array.of(0x12345678); + let arr8 = new Uint8Array(arr32.buffer); + switch ((arr8[0]*0x1000000) + (arr8[1]*0x10000) + (arr8[2]*0x100) + (arr8[3])) { + case 0x12345678: + return BIG_ENDIAN; + case 0x78563412: + return LITTLE_ENDIAN; + default: + throw new Error('Unknown endianness'); + } +} +``` + +总之,与普通数组相比,TypedArray数组的最大优点就是可以直接操作内存,不需要数据类型转换,所以速度快得多。 + +### BYTES_PER_ELEMENT属性 + +每一种视图的构造函数,都有一个`BYTES_PER_ELEMENT`属性,表示这种数据类型占据的字节数。 + +```javascript +Int8Array.BYTES_PER_ELEMENT // 1 +Uint8Array.BYTES_PER_ELEMENT // 1 +Int16Array.BYTES_PER_ELEMENT // 2 +Uint16Array.BYTES_PER_ELEMENT // 2 +Int32Array.BYTES_PER_ELEMENT // 4 +Uint32Array.BYTES_PER_ELEMENT // 4 +Float32Array.BYTES_PER_ELEMENT // 4 +Float64Array.BYTES_PER_ELEMENT // 8 +``` + +这个属性在`TypedArray`实例上也能获取,即有`TypedArray.prototype.BYTES_PER_ELEMENT`。 + +### ArrayBuffer与字符串的互相转换 + +`ArrayBuffer`转为字符串,或者字符串转为`ArrayBuffer`,有一个前提,即字符串的编码方法是确定的。假定字符串采用UTF-16编码(JavaScript的内部编码方式),可以自己编写转换函数。 + +```javascript +// ArrayBuffer转为字符串,参数为ArrayBuffer对象 +function ab2str(buf) { + return String.fromCharCode.apply(null, new Uint16Array(buf)); +} + +// 字符串转为ArrayBuffer对象,参数为字符串 +function str2ab(str) { + var buf = new ArrayBuffer(str.length * 2); // 每个字符占用2个字节 + var bufView = new Uint16Array(buf); + for (var i = 0, strLen = str.length; i < strLen; i++) { + bufView[i] = str.charCodeAt(i); + } + return buf; +} +``` + +### 溢出 + +不同的视图类型,所能容纳的数值范围是确定的。超出这个范围,就会出现溢出。 + +TypedArray数组对于溢出,采用的处理方法是求余值。正向溢出(overflow)等于,当前数据类型的最小值加上余值,再减去1;负向溢出(underflow)等于当前数据类型的最大值减去余值,再加上1。 + +```javascript +var uint8 = new Uint8Array(1); + +uint8[0] = 256; +uint8[0] // 0 + +uint8[0] = -1; +uint8[0] // 255 +``` + +上面例子中,8位无符号整数的数值范围是0到255,超出这个范围,就是溢出。256相当于正向溢出1,回到最小值0;-1相当于负向溢出1,回到最大值255。 + +下面是8位带符号整数的例子。 + +```javascript +var int8 = new Int8Array(1); + +int8[0] = 128; +int8[0] // -128 + +int8[0] = -129; +int8[0] // 127 +``` + +上面例子中,8位带符号整数的数值范围是-128到127。128相当于正向溢出1,等于-128;-129相当于负向溢出1,等于127。 + +`Uint8ClampedArray`视图的溢出,与上面的规则有所不同。负向溢出都等于0,正向溢出都等于255。 + +```javascript +var uint8c = new Uint8ClampedArray(1); + +uint8c[0] = 256; +uint8c[0] // 255 + +uint8c[0] = -1; +uint8c[0] // 0 +``` + +上面例子中,`Uint8C`类型的数值范围与8位无符号整数相同,都是0到255。正向溢出都等于255,负向溢出都等于0。 + +### TypedArray.prototype.buffer + +TypedArray实例的buffer属性,返回整段内存区域对应的`ArrayBuffer`对象。该属性为只读属性。 + +```javascript +var a = new Float32Array(64); +var b = new Uint8Array(a.buffer); +``` + +上面代码的`a`视图对象和`b`视图对象,对应同一个`ArrayBuffer`对象,即同一段内存。 + +### TypedArray.prototype.byteLength,TypedArray.prototype.byteOffset + +`byteLength`属性返回TypedArray数组占据的内存长度,单位为字节。`byteOffset`属性返回TypedArray数组从底层`ArrayBuffer`对象的哪个字节开始。这两个属性都是只读属性。 + +```javascript +var b = new ArrayBuffer(8); + +var v1 = new Int32Array(b); +var v2 = new Uint8Array(b, 2); +var v3 = new Int16Array(b, 2, 2); + +v1.byteLength // 8 +v2.byteLength // 6 +v3.byteLength // 4 + +v1.byteOffset // 0 +v2.byteOffset // 2 +v3.byteOffset // 2 +``` + +### TypedArray.prototype.length + +`length`属性表示TypedArray数组含有多少个成员。注意将`byteLength`属性和`length`属性区分,前者是字节长度,后者是成员长度。 + +```javascript +var a = new Int16Array(8); + +a.length // 8 +a.byteLength // 16 +``` + +### TypedArray.prototype.set() + +TypedArray数组的`set`方法用于复制数组(正常数组或TypedArray数组),也就是将一段内容完全复制到另一段内存。 + +```javascript +var a = new Uint8Array(8); +var b = new Uint8Array(8); + +b.set(a); +``` + +上面代码复制`a`数组的内容到`b`数组,它是整段内存的复制,比一个个拷贝成员的那种复制快得多。`set`方法还可以接受第二个参数,表示从`b`对象哪一个成员开始复制`a`对象。 + +```javascript +var a = new Uint16Array(8); +var b = new Uint16Array(10); + +b.set(a, 2) +``` + +上面代码的`b`数组比`a`数组多两个成员,所以从`b[2]`开始复制。 + +### TypedArray.prototype.subarray() + +`subarray`方法是对于TypedArray数组的一部分,再建立一个新的视图。 + +```javascript +var a = new Uint16Array(8); +var b = a.subarray(2,3); + +a.byteLength // 16 +b.byteLength // 2 +``` + +`subarray`方法的第一个参数是起始的成员序号,第二个参数是结束的成员序号(不含该成员),如果省略则包含剩余的全部成员。所以,上面代码的`a.subarray(2,3)`,意味着b只包含`a[2]`一个成员,字节长度为2。 + +### TypedArray.prototype.slice() + +TypeArray实例的`slice`方法,可以返回一个指定位置的新的TypedArray实例。 + +```javascript +let ui8 = Uint8Array.of(0, 1, 2); +ui8.slice(-1) +// Uint8Array [ 2 ] +``` + +上面代码中,`ui8`是8位无符号整数数组视图的一个实例。它的`slice`方法可以从当前视图之中,返回一个新的视图实例。 + +`slice`方法的参数,表示原数组的具体位置,开始生成新数组。负值表示逆向的位置,即-1为倒数第一个位置,-2表示倒数第二个位置,以此类推。 + +### TypedArray.of() + +TypedArray数组的所有构造函数,都有一个静态方法`of`,用于将参数转为一个TypedArray实例。 + +```javascript +Float32Array.of(0.151, -8, 3.7) +// Float32Array [ 0.151, -8, 3.7 ] +``` + +### TypedArray.from() + +静态方法`from`接受一个可遍历的数据结构(比如数组)作为参数,返回一个基于这个结构的TypedArray实例。 + +```javascript +Uint16Array.from([0, 1, 2]) +// Uint16Array [ 0, 1, 2 ] +``` + +这个方法还可以将一种TypedArray实例,转为另一种。 + +```javascript +var ui16 = Uint16Array.from(Uint8Array.of(0, 1, 2)); +ui16 instanceof Uint16Array // true +``` + +`from`方法还可以接受一个函数,作为第二个参数,用来对每个元素进行遍历,功能类似`map`方法。 + +```javascript +Int8Array.of(127, 126, 125).map(x => 2 * x) +// Int8Array [ -2, -4, -6 ] + +Int16Array.from(Int8Array.of(127, 126, 125), x => 2 * x) +// Int16Array [ 254, 252, 250 ] +``` + +上面的例子中,`from`方法没有发生溢出,这说明遍历是针对新生成的16位整数数组,而不是针对原来的8位整数数组。也就是说,`from`会将第一个参数指定的TypedArray数组,拷贝到另一段内存之中(占用内存从3字节变为6字节),然后再进行处理。 + +## 复合视图 + +由于视图的构造函数可以指定起始位置和长度,所以在同一段内存之中,可以依次存放不同类型的数据,这叫做“复合视图”。 + +```javascript +var buffer = new ArrayBuffer(24); + +var idView = new Uint32Array(buffer, 0, 1); +var usernameView = new Uint8Array(buffer, 4, 16); +var amountDueView = new Float32Array(buffer, 20, 1); +``` + +上面代码将一个24字节长度的ArrayBuffer对象,分成三个部分: + +- 字节0到字节3:1个32位无符号整数 +- 字节4到字节19:16个8位整数 +- 字节20到字节23:1个32位浮点数 + +这种数据结构可以用如下的C语言描述: + +```c +struct someStruct { + unsigned long id; + char username[16]; + float amountDue; +}; +``` + +## DataView视图 + +如果一段数据包括多种类型(比如服务器传来的HTTP数据),这时除了建立`ArrayBuffer`对象的复合视图以外,还可以通过`DataView`视图进行操作。 + +`DataView`视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,`ArrayBuffer`对象的各种TypedArray视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而`DataView`视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。 + +`DataView`视图本身也是构造函数,接受一个`ArrayBuffer`对象作为参数,生成视图。 + +```javascript +DataView(ArrayBuffer buffer [, 字节起始位置 [, 长度]]); +``` + +下面是一个例子。 + +```javascript +var buffer = new ArrayBuffer(24); +var dv = new DataView(buffer); +``` + +`DataView`实例有以下属性,含义与`TypedArray`实例的同名方法相同。 + +- DataView.prototype.buffer:返回对应的ArrayBuffer对象 +- DataView.prototype.byteLength:返回占据的内存字节长度 +- DataView.prototype.byteOffset:返回当前视图从对应的ArrayBuffer对象的哪个字节开始 + +`DataView`实例提供8个方法读取内存。 + +- **getInt8**:读取1个字节,返回一个8位整数。 +- **getUint8**:读取1个字节,返回一个无符号的8位整数。 +- **getInt16**:读取2个字节,返回一个16位整数。 +- **getUint16**:读取2个字节,返回一个无符号的16位整数。 +- **getInt32**:读取4个字节,返回一个32位整数。 +- **getUint32**:读取4个字节,返回一个无符号的32位整数。 +- **getFloat32**:读取4个字节,返回一个32位浮点数。 +- **getFloat64**:读取8个字节,返回一个64位浮点数。 + +这一系列get方法的参数都是一个字节序号(不能是负数,否则会报错),表示从哪个字节开始读取。 + +```javascript +var buffer = new ArrayBuffer(24); +var dv = new DataView(buffer); + +// 从第1个字节读取一个8位无符号整数 +var v1 = dv.getUint8(0); + +// 从第2个字节读取一个16位无符号整数 +var v2 = dv.getUint16(1); + +// 从第4个字节读取一个16位无符号整数 +var v3 = dv.getUint16(3); +``` + +上面代码读取了`ArrayBuffer`对象的前5个字节,其中有一个8位整数和两个十六位整数。 + +如果一次读取两个或两个以上字节,就必须明确数据的存储方式,到底是小端字节序还是大端字节序。默认情况下,`DataView`的`get`方法使用大端字节序解读数据,如果需要使用小端字节序解读,必须在`get`方法的第二个参数指定`true`。 + +```javascript +// 小端字节序 +var v1 = dv.getUint16(1, true); + +// 大端字节序 +var v2 = dv.getUint16(3, false); + +// 大端字节序 +var v3 = dv.getUint16(3); +``` + +DataView视图提供8个方法写入内存。 + +- **setInt8**:写入1个字节的8位整数。 +- **setUint8**:写入1个字节的8位无符号整数。 +- **setInt16**:写入2个字节的16位整数。 +- **setUint16**:写入2个字节的16位无符号整数。 +- **setInt32**:写入4个字节的32位整数。 +- **setUint32**:写入4个字节的32位无符号整数。 +- **setFloat32**:写入4个字节的32位浮点数。 +- **setFloat64**:写入8个字节的64位浮点数。 + +这一系列set方法,接受两个参数,第一个参数是字节序号,表示从哪个字节开始写入,第二个参数为写入的数据。对于那些写入两个或两个以上字节的方法,需要指定第三个参数,false或者undefined表示使用大端字节序写入,true表示使用小端字节序写入。 + +```javascript +// 在第1个字节,以大端字节序写入值为25的32位整数 +dv.setInt32(0, 25, false); + +// 在第5个字节,以大端字节序写入值为25的32位整数 +dv.setInt32(4, 25); + +// 在第9个字节,以小端字节序写入值为2.5的32位浮点数 +dv.setFloat32(8, 2.5, true); +``` + +如果不确定正在使用的计算机的字节序,可以采用下面的判断方式。 + +```javascript +var littleEndian = (function() { + var buffer = new ArrayBuffer(2); + new DataView(buffer).setInt16(0, 256, true); + return new Int16Array(buffer)[0] === 256; +})(); +``` + +如果返回`true`,就是小端字节序;如果返回`false`,就是大端字节序。 + +## 二进制数组的应用 + +大量的Web API用到了`ArrayBuffer`对象和它的视图对象。 + +### AJAX + +传统上,服务器通过AJAX操作只能返回文本数据,即`responseType`属性默认为`text`。`XMLHttpRequest`第二版`XHR2`允许服务器返回二进制数据,这时分成两种情况。如果明确知道返回的二进制数据类型,可以把返回类型(`responseType`)设为`arraybuffer`;如果不知道,就设为`blob`。 + +```javascript +var xhr = new XMLHttpRequest(); +xhr.open('GET', someUrl); +xhr.responseType = 'arraybuffer'; + +xhr.onload = function () { + var let arrayBuffer = xhr.response; + // ··· +}; + +xhr.send(); +``` + +如果知道传回来的是32位整数,可以像下面这样处理。 + +```javascript +xhr.onreadystatechange = function () { + if (req.readyState === 4 ) { + var arrayResponse = xhr.response; + var dataView = new DataView(arrayResponse); + var ints = new Uint32Array(dataView.byteLength / 4); + + xhrDiv.style.backgroundColor = "#00FF00"; + xhrDiv.innerText = "Array is " + ints.length + "uints long"; + } +} +``` + +### Canvas + +网页`Canvas`元素输出的二进制像素数据,就是类型化数组。 + +```javascript +var canvas = document.getElementById('myCanvas'); +var ctx = canvas.getContext('2d'); + +var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); +var uint8ClampedArray = imageData.data; +``` + +需要注意的是,上面代码的`typedArray`虽然是一个类型化数组,但是它的视图类型是一种针对`Canvas`元素的专有类型`Uint8ClampedArray`。这个视图类型的特点,就是专门针对颜色,把每个字节解读为无符号的8位整数,即只能取值0~255,而且发生运算的时候自动过滤高位溢出。这为图像处理带来了巨大的方便。 + +举例来说,如果把像素的颜色值设为`Uint8Array`类型,那么乘以一个gamma值的时候,就必须这样计算: + +```javascript +u8[i] = Math.min(255, Math.max(0, u8[i] * gamma)); +``` + +因为`Uint8Array`类型对于大于255的运算结果(比如0xFF+1),会自动变为0x00,所以图像处理必须要像上面这样算。这样做很麻烦,而且影响性能。如果将颜色值设为`Uint8ClampedArray`类型,计算就简化许多。 + +```javascript +pixels[i] *= gamma; +``` + +`Uint8ClampedArray`类型确保将小于0的值设为0,将大于255的值设为255。注意,IE 10不支持该类型。 + +### WebSocket + +`WebSocket`可以通过`ArrayBuffer`,发送或接收二进制数据。 + +```javascript +var socket = new WebSocket('ws://127.0.0.1:8081'); +socket.binaryType = 'arraybuffer'; + +// Wait until socket is open +socket.addEventListener('open', function (event) { + // Send binary data + var typedArray = new Uint8Array(4); + socket.send(typedArray.buffer); +}); + +// Receive binary data +socket.addEventListener('message', function (event) { + var arrayBuffer = event.data; + // ··· +}); +``` + +### Fetch API + +Fetch API取回的数据,就是`ArrayBuffer`对象。 + +```javascript +fetch(url) +.then(function(request){ + return request.arrayBuffer() +}) +.then(function(arrayBuffer){ + // ... +}); +``` + +### File API + +如果知道一个文件的二进制数据类型,也可以将这个文件读取为ArrayBuffer对象。 + +```javascript +var fileInput = document.getElementById('fileInput'); +var file = fileInput.files[0]; +var reader = new FileReader(); +reader.readAsArrayBuffer(file); +reader.onload = function () { + var arrayBuffer = reader.result; + // ··· +}; +``` + +下面以处理bmp文件为例。假定file变量是一个指向bmp文件的文件对象,首先读取文件。 + +```javascript +var reader = new FileReader(); +reader.addEventListener("load", processimage, false); +reader.readAsArrayBuffer(file); +``` + +然后,定义处理图像的回调函数:先在二进制数据之上建立一个DataView视图,再建立一个bitmap对象,用于存放处理后的数据,最后将图像展示在canvas元素之中。 + +```javascript +function processimage(e) { + var buffer = e.target.result; + var datav = new DataView(buffer); + var bitmap = {}; + // 具体的处理步骤 +} +``` + +具体处理图像数据时,先处理bmp的文件头。具体每个文件头的格式和定义,请参阅有关资料。 + +```javascript +bitmap.fileheader = {}; +bitmap.fileheader.bfType = datav.getUint16(0, true); +bitmap.fileheader.bfSize = datav.getUint32(2, true); +bitmap.fileheader.bfReserved1 = datav.getUint16(6, true); +bitmap.fileheader.bfReserved2 = datav.getUint16(8, true); +bitmap.fileheader.bfOffBits = datav.getUint32(10, true); +``` + +接着处理图像元信息部分。 + +```javascript +bitmap.infoheader = {}; +bitmap.infoheader.biSize = datav.getUint32(14, true); +bitmap.infoheader.biWidth = datav.getUint32(18, true); +bitmap.infoheader.biHeight = datav.getUint32(22, true); +bitmap.infoheader.biPlanes = datav.getUint16(26, true); +bitmap.infoheader.biBitCount = datav.getUint16(28, true); +bitmap.infoheader.biCompression = datav.getUint32(30, true); +bitmap.infoheader.biSizeImage = datav.getUint32(34, true); +bitmap.infoheader.biXPelsPerMeter = datav.getUint32(38, true); +bitmap.infoheader.biYPelsPerMeter = datav.getUint32(42, true); +bitmap.infoheader.biClrUsed = datav.getUint32(46, true); +bitmap.infoheader.biClrImportant = datav.getUint32(50, true); +``` + +最后处理图像本身的像素信息。 + +```javascript +var start = bitmap.fileheader.bfOffBits; +bitmap.pixels = new Uint8Array(buffer, start); +``` + +至此,图像文件的数据全部处理完成。下一步,可以根据需要,进行图像变形,或者转换格式,或者展示在Canvas网页元素之中。 diff --git a/docs/reference.md b/docs/reference.md index a0818d4b3..7024441d0 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -94,6 +94,14 @@ - Jason Orendorff, [ES6 In Depth: Symbols](https://hacks.mozilla.org/2015/06/es6-in-depth-symbols/) - Keith Cirkel, [Metaprogramming in ES6: Symbols and why they're awesome](http://blog.keithcirkel.co.uk/metaprogramming-in-es6-symbols/): Symbol的深入介绍 +## 二进制数组 + +- Ilmari Heikkinen, [Typed Arrays: Binary Data in the Browser](http://www.html5rocks.com/en/tutorials/webgl/typed_arrays/) +- Khronos, [Typed Array Specification](http://www.khronos.org/registry/typedarray/specs/latest/) +- Ian Elliot, [Reading A BMP File In JavaScript](http://www.i-programmer.info/projects/36-web/6234-reading-a-bmp-file-in-javascript.html) +- Renato Mangini, [How to convert ArrayBuffer to and from String](http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String) +- Axel Rauschmayer, [Typed Arrays in ECMAScript 6](http://www.2ality.com/2015/09/typed-arrays.html) + ## Set和Map - Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构 diff --git a/sidebar.md b/sidebar.md index d1ea5c3eb..174d24e1b 100644 --- a/sidebar.md +++ b/sidebar.md @@ -17,6 +17,7 @@ 1. [对象的扩展](#docs/object) 1. [Symbol](#docs/symbol) 1. [Proxy和Reflect](#docs/proxy) +1. [二进制数组](#docs/arraybuffer) 1. [Set和Map数据结构](#docs/set-map) 1. [Iterator和for...of循环](#docs/iterator) 1. [Generator函数](#docs/generator) From f86ef7d9dfc012c8f0dfb6a3cbf430035b8a5198 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 1 Oct 2015 21:27:41 +0800 Subject: [PATCH 0027/1221] edit arraybuffer --- docs/arraybuffer.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/arraybuffer.md b/docs/arraybuffer.md index 8a8b5b396..b7262fea6 100644 --- a/docs/arraybuffer.md +++ b/docs/arraybuffer.md @@ -1,3 +1,5 @@ +# 二进制数组 + ArrayBuffer对象、TypedArray对象、DataView对象是JavaScript操作二进制数据的一个接口。这些对象早就存在,属于独立的规格,ES6将它们纳入了ECMAScript规格,并且增加了新的方法。 这些对象原始的设计目的,要从WebGL项目的诞生说起。所谓WebGL,就是指浏览器与显卡之间的通信接口,为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个32位整数,两端的JavaScript脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像C语言那样,直接操作字节,将4个字节的32位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。 From f54bda74268510630f38a0207a7deee21f1762b2 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 1 Oct 2015 21:49:31 +0800 Subject: [PATCH 0028/1221] edit arraybuffer --- docs/arraybuffer.md | 22 ++++++++++++---------- index.html | 16 ++++++++-------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/arraybuffer.md b/docs/arraybuffer.md index b7262fea6..18574a05d 100644 --- a/docs/arraybuffer.md +++ b/docs/arraybuffer.md @@ -2,19 +2,21 @@ ArrayBuffer对象、TypedArray对象、DataView对象是JavaScript操作二进制数据的一个接口。这些对象早就存在,属于独立的规格,ES6将它们纳入了ECMAScript规格,并且增加了新的方法。 -这些对象原始的设计目的,要从WebGL项目的诞生说起。所谓WebGL,就是指浏览器与显卡之间的通信接口,为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个32位整数,两端的JavaScript脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像C语言那样,直接操作字节,将4个字节的32位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。 +这些对象原始的设计目的,与WebGL项目有关。所谓WebGL,就是指浏览器与显卡之间的通信接口,为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个32位整数,两端的JavaScript脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像C语言那样,直接操作字节,将4个字节的32位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。 -`ArrayBuffer`等对象就是在这种背景下诞生的。它很像C语言的数组,允许开发者以数组下标的形式,直接操作内存。它们大大增强了JavaScript处理二进制数据的能力,使得开发者有可能通过JavaScript与操作系统的原生接口进行二进制通信。 +二进制数组就是在这种背景下诞生的。它很像C语言的数组,允许开发者以数组下标的形式,直接操作内存,大大增强了JavaScript处理二进制数据的能力,使得开发者有可能通过JavaScript与操作系统的原生接口进行二进制通信。 -ArrayBuffer对象与TypedArray对象、DataView对象的区别是:前者代表底层数据,不区分类型;后两者代表上层视图,具有类型。 +二进制数组由三个对象组成。 -- ArrayBuffer对象:代表内存的一段二进制数据,可以通过“视图”进行操作。各种“视图”具有数组接口,可以用数组的方法操作内存。 -- TypedArray对象:提供多个构造函数,用来生成不同类型的视图,比如`Uint8Array`(无符号8位整数)数组视图, `Int16Array`(16位整数)数组视图, `Float32Array`(32位浮点数)数组视图等等。 -- DataView对象:可以自定义字节序,解读同一段内存,比如Uint8(无符号8位整数)、Int16(16位整数)、Float32(32位浮点数)等等。 +**(1)ArrayBuffer对象**:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。 -ArrayBuffer对象的视图支持以下数据类型。 +**(2) TypedArray对象**:用来生成内存的视图,通过9个构造函数,可以生成9种数据格式的视图,比如`Uint8Array`(无符号8位整数)数组视图, `Int16Array`(16位整数)数组视图, `Float32Array`(32位浮点数)数组视图等等。 -数据类型|字节长度|含义|对应的C语言类型 +**(3)DataView对象**:用来生成内存的视图,可以自定义格式和字节序,比如第一个字节是Uint8(无符号8位整数)、第二个字节是Int16(16位整数)、第三个字节是Float32(32位浮点数)等等。 + +简单说,ArrayBuffer对象代表原始的二进制数据,TypedArray对象代表确定类型的二进制数据,DataView对象代表不确定类型的二进制数据。它们支持的数据类型一共有9种(DataView对象支持除`Uint8C`以外的其他8种)。 + +数据类型 | 字节长度 | 含义 | 对应的C语言类型 --------|--------|----|--------------- Int8|1|8位带符号整数|signed char Uint8|1|8位不带符号整数|unsigned char @@ -26,7 +28,7 @@ Uint32|4|32位不带符号的整数|unsigned int Float32|4|32位浮点数|float Float64|8|64位浮点数|double -很多浏览器的API使用ArrayBuffer对象,操作二进制数据,下面是其中的几个。 +很多浏览器操作的API,用到了二进制数组操作二进制数据,下面是其中的几个。 - File API - XMLHttpRequest @@ -38,7 +40,7 @@ Float64|8|64位浮点数|double ### 概述 -`ArrayBuffer`对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(`TypedArray`视图和`DataView`视图)来读写。视图的作用是以指定格式解读二进制数据。 +`ArrayBuffer`对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(`TypedArray`视图和`DataView`视图)来读写,视图的作用是以指定格式解读二进制数据。 `ArrayBuffer`也是一个构造函数,可以分配一段可以存放数据的连续内存区域。 diff --git a/index.html b/index.html index 8e41d3565..f0cf41184 100644 --- a/index.html +++ b/index.html @@ -29,15 +29,15 @@