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

一、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

Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?的更多相关文章

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

    响应式优化. a. defineProperty API 的局限性最大原因是它只能针对单例属性做监听. Vue2.x 中的响应式实现正是基于 defineProperty 中的 descriptor, ...

  2. vue3.0里的生命周期函数

  3. Vue3.0响应式原理

    Vue3.0的响应式基于Proxy实现.具体代码如下: 1 let targetMap = new WeakMap() 2 let effectStack = [] //存储副作用 3 4 const ...

  4. Vue3.0新版API之composition-api入坑指南

    关于VUE3.0 由于vue3.0语法跟vue2.x的语法几乎是完全兼容的,本文主要介绍了如何使用composition-api,主要分以下几个方面来讲 使用vite体验vue3.0 composit ...

  5. vue3.0 的 Composition API 的一种使用方法

    网上讨论的文章已经很多了,这里举一个简单的例子来讨论一下 Composition API 的用法,具体问题才好具体讨论嘛. 假如我们要做一个论坛的讨论列表和分页,以前是把需要的数据都放在data里面, ...

  6. Vue3.0 响应式数据原理:ES6 Proxy

    Vue3.0 开始用 Proxy 代替 Object.defineProperty了,这篇文章结合实例教你如何使用Proxy 本篇文章同时收录[前端知识点]中,链接直达 阅读本文您将收获 JavaSc ...

  7. vue3.0 composition API

    一.Setup函数 1.创建时间:组件创建之前被调用,优先与created被调用,this指向的实例为window,created所指向的实例为proxy 2.this指向:不会指向组件实例 3.参数 ...

  8. 基于 Vue3.0 Composition Api 快速构建实战项目

    Quick Start 项目源码:https://github.com/Wscats/vue-cli 本项目综合运用了 Vue3.0 的新特性,适合新手学习

  9. 预计2019年发布的Vue3.0到底有什么不一样的地方?

    摘要: Vue 3.0预览. 原文:预计今年发布的Vue3.0到底有什么不一样的地方? 作者:小肆 微信公众号:技术放肆聊 Fundebug经授权转载,版权归原作者所有. 还有几个月距离 vue2 的 ...

  10. vue3.0和2.0的区别,Vue-cli3.0于 8月11日正式发布,更快、更小、更易维护、更易于原生、让开发者更轻松

    vue3.0和2.0的区别Vue-cli3.0于 8月11日正式发布,看了下评论,兼容性不是很好,命令有不少变化,不是特别的乐观vue3.0 的发布与 vue2.0 相比,优势主要体现在:更快.更小. ...

随机推荐

  1. JS 前序遍历、中序遍历、后序遍历、层序遍历详解,深度优先与广度优先区别,附leetcode例题题解答案

    壹 ❀ 引 按照一天一题的速度,不知不觉已经刷了快两多月的leetcode了,因为本人较为笨拙,一道简单的题有时候也会研究很久,看着提交了两百多次,其实也才解决了70来道简单题,对于二分法,双指针等也 ...

  2. Laravel入坑指南(3)——模板

    各位小伙伴有缘聚到这里,说明对于Laravel的路由和控制器已经有点了解了. 会写业务逻辑之后,如何把结果漂亮地展示出来,就是我们要解决的问题.(前后端分离的同学,请自动忽略)在MVC的世界里,漂亮的 ...

  3. 将字符串"a,b,c"以逗号分隔转换为数组并打印

    主要利用了String的split方法. package com.dylan.test; /** * @author xusucheng * @create 2017-12-22 **/ public ...

  4. vmware之NAT模式配置

    ​ 题外话之前的题外话,本文迁移自别的社区,三年前大学实习时写下本文,过了几年再回过头来看,虽然讲得浅显,作为入门笔记也勉强合格. ---------------------------------- ...

  5. win32 - WaitForMultipleObjects的使用

    创建5个线程,并无限期地打印某些内容 #include <Windows.h> #include <stdio.h> DWORD IDs[5]; DWORD WINAPI Th ...

  6. 走进Kaggle的未知领域:性别和年龄推断算法解析

    ​ 1.环境设置: 此环节将加载实现笔记本无缝功能的基本模块,包括NumPy.Pandas和TensorFlow等库.此外,它还建立了关键的环境常数,如图像尺寸和学习率,这对后续分析和模型训练至关重要 ...

  7. channel管道

    channel 如果说goroutine是并发体的话,那么channels则是他们之间的通信机制.一个channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发生值 ...

  8. django1.11和django2.2中namespace的用法

    django1.11中namespace用法 urlpatterns = [ url(r'^user/', include('user.urls', namespace='user')) ] djan ...

  9. Java面向对象之接口和抽象类的区别一目了然

    介绍 相信对于Java面向对象部分,很多人很长一段时间对于接口和抽象类的区别,使用场景都不是很熟悉,同是作为抽象层重要的对象,工作中到底什么情况下使用抽象类,不是很清楚.本文就一次性把这些概念一次性说 ...

  10. Apifox:成熟的测试工具要学会自己写接口文档

    好家伙, 在开发过程中,我们总是避免不了进行接口的测试, 而相比手动敲测试代码,使用测试工具进行测试更为便捷,高效 今天发现了一个非常好用的接口测试工具Apifox 相比于Postman,他还拥有一个 ...