ES6学习笔记篇四之迭代器生成器、反射与代理

一、迭代器(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")
发布了21 篇原创文章 · 获赞 82 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Newbie___/article/details/104465844