一、迭代器(Iterator)
1、基础概念
迭代:从一个数据集合中按照一定的顺序,不断取出数据的过程。
迭代器(iterator):一个具有next方法的对象,next方法返回下一个数据并且能指示是否迭代完成
迭代器创建函数(iterator creator):一个返回迭代器的函数
2、JS中的迭代器
JS
规定,如果一个对象具有next
方法,并且该方法返回一个对象,该对象的格式如下:
- next方法:用于得到下一个数据
- 返回的对象 {value: 值, done: true/false}
- value:下一个数据的值
- done:boolean,是否迭代完成
模拟一个js
的迭代器
const arr = [1,2,3,4,5]
const iterator = {
i:0,
next(){
let result = {
value:arr[this.i],
done:this.i>=arr.length
}
this.i++
return result
}
}
console.log(iterator.next()) //{value: 1, done: false}
通过迭代器遍历上面的数组
let data = iterator.next()
while(!data.done){
console.log(data.value)
data = iterator.next()
}
模拟一个迭代器创建函数
//迭代器创建函数
function createIterator(arr){
let i = 0;
return{
next(){
let result = {
value:arr[i],
done:i>=arr.length
}
i++
return result
}
}
}
const arr = [1,2,3,4,5]
const it = createIterator(arr)
console.log(it.next())
用迭代器和迭代器创建函数完成斐波那契数列
function createFeiboIterator(){
let pre1 = 1,
pre2 = 1,
n = 1;
return {
next(){
let value;
if(n <= 2){
value = 1
}else{
value = pre1 + pre2
}
const result = {
value,
done:false
}
pre1 = pre2
pre2 = result.value
n++
return result
}
}
}
let it = createFeiboIterator()
it.next()
3、可迭代协议
ES6规定,如果一个对象具有知名符号属性Symbol.iterator
,就可以认为是“可迭代的”。Symbol.iterator
属性本身是一个函数。执行它就会返回一个迭代器。
原生具备 Iterator 接口的数据结构如下:
1、Array
2、Map
3、Set
4、String
5、TypedArray
6、函数的 arguments 对象
7、NodeList 对象
4、for-of 循环
一个数据结构只要部署了Symbol.iterator
属性,就被视为具有 iterator 接口,就可以用for...of
循环遍历它的成员。也就是说,for...of
循环内部调用的是数据结构的Symbol.iterator
方法。只要符合可迭代协议,都可以用for...of
循环遍历
for-of 循环用于遍历可迭代对象,格式如下
//迭代完成后循环结束
for(const item in iterable){
//iterable:可迭代对象
//item:每次迭代得到的数据
}
//只要是可迭代对象,都可以
for (const item of arr) {
console.log(item)
}
手写迭代器完成可迭代协议
const obj = {
a:1,
b:2,
[Symbol.iterator](){
const keys = Object.keys(this)
let i = 0;
return{
next:()=>{
const propName = keys[i]
const propValue = this[propName]
const result = {
value:{propName:keys[i],propValue:propValue},
done:i>=keys.length
}
i++
return result
}
}
}
}
for (const item of obj) {
console.log(item)
}
5、使用Symbol.iterator
(1)解构赋值
对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator
方法。
(2)展开运算符
展开运算符默认也会调用Symbol.iterator
方法。
例子:
const obj = {
a:1,
b:2,
[Symbol.iterator](){
const keys = Object.keys(this)
let i = 0;
return{
next:()=>{
const propName = keys[i]
const propValue = this[propName]
const result = {
value:{propName:keys[i],propValue:propValue},
done:i>=keys.length
}
i++
return result
}
}
}
}
const arr = [...obj]
console.log(arr)
//[{propName: "a", propValue: 1},{propName: "b", propValue: 2}]
二、生成器 (Generator)
1、生成器函数创建
Generator 函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态。生成器是一个通过构造函数Generator
创建的对象,生成器既是一个迭代器,同时也是一个可迭代对象。
//在function后或方法名前加一个*
//这是一个生成器函数,该函数一定返回一个生成器
function* method(){}
2、yield关键字
由于 Generator 函数返回的迭代器对象,只有调用next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。
遍历器对象的next
方法的运行逻辑如下。
(1)遇到yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
(2)下一次调用next
方法时,再继续往下执行,直到遇到下一个yield
表达式。
(3)如果没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。
(4)如果该函数没有return
语句,则返回的对象的value
属性值为undefined
。
3、next()方法
每次调用生成器的next
方法,生成器函数会从函数头部或上一次停止的位置开始运行,到下一个yield
关键字位置或return时停止运行
function* test(){
console.log("第1次运行")
yield 1
console.log("第2次运行")
yield 2
console.log("第3次运行")
yield 3
console.log("第4次运行")
return 4
}
const generator = test()
console.log(generator)//含有next方法和知名符号Symbol.iterator
generator.next() //第1次运行 返回值是:{value: 1, done: false}
generator.next()//第2次运行 返回值是:{value: 2, done: false}
generator.next()//第3次运行 返回值是:{value: 3, done: false}
generator.next()//第4次运行 返回值是:{value: 4, done: true}
通过Generator函数创建迭代器
const arr = [1,2,3,4,5]
function* createIterator(arr){
for (const item of arr) {
yield item
}
}
const iterator = createIterator(arr)
通过Generator函数完成斐波那契数列
function* createFeiboIterator(){
let pre1 = 1,
pre2 = 1,
n = 1;
while(true){
if(n<=2){
yield 1
}else{
const newValue = pre1 + pre2
yield newValue
pre1 = pre2
pre2 = newValue
}
n++
}
}
let it = createFeiboIterator()
4、注意细节
(1)生成器函数可以有返回值,返回值出现在第一次done为true时的value属性中
function* test(){
console.log("第1次运行")
yield 1
console.log("第2次运行")
yield 2
console.log("第3次运行")
return 10
}
const generator = test()
console.log(generator.next())//第三次运行时,value值为10
(2)调用生成器的next方法时,可以传递参数,传递的参数会交给yield表达式的返回值
(3) 第一次调用next方法时,传参没有任何意义
function* test(){
console.log("第1次运行")
let res = yield 1
console.log("第2次运行")
res = yield 2 + res
console.log("第3次运行")
}
const generator = test()
console.log(generator.next(1)) //毫无作用
console.log(generator.next(5)) //res = 5
console.log(generator.next()) //res = undefined
(4)在生成器函数内部,可以调用其他生成器函数,但是要注意加上*号
function* g(){
yield "a"
yield "b"
}
function* test(){
yield* g()
yield 2
yield 3
}
const generator = test()
5、return()和throw方法
return方法:调用该方法,可以提前结束生成器函数,从而提前让整个迭代过程结束
function* test(){
yield 1
yield 2
yield 3
}
const generator = test()
console.log(generator.next()) //{value: 1, done: false}
console.log(generator.return())//{value: undefined, done: true}
console.log(generator.next())//{value: undefined, done: true}
throw方法:调用该方法,可以在生成器中产生一个错误
function* test(){
yield 1
yield 2
yield 3
}
const generator = test()
console.log(generator.next())
console.log(generator.throw("报错!"))//Uncaught 报错!
console.log(generator.next())
6、异步任务控制,地狱回调
function* hellCallBack(value1) {
const value2 = yield callBack1(value1);
const value3 = yield callBack2(value2);
const value4 = yield callBack3(value3);
const value5 = yield callBack4(value4);
// ...
}
三、属性描述符
在学习反射之前,我们先回顾一下属性描述符的概念。
Property Descriptor 属性描述符 是一个普通对象,用于描述一个属性的相关信息
1、获取属性描述
通过Object.getOwnPropertyDescriptor(对象, 属性名)
可以得到一个对象的某个属性的属性描述符
let obj = {
a:1,
b:2
}
const des = Object.getOwnPropertyDescriptor(obj,"a")
console.log(des)
/*
{
value: 1,
writable: true,
enumerable: true,
configurable: true
}
*/
Object.getOwnPropertyDescriptors(对象)
可以得到某个对象的所有属性描述符
let obj = {
a:1,
b:2
}
console.log(Object.getOwnPropertyDescriptors(obj))
/*
{
a:{configurable: true,enumerable: true,value: 1,writable: true},
b:{configurable: true,enumerable: true,value: 2,writable: true}
}
*/
解释:
- value:属性值
- configurable:该属性的描述符是否可以修改
- enumerable:该属性是否可以被枚举
- writable:该属性是否可以被重新赋值
2、 配置其属性描述符
Object.defineProperty(对象, 属性名, 描述符)
为某个对象的某个属性 添加属性或 修改属性
let obj = {
a:1,
b:2
}
Object.defineProperty(obj,"a",{
configurable:false,
writable:false //a属性不能重新被赋值
})
Object.defineProperties(对象, 多个属性的描述符)
为某个对象的所有属性 添加属性或 修改属性
let obj = {
a:1,
b:2
}
Object.defineProperties(obj,{
configurable:false,
enumerable:fals
})
3、存取器属性
属性描述符中,如果配置了 get 和 set 中的任何一个,则该属性,不再是一个普通属性,而变成了存取器属性。
get 和 set配置均为函数,如果一个属性是存取器属性,则读取该属性时,会运行get方法,将get方法得到的返回值作为属性值;如果给该属性赋值,则会运行set方法。
存取器属性最大的意义,在于可以控制属性的读取和赋值。
let obj = {
a:1,
b:2
}
Object.defineProperty(obj,"c",{
get(){
console.log("运行了属性a的get函数")//
return obj._c
},
set(val){
console.log("运行了属性a的set函数",val)
obj._c = val
}
})
obj.c = 3
console.log(obj.c)
//以上代码输出结果
/*
>运行了属性a的set函数 3
>运行了属性a的get函数
>3
*/
三、反射(Reflect)
Reflect是ES6操作对象而提出的新API。可以让开发者通过调用这些方法,访问JS一些底层的功能。
使用Reflect可以实现对属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中 等等功能。为的是减少魔法,让代码更加纯粹。
Reflect对象的静态方法(共13个)
(1)Reflect.set(target, propertyKey, value):设置对象target
的属性propertyKey
的值为value
,等同于给对象的属性赋值
let obj = {
a:1,
b:2
}
Reflect.set(obj,"a",10)//等同于obj.a = 10
(2)Reflect.get(target, propertyKey):读取对象target
的属性propertyKey
,等同于读取对象的属性值
Reflect.get(obj,"a")//等同于obj.a
(3)Reflect.apply(target, thisArgument, argumentsList):调用一个指定的函数,并绑定this
和参数列表。等同于函数调用
function test(a,b){
console.log("test",a,b)
}
Reflect.apply(test,null,[1,2])
(4)Reflect.deleteProperty(target, propertyKey):删除一个对象的属性
let obj = {
a:1,
b:2
}
Reflect.deleteProperty(obj,"a")//等同于delete obj.a
(5)Reflect.defineProperty(target, propertyKey, attributes):类似于Object.defineProperty
,不同的是如果配置出现问题,返回false而不是报错
(6)Reflect.construct(target, argumentsList):用构造函数的方式创建一个对象
function Test(a,b){
this.a = a
this.b = b
}
const t = Reflect.construct(Test,[1,2])
console.log(t)
(7)Reflect.has(target, propertyKey):判断一个对象是否拥有一个属性
let obj = {
a:1,
b:2
}
Reflect.has(obj,"a")//等同于 "a" in obj
四、代理(Proxy)
Proxy :在目标对象之外加一层“拦截”,外界的访问该对象,都必须先通过这层拦截,因此可以在访问该对象时做一些过滤或这改写。提供了修改底层实现的方式
/*
代理一个目标对象
target:目标对象
handler:是一个普通对象,其中可以重写底层实现
return 返回一个代理对象
*/
new Proxy(target, handler)
handler配置对象可以重写Reflex中的所有静态方法。
let obj = {
a:1,
b:2
}
const proxy = new Proxy(obj,{
//可以重写Reflex中所有的方法
set(target,propertyKey,value){
console.log(target,propertyKey,value)
target[propertyKey] = value
}
})
proxy.a = 2
console.log(proxy)
观察者模式
有一个对象,是观察者,它用于观察另外一个对象的属性值变化,当属性值变化后会收到一个通知,可能会做一些事。
function observer(target){
const div = document.getElementsByTagName("div")[0]
const proxy = new Proxy(target,{
get(target,prop){
return Reflect.get(target,prop)
},
set(target,prop,value){
Reflect.set(target,prop,value)
render()
},
})
render()
function render(){
let html = ""
for(const prop of Object.keys(target)){
html+=`
<p><span>${prop}</span>:<span>${target[prop]}</span></p>
`
}
div.innerHTML = html
}
return proxy
}
const obj = {
a:1,
b:2
}
const proxy = observer(obj)
偷懒的构造函数
在每次书写类时,都会要写构造函数,非常麻烦。
class User{
constructor(firstName,lastName,age){
this.firstName = firstName
this.lastName = lastName
this.age = age
}
}
我们可以封装一个自己的方法,通过代理,来直接完成构造函数中的赋值操作
class User{}
function constructorProxy(Class,...propNames){
return new Proxy(Class,{
construct(target,arumentList){
const obj = Reflect.construct(target,arumentList)
propNames.forEach((name,i)=>{
obj[name] = arumentList[i]
})
return obj
}
})
}
const UserProxy = new constructorProxy(User,"firstName","lastName","age")
const a = new UserProxy("a","b",18)
console.log(a)
可验证的函数参数
function sum(a, b){
return a + b
}
function validatorFunction(func,...types){
const proxy = new Proxy(func,{
apply(target, thisArgument, argumentsList){
types.forEach((ele,i)=>{
const arg = argumentsList[i]
if(typeof arg !== ele){
throw new TypeError(`第${i+1}个参数${argumentsList[i]}类型错误`)
}
})
Reflect.apply(target, thisArgument, argumentsList)
}
})
return proxy
}
const sumProxy = validatorFunction(sum,"number","number")
sumProxy(1,"a")