Reference:
Vue官方推荐ES2015
目录
[增强的对象文字(Enhanced Object Literals)](#增强的对象文字(Enhanced Object Literals))
[模板字符串(Template Strings)](#模板字符串(Template Strings))
[默认值+其他+扩散(Default + Rest + Spread)](#默认值+其他+扩散(Default + Rest + Spread))
[Let + Const](#Let + Const)
箭头(Arrows)
箭头是使用箭头(=>
)定义的函数,类似于JDK8中的->
定义函数的方式,如果箭头函数在另外一个函数内时,他可以访问父函数中的参数,使用tihs的时候和围绕他的代码一致,可参考下列例子,箭头函数相当于一个匿名函数。
1 | // Expression bodies |
类(Classes)
ES2015类比基于原型的OO模式简单。拥有一个方便的声明形式使类模式更易于使用,并鼓励互操作性。类支持基于原型的继承,超级调用,实例和静态方法以及构造函数。
这里需要注意的是,在类中定义的方法,都是带有作用域的普通函数,而不是箭头函数,方法内第一层所引用的this都指向当前实例,如果实例方法内包含箭头函数,则引擎就会根据包含层级把箭头函数内引用的this所指向的实际对象一直向上层搜索,直到到达一个函数作用域或块级作用域为止。如果一直搜索到达了运行环境的最上层,就会被指向undefined
1 | class SkinnedMesh extends THREE.Mesh { |
增强的对象文字(Enhanced Object Literals)
1 | var obj = { |
模板字符串(Template Strings)
当我们使用普通的字符串时,会使用单引号或双引号来包裹字符串的内容,在ES2015的模板字符串中使用反勾号`。
支持元素注入:
可以将一些元素注入到ES2015的模板字符串中。
1
2
3
4
5
6
7
8
9
10
11
12//Syntax:`before-${injectVariable}-after`
const str="hello world"
const num=1
const bool=true
const obj={foo:'bar'}
const arr=[1,2,3]
const str1=`String:${str}` //=>String:hello world
const str2=`Number:${num}` //=>Number:1
const str3=`Boolean:${bool}` //=>Boolean:true
const str4=`Object:${obj}` //=>Object:[object Object]
const str5=`Array:${arr}` //=>Array:1,2,3支持换行
1
2
3
4
5
6
7
8
9
10/**
*Syntax:`
*content
*`
*/
const sql=`
select * from Users
where FirstName='mike'
limit 5;
`多行字符串无法像普通字符串使用双引号嵌套单引号来表达字符串中的字符串,可以使用反斜杠将需要显示的反勾号转义为普通的字符。添加了`用于打印`。
1
2const str1="Here is the outer string.'This is a string in another string'"
const str2=`Here is the outer string.\`This is a string in another string\``
解构(Destructuring)
解构允许使用模式匹配进行绑定,支持数组和对象,解构是故障弱化(fail-soft)的,就像标准对象搜索foo["bar"]
一样,当没找到数据时产出undefined
1 | // list matching |
默认值+其他+扩散(Default + Rest + Spread)
被调用者设定默认值,将数组变为方法调用参数的连续输入,将后面的参数作为一个数组,Rest arguments
更直接地替换了对常见情况的需求和解决
类似于Python的写法
1 | function f(x, y=12) { |
1 | function f(x, ...y) { |
1 | function f(x, y, z) { |
Let + Const
let和const是继var之后新的变量定义方法,与let相比,const更容易被理解。const就是constant的缩写,用于定义变量,即不可变量。const定义常量的原理是阻隔变量名所对应的内存地址被改变。
let
声明的变量只在它所在的代码块有效。
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
变量与内存之间的关系由三个部分组成:变量名、内存绑定和内存(内存地址)。
ECMAScript在对变量的引用进行读取时,会从该变量对应的内存地址所指向的内存空间中读取内容。当用户改变变量的值时,引擎会重新从内存中分配一个新的内存空间以存储新的值,并将新的内存地址与变量进行绑定。const的原理便是在变量名与内存地址之间建立不可变得绑定,当后面的程序尝试申请新的内存空间时,引擎便会抛出错误。
在ES2015中,let可以说是var的进化版本,var大部分情况下可以被let替代,let和var的异同点如下表:
1 | function f() { |
Iterators + For…Of
ECMAScript引入了一种新的循环语句for…of,主要的用途是代替for…in循环语句;为Array对象引入了Array.forEach方法以代替for循环,Array.forEach方法的特点是自带闭包,以解决因为缺乏块级作用域导致需要使用取巧的方法来解决var的作用域问题。
因为块级作用域的存在,使得for循环中的每一个当前值可以仅保留在所对应的循环体中,配合for-of循环语句更是免去了回调函数的使用。
for...of
可以用来遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等
1 | let a = [1,2,3,7,8,9,10,12]; |
生成器(Generators)
类似Python中的生成器
生成器的主要功能是:通过一段程序,持续迭代或枚举出符合某个公式或算法的有序数列中的元素,这个程序便是用于实现这个公式或算法的,而不需要将目标数列完整写出。
生成器函数使用function*
表示这是一个生成器函数
1 | function* fibo(){ |
生成器函数的函数体内容将会是所生成的生成器的执行内容,在这些内容之中,yield
语句的引入使得生成器函数与普通函数有了区别。yield
语句的作用与return语句冇些相似,但并非退出函数体,而是切出当前函数的运行时(此处为一个类协程,Semi-coroutine),与此同时可以将一个值(可以是任何类型)带到主线程中。
1 | var fibonacci = { |
如上方的例子,yield
的作用是在每次执行到yield
的时候输出cur的值,相当于每次执行生成器的时候,fibonacci
的for就循环一次,到yield
的时候返回结果,下次调用的时候继续循环(不是重新开始)
生成器的接口是(仅使用TypeScript类型语法进行展示)
1 | interface Generator extends Iterator { |
编码(Unicode)
支持完整Unicode的非破坏性添加,包括字符串中的新unicode文字形式和u
处理代码点的新RegExp 模式,以及用于处理21位代码点级别的字符串的新API。这些新增功能支持在JavaScript中构建全局应用程序
1 | // same as ES5.1 |
模块(Modules)
组件定义的语言层面的支持,从流行的JavaScript模块加载器(AMD,CommonJS)中编纂成的模式,由主机定义的默认加载程序定义的运行时行为。隐式异步模型 - 在请求的模块可用和处理之前,代码不会执行。
引入模块
ES Module中有很多种引入模块的方法,最基本的便是import语句。
1
2
3
4
5import name form 'module-name'
import * as name from 'module-name'
import {member} from 'module-name'
import {meber as alias} from 'module-name'
import 'module-name'引入默认模块
1
2
3
4
5//Syntax:import namespace from 'module-name'
import http from 'http'
import url from 'url'
import fs from 'fs'引入模块部分接口
ES2015中的 模块化机制支持引入一个模块的部分接口1
2
3
4
5
6//Syntax:import {meber1,meber2} from 'module-name'
import {isEmpty} from 'lodash'
import {EventEmitter} from 'events'
console.log(isEmpty({})) //=>true从模块中局部引用的接口定义一个别名,以避免指代不明或接口重名的情况出现
1
2
3//Syntax:import {meber as alias} from 'module-name'
import {createServer as createHTTPServer} from 'http'
import {createServer as createHTTPSServer} from 'https'引入全部局部接口到指定命名空间
有的模块不会定义默认接口,只是定义了若干个命名接口,将其中的所有接口定义到一个命名空间中,使用以下语法。1
2
3
4//Syntax:import * as namespace from 'module-name'
import * as lib from 'module'
lib.method1()
lib.method2()混入引入默认接口和命名接口
同时引入默认接口和其它命名接口,可以通过混合语句来实现。1
2//Syntax:import {default as <default name>,method1} from 'module-name'
import {default as Client,utils} from 'module'注意:引入的默认接口必须要使用as语句被赋予一个别名,因为在除模块引入语句以外的地方default是一个保留关键字,所以无法使用。
1
import {default ,utils} from 'module' //Wrong
简洁的语法
1
2
3//Syntax:import <default name>,{<named modules>} from 'module-name'
import Client,{utils} from 'module'
import Client,* as lib from 'module'不引入接口,仅运行模块代码
在某些场景下,一些模块并不需要向外暴露任何接口,只需要执行内容的代码(如系统初始化)。1
2//Syntax:import 'module-name'
import 'system-apply'定义模块
ES Module中以文件名及其相对或绝对路径作为该模块被引用时的标识。
暴露模块
暴露单一接口
如果需要定义一个项目内的工具集模块,需要将其中定义的函数或者对象暴露到该文件所定义的模块上。1
2
3
4
5
6
7
8
9
10
11
12
13//Syntax:export <statement>
//module.js
export const apiRoot='http://example.com/api'
export function method(){
//...
}
export class foo{
//...
}
//app.js
import {method,foo} from 'module.js'export 语句后所跟着的语句需要具有生命部分和赋值部分
1.声明部分(Statement)为export语句提供了所暴露接口的标识;
2.赋值部分(Assignment)为export语句提供了接口的值。那些不符合这两个条件的语句无法被暴露在当前文件所定义的模块上,以下代码被视为非法代码。
1
2
3
4
5
6
7//1
export 'foo'
//2
const foo='bar'
export foo
//3
export function(){}暴露模块默认接口
在某些时候,一个模块只需要暴露一个接口,比如需要使用模块机制定义一个只含有一个单一工具类的模块时,就没有必要让这个工具类成为该模块的一部分,而是让这个类成为这个模块。1
2
3
4
5
6
7//Syntax:export default <value>
//client.js
export default class Client{
//...
}
//app.js
import Client from 'client.js'混合使用暴露接口语句
开发者可以为一个模块同时定义默认接口和其它命名接口1
2
3
4
5
6
7
8//module.js
export default class Client{
//...
}
export const foo='bar'
//app.js
import Client,{foo} from 'module'暴露一个模块的所有接口
在第三方类库的开发中,不免需要将各种不同的功能块分成若干个模块来进行开发,以便管理。ES Module可以将import语句和export组合,直接将一个模块的接口暴露到另外一个模块上。1
2
3
4
5
6
7//Syntax:export * from 'other-module'
//module-1.js
export function foo(){/*....*/}
//module.js
export * from 'module-1'
//app.js
import {foo} from 'module'暴露一个模块的部分接口
1
2
3//Syntax:export {member} from 'module-name'
export {member} from 'module'
export {default as ModuleDefault} from 'module'暴露一个模块的默认接口
可以将一个模块的默认接口作为另一个模块的默认接口。1
export {default} from 'module'
Symbol
这是一种新的原始数据类型,表示独一无二的值,Symbol的值具有互不等价的特性,开发者同时可以为Symbol值添加一个描述。
基本数据类型有6种:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)
基本语法
生成唯一的Symbol值
执行Symbol({description})函数可以生成一个与其它Symbol值互不等价的Symbol值,其中Symbol()函数可以接受一个除Symbol值以外的值作为该Symbol值的描述,以便通过开发者的辨认判断其为可选的。
1
2
3
4
5
6
7
8
9//Syntax:Symbol([description]):Symbol
const symbol=Symbol() //=>Symbol()
const symbolForSomething=Symbol('something') //=>Symbol(something)
const symbolWithNumber=Symbol(3.14) //=>Symbol(3.14)
const symbolWidthObject=Symbol({'foo':'bar'}) //=>Symbol([object Object])
//Don't use a symbol to be another symbol's description
const anotherSymbol=Symbol(symbol) //=>TypeError:Cannot convert a Symbol value to a string描述值仅仅是起到描述的作用,不会对Symbol值本身起到任何改变的作用。即便是两个具有相同描述值的Symbol值也不具有等价性。
1
2
3const symbol1=Symbol('footer')
const symbol2=Symbol('footer')
symbol1==symbol2 //=>false注意:Symbol函数并不是一个构造函数,不能使用new语句来生成Symbol“对象”,否则会抛出TypeError错误。
由此可知,Symbol是一种值类型而非引用类型。这就意味着如果将Symbol值作为函数形参进行传递,将会进行复制值传递而非引用传递,这跟其它值类型(字符串,数字等)的行为是一致的。
1
2
3
4
5
6
7
8
9
10const symbol=Symbol('hello')
function fn1(_symbol){
return _symbol==symbol
}
console.log(fn1(symbol)) //=>true
function fn2(_symbol){
_symbol=null
console.log(_symbol)
}
fn2(symbol) //=>null如果希望得到一个Symbol“对象”,可以使用Object()函数实现。
1
2
3
4const symbol=Symbol('foo')
typeof symbol //=>symbol
const symbolObj=Object(symbol)
typeof symbolObj //=>object注册全局可重用Symbol
ES2015标准除了提供具有唯一性的Symbol值以外,同样还允许开发者在当前运行时中定义一些全局有效性的Symbol。开发者可以通过一个key向当前运行时注册一个需要在其他程序中使用的Symbol。
1
2//Syntax:Symbol.for([key]):Symbol
const symbol=Symbol.for('footer')Symbol. for ()与Symbol ()的区別是,Symbol . for ()会根据传入的key在全局作用域中注册一个Symbol值,如果某一个key从未被注册到全局作用域中,便会创建一个Symbol值并根据key注册到全局环境中。如果该key己被注册,就会返冋一个与第一次使用所创建的Symbol值等价的Symbol值。
1
2
3
4
5
6
7
8const symbol = Symbol.for('foo')
const obj ={}
obj[symbol] = 'bar'
const anotherSymbol = Symbol.for('foo')
console.log(symbol === anotherSymbol) //=> true
console.log (obj [anotherSymbol]) //=> jbar这在大型系统的开发中可以用于一些全局的配罝数据中或者用于需要多处使用的数据中。
获取全局Symbol的key
既然可以通过字符串的key在全局环境中注册一个全局Symbol,那么同样也可以根据这些全局的Symbol获取到它们所对应的key。
1
2
3//Syntax:Symbol kefFor(<global symbol>):String
const symbol=Symbol.for('foobar')
console.log(Symbol.keyFor(symbol)) //=>foobar
常用Symbol值
ES2015标准定义了一些内置的常用Symbol值,这些Symbol值的应用深入到了 ECMAScript引擎运行中的各个角落。开发者可以运用这些常用Symbol值对代码的内部运行逻辑进行修改或拓展,以实现更高级的需求。
Symbol.iterator
在ES2015标准中定义了可迭代对象(Iterable Object)和新的for-of循环语句,其中可迭代对象并不是一种类型,而是带有@@iterator属性和可以被for-of循环语句所遍历的对象的统称。
Symbol.hasInstance
Symbol.haslnstance为开发者提供了可以用于扩展instanceof语句内部逻辑的权限,开发者可以将其作为属性
键,用于为一个类定义静态方法,该方法的第一个形参便是被检测的对象,而该方法的返回值便是决定了当次instanceof语句的返回结果。1
2
3
4
5
6
7class Foo (
static [Symbol.haslnstance](obj) {
console.log(obj) //=>{}
return true
}
}
console.log({} instanceof Foo) //=>trueSymbol.match
Symbol.match是正则表达式(或者对象)在作为字符串使用match ()方法时,内部运行逻辑的自定义逻辑入口。开发者可以通过Symbol.match来自行实现match ()方法的运行逻辑,比如利用strcmp (在ECMAScript中为String.prototype.localeCompare())来实现。
1
2
3
4
5
6
7
8const re = /foo/
re[Symbol.match]=function(str){
const regexp=this
console.log(str) //=>bar
//...
return true
}
'bar'.match(re) //=>trueSymbol.toPrimitive
Symbol.toPrimitive为开发者提供了更高级的控制权力,使得引用类型的对象在转换为值类型时可以进行自定义处理,无论是转换为字符串还是数字。
开发者可以使用Symbol.toPrimitive作为属性键为对象定义一个方法,这个方法接受一个参数,这个参数用于判断当前隐式转换的目标类型。
需要注意的是,这里的default并不是因为目标类型无法被转换,而是因为语法上容易造成混乱。
Symbol.toStringTag
常用Symbol的值在前面己经提到过,它的作用是可以决定这个类的实例在调用toString()方法时的标签内容。
在Object类及其所有的子类的实例中,有一个利用Symbol .toStringTag作为键的属性,该属性定义着当这个对象的toString()方法被调用时,所返回的Tag的内容是什么。
比如在开发者定义的类中,就可以通过Symbol. toStringTag来修改toString()屮的标签内容,利用它作为属性键为类型定义一个Getter。
1
2
3
4
5
6
7class Bar {}
class Foo{
get [Symbol.toStringTagl() { return 'Bar'}
}
const obj =new Foo()
console.log(obj .toString() ) //=> [object Bar]
Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
1 | var proxy = new Proxy({}, { |
Proxy
接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy
的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get
方法,用来拦截对目标对象属性的访问请求。get
方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35
,所以访问任何属性都得到35
。
注意,要使得Proxy
起作用,必须针对Proxy
实例(上例是proxy
对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
一个技巧是将 Proxy 对象,设置到object.proxy
属性,从而可以在object
对象上调用。
1 | var object = { proxy: new Proxy(target, handler) }; |
Proxy 实例也可以作为其他对象的原型对象。
1 | var proxy = new Proxy({}, { |
上面代码中,proxy
对象是obj
对象的原型,obj
对象本身并没有time
属性,所以根据原型链,会在proxy
对象上读取该属性,导致被拦截。
同一个拦截器函数,可以设置拦截多个操作。
1 | var handler = { |
对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
下面是 Proxy 支持的拦截操作一览,一共 13 种。
- **get(target, propKey, receiver)**:拦截对象属性的读取,比如
proxy.foo
和proxy['foo']
。 - **set(target, propKey, value, receiver)**:拦截对象属性的设置,比如
proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。 - **has(target, propKey)**:拦截
propKey in proxy
的操作,返回一个布尔值。 - **deleteProperty(target, propKey)**:拦截
delete proxy[propKey]
的操作,返回一个布尔值。 - **ownKeys(target)**:拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。 - **getOwnPropertyDescriptor(target, propKey)**:拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。 - **defineProperty(target, propKey, propDesc)**:拦截
Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。 - **preventExtensions(target)**:拦截
Object.preventExtensions(proxy)
,返回一个布尔值。 - **getPrototypeOf(target)**:拦截
Object.getPrototypeOf(proxy)
,返回一个对象。 - **isExtensible(target)**:拦截
Object.isExtensible(proxy)
,返回一个布尔值。 - **setPrototypeOf(target, proto)**:拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 - **apply(target, object, args)**:拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。 - **construct(target, args)**:拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。
Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise
对象。
Reflect
Reflect
对象与Proxy
对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect
对象的设计目的有这样几个。
(1) 将Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。也就是说,从Reflect
对象上可以拿到语言内部的方法。
(2) 修改某些Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。
1 | // 老写法 |
(3) 让Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
1 | // 老写法 |
(4)Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
1 | Proxy(target, { |
上面代码中,Proxy
方法拦截target
对象的属性赋值行为。它采用Reflect.set
方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。
下面是另一个例子。
1 | var loggedObj = new Proxy(obj, { |
上面代码中,每一个Proxy
对象的拦截操作(get
、delete
、has
),内部都调用对应的Reflect
方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。
有了Reflect
对象以后,很多操作会更易读。
1 | // 老写法 |
静态方法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)
上面这些方法的作用,大部分与Object
对象的同名方法的作用都是相同的,而且它与Proxy
对象的方法是一一对应的。下面是对它们的解释。