Vue 响应式原理剖析 - 数据更新常见问题

更新日期: 2022-03-15阅读: 963标签: 响应式

对象数据的某些修改无法被检听?

如下的一个场景, obj.message 赋值时能否被监听响应呢?

var vm = new vue({
    data: {
        obj: {
            a: 1
        },
    },
    template: '<div>{{ obj.message }}</div>'
});

vm.obj.message = 'modified';

答案是不能被监听的。原因:对象属性的添加和删除无法被 Object.defineProperty 监听,正如前文所述,Vue 的数据响应式基于 Object.defineProperty 实现,因此也受限。

解决办法:Vue 提供了特定的方法 vm.$set(obj, propertyName, newValue) 来处理这种情况,至于该方法的具体逻辑,后面会详细展开说明。


数组数据的某些修改无法被监听?

如下的一个场景,三个赋值语句里面,哪个能被监听响应呢?

const vm = new Vue({
    data: {
        items: [1, 2, 3, 4, 5],
    },
});
vm.items[1] = 8;
vm.items[5] = 6;
vm.items.length = 2;

答案是三个操作都不能被监听到。原因:

  • 第二个操作 vm.items[5] = 6 应该是比较明显的,与上面对象添加和删除属性类似,数组新添加的元素和删除元素无法被 Object.defineProperty 监听。
  • 第三个操作 vm.items.length = 2 也是由于 Object.defineProperty 的限制,数组的长度直接修改也无法被监听。
  • 最容易误判的可能是第一个操作 vm.items[1] = 8 ,一种较为常规的说法是 Object.defineProperty 不支持监听数组元素的变化,要验证这个说法可以直接用一个例子说明真实的情况。

如下的一个例子:

function definereactive(obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            console.log('读取 index ' + key, '当前值是 ' + val)
            return val;
        },
        set(newVal) {
            if (val === newVal) {
                return;
            }
            val = newVal
            console.log('修改 index ' + key, '新值是 ' + val)
        }
    })
}

const testArray = [1, 2, 3, 4, 5]

testArray.forEach((c, index) => {
    defineReactive(testArray, index, c)
});

testArray[0] = 100;
testArray[5] = 600;

可以看到, 超出范围的数组元素操作 Object.defineProperty 确实无法监听,但范围内的元素重新赋值是可以被监听的。 那么为什么在 Vue 中对数组类型的 data ,直接使用下标赋值无法被监听呢?

答案是出于性能考虑,从上面的基础例子中可以看到, 对象和数组如果需要监听每个属性和元素,实际上是对每个属性或者元素进行 Object.defineProperty 劫持,对象是监听 key 而数组则是以数字下标作为 key ,数组的数据量可能会很大,因此 Vue 出于性能考虑,并没有对元素下标进行响应式处理。 作为补充,Vue 对数组原型链上的几个方法进行劫持,对于会导致元素新增的3个方法 push 、 pop 、 unshift 会在内部获取新增的元素,执行响应式的处理:

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    ob.dep.notify()
    return result
  })
})

与对象类似,如果需要为数字元素重新赋值,可以使用 vm.$set(arr, indexOfItem, newValue) 方法,这里展示一下 $set 的具体实现:

// 精简了非 production 逻辑
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

主要的逻辑包括以下操作:

  1. 这个方法同时用于对象和数组,因此第一步会检验传入的 target 是否为数组,并且传入的 key (即数组的数字下标)是否符合数组的长度范围(如上面所述, Object.defineProperty 不支持劫持新添加的元素),符合的元素会调用 splice 插入到数组中,由于 splice 已经被劫持,新增加的元素会进行「响应式」处理。
  2. 判断如果 key 原先已存在,则无需再监听。
  3. 判断是根节点 Vue,即最外层的 Vue,或者已经有 __ob__ 属性(表示已经进行响应式处理,详情可以浏览前文),则无需再进行监听。
  4. 如果不符合前面的条件,则表明该属性需要执行「响应式」处理,会调用 defineReactive 方法(响应式数据封装的入口方法,详情可以浏览前文)。
原文 https://kayosite.com/vue-reactivity-common-problems.html

链接: https://fly63.com/article/detial/11267

移动端web app要使用rem实现自适应布局:font-size的响应式

rem是相对于根元素html,这样就意味着,我们只需要在根元素确定一个px字号,则可以来算出元素的宽高。

使用现代CSS的响应式版面

通过模块化缩放,使用传统属性和calc()来动态缩放你的字体大小.为字体大小使用百分比.给文本内容和媒体查询使用em,针对不同视口尺寸使用不同缩放值.视口越小,缩放比例越小,使用媒体查询或者media()函数基于视口来改变比例和基础字号

web响应式图片的5种实现

在目前的前端开发中,我们经常需要进行响应式的网站开发。本文着重介绍一下弹性图片,也就是响应式图片的解决方案:js或服务端、srcset 、sizes 、picture标签、svg图片

HTML5+CSS3响应式垂直时间轴,高端,大气

HTML5+CSS3响应式垂直时间轴,使用了HTML5标签<section>,时间轴中所有的内容包括标题、简介、时间和图像都放在.cd-timeline-block的DIV中,多个DIV形成一个序列,并把这些DIV放在<section>中。

实现响应式_CSS变量

CSS 变量是 CSS 引入的一个新特性,目前绝大多数浏览器已经支持了,它可以帮助我们用更少的代码写出同样多的样式,大大提高了工作效率,本篇文章将教你如何使用 CSS 变量(css variable)。CSS中原生的变量定义语法是:--*,变量使用语法是:var(--*),其中*表示变量名称

vue响应式原理及依赖收集

Vue通过设定对象属性的setter/getter方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

vue响应式系统--observe、watcher、dep

Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的JavaScript 对象,而当你修改它们时,视图会进行更新,这使得状态管理非常简单直接,我们可以只关注数据本身

Responsive Web Design 响应式网页设计

常见的布局方案:固定布局:以像素作为页面的基本单位,不管设备屏幕及浏览器宽度,只设计一套尺寸;可切换的固定布局:同样以像素作为页面单位,参考主流设备尺寸

响应式布局的实现

响应式布局,即 Responsive design,在实现不同屏幕分辨率的终端上浏览网页的不同展示方式。通过响应式设计能使网站在手机和平板电脑上有更好的浏览阅读体验。响应式布局的关键不仅仅在于布局

深入响应式原理

说到响应式原理其实就是双向绑定的实现,说到 双向绑定 其实有两个操作,数据变化修改dom,input等文本框修改值的时候修改数据1. 数据变化 -> 修改dom;2. 通过表单修改value -> 修改数据

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!