这里有三种简单地方式用于ES6编程:
Web浏览器:使用 Babel REPL ,可以将ES6编译成ES5的平台,并且并不需要安装。
命令行:使用 babel-node
,可以执行ES6的Node.js版本(会在内部编译es5)。需要通过 npm
安装。
各种js引擎:根据 ES6语法兼容表 ,找出被支持的ES6功能。
对于第一点和第二点,这有更多细节。
1.1 Babel REPL
Babel REPL主要有四个部分:
1、左上角包含es6源代码
2、左下角可以查看es6中的语法错误
3、右上角是es6被编译成es5的源代码
4、右下角是通过 console.log()
的输出结果
1.2 babel-node
babel-node可以通过 npm
安装:
$ npm install --global babel
跟node一样,可以命令开启REPL交互:
$ babel-node
一旦开启REPL,就可以去执行ES6代码:
> let arr = [1, 2, 3]; > arr.map(x => x * x) [ 1, 4, 9 ]
注意: babel-node目前不支持多行输入
Babel官网上有管多关于 Babel CLI 的工具。
es6有两种新的方式来声明变量:
1、 let
用于声明块级作用于变量
2、 const
用于声明常量,其值不能被改变。
let
或 const
可以用来替代 var
声明变量,但是不能盲目使用,因为不同的作用域会改变代码的行为。例如:
var x = 3; function func(randomize) { if (randomize) { var x = Math.random(); // (A) scope: whole function return x; } return x; // accesses the x from line A } func(false); // undefined
func()
会意外地返回 undefined
。你可以重写这部分代码,就知道为什么返回 undefined
了:
var x = 3; function func(randomize) { var x; if (randomize) { x = Math.random(); return x; } return x; } func(false); // undefined
如果用 let
代替之前的 var
,就会得到不同的结果:
let x = 3; function func(randomize) { if (randomize) { let x = Math.random(); return x; } return x; } func(false); // 3
因此,盲目地用 let
或 const
代替 var
是有风险的,我的建议是:
let
或 const
2、丢弃旧代码或仔细认证
更多信息: es6中的变量和作用域
在es5中,如果要维护本地变量,不得不使用IIFE:
(function () { // open IIFE var tmp = ···; ··· }()); // close IIFE console.log(tmp); // ReferenceError
而在es6中,则只需要代码块和 let
声明:
{ // open block let tmp = ···; ··· } // close block console.log(tmp); // ReferenceError
更多信息: 避免IIFEs
在es6中,对于字符串插值和多行字符串,Javscript能得到其字面值。
4.1 字符串插值
在es5中,是将结果放在字符串中进行拼接:
function printCoord(x, y) { console.log('('+x+', '+y+')'); }
es6中,通过字符串字面模板,可以在字符串中插入变量值:
function printCoord(x, y) { console.log(`(${x}, ${y})`); }
4.2 多行字符串
模板字面量也能输出多行字符串。例如,在es5中,输出多行字符串,得这样:
var HTML5_SKELETON = '<!doctype html>/n' + '<html>/n' + '<head>/n' + ' <meta charset="UTF-8">/n' + ' <title></title>/n' + '</head>/n' + '<body>/n' + '</body>/n' + '</html>/n';
如果通过反斜线转义新行,代码看起来会舒服点(但还是要显示的添加新行):
var HTML5_SKELETON = '/ <!doctype html>/n/ <html>/n/ <head>/n/ <meta charset="UTF-8">/n/ <title></title>/n/ </head>/n/ <body>/n/ </body>/n/ </html>';
而es6得模板字面量能跨多行:
const HTML5_SKELETON = ` <!doctype html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> </body> </html>`;
更多信息: 字面量模板
在es5中,在函数表达式中必须小心使用 this
。在示例中,我创建了辅助变量 _this_
,以便于我能再line B处使用:
function UiComponent { var _this = this; // (A) var button = document.getElementById('myButton'); button.addEventListener('click', function () { console.log('CLICK'); _this.handleClick(); // (B) }); } UiComponent.prototype.handleClick = function () { ··· };
在es6中,可以使用箭头函数,它不会影响 this
:
class UiComponent { constructor() { let button = document.getElementById('myButton'); button.addEventListener('click', () => { console.log('CLICK'); this.handleClick(); // (A) }); } handleClick() { ··· } }
对于只返回结果的短小回调函数,箭头函数是非常便利的。在es5中,如下的回调是相对冗长的:
var arr = [1, 2, 3]; var squares = arr.map(function (x) { return x * x });
在es6中,箭头函数会更简洁:
let arr = [1, 2, 3]; let squares = arr.map(x => x * x);
在定义参数时,如果只有一个参数,括号是可以省略的。因而,(x) => x x and x => x x 都是允许的。
更多信息: 箭头函数
一些函数或方法能通过数组或对象返回多个值。在es5中,总是需要创建中间变量来访问返回值。es6中,可以使用解构。
exec()
返回类数组对象的捕获组。es5中,当要访问捕获组的值时,需要一个中间变量:
var matchObj = /^(/d/d/d/d)-(/d/d)-(/d/d)$/ .exec('2999-12-31'); var year = matchObj[1]; var month = matchObj[2]; var day = matchObj[3];
es6中,解构使代码更简单:
let [, year, month, day] = /^(/d/d/d/d)-(/d/d)-(/d/d)$/ .exec('2999-12-31');
空得逗号表示跳过数组的第一个元素。
Object.getOwnPropertyDescriptor()
会返回一个属性描述符对象。
es5中,仍需要一个中间变量来访问你感兴趣的对象属性值:
var obj = { foo: 123 }; var propDesc = Object.getOwnPropertyDescriptor(obj, 'foo'); var writable = propDesc.writable; var configurable = propDesc.configurable; console.log(writable, configurable); // true true
es6,可以使用解构:
let obj = { foo: 123 }; let {writable, configurable} = Object.getOwnPropertyDescriptor(obj, 'foo'); console.log(writable, configurable); // true true
{writable, configurable}
的值如下:
{ writable: writable, configurable: configurable }
更多信息: 解构
先说es6,一般会这样迭代数组:
var arr = ['a', 'b', 'c']; for (var i=0; i<arr.length; i++) { var elem = arr[i]; console.log(elem); }
也可以使用 Array
的 forEach
:
arr.forEach(function (elem) { console.log(elem); });
for
循环的优点是可以中断, forEach
的优点是简洁。
而es6的 for-of
循环则结合了两者的优点:
let arr = ['a', 'b', 'c']; for (let elem of arr) { console.log(elem); }
for-of
循环也能通过数组的 entries
方法和解构返回数组的索引和对应的值:
for (let [index, elem] of arr.entries()) { console.log(index+'. '+elem); }
更多信息: for-of循环
es5中,指定参数默认值得这样:
function foo(x, y) { x = x || 0; y = y || 0; ··· }
es6有个更简洁的语法:
function foo(x=0, y=0) { ··· }
es6语法还一个优点:参数默认值只能被 undefined
触发,而在es5中,则会被任何z为 false
的值触发。
更多信息: 参数默认值
在Javascript中,命名参数的普遍方式是对象字面量:
selectEntries({ start: 0, end: -1 });
其等价实现:
function selectEntries(options) { var start = options.start || 0; var end = options.end || -1; var step = options.step || 1; ··· }
es6中,解构是语法更简单:
function selectEntries({ start=0, end=-1, step=1 }) { ··· }
9.1 使参数可选
es5中使参数可选的做法是这样的:
function selectEntries(options) { options = options || {}; // (A) var start = options.start || 0; var end = options.end || -1; var step = options.step || 1; ··· }
es6中,可以指定 {}
为参数的默认值:
function selectEntries({ start=0, end=-1, step=1 } = {}) { ··· }
更多信息:: 模拟命名参数
es5中,若想让方法或函数接受任意个数的参数,就必须使用指定的 arguments
变量:
function logAllArguments() { for (var i=0; i < arguments.length; i++) { console.log(arguments[i]); } }
es6中,可以使用 ...
操作达到同样地效果:
function logAllArguments(...args) { for (let arg of args) { console.log(arg); } }
还有更nice的语法:
function format(pattern, ...args) { ··· }
而es5中的处理则相对笨拙:
function format() { var pattern = arguments[0]; var args = arguments.slice(1); ··· }
更多信息: Rest parameters
es5中, apply()
会将数组转会成参数,es6中使用散布操作符达到同样地目的。
11.1 Math.max()
es5--> apply()
:
> Math.max.apply(null, [-1, 5, 11, 3]) 11
es6-->spread operator:
> Math.max(...[-1, 5, 11, 3]) 11
11.2 Array.prototype.push()
es5--> apply()
:
var arr1 = ['a', 'b']; var arr2 = ['c', 'd']; arr1.push.apply(arr1, arr2); // arr1 is now ['a', 'b', 'c', 'd']
es6-->spread operator:
let arr1 = ['a', 'b']; let arr2 = ['c', 'd']; arr1.push(...arr2); // arr1 is now ['a', 'b', 'c', 'd']
更多信息: spread operator
ES5 – concat():
var arr1 = ['a', 'b']; var arr2 = ['c']; var arr3 = ['d', 'e']; console.log(arr1.concat(arr2, arr3)); // [ 'a', 'b', 'c', 'd', 'e' ]
ES6 – spread operator:
let arr1 = ['a', 'b']; let arr2 = ['c']; let arr3 = ['d', 'e']; console.log([...arr1, ...arr2, ...arr3]); // [ 'a', 'b', 'c', 'd', 'e' ]
更多信息: spread operator
对于构造器语法,es6的类则更简便。
es5中实现一个基本类如下:
function Person(name) { this.name = name; } Person.prototype.describe = function () { return 'Person called '+this.name; };
es6中,类提供了更简洁的语法:
class Person { constructor(name) { this.name = name; } describe() { return 'Person called '+this.name; } }
es5实现了类的派生,下面是实现派生类的一种规范方法:
function Employee(name, title) { Person.call(this, name); // super(name) this.title = title; } Employee.prototype = Object.create(Person.prototype); Employee.prototype.constructor = Employee; Employee.prototype.describe = function () { return Person.prototype.describe.call(this) // super.describe() + ' (' + this.title + ')'; };
es6内置了类派生语法,要借助 extends
关键字:
class Employee extends Person { constructor(name, title) { super(name); this.title = title; } describe() { return super.describe() + ' (' + this.title + ')'; } }
更多信息: 类
跟上面有点类似。es5中自定义error:
function MyError() { // Use Error as a function var superInstance = Error.apply(null, arguments); copyOwnPropertiesFrom(this, superInstance); } MyError.prototype = Object.create(Error.prototype); MyError.prototype.constructor = MyError;
es6通过派生实现:
class MyError extends Error { }
更多信心: Subclassing built-in constructors
语法上的差别。es5实现:
var obj = { foo: function () { ··· }, bar: function () { this.foo(); }, // trailing comma is legal in ES5 }
es6:
let obj = { foo() { ··· }, bar() { this.foo(); }, }
更多信息: 方法定义
es5中利用对象来实现图的数据结构,需要将对象的 prototype
指向 null
,并保证 __proto__
上没有对应的键。
var dict = Object.create(null); function countWords(word) { var escapedWord = escapeKey(word); if (escapedWord in dict) { dict[escapedWord]++; } else { dict[escapedWord] = 1; } } function escapeKey(key) { if (key.indexOf('__proto__') === 0) { return key+'%'; } else { return key; } }
es6则内置了Map数据结构;
let map = new Map(); function countWords(word) { let count = map.get(word) || 0; map.set(word, count + 1); }
更多信息: Maps and Sets
es5中,模块系统是基于AMD或CommocJS语法。es6内置了模块语法,但并没有得到Javascript引擎良好支持。
17.1 多个导出
在CommonJS中,可以这样实现:
//------ lib.js ------ var sqrt = Math.sqrt; function square(x) { return x * x; } function diag(x, y) { return sqrt(square(x) + square(y)); } module.exports = { sqrt: sqrt, square: square, diag: diag, }; //------ main1.js ------ var square = require('lib').square; var diag = require('lib').diag; console.log(square(11)); // 121 console.log(diag(4, 3)); // 5
es6的语法是酱紫的:
//------ lib.js ------ export const sqrt = Math.sqrt; export function square(x) { return x * x; } export function diag(x, y) { return sqrt(square(x) + square(y)); } //------ main1.js ------ import { square, diag } from 'lib'; console.log(square(11)); // 121 console.log(diag(4, 3)); // 5
或者作为一个对象导入:
//------ main2.js ------ import * as lib from 'lib'; // (A) console.log(lib.square(11)); // 121 console.log(lib.diag(4, 3)); // 5
17.2 单个导出
Node.js继承了CommonJS的语法,能从模块导出单个值:
//------ myFunc.js ------ module.exports = function () { ··· }; //------ main1.js ------ var myFunc = require('myFunc'); myFunc();
es6通过 export default
实现:
//------ myFunc.js ------ export default function () { ··· } // no semicolon! //------ main1.js ------ import myFunc from 'myFunc'; myFunc();
更多信息: Modules
相关文章: ECMAScript 6新特性介绍
译文出处: Getting started with ECMAScript 6