1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 18:32:22 +00:00

add ES7 features

This commit is contained in:
ruanyf 2015-09-18 08:06:09 +08:00
parent 8ac0c18bd7
commit 97f6f6d98e
5 changed files with 263 additions and 63 deletions

View File

@ -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`属性发生变化)。

View File

@ -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的原始定义影响到所有实例。
**4name属性**
@ -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因此这个属性可以用来确定构造函数是怎么调用的。

View File

@ -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所以调用时不用提供这个值。
总结一下递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令所有的循环都用递归实现这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言比如LuaES6只需要知道循环可以用递归代替而一旦使用递归就最好使用尾递归。
## 函数参数的尾逗号
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',
);
```

View File

@ -52,9 +52,9 @@ Node.js和io.js一个部署新功能更快的Node分支是JavaScript语言
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/<version number>/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
<script src="node_modules/babel-core/browser.js"></script>
@ -204,14 +216,18 @@ Babel也可以用于浏览器。
</script>
```
上面代码中,`browser.js`是Babel提供的转换器脚本可以在浏览器运行。用户的ES6脚本放在script标签之中但是要注明`type="text/babel"`
上面代码中,`browser.js`是Babel提供的转换器脚本可以在浏览器运行。用户的ES6脚本放在`script`标签之中,但是要注明`type="text/babel"`
Babel配合Browserify一起使用可以生成浏览器能够直接加载的脚本。
这种写法是实时将ES6代码转为ES5对网页性能会有影响。生产环境需要加载已经转码完成的脚本。
`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代码。
@ -373,16 +421,42 @@ fs.writeFileSync('out.js.map', result.sourceMap);
2013年3月ES6的草案封闭不再接受新功能了。新的功能将被加入ES7。
ES7可能包括的功能有
任何人都可以向ES7提案从提案到变成正式标准需要经历五个阶段。每个阶段的变动都需要由TC39委员会批准。
1**Object.observe**:用来监听对象(以及数组)的变化。一旦监听对象发生变化,就会触发回调函数。
- Stage 0 - Strawman展示阶段
- Stage 1 - Proposal征求意见阶段
- Stage 2 - Draft草案阶段
- Stage 3 - Candidate候选人阶段
- Stage 4 - Finished定案阶段
2**Async函数**在Promise和Generator函数基础上提出的异步操作解决方案。
3**Multi-Threading**多线程支持。目前Intel和Mozilla有一个共同的研究项目RiverTrail致力于让JavaScript多线程运行。预计这个项目的研究成果会被纳入ECMAScript标准。
4**Traits**它将是“类”功能class的一个替代。通过它不同的对象可以分享同样的特性。
其他可能包括的功能还有更精确的数值计算、改善的内存回收、增强的跨站点安全、类型化的更贴近硬件的低级别操作、国际化支持Internationalization Support、更多的数据结构等等。
一个提案只要能进入Stage 2就差不多等于肯定会包括在ES7里面。
本书的写作目标之一是跟踪ECMAScript语言的最新进展。对于那些明确的、或者很有希望列入ES7的功能尤其是那些Babel已经支持的功能都将予以介绍。
本书介绍的ES7功能清单如下。
**Stage 0**
- es7.comprehensions数组推导
- es7.classProperties类的属性
- es7.functionBind函数的绑定运算符
**Stage 1**
- es7.decorators修饰器
- es7.exportExtensionsexport的扩展写法
- es7.trailingFunctionCommas函数参数的尾逗号
**Stage 2**
- es7.exponentiationOperator指数运算符
- es7.asyncFunctionsansyc函数
- es7.objectRestSpread对象的Rest参数和扩展运算符
Babel转码器对Stage 2及以上阶段的功能是默认支持的。对于那些默认没有打开的功能需要手动打开。
```bash
$ babel --stage 0
$ babel --optional es7.decorators
```

View File

@ -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';