Loading...
墨滴

小石头

2021/08/16  阅读:40  主题:自定义主题1

vue数组响应式原理

数组可以用defineProperty进行监听。但是考虑性能原因,不能数组一百万项每一项都循环监听(那样性能太差了)。所以没有使用Ojbect.defineProperty对数组每一项进行拦截,而是选择劫持数组原型上的个别方法并重写。

具体重写的有:

pushpopshiftunshiftsortreversesplice (这七个都是会改变原数组的)

另外要注意的是:

不是直接粗暴重写了Array.prototype上的push等方法,而是通过原型链继承与函数劫持进行的移花接木。并且只监听调用了defineReactive函数时传进来的数组。

具体实现思路:

以push为例,而是利用Object.create(Array.prototype)生成新的数组对象,该对象的__proto__指向Array.prototype。并在对象身上创建push等函数,利用函数劫持,在函数内部Array.prototype.push.call调用原有push方法,并执行自己劫持的代码(如视图更新)。最后将需要绑定的数组的__proto__由指向Array.prototype改向指成拥有重写方法的新数组对象。具体看下边源码仿写,真实Array.prototype里的祖宗级别push等方法没有动。

思考:

为啥不重写map等也是修改原数组的方法呢?

特别注意:

在Vue中修改数组的索引和长度,是无法被监控到并做响应式视图更新的。需要通过以上7种变异方法修改数组才会触发数组对应watcher进行更新。 数组中如果是对象数据类型的也会进行递归劫持。 如果情节需要,通过索引来修改数组里的内容。可以通过Vue.$set()方法来进行处理,或者使用splice方法实现。(其实$set内部的核心也是splice方法)

原理mock:

vue【数组】响应式数据原理mock

let state = [1,2,3]; //待监听的数据

// 1、响应式数据-函数劫持实现数组原型方法重写
let OriginalArray = Array.prototype; 
/* 
  并不是直接改写原型上的方法。而是给当前待监听的数组
  原型链上加了push等方法劫持了Array原型的push方法。 
*/

let arrayMethods = Object.create(OriginalArray) 
/* 
  创建一个新对象(对象or数组由第一个参数决定)
  带着指定的原型对象(Array.prototype) 
*/

console.log(
  arrayMethods,
   // 原型修改
  arrayMethods.__proto__ === OriginalArray,
  arrayMethods.__proto__ === Array.prototype
)
function defineReactive(obj{
  // 【函数劫持】改写这个新对象身上的push、splice等数组方法
  arrayMethods.push = function(...args){
    // 并还是调用原生的push方法
    OriginalArray.push.apply(this, args) // 或者用call(this, ...args)
    // 然后这里边做自己的事情,比如视图更新(具体源码怎么更新的视图?)
    render()
  }
  // 
  obj.__proto__ = arrayMethods
/* 
  修改传进来的、被监听的数组的原型链,链接数组与被重写的方法。
  原本__proto__指向Array.prototype,现在中间给他包了一层,指向我们重写的原型方法。
  并在重写的原型方法里再调用Array.prototype的同名原型方法。 
*/

}
defineReactive(state);
// 操作dom
function render({
  app.innerHTML = state;
}
render()
// 更改数据,观察dom修改
btn.onclick = () => {
  state.push(state[state.length - 1] + 1)
}

源码位置:

github:src/core/observer/array.js:8

小石头

2021/08/16  阅读:40  主题:自定义主题1

作者介绍

小石头