• 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]');

  • 相关阅读:
    vue+elementui+sortable.js实现表格行和列的拖拽
    vs2019生成项目超级慢,无法生成项目
    Mac brew 安装的 Mysql 8 重置 root 密码
    底层原理
    顶层设计 管程技术
    VS2019 对类类型的匿名对象的优化
    V2019编写C/C++时没有与参数列表匹配的重载函数实例
    Datagrip Communications link failure The last packet successfully received from the server was 171
    SpringBoot入门14(springboot配置thymeleaf使用YML)
    完美解决CentOS8 yum安装AppStream报错,更新yum后无法makecache的问题
  • 原文地址:https://www.cnblogs.com/xkloveme/p/9289376.html
Copyright © 2020-2023  润新知