Promise
- Promise 就是一个对象,用来传递异步操作的消息,代表了未来才会知道结果的事件(通常是一个异步操作)
- Promise有两个特点:
- 1.对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled) 和 Rejected(已失败),只有异步操作的结果可以决定当前是哪一种状态
- 2.一旦状态改变就不会再变,任何时候都能得到这个结果
- Promise还有一些缺点,首先,无法取消 Promise,其次,如果不设置回调函数,Promise内部抛出的错误不会反映到外部,再者,当处于 Pending 状态时,无法得知目前发展到哪个阶段
- Promise 是一个构造函数,用于生成 Promise 实例,Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。它们是两个函数,由Javascript引擎提供,不用自己部署。
- resolve函数的作用是将 Promise 对象的状态从 Pending 变成 Fulfilled;reject函数的作用是将Promise对象的状态从 Pending 变为 Rejected
- Promise实例生成以后,可以用then分别制定Resolved 和 Rejected状态的回调函数,then 方法可以接受两个回调函数做参数,第一个回调函数是Promise变为Resolved时调用,第二个是Promise变为Rejected时调用,其中,第二个参数是可选的,并且建议catch语句替代
- 如果调用resolve和reject函数时带有参数,那么这些参数会被传递给回调函数,reject函数的参数通常是Error对象的实例,表示抛出错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例,表示异步操作结果有可能是一个值,也有可能是另一个异步操作
- Promise.prototype.then Promise实例具有 then 方法,then方法是定义在原型对象上的,作用是为Promise添加状态改变时的回调函数
- Promise.prototype.catch 方法是 .then(null, rejection)的别名,用于指定发生错误时的回调函数;
- Promise.prototype.catch catch方法返回的还是一个Promise对象,还可以接着调用then方法;catch后面的then报错,前面的catch无法捕获错误
- Promise.prototype.finally 在Promise结束时,无论结果是fulfilled或rejected,都会执行这个指定的回调函数
- Promise.all 方法用于将多个Promise实例包装成一个新的Promise实例,Promise.all方法的参数不一定是数组,但是必须具有 Interator 接口
- var p = Promise.all([p1, p2, p3])中,只有p1、p2、p3的状态都编程Fulfilled,p的状态才回编程Fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数;只有p1、p2、p3中有一个为Rejected,p的状态就变成了Rejected,此时第一个被Rejected的实例的返回值会传给p的回调函数
- Promsie.allSettled() 方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果
- Promise.race 方法同样是将多个 Promise实例包装成一个新的Promise实例
- var p = Promise.race([p1, p2, p3])只要p1、p2、p3中有一个实例率先改变状态,p的状态就跟着改变,那个率先改变状态的Promise实例的返回值,就传递给p的回调函数
- Promise.any() 接收一个Promise可迭代对象,只要其中的一个promise成功,就返回那个已经成功的promise。如果可迭代对象中没有一个promise成功,就返回一个失败的promise。
- Promise.resolve可以将现有对象转为Promise对象,如果Promise.resolve方法的参数不是thenable对象(具有then方法的对象),则返回一个新的Promise对象,且其状态为Resolved
Promise.resolve('foo')
<!-- 等价于 -->
new Promise(resolve => resolve('foo'))- Promise.reject()方法也会返回一个新的Promise实例,状态为Rejected
手写Promis:
// 手写Promise
class myPromise {
static PENDING = "pending";
static FULFILLED = "fulfilled";
static REJECTED = "rejected";
constructor(func) {
this.PromiseState = myPromise.PENDING;
this.PromiseResult = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
try {
func(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
resolve(result) {
// console.log("resolve执行:", result);
if (this.PromiseState === myPromise.PENDING) {
setTimeout(() => {
this.PromiseState = myPromise.FULFILLED;
this.PromiseResult = result;
this.onFulfilledCallbacks.forEach(callback => callback(result));
})
}
}
reject(reason) {
// console.log("reject执行:", reason, 999, this);
if (this.PromiseState === myPromise.PENDING) {
setTimeout(() => {
// console.log("reject内部执行", this);
this.PromiseState = myPromise.REJECTED;
this.PromiseResult = reason;
this.onRejectedCallbacks.forEach(callback => callback(reason));
// console.log("reject内部执行2222", this);
})
}
}
then(onFulfilled, onRejected) {
// console.log("then调用:");
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : v => v;
onRejected = typeof onRejected === "function" ? onRejected : reason => {
// console.log("reason", reason)
throw reason;
}
// console.log("onFulfilled -- onRejected", onFulfilled, onRejected);
const promise2 = new myPromise((resolve, reject) => {
if (this.PromiseState === myPromise.FULFILLED) {
setTimeout(() => {
try {
// console.log(9)
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
// console.log(91)
console.log(e);
reject(e);
}
})
} else if (this.PromiseState === myPromise.REJECTED) {
setTimeout(() => {
try {
// console.log(8)
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
// console.log(81)
console.log(e);
reject(e);
}
})
} else if (this.PromiseState === myPromise.PENDING) {
this.onFulfilledCallbacks.push(() => {
try {
// console.log(7)
let x = onFulfilled(this.PromiseResult);
let a= resolvePromise(promise2, x, resolve, reject);
// console.log('aaa', this, a);
} catch (e) {
// console.log(71,e)
console.log(e);
reject(e);
}
});
this.onRejectedCallbacks.push(() => {
try {
// console.log(6)
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
// console.log(61)
console.log(e);
reject(e);
}
});
}
})
return promise2;
}
}
/**
*
* @param {promise} promise2 promise1.then方法返回的新的promise对象
* @param {[type]} x promise1中onFulfilled或onRejected的返回值
* @param {[type]} resolve promise2的resolve方法
* @param {[type]} reject promise2的reject方法
*/
function resolvePromise(promise2, x, resolve, reject) {
if(promise2 === x){
// console.log("循环引用", promise2, promise2.resolve, promise2.reject);
throw new TypeError('Chaining cycle detected for promise');
// return reject(new TypeError('Chaining cycle detected for promise'));
}
if(x instanceof myPromise){
if(x.PromiseState === myPromise.PENDING){
x.then(y => {
resolvePromise(promise2, y, resolve, reject);
}, reject)
} else if(x.PromiseState === myPromise.FULFILLED){
resolve(x.PromiseResult);
} else if(x.PromiseState === myPromise.REJECTED){
reject(x.PromiseResult);
}
} else if (x !== null && (typeof x === "object" || typeof x === "function")){
try {
var then = x.then;
} catch (e){
return reject(e);
}
if(typeof then === "function"){
let called = false;
try {
then.call(
x,
y => {
if(called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
if(called) return;
called = true;
reject(r);
}
)
} catch (e) {
if(called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
} else {
return resolve(x);
}
}
// let res = () => {
// console.log(1);
// }
// let rej = () => {
// console.log(2);
// }
// 1.不能在new Promise时为excutor函数传入resolve/reject函数,因为new Promise时,resolve和reject函数都是形参,
// 实际上这两个函数是在Promise内部封装好的,并在执行excutor函数时传入,而new Promise时,只是调用了这两个函数
// 2.new Promise时的resolve和reject只是形参,真正调用是在new生成实例后,所以调用resolve、reject时需要绑定this指向
// 3.第一次走then还是catch 要看new Promise时 调用了resolve还是reject,之后链式调用走then还是catch 要看上一次then/catch的返回值
// 返回值是普通值,走then;是Error,走catch;是Promise,则根据Promise的状态决定
// console.log(1);
// var p1 = new myPromise((resolve, reject) => {
// // console.log("执行func");
// console.log(2);
// setTimeout(() => {
// console.log("before:", p1.PromiseState);
// resolve("执行resolve1");
// console.log("after:", p1.PromiseState);
// console.log(4);
// })
// // reject("执行reject1")
// // throw new Error("抛出错误");
// })
// // var p2 = new myPromise((resolve, reject) => {
// // reject("执行reject2");
// // resolve("执行resolve2")
// // })
// p1.then(
// result => {
// console.log("fulfilled", result);
// },
// // null,
// reason => {
// console.log("rejected", reason);
// }
// )
// console.log(3);
// // console.log("p1", p1);
// // console.log("p2", p2);
// 多次调用
// var promise = new myPromise((resolve, reject) => {
// setTimeout(() => {
// resolve("success");
// }, 2000);
// })
// promise.then(value => {
// console.log(1);
// console.log("resolve", value);
// })
// promise.then(value => {
// console.log(2);
// console.log("resolve", value);
// })
// promise.then(value => {
// console.log(3);
// console.log("resolve", value);
// })
// 链式调用
// let p1 = new myPromise((resolve, reject) => {
// resolve(10);
// });
// p1.then(res => {
// console.log("fulfilled", res);
// return 2 * res;
// }).then(res => {
// console.log("fulfilled", res);
// })
// 循环引用报错
// var promise = new myPromise((resolve, reject) => {
// // resolve(100);
// reject(200);
// })
// var p1 = promise.then(value => {
// console.log(value)
// // throw new Error()
// return p1
// })
// console.log(p1,'p1')
// p1.then((res) => {
// console.log("res2", res);
// }, (rej) => {
// console.log("rej2", rej);
// })
// test
// var promise = new Promise((resolve, reject) => {
// // resolve(100);
// // reject(200);
// throw new Error(300);
// })
// var p1 = promise.then((res) => {
// console.log("res1", res);
// }, (rej) => {
// console.log("rej1", rej);
// })
// p1.then((res) => {
// console.log("res2", res);
// }, (rej) => {
// console.log("rej2", rej);
// })
// 跑测试用例
// myPromise.deferred = function () {
// let result = {};
// result.promise = new myPromise((resolve, reject) => {
// result.resolve = resolve;
// result.reject = reject;
// });
// return result;
// }
// module.exports = myPromise;Set
- Set是es6提供的新的数据结构,类似数组,但是成员的值都是唯一的、没有重复的值;
- Set本身是一个构造函数,用来生成Set数据结构;
- Set可以接受一个数组或具有iterable接口的其它数据结构作为参数,用来初始化;
Set实例的属性和方法
- Set.prototype.constructor 构造函数,默认就是Set函数
- Set.prototype.size 返回Set实例的成员总数
- 四个操作方法:
- Set.prototype.add(value) 添加某个值 返回Set结构本身
- Set.prototype.delete(value) 删除某个值 返回一个布尔值,表示删除是否成功
- Set.prototype.has(value) 返回一个布尔值 表示该值是否为Set的成员
- Set.prototype.clear() 清除所有成员,没有返回值
- 四个遍历方法:
- Set.prototype.keys() 返回键名的遍历器
- Set.prototype.values() 返回键值的遍历器
- Set.prototype.entried() 返回键值对的遍历器
- Set.prototype.forEach() 使用回调函数遍历每个成员
- Set结构的遍历顺序就是插入顺序
- keys、values、entries方法返回的都是遍历器对象,由于Set结构没有键名只有键值,所以keys方法和values方法行为完全一致
WeakSet
- WeakSet的成员只能是对象,不能是其他类型的值
- WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用
- WeakSet也有add、delete、has方法,没有size、forEach方法
// Set
var log = console.log;
// 唯一值
// const s = new Set();
// [2,3,4,5,5,3,2].map(x=>s.add(x));
// for(let i of s) {
// console.log(i);
// }
// Set函数的参数 为数组或其他 interable 接口的数据结构
// const set = new Set([1,2,3,5,5]);
// console.log([...set]);
// const set = new Set(document.querySelectorAll('div'));
// console.log(set.size);
// const set = new Set();
// document.querySelectorAll('div').forEach(div=>set.add(div));
// console.log(set.size)
// 应用:数组去重
// console.log([...new Set([2,3,5,6,5,3])]);
// 应用:字符串去重
// console.log([...new Set('ababbc')].join(''));
// 向Set中加入值,不会发生类型转换 内部采用精确相等判断,但NaN判定等于自身,即只能有一个NaN
// let set = new Set();
// let a = NaN;
// let b = NaN;
// set.add(a);
// set.add(b);
// console.log(set);
// 两个对象总是不相等
// let set = new Set();
// set.add({});
// console.log(set.size);
// set.add({});
// console.log(set.size);
// Set实例的属性和方法
// Set.prototype.constructor 构造函数 默认就是Set函数
// Set.prototype.size 返回Set实例的成员总数
// Set实例的方法分为两大类:操作方法 和 遍历方法
// 四个操作方法(用于操作数据)
// Set.prototype.add(value): 添加某个值,返回Set结构本身
// Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功
// Set.prototype.has(value): 返回一个布尔值,表示该值是否为Set的成员
// Set.prototype.clear(): 清除所有成员,没有返回值
// 四个遍历方法
// Set.prototype.keys: 返回键名的遍历器
// Set.prototype.values: 返回键值的遍历器
// Set.prototype.entries: 返回键值对的遍历器
// Set.prototype.forEach(): 使用回调函数遍历每个成员
// 操作方法
// let s = new Set();
// s.add(1).add(1).add(2);
// log(s.size);
// log(s.has(1));
// log(s.has(2));
// log(s.has(3));
// s.delete(2);
// log(s.has(2));
// object结构 和 Set结构 在判断是否包括一个键时写法的区别
// const properties = {
// 'width': 1,
// 'height': 1
// };
// if(properties['width']){
// // do something
// log('obj has property width');
// }
// const properties = new Set();
// properties.add('width');
// properties.add('height');
// if(properties.has('width')){
// // do something
// log('set has key width');
// }
// Array.from可以将Set结构转为数组
// const items = new Set([1,2,3,5,6]);
// const array = Array.from(items);
// log('Set:',items);
// log('Array:', array);
// 另一种数组去重方法
// function dedupe (array){
// return Array.from(new Set(array));
// }
// log(dedupe([1,2,1,4,2,3]));
// 遍历方法
// keys方法、values方法 行为完全一致,entries方法返回一个键名和键值的数组
// let set = new Set(['red', 'green', 'blue']);
// for(let item of set.keys()){
// log(item);
// }
// for(let item of set.values()){
// log(item);
// }
// for(let item of set.entries()){
// log(item);
// }
// forEach()方法
// forEach方法的参数是一个回调函数,回调函数的参数依次是键值、键名、遍历对象本身;forEach方法还可以指定第二个参数目标是绑定处理函数内部的this对象
// let set = new Set([1, 4, 9]);
// set.forEach((value, key) => log(key + ' : ' + value + this), {a:1}); // 回调函数是箭头函数时,指定this失效
// set.forEach(function (value, key){log(key + ' : ' + value + this)}, {a:1});
// 遍历的应用
// 扩展运算法(...) 内部使用的是for...of循环,也可以用于Set结构
// let set = new Set(['red', 'green', 'blue']);
// let arr = [...set];
// log(arr);
// 扩展运算符合Set相结合,用于去除数组重复成员
// 数组的map和filter方法也可以间接用于Set
// let set = new Set([1, 2, 3]);
// set = new Set([...set].map(x => x * 2));
// log(set);
// set = new Set([...set].filter(x => x%2 == 0));
// log(set);
// Set可以很容易地实现并集(Union)、交集(Intersect)和 差集(Difference)
// let a = new Set([1, 2, 3]);
// let b = new Set([4, 3, 2]);
// // 并集
// let union = new Set([...a,...b]);
// log(union);
// // 交集
// let intersect = new Set([...a].filter(x=>b.has(x)));
// log(intersect);
// // 差集 a相对于b
// let difference = new Set([...a].filter(x => !b.has(x)));
// log(difference);
// 如果想在遍历操作中,同步改变原来的Set结构,目前没有直接方法,但有两种变通方法。一是利用原Set结构映射出一个新结构,然后复制给原来的Set结构,一种是利用Array.from方法
// 方法一
// let set = new Set([1,2,3]);
// set = new Set([...set].map(val => val * 2));
// log(set);
// 方法二
// let set = new Set([1, 2, 3]);
// set = new Set(Array.from(set, val => val * 2));
// WeakSet
// 相同点:不重复
// 不同点1:WeakSet成员只能是对象,不能是其他类型的值
// 不同点2:WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还在WeakSet中
// WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息,只要这些对象再外部消失,它在WeakSet里面的引用就会自动消失
// 所以WeakSet的成员是不适合引用的,因为它随时会消失,引用前引用后成员个数可能是不一样的;所以WeakSet是不可遍历的
// WeakSet构造函数
// const ws = new WeakSet();
// 任何具有Iterable接口的数据都可以作为WeakSet的参数
// const a = [[1, 2], [3, 4]];
// const ws = new WeakSet(a);
// log(ws);
// WeakSet 结构有以下三个方法:
// WeakSet.prototype.add(value): 向WeakSet实例添加一个成员
// WeakSet.prototype.delete(value): 清除WeakSet实例的指定成员
// WeakSet.prototype.has(value): 返回一个布尔值,表示某个值是否在WeakSet实例中
// const ws = new WeakSet();
// const obj = {};
// const foo = {};
// ws.add(window);
// ws.add(obj);
// log(ws.has(window));
// log(ws.has(foo));
// ws.delete(window);
// log(ws.has(window));
// log(ws.has(obj));
// log(ws.size, ws.forEach);
// WeakSet不能遍历,是用因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处是,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏
// const foos = new WeakSet();
// class Foo{
// constructor (){
// foos.add(this);
// }
// method (){
// if((!foos.has(this))){
// throw new TypeError('Foo.prototype.method 只能再Foo实例上调用!');
// }
// }
// }
// WeakSet能避免内存泄漏
// var dom = document.querySelector('div');
// // var obj = { dom };
// // var set = new Set();
// // set.add(dom);
// var ws = new WeakSet();
// ws.add(dom);
// // console.log(obj, set);
// console.log(ws);
// dom.parentElement.removeChild(dom)
// dom = null;
// // console.log(obj, set); // 在obj和set中,dom没有被清除,这样的内存占用积累多了以后,就会产生内存泄漏
// console.log(ws); // 打印内容看似一样 其实展开之后发现已经清掉了dom,所以不会产生内存泄漏Map
- Map是ES6提供的一种数据结构,类似对象,也是键值对的结合,但是Map的键不限于字符串,各种类型的值(包括对象)都可以当做键名;也就是说,Object提供了"字符串-值"的对应,Map提供了“值-值”的对应,是一种更完善的Hash结构
- Map构造函数可以接受数组作为参数,用于快速生成一个Map结构
实例的属性和方法
- size属性 返回Map结构的成员总数
- Map.prototype.set(key, value) set方法设置键名key对应的键值value,然后返回整个Map结构
- Map.prototype.get(key) get方法读取key对应的键值,找不到则返回undefined
- Map.prototype.has(key) has方法返回一个布尔值,表示某个键是否在当前Map对象之中
- Map.prototype.delete(key) delete方法删除某个键,返回true,如果删除失败,返回false
- Map.prototype.clear() clear方法清除所有成员 没有返回值
- 四个遍历方法:
- Map.prototype.keys():返回键名的遍历器
- Map.prototype.values():返回键值的遍历器
- Map.prototype.entries():返回所有成员的遍历器
- Map.prototype.forEach(): 遍历Map的所有成员
- Map的遍历顺序就是插入顺序
Map结构与其他数据结构的互相转换
- Map转为数组 使用扩展运算符 ...
- 数组转为Map 把数组传入Map构造函数即可
- Map转为对象 如果Map的所有key都是字符串,可以无损转为对象,使用Object.create创建obj,再遍历赋值
- 对象转为Map 通过构造函数及Object.entries方法
- Map转为JSON 分两种情况 Map的键名都是字符串 可以转为对象JSON,否则可以转为数组JSON 使用构造函数
- JSON转为Map 构造函数 + 遍历方法
WeakMap
- WeakMap与Map结构类似,也是用于生成键值对的集合
- WeakMap只接受对象作为键名(null除外) 不接受其他类型的值(包括Symbol也不行)作为键名
- WeakMap的键名所指向的对象 不计入垃圾回收机制 -- 避免内存泄露
- WeakMap没有遍历操作,没有size属性 只有四个方法可用:get()、set()、has()、delete()
WeakRef
- 直接创建对象的弱引用
FinalizationRegistry
- 清理注册表功能 用于指定目标对象被垃圾回收机制清除以后 所要执行的函数
// Map
var log = console.log;
// Map和Object都是一种键值对的Hash结构
// Object只能以字符串作为键名
// const data = {};
// const element = document.querySelector('div');
// data[element] = 'metadata';
// log(data, data['[object HTMLDivElement]']); // {[object HTMLDivElement]: 'metadata'}[object HTMLDivElement]: "metadata"[[Prototype]]: Object 'metadata'
// Map可以以各种类型的值(包括对象)作为键,是一种更完善的Hash结构
// const m = new Map();
// const o = { p: 'Hello World' };
// m.set(o, 'content');
// log(m.get(o));
// log(m.has(o));
// m.delete(o);
// log(m.has(o));
// Map也可以接受一个数组作为参数
// const map = new Map([
// ['name', '张三'], // 只有数组的前两项别识别为key value
// ['title', 'Author']
// ]);
// log(map.size);
// log(map.has('name'));
// log(map.get('name'));
// log(map.has('title'));
// log(map.get('title'));
// Map构造函数接受数组作为参数,实际上执行的是下面算法
// const items = [
// ['name', '张三'],
// ['title', 'Author']
// ];
// const map = new Map();
// items.forEach(
// ([key, value]) => map.set(key, value)
// );
// log(map);
// 类似数组,任何具有Interable接口,且每个成员都是一个双元素数组的数据结构,都可以作为Map构造函数的参数,所以 Set 和 Map 都可以用来生成新的 Map
// const set = new Set([
// ['foo', 1],
// ['bar', 2]
// ]);
// const m1 = new Map(set);
// log(m1.get('foo'));
// const m2 = new Map([['baz', 3]]);
// const m3 = new Map(m2);
// log(m3.get('baz'));
// l连续对同一个键多次赋值,后面的值将覆盖前面的值
// 获取未知键时,返回undefined
// 只有键的引用地址相同,Map才会将其视为同一个键
// Map 的键实际上是跟北村地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,当我们扩展别人的库的时候,不必再担心自己的属性与原作者的属性同名
// const map = new Map();
// map.set(['a'], 555);
// log(map.get(['a'])); // undefined
// 如果Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map都会视为一个键,undefined和null是两个不同的键,NaN也视为一个键
// let map = new Map();
// map.set(-0, 123);
// log(map.get(+0));
// map.set(true, 1);
// map.set('true', 2);
// log(map.get(true));
// map.set(undefined, 3);
// map.set(null, 4);
// log(map.get(undefined));
// map.set(NaN, 123);
// log(map.get(NaN));
// 实例的属性和操作方法
// size属性 返回Map结构的成员总数
// Map.prototype.set(key, value) set方法设置键名key对应的键值value 然后返回整个Map结构(所以可以采用链式写法),如果key已经存在,则键值会被更新,否则就生成该键
// Map.prototype.get(key) get方法读取对应的键值,如果找不到key,就返回undefined
// Map.prototype.has(key) 返回一个布尔值 表示某个键是否在当前Map对象之中
// Map.prototype.delete(key) delete方法删除某个键,返回true,如果删除失败,返回false
// Map.prototype.clear() clear方法清除所有成员,没有返回值
// 实例的遍历方法
// Map.prototype.keys() 返回键名的遍历器
// Map.prototype.values() 返回键值的遍历器
// Map.prototype.entries() 返回所有成员的遍历器
// Map.prototype.forEach() 遍历Map的所有成员
// Map的遍历顺序就是插入顺序
// const map = new Map([
// ['F', 'no'],
// ['T', 'yes']
// ]);
// for(let key of map.keys()){
// log(key);
// }
// for(let value of map.values()){
// log(value);
// }
// for(let item of map.entries()){
// log(item); // 键值对数组 ['F', 'no]
// }
// for(let [key, value] of map){ // Map结构默认部署Iterator接口
// console.log(key, value);
// }
// Map结构转为数组结构,比较快速的方法是使用扩展运算符(...)
// const map = new Map([
// [1, 'one'],
// [2, 'two'],
// [3, 'three']
// ]);
// log([...map.keys()]);
// log([...map.values()]);
// log([...map.entries()]);
// log([...map]);
// 结合数组的map方法、filter方法,可以实现Map的遍历和过滤 (Map本身没有map和filter方法)
// const map0 = new Map()
// .set(1, 'a')
// .set(2, 'b')
// .set(3, 'c');
// const map1 = new Map(
// [...map0].filter(([k,v]) => k < 3)
// )
// log(map0,map1);
// Map的forEach方法和数组的forEach方法类似
// 与其它数据结构的相互转换
// Map转为数组, 可以使用扩展运算符
// 数组转为Map 将数组传入Map构造函数即可
// Map转为对象 如果所有Map的key都是字符串,它可以无损的转为对象
// function strMapToObj (strMap){
// let obj = Object.create(null);
// for(let [k, v] of strMap){
// obj[k] = v;
// }
// return obj;
// }
// let myMap = new Map()
// .set('yes', true)
// .set('no', false);
// log(myMap, strMapToObj(myMap));
// // 对象转为Map 对象转为Map可以使用Object.entries() 或自己实现一个转换函数
// let obj = {'a': 1, 'b': 2};
// let map = new Map(Object.entries(obj));
// log(obj,map);
// function objToStrMap (obj){
// let strMap = new Map();
// for(let k of Object.keys(obj)){
// strMap.set(k, obj[k]);
// }
// return strMap;
// }
// log(objToStrMap({yes:true,no:false}));
// // Map转JSON 此时要区分两种,若Map的键名都是字符串,这时可以转为对象JSON,若Map的键含有非字符串,可以转为数组JSON
// function strMapToJson (strMap){
// return JSON.stringify(strMapToObj(strMap));
// }
// log(strMapToJson(myMap));
// function mapToArrayJson (map){
// return JSON.stringify([...map]);
// }
// log(mapToArrayJson(myMap));
// // JSON转为Map 正常情况下 所有键名都是字符串
// function jsonToStrMap(jsonStr){
// return objToStrMap(JSON.parse(jsonStr));
// }
// log(jsonToStrMap('{"yes": true, "no": false}'));
// // 有一种特殊情况, 整个JSON是一个数组,且每个数组成员本身,又是一个有两个成员的数组,这时它可以一一对应的转成Map
// function jsonToMap(jsonStr){
// return new Map(JSON.parse(jsonStr));
// }
// log(jsonToMap('[[true,7],[{"foo": 3},["abc"]]]'));
// WeakMap
// 相同点:WeakMap与Map类似 也是用于生成键值对的集合
// 不同点1:WeakMap只接受对象作为键名(不包括null),不接受其他类型的值作为键名
// 不同点2:WeakMap键名所指向的对象,不计入垃圾回收机制,它的键名所引用的对象都是弱引用,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存
// 基本上,如果你想要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用WeakMap,WeakMap结构有助于防止内存泄漏。WeakMap弱引用的只是键名,而不是键值,键值依然是正常引用
// WeakMap的语法 与Map的区别主要是:1.没有便利操作(keys、values、entries)及size属性;2.无法清空,没有clear方法
// WeakMap的用途
// 用途1:DOM节点作为键名
let myWeakMap = new WeakMap();
myWeakMap.set(
document.getElementById('logo'),
{ timesClicked: 0 }
);
document.getElementById('logo').addEventListener('click', function (){
let logoData = myWeakMap.get(document.getElementById('logo'));
console.log(logoData.timesClicked);
logoData.timesClicked++;
}, false);
document.getElementById('rm').addEventListener('click', function (){
let logoData = myWeakMap.get(document.getElementById('logo'));
console.log('删除前',logoData.timesClicked);
var dom = document.getElementById('logo');
dom.parentElement.removeChild(dom);
let logoData1 = myWeakMap.get(document.getElementById('logo'));
console.log('删除后',logoData1); // undefined
}, false);
// 用途2:部署私有属性
const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
constructor (counter,action){
_counter.set(this, counter);
_action.set(this, action);
}
dec (){
let counter = _counter.get(this);
console.log('counter:', counter);
if(counter < 1) return;
counter--;
_counter.set(this, counter);
if(counter === 0){
_action.get(this)();
}
}
}
var c = new Countdown(3, () => console.log('DONE'));
c.dec();
c.dec();
c.dec();
// WeakRef 弱引用对象
// WeakSet 和 WeakMap都是基于弱引用的数据结构,ES2021提供了WeakRef对象,用于直接创建对象的弱引用
let target = {};
let wr = new WeakRef(target);
log(wr);
let obj = wr.deref();
if(obj){
log('未被清除');
}else {
log('已被清除');
}
target = null;
if(obj){
log('未被清除');
}else {
log('已被清除');
}
// 弱引用对象的一大用处就是作为缓存,未被清除时可以从缓存取值,一旦清除缓存就自动失效
// FinalizationRegistry ES2021引入了清理注册表功能FinalizationRegistry,用来指定目标对象呗垃圾回收机制清除以后,所要执行的回调函数
const registry = new FinalizationRegistry(heldValue => {
// ....
});
registry.register(theObject, "some value");Symbol
- ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值,Symbol 值通过 Symbol 函数生成。
- Symbol 函数的参数只表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的
- Symbol 值不能和其它类型的值进行运算,否则会报错
- Symbol值可以显式的转为字符串
var s = Symbol('foo'); String(s); // 'Symbol(foo)' s.toString(); // 'Symbol(foo)' - Symbol值也可以转为布尔值,但不能转为数值
var s = Symbol('foo'); Boolean(s); // true - Symbol作为对象的属性名时,不能使用点运算符,需要使用方括号结构,在对象内部,使用Symbol值定义属性时,Symbol值必须放在方括号中
- Symbol值作为属性名时,该属性还是公开属性,不是私有属性
- Symbol类型还可以用来定义一组常量,这可以保证没有重复值
- 属性名的遍历:Symbol作为属性名,该属性不会出现在for..in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames() 返回,可以用Object.getOwnPropertySymbols 方法获取,另外Reflect.ownKeys 方法可以返回所有类型的键名,包括常规键名和Symbol键名
- Symbol.for: 有时候我们希望重新使用同一个Symbol值 Symbol.for 方法可以做到。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值,如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。
- Symbol.for() 与 Symbol() 这两种写的,都会生成新的Symbol。区别是,前者会被登记在全局环境中供搜索,而后者不会。Symbol.for()不会每次调用都返回一个新的Symbol类型的值,而是先检查给定的key是否已存在,如果不存在,才回新建一个值。Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。
- Symbol.keyFor(): 该方法返回一个已登记的Symbol类型值的key
- Symbol.for 为 Symbol 值登记的名字是全局环境的,可以在不同的iframe或service.worker中取到同一个值
/**Symbol */
// let s = Symbol();
// 属性名的遍历
// var obj = {};
// var a = Symbol('a');
// var b = Symbol.for('b');
// obj[a] = 'Hello';
// obj[b] = 'World';
// var objectSymbols =Object.getOwnPropertySymbols(obj);
// console.log(objectSymbols);
// var objectReflct = Reflect.ownKeys(obj);
// console.log(objectReflct);
// Symbol.for 为 Symbol 值登记的名字是全局环境的,可以在不同的iframe或service.worker中取到同一个值
// var s1 = Symbol.for('foo');
// var s2 = Symbol.for('foo');
// // console.log(s1 === s2)
// iframe = document.createElement('iframe');
// iframe.src = String(window.location);
// document.body.appendChild(iframe);
// var bool = iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')
// console.log(bool);
// ES6 提供了11个内置的Symbol值,指向语言内部使用的方法
// 1.Symbol.hasInstance -- instanceof 内部调用的这个方法(实际未调用自己设置的这个方法)
// class MyClass {
// [Symbol.hasInstance](foo){
// console.log(foo,'foo')
// return foo instanceof Array;
// }
// }
// var o = new MyClass();
// // var bool = o instanceof Array;
// var bool = o instanceof MyClass;
// console.log(bool);
// console.log(MyClass[Symbol.hasInstance](o));
// 2.Symbol.isConcatSpreadable 等于一个布尔值,表示该数据结构是否是可扩展的
// 对于数组 表示该使用Array.prototype.concat()时,是否可以展开
// let arr1 = ['c', 'd'];
// let result = ['a', 'b'].concat(arr1, 'e');
// console.log(result);
// let arr2 = ['c', 'd'];
// arr2[Symbol.isConcatSpreadable] = false;
// let result = ['a', 'b'].concat(arr2, 'e');
// console.log(result);
// 数组的 Symbol.isConcatSpreadable 属性默认为 true,表示可以展开。类数组对象也可以展开,但其Symbol.isConcatSpreadable默认为false,必须手动打开
// 对于一个类 Symbol.isConcatSpreadable 属性必须写成一个返回布尔值的方法。
// 3.Symbol.species 对象作为构造函数时,如果这个方法存在,就会使用这个属性作为构造函数来创造新的实例对象
// 4. Symbol.match 指向一个函数,调用str.match时会调用
// 5. Symbol.replace 指向一个函数,调用String.prototype.replace 方法时调用
// 6. Symbol.search 指向一个函数,调用String.prototype.replace 方法时调用
// 7. Symbol.split 指向一个方法,调用String.prototype.split 方法时调用
// 8. Symbol.interator 指向默认遍历器方法,即对象在for...of循环时会调用这个方法
// 9. Symbol.toPrimitive 指向一个方法,对象被转为原始类型的值时会调用这个方法,返回该对象对应的原始类型的值
// let obj = {
// [Symbol.toPrimitive] (hint){
// switch (hint){
// case 'number':
// return 123;
// case 'string':
// return 'str';
// case 'default':
// return 'default';
// default:
// throw new Error();
// }
// }
// }
// console.log(2 * obj); // 246
// console.log(3 + obj); // 3default
// console.log(obj === 'default'); // false
// console.log(String(obj)); // default
// 10. Symbol.toStringTag 指向一个方法,在对象上调用 Object.prototype.toString方法时,如果这个属性存在,其返回值会出现在toString方法返回的字符串中,表示对象的类型。也就是说,这个属性可以用于定时[object Object] 或 [object Array]中object后面的字符串
// var type = ({[Symbol.toStringTag]: 'Foo'}).toString();
// console.log(type) // Foo
// class Collection {
// get [Symbol.toStringTag] (){
// return 'xxx';
// }
// }
// var x = new Collection();
// console.log(Object.prototype.toString.call(x)); // [object xxx]
// 11. Symbol.unscopables 指向一个方法,制定了使用with关键字时,哪些属性会被with环境排除
// 没有 unscopables 时
// class MyClass {
// foo(){
// console.log(1);
// return 1;
// }
// }
// var foo = function (){
// console.log(2);
// return 2;
// }
// with(MyClass.prototype){
// foo(); // 1
// }
// 有 unscopables 时
// class MyClass {
// foo(){
// console.log(1);
// return 1;
// }
// get[Symbol.unscopables] (){
// return { foo: true }; // MyClass 的 foo方法被排除在with环境中
// }
// }
// var foo = function (){
// console.log(2);
// return 2;
// }
// with(MyClass.prototype){
// foo();
// }
/**其它相关知识 1 */
// ES6 新增内置对象的 Symbol.toStringTag 属性值如下:
// 可直接在控制台查看 -- 通过构造函数
// JSON[Symbol.toStringTag]: 'JSON'
// Math[Symbol.toStringTag]: 'Math'
// ArrayBuffer.prototype[Symbol.toStringTag]:'ArrayBuffer'
// DataView.prototype[Symbol.toStringTag]: 'DataView'
// Map.prototype[Symbol.toStringTag]: 'Map'
// Promise.prototype[Symbol.toStringTag]: 'Promise'
// Set.prototype[Symbol.toStringTag]: 'Set'
// WeakMap.prototype[Symbol.toStringTag]: 'WeakMap'
// WeakSet.prototype[Symbol.toStringTag]: 'WeakSet'
// Symbol.prototype[Symbol.toStringTag]: 'Symbol'
// 需要生成实例 在实例上查看
// Module[Symbol.toStringTag]: 'Module'
// %TypedArray%.prototype[Symbol.toStringTag]: 'Uint8Array等'
// %MapIteratorPrototype%[Symbol.toStringTag]: 'Map Iterator'
// %SetIteratorPrototype%[Symbol.toStringTag]: 'Set Iterator'
// %StringIteratorPrototype%[Symbol.toStringTag]: 'String Iterator'
// Generator.prototype[Symbol.toStringTag]: 'Generator'
// GeneratorFunction.prototype[Symbol.toStringTag]: 'GeneratorFunction'
/**其它相关知识 2 */
// /*
// dataview 了解
// const buffer = new ArrayBuffer(16);
// const view1 = new DataView(buffer);
// const view2 = new DataView(buffer, 12, 4);
// console.log(view1,view2)
// view1.setInt8(12, 42);
// console.log(view1);
// console.log(view2.getInt8(0));
// DataView 视图是一个可以从二进制ArrayBuffer对象中毒蝎多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题
// 参数: new DataView(buffer [, byteOffset [, byteLength]])
// buffer:一个已经存在的ArrayBuffer或SharedArrayBuffer对象,DataView对象的数据源
// byteOffset:此DataView对象的第一个字节在buffer中的字节偏移,未指定时,默认从第一个字节开始
// byteLength:此DataView对象的字节长度,未指定,则这个视图的长度将匹配buffer的长度
// 返回值:一个表示指定数据缓存区的心 DataView 对象。 --- 你可以把返回的对象想象成一个二进制字节缓存区 array buffer 的“解释器”——它知道如何在读取或写入时正确地转换字节码。这意味着它能在二进制层面处理整数与浮点转化、字节顺序等其他有关的细节问题。
// */
/**其它相关知识 3 */
// /*
// with 语句
// 警告:不建议使用with语句,因为它可能是混淆错误和兼容性问题的根源
// with语句 扩展一个语句的作用域链
// with (expression) {
// statement
// }
// expression
// 将给定的表达式添加到在评估语句时使用的作用域链上。表达式周围的括号是必需的。
// statement
// 任何语句。要执行多个语句,请使用一个块语句 ({ ... })对这些语句进行分组。
// */Proxy
- Proxy 可以理解成在目标对象前架设一个“拦截”层,由它来代理某些操作,可以理解为代理器
- ES6 原生提供Proxy构造函数,用于生成Proxy实例
- 作为构造函数Proxy接受两个参数:第一个参数是所要代理的目标对象,第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作
- 要使Proxy起作用,必须针对Proxy实例进行操作,而不是针对目标对象进行操作
- 一个技巧是将Proxy对象设置到object.proxy属性,从而可以在object对象上调用
- Proxy实例也可以作为其它对象的原型对象
Proxy支持的拦截操作
- get(target,propKey,reveiver) get方法用于拦截某个属性的读取操作;get方法可以继承;利用get操作,可以实现属性的链式操作
- set(target,propKey,value,receiver) set方法拦截对象属性的设置;利用set方法还可以数据绑定,即每当对象发生变化时,会自动更新DOM;结合get和set,可以设置以"_"开头的私有属性,防止其被外界读取
- has(target, propKey) 拦截propKey in proxy 的操作 返回一个布尔值;has方法可以隐藏某些属性,不被in操作符发现;如果原对象(target)不可配置或禁止扩展,此时has拦截会报错
- deleteProperty(target, propKey) 拦截delete proxy[propKey]的操作,返回一个布尔值;deleteProperty用于拦截delete操作,如果这个方法抛出错误或返回false,当前属性就无法被delete命令删除
- enumerate(target) 拦截for(var x in proxy) 返回一个遍历器,即拦截for..in循环如果enumerate方法返回的不是一个对象,就会报错
- hasOwn(target, propKey) 拦截proxy.hasOwnProperty('foo'),返回一个布尔值;
- ownKeys(target) 拦截Object.getOwnpropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组,该方法返回对象所有自身属性,而Object.keys()仅返回对象可遍历的属性
- getOwnpropertyDescriptor(target, propKey) 拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
- defineProperty(target,propKey,propDesc) 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值
- preventExtensions(target) 拦截Object.preventExtensions(proxy) 返回一个布尔值
- getPrototypeOf(target) 拦截Object.getPrototypeOf(proxy),返回一个对象
- isExtensible(target) 拦截Object.isExtensible(proxy),返回一个布尔值
- setPrototypeOf(target, proto) 拦截Object.setPrototypeOf(proxy,proto),返回一个布尔值,
- 如果目标对象时函数,那么还有两种操作可以拦截:
- apply(target, object, args) 拦截Proxy实例作为函数调用的操作,比如proxy(...args)、proxy.call(object,...args)、proxy.apply(object,args);
- construct(target, args, proxy) 拦截Proxy实例作为构造函数调用的操作,比如 new proxy(...args)
/* Proxy */
// var target = {}
// var obj = new Proxy(target, {
// get: function (target, key, receiver){
// console.log(`getting ${key}`, target, key, receiver);
// return Reflect.get(target, key, receiver);
// },
// set: function (target, key, value, receiver){
// console.log(`setting ${key}`, target, key, value, receiver);
// return Reflect.set(target, key, value, receiver);
// }
// })
// obj.count = 1;
// ++obj.count;
// target.count = 1; // 给目标对象直接操作 不会触发proxy 只有操作代理对象Proxy的实例 才会触发
// var proxy = new Proxy({}, {
// get: function(target, property){
// return 35;
// }
// })
// console.log(Object.prototype.toString.call(proxy)); // '[object Object]'
// console.log(Object.prototype.toString.call(Proxy)); // '[object Function]'
// console.log(proxy.time); // 35
// console.log(proxy.name); // 35
// 要使Proxy起作用,必须针对Proxy实例进行操作,而不是针对目标对象进行操作
// var target = {}
// var handler = {}
// var proxy = new Proxy(target, handler);
// proxy.a = 'b'
// console.log(target.a)
// 一个拦截器可以设置多个拦截操作
// var funcTarget = function (x, y){
// return x + y;
// }
// var funcHandler = {
// get: function (target, name){
// if(name === 'prototype') return Object.prototype;
// return 'Hello, ' + name;
// },
// apply: function (target, thisBinding, args){ return args[0]; },
// construct: function (target, args){ return { value: args[1] }; }
// }
// var fProxy = new Proxy(funcTarget, funcHandler);
// console.log(fProxy(1, 2));
// console.log(new fProxy(1, 2).value);
// console.log(fProxy.prototype);
// console.log(fProxy.hhk);
// var objTarget = {}
// var objHandler = {
// get: function (target, key, receiver){
// console.log(`getting ${key}`, target, key, receiver);
// return Reflect.get(target, key, receiver);
// },
// set: function (target, key, value, receiver){
// console.log(`setting ${key}`, target, key, value, receiver);
// return Reflect.set(target, key, value, receiver);
// },
// has: function (target, propKey){
// return false; // 返回值会被强制转为boolean再进行return
// },
// deleteProperty: function (target, propKey){
// // delete code --- 需要自定义删除操作 否则删除失败
// return true;
// },
// }
// var objProxy = new Proxy(objTarget, objHandler);
// console.log('name1' in objProxy); // 需查看在代理对象中的属性,而不是目标对象
// objProxy.name = 1;
// console.log(delete objProxy['name'])
// console.log(objProxy['name'])
// get方法可以继承
// let proto = new Proxy({}, {
// get(target, propertyKey, receiver) {
// console.log('GET '+ propertyKey);
// return target[propertyKey];
// }
// })
// let obj = Object.create(proto);
// obj.xxx
// 使用get方法拦截实现数组读取负数索引
// function createArray (...elements) {
// let handler = {
// get (target, propKey, receiver){
// let index = Number(propKey);
// if(index < 0){
// propKey = String(target.length + index);
// }
// return Reflect.get(target, propKey, receiver);
// }
// };
// let target = [];
// target.push(...elements);
// return new Proxy(target, handler);
// }
// let tempArr = [1, 2, 3];
// let arr = createArray(...tempArr);
// // let arr = createArray('a', 'b', 'c');
// console.log(arr[1], arr[-1]);
// 通过Proxy,可以将读取属性的操作(get)转变为执行某个函数,从而实现属性的链式操作
// var pipe = (function (){
// var pipe;
// return function (value){
// pipe = [];
// return new Proxy({}, {
// get: function (pipeObject, fnName, receiver){
// if(fnName == 'get'){
// return pipe.reduce(function (val, fn){
// return fn(val);
// }, value)
// }
// pipe.push(fnObj[fnName]);
// return receiver;
// }
// });
// }
// }());
// var fnObj ={
// double: n => n * 2,
// pow: n => n * n,
// reverseInt: n => n.toString().split('').reverse().join('') | 0
// }
// var result = pipe(3).double.pow.reverseInt.get;
// console.log(result);
// set 方法用于拦截某个属性的赋值操作
// 假定Person对象有一个age属性 该属性应该是一个不大于200的整数,可以使用Proxy保证age属性符合要求
// let validator = {
// set: function (obj, prop, value){
// if(prop === 'age'){
// if(!Number.isInteger(value)){
// throw new TypeError('The age is not an integer');
// }
// if(value > 200){
// throw new RangeError('The age seems invalid');
// }
// }
// obj[prop] = value;
// }
// }
// let person = new Proxy({}, validator);
// person.age = 30;
// console.log(person.age);
// 利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新DOM
// 利用set方法,还可以设置私有属性,比如以下划线_开头,防止外部访问
// var handler = {
// get (target, key){
// invariant(key, 'get');
// return target[key];
// },
// set (target, key, value){
// invariant(key, 'set');
// target[key] = value;
// return true;
// }
// }
// function invariant (key, action){
// if(key[0] === '_'){
// throw new Error(`Invalid attempt to ${action} privite "${key}" property`);
// }
// }
// var proxy = new Proxy({}, handler);
// proxy._prop
// proxy._prop = 1
// apply() 方法拦截函数的调用、call 和 apply 操作
// apply方法可以接受3个参数,分别是目标对象、目标对象的上下文(this)和目标对象的参数数组
// var target = function (){ return 'I am the target'; };
// var handler = {
// apply: function (){
// return 'I am the proxy';
// }
// };
// var p = new Proxy(target, handler);
// console.log(p());
// var twice = {
// apply: function (target, ctx, args){
// return Reflect.apply(...arguments) * 2;
// }
// };
// function sum (left, right){
// return left + right;
// }
// var proxy = new Proxy(sum, twice);
// console.log(proxy(1, 2));
// console.log(proxy.call(null, 5, 6));
// console.log(proxy.apply(null, [7, 8]));
// // 直接调用Reflect.apply方法也会被拦截
// console.log(Reflect.apply(proxy, null, [9, 10]));
// has() has方法可以隐藏某些属性 不被 in 操作符发现
// var handler = {
// has (target, key){
// if(key[0] === '_'){
// return false;
// }
// return key in target;
// }
// };
// var target = {
// _prop: 'foo',
// prop: 'lip'
// }
// var proxy = new Proxy(target, handler);
// console.log('prop' in proxy);
// console.log('_prop' in proxy);
// 如果原对象不可配置或禁止扩展,那么此时has拦截会报错
// var obj = { a: 10 }
// Object.preventExtensions(obj);
// var p = new Proxy(obj, {
// has: function (target, prop){
// return false;
// }
// });
// console.log('a' in p);
// construct() construct方法用于拦截new 命令
// var handler = {
// construct (target, args){
// return new target(...args);
// }
// }
// var p = new Proxy(function (){}, {
// construct: function (target, args){
// console.log('called: ' + args.join(', '));
// return { value: args[0] * 10};
// }
// });
// console.log(new p(5).value);
// 如果construct方法返回的不是对象,就会抛出错误
// var p = new Proxy(function (){}, {
// construct: function (target, args){
// return 1;
// }
// })
// new p();
// deleteProperty() deleteProperty 方法用于拦截 delete 操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除
// var handler = {
// deleteProperty (target, key){
// // invariant(key, 'delete');
// delete target[key];
// 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;
// defineProperty() defineProperty 方法拦截了Object.defineProperty 操作
// var handler = {
// defineProperty (target, key, descriptor){
// // console.log(target, key, descriptor);
// return false;
// }
// }
// var target = {};
// var proxy = new Proxy(target, handler);
// proxy.foo = 'bar';
// enumerate() enumerate 方法用于拦截for...in循环
// var handler = {
// enumerate (target){
// console.log(target); // 实际未生效 没有打印 enumerate方法未走到 可能是目前的ES标准暂未实现
// return Object.keys(target).filter(key => key[0] !== '_')[Symbol.iterator]();
// }
// }
// var target = {
// prop: 'foo',
// _bar: 'baz',
// _prop: 'foo',
// }
// var proxy = new Proxy(target, handler);
// for(let key in proxy){
// console.log(key);
// }
// var p = new Proxy({}, {
// enumerate (target) {
// console.log('called'); // 未打印
// return ['a', 'b', 'c'][Symbol.iterator]();
// }
// });
// for(var key in p){
// console.log(key); // 未打印
// }
// 如果enumerate方法返回的不是一个对象 就会报错
// var p = new Proxy({}, {
// enumerate (target){
// return 1;
// }
// });
// for(var key in p){
// // 应该报错 实际未报错
// }
// getOwnPropertyDescriptor() getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor,返回一个属性描述对象或undefined
// var handler = {
// getOwnPropertyDescriptor (target, key){
// if(key[0] === '_'){
// // return 1; //返回值不为属性描述对象或undefined时 报错
// return;
// }
// return Object.getOwnPropertyDescriptor(target, key);
// }
// }
// var target = {
// _foo: 'bar',
// baz: 'tar',
// }
// var proxy = new Proxy(target, handler);
// console.log(Object.getOwnPropertyDescriptor(proxy, 'wat'));
// console.log(Object.getOwnPropertyDescriptor(proxy, '_foo'));
// console.log(Object.getOwnPropertyDescriptor(proxy, 'baz'));
// getPrototypeOf() getPrototypeOf方法主要用于拦截Object.getPrototypeOf()运算符,以及下面其他一些操作:
// Object.prototype.__proto__
// Object.prototype.isPrototypeOf()
// Object.getPrototypeOf()
// Reflect.getPrototypeOf()
// instanceof 运算符
// var proto = { a: 1 };
// var p = new Proxy({}, {
// getPrototypeOf (target){
// console.log('called');
// return proto;
// }
// });
// console.log(Object.getPrototypeOf(p) === proto); // true
// isExtensible() isExtensible方法拦截Object.isExtensible 操作
// var p = new Proxy({}, {
// isExtensible (target){
// console.log('called');
// return true;
// }
// });
// console.log(Object.isExtensible(p));
// 这个方法有一个强限制,如果不能满足以下条件就会报错:
// Object.isExtensible(proxy) === Object.isExtensible(target)
// 如下
// var p = new Proxy({}, {
// isExtensible: function (target) {
// return false;
// }
// })
// console.log(Object.isExtensible(p)); // 报错
// ownKeys() ownKeys方法用于拦截Object.keys操作
// let target = {
// hello1: 1,
// world2: 2
// };
// let handler = {
// ownKeys (target){
// console.log('called'); // 此处有调用
// // return ['hello', 'world']; // 设置的返回值没生效 只有target自身有的属性才能生效
// return Reflect.ownKeys(target).concat(['hello', 'world']);
// }
// };
// let proxy = new Proxy(target, handler);
// // console.log(Object.keys(proxy));
// for(let key of Object.keys(proxy)){
// console.log(key); // prop
// }
// 拦截第一个字符为下划线的属性名
// var target = {
// _bar: 'foo',
// _prop: 'bar',
// prop: 'baz',
// };
// var handler = {
// ownKeys (target){
// return Reflect.ownKeys(target).filter(key => key[0] !== '_');
// }
// };
// var proxy = new Proxy(target, handler);
// for(let key of Object.keys(proxy)){
// console.log(key); // prop
// }
// preventExtensions() preventExtensions 方法拦截Object.preventExtensions() 该方法必须返回一个布尔值
// 这个方法有一个限制,只有当Object,isExtensible(proxy)为false(即不可扩展)时,proxy.preventExtensions才能返回true 否则会报错
// var p = new Proxy({}, {
// preventExtensions (target){
// return true;
// }
// })
// console.log(Object.isExtensible(p)); // true
// console.log(Object.preventExtensions(p)); // 报错 只有Object.isExtensible返回false时 Object.preventExtensions才能返回true 否则报错
// 为防止出现这个报错问题,通常要在proxy.preventExtensions方法中调用一次 Object.preventExtensions
// var p = new Proxy({}, {
// preventExtensions (target){
// console.log('called');
// Object.preventExtensions(target);
// return true;
// }
// })
// console.log(Object.isExtensible(p)); // true
// console.log(Object.preventExtensions(p)); // 返回值为proxy实例
// setPrototypeOf() setPrototypeOf方法主要用于拦截Object.setPrototypeOf方法
// 不允许修改原型的例子
// var handler = {
// setPrototypeOf (target, proto){
// throw new Error('Changing the prototype is forbidden'); // 不允许修改原型的场景
// // return Reflect.setPrototypeOf(target, proto); // 设置生效的场景
// }
// };
// var proto = {};
// var target = function (){};
// var proxy = new Proxy(target, handler);
// console.log(Object.setPrototypeOf(proxy, proto));
// console.log(Object.getPrototypeOf(proxy) === proto);
// Proxy.revocable() 返回一个可取消的Proxy实例
// let target = {};
// let handler = {};
// let {proxy, revoke } = Proxy.revocable(target, handler);
// proxy.foo = 123;
// console.log(proxy.foo);
// revoke();
// console.log(proxy.foo); // 报错 TypeError: Cannot perform 'get' on a proxy that has been revokedIterator 接口 和 for...of 循环
- 遍历器(Iterator)是一种机制,为各种不同的数据结构提供统一的访问机制,任何数据结构,只要部署了Iterator接口,就可以完成遍历操作
- Iterator 的作用有三个:一是为各种数据结构提供统一的、简便的访问接口;二是使得数据结构的成员能够按某种 次序排列;三是ES6创造了一种新的遍历命令————for...of循环,Iterator接口主要供for...of消费
- ES6规定,默认的Iterator接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是"可遍历的"
- 在es6中,有3类数据结构原生具备Iterator接口:数组、某些类数组的对象、Set和Map结构
- 调用 Iterator 接口的场景: 解构赋值、扩展运算符、yield*、for...of、Array.from、Map()、Set()、WeakMap()、WeakSet()、Promise.all()、Promise.race()
- 字符串是一个类数组对象,也原生具有Iterator接口,Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器对象,在其上可以调用next方法实现对于字符串的遍历
- for...of循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象(比如arguments对象、DOM NodeList对象)、Generator对象、字符串
- for...in循环只能获得数组或对象的键名key,for...of循环获取的是键值value
- for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性
var arr = [3,4,5]; arr.foo = 'hello'; for(let v of arr){ console.log(v); // 3,4,5 } for(let i in arr){ console.log(i); // 0,1,2'foo' } - 计算生成的数据结构:有些数据结构是在现有数据结构的基础上计算生成的,比如ES6的数组、Set、Map都部署了entries、keys、values三个方法,调用后都返回遍历器对象
- 对于普通对象,for...of循环不能直接使用,可以通过Object.keys获取对象的键名,再使用for...of遍历这个数组;或者将数组的Symbol.iterator赋给其它对象的Symbol.iterator(比如jQuery对象);或者使用Generator函数将对象重新包装一下
var obj = { a: 1, b: 2, c: 3 } function* entries(obj){ for(let key of Object.keys(obj)){ yield [key, obj[key]]; } } for(let [key,value] of entries(obj)){ console.log(key, "->", value); } - for...of与其它遍历语法的比较:
- 数组最原始的for循环:写法比较麻烦
- 数组forEach循环:无法中途跳出forEach循环,break命令或return命令都不能奏效
- for...in循环可以遍历键名,但还会遍历手动添加的其它键,甚至包括原型链上的键,且某些情况下回以任意顺序遍历键名,for...in循环主要是为遍历对象而设计的,不适用于遍历数组
- for...of写法同for...in一样简洁,且可以配合break、continue、return使用
// Iterator 接口 和 for...of 循环
// yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口
// let generator = function* (){
// yield 1;
// yield* [2,3,4];
// yield 5;
// }
// var iterator = generator();
// console.log(iterator.next());
// console.log(iterator.next());
// console.log(iterator.next());
// console.log(iterator.next());
// console.log(iterator.next());
// console.log(iterator.next());
// 可以覆盖原生的Symbol.iterator方法,达到修改遍历器行为的目的
// var str = new String('hi');
// console.log([...str]);
// str[Symbol.iterator] = function (){
// return {
// next: function (){
// if(this._first){
// this._first = false;
// return { value: 'haha', done: false }
// } else {
// return { done: true }
// }
// // return { value: 'haha', done: false } // 会一直取next 导致内存溢出
// // return { value: 'haha', done: true } // done:true则不会内存溢出
// },
// _first: true
// }
// }
// console.log([...str]);
// console.log(str);
// Symbol.iterator 方法的最简单实现还是使用 Generator 函数
// let obj = {
// * [Symbol.iterator] (){
// yield 'hello';
// yield 'world';
// // yield* 'hello';
// // yield* 'world';
// }
// };
// for(let x of obj){
// console.log(x);
// }
// for...of循环内部调用的是数据结构的 Symbol.iterator 方法
// 数组
// var arr = ['red','green','blue'];
// var iterator = arr[Symbol.iterator]();
// for(let v of arr){
// console.log(v);
// }
// for(let v of iterator){
// console.log(v);
// }
// var arr = [3,4,5];
// arr.foo = 'hello';
// for(let v of arr){
// console.log(v); // 3,4,5
// }
// for(let i in arr){
// console.log(i); // 0,1,2'foo'
// }
// 普通对象
// jQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// var obj = {
// a: 1,
// b: 2,
// c: 3
// }
// function* entries(obj){
// for(let key of Object.keys(obj)){
// yield [key, obj[key]];
// }
// }
// for(let [key,value] of entries(obj)){
// console.log(key, "->", value);
// }Generator函数
- 可以把Generator函数理解成一个状态机,内部封装了很多状态
- 调用Generator函数后,该函数并不执行,返回的也不是函数运行的结果,而是返回一个遍历器对象(一个指向内部状态的指针对象),
- 必须调用遍历器对象的next方法,使得指针移向下一状态,next方法返回一个有着value和done属性的对象
- Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行
- yield语句和return语句有区别,yield语句可以执行多次,return语句只执行一次,执行return后 返回的对象done变为true
- yield语句本身没有返回值,会者说返回值总是undefined
- next方法可以带一个参数,该参数会被当做上一条yield语句的返回值
- for...of循环可以自动遍历Generator函数,此时不需要调用next方法
- for...of循环、扩展运算符、解构赋值、Array.from方法内部调用的都是遍历器接口,所以都可以将Generator函数返回的Iterator对象作为参数
- 原生的js对象没有遍历接口,可以使用for...of遍历对象的key的数组,再配合Generator函数和yield 为对象部署遍历器接口
- Generator函数返回的遍历器对象上有一个throw方法:Generator.prototype.throw() 该方法可以在函数体外抛出错误,然后在函数体内捕获
- Generator函数返回的遍历器对象上有一个return方法:Generator.prototype.return() 可以返回给定的值,并结束Generator函数的遍历,遍历器对象调用return方法后,返回值的value属性就是return方法的参数,同时Generator函数终止遍历,返回值的done属性为true,以后再调用next方法,done属性总是返回true
- yield* 语句可以在一个Generator函数内执行另一个Generator函数:从语法角度看,如果一个yield命令后面跟的是一个遍历器对象,那么需要在yield命令后面加上星号*,表明返回的是一个遍历器对象。yield* 可以视为for...of和yield组合的简写。
- Generator函数返回的总是遍历器对象,而不是 this 对象 (可以先创建一个空对象,在通过bind绑定Generator函数内部的this)
- Generator函数推导:针对大数组的遍历,可以在遍历时再生成数组,节省系统资源
- Generator 与状态机:相比ES5实现状态机,不用再保存外部状态变量,更简洁,更安全(不会被非法篡改),更符合函数式编程思想;Generator之所以可以不用外部变量保存状态,因为它本身就包含了一个状态信息,即目前是否处于暂停状态
- Generator函数的应用
- 异步操作的同步化表达:ajax请求、读文件等异步操作
- 控制流管理
- 部署Interator接口
- 作为数据结构
// Generator 函数
// function* helloWorldGenerator() {
// console.log(1);
// yield 'hello';
// console.log(2);
// yield 'world';
// console.log(3);
// return 'ending';
// }
// var hw = helloWorldGenerator();
// console.log(hw)
// hw.next();
// hw.next();
// function * f(){
// for(var i = 0; true; i++){
// var reset = yield i;
// console.log(reset,'reset');
// if(reset){ i = -1; }
// }
// }
// var g = f();
// console.log(g.next());
// console.log(g.next());
// console.log(g.next());
// console.log(g.next());
// console.log(g.next(true));
// function* dataConsumer (){
// console.log('Started');
// console.log(`1.${yield}`);
// console.log(`2.${yield}`);
// return 'result';
// }
// let genObj = dataConsumer();
// genObj.next(); // Started
// genObj.next('hi'); // 1.hi
// genObj.next('haha'); // 2.haha
// genObj.next('hehe'); // 已结束运行
// var g = function* (){
// while (true){
// try{
// yield;
// } catch (e){
// if(e != 'a') throw e;
// console.log('内部捕获', e);
// }
// }
// }
// var i = g();
// i.next();
// try {
// i.throw('a');
// i.throw('b');
// } catch (e){
// console.log('外部捕获', e);
// }
// var gen = function* (){
// yield console.log('hello');
// yield console.log('world');
// }
// var g = gen();
// g.next();
// try{
// // g.throw();
// throw new Error();
// } catch (e){
// console.log('catch')
// g.next();
// }
// function* getFuncWithReturn (){
// yield 'a';
// yield 'b';
// return 'the result';
// }
// function* logReturned(getObj){
// let result = yield* getObj;
// console.log(result);
// }
// var it = [...logReturned(getFuncWithReturn())];
// 使用yield* 命令去除嵌套数组的所有成员
// function* iterTree (tree){
// if(Array.isArray(tree)){
// for(let i = 0;i < tree.length;i++){
// yield* iterTree(tree[i]);
// }
// } else {
// yield tree;
// }
// }
// var tree = ['a', ['b', 'c'], ['d', 'e']];
// for(let x of iterTree(tree)){
// console.log(x);
// }
/** 使用yield*语句遍历完全二叉树 */
// // 二叉树构造函数,三个参数分别是:左子树、当前节点、右子树
// function Tree (left, label, right){
// this.left = left;
// this.label = label;
// this.right = right;
// }
// // 中序(inorder)遍历函数
// // 由于返回的是一个遍历器,所以要使用Generator函数
// // 函数体内部采用递归算法,所以左子树、右子树需要用yield*遍历
// function* inorder (t){
// if(t){
// yield* inorder(t.left);
// yield t.label;
// yield* inorder(t.right);
// }
// }
// // 生成二叉树
// function make(array){
// if(array.length == 1) return new Tree(null, array[0], null);
// return new Tree(make(array[0]), array[1], make(array[2]));
// }
// var tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
// // 遍历二叉树
// var result = [];
// for(let node of inorder(tree)){
// result.push(node);
// }
// function* g(){}
// g.prototype.hello = function (){
// return 'hi';
// }
// let obj = g();
// obj instanceof g; //true
// obj.hello(); // hi
// function* F(){
// yield this.x = 2;
// yield this.y = 3;
// }
// console.log('next' in new F()); // F is not a constructor // chrome 101
// function* F(){
// yield this.x = 2;
// yield this.y = 3;
// }
// var obj = {};
// var f = F.call(obj);
// console.log(f.next());
// console.log(f.next());
// console.log(f.next());
// // Generator 函数推导
// var bigGenerator = function* (){
// for(let i = 0;i < 100000; i++){
// yield i;
// }
// }
// // var squared = (for (n of bigGenerator()) n*n); // 报错
// var squared = bigGenerator();
// console.log(squared.next());
// Generator 与状态机
// var clock = function*(_){
// while(true){
// console.log('Tick!');
// yield _;
// console.log('Tock!');
// yield _;
// }
// };
// var c = clock();
// c.next();
// c.next();
// c.next();
// 通过Generator函数逐行读取文本
// function* numbers(){
// let file = new FileReader('a.txt'); // 此种方式读取文件暂不支持 需要配合input来读取
// try {
// while(!file.eof) {
// yield parseInt(file.readLine(), 10);
// }
// } finally {
// file.close();
// }
// }
/** FileReader 读取文件 */
// var myFile = document.querySelector('#myFile');
// myFile.onchange = function (){
// var file = myFile.files[0];
// console.log('file', file); // File 对象
// let reader = new FileReader('a.txt');
// // reader.readAsDataURL(file); // 读取为base64
// reader.readAsText(file); // 读取为文本
// reader.onload = function (){
// // const img = new Image() // 若读取的文件是图片 则采用base64格式赋给Img对象
// // img.src = reader.result
// var data = reader.result;
// console.log('data:', data);
// document.body.innerHTML += reader.result
// }
// reader.onerror = function (){
// console.log('读取失败');
// }
// }
/**nodejs读取文件 */
// 1. 通过readline的方式
// const fs = require('fs');
// const readline = require('readline');
// let rl = readline.createInterface({
// input: fs.createReadStream("./index.md")
// })
// let index = 0;
// rl.on('line', line => {
// index++;
// console.log(`第${index}行`, line);
// })
// 2.通过stream的方式Reflect
- Reflect 对象与Proxy对象一样,也是ES6 为了操作对象而新提供的API,Reflect对象的设计目的有以下几个:
- 1.将Object对象的一些明显属于语言层面的方法放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来新方法将只部署在Reflect对象上
- 2.修改某些Object方法的返回结果,让其变得更合理。比如Object.defineProperty(obj, name, desc)在无法定义属性时会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
- 3.让Object操作都编程函数行为。某些Object操作是命令式,比如 name in obj 和 delete obj[name],而 Reflect.has(obj, name) 和 Reflect.deleteProperty(obj, name)让它们变成了函数行为
- 4.Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为
Reflect对象的方法
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.defineProperty(target, name, desc) -- 返回一个布尔值,表示操作是否成功,其对应的Object方法在失败时会抛出错误
- Reflect.getOwnPropertyNames(target)
- Reflect.getPrototypeOf(target) -- 读取对象的 __proto__属性,等同于Object.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype) -- 设置对象的__proto__属性,(Object没有与此对应的方法)
- Reflect.deleteProoerty(target, name) -- 等同于 delete target[name]
- Reflect.enumerate(target)
- Reflect.freeze(target) -- 返回一个布尔值,表示操作是否成功,其对应的Object方法在失败时会抛出错误
- Reflect.seal(target) -- 封闭一个对象,阻止添加新属性,并将所有现有属性标记为不可配置,当前属性的值只要原来是可写的就可以改变;返回一个布尔值,表示操作是否成功,其对应的Object方法在失败时会抛出错误
- Reflect.preventExtensions(target) -- 返回一个布尔值,表示操作是否成功,其对应的Object方法在失败时会抛出错误
- Reflect.isFrozen(target)
- Reflect.isSealed(target)
- Reflect.isExtensible(target)
- Reflect.has(target, name) -- 等同于 name in target
- Reflect.hasOwn(target, name)
- Reflect.keys(target)
- Reflect.get(target, name, receiver) -- 查找兵返回target的name属性,如果没有该属性,返回undefined,如果name属性部署了读取函数,则读取函数的this绑定receiver
- Reflect.set(target, name, value, receiver) -- 设置target对象的name属性等于value,如果name属性设置了赋值函数,则赋值函数的this绑定receiver,返回一个布尔值,表示操作是否成功,其对应的Object方法在失败时会抛出错误
- Reflect.apply(target, thisArg, args) -- 等同于Function.prototype.apply.call(target, thisArg, args) 一般来说 要绑定一个函数的this对象,可以写成fn.apply(obj,args),但如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args) 采用Reflect对象可以简化这种操作
- Reflect.construct(target, args) -- 等同于 new target(...args) 这提供了一种不使用new 来调用构造函数的方法
// Reflect
// let target = {}
// let proxy = new Proxy(target, {
// set (target, propKey, value, receiver){
// var success = Reflect.set(target, propKey, value, receiver);
// if(success){
// console.log(`property` + propKey + ' on ' + target + ' set to ' + value);
// }
// return success;
// }
// })
// proxy.name = 'abc'
// console.log(proxy.name)
// var obj = {};
// var loggedObj = new Proxy(obj, {
// get(target, name){
// console.log('get', target, name);
// return Reflect.get(target, name);
// },
// set(target, name, value){
// console.log('set ' + name + ' of ' + value);
// return Reflect.set(target, name, value);
// },
// deleteProperty (target, name){
// console.log('delete' + name);
// return Reflect.deleteProperty(target, name);
// },
// has (target, name){
// console.log('has' + name);
// return Reflect.has(target, name);
// }
// });
// loggedObj.name = 1;
// console.log('name' in loggedObj);
// delete loggedObj.name;
// console.log('name' in loggedObj);二进制数组
- 二进制数组(ArrayBuffer对象、TypedArray视图 和 DataView视图) 是JavaScript操作二进制数据的一个接口。
- 这个接口的原始设计目的与WebGL项目有关,允许像C语言一样直接操作字节。二进制数组很像C语言的数组,允许开发者以数组下标的形式直接操作内存,使开发者能通过JavaScript与操作系统的原生接口欧进行二进制通信
- ArrayBuffer对象:代表内存中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这样就可以用数组的方法操作内存
- TypedArray视图:共包括9种视图
- Int8 8位带符号整数 字节长度1 对应的C语言类型:signed char
- Uint8 8位不带符号整数 字节长度1 对应的C语言类型:unsigned char
- Uint8C 8位不带符号整数(自动过滤溢出) 字节长度1 对应的C语言类型:unsigned char
- Int16 18位带符号整数 字节长度2 对应的C语言类型:short
- Uint16 16位不带符号整数 字节长度2 对应的C语言类型:unsigned short
- Int32 32位带符号整数 字节长度4 对应的C语言类型:int
- Uint32 32位不带符号整数 字节长度4 对应的C语言类型:unsigned int
- Float32 32位浮点数 字节长度4 对应的C语言类型:float
- Float64 64位浮点数 字节长度8 对应的C语言类型:double
- DataView视图:可以自定义复合格式的视图。可以第一个字节是Uint8、第二个字节是Int16等
- 总结:ArrayBuffer对象代表原始的二进制数据,TypedArray视图用于读/写简单类型的二进制数据,DataView视图用于读/写复杂类型的二进制数据
- 很多浏览器操作的API用到了二进制数组操作二进制数据,比如:File API、XMLHttpRequest、Fetch API、Canvas、WebSockets
ArrayBuffer对象
- ArrayBuffer对象代表储存二进制数据的一段内存,它不能直接读/写,只能通过视图(TypedArray视图和DataView视图)读/写,视图的作用是以指定格式解读二进制数据
- ArrayBuffer也是一个构造函数,参数是所需要的的内存大小,单位是字节;为了读/写这段内存,需要为它指定视图,创建DataView视图,需要提供ArrayBuffer对象实例作为参数。
var buf = new ArrayBuffer(32); var dataView = new DataView(buf); - TypedArray视图与DataView视图的一个区别是,它不是一个构造函数,而是一组构造函数,使用两种不能视图操作同一段内存时,一个视图修改底层内存会影响到另一个
- ArrayBuffer.prototype.byteLength ArrayBuffer实例的byteLength属性返回所分配的内存区域的字节长度(如果分配的内存区域很大,有可能失败,因为可能没有那么多连续空余的内存,因此有必要检测是否分配成功)
- ArrayBuffer.prototype.slice() ArrayBuffer 实例有一个slice方法,允许将内存区域的一部分复制生成一个新的ArrayBuffer对象;slice方法其实包含两步,第一步先分配一段新内存,第二部将原来那个ArrayBuffer对象复制过去;slice方法接受两个参数,第一个参数表示复制开始的字节序号(包含该字节),第二个参数表示复制截止的字节序号(不含该字节),如果省略第二个参数,则默认复制到原ArrayBuffer对象的结尾 (除了slice方法,ArrayBuffer对象不提供任何直接读/写内存的方法,只允许在其上建立视图,然后通过视图进行读/写)
- ArrayBuffer.isView() ArrayBuffer 有一个静态方法isView,返回一个布尔值,表示参数是否为ArrayBuffer的视图实例,即判断参数是否为TypedArray实例或DataView实例
TypedArray 视图
- ArrayBuffer对象作为内存区域可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)。TypedArray视图一共包括9种类型,每一种视图都是一种构造函数。
- Int8 8位带符号整数 字节长度1 对应的C语言类型:signed char
- Uint8 8位不带符号整数 字节长度1 对应的C语言类型:unsigned char
- Uint8C 8位不带符号整数(自动过滤溢出) 字节长度1 对应的C语言类型:unsigned char
- Int16 18位带符号整数 字节长度2 对应的C语言类型:short
- Uint16 16位不带符号整数 字节长度2 对应的C语言类型:unsigned short
- Int32 32位带符号整数 字节长度4 对应的C语言类型:int
- Uint32 32位不带符号整数 字节长度4 对应的C语言类型:unsigned int
- Float32 32位浮点数 字节长度4 对应的C语言类型:float
- Float64 64位浮点数 字节长度8 对应的C语言类型:double
- 普通数组与TypedArray数组的差异主要有:
- TypedArray 数组的所有成员都是同一种类型
- TypedArray数组的成员是连续的,不会有空位
- TypedArray数组成员的默认值是0
- TypedArray数组只是一层视图,本身不储存数据,它的数据都储存在底层的ArrayBuffer对象中,要获取底层对象必须使用buffer属性
- 构造函数,TypedArray的9种构造函数,可以生成不同类型的数组实例,视图构造函数可以接受3个参数:
- 第一个参数buffer(必需):视图对应的底层ArrayBuffer对象
- 第二个参数byteOffset=0(可选):视图开始的字节序号,默认从0开始 -- btyeOffset必需与所要建立的数据类型一致,否则报错(即ArrayBuffer中的字节数与视图的字节数能匹配),如果想从任意字节开始解读ArrayBuffer,必需使用DataView视图
- 第三个参数length(可选):视图包含的数据个数,默认直到本段内存区域结束
- TypedArray(length)视图还可以不通过ArrayBuffer对象,而是直接分配内存生成,此时构造函数的参数为成员数,即对应length属性
- TypedArray(typedArray) TypedArray数组的构造函数还可以接受另一个TypedArray实例作为参数,新数组会重新开辟一段内存储存数据,而不会共用之前的内存
- TypedArray(arrayLikeObject)构造函数的参数也可以是一个普通数组,然后直接生成TypedArray实例,TypedArray数组也可以转换回普通数组(Array.prototype.slice.call(typedArray))
- 数组方法:普通数组的操作方法和属性对TypedArray数组完全适用,但TypedArray数组没有concat方法
- TypedArray数组与普通数组一样部署了Iterator接口,可以使用for...of循环
- 字节序:字节序指的是数值在内存中的表示方式。x86体系的计算机都采用小端字节序(小端字节序将最不重要的字节排在后面,大端字节序相反),TypedArray数组内部也采用小端字节序读/写数据,或者更准确的说,按照本机操作系统设定的字节序读/写数据,这就导致对于大端字节序,TypedArray无法正确解析,为此引入了DataView对象,可以设定字节序
- BYTES_PER_ELEMENT 属性:每一种视图的构造函数,都有一个BYTES_PER_ELEMENT属性,表示这种数据类型占据的字节数,这个属性在TypedArray实例上也可以获取
- ArrayBuffer与字符串的互相转换:两者相互转换有一个前提,即字符串的编码方式是确定的
- 溢出:TypedArray数组对溢出才用的处理方法是求余值。正向溢出时,等于最小值加余值减一;负向溢出时,等于最大值减余值加一;不同长度级是否带符号的TypedArray数组,取值范围不一样;8位无符号整数范围为0~255;8位带符号整数的取值范围是-128~127。Uint8ClampedArray视图的溢出规则不太一样,负向溢出都为0,正向溢出都为255。
- TypedArray.prototype.buffer:TypedArray实例的buffer属性返回整段内存对应的ArrayBuffer对象,只读属性
- TypedArray.prototype.byteLength: 该属性返回TypedArray数组占据的内存长度,单位为字节,只读属性
- TypedArray.prototype.byteOffset:该属性返回TypedArray数组从底层ArrayBuffer对象的哪个字节开始,只读属性
- TypedArray.prototype.length:该属性表示TypedArray数组含有多少个成员,是成员长度,对应8位的TypedArray数组,length和byteLength一致
- TypedArray.prototype.set() set方法用于复制数组,也就是将一段内存完全复制到另一段内存,set方法还可以接受第二个参数,表示从目标对象的第几个成员,开始复制被复制对象
- TypedArray.prototype.subarray() subarray方法是对于TypedArray数组的一部分再建立一个新的视图,subarray方法的第一个参数是起始成员序号,第二个参数是结束成员序号(不包含,省略则包含剩余的全部成员)
- TypedArray.prototype.slice() slice方法返回一个指定位置的TypedArray实例,slice方法的参数表示原数组的具体位置,负值表示倒数第几个;
- TypedArray.of() of方法用于将一个参数转为TypedArray实例
- TypedArray.from() from方法接受一个可遍历的数据结构(比如数组)作为参数,返回一个机遇次结构的TypedArray实例;该方法还可以将一种TypedArray实例转为另一种;from方法还可以接受一个函数作为第二个传参数,用于对每个元素进行遍历,类似map方法 --- 函数的处理在from之后,先转换类型再进行运算判断是否溢出
复合视图
- 由于视图的构造函数可以指定起始位置和长度,所以同一段内存中可以依次存放不同类型的数据,叫做 复合视图
DataView视图
- 如果一段数据中包括多种数据类型(比如服务器传来的HTTP数据),这是除了复合视图外,还可以通过DataView视图来操作
- DataView视图本身也是构造函数,接受一个ArrayBuffer对象作为参数生成视图
- 原型上有三个属性:buffer、btyeLength、byteOffset
- 8个方法读取内存:getInt8、getUint8等
- 8个方法写入内存:setInt8、setUint8等
二进制数组的应用
- Ajax
- Canvas
- WebSocket
- Fetch API
- File API
// 二进制数组
// ArrayBuffer也是一个构造函数,参数是所需要的的内存大小,单位是字节;为了读/写这段内存,需要为它指定视图,创建DataView视图,需要提供ArrayBuffer对象实例作为参数
// var buf = new ArrayBuffer(32);
// var dataView = new DataView(buf);
// console.log(dataView.getUint8(0));
// 使用两种不能视图操作同一段内存时,一个视图修改底层内存会影响到另一个
// var buffer = new ArrayBuffer(12);
// var x1 = new Int32Array(buffer);
// x1[0] = 1;
// var x2 = new Uint8Array(buffer);
// console.log(x2[0]);
// x2[0] = 2;
// console.log(x1[0],x2[0]);
// 检测是否分配内存成功
// var buf = new ArrayBuffer(19);
// console.log(buf.byteLength);
// if(buf.byteLength === 19){
// console.log('内存分配成功');
// } else {
// console.log('内存分配失败');
// }
// var buffer = new ArrayBuffer(8);
// var newBuffer = buffer.slice(0, 3);
// var buffer = new ArrayBuffer(8);
// console.log(ArrayBuffer.isView(buffer)); // false
// var v = new Int32Array(buffer);
// console.log(ArrayBuffer.isView(v)); // true
// 同一个ArrayBuffer对象上,可以根据不同的数据类型建立多个视图
// 创建一个8字节的ArrayBuffer
// var b = new ArrayBuffer(8);
// // 创建一个指向b的Int32视图,开始于字节0,直到缓冲区的末尾
// var v1 = new Int32Array(b);
// // 创建一个指向b的Unit8视图,开始于字节2,知道缓冲区末尾
// var v2 = new Uint8Array(b, 2);
// // 创建一个指向b的Int16视图 开始于字节2,长度为2
// var v3 = new Int16Array(b, 2, 2);
// var buffer = new ArrayBuffer(8);
// // 可以理解为用Int16视图解读此buffer时,前面空出的字节也一样得用这种方式解读 所以起始字节需要能被整除
// var i16 = new Int16Array(buffer, 1); // Uncaught RangeError: start offset of Int16Array should be a multiple of 2
// var f64a = new Float64Array(8);
// f64a[0] = 10;
// f64a[1] = 20;
// f64a[2] = f64a[0] + f64a[1];
// console.log(f64a);
// TypedArray数组的构造函数可以接受另一个TypedArray实例或普通数组作为参数,新数组会开辟一段新的内存储存数据
// var x = new Int8Array([1, 1]);
// var y = new Int8Array(x);
// console.log(x[0]);
// console.log(y[0]);
// x[0] = 2;
// console.log(x[0]);
// console.log(y[0]);
// TypedArray数组也可以转换回普通数组
// var typedArray = new Int8Array([1,2,3]);
// var normalArray = Array.prototype.slice.call(typedArray);
// console.log(normalArray);
// TypedArray数组没有concat方法,如果想要合并多个TypedArrray数组,可以用下面这个函数
// function concatnate (resultConstructor, ...arrays){
// let totalLength = 0;
// for(let arr of arrays){
// totalLength += arr.length;
// }
// let result = new resultConstructor(totalLength);
// let offset = 0;
// for(let arr of arrays){
// result.set(arr, offset);
// offset += arr.length;
// }
// return result;
// }
// var result = concatnate(Uint8Array, Uint8Array.of(1, 2), Uint16Array.of(3, 4));
// console.log(result);
// TypedArray数组与普通数组一样部署了 Interator 接口,所以可以遍历
// let ui8 = Uint8Array.of(0, 1, 3);
// for(let byte of ui8){
// console.log(byte);
// }
// 字节序是指数值在内存中的表示方式
// var buffer = new ArrayBuffer(16);
// var int32View = new Int32Array(buffer);
// for(var i = 0;i < int32View.length;i++){
// int32View[i] = i * 2;
// }
// var int16View = new Int16Array(buffer);
// for(var i = 0;i < int16View.length;i++){
// console.log('Entry '+ i + ': ' + int16View[i]);
// }
// 假定某段buffer包含如下字节: [0x02,0x01,0x03,0x07] --- 0x开头为16进制数字 0b二进制 0八进制 可直接以此定义变量
// var buffer = new ArrayBuffer(4);
// var v1 = new Uint8Array(buffer);
// v1[0] = 2;
// v1[1] = 1;
// v1[2] = 3;
// v1[3] = 7;
// var uInt16View = new Uint16Array(buffer);
// // 计算机采用小端字节序 所以头两个字节等于258
// if(uInt16View[0] === 258){
// // 计算机是小端字节序时,Uint8Array视图在内存中第一个数字为0x02 第二个为0x01 而在Uint16Array中读取的是0x0102 将决定其大小的最不重要的字节放在前面,最重要的字节放在后面 16进制0x0102对应10进制也就是258
// console.log('OK');
// }
// for(var i=0;i<v1.length;i++){
// console.log('v1:' + v1[i]);
// }
// for(var i=0;i<uInt16View.length;i++){
// console.log('uInt16View:' + uInt16View[i]);
// }
// 下面函数可以用于判断当前视图是小端字节序还是大端字节序
// const BIG_ENDIAN = Symbol('BIG_ENDIAN');
// const LITTLE_ENDIAN = Symbol('LITTLE_ENDIAN');
// function getPlatformEndianness(){
// let arr32 = Uint32Array.of(0x12345678);
// let arr8 = new Uint8Array(arr32.buffer);
// switch ((arr8[0] * 0x1000000) + (arr8[1] * 0x10000) + (arr8[2] * 0x100) + (arr8[3])) {
// case 0x12345678:
// return BIG_ENDIAN;
// case 0x78563412:
// return LITTLE_ENDIAN;
// default:
// throw new Error('Unknown endianness');
// }
// }
// let result = getPlatformEndianness();
// console.log(result);
// ArrayBuffer与字符串的互相转换
// ArrayBuffer转为字符串,参数为ArrayBuffer对象
// function ab2str (buf){
// return String.fromCharCode.apply(null, new Uint16Array(buf));
// }
// var buf = new ArrayBuffer(8);
// console.log(ab2str(buf));
// 字符串转为ArrayBuffer对象,参数为字符串
// function str2ab(str){
// var buf = new ArrayBuffer(str.length * 2); // 每个字符占用两个字节
// var bufView = new Uint16Array(buf);
// for(var i = 0;i < str.length;i++){
// bufView[i] = str.charCodeAt(i);
// }
// return buf;
// }
// console.log(str2ab('abc'));
// 溢出 正向溢出 和 负向溢出
// var uint8 = new Uint8Array(1); // 8位无符号整数Uint8Array的取值范围是 0~255
// uint8[0] = 256;
// console.log(uint8[0]); // 0 正向溢出取最小值加余值再减1
// uint8[0] = -1;
// console.log(uint8[0]); // 255 负向溢出取最大值减余值再加1
// var int8 = new Int8Array(1); // 8位带符号整数Int8Array的取值范围是-128~127
// int8[0] = 128;
// console.log(int8[0]); // 正向溢出
// int8[0] = -129;
// console.log(int8[0]); // 负向溢出
// Uint8ClampedArray视图的溢出与其它视图有所区别,负向溢出等于0,正向溢出都等于255
// var uint8c = new Uint8ClampedArray(1);
// uint8c[0] = 256;
// console.log(uint8c[0]);
// uint8c[0] = -1;
// console.log(uint8c[0]);
// TypedArray实例的buffer属性返回整段内存区域对应的ArrayBuffer对象,只读属性
// var a = new Float32Array(64);
// var b = new Uint8Array(a.buffer);
// byteLength属性返回TypedArray数组占据的内存长度,单位是字节。byteOffset属性返回TypedArray数组从底层ArrayBuffer对象的哪个字节开始,只读属性
// var b = new ArrayBuffer(8);
// var v1 = new Int32Array(b);
// var v2 = new Uint8Array(b, 2);
// var v3 = new Int16Array(b, 2, 2);
// console.log(v1.byteLength);
// console.log(v2.byteLength);
// console.log(v3.byteLength);
// console.log(v1.byteOffset);
// console.log(v2.byteOffset);
// console.log(v3.byteOffset);
// TypedArray.prototype.length length属性表示TypedArray数组含有多少个成员
// var a = new Int16Array(8);
// console.log(a.length);
// console.log(a.byteLength);
// console.log(a);
// TypedArray.prototype.set() 用于复制数组 将一段内存复制到另一段
// var a = new Uint8Array(8);
// var b = new Uint8Array(8);
// a[0] = 1;
// console.log(a,b);
// b.set(a);
// console.log(a,b);
// b = new Uint8Array(10);
// b.set(a,2)// 长度需对应
// console.log(a,b);
// TypedArray.prototype.subarray()对TypedArray数组的一部分再建立一个新的视图
// var a = new Uint16Array(8);
// var b = a.subarray(2,3);
// console.log(a.byteLength, b.byteLength, a, b);
// TypedArray.prototype.slice() 返回一个指定位置的新的TypedArray实例
// let ui8 = Uint8Array.of(0, 1, 2);
// console.log(ui8.slice(-1));
// TypedArray.of() 将参数转为一个TypedArray实例
// console.log(Float32Array.of(0.151, -8, 3.7));
// 下面三种方法会生成同样的TypedArray数组
// 方法一
// let tarr = new Uint8Array([1,2,3]);
// console.log(tarr);
// 方法二
// let tarr = Uint8Array.of(1, 2, 3);
// console.log(tarr);
// 方法三
// let tarr = new Uint8Array(3);
// tarr[0] = 1;
// tarr[1] = 2;
// tarr[2] = 3;
// console.log(tarr);
// TypedArray.from() 接受一个可遍历的数据结构作为参数,返回一个基于此结构的TypedArray实例
// console.log(Uint16Array.from([1,2,3]));
// 这个方法还可以将一种TypedArray实例转为另一种
// var ui16 = Uint16Array.from(Uint8Array.of(0,1,2));
// console.log(ui16 instanceof Uint16Array,ui16);
// from方法还可以接受一个函数作为第二参数,用于对每个元素进行遍历,类似map方法
// var i8 = Int8Array.of(127,126,125).map(x=>2*x);
// console.log(i8); // 有溢出 Int8Array([-2,-4,-6])
// var i16 = Int16Array.from(Int8Array.of(127, 126, 125), x => 2 * x);
// console.log(i16); //没有溢出 说明遍历针对的是from操作后的16位整数数组而不是原来的8位整数数组es6数组方法总结
1、forEach
特点:不改变原数组、无返回值、不能用break跳出循环(只能return跳出单次循环)
参数: 单项item 索引index 原数组arr2、map
特点:不改变原数组 返回一个新数组(中间可对item进行处理) 不能break跳出循环(只能return跳出单次循环)
参数: 单项item 索引index 原数组arr --> forEach&map 区别在于是否返回一个新数组3、filter
特点: 不改变原数组 返回一个新数组 不能break 返回过滤后的数组 参数: 单项item 索引index 原数组arr --> filter&map 区别在于return的返回机制 map的return是真正把return后的值返回给新数组当成其对应的一个新item filter的return 是判断return后面值 强转为boolean以后 为true则将此次循环的item返回 即push到新数组 为false 则 跳出此次循环进行下一次 并且不往新数组里面push任何内容4、every 特点: 不改变原数组 返回一个布尔值 不能break 执行每次循环的 return 后的语句 如果return后的语句都为 true 则整次循环返回 true 否则返回 false 参数:同上 --> 可用于类似验证性的判断 类似自动化用例里面的判断逻辑 不必先声明一个布尔值 条件不符是置为 非 这个方法直接返回想要的结果
5、some 特点: 不改变原数组 返回一个布尔值 不能break 顺序执行每次循环 当return后的语句 强转为boolean以后为true时 结束整次循环 并返还true 如果循环结束 所有return全为false 则整体返回false 参数同上
--> every&some 两个方法可以分别适用于两个相对的场景 有时需要判断全为true 有时需要判断有没有true 这两个方法可以极大简化代码 基本能涵盖大部分类似场景
- 6、reduce
特点: 不改变原数组 返回类型值任意 不能break 顺序执行每次循环(当不传初始值时 从index=1开始遍历) 参数: 两个参数 第一个为一个回调函数 必选 第二个为遍历开始时的初始值 可选 类型任意 回调函数的参数: 四个 (prev,cur,index,arr)
第一个为循环开始的初始值prev reduce不传二参时初始值prev为要遍历数组的index=0的项 同时遍历改为从index=1开始执行
第二个为当前遍历的数组项cur 可以在回调函数体内对cur和prev进行任意操作 然后return 任意类型的值 注意改值最好与prev的类型一致 第三个为当前遍历的数组项的index 第四个为当前数组 -->使用reduce可以简化很多操作