写点什么

Vue 数据响应 Object.defineProperty

作者:空城机
  • 2022 年 5 月 17 日
  • 本文字数:2076 字

    阅读完需:约 7 分钟

Vue数据响应Object.defineProperty

Vue 数据响应

在网页开发中,一般数据想要渲染到页面中,需要依赖于操作 DOM 更新。 VueReact则是使用数据驱动视图,也就是数据改变,DOM 也相应完成变化。


而数据变化更新 DOM 也分为侵入式和非侵入式

侵入式和非侵入式

Vue属于非侵入式,React小程序数据变化输入侵入式。

侵入式设计,就是设计者将框架功能“推”给客户端;


而非侵入式设计,则是设计者将客户端的功能“拿”到框架中用


侵入式设计带来的最大缺陷是,代码需要依赖框架的代码,如果把框架拿掉或者换一个框架,就需要重新修改代码


下面的例子中,vue改变 a 的值没有调用其他的 API,而react小程序则调用了setStatesetData的 API


Vue:

this.a++;
复制代码


React:

this.setState({    a: this.state.a + 1});
复制代码


小程序:

this.setData({    a: this.state.a + 1});
复制代码

Object.defineProperty() 数据代理

当然,本文的重点在于 Object.defineProperty


在上面说到,在React和小程序当中,因为调用 API 方法改变数值,所以界面改变也很好理解,API 方法对应会改变 DOM。


那么Vue改变数值时是非侵入式,那么界面应该响应数值改变而改变?


在这里就需要提到Object.defineProperty数据代理了,MDN 地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty


这是一个 JavaScript 引擎的 API,可以来检测对象属性的变化


在 vue3 中使用 proxy 代替defineProperty,但是 proxy 具有兼容性问题,且无法 polyfill (基本上 IE 浏览器可以放弃)

参考: https://caniuse.com/?search=proxy


之前在《前端面试中有趣的题目》 中也有用到过该方法去定义一个 const


Object.defineProperty中具有 get 和 set 方法,当 Vue 去改变 a 值时,a 属性已经被 set 给劫持了。(get 和 set 都需要变量进行周转)


例子: 在下面会看到例子当中 obj 对象存在一个_a属性,这是为了对应之后设置agetset,现在修改obj.a的数值,也能够打印console中的语句。如果在set方法中添加updateView更新视图方法,那么就能够简单的达到了数据同步的效果

let obj = {    _a: 0}
Object.defineProperty(obj, 'a', { get() { return this._a; }, set(n) { console.log('设置a属性的值' + n); this._a = n; // 触发更新视图 //updateView(); }})
obj.a = 0; // 设置a属性的值0obj.a++; // 设置a属性的值1obj.a++; // 设置a属性的值2obj.a += 10; // 设置a属性的值12console.log(obj.a); // 12
复制代码

封装一个 defineProperty 方法

先定义一个等一下要测试的数据

let personInfo = {    name: 'zhangsan',    age: 20,    info: {        address: '江苏'    }}
复制代码


在上面的基础上,可以对其进行封装

function defineProperty(object, key, data) {    if (!data) {        data = object[key]    }    Object.defineProperty(object, key, {        get() {            return data;        },        set(val) {            if (data != val) {                console.log(`${key}的原值${data}被更新为:${val}`)                data = val;            }        }    })}
复制代码


对其中的name属性调用方法进行代理,能够在控制台看到name的原值zhangsan被更新为:lisi被打印

defineProperty(personInfo, 'name');personInfo.name = 'lisi'  // name的原值zhangsan被更新为:lisi
复制代码




并且在此封装好的基础上,可以再写一个方法,对对象进行分析,遍历对象各个属性放入方法代理

function observe(obj) {    if (typeof obj != 'object') {        return ;    }     for(let key in obj) {        observeReactive(obj, key);    }}
复制代码


这样一来层次不深的属性基本已经被 defineProperty 代理了

observe(personInfo);personInfo.age = 30;  // age的原值20被更新为:30
复制代码


不过如果是内部还有层次的就不行了,比如下面的例子,就没有打印出来

personInfo.info.address = '浙江';
复制代码


那么就对属性再做一次判断,如果还是对象,继续遍历

function defineProperty(object, key, data) {    if (!data) {        data = object[key]    }    // 递归    if (typeof data == 'object') {        observe(data);    }    Object.defineProperty(object, key, {        get() {            return data;        },        set(val) {            if (data != val) {                console.log(`${key}的原值${data}被更新为:${val}`)                data = val;                // 新增加对象也要observe                if (typeof val == 'object') {                    observe(data);                }            }        }    })}
复制代码


这样一来address的原值江苏被更新为:浙江就能够被打印出来了



Object.defineProperty 缺点

Object.defineProperty还是有挺多不足的


  • 比如上面说到的深度监听,需要递归到底,一次性的计算量很大。

  • 无法监听新增属性/删除属性 (所以在 vue 中会有Vue.setVue.delete)

  • 数据劫持并不能对数组的pushshift等方法生效生效,无法原生监听数组,需要特殊处理

发布于: 刚刚阅读数: 3
用户头像

空城机

关注

曾经沧海难为水,只是当时已惘然 2021.03.22 加入

业余作者,在线水文 主要干前端的活,业余会学学python 欢迎各位关注,互相学习,互相进步

评论

发布
暂无评论
Vue数据响应Object.defineProperty_vue.js_空城机_InfoQ写作社区