reactive代理_Proxy API--Vue3响应式对象reactive揭秘-程序员宅基地

技术标签: reactive代理  删除对象中的某个属性  

89024c1c79f87c4e00a656b5da75ff00.png

Proxy API对应的Proxy对象是ES2015就已引入的一个原生对象,用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。

从字面意思来理解,Proxy对象是目标对象的一个代理器,任何对目标对象的操作(实例化,添加/删除/修改属性等等),都必须通过该代理器。因此我们可以把来自外界的所有操作进行拦截和过滤或者修改等操作。

基于Proxy的这些特性,常用于:

  • 创建一个可“响应式”的对象,例如Vue3.0中的reactive方法。
  • 创建可隔离的JavaScript“沙箱”。

Proxy常见用法

Proxy语法:

const p = new Proxy(target, handler)
  • target:要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler:以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

例如下面一个很简单的用法:

let foo = {
 a: 1,
 b: 2
}
let handler = {
    get:(obj,key)=>{
        console.log('get')
        return key in obj ? obj[key] : undefined
    }
}
let p = new Proxy(foo,handler)
console.log(p.a) // 1

上面代码中p就是foo的代理对象,对p对象的相关操作都会同步到foo对象上。

同时Proxy也提供了另一种生成代理对象的方法Proxy.revocable()

const { proxy,revoke } = Proxy.revocable(target, handler)

该方法的返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke},其中:

  • proxy:表示新生成的代理对象本身,和用一般方式new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。
  • revoke:撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。

例如:

let foo = {
 a: 1,
 b: 2
}
let handler = {
    get:(obj,key)=>{
        console.log('get')
        return key in obj ? obj[key] : undefined
    }
}
let { proxy,revoke } = Proxy.revocable(foo,handler)

console.log(proxy.a) // 1

revoke()

console.log(proxy.a) // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

需要注意的是,一旦某个代理对象被撤销,它将变得几乎完全不可调用,在它身上执行任何的可代理操作都会抛出 TypeError 异常。

Proxy的handler

上面代码中,我们只使用了get操作的handler,即当尝试获取对象的某个属性时会进入这个方法,除此之外Proxy共有接近14个handler也可以称作为钩子,它们分别是:

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 运算符,比如在执行 delete proxy.foo 时。

handler.ownKeys():
当执行Object.getOwnPropertyNames(proxy) 和Object.getOwnPropertySymbols(proxy)时触发。

handler.apply():
当代理对象是一个function函数时,调用apply()方法时触发,比如proxy.apply()。

handler.construct():
当代理对象是一个function函数时,通过new关键字实例化时触发,比如new proxy()。

结合这些handler,我们可以实现一些针对对象的限制操作,例如:

  • 禁止删除和修改对象的某个属性
let foo = {
    a:1,
    b:2
}
let handler = {
    set:(obj,key,value,receiver)=>{
        console.log('set')
        if (key == 'a') throw new Error('can not change property:'+key)
        obj[key] = value
        return true
    },
    deleteProperty:(obj,key)=>{
        console.log('delete')
        if (key == 'a') throw new Error('can not delete property:'+key)
        delete obj[key]
        return true
    }
}

let p = new Proxy(foo,handler)

p.a = 3 // Uncaught Error

delete p.a  // Uncaught Error

其中,set方法的receiver通常是 Proxy 本即 p,但是当有一段代码执行 obj.name = "jen", obj 不是一个 proxy,且自身不含 name 属性,但是它的原型链上有一个 proxy,那么,那个 proxy 的handler里的set方法会被调用,而此时obj 会作为 receiver 这个参数传进来。

  • 对属性的修改进行校验
let foo = {
    a:1,
    b:2
}
let handler = {
    set:(obj,key,value)=>{
        console.log('set')
        if (typeof(value) !== 'number') throw new Error('can not change property:'+key)
        obj[key] = value
        return true
    }
}

let p = new Proxy(foo,handler)

p.a = 'hello' // Uncaught Error

Proxy和响应式对象reactive

Vue3中的响应式对象:

import {ref,reactive} from 'vue'
...
setup(){
  const name = ref('test')
  const state = reactive({
    list: []
  })
  return {
      name,
      state
  }
}
...

在Vue3中,composition-api提供了一种创建响应式对象的方法reactive,其内部就是利用了Proxy API来实现的,特别是借助handler的set方法,可以实现双向数据绑定相关的逻辑,这对于Vue2.x中的Object.defineProperty()是很大的改变。

  • Object.defineProperty()只能单一的监听已有属性的修改或者变化,无法检测到对象属性的新增或删除,而Proxy则可以轻松实现。
  • Object.defineProperty()无法监听属性值是数组类型的变化,而Proxy则可以轻松实现。

例如监听数组的变化:

let arr = [1]
let handler = {
    set:(obj,key,value)=>{
        console.log('set')
        return Reflect.set(obj, key, value);
    }
}

let p = new Proxy(arr,handler)
p.push(2)

Reflect.set()用于修改数组的值,参考Reflect,但是对于多层对象嵌套问题,需要经过一定的处理:

let foo = {
    a:1,
    b:2
}
let handler = {
    set:(obj,key,value)=>{
        console.log('set')
        // 双向绑定相关逻辑
        obj[key] = value
        return true
    }
}

let p = new Proxy(foo,handler)

p.a = 3

上面代码中,对于简单的对象foo是完全没问题的,但是如果foo是一个复杂对象,里面嵌套的很多对象,那么当去尝试修改里层对象的值时,set方法就不会触发,为了解决这种场景,在Vue3中,采用了递归的方式来解决这个问题:

let foo = {a:{c:3,d:{e:4}},b:2}
const isObject = (val)=>{
    return val !== null && typeof val === 'object'
}
const createProxy = (target)=>{
    let p = new Proxy(target,{
        get:(obj,key)=>{
            let res = obj[key] ? obj[key] : undefined

            // 判断类型,避免死循环
            if (isObject(res)) {
                return createProxy(res)
            } else {
                return res
            }
        },
        set: (obj, key, value)=> {
          console.log('set')
          obj[key] = value;

        }
    })

    return p
}

let result = createProxy(foo)

result.a.d.e = 6 // 打印出set

当尝试去修改一个多层嵌套的对象的属性时,会触发该属性的上一级对象的get方法,利用这个就可以对每个层级的对象添加Proxy代理,这样就实现了多层嵌套对象的属性修改问题。

当然,上面这段代码只是Vue3中reactive的一个缩影,更多的细节可以浏览相关源码来了解。

就目前来看,Porxy API相关内容是从ES2015才引入的标准,并且业界相关的polyfill也不是很完善,所以使用此API相关的框架要慎重的考虑兼容性问题。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39927683/article/details/110103766

智能推荐

SAR辐射校正_sar图像辐射校正-程序员宅基地

文章浏览阅读2.9k次。1)PPT: SAR数据基本处理技术: https://wenku.baidu.com/view/703635ca69dc5022abea0043.html2)高分辨率 SAR 定标参考目标辐射特性的校正方法.pdf http://xueshu.baidu.com/s?wd=paperuri%3A%2855f5104a42025ce12b39f33b90536e88%29&fil..._sar图像辐射校正

黑马程序员——OC基础学习(五)---分类(Category),代码块Block和protocol代理设计模式_java partial oc category-程序员宅基地

文章浏览阅读727次。分类(Category),代码块Block和protocol代理设计模式知识总结_java partial oc category

Check Point R80.10 SmartConsole汉化&生成中文报表_checkpoint smartconsole-程序员宅基地

文章浏览阅读4k次,点赞2次,收藏7次。一.概述为了应对国产化的需求,CheckPoint从R80.10开始,支持对SmartConsole界面的汉化,并且可以导出中文报表。下面我们将以实际效果展示汉化的结果供参考。此汉化,需要下载一个SmartConsole的补丁程序:LanguageConfigPatch。下载此程序请查看sk112276二.SmartConsole汉化解压LanguageConfigPatch,copy解压的文件到..._checkpoint smartconsole

Apache Solr 远程命令执行漏洞(CVE-2019-0193)-程序员宅基地

文章浏览阅读1.5k次。Apache Solr 远程命令执行漏洞(CVE-2019-0193)声明:本篇文章仅限用于学习信息安全技术,请勿用于非法途径!1.漏洞描述2019年8月1日,Apache Solr官方发布了CVE-2019-0193漏洞预警,漏洞危害评级为严重。当solr开启了DataImportHandler功能,该模块中的DIH配置都可以通过外部请求dataconfig参数进行修改,DIH可包含..._cve-2019-0193

探秘Google DataLab:数据科学与机器学习的强大工具-程序员宅基地

文章浏览阅读487次,点赞8次,收藏8次。探秘Google DataLab:数据科学与机器学习的强大工具项目地址:https://gitcode.com/googledatalab/datalab项目简介Google DataLab 是一个开源项目,由Google提供,它为数据科学家和工程师提供了统一的工作环境,用于探索、分析和可视化数据,并构建和部署机器学习模型。DataLab基于Jupyter Notebook,结合了Googl...

算法和数据结构(Python)——区间合并_labuladong。合并区间-程序员宅基地

文章浏览阅读1.3k次。适用情况当你需要产生一堆相互之间没有交集的区间的时候当你听到重叠区间的时候解决思路:把每个区间按start排序,区间起始点为最小的start循环判断两个区间是否重叠(对于很多元素的对比,化简的思路是先只看两个元素怎么比较,然后循环迭代)重叠则找max end;不重叠则加入一个新区间元素抽象模式intervals.sort(<排序>)for <进入循环&g..._labuladong。合并区间

随便推点

ADB的使用-程序员宅基地

文章浏览阅读83次。本质其实就是一个加强版的串口工具(这样理解大差不差)。adb命令其实对应一个server,在板子上有一个adb的守护进程。晚上大概试用了一下,就只是一个工具,真的是很乏善可陈。除此之外就是命令行工具,此外好像没啥了额。3 支持Android特色,可以安装apk。2 集成了ftp的功能,可以上传文件。1 支持网络,可以多客户端。

window10 下自带 ubuntu 系统git clone 出现 “Failed to connect to github.com port 443: Connection refused” 问题_ubuntu16.04 git clone时443: connection refused-程序员宅基地

文章浏览阅读1.2k次。window10 系统下的ubuntu 系统 git clone 出现Failed to connect to github.com port 443: Connection refused 问题fatal: unable to access 'https://github.com/kaldi-asr/kaldi.git/': Failed to connect to github.com port 443: Connection refused不论打开代理软件还是关掉代理软件都无法Git._ubuntu16.04 git clone时443: connection refused

gradle Received status code 403 from server: Forbidden-程序员宅基地

文章浏览阅读1.1k次。android studio同步nexus/maven仓库,因为代理报Received status code 403 from server: Forbidden错误_received status code 403 from server: forbidden

【解决】virtualbox导入虚拟机后不能联网的问题_vmware ova 导入virtualbox 连不上网-程序员宅基地

文章浏览阅读5.4k次,点赞3次,收藏7次。当你把导出的虚拟机文件(.ova)导入到virtualbox中,发现该虚拟机不能联网,不管在【设置->网络】中切换什么样的网络类型,都无济于事。下面给出解决方法,在看解决方法之前必须保证.ova文件在被导出之前可以正常上网。解决方法: 1、打开文件 vi /etc/udev/rules.d/70-persistent-net.rules 找到eth1对应的mac地址,如:08:00:27:8d_vmware ova 导入virtualbox 连不上网

浅析那些带着“主角光环“的泰坦尼克号幸存者-程序员宅基地

文章浏览阅读1.2k次。作者简介Introduction邬书豪,车联网数据挖掘工程师 ,R语言中文社区专栏作者。微信ID:tsaiedu知乎专栏:https://www.zhihu.com/people/wu-shu-hao-67/activities往期回顾kaggle:数据科学社区调查报告(附学习视频)kaggle:员工离职预测(附学习视频)Kaggle:纽约的士旅程数据简要分析Kaggle:R可视化分析美国枪击案(_泰坦尼克号幸存者数据挖掘

java模板引擎:velocity_velocity依赖java-程序员宅基地

文章浏览阅读2.4k次。在java中,同类型的模块代码结构往往是相似的。以Spring为例,一个工程都会包含controller,service,dao,modal等类型的代码。而dao用于数据库的访问,不同的表所对应的dao类都会有get,set,select,delete等操作,若手动编写dao,则这些dao有很多代码是类似的。于是希望自动生成这部分代码。一个非常直观的思路就是:写一个String模板,然后使用字符..._velocity依赖java

推荐文章

热门文章

相关标签