转载

Getting started with ECMAScript 6

1、尝试ES6

这里有三种简单地方式用于ES6编程:

1. Web浏览器 :使用 Babel REPL ,可以将ES6编译成ES5的平台,并且并不需要安装。

2. 命令行 :使用 babel-node ,可以执行ES6的Node.js版本(会在内部编译es5)。需要通过 npm 安装。

3. 各种js引擎 :根据 ES6语法兼容表 ,找出被支持的ES6功能。

对于第一点和第二点,这有更多细节。

1.1 Babel REPL

Babel REPL主要有四个部分:

1、左上角包含es6源代码

2、左下角可以查看es6中的语法错误

3、右上角是es6被编译成es5的源代码

4、右下角是通过 console.log() 的输出结果

Getting started with ECMAScript 6

1.2 babel-node

babel-node可以通过 npm 安装:

  1. $ npm install --global babel

跟node一样,可以命令开启REPL交互:

  1. $ babel-node

一旦开启REPL,就可以去执行ES6代码:

  1. > let arr = [1, 2, 3];
  2. > arr.map(x => x * x)
  3. [ 1, 4, 9 ]

注意: babel-node目前不支持多行输入

Babel官网上有管多关于 Babel CLI 的工具。

2. 从var到let/const

es6有两种新的方式来声明变量:

1、 let 用于声明块级作用于变量

2、 const 用于声明常量,其值不能被改变。

letconst 可以用来替代 var 声明变量,但是不能盲目使用,因为不同的作用域会改变代码的行为。例如:

  1. var x = 3;
  2. function func(randomize) {
  3. if (randomize) {
  4. var x = Math.random(); // (A) scope: whole function
  5. return x;
  6. }
  7. return x; // accesses the x from line A
  8. }
  9. func(false); // undefined

func() 会意外地返回 undefined 。你可以重写这部分代码,就知道为什么返回 undefined 了:

  1. var x = 3;
  2. function func(randomize) {
  3. var x;
  4. if (randomize) {
  5. x = Math.random();
  6. return x;
  7. }
  8. return x;
  9. }
  10. func(false); // undefined

如果用 let 代替之前的 var ,就会得到不同的结果:

  1. let x = 3;
  2. function func(randomize) {
  3. if (randomize) {
  4. let x = Math.random();
  5. return x;
  6. }
  7. return x;
  8. }
  9. func(false); // 3

因此,盲目地用 letconst 代替 var 是有风险的,我的建议是:

1、只在新代码中使用 letconst

2、丢弃旧代码或仔细认证

更多信息: es6中的变量和作用域

3.从IIFEs到代码块

在es5中,如果要维护本地变量,不得不使用IIFE:

  1. (function () { // open IIFE
  2. var tmp = ···;
  3. ···
  4. }()); // close IIFE
  5. console.log(tmp); // ReferenceError

而在es6中,则只需要代码块和 let 声明:

  1. { // open block
  2. let tmp = ···;
  3. ···
  4. } // close block
  5. console.log(tmp); // ReferenceError

更多信息: 避免IIFEs

4.从字符串拼接到模板常量

在es6中,对于字符串插值和多行字符串,Javscript能得到其字面值。

4.1 字符串插值

在es5中,是将结果放在字符串中进行拼接:

  1. function printCoord(x, y) {
  2. console.log('('+x+', '+y+')');
  3. }

es6中,通过字符串字面模板,可以在字符串中插入变量值:

  1. function printCoord(x, y) {
  2. console.log(`(${x}, ${y})`);
  3. }

4.2 多行字符串

模板字面量也能输出多行字符串。例如,在es5中,输出多行字符串,得这样:

  1. var HTML5_SKELETON =
  2. '<!doctype html>/n' +
  3. '<html>/n' +
  4. '<head>/n' +
  5. ' <meta charset="UTF-8">/n' +
  6. ' <title></title>/n' +
  7. '</head>/n' +
  8. '<body>/n' +
  9. '</body>/n' +
  10. '</html>/n';

如果通过反斜线转义新行,代码看起来会舒服点(但还是要显示的添加新行):

  1. var HTML5_SKELETON = '/
  2. <!doctype html>/n/
  3. <html>/n/
  4. <head>/n/
  5. <meta charset="UTF-8">/n/
  6. <title></title>/n/
  7. </head>/n/
  8. <body>/n/
  9. </body>/n/
  10. </html>';

而es6得模板字面量能跨多行:

  1. const HTML5_SKELETON = `
  2. <!doctype html>
  3. <html>
  4. <head>
  5. <meta charset="UTF-8">
  6. <title></title>
  7. </head>
  8. <body>
  9. </body>
  10. </html>`;

更多信息: 字面量模板

5.从函数表达式到箭头函数

在es5中,在函数表达式中必须小心使用 this 。在示例中,我创建了辅助变量 _this_ ,以便于我能再line B处使用:

  1. function UiComponent {
  2. var _this = this; // (A)
  3. var button = document.getElementById('myButton');
  4. button.addEventListener('click', function () {
  5. console.log('CLICK');
  6. _this.handleClick(); // (B)
  7. });
  8. }
  9. UiComponent.prototype.handleClick = function () {
  10. ···
  11. };

在es6中,可以使用箭头函数,它不会影响 this

  1. class UiComponent {
  2. constructor() {
  3. let button = document.getElementById('myButton');
  4. button.addEventListener('click', () => {
  5. console.log('CLICK');
  6. this.handleClick(); // (A)
  7. });
  8. }
  9. handleClick() {
  10. ···
  11. }
  12. }

对于只返回结果的短小回调函数,箭头函数是非常便利的。在es5中,如下的回调是相对冗长的:

  1. var arr = [1, 2, 3];
  2. var squares = arr.map(function (x) { return x * x });

在es6中,箭头函数会更简洁:

  1. let arr = [1, 2, 3];
  2. let squares = arr.map(x => x * x);

在定义参数时,如果只有一个参数,括号是可以省略的。因而,(x) => x * x and x => x * x 都是允许的。

更多信息: 箭头函数

6.处理多个返回值

一些函数或方法能通过数组或对象返回多个值。在es5中,总是需要创建中间变量来访问返回值。es6中,可以使用解构。

6.1 通过数组返回多个值

exec() 返回类数组对象的捕获组。es5中,当要访问捕获组的值时,需要一个中间变量:

  1. var matchObj =
  2. /^(/d/d/d/d)-(/d/d)-(/d/d)$/
  3. .exec('2999-12-31');
  4. var year = matchObj[1];
  5. var month = matchObj[2];
  6. var day = matchObj[3];

es6中,解构使代码更简单:

  1. let [, year, month, day] =
  2. /^(/d/d/d/d)-(/d/d)-(/d/d)$/
  3. .exec('2999-12-31');

空得逗号表示跳过数组的第一个元素。

6.2 通过对象返回多个值

Object.getOwnPropertyDescriptor() 会返回一个属性描述符对象。

es5中,仍需要一个中间变量来访问你感兴趣的对象属性值:

  1. var obj = { foo: 123 };
  2. var propDesc = Object.getOwnPropertyDescriptor(obj, 'foo');
  3. var writable = propDesc.writable;
  4. var configurable = propDesc.configurable;
  5. console.log(writable, configurable); // true true

es6,可以使用解构:

  1. let obj = { foo: 123 };
  2. let {writable, configurable} =
  3. Object.getOwnPropertyDescriptor(obj, 'foo');
  4. console.log(writable, configurable); // true true

{writable, configurable} 的值如下:

  1. { writable: writable, configurable: configurable }

更多信息: 解构

7.从for到forEach再到for-of

先说es6,一般会这样迭代数组:

  1. var arr = ['a', 'b', 'c'];
  2. for (var i=0; i<arr.length; i++) {
  3. var elem = arr[i];
  4. console.log(elem);
  5. }

也可以使用 ArrayforEach

  1. arr.forEach(function (elem) {
  2. console.log(elem);
  3. });

for 循环的优点是可以中断, forEach 的优点是简洁。

而es6的 for-of 循环则结合了两者的优点:

  1. let arr = ['a', 'b', 'c'];
  2. for (let elem of arr) {
  3. console.log(elem);
  4. }

for-of 循环也能通过数组的 entries 方法和解构返回数组的索引和对应的值:

  1. for (let [index, elem] of arr.entries()) {
  2. console.log(index+'. '+elem);
  3. }

更多信息: for-of循环

8. 参数默认值

es5中,指定参数默认值得这样:

  1. function foo(x, y) {
  2. x = x || 0;
  3. y = y || 0;
  4. ···
  5. }

es6有个更简洁的语法:

  1. function foo(x=0, y=0) {
  2. ···
  3. }

es6语法还一个优点:参数默认值只能被 undefined 触发,而在es5中,则会被任何z为 false 的值触发。

更多信息: 参数默认值

9.命名参数

在Javascript中,命名参数的普遍方式是对象字面量:

  1. selectEntries({ start: 0, end: -1 });

其等价实现:

  1. function selectEntries(options) {
  2. var start = options.start || 0;
  3. var end = options.end || -1;
  4. var step = options.step || 1;
  5. ···
  6. }

es6中,解构是语法更简单:

  1. function selectEntries({ start=0, end=-1, step=1 }) {
  2. ···
  3. }

9.1 使参数可选

es5中使参数可选的做法是这样的:

  1. function selectEntries(options) {
  2. options = options || {}; // (A)
  3. var start = options.start || 0;
  4. var end = options.end || -1;
  5. var step = options.step || 1;
  6. ···
  7. }

es6中,可以指定 {} 为参数的默认值:

  1. function selectEntries({ start=0, end=-1, step=1 } = {}) {
  2. ···
  3. }

更多信息:: 模拟命名参数

10.从arguments到参数扩展

es5中,若想让方法或函数接受任意个数的参数,就必须使用指定的 arguments 变量:

  1. function logAllArguments() {
  2. for (var i=0; i < arguments.length; i++) {
  3. console.log(arguments[i]);
  4. }
  5. }

es6中,可以使用 ... 操作达到同样地效果:

  1. function logAllArguments(...args) {
  2. for (let arg of args) {
  3. console.log(arg);
  4. }
  5. }

还有更nice的语法:

  1. function format(pattern, ...args) {
  2. ···
  3. }

而es5中的处理则相对笨拙:

  1. function format() {
  2. var pattern = arguments[0];
  3. var args = arguments.slice(1);
  4. ···
  5. }

更多信息: Rest parameters

11.从apply到散布操作符(…)

es5中, apply() 会将数组转会成参数,es6中使用散布操作符达到同样地目的。

11.1 Math.max()

es5–> apply() :

  1. > Math.max.apply(null, [-1, 5, 11, 3])
  2. 11

es6–>spread operator:

  1. > Math.max(...[-1, 5, 11, 3])
  2. 11

11.2 Array.prototype.push()

es5–> apply() :

  1. var arr1 = ['a', 'b'];
  2. var arr2 = ['c', 'd'];
  3. arr1.push.apply(arr1, arr2);
  4. // arr1 is now ['a', 'b', 'c', 'd']

es6–>spread operator:

  1. let arr1 = ['a', 'b'];
  2. let arr2 = ['c', 'd'];
  3. arr1.push(...arr2);
  4. // arr1 is now ['a', 'b', 'c', 'd']

更多信息: spread operator

从concat()到(…)

ES5 – concat():

  1. var arr1 = ['a', 'b'];
  2. var arr2 = ['c'];
  3. var arr3 = ['d', 'e'];
  4. console.log(arr1.concat(arr2, arr3));
  5. // [ 'a', 'b', 'c', 'd', 'e' ]

ES6 – spread operator:

  1. let arr1 = ['a', 'b'];
  2. let arr2 = ['c'];
  3. let arr3 = ['d', 'e'];
  4. console.log([...arr1, ...arr2, ...arr3]);
  5. // [ 'a', 'b', 'c', 'd', 'e' ]

更多信息: spread operator

13.从构造器到类

对于构造器语法,es6的类则更简便。

13.1 基本类

es5中实现一个基本类如下:

  1. function Person(name) {
  2. this.name = name;
  3. }
  4. Person.prototype.describe = function () {
  5. return 'Person called '+this.name;
  6. };

es6中,类提供了更简洁的语法:

  1. class Person {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. describe() {
  6. return 'Person called '+this.name;
  7. }
  8. }

13.2 派生类

es5实现了类的派生,下面是实现派生类的一种规范方法:

  1. function Employee(name, title) {
  2. Person.call(this, name); // super(name)
  3. this.title = title;
  4. }
  5. Employee.prototype = Object.create(Person.prototype);
  6. Employee.prototype.constructor = Employee;
  7. Employee.prototype.describe = function () {
  8. return Person.prototype.describe.call(this) // super.describe()
  9. + ' (' + this.title + ')';
  10. };

es6内置了类派生语法,要借助 extends 关键字:

  1. class Employee extends Person {
  2. constructor(name, title) {
  3. super(name);
  4. this.title = title;
  5. }
  6. describe() {
  7. return super.describe() + ' (' + this.title + ')';
  8. }
  9. }

更多信息: 类

14.从自定义error到Error派生

跟上面有点类似。es5中自定义error:

  1. function MyError() {
  2. // Use Error as a function
  3. var superInstance = Error.apply(null, arguments);
  4. copyOwnPropertiesFrom(this, superInstance);
  5. }
  6. MyError.prototype = Object.create(Error.prototype);
  7. MyError.prototype.constructor = MyError;

es6通过派生实现:

  1. class MyError extends Error {
  2. }

更多信心: Subclassing built-in constructors

15.从对象字面量的函数表达式和方法定义

语法上的差别。es5实现:

  1. var obj = {
  2. foo: function () {
  3. ···
  4. },
  5. bar: function () {
  6. this.foo();
  7. }, // trailing comma is legal in ES5
  8. }

es6:

  1. let obj = {
  2. foo() {
  3. ···
  4. },
  5. bar() {
  6. this.foo();
  7. },
  8. }

更多信息: 方法定义

16.从对象到图

es5中利用对象来实现图的数据结构,需要将对象的 prototype 指向 null ,并保证 __proto__ 上没有对应的键。

  1. var dict = Object.create(null);
  2. function countWords(word) {
  3. var escapedWord = escapeKey(word);
  4. if (escapedWord in dict) {
  5. dict[escapedWord]++;
  6. } else {
  7. dict[escapedWord] = 1;
  8. }
  9. }
  10. function escapeKey(key) {
  11. if (key.indexOf('__proto__') === 0) {
  12. return key+'%';
  13. } else {
  14. return key;
  15. }
  16. }

es6则内置了Map数据结构;

  1. let map = new Map();
  2. function countWords(word) {
  3. let count = map.get(word) || 0;
  4. map.set(word, count + 1);
  5. }

更多信息: Maps and Sets

17.从CommonJS模块到es6 模块

es5中,模块系统是基于AMD或CommocJS语法。es6内置了模块语法,但并没有得到Javascript引擎良好支持。

17.1 多个导出

在CommonJS中,可以这样实现:

  1. //------ lib.js ------
  2. var sqrt = Math.sqrt;
  3. function square(x) {
  4. return x * x;
  5. }
  6. function diag(x, y) {
  7. return sqrt(square(x) + square(y));
  8. }
  9. module.exports = {
  10. sqrt: sqrt,
  11. square: square,
  12. diag: diag,
  13. };
  14. //------ main1.js ------
  15. var square = require('lib').square;
  16. var diag = require('lib').diag;
  17. console.log(square(11)); // 121
  18. console.log(diag(4, 3)); // 5

es6的语法是酱紫的:

  1. //------ lib.js ------
  2. export const sqrt = Math.sqrt;
  3. export function square(x) {
  4. return x * x;
  5. }
  6. export function diag(x, y) {
  7. return sqrt(square(x) + square(y));
  8. }
  9. //------ main1.js ------
  10. import { square, diag } from 'lib';
  11. console.log(square(11)); // 121
  12. console.log(diag(4, 3)); // 5

或者作为一个对象导入:

  1. //------ main2.js ------
  2. import * as lib from 'lib'; // (A)
  3. console.log(lib.square(11)); // 121
  4. console.log(lib.diag(4, 3)); // 5

17.2 单个导出

Node.js继承了CommonJS的语法,能从模块导出单个值:

  1. //------ myFunc.js ------
  2. module.exports = function () { ··· };
  3. //------ main1.js ------
  4. var myFunc = require('myFunc');
  5. myFunc();

es6通过 export default 实现:

  1. //------ myFunc.js ------
  2. export default function () { ··· } // no semicolon!
  3. //------ main1.js ------
  4. import myFunc from 'myFunc';
  5. myFunc();

更多信息: Modules

相关文章: ECMAScript 6新特性介绍

本文根据@Rauschmayer的 《Getting started with ECMAScript 6》 所译,整个译文带有我自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处: http://www.2ality.com/2015/08/getting-started-es6.html

正文到此结束
Loading...