ES6 中的 Reflect 和 Proxy

Reflect

Reflect 是ES6中新增的特征。它是一个一般对象,下面有13个静态要领(enumerate在终究的宣布版中被移除),可以再全局下接见。它不能当作函数挪用,也不可以用new操纵符天生一个实例。

起首来讲,Reflect的提出是为了整合之前JS中存在的一些不太合理的处所。

1)越发有效的返回值

Object.getOwnPropertyNames(Reflect)
// ["defineProperty", "deleteProperty", "apply", "construct", "get", "getOwnPropertyDescriptor", "getPrototypeOf", "has", "isExtensible", "ownKeys", "preventExtensions", "set", "setPrototypeOf"]

Reflect.apply

功用跟Function.prototype.apply相似。

var a = [1,2,3];
Math.max.apply(null, a);// 3
Reflect.apply(Math.max, null, a); // 3

Reflect.isExtensible / Reflect.preventExtensions

Reflect.preventExtensions用于让一个对象变成不可扩大。它返回一个布尔值,示意是不是操纵胜利。
Reflect.isExtensible示意当前对象是不是可扩大, 返回一个布尔值。

var o = {};

Reflect.isExtensible(o);// true
Object.isExtensible(o);// true

Object.freeze(o);

Reflect.isExtensible(o);// false
Object.isExtensible(o);// false

// 这两要领的区分
Object.isExtensible(1) // false
Reflect.isExtensible(1) // 报错

var newO = {};
Reflect.isExtensible(newO);// true
Reflect.preventExtensions(newO); // true   返回true,示意该操纵胜利
Reflect.isExtensible(newO);// false

Reflect.set / Reflect.get

设置和猎取对象属性, 这两个要领还许可接收一个reciever,用于重定义setter和getter要领的上下文。

var o = {};
Reflect.set(o, 'key', 'value');
console.log(o); // {key: "value"}

Reflect.get(o, 'key'); // "value"
Reflect.get(o, 'nokey'); // undefined

下面演示一下receiver的运用要领


var o = {
    name: 'O'
}

Object.defineProperty(o, 'sayHi', {
    get(){
        return 'hi, I am ' + this.name
    }
})

o.sayHi; // "hi, I am O"

var receiver = {
    name: 'receiver'
}
// 下面是症结, 看好咯~
Reflect.get(o, 'sayHi', receiver); // "hi, I am receiver"

// 下面实验了下Proxy的用法,可以疏忽
var p = new Proxy(o, {
    get(o, k, p){
        return o[k] ? Reflect.get(o, k, receiver) : undefined;
    },
    set(o, k, v, p){
        v += +new Date();
        Reflect.set(o, k, v, p)
    }
});
p.sayHi; // "hi, I am receiver"
p.t = 'time:';
p.t; // "time:1530865528713"

Reflect.ownKeys

Reflect.ownKeys要领用于返回对象的一切属性数组。
这个数组的排序是依据: 先显现数字, 数字依据大小排序,然后是 字符串依据插进去的递次排序, 末了是symbol范例的key也依据插进去插进去递次排序。
涌现这类排序是由于,你给一个对象属性赋值时刻, 对象的key的排序划定规矩就是先数字, 在字符串, 末了是symbol范例的数据。

Reflect.ownKeys(JSON); // ["parse", "stringify", Symbol(Symbol.toStringTag)]
Object.getOwnPropertyNames(JSON); // ["parse", "stringify"]
Object.getOwnPropertySymbols(JSON); // [Symbol(Symbol.toStringTag)]

Reflect.has

用于推断对象是不是具有某个属性

Reflect.has(JSON, 'parse'); // true
Reflect.has(JSON, 'nokeyxx'); // false
Reflect.has(1, 'parse');// Uncaught TypeError: Reflect.has called on non-object

Reflect.construct

功用相似于new操纵符

var a = new Array(3);
console.log(a); // [empty × 3]

var b =  Reflect.construct(Array, [3])
console.log(b); // [empty × 3]

Reflect.defineProperty / Reflect.deleteProperty

Reflect.defineProperty 对应于Object.DefineProperty;
Reflect.deleteProperfy 对应于 delete 语句;

var o = {};
Object.defineProperty(o, 'sayHi', {
    configurable: true,
    get(k){
        return this[k] ? "Hi, I am " + this[k] : 'I have no name';
    }
});

o.sayHi;// "I have no name"

Reflect.defineProperty(o, 'sayHello', {
    configurable: true,
    get(k){
        return this[k] ? "Hi, I am " + this[k] : 'I have no name';
    }
})
o.sayHello; // "I have no name"

//  演示属性删除要领
delete o.sayHi; // true
Reflect.deleteProperty(o, 'sayHello'); // true

Reflect.setPrototypeOf / Reflect.getPrototypeOf

这两个要领用于设置和接见对象的原型__proto__

function A(){}; 
A.prototype.name= "A";
Reflect.getPrototypeOf(A)
// ƒ () { [native code] }

var a = new A();
Reflect.getPrototypeOf(a)
// {name: "A", constructor: ƒ}

a.name; // "A"


var c = {};
Reflect.setPrototypeOf(c, A.prototype);
c.name; // "A"
A.prototype.newName = "C";
c.newName; // "C"

Reflect.getOwnPropertyDescriptor

基本等同于Object.getOwnPropertyDescriptor,用于获得指定属性的形貌对象。

Reflect.getOwnPropertyDescriptor(JSON, 'parse'); // {value: ƒ, writable: true, enumerable: false, configurable: true}
var c = {};
Reflect.defineProperty(c, 'name', {
    configurable: true,
    enumerable: true,
    value: 'CCC',
    writable: false
});

Reflect.getOwnPropertyDescriptor(c, 'name');  // {value: "CCC", writable: false, enumerable: true, configurable: true}

c.name; // "CCC"
c.name = 111;
c.name; // "CCC", 由于writable=== false

Proxy

Proxy 对象用于定义JS对象的基本操纵行动(如属性查找,赋值,罗列,函数挪用等)。

下面看一个修正对象get行动的demo:

var obj = { n : 1};
var p = new Proxy(obj, {
    get(t, k){
        if( Reflect.has(t, k) ){
            return t[k] + 100;
        } else {
            throw Error('no the property') 
        }
    }
});

p.n; // 101
p.p; // Uncaught Error: no the property

关于Proxy详细可以从新定义哪些基本操纵:

  • defineProperty
  • deleteProperty
  • apply
  • construct
  • get
  • getOwnPropertyDescriptor
  • getPrototypeOf
  • has
  • isExtensible
  • ownKeys
  • preventExtensions
  • set
  • setPrototypeOf

这个列表跟 Reflect 具有的静态要领是一致的。更多信息可以参考Proxy可以处置惩罚的要领列表 的形貌

不说上面这些值基本操纵,我们枚举下一下Proxy的一些运用场景。

设置默认值

假如接见一个对象中还没有初始化的值,Proxy可以经由过程代办get要领,返回一个默认值。

    function setDefault(defaults) {
        const handler = {
            get(t, k) {
                return Reflect.get(t, k) || defaults[k];
            }
        }

        return new Proxy({},handler);
    }

    const d = setDefault({
        name: 'name'
    });

    const log = console.log;

    log(d.name); // "name"
    d.name = 'new name';
    log(d.name); // "new name"
    log(d);

隐蔽私有属性

Proxy可以代办对象的基本操纵,我们经由过程代办 get 要领,掌握外部对对象内部属性的要领。如许便可以到达隐蔽某些特征(私有属性)的目标。

    const log = console.log;

    function hideProp(obj,filter) {
        const handler = {
            get(t, k) {
                if(!filter(k)){
                    let v = Reflect.get(t, k);
                    if(typeof v === 'function'){
                        v = v.bind(t);
                    }
                    return v;
                }
                
            }
        }

        return new Proxy(obj,handler);
    }

    function filter(key){
        return key[0] === '_';
    }

    const o = {
        _p : 'no access',
        p: 'access',
        f: function(){
            log(this._p);
        }
    };

    const p = hideProp(o, filter);

    log(p.p); // access
    log(p._p); // undefined
    log(p.f());   // no access

更圆满的罗列

许多JavaScript代码运用字符串、一般或凝结的对象作为罗列。这些处理计划有其范例的安全题目,而且一般轻易失足。

Proxy可以供应一个可行的替换计划。我们采纳一个简朴的键值对象,并经由过程庇护它免受(以至无意的)修正而使其越发硬朗。以至比Object.freeze越发完美。(Object.freeze不许可修正,但不会激发毛病,而是寂静报错)


    const log = console.log;

    function enumF(obj) {
        const handler = {
            get(t, k) {
                if(Reflect.has(t, k)){
                    let v = Reflect.get(t, k);
                    if(typeof v === 'function'){
                        v = v.bind(t);
                    }
                    return v;
                }
                
            },
            set(t){
                throw new TypeError('Enum is read only');
            }
        }

        return new Proxy(obj,handler);
    }

    const o = {
        p: 'access',
        f: function(){
            log(this._p);
        }
    };

    const p = enumF(o);

    log(p.p); // access
    p.p = 'modified';  // TypeError

    // 对照Object.freeze
    const o1 = {
        p: '1'
    }
    Object.freeze(o1);
    o1.p = 'modified';
    log(o1.p);

跟踪对象变化

由于Proxy可以截获对象的大部分基本操纵,因而我们实际上是可以跟踪的对 对象的接见和修正。经由过程纪录这些接见和修正信息,能纪录下对这个对象的一切操纵纪录。

    const log = console.log;

    function trackChange(obj, onchange) {
        const handler = {
            deleteProperty(t, k){
                const oldVal = t[k];
                Reflect.deleteProperty(t, k);
                onchange(t, k , undefined, oldVal)
            },
            set(t,k, v){
                const oldVal = t[k];
                Reflect.set(t, k , v);
                onchange(t, k, v, oldVal);
            }
        }

        return new Proxy(obj,handler);
    }

    const o = {
        p: 'access',
        f: function(){
            log(this._p);
        }
    };

    const p = trackChange(o, (t,k,newV, oldV)=>{
        log(`the property: ${k} change from old value [${oldV}] to new value [${newV}]`)
    });

    
    p.p = 'modified';

    delete p.f;

Proxy完成单例形式

来看看经由过程Proxy怎样完成 单例形式。


    const log = console.log;

    function singleInstance(obj){
        let instance,
            handler = {
                construct:function(t){
                    if(!instance){
                        instance = Reflect.construct(...arguments);
                    }
                    return instance;
                }
            }

        return new Proxy(obj, handler);
    }

    function A(){
        this.v = 1;
    }

    const p = singleInstance(A);

    const p1 = new p();
    const p2 = new p();

    log(p1.v);
    log(p2.v);
    p1.v = 'new value';
    log(p1.v);
    log(p2.v);

总结

总结一下,上面的几个例子中,我们基本上代办的都是对象的get,set,deleteProperty要领。这些都是对象的罕见操纵。用好了Proxy可以处理许多题目,比方vue.js数据双向绑定的特征实际上也可以运用Proxy完成。

说了这么多,然则并不极力推荐运用Proxy,并非出于兼容性题目。主如果机能方面,可以一定的是,运用了Proxy一定不如原有对象接见速率更快。Proxy相当于在对象前添加了一道墙,任何操纵都要先经由Proxy处置惩罚。这一定会增添本钱。写这篇文章的重要目标是 愿望可以相识更多的东西,为本身处理题目增添别的一种计划

【参考资料】

ecma 262:Reflect

为何要运用Reflect的缘由

实例剖析 ES6 Proxy 运用场景

ES6 Features – 10 Use Cases for Proxy

    原文作者:胡斐
    原文地址: https://segmentfault.com/a/1190000016011511
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞