===
有部分代码显示不清楚的情况大家可以去我语雀中看:链接
第六部分
Generator
直接翻译其意思是生成器,其作用是当我们调用一个自己定义好的函数的时候,函数内部封装的代码不会一次性执行完,而是存在了暂停的可能,也就是可以一次性只执行一部分。
基本使用:
我们只需要在声明一个函数的时候在function后加上*便好。
function* gen() {
yield 1;
yield 2;
yield 3;
}
let g = gen();
console.log(g);
我们可以发现当我们调用生成器函数时其内部定义的语句并不会立即执行,而是向我们返回一个可迭代对象。
接下来我们使用next方法调用返回的可迭代对象,并在函数内部使用yield关键字设置返回值。
function* f(){
console.log('第一次执行');
yield 1;
console.log('第二次执行');
yield 2;
console.log('第三次执行');
yield 3;
}
let iteratorObj = f();
console.log(iteratorObj);
console.log(iteratorObj.next());
console.log(iteratorObj.next().value);
console.log(iteratorObj.next().value);
console.log(iteratorObj.next().value);
console.log(iteratorObj);
我们可以发现当我们使用next方法调用生成器函数返回出来的对象的时候,与我们之前在 for of那里讲过的可迭代对象十分相似,那个对象也有一个next方法,每次会返回当前的可迭代状态done与当前值value。
所以我们可以知道了调用生成器函数其函数内部的语句不会立即执行,而是向我们返回一个可迭代对象,我们可以调用这个对象的next方法查看到yield的返回值,当然每次调用next方法后,函数内部的语句在执行到yield时便会暂停,等待下次的调用,当执行完最后一个关键字时可迭代状态变为true,可迭代对象的状态变为closed。
next()调用中传参:
在我们使用迭代对象的next方法时,我们可以在next方法里给上一个yield传递参数,所以我们只有在第二次使用next方法时才可以传递参数。
在传递参数后我们便可以使用一个变量在yield关键字之后接收这个参数,在上一个yield之后,下一个yield关键字之后使用这个参数了,具体如下。
function* f(){
let a = yield 1;
console.log(a);
let b = yield a * 2;
console.log(b);
yield b * 3;
}
let iteratorObj = f();
console.log(iteratorObj.next()); // {value: 1, done: false}
console.log(iteratorObj.next()); // 先输出undefined 后输出 {value: NaN, done: false}
console.log(iteratorObj.next(10)); // 先输出10 后输出 {value: 30, done: false}
好了看了以上的例子相信大家已经对生成器函数与yield关键字有了一个基本的理解。
生成器函数的基本应用场景:
1.让函数返回多个值:
function* calculate(a, b) {
yield a + b;
yield a - b;
}
let it = calculate(10, 5);
console.log(it.next().value);
console.log(it.next().value);
2.既然生成器函数会返回一个可迭代对象,那么我们可以通过生成器函数给一个对象添加*[Symbol.iterator],让其具有可迭代性。
let obj = {
a:111,
b:222,
c:333,
*[Symbol.iterator](){
yield this.a;
yield this.b;
yield this.c;
}
}
let iteratorObj = obj[Symbol.iterator]();
console.log(iteratorObj);
console.log(iteratorObj.next());
在添加迭代器后我们的对象也可以使用for of进行遍历了。
let obj = {
a:111,
b:222,
c:333,
*[Symbol.iterator](){
yield this.a;
yield this.b;
yield this.c;
}
}
for(let v of obj) {
console.log(v);
}
// 111
// 222
// 333
需要注意的是一旦所有的yield关键字执行完,那么迭代对象的状态立即变为true也就是说即使此时对象还有其他属性没有遍历出那么我们也不会再对其进行遍历。
let obj = {
a:111,
b:222,
c:333,
d:444,
*[Symbol.iterator](){
yield this.a;
yield this.b;
yield this.c;
yield 'xxxx';
}
}
for(let v of obj) {
console.log(v);
}
// 输出
// 111
// 222
// 333
// xxxx
Set数据结构
其本身是一个构造函数,用来生成set数据结构,类似于数组,但是其内部的值永远不会重复。
let set = new Set([1,2,3,4,4,3]);
console.log(set);
我们无法通过索引来获取set内部的值:
let set = new Set([1,2,3,4,4,3]);
console.log(set[0]); // undefined
基本用法:
其基本操作可以分为三部分:增(add),删(delete,clear),查(size,has),注意没有改。直接看代码:
let set = new Set([1,2,3,4,4,3]);
console.log(set[0]); // undefined
set.add('xx');
console.log(set); // Set(5) {1, 2, 3, 4, "xx"}
set.delete(1);
console.log(set); // Set(4) {2, 3, 4, "xx"}
console.log(set.has(1)); // false
console.log(set.size); // 4
// console.log(set.calear()); // 清空set
当我们打印一个实例对象的时候可以发现有[Symbol.iterator]属性,也就是说其部署了Iterator接口我们可以使用for of对其进行遍历。
let set = new Set([1,2,3,4,4,3]);
console.log(set);
for(let item of set) {
console.log(item);
}
// 1,2,3,4
当然也可以使用...set结构赋值直接将其转化为数组。
let set = new Set([1,2,3,4,4,3]);
let arr = [...set];
console.log(arr);
// [1, 2, 3, 4]
在使用扩展运算符的时候也完成了数组去重的而作用。
Array.prototype.unique = function () {
return [...new Set(this)];
};
Map数据结构:
基本用法:
map数据结构类似于python中的字典,map直译过来是映射的意思,以key=>value的形式存储数据。我们直接来看其基本用法。其和set类似,我们也可以将其分为:
-
**增、改s****et(key,value):**给map添加一对键值对,如果当前key已存在则对value进行更新。
-
**删****delete(key),clear():**delete与Set数据结构里的delete相似都是删除某一元素,在这里会将map里的键值对删除,clear会将Map里所有的键值对删除。
-
**查****get(key),has(key),.size:**get很明显就是得到map里key对应的value,如果当前key不存在放回undefined,而has是查看字典里当前的key是否存在返回一个布尔值,.size属性是查看字典的里成员总数。
同样Map的原型上具有[Symbol.iterator]属性,所以我们可以使用for of对map实例对象进行遍历:
let map = new Map();
map.set('name','andy');
map.set('age',18);
for(let [key,value] of map) {
console.log('key:'+key);
console.log('value:'+value);
}
// key:name
// value:andy
// key:age
// value:18
还可以这样:
for(let value of map.values()) {
console.log('value:'+value);
}
for(let key of map.keys()) {
console.log('key:'+key);
}
// value:andy
// value:18
// key:name
// 21 key:age
for(let [key,value] of map.entries()) {
console.log(key,value);
}
// name andy
// age 18
map.forEach((value,key) => {
console.log(value,key);
})
// andy name
// 18 "age
既然具有迭代器那么我们便可以使用...扩展运算符来将其转换为数组:
let map = new Map();
map.set('name','andy');
map.set('age',18);
let arr = [...map];
console.log(arr);
Map与Object的区别:
-
首先我们知道Map是部署了迭代器的,所以我们知道每次迭代map时其迭代出的key:value是有序的,而obj不同。
-
一个
Map的键可以是任意值,包括函数、对象或任意基本类型。而Object的建只能是String与Symbol类型。 -
Map具有size属性,而Object没有,其长度只能计算得出。
Proxy
用简单的语言表达其作用就是设置我们对对象的读写操作的行为。
我们可以先看一个栗子:
let obj = {
a:1,
b:2
}
console.log(obj.a);// 1
obj.a = 3;
console.log(obj.a);// 2
以上代码是不是大家再熟悉不过了,我们会想很自然的认为obj.a === 1,给obj.a赋值后其就变为了新的值。但是有没有一种方法可以使得我们在获取obj.a时我们得到的返回值不是1,我们给其赋值时,我们可以不改变obj.a 的值。这里我们就需要使用到Proxy代理了。
其基本语法是:
const p = new Proxy(target, handler);
这里的target就是我们要控制的对象,handler就是我们要添加的控制的方法。我们再来看接下来的例子:
let obj = {
a:1,
b:2,
}
let p = new Proxy(obj,{
get(obj,prop){
console.log('监听属性值的获取');
return obj[prop] + 1;
}
})
console.log(p);
console.log(p.a);
console.log(p.b);
我们来看控制台的输出:
我们得到的属性值不再是原始值了,但是我们的原生对象的值并没有改变。所以get方法就是用来控制我们对对象的读取操作的。get的两个参数,obj就是我们实例化时传递进来的对象,prop就是我们对象的属性名key。接下来我们接着看:
let obj = {
a:1,
b:2,
}
let p = new Proxy(obj,{
get(obj,prop){
return obj[prop] + 1;
},
set(obj,prop,value) {
if(value < 0) return;
obj[prop] = value;
}
})
console.log(p.a); // 2
p.a = 3;
console.log(p.a); // 4
p.a = -2;
console.log(p.a); // 4
console.log(p); // Proxy {a: 3, b: 2}
console.log(obj); // {a: 3, b: 2}
我们可以发现我们成功改变了原本会被修改的属性值(p.a应等于-2),所以set用来控制我们对对象属性的修改value就是我们原本赋的值(-2)。
基本使用:
Vue的数据响应式便是依靠get与set做到对数据的监听的,有兴趣的同学可以看我们的这此处为语雀文档,点击链接查看:www.yuque.com/andylm/dgws…。
完结!!!✌✌✌✌✌🎈🎈🎈🎈🎈✨✨✨✨✨✨🎉🎉🎉🎉🎉🎉🎉