1. ECMAScript 6 入門教程—Reflect

        共 10834字,需瀏覽 22分鐘

         ·

        2020-09-29 21:42

        作者 | 阮一峰

        1、概述

        Reflect對象與Proxy對象一樣,也是 ES6 為了操作對象而提供的新 API。Reflect對象的設(shè)計目的有這樣幾個。
        (1) 將Object對象的一些明顯屬于語言內(nèi)部的方法(比如Object.defineProperty),放到Reflect對象上?,F(xiàn)階段,某些方法同時在Object和Reflect對象上部署,未來的新方法將只部署在Reflect對象上。也就是說,從Reflect對象上可以拿到語言內(nèi)部的方法。
        (2) 修改某些Object方法的返回結(jié)果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)則會返回false。
        // 老寫法
        try {
        Object.defineProperty(target, property, attributes);
        // success
        } catch (e) {
        // failure
        }

        // 新寫法
        if (Reflect.defineProperty(target, property, attributes)) {
        // success
        } else {
        // failure
        }
        (3) 讓Object操作都變成函數(shù)行為。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數(shù)行為。
        // 老寫法
        'assign' in Object // true

        // 新寫法
        Reflect.has(Object, 'assign') // true
        (4)Reflect對象的方法與Proxy對象的方法一一對應(yīng),只要是Proxy對象的方法,就能在Reflect對象上找到對應(yīng)的方法。這就讓Proxy對象可以方便地調(diào)用對應(yīng)的Reflect方法,完成默認(rèn)行為,作為修改行為的基礎(chǔ)。也就是說,不管Proxy怎么修改默認(rèn)行為,你總可以在Reflect上獲取默認(rèn)行為。
        Proxy(target, {
        set: function(target, name, value, receiver) {
        var success = Reflect.set(target, name, value, receiver);
        if (success) {
        console.log('property ' + name + ' on ' + target + ' set to ' + value);
        }
        return success;
        }
        });
        上面代碼中,Proxy方法攔截target對象的屬性賦值行為。它采用Reflect.set方法將值賦值給對象的屬性,確保完成原有的行為,然后再部署額外的功能。
        下面是另一個例子。
        var loggedObj = new Proxy(obj, {
        get(target, name) {
        console.log('get', target, name);
        return Reflect.get(target, name);
        },
        deleteProperty(target, name) {
        console.log('delete' + name);
        return Reflect.deleteProperty(target, name);
        },
        has(target, name) {
        console.log('has' + name);
        return Reflect.has(target, name);
        }
        });
        上面代碼中,每一個Proxy對象的攔截操作(get、delete、has),內(nèi)部都調(diào)用對應(yīng)的Reflect方法,保證原生行為能夠正常執(zhí)行。添加的工作,就是將每一個操作輸出一行日志。
        有了Reflect對象以后,很多操作會更易讀。
        // 老寫法
        Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1

        // 新寫法
        Reflect.apply(Math.floor, undefined, [1.75]) // 1

        2、靜態(tài)方法

        Reflect對象一共有 13 個靜態(tài)方法。
        • Reflect.apply(target, thisArg, args)

        • Reflect.construct(target, args)

        • Reflect.get(target, name, receiver)

        • Reflect.set(target, name, value, receiver)

        • Reflect.defineProperty(target, name, desc)

        • Reflect.deleteProperty(target, name)

        • Reflect.has(target, name)

        • Reflect.ownKeys(target)

        • Reflect.isExtensible(target)

        • Reflect.preventExtensions(target)

        • Reflect.getOwnPropertyDescriptor(target, name)

        • Reflect.getPrototypeOf(target)

        • Reflect.setPrototypeOf(target, prototype)

        上面這些方法的作用,大部分與Object對象的同名方法的作用都是相同的,而且它與Proxy對象的方法是一一對應(yīng)的。下面是對它們的解釋。

        Reflect.get(target, name, receiver)

        Reflect.get方法查找并返回target對象的name屬性,如果沒有該屬性,則返回undefined。
        var myObject = {
        foo: 1,
        bar: 2,
        get baz() {
        return this.foo + this.bar;
        },
        }

        Reflect.get(myObject, 'foo') // 1
        Reflect.get(myObject, 'bar') // 2
        Reflect.get(myObject, 'baz') // 3
        如果name屬性部署了讀取函數(shù)(getter),則讀取函數(shù)的this綁定receiver。
        var myObject = {
        foo: 1,
        bar: 2,
        get baz() {
        return this.foo + this.bar;
        },
        };

        var myReceiverObject = {
        foo: 4,
        bar: 4,
        };

        Reflect.get(myObject, 'baz', myReceiverObject) // 8
        如果第一個參數(shù)不是對象,Reflect.get方法會報錯。
        Reflect.get(1, 'foo') // 報錯
        Reflect.get(false, 'foo') // 報錯

        Reflect.set(target, name, value, receiver)

        Reflect.set方法設(shè)置target對象的name屬性等于value。
        var myObject = {
        foo: 1,
        set bar(value) {
        return this.foo = value;
        },
        }

        myObject.foo // 1

        Reflect.set(myObject, 'foo', 2);
        myObject.foo // 2

        Reflect.set(myObject, 'bar', 3)
        myObject.foo // 3
        如果name屬性設(shè)置了賦值函數(shù),則賦值函數(shù)的this綁定receiver。
        var myObject = {
        foo: 4,
        set bar(value) {
        return this.foo = value;
        },
        };

        var myReceiverObject = {
        foo: 0,
        };

        Reflect.set(myObject, 'bar', 1, myReceiverObject);
        myObject.foo // 4
        myReceiverObject.foo // 1
        注意,如果 Proxy對象和 Reflect對象聯(lián)合使用,前者攔截賦值操作,后者完成賦值的默認(rèn)行為,而且傳入了receiver,那么Reflect.set會觸發(fā)Proxy.defineProperty攔截。
        let p = {
        a: 'a'
        };

        let handler = {
        set(target, key, value, receiver) {
        console.log('set');
        Reflect.set(target, key, value, receiver)
        },
        defineProperty(target, key, attribute) {
        console.log('defineProperty');
        Reflect.defineProperty(target, key, attribute);
        }
        };

        let obj = new Proxy(p, handler);
        obj.a = 'A';
        // set
        // defineProperty
        上面代碼中,Proxy.set攔截里面使用了Reflect.set,而且傳入了receiver,導(dǎo)致觸發(fā)Proxy.defineProperty攔截。這是因為Proxy.set的receiver參數(shù)總是指向當(dāng)前的 Proxy實例(即上例的obj),而Reflect.set一旦傳入receiver,就會將屬性賦值到receiver上面(即obj),導(dǎo)致觸發(fā)defineProperty攔截。如果Reflect.set沒有傳入receiver,那么就不會觸發(fā)defineProperty攔截。
        let p = {
        a: 'a'
        };

        let handler = {
        set(target, key, value, receiver) {
        console.log('set');
        Reflect.set(target, key, value)
        },
        defineProperty(target, key, attribute) {
        console.log('defineProperty');
        Reflect.defineProperty(target, key, attribute);
        }
        };

        let obj = new Proxy(p, handler);
        obj.a = 'A';
        // set
        如果第一個參數(shù)不是對象,Reflect.set會報錯。
        Reflect.set(1, 'foo', {}) // 報錯
        Reflect.set(false, 'foo', {}) // 報錯

        Reflect.has(obj, name)

        Reflect.has方法對應(yīng)name in obj里面的in運算符。
        var myObject = {
        foo: 1,
        };

        // 舊寫法
        'foo' in myObject // true

        // 新寫法
        Reflect.has(myObject, 'foo') // true
        如果Reflect.has()方法的第一個參數(shù)不是對象,會報錯。

        Reflect.deleteProperty(obj, name)

        Reflect.deleteProperty方法等同于delete obj[name],用于刪除對象的屬性。
        const myObj = { foo: 'bar' };

        // 舊寫法
        delete myObj.foo;

        // 新寫法
        Reflect.deleteProperty(myObj, 'foo');
        該方法返回一個布爾值。如果刪除成功,或者被刪除的屬性不存在,返回true;刪除失敗,被刪除的屬性依然存在,返回false。
        如果Reflect.deleteProperty()方法的第一個參數(shù)不是對象,會報錯。

        Reflect.construct(target, args)

        Reflect.construct方法等同于new target(...args),這提供了一種不使用new,來調(diào)用構(gòu)造函數(shù)的方法。
        function Greeting(name) {
        this.name = name;
        }

        // new 的寫法
        const instance = new Greeting('張三');

        // Reflect.construct 的寫法
        const instance = Reflect.construct(Greeting, ['張三']);
        如果Reflect.construct()方法的第一個參數(shù)不是函數(shù),會報錯。

        Reflect.getPrototypeOf(obj)

        Reflect.getPrototypeOf方法用于讀取對象的__proto__屬性,對應(yīng)Object.getPrototypeOf(obj)。
        const myObj = new FancyThing();

        // 舊寫法
        Object.getPrototypeOf(myObj) === FancyThing.prototype;

        // 新寫法
        Reflect.getPrototypeOf(myObj) === FancyThing.prototype;
        Reflect.getPrototypeOf和Object.getPrototypeOf的一個區(qū)別是,如果參數(shù)不是對象,Object.getPrototypeOf會將這個參數(shù)轉(zhuǎn)為對象,然后再運行,而Reflect.getPrototypeOf會報錯。
        Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
        Reflect.getPrototypeOf(1) // 報錯

        Reflect.setPrototypeOf(obj, newProto)

        Reflect.setPrototypeOf方法用于設(shè)置目標(biāo)對象的原型(prototype),對應(yīng)Object.setPrototypeOf(obj, newProto)方法。它返回一個布爾值,表示是否設(shè)置成功。
        const myObj = {};

        // 舊寫法
        Object.setPrototypeOf(myObj, Array.prototype);

        // 新寫法
        Reflect.setPrototypeOf(myObj, Array.prototype);

        myObj.length // 0
        如果無法設(shè)置目標(biāo)對象的原型(比如,目標(biāo)對象禁止擴展),Reflect.setPrototypeOf方法返回false。
        Reflect.setPrototypeOf({}, null)
        // true
        Reflect.setPrototypeOf(Object.freeze({}), null)
        // false
        如果第一個參數(shù)不是對象,Object.setPrototypeOf會返回第一個參數(shù)本身,而Reflect.setPrototypeOf會報錯。
        Object.setPrototypeOf(1, {})
        // 1

        Reflect.setPrototypeOf(1, {})
        // TypeError: Reflect.setPrototypeOf called on non-object
        如果第一個參數(shù)是undefined或null,Object.setPrototypeOf和Reflect.setPrototypeOf都會報錯。
        Object.setPrototypeOf(null, {})
        // TypeError: Object.setPrototypeOf called on null or undefined

        Reflect.setPrototypeOf(null, {})
        // TypeError: Reflect.setPrototypeOf called on non-object

        Reflect.apply(func, thisArg, args)

        Reflect.apply方法等同于Function.prototype.apply.call(func, thisArg, args),用于綁定this對象后執(zhí)行給定函數(shù)。
        一般來說,如果要綁定一個函數(shù)的this對象,可以這樣寫fn.apply(obj, args),但是如果函數(shù)定義了自己的apply方法,就只能寫成Function.prototype.apply.call(fn, obj, args),采用Reflect對象可以簡化這種操作。
        const ages = [11, 33, 12, 54, 18, 96];

        // 舊寫法
        const youngest = Math.min.apply(Math, ages);
        const oldest = Math.max.apply(Math, ages);
        const type = Object.prototype.toString.call(youngest);

        // 新寫法
        const youngest = Reflect.apply(Math.min, Math, ages);
        const oldest = Reflect.apply(Math.max, Math, ages);
        const type = Reflect.apply(Object.prototype.toString, youngest, []);

        Reflect.defineProperty(target, propertyKey, attributes)

        Reflect.defineProperty方法基本等同于Object.defineProperty,用來為對象定義屬性。未來,后者會被逐漸廢除,請從現(xiàn)在開始就使用Reflect.defineProperty代替它。
        function MyDate() {
        /*…*/
        }

        // 舊寫法
        Object.defineProperty(MyDate, 'now', {
        value: () => Date.now()
        });

        // 新寫法
        Reflect.defineProperty(MyDate, 'now', {
        value: () => Date.now()
        });
        如果Reflect.defineProperty的第一個參數(shù)不是對象,就會拋出錯誤,比如Reflect.defineProperty(1, 'foo')。
        這個方法可以與Proxy.defineProperty配合使用。
        const p = new Proxy({}, {
        defineProperty(target, prop, descriptor) {
        console.log(descriptor);
        return Reflect.defineProperty(target, prop, descriptor);
        }
        });

        p.foo = 'bar';
        // {value: "bar", writable: true, enumerable: true, configurable: true}

        p.foo // "bar"
        上面代碼中,Proxy.defineProperty對屬性賦值設(shè)置了攔截,然后使用Reflect.defineProperty完成了賦值。

        Reflect.getOwnPropertyDescriptor(target, propertyKey)

        Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定屬性的描述對象,將來會替代掉后者。
        var myObject = {};
        Object.defineProperty(myObject, 'hidden', {
        value: true,
        enumerable: false,
        });

        // 舊寫法
        var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');

        // 新寫法
        var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');
        Reflect.getOwnPropertyDescriptor和Object.getOwnPropertyDescriptor的一個區(qū)別是,如果第一個參數(shù)不是對象,Object.getOwnPropertyDescriptor(1, 'foo')不報錯,返回undefined,而Reflect.getOwnPropertyDescriptor(1, 'foo')會拋出錯誤,表示參數(shù)非法。

        Reflect.isExtensible (target)

        Reflect.isExtensible方法對應(yīng)Object.isExtensible,返回一個布爾值,表示當(dāng)前對象是否可擴展。
        const myObject = {};

        // 舊寫法
        Object.isExtensible(myObject) // true

        // 新寫法
        Reflect.isExtensible(myObject) // true
        如果參數(shù)不是對象,Object.isExtensible會返回false,因為非對象本來就是不可擴展的,而Reflect.isExtensible會報錯。
        Object.isExtensible(1) // false
        Reflect.isExtensible(1) // 報錯

        Reflect.preventExtensions(target)

        Reflect.preventExtensions對應(yīng)Object.preventExtensions方法,用于讓一個對象變?yōu)椴豢蓴U展。它返回一個布爾值,表示是否操作成功。
        var myObject = {};

        // 舊寫法
        Object.preventExtensions(myObject) // Object {}

        // 新寫法
        Reflect.preventExtensions(myObject) // true
        如果參數(shù)不是對象,Object.preventExtensions在 ES5 環(huán)境報錯,在 ES6 環(huán)境返回傳入的參數(shù),而Reflect.preventExtensions會報錯。
        // ES5 環(huán)境
        Object.preventExtensions(1) // 報錯

        // ES6 環(huán)境
        Object.preventExtensions(1) // 1

        // 新寫法
        Reflect.preventExtensions(1) // 報錯

        Reflect.ownKeys (target)

        Reflect.ownKeys方法用于返回對象的所有屬性,基本等同于Object.getOwnPropertyNames與Object.getOwnPropertySymbols之和。
        var myObject = {
        foo: 1,
        bar: 2,
        [Symbol.for('baz')]: 3,
        [Symbol.for('bing')]: 4,
        };

        // 舊寫法
        Object.getOwnPropertyNames(myObject)
        // ['foo', 'bar']

        Object.getOwnPropertySymbols(myObject)
        //[Symbol(baz), Symbol(bing)]

        // 新寫法
        Reflect.ownKeys(myObject)
        // ['foo', 'bar', Symbol(baz), Symbol(bing)]
        如果Reflect.ownKeys()方法的第一個參數(shù)不是對象,會報錯。

        3、實例:使用 Proxy 實現(xiàn)觀察者模式

        觀察者模式(Observer mode)指的是函數(shù)自動觀察數(shù)據(jù)對象,一旦對象有變化,函數(shù)就會自動執(zhí)行。
        const person = observable({
        name: '張三',
        age: 20
        });

        function print() {
        console.log(`${person.name}, ${person.age}`)
        }

        observe(print);
        person.name = '李四';
        // 輸出
        // 李四, 20
        上面代碼中,數(shù)據(jù)對象person是觀察目標(biāo),函數(shù)print是觀察者。一旦數(shù)據(jù)對象發(fā)生變化,print就會自動執(zhí)行。
        下面,使用 Proxy 寫一個觀察者模式的最簡單實現(xiàn),即實現(xiàn)observable和observe這兩個函數(shù)。思路是observable函數(shù)返回一個原始對象的 Proxy 代理,攔截賦值操作,觸發(fā)充當(dāng)觀察者的各個函數(shù)。
        const queuedObservers = new Set();

        const observe = fn => queuedObservers.add(fn);
        const observable = obj => new Proxy(obj, {set});

        function set(target, key, value, receiver) {
        const result = Reflect.set(target, key, value, receiver);
        queuedObservers.forEach(observer => observer());
        return result;
        }
        上面代碼中,先定義了一個Set集合,所有觀察者函數(shù)都放進這個集合。然后,observable函數(shù)返回原始對象的代理,攔截賦值操作。攔截函數(shù)set之中,會自動執(zhí)行所有觀察者。
        本文完~

        瀏覽 44
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 几把插逼| 黄 色 成 人 免费 视频 69福利区 | 操逼操逼操逼操逼操逼操逼操逼操逼操逼 | 久久伦理视频 | 欧美中文乱伦 |