首页 > 其他分享 >Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?

Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?

时间:2024-03-05 21:14:54浏览次数:20  
标签:API obj target res return Vue3.0 Proxy key const

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

 

一、Object.defineProperty

定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

为什么能实现响应式

通过defineProperty 两个属性,getset

  • get

属性的 getter 函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值

  • set

属性的 setter 函数,当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined

下面通过代码展示:

定义一个响应式函数defineReactive

function update() {
    app.innerText = obj.foo
}

function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
        get() {
            console.log(`get ${key}:${val}`);
            return val
        },
        set(newVal) {
            if (newVal !== val) {
                val = newVal
                update()
            }
        }
    })
}

调用defineReactive,数据发生变化触发update方法,实现数据响应式

const obj = {}
defineReactive(obj, 'foo', '')
setTimeout(()=>{
    obj.foo = new Date().toLocaleTimeString()
},1000)

在对象存在多个key情况下,需要进行遍历

function observe(obj) {
    if (typeof obj !== 'object' || obj == null) {
        return
    }
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key])
    })
}

如果存在嵌套对象的情况,还需要在defineReactive中进行递归

function defineReactive(obj, key, val) {
    observe(val)
    Object.defineProperty(obj, key, {
        get() {
            console.log(`get ${key}:${val}`);
            return val
        },
        set(newVal) {
            if (newVal !== val) {
                val = newVal
                update()
            }
        }
    })
}

当给key赋值为对象的时候,还需要在set属性中进行递归

set(newVal) {
    if (newVal !== val) {
        observe(newVal) // 新值是对象的情况
        notifyUpdate()
    }
}

上述例子能够实现对一个对象的基本响应式,但仍然存在诸多问题

现在对一个对象进行删除与添加属性操作,无法劫持到

const obj = {
    foo: "foo",
    bar: "bar"
}
observe(obj)
delete obj.foo // no ok
obj.jar = 'xxx' // no ok

当我们对一个数组进行监听的时候,并不那么好使了

const arrData = [1,2,3,4,5];
arrData.forEach((val,index)=>{
    defineProperty(arrData,index,val)
})
arrData.push() // no ok
arrData.pop()  // no ok
arrDate[0] = 99 // ok

可以看到数据的api无法劫持到,从而无法实现数据响应式,

所以在Vue2中,增加了setdelete API,并且对数组api方法进行一个重写

还有一个问题则是,如果存在深层的嵌套对象关系,需要深层的进行监听,造成了性能的极大问题

小结

  • 检测不到对象属性的添加和删除
  • 数组API方法无法监听到
  • 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题

二、proxy

Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了

ES6系列中,我们详细讲解过Proxy的使用,就不再述说了

下面通过代码进行展示:

定义一个响应式方法reactive

function reactive(obj) {
    if (typeof obj !== 'object' && obj != null) {
        return obj
    }
    // Proxy相当于在对象外层加拦截
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            const res = Reflect.get(target, key, receiver)
            console.log(`获取${key}:${res}`)
            return res
        },
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
            console.log(`设置${key}:${value}`)
            return res
        },
        deleteProperty(target, key) {
            const res = Reflect.deleteProperty(target, key)
            console.log(`删除${key}:${res}`)
            return res
        }
    })
    return observed
}

测试一下简单数据的操作,发现都能劫持

const state = reactive({
    foo: 'foo'
})
// 1.获取
state.foo // ok
// 2.设置已存在属性
state.foo = 'fooooooo' // ok
// 3.设置不存在属性
state.dong = 'dong' // ok
// 4.删除属性
delete state.dong // ok

再测试嵌套对象情况,这时候发现就不那么 OK 了

const state = reactive({
    bar: { a: 1 }
})

// 设置嵌套对象属性
state.bar.a = 10 // no ok

如果要解决,需要在get之上再进行一层代理

function reactive(obj) {
    if (typeof obj !== 'object' && obj != null) {
        return obj
    }
    // Proxy相当于在对象外层加拦截
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            const res = Reflect.get(target, key, receiver)
            console.log(`获取${key}:${res}`)
            return isObject(res) ? reactive(res) : res
        },
    return observed
}

三、总结

Object.defineProperty只能遍历对象属性进行劫持

function observe(obj) {
    if (typeof obj !== 'object' || obj == null) {
        return
    }
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key])
    })
}

Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的

function reactive(obj) {
    if (typeof obj !== 'object' && obj != null) {
        return obj
    }
    // Proxy相当于在对象外层加拦截
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            const res = Reflect.get(target, key, receiver)
            console.log(`获取${key}:${res}`)
            return res
        },
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
            console.log(`设置${key}:${value}`)
            return res
        },
        deleteProperty(target, key) {
            const res = Reflect.deleteProperty(target, key)
            console.log(`删除${key}:${res}`)
            return res
        }
    })
    return observed
}

Proxy可以直接监听数组的变化(pushshiftsplice

const obj = [1,2,3]
const proxtObj = reactive(obj)
obj.psuh(4) // ok

Proxy有多达13种拦截方法,不限于applyownKeysdeletePropertyhas等等,这是Object.defineProperty不具备的

正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外setdelete方法)

// 数组重写
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {
  arrayProto[method] = function () {
    originalProto[method].apply(this.arguments)
    dep.notice()
  }
});

// set、delete
Vue.set(obj,'bar','newbar')
Vue.delete(obj),'bar')

Proxy 不兼容IE,也没有 polyfilldefineProperty 能支持到IE9

参考文献

  • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

标签:API,obj,target,res,return,Vue3.0,Proxy,key,const
From: https://www.cnblogs.com/smileZAZ/p/18054957

相关文章

  • Proxy 的 性 能 可 能 比 defineProperty 更 差
    老有人跑来跟我说Proxy和defineProperty相比,是性能的巨大提升。我一听,这不对劲啊,跟我学的知识不太一样,我明明记得Proxy性能比defineProperty更差。所以我就写了几个简单的例子来验证一下。这个例子的逻辑非常简单,我们在大数据量循环的过程中,分别用 Object.definePropert......
  • C# WinForm基于owin创建WebApi
    在实际的项目开发中,可能会有在WinForm程序中提供Web服务器的需求。通过owin可以很方便的实现,并且可提供Web静态文件访问服务。操作方法:1.在NuGet引用owinMicrosoft.AspNet.WebApi.OwinMicrosoft.AspNet.WebApi.OwinSelfHostMicrosoft.Owin.StaticFiles2.添加服务启动配置类 ......
  • 变量$host、$http_host、$proxy_host区别
    //如果想让Host是crmtest.aty.sohuno.com,则进行如下设置:proxy_set_headerHostcrmtest.aty.sohuno.com;//如果不想改变请求头“Host”的值,可以这样来设置:proxy_set_headerHost$http_host;//但是,如果客户端请求头中没有携带这个头部,那么传递到后端服务器的请求也不含这个头......
  • 使用go写的一个api接口
    记录一下使用go写的一些脚本packagemainimport( "encoding/json" "fmt" "log" "net/http" "os" "os/exec" "strconv" "strings" "sync" "time")var( requestCo......
  • resurfaceio graylog 的api 安全方案
    resurfaceio是graylog的api安全方案,包含的特性特性简易的api调用捕捉立即攻击以及异常的rest以及graphqlapi处理基于webhook,sql查询,以及数据导出自动化处理快速部署本地或者基于k8s的云环境架构设计resurfaceio对于流量的处理基于了goreplay扩展参考网络流量......
  • Kubelet安装时子节点出现:kube-proxy-7jxg4 ContainerCreating
    一般分为两种情况主节点问题和kube-proxy问题:1、查看报错:kubectldescribepodkube-proxy-7jxg4-nkube-system2、子节点查看相关报错信息journalctl-ukubelet-f可以看出是创建容器失败:1、可能是docker镜像没有导入2、网络问题,重启看一下cri-docker是否有报错信息......
  • 阿里巴巴/1688 api接口 获取商品详情 数据采集
    iDataRiver平台https://www.idatariver.com/zh-cn/提供开箱即用的阿里巴巴1688电商数据采集API,供用户按需调用。接口使用详情请参考阿里巴巴1688接口文档接口列表1.获取商品详情参数类型是否必填默认值示例值描述apikeystring是idr_***从控制台里复制api......
  • 3-1低阶API示范
    下面的范例使用Pytorch的低阶API实现线性回归和DNN二分类低阶API主要包括张量操作,计算图和自动微分importosimportdatetime#打印时间defprintbar():nowtime=datetime.datetime.now().strftime('%Y-%m-%d%H:%M:%S')print('\n'+'========='*8+'%s'......
  • 37vector容器与API
    vector容器与API#include<iostream>#include<vector>usingnamespacestd;/*vector容器:向量容器底层数据结构:动态开辟的数组,每次以原来空间2倍进行扩容vector<int>vec;增加:vec.push_back(20);末尾添加元素O(1)导致容器扩容vec.insert(it,20);it迭代器指向的位......
  • 38deque, list及其API
    deque,list及其APIdeque:双端队列容器。底层数据结构:动态开辟的二维数组,一维数组是指针数组,长度从2开始,以2倍的方式进行扩容,每次扩容后,原来第二维的数组,从新的第一维数组的下标oldsize/2开始存放,上下都预留相同的空行,方便支持deque的首尾元素添加。deque<int>deq;......