ECMAScript 6.0是JavaScript语言的2015年6月的发布版。
一.let和const命令
let:
用来声明变量,用法类似于var
,但是只在let
命令所在的代码块内有效。var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
-
const:
声明一个只读的常量。对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const
命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。const foo = {}; foo.prop = 123; foo.prop;// 123 foo = {}; // TypeError: "foo" is read-only
注:对于
let
和const
来说,变量一旦声明过就不能再重新声明;但var可以
二.变量的解构赋值
- 数组解构赋值:这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
let [x, y, ...z] = ['a']; x; // "a" y; // undefined z; // []
- 对象的解构赋值
let { foo, bar } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb
let foo; ({foo} = {foo: 1});
- 字符串解构赋值
const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o"
- 函数参数的解构赋值
function add([x, y]){ return x + y; } add([1, 2]); // 3
三.字符串的扩展
- 模板字符串(template string):用反引号(`)标识,它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量(写在
${}
之中)const tmpl = addrs => ` <table> ${addrs.map(addr => ` <tr><td>${addr.first}</td></tr> <tr><td>${addr.last}</td></tr> `).join('')} </table> `; const data = [ { first: '<Jane>', last: 'Bond' }, { first: 'Lars', last: '<Croft>' }, ]; console.log(tmpl(data)); // <table> // // <tr><td><Jane></td></tr> // <tr><td>Bond</td></tr> // // <tr><td>Lars</td></tr> // <tr><td><Croft></td></tr> // // </table>
四.数组的扩展
- Array.from():用于将类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)转为真正的数组
Array.from(arrayLike, x => x * x); // 等同于 Array.from(arrayLike).map(x => x * x); Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
五.函数的扩展
- 默认值与解构赋值的默认值结合起来使用
function fetch(url, { body = '', method = 'GET', headers = {} }) { console.log(method); } fetch('http://example.com', {}) // "GET" fetch('http://example.com') // 报错
function fetch(url, { method = 'GET' } = {}) { console.log(method); } fetch('http://example.com') // "GET"
注:函数的length属性返回没有指定默认值的参数个数
- rest参数(...变量名):rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中,rest 参数之后不能再有其他参数
function push(array, ...items) { items.forEach(function(item) { array.push(item); console.log(item); }); } var a = []; push(a, 1, 2, 3)
- 扩展运算符(...):扩展运算符好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列
console.log(...[1, 2, 3]) // 1 2 3 console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 [...document.querySelectorAll('div')] // [<div>, <div>, <div>]
- 箭头函数:如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回
var getTempItem = id => ({ id: id, name: "Temp" });
箭头函数使用说明:
(1)函数体内的this
对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new
命令,否则会抛出一个错误。
(3)不可以使用arguments
对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
(4)不可以使用yield
命令,因此箭头函数不能用作Generator函数。
- 尾调用优化
尾调用(Tail Call)指函数最后一步调用另一个函数。函数调用自身,称为递归;函数尾调用自身,称为尾递归。 递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } factorial(5) // 120
上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。
function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5, 1) // 120
下为fibonacci 递归调用:
function Fibonacci (n) { if ( n <= 1 ) {return 1}; return Fibonacci(n - 1) + Fibonacci(n - 2); } Fibonacci(10); // 89 // Fibonacci(100) // Fibonacci(500) // 堆栈溢出了
使用尾递归优化过的fibonacci 算法如下:
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) { if( n <= 1 ) {return ac2}; return Fibonacci2 (n - 1, ac2, ac1 + ac2); } Fibonacci2(100) // 573147844013817200000 Fibonacci2(1000) // 7.0330367711422765e+208 Fibonacci2(10000) // Infinity
在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。
func.arguments
:返回调用时函数的参数。func.caller
:返回调用当前函数的那个函数。
尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。在正常模式下或者那些不支持该功能的环境中,采用“循环”换掉“递归”,减少调用栈,就不会溢出。
function sum(x, y) { if (y > 0) { return sum(x + 1, y - 1); } else { return x; } } sum(1, 100000) // Uncaught RangeError: Maximum call stack size exceeded(…)
蹦床函数(trampoline)可以将上述递归执行转为循环:
function trampoline(f) { while (f && f instanceof Function) { f = f(); } return f; } function sum(x, y) { if (y > 0) { return sum.bind(null, x + 1, y - 1); } else { return x; } } trampoline(sum(1, 100000)) // 100001
蹦床函数并不是真正的尾递归优化,下面的实现才是:
function tco(f) { var value; var active = false; var accumulated = []; return function accumulator() { accumulated.push(arguments); if (!active) { active = true; while (accumulated.length) { value = f.apply(this, accumulated.shift()); } active = false; return value; } }; } var sum = tco(function(x, y) { if (y > 0) { return sum(x + 1, y - 1) } else { return x } }); sum(1, 100000) // 100001
六.对象的扩展
- 属性的简洁表示法:属性名可为变量名, 属性值可为变量的值
var foo = 'bar'; var baz = {foo}; baz // {foo: "bar"} // 等同于 var baz = {foo: foo};
-
属性名表达式
var lastWord = 'last word'; var a = { 'first word': 'hello', [lastWord]: 'world' }; a['first word'] // "hello" a[lastWord] // "world" a['last word'] // "world"
七.Set和Map数据结构
- Set:类似于数组,但是成员的值都是唯一的,没有重复的值
// 去除数组的重复成员 [...new Set(array)]
- Map:类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
var map = new Map([ ['name', '张三'], ['title', 'Author'] ]); map.size // 2 map.has('name') // true map.get('name') // "张三" map.has('title') // true map.get('title') // "Author"
八.Proxy
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
- this问题:目标对象内部的
this
关键字会指向 Proxy 代理,这时this可
绑定原始对象const target = new Date('2015-01-01'); const handler = { get(target, prop) { if (prop === 'getDate') { return target.getDate.bind(target); } return Reflect.get(target, prop); } }; const proxy = new Proxy(target, handler); proxy.getDate() // 1
九.Reflect
Reflect对象一共有13个静态方法:
Reflect.apply(target,thisArg,args) Reflect.construct(target,args) Reflect.get(target,name,receiver) Reflect.set(target,name,value,receiver) Reflect.defineProperty(target,name,desc) Reflect.deleteProperty(target,name) Reflect.has(target,name) Reflect.ownKeys(target) Reflect.isExtensible(target) Reflect.preventExtensions(target) Reflect.getOwnPropertyDescriptor(target, name) Reflect.getPrototypeOf(target) Reflect.setPrototypeOf(target, prototype)
-
Reflect.get(target, name, receiver)
var myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; }, }; var myReceiverObject = { foo: 4, bar: 4, }; Reflect.get(myObject, 'baz', myReceiverObject) // 8
- 使用-Proxy-实现观察者模式
const person = observable({ name: '张三', age: 20 }); function print() { console.log(`${person.name}, ${person.age}`) } observe(print); person.name = '李四'; // 输出 // 李四, 20 const queuedObservers = new Set(); const observe = fn => queuedObservers.add(fn); const observable = obj => new Proxy(obj, {set}); function set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); queuedObservers.forEach(observer => observer()); return result; }
十.Promise
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理强大。
let promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve(); }); promise.then(function() { console.log('Resolved.'); }); console.log('Hi!'); // Promise // Hi! // Resolved
十一.Iterator和for...of循环
Iterator的作用有三个:
- 为各种数据结构,提供一个统一的、简便的访问接口
- 使得数据结构的成员能够按某种次序排列
- 创造了一种新的遍历命令
for...of
循环,Iterator接口主要供for...of
消费
const arr = ['red', 'green', 'blue']; for(let v of arr) { console.log(v); // red green blue } const obj = {}; obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr); for(let v of obj) { console.log(v); // red green blue }
十二.Generator
形式上,Generator 函数是一个普通函数,但是有两个特征:
function
关键字与函数名之间有一个星号- 函数体内部使用
yield
语句,定义不同的内部状态
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3]
-
Generator函数的this
function* g() { this.a = 11; } g.prototype.hello = function () { return 'hi!'; }; let obj = g(); obj instanceof g // true obj.hello() // 'hi!' obj.a // undefined
-
Generator 函数:可以暂停执行和恢复执行,可以作为异步编程的完整解决方案——函数体内外的数据交换和错误处理机制
function* gen(x){ try { var y = yield x + 2; } catch (e){ console.log(e); } return y; } var g = gen(1); g.next(); g.throw('出错了');
-
JavaScript 语言的 Thunk 函数:Thunk 函数将多参数函数替换成一个只接受回调函数作为参数的单参数函数
// 正常版本的readFile(多参数版本) fs.readFile(fileName, callback); // Thunk版本的readFile(单参数版本) var Thunk = function (fileName) { return function (callback) { return fs.readFile(fileName, callback); }; }; var readFileThunk = Thunk(fileName); readFileThunk(callback);
十三.async函数
async 函数是Generator 函数的语法糖,返回一个 Promise 对象,可以使用then
方法添加回调函数。 async 函数return
语句返回值或抛出异常,Promise 对象状态发生变化,then和catch
方法回调函数捕获处理。正常情况下,await
命令后面是一个 Promise 对象,如果不是,会被转成一个立即resolve
的 Promise 对象。
async function f() { await Promise.reject('出错了'); } f() .then(v => console.log(v)) .catch(e => console.log(e)) // 出错了
十四.Class
基本上,ES6的class
可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 调用父类的toString() } }
-
原生构造函数无法继承
ECMAScript的原生构造函数大致有下面这些: Boolean(),Number(),String(),Array(),Date(),Function(),RegExp(),Error(),Object()
- Class的取值函数(getter)和存值函数(setter)
class MyClass { constructor() { // ... } get prop() { return 'getter'; } set prop(value) { console.log('setter: '+value); } } let inst = new MyClass(); inst.prop = 123; // setter: 123 inst.prop // 'getter'
- Class 的 Generator 方法:方法之前加上星号(
*
),就表示该方法是一个 Generator 函数class Foo { constructor(...args) { this.args = args; } * [Symbol.iterator]() { for (let arg of this.args) { yield arg; } } } for (let x of new Foo('hello', 'world')) { console.log(x); } // hello // world
- Mixin模式的实现
function mix(...mixins) { class Mix {} for (let mixin of mixins) { copyProperties(Mix, mixin); copyProperties(Mix.prototype, mixin.prototype); } return Mix; } function copyProperties(target, source) { for (let key of Reflect.ownKeys(source)) { if ( key !== "constructor" && key !== "prototype" && key !== "name" ) { let desc = Object.getOwnPropertyDescriptor(source, key); Object.defineProperty(target, key, desc); } } }
十五.Module
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict"
。
严格模式主要有以下限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with
语句 - 不能对只读属性赋值,否则报错
- 不能使用前缀0表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
delete prop
,会报错,只能删除属性delete global[prop]
eval
不会在它的外层作用域引入变量eval
和arguments
不能被重新赋值arguments
不会自动反映函数参数的变化- 不能使用
arguments.callee
- 不能使用
arguments.caller
- 禁止
this
指向全局对象 - 不能使用
fn.caller
和fn.arguments
获取函数调用的堆栈 - 增加了保留字(比如
protected
、static
和interface
)
-
export 命令和import命令:export命令定义了模块的对外接口,通过import命令加载模块
// circle.js export function area(radius) { return Math.PI * radius * radius; } export function circumference(radius) { return 2 * Math.PI * radius; } // main.js import * as circle from './circle'; console.log('圆面积:' + circle.area(4)); console.log('圆周长:' + circle.circumference(14));
- 模块的整体加载:星号(
*
)指定一个对象,所有输出值都加载在这个对象上面import * as circle from './circle';
-
export default 命令:为模块指定默认输出
// export-default.js export default function () { console.log('foo'); } // import-default.js import customName from './export-default'; customName(); // 'foo'
-
模块的继承
// circleplus.js export * from 'circle'; export var e = 2.71828182846; export default function(x) { return Math.exp(x); }
-
浏览器的模块加载
<script type="module" src="foo.js"></script>
浏览器对于带有
type="module"
的<script>
,都是异步加载外部脚本,不会造成堵塞浏览器。对于外部的模块脚本(上例是foo.js
),有几点需要注意:- 该脚本自动采用严格模块。
- 该脚本内部的顶层变量,都只在该脚本内部有效,外部不可见。
- 该脚本内部的顶层的
this
关键字,返回undefined
,而不是指向window
。
- 循环加载:ES6处理“循环加载”与CommonJS有本质的不同,是动态引用,如果使用
import
从一个模块加载变量(即import foo from 'foo'
),那些变量不会被缓存,而是成为一个指向被加载模块的引用// a.js // 这一行建立一个引用, // 从`b.js`引用`bar` import {bar} from './b.js'; export function foo() { // 执行时第一行输出 foo console.log('foo'); // 到 b.js 执行 bar bar(); console.log('执行完毕'); } foo(); // b.js // 建立`a.js`的`foo`引用 import {foo} from './a.js'; export function bar() { // 执行时,第二行输出 bar console.log('bar'); // 递归执行 foo,一旦随机数 // 小于等于0.5,就停止执行 if (Math.random() > 0.5) { foo(); } }
-
跨模块常量:
const
声明的常量只在当前代码块有效,如果想设置跨模块的常量(即跨多个文件),可以采用下面的写法// constants.js 模块 export const A = 1; export const B = 3; export const C = 4; // test1.js 模块 import * as constants from './constants'; console.log(constants.A); // 1 console.log(constants.B); // 3 // test2.js 模块 import {A, B} from './constants'; console.log(A); // 1 console.log(B); // 3
十六.SIMD(Single Instruction/Multiple Data):单指令/多数据
SIMD 提供12种数据类型,总长度都是128个二进制位。
- Float32x4:四个32位浮点数
- Float64x2:两个64位浮点数
- Int32x4:四个32位整数
- Int16x8:八个16位整数
- Int8x16:十六个8位整数
- Uint32x4:四个无符号的32位整数
- Uint16x8:八个无符号的16位整数
- Uint8x16:十六个无符号的8位整数
- Bool32x4:四个32位布尔值
- Bool16x8:八个16位布尔值
- Bool8x16:十六个8位布尔值
- Bool64x2:两个64位布尔值
每种数据类型被x
符号分隔成两部分,后面的部分表示通道数,前面的部分表示每个通道的宽度和类型。比如,Float32x4
就表示这个值有4个通道,每个通道是一个32位浮点数。
var a = SIMD.Float32x4(1, 2, 3, 4); var b = SIMD.Float32x4(5, 6, 7, 8); var c = SIMD.Float32x4.add(a, b); // Float32x4[6, 8, 10, 12]
参考:http://es6.ruanyifeng.com/ (由于原作内容太多,为精简,也为实际开发备料,以及日后再次查阅之用)