一、为什么要重写数组方法?
Vue 为了实现响应式数据绑定,需要能够捕获到数据的变化,以便在数据发生改变时自动更新视图。JavaScript 中的数组是引用类型,所以常规的引用跟踪机制不能捕获数组内部的变化。因为Vue的响应式是通过 Object.defineProperty()实现的,这个api没办法监听数组长度的变化,也就没办法监听数组的新增。所以为了更友好的操作数组并触发响应式检测,Vue对改变原数组(数组本身)的方法进行了重写。
Vue通过原型拦截的方式重写了数组的7个方法,首先获取到这个数组的Observer。如果有新的值,就调用observeArray对新的值进行监听,然后调用notify,通知render watcher,执行update。
二、vue重写数组的七个方法分别是哪些?
push()、pop()、shift()、unshift()、splice()、sort()、reverse()
说明:vue只重写了这7个方法,使用其它数组方法不会主动触发试图更新,例如 concat、slice 等,如果需要触发更新,我们可以使用Vue.set() 或 this.$set() 方法手动触发更新。另外,Vue3处理响应数据使用了Proxy,系统会自动捕获这些操作,并在数据变化时触发视图更新,所以不需要重写数组方法。
三、重写数组方法源码
// 获取数组的原型Array.prototype
var arrayProto = Array.prototype// 新建一个继承于Array的对象
var arrayMethods = Object.create(arrayProto)// 列出需要重写的数组方法名
var methodsToPatch = ["push","pop","shift","unshift","splice","sort","reverse",
]// 遍历上述数组方法名,依次将上述重写后的数组方法添加到arrayMethods对象上
methodsToPatch.forEach(function (method) {// 缓冲原始数组的方法var original = arrayProto[method]// 利用Object.defineProperty对方法的执行进行改写def(arrayMethods, method, function mutator(val) {var args = [],len = arguments.length;while (len--) args[len] = arguments[len]// 执行原数组方法var result = original.apply(this, args)var insertedswitch (method) {case "push":case "unshift":inserted = args;break;case "splice":inserted = args.slice(2);break;}// 在此处进行通知return result})
})// 进行监听
function def(obj, key, val, enumerable) {Object.defineProperty(obj, key, {value: val,enumerable: !!enumerable,writable: true,configurable: true})
}
具体来说,当调用重写的数组方法时,Vue 会执行以下步骤:
1.调用原始的数组方法,例如 push()、pop()、splice() 等。
2.在执行数组方法后,Vue 检测到数组发生了变化。
3.Vue 发出通知,触发视图的重新渲染,确保视图与数据保持同步。
这种重写机制使得开发者在操作数组时无需手动去触发视图更新,Vue 自动为你处理了这部分逻辑。
说明:这些重写仅适用于通过 Vue 实例声明的数组。如果你直接使用原生的数组方法,Vue 将无法捕获到变化,可能导致视图不更新。