ES6学习笔记之Proxy

737 阅读3分钟

Proxies

 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)
     }
 })

看看我们创建的对象:

> obj.count = 1;
    setting count!
> ++obj.count;
    getting count!
    setting count!
    2

我们拦截了这个对象的属性获取,重载了"."运算符。

什么是对象

  • 对象有属性。可以获取,设置,删除它们。
  • 对象有原型。JS的继承靠这个实现。
  • 有些对象是函数或者构造函数。你可以调用它们。

JS程序对对象做的事情,基本都是通过属性、原型和函数。

ECMAScript标准委员会定义了14个内置方法,所有对象的通用接口。[[]]强调这些是内置方法,一般js代码看不到,不能调用、删除或者改写这些方法。下面列出了几个比较常用的。

  • obj.[[Get]](key,receiver) 获取某属性的值。 调用:obj.prop或者obj[key]

obj is the object currently being searched; receiver is the object where we first started searching for this property. Sometimes we have to search several objects. obj might be an object on receiver’s prototype chain.

  • obj.[[Set]](key,value,receiver) 给对象的某属性赋值。 调用:obj.prop = value 或者obj[key] = valueobj.prop += 2先调用[[Get]]方法,再调用[[Set]]方法。++--一样。

  • obj.[[HasProperty]](key) 看某属性是否存在。 调用:key in obj

  • obj.[[Enumerate]]() 列出对象的可枚举属性。 调用:for (key in obj) ...。 返回一个可迭代对象,for-in循环获取一个对象的属性名。

  • obj.[[GetPrototypeOf]] 返回对象的原型。 调用:obj.__proto__或者Object.getPrototypeOf(obj)

  • functionObj.[[Call]](thisValue,arguments) 调用一个函数。 调用:functionObj()或者x.method() 可选,不是所有对象都是函数。

  • constructorObj.[[Construct]](arguments,newTarget) 激活一个构造函数。 调用举例:new Date(2019, 9, 10)。 可选。不是所有对象都是构造函数。

ES6标准语法或内置方法,所有和对象有关的,都是被这14个内置方法确定的。proxy做的的是取代这些方法。 当我们覆盖这些内置方法时,我们会改变像obj.prop一样的核心语法,Object.keys()的内置方法,甚至更多。

Proxy

ES6定义了一个新的全局构造函数:Proxy。它有两个参数,一个目标对象,一个处理对象。 一个简单例子:

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

先不管处理程序对象,只关注proxy和target的关联。 所有proxy的内置方法都传递给target。即,如果啥调用proxy.[[Enumerate]](),它返回target.[[Enumerate]]()

先试试。调用proxy.[[Set]]()proxy.color = "pink";

发生了什么? proxy.[[Set]]()调用了target.[[Set]](),所以给target增加了一个新的属性。

> target.color
    "pink"

proxy基本上和它的target行为一致。 proxy !== targetproxy有时候会不能通过类型检查,而target能通过。 即使一个proxy的target是DOM元素,代理也不是真的元素。 document.body.appendChild(proxy)会失败,报TypeError

Proxy handlers

现在来看处理程序对象。这个让proxy有用。 这个处理程序对象的方法会覆盖proxy的任意内置方法。 比如想要拦截给对象属性赋值的其他,可以自己定义一个handler.set()方法。

var target = {};
var handler = {
    set: function (target, key, value, receiver){
        throw new Error("不允许给这个对象赋值!")
    }
};
var  proxy = new Proxy(target, handler);
> proxy.name = "June"
    Error:"不允许给这个对象赋值!"

MDN上有处理程序方法的完整列表。 ES6定义了14个内置方法。

例子:“不可能的”自动填充对象

> var tree = Tree();
> tree
    { }
> tree.branch1.branch2.twig = "green";
> tree
    { branch1: { branch2: { twig: "green" } } }
> tree.branch1.branch3.twig = "yellow";
    { branch1: { branch2: { twig: "green" },
                 branch3: { twig: "yellow" }}}

中间的对象branch1,branch2,branch3在需要的时候就自动创建了。怎么做到的? 用proxies,几行代码就可以搞定。

function Tree() {
  return new Proxy({}, handler);
}

var handler = {
  get: function (target, key, receiver) {
    if (!(key in target)) {
      target[key] = Tree();  // auto-create a sub-Tree
    }
    return Reflect.get(target, key, receiver);
  }
};

注意最后的Reflect.get()。一个通用需求,代理处理程序方法,告知“现在就做委派给目标的默认行为”。(不知道我在写些什么鬼=-=)

原文:hacks.mozilla.org/2015/07/es6…