前言

现在国内的两大框架:vue、react。对于这两个框架,相信大家多多少少都接触过,对于vue而言,有一个很重要的特点,那就是响应式。vue2的响应式采用的是ES5的Object.defineproperty对数据进行劫持。而vue3则是采用ES6的Proxy对数据进行劫持。想要了解vue3的响应式就必须了解Proxy。

一、Proxy的认识

Proxy是ES6新增的,它是一个类,是用于帮助我们创建一个代理对象,如果我们需要监听对象的操作,那么我们可以通过Proxy先创建一个代理对象,之后对该对象的所有操作都通过代理来完成。他与Object.defineProperty最大的区别就在于Object.defineProperty直接监听对象的属性,proxy是监听整个代理对象。

二、Proxy的基本使用

创建一个对象

const obj = {name:'cj',age:18
}

再创建Proxy代理对象

const objProxy = new Proxy(obj,{})

参数一:需要代理的对象

参数二:捕获器,对代理对象的属性进行访问、赋值等操作的时候触发,与Object.defineproperty的存取描述符类似。如果为空对象,就只有set、get这两个默认捕获器,并且不会有过多的操作,get捕获器就直接返回访问属性的值,set捕获器就将新的值赋值给访问属性。

示例:

const objProxy = new Proxy(obj,{})console.log(objProxy.name)   //'cj'
console.log(objProxy.age)    //18objProxy.name = 'wx'
objProxy.age = 20//对代理对象操作后,代理对象就会对原对象进行操作
console.log(obj.name)       //'wx'
console.log(obj.age)        //20

注意:Proxy只能代理对象(Object、Function、Array),非对象值无法进行代理。Proxy也只能够代理对象的基本操作,无法代理对象的复合操作。什么是基本操作?什么又是复合操作?

const obj = {name:'cj',bar(){console.log('bar')    }
}
const objProxy = new Proxy(obj,{})function foo() { console.log('foo') }
const fooProxy = new Proxy(foo,{apply(){    //apply是对函数调用进行拦截//...}
})objProxy.name;     //基本操作
fooProxy();    //基本操作objProxy.bar()    //复合操作

上面访问代理对象的属性、调用函数就是基本操作。调用对象里的方法就是复合操作,它可以分为两步:第一步是访问对象的方法,第二步是调用该方法。

三、Proxy捕获器的使用

Proxy捕获器用来对代理对象属性进行访问、赋值等操作时的一个捕获。与Object.defineproperty的存取描述符类似。下面我们就认识一下常用的四个捕获器的基本使用。

const objProxy = new Proxy(obj,{//get操作符get:function(target,key){console.log(`监听到访问${key}属性`,target)return target[key]    //返回访问属性的值},//set操作符set:function(target,key,newValue){console.log(`监听到给${key}属性设置值`,target)tarset[key] = newValue    //将属性最新值,赋值给代理对象属性}//has操作符has:function(target,key){console.log(`监听到使用in操作符${key}`,target)return key in target}//delete操作符deleteProperty:function(){console.log(`监听到使用delete操作符${key}`,target)delete target[key]    }
})console.log(objProxy.name)
console.log(objProxy.age)objProxy.name = 'wx'
objProxy.age = 20//in操作符
console.log('name' in objProxy)  //true
//delete操作符
delete objProxy.name

Proxy的13种捕获器:

  • handler.get():属性读取操作的捕捉器。
  • handler.set():属性设置操作的捕捉器。
  • handler.has():in 操作符的捕捉器。
  • handler.deleteProperty():delete 操作符的捕捉器。
  • handler.getPrototypeOf():Object.getPrototypeOf 方法的捕捉器。
  • handler.setPrototypeOf():Object.setPrototypeOf 方法的捕捉器。
  • handler.isExtensible():Object.isExtensible 方法的捕捉器。
  • handler.preventExtensions():Object.preventExtensions 方法的捕捉器。
  • handler.getOwnPropertyDescriptor():Object.getOwnPropertyDescriptor 方法的捕捉器。
  • handler.defineProperty():Object.defineProperty 方法的捕捉器。
  • handler.ownKeys():Object.getOwnPropertyNames 方法和Object.getOwnPropertySymbols 方法的捕捉器。
  • handler.apply():函数调用操作的捕捉器。(函数也是一个对象,这里就对函数调用时进行监听)
  • handler.construct():new 操作符的捕捉器。(函数执行new操作符的时候进行监听)

总结:

  • Proxy除了set、get捕获器能监听对象属性,还有其它的操作符,一共有13种。
  • 使用Proxy创建的代理对象进行操作的好处是,可以不用直接通过Object.defineproperty去操作原对象,通过Object.defineproperty直接操作原对象就会将对象原本的数据属性描述符变成了访问属性描述符
  • Object.defineproperty没有类似于has、deleteProperty等这样的捕获器,只有存取属性描述符(set、get)

四、Reflect的认识

Reflect是一个对象,字面意思是"反射"。

Reflect有什么用呐?

它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法; 比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf();

如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?

这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的;所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上。

那么Object和Reflect对象之间的API关系,可以参考MDN文档:

MDN Web Docswww

五、 Reflect基本使用

我们对上面的代码做一下修改,使用Reflect代替一下

const objProxy = new Proxy(obj,{get:function(target,key){console.log(`监听到访问${key}属性`,target)return Reflect.get(target,key)    //改为Reflect.get},set:function(target,key,newValue){console.log(`监听到给${key}属性设置值`,target)Reflect.set(target,key,newValue)    //改为Reflect.set}
})console.log(objProxy.name)objProxy.name = 'wx'

用Reflect的好处是什么呐?

  1. 之前的方式是说到底还是在操作原对象,因为都是在用target、key等直接去操作,改用Reflect就真正意义上不直接操作原对象。
  2. 在有的时候Reflect会更加有用。比如:使用Object.freece(obj)将对象冻结后,之前的方式就无法判断出设置值到底是设置成功了还是失败了。而Reflect可以有返回值,代表是设置成功还是失败。

示例

const istrue = Reflect.set(target,key,newValue)
const result = istrue?'设置成功':"设置失败"

常见的Reflect方法

  • Reflect.getPrototypeOf(target):类似于 Object.getPrototypeOf()。
  • Reflect.setPrototypeOf(target, prototype):设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true。
  • Reflect.isExtensible(target):类似于 Object.isExtensible()
  • Reflect.preventExtensions(target):类似于 Object.preventExtensions()。返回一个Boolean。
  • Reflect.getOwnPropertyDescriptor(target, propertyKey) :类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined.
  • Reflect.defineProperty(target, propertyKey, attributes):和 Object.defineProperty() 类似。如果设置成功就会返回 true
  • Reflect.ownKeys(target):返回一个包含所有自身属性(不包含继承属性)的数组。(类似于Object.keys(), 但不会受enumerable影响)。
  • Reflect.has(target, propertyKey) :判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
  • Reflect.get(target, propertyKey[, receiver]):获取对象身上某个属性的值,类似于 target[name]。
  • Reflect.set(target, propertyKey, value[, receiver]):将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
  • Reflect.deleteProperty(target, propertyKey) :作为函数的delete操作符,相当于执行 delete target[name]。
  • Reflect.apply(target, thisArgument, argumentsList) :对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和Function.prototype.apply() 功能类似。
  • Reflect.construct(target, argumentsList[, newTarget]):对构造函数进行 new 操作,相当于执行 new target(...args)。

六、receiver参数

Proxy的get、set捕获器有receiver参数,即指定接收者receiver,可以把它理解为函数调用过程中的this。那它存在的作用是什么呐?

请看下面示例:

const obj = {_name:'cj',age:18,get name(){     //将存取描述符写入对象体内return this._name    //this === obj},set name(newValue){this._name = newValue}
}const objProxy = new Proxy(obj,{get:function(target,key){console.log(`监听到访问${key}属性`)return Reflect.get(target,key)},set:function(target,key,newValue){console.log(`监听到给${key}属性设置值`)Reflect.set(target,key,newValue)}
})console.log(objProxy.name)

通过objProxy.name去访问属性首先会进入get捕获器,然后通过Reflect会执行name存取描述符,在name存取描述符中,会通过this访问_name。

这里进行了两次属性的访问,一次是objProxy的name属性访问,一次是obj的_name属性访问,那这里会打印几次?

答案是:一次

因为objProxy.name访问是会触发捕获器的,这里会打9印一次。然后通过this访问_name是不会再触发捕获器的,因为捕获器只会当访问代理对象的属性时才会触发,而通过this访问,这里的this是指向obj的,而不是objProxy,所以不会触发捕获器。

但是我们要做到通过this访问对象内部属性也要能触发捕获器,这个时候receiver参数就排上用场了。前面我们说receiver可以看作函数调用的this,那么当我们通过objProxy.name访问时,this就指向为objProxy代理对象了,将receiver传递给Reflect,再通过this访问就会触发捕获器了。这样使得无论是外部访问objProxy的属性还是obj内部通过this访问属性都会经过捕获器。

配合使用receiver

const obj = {_name:'cj',age:18,get name(){return this._name      //this === objProxy},set name(newValue){this._name = newValue}
}const objProxy = new Proxy(obj,{get:function(target,key,receiver){console.log(`监听到访问${key}属性`,target[key],target)return Reflect.get(target,key,receiver)    //将receiver传递给Reflect.get},set:function(target,key,newValue,receiver){console.log(`监听到给${key}属性设置值`,target[key],target)Reflect.set(target,key,newValue,receiver)}
})console.log(objProxy.name)
//第一次打印:console.log(`监听到访问name属性`,'cj',{_name:"cj",age:18})
//第二次打印:console.log(`监听到访问_name属性`,'cj',{_name:"cj",age:18})

将receiver传递给Reflect,然后Reflect执行get存取描述符,这个时候的this就是指向objProxy这个代理对象了。通过this访问也就会触发捕获器了。

总结

Proxy能很好的监听整个对象的变化,vue3放弃了Object.defineproperty改用Proxy,弥补了vue2响应式无法对引用数据类型的增添、删除无法监听到的缺陷。vue2只能通过提供的$setAPI手动将添加或删除的属性加入响应式中。

想要了解vue3响应式,就得必须了解Proxy

Proxy与Reflect详解相关推荐

  1. Proxy和Reflect详解

    Proxy和Reflect详解 之前一直没有理解proxy代理是啥意思,只是感觉很深奥的样子,最近正好在研究vue3的响应式原理,发现vue3是使用proxy完成响应式的,因此仔细的研究了一下prox ...

  2. 【Java】代理模式(Proxy模式)详解

    目录 1.代理模式 2.静态代理 2.1 通过继承实现静态代理 2.2 通过组合实现静态代理 3.动态代理 3.1 JDK动态代理 3.2 cglib动态代理 3.3 SpringAOP使用以及原理 ...

  3. JavaScript 反射机制及 Reflect 详解

    来自 | https://www.cnblogs.com/Leophen/archive/2021/06/02/14838608.html 一.什么是反射机制 反射机制是在编译阶段不知道是哪个类被加载 ...

  4. JS 反射机制及 Reflect 详解

    一.什么是反射机制 反射机制是在编译阶段不知道是哪个类被加载,而是在运行的时候才加载.执行. 也就是说,反射机制指的是程序在运行时能够获取自身的信息. js 中的 apply 就是反射机制. 二.Re ...

  5. Burp suite Proxy代理模块详解

    前言: 首先,我们要知道代理流程是整个web安全测试的基础,所以我们将第一个并且详细的演示他的功能. Proxy 代理模块处于浏览器和web服务器的中间 ,处理HTTP请求和响应就是两个基本功能,还有 ...

  6. 代理模式(Proxy模式)详解

    在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象.例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买.又如 ...

  7. nginx配置详解,完全卸载nginx, nginx https配置

    nginx 配置文件详解 文章目录 nginx 配置文件详解 Ubuntu nginx 目录结构 nginx 配置文件结构 默认nginx.conf( (1.18.0版本) location详解,pr ...

  8. windows curl命令详解

    概述 Curl命令可以通过命令行的方式,执行Http请求.在Elasticsearch中有使用的场景,因此这里研究下如何在windows下执行curl命令. 软件下载 下载地址:https://cur ...

  9. Android 各大厂面试题汇总与详解(持续更新)

    介绍 目前网络中出现了好多各种面试题的汇总,有真实的也有虚假的,所以今年我将会汇总各大公司面试比较常见的问题,逐一进行解答.会一直集成,也会收集大家提供的面试题,如有错误,请大家指出,经过排查存在,会 ...

最新文章

  1. onnx KeyError: ‘upsample_bilinear2d‘
  2. mysql 单表查询
  3. linux系统root默认密码是多少钱,linux root默认密码忘记后的解决方法
  4. 微信小程序开发:学习笔记[8]——页面跳转及传参
  5. (附源码gitHub下载地址)spring boot -jta-atomikos分布式事务
  6. oracle 删除表 索引也会删除吗,Oracle 删除当前用户下所有的表、索引、序列
  7. Rails中select2 实现多选框的效果
  8. eclipse 汉化
  9. Intel/Altera 系列FPGA简介
  10. 测试方案包括哪些内容
  11. 做大数据的公司_技术
  12. 博尔顿大学介绍让学生们在9月重返校园的创新措施
  13. Mifare UltraLight
  14. americdan-life
  15. C++实现3型文法转换词法生成器
  16. 南京工业大学计算机专业全国排名,计算机专业全国排名.doc
  17. 富文本在TextView中显示图片
  18. mac 查询端口被哪个进程占用
  19. 编辑区域地图json数据
  20. 局域计算机网 简称为,局域计算机网,简称为局域网,它的英文缩写是WAN。

热门文章

  1. iOS 官方文档翻译
  2. “直播+”时代,品牌服装电商之路
  3. 服装行业拼的是实力——智能制造
  4. Bus Hound 工具抓取串口数据(PC端抓取USB转串口数据)
  5. java_java开发工程师
  6. xv6 - lab0 - 课程介绍
  7. 汽车以太网测试之UpperTester
  8. 清华研究生情侣发SCI、做实验,毕业后勇敢离京选择小城市
  9. java基础之数据类型
  10. 用 Java 生成和识别二维码就这么简单