《ES6标准入门》(第3版作者:阮一峰)读书笔记 9-18章
国内十分有名的一本书--《ES6标准入门》进行个人的读书总结,对书中的知识点进行实践验证和记录
ES6引入一个新的原始数据类型Symbol,前六种分别是:Undefined、Null、、Boolean、String、Number、Object。可以作为属性名。保证每个属性的名字都是独一无。
let s = Symbol();
typeof s; // "symbol"
Symbol函数可以接受一个字符串作为参数,主要是为了区分。
var s1 = Symbol('foo');
var s2 = Symbol('bar');
console.log(s1,s2) // Symbol(foo) Symbol(bar)
如果Symbol的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后再生成一个Symbol值。
const obj = {
toString(){
return 'abc'
}
}
const sym = Symbol(obj);
sym; // Symbol(abc)
const obj = {}
const sym = Symbol(obj);
sym; // Symbol([object Object])
没有参数的情况
var s1 = Symbol();
var s2 = Symbol();
s1 === s2; // false
有参数的情况
var s1 = Symbol('foo');
var s2 = Symbol('foo');
s1 === s2; // false
不能与其他值进行运算,否则会报错。
var sym = Symbol('foo');
"your symbol is" + sym // Uncaught TypeError: Cannot convert a Symbol value to a string
var sym = Symbol('foo');
`your symbol is ${sym}` // Uncaught TypeError: Cannot convert a Symbol value to a string
但是Symbol值可以显式转换为字符串。
var sym = Symbol('symbol value')
String(sym) // "Symbol(symbol value)"
sym.toString() // "Symbol(symbol value)"
另外Symbol值也可以转为布尔值,但是不能转换为数值。
var sym = Symbol('symbol value')
Boolean(sym) // true
var sym = Symbol('symbol value')
Number(sym) // Uncaught TypeError: Cannot convert a Symbol value to a number
Symbol值是不相等,可以作为标识符用于对象的属性名。 var mySymbol = symbol(); 第一种写法 var a = {}; a[mySymbol] = "hello!" 第二种写法 var a = { [mySymbol]: 'hello!' } 第三种写法 var a = {}; Object.defineProperty(a,mySymbol,{value:'Hello!'}) 不能使用点运算符设置Symbol属性名
var mySymbol = Symbol();
var a = {};
a.mySymbol = 'Hello!';
console.log(a[mySymbol]) // undefined
console.log(a['mySymbol']) // Hello!'(错误的设置了一个字符串属性名)
在对象内部定义Symbol属性时,必须放在方括号内,否则又会错误的设置为字符串属性名。
let s = Symbol();
let obj ={
[s]: function(){}
}
obj[s]; // ƒ (){}
采用对象增强的写法
let s = Symbol();
let obj ={
[s](){}
}
Symbol类型还可以定义一组常量,保证这组常量的值不相等。
let obj= {
a: Symbol('a'),
b: Symbol('b')
}
属性a和属性b绝对不相等了。
const a = Symbol();
const b = Symbol();
function judge(type){
switch(type){
case a: return 'a';
case b: return 'b';
default: return 'default';
}
}
judge(a); // "a"
常量使用Symbol值最大好处就是,不可能有其他相同的值。
略
Symbol作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回。Object.getOwnPropertySymbols方法返回一个数组。
let obj = {
a: 'a',
b: Symbol('b'),
[Symbol('c')]: 'c',
[Symbol('d')]: 'd'
}
for(let key in obj){
console.log(key)
} // a b
console.log(Object.keys(obj)) // ["a", "b"]
console.log(Object.getOwnPropertyNames(obj)) // ["a", "b"]
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(c), Symbol(d)]
全新的API--Reflect.ownKeys方法可以返回所有类型的键名。
let obj = {
a: 'a',
b: Symbol('b'),
[Symbol('c')]: 'c',
[Symbol('d')]: 'd'
}
Reflect.ownKeys(obj) // ["a", "b", Symbol(c), Symbol(d)]
有时候我们使用同一个Symbol值,可以使用Symbol.fo。
var s1 = Symbol.for('a'); // 有时直接返回,无时创建
var s2 = Symbol.for('a');
s1 === s2 // true
但是
var s1 = Symbol('b');
var s2 = Symbol.for('b');
s1 === s2 // false
var s1 = Symbol('b');
var s2 = Symbol('b'); // 每次都会创建新的,而且不相等
s1 === s2 // false
Symbol.keyFor方法返回一个已登记的Symbol类型值的key
var s1 = Symbol.for('foo');
Symbol.keyFor(s1); // "foo"
var s2 = Symbol('foo');
Symbol.keyFor(s2); // undefined
略
对象的Symbol.hasInstance属性指向内部的一个方法,对象使用instanceof运算符会调用这个方法,判断该对象是否未某个构造函数的实例。比如,foo instanceof Foo在语言内部实际调用的是Foo[Symbol.hasInstance](foo)
class MyClass{
[Symbol.hasInstance](foo){
return foo instanceof Array;
}
}
[1,2,3] instanceof new MyClass(); // true(实际调用的是内部的[Symbol.hasInstance](foo)方法)
最好不要这样子弄,改变了正确返回,例如上例应该返回false
对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象使用Array.prototype.concat()时是否可以展开。
let arr = [1,2];
['3','4'].concat(arr,'5') // ["3", "4", 1, 2, "5"](默认是可以展开的)
arr[Symbol.isConcatSpreadable] // undefined
let arr2 = [1,2];
arr2[Symbol.isConcatSpreadable] = false;
['3','4'].concat(arr2,'5') // ["3", "4",[1,2] , "5"](不能展开了,直接整个推进去了)
对象的Symbol.species属性指向当前对象的构造函数。创造实例时默认会调用这个方法,使用这个属性返回的函数当作构造函数来创造新的实例对象。
class MyArray extends Array{
static get [Symbol.species](){
return Array;
}
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x**2)
console.log(mapped instanceof MyArray) // false
console.log(mapped instanceof Array) // true(使用这个属性导致构造函数被替换,那为什么要使用它呢)
对象的Symbol.match属性指向一个函数,执行str.match(obj)时,如果该属性存在,会调用它返回该方法的返回值。
String.prototype.match(regexp)
等同于regexp[Symbol.match](this)
class MyMatcher {
[Symbol.match](str){
return 'leezw'.indexOf (str)
}
}
'e'.match(new MyMatcher()) // 1
对象的Symbol.replace属性指向一个方法,当对象被String.prototype.replace方法调用时会放回该方法的返回值。
String.prototype.replace(seachValue, replaceValue)
//等同于
searchValue[Symbol.replace](this,replaceValue)
const x = {};
x[Symbol.replace] = (...s) => console.log(s);
'Hello'.replace(x,'world') // ["Hello", "world"](两个参数被打印出来)
对象的Symbol.search属性指向一个方法,当对象被String.prototype.search方法调用时会返回该方法的返回值。
String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)
class MySearch{
construtor(value){
this.value = value;
}
[Symbol.search](str){
return str.indexOf(this.value)
}
}
'leezhenwang'.search(new MySearch('foo')) // -1
对象的Symbol.split属性指向一个方法,当对象被String.prototype.split方法调用时会返回该方法的返回值。
String.prototype.split(separator,limit)
// 等同于
separator[Symbol.split](this,limit)
class MySplit{
constructor(value){
this.value = value;
}
[Symbol.split](str){
return str.indexOf(this.value)
}
}
'lzw'.split(new MySplit('l')) // 0
对象的Symbol.iterator属性指向该对象的默认遍历器。 使对象可以使用for of
class Iterator {
*[Symbol.iterator](){
let keys = Object.keys(this);
let i = 0
while(this[keys[i]]){
yield this[keys[i++]]
}
}
}
let newIterator = new Iterator();
newIterator.a = 'a';
newIterator.b = 'b';
for(let key of newIterator){
console.log(key)
}
对象的Symbol.toPromitive属性指向一个方法,对象转换为原始类型的值使会调用这个方法,返回该对象对应的原始类型值。
let obj ={
[Symbol.toPrimitive](hint){
switch(hint){
case 'number': return 123;
case 'string': return 'lzw';
case 'default': return 'default';
default: throw new Error();
}
}
}
console.log(2*obj); // 246
console.log(3+obj); // 3default
console.log(obj == 'default') // true
String(obj) // "lzw"
对象的Symbol.toStringTag属性指向一个方法,在对象调用Object.prototype.toString方法时,如果这个属性存在,其返回值会出现在toString方法返回的字符串中,表示对象的类型。
console.log({[Symbol.toStringTag]: 'foo'}.toString()) // [object foo]
对象的Symbol.unscopeables属性指向一个对象,指定了使用with关键字时那些属性会被with环境排除。
Array.prototype[Symbol.unscopables] // {copyWithin: true, entries: true, fill: true, find: true, findIndex: true, …}
ES6提供新的数据结构Set。类似于数组但是成员值是唯一的,没有重复。Set本身是一个构造函数,用来生成Set数据结构。
const s = new Set();
[1,1,2,2,3,3,4,4,5,5,6,6].forEach(x=>s.add(x))
console.log(s) // Set(6) {1, 2, 3, 4, 5, 6}(不会重复添加值)
Set函数接收一个数组(或者其他具有iterable接口的其他数据结构)作为参数,用来初始化。
const set = new Set([1,1,1,2,3,4,6,4,4,5])
console.log(set) // Set(6) {1, 2, 3, 4, 6, 5}(不会重复)
const set = [...document.querySelectorAll('div')]; // 具有iterable接口
console.log(set)
//用于去除重复成员
[...new Set(array)]
注意'5'和5不会被认为是同样的值,但是NaN会被认为等于自身。
const set = new Set();
set.add(5);
set.add('5');
set.add(NaN);
set.add(NaN);
console.log(set) // Set(3) {5, "5", NaN}
两个对象永远是不相等的
const set = new Set();
set.add({});
set.add({});
set.add({a:1});
set.add({a:1});
console.log(set) // Set(4) {{…}, {…}, {…}, {…}}
Set结构实例有以下属性 Set.prototype.constructor: 构造函数,默认是Set函数。 Set.prototype.size: 返回Set实例的成员总数。 Set实例的操作方法: add(value): 添加某个值,返回Set结构本身 delete(value): 删除某个值,返回一个布尔值,表示删除成功与否。 has(value): 是否包含某个值。 clear(): 清除所有成员 const set = new Set();
set.add(1);
set.add(2);
set.add(2);
console.log(set.size); // 2(2重复只写入一个)
console.log(set.has(1)); // true
console.log(set.has(2)); // ture
console.log(set.has(3)); // false
console.log(set.delete(2)) // true
console.log(set) // Set(1) {1}
set.delete(3) // false(没有这个值可供删除)
去除数组重复的方法
[...(new Set([1,2,2,3,4]))] // [1, 2, 3, 4]
或者
Array.from(new Set([1,2,2,3,4,4])) // [1, 2, 3, 4]
Set结构的实例有4个遍历方法。 keys(): 返回键名的遍历器 values(): 返回键值的遍历器 entries(): 返回键值对的遍历器 forEach(): 使用回调函数遍历每个成员
let set = new Set([1,2,3])
for(let item of set.keys()){
console.log(item)
} // 1 2 3
for(let item of set.values()){
console.log(item)
} // 1 2 3
for(let item of set.entries()){
console.log(item)
} // [1, 1] [2, 2] [3, 3](两个成员值一样的)
Set结构实例默认可遍历,其默认遍历器生成函数就是它的values方法
Set.prototype[Symbol.iterator] === Set.prototype.values
let set = new Set([1,2,3]);
set.values(); // SetIterator {1, 2, 3}
可以省略values方法,直接for of 循环遍历Set
let set = new Set([1,2,3]);
for(let item of set){
console.log(item)
} // 1 2 3
forEach方法对每个成员执行某种操作。
let set = new Set([1,2,3]);
set.forEach((value,key)=>console.log(value*2,key))
set; // Set(3) {1, 2, 3}
遍历的应用 拓展运算符内部使用for of循环,所以也可以用于Set结构。
[...(new Set([1,2,2,3,3,4]))] // [1, 2, 3, 4](去除重复的数组成员)
数组的map和filter方法也可以用于Set
let set = new Set([1,2,3]);
set = new Set([...set].map(x=>x**2))
console.log(set) // Set(3) {1, 4, 9}
let set = new Set([1,2,3]);
set = new Set([...set].filter(x=>x>1))
console.log(set) // Set(2) {2, 3}
Array.from方法 遍历过程改变原来的Set结构的方法 方法一
let set = new Set([1,2,3]);
set = new Set([...set].map(x=>x**2))
console.log(set) // Set(3) {1, 4, 9}
方法二
let set = new Set([1,2,3]);
set = new Set(Array.from(set,val => val**2))
console.log(set) // Set(3) {1, 4, 9}
weakSet结构与set类似,也是不重复的值的集合。 区别一: 成员只能是对象
const ws = new WeakSet();
ws.add(1); // 报错 Uncaught TypeError: Invalid value used in weak set
const ws = new WeakSet();
ws.add({}); // 对象没问题
区别二: WeakSet的对象是弱引用,垃圾回收机制不会管weakSet有没有对该对象进行引用,所以可能会莫名其妙的消失了。该结构不可遍历。 所以为什么还要使用它?
WeakSet是一个构造函数,可以使用new 命令创建WeakSet数据结构。 const ws = new WeakSet() 任何具有iterator接口的对象都可以作为WeakSet的参数。
const ws = new WeakSet([[1,2],[3,4]]); // 数组的每一项必须是对象,数组也是对象
ws.add({});
ws; // WeakSet {Array(2), {…}, Array(2)}
拥有add、delete、has三个方法,没有size属性,不能遍历,因为成员随时消失
ws.size // undefined
使用例子
const foos = new WeakSet();
class Foo{
constructor(){
foos.add(this)
}
method(){
if(!foos.has(this)){
throw new TypeError('只能在实例上调用')
}else{
console.log('在实例上调用了')
}
}
}
let newFoo = new Foo(); // 实例在时WeakSet对应成员就肯定存在,实例不再时也不用担心内存泄露
newFoo.method(); // 在实例上调用了
Foo.prototype.method(); // 报错 Uncaught TypeError: 只能在实例上调用
js对象本质上是键值对的集合,但是只能使用字符串作为键,所以有了新的数据结构Map。反正有对应关系就行了。
const map = new Map();
const obj = {a: 1};
map.set(obj,'content');
console.log(map.get(obj)) // content
console.log(map.has(obj)) // true
console.log(map.delete(obj)) // true
console.log(map.has(obj)) // false
可以接受一个数组作为参数。数组每一项包含键值对。
const map = new Map([
['name','lzw'],
['age',18],
['job','worker']
])
console.log(map.size) // 3
console.log(map.has('name')) // true
console.log(map.get('name')) // lzw
console.log(map.delete('name')) // ture
console.log(map) // Map(2) {"age" => 18, "job" => "worker"}
不仅仅是数组,任何具有Iterator接口且每个成员都是双元素数组的数据结构都可以作为Map构造函数参数。所以Set和Map都可以生成新的Map。
let arr = [
['a','1'],
['b','2']
]
const set = new Set(arr);
const map1 = new Map(set); // 使用Set数据类型
const map2 = new Map(arr)
const map3 = new Map(map2) // 使用Map数据类型
console.log(map1,map3) // Map(2) {"a" => "1", "b" => "2"} Map(2) {"a" => "1", "b" => "2"}
同一键值后面的值会覆盖前面的值
map1.set(1,2).set(1,3) // Map(3) {"a" => "1", "b" => "2", 1 => 3}(覆盖了前面的值)
未知值返回undefined
map1.get(44) // undefined
注意:只有对同一个对象的引用,Map结构才将其视为同一个键。
const map = new Map();
map.set(['a'],111);
map.get(['a']); // undefined(两个数组虽然相等,当时内存地址不一样)
应改为
let arr = ['a']
const map = new Map();
map.set(arr,111);
map.get(arr);
引用类型值,内存地址不一致的,引用指向不一致的,即使数值相同也当作不同的键值。而简单类型值仅仅值相等即可。0 和-0视为相等,NaN视为与自身相等。null === null 、undefined === undefined
let arr = ['a']
const map = new Map();
map.set(1,111);
map.get(1); // 111(可以取出来值)
map.set(-0,000)
map.get(0) // 0
map.set(NaN,NaN)
map.get(NaN) // NaN
Map结构的实例的属性和操作方法 size属性
let map = new Map([
[1,1],
[2,2]
])
map.size; // 2
set(key,value)无则创建有则覆盖
let map = new Map([
[1,0],
])
map.set(1,1).set(2,2).set(3,3);
console.log(map) // Map(3) {1 => 1, 2 => 2, 3 => 3}
get(key)
let map = new Map([
[1,0],
])
map.get(1)) // 0
has(key)
let map = new Map([
[1,0],
])
map.has(1) // true
delete(key)
let map = new Map([
[1,0],
])
map.delete(1);
console.log(map) // Map(0) {}
clear()
let map = new Map([
[1,0],
])
map.clear();
console.log(map) // Map(0) {}
Map原生提供了3个遍历器生成函数和1个遍历方法
let map = new Map([
['key1','value1'],
['key2','value2']
])
for(let key of map.keys()){
console.log(key)
} // key1 key2
for(let value of map.values()){
console.log(value)
} // value1 value2
for(let item of map.entries()){
console.log(item)
} // ["key1", "value1"] ["key2", "value2"]
for(let [key,value] of map.entries()){ //解构上面的item
console.log(key,value)
} // key1 value1 // key2 value2
for(let [key,value] of map){ // entries()可以省略
console.log(key,value)
} // key1 value1 // key2 value2
entries()为什么可以省略? 因为
map[Symbol.iterator] === map.entries // true
结合拓展运算符
let map = new Map([
[1,'one'],
[2,'two'],
])
console.log([...map.keys()]) // [1, 2]
console.log([...map.values()]) // ["one", "two"]
console.log([...map.entries()]) // [Array(2), Array(2)]
[...map] // [Array(2), Array(2)]
再结合map和filter方法
let map = new Map([
[1,'one'],
[2,'two'],
[3,'three'],
])
const map1 = new Map([...map].filter(([key,value])=> key<3))
const map2 = new Map([...map].map(([key,value])=>[key**2,value]))
console.log(map1,map2) // Map(2) {1 => "one", 2 => "two"} Map(3) {1 => "one", 4 => "two", 9 => "three"}
forEach方法
let map = new Map([
[1,'one'],
[2,'two'],
[4,'three'],
])
map.forEach((value,key)=>{ // 值和键的位置互调了
console.log(value,key)
})
// one 1
// two 2
// three 4
第二个参数用于绑定this
let map = new Map([
[1,'one'],
[2,'two'],
[4,'three'],
])
let obj = {
forEachFunc: function(value,key){
console.log(value,key)
}
}
map.forEach(function(value,key){ // 这时候不能使用箭头函数了
this.forEachFunc(value,key)
},obj)
Map转换为数组
let map = new Map([
[1,'one'],
[2,'two'],
[3,'three'],
])
console.log([...map]) // [Array(2), Array(2), Array(2)]
数组转换为Map
let map = new Map([
[1,'one'],
[{a:1},'two'],
])
console.log(map) // Map(2) {1 => "one", {…} => "two"}
Map转为对象 如果Map的所有键都是字符串,则可以转为对象。
let map = new Map([
['a','one'],
['b','two'],
])
function strMapToObj(strMap){
let obj = Object.create(null);
for(let [key,value] of strMap){
obj[key] = value;
}
return obj;
}
strMapToObj(map); // {a: "one", b: "two"}
对象转为Map
let obj1 = {a: 1,b:2};
function objToMap(obj){
let strMap = new Map();
for(let [key,value] of Object.entries(obj)){
strMap.set(key,value)
}
return strMap;
}
objToMap(obj1) // Map(2) {"a" => 1, "b" => 2}
Map转为JSON 情况一:Map的键名都是字符串,这时可以选择转为对象JSON
function strMapToJson(strMap){
return JSON.stringify(strMapToObj(strMap))
}
function strMapToObj(strMap){
let obj = Object.create(null);
for(let [key,value] of strMap){
obj[key] = value;
}
return obj;
}
let map = new Map([
['a','one'],
['b','two'],
])
strMapToJson(map) // "{"a":"one","b":"two"}"
情况二:Map的键名有非字符串,这时可以转为数组JSON
let map = new Map([
['a','one'],
['b','two'],
[{c:3},'three'],
])
function mapToArrayJson(map){
return JSON.stringify([...map])
}
mapToArrayJson(map) // "[["a","one"],["b","two"],[{"c":3},"three"]]"
JSON转为Map 情况一:
function objToMap(obj){
let strMap = new Map();
for(let [key,value] of Object.entries(obj)){
strMap.set(key,value)
}
return strMap;
}
function jsonToStrMap(jsonStr){
return objToMap(JSON.parse(jsonStr))
}
jsonToStrMap('{"a":1,"b":2}') // Map(2) {"a" => 1, "b" => 2}
情况二:整个JSON是一个数组
function jsonToMap(jsonStr){
return new Map(JSON.parse(jsonStr))
}
console.log(jsonToMap('[["a","one"],["b","two"],[{"c":3},"three"]]')) // Map(3) {"a" => "one", "b" => "two", {…} => "three"}
WeakMap结构与Map结构类似,用于生成键值对的集合。 区别一: WeakMap只接受对象作为键名(null除外)
const wm = new WeakMap();
wm.set(1,1) // 报错 Uncaught TypeError: Invalid value used as weak map key
const wm = new WeakMap();
wm.set(null,1) // 报错 Uncaught TypeError: Invalid value used as weak map key
const wm = new WeakMap();
wm.set({},1)
console.log(wm) // WeakMap {{…} => 1}
区别二: WeakMap的键名所指对象不计入垃圾回收机制,防止出现垃圾回收机制不释放内存,需要手动释放的问题。
const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
[e1,'foo元素'],
[e2,'bar元素']
]
// 不需要e1 e2时
// 必须手动删除
arr[0] = null; // 忘记清除就会内存泄露
arr[1] = null;
利用弱引用的例子
const wm = new WeakMap();
const element = document.getElementById('one-google');
wm.set(element,'googlepage');
wm.get(element) // 'googlepage'
DOM节点对象的引用计数是1,而不是2。取消引用后内存会立即释放,即DOM节点被删除时,键值对会立即释放。WeakMap适用于对象将来消失的场景,防止内存泄露。
const wm = new WeakMap();
let key = {};
let obj = {foo:1};
wm.set(key,obj);
obj = null;
wm.get(key); // {foo: 1}(正常引用,已经存好该对象的内存地址)
key = null;
wm.get(key); // undefined(弱引用,根据key的来判断)
为什么依然可以取到值?因为键值对的值已经存好了指向{foo: 1}的指针。
// size、forEach、clear方法都不存在 const wm = new WeakMap(); console.log(wm.size); // undefined console.log(wm.forEach) // undefined console.log(wm.clear) // undefined 4个方法:get()、set()、has()、delete()
略
用途一: 注册监听事件的listener对象很适用WeakMap来实现。
const listener = new WeakMap();
const element1 = document.getElementById('user-content-wrapper')
function handler1(){
console.log('点击了')
}
listener.set(element1,handler1); // DOM节点消失监听函数自动消除
element1.addEventListener('click',listener.get(element1),false)
用途二:部署私有属性 略
Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改。设置了一个“拦截”层,外界访问必须先通过这层拦截。
var obj = new Proxy({},{
get: function(target,key,receiver){
console.log(`${target},${key}`)
return Reflect.get(target,key,receiver);
},
set: function(target,key,value,receiver){
console.log(`${target},${key},${value}`)
return Reflect.set(target,key,value,receiver);
}
})
obj.count = 1;
// [object Object],count,1
++obj.count;
// [object Object],count
// [object Object],count,2
Proxy实际上重载了点运算符,即用自己的定义覆盖了语言的原始定义。
ES6原生提供Proxy构造函数,用于生成Proxy实例。
var proxy = new Proxy(target,handler)
var proxy = new Proxy({},{
get: function(target,property){
return 'lzw'
}
})
console.log(proxy.a) // lzw
console.log(proxy.b) // lzw
技巧:将Proxy对象设置到object.proxy属性,从而可以在object对象上调用。
var object = { proxy: new Proxy(target,value) }
Proxy实例也可以作为其他对象的原型
var proxy = new Proxy({},{
get: function(){
return 'lzw'
}
})
let obj = Object.create(proxy);
obj.name; // lzw(原型上读取该属性时被拦截)
同一个拦截器函数可以设置多个操作 get(target,propKey,receiver) 拦截对象属性的读取,最后一个参数receiver是一个可选对象。 set(target,propKey,value,receiver) 拦截对象属性的设置,返回一个布尔值 has(target,propKey) 拦截propKey in proxy 操作,返回一个布尔值 deleteProperty(target,propKey) 拦截delete proxy[propKey]操作,返回一个布尔值 ownKyes(target) 拦截Object.geoOwnPrppertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。盖方法返回目标对象所有自身属性属性名。 getOwnPropertyDescriptor(target,propKey) 拦截Object.getOwnPropertyDescriptor(proxy,propKey) defineProperty(target,propKey,propDesc) Object.defineProperty(proxy,propKey,propDesc) 拦截Object.defineProperty(proxy,propKey,propDesc)、Object.defineProperties(proxy,propDescs),返回一个布尔值 preventExtentsions(target) 拦截Object.preventEventsions(proxy),返回一个布尔值 getPrototypeOf(target) 拦截Object.getPrototypeOf(proxy),返回一个布尔值 isExtensible(target) 拦截Object.isExtensible(proxy),返回一个布尔值 setPrototypeOf(target,proto) 拦截Object.setPrototypeOf(proxy,proto),返回一个布尔值。 如果目标对象时函数,那么还有两种额外操作可以拦截 apply(target,object,args) 拦截Proxy实例,并将其作为函数调用的操作,比如proxy(...args)、proxy.call(object,...args)、proxy.apply(...) construct(target,args) 拦截Proxy实例作为构造函数调用操作,比如 new Proxy(...args)
get方法用于拦截某个属性的读取操作。
var person = {
name: 'lzw'
}
var proxy = new Proxy(person,{
get: function(target,property){
if(property in target){
return target[property];
}else{
throw new ReferenceError(`${property} property does not exist.`)
}
}
})
proxy.a; // Uncaught ReferenceError: a property does not exist.
get方法可以继承
// 接上例
let obj1 = Object.create(proxy);
obj1.xxx; // Uncaught ReferenceError: xxx property does not exist.
如果一个属性不可配置或者不可写,则该属性不能被代理,通过Proxy对象访问该属性将会报错。
var target = Object.defineProperties({},{
foo:{
value: 'foo',
configurable: false,
},
bar: {
value: 'bar',
writable: false
}
})
const handler = {
get(target,propKey){
return 'foobar'
}
}
const proxy = new Proxy(target, handler);
proxy.bar; // Uncaught TypeError: 'get' on proxy: property 'bar' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'bar' but got 'foobar')
proxy.foo; // Uncaught TypeError: 'get' on proxy: property 'bar' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'bar' but got 'foobar')
set方法拦截某个属性的赋值操作
let validator ={
set: function(target,property,value){
if(property === 'num'){
if(value < 0){
throw new Error('不要写入负数')
}
}
target[property] = value;// 其他属性或者符合要求的数字直接保存
}
};
let numObj = new Proxy({},validator);
numObj.a = 1; // 正常写入不报错
numObj.num = 1;// 正常写入不报错
numObj.num = -1; // 报错 Uncaught Error: 不要写入负数
apply方法拦截函数的调用、call和apply操作 apply方法可以接受3个参数,分别是目标对象、目标对象的上下文对象和目标对象的参数数组
var handler = {
apply(target,ctx,args){
return Reflect.apply(...arguments)*2
}
}
function add(a,b){
return a+b;
}
var proxy = new Proxy(add, handler);
proxy(1,2) // 6(可以改变返回结果)
proxy.call(null,1,2) // 6
proxy.apply(null,[1,2]) // 6
has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时会被拦截。
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)
'_prop' in proxy; // false
for(let key in proxy){
console.log(key)
} // _prop prop(不能拦截for...in)
不能拦截for 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(Object.freeze(target),handler) // 冻结对象
'_prop' in proxy; // 报错 Uncaught TypeError: 'has' on proxy: trap returned falsish for property '_prop' which exists in the proxy target as non-configurable
construct方法用于new命令
var func = function(){}
var handler = {
construct: function(target,args){
return {b: args[0]*10}
}
}
var p = new Proxy(func,handler)
new p(1) // {b: 10}
deleteProperty方法用于拦截delete操作,返回false则无法删除
var target = {a: 1,_a: 2}
var handler = {
deleteProperty: function(target,key){
if(key[0] === '_'){
return false
}
}
}
var proxy = new Proxy(target,handler)
console.log(delete proxy._a) // false(无法删除)
console.log(proxy) // Proxy {a: 1, _a: 2}
defineProperty方法拦截了Object.defineProperty操作
var target = {a: 1,_a: 2}
var handler = {
defineProperty: function(target,key,descriptor){
if(key[0] === '_'){ // 某些属性不能添加
return false
}
return Object.defineProperty(target,key,descriptor)
}
}
var proxy = new Proxy(target,handler)
proxy.b = 2
proxy._b = 2;
console.log(proxy) // Proxy {a: 1, _a: 2, b: 2}(_b属性无法添加)
getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor(),返回一个属性对象或者undefined
var target = {a: 1,_a: 2}
var handler = {
getOwnPropertyDescriptor: function(target,key){
if(key[0] === '_'){
return;
}
return Object.getOwnPropertyDescriptor(target,key)
}
}
var proxy = new Proxy(target,handler)
console.log(Object.getOwnPropertyDescriptor(proxy,'_a')) // undefined
console.log(Object.getOwnPropertyDescriptor(proxy,'a')) // {value: 1, writable: true, enumerable: true, configurable: true}
getPrototypeOf方法主要用来拦截获取对象原型。包括以下操作:
Object.prototype._proto_
、Object.prototype.isPrototypeOf()
、Object.getPrototypeOf().
、Reflect.getPrototypeOf
、instanceOf
、
var target = {a: 1,_a: 2}
var handler = {
getPrototypeOf: function(target,key){
return target
}
}
var proxy = new Proxy(target,handler)
console.log(proxy.__proto__ === target); // true
console.log(target.isPrototypeOf(proxy)) // true
console.log(Object.getPrototypeOf(proxy) === target) // true
console.log(Reflect.getPrototypeOf(proxy) === target) // true
isExtentsible方法拦截Object.isExtensible操作
var p = new Proxy({},{
isExtensible: function(target){
console.log('jll');
return true;
}
})
Object.isExtensible(p)
// jll
// true
ownKeys方法用来拦截对象自身属性的读取操作。包括: Object.getOwnPropertyNames() Object.getOwnPropertySymbols() Object.keys() 例子:
var target = {
a: 1,
_a: 1,
[Symbol('a')]: 1,
[Symbol('_a')]: 1
}
var handler = {
ownKeys(obj){
return ['a'] // 不返回数组会报错
}
}
var proxy = new Proxy(target,handler);
console.log(Object.keys(proxy)) // ['a']
console.log(Object.getOwnPropertyNames(proxy)) // ['a']
console.log(Object.getOwnPropertySymbols(proxy)) // []
不可拓展时必须原样返回
preventExtensions方法拦截Object.preventExtentsions()
var target1 = {}
var handler = {
preventExtensions(target){
console.log('jll');
Object.preventExtensions(target);
return true; // 必须返回
}
}
var proxy = new Proxy(target1,handler);
Object.preventExtensions(proxy)
// jll
// true
setPrototypeOf方法主要用于拦截Object.setPrototypeOf方法
var target1 = {}
var handler = {
setPrototypeOf(target,proto){
throw new Error('不能修改原型对象')
}
}
var proxy = new Proxy(target1,handler);
Object.setPrototypeOf(proxy,target1) // Uncaught Error: 不能修改原型对象
Proxy.revocable方法返回一个可取消的Proxy实例
let target = {};
let handler = {};
let {proxy,revoke} = Proxy.revocable(target,handler)
proxy.a = 123;
console.log(proxy.a) // 123
revoke(); // 取消后无法访问
proxy.foo; // 报错 Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
Proxy代理情况下,this关键字会指向Proxy代理。
var target = {
checkBind: function (){
console.log(this === proxy)
}
}
var handler = {};
const proxy = new Proxy(target,handler);
proxy.checkBind(); // true
使用WeakMap结合对象存储时,会发生指向错误,导致取不到值
var _name = new WeakMap();
class Person{
constructor(name){ // 实例化时设置WeakMap键值对
_name.set(this,name)
}
get name(){
return _name.get(this)
}
}
const lee = new Person('lee')
console.log(lee.name) // lee
const proxy = new Proxy(lee,{})
console.log(proxy.name) // undefined(由于this指向Proxy对象导致无法取到WeakMap里面的值)
原生对象内部属性必须通过正确的this获取
var target = new Date();
var proxy = new Proxy(target,{});
proxy.getDate(); // 报错 Uncaught TypeError: this is not a Date object.
可以通过重新绑定this指向解决
var target = new Date();
var proxy = new Proxy(target,{
get(target,property){
if(property === 'getDate'){
return target.getDate.bind(target); // 重新指定this
}
return Reflect.get(target,property)
}
});
proxy.getDate();// 22
ES6提供得到操作对象的API。 1、将Object对象一些明显属于语言内部的方法放到Reflect上 2、修改Object返回结果,使其更加合理,例如Reflect.defineProperty(target,property,attributes)会返回false,在Object上只会抛出错误 3、让Object操作变成函数行为。例如,key in obj变为Reflect.has(obj,key)、delete obj[key]变为Reflect.deleteProperty(obj,key) 4、Reflect对象的方法与Proxy对象方法一一对应
var target = {};
var proxy = new Proxy(target,{
get(target,property){ // Proxy对象get方法
if(property === 'getDate'){
return target.getDate.bind(target);
}
return Reflect.get(target,property) // Reflect的get方法
}
});
Reflect.apply(target,thisArg,args) Reflect.construct( target,args) Reflect.get(target,name,receiver) Reflect.set(target,name, value,receiver) Reflect.defineProperty( target,name,desc) Reflect.deleteProperty( target, name) Reflect.has( target,name) Reflect.ownKeys(target) Reflect.isExtensible(target) Reflect. preventExtensions( target) Reflect.getOwnPropertyDescriptor(target, name) Reflect.getPrototypeOf(target) Reflect.setPrototypeOf(target, prototype)
返回对象的属性值。
var obj = {
a: 1,
b:2,
get c(){
return this.a + this.b;
}
}
console.log(Reflect.get(obj,'a')) // 1
console.log(Reflect.get(obj,'b')) // 2
console.log(Reflect.get(obj,'c')) // 3
第三个参数绑定this指向
var obj = {
a: 1,
b:2,
get c(){
return this.a + this.b;
}
}
var receiverObj = {
a: 3,
b:3
}
console.log(Reflect.get(obj,'c',receiverObj)) // 6(this指向改变了)
第一个参数不是对象时报错
Reflect.get(1,'c',receiverObj) // 报错 Uncaught TypeError: Reflect.get called on non-object
Reflect.get({},'c',receiverObj) // undefined(不存在时返回undefined)
设置对象的属性值
var obj = {
a: 1,
set b(value){
return this.a = value;
}
}
Reflect.set(obj,'b',2)
console.log(obj) // {a: 2}
第三个参数改变set函数this指向
var obj = {
a: 1,
set a(value){
return this.a = value;
}
}
var receiverObj = {
a: 2,
}
Reflect.set(obj,'a',3,receiverObj)
console.log(receiverObj) // {a: 3}(改变的是另一个对象,this指向改变)
第一个参数不是对象会报错
Reflect.set(1,'a',1) // 报错 Uncaught TypeError: Reflect.set called on non-object
Reflect.set会触发Proxy.defineProperty
var target = {};
var handler = {
set(target,key,value,receiver){
console.log('触发了set函数')
Reflect.set(target,key,value,receiver)
},
defineProperty(target,key,attribute){
console.log('触发了定义函数')
Reflect.defineProperty(target,key,attribute)
}
}
var proxy = new Proxy(target,handler)
Reflect.set(proxy,'a',1)
// 触发了set函数
// 触发了定义函数
var obj={a:1};
console.log('a' in obj) // true(旧写法)
console.log(Reflect.has(obj,'a')) // true(新写法)
如果第一个参数不是对象报错
console.log(Reflect.has(1,'a')) // 报错 Uncaught TypeError: Reflect.has called on non-object
Reflect.deleteProperty方法删除属性
var obj={a:1,b:2};
delete obj.a; // 旧写法
Reflect.deleteProperty(obj,'b'); // 新写法
obj; // {}
Reflect.construct调用构造函数,类似new创建对象
function Person(name,age){
this.name = name;
this.age = age;
}
var person1 = new Person('mht',30); // 旧写法
var person2 = Reflect.construct(Person,['lee',18]) // 新写法
console.log(person1,person2) // Person {name: "mht", age: 30} Person {name: "lee", age: 18}
获取对象的_proto_属性,对应Object.getPrototype(obj) function Person(){}
const person = new Person()
console.log(Object.getPrototypeOf(person) === Person.prototype) // ture(旧写法)
console.log(Reflect.getPrototypeOf(person) === Person.prototype) // true (新写法)
区别: Object.getPrototype(obj)会转换非对象为对象
console.log(Object.getPrototypeOf(1)) // Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
console.log(Reflect.getPrototypeOf(1)) // 报错 Uncaught TypeError: Reflect.getPrototypeOf called on non-object
设置对象的_proto_属性,返回第一个一个参数对象,对应Object.setPrototypeOf(obj,newProto)
function Person(){}
var person = new Person()
var proto1 = {a:1}
var proto2 = {b:2}
Object.setPrototypeOf(person,proto1)
console.log(person.__proto__ === proto1 ) // true
Reflect.setPrototypeOf(person,proto2)
console.log(person.__proto__ === proto2) // true
如果第一个参数是不是对象,Object.setPrototypeOf()会返回第一个参数,而Rflect.setPrototypeOf()会报错
Object.setPrototypeOf(1,{}) // 1
Reflect.setPrototypeOf(1,{}) // 报错 Uncaught TypeError: Reflect.setPrototypeOf called on non-object
第一个参数是null或者undefined,两者都会报错
Object.setPrototypeOf(null,{}) // 报错 Uncaught TypeError: Object.setPrototypeOf called on null or undefined
Object.setPrototypeOf(undefined,{}) // 报错 Uncaught TypeError: Object.setPrototypeOf called on null or undefined
Reflect.setPrototypeOf(undefined,{}) // 报错 Uncaught TypeError: Reflect.setPrototypeOf called on non-object
Reflect.setPrototypeOf(null,{}) // 报错 Uncaught TypeError: Reflect.setPrototypeOf called on non-object
Reflect.apply方法等同于Function.prototype.apply.call(func,thisArg,args) 旧写法
const ages = [1,2,3,4,5,6,7,8,9];
const youngest = Math.min.apply(Math,ages) // 实际上是把参数转换为数组
const oldest = Math.max.apply(Math,ages)
const type = Object.prototype.toString.call(youngest)
console.log(youngest,oldest,type) // 1 9 "[object Number]"
新写法
const ages = [1,2,3,4,5,6,7,8,9];
const youngest = Reflect.apply(Math.min,Math,ages)
const oldest = Reflect.apply(Math.max,Math,ages)
const type = Reflect.apply(Object.prototype.toString,youngest,[])
console.log(youngest,oldest,type) // 1 9 "[object Number]"
Rflect.defineProperty方法基本等同于Object.defineProperty,用来定于属性,后者会逐渐废除。
var obj = {};
// 旧写法
Object.defineProperty(obj,'a',{
value: 1
})
// 新写法
Reflect.defineProperty(obj,'b',{
value: 2
})
console.log(obj) // {a: 1, b: 2}
第一个参数不是对象会抛出错误
Reflect.defineProperty(1,'a') // 报错 Uncaught TypeError: Reflect.defineProperty called on non-object
at Object.defineProperty (<anonymous>)
Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,获取对象的描述
var obj = {};
Object.defineProperties(obj,{
a: {
value:1,
writable: false
},
b:{
value: 2,
configurable: false
}
})
// 旧写法
console.log(Object.getOwnPropertyDescriptor(obj,'a'))
// 新写法
console.log(Reflect.getOwnPropertyDescriptor(obj,'a'))
如果第一个参数不是对象
Object.getOwnPropertyDescriptor(1,'a') // undefined(返回undefined)
Reflect.getOwnPropertyDescriptor(1,'a') // 报错 Uncaught TypeError: Reflect.getOwnPropertyDescriptor called on non-object
Reflect.isExtentsible方法对应Object.isExtensible
var obj = {};
console.log(Object.isExtensible(obj)) // true
console.log(Reflect.isExtensible(obj)) // true
如果第一个参数不是对象
console.log(Object.isExtensible(1)) // false
console.log(Reflect.isExtensible(1)) // 报错 Uncaught TypeError: Reflect.isExtensible called on non-object
Reflect.preventExtensions对应Object.preventExtensions方法,用于使一个对象变为不可拓展的。
var obj1 = {};
var obj2 = {};
console.log(Object.preventExtensions(obj1)) // {}
console.log(Reflect.preventExtensions(obj2)) // true(与上面的返回值不一样)
第一个参数不是对象时
console.log(Object.preventExtensions(1)) // 1
console.log(Reflect.preventExtensions(1)) // 报错 Uncaught TypeError: Reflect.preventExtensions called on non-object
Reflect.ownKeys方法适用于返回对象的所有属性
var obj = {
a:1,
b:2,
[Symbol('a')]: 1,
[Symbol('b')]: 2,
}
console.log(Object.getOwnPropertyNames(obj)) // ["a", "b"]
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(a), Symbol(b)]
console.log(Reflect.ownKeys(obj)) // ["a", "b", Symbol(a), Symbol(b)]
两个特点: 1、状态不受外界影响。2、一旦状态改变旧不会再变,任何时候都是这个结果。 缺点: 1、无法取消2、不设置回调函数无法将Promise错误反应到外部3、pending状态无法得知目前进展到哪一个阶段。
var promise = new Promise(function(resolve,reject){
// ...some code
if(/*异步操作成功*/){
resolve(value)
}else{
reject(error)
}
})
promise.then(function(value){
// success
},function(error){
// failure
})
例子
var promise = new Promise(function (resolve,reject){
setTimeout(()=>resolve('这个是传递的参数'), 1000)
})
promise.then(function(value){
console.log(value)
})
// 1秒后输出:这个是传递的参数
何时resolve何时调用对应的成功回调,何时reject何时调用错误回调 立即resolve就会立即调用成功回调函数,reject同理
var promise = new Promise(function (resolve,reject){
resolve('这个是传递的参数')
})
promise.then(function(value){
console.log(value)
}) // 输出: 这个是传递的参数
Promise.resolve('这个是传递的参数').then((value)=>{
console.log(value)
}) // 这个是传递的参数
reject函数参数一般是Error对象实例,表示抛出错误。resolve函数的参数可以是正常参数也可以是Promise实例
var p1 = new Promise(function(resolve,reject){
resolve('成功的参数')
})
var p2 = new Promise(function(resolve,reject){
resolve(p1)
})
p2.then(value =>{console.log(value)}) // 成功的参数
p1的状态决定p2的状态。
var p1 = new Promise(function(resolve,reject){
reject('失败的参数')
})
var p2 = new Promise(function(resolve,reject){
resolve(p1)
})
p2.then(value =>{console.log(value)},error=>{console.log(error)}) // 失败的参数
调用resolve和reject不会立即终结函数的执行
var p = new Promise(function(resolve,reject){
resolve('传递的参数')
console.log('这里可以继续执行')
})
p.then(value=>{console.log(value)})
// 这里可以继续执行
// 传递的参数
如果想要终结呢?增加return
new Promise(function(resolve,reject){
return resolve('传递的参数')
console.log('这里可以继续执行') // 这里被终结了
}).then(value=>{console.log(value)})
// 传递的参数
Promise实例具有then方法,即then方法是定义在原型对象上,为Promise实例添加状态改变时的回调函数。then方法返回一个新的Promise实例(不是原来的Promise实例),因此可以采用链式写法。
new Promise(function(resolve,reject){
resolve('传递的参数1')
}).then(value=> '传递的参数2')
.then(value=> {console.log(value)}) // 传递的参数2
指定发生错误时的回调函数 可以采用不同的方式接受错误
new Promise(function(resolve,reject){
throw new Error('这是个错误')
}).catch(error=>{console.log(error)}) // Error: 这是个错误
相当于
new Promise(function(resolve,reject){
throw new Error('这是个错误')
}).then(null,error=>{console.log(error)}) // Error: 这是个错误
采用不同的方式抛出错误
new Promise(function(resolve,reject){
throw new Error('这是个错误')
}).catch(error=>{console.log(error)}) // Error: 这是个错误
new Promise(function(resolve,reject){
reject(new Error('这是个错误'))
}).then(null,error=>{console.log(error)}) // Error: 这是个错误
状态已经变为resolved,再抛出错误是无效的。
new Promise(function(resolve,reject){
resolve('成功的参数')
throw new Error('这是个错误') // 无效
}).then(value=>{console.log(value)})
.catch(error=>{console.log(error)})
// 成功的参数
Promise对象的错误具有“冒泡”性质,会一直向后传递。
Promise.resolve('这是传递的参数').then(()=>{
throw new Error('这是一个报错')
}).then(()=>{
// 随便写点什么
}).catch((error)=>{
console.log(error)
})
// Error: 这是一个报错(错误总会被捕获)
建议:使用catch捕获错误,而不是使用then的第二个回调参数。 那么没有catch捕获错误时会怎么样?
Promise.resolve().then(()=>{
console.log(a+2)
})
// 报错 Promise {<rejected>: ReferenceError: a is not defined ...}(但不会终止脚本执行)
promise中使用setTimeout
var p = new Promise(function(resolve,reject){
resolve('这是传递的参数')
setTimeout(()=>{
throw new Error('抛出错误')
})
})
p.then((value)=>{
// console.log(value)
}).catch((err)=>{
console.log(err)
})
// 报错 Uncaught Error: 抛出错误(这个错误无法被catch捕获)
如果catch方法也是可以抛出的,尽量不要进行复杂操作
var p = new Promise(function(resolve,reject){
throw new Error('错误')
})
p.catch((err)=>{
console.log(x+1)
})
// 报错 Uncaught (in promise) ReferenceError: x is not defined
Promise.all方法用于将多个Promise实例包装成一个新的Promise实例 var p = Promise.all ([pl , p2 , p3]) ; // 参数必须具有iterator接口 全部实例状态都变为fulfilled,或者其中一个变为rejected,才会调用后面的回调函数。 全部通过的例子
var p1 = new Promise((resolve)=>{
setTimeout(()=>{
resolve(1)
},1000)
})
var p2 = new Promise((resolve)=>{
setTimeout(()=>{
resolve(2)
},2000)
})
Promise.all([p1,p2]).then(([value1,value2])=>{
console.log(value1+value2)
})
// 3
其中一个为rejected的例子
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('报错信息')
},1000)
})
var p2 = new Promise((resolve)=>{
setTimeout(()=>{
resolve(2)
},2000000)
})
Promise.all([p1,p2]).then(([value1,value2])=>{
console.log(value1+value2)
}).catch(err=>{
console.log(err)
})
// 输出 报错信息(不用等待p2执行完)
如果实例本身也有catch方法,那么两个都会执行
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('报错信息')
},1000)
})
p1.catch(err=>{
console.log(err)
})
var p2 = new Promise((resolve)=>{
setTimeout(()=>{
resolve(2)
},2000000)
})
Promise.all([p1,p2]).then(([value1,value2])=>{
console.log(value1+value2)
}).catch(err=>{
console.log(err)
})
// 报错信息
// 报错信息
Promise.race将多个Promise实例包装成一个新的Promise实例。 var p = Promise.race ([pl , p2 , p3]) ;
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('报错信息')
},1000)
})
var p2 = new Promise((resolve)=>{
setTimeout(()=>{
resolve(2)
},2000000)
})
Promise.race([p1,p2]).then((value)=>{
console.log(value)
}).catch(err=>{
console.log(err)
})
// 打印输出 报错信息
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('报错信息')
},1000)
})
var p2 = new Promise((resolve)=>{
setTimeout(()=>{
resolve(2)
},200)
})
Promise.race([p1,p2]).then((value)=>{
console.log(value)
}).catch(err=>{
console.log(err)
})
// 打印输出 2
将对象转换为Promise对象
Promise.resolve('foo')
// 等价于
new Promise(resolve=>resolve('foo'))
情况一: 参数时一个Promise实例
如果参数是Promise实例,那么Promise.resolve直接返回
var p = new Promise((resolve)=>{
resolve(1)
})
Promise.resolve(p).then(value=>{
console.log(value)
})
// 1(Promise对象被直接返回)
情况二: 参数是一个thenable对象,即具有then方法的对象
var thenableObj = {
then: function(resolve){
resolve('1')
}
}
Promise.resolve(p).then(value=>{
console.log(value)
})
// 1(更改为then方法里面的resolve)
情况三:参数不是具有 then 方法的对象或根本不是对象
Promise.resolve(1).then(value=>{
console.log(value)
}) // 1(直接返回)
情况四: 不带任何参数 会在事件循环结束时执行then回调方法,而setTimeout是下一轮事件循环执行
console.log('1')
setTimeout(()=>{
console.log('3')
})
Promise.resolve().then(value=>{
console.log('2')
})
// 1 2 3
返回一个状态为rejected的新的Promise实例。reject与resolve不同,只会原封不动的传递给reject回调函数。
Promise.reject('错误').then(null,err=>{
console.log(err)
})
// 错误
Promise.resolve(1).finally(value =>{console.log(value)}) // 1
只要部署了Iterator接口就可以完成遍历操作,一般是供for...of消费。每次调用next方法都会返回数据结构的信息。let arr = [1,2];
let it = arr[Symbol.iterator]();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: undefined, done: true}
只要具有Symbol.iterator属性,就认为是可以遍历的,会调用这个方法返回结果,作为属性值必须放在方括号中。
var obj ={
[Symbol.iterator]: function(){
return {
next:function(){
return {
value: 1,
done: true
}
}
}
}
}
var it = obj[Symbol.iterator]();
it.next(); // {value: 1, done: true}
原生具备Iterator接口数据结构如下 Array Map Set String TypeArray 函数的arguments对象 NodeList对象
let it = arr[Symbol.iterator]();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: undefined, done: true}
对象需要通过Symbol.iterator属性手动指定
var obj ={
value: 1,
[Symbol.iterator]: function(){
return this // 巧妙地使其调用自身的next方法,进而可以获取正确的this指向
},
next:function(){
if(this.value< 3){
return {
value: this.value++,
done: false
}
}else{
return {
value: this.value,
done: true
}
}
}
}
var it = obj[Symbol.iterator]();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
类数组遍历
var arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
}
for(var item of arrayLike){
console.log(item)
} // a b c
当Symbol.iterator方法对应的不是遍历器生成函数,不返回一个遍历器对象是,解析引擎会报错。
var obj = {};
obj[Symbol.iterator] = ()=>{}
console.log([...obj]) // 报错 Uncaught TypeError: Result of the Symbol.iterator method is not an object
与while遍历结合
let arr = [1,2,3,4]
let it = arr[Symbol.iterator]();
let result = it.next()
while(!result.done){
console.log(result.value)
result = it.next()
} // 1 2 3 4
解构赋值 对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法
let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
let [first,...rest] = set;
console.log(x,y,first,rest) // a b a ["b", "c"]
拓展运算符 ...扩展运算符也会默认调用Iterator接口
var str = 'lee';
console.log([...str]) // ["l", "e", "e"]
yield* yield* 后面跟的是一个可遍历结构,会调用该结构的遍历器接口
var generator = function * (){
yield 1;
yield* 'lee'; // 字符串具有遍历接口
yield 5;
}
var it = generator();
it.next() // {value: 1, done: false}
it.next() // {value: "l", done: false}
it.next() // {value: "e", done: false}
it.next() // {value: "e", done: false}
it.next() // {value: 5, done: false}
it.next() // {value: undefined, done: true}
其他场合 for of
[...('lee')] // ["l", "e", "e"]
var str = 'lee';
var iterator = str[Symbol.iterator]();
iterator.next() // {value: "l", done: false}
Symbol.iterator方法最简单的实现是使用Generator函数
var myIterable = {
*[Symbol.iterator](){
yield 1;
yield 2;
}
};
console.log([...myIterable]) // 1 2
遍历器对象除了next方法,还有throw方法和return方法 for of循环提前退出(出错或者遇到break、continue)会调用return方法
var obj ={
value: 1,
[Symbol.iterator]: function(){
return this // 巧妙地使其调用自身的next方法,进而可以获取正确的this指向
},
next:function(){
if(this.value< 3){
return {
value: this.value++,
done: false
}
}else{
return {
value: undefined,
done: false
}
}
},
return(){
console.log('进入return了')
return {value: undefined,done: true}
}
}
for(let value of obj){
if(value == 2){
break;
}
}
// 进入return了
for of循环内部调用的是数据结构的Symbol.iterator方法
数组原生具备iterator接口
var arr = [1,2,3,4];
for(let value of arr){
console.log(value)
} // 1 2 3 4
Set、Map结构原生具有Iterator接口,可以直接使用for...of循环
var set = new Set([1,2,3]);
console.log([...set]) // [1, 2, 3]
var map = new Map([[1,'a'],[2,'b']])
console.log([...map]) // [[1,'a'],[2,'b']]
for(var arr of map){
console.log(arr)
} // [1, "a"] [2, "b"]
for(var [key,value] of map){
console.log(key,value)
}
// 1 "a"
// 2 "b"
entries()、keys()、values() // ES6数组、Set、Map都部署了这个三个方法 Map结构的iterator接口默认就是调用entries方法。
let map = new Map([[1,"a"],[2,"b"]])
for(var [key,value] of map){
console.log(key,value)
}
// 1 "a"
// 2 "b"
var set = new Set([1,2,3,4])
for(let key of set.keys()){
console.log(key)
} // 1 2 3 4
字符串、DOM NodeList对象、arguments对象 字符串(可以正确识别 32 UTF-16 字符)
var str = 'lee';
console.log([...str]) // ["l", "e", "e"]
DOM NodeList对象
var nodeList = document.querySelectorAll('span')
console.log([...nodeList]) // 略
arguments对象
function add(){
console.log([...arguments])
}
add(1,2,3,4) // [1, 2, 3, 4]
不具备Iterator接口的类数组对象,先用Array.from方法将其转换
var arrayLike = {length: 2,0:'a',1:'b'}
for(let key of Array.from(arrayLike)){
console.log(key)
} // a b
遍历对象 方法一:
var obj ={
a: 1,
b: 2
}
for(var key of Object.keys(obj)){
console.log(key)
} // a b
方法二:
var obj ={
a: 1,
b: 2
}
function* entries(){
for(let [key,value] of Object.entries(obj)){
yield [key,value]
}
}
for(var [key,value] of entries(obj)){
console.log(key,value)
}
// a 1
// b 2
for in会返回包括原型链上的键名
var obj={
a: 1,
b:2
}
Object.setPrototypeOf(obj,{c: 3})
for(let key in obj){
console.log(key,obj[key])
}
// a 1
// b 2
// c 3(原型链上的也返回了)
for循环写起来比较繁琐 forEach
[1,2,3].forEach(item=>{
console.log(item);
return; // 问题无法跳出循环
})
for of 优点: 可以与break和continue、return搭配使用
var obj={
a: 1,
b:2
}
Object.setPrototypeOf(obj,{c: 3})
for(let key of Object.keys(obj)){
console.log(key,obj[key])
}
// a 1
// b 2(没有访问原型链上的键)
执行Generator函数会返回一个遍历器对象,封装了多个状态的状态机。 两个特征: 一是function命令和函数名之间有一个星号;二是函数体内使用yield语句定义不同的内部状态。
function *iterator(){
yield 1;
yield 2;
yield 3
}
const it = iterator();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: undefined, done: true}
function *iterator(){
yield 1;
return 2;
yield 3
}
var it = iterator();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: undefined, done: true}(return 会打断yield产出)
不使用yield时
function *iterator(){
console.log(2)
}
var it = iterator();
console.log(1)
it.next();
// 1
// 2(可以延迟执行)
yield用在其他地方时报错
function iterator(){
yield 1
}
iterator(); // 报错 Uncaught SyntaxError: Unexpected number
yield不能用在forEach循环中
function *iterator(){
[1,2,3,4].forEach(item=>{
yield item;
})
}
for(var key of iterator()){
console.log(key)
}
// 报错 Uncaught SyntaxError: Unexpected identifier
应改为for循环
function *iterator(){
var arr = [1,2,3,4];
for(var i = 0;i<arr.length;i++){
yield arr[i]
}
}
for(var key of iterator()){
console.log(key)
} // 1 2 3 4
yield在另一个表达式之中,必须放在圆括号内
function *example(){
console.log('hello'+ yield 1)
}
var it = example();
it.next(); // 报错 Uncaught SyntaxError: Unexpected identifier
修改后
function *example(){
console.log('hello'+ (yield 1))
}
var it = example();
it.next(); // {value: 1, done: false}
it.next(); // helloundefined {value: undefined, done: true}
yield表达式用作函数参数
function add(a,b){
console.log(a+b)
}
function *example(){
add(yield 1,yield 2)
}
var it = example();
it.next(); // {value: 1, done: false}
用于赋值表达式
function *example(){
var a= yield;
console.log(a)
}
var it = example();
it.next();
Generator函数就是遍历生成函数,调用next方法时返回这些结构{value: undefined, done: false},因此可以赋值对象的Symbol.iterator属性。
var myIterable = {};
myIterable[Symbol.iterator]= function *(){
yield 1;
yield 2;
yield 3;
}
console.log([...myIterable]) // [1, 2, 3]
function* iterator(x){
var a = 2*(yield x)
var b = yield(a/4);
return a+b;
}
var it = iterator(4);
it.next(); // {value: 4, done: false}
it.next(); // {value: NaN, done: false}(因为2*undefine结果为NaN)
it.next(); // {value: NaN, done: false}
function* iterator(x){
var a = 2*(yield x)
var b = yield(a/4);
return a+b;
}
var it = iterator(4);
it.next(); // {value: 4, done: false} (第一次传递参数不可用)
it.next(5); // {value: 2.5, done: false}
it.next(6); // {value: 16, done: true}
可以逐步传入参数,(yield x)相当于一个有产出的参数变量
for...of循环可以自动遍历Generator函数生成的Iterator对象,不需要调用next方法。
function *foo(){
yield 1;
yield 2;
yield 3;
return 4; // 这个不会遍历出来
}
for(let value of foo()){
console.log(value)
}
// 1 2 3
console.log([...foo()]) // [1, 2, 3]
Array.from(foo()) // [1, 2, 3]
let [x,y] = foo()
console.log(x,y) // 1 2
Generator函数返回的遍历器对象都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。 外部抛出内部捕获的例子
var gen = function *(){
try{
yield 1;
}catch(e){
console.log(e,'内部1')
}
try{
yield 2;
}catch(e){
console.log(e,'内部2')
}
}
var it = gen();
it.next();
try{
it.throw('捕获错误')
it.throw('捕获错误')
}catch(e){
console.log(e,'外部1')
}
it.next();
try{
it.throw('捕获错误')
}catch(e){
console.log(e,'外部2')
}
// 捕获错误 内部1
// 捕获错误 内部2
// 捕获错误 外部2
内部错误外部捕获
function* gen(){
yield 1;
throw new Error('错误')
}
var it = gen();
it.next(); // {value: 1, done: false}
try{
it.next()
}catch(e){
console.log(e)
}
// Error: 错误
Generator函数返回遍历器对象还有一个return方法,可以返回给定的值,并终结Generator函数的遍历。
function *gen(){
yield 1;
yield 2;
yield 3;
}
var g = gen();
console.log(g.next()) // {value: 1, done: false}
console.log(g.return('foo')) // {value: "foo", done: true}
console.log(g.next()) // {value: undefined, done: true}(已经被终结了)
return方法要在finally代码后执行
function *gen(){
yield 1;
try{
yield 2;
}finally{
yield 3;
}
}
var g = gen();
console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 2, done: false}
console.log(g.return('foo')) // {value: 3, done: false}
console.log(g.next()) // {value: "foo", done: true}
使用yield* 语句在Generator函数里面再执行另一个函数,不使用的话完全没有效果
function *gen1(){
yield 1;
yield *gen2()
yield 3;
}
function *gen2(){
yield 2;
}
var g = gen1();
console.log([...g]) // [1, 2, 3]
function *gen1(){
yield 1;
yield *'lee'
yield 3;
}
var g = gen1();
console.log([...g]) // [1, "l", "e", "e", 3]
let obj = {
* gen(){
//代码
}
}
等同于
let obj = {
gen: function *(){
// 代码
}
}
function *gen(){
this.a = 1;
}
gen.prototype.b = function(){
return 'b'
}
var obj = gen();
console.log(obj instanceof gen) // true
console.log(obj.b()) // b(obj是gen函数实例,可以调用其原型链上的方法)
console.log(obj.a) // undefirned(返回的是遍历器对象,无法当作构造函数)
不能结合new操作符创建对象
function *gen(){
this.a = 1;
}
var obj = new gen(); // 报错 Uncaught TypeError: gen is not a constructor
改造其为构造函数
function *Gen(a){
this.a = a;
yield this.b = 2;
yield this.c = 3;
}
var f = Gen.call(Gen.prototype,1); // 作用域绑定到原型上
console.log(f.next()); // {value: 2, done: false}
console.log(f.next()); // {value: 3, done: false}
console.log(f.next()); // {value: undefined, done: true}
console.log(f.a); // 1
console.log(f.b); // 2
console.log(f.c); // 3
增加中间函数
function *Gen(a){
this.a = a;
yield this.b = 2;
yield this.c = 3;
}
function F(a){
return Gen.call(Gen.prototype,a);
}
var f = new F(1);
console.log(f.next()); // {value: 2, done: false}
console.log(f.next()); // {value: 3, done: false}
console.log(f.next()); // {value: undefined, done: true}
console.log(f.a); // 1
console.log(f.b); // 2
console.log(f.c); // 3
略
async函数是异步操作变得更加方便,是Generator函数语法糖。 1.内置执行器 2.更好的语义 3.更广的适用性 4.返回值是Promise
async函数返回一个Promise对象,使用then方法添加回调函数。当函数执行的时候,一旦遇到awat就会先返回等待异步操作完成,再执行函数体内后面的语句。
var promise = new Promise((resolve)=>{
setTimeout(()=>{
resolve()
console.log('即使延迟这里也优先执行')
},1000)
})
async function asyncFunc(){
await promise;
console.log('函数体后面延迟执行')
}
asyncFunc().then(()=>{
console.log('呜呜呜我最后执行')
})
// 即使延迟这里也优先执行
// 函数体后面延迟执行
// 呜呜呜我最后执行
如果想要往promise中添加参数,添加多一层函数
function(ms){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve()
},ms)
})
}
async函数多种使用形式
// 函数声明
async function foo(){}
// 函数表达式
const foo = async function (){}
// 对象方法
let obj = {async foo(){}}
obj.foo().then(...
// Class方法
class obj{
async foo(){}
}
// 箭头函数
const foo = async ()=>{}
难点错误处理机制
async函数返回一个Promise对象。return返回的值,会成为then方法回调函数的参数。
async function f(){
return '这是then函数需要的参数'
}
f().then(v=>console.log(v))
// 这是then函数需要的参数
采用then方法的第二个回调函数处理错误
async function f(){
throw new Error('出错了')
}
f().then(v=>consoloe.log(v),e=>console.log(e)) // 出错了
内部异步操作执行完才会执行then方法指定回调函数,除非遇到错误或者return
await后面是一个Promise对象,不是的话会自动转换为Promise对象。
async function f(){
return await 123
}
f().then(v=>console.log(v))
// 123
也可以用.catch方法接受错误,或者rejected状态的参数
async function f(){
await Promise.reject('这是rejected参数')
}
f().then(v=>console.log(v)).catch((e)=>console.log(e))
// 这是rejected参数
async function f(){
await obj.a
}
f().then(v=>console.log(v)).catch((e)=>console.log(e))
// ReferenceError: obj is not defined
只要其中一个Promise语句变为reject,整个async函数都会中断
async function f(){
await Promise.reject('出错了')
await Promise.resolve('这里不会执行') // 不会执行
}
f().catch(e=>console.log(e)) // 出错了
用try catch包裹避免出错的地方,跳过出错继续执行
async function f(){
try{
await Promise.reject('出错了')
}catch(e){
console.log(e)
}
return await Promise.resolve('这里可以执行了')
}
f().then(v=>console.log(v)).catch(e=>console.log(e))
// 出错了
// 这里可以执行了
另外一种解决方法Promise对象添加一个catch方法,处理前面可能出现的错误。
async function f(){
await Promise.reject('出错了').catch(e=>console.log(e))
return await Promise.resolve('这里可以执行了')
}
f().then(v=>console.log(v))
// 出错了
// 这里可以执行了
如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject。
async function f(){
await Promise.resolve(obj.a)
}
f().then(v=>console.log(v)).catch(e=>console.log(e))
// 报错 ReferenceError: obj is not defined
防止出错用try catch包裹,或者调用catch方法
第一点:防止出错,try catch包裹或者调用catch方法 第二点:不存在继发关系最好让它们同时触发 错误示范
async function foo(){
await new Promise((resolve)=>{
setTimeout(()=>{
console.log('这是没有继发关系的第一个await')
resolve()
},1000)
})
await new Promise((resolve)=>{
setTimeout(()=>{
console.log('这是没有继发关系的第二个await')
resolve()
},800)
})
}
foo();
// 这是没有继发关系的第一个await
// 这是没有继发关系的第二个await(依然要等前面一个执行完毕)
正确示范一:使用Promise.all()
async function foo(){
let await1 = new Promise((resolve)=>{
setTimeout(()=>{
console.log('这是没有继发关系的第一个await')
resolve()
},1000)
})
let await2 =new Promise((resolve)=>{
setTimeout(()=>{
console.log('这是没有继发关系的第二个await')
resolve()
},800)
})
await Promise.all([await1,await2])
}
foo();
// 这是没有继发关系的第二个await
// 这是没有继发关系的第一个await(如果异步操作时间短会更早结束)
正确示范二:
async function foo(){
let p1 = function(){
return new Promise((resolve)=>{
setTimeout(()=>{
console.log('这是没有继发关系的第一个await')
resolve()
},1000)
})
}
let p2 = function(){
return new Promise((resolve)=>{
setTimeout(()=>{
console.log('这是没有继发关系的第二个await')
resolve()
},800)
})
}
let res1 = p1();
let res2 = p2();
let await1 = await res1;
let await2 = await res2;
}
foo();
// 这是没有继发关系的第二个await
// 这是没有继发关系的第一个await
节约了时间,可同时执行两个异步操作。 第三点:await命令只能用于async函数中
略
略