资讯专栏INFORMATION COLUMN

ES6 中的 Reflect 和 Proxy

wzyplus / 2492人阅读

摘要:是中新增的特性。首先来说,的提出是为了整合之前中存在的一些不太合理的地方。表示当前对象是否可扩展,返回一个布尔值。更完美的枚举很多代码使用字符串普通或冻结的对象作为枚举。通过记录这些访问和修改信息,能记录下对这个对象的所有操作记录。

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://www.ucloud.cn/yun/108182.html

相关文章

  • ES6 Proxy/Reflect 浅析

    摘要:即必须有返回值其中可接受三个参数,为目标对象,为属性名,为实际接受的对象,默认为本例中新建的,如果单独指出一个对象,可使指出对象受到相同的方法作用。且中的必须有返回值,的不用,这也正是因为在他之后还会执行所以不需要。 ES6 Proxy/Reflect Proxy 拦截器 proxy是es6的新特性,简单来讲,即是对目标对象的属性读取、设置,亦或函数调用等操作进行拦截(处理)。 let...

    Juven 评论0 收藏0
  • ES6学习笔记4-ProxyReflect、Decorator、Module

    摘要:拦截实例作为构造函数调用的操作,比如。方法等同于,这提供了一种不使用,来调用构造函数的方法。方法对应,返回一个布尔值,表示当前对象是否可扩展。这是的一个提案,目前转码器已经支持。别名或修饰器在控制台显示一条警告,表示该方法将废除。 Proxy Proxy 这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器,即用自己的定义覆盖了语言的原始定义。ES6 原生提供 Proxy...

    lushan 评论0 收藏0
  • JavaScript 中的 ES6 Proxy

    摘要:下面是实现的函数为异步函数添加自动超时功能超时时间异步函数包装后的异步函数测试一下,是可以正常调用与访问其上的属性的好了,这便是吾辈最常用的一种方式了封装高阶函数,为函数添加某些功能。 JavaScript 中的 ES6 Proxy 吾辈博客的原文地址: https://blog.rxliuli.com/p/c3... 场景 就算只是扮演,也会成为真实的自我的一部分。对人类的精神来说...

    cucumber 评论0 收藏0
  • ES6Reflect

    摘要:查找并返回对象的属性例例属性部署了读取函数返回的是的参数对象注意如果的第一个参数不是对象,则会报错。它返回一个布尔值,表示是否操作成功用于返回对象的所有属性使用和实现观察者模式请参考观察者模式 1、什么是Reflect?为操作对象而提供的新API 2、为什么要设计Reflect?(1)将Object对象的属于语言内部的方法放到Reflect对象上,即从Reflect对象上拿Object...

    BingqiChen 评论0 收藏0
  • ES6中的代理(Proxy反射(Reflection)

    摘要:代理和反射的定义调用可常见代替其它目标对象的代理,它虚拟化了目标,所以二者看起来功能一致。代理可拦截引擎内部目标的底层对象操作,这些底层操作被拦截后会触发响应特定操作的陷阱函数。 代理和反射的定义 调用 new Proxy() 可常见代替其它目标 (target) 对象的代理,它虚拟化了目标,所以二者看起来功能一致。 代理可拦截JS引擎内部目标的底层对象操作,这些底层操作被拦截后会触发...

    Markxu 评论0 收藏0

发表评论

0条评论

wzyplus

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<