迭代器与生成器

Proxy的实现

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

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

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

set方法

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

        我们来结合银行的例子,来用get方法和set方法的实现,这段代码有点长,但是注释很详细,注意看代码注释:

几乎每一句代码都有注释,这段代码对应的故事情节是这样的:老王有的银行账户里面有一些存款,其中人民币1000元,美元0元。

有一天,他来到银行柜台前,找到一个叫banker的工作人员,取款之前看看账户里面还有多少钱,然后工作人员banker开始帮他操作(也就是代理)。

banker告诉他:“您账户里面有人民币1000元,可以取款的,但美元余额不足“。

接着,老王不打算取款了,打算存500美元。

在填写存款单据的时候,把500不小心写成了“五百“,banker告诫老王:”这样是写不行的,一定要写阿拉伯数字,这样写银行无法帮您存款的“。结果存款失败,账户里面的美元还是0元。

 没关系,马上改过来,把“五百“改成500。

存款成功,账户里面的美元已有500元。

        故事的整个经过就是这样,有了Proxy代理(银行工作人员bank),帮助老王完成查看银行存款和取款的操作(代理),避免了一些误操作。

        get方法拦截了读取操作,set方法拦截了改写操作。Proxy除了支持以上拦截程序,还支持一系列的拦截函数,我们选择几个常用的讲解!

ownKeys方法

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

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

has方法

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

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

apply方法

除了对象类型的变量可以被代理,函数也可以被代理。如果被代理的变量是一个函数,那么还会支持一个拦截程序:apply调用。

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

proxy.revocable方法

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

 

这个案例大家要注意的是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循环来遍历数组的缺点就是:代码不够简洁。

方式二:

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

方式三:

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

那我们来看看for...of的是实现:

for...of的优势:

写法比for循环简洁很多;

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

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

(2)、循环可以终止

以上案例:用break实现了终止整个循环,不会继续后面的遍历,所以打印结果为:1  2。

可以跳过当前循环:

用continue跳过当前循环,继续后面的循环,所以打印结果为:1  2  4  5。

(3)、得到数字类型索引

 

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

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

(4)、遍历字符串

for...of 支持字符串的遍历。

(5)、遍历DOM list

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

 for...of支持set和map解构的遍历,考虑到set和map我们还没学习,我们在后面章节一并讲解。
演示示例:ES6中for…of的使用

3.Iterator遍历器

(1)、for…of为什么不遍历object对象

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

 

     oh no,程序报错了,for...of根本不支持遍历普通的Object对象,还出现了错误提示:obj[Symbol.iterator]不是一个function,这是什么鬼?

 为什么数组,Set和Map结构又可以支持for...of的遍历呢?

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

        那么,我们就亲自来验证一下,它们的原型中到底是不是有个叫Symbol.iterator的方法:

 

从上往下看,确实,唯独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结构。

        现在你该知道为什么文章第一个案例会出现错误提示:obj[Symbol.iterator]不是一个function 了吧,因为Object对象的原型上压根就没有[Symbol.iterator]() 方法啊。

(2)、Iterator原理

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

 

 第1次调用next( )方法:返回数组的第1个元素:“a”,以及done的值为fasle,表示循环没有结束,继续遍历。

        第2次调用next( )方法:返回数组的第2个元素:“b”,以及done的值还是为fasle,表示循环没有结束,继续遍历。

        第3次调用next( )方法:返回数组的第3个元素:“c”,以及done的值依然为fasle,表示循环没有结束,继续遍历。

        第4次调用next( )方法:返回的value值为undefined,以及done的值变成了true,表示遍历结束。

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

(3)、自定义Iterator遍历器

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

那我们就试试看,给一个Object对象加一个[Symbol.iterator]( )方法,看它是不是就能被for...of遍历了?  

 

 

 

上面这个案例也许你看了觉得很复杂,没看懂,没关系,我一起来分析它的结构就够了!

        我们定义了一个Object对象,同时给它添加了[Symbol.iterator]()方法,并在[Symbol.iterator]()方法实现了next( )方法,next( )方法返回的对象包含了value属性和done属性。

        具体细节如果不理解没关系,我们确实看到了给Object对象加上了[Symbol.iterator]()方法后,最后确实能被for...of遍历了。

        这就是说,我们可以创建一个可遍历的对象,并且自定义它的遍历行为。或者说可以通过添加[Symbol.iterator]()方法,把一个不可遍历的Object对象,变成可遍历的对象。

(4)、Iterator遍历器的价值

新特性for...of之所以能够遍历各种不同的数据结构,正是因为这个数据结构都实现了Iterator遍历器接口,供for...of遍历。如果没有实现Iterator接口,则该数据结构无法被for...of遍历,比如:普通的Object对象。

4.Generator函数

(1)、声明Generator函数

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

 

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

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

Generator函数函数内部有新的关键字:yield( 产出),普通函数没有。

(2)、调用Generator函数

带着这两个疑问我们往下看,我们试着就调用一下这个名字叫Hello的Generator函数,看看会发生什么:

 

 

没错,你可以把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的返回值,我们来看一下:

 

 

注意函数体内的第一个yield关键字,我们把它的返回值赋值给了一个变量res。

        再看2次next方法的调用:

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

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

(6)、关键字‘yield*’

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

        我们来看看怎么玩,代码有点长,但是很好理解:

 

 

 

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

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

(7)、Generator函数的用途

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

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

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

5.类基本用法

(1)、类的属性和方法

声明一个类的写法:

代码很简短,我们通过关键字class来声明一个名字叫Animal的类,可以看到类里面(花括号 {}里面)有一个叫constructor(构造、建造)方法,它就是构造方法,构造方法里面的this,指向的是该类实例化后的对象,这就是实现了一个类的声明。

其中,构造方法constructor是一个类必须要有的方法,默认返回实例对象;创建类的实例对象的时候,会调用此方法来初始化实例对象。如果你没有编写constructor方法,执行的时候也会被加上一个默认的空的constructor方法。

(2)、类的实例化对象

了解了类的声明和constructor构造函数的特点,我们下面来了解如何给类添加属性和方法。

我们把类名后面的括号{ }里面的内容称之为类体,我们会在类体内来编写类的属性和方法。上面的案例中,类体内有2个方法:constructor( )、getName()。

        其中constructor方法是构造方法,在实例化一个类的时候被调用。constructor方法是必须的,也是唯一的,一个类体不能含有多个constructor构造方法。我们可以在方法里面自定义一些对象的属性,比如案例中的name属性。

 此外,我们还自定义了一个getName( )方法,它属于类的实例方法,实例化后对象可以调用此方法。

(3)、类的自定义方法

掌握了类的属性和方法的写法,接下来,我们学习如何创建对象和使用对象的实例方法:

还是同一个类Animal,我们通过new来创建了实例对象dog,构造方法会把传进去的参数“dog”通过this.name赋值给对象的name属性,所以dog的name属性为“dog”,对象dog还可以调用自己的实例方法getName( ),结果返回:“This is a dog”。

        实例对象的创建有几个要注意的事项:

1.必须使用new创建字来创建类的实例对象

2.先声明定义类,再创建实例,否则会报错

(4)、类的静态方法

如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”

class Father { static testMethod() { return 'hello'; } } 

Father.testMethod()

类的静态方法可以被类直接调用

(5)、类的继承

说到类class,就不得不说类的继承,ES6使用extends关键字来实现子类继承父类,我们来演示一下:

上面的案例中,我们定义两个类,Animal类作为父类,Dog类作为子类,然后通过关键字extends来实现继承,此外,我们还注意到一个关键字super,它相当于是父类中的this。

 我们可以用super来引用父类,访问父类的方法,我们来演示一下:

在父类中,我们定义了say方法,想要在子类中调用父类的say方法的话,我们使用super.say( )即可实现。    

        使用super有几个要注意的事项:

子类必须在constructor方法中调用super方法

调用super( ),才可以使用this,否则报错

     以上就是关于类继承的介绍,重点在于关键字extends和super,尤其是super的理解和使用,大家需要理解透彻。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值