为什么在vue2中改变数据视图不会更新,带你阅读源码
创始人
2024-03-21 14:02:01

1. 监听数组变化

  • 其实 Vue 监听数组变化的原理非常简单, 就是将数组的主要方法包裹了一遍
  • 只要用户调用以下方法, 就会通知 Watcher 自动更新视图:
    • push()
    • pop()
    • shift()
    • unshift()
    • splice()
    • sort()
    • reverse()

演示

工程源码: src/core/observer/array.js

// 获取数组的原型 Array.prototype, 为了方便拿到数组原有的方法
const arrayProto = Array.prototype// 创建一个空对象 arrayMethods, 并将 arrayMethods 的原型指向 Array.prototype
export const arrayMethods = Object.create(arrayProto)// 列出需要重写的方法
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse'
]// 遍历列出的方法
methodsToPatch.forEach(function (method) {// 找到原来的函数体const original = arrayProto[method]// def 就是 defineProperty() 可以参见 util/lang.jsdef(arrayMethods, method, function mutator (...args) {// 调用原来的函数const result = original.apply(this, args)// 该数组是响应式的时候, 上面会有一个 __ob__ 属性const ob = this.__ob__let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}// 判断如果添加的元素是对象或数组, 这些处理不重要if (inserted) ob.observeArray(inserted)// 重点在这里: 每个响应式数组上都会有一个 __ob__ 利用我们保留的 __ob__ 属性获取 notify 方法来通知 Watcher 更新视图ob.dep.notify()return result})
})

在这里插入图片描述
**Object.create()**方法用于创建一个对象,使现有的对象作为新创建对象的原型(prototype)

总结

  • Vue 之所以能监听到数组的变化, 是因为把数组中常用的方法重新包装了一层
  • 所谓的包装就是又写了个对象, 这个对象里拥有 Array 原型中的方法, 内部也是调用的 Array 原型中的方法, 额外多调用了一下 notify() 通知 Watcher 更新视图, 最后将原型也指向 Array 的原型, 形成了一种继承关系
  • 拓展: 这种对方法的包装, 我们也称为方法的重写
  • Vue 通过对方法的重写实现了数组修改而通知视图变化, 程序员们无需学习任何新的知识, 中间的过程是完全无感的, 这种设计理念也被称为非侵入式的响应式原理, 与之对应的就是侵入式的响应式设计理念, 如: 小程序 / react
    • 小程序: setData()
    • react: setState()

2.$set 的原理

数组修改数据为什么不会被监测? 对象属性为什么可以?

网友对尤雨溪提出的 issues:

https://github.com/vuejs/vue/issues/8562

  • Vue 实现响应式原理是依赖 Object.defineProperty() 方法
  • 数组可以看做是索引和值的键值对, 同样可以被监测到
// 对象
const obj = {age: 18 // 键为 age 值为 18
}// 数组
const arr = [18] // 等同于键为 0, 值为 18

监测数组

  • 通过结果可以观察出, 如果数据量提升到十万甚至百万, 对数组进行修改时, 性能损耗会非常大

  • Vue 官方通过权衡后, 觉得这样做性价比不高, 所以没有对数组进行监测

  • 尤雨溪原话

    性能代价和获得的用户体验收益不成正比。

const arr = []defineReactive(arr, 0, 'a')
defineReactive(arr, 1, 'b')
defineReactive(arr, 2, 'c')
defineReactive(arr, 3, 'd')
defineReactive(arr, 4, 'e')console.log(arr) // 访问数组的所有方法, 所有属性的 get 都会执行一次
arr.push('f') // 末尾追加, 所以只需要把数组中所有元素遍历一遍, 所有属性 get 都会执行一次
arr.unshift('-a') // 开头插入, 会导致数组元素顺序发生变化, 所有属性的 get 和 set 都会执行一次

演示

工程源码: src\core\observer\index.js

复习 $set 使用方法

  • $set() 有三个参数
    • 参数1: 要修改的对象 / 数组
    • 参数2: 要修改的键(索引)是什么
    • 参数3: 要设置的值是什么
export function set (target, key, val) {// 判断是否为数组if (Array.isArray(target)) {// 这里的操作是怕传入一个越界的索引, 往里面添加数据// 传入的索引和当前长度取最大值target.length = Math.max(target.length, key)// 删除原来位置的元素, 然后在当前位置添加一个数据, 就完成了替换更新// 而由于 splice() 是数组重写的方法, 所以会重新更新页面target.splice(key, 1, val)return val}// 如果是对象就走后面的代码, 如果是数组就不看后面的了if (key in target && !(key in Object.prototype)) {// 如果你修改的对象, 属性在原来就存在, 说明已经被劫持了// 直接修改对象属性即可, 会自动更新视图target[key] = valreturn val}const ob = target.__ob__if (!ob) {target[key] = valreturn val}defineReactive(ob.value, key, val)ob.dep.notify()return val
}

总结

  • 所谓的 $set 方法, 内部做了判断, 分为以下三种情况:
    1. 如果传入的是数组, 修改数组中的元素, 就是用的 splice()
    2. 如果传入的是对象,
      1. 属性已存在于对象中, 就直接改值, 说明数据已被劫持
      2. 属性不存在于对象中, 说明这是新的属性, 所以使用 defineReactive() 进行数据劫持

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...