ES6迭代器与生成器

Proxy的实现

我们用代码来演示

//定义一个对象person

var person = {"name" : "张三"}

//创建一个代理对象pro,代理person的读写操作

var pro = new Proxy(person , {

get:function (target,property){

    return "李四"

}})

pro.name ;  //李四

先定义一个对象,含有name属性,值为“张三”,创建一个代理对象pro,对象person的操作都交给代理对象pro,这不,看最后一句代码,如果你要读取person对象的name属性,就要用pro.name,而不是person的name。我们看到的结果是:“李四“而不是person对象重点张三,因为代理过程中,get方法实现了拦截的作用,不管你读取什么属性,我都返回”李四“。

new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为

        这就是代理Proxy的作用,将一个对象交给了Proxy代理,然后通过编写处理函数,来拦截目标对象的操作。上面的案例,你可以理解为,person对象的name属性值不想被别人知道是“张三“,就设置了一个代理,让别人读取的时候,只获取到”李四“。

 

(2)set方法

而上面提到的“编写处理函数“,get方法就是其中一个,除了get方法以外,还有一个很常用的是:set方法,它用于拦截对对象的写操作。

 

例:

    // 定义一个对象,含有RMB和doller属性值
   var bankAccount = {"RMB":1000,"doller":0};
//    创建一个Proxy代理实例
   var banker = new Proxy(bankAccount,{
// 编写get处理程序
    get:(target,property)=>{
        // 判断余额是否大于0
        if(target[property]>0){
            // 有余额就返回余额值
            return target[property]
        }else{
           
            return "余额不足"
        }
    },
    // 编写set处理程序
    set:(target,property,value)=>{
        // 存入的数额必须是一个数字类型
        if(!Number.isInteger(value)){
            return "请设置正确的数值";
        }
        // 修改属性的值
        target[property] = value;

    }
   })

   console.log(banker.RMB);//1000
   console.log(banker.doller);//余额不足
   banker.doller = "五百"
   console.log(banker.doller);//余额不足
   banker.doller = 1000
   console.log(banker.doller);//1000

 

(3)ownKeys方法

ownKeys拦截操作,拦截过滤Object.Keys()对对象的属性遍历。

// 定义一个对象person,有三个属性
let person = {"name":"老王","age":40,"height":1.8}
// 创建一个代理对象
let proxy = new Proxy(person,{
    // ownKeys过滤对对象的属性遍历
    ownKeys:(target) =>{
        return ["name","age"]
    }
})
console.log(Object.keys(person));//["name","age","height"]

console.log(Object.keys(proxy));//["name","age"]
 

我们编写的ownKeys方法程序,不管你有多少属性,只返回两个属性name和age。我们看最后两行代码:Object.keys(person); 这里我们不使用代理,直接用keys( )函数遍历person对象,得到的person对象的原本属性"name"、 "age"和"height"。而Object.keys(proxy) 这句代码遍历的是被代理的proxy对象,所以,得到的只是被过滤后的结果:[“name”,”age”]。

 

(4)has方法

has( )拦截操作:拦截key in object的操作,结果会返回一个布尔值。

例:
var person1 = {"name":"张三","age":20}
var proxy1 = new Proxy(person1,{
    has:(target,prop)=>{
        if(target[prop] === undefined){
            return false
        }else{
            return true
        }
    }
})

console.log("name" in proxy1);//true
console.log("height" in proxy1);//false

has( )方法用于是判断是否含有指定的键值对,有,就返回true。否则返回false。对象含有name属性,所以返回true,没有height属性,返回false。

 

(5)apply方法

例:

let fn=function(){
    alert("我是前端君")
}
let proxy = new Proxy(fn,{
    apply:()=>{
        alert("我是隔壁老王")
    }
})
proxy() //我是隔壁老王
 

 最后一句代码,proxy本身是一个代理实例对象,因为它代理的是一个函数fn,所以可以直接用函数的形式调用proxy( );当它当作函数调用的时候,就会被apply拦截,执行alert('我是隔壁老王')。

 

(6)proxy.revocable方法

如果创建了代理之后又想取消代理的话,我们可以用Proxy.revocable( )(可废止的,可撤回的;)函数来实现,它会返回一个对象,对象中含有一个proxy属性,它就是Proxy的代理实例对象;还有一个revoke属性,它是一个方法,用于取消代理。

 

例:

let person = {"name":"zhangsan"}

let handle = {
    get:(target,prop)=>{
        return "李四"
    }
}
let object = Proxy.revocable(person,handle)
console.log(object.proxy.name);//李四
console.log(object);//{proxy: Proxy, revoke: ƒ}
// 调用返回对象object的revoke方法,取消代理
object.revoke();
console.log(object.proxy.name);//报错,代理被取消
 

这个案例大家要注意的是Proxy.revocable( )方法返回的结果,它是一个对象,在控制台打印出来后的结果是:Object{ proxy:Object , revoke:function(){....} }。有一个proxy属性,它就是Proxy代理实例,还有一个属性revoke,它是一个方法,专用于取消代理。

        我们使用object.proxy.name来读取name的属性,由于被代理拦截了,只能读取到“李四”,接着我们调用revoke( )方法取消代理,然后再使用object.proxy.name的时候就会报错了,代理已经不存在了。

 

 

for....of使用

for...of 一种用于遍历数据结构的方法。它可遍历的对象包括数组,字符串,set和map结构等具有iterator 接口的数据结构。

我们先来看看几种传统的遍历数组的方式以及它们的缺陷:

方式一:利用for循环来遍历数组的缺点就是:代码不够简洁。

var arr = [ 1 , 2 , 3 , 4 , 5 ];

for(let i = 0; i < arr.length - 1;i++ ){

}

 

方式二: 利用forEach循环代码量少了很多,写法更加简洁,缺点就是:无法中断停止整个循环。

var arr = [1 , 2 , 3 , 4 ];

arr.forEach((item , index)=>{

})

 

方式三:

var arr = [1 , 2 , 3 , 4 , 5];

for(let i  in  arr){

}

for...in循环更常用于对象的循环,如果用于数组的循环,那么就要注意了,上述代码中每次循环中得到的i是字符串类型,而不是预料中的数字类型,要想对它进行运算,那得先要进行类型转换,造成不方便。

 

for ...of

优势:

写法比for循环简洁很多;

可以用break来终止整个循环,或者continute来跳出当前循环,继续后面的循环;

结合keys( )获取到循环的索引,并且是数字类型,而不是字符串类型。

var arr = [1, 2 , 3 , 4 , 5];

for(let value of arr){

  console.log(value);

}

 

(2)循环可终止

var arr = [1 , 2 , 3 , 4 , 5];

for(let value of arr){

if ( value == 3 ){

break;

}

console.log(value)}//打印 1  2

可以跳出当前循环

var arr = [1 , 2 , 3 ,4 , 5 ];

for(let value of arr){

if( value == 3 ){

//跳出当前循环,继续后边循环

  continue;

}

console.log( value )  }  //打印1 2 4 5

 

(3)得到数字类型索引

 

var arr = [1 , 2 , 3 , 4 , 5];

for ( let index of arr.keys() ){

  console.log( index ) ; }

//依次打印:0  1  2  3  4

使用数组的扩展keys( ),获取键名再遍历,得到的index是数字类型的。

  此外,相比于for...in循环专门为对象设计,for...of循环的适用范围更广。

 

(4)遍历字符串

 let word =  "我是前端君"

for ( let w  of  word ){

  console.log( w ) ;

} //打印 :我是前端君

 

(5)遍历DOM list

for...of支持类数组的遍历,例如DOM List。

 

  <p>1</p>
    <p>2</p>
    <p>3</p>
let pList = document.getElementsByTagName("p")
for(let p of pList){
    console.log(p);
}
//打印结果: 
  <p>1</p>
    <p>2</p>
    <p>3</p>
 for...of支持set和map解构的遍历
 
 
 
Iterator 遍历器

我们讲了简单又实用的for...of,我们可以使用它来遍历数组,字符串,Set和Map结构,但是有没有发现,我们并没有说它可以遍历Object对象,为什么不试试用它来遍历Object对象呢?

 

let obj = {"name" : "前端君"}

for ( let v of obj ){

  console.log( v );

}

//结果:报错
//错误提示:obj[Symbol.iterator]不是一个function

 

要想能够被for...of正常遍历的,都需要实现一个遍历器Iterator。而数组,Set和Map结构,早就内置好了遍历器Iterator(又叫迭代器),它们的原型中都有一个Symbol.iterator方法;而Object对象并没有实现这个接口,使得它无法被for...of遍历。

//数组
    Array.prototype[Symbol.iterator];
    //结果:function values(){...}

    //字符串
    String.prototype[Symbol.iterator];
    //结果:function [Symbol.iterator](){...}

    //Set结构
    Set.prototype[Symbol.iterator];
    //结果:function values(){...}

    //Map结构
    Map.prototype[Symbol.iterator];
    //结果:function entries(){...}

    //Object对象
    Object.prototype[Symbol.iterator];
    //结果:undefined


唯独Object对象的原型上没有Symbol.iterator,返回了:undefined。其他的数据类型的原型上都含有一个名字叫Symbol.iterator的方法Function。

 

注意:Symbol.iterator 是Symbol 对象的 iterator 属性,是一个特殊的Symbol值,因此,当它作为prototype对象属性名的时候,获取它的时候需要使用[ ]的形式: prototype[Symbol.iterator],不能使用点形式获取:prototype.Symbol.iterator。

也就说,只要一个数据结构拥有一个叫[Symbol.iterator]()方法的数据结构,就可以被for...of遍历,我们称之为:可遍历对象。比如:数组,字符串,Set和Map结构。

 

Iterator 原理

当可遍历对象被for...of遍历的时候,[Symbol.iterator]()就会被调用,返回一个iterator对象。其中还有一个很重要的方法:next( );

for...of的原理就是:先调用可遍历对象的[Symbol.iterator]( )方法,得到一个iterator遍历器对象,然后就在遍历器上不断调用next( )方法,直到done的值为true的时候,就表示遍历完成结束了。

调用next()方法会返回一个对象

有两个属性

value:属性值

done:状态值

{value: "c", done: false}

当返回的值为{value: undefined, done: true}

就表示遍历结束

 

既然有了[Symbol.iterator]()方法就算是可遍历对象,那么我给Object对象手动加上一个[Symbol.iterator]()方法,

 

例:

var obj = {
    name:"lisi",
    age:18,
    sex:"男",
    height:180,

    [Symbol.iterator]:function(){
        let index = 0
        // 对象属性名的数组
        let list = Object.keys(obj)
        return{
            next:()=>{
                let value = {
                    key:list[index],
                    value:this[list[index]]
                }
                let done
                if(index>=list.length){
                    done = true
                }else{
                    done = false
                }
                index++
                return{value,done}
            }
        }
    }
}
for(let v of obj){
    console.log(v);
}
 
 
 
(4)Generator函数

Generator函数,又称生成器函数,是ES6的一个重要的新特性。

// 声明一个hello的Generator函数
    function* hello(name){
        yield `hello${name}`;
        yield `how are you `;
        yield `bye`;
    }
 

上面这个就是Generator函数,乍一看,是不是跟普通的函数没什么两样?确实很像,但是我们要知道它有两个重要的区别:

普通函数用function来声明,Generator函数用function*声明。

Generator函数函数内部有新的关键字:yield( 产出),普通函数没有。
 
//调用hello函数
let ite = hello("前端君")
console.log(ite.next());//{value: 'hello前端君', done: false}
console.log(ite.next());//{value: 'how are you ', done: false}
console.log(ite.next());//{value: 'bye', done: false}
console.log(ite.next());//{value: undefined, done: true}
 

看到这里,估计你也看到了一个熟悉的面孔:next()方法。一开始,我们调用Hello(“前端君”),函数执行后,返回了一个:[object Genrator]生成器对象,我们把它赋值到变量ite中,仅此而已,并没有做太多的事情。

 

看到他们打印的结果,发现这里生成器next()和遍历器iterator的next()方法,返回的结果都是一样的

你可以把Generator函数被调用后得到的生成器理解成一个遍历器iterator,用于遍历函数内部的状态。

 

(3)Generator函数的行为

通过上面的案例,我们知道了:Generator函数被调用后并不会一直执行到最后,它是先回返回一个生成器对象,然后hold住不动,等到生成器对象的next( )方法被调用后,函数才会继续执行,直到遇到关键字yield后,又会停止执行,并返回一个Object对象,然后继续等待,直到next( )再一次被调用的时候,才会继续接着往下执行,直到done的值为true。

 

(4)yield语句的使用

而yield在这里起到了十分重要的作用,就相当于暂停执行并且返回信息。有点像传统函数的return的作用,但不同的是普通函数只能return一次,但是Generator函数可以有很多个yield。而return代表的是终止执行,yield代表的是暂停执行,后续通过调用生成器的next( )方法,可以恢复执行。

 

(5)next方法接收参数

此外,next( )方法还可以接受一个参数,它的参数会作为上一个yield的返回值,我们来看一下

function* hello(){
    let res = yield`hello`
    yield res;
}

let ite = hello()
console.log(ite.next());//{value: 'hello', done: false}
console.log(ite.next("111"));//{value: '111', done: false}
 

第1次调用next( )方法,返回的对象属性value值为“hello”,属性done值为:fasle,并暂停执行。

        第2次next( )方法,传入参数:字符串“111”。此时,第二个yield关键字紧跟着的是变量res,而变量res的值正是上一个关键字yield的返回值。也就是说这个值正是我们传入的参数:“111”。因为:next( )的参数会作为上一个yield的返回值。

 

 

(6)关键字 ‘ yield* ’ 

在一个Generator函数里面,如果我们想调用另一个Generator函数,就需要用到的关键字是:yield*。

例:

function* gen1(){
    yield "gen1 start";
    yield "gen1 end"
}

function* gen2(){
    yield "gen2 start"
    yield "gen2 end"
}

function* start(){
    yield "start";
    yield* gen1();
    yield*gen2();
    yield "end"
}

let ite = start();
console.log(ite.next());//{value: "start", done: false}
console.log(ite.next());//{value: "gen1 start", done: false}
console.log(ite.next());//{value: "gen1 end", done: false}
console.log(ite.next());//{value: "gen2 start", done: false}
console.log(ite.next());//{value: "gen2 end", done: false}
console.log(ite.next());//{value: "end", done: false}
console.log(ite.next());//{value: undefined, done: true}
 

这里使用了关键字yield*来实现调用另外两个Generator函数。从后面的多个next( )方法得到的结果看,我们可以知道:

        如果一个Generator函数A执行过程中,进入(调用)了另一个Generator函数B,那么会一直等到Generator函数B全部执行完毕后,才会返回Generator函数A继续执行。

 

(7)Generator函数的用途

以上就是对Generator函数的讲解介绍,它是ES6的一个很重要的新特性。它可以控制函数的内部状态,依次遍历每个状态;可以根据需要,轻松地让函数暂停执行或者继续执行。

        根据这个特点,我们可以利用Generator函数来实现异步操作的效果。

        原理是:利用Generator函数暂停执行的作用,可以将异步操作的语句写到yield后面,通过执行next方法进行回调。

 

posted @ 2022-03-08 15:45  ..Shmily  阅读(56)  评论(0)    收藏  举报