参考资料javascript
ECMAScript6入门:http://es6.ruanyifeng.com/html
官方文档:https://babeljs.io/learn-es2015/java
开发软件:WebStorm 开源地址:https://coding.net/u/chenxygx/p/CodeSave/git/tree/master/ECMAScript2015node
npm installreact
Settings - Keymap : Main menu - Code - Reformat Code (配置格式化文件)webpack
babel软件须要WebStorm配置一下git
须要全局安装 babel,es6
npm install babel-preset-env --save-dev
npm install --save-dev babel-cli
npm install -g babel-cli
npm install --save-dev babel-plugin-transform-es2015-modules-commonjs
而后须要添加.babelrc文件,用来控制生成es2015github
{
"presets": ["env"]
}
而后package.json添加build,script用来控制编译目录web
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "babel Script --watch --out-dir lib"
},
ES5只有两种声明变量方式:var命令 和 function 命令。
ES6还有四种声明变量方式:let命令 、Const命令、import命令、class命令
用来声明变量,相似于var,声明的变量只在代码快({}表示代码块)内有效。而且不会受到变量提高的影响。
若是区块中存在let和const命令,就会造成封闭做用域,在声明以前使用变量就会报错。这种行为称为:暂时性死区
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
let不容许重复声明,不能在函数内部从新声明参数。有块级做用域,就再也不须要 当即执行函数表达式(IIFE)了
const声明一个只读的常量,一旦声明,常量就不能改变。必须在声明的同时,进行初始化。
const做用域和let命令相同。只在声明所在块级做用域内有效。
const变量也不会提高,一样存在 暂时性死区
const本质上是指变量指向的内存地址不得改动。但对象和数组是能够进行变更的。
const x = {}; x.prop = 1; x = {};
上面代码,能够对x进行添加属性,但不能从新进行赋值改变地址。
若是想让对象或数组彻底冻结,可使用object.freeze方法。
const x = Object.freeze({ prop : 2 }); x.prop = 1; console.log(x.prop); // 2
顶级对象在浏览器环境指 window对象,在node指的是global对象。
由于顶级对象在各类实现中不统一,通常使用this变量,可是会有一些局限性。
全局环境中,this会返回顶层对象。可是node模块和ES2015模块中,this返回的是当前模块。
函数里的this,若是不是做为对象运行,而是单纯的函数,this会指向顶层对象。
针对this指向,能够查看javascript知识点记录
综上所述,能够在两种状况下都获取顶层对象的方法有两种
// 方法一 !(function () { (typeof window !== 'undefined' ? window : (typeof process === 'object' && typeof require === 'function' && typeof global === 'object') ? global : this); this.a = 1; })() console.log(a); // 方法二 var getGlobal = function () { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } throw new Error('unable to locate global object'); }; getGlobal().b = 2; console.log(b);
ES6容许按照必定模式,从数组和对象中提取值,对变量进行赋值,被称为解构。
let [a,b,c] = [1,2,3]; console.log(a+b+c); // 6
上面代码表示,能够从数组中提取值,按照对应次序位置,对变量进行赋值。
若是解构不成功,变量的值就等于 undefined
let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3 let [ , , third] = ["foo", "bar", "baz"]; third // "baz" let [x, , y] = [1, 2, 3]; x // 1 y // 3 let [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4] let [x, y, ...z] = ['a']; x // "a" y // undefined z // []
对象的解构变量必须与属性同名,才能够取到值。次序不一致是没有影响的,如取不到值返回undefined
let { bar, foo } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb" let { baz } = { foo: "aaa", bar: "bbb" }; baz // undefined
若是但愿变量名与属性名不一致,必须写成下面这样。此时foo 和 bar是匹配模式,f是变量。
let { foo: f, bar: b } = { foo: "aaa", bar: "bbb" };
采用解构的写法,变量不能从新声明,因此若是有赋值的变量从新声明就会报错。
解构也能够用于嵌套结构的对象。此时p是模式不会赋值
let obj = { p: [ 'Hello', { y: 'World' } ] }; let { p: [x, { y }] } = obj; x // "Hello" y // "World"
对于结构嵌套对象,也是一样的操做。
var node = { loc: { start: { line: 1, column: 5 } } }; let { loc:{start:{line:l,column:c}} } = node; console.log(l + c);
let obj = {}; let arr = []; ({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true }); obj // {prop:123} arr // [true]
对象的解构也能够设定默认值,使用 = 能够在目标值的属性等于undefined的时候,进行赋初始化值
var {x = 3} = {}; x // 3 var {x, y = 5} = {x: 1}; x // 1 y // 5 var {x:y = 3} = {}; y // 3 var {x:y = 3} = {x: 5}; y // 5 var { message: msg = 'Something went wrong' } = {}; msg // "Something went wrong"
若是要使用一个已经声明的变量,须要将内容嵌套在一个大括号里,告诉js不当作代码段处理
let x;
({x} = {x: 1});
字符串也能够进行解构,将字符串拆分红数组对象。
const [a, b, c, d, e] = 'hello'; a // "h" b // "e" let {length : len} = 'hello'; len // 5
若是等号右边是数值或布尔值,则会转换为对象。也就是说赋值的变量与等号右边类型相同。
赋值的规则是只要右边不是对象和数组,就会转换成对象。undefined和null是没法进行赋值的。
let {toString: s} = 123; s === Number.prototype.toString // true let {toString: s} = true; s === Boolean.prototype.toString // true let { prop: x } = undefined; // TypeError let { prop: y } = null; // TypeError
函数的参数也能够进行解构赋值,而且可使用默认值。
function move({x = 0, y = 0} = {}) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, 0] move({}); // [0, 0] move(); // [0, 0]
可是注意,若是
move({x, y} = { x: 0, y: 0 })
则不会给参数赋默认值,由于上面代码是给函数的参数给默认值,而不是给变量赋默认值。
move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0]
undefined也会触发函数参数的默认值。
[1, undefined, 3].map((x = 'yes') => x); // [ 1, 'yes', 3 ]
ES6的规则是,只要有可能致使解构的歧义,就不得使用圆括号。
可使用圆括号的只有一种场景:赋值语句的非模式部分。
[(b)] = [3]; // 正确 ({ p: (d) } = {}); // 正确 [(parseInt.prop)] = [3]; // 正确
1. 交换两个变量的值
let [x,y] = [1,2];
[x,y] = [y,x];
console.log(x+","+y); // 2,1
2. 从函数返回多个值
// 返回数组 function example() { return [1,2,3]; } let [a,b,c] = example(); // 1,2,3 // 返回对象 function exampleObj(){ return{ foo:1, bar:2 } } let{foo,bar} = exampleObj(); // 1,2
3. 函数参数的定义,能够将一组参数与变量名对应起来。
// 参数数组 function f([a,b,c]) { console.log(a+","+b+","+c); } f([1,2,3]); // 参数对象 function ff({a,b,c}){ console.log(a+","+b+","+c); } ff({a:1,b:2,c:3});
4. 提取JSON数据
let jsonData = { "Name":"A", "Old":12 } let {Name,Old} = jsonData; console.log(Name+Old);
5. 函数参数的默认值
jQuery.ajax = function (url, { async = true, beforeSend = function () {}, cache = true, complete = function () {}, crossDomain = false, global = true, // ... more config }) { // ... do stuff };
6. 遍历Map结构
var map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] of map) { console.log(key + " is " + value); } //仅获取键 let[key] //仅获取值 let[,value]
7. 输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");
Js容许使用\uxxxxx形式表示一个字符,其中xxx表示字符的Unicode码点
单一码点只能在\u0000-\uffff之间。若是超过这个范围须要使用两个字符。
ES6对此进行了改进,只要将码点放进大括号,就能正确解读该字符。
"\u{20BB7}" // "𠮷" "\u{41}\u{42}\u{43}" // "ABC"
String.codePointAt() 会返回字符串的码点。
String.fromCodePoint() 会从码点返回字符。
ES6为字符串添加了for...of循环遍历
for(let codePoint of 'hello')
{
console.log(codePoint);
}
for...of循环,能够识别大于0xFFFF的码点。也就是能够识别汉字
includes('') //返回布尔值,表示是否找到了参数字符串
startsWith('') //返回布尔值,表示是否在字符串开头
endsWith('') //返回布尔值,表示是否在字符串结尾
'hello'.repeat(2) //返回一个新字符串,表示将原字符串重复n次,若是是小数则取整,负数会报错
'x'.padStart(2,'x') //若是某个字符串不足两位长度,就在头补全
'x'.padEnd() //尾部补全
模板字符串是加强版的字符串,使用 ` 反引号标识。用来定义多行字符串,或者在字符串中嵌入变量。
// 普通字符串 `In JavaScript '\n' is a line-feed.` // 多行字符串 `In JavaScript this is not legal.` console.log(`string text line 1 string text line 2`); // 字符串中嵌入变量 var name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?`
$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim());
若是使用模板字符串表示多行字符串,全部的空格和缩进都会被保留在输出中。若是不想保留,能够在末尾使用trim()删除
模板字符串中嵌套变量,可使用 ${变量名},括号内能够听任何计算和表达式,还能够调用函数
http://es6.ruanyifeng.com/#docs/regex
http://es6.ruanyifeng.com/#docs/number
ES6容许为函数的参数设置默认值,直接写在参数定义的后面使用=号
function log(x, y = 'World') { console.log(x, y); }
参数的变量都是默认声明的,因此不能使用let 或 const再次声明。
若是参数默认值是变量,则每次都从新计算默认表达式的值。
函数默认值必须在函数的尾部,非尾部定义是没有办法省略的。
一旦设置了参数的默认值,参数会造成一个单独的做用域。
等初始化结束之后,这个做用域就会消失。
var x = 1; function f(x, y = x) { console.log(y); } f(2) // 2
ES6引用rest参数,用于获取函数的多余参数,这样就不须要使用arguments对象了。
rest参数是一个数组变量,该变量将多余的数组存放进去。
function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // 10
扩展运算符是三个点(...),将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3]) // 1 2 3 console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5
扩展运算符能够展开数组,也就是说不须要apply方法了。
// ES5的写法 Math.max.apply(null, [14, 3, 77]) // ES6的写法 Math.max(...[14, 3, 77])
在看一个把一个数组添加到另外一个数组尾部的例子
// ES5的写法 var arr1 = [0, 1, 2]; var arr2 = [3, 4, 5]; Array.prototype.push.apply(arr1, arr2); // ES6的写法 var arr1 = [0, 1, 2]; var arr2 = [3, 4, 5]; arr1.push(...arr2);
1. 合并数组
// ES5 [1, 2].concat(more) // ES6 [1, 2, ...more] var arr1 = ['a', 'b']; var arr2 = ['c']; var arr3 = ['d', 'e']; // ES5的合并数组 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6的合并数组 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
2. 与解构赋值结合。若是扩展运算符用于给数组赋值,只能放在参数最后一位
// ES5 a = list[0], rest = list.slice(1) // ES6 [a, ...rest] = list
3. 函数的返回值
Javascript函数只能返回一个值,若是须要返回多个,就只能使用数组或对象。
扩展运算符提供了解决这个问题的一个变通方法。
var dateFields = readDateFields(database); var d = new Date(...dateFields);
4. 扩展运算符能够将字符串转为数组
[...'hello'] // [ "h", "e", "l", "l", "o" ]
函数的 name 属性,返回该函数的函数名。
function foo() {} foo.name // "foo"
ES6容许使用 “箭头” (=>)定义函数。
var a = v => console.log(v); // 等同于 var b = function(v){ console.log(v); }
var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };
若是箭头函数的代码块部分多于一条语句,就须要使用大括号将他们括起来。而且使用return返回。
var sum = (num1, num2) => { return num1 + num2; }
大括号会被解析为代码块,若是箭头函数直接返回一个对象,则必须在对象外面加上括号
var getTempItem = id => ({ id: id, name: "Temp" });
箭头函数能够与变量解构一块儿使用
const full = ({ first, last }) => first + ' ' + last; // 等同于 function full(person) { return person.first + ' ' + person.last; }
也可使得表达式更加简洁一些
const isEven = n => n % 2 == 0; const square = n => n * n;
箭头函数的一个做用就是简化回调函数
// 正常函数写法 [1,2,3].map(function (x) { return x * x; }); // 箭头函数写法 [1,2,3].map(x => x * x); // 正常函数写法 var result = values.sort(function (a, b) { return a - b; }); // 箭头函数写法 var result = values.sort((a, b) => a - b);
箭头函数还能够与rest结合使用
const numbers = (...nums) => nums; numbers(1, 2, 3, 4, 5) // [1,2,3,4,5] const headAndTail = (head, ...tail) => [head, tail]; headAndTail(1, 2, 3, 4, 5) // [1,[2,3,4,5]]
1. 函数内的this对象,就是定义时所在的对象,而不是运行时所在的对象
2. 不能够当作构造函数,不可使用new
3. 不可使用 arguments对象,若是须要可使用rest替代
4. 不可使用 yield 命令
箭头函数能够嵌套使用
const plus1 = a => a + 1; const mult2 = a => a * 2; mult2(plus1(5)) // 12
箭头函数可让this固定化,由于箭头函数根本没有本身的this,致使内部的this就是外层代码块的this。
也正是由于没有this,因此不能作为构造函数。
function foo() { return () => { return () => { return () => { console.log('id:', this.id); }; }; }; } var f = foo.call({id: 1}); var t1 = f.call({id: 2})()(); // id: 1 var t2 = f().call({id: 3})(); // id: 1 var t3 = f()().call({id: 4}); // id: 1
上面代码中,只有一个this,就是函数foo的this。由于全部的内层函数都是箭头函数,都没有本身的this。
ES6容许直接写入变量和函数,做为对象的属性和方法
var foo = 'bar'; var baz = {foo}; baz // {foo: "bar"} // 等同于 var baz = {foo: foo};
ES6容许字面量定义对象,将变量用做对象的属性名、方法名。两个表达式不能同时使用
let propKey = 'foo'; let obj = { [propKey]: true, ['a' + 'bc']: 123 };
函数的name属性,返回函数名。对象方法也是函数,也有name属性。
Object.is用来比较两个值是否严格相等,与严格比较运算符(===)一致。
Object.assign 用于将对象合并,将源对象全部可枚举的属性,复制到目标对象中。
object.assign 使用的是浅拷贝。
常见途径
1. 为对象添加属性(将x和y拷贝到对象实例上)
class Point { constructor(x, y) { Object.assign(this, {x, y}); } }
2. 为对象添加方法
Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } }); // 等同于下面的写法 SomeClass.prototype.someMethod = function (arg1, arg2) { ··· }; SomeClass.prototype.anotherMethod = function () { ··· };
3. 克隆对象
function clone(origin) { return Object.assign({}, origin); }
上面的克隆代码,只能克隆原始对象值,不能克隆他的继承值。若是想保持继承链,能够用下面代码
function clone(origin) { let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin); }
4. 合并多个对象
const merge = (target, ...sources) => Object.assign(target, ...sources);
若是但愿合并成一个空的对象,能够用下面的代码
const merge = (...sources) => Object.assign({}, ...sources);
ES6一共有5中方式能够遍历对象的属性
for ... in:循环遍历自身和继承的可枚举属性(不含Symbol属性)
Object.keys(obj):返回一个数组,包括对象自身的全部可枚举属性。(不含继承、不含Symbol属性)
Object.getOwnPropertyNames(obj):返回一个数组,包括对象自身的全部属性,包括不可枚举的属性。(不含继承、不含Symbol属性)
Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的Symbol属性。
Reflect.ownKeys(obj):返回一个数组,包含对象自身的属性。(不含继承、含Symbol属性)
以上5中遍历方式,都会按照如下顺序。
1. 遍历全部属性名为数值的属性,按照数字排序
2. 遍历全部属性名为字符串的属性,按照生成时间排序
3. 遍历全部属性名为Symbol值的属性,按照生成时间排序
用来读取或设置当前对象的 prototype 对象。
它其实是一个内部属性,不对外开放。因此尽可能不要使用这个属性。
prototype是函数的一个属性。注意是函数,对象中是没有的。这个属性指向对象的原型的属性。
__proto__是一个对象拥有的内置属性,是JS内部使用寻找原型链的属性。
当咱们须要使用prototype的时候,下面例子进行new时分为三步。
var Person = function(){}; var p = new Person();
1. var p = {}; 初始化一个对象p
2. p.__proto__ = Person.prototype;
3. Person.call(p) 构造p,初始化p
与__proto__属性相同,用来设置一个对象的prototype对象,返回参数对象自己。
这是ES6推荐的设置对象的方法。左侧是待设置对象,右侧是设置继承对象。
var obj1 = { Name: "C" }; var obj2 = { Old : 12 }; Object.setPrototypeOf(obj2,obj1); console.log(obj2.Old); //12 console.log(obj2.Name); //C
若是第一个参数不是对象,则会给他自动转为对象,但因为返回的仍是第一个参数,因此不会产生效果。
因为undefined 和 null 没法转为对象,因此若是当作第一个参数,则会报异常。
该方法与Object.setPrototypeOf 方法配套,用于读取一个对象的原型对象。
Object.getPrototypeOf(obj);
let obj1 = { Name: "C" }; let obj2 = { Old: 12 }; let obj3 = () => ({Name: "CC"}); let obj4 = function () {}; Object.setPrototypeOf(obj2, obj1); Object.setPrototypeOf(obj4, obj3); console.log(Object.getPrototypeOf(obj4) == obj4.__proto__); //true console.log(Object.getPrototypeOf(obj2) === obj2.__proto__); //true console.log(Object.getPrototypeOf(new obj4()) == obj4.prototype); //true
若是参数不是对象,会被自动转为对象。若是参数是undefined或null,没法转为对象,会报错。
返回一个数组,成员是参数对象自身的键名(不含继承)
let obj1 = {Name: "C"}; let obj2 = {Old: 12, Old2 : 13}; Object.setPrototypeOf(obj2, obj1); console.log(Object.keys(obj2)); // [ 'Old', 'Old2' ]
返回一个数组,成员是参数对象自身的键值(不含继承)
let obj1 = {Name: "C"}; let obj2 = {Old: 12, Old2 : 13}; Object.setPrototypeOf(obj2, obj1); console.log(Object.values(obj2)); // [ 12, 13 ]
返回一个数组,成员是参数对象自身的键值对(不含继承)
let obj1 = {Name: "C"}; let obj2 = {Old: 12, Old2 : 13}; Object.setPrototypeOf(obj2, obj1); console.log(Object.entries(obj2)); // [ [ 'Old', 12 ], [ 'Old2', 13 ] ]
返回某个对象属性的描述对象(不含继承)
let obj1 = {Name: "C"}; let obj2 = {Old: 12, Old2 : 13}; Object.setPrototypeOf(obj2, obj1); console.log(Object.getOwnPropertyDescriptor(obj2,"Old")); //{ value: 12, // writable: true, // enumerable: true, // configurable: true }
ES6引入了一种原始数据类型Symbol,表示独一无二的值。
Symbol值经过Symbol函数生成。凡是属性名属于Symbol类型,都是独一无二的,能够保证不会产生冲突。
let s = Symbol(); typeof s // "symbol"
Symbol函数前不能使用new命令,由于生成Symbol是一个原始类型的值,不是对象。
Symbol能够接受一个字符串做为参数,用来表示描述或区分。若是Symbol参数是一个对象,则会调用该对象的toString方法。
Symbol不能与其余类型的值进行运算,可是能够显示转换为字符串,也能够转换Boolean类型,但不能转为数值。
因为每一个Symbol值都是不相等的,用于对象的属性名,就能保证不会出现同名的属性。
var mySymbol = Symbol(); // 第一种 var obj = {}; obj[mySymbol] = "C"; //第二种 var obj2 = { [mySymbol]:"C2" } //第三种 var obj3 = {}; Object.defineProperty(obj3,mySymbol,{value:"C3"}); console.log(obj[mySymbol]);
Symbol做为属性名的时候,不能用点运算符。由于点运算符后面老是字符串,不会读取标示;
同理,在对象内部使用Symbol定义属性时,必须放在方括号[]内。若是不放在方括号内,则当作字符串处理。
Symbol还能够定义一组常量,保证这组常量的值都是不相等的。
log.levels = { DEBUG: Symbol('debug'), INFO: Symbol('info'), WARN: Symbol('warn') }; log(log.levels.DEBUG, 'debug message'); log(log.levels.INFO, 'info message');
const COLOR_RED = Symbol(); const COLOR_GREEN = Symbol(); ---------------------------------------------- function getComplement(color) { switch (color) { case COLOR_RED: return COLOR_GREEN; case COLOR_GREEN: return COLOR_RED; default: throw new Error('Undefined color'); } }
常量使用Symbol值最大好处是,其余任何值都不可能有相同的值了。
Symbol值做为属性名时,该属性仍是公开属性,不是私有属性。
魔法数指,在代码中屡次出现的字符串或数值。会与代码进行强耦合,应该尽可能消除魔法数。
若是魔法数的值不重要,只是为了区分,就可使用Symbol来进行修改。
function getAreaBefore(shape,options) { var area = 0; switch(shape) { case 'Triangle': area = .5 * options.width * options.height; break; } return area; } console.log(getAreaBefore('Triangle',{width:100,height:100})); var shareType = { Triangle:Symbol() } function getAreaAfter(shape,options) { var area = 0; switch(shape){ case shareType.Triangle: area = .5 * options.width * options.height; break; } return area; } console.log(getAreaAfter(shareType.Triangle,{width:100,height:100}));
Symbol做为属性名,不会出如今 for...in、for...of循环中,也不会在Object.keys、Object.getOwnPropertyNames、Json.stringify中
有一个Object.getOwnPropertySymbols方法,能够获取指定对象全部Symbol属性名。
Reflect.ownKeys方法也能够返回全部类型的键名,包括常规的和Symbol的。
能够接受一个字符串做为参数,而后搜索有没有以该参数做为名称的Symbol值。若是有就返回,没有就新建。
var s1 = Symbol.for('foo'); var s2 = Symbol.for('foo'); s1 === s2 // true
能够返回一个已登记的Symbol类型值的key。
var s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" var s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
单例模式指一个类,任什么时候候都是返回同一个实例。咱们为了防止这个类的全局变量修改,可使用Symbol
// mod.js const FOO_KEY = Symbol.for('foo'); function A() { this.foo = 'hello'; } if (!global[FOO_KEY]) { global[FOO_KEY] = new A(); } module.exports = global[FOO_KEY];
require('./mod.js');
console.log(global[Symbol.for('foo')].foo); //hello
当其余对象使用instanceof运算符时,判断是否为该对象的实例时,会调用此方法。
class MyClass{ [Symbol.hasInstance](foo){ return foo instanceof Array; } } var result = [1,2,3] instanceof new MyClass(); console.log(result); //true
布尔值属性,表示该对象使用Array.prototype.concat() 时,是否能够展开。默认为能够展开
let arr1 = ['c', 'd']; ['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e'] let arr2 = ['c', 'd']; arr2[Symbol.isConcatSpreadable] = false; ['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
相似数组的对象也能够展开,但他的属性默认值为false,必须手动打开。
let obj = {length: 2, 0: 'c', 1: 'd'}; ['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e'] obj[Symbol.isConcatSpreadable] = true; ['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
对于一个类来讲,此属性必须写成实例的属性。
class A1 extends Array { constructor(args) { super(args); this[Symbol.isConcatSpreadable] = true; } } class A2 extends Array { constructor(args) { super(args); this[Symbol.isConcatSpreadable] = false; } }
建立实例时,默认会调用这个方法,使用这个属性返回的函数当作构造函数,来建立新的实例对象。
class MyArray extends Array { static get [Symbol.species]() { return Array; } } var a = new MyArray(1,2,3); var mapped = a.map(x => x * x); mapped instanceof MyArray // false mapped instanceof Array // true
当执行str.match(myObject)时,若是该属性存在,会调用它,返回该方法的返回值。
class MyMatcher { [Symbol.match](string) { return 'hello world'.indexOf(string); } } 'e'.match(new MyMatcher()) // 1
当该对象被string.prototype.replace方法调用时,返回该方法的返回值。
const x = {}; x[Symbol.replace] = (...s) => console.log(s); 'Hello'.replace(x, 'World') // ["Hello", "World"]
该对象被String.prototype.search方法调用时,会返回该方法的返回值。
class MySearch { constructor(value) { this.value = value; } [Symbol.search](string) { return string.indexOf(this.value); } } 'foobar'.search(new MySearch('foo')) // 0
该对象被String.prototype.split方法调用时,会返回该方法的返回值
class MySplitter { constructor(value) { this.value = value; } [Symbol.split](string) { var index = string.indexOf(this.value); if (index === -1) { return string; } return [ string.substr(0, index), string.substr(index + this.value.length) ]; } } 'foobar'.split(new MySplitter('foo')) // ['', 'bar'] 'foobar'.split(new MySplitter('bar')) // ['foo', ''] 'foobar'.split(new MySplitter('baz')) // 'foobar'
指向对象的默认遍历器方法。对象进行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属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
Symbol.toPrimitive被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式。
1. Number:该场合须要转成数值
2. String : 该场合须要转成字符串
3. Default:该场合能够转成数值或字符串
let obj = { [Symbol.toPrimitive](hint) { switch (hint) { case 'number': return 123; case 'string': return 'str'; case 'default': return 'default'; default: throw new Error(); } } }; 2 * obj // 246 3 + obj // '3default' obj == 'default' // true String(obj) // 'str'
在该对象上面调用Object.prototype.toString方法时,若是这个属性存在,他的返回值会出如今方法返回值中。
// 例一 ({[Symbol.toStringTag]: 'Foo'}.toString()) // "[object Foo]" // 例二 class Collection { get [Symbol.toStringTag]() { return 'xxx'; } } var x = new Collection(); Object.prototype.toString.call(x) // "[object xxx]"
相似于数组,全部成员的值都是惟一的,没有重复的值。
const s = new Set(); [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x)); for (let i of s) { console.log(i); } // 2 3 5 4
Set有如下属性
constructor:构造函数,默认就是Set函数
size:返回set实例的成员总数
实例方法分为两大类,操做方法、遍历方法
操做方法:
add(value):添加某个值,返回Set结构自己
delete(value):删除某个值,返回一个布尔值,表示删除是否成功
has(value):返回一个布尔值,表示该值是否为Set成员
clear():清除全部成员,没有返回值
Array.form:能够将Set结构转为数组
这样去重复,也能够写成以下格式
function dedupe(array) { return Array.from(new Set(array)); } dedupe([1, 1, 2, 3]) // [1, 2, 3]
遍历操做:
keys():返回键名遍历器
values() :返回键值遍历器
entries() : 返回键值对遍历器
forEach():使用回调函数遍历每一个成员。参数依次为键值、键名、集合自己
let set = new Set([1, 2, 3]); set.forEach((value, key) => console.log(value * 2) )
运用
let a = new Set([1, 2, 3]); let b = new Set([4, 3, 2]); // 并集 let union = new Set([...a, ...b]); // Set {1, 2, 3, 4} // 交集 let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3} // 差集 let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}
WeakSet与Set相似,也是不重复的集合,WeakSet不能进行遍历。与Set有两个区别
1. WeakSet成员只能是对象,若是是其余值会报错。
2. WeakSet中的对象都是弱引用,垃圾回收机制不考虑WeakSet对该对象的引用。
若是其余对象都不在引用该对象,那么会直接回收,不会考虑WeakSet。
因为上述特色,WeakSet成员不适合引用,由于会随时消失。
const a = [[1, 2], [3, 4]]; const ws = new WeakSet(a); // WeakSet {[1, 2], [3, 4]}
WeakSet结构有三个方法
add(value):添加一个新成员
delete(value):删除指定成员
has(value):是否包含指定成员
键值对的集合,键的范围不限于字符串。提供了值-值对应。
const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false
Map结构的实例有如下属性和操做方法。
Size:返回Map结构的成员总数
set(key,value):设置键名对应的值,而后返回整个Map结构
get(key):读取key的键值,找不到返回undefined
has(key):返回一个布尔值,表示某个键是否存在
delete(key):删除某个键,返回true,若是失败返回false
clear():清除全部成员,没有返回值
遍历方法
keys():返回键名遍历器
values() :返回键值遍历器
entries() : 返回键值对遍历器
forEach():使用回调函数遍历每一个成员。参数依次为键值、键名、集合自己
与其余数据结构转换
Map转数组
const myMap = new Map() .set(true, 7) .set({foo: 3}, ['abc']); [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
数组转Map
new Map([ [true, 7], [{foo: 3}, ['abc']] ]) // Map { // true => 7, // Object {foo: 3} => ['abc'] // }
Map转对象
function strMapToObj(strMap) { let obj = Object.create(null); for (let [k,v] of strMap) { obj[k] = v; } return obj; } const 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}) // Map {"yes" => true, "no" => false}
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}'
JSON转Map
function jsonToStrMap(jsonStr) { return objToStrMap(JSON.parse(jsonStr)); } jsonToStrMap('{"yes": true, "no": false}') // Map {'yes' => true, 'no' => false}
WeakMap结构与Map结构相似,用于生成键值对的集合
WeakMap与Map的区别有两点
1. WeakMap只接受对象做为键名,不接受其余的值做为键名
2. WeakMap的键名所指的对象,不计入垃圾回收机制
用来修改某些操做的默认行为,在目标对象以前架设一层拦截,外界对对象的访问都必须先经过这层拦截。
ES6原生提供Proxy构造函数,用来生成Proxy实例
var proxy = new Proxy(target, handler);
var obj = new Proxy({},{ get:function(target,key,receiver){ console.log(`get ${key}`); return Reflect.get(target,key,receiver); }, set:function(target,key,value,receiver){ console.log(`set ${key}`); return Reflect.set(target,key,value,receiver); } }); obj.count = 1; // set count obj.count++; //get count set count
Proxy接受两个参数,
第一个参数是所代理的目标,及若是没有Proxy介入,原来访问的对象
第二个参数是一个配置对象,对于每个被代理的操做,须要提供一个对应的处理函数。
能够将Proxy对象,设置到object.proxy属性,从而能够在object对象上调用
var object = { proxy: new Proxy(target, handler) };
proxy实例也能够做为其余对象的原型对象
同一个拦截器函数,能够设置拦截多个操做。
下面是Proxy支持的拦截操做
get(target,propKey,receiver):拦截对象属性的读取,好比proxy.foo和proxy['foo']
set(target,propKey,value,receiver):拦截对象属性的设置,返回一个布尔值
has(target,propKey):拦截propKey in proxy的操做,返回一个布尔值
用来拦截HasProperty操做,即判断对象是否具备某个属性时。典型的操做就是in运算符
var handler = { has(target,key){ if(key[0] === '_'){ return false; } return key in target; } }; var target = { _prop:'foo',prop:'foo' }; var proxy = new Proxy(target,handler); console.log('_prop' in proxy); // false console.log('prop' in proxy); // true
deleteProperty(target,propKey):拦截delete proxy[propKey]的操做,返回一个布尔值
deleteProperty方法用于拦截delete操做,若是这个方法抛出错误或者返回false,当前属性没法被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
ownKeys(target):
拦截Object.getOwnPropertyNames(proxy)\Object.getOwnPropertySymblos(proxy)\Object.keys(proxy)
返回一个数组,该方法返回目标对象全部自身的属性和属性名,而Object.keys的返回值结果仅包括目标对象自身的可遍历属性
let target = { _bar: 'foo', _prop: 'bar', prop: 'baz' }; let handler = { ownKeys (target) { return Reflect.ownKeys(target).filter(key => key[0] !== '_'); } }; let proxy = new Proxy(target, handler); for (let key of Object.keys(proxy)) { console.log(target[key]); } // "baz"
getOwnPropertyDescriptor(target,propKey):
拦截Object.getOwnPropertyDescriptor(proxy,propkey),返回属性的描述对象
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.getOwnPropertyDescriptor(proxy, '_foo') // undefined Object.getOwnPropertyDescriptor(proxy, 'baz') // { value: 'tar', writable: true, enumerable: true, configurable: true }
defineProperty(target,propKey,proDesc):
拦截Object.defineProperty(proxy,propkey,propDesc)/Object.defineProperties(proxy,proDesc)返回一个布尔值
var handler = { defineProperty (target, key, descriptor) { return false; } }; var target = {}; var proxy = new Proxy(target, handler); proxy.foo = 'bar'
preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
var p = new Proxy({}, { preventExtensions: function(target) { console.log('called'); Object.preventExtensions(target); return true; } }); Object.preventExtensions(p) // "called" // true
getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
var proto = {}; var p = new Proxy({}, { getPrototypeOf(target) { return proto; } }); Object.getPrototypeOf(p) === proto // true
isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
var p = new Proxy({}, { isExtensible: function(target) { console.log("called"); return true; } }); Object.isExtensible(p) // "called" // true
setPrototypeOf(target,proto):拦截Object.setPrototypeOf(proxy,proto),返回一个布尔值
var handler = { setPrototypeOf (target, proto) { throw new Error('Changing the prototype is forbidden'); } }; var proto = {}; var target = function () {}; var proxy = new Proxy(target, handler); Object.setPrototypeOf(proxy, proto); // Error: Changing the prototype is forbidden
apply(target,object,args):拦截proxy实例做为函数调用的操做,好比proxy(...args)/proxy.call(object,...args)/proxy.apply(...)
apply方法拦截函数的调用,call和apply操做。方法能够接受三个参数,分别是目标对象、上下文对象、参数数组。
var twice = { apply(target,ctx,args){ return Reflect.apply(...arguments) * 2; //return Reflect.apply(target,ctx,args) * 2; } }; function sum(left,right){ return left+right; }; var proxy = new Proxy(sum,twice); console.log(proxy(1,2)); // 6 console.log(proxy.apply(null,[1,2])); // 6 console.log(proxy.call(null,1,2)); // 6
construct(target,args):拦截Proxy实例做为构造函数调用的操做,好比new Proxy(...args)
construct方法能够接受两个参数
1. target:目标对象
2. 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
Proxy.revocable方法返回一个可取消的Proxy实例
let target = {}; let handler = {}; let {proxy, revoke} = Proxy.revocable(target, handler); proxy.foo = 123; proxy.foo // 123 revoke(); proxy.foo // TypeError: Revoked
Proxy代理的状况下,目标对象内部的this关键字会指向Proxy代理
const target = { m: function () { console.log(this === proxy); } }; const handler = {}; const proxy = new Proxy(target, handler); target.m() // false proxy.m() // true
Reflect对象与Proxy对象同样,也是ES6为了操做对象而提供的新API
Reflect对象的设计目的有这样几个
1. 将Object对象的一些明显属于语言内部的方法,放到Reflect对象上。
2. 修改某些Object方法的返回结果,让其变得更合理。
// 老写法 try { Object.defineProperty(target, property, attributes); // success } catch (e) { // failure } // 新写法 if (Reflect.defineProperty(target, property, attributes)) { // success } else { // failure }
3. 让Object操做都变成函数行为。某些Object操做是命令式的,好比name in obj
// 老写法 'assign' in Object // true // 新写法 Reflect.has(Object, 'assign') // true
4. Reflect对象的方法与Proxy对象的方法一一对应,主要Proxy对象的方法,Reflect就有
var loggedObj = new Proxy(obj, { get(target, name) { console.log('get', target, name); return Reflect.get(target, name); }, deleteProperty(target, name) { console.log('delete' + name); return Reflect.deleteProperty(target, name); }, has(target, name) { console.log('has' + name); return Reflect.has(target, name); } });
Reflect对象一共有13个静态方法
Reflect.apply(target,thisArg,args)
用于绑定this对象后执行给定函数
const ages = [11, 33, 12, 54, 18, 96]; // 旧写法 const youngest = Math.min.apply(Math, ages); const oldest = Math.max.apply(Math, ages); const type = Object.prototype.toString.call(youngest); // 新写法 const youngest = Reflect.apply(Math.min, Math, ages); const oldest = Reflect.apply(Math.max, Math, ages); const type = Reflect.apply(Object.prototype.toString, youngest, []);
Reflect.construct(target,args)
方法等同于new target(...args),提供了一种不使用new,来调用构造函数的方法
function Greeting(name) { this.name = name; } // new 的写法 const instance = new Greeting('张三'); // Reflect.construct 的写法 const instance = Reflect.construct(Greeting, ['张三']);
Reflect.get(target,name,receiver)
查找并返回target对象的name属性,若是有读取函数,则读取函数的this绑定receiver
var myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; }, }; var myReceiverObject = { foo: 4, bar: 4, }; Reflect.get(myObject, 'baz', myReceiverObject) // 8
Reflect.set(target,name,value,receiver)
设置target对象的name属性等于value,若是设置了赋值函数,则赋值函数this绑定receiver
var myObject = { foo: 4, set bar(value) { return this.foo = value; }, }; var myReceiverObject = { foo: 0, }; Reflect.set(myObject, 'bar', 1, myReceiverObject); myObject.foo // 4 myReceiverObject.foo // 1
Reflect.defineProperty(target,name,desc)
用来为对象定义属性。若是第一个参数不是对象,就会抛出错误
function MyDate() { /*…*/ } // 旧写法 Object.defineProperty(MyDate, 'now', { value: () => Date.now() }); // 新写法 Reflect.defineProperty(MyDate, 'now', { value: () => Date.now() });
Reflect.deleteProperty(target,name)
等同于delete obj[name],用于删除对象的属性。
若是删除成功,或者删除属性不存在,返回true。
删除失败,或者被删除的属性依然存在,返回false。
const myObj = { foo: 'bar' }; // 旧写法 delete myObj.foo; // 新写法 Reflect.deleteProperty(myObj, 'foo');
Reflect.has(target,name)
Reflect.has方法对应name in obj里面的in运算符
var myObject = { foo: 1, }; // 旧写法 'foo' in myObject // true // 新写法 Reflect.has(myObject, 'foo') // true
Reflect.ownKeys(target)
方法用于返回对象的全部属性
var myObject = { foo: 1, bar: 2, [Symbol.for('baz')]: 3, [Symbol.for('bing')]: 4, }; // 旧写法 Object.getOwnPropertyNames(myObject) // ['foo', 'bar'] Object.getOwnPropertySymbols(myObject) //[Symbol.for('baz'), Symbol.for('bing')] // 新写法 Reflect.ownKeys(myObject) // ['foo', 'bar', Symbol.for('baz'), Symbol.for('bing')]
Reflect.isExtensible(target)
返回布尔值,表示当前对象是否能够扩展
const myObject = {}; // 旧写法 Object.isExtensible(myObject) // true // 新写法 Reflect.isExtensible(myObject) // tru
Reflect.preventExtensions(target)
用于让一个对象变为不可扩展,返回一个布尔值,表示是否操做成功
var myObject = {}; // 旧写法 Object.isExtensible(myObject) // true // 新写法 Reflect.preventExtensions(myObject) // true
Reflect.getOwnPropertyDescriptor(target, name)
用于获得指定属性的描述对象,若是第一个参数不是对象,不报错返回undefined。
var myObject = {}; Object.defineProperty(myObject, 'hidden', { value: true, enumerable: false, }); // 旧写法 var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden'); // 新写法 var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');
Reflect.getPrototypeOf(target)
方法用于读取对象的__prop__的属性,对应Object.getPrototypeOf
const myObj = new FancyThing(); // 旧写法 Object.getPrototypeOf(myObj) === FancyThing.prototype; // 新写法 Reflect.getPrototypeOf(myObj) === FancyThing.prototype;
Reflect.setPrototypeOf(target, prototype)
方法用于设置对象的__prop__属性,返回第一个参数对象
const myObj = new FancyThing(); // 旧写法 Object.setPrototypeOf(myObj, OtherThing.prototype); // 新写法 Reflect.setPrototypeOf(myObj, OtherThing.prototype);
异步编程的一种解决方案,比回调函数和事件,更合理和强大。ES6将其写入语言标准,提供了Promise对象
Promise就是一个容器,保存某个将来才会结束的事件(一般是一个异步操做)的结果。
从语法上来讲,Promise是一个对象,能够获取异步信息,提供统一API,各类操做均可以用一样的方法进行处理。
Promise对象有两个特色
1. 对象的状态不受外界影响,Promise对象表明一个异步操做,三种状态:Pending进行中,Resolved已完成,Rejected已失败
只有异步操做的结果,能够决定当前是哪种状态,任何其余操做都没法改变这个状态。
2. 一旦状态改变,就不会再变,任什么时候候均可以获得这个结果。Promise对象的状态改变,只有两种可能
从Pending改变为Resolved
从Pending改变为Rejected
只要这两种状况发生了,状态就凝固了,不会再变了。会一直保持这个结果。
有了Promise对象,能够将异步操做以同步操做的流程表达出来,避免了层层嵌套的回调函数。提供了统一的接口,操做更方便
Promise缺点有三个
没法取消,一旦新建他就会当即执行。
若是不设置回调,异常则不会反应到外部。
当处于Pending状态时,没法得知进展到哪个阶段
Promise对象是一个构造函数,用来生成Promise实例。
var promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操做成功 */){ resolve(value); } else { reject(error); } });
容许接收一个函数做为参数,函数的两个参数分别是resolve,reject,这两个参数由js引擎提供,不用本身部署。
resolve函数的做用是,将Promise对象的状态从Pending变为Resolved。在成功时调用,并将异步操做结果做为参数传递。
reject函数的做用是,将Promise对象的状态从Pending编程Rejected,在操做失败时调用,并将异步操做报出的错误做为参数传递出去。
实例生成后,能够用then方法分别制定Resolved状态和Rejected状态的回调函数。
promise.then(function(value) { // success }, function(error) { // failure });
then方法能够接受两个回调函数做为参数,第一个回调是状态为Resolved,第二个回调是状态为Rejected时调用。
其中第二个函数是可选的,两个函数都接受Promise对象传出的值做为参数。
function timeout(ms){ return new Promise((resolve,reject) => { if(ms > 1000) resolve("--成功"); else reject("--失败"); }); } timeout(1000).then((value)=>{ console.log("成功"+value); },(value)=>{ console.log("失败"+value); }); timeout(1001).then((value)=>{ console.log("成功"+value); },(value)=>{ console.log("失败"+value); });
上面例子表示,返回一个Promise实例,若是参数大于1000则改变状态为Resolved,触发then绑定事件。
Promise新建后,就会当即执行。then方法指定的回调将在当前脚本全部同步任务完成后执行,因此Resolved最后输出。
let promise = new Promise((resolve,reject)=>{ console.log("promise"); resolve(); }); promise.then(()=>console.log("Resolved.")); console.log("Hi"); // promise > Hi > Resolved
若是调用resolve函数和reject函数时带有参数,那么他们的参数会传递给回调函数。
reject函数的参数一般是Error对象的实例
resolve函数的参数除了正常值之外,还多是另外一个Promise实例,表示异步操做的结果有多是一个值,也有多是另外一个操做。
若是p1 p2都是Promise实例,可是p2的resolve方法将p1做为参数,一个异步操做的结果是返回另外一个异步操做。
此时p1状态就会传递给p2,也就是说,p1的状态决定了p2的状态。若是p1状态时Pending,那么p2的回调就会等待。
若是p1的状态时Resolve或Rejected,那么p2的回调就会当即执行。
var p1 = new Promise((resolve,reject)=>{ setTimeout(()=>reject(new Error('fail')),3000); }); var p2 = new Promise((resolve,reject)=>{ setTimeout(()=>resolve(p1),1000); }); p2.then(result=>console.log(result)).catch(error=>console.log(error.toString()));
//Error : fail
为Promise实例添加状态改变时的回调函数。第一个参数是Resolved状态的回调,第二个是Rejected状态的回调
then方法返回的是新的Promise实例,能够采用链式的then,来指定一组按照次序调用的回调函数。
var promise = new Promise((resolve,reject)=>resolve());
promise.then(()=>console.log(1)).then(()=>console.log(2));
catch方法是.then(null,rejection)的别名,用于指定发生错误时的回调函数。
针对错误的抛出能够有两种写法
var promise = new Promise((resolve,reject) => { throw "Error"; }); var promise2 = new Promise((resolve,reject) =>{ reject("Error"); }) promise.catch(error=>console.log(error)); //Error promise2.catch(error=>console.log(error)); //Error
若是在reject后面,在执行throw是没有效果的。
用于将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.all([p1, p2, p3]);
接受一个数组做为参数,参数必须是Promise对象的实例,若是不是会进行转换。
状态由p1 p2 p3来决定,分为两种状况
1. 只有状态都变成 Resolved ,p的状态才会变成 Resolved
2. 只有有一个状态为 Rejected,p的状态就会变成 Rejected
var p1 = new Promise((resolve,reject)=>resolve()); var p2 = new Promise((resolve,reject)=>resolve()); var p = Promise.all([p1,p2]).then(()=>console.log("成功")).catch(()=>console.log("失败"));
一样是将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.race([p1, p2, p3]);
有一个实例率先改变状态,p的状态就跟着改变,并执行回调函数。
将现有的对象转为Promise对象,就须要使用Promise.resolve。例如把ajax方法,转为Promise
var jsPromise = Promise.resolve($.ajax('/whatever.json'));
Promise.resolve参数分为四种状况
1. 参数是一个Promise实例
若是参数是Promise实例,那么不作任何修改原封不动的返回这个实例
2. 参数是一个thenable对象
thenable对象指具备then方法的对象,好比
let thenable = { then: function(resolve, reject) { resolve(42); } };
Promise.resolve会将这个对象转为Promise对象,而后就当即执行thenable对象的then方法
let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 });
3. 参数不具备then方法的对象,或根本就不是对象
若是参数是一个原始值或不具备then方法的对象,则返回一个新的Promise对象,状态为 d
var p = Promise.resolve("Hello");
p.then(result=>console.log(result)); //Hello
字符串不具备then方法,返回的Promise实例的状态一开始就是Resolved,回调会当即执行,同时将参数传递给回调。
4. 不带任何参数
调用时不带参数,直接返回一个Resolved状态的Promise对象。
当即resolve的Promise对象,是在本轮事件循环的结束时。
setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one'); // one // two // three
setTimeout是下一轮事件循环的开始执行
会返回一个新的Promise实例,状态为rejected
var p = Promise.reject('出错了'); // 等同于 var p = new Promise((resolve, reject) => reject('出错了')) p.then(null, function (s) { console.log(s) }); // 出错了
Promise对象的回调链,无论以then方法或catch方法结尾,最后一个方法抛出的错误,都没法捕获
由于能够提供一个done方法,老是处于回调链尾端,保证抛出任何可能出现的错误。
Promise.prototype.done = function (onFulfilled, onRejected) { this.then(onFulfilled, onRejected) .catch(function (reason) { // 抛出一个全局错误 setTimeout(() => { throw reason }, 0);
}); }; var p = Promise.resolve("Hello"); p.then(result=>{ throw new Error(result); }).done();
用于指定无论Promise对象最后状态如何,都会执行的操做。接受一个普通回调做为参数。
Promise.prototype.finally = function (callback) { let P = this.constructor; return this.then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) ); }; var p = Promise.resolve("Hello"); p.then(result=>{ throw new Error(result); }).finally(function(){ console.log("finally"); });
const preloadImage = function (path) { return new Promise(function (resolve, reject) { var image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; }); }; preloadImage("images/0.jpg") .then(()=>console.log("图片加载成功")) .catch(result=>console.log("图片加载失败"));
JS原有表示集合的数据结构,主要是数组和对象,ES6又添加了Map和Set。
须要一种统一的接口机制,来处理全部不一样的数据结构。
遍历器(Iterator)就是这样的一个机制,它是一种接口,为不一样的数据结构提供统一访问机制。
任何数据结构只要不输Iterator接口,就能够完成遍历操做(即依次处理该数据结构的全部成员)
Iterator做用有三种
1. 为各类数据结构,提供一个统一的、简单的访问接口
2. 使得数据结构的成员可以按照某种次序排列
3. ES6创造了一种新的遍历命令for...of循环,Iterator接口主要提供for...of使用
Iterator遍历过程是这样的
1. 建立一个指针对象,指向当前数据结构的起始位置。遍历器对象自己就是一个指针对象
2. 第一次调用next方法,能够将指针指向数据结构的第一个成员
3. 第二次调用next方法,能够指向第二个成员
4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置
每一次调用next方法,都会返回数据结构的当前成员的信息。返回一个包含value和done两个属性的对象。
其中value属性是当前成员的值,done属性是一个布尔值,表明遍历是否结束。若是done是true,则不会再进行循环。
var it = makeIterator(['a', 'b']); console.log(it.next()) // { value: 'a' } console.log(it.next()) // { value: 'b' } console.log(it.next()) // { done: true } function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++]} : {done: true}; } }; }
这就是一个遍历器生成函数,做用就是返回一个遍历器对象。对数组执行这个函数,会返回该数组的遍历器对象。
在ES6中,有些数据结构原生具有Iterator接口(好比数组),即不用处理,就能够被for...of循环遍历,有些不行(好比对象)。
缘由在于,这些数据结构原生部署了Symbol.iterator属性,另一些数据结构没有。
凡是部署了Symbol.iteraotr属性的数据结构,就称为部署了遍历器的接口。调用这个接口,就会返回一个遍历器对象。
ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性。
一个数据结构只要具备Symbol.iterator属性,就能够认为是可遍历的。
Symbol.iterator属性自己就是一个函数,就是当前数据结构默认的遍历器生成函数。
执行这个函数,就会返回一个遍历器,至于属性名Symbol.iterator,它是一个表达式,返回symbol对象的iterator属性
这是一个预约好的,类型为Symbol的特殊值,因此要放在括号内。
const obj = { // [Symbol.iterator] : function () { // return { // next: function () { // return { // value: 1, // done: true // }; // } // }; // } [Symbol.iterator]: () => ({ next: () => ({ value: 1, done: true }) }) }; var objs = obj[Symbol.iterator](); console.log(objs.next()); //{ value: 1, done: true }
对象obj是可遍历的,由于具备Symbol.iterator属性。执行这个属性会返回一个遍历器对象。
这个对象根本特征就是具备next方法,每次调用next方法,都会返回一个表明当前成员的信息对象。
ES6,有三类数据结构原生具备Iterator接口:数组、相似数组的对象、Set/Map结构
let arr = ['a', 'b', 'c']; let iter = arr[Symbol.iterator](); console.log(iter.next()) // { value: 'a', done: false }
上面代码中,arr是一个数组,原生就具备遍历器接口,部署在arr的Symbol.iterator属性上面。
对象之因此没有默认部署Iterator接口,是由于对象的那个属性先遍历,那个后遍历是不肯定的,须要手动指定。
遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。
不过,对象部署遍历器接口并非很必要,这是对象实际上被当作Map结构使用,ES6原生提供了。
一个对象若是要有可被for...of循环调用的Iterator接口,就必须在Symbol.iterator的属性上部署遍历器生成方法
class RangeIterator { constructor(start, stop) { this.value = start; this.stop = stop; } [Symbol.iterator]() { return this; } next() { let val = this.value; if (val < this.stop) { this.value++; return {value: val} } return {done: true}; } } function range(start,stop) { return new RangeIterator(start,stop); } for(let item of range(0,3)) { console.log(item); }
代码是一个类部署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; current = current.next; return { done: false, 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(i); } // 1 // 2 // 3
调用该方法会返回遍历器对象iterator,调用该对象的next方法,返回一个值的同时,自动将内部指针移到下一个实例
下面是为对象添加Iterator接口的例子
const obj = { data:['hello','world'], [Symbol.iterator](){ const self = this; let index = 0; return{ next(){ if(index<self.data.length) { return{value:self.data[index++]}; } else{ return{done:true}; } } } } } for(let item of obj) { console.log(item); }
对于相似数组对象存在数组键名和length属性,部署Iterator接口,有一个简便方法。
就是Symbol.iterator方法直接引用,数组的Iterator接口。
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; // 或者 NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
const obj1 = { 0:'a', 1:'b', 2:'c', length:3, [Symbol.iterator]:Array.prototype[Symbol.iterator] }; for(let item of obj1) { console.log(item); }
注意的是,必须是 0,1,2 这种属性。普通属性部署Symbol.iterator是无效的。
1. 解构赋值
对数组和Set结构进行解构赋值,会默认调用Symbol.iterator方法
const obj1 = { 0:'a', 1:'b', 2:'c', length:3, [Symbol.iterator]:Array.prototype[Symbol.iterator] }; const obj2 = new Set().add('a').add('b').add('c'); let [x,y] = obj1; let [a,b] = obj2; let [c,...rest] = obj2; console.log(x+","+y); // a,b console.log(a+","+b); // a,b console.log(c+","+rest); // a,b,c
2. 扩展运算符
使用扩展运算符,也会调用默认的Iterator接口
const str ='hello'; console.log([...str]); // [ 'h', 'e', 'l', 'l', 'o' ] const arr = ['b','c']; console.log(['a',...arr,'d']); // [ 'a', 'b', 'c', 'd' ]
上面代码的扩展运算符,就调用内部Iterator接口
这提供了一种简便机制,能够将任何部署Iterator接口的数据结构,转为数组。
只要某个数据结构部署了Iterator接口,就能够对它使用扩展运算符,将其转为数组
const obj1 = { 0:'a', 1:'b', 2:'c', length:3, [Symbol.iterator]:Array.prototype[Symbol.iterator] }; let arr = [...obj1]; console.log(arr); // [ 'a', 'b', 'c' ]
3. yield*
yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口
const obj1 = { 0:'a', 1:'b', 2:'c', length:3, [Symbol.iterator]:Array.prototype[Symbol.iterator] }; let generator = function* () { yield 1; yield* obj1; yield 5; }; for(let item of generator()) { console.log(item); // 1 a b c 5 }
4. 其余场合
数组的遍历会调用遍历器接口,因此任何接受数组做为参数的场合,其实都调用了遍历器接口
new Map([['a',1],['b',2]])
)字符串是一个相似的数组对象,也原生具备Iterator接口。
var someString = "hi"; typeof someString[Symbol.iterator] // "function" var iterator = someString[Symbol.iterator](); iterator.next() // { value: "h", done: false } iterator.next() // { value: "i", done: false } iterator.next() // { value: undefined, done: true }
上面代码中,调用Symbol.iterator方法返回一个遍历器对象,在这个遍历器对象上能够调用next实现遍历。
能够覆盖原生的Symbol.iterator方法,达到修改遍历器行为的目的。
let str = new String("hi"); console.log([...str]); // [ 'h', 'i' ] str[Symbol.iterator] = ()=>({ next(){ if (this._first) { this._first = false; return { value: "bye" }; } else { return { done: true }; } }, _first:true }) console.log([...str]); // [ 'bye' ] console.log(str); // [String: 'hi']
上面代码中,字符串str的Symbol.iterator方法被修改了,因此扩展运算符返回的值变成bye,而字符串自己仍是hi
Symbol.iterator方法的最简单实现,仍是使用Generator函数。
let obj = { * [Symbol.iterator]() { yield 'hello'; yield 'world'; } }; for (let x of obj) { console.log(x); } // hello // world
遍历器对象除了具备next方法,还能够有return方法和throw方法。
若是本身写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是可选部署的。
若是for...of循环提早退出(break,continue),就会调用return方法。
let str = "hello"; function readLinesSync() { let index=0; return { [Symbol.iterator]:()=>({ next() { if(index<str.length) return {value:str[index++]}; else return{done:true}; }, return() { console.log("执行return方法"); return { done: true }; } }) }; }; for(let item of readLinesSync()) { console.log(item); // h 执行return方法 break; }
一个数据结构只要部署了Symbol.iterator属性,就被视为具备iterator接口,就能够用for...of循环遍历它的成员。
也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。
for...of循环可使用的范围包括数组、Set、Map,某些相似数组的对象、Generator对象、字符串。
数组原生具有iterator接口,for...of循环本质上就是调用这个接口产生的遍历器。
for...in循环,只能得到对象的键名,不能直接获取键值。
for...of循环,容许遍历得到键值。数组的遍历器接口只返回具备数字索引的属性。
let arr = [3, 5, 7]; arr.foo = 'hello'; for (let i in arr) { console.log(i); // "0", "1", "2", "foo" } for (let i of arr) { console.log(i); // "3", "5", "7" }
Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数不一样。
Generator函数有多种理解,从语法上能够理解成一个状态机,封装了多个内部状态。
执行Generator函数会返回一个遍历器对象,除了状态机,仍是一个遍历器对象生成函数。
返回的遍历器对象,能够依次遍历Generator函数内部的每个状态。
形式上,Generator函数是一个普通函数,但有两个特征
1. function关键字与函数名之间有*号
2. 函数体内部使用yield表达式,定义不一样的状态
function* hello(){ yield "hello"; yield "world"; return "ending"; } var ho = hello(); console.log(ho.next()); // { value: 'hello', done: false } console.log(ho.next()); // { value: 'world', done: false } console.log(ho.next()); // { value: 'ending', done: true }
上面代码定义了一个Generator函数,它内部有两个yield表达式,该函数有三个状态。
Generator函数的调用方式和普通函数同样,是在函数名后面加上一对圆括号。
不一样的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结束结果,而是一个指向内部状态的指针对象。
下一步,必须调用next方法,使得指针移动到下一个状态。
每次调用next方法, 内部指针就从函数头部或上一次中止的位置开始执行,直到碰见另外一个yield表达式或return语句
换言之,Generator函数是分段执行,yield表达式是暂停执行的标记,而next方法能够恢复执行。
ES6没有规定function关键字和函数名之间的星号的位置,因此如下写法均可以经过。
function * foo(x, y) { ··· } function *foo(x, y) { ··· } function* foo(x, y) { ··· } function*foo(x, y) { ··· }
Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,提供了一种可暂停的函数。yie表达式就是暂停标志。
遍历器对象的next方法运行逻辑以下
1. 遇到yield表达式,就暂停执行后面的操做,并返回对象属性value是,紧跟在yield后面的表达式的值。
2. 下一次调用next方法时,再继续执行下去,直到遇到下一个yield表达式
3. 若是没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式,做为返回对象的value属性。
4. 若是该函数没有return语句,则返回的对象的value属性值为undefined。
须要注意的是,yield表达式后面的表达式,只有当调用next方法时,才会执行。
return语句不具有位置记忆的功能,一个函数里,只能执行一次return语句,但能够执行多个yield。
function* f() { console.log('执行了!') } var generator = f(); setTimeout(function () { generator.next() }, 2000);
Generator函数能够不用yield表达式,这时就变成了一个单纯的暂缓执行函数。
上面代码中,若是函数f是普通函数,在赋值的时候就会执行。可是f是Generator函数,只有调用next方法时,才会执行。
注意:yield表达式只能在Generator函数里面使用,在其余地方使用就会报错。
Generator函数就是遍历器生成函数,所以能够把Generator赋值给对象的Symbol.iterator属性,从而使得对象具备Iterator接口
var myIterable = {}; myIterable[Symbol.iterator] = function* (){ yield 1; yield 2; yield 3; }; var result = [...myIterable]; console.log(result); // [1,2,3]
上面代码就是把Generator函数赋值给Symbol.iterator属性,让对象有了接口,就能够被...运算符遍历了。
Generator函数执行后,返回一个遍历器对象,该对象自己就具备Symbol.iterator属性,执行后返回自身。
yield表达式自己没有返回值,next方法能够带一个参数,这个参数就被当作上一个yield的返回值。
function* f() { for (var i = 0; i < 5; i++) { var reset = yield i; if (reset) { break; } } } var g = f(); console.log(g.next()); //{ value: 0, done: false } console.log(g.next(true)); //{ value: undefined, done: true }
这个功能的语法意义很重要,Generator函数从暂停状态到恢复执行,他的上下文是不变的。
经过next方法参数,就有办法在Generator函数开始运行以后,继续向函数体内注入值。
function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }
因为next方法的参数表示上一个yield表达式的返回值,因此第一次使用next方法时,不能带有参数。
V8引擎直接忽略第一次使用next方法的参数。
循环能够自动遍历Generator函数时生成的Iterator对象,再也不须要调用next方法。
function* foo() { for (let i = 0; i < 5; i++) { yield i; } } for (let i of foo()) { console.log(i); }
利用Generator函数和for...of循环,能够实现斐波那契数列
function* feibo() { let [prev, curr] = [0, 1]; while (true) { [prev, curr] = [curr, prev + curr]; yield curr; } } for (let n of feibo()) { if (n > 20) break; console.log(n); }
利用for...of循环,能够写出遍历任意对象的方法,原生Javascript对象没有遍历接口,没法使用for...of,经过Generator就能够为他加上。
function* objectEntries(obj) { let propKeys = Reflect.ownKeys(obj); for (let propKey of propKeys) { yield [propKey, obj[propKey]]; } } let jane = {first: "Jane", last: "Doe"}; for (let [key, value] of objectEntries(jane)) { console.log(`${key}-${value}`); }
或者,咱们可使用Generator函数加到对象的Symbol.iterator属性上面
function* objectEntries() { let propKeys = Object.keys(this); for (let propKey of propKeys) { yield [propKey, this[propKey]]; } } let jane = {first: "Jane", last: "Doe"}; jane[Symbol.iterator] = objectEntries; for (let [key, value] of jane) { console.log(`${key}-${value}`); }
除了for...of之外,扩展运算符 ... ,解构赋值 ,Array.from方法内部调用的,都是遍历器接口。
function* objectEntries() { let propKeys = Object.keys(this); for (let propKey of propKeys) { yield [propKey, this[propKey]]; } } let jane = {first: "Jane", last: "Doe"}; jane[Symbol.iterator] = objectEntries; for (let [key, value] of jane) { console.log(`${key}-${value}`); }
Generator函数返回的遍历器对象,都有一个throw方法,能够在函数体外抛出错误,而后在Generator函数体内捕获
var g = function* () { try { yield; } catch (e) { console.log('内部捕获', e); } }; var i = g(); i.next(); try { i.throw('a'); i.throw('b'); } catch (e) { console.log('外部捕获', e); } // 内部捕获 a // 外部捕获 b
对象i连续抛出两个错误,第一个错误被Generator内部异常捕获,第二次由于内部的异常已经执行过了,因此就抛出外部的
Generator函数返回的遍历器对象,还有一个return方法,能够返回给定的值,而且终结遍历Generator函数
function* gen() { yield 1 yield 2 } const g = gen(); console.log(g.return("c")); // { value: 'c', done: true } console.log(g.next()); // { value: undefined, done: true }
若是return不提供参数,则返回值value为undefined。
若是Generator函数里面有try...finally,则return方法会推迟到finally代码块执行完再执行。
若是在Generator函数里面调用另外一个Generator函数,是没有效果的。
这个时候就须要使用yield * 表达式,用来在一个Generator函数里面执行另外一个函数。
function * foo() { yield 1 yield 2 } function * bar() { yield 3 yield* foo(); yield 4 } for (let v of bar()) { console.log(v); } // 3 1 2 4
若是一个对象的一个属性是Generator函数,可使用星号来表示函数,一下两个对象是等效的。
let obj = { * myGeneratorMethod(){ yield 1 yield 2 } } let obj2 = { myGeneratorMethod: function*() { yield 1 yield 2 } } for (let v of obj.myGeneratorMethod()) { console.log(v); } for (let v of obj2.myGeneratorMethod()) { console.log(v); }
Generator函数的返回的遍历器对象,是函数的实例,继承了prototype方法。
Generator函数的暂停执行效果,意味着能够把异步操做写在yield表达式里面,等调用next方法时再日后执行。
至关于不须要写回调函数,异步操做的后续操做能够放在yield表达式下面,要等到调用next方法再执行。
因此Generator函数的一个重要实际意义就是用来处理异步操做,改写回调函数。
能够经过Generator函数逐行读取文本文件。下面代码使用yield表达式能够手动逐行读取文件。
function* numbers() { let file = new FileReader("numbers.txt"); try { while(!file.eof) { yield parseInt(file.readLine(), 10); } } finally { file.close(); } }
若是一个多步操做很是耗时,可使用Generator按次序自动执行全部步骤。
let steps = [step1Func, step2Func, step3Func]; function *iterateSteps(steps){ for (var i=0; i< steps.length; i++){ var step = steps[i]; yield step(); } }
steps封装了一个任务的多个步骤,iterateSteps依次给这些步骤添加上yield命令。
分解完步骤后,还能够将项目分解成多个依次执行的任务。
let jobs = [job1, job2, job3]; function* iterateJobs(jobs){ for (var i=0; i< jobs.length; i++){ var job = jobs[i]; yield* iterateSteps(job.steps); } }
数组jobs封装了一个项目的多个任务,interateJobs则依次为这些任务添加上yield命令
最后用for...of循环一次性执行全部的步骤
for (var step of iterateJobs(jobs)){ console.log(step.id); }
异步就是一个任务不是连续完成的,先执行第一段,等作好准备,在执行第二段。
好比,任务是读取文件进行处理,第一段就是发送请求读取文件,等系统返回文件,在执行第二段处理文件。
这种不连续的操做,就称为异步操做。
Javascript语言对异步编程的实现,就是用回调函数。把任务的第二部分单独写在一个函数里面。
从新执行这个任务,就直接调用这个函数。好比读取文件的操做。
fs.readFile('/etc/passwd', 'utf-8', function (err, data) { if (err) throw err; console.log(data); });
redFile第三个参数,就是回调函数,也就是任务的第二段
回调函数自己并无什么问题,可是若是出现多个回调函数嵌套,则会变成横向发展没法管理。
由于多个异步操做造成了强耦合,只要有一个操做须要为修改,它的上下层函数,就都要跟着改变。
这种状况,被称为回调函数地狱“callback hell”
使用Promise就是为了解决这个问题,容许将回调函数的嵌套,改成链式调用,连续读取多个文件。
var readFile = require('fs-readfile-promise'); readFile(fileA) .then(function (data) { console.log(data.toString()); }) .then(function () { return readFile(fileB); }) .then(function (data) { console.log(data.toString()); }) .catch(function (err) { console.log(err); });
Promise写法是回调函数的改进版,使用then之后,异步任务会变得更加清晰。
Promise最大的缺点就是代码冗余,任务都被Promise包装了,语以变得不清晰。
协程,意思是多个线程相互协做,完成异步任务。
协程大体的流程以下:
1. 协程A开始执行。
2. 协程A执行到一半,进入暂停,执行权转移到协程B。
3. (一段时间后)协程B交还执行权。
4. 协程A恢复执行。
读取文件的协程写法
function *asyncJob() { // ...其余代码 var f = yield readFile(fileA); // ...其余代码 }
asyncJob是一个协程,其中yield是关键。表示执行到此处,执行权将交给其余线程。yield是异步两个阶段的分界线。
协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续日后执行。
它的最大优势,就是代码的写法很是像同步操做。
Generator函数协程在ES6实现,最大特色就是交出函数的执行权(暂停执行)
整个Generator函数就是一个封装的异步函数,操做中须要暂停的部分都用yield注明。
function* gen(x) { var y = yield x + 2; return y; } var g = gen(1); g.next(); g.next();
调用Generator函数,返回一个内部指针g。调用g的next方法,会移动内部指针,指向第一个遇到的yield语句。
next方法是分阶段执行Generator函数,每次调用会返回一个对象。value表示yield后面表达式的值,done表示是否执行完毕。
function* gen(){ var url = 'http://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio); } var g = gen(); var result = g.next(); result.value.then(function(data){ console.log(data.json); }).then(function(data){ g.next(data) }).catch(function(data){ console.log(data); });
Generator封装一个异步操做,先读取一个接口数据,而后用JSON返回。
上面操做就是,执行Generator函数,返回一个遍历器对象,使用next方法,执行异步的第一个阶段。
Fetch返回的是一个Promise对象,所以then方法调用下一个next方法。
虽然上面的Generator函数将异步操做表示的很简洁,可是流程管理却不是很方便。
Thunk是自动执行Generator函数的一种方法。
求值策略,函数的参数到底应该什么时候求值。分为两种意见,示例以下:
var x = 1; function f(m) { return m * 2; } f(x + 5)
1.传值调用,在进入函数体以前,就计算x+5的值(等于6),再将6传入函数里面。C语言采用这种策略
2.传名调用,将表达式x+5传入函数体,只有在用到它的时候求值。Haskell语言采用这种策略。
编译器的传名调用实现,每每是将参数放到一个临时函数中,再将这个临时函数传入函数体。这个函数就是Thunk函数。
function f(m) { return m * 2; } var thrun = function () { return x + 5; } function f(thrun) { return thrun() * 2; }
Javascript语言中Thrun函数替换的不是表达式,而是多参函数,将其替换成一个只接受回调函数做为参数的单参数函数
// 正常版本的readFile(多参数版本) fs.readFile(fileName, callback); // Thunk版本的readFile(单参数版本) var Thunk = function (fileName) { return function (callback) { return fs.readFile(fileName, callback); }; }; var readFileThunk = Thunk(fileName); readFileThunk(callback);
fs模块的readFile方法是一个多参函数,参数分别为文件名和回调函数。通过转换它变成了一个单参函数,只接受回调函数做为参数。
这个单参版本就叫作Thunk函数。任何函数只要有回调函数,就能写成Thunk函数的形式。例如:
// ES5版本 var Thunk = function(fn){ return function (){ var args = Array.prototype.slice.call(arguments); return function (callback){ args.push(callback); return fn.apply(this, args); } }; }; // ES6版本
使用上面的转换器,生成fs.readFile的Thunk函数。
var readFileThunk = Thunk(fs.readFile); readFileThunk(fileA)(callback); //完整示例 function f(a, cb) { cb(a); } const ft = Thunk(f); ft(1)(console.log) // 1
生产环境的转换器,建议使用Thunkify模块。
首先安装:npm install thunkify
使用方法以下:
var thunkify = require("thunkify"); var fs = require("fs"); var read = thunkify(fs.readFile); read("package.json")(function(err,str){ console.log(err); console.log(str.toString()); })
Thunk函数能够用于Generator函数的自动流程管理,能够自动执行
若是须要异步操做中,保证前一步执行完,才能执行后一步,就须要Thunk函数。
Generator函数封装了两个异步操做,yield命令用于将程序的执行权移出Generator函数。
Thunk函数能够在回调函数里面,将执行权交还给Generator函数。
var thunkify = require("thunkify"); var fs = require("fs"); var readFileThunk = thunkify(fs.readFile); var gen = function* () { var r1 = yield readFileThunk("package.json"); console.log(r1.toString()); var r2 = yield readFileThunk("demo1.html"); console.log(r2.toString()); } 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函数的内部指针,表示目前执行到哪一步。next方法负责将指针下移,返回该步的信息(value属性和done属性)
Generator函数的执行过程,其实就是将同一个回调函数,反复传入next方法的value属性。变成递归来自动完成这个过程。
Thunk函数真正的用途,在于自动执行Generator函数。下面就是基于Thunk函数的Generator执行器
var thunkify = require("thunkify"); var fs = require("fs"); var readFileThunk = thunkify(fs.readFile); function run(fn) { var gen = fn(); function next(err, data) { if (data) console.log(data.length); var result = gen.next(data); if (result.done) return; result.value(next); } next(); } var g = function* () { var r1 = yield readFileThunk("package.json"); var r2 = yield readFileThunk("demo1.html"); } run(g);
run函数就是一个Generator函数的自动执行器,内部的next函数就是Thunk函数的回调函数。
next函数先将指针移到Generator函数的下一步gen.next,而后判断Generator函数是否结束result.done。
若是没有结束,就将next函数再传入Thunk函数result.value,不然就直接退出。
上面代码中,函数g封装了n个异步操做的读取文件操做,只要执行run函数,这些操做就会自动完成。
co模块是TJ Holowaychuk开发的小工具,用于Generator函数的自动执行。co模块可让你不用写Generator函数的执行器
Generator函数只要传入co函数,就会自动执行。co函数返回Promise对象,所以可使用then方法添加回调函数。
var thunkify = require("thunkify"); var fs = require("fs"); var readFile = thunkify(fs.readFile); var co = require("co"); var gen = function* () { var f1 = yield readFile("demo1.html"); var f2 = yield readFile("package.json"); console.log(f1.length); console.log(f2.length); } co(gen).then(function () { console.log('Generator 函数执行完成'); });
Co模块原理
Generator就是一个异步操做的容器,自动执行须要一种机制,当异步操做有告终果,可以自动交回执行权。
两种方法能够作到
1.回调函数,将异步操做包装成Thunk函数,在回调函数中交回执行权
2.Promise对象,将异步操做包装秤Promise对象,用then方法交回执行权
co模块就是将两种自动执行器包装成一个模块,使用co的前提条件是,Generator函数的yield命令后面,只能是Thunk函数或Promise对象
ES2017标准引入async函数
http://es6.ruanyifeng.com/#docs/async
ES6引入了Class的概念,做为对象的模板可使用class关键字定义类。
class至关于ES5的语法糖。下面示例来展现ES6写法的不一样
//ES5写法 function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function () { return "重构后的" + this.x + "," + this.y; } var P = new Point(1, 2); console.log(P.toString()); //ES6写法 class Point2 { constructor(x, y) { this.x = x; this.y = y; } toString() { return "重构后的" + this.x + "," + this.y; } } var P2 = new Point2(1, 2); console.log(P2.toString());
上面代码定义了类,constructor是构造方法,this就是实例对象。定义类的方法时,不须要添加function关键字,方法之间不须要分隔符
使用的时候,直接对类进行new命令就能够了。prototype属性在class上面继续存在,全部的方法都定义在类的prototype属性上面。
在类的实例上调用方法,其实就是调用原型上的方法。prototype对象的constructor属性指向类自己。
类的内部全部定义的方法,都是不可枚举的,ES5定义的方法能够枚举。
Object.assign方法能够向类添加多个方法
class Point { constructor(x, y) { this.x = x; this.y = y; } } Object.assign(Point.prototype, { toString() { console.log("ToString"); }, toValue() { console.log("ToValue"); } }) var P = new Point(1, 2); P.toString(); P.toValue();
类的属性名,能够采用变量来定义。
names = "toString"; class Point { constructor(x, y) { this.x = x; this.y = y; } } Object.assign(Point.prototype, { [names]() { console.log("ToString"); }, toValue() { console.log("ToValue"); } }) var P = new Point(1, 2); P.toString(); P.toValue();
类和模块的内部就是严格模式,不须要使用use strict。ES6整个语言都是严格模式
constructor方法是类的默认方法,经过new命令生成对象实例时,自动调用该方法。
一个必须拥有constructor方法,若是没有显示定义,会默认添加一个空的。
constructor方法默认返回实例对象this,能够指向返回另外一个对象。
类必须使用new,不然会报错。属性除非显式定义在其自己(this)上,不然都会定义在原型上
和函数同样,类也可使用表达式来定义。name属性是函数默认特性
const MyClass = class Me { getClassName() { return Me.name; } } let inst = new MyClass(); console.log(inst.getClassName()); //Me
上述代码中Me只在Class内部使用,指当前类。若是类的内部没有用到的话,能够省略Me写成
const MyClass = class { /* ... */ };
使用表达式能够写出当即执行的Class,person就是当即执行的类的实例
let person = new class{ constructor(name){ this.name = name; } sayName(){ console.log(this.name); } }("cxy"); person.sayName(); //cxy
经过命名来区分,_person是私有方法,Peroson是公有方法
类的方法内部若是含有this,默认指向类的实例。可是若是单独使用该方法极可能报错。
能够在构造方法中绑定this,这样就不会找不到了
或者直接使用箭头函数
class Logger { constructor() { this.printName = this.printName.bind(this); this.printName = (name = 'there') => { this.print(`Hello ${name}`); } } }
若是某个方法以前加上星号(*),就表示该方法是一个Generator函数。
const Foo = class{ constructor(...args){ this.args = args; } *[Symbol.iterator](){ for(let arg of this.args){ yield arg; } } } for(let item of new Foo(1,2,3)){ console.log(item); //1,2,3 }
若是在一个方法前面加上static关键字,就表示该方法不会被实例继承,而是直接经过类来调用。称为静态方法
class Foo{ static classMethod(){ return "Hello"; } } console.log(Foo.classMethod()); //Hello
若是静态方法包含this关键字,这个this指向类而不是实例
Foo.prop = 1; 直接类后面.就能够建立静态属性
若是构造函数不是经过new命令调用,new.target会返回undefined。类内部调用new.target,返回当前正在运行的Class。
function Person(name) { if (new.target !== undefined) { this.name = name; } else { throw new Error('必须使用 new 命令生成实例'); } }
须要注意的是,子类继承父类时,new.target会返回子类。能够利用这个特色来写出抽象类
class Shape { constructor() { if (new.target == Shape) { throw new Error("本类不能实例化"); } } }
class经过extends关键字实现继承
class Point {
}
class ColorPoint extends Point {
}
super表示父类的构造函数,能够新建父类的this对象。而且能够调用父类方法。相似于Net中的base关键字
class Shape { constructor(x, y) { if (new.target == Shape) { throw new Error("本类不能实例化"); } this.x = x; this.y = y; } toString() { return `Shape x=${this.x},y=${this.y}`; } } class Point extends Shape { constructor(x, y, z) { super(x, y); this.z = z; } toString() { console.log(`Point z=${this.z}-${super.toString()}`); } } new Point(1, 2, 3).toString(); //Point z=3-Shape x=1,y=2
子类必须在构造函数中调用super方法,不然新建实例会报错。由于子类没有本身的this对象,是继承父类的this对象
不调用super就得不到this对象。
若是子类没有定义constructor方法,在继承状态下会默认添加以下代码。
class ColorPoint extends Point { } // 等同于 class ColorPoint extends Point { constructor(...args) { super(...args); } }
须要注意,必须在使用this以前,调用super。不然会报错
Object.getPrototypeOf
方法能够用来从子类上获取父类能够用来判断是否继承
super关键字既能够当作函数,也能够当作对象
当作函数的时候,表示父类的构造函数。ES6要求子类的构造函数必须执行一次super函数。
当作对象的时候,在普通方法中表示父类原型对象,在静态方法中指向父类。
class A { constructor() { this.p = 2; } } class B extends A { get m() { return super.p; } } let b = new B(); b.m // undefined
定义在父类实例上的方法或属性是没法经过super调用的。定义在原型是能够的
class A {} A.prototype.x = 2; class B extends A { constructor() { super(); console.log(super.x) // 2 } } let b = new B();
Mixin指多个对象合成一个新的对象,新对象具备各个组成成员的接口,
function mix(...mixins) { class Mix {} for (let mixin of mixins) { copyProperties(Mix, mixin); // 拷贝实例属性 copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性 } return Mix; } function copyProperties(target, source) { for (let key of Reflect.ownKeys(source)) { if ( key !== "constructor" && key !== "prototype" && key !== "name" ) { let desc = Object.getOwnPropertyDescriptor(source, key); Object.defineProperty(target, key, desc); } } }
上面的mix函数能够将多个对象合成一个类,使用的时候只要继承这个类便可
class DistributedEdit extends mix(Loggable, Serializable) { // ... }
提案阶段 http://es6.ruanyifeng.com/#docs/decorator
ES6以前制定了一些模块加载方案,最主要的有CommonJS和AMD两种,前者用于服务器,后者用于浏览器。
ES6在语言层面上,实现了模块功能。彻底能够取代CommonJS和AMD
ES6的模块设计思想是尽可能的静态化,使得编译器就能肯定模块的依赖关系,以及输入和输出的变量
CommonJS模块就是对象,输入时必须查找对象属性
// CommonJS模块 let { stat, exists, readFile } = require('fs'); // 等同于 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile;
上面代码的实质是总体加载fs模块,生成一个对象_fs,而后从这个对象上读取三个方法。
ES6模块不是对象,而是经过export命令显式指定输出的代码。在经过import命令输入。
注意:使用import没法再js中进行调试代码,只能调试babel转换后的代码。
// ES6模块 import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块中加载三个方法,其余不加载。这种加载称为“编译时加载”或静态加载,即ES6编译时就完成模块加载。
效率比CommonJS模块的加载高出不少,这也致使了无法引用ES6模块自己,由于他不是对象。
模块功能主要是两个命令构成:export和import。export命令用于规定模块的对外接口,Import命令用于输入其余模块提供的功能,
一个模块就是一个独立的文件,该文件内部的全部变量,外部没法获取。
若是你但愿外部可以获取某个变量,必须使用export关键字输出该变量或者使用export{}输出变量,{}不可省略
//Demo2 export let firstName = "Demo2" export let lastName = "js" or let firstName = "Demo2" let lastName = "js" export {firstName, lastName} //Demo1 import {firstName,lastName} from "./Demo2"; console.log(`${firstName}-${lastName}`); //Demo2-js
优先推荐export{}这种输出方式,能够直接在脚本尾部清楚的看出。
export命令除了输出变量,还能够输出函数或类,例如
export function multiply(x, y) { return x * y; };
可使用as关键字对接口进行重命名。
// 写法一 export var m = 1; // 写法二 var m = 1; export {m}; // 写法三 var n = 1; export {n as m};
另外,export语句输出的接口,与其对应的值是动态绑定的,经过接口能够取得模块内部实时的值
//Demo2 let foo = 'bar'; setTimeout(() => foo = 'baz', 500) export {foo} //Demo1 import {foo} from './Demo2' setTimeout(() => console.log(foo), 100); //bar setTimeout(() => console.log(foo), 501); //baz
这一点与CommonJS不一样,CommonJS模块输出的值得缓存,不存在动态更新。
export命令只能存在于模块顶层,不能再函数里面写。
export定义模块对外接口后,其余JS文件就能够经过import加载
import命令接受一对大括号,指定其余模块须要导入的变量名,大括号的变量名必须与对外接口相同。
若是想设置别名,须要使用as关键字。
import { lastName as surname } from './profile';
impor的from是指定模块文件的位置,能够是相对路径,也能够是绝对路径。.js后缀能够省略。
若是只是模块名,不带有路径,那么必须有配置文件,告诉js引用模块的位置。
import命令有提高效果,会自动提高到模块头部,首先执行。
import语句会执行全部加载的模块,所以能够写成。可是不会输入任何值
import 'lodash';
目前,经过Babel转码,CommonJS模块的require命令和import命令能够共存
可是最好不这样作,由于import在静态解析阶段执行。是模块中最先执行的,会有可能出问题。
能够经过*号来加载整个模块,全部输出值都在这个对象上面。
//Demo2 let firstName = "Demo2" let lastName = "js" export {firstName, lastName}; //Demo1 import * as Demo2 from './Demo2' console.log(`${Demo2.firstName}.${Demo2.lastName}`); //Demo2.js
export default命令,能够为模块指定默认输出。import能够为该模块提供任何名字。export default也能够用于非匿名函数 export default foo;
//Demo2 export default function(){ console.log("foo"); } //Demo1 import foo from './Demo2' foo(); //foo
一个模块只能有一个默认输出。在import的时候注意不要加大括号
若是在一个模块中,先输入后输出同一个模块。import语句能够与export写在一块儿。
export { foo, bar } from 'my_module'; // 等同于 import { foo, bar } from 'my_module'; export { foo, bar };
模块的接口更名和总体输出,也能够这么写
// 接口更名 export { foo as myFoo } from 'my_module'; // 总体输出 export * from 'my_module';
修改默认接口的写法以下
export { es6 as default } from './someModule'; // 等同于 import { es6 } from './someModule'; export default es6;
也能够修改默认接口为显式接口
export { default as es6 } from './someModule';
模块之间也能够进行继承,
//Demo3 export function foo(){ console.log("foo"); } //Demo2 export * from './Demo3' export default function fo() { console.log("fo"); } //Demo1 import Demo2,* as Demo from './Demo2' Demo2(); Demo.foo();
浏览器加载ES6模块,也使用<script>标签,可是必须加入type="module"属性。注意:只有谷歌浏览器支持ES6写法,import这种只有使用webpack进行打包才可使用。
<script type="module" src="dist/Demo1.js"></script>
浏览器对于带有type="module"的script都是异步加载,不会形成堵塞浏览器,等同于打开了defer属性
若是页面有多个moudle会按照页面出现顺序依次加载。使用async属性,不会按照出现顺序加载。
他们有两大重要差别
1.CommonJS模块输出的是一个值得拷贝,ES6模块输出的是值得引用
2.CommonJS模块是运行时加载,ES6模块时编译时输出接口
let取代var,由于没有反作用
全局常量优先使用const
静态字符串一概使用单引号或反引号,不使用双引号
数组成员给变量赋值时,优先使用解构赋值
const arr = [1, 2, 3, 4]; // good const [first, second] = arr;
函数的参数若是是对象成员,优先使用解构赋值
// good function getFullName(obj) { const { firstName, lastName } = obj; } // best function getFullName({ firstName, lastName }) { }
若是函数返回多个值,优先使用对象的解构赋值,而不是数组赋值。这样便于之后添加返回值,以及更改返回值的顺序
// good function processInput(input) { return { left, right, top, bottom }; } const { left, right } = processInput(input);
单行定义对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。
// good const a = { k1: v1, k2: v2 }; const b = { k1: v1, k2: v2, };
对象尽可能静态化,一旦定义就不得随意添加新的属性。若是添加属性不可避免使用Object.assign方法
// if reshape unavoidable const a = {}; Object.assign(a, { x: 3 }); // good const a = { x: null }; a.x = 3;
若是属性名是动态的,能够在建立对象时,使用属性表达式定义。
// good const obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true, };
对象属性和方法尽可能采用简洁写法,编译描述和书写。
使用扩展运算符拷贝数组
// good const itemsCopy = [...items];
使用Array.from方法,将相似数组的对象转为数组。
const foo = document.querySelectorAll('.foo'); const nodes = Array.from(foo);
当即执行函数能够写成箭头函数的形式
(() => { console.log('Welcome to the Internet.'); })();
须要使用函数表达式的场合,尽可能用箭头函数替代。由于更简洁而且绑定了this
// good [1, 2, 3].map((x) => { return x * x; }); // best [1, 2, 3].map(x => x * x);
箭头函数取代了Function.prototype.bind不该再用self/_this/that绑定this
// bad const self = this; const boundMethod = function(...params) { return method.apply(self, params); } // acceptable const boundMethod = method.bind(this); // best const boundMethod = (...params) => method.apply(this, params);
简单的、单行的、不会复用的函数,建议采用箭头函数。不然仍是应该采用传统函数的写法
全部配置项都应集中在一个对象,放在最后一个参数,布尔值不可直接做为参数
// good function divide(a, b, { option = false } = {}) { }
不要再函数体内使用arugments变量,使用rest运算符(...)代替。
由于rest运算符显示代表你想要获取参数,而arugments是一个相似数组的对象,rest运算符能够提供一个真正的数组。
// good function concatenateAll(...args) { return args.join(''); }
使用默认值语法设置函数参数的默认值
// good function handleThings(opts = {}) { // ... }
若是须要key:value的数据结构,使用Map结构。由于Map内置遍历机制
let map = new Map(arr); for (let key of map.keys()) { console.log(key); } for (let value of map.values()) { console.log(value); } for (let item of map.entries()) { console.log(item[0], item[1]); }
使用Class取代须要prototype的操做,由于Class的写法更简洁,更易于理解。
// bad function Queue(contents = []) { this._queue = [...contents]; } Queue.prototype.pop = function() { const value = this._queue[0]; this._queue.splice(0, 1); return value; } // good class Queue { constructor(contents = []) { this._queue = [...contents]; } pop() { const value = this._queue[0]; this._queue.splice(0, 1); return value; } }
使用extends实现继承,这样更简单,不会有被破坏instanceof运算的危险。
// good class PeekableQueue extends Queue { peek() { return this._queue[0]; } }
Module语法是JavaScript模块的标准写法,坚持使用这种写法。使用import取代require,使用export取代module.exports
// commonJS的写法 var React = require('react'); var Breadcrumbs = React.createClass({ render() { return <nav />; } }); module.exports = Breadcrumbs; // ES6的写法 import React from 'react'; class Breadcrumbs extends React.Component { render() { return <nav />; } }; export default Breadcrumbs;
若是模块只有一个输出值,就是用export default,若是有多个输出值就不使用。不要同时使用两个
不要在模块中使用通配符,这样能够确保你的模块中有一个默认输出
// bad import * as myObject from './importModule'; // good import myObject from './importModule';
若是模块默认输出一个函数,首字母小写
若是模块默认输出一个对象,首字母大写
语法规则和代码风格检测工具,确保语法正确,风格统一
安装:npm i -g eslint
安装插件:
npm i -g eslint-config-airbnb npm i -g eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
根目录新建.eslintrc文件配置
{ "extends": "eslint-config-airbnb" }
建立不符合规则的js
var unusued = 'I have no purpose!'; function greet() { var message = 'Hello, World!'; alert(message); } greet();
使用ESLint检查:eslint index.js
$