Cocos ES6基础

部署进度

$curl -o https://raw.githubusercontent.com/creationix/nvm//install.sh|bash
激活nvm
$source ~/.nvm/nvm.sh
安装node.js
$nvm install node
切换到该版本
$nvm use node
验证安装
$ node --v8-otions | grep harmony
看本机对es6的支持程度
$ npm install -g es-checker

Babel转码器

//转码前
input.map(item=>item+1);
//转码后
input.map(function (item){
return item+1;
});
后者通用性更强,也就是安装转码器就可以用前者编写方式,会自动转成后者

命令行环境

$npm install --global babel -cli
$npm install --save babel-preset-es2015
然后在目录下新建配置文件。babelrc.
//.babelrc { "presets":['es2015'] }

Balbel 自带babel-node命令

$babel-node

console.log([1,2,3]).map(x=>x*x)
[1,4,9]

也可以直接运行ES6脚本
$ babel-node es6.js

-o 或 --out-file参数可以重定向文件
$babel es6.js -o es5.js
或者
$babel es6.js --out-file es5.js
-d用于转换整个目录
$babel -d build-dir source-dir
-d后面是目录,如果要生成source map文件,后面加-s
$babel -d build-dir source-dir -s

浏览器环境

1.安装 babel-core模块
$ npm install babel-core@5
2.安装babellify模块
$npm install --save-dev babelify babel-preset-2015
然后用命令行转换ES6脚本
$ browserify script.js -o bundle.js \ -t[babelify --presets[es2015 react]]
在package.json添加代码

{
"browserify:{
“transform”:[[“babellify”,{“presets”:[“es2015”]}]]
}
}

还有Node.js环境的,一般在Cocos上编辑了,这里跳过

let 和 const 命令

let 是局部变量

var 是全局静态变量,不受定义区域影响,一但定议全局可访问
为了增强代码的容错率,建议能用let不要用var,var带来方便,也会带来bug,看情况用

const 常量

跨模块:export const A=1;

全局对象属性

以下这两种方法是一样的
window.a=1; a //1
a=2; window.a //2
a 就是用var定义的变量

变量的解构赋值

数组的解构赋值

正常赋值是这样:

var a=1;
var b=1;

ES6可以写成这样

var[a,b,c]=[1,2,3]

下面举例:

let [a,[[b],c]]=[1,[[2,5],3],4]
a //1
b//2
c//3

let [ , ,c]=[“1”,“2”,“3”]
c //3;

let [a, …b]=[1,2,3,4];
a //1
b //2,3,4

let [a,c, …b]=[1,];
a //1
c //undefined
b //[ ]

let [x=1,y=x]=[] //x=1, y=1,
let [x=1,y=x]=[1,2] //x=1, y=2,
let [x=y,y=1]=[] //报错

解构一样适用于const

对象解构赋值

var { a, b}={a:1,b:2}

与数组的规则一样,有对应的键才会有对应的值,否则就是未定义,唯一的区别只是数组是按索引来确认赋,而对象是按成员

字符串的解构赋值

把字符串看成字符数组

const [a,b,c,d]=“hello”
a //“h”
b //“e”

数值和布尔值的解构赋值

let { toString: s}=123 //它的意思就是 s = 123.toString()
s===Number.prototype.toString //true

函数参数的解构赋值

下面add的参数不是数组,而是通过解构得到的变量x和y

function add([x,y]){
return x+y;
}
add([1,2]) //3
再来个例子
[[1,2],[3,4]].map([a,b]=>a+b);
[3,7]

还可以这样写

function move({x=0,y=0}={}){
return [x,y];
}
move({x:3,y:2}) //[3,2]
move({}) //[0,0]
move(); //[0,0]

function move({x,y}={x:0,y:0}){
return [x,y];
}
move({x:3,y:2}) //[3,2]
move({}) //[undefined,undefined]
move(); //[0,0]

另外undefined 会触发参数的默认值

[1, undefined,2].map((x=‘yes’)=>x)
[1,‘yes’,2]//这里其实是把前面的数组读了出来。

圆括号问题

1.变量声明语句中,模式不能带有圆括号
var [(a)]=[1];//报错
2.函数参数中,模式不能带有圆括号
function f([(z)]){return z}//报错
3.就是模式
({a:b})={a:32};//报错

只有一种情况可以用:赋值语句的非模式部分可以使用圆括号

什么叫模式?看下面这句
({p:(d)}={})其中p就是模式,模式不会被赋值

字符串的扩展

字符的Unicode 表示法

”\uD842\uDFB7“
//吉
“\u20BB7”
//" 7"
“u{20BB7}”
//吉

codePointAt()

var s=“吉”
s.charAt(0); //’ ’
s.charCodeAt(0);//55362
s.codePointAt(0).toString(16) //“20bb7”

for (let ch of s){
consold.log(ch.codePointAt(0)).toString(16));
}
//“20bb7”
//""

String.fromCodePoint()

有提取字码,就有需要还原字码

String.fromCodePoint(0x20bb7)
//”吉"

字符串的遍历器接口

就是使用for …of 1

at()

'吉'.at(0); //吉

normalize()

'\u01d1'.normalize()==='\u004F\030C'.normalize() //true

includes(),startWith(),endWith()

看名字都知道干嘛用的
想知道某字符是否被包含在内用 includes()
想知道某字符是否在开头用starWith()
想知道某字符是否在结尾用endWith()

repeat()

看名字
想重复某些字,又懒得打可以这样写
‘x’.repeat(3); //"xxx"

padStart(),padEnd()

字符串补全用的
‘x’.padStart(5,"ab") //'ababx'
‘x’.padEnd(5,"ab") //'xabab'

模版字符串

正常我们拼接字符串是这样
“a”+"b"+"c"
用反引号就可以将字符串变成模版字符串
’abc‘
看起来并不明显,这个主要用来做变量引入
下面来看带变量的
var name ="Bob",time="today"; ''hello ${name}, how are you ${time}?" //同样的道理还可以引入函数${f()}
如果内部也要用反引号也很简单,用反斜杠转义就行了

标签模版

var a=5;
var b=10;
tag ‘Hello ${a +b} world ${a*b}’ //tag相当于函数名,后面接的是参数,会依次接收到多个参数
//“Hello”
//“world”
//""
//15
//50

第一个参数:[‘Hello’,‘world’,’ ']
第二个参数:15
第三个参数:50

String.raw()

String.raw 方法可以作为处理模版字符串的基本方法,它会将所有 变量替换 ,并对反斜线进行转义,方便下一步作为 字符串使用。

String.raw({raw:‘test’},0,1,2);
//‘t0e1s2t’
//等同于
String.raw({raw:[‘t’,‘e’,‘s’,‘t’]},0,1,2);

以上的代码我再解释一下,它就相当于把字符串看成数组,后面的参数是用来跟对应的数组索引的值进行拼接用的,相当于

var s=‘test’;
s.at(0)+0 //t0
s.at(1)+1 //e1
s.at(2)+2 //s2
s.at(0)+0+s.at(1)+1+s.at(2)+2 //t0e1s2t

正则的扩展

/…/ 正则表达式用斜杠包住

RegExp 构造函数

var regex = new RegExp(“xyz”,“i”);
//等价于
var regex=/xyz/i;
ES6可以这样写
var regex= new RegExp(xyz/i);

new RegExp(/abc/ig,'i').flags //"i"

字符串的正则方法

以下都可以使用正则表达式作为参数
String.prototype.match
String.prototype.replace
String.prototype.search
String.prototype.split

u修饰符

含义为“Unicode 模式”,用来正确处理大于\uFFFF的Unicode字符。

/^\uD83D/u.test(’\uD83D\uDC2A’)
//false
/^\uD83D/.test(’\uD83D\uDC2A’)
//ture

点字符

var s = “吉”
/^. / t e s t ( s ) / / f a l s e / . /test(s) // false /^. /test(s)//false/./u.test(s) //true

Unicode 字符表示法

/\u{61}/.test(‘a’) //false 不加u则会表示匹配61个连续的u
/\u{61}/u.test(‘a’) //true
/\u{20BB7}/.test(‘吉’) //true

量词

/a{2}/.test(‘aa’) //true
/a{2}/.u.test(‘aa’)//true
/吉{2}/.test(‘吉吉’) // false
/吉{2}/u.test(‘吉吉’) //true

预定义模式

/^\SKaTeX parse error: Undefined control sequence: \S at position 24: …吉') //false /^\̲S̲/u.test(‘吉’) //true
i 修饰符
/[a-z]/i.test(’\u212A’) //false
/[a-z]/iu.test(’\u212A’) //true

y 修饰符

意为“sticky”(粘连)修饰符
与g修饰符不同,虽然都是全局匹配,但y的匹配从第一个字符开始,而g没有位置要求,而且y是向右原则,匹配后只认右边的,左边的默认为空字符

var s = “aaa_aa_a”;
var r1=/a+/g;
var r2 =/a+/y;
//运行第一次两个都能匹配:
r1.exec(s) //[“aaa”]
r2.exec(s) //[“aaa”]
//运行第二次,注意剩下的是 :“_aa_a”
r1.exec(s) //[“aa”]
r2.exec(s) //null

使用lastIndex可以更好的说明y修饰符

const REGEX=/a/g;
设置从2开始
REGEX.lastIndex=2
const match=REGEX.exec(“xaya”); //下标是2,从y开始,匹配"ya"
match.index=3 //字符串下标从0开始,最大是3

若是/a/y,则会匹配失败

const REGEX=/a/y;
设置从2开始
REGEX.lastIndex=2
const match=REGEX.exec(“xaya”); //下标是2,从y开始,匹配"ya"
match //null

sticky属性

用来确认是否有y修饰的正则

var r=/hello\d/y;
r.sticky //true

flags属性

用来获取正则的内容

abc/ig.source //“abc”
abc/ig.flags //“gi”

RegExp.escape()

字符串必须转义才能作为正则模式

但用RegExp.escape(str),它会将所有正则字符转义。

数值的扩展

二进制和八进制数值的表示法

二进制用0b
八进制用0o(注意大小写)

Number(‘0b111’) //7
Number(‘0o10’) //8

Number.isFinite(),Number.isNaN()

Number.isFinite()用来检测一个数值是否非无穷

Number.isFinite(15); //true
Number.isFinite(‘aaa’); //false

Number.isNaN()用来检测一个值 是否为NaN

Number.isNaN(15); //false
Number.isNaN(‘15’); //false
Number.isNaN(NaN); //true
Number.isNaN(‘true’/‘true’); //true

Number.parseInt(),Number.parseFloat()

字符串转数值方法

Number.parseInt(‘12.5’); //12
Number.parseFloat(‘12.45#’); //12.45

Number.isInteger()

用来断判是否整数

Number.isInteger(25.0); //true
Number.isInteger(25.1); //false

Number.EPSILON

ES6在Number对象上新增了一个极小的常量

Number.EPSILON //2.220116049250313e-16
Number.EPSILON.toFIxed(20); //‘0.0000000000000022204’

引入一个这么小的量,目的在于为浮点数计算设置一个误差范围,我们知道浮点数计算是不精确的

0.1 +0.2; //0.30000000000000004
0.1+0.2-0.3; //5.551115123125783e-17
5.551115123125783e-17.toFixed(20); //‘0.00000000000000055551’
如果这个误差能够小于Number.EPSILON,我们就可认为得到正确的结果

安全整数和 Number.isSafeInteger()

用来判断数值是否落在安全范围,超出安全范围数值就会变成错误的结果

Number.isSafeInteger(null); //false

Math对象的扩展

Math.trunc()

直接砍掉整数后面的小数

Math.trunc(4.9); //4

Math.sign()

用来判断是否带负号的数

Math.sign(5); //1
Math.sign(-0); //-1

Math.cbrt(num:number)

算出一个数的立方根

函数可变参数形式

function(...value)

合并数组

[1,2].concat([3,4,5]);

字典形式

let map=new Map([
[1,‘one’],
[2,‘two’],
[3,‘three’],
]);
let arr=[…map.keys()]; //[1,2,3]

Generator函数

var go =function*(){
yield 1;
yield 2;
yield 3;
}
[…go()] //[1,2,3]

箭头函数

匿名函数的简化写法
(...value)=>{}

Symbol

ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,能够保证每个属性的名字都是独一无二的就好了,这样就能从根本上防止属性名冲突。这就是ES6引入Symbol的原因。

let s=Symbol();
typeof s //“symbol”

注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上它是一种类似于字符串的数据类型

Symbol可以接受一个字符串作为参数,表示对Symbol的描述,主要是为了在控制台显示或转为字符串时比较容易区分

var s1= Symbol(foo);
var s2=Symbol(bar);
s1 //Symbol(foo);
s2 //Symbol(bar);

如果不加参数,toString后都是一样显示Symbol()不好区分,但s1和s2是不相等的

作为属性名的Symbol

由于每一个Symbol值都是不相等的,这意味着Symbol 值 可以作为 标识符用于对象的属性名,保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

//(1)
var mySymbol=Symbol();
var a={};
a[mySymbol]=Hello!;
//(2)
var a ={
[mySymbol]=Hello! //在对象内使用Symbol值 定义属性时,Symbol值必须放在方括号中
};
//(3)
var a ={};
Object.defineProperty(a,mySymbol,{value:Hello!});
以上写法都得到同样的结果
a[mySymbol] //Hello!

这个方法会区别每个变量,即使同名也不会冲突,但慎用,会使代码不稳键

魔术字符串

这名取得好,就是个全局通用变量,日常使用,没毛病

属性名的遍历

var obj={}
var a=Symbol.for(b);
var b=Symbol.for(a);
obj[a]=“Hello”;
obj[b]=“world”;
var objectSymbols= Object.getOwnPropertySymbols(obj);

objectSymbols
//[Symbol(a),Symbol(b)]

var obj={}
var foo = Symbol(foo);
Object.defineProperty(obj,foo,{
value:“foobar”,
})
for(var i in obj){
console.log(i); //无输出
}
Object.getOwnPropertyNames(obj); //[]
Object.getOwnPropertySymbols(obj); //[Symbol(foo)]

Reflect.ownKeys(obj); //[value] 返回属性名

Symbol.for(),Symbol.keyFor()

这个是如果希望使用同一个Symbol值

var s1 =Symbol.for(foo);
var s2=Symbol.for(foo);
s1===s2 //true

内置的Symbol值

Symbol.hasInstance

Foo[Symbol.hasInstance] (foo){
return foo instanceof Array;
}
var o=new MyClass();
o instanceof Arry //false 是否是一个数组的实例

Symbol.isConcatSpreadable()

对象的Symbol.isConcatSpreadable 属性等于一个布尔值,表示该对象使用Array.prototype.conat()时是否可以展开

let arr1=[‘c’,d];
[‘a’,b].concat(arr1,e) //[‘a’,‘b’,‘c’,‘d’,‘e’]

Symbol.species()

对象的Symbol.species 属性指向一个方法,对象作为构造函数创造实例时会调用 这个方法。即如果this.constructor[Symbol.species]存在,就会使用这个属性作为构造函数来创造新的实例对象

static get [Symbol.species] (){
return this;
}

Symbol.match

对象的Symbol.match 属性指向一个函数,当执行str.match(myObject)时,如果该属性存在。会调用它返回该方法的返回值

String.prototype.match(regexp)
//等同于
regexp[Symbol.match] (this)
class MyMatcher(){
[Symbol.match] (string){
return hello world.indexOf(string);
}
}
‘e’.match(new MyMatcher()) // 1 拿返回值匹配字符串

Symbol.replace()

String.prototype.replace(searchValue,replaceValue)
//等同于
searchValue[Symbol.replace] (this.replaceValue)

Symbol.search()

String.prototype.search(regexp)
//等同于
regexp[Symbol.search] (this)

calss MySearch(){
constructor(value){
this.value=value
}
[Symbol.search] (string){
return string.indexOf(this.value);
}
}
foobar.search(new MySearch(foo)) //0

Symbol.split

String.prototype.split(separator,limit)
//separator[Symbol.split] (this,limit)

Symbol.interator()

用于返回遍历器的方法,加上后可以做集合对象,然后可以用for…of循环
class Collection{
*Symbol.iterator{
let i=0;
while(this[i]!==undefined){
yield this[i];
++i;
}
}
}

let myCollection =new Collection();
myCollection[0]=1;
myCollection[1]=2;
for(let value of myCollection){
console.log(value);
}
//1
//2

Symbol.toPrimitive()

可以做扩展接口

let obj ={
[Symbol.toPrimitive] (hint){
switch(hint){
case number:
return 123;l
case string:
return str;
case default:
return ‘default’;
default:
throw new Error();
}
}
};

2*obj //246
3+obj // 3default
obj===default //true
String(obj) //str

Symbol.toStringTag

对象的Symbol.toStringTag 属性指向一个方法,在对象上调用 Object.prototype.toString方法时如果这个属性存在,其返回值 会出现 在toString 方法返回的字符串中,表示 对象的类型。也就是说, 这个属性可用于定制[object Object]或[object Array]中object 后面的字符。

({[Symbol.toStringTag]:foo}.toString())
//"[object Foo]"

class Collection{
get [Symbol.toStringTag] (){
return xxx;
}
}
var x = new Collection();
Object.prototype.toString.call(x); //"[object xxx]"

Symbol.unscopables

用于在with环境下排除对象
calss MyCalss{

foo(){return 1;}
get[Symbol.unsopables](){
return {foo:true};  //这里把foo排除了
}

}

var foo=funcation(){return2};
with(MyClass.prototype){ //with环境
foo();//2
}

Proxy 和Refect

Proxy

代理,用于重载对象的方法
var proxy= new Proxy({},{
get: function(target,property){
return 35;
}
});
proxy.time //35
proxy.name //35
proxy.title //35

var target={};
var handler={};
var proxy =new Proxy(target,handler);
proxy.a=b;
target.a //“b”

get()

get方法用于拦截某个属性的读取操作。前面已经有一个例子,下面是另一个拦截读取操作的例子。

var person={
name:“张三”
};

var proxy= new Proxy(person,{
get:function(target,property){
if(property in target){
return target[property];
} else {
throw new ReferenceError(“Property”"+property+"“doesnot exist.”);
}
}
});

proxy.name //“张三”
proxy.age //抛出一个错误
上面的代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性只会返回 undefined

get 方法可以继承。
let proto = new Proxy({},{
get(target,propertyKey,reciver){
console.log(GET+propertyKey);
return target[propertyKey];
}
});

let obj = Object.create(proto);

obj.xxx //“GET xxx”

上面的代码中,拦截操作定义在Prototype对象上,所以如果读取obj 对象继承的属性,拦截会生效。

下面的例子使用get 拦截实现数组读取负数索引。
function createArray(…elements){
let handler ={
get(target,propKey,receiver){
let index=Number(propKey);
if(index<0){
propKey=String(target.length + index);
}
return Reflect.get(target,propKey,receiver);
}
};
let target =[];
target.push(…elements);
return new Proxy(target,handler);
}

let arr = createArray(a,b,c);
arr[-1] //c

上面的代码中,数组的位置参数是-1, 就会输出数组的最后一个成员。

利用Proxy,可以将读取属性的操作(get)转变为执行某个函数,从而实现属性的链式操作。
var pipe =(function(){
var pipe;
return function (value){
pipe=[];
return new Proxy({},{
get: function(pipeObject,fnName){
if(fnName==“get”){
return pipe.reduce(function(val,fn){
return fn(val);
},value);
}
pipe.push(window[fnName]);
return pipeObject;
}
});
}
}());

var double =n => n2;
var pow=n=>n
n;
var reverseInt =n =>n.toString().split().reverse().Join()|0;

pipe(3).double.pow.reverseInt.get //get
//63

上面的代码设置Proxy后达到了将函数名链式使用的效果。

set()

set方法用于拦截某个属性的赋值操。
假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy对象保证age的属性符合要求.

对象保证age 的属性值符合要求。
let validator ={
set: function(obj,prop,value){
if(prop===age){
if(!Number.isInteger(value)){
throw new TypeError(The age isnot an integer);
}
if(value>200){
throw new RangeError(The age seems invalid);
}
}
//对于age以外的属性,直接保存
obj[prop]=value;
}
};

let person= new Proxy({},validator);

person.age=100;
person.age //100
person.age =young //报错
person.age =300 //报错

上面的代码中,由于设置了存值函数set, 任何不符合要求的age 属性赋值都 会抛出一个错误。利用set方法,还可以数据绑定,即每当对象发生变化 时,会自动 更新 DOM

apply()

apply方法拦截函数的调用,call和apply操作。

var handler={
apply(target,ctx,args){
return Reflect.apply(…arguments);
}
}
apply 方法可以接受3个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
下面是一个例子。
var target= function(){ return I am the target;};
var handler={
apply:function(){
return I am the proxy;
}
};

var p=new Proxy(target,handler);
p() ===I am the proxy;
//true

上面的代码中,变量p是Proxy 的实例,当作函数调用 时(p()),就会被apply 方法拦截。返回上一个字符串。

has()

has 方法可以隐藏某些属性,不被in 操作符发现
var handler ={
has(target,key){
if(key[0]===_){
reutrn false;
}
return key in target;
}
};
var target = { prop:foo, prop:foo};
var proxy =new Proxy(target,handler);
_prop in proxy //false
上面的代码中,如果原对象的属性名的第一个字符是下画线,proxy,has 就会返回 flase, 从而不会被in 运算符发现

如果原对象不可配置或者禁止扩展,那么这时has 拦截会报错。

var obj={a:10};
Object.preventExtensions(obj);
var p= new Proxy(obj,{
has: function(target,prop){
return false;
}
});

“a” in p; // TypeError is throw

上面代码中,obj 对象禁止扩展,结果使用has 拦截就会报错。

construct()

construct 方法用于拦截new 命令
var handler={
construct(target,args){
return new target(…args);
}
}

下面是一个例子

var p = new Proxy(function(){},{
construct:function(target,args){
console.log(called:+args.join(,));
return { value: args[0]*10};
}
});

new p(1).value
//“called: 1”
// 10
如果 constuct 方法返回 的不是对象,就会抛出错误。
var p= new Proxy(function(){},{
constuct: function(target,argumentsList){
return 1;
}
});

new p() // 报错

deleteProperty()

deleteProperty 方法用于拦截delete 操作,如果这个方法抛出错误或者返回fales, 当前属性就无法被 delete 命令删除。
var handler={
deleteProperty(target,key){
invariant(key,delete);
return true;
}
}
function invariant(key,action){
if(key[0]===_){
throw new Error(Invalid attempt to ${action} private "${key}" property);
}
}

var target = { _prop:foo }

var proxy= new Proxy(target, handler)
delete proxy._prop
//Error: Invalid attempt to delete private “_prop” property
上面的代码中,deleteProperty方法拦截了delete操作符,删除第一个字符为下画线的属性会报错

defineProperty()

defineProperty 方法拦截了Object.defineProperty操作 // 定义变量

var handler ={
defineProperty(target,key,descriptor){
return false
}
}

var target={}
var proxy = new Proxy(target,handler)
proxy.foo=‘bar’
//TypeError:proxy: proxy deifineProperty handler returned false for property "foo"
上面的代码中,defineProperty 方法返回 fase ,导致添加新属性会抛出错误。

enumerate()

enumerate 方法用于拦截for … in 循环。 注意与Proxy 对象的has方法区分,后者用于拦截in 操作符,对for…in 循环无效

var handler= {
enumerate(target){
return Object.keys(target).filter(key=>key[0]!==_)Symbol.iterator;
}
}
var target={prop:foo , _bar:bar,_prop:foo}
var propxy=new Proxy(target,handler);
for(let key in proxy){
console.log(key);
//“prop”
}

上面的代码中,enumrate 方法取出原对象的所有属性名,将其中第一个字符 等 于下画线的都 过滤掉,然后返回 这些符合 条件 的属性名的一个遍历对象,供for … in 循环消费

下面的另一个例子
var p = new Proxy({},{
enumerate(target){
console.log(“called”);
return [“a”,“b”,“c”] [Symbol.iterator] ();//[Symbol.iterator]被指定成for循环可用于遍历的对象
}
});

for(var x in p){
console.log(x);
}
//“called”
//“a”
//“b”
//“c”
如果enumerate 方法返回 的不是一个对象,就会报错。

var p= new Proxy({},{
enumerate(target){
return 1;
}
});

for(var x in p) {} //报错

getOwnpropertyDescriptor()

getOwnPropertyDescriptor 方法拦截Object.getOwnPropertyDescriptor,返回一个属性描述对象或者undefined.

var handler ={
getOwnPropertyDescriptor(target,key){
if(key[0]===_){
return
}
return Object.getOwnPropertyDescriptor(target,key)
}
}
var target={_foo: ‘bar’,baz:tar};
var proxy= new Proxy(target,handler);
Object.getOwnPropertyDescriptor(proxy,wat);
//undefined
Object.getOwnPropertyDescirptor(proxy,_foo)
//undefined
Object.getOwnPropertyDescirptor(proxy,baz)
//{ value: tar, writable: true, enumerable: true, configurable:true }
上面的代码 中,handler.getOwnPropertyDescirptor方法对于第一个字符为下画线的属性名会返回 undefined.

getPrototyeOf()

getPrototypeOf 方法主要用于拦截Object.getPrototypeOf()运算符,以及其他 一些操作。

var proto= {}
var p=new Proxy({},{
getPrototypeOf(target){
return proto;
}
});

Object.getPrototypeOf§===proto //true;

isExtensible()

isExtensible 方法拦截Object.isExtensible操作。

var p= new Proxy({},{
isExtensible:function(target){
console.log(“called”);
return true;
}
});

Object.isExtensible§;
//“called”
//true

ownKeys()

ownKeys 方法用于拦截Object.keys()操作
let target ={};
let handler ={
ownKeys(target){
return [hello,world];
}
};

let proxy=new Proxy(target,handler);

Object.keys(proxy);
//[hello,world];

上面的代码拦截了对于target 对象的Object.keys()操作,返回预先设定的数组。
下面的例子则是拦截第一个字符为下画线的属性名;

preventExtensions()

prevenExtensions 方法拦截Object.preventExtensions(). 该方法必须 返回 一个布尔值。
这个方法有一个限制,只有当Object.isExtensible(proxy)为false(即不可扩展)时,proxy,preventExtensions才能返回true,否则会报错。

var p= new Proxy({},{
preventExtensions:funcation(target){
return true;
}
});
Object.preventExtensions§;//报错

上面的代码中,proxy.preventExtensions方法返回 true, 但此时Object.isExtensible(proxy)会返回 true, 因些报错
为了防止 出现这个问题,通常要在proxy.prevenExtensions方法中调用一次Object.preventExtensions.
var p=new Proxy({},{
preventExtensions:function(target){
console.log(“called”);
Object.preventExtensions(target);
return true;
}
});
Object.preventExtensions§;
//“called”
//true

setPrototypeOf()

setPrototypeOf 方法主要用于拦截Object.setPrototypeOf方法
下面是一个例子
var handler={
setPrototypeOf(target,proto){
throw new Error(Changing the prototype is forbidden);
}
}

var proto={}
var target=function() {};
var proxy=new Proxy(target,handler);
proxy.setPrototypeof(proxyk,proto);
//Error: Changing the prototype is forbidden

上面的代码 中,只要修改target的原型对象就会报错

Proxy.revocable()

Proxy.recocable方法返回 一个可取消的Proxy 实例 。

let target ={}
let handler={};
let {proxy,revoke}=Proxy.revocalbe(target,handler);// 调用 这个方法会返回两个对象,一个是实例,另一个是取消实例的方法

proxy.foo=123
proxy.foo //123

revoke();

proxy.foo // TypeError: Revoked
Proxy.recoable 方法返回一个对象,其proxy 属性是Proxy实例,revoke 属性是一个函数。可以取消Proxy实例。 上面的代码中,当执行revoke 函数后再访问Proxy 实例,就会抛出一个错误。

Reflect

Reflect 对象与Proxy 对象一样,也是ES6 为了操作对象而提供的新API
1.将Object 对象的一些明显 属于语言层面的方法放到Reflect 对象上。 未来的新方法只部署在Reflect对象上
2.修改某些Object方法的返回结果,让其变得更合理。比如在无法定义属性时不报错,而是返回false
3. 让Object操作都 变成函数 行为。某些Object 操作是命令式,比如name in obj 和delete obj[name], 而Reflect.has(obj,name)和Reflect.deleteproperty(obj,name)让它们成了函数行为
4. Reflect 对象的方法与Proxy 对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象找到对应的方法。 这就让Proxy对象可以方便 地调用 对应的Reflect 方法完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect 上获取默认行为 。

Proxy(target,{
set: function(target,name,value,receiver){
var success =Reflect.set(target,name,value,receiver);
if(success){
log(property+name+on+target+set to +value);
}
return success;
}
});

上面的代码中,Proxy方法拦截了target 对象的属性赋值行为。它采用Reflect.set 方法赋值给对象的属性,然后再部署额外的功能。

Reflect 对象的方法

Reflect.get(target,name,receiver)
查找并返回target对象的name 属性,如果没有该属性,则返回undefined.
如果 name 属性部署了读取函数,则读取函数的this 绑定receiver.
var obj={
get foo(){ return this.bar();},
bar:function(){…}
}

//下面的语句会让this.bar() 变成调用wrapper.bar()
Reflect.get(obj,“foo”,wrapper);
Reflect.set(target,name,value,receiver);
设置target对象的name 属性等于value。 如果name 属性设置了赋值函数,则赋值函数的this 绑定 receiver

Reflect.has(obj,name)
等同于name in obj.

Reflect.deleteProperty(obj,name)
等同于delete obj[name].

Reflect.consturct(target,args)
等同于new target(…args), 这提供了一种不使用new 来调用 的构造 函数的方法。

Reflect.getPrototypeOf(obj)
读取对象的_proto__属性,等同于Object.getPrototypeOf(obj).

Reflect.setPrototypeOf(obj,newProto)
设置对象的__proto__属性。注意,Object对象没有与些对应的方法。

Reflect.apply(fun,thisArg,args)
等同于function.prototype.apply.call(fun,thisArg,args).一般说来,如果要绑定一个函数的this 对象,可以写成fn.apply(obj,args),但如果函数定义了自己的apply方法,就只能写成Funcation.prototype.apply.call(fn,obj,args),采用Reflect对象可以简化这种操作。

另外,需要注意的是,Reflect.set(),Reflect.defineProperty()、Reflect.freeze()、Reflect.seal()和Reflect.prevenExtensions() 返回一个布尔值,表示操作是否成功。其对应的Object方法在失败时都会抛出错误

//失败时抛出错误
Object.defineProperty(obj,name,desc);
//失败时返回false
Reflect.defineProperty(obj,name,desc);
上面的代码中,Reflect.defineProperty方法的作用与Object.defineProperty是一样的,都 是为对象定义一个属性。但是,Reflect.defineProperty 方法失败时不会抛出错误,只会返回 false

创建一个Poxy对象就能做这些事,可以用于编写框架

二进制数组

ArrayBuffer对象

var buffer =new ArrayBuffer(12);
var x1=new Int32Array(buffer);
x1[0]=1;
var x2 =new Uint8Array(buffer);
x2[0]=2

x1[0] //2

上面的代码对两只一段内存分别建立两种视图: 32位带符号整数(Int32Array构造函数)和8位不带符号整数(Uint8Array 构造函数)。由于两个视图对应的是同一段内存,因此一个视图修改底层内存会影响 在另一个视图.

TypedArray 视图的构造函数除了接受 ArrayBuffer实例 作为参数,还可以接受普通数组作为参数,直接分配内存生成底层的ArrayBuffer实例,同时完成对这段内在的赋值。
var typedArray =new Uint8Array([0,1,2]);
typedArray.length //3

typedArray[0]=5;
typeArray //[5,1,2]

上面的代码使用TypedArray 视图的Uint8Array 构造函数新建一个不带符号的8倍整数视图。
可以看到,Uint8Array 直接使用普通数组作为参数,对底层内在的赋值同时完成。

ArrayBuffer.prototype.byteLength

要获取ArrayBuffer实例的 byteLength 属性返回所分配 的内存区域的字节长度。

ArryBuffer.prototype.slice()

复制生成一个新的对象

var buffer=new ArrayBuffer(8);
var newBuffer=buffer.slice(0,3);

ArrayBuffer.isView

判断参数是否为TypedArray 实例或DataView实例

var buffer =new ArrayBuffer(8);
ArrayBuffer.isView(buffer); //flase

var v= new Int32Array(buffer);
ArrayBuffer.isView(v) //true

TypedArray 视图

var f64a=new Float64Array(8);
f64a[0]=10;
f64a[1]=20;
f64a[2]=f64a[0]+f64a[1];

上面代码生成一个8 个成员的Float64Array数组共(共64字节), 然后依次对每个成员赋值。
这时,视图构造函数的参数就是成员的个数。可以看到,视图数组的赋值操作与普通数组毫无二致

数组方法

注意,TypeedArray数组没有concat方法。如果想要合并多个TypedArray数组,可以用下面这个函数。

function concatenate(resultConstructor,…arrays){
let totalLength =0;
for(let arr of arrays){
totalLength += arr.length;
}
let result = new resultConstructor(totalLength);
let offset =0;
for(let arr of arrays){
result.set(arr,offset);
offset +=arr.length;
}
return result;
}

concatenate(Uint8Array,Uint8Array.of(1,2),Uint8Array.of(3,4));
//Uint8Array[1,2,3,4]

另外,TypedArray数组与普通数组一样部署了Iterator 接口, 所以可以遍历。
let ui8=Uint8Array.of(0,1,2);
for(letbyte of ui8){
console.log(byte);
}
//0
//1
//2

字节序

字节序指的是数值在内在中的表示方式

var buffer=new ArrayBuffer(16);
var int32View=new Int32Array(buffer);

for(var i=0; i< int32View.length;i++){
int32View[i]=i*2;
}

上面的代码生成 一个16字节的ArrayBuffer对象,然后在其基础上建立了一个32位整数的视图。由于 每个32位整数占据4个字节,所以一共可以写入4个整数,依次为0、2、4、6.

BYTES_PER_ELEMENT属性

每一种视图的构造函数,都有一个BYTES_PER_ELEMENT属性,表示这种数据类型占据的字节数

ArrayBuffer与字符串的互相转换

ArrayBuffer转为字符串,或者字符串转为ArrayBuffer,有一个前提,即字符串的编码方法是确定的。假定字符串采用UTF-16编码(JavaScriptr的内部编码方式),那么可自己编写转换函数。

//ArrayBuffer 转为字符串,参数为ArrayBuffer 对象
function ab2str(buf){
return String.fromCharCode.apply(null,new Uint16Array(buf));
}

//字符串转为ArrayBuffer 对象,参数为ArrayBuffer对象
function str2ab(str){
var buf = new ArrayBuffer(str.length * 2); //每个字符占用2个字节
var bufView =new Uint64Array(buf);
for(var i = 0,strlen= str.length; i <striLen; i++){
bufView[i] = str.charCodeAt(i);
}
return buf;
}

溢出

不同的视图类型所能容纳的数值范围是确定的。超出这个范围就会出现溢出。
TypedArray 数组对于溢出采用的处理方法是求余值。 正向溢出(overflow) 的含义是输入值大于当前数据类型的最大值,最后得到的值 就等于当前数据类型最小值 加上余值,再减去1;负向溢出(underflow)等于当前数据类型的最大值减去余值,再加上1.

var uint8 = new Uint8Array(1);
uint8[0]=256;
uint8[0] //0

uint8[0] =-1;
uint8[0] //255;

TypedArray.prototype.buffer

TypedArray 实例的buffer 属性返回整段内存区域对应的ArrayBuffer 对象。该属性为只读属性。
var a=new Float32Array(64);
var b = new Uint8Array(a.buffer);

以上代码中的a视图对象和b视图对象,对应同一个ArrayBuffer对象,即同一段内存。

TypedArray.prototype.byteLength,TypedArray.prototype.byteOffset

byteLength 属性返回TypedArray 数组占据的内存长度,单位字节。byteOffset 属性返回TypedArray数组从底层ArrayBuffer对象的哪个字节开始。这两个属性都 是只读属性。

var b = new ArrayBuffer(8);

var v1 =new Int32Array(b);
var v2 = new Uint8Array(b,2);
var v3 = new Init16Array(b,2,2);

v1.byteLength //8
v2.byteLength //6
v3.byteLength //4

v1.byteOffset //0
v2.byteOffset //2
v3.byteOffset //2

TypedArray.prototype.length
length 属性表示TypeArray 数组含有多少个成员。注意区分byteLength 属性和length 属性, 前者是字节长度,后者是成员长度。

var a =new Int16Array(8);
a.length //8
a.byteLength//16;

TypeArrray.prototype.set()

TypeArray数组的set 方法用于复制数组(普通数组或TypedArray数组),也就是将一段内存完全复制到另一段内存。
var a = new Uint8Array(8);
var b = new Uint8Array(8);
b.set(a);
上面的代码复制a 数组的内容到b 数组,它是整段内存的复制,比一个个复制成员的那种复制快得多。

set 方法还可以接受第二个参数,表示 从b对象的哪一个成员开始复制a对象。

var a =new Uint16(8);
var b =new Uint16Array(10) ;
b.set(a,2);
以上代码中的b数组比a数组多两个成员,所以从b[2]开始复制。

TypedArray.prototype.subarray()

subarray方法是对于TypedArray 数组的一部分再建立一个新的视图。
var a =new Uint16Array(8);
var b= a.subarray(2,3);

a.byteLength //16
b.byteLength //2

subarray 方法的第一个参数是起始的成员序号,第二个参数是结束的成员序号(不含该成员),如果省略则包含剩余的全部成员。所以上面的a.subarray(2,3)意味着b 只包含a[2]一个成员 字节长度为2.

TypedArray.prototype.slice()

TypedArray实例的slice 方法可以返回一个指定位置的新的TypedArray实例。
let ui8=Uinti8Array.of(0,1,2);
ui8.slice(-1);
//Uint8Array [2]
上面的代码中,ui8是8位无符号整数数组视图的一个实例。它的slice 方法可从当前视图中返回一个新的视图实例。

slice 方法的参数表示原数组的具体位置。负值表示逆向计数的位置,即-1 为倒数第一个位置,-2表示倒数第二个位置,以此类推。

TypedArray.of()

TypedArray 数组的所有构造函数都 有一个静态方法of, 用于将参数转为一个TypedArray实例。
Float32Array.of(0.151,-8,3.7)
// Float32Array[0.151,-8,3.7]
下面3种方法会生成同样的TypedArray数组。
//方法一
let tarr =new Uint8Array([1,2,3]);

//方法二
let tarr=Uint8Array.of(1,2,3);

//方法三
let tarr = new Uint8Array(3);
tarr[0]=0;
tarr[1]=1;
tarr[2]=2;

TypedArray.from()

静态方法from 接受一个可遍历的数据结构(比如数组)作为参数,返回一个基于些结构的TypedArray实例。

Uint16Array.from([0,1,2])
// Uint16Array[0,1,2]
这个方法还可以将一种TypedArray实例转为另一种。
var ui16=Uint16Array.from(Uint8Array.of(0,1,2));
ui16 instanceof Uint16Array //true

from 方法还可以接受一个函数作为第二个参数,用于对每个元素进行扁历,功能类似map方法。

Int8Array.from(127,126,125).map(x=>2*x)
//Int8Array[-2,-4,-6];

Int16Array.from(Int8Array.of(127,126,125),x=>2*x) //相当于遍历, 后面的是内容处理
//Int16Array[-2,-4,-6];

上面的例子中,from 方法没有发生溢出,这说明遍历是针对新生成的16位整数数组,而不是原来的8位整数数组。也就是说,from会将第一个参数指定的TypedArray数组复制到另一段内存中(占用内存从3字节 变为6字节),再进行处理。

复合视图

由于视力的构造函数可以指定起始位置和长度,所以在同一段内在中可以依次存放不同类型的数据,这叫作“复合视图”。

var buffer = new ArrayBuffer(24);

var idView=new Uint32Array(buffer,0,1);
var usernameView=new Uint32Array(buffer,4,16);
var amountDueView=new Float32Array(buffer,20,1);

上面代码将一个24字节的ArrayBuffer对象分成了3个部分:

  1. 字节0到3:1个32位无符号整数。
  2. 字节4到字节19: 16个8位整数。
  3. 字节20到字节23: 1个32位浮点数。

DataView 视图

如果一段数据 包括多种类型(比如服务器传来的HTTP数据),这时除了建立ArrayBuffer 对象的复合视图外,还可以通过 DataView视图进行操作

var buffer = new ArrayBuffer(24);
var dv =new DataView(buffer);
//从第1个字节读取一个8位无符号整数
var v1= dv.getUint8(8);

//从第2个字节读取一个16位无符号整数
var v2=dv.getUint16(1);

//从第4个字节读取一个16位无符号整数
var v3 = dv.getUint16(3)

上面的代码读取了ArrayBuffer 对象的前5个字节,其中有1个8位整数和2个16位整数。

如果一次读取两个或两个以上字节,就必须明确数据的存储方式,到底是小端字节序还是大端字节序。默认情况下,DataView 的get 方法使用大端字节序2解读数据,如果需要使用小端字节序解读,必须在get 方法的第二个参数指定true。
//小端字节序
var v1=dv.getUint16(1,true);

//大端字节序
var v2 = dv.getUint16(3,false);

//大端字节序
var v3 = dv.getUint16(3);

写入内存

//在第1个字节,以大端字节序写入值为25的32位整数
dv.setInt32(0,25,false);
//在第5个字节,以大端字节序写入值为25的32位整数
dv.setInt32(4,25);
//在第9个字节,以大端字节序写入值为2.5的32位整数
dv.setFloat32(8,2.5,true);
如果不确定所使用计算机的字节序,可以采用下面的判断方式。

var littleEndian = {function(){
var buffer=new ArrayBuffer(2);
new DataView(buffer).setInt16(0,256,true);
return new Int16Array(buffer)[0] === 256;
}}();

如果返回true,就是小端字节序; 如果返回 false,就是大端字节序。

Set 和Map 数据结构

Set

ES6提供了新的数据结构——Set. 它类似于数组,但是成员的值。Set本身是一个构造函数,用来生成Set数据结构。
var s =new Set();

[2,3,5,4,5,2,2].map(x =>s.add(x))

for(i of s){ sonsole.log(i)}
//2 3 5 4

上面的代码通过 add方法向Set结构加入成员,结果表明Set结构不会添加重复的值。
Set 函数可以接受一个数组(或类似数组的对象) 作为参数,用于初始化。

var set = new Set([1,2,3,4,4])
[…set]
//[1,2,3,4]

var items = new Set([1,2,3,4,5,5,5,5]);
items.size //5

function divs(){
return […document.querySelectorAll(dev)]
}

var set =new Set(divs())
set.size //56

//类似于
divs().forEach(div=>set.add(div))
set.size //56

向Set 加入值 时不会发生类型转换,所以5和“5” 是两个不同的值。Set 内部判断丙个值 是否不同使用的算法类似于精确相等运算符(===)。这意味着,两个对象总是不相等的。唯一的例外 是NaN等于自身(精确相等运算符认为NaN不等于自身)。

let set =new Set();
set.add({});
set.size //1

set.add({});
set.size //2

var properties = new Set();

properties.add(“width”);
properties.add(“height”);
if(properties.has(someName){
//do something;
})

Array.from 方法可以将Set 结构转为数组。
var items= new Set([1,2,3,4,5]);
var array = Array.from(items);
这就提供了一种去除数组的重复元素的方法。
function dedupe(array){
return Array.from(new Set(array));
}

dedupe([1,1,2,3]) //[1,2,3]

遍历操作
for ( let item of set.keys()){
console.log(item);
}

WeakSet

WeakSet结构与Set类似,也是不重复的集合。但是,它与Set有两个区别。
首先,WeakSet的成员只能是对象,而不能是其他类型的值。
其次,WeakSet中的对象都 是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用。也就是说, 如果其他对象都不再引用 该对象,那么垃圾回收机制会自动回收该 对象所占用的内存,不考虑该对象还存在于WeakSet中。 这个特点意味着无法引用WeakSet的成员,因此WeakSet是不可遍历的。

var ws=new WeakSet();
ws.add(1);
//TypeError: Invalid value used in weak set
ws.add(Symbol())
//TypeError: invalid value used in weak set

只能是对象
还可以在创建时传入构造函数
var ws = new WeakSet(…);
上面的代码试图向WeakSet添加一个数值和Symbol 值,结果报错。

Map

JavaScript 的对象(Object) 本质上是键值对我集合(Hash结构),但是只能用字符串作为键。
这给它的使用带来了很大的限制

var data = {}
var element = document.getElementById(“myDiv”);

data[element]=metadata;
data["[Object HTMLDivElement]"] //metadata

上面的代码愿意是将一个DOM节点作为对象data 的键,但是由于 对象只接受字符串作为键名,所以element 被自动转为字符串[Object HTMLDivElement].

但Map 的键不一样,它可以用于更多类型

let map = new Map([
[1,one],
[1,two],
[1,three],
]);

[…map.keys()]
//[1,2,3]

let map0 = new Map().set(1,a).set(2,b).set(c);

let map2=new Map([
[…map0].map([k,v]=>[k * 2,_+v]);
]);

//产生Map 结构{2=>_a,4=>_b,6=_c

Map转数组

[…myMap]

数组转Map

new Map([[true, 7],[{foo:3},[abc]]])

Map 转为对象

如果Map 的所有键都 是字符串,则其可以转为对象
funcation strMapToObj(strMap){
let obj =Object.create(null);
for(let [k,v] of strMap){
obj[k]=v;
}
return obj;
}
let myMap= new Map().set(yes,true).set(no,false);
strMapToObj(myMap)
// {yes:true,no:false}

对象转为Map

function objToStrMap(obj){
let strMap=new Map()
for(let k of Object.keys(obj)){
strMap.set(k,obj[k]);
}
return strMap;
}
objToStrMap({yes:true, no: false})
//[[yes,true],[no,false]];

Map 转为JSON

Map 转为JSON要区分两种情况,一种情况是,Map的键名都 是字符串,这时可以选择转为对象JSON
function strMapToJson(strMap){
return JSON.stringify(strMapToObj(strMap));
}
let myMap =new Map().set(yes,true).set(no,false);
strMapToJson(myMap);
//{"yes":true,"no":false}

另一种情况是Map键名有非字符串, 这时可以选择转为数组JSON。
function mapToArrayJson(map){
return JSON.stringify([…map]);
}
let myMap =new Map().set(true,7).set({foo,3}:{abc});
strMapToArrayJson(myMap);
//``[[true,7],[{“foo”:3}],[“abc”]]`

JSON转为Map

JSON转为Map,正常情况下所有键名都 是字符串
function jsonToStrMap(jsonStr){
return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap({"yes":true,"no":false})
//Map{yes=>true,no=>false}

但是,有一种特殊情况,整个JSON就量个数组,且每个数组成员本身又是一个有两个成员的数组。这时,它可以一一对应地转为Map.这往往是数组转为JSON的逆操作。
function jsonToMap(jsonStr){
return new Map(JSON.parse(jsonStr));
}

jsonToMap([true:7],[{"foo":3},[abc]]]);
//Map {true =>7,Object {foo:3}=>[abc]};

WeakMap

WeakMap 结构与Map 结构基本类似,唯一 的区别 是它只接受对象作为键名(null除外),不接爱其他类型的值 作为键名,而且键名所指向的对象不讲入垃圾回收机制。WeakMap 结构有助于防止 内存泄漏。

WeakMap 应用的典型场合就是DOM节点作为键名。下面是一个例子。
let myElement=document.getElementById(logo);
let myWeakmap=new WeakMap();
myWeakmap.set(myElement,{timesClicked:0});

myElement.addEventListener(click,funcion(){
let logoData= myWeakmap.get(myElement);
logoData.timesClicked ++;
myWeakmap.set(myElement.logoData);
},false);

上面的代码中,myElement是一个DOM节点,每当发生click事件,就更新一个状态。我们将这个状态作为键值放在WeakMap里,对应的键名就是myElement.一旦这个SOM节点删除,
该状态就会自动消失,不存在内存泄漏风险。

Iterator和for …of 循环

任何数据结构,只要部署Interator接口,就可以完成遍历操作

let arr = [a,b,c];
let iter = arrSymbol.iterator;
iter.next(); //{value:a , done : false}
iter.next(); //{value:b , done : false}
iter.next(); //{value: undefined, done: true}

上面的代码中,变量arr是一个数组,原生具有遍历器接口,部署在arr 的Symbol.iterator属性上.所以,调用 这个属性就会得到遍历器对象。

上面提到,原生部署Iterator 接口的数据结构有3类,对于这3类数据结构,不用自己写遍历器生成函数,for…of 循环会自动 遍历它们。除此之外,其他数据结构(主要是对象)的Iterator 接口,都 需要自己在Symbol.iterator属性上部署,这样才会被 for… of 循环遍历。

对象(Object)之所以没有默认部署Iterator接口,是因为对象的哪个 属性先遍历,哪个 属性后遍历是不确定 的,需要 开发者手动指定。本质上,遍历器是一种线性处理。对于 任何 非线性的数据结构,部署遍历器接口就等于 部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被 当作 Map结构使用,ES5没有Map结构,而ES6原生提供了。、

一个对象如果要有可被 for…of循环调用 的Iterator接口,就必须在Symbol.iterator属性上部署遍历器生成 方法(原型链上的对象具有该 方法也可)

class RangeIterator{
constructor(start,stop){
this.value=start;
this.stop=stop;
}
Symbol.iterator{return this;}

next(){
	var value=this.value;
	if(value<this.stop){
		this.value++;
		return (done:false,value:value);
	}else{
		return {done:true,value:undefined};
	}
}

}

function range(start,stop){
return new RangIterator(start,stop);
}

for(var value of range(0,3)){
console.log(value);
}

上面的代码是一个类部署Iterator接口的写法。Symbol.iterator 属性对应一个函数,执行后返回 当前对象的遍历器对象。
下面是通过 遍历器实现 指针结构的例子。

function Obj(value){
this.value=value;
this.next=null;
}

Obj.prototype[Symbol.iterator]=function(){
var iterator={
next:next;
};
var current=this;
function next(){
if(current){
var value= current.value;
var done= current == null;
current = current.next;
return{
done:done,
value:value
}
}else{
return{
done:true;
}
}
}
return iterator;
}

var one =new Obj(1);
var two =new Obj(2);
var three =new Obj(3);

one.next=two;
two.next=three;
for(var i of one){
console.log();
}

//1
//2
//3
// 原意就是自己可以指定一下next是谁,加上Symbol.iterator只是让它可以用for… of 遍历
字符串也有Iterator接口,用法一样,不过是写好的

for … of 循环

它可以用来遍历对象,区别于for…in 只能遍历
for…in循环有几个缺点。
1.数组的键名是数字,但是for…in 循环是以字符串作为 键名,“0”、“1”、“2” 等。
2.for…in 循环不仅遍历数字键名,还会遍历手动添加 的其他键,甚至包括原型链上的键。
3.某些情况下,for…in循环会以任意顺序遍历键名。
总之,for…in 循环主要是为遍历对象而设计的,不适用于遍历数组
for…of 循环相比上面几种做法有一些显著的优点。
for(let value of myArray){
console.log(value);
}

4.有着同for…in 一样的简洁语法,但是没有for…in 那些缺点
5.不同于forEach 方法,它可以与break、continue和return配合使用。
6.提供了遍历所有数据结构的统一操作接口。
下面是一个使用break语句跳出for…of 循环的例子。
for(var n of fibonacci){
if(n>1000)
break;
console.log(n);
}

for…of 会遍历出数组,知道当前的索引是在哪,省去for循环的index操作,这在多重循环可以很有效地规避一些index重复引用问题
上面的例子会输出斐波纳契数列小于等于1000的项。如果当前项大于1000,就会使用break语句跳出for…of循环。

Generator

使用起来特别简单,就是定义一个function函数,注意带上“”号

状态机
function* helloWorldGenerator(){
yield hello;
yield world;
return ending;
}

var hw =helloWorldGenerator();

hw.next();
//{value:hello,done:false}

hw.next();
//{value:world,done:false}

hw.next();
//{value:ending,done:true:}
调用next方法会拿到 yield 的值。

Generator.prototype.throw()

var g= function*(){
while(true){
try{
yield;
} catch(e){
if (e!=a) throw e;
console.log(内部捕获,e);
}
}
};

var i=g();
i.next();

try{
i.throw(a);
i.throw(b);
}catch(e){
console.log(外部捕获,e);
}

//内部捕获 a
//外部捕获 b

如果Generator 函数内部部署了try…catch 代码块,那么遍历器的throw方法抛出的错误不影响 下一次遍历,否则遍历直接终止。

Generator.prototype.return()

用来终结Generator函数的遍历
function* gen(){

yield 1;
yield 2;
yield 3;

}

var g= gen();

g.next() //{value:1,done:false}
g.return(“foo”) //{value: “foo”, done:true}
g.next() //{value: undefined,done:true};

上面的代码中,遍历器对象g调用return方法后,返回值的value 属性就是return 方法的参数foo。同时,Generator函数的遍历终止,返回值 的done属性为true,以后再调用next方法,done属性总是返回true.

yield* 语句

如果在Generator函数内部调用 另一个Generator函数,默认情况下是没有效果的。
function* foo(){
yield ‘a’;
yield ‘b’;
}

function* bar(){
yield ‘x’;
foo();
yield ‘y’;
}

for(let v of bar()){
console.log(v);
}

//“x”
//“y”

上面的代码中,foo 和bar 都是Genertator函数,在bar 里面 调用foo是不会有效果的。

这时就需要用到yield*语句,用来在一个Generator函数里面执行另一个Generator函数。

function* bar(){
yield ‘x’;
yield * foo();
yield ’y‘;
}

//等同于
function*bar(){
yield ‘x’;
yield ‘a’;
yield ‘b’;
yield ‘y’;
}

作为对象属性的Generator函数

如果一个对象的属性是Generator 函数,那么可以简写成下面的形式。
let obj={
* myGeneratorMethod(){

}
}

上面的代码中,myGeneratorMethod 属性前面有一个星号,表示穿上属性是一个Generator函数,其完整形式如下,与上面的写法是等价的。
let obj={
myGeneratorMethod:function*(){
//…
}
};

Generator 函数的this

Generator函数总是返回 一个遍历器,ES6规定这个遍历器是Generator 函数的实例,它也继承了Generator函数的prototype对象上的方法。
function* g(){}

g.prototype.hello = function(){
return ‘hi!’;
}

let obj = g();

obj instanceof g //true
obj.hello() //‘hi!’;

上面的代码表明,Generator函数g返回的遍历器obj是g 的实例。而且继承了g.prototype,但是,如果把g当作普通的构造函数,则并不会生效,因为g返回的总是遍历器对象

function* g(){
this.a=11;

let obj =g()
obj.a //undefined

上面的代码中,Generator 函数 g 在 this 对象上添加了一个属性a,但是obj对象拿不到这个属性。
function* F(){
yield this.x =2;
yiled this.y =3;
}

上面的代码中,函数F是一个构造函数,又是一个Generator函数。这时,使用new命令就无法生成F的实例了。因为F返回的是一个内部指针。

’next‘ in (new F())
//true

上面的代码中,由于new F() 返回是一个Iterator对象,具有next方法,所以上面的表达式值 为true。

如果要把Generator 函数当作正常的构造函数使用,可以采用下面的变通方法。首先生成一个空对象,使用bind 方法绑定Generator 函数内部的this.这样,构造函数调用以后,这个空对象就是Generator函数实例对象了。

function* F(){
yield this.x=2;
yield this.y=3;
}

var obj={};
var f=F.bind(obj)();

f.next(); //Object {value:2,done:false}
f.next(); //Object {value:3,done:false}
f.next(); //Object {value:undefined,done:true}

obj //{x:2,y:3}

上面的代码中,首先是F内部的this 对象绑定obj对象,然后调用它,返回一个Iterator对象。这个对象执行了3次next 方法(因为F内部有2条yield语句),完成F内部所有代码的运行。这时,所有内部 属性都 绑定在obj对象上,因而obj 对象也就成了F的实例 。

Generator函数推导

Generator 函数推导是对数组结构的一种模拟,其最大 的优点是惰性求值,即直到真正用到时才会求值,这样可以保证效率。请看下面的例子

let bigArray=new Array(100000);
for(let i = 0;i<100000;i++){
bigArray[i]=i;
}

let first = bigArray.map(n=>nn)[0]
console.log(first);
上面的例子遍历了一个大数组,份量 是在真正遍历之前,这个数组就已经生成,占用了系统资源。
如果改用Generator函数推导,就能避免这一点。下面的代码只在用到时才会生成 一个大数组。
let bigGenerator =function
(){
for(let i = 0; i < 100000;i++){
yield i;
}
}

let squared = (for(n of bigGenerator())n*n);

console.log(squared.next());

它可以用于制作协程,单线程异步操作
写好了内部流程,在外面自己调next(),可使本来同步进行的流程中间穿插别的流程,用起来更加灵活。

Promise对象

所谓Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的API,可供进一步处理。

Promise 对象有以下两个特点

  1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有3种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled) 和 Rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其它操作都无法改变这个状态。这也是“Promise“ 这个名字的由来,它在英语中意思就是”承诺“,表示其他手段无法改变。
  2. 一旦状态改变就不会再变,任何时候都 可以得到这 个结果。Promise对象的状态改变只有两种可能:从Pending变为Resolved和从Pending变为Rejected.只其中之一发生,状态就凝固了,不会再变,会一直保持这个结果。就算改变已经发生,你再对Promise对象添加回调函数,也会得到这个结果,这与事件(Event)完全不同。事件的特点是,如果你错过了它,再去监听是得不到结果的

var promise = new Promise(function(resolve,reject)){
//…some code
if(/*异步操作成功*/){
resolve(value);
}else{
reject(error);
}
});
Promise构造函数接受一个函数作为参数,该函数的两具参数分别是resolve和reject 。 它们是两个函数,由JavaScript引擎提供,不用自己部署

下面是一个Promise对象的简单例子。
function timeout(ms){
return enw Promise((resolve,reject)=>{
setTimeout(resolve,ms,‘done’); //这是一个时间延时操作
});
}

//then在这里绑定了一个函数,当执行完timeout后会才会继续执行这个函数
timeout(100).then((value)=>{
console.log(value);
});

上面 的代码中,timeout 方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为Resolved,就会触发then方法绑定的回调函数。

Promise.prototype.then()

Promise实例具有then方法,也就是说then方法是定义在原型对象Promise.prototype上的。
它的作用是为Promise实例添加状态改变时的架设函数。前面说过,then方法的第一个参数是Resolved状态的回调函数,第二参数(可选)是Reject状态的回调函数。

then方法返回的是一个新的Promise实例(注意,,不是原来那个Promise实例)。因此可以采用链式写法,即then 方法后面再调用 另一个then方法。
getJSON("/posts.json").then(function(json){
return json.post;
}).then(function(post){
//…
});

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null,rejetion)的别名,用于指定发生错误时的回调函数。

getJSON("/posts.json").then(function(posts){
//…
}).catch(function(error){
//处理前一个回调函数运行时发生的错误
console.log(‘发生错误!’,error);
});

上面的代码中,getJSON方法返回一个Promise对象,如果该对象状态变为Resolved,则会调用then 方法指定的回调函数;如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调函数处理这个错误。

Promise.all()

Promise.all 方法用于将多个Promise实例包装成一个新的Promise实例。
var p = Promise.all([p1,p2,p3]);
上面的代码中,Promise.all方法接受一个数组作为参数,p1,p2,p3都是Promise对象的实例;如果不是,就会先调 用下面讲到的Promise.resolve,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数不一定是数组,但是必须具有Interator接口,且返回的每个成员都是Promise实例。)

p 的状态由p1、p2、p3决定,分成两种情况。
1.只有p1、p2、p3的状态都变成Fulfilled,p的状态才会变成Fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
2.只要p1、p2、p3中有一个被Rejected,p的状态就变成Rejected,此时第一个被Rejected的实例的返回值会传递给p的回调函数。

下面是一个具体的例子。
//生成一个Promise对象的数组
var promises=[2,3,5,7,11,13].map(function(id){
return getJSON("/prost/"+id+".json");
});

Promise.all((promises).then(function(posts){
//…
}).catch(function(reason){
//…
});

Promise.race()

Promise.race 方法同样是将多个Promise实例包装成一个新的Promise实例。
var p=Promise.race([p1,p2,p3]);
上面的代码中,只要p1、p2、p3中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
Promise.race 方法的参数与Promise.all方法一样,如果不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。
下面是一个例子,如果指定时间内没有获行结果,就将Promise的状态变为Rejected,否则变为Resolved.
var p=Promise.race([
fetch(’/resource-that-may-take-a-while’),
new Promise(function(resolve,reject){
setTimeout(()=>reject(new Error(‘request timeout’)),5000);
})
])
p.then(respones=>console.log(response))
p.catch(error=>console.log(error));

上面的代码中,如果5秒之内fetch方法无法返回结果,变量p的状态就会变为Rejected,从而角尺catch方法指定的回调函数。

Promise.resolve()

有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。
var jsPromise=Promise.resolve($.ajax(’/whatever.json’));
上面的代码将jQuery生成的deferred对象转为新的Promise对象。
Promise.resolve等价于下面的写法 。
Promise.resolve(‘foo’)
//等价于
new Promise(resolve=>resolve(‘foo’))
如果Promise.resolve方法的参数不是具有then方法的对象(又称thenable对象),则返回 var p=Promise.resolve(‘Hello’);

p.then(function(s){
console.log(s)
});

//Hello
上面代码生成了一个新的Promise对象的实例p.由于字符串“Hello”不属于异步操作(判断方法是它不是具有then方法的对象),返回Promise 实例的状态从一生成就是Resolved,所以回调函数会立即执行。Promise.resolve方法的参数会同时传给回调函数。

Promise.resolve方法允许调用时不带参数。所以,如果希望得到一个Promise对象,方便的方法就是直接调用Promise.resolve方法。
var p=Promise.resolve();
p.then(funcetion(){
//…
});
上面 的变量p就是一个Promise对象。
如果Promise.resolve方法的参数是一个Promise实例,则会被原封不动地返回。

Promise.reject()

Promise.reject(reason)方法也会返回一个新的Promise实例,状态为Rejected。Promise.reject方法的参数reason会被传递给实例的回调函数

var p=Promise.reject(‘出错了’);
//等同于
var p=new Promise((resolve,reject)=>reject(‘foo’))

p.then(null,function(s){
console.log(s);
}):

//出错了
上面的代码生成一个Promise对象的实例p,状态为Rejected,回调函数会立即执行。

以上这些方法会省去一些常用 的代码量

两个有用的附加方法

done()

Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。为些,我们可以提供一个done方法,总是牌回调链的尾端,保证抛出任何可能出现的错误。
asyncFunc().then(f1).catch(r1).then(f2).done();

其实现代代码相当简单。
Promise.prototype.done=function(onFulfilled,onRejected){
this.then(onFulfilled,onRejected).catch(function(reason){
//抛出一个全局错误
setTimeout(()=>{throw reason},0);
});
};
由上可见,done方法可以像then方法那样用,提供Fuilfilled和Rejected状态的回调函数,也可以不提供任何参数。但不管怎样,done方法都会捕捉到任何可能出现的错误,并向全局抛出。

finally()
finally 方法用于指定不管Promise对象最后状态如何都会执行的操作。它与done方法的最大 区别在于,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

下面是一个例子,服务器使用Promise处理请求,然后使用finally方法关掉服务器。
server.listen(0).then(function(){
//run test
}).finally(server.stop);

它的实现也很简单。
Promise.prototype.finally=function(callback){
let p =this.constructor;
return this.then(
value =>P.resolve(callback()).then(()=>value),
reson=>P.resolve(callback()).then(()=>{throw reason});
);
};

上面的代码中,不管前面的Promise是Fulfilled还是Rejected,都会执行回调函数callback.

异步操作的async函数、

Generator函数

function asnycJob(){
//…其它代码
var f=yiled readFile(fileA);
//…其它代码
}

上面的函数asnycJob是一个协和,它的奥妙就在于其中的yield命令。它表示执行到此处执行权将交给其他协程。也就是说,yiled命令是异步两个阶段的分界线。
协程遇到yiled命令就软,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点就是代码的写法非常像同步操作。如果去除yield命令,简直一模一样。

下面看看如何使用Generator函数执行一个真实的异步任务。
var fetch=require(‘node-fetch’);

function * gen(){
var url=‘https://api.github.com/users/github’;
var result=yield fetch(url);
console.log(result.bio);
}
上面的代码中,Generator函数封装一个异步操作,先读取一个远程接口,然后从JSON格式的数据解析信息。就像前面说的,这段代码非常像同步操作,只是加上了yield命令。
执行这段代码的方法如下。
var g=gen();
var result=g.next();

result.value.then(function(data){
return data.json();
}).then(function(data){
g.nect(data);
});

上面的代码中,首先执行Generator 函数获取遍历器对象,然后使用nest方法(第二行)执行异步任务的第一阶段。由于Fetch模块返回的是一个Promise对象,因此要用then方法调用下一个next方法
可以看到,虽然Generator函数异步操作表示得很简洁,但是流程管理(即何时执行第一阶段,何时执行第二阶段)却不方便 ,

Thunk函数

参数的求值策略

var x=1;
function f(m){
return m*2;
}

f(x+5)
上面的代码先定义函数f, 然后向它传入表达式x+5.请问,这个表达式应该何时求值?
一种意见是“传值调用”(call by value),即在进入函数体前就计算 x+5的值(等于6),再将这个值传入f.C语言就采用了这种策略。
f(x+5)
//传值调用时等同于
f(6)
另一种意见是“传名调用”(call by name),即直接将表达式x+5传入函数体,只在用到它时求值。Haskell 语言采用了这种策略。
f(x+5)
//传名调用时等同于
(x+5)*2;
传值调用和传名调用,哪一种比较好?回答是各有利弊。传值调用比较简单,但是对参数求值是时实际上尚未用到这个参数,有可能造成性能损失

编译器的“传名调用”实现往往是先将参数放到一个临时函数中,再将这个临时函数传入函数体。这个临时函数就叫作Thunk函数。

function f(m){
return m*2;
}

f(x+5);

//等同于
var thunk = function(){
return x+5;
};

function f(thunk){
return thunk()*2
};

上面的代码中,函数f的参数x+5被一个函数替换了。凡是用到原参数的地方,对Thunk函数求值即可。这就是Thunk函数定义,它是“传名调用”的一种实现策略,用来替换某个表达式

以 读取文件为例,下面的Generator 函数封闭了两个异步操作
var fs=require(‘fs’);
var thunkify=require(‘thunkify’);
var readFile = thunkify(fs.readFile);

var gen = function*(){
var r1=yield readFile(’/etc/fstab’);
console.log(r1.toString())
var r2=yiled readFile(’/etc/shells’);
console.log(r2.toString());
}

上面的代码中,yield命令用于将程序的执行权移出Generator函数。那么就需要一种方法将执行权责地交还给Generator函数。
var g=gen();
var r1=g.next();
r1.value(function(err,data){
if(err)throw err;
var r2=g.next(data);
r2.value(function(err,data){
if(err)throw err;
g.next(data);
});
});

上面的代码中,变量g是Generator函数的内部指针,表示目前执行到哪一步。

co模块

用它可以不用写Generator函数的执行器。

var gen=function*(){
var f1=yield readFile(’/etc/fstab’);
var f2=yield readFile(’/etc/shells’);
console.log(f1.toString());
console.log(f2.toString());
}

var co = require(‘co’);
co(gen);

上面代码中,Generator 函数只要传入co函数就会自动执行。
co 函数返回一个Promise对象,因此可以用then 方法添加回调函数。
co(gen).then(function(){
console.log(‘Generator 函数执行完成’);
})

原理:
1.回调函数。将异步操作包装成Thunk函数,在回调函数中交回执行权。
2.Promise对象。将异步操作包装成Promise对象,用then方法交回执行权

function run(gen){
var g=gen();
function next(data){
var result=g.next(data);
if(result.done) return result.value;
result .value.then(function(data){
next(data);
});
}
next();
}

run(gen);

上面的代码中,只要Generator函数还没有执行到最后 一步,next函数就调用 自身以实现自动执行。

处理并发的异步操作

co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成才进行下一步。
这时,要把并发的操作都放在数组或对象里面,跟在yield语句后面。

//数组写法
co(function*(){
var res=yield[
Promise.resolve(1),
Promise.resolve(2),
];
console.log(res);
}).catch(onerror);

//对象的写法
co(function*(){
var res=yield{
1:Promise.resolve(1);
2:Promise.resolve(2);
};
console.log(res);
})catch(onerror);

下面是另一个例子
co(function*(){
var values=[n1,n2,n3];
yield values.map(somethingAsync);
});

function* somethingAsync(x){
//do something async
return y;
}
上面的代码允许并发3个somethingAsync异步操作,等到它们全部完成才会进行下一步。也就是异步中的同步操作

async 函数

async函数就是Generator函数的语法糖。

前文有一个Generator函数,依次读取两个文件
var fs=require(‘fs’);

var readFile= function(fileName){
return new Promise(function(resolve,reject){
fs.readFile(fileName,function(error,data){
if(error)reject(error);
resolve(data);
});
});
};

var gen=function*(){
var f1=yield readFile(’/etc/fstab’);
var f2 = yield readFile(’/etc/shells’);
console.log(f1.toString());
console.log(f2.toString());
}

写成async 函数就是下面这样。两个关键字 一个async 另一个await
var asyncReadFile=async function(){
var f1=await readFile(/etc/fstab);
var f2=await.readFile(/etc/shells);
console.log(f1.toString());
console.log(f2.toString());
};

一比较就会发现,async函数就是将Generator函数的星号(*)替换成async,将yield 替换成await,仅此而已。
async 函数对Generator函数的改进体现在以下4点。
1.内置执行器。Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async 函数的执行与普通函数一模一样,只要一行。
var result=asyncReadFile();
2.上面的代码调用 了asyncReadFile函数,然后它就会自动执行,输出最后结果。完全不像Generator函数,需要调用next方法,或者用co模块,才能得到真正执行,从而得到最终结果。
3.更好的语义。async和await 比起星号和yield,语义更清楚。async表示函数里的异步操作,await 表示紧跟在后面的表达需要等待结果。
4更广的适用性。co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这等同于同步操作)。
5.返回值是Promise.async函数的返回值 是Promise对象,这比Generator函数的返回值 是Iterator对象方便多了。你可以用then方法指定下一步操作。
进一步说,async函数完全可以看作由多个异步操作包装成的一个Promise对象,而await命令就是内部 then 命令的语法糖。

async 函数的实现
async函数的实现就是将Generator函数和自动执行器包装在一个函数中。
async function fn(args){
//…
}
//等同于
function fn(args){
return spawn(function*(){
//…
});
}

所有的async函数都可以写成上面的第二种形式。其中的spawn 函数就是自动执行器。
下面给出spawn函数的实现,基本就是前文自动执行器的翻版。
function spawn(genF){
return new Promise(function(resolve,reject){
var gen=genF();
function step(nextF){
try{
var next=nextF();
}catch(e){
return reject(e);
}
if(next.done){
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v){
step(function(){ return gen.next(v);});
},function(e){
step(function(){return ten.throw(e);});
});
}
step(function(){return gen.next(undefined);});
});
}

注意点

await 命令后面的Promise对象,运行结果可能是Rejected,所以最好把await命令放在try…catch代码块中。
async function myFunction(){
try{
await somethingThatReturnsAPromise();
}catch(err){
console.log(err);
}
}

//另一种写法
async function myFunction(){
await somethingTahtReturnsAPromise().catch(function(err){
console.log(err);
});
}
await 命令只用用在async函数中,用在普通函数中会报错。

async function dbFuc(db)}{
let docs=[{},{},{}];
//报错
docs.forEach(function(doc){
await db.post(doc);
});
}
上面的代码会报错,因为await用在了普通函数中,但是,即便将forEach方法的参数改成async函数也有问题。
async function dbFuc(db){
let docs=[{},{},{}];
//可能得到错误结果
docs.forEach(async function(doc){
await db.post(doc);
});
}

上面的代码可能不会正常工作,原因是这时3 个db.post 操作将并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。
async function dbFuc(db){
let docs=[{},{},{}];
for(let doc of docs){
await db.post(doc);
}
}

如果确实希望多个请求前功并发执行,可以使用Promise.all 方法。
async function dbFuc(db){
let docs=[{},{},{}];
let promises=docs.map((doc)=>db.post(doc));
let results=await Promise.all(promises);
console.log(results);
}

//或者使用下面的写法

aync function dbFuc(db){
let docs=[{},{},{}];
let promises=docs.map((doc)=>db.post(doc));
let results=[];
for(let promise of promises){
results.push(await promise);
}
console.log(results);
}

因为是封装好的,会比Promise、Generator使用起来更方便,同时灵活性会相对较低

Class

定义变量可以不指定类型,默认为any,最好指定,方便代码编写效率
实例对象用new
name 属性返回 紧跟在class关键字后面的类名
class Point(){}
Point.name //“Point” 老实说,我看不出这个有什么用。都已经知道类名了还要这个干嘛,可能只是为了方便点,和防止手误,拼写错误

Class表达式

const MyClass = class Me{
getClassName(){
return Me.name;
}
};

上面的代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass 而不是Me,Me只在Class内部代码可用,指代表当前类,就是this

不存在变量提升

Class 不存在变量提升,名字取得好,就是不能在未定义前使用。

new Foo(); //ReferenceError
class Foo{}
上面代码中,Foo使用在前,定义在后,这样会报错
{
let Foo=class();
class Bar extends Foo{
}
}

如果存在Class的提升 ,上面的代码将报错 let 命令也是不提升的。

Class继承

用 extends

类的属性和_proto_属性

大多数浏览器的ES5实现中,每一个对象都有_proto_属性,指向对应的构造函数的prototype 属性。Class作为 构造 函数的语法糖,同时有prototype属性和_proto_属性,因此同时存在两条承链。
1.子类的_proto_ 属性表示构造函数的继承,总是指向父类。
2. 子类prototpe属性的_proto_属性表示方法的继承,总是指向父类的prototype属性。
class A{
}

class B extends A{
}
B.proto ===A //true
B.prototype.proto===A.prototype //true;
上面的代码中子类B的__proto_属性指向父类A,子类B的prototype属性的_proto_属性指向父类 A的prototype属性

这样的结果是因为类的继承是按照下面的模式实现的
class A{
}
class B{
}
//B的实例继承A的实例
Object.setPrototypeOf(B.prototype,A.prototype);

//B继承A的静态属性
Object.setPrototyeOf(B,A);

Object.setPrototypeOf=function (obj,proto){
Obj.proto=proto;
return obj;
}
因此,就得到了上面的结果。

Extends的继承目标

extends关键字后面可以跟多种类型的值
class B extends A{
}

上面的A 只要是一个有prototype 属性的函数,就能被B继承。由于 函数都有prototype属性,因此A可以是任意函数
下面讨论3种特殊情况
第1种特殊情况,子类继承Object类。
class A extends Object{}

A.proto ====Object //true
A.prototype.proto === Object.prototype //true

这种情况下,A其实就是构造函数Object的复制,A的实例 就是Object的实例

第2种特殊情况,不存在任何继承。
class A{
}

A.proto === Function.prototype //true
A.prototype.proto === Object.prototype //true
这种情况下,A作为一个基类(即不存在任何继承) 就是一个普通函数,所以直接继承Function.prototype.但是,A调用后返回一个空对象(即Object实例),所以A.prototype.__proto__指向构造函数(Object)的prototype属性
第3种情况,子类继承null
class A extends null{
}

A.proto === function.prototype //true
A.prototype.proto=== undefined //true
这种情况下,与第二种情况非常像。Ab也是一个普通函数,所以直接继承Function.prototype. 但是,A调用 后返回的对象不继承任何方法,所以 它的__proto__指向Function.prototype.即实质上执行了下面的代码

class C extends null{
constructor(){return Object.create(null);}
}

Object.getPrototypeOf()

Object.getPrototypeOf方法可用于从子类上获取父类。
object.getPrototypeOf(ColorPoint)===Point
//true
因此,可以使用这个方法判断 一个类是否继承了另一个类。

super关键字

上面进过,在子类中super 关键父类实例。
class B extends A{
get m(){
return this._p * super._p;
}
set m() {
throw new Error(‘这属性只读’)
}
}

上面的代码中,子类 通过 super 关键字调用 了父类的实例。
由于 对象总是继承其他对象的,所以 可以在任意一个对象 中使用super关键字。

var obj ={
toString(){
return “MyObject”+super.toString();
}
}

obj.toString(); //MyObject :[object Object]

实例的 __proto__属性

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性,也就是说,子类的原型的原型是父类的原型

原生构造函数的继承

class MyArray extends Array{
constructor(…args){
super(…args);
}
}

var arr = new MyArray();
arr[0] =12;
arr.length //1

arr.length =0;

arr[0] //undefined

上面代码定义了一个MyArrary类, 继承了Arrary构造函数,就可以从MyArray生成数组的实例,这意味着,ES6可以自定义原生数据结构(比如Array、String等)的子类

Class 的取值 函数(getter) 和存值 函数(setter)

class MyClass{
constructor(){
//…
}
get prop(){
return ‘getter’;
}
set prop(){
console.log(‘setter:’+value)
}
}
let inst = new MyClass();
inst.prop=123
//setter:123

inst.prop
//‘getter’
上面的代码中,prop 属性有对应的存值 函数和取值函数,因此赋值和读取行为 都被自定义了。
存值 函数和取值函数是设置在属性的descriptor对象上的。

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
上面的代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个Generator函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for… of 循环会自动调 用这个遍历器。

Class 的静态

Class的静态属性

用static

new.target属性

new 是从构造函数生成 实例的命令。ES6为new命令引入了new.target 属性,(在构造函数中)返回 new 命令所作用的构造函数。如果构造函数不是通过 new 命令调用 的,那么new.target会返回undefine,因此这个属性可用于确定 构造函数是怎么调用的
这个可以用来限制父类是否能被实例化

Mixin 模式的实现

Mixin模式指的是将多个类的接口“混入”(mix in) 另一个类。它在ES6的实现如下。
function mix(…mixins){
class Mix()
for(let mixin of mixins){
copyProperties(Mix,mixin);
copyProperties(Mix.prototype,mixin.prototype);
}
return Mix;
}

function copyPrototies(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);
}
}
}

上面的mix函数可将多个对象合成为一个类。使用时只需继承这个类即可。
class DistrbutedEdit extends mix(Loggable,Serializable){
//…
}

修饰器

类的修饰

修饰器是一个表达式, 用于修改类的行为。

修饰器的行为的改变,是在代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。

function testable(target){
target.isTestable=true;
}

//事先把对应要做的事都做了,后面只需要加上这个特性
@ testable
class MyTestableClass{};

console.log(MyTexstableClass.isTestable) //true

上面的代码中@testable就是一个修饰器。它修改了MyTestableClass这个类的行为 。为它加上了静态属性isTestable.
基本上修饰器的行为 如下
@decorator
class A{}

//等同于
class A {}
A = decorator(A) || A;
就就是说,修饰器本质上就是能在编译时执行的函数。

修饰器函数可以接受3个参数,依次是目标函数、属性名和该 属性的描述对象。后两个参数可省略。上面的代码中,testable函数的参数target就是所要修饰的对象。如果希望修饰器的行为 能够根据目标对象的不同而不同,就要在外面再封装一层函数
function testable(isTestable){
return function(target){
target.isTestable=isTestable;
}
}
@testable(true)
class MyTestableClass()
MyTestableClass.isTestable //true

@testable(false)
class MyTestableClass()
MyTestableClass.isTestable //false

方法的修饰

修饰器还可以修饰类的属性

class Person{
@readonly
name(){return ‘${this.first} ${this.last}’}
}

上面的代码中,修饰器readonly用来修饰“类”的name 方法

此时,修饰器函数一共可以接受3个参数,第1个参数是所要修饰 的目标对象,第2个参数是所要修饰的属性名,第3 个参数是该 属性的描述对象。
readonly(Person.prototype,‘name’,descriptor);
//可以修改属性的描述对象
function readonly(target,name, descirptor){
//descirptor对象原来的值 如下
//{
// value:specifiedFunction,
// enumerable:false,
// configurable:true,
// writable:true,
//}
descriptor.wirtable=false;
return descriptor;
}

Object.defineProperty(Person.prototype,‘name’,descriptor);
上面的代码说明,修饰器(readonly)会修饰属性的描述对象(descriptor),然后被修改的描述对象再用来定义属性。

为什么修饰器不能用于函数

函数提升
var counter=0;
var add = function(){
counter++;
};

@add
function foo(){
}
上面的代码本意是执行后counter等于1,但实际上结果是counter等于0,因为函数提升。使得实际执行代码如下
var counter;
var add;

@add
function foo(){
}

counter=0;

add= function(){
counter++;
};

core-decorators.js

是一个第三方模块,提供了几个常见的修饰器。通过它可以更好地理解修饰器

@autobind //使方法中的this对象绑定原始对象。

import{autobind} from ‘core -descrators’

class Person{
@autobind
getPerson(){
return this;
}
}

let person =new Person();
let getPerson=person.getPerson;
getPerson===person;
//true

@readonly

@override 修饰器检查子类的方法是否正确覆盖了父类的同名方法,如果不正确会报错

import {override} from ‘core-descorator’;

class Parent{
speak(first,seconds){}
}

class Child extends Parent{
@override
speak(){ }
//SyntaxError :Child# speak() does no properly override Parent #speak(first,second
)
}

在修饰器的基础上可以实现 Mixin模式。

Trait

也是一种修饰器,效果与Minxin类似,但提供更多功能,比如防止同名方法的冲突、排除混入某些方法、为混入的方法起别名等

Module

export 命令

模块功能主要由两个命令构成:export 和import。export 用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

模块的整体加载

除了指定加载某个输出值 ,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值 都加载在这个对象上。

import * as circle from ‘./circle’;

module 命令

可以取代import语句,达到整体输入模块的作用

module circle from ‘./circle’

export defaault 命令

从前面的例子可以看出,使用import 命令时用户需要知道所要加载的变量名或函数名,否则 无法加载。但是,用户肯定希望快速上手,未必愿意总计文档去了解模块有哪些属性和方法,
为了方便用户,使其不用阅读文档就能加载模块,就要用到export default 命令,为模块指定默认输出

export default function(){
console.log(‘foo’)
}

import customName from ‘./export-default’;

customName; //‘foo’

上面的import命令可以用任意名称指向export-default.js 输出的方法,这时就不知道原模块输出的函数名。需要 注意的是,这时import命令后面不使用大括号

export default 42; //输出默认值
export default class {…} //输出类

import MyClass from ‘MyClass’
let o= new MyClass();

模块的继承

模块之间也可以继承
假设有一个circleplus模块继承了circle模块

export *from ‘circle’;
export var e = 2.6.1828182846;
export default function(x){
return Math.exp(x);
}

上面的export 表示输出circle模块的所有属性和方法。注意,export *命令会忽略circle 模块的default 方法。之后 又输出了自定义的e变量和默认方法。
export {area as circleArrea} from ‘circle’;
上面的代码表示,只输出circle模块的area方法,且将其改名为cricleArea.

加载上面的模块的写法如下 。
module math from ‘circleplus’;
import exp from “circleplus”;
console.log(math.E);
上面的imoprt exp 表示,将circleplus模块 的默认方法加载为exp方法

ES6模块加载的实质

ES6模块输出的是值的引用 。动态引用,不会有缓存值,原始值 变,输入值也会跟着变
CommonJS
//lib.js
var counter = 3;
function incCounter(){
counter ++;
}

module.exports={
counter:counter,
incCounter:incCounter,
}
//main.js
var counter = require(’./lib’).counter;
var incCounter=require(’./lib’).incCounter;

console.log(counter) //3
incCounter();
console.log(counter); // 3

ES6
//lib.js
export var counter = 3;
export function incCounter(){
counter ++;
}

module.exports={
counter:counter,
incCounter:incCounter,
}

//main1.js
import {counter,incCounter} from ‘./lib’

console.log(counter) //3
incCounter();
console.log(counter); // 4

循环加载

指的是a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。

//a. js
var b= require(‘b’);

//b.js
var a=require(‘a’);

通常,“循环加载”表示存在强耦合,如果处理不好,还可能导致递归加载,使得程序无法执行,因此应该避免
ES6 处理“循环加载”是动态引用,遇到模块加载命令import时不会去执行模块,只是生成一个指向被加载模块的引用,需要开发者自己保证真正取值 时能够取到值。只要引用是存在的,就能执行。


  1. 用 for … of循环 ↩︎

  2. 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。小端字节序:低位字节在前,高位字节在后,即以0x1122形式储存。 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值