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")
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章