ECMAScript 6(如下简称ES6)是JavaScript语言的下一代标准。由于当前版本的ES6是在2015年发布的,因此又称ECMAScript 2015。也就是说,ES6就是ES2015。javascript
说明:此文章根据《实战ES2015:深刻现代JavaScript+应用开发》这本书作的笔记,更多详细内容请查看书籍。电子版在文章底部。php
ECMAScript的发展速度在不断加快,影响范围愈来愈大,除了Web前端开发之外,借助着Node.js的力量在服务器、桌面端甚至硬件设备等领域中也发光发热着。前端
ES2015标注提供了许多新的语法和编程特性以提升ECMAScript的开发效率并优化ECMAScript的开发体验。
ES2015的别名:Harmony(和谐)java
ECMAScript带来了可用性很是高的语法糖,这些语法糖的开发初衷是方便开发者使用,使用语法糖可以增长程序的可读性,从而减小程序代码出错的概率。
如ES2015中很是重要的箭头函数,大大地加强了ECMAScript在复杂业务逻辑中的处理能力。python
使用ES2015前:正则表达式
el.on('click',function(evt) { var self = this; fecch('/api').then(function (res) { return res.json(); }).then(function (result) { self.something(result); //... }) })
使用ES2015后:算法
el.on('click',evt=>{ fetch('/api').then(res=>res.json()).then(result=>this.something(result)) })
在程序代码能够经过模块化进行解耦后,组件化开发便能借此进一步推动项目程序的工程化进度。组件化开发是模块化开发的高级体现,组件化更能表现出模块化开发的意义和重要性。
组件化开发所重视的是组件之间的非耦合关系和组件的可重用性,组件之间也能够存在依赖性,能够利用模块化来实现组件化开发。同一类内容块能够抽象化为一个组件,并在生产中重复使用。sql
const为ECMAScript带来了定义常量的能力,let为ECMAScript修复了从前var由于代码习惯不佳而致使的代码做用域混乱等问题,同时实现了块状做用域。
const能够实现变量名与内存地址的强绑定,让变量不会由于除了定义语句和删除语句之外的代码而丢失内存地址的绑定,从而保证了变量与内存之间的安全性。编程
总结:语法糖、模块化、组件化等工程优点,能够在实际开发中提高开发效率和代码质量。json
let和const是继var以后新的变量定义方法,与let相比,const更容易被理解。const就是constant的缩写,用于定义变量,即不可变量。const定义常量的原理是阻隔变量名所对应的内存地址被改变。
变量与内存之间的关系由三个部分组成:变量名、内存绑定和内存(内存地址)。
ECMAScript在对变量的引用进行读取时,会从该变量对应的内存地址所指向的内存空间中读取内容。当用户改变变量的值时,引擎会从新从内存中分配一个新的内存空间以存储新的值,并将新的内存地址与变量进行绑定。const的原理即是在变量名与内存地址之间创建不可变得绑定,当后面的程序尝试申请新的内存空间时,引擎便会抛出错误。
在ES2015中,let能够说是var的进化版本,var大部分状况下能够被let替代,let和var的异同点以下表:
在ECMAScript中,一个变量(或常量)的生命周期(Life Cycle)模式是固定的,由两种因素决定,分别是做用域和对其的引用。
(1)通常状况下,使用const来定义值的存储容器(常量);
(2)只有在值容器明确地被肯定将会被改变时才使用let来定义(变量);
(3)再也不使用var。
ECMAScript引入了一种新的循环语句for...of,主要的用途是代替for...in循环语句;为Array对象引入了Array.forEach方法以代替for循环,Array.forEach方法的特色是自带闭包,以解决由于缺少块级做用域致使须要使用取巧的方法来解决var的做用域问题。
由于块级做用域的存在,使得for循环中的每个当前值能够仅保留在所对应的循环体中,配合for-of循环语句更是免去了回调函数的使用。
const arr=[1,2,3]; for(const item of arr){ console.log(item); }
配合ES2015中的解构(Destructuring)特性,在处理JSON数据时,更加驾轻就熟。
const Zootopia=[ {name:'Nick',gender:1,species:'Fox'}, {name:'Judy',gender:0,species:'Bunny'} ]; for(const {name,species} of Zootopia){ console.log(`hi,I am ${name},and I am a ${species}`); }
forEach方法须要传入一个回调函数来接收循环的每个循环元素并做为循环体以执行。同时,这个回调函数在标准定义中会被传入三个参数,分别为:当前值,当前值的下标和循环数组自身。在ES2015标准中,数组类型再次被赋予了一个名为entries的方法,它能够返回对应的数组中每个元素与其下标配对的一个新数组。
这个新特性能够与解构和for-of循环配合使用。
const Zootopia=[ {name:'Nick',gender:1,species:'Fox'}, {name:'Judy',gender:0,species:'Bunny'} ]; for(const [index,{name,species}] of Zootopia.entries){ console.log(`${index}.Hi,I am ${name},and I am a ${species}`); } //0.Hi,I am Nick,and I am a Fox //1.Hi,I am Judy,and I am a Bunny
箭头函数,顾名思义即是使用箭头(=>)进行定义的函数,属于匿名函数(Anonymous Function)一类。
相对于传统的function语句,箭头函数在简单函数使用中更为简洁直观。
const arr=[1,2,3]; //箭头函数 const squares=arr.map(x=>x*x); //传统语法 const squares=arr.map(function(x){return x*x});
箭头函数有四种使用语法
(1)单一参数的单行箭头函数
//Syntax:arg=>statement const fn=foo=>`${foo} world` //means return `foo +' world'`
这是箭头函数最简洁的形式,常见于用做简单的处理函数,如过滤。
let array=['a','bc','def','ghij']; array=array.filter(item=>item.length>=2); //bc,def,ghij
(2)多参数的单行箭头函数
//Syntax:(arg1,arg2)=>statement const fn=(foo,bar)=>foo+bar
多参数的语法跟普通函数同样,以括号来包裹参数列,这种形式常见于数组的处理,如排序。
let array=['a','bc','def','ghij']; array=array.sort((a,b)=>a.length<b.length); //ghij,def,bc,a
(3)多行箭头函数
//Syntax:arg=>{...} //单一参数 foo=>{return `${foo} world`}
//Syntax:(arg1,arg2)=>{...} //多参数 (foo+bar)=>{return foo+bar}
(4)无参数箭头函数
若是一个箭头函数无参数传入,须要用一对空的括号来表示空的参数列表。
//Syntax:()=>statement const greet=()=>'hello world'
当咱们使用普通的字符串时,会使用单引号或双引号来包裹字符串的内容,在ES2015的模板字符串中使用反勾号`。
//Syntax:`string...` const str=`something`
(1)支持元素注入:
能够将一些元素注入到ES2015的模板字符串中。
//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
(2)支持换行:
/**
*Syntax:`
*content
*`
*/
const sql=`
select * from Users
where FirstName='mike' limit 5; `
多行字符串没法像普通字符串使用双引号嵌套单引号来表达字符串中的字符串,可使用反斜杠将须要显示的反勾号转义为普通的字符。添加了\`用于打印`。
const 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\``
在ES2015以前的ECMAScript的标准中,对象字面量只是一种用于表达对象的语法,只具备表达的功能,并不起到更大的做用。在ES2015中,为ECMASCript开发者开放了更多关于对象的操做权限,其中便有更多的对象字面量语法。
(1)函数类属性的省略语法:
ES2015中引入了类机制(Class),普通的对象字面量也吸取了一些语法糖,可让方法属性省略function,以一种直观的语法来表达。
//Syntax:{method(){...}} const obj={ //before foo:function(){ return 'foo' }, //after bar(){ return 'bar' } }
有了这个语法糖,对象字面量中的方法类属性更像是一个方法,而不仅是一个以函数为值得属性。
(2)支持_proto_注入:
在ES2015中开放了向对象字面量注入_proto_的功能,这样作的意义在于开发者能够获得更高的操做权限,从而更加灵活地建立和操做对象。
在ES2015标准中,开发者容许直接向一个对象字面量注入_proto_,使其直接成为指定类的一个实例,无须另外建立一个类来实现继承。
//Syntax:{_proto_:...} import {EventEmitter} from 'events' const machine={ _proto_:new EventEmitter(), method(){...} } console.log(machine) //=>EventEmitter{} console.log(machine instanceof EventEmitter) //=>true
(3)可动态计算的属性名:
在ES2015标准对于对象字面量的处理中,引入了一个新语法,这个语法容许咱们直接使用一个表达式来表达一个属性名。
//Syntax:{[statement]:value} const prefix='es2015' const obj={ [prefix+'enhancedObject']:'foobar' }
(4)将属性名定义省略:
在某些场景中,须要将一些已经别定义的变量(或常量)做为其它对象字面量的属性值进行返回或传入操做。
//Syntax:{injectVariable} const foo=123 const bar =()=>foo const obj={ foo, bar } console.log(obj) //=>{foo:123,bar:[Function:bar]}
在ES2015以前工程师们通常使用对象字面量和数组来模拟函数多返回值,在ES2015中一样可使用相似的语法来实现函数多返回值,且语法上更加简洁。
(1)使用对象做为返回载体(带有标签的多返回值)
//Syntax:{arg1,arg2}={arg1:value1,arg2:value2} function getState(){ return { error:null, logined:true, user:{}, } } const {error,logined,user}=getState()
(2)使用数组做为返回载体
使用数组做为返回载体与使用对象做为返回载体的区别是:数组须要让被赋予的变量(或常量)名按照数组的顺序得到值。
//Syntax:[arg1,arg2]=[value1,value2] const[foo,bar]=[1,2] console.log(foo,bar) //=>1 2
跳过数组中某些元素,经过空开一个元素的方式来实现。
//Syntax:[arg1, ,bar]=[1,2,3] console.log(foo,bar) //=>1 3
不定项的获取后续元素,用...语句实现。
//Syntax:[arg1,arg2,...restArgs]=[value1,value2,value3,value4] const [a,b,...rest]=[1,2,3,4,5] console.log(a,b) //=>1 2 console.log(rest) //=>[3,4,5]
(3)使用场景
function fetchData(){ return new Promise((resolve,reject)=>{ resolve(['foo','bar']) }) } fetchData().then(([value1,value2])=>{ console.log(value1,value2) //=>foo bar }) fetchData().then([value1,value2]=>{ //=>SyntaxError //... })
若是参数过多但在某些场景下并不须要所有参数,或者文档约定不完善的状况下,使用对象做为传递载体更佳。
function fetchData(){ return new Promise((resolve,reject)=>{ resolve({ code:200, message:ok, data:['foo','bar'] }) }) } fetchData().then(({data})=>{ console.log(data) //=>foo bar ... })
function swap(a,b){ var tmp=a a=b b=tmp } let foo=1 let bar=2 //Before Swap console.log(foo,bar) //=>1 2 //Swap [foo,bar]=[bar,foo] //After Swap console.log(foo,bar) //=>2 1
(4)高级用法
function fetchData(){ return{ response:['foo','bar'] } } const{response:data}=fetchData() console.log(data) //=>foo bar
//Object const {foo,bar}={foo:1} console.log(foo,bar) //=>1 undefined //Array const [a,b,c]=[1,2] console.log(a,b,c) //=>1 2 undefined
若是不但愿获得undefined,能够为参数赋予一个默认值,当没法匹配到相应的值时,会使用该默认值。
const {foo=1}={bar:1} console.log(foo) //=>1 const [a,b=2]=[1] console.log(a,b) //=>1 2
//Object in Object const {a,b:{c}}={a:1,b:{c:2}} console.log(a,c) //=>1 2 //Array in Object const {d,e:[f]}={d:1,e:[2,3]} console.log(d,f) //=>1 2 //Object in Array consot [g,{h}]=[1,{h:2}] console.log(g,h) //=>1 2 //Array in Array const [i,[j]]=[1,[2,3]] console.log(i,j) //=>1 2
(1)默认参数值
使用语法:
ES2015中使用语法直接实现默认参数值语法显得更加简洁而直观。
//Syntax:function name(arg=defaultValue){...} function fn(arg='foo'){ console.log(arg) } fn() //=>foo fn('bar') //=>bar
使用场景:
同时提供回调函数和Promise返回方式的接口
const noop=()=>{} function api(callback=noop){ return new Promise((resolve,reject)=>{ const value='footbar' resolve(value) callback(null,value) }) } //Callback api((err,data)=>{ if(err) return console.error(err) }) //Promise api().then(value=>{ //... }) .catch(err=>console.error(err))
函数的默认参数特性用在某一个对象的方法中,所指定的默认参数还能够被定为该对象的某一个属性
const obj={ msg:'World', greet(message=this.msg){ console.log(`Hello ${message}`) } } obj.greet() //=>Hello World obj.greet('ES2015') //=>Hello ES2015
(2)剩余参数
使用语法
ES2015中对剩余参数有了更为优雅和标准的语法,直接将须要获取的参数列表转换为一个正常数组,以便使用。
//Syntax:function fn([arg,]...restArgs){} function fn(foo, ...rest){ console.log(`foo:${foo}`) console.log(`Rest Arguments:${rest.join(',')}`) } fn(1,2,3,4,5) //=>foo:1 //Rest Arguments:2,3,4,5
使用场景
十分经常使用的merge和mixin函数(合并对象)就会须要使用到剩余函数这个特性来实线。
function merge(target={},...objs){ for(const obj of objs){ const keys=Object.keys(obj) for(const key of keys){ target[key]=obj(key) } } return target } console.log(merge({a:1},{b:2},{c:3})) //=>{a:1,b:2,c:3}
注意事项
注意:一旦一个函数的参数列表中使用了剩余参数的语法糖,便不能够再添加任何参数,不然会抛出错误。
function fn1(...rest){} //Correct function fn1(...rest,foo){} //Syntax Error
arguments与剩余函数
虽然从语言角度看,arguments和...args是能够同时使用的,但有一种状况除外,arguments在箭头函数中,会跟随上下文绑定到上层,因此在不肯定上下文绑定结果的状况下,尽量不要在箭头函数中使用arguments,而要使用..args。
(3)解构传参
ES2015中的解构传参是使用数组做为传入参数以控制函数的调用状况,不一样的是解构传参不会替换函数调用中的上下文。
与剩余参数同样,解构传参使用...做为语法糖标识符。
//Syntax:fn(...[arg1,arg2]) function sum(...numbers){ return numbers.reduce((a,b)=>a+b) } sum(...[1,2,3]) //=>6
在ECMAScript中定义了如下几种基本的数据结构,分为值类型(Primitive Types)和引用类型(Reference
Types)。
值类型数据结构:
引用类型数据结构:
(1)Set有序集合
ECMAScript中,Array表示一系列元素的有序集合,其中每个元素都会带有自身处在这个集合内的位置并以天然数做为标记,即带有下标。无序集合能够把它当成没有排序概念的数组,而且元素不可重复。
使用语法
在ES2015中,集合与数组不同的是,集合没法像数组那样使用[]语法来直接生成,而须要用新建对象的方法来建立一个新的集合对象。
//Syntax:new Set([iterable]):Set const set=new Set()
可使用一个现成的数组做为集合对象的初始元素
const set=new Set([1,2,3])
集合对象的操做方法
增删元素
能够经过add、delete和clear方法来添加,删除,清空集合内的元素。
const set =new Set() //添加元素 set.add(1) .add(2) .add(3) .add(3) //这一句不会起到任何做用,由于元素3已存在于集合内 console.log(set) //Set{1,2,3} //删除元素 set.delete(2) console.log(set) //Set{1,3} //清空集合 set.clear() console.log(set) //set{}
检查元素
const set=new Set([1,2,3]) //检查元素 set.has(2) //=>true set.has(4) //=>false
遍历元素
集合对象自身定义了forEach方法,跟数组类型中的forEach同样,传入一个回调函数以接受集合内的元素,而且能够为这个回调函数指定一个上下文。
const set=new Set([1,2,3,4]) set.forEach(item=>{ console.log(item) }) //=>1 2 3 4 set.forEach(item=>{ console.log(item*this.foo) },{foo:2}) //=>2 4 6 8
在ES2015中,因为Symbol的引入,数组等类型有了一个新属性Symbol.iterator(迭代子),这些类型的新名称--可迭代对象(Iterable Object),其中包括数组类型、字符串类型、集合类型、字典类型(Map)、生成器类型(Generator),for-of循环语句能够对可迭代对象进行迭代,配合const或let使用,从而解决forEach方法不可中断的问题。
const set=new Set([1,2,3,4]) for(const val of set){ console.log(val) } //=>1 2 3 4
(2)WeakSet
WeakSet最大的应用意义在于,能够直接对引擎中垃圾收集器的运行状况有程序化的探知方式,开发者能够利用WeakSet的特性以更高的定制化方案来优化程序的内存使用方案。
WeakSet与Set的区别:
a.WeakSet不能包含值类型元素,不然会抛出一个TypeError;
b.WeakSet不能包含无引用的对象,不然会自动清除出集合;
c.WeakSet没法被探知其大小,也没法被探知其中所包含的元素。
(3)Map映射类型
映射类型在计算机科学中的定义属于关联数组(Associative Array),关联数组的定义为若干个键值对(Key/Value Pair)组成的集合,其中每个键都只能出现一次。
使用语法
映射类型须要建立一个相应的实例来使用。
//Syntax:new Map([iterable]):Map const map=new Map()
在建立映射对象时,能够将一个以二元数组(键值对)做为元素的数组传入到构建函数中,其中每个键值对都会加入到该映射对象中。该数组内的元素会以数组顺序进行处理,若是存在相同的键,则会按照FIFO(First In First Out,先进先出)原则,以该键最后一个处理的对应值为最终值。
const map = new Map([['foo', 1 ], [ 'foo', 2 ]]) console.log(map.get('foo')) //=> 2
与对象字面量同样,映射对象能够对其中的键值对进行添加、检查、获取、删除等操做。
固然,做为新特性的映射对象也拥有一些Object没有的方法。
增删键值对
与集合对象相似,能够经过set、delete和clear方法对映射对象内的键值对进行操做。
const map=new Map() // 添加键值对 map.set('foo','hello') map.set('bar','es2015') map.set('bar','world') //=>将覆盖以前加入的值 //删除指定的键值对 map.delete('foo') //清空映射对象 map.clear()
获取键值对
映射对象由键值对组成,因此能够利用键来获取相应的值。
const map=new Map() map.set('foo','bar') console.log(map.get('foo')) //=> bar
检查键值对
映射对象能够经过has (key)方法来检査其中是否包含某一个键值对
const map=new Map([ 'foo', 1 ]) console.log(map.has('foo')) //=> true console.log(map.has('bar')) //=>false
遍历键值对
映射对象是关联数组的一种实现,因此映射对象在设计上一样是一种可迭代对象,能够经过for-of循环语句对其中的键值对进行历遍。也可使用己实如今映射对象中的forEach方法来进行历遍。
映射对象带有entries ()方法,这个与集合对象中的entries()相似,用于返回一个包
含全部键值对的可迭代对象,而for-of循环语句和forEach即是先利用entries ()方法先
将映射对象转换为一个类数组对象,而后再进行迭代。
const map=new Map([['foo',1],['bar',2]]) console.log(Array.from(map.entries())) //=>[['bar',1],['bar',2]] for(const [key,value] of map){ console.log(`${key}:${value}`) } //=>foo:1 bar:2 map.forEach((value,key,map)=>{ console.log(`${key}:${value}`) })
(4)WeakMap
WeakMap的键会检查变量引用,只要其中任意一个引用被解除,该值对就会被删除。
//Syntax:new WeakMap([iterable]):WeakMap const weakm=new WeakMap() let keyObject={id:1} const valObject={score:100} weakm.set(keyObject,valObject) weakm.get(keyObject) //=>{score:100} keyObject=null console.log(weakm.has(keyObject)) //=>false
ES2015中的类语法与其余C语言家族成员的类语法有许多相同之处,若是开发者有在
JavaScript中使用过基于原型的类机制,那么也能够很容易接受ES2015的语法。
基本定义语法
// Syntax: class name { ... } class Animal { constructor(family, specie, hue) { this.family =family this.specie = specie this.hue = hue yell() { console.log(this.hue) } } const doge = new Animal('Canidae', 'Canis lupus’, 'Woug') doge.yell() //=> Woug
这里须要注意的是,在类中定义的方法,都是带有做用域的普通函数,而不是箭头函数,方法内第一层所引用的this都指向当前实例,若是实例方法内包含箭头函数,则引擎就会根据包含层级把箭头函数内引用的this所指向的实际对象一直向上层搜索,直到到达一个函数做用域或块级做用域为止。若是一直搜索到达了运行环境的最上层,就会被指向undefined。
class Point{ constructor(x,y){ this.x=x this.y=y } moveRight(step){ return new Promise(resolve=>resolve({ x:this.x+step, y:this.y })) } } const p=new Point(2,5) p.moveRight(3) .thien(({x,y})=>console.log(`(${x},${y}`)) //=>(5,5)
继承语法
//Syntax:class SubClass extends SuperClass{} class Point2D{ constructor(x,y){ this.x=x this.y=y } toString(){ return `(${this.x},${this.y})` } } class Point3D extends Point2D{ constructor(x,y,z){ super(x,y) this.x=x } toString(){ return `(${this.x},${this.y},${this.z})` } }
ES2015的继承语法能够将之前使用构建函数模拟的类做为父类来继承,并不是只由class语法定义的类才可使用。
function Cat() {} Cat.prototype.climb = function () { return "I can climb" } Cat.prototype.yell = function () { return "Meow" } class Tiger extends Cat{ yell(){ return "Aoh" } } const tiger=new Tiger() console.log(tiger.yell()) //=>Aoh console.log(tiger.climb()) //=>I can climb
须要注意的是,若是一个子类继承了父类,那么在子类的constructor构造函数中必须使用super函数调用父类的构造函数后才能在子类的constructor构造函数中使用this,不然会报出this is defined的错误。
class Foo{} class Bar extends Foo{ constructor(){ this.property=1 } } new Bar() //=>RerenceError:this is defined
这个问題在除constructor构造函数之外的方法中并不会出现,即使在子类的构造
函数中并无调用super函数,在其余方法中依然能够调用this来指向当前实例。
Getter/Setter
Getter/Setter是一种元编程(Meta-programming)的概念,元编程的特色在于,容许程序能够对运行时(Runtime)的对象进行读取和操做,从而使程序能够脱离代码从字面上为程序定义的一些限制,有了对对象的更高操做权限。
const List={ _array:[], set new(value){ this._array.push(value) }, get last(){ return this._array[0] }, get value(){ return this._array } } List.new=1 List.new=2 List.new=3 console.log(List.last) //=>1 console.log(List.value) //=>[1,2,3]
ES2015的类机制一样支持Getter/Setter在类中的使用,配合元编程的概念,类的能力会变得更增强大。
class Point{ constructor(x,y){ this.x=x this.y=y } get d(){ return Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)) } } const p=new Point(3,4) console.log(p.d) //=>5
静态方法
能够经过实现一个静态方法来扩展类
// Syntax: class Name { static fn() { ... } } class Animal { constructor(family, specie, hue) { this.family = family this.specie = specie this.hue = hue } yell() { console.log(this.hue) } static extend(constructor, ..._args) { return class extends Animal { constructor{...args) { super(..._args) constructor.call(this, ...args) } } } } const Dog = Animal.extend(function(name) { this.name = name }, 'Canidae', 'Canis lupus', 'Woug') const doge=new Dog('Doge') doge.yell(> //=> Woug console.log(doge.name) //=> Doge
高级技巧
在Object类及其全部子类(在ECMAScript中,除了null、undefined之外,一切类型和类均可以看作是Object的子类)的实例中,有一个利用Symbol.toStringTag做为键的属性,定义着当这个对象的toString()方法被调用时,所返回的Tag的内容是什么。这就意味着能够进行一些自定义操做,经过[]语法和Getter特性为一个类自定义toString标签。
class Foo{ get [Symbol.toStringTag](){ return 'Bar' } } const obj=new Foo() console.log(obj.toString()) //=>[object Bar]
注意事项
类的继承必须是单项的,不可能出现A类继承于B类的同时B类也继承A类的现象,这就意味着,父类必须在子类定义以前被定义。
生成器的主要功能是:经过一段程序,持续迭代或枚举出符合某个公式或算法的有序数列中的元素,这个程序即是用于实现这个公式或算法的,而不须要将目标数列完整写出。
生成器是ES2015中同时包含语法和底层支持的一个新特性。
(1)基本概念
生成器函数
生成器函数是ES2015中生成器的最主要表现方式,它与普通函数的语法差异在于,在function语句以后和函数名以前,有一个“*”做为生成器函数的标示符。
function* fibo(){ //... }
生成器函数并非强制性使用声明式进行定义的,与普通函数—样也可使用表达式进行定义。
const fnName = function*() {/*...*/}
生成器函数的函数体内容将会是所生成的生成器的执行内容,在这些内容之中,yield语句的引入使得生成器函数与普通函数有了区别。yield语句的做用与return语句冇些类似,但并不是退出函数体,而是切出当前函数的运行时(此处为一个类协程,Semi-coroutine),与此同时能够将一个值(能够是任何类型)带到主线程中。
咱们以一个比较形象的例子来作比喻,你能够把整个生成器运行时当作一条长长的瑞士卷,while (true)是无限长的,ECMAScript引擎每一次遇到yield语句时,就比如在瑞士卷上切一刀,而切面所呈现的“纹路”则是yield语句所得的值。
生成器
从计算机科学角度上看,生成器是—种类协程或半协程(Semi-coroutine),它提供了一种能够经过特定语句或方法使其执行对象(Execution)暂停的功能,而这语句通常都是yield语句。上面的斐波那契数列生成器即是经过yield语句将每一次的公式计算结果切出执行对象,并带到主线程上来的。
在ES2015中,yield语句能够将一个值带出协程,向主线程也能够经过生成器对象的方法将一个值带回生成器的执行对象中去。
const inputValue =yield outputValue
生成器切出执行对象并带出outputValue,主线程通过同步或异步处理后,经过.next (val)方法将inputValue带回生成器的执行对象中。
(2)使用方法
构建生成器函数
使用生成器的第一步天然是要构建一个生成器函数,以生成相对应的生成器对象。
启动生成器
生成器函数不能直接做为普通的函数来使用,由于在调用时没法直接执行其中的逻辑代码。执行生成器函数会返回一个生成器对象,用于运行生成器内容和接受其中的值。
运行生成器内容
由于生成器对象自身也是一种可迭代对象,因此直接使用for-of循环将其中输出的值打印出来。
Promise意在让异步代码变得干净和直观,让异步代码变得井井有理。
Promise在设计上具备原子性,即只有三种状态:等待(Pending)、成功(Fulfilled)、失败(Rejected)。在调用支持Promise的异步方法时,逻辑变得很是简单,在大规模的软件工程开发中具备良好的健壮性。
(1)基本语法
建立Promise对象:
要想给一个函数赋予Promise能力,就要先建立一个Promise对象,并将其做为函数值返回。Promise对象要求传入一个函数,并带有resolve和reject参数。这是两个用于结束Promise等待的函数,对应的状态分别是成功和失败。
//Syntax: //new Promise(executor):Promise //new Promise((resolve,reject)=>statements):Promise function asyncMethod(...args){ return new Promise((resolve,reject)=>{ //... }) }
将新建立的Promise对象做为异步方法的返回值,全部的状态就可使用它所提供的方法进行控制了。
进行异步操做:
建立了 Promise对象后,就能够进行异步操做,并经过resolve (value)和
reject (reason)方法来控制Promise的原子状态。
//Syntax: //resolve(value) //reject(reason) new Promise((resolve,reject)=>{ api.call('fetch-data',(err,data)=>{ if(err) return reject(err) resolve(data) }) })
其中在Promise的首层函数做用域中一旦出现throw语句,Promise对象便会直接进入失败状态,并以throw语句的抛出值做为错误值进行错误处理。
(new Promise(function() { throw new Error ('test') ))) .catch(err =>console.error(err))
可是相对的return语句并不会使Promise对象进入成功状态,而会使Promise停留在等待状态。因此在Promise对象的执行器(executor)内须要谨慎使用return语句来控制代码流程。
处理Promise的状态
与resolve(value)和reject(reason)方法对应的是,Promise对象有两个用于处理Promise对象状态变化的方法。
这两个方法都会返回一个Promise对象,Promise对象的组合便会成为一个Promise对象链,呈流水线的模式做业。
//Syntax:promise.then(onFulfilled).catch(onRejected):Promise asyncMethod() .then((...args)=>args /*...*/) .catch(err=>console.error(err))
Promise链式处理默认被实现,即.then(onFulfilled)或.catch(onRejected)会处理在onFulfilled和onRejected中所返回或抛出的值。
若是onFulfilled或onRejected中所返回的值是一个Promise对象,则该Promise对象会被加入到Promise的处理链中。
若是onFulfilled或onRejected中返回的值并非一个Promise对象,则会返回一个己经进入成功状态的Promise对象。
若是onFulfilled或onRejected中由于throw语句而抛出一个错误err,则会返回一个已经进入失败状态的Promise对象。
之因此说Promise对象链呈流水线的模式进行做业,是由于在Promise对象对自身的onFulfilled和onRejected响应器的处理中,会对其中返回的Promise对象进行处理。其内部会将这个新的Promise对象加入到Promise对象链中,并将其暴露出来,使其继续接受新的Promise对象的加入。只有当Promise对象链中的上一个Promise对象进入成功或失畋阶段,下一个Promise对象才会被激活,这就造成了流水线的做业模式。
Promise对象链还有一个十分实用的特性--Promise对象的状态是具备传递性的。
若是Promise对象链中的某一环出现错误,Premise对象链便会从出错的环节开始,不断向下传递,直到出现任何一环的Promise对象对错误进行响应为止。
(2)高级使用方法
Promise.all(iterable)
该方法能够传入一个可迭代对象(如数组),并返回一个Promise对象,该Promise对象会
在当可迭代对象中的所冇Promise对象都进入完成状态(包括成功和失畋)后被激活。
1.若是可迭代对象中的全部Promise对象都进入了成功状态,那么该方法返回的Promise
对象也会进入成功状态,并以一个可迭代对象来承载其中的全部返回值。
2.若是可迭代对象中Promise对象的其中一个进入了失败状态,那么该方法返回的Promise
对象也会进入失败状态,并以那个进入失败状态的错误信息做为本身的错误信息。
//Syntax:Promise.all(iterable):Promise const promises=[async(1),async(2),async(3),async(4)] Promise.all(promises) .then(values=>{ //... }) .catch(err=>console.error(err))
Promise.race(iterable)
Promise .race (iterable)方法一样也接受一个包含若干个Promise对象的可迭代对象,但不一样的是这个方法会监听全部的Promise对象,并等待其中的第一个进入完成状态的Promise对象,一旦有第一个Promise对象进入了完成状态,该方法返回的Promise对象便会根据这第一个完成的Promise对象的状态而改变。
//Syntax:Promise.race(iterable):Promise const promises=[async(1),async(2),async(3),async(4)] Promise.race(promises) .then(values=>{ //... }) .catch(err=>console.error(err))
ECMAScript包含了以往模块加载库的主要功能,还添加了一些很是使用的设计,以提升ECMAScript的模块化管理功能。
(1)引入模块
ES Module中有不少种引入模块的方法,最基本的即是import语句。
import 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'
引入默认模块
//Syntax:import namespace from 'module-name' import http from 'http' import url from 'url' import fs from 'fs'
引入模块部分接口
ES2015中的 模块化机制支持引入一个模块的部分接口
//Syntax:import {meber1,meber2} from 'module-name' import {isEmpty} from 'lodash' import {EventEmitter} from 'events' console.log(isEmpty({})) //=>true
从模块中局部引用的接口定义一个别名,以免指代不明或接口重名的状况出现。
//Syntax:import {meber as alias} from 'module-name' import {createServer as createHTTPServer} from 'http' import {createServer as createHTTPSServer} from 'https'
引入所有局部接口到指定命名空间
有的模块不会定义默认接口,只是定义了若干个命名接口,将其中的全部接口定义到一个命名空间中,使用如下语法。
//Syntax:import * as namespace from 'module-name' import * as lib from 'module' lib.method1() lib.method2()
混入引入默认接口和命名接口
同时引入默认接口和其它命名接口,能够经过混合语句来实现。
//Syntax:import {default as <default name>,method1} from 'module-name' import {default as Client,utils} from 'module'
注意:引入的默认接口必需要使用as语句被赋予一个别名,由于在除模块引入语句之外的地方default是一个保留关键字,因此没法使用。
import {default ,utils} from 'module' //Wrong
简洁的语法
//Syntax:import <default name>,{<named modules>} from 'module-name' import Client,{utils} from 'module' import Client,* as lib from 'module'
不引入接口,仅运行模块代码
在某些场景下,一些模块并不须要向外暴露任何接口,只须要执行内容的代码(如系统初始化)。
//Syntax:import 'module-name' import 'system-apply'
(2)定义模块
ES Module中以文件名及其相对或绝对路径做为该模块被引用时的标识。
(3)暴露模块
暴露单一接口
若是须要定义一个项目内的工具集模块,须要将其中定义的函数或者对象暴露到该文件所定义的模块上。
//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 export 'foo' //2 const foo='bar' export foo //3 export function(){}
暴露模块默认接口
在某些时候,一个模块只须要暴露一个接口,好比须要使用模块机制定义一个只含有一个单一工具类的模块时,就没有必要让这个工具类成为该模块的一部分,而是让这个类成为这个模块。
//Syntax:export default <value> //client.js export default class Client{ //... } //app.js import Client from 'client.js'
混合使用暴露接口语句
开发者能够为一个模块同时定义默认接口和其它命名接口。
//module.js export default class Client{ //... } export const foo='bar' //app.js import Client,{foo} from 'module'
暴露一个模块的全部接口
在第三方类库的开发中,难免须要将各类不一样的功能块分红若干个模块来进行开发,以便管理。ES Module能够将import语句和export组合,直接将一个模块的接口暴露到另一个模块上。
//Syntax:export * from 'other-module' //module-1.js export function foo(){/*....*/} //module.js export * from 'module-1' //app.js import {foo} from 'module'
暴露一个模块的部分接口
//Syntax:export {member} from 'module-name' export {member} from 'module' export {default as ModuleDefault} from 'module'
暴露一个模块的默认接口
能够将一个模块的默认接口做为另外一个模块的默认接口。
export {default} from 'module'
Symbol的值具备互不等价的特性,开发者同时能够为Symbol值添加一个描述。
(1)基本语法
//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值也不具备等价性。
const symbol1=Symbol('footer') const symbol2=Symbol('footer') symbol1==symbol2 //=>false
注意:Symbol函数并非一个构造函数,不能使用new语句来生成Symbol“对象”,不然会抛出TypeError错误。
new Symbol() //=>TypeError:Symbol is not a constructor
由此可知,Symbol是一种值类型而非引用类型。这就意味着若是将Symbol值做为函数形参进行传递,将会进行复制值传递而非引用传递,这跟其它值类型(字符串,数字等)的行为是一致的。
const 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()函数实现。
const symbol=Symbol('foo') typeof symbol //=>symbol const symbolObj=Object(symbol) typeof symbolObj //=>object
//Syntax:Symbol.for([key]):Symbol const symbol=Symbol.for('footer')
Symbol. for ()与Symbol ()的区別是,Symbol . for ()会根据传入的key在全局做用域中注册一个Symbol值,若是某一个key从未被注册到全局做用域中,便会建立一个Symbol值并根据key注册到全局环境中。若是该key己被注册,就会返冋一个与第一次使用所建立的Symbol值等价的Symbol值。
const symbol = Symbol.for('foo') const obj ={} obj[symbol] = 'bar' const anotherSymbol = Symbol.for('foo') console.log(symbol === anotherSymbol) //=> true console.log (obj [anotherSymbol]) //=> jbar
这在大型系统的开发中能够用于一些全局的配罝数据中或者用于须要多处使用的数据中。
//Syntax:Symbol kefFor(<global symbol>):String const symbol=Symbol.for('foobar') console.log(Symbol.keyFor(symbol)) //=>foobar
(2)经常使用Symbol值
ES2015标准定义了一些内置的经常使用Symbol值,这些Symbol值的应用深刻到了 ECMAScript引擎运行中的各个角落。开发者能够运用这些经常使用Symbol值对代码的内部运行逻辑进行修改或拓展,以实现更高级的需求。
(3)Symbol.iterator
在ES2015标准中定义了可迭代对象(Iterable Object)和新的for-of循环语句,其中可迭代对象并非一种类型,而是带有@@iterator属性和能够被for-of循环语句所遍历的对象的统称。
for-of循环语句与可迭代对象
for-of循环语句是ES2015中新增的循环语句,它能够对全部可迭代对象进行遍历,而不只仅是数组。在ES2015中,默认的可迭代对象有:数组(Array)、字符串(String)、类型数组(TypedArray)、映射对象(Map)、集合对象(Set)和生成器实例(Generator)。
// Array for (const el of [ 1, 2, 3 ]) console.log(el) // String for (const word of 'Hello World') console.log(word) // TypedArray for (const value of new Uint8Array([ 0x00, Oxff J)) console.log(value) //Map for (const entry of new Map ([ [' a', 1], [ 'b', 2]]) console.log (entry) //Set for (const el of new Set([ 1, 2, 3, 3, 3 ])) console.log (el) // Generator function* fn() { yield 1 } for (const value of fn ()) console.log(value)
(4)Symbol.hasInstance
Symbol.haslnstance为开发者提供了能够用于扩展instanceof语句内部逻辑的权限,开发者能够将其做为属性
键,用于为一个类定义静态方法,该方法的第一个形参即是被检测的对象,而该方法的返回值即是决定了当次instanceof语句的返回结果。
class Foo ( static [Symbol.haslnstance](obj) { console.log(obj) //=>{} return true } } console.log({} instanceof Foo) //=>true
(5)Symbol.match
Symbol.match是正则表达式(或者对象)在做为字符串使用match ()方法时,内部运行逻辑的自定义逻辑入口。开发者能够经过Symbol.match来自行实现match ()方法的运行逻辑,好比利用strcmp (在ECMAScript中为String.prototype.localeCompare())来实现。
const re = /foo/ re[Symbol.match]=function(str){ const regexp=this console.log(str) //=>bar //... return true } 'bar'.match(re) //=>true
(6)Symbol.toPrimitive
Symbol.toPrimitive为开发者提供了更高级的控制权力,使得引用类型的对象在转换为值类型时能够进行自定义处理,不管是转换为字符串仍是数字。
开发者可使用Symbol.toPrimitive做为属性键为对象定义一个方法,这个方法接受一个参数,这个参数用于判断当前隐式转换的目标类型。
须要注意的是,这里的default并非由于目标类型没法被转换,而是由于语法上容易形成混乱。
(7)Symbol.toStringTag
经常使用Symbol的值在前面己经提到过,它的做用是能够决定这个类的实例在调用toString()方法时的标签内容。
在Object类及其全部的子类的实例中,有一个利用Symbol .toStringTag做为键的属性,该属性定义着当这个对象的toString()方法被调用时,所返回的Tag的内容是什么。
好比在开发者定义的类中,就能够经过Symbol. toStringTag来修改toString()屮的标签内容,利用它做为属性键为类型定义一个Getter。
class Bar {} class Foo{ get [Symbol.toStringTagl() { return 'Bar'} } const obj =new Foo() console.log(obj .toString() ) //=> [object Bar]