Vue 在操作数组时的问题
Vue 中我们在操作数组时会遇到一些问题,明明数据已经被修改了,但是为什么页面没有刷新呢?对于这个问题官方文档中已经给出答案,检测变化的注意事项
7 个方法的包装
Vue 将 Array 的 7 个方法进行了包装,使用这些方法操作数组可以出发页面的刷新。
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
7 个方法的使用
-
push()
方法用于向数组末尾添加元素,可添加一个或多个,返回值为数组 length -
pop()
方法用于删除数组末尾元素,返回值为删除的元素 -
shift()
方法用于删除数组第一个元素,返回值为删除的元素 -
unshift()
方法用于像数组首部添加元素,可添加一个或多个,返回值为数组 length -
splice()
方法用于删除、添加、替换元素,-
arrayObject.splice(index,howmany,item1,.....,itemX)
第一个参数 index 为其实下标,第二个参数howmany
为删除个数,第三个及之后参数为像数组添加的元素 -
如果只传第一个元素,则删除下标
index
后的所有元素 -
如果传两个参数,则删除起始下标
index
开始之后的howmany
个元素 -
如果传三个参数,则删除起始下标
index
开始之后的howmany
个元素,添加items
元素
-
-
sort()
方法用于对数组排序,不传参数则按照字符编码排序,也可传入一个有两个形参的回调函数,然后根据返回值排序,(此处将回调函数的参数命名为 a 和 b)- 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
- 若 a 等于 b,则返回 0。
- 若 a 大于 b,则返回一个大于 0 的值。
-
reverse()
方法用于颠倒数组中元素的顺序
7 个数组方法的实现原理
这 7 个方法能够出发页面的刷新并不是因为他原本就能够触发,而是在 Vue 中对这 7 个方法做了特殊的逻辑处理,其实现方式就是对这 7 个方法进行了包装
.\src\core\observer\array.js
import { def } from '../util/index'
// 存储 Array 的 prototype 对象
const arrayProto = Array.prototype
// 创建原型指向 arrayProto 对象的 arrayMethods 对象
export const arrayMethods = Object.create(arrayProto)
// 定义 7 个方法名称的数组
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
// 遍历 7 个方法名称的数组
methodsToPatch.forEach(function (method) {
// 存储 Array 原本的原型方法,放在这里取是因为只需要取一次,利用闭包以空间换时间
const original = arrayProto[method]
// def 就是对 defineProperty 的简单包装,将我们包装后的方法添加到 arrayMethods 对象中
def(arrayMethods, method, function mutator (...args) {
// 先调用 Array 原本的原型方法,进行方法原有的逻辑处理,然后再处理添加的逻辑
const result = original.apply(this, args)
// __ob__ 就是 Dep 对象
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)
// 通知 watcher 继而更新视图
ob.dep.notify()
// 返回原方法的返回值
return result
})
})
可以看出,这里暴露了原型为 Array.prototype
的 arrayMethods
对象,在这个对象中我们对 Array
中这 7 个方法进行了包装。
那我们包装完了,它又是如何修改我们的在 data 中的数组对象的呢,原因就是他在对数组对象进行响应式化时对其方法进行了修改,修改分为两种情况,
- 在我们的环境中对象拥有
__ob__
属性时便直接将数组的原型指向了我们的arrayMethods
对象,即在原型链中添加了一层,也就是我们的arrayMethods,当数组对象调用这 7 个方法时,在对象中找不到,便在原型对象中找,找到便直接调用了 - 如果没有
__ob__
对象,则直接将这 7 个方法定义到我们的数组对象中去
.\src\core\observer\index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
// 如果有 __ob__ 对象则更改原型链
protoAugment(value, arrayMethods)
} else {
// 否则直接在数组对象中定义方法
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
walk (obj: Object) {
...
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
// 修改原型链
function protoAugment (target, src: Object) {
target.__proto__ = src
}
// 将 7 个方法定义到数组对象中
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
图中可以看出 books 原型链中添加了我们的 arrayMethods
使用 Vue.set 操作数组
从源码中可以看出,使用 set
方法操作数组其实就是调用了 splice
方法(我们修改之后的 splice
方法)
.\src\core\observer\index.js
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
}
}
Vue3 中的数组操作
Vue3 的响应式原理是使用了 proxy
,不同于 Vue2 中使用的 defineProperty
,不存在上述问题,可以通过下标修改数组,也可以直接修改数组 length
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!