Vue中的代理Proxy和$refs

前言

最近国内 VPN 限制的严厉的多,一不小心就会被抓去签保证书,为了能和小伙伴联系,也为了能查到相关资讯,了解国内动态,关注权利的猎物老郭 特此写博,然而这一切都是免费的!免费的!免费的!(重要的事情讲三遍)

正文开始

一、 代理 Proxy

  1. 介绍

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

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

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

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 方法,可以随时注销所有的代理操作。

一个代理的例子:

1const 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 遇到这种情况会扔出一个错误。

  1. 陷阱代理

使用 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 的值等于 target,key 等于”count”,value 等于 1,receiver 等于 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 returnnull;

5 },

6 setPrototypeOf(trapTarget, proto) {

7 returnfalse;

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 元素;如果用在子组件上,引用就指向组件实例:

hello

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

document.querySelector(‘[ref=demo]’);