Vue中的代理Proxy和$ref

一、   代理Proxy

1.   介绍

1. Proxy又称为代理。在现实生活中,大家对代理二字并不会太陌生,比如某产品的代理。打个比方来说,我们要买一台手机,我们不会直接到一家手机厂去买,会在手机的代理商中买。

2. JavaScript中,Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程,即对编程语言进行编程。

   3.    Proxy可以理解成,在目标对象之前架设一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器。

2. 基本语法

1 // @param {Object} target 用来被代理的对象

2 // @param {Object} handler 用来设置代理的对象

3 let proxy = new Proxy(target, handler)

1 Proxy,见名知意,其功能非常类似于设计模式中的代理模式,该模式常用于三个方面:

2 拦截和监视外部对对象的访问

3 降低函数或类的复杂度

4 在复杂操作前对操作进行校验或对所需资源进行管理

5 在支持 Proxy 的浏览器环境中,Proxy 是一个全局对象,可以直接使用。Proxy(target, handler) 是一个构造函数,target 是被代理的对象,handlder 是声明了各类代理操作的对象,最终返回一个代理对象。外界每次通过代理对象访问 target 对象的属性时,就会经过 handler 对象,从这个流程来看,代理对象很类似 middleware(中间件)。那么 Proxy 可以拦截什么操作呢?最常见的就是 get(读取)、set(修改)对象属性等操作,完整的可拦截操作列表请点击这里。此外,Proxy 对象还提供了一个 revoke 方法,可以随时注销所有的代理操作。

一个代理的例子:

1 const target = { name: 'Billy Bob', age: 15 };
2 const handler = { get(target, key, proxy) { const today = new Date();
3         console.log(`GET request made for ${key} at ${today}`);
 return Reflect.get(target, key, proxy); } };
4 const proxy = new Proxy(target, handler);
5 proxy.name; // => "GET request made for name at Thu Jul 21 2016 15:26:20 GMT+0800 (CST)" // => "Billy Bob"

 

 

1.Reflect称为反射。它也是ES6中为了操作对象而提供的新的API,用来替代直接调用Object的方法。Reflect是一个内置的对象,它提供可拦截JavaScript操作的方法。方法与代理处理程序的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

Reflect与大多数全局对象不同,Reflect没有构造函数。你不能将其与一个new运算符一起使用,或者将Reflect对象作为一个函数来调用。

1.   处理器对象handler

处理器对象handler一共提供了14种可代理操作,每种操作的代号(属性名/方法名)和触发这种操作的方式如下:

 

handler.getPrototypeOf():在读取代理对象的原型时触发该操作,比如在执行Object.getPrototypeOf(proxy)

handler.setPrototypeOf():在设置代理对象的原型时触发该操作,比如在执行Object.setprototypeOf(proxy, null)

handler.isExtensible():在判断一个代理对象是否是可扩展时触发该操作,比如在执行Object.isExtensible(proxy)

handler.preventExtensions():在让一个代理对象不可扩展时触发该操作,比如在执行Object.preventExtensions(proxy)

handler.getOwnPropertyDescriptor():在获取代理对象某个属性的属性描述时触发该操作,比如在执行Object.getOwnPropertyDescriptor(proxy, 'foo')

handler.defineProperty():在定义代理对象某个属性时的属性描述时触发该操作,比如在执行Object.defineProperty(proxy,'foo',{})

handler.has():在判断代理对象是否拥有某个属性时触发该操作,比如在执行'foo' in proxy

handler.get():在读取代理对象的某个属性时触发该操作,比如在执行proxy.foo

handler.set():在给代理对象的某个赋值时触发该操作,比如在执行proxy.foo = 1

handler.deleteProperty():在删除代理对象的某个属性时触发该操作,比如在执行delete proxy.foo

handler.ownKeys():在获取代理对象的所有属性键时触发该操作,比如在执行Object.getOwnPropertyNames(proxy)

handler.apply():在调用一个目标对象为函数的代理对象时触发该操作,比如在执行proxy()

handler.construct():在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy()

Reflect对象拥有对应的可以控制各种元编程任务的静态方法。这些功能和Proxy一一对应。

下面的这些名称你可能看起来很眼熟(因为他们也是Object上的方法):

 

Reflect.getOwnPropertyDescriptor(..)

Reflect.defineProperty(..)

Reflect.getPrototypeOf(..)

Reflect.setPrototypeOf(..)

Reflect.preventExtensions(..)

Reflect.isExtensible(..)

这些方法和在Object上的同名方法一样。然后,一个区别在于,Object上这么方法的第一个参数是一个对象,Reflect遇到这种情况会扔出一个错误。

 

 

 

 

3.   陷阱代理

使用set陷阱验证属性

假设创建一个属性值是数字的对象,对象中每新增一个属性都要加以验证,如果不是数字必须抛出错误。为了实现这个任务,可以定义一个set陷阱来覆写设置值的默认特性。

set陷阱接受4个参数:

trapTaqget 用于接收属性(代理的目标)的对象

key 要写入的属性键(字符串或Symbol类型)

value 被写入属性的值

receiver 操作发生的对象(通常是代理)

Reflect.set()set陷阱对应的反射方法和默认特性,它和set代理陷阱一样也接受相同的4个参数,以方便在陷阱中使用。如果属性已设置陷阱应该返回true,如果未设置则返回false(Reflect.set()方法基于操作是否成功来返回恰当的值)

 

可以使用set陷阱并检查传入的值来验证属性值:

 

1 //set陷阱并检查传入的值来验证属性值

 2 let target = { name: "target" };

 3 let proxy = new Proxy(target, {

 4     set(trapTarget, key, value, receiver) {

 5         // 忽略已有属性,避免影响它们

 6         if (!trapTarget.hasOwnProperty(key)) {

 7             if (isNaN(value)) { throw new TypeError("Property must be a number."); }

 8         } // 添加属性

 9         return Reflect.set(trapTarget, key, value, receiver);

10     }

11 });

12 // 添加一个新属性

13 proxy.count = 1;

14 console.log(proxy.count); // => 1

15 console.log(target.count); // => 1 // 你可以为 name 赋一个非数值类型的值,因为该属性已经存在

16 proxy.name = "proxy";

17 console.log(proxy.name); // => "proxy"

18 console.log(target.name); // => "proxy"

// 抛出错误 proxy.anotherName = "proxy";

这段代码定义了一个代理来验证添加到target的新属性,当执行proxy.count=1时,set陷阱被调用,此时trapTarget的值等于targetkey等于"count"value等于1receiver等于proxy

由于target上没有count属性,因此代理继续将value值传入isNaN(),如果结果是NaN,则证明传入的属性值不是数字,同时也抛出一个错误。在这段代码中,count被设置为1,所以代理调用Reflect.set()方法并传入陷阱接受的4个参数来添加新属性。

proxy.name可以成功被赋值为一个字符串,这是因为target已经拥有一个name属性,但通过调用trapTarget.hasownproperty()方法验证检查后被排除了,所以目标已有的非数字属性仍然可以被操作。

然而,将proxy.anotherName赋值为一个字符串时会抛出错误。目标上没有anotherName属性,所以它的值需要被验证,而由于"Proxy"不是一个数字值,因此抛出错误。

set代理陷阱可以拦截写入属性的操作,get代理陷阱可以拦截读取属性的操作

用get陷阱验证对象结构(Object Shape)

JS有一个时常令人感到困惑的特殊行为,即读取不存在的属性时不会抛出错误,而是用undefined代替被读取属性的值。

1 //get陷阱验证对象结构(Object Shape)

2 let target = {};

3 console.log(target.name); // => undefined

对象结构是指对象中所有可用属性和方法的集合,JS引擎通过对象结构来优化代码,通常会创建类来表示对象,如果可以安全地假定一个对象将始终具有相同的属性和方法,那么当程序试图访问不存在的属性时会抛出错误。代理让对象结构检验变得简单。

 

因为只有当读取属性时才会检验属性,所以无论对象中是否存在某个属性,都可以通过get陷阱来检测,它接受3个参数:

 

trapTarget 被读取属性的源对象(代理的目标)

key 要读取的属性键(字符串或Symbol)

receiver 操作发生的对象(通常是代理)

由于get陷阱不写入值,所以它复刻了set陷阱中除value外的其他3个参数,Reflect.get()也接受同样3个参数并返回属性的默认值。

 

如果属性在目标上不存在,则使用get陷阱和Reflect.get()时会抛出错误:

 

 

let proxy = new Proxy({}, {

    get(trapTarget, key, receiver) {

        if (!(key in receiver)) {

            throw new TypeError("Property " + key + " doesn't exist.");

        }

        return Reflect.get(trapTarget, key, receiver);

    }

});

// 添加属性的功能正常

proxy.name = "proxy";

console.log(proxy.name); // => "proxy"

// 读取不存在属性会抛出错误

console.log(proxy.nme); // => 抛出错误

 

 

 

原型代理陷阱

Object.setPrototypeOf()方法被用于作为ES5中的Object.getPrototypeOf()方法的补充。通过代理中的setPrototypeOf陷阱和getPrototypeOf陷阱可以拦截这两个方法的执行过程,在这两种情况下,Object上的方法会调用代理中的同名陷阱来改变方法的行为。

两个陷阱均与代理有关,但具体到方法只与每个陷阱的类型有关,setPrototypeOf陷阱接受以下这些参数:

trapTarget 接受原型设置的对象(代理的目标)

proto 作为原型使用的对象

传入Object.setPrototypeOf()方法和Reflect.setPrototypeOf()方法的均是以上两个参数,另一方面,getPrototypeOf陷阱中的Object.getPrototypeOf()方法和Reflect.getPrototypeOf()方法只接受参数trapTarget

原型代理陷阱的运行机制

原型代理陷阱有一些限制。首先,getPrototypeOf陷阱必须返回对象或null,否则将导致运行时错误,返回值检查可以确保Object.getPrototypeOf()返回的总是预期的值;其次,在setPrototypeOf陷阱中,如果操作失败则返回的一定是false,此时Object.setPrototypeOf()会抛出错误,如果setPrototypeOf返回了任何不是false的值,那么Object.setPrototypeOf()便假设操作成功。

 

以下示例通过总是返回null,且不允许改变原型的方式隐藏了代理的原型:

1 let target = {};

 2 let proxy = new Proxy(target, {

 3     getPrototypeOf(trapTarget) {

 4         return null;

 5     },

 6     setPrototypeOf(trapTarget, proto) {

 7         return false;

 8     }

 9 });

10 let targetProto = Object.getPrototypeOf(target);

11 let proxyProto = Object.getPrototypeOf(proxy);

12 console.log(targetProto === Object.prototype); // => true

13 console.log(proxyProto === Object.prototype); // => false

14 console.log(proxyProto); // => null

// 成功

15 Object.setPrototypeOf(target, {});

// 抛出错误

16 Object.setPrototypeOf(proxy, {});

 

代码强调了target和proxy的行为差异。Object.getPrototypeOf()给target返回的是值,而给proxy返回值时,由于getPrototypeOf陷阱被调用,返回的是null;同样,Object.setPrototypeOf()成功为target设置原型,而给proxy设置原型时,由于setPrototypeOf陷阱被调用,最终抛出一个错误。

一、   vue$ref

 

一个象,持有注册 ref 特性 的所有 DOM 元素和例。

ref 被用来元素或子件注册引用信息。引用信息将会注册在父件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子件上,引用就指向例:

<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p>

<!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>

ref属性不是一个标准的HTML属性,只是Vue中的一个属性。实际上,它甚至不会是DOM的一部分,所以在浏览器中你查看渲染的HTML,你是看不到有关于ref的任何东西。因为在它前面没有添加:,而且它也不是一个指令

<div ref="demo"></div>

document.querySelector('[ref=demo]');

 

posted @ 2018-07-10 15:35  云中一樵夫  阅读(4158)  评论(0编辑  收藏  举报