JavaScript学习之ES6 ES2015学记笔记(十一)-代理(Proxy)

说道Proxy就会想到各种编程中需要使用的代理服务器,在ES6新增这样一个新对象:

var obj = new Proxy({}, {
    get: function (target, key, receiver) {
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
    },
    set: function (target, key, value, receiver) {
        console.log(`setting ${key}!`);
        return Reflect.set(target, key, value, receiver);
    }
});

出现了setting count,getting count,因为拦截了对象的属性访问方法。

如何实现Proxy

通过虚拟化-ES6 则为 JavaScript 中最基本的概念“对象(object) ”引入了虚拟化支持。

什么是对象

目前的定义:对象是属性的集合

所有的对象都具有一些基本的功能:

  • 对象都有属性。你可以 get、 set 或删除它们或做更多操作
  • 对象都有原型。 这也是 JS 中继承特性的实现方式。
  • 有一些对象是可以被调用的函数或构造函数。
     

ES6标准中定义了14种方法双方括号[[ ]]代表内部方法

  • obj.[[Get]](key, receiver) —— 获取属性值。
    当 JS 代码执行以下方法时被调用: obj.prop 或 obj[key]。
    obj 是当前被搜索的对象, receiver 是我们首先开始搜索这个属性的对象。有时我们必须要搜索几个对象, obj 可能是一个在 receiver 原型链上的对象。
  • obj.[[Set]](key, value, receiver) —— 为对象的属性赋值。
    当 JS 代码执行以下方法时被调用: obj.prop = value 或 obj[key] = value。
    执行类似 obj.prop += 2 这样的赋值语句时,首先调用[[Get]]方法,然后调用[[Set]]方法。对于++和--操作符来说亦是如此。
  • obj.[HasProperty] —— 检测对象中是否存在某属性。
    当 JS 代码执行以下方法时被调用: key in obj。
  • obj.[Enumerate] ——列举对象的可枚举属性。
    当 JS 代码执行以下方法时被调用: for (key in obj) …这个内部方法会返回一个可迭代对象, for-in 循环可通过这个方法得到对象属性的名称。
  • obj.[GetPrototypeOf] —— 返回对象的原型。
    当 JS 代码执行以下方法时被调用: obj.[__proto__]或Object.getPrototypeOf(obj)。
  • functionObj.[[Call]](thisValue, arguments) —— 调用一个函数。
    当 JS 代码执行以下方法时被调用: functionObj()或 x.method()。可选的。不是每一个对象都是函数。
  • constructorObj.[[Construct]](arguments, newTarget) —— 调用一个构造函数。
    当 JS 代码执行以下方法时被调用:举个例子, new Date(2890, 6, 2)。可选的。不是每一个对象都是构造函数。
    参数 newTarget 在子类中起一定作用。
     

代理Proxy

           ES6规范定义了一个全新的全局构造函数:Proxy它可以接受两个参数:目标对象(target) 与句柄对象(handler)。

var target = {}, handler = {};
var proxy = new Proxy(target, handler);

           先讨论代理和目标对象之间的关系,再研究句柄对象的功用

           将代理的所有内部方法转发至目标,例如调用proxy.[[Enumerate]]()就返回target.[[Enumerate]]();执行一条触发proxy.[[Set]]()方法的语句:proxy.color = "pink";

创建的代理会尽可能与目标的行为一致,当然也不完全相同 proxy!==target.

         如果代理的目标是一个 DOM 元素,相应的代理就不是,此时类似 document.body.appendChild(proxy)的操作会触发类型错误(TypeError)。

代理句柄

      句柄对象的方法可以覆写任意代理的内部方法。例如可以定义一个handler.set()方法拦截所有给对象赋值的行为:

var target = {};
var handler = {
    set: function (target, key, value, receiver) {
        throw new Error("请不要为这个对象设置属性。 ");
    }
};
var proxy = new Proxy(target,handler);

MDN上关于Proxy的示例代码可以参考示例代码学习

示例一:“对象自动填充”

实现一个Tree()函数:

function Tree() {
    return new Proxy({}, handler);
}
var handler = {
    get: function (target, key, receiver) {
    if (!(key in target)) {
    target[key] = Tree(); // 自动创建一个子树
}
    return Reflect.get(target, key, receiver);
}
};

在ES6中定义了一个新的反射reflect对象,只执行委托给目标的默认行为,reflect.get()

示例二:只读视图

        我们需要实现一个函数, readOnlyView(object),它可以接受任何对象作为参数,并返回一个与此对象行为一致的代理,该代理不可被变更, 就像这样:

> var newMath = readOnlyView(Math);
> newMath.min(54, 40);
40
> newMath.max = Math.min;
Error: can't modify read-only view
> delete newMath.sin;
Error: can't modify read-only view

为了实现上述功能,需要进行干预,第一步是拦截可能修改目标对象的五种内部方法:

function NOPE() {
    throw new Error("can't modify read-only view");
}
    var handler = {
// 覆写所有五种可变方法。
        set: NOPE,
        defineProperty: NOPE,
        deleteProperty: NOPE,
        preventExtensions: NOPE,
        setPrototypeOf: NOPE
    };
function readOnlyView(target) {
    return new Proxy(target, handler);
}

借助只读视图阻止了赋值、属性定义等过程。

上述方如果使用[[Get]]的一些方法任然可能返回可变对象。所以即使一些对象x是只读视图,但是x.prop仍然是可变的,BUG

解决方法如下:

function NOPE() {
    throw new Error("can't modify read-only view");
}
    var handler = {
// 覆写所有五种可变方法。
        set: NOPE,

        // 在只读视图中包裹其它结果。
        get: function (target, key, receiver) {
        // 从执行默认行为开始。
            var result = Reflect.get(target, key, receiver);
            // 确保返回一个不可变对象!
            if (Object(result) === result) {
            // result 是一个对象。
            return readOnlyView(result);
            }
            // result 是一个原始原始类型,所以已经具备不可变的性质。
            return result;
        },
        defineProperty: NOPE,
        deleteProperty: NOPE,
        preventExtensions: NOPE,
        setPrototypeOf: NOPE
    };
function readOnlyView(target) {
    return new Proxy(target, handler);
}

同样,getPrototypeOf 和 getOwnPropertyDescriptor 这两个方法也需要进行同样的处理,创建代理非常简单,但是创建一个具有直观行为的代理相当困难

代理的优缺点

  • 代理可以帮助你观察或记录对象访问,当调试代码时助你一臂之力,测试框架用代理来创建模拟对象(mock object)。
  • 代理可以帮助你强化普通对象的能力,例如:惰性属性填充。
  • 与 WeakMap 深度结合。在 WeakMap 中创建代理时的缓存内存,无论传递多少次对象给 readOnlyView,只创建一个代理
  • 代理可解除。 ES6 规范中还定义了另外一个函数: Proxy.revocable(target,handler)。这个函数可以像 new Proxy(target, handler)一样创建代理,但是创建好的代理后续可被解除。(Proxy.revocable 方法返回一个对象,该对象有一个.proxy 属
    性和一个.revoke 方法。),代理解除后停止运行并抛出所有内部的方法。
  • 对象不变性。在某些情况下, ES6 需要代理的句柄方法来报告与目标对象状态一致结果,以此来保证所有对象甚至是代理的不变性。举个例子,除非目标不可扩展(inextensible),否则代理不能被声明为不可扩展的

对象到底什么?

       对象是什么?可能现在最贴切的答案需要用 12 个内部方法进行定义:对象是在 JS程序中拥有[[Get]]、 [[Set]]等操作的实体。

     
 

本节参考《ES6-In-Depth》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值