{T}

Vue2 面试题

1-Vue2.0 响应式数据的原理

整体思路是数据劫持+观察者模式。对象内部通过 defineReactive 方法,使用 Object.defineProperty 将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现

当页面使用对应属性时,每个属性都拥有自己的 dep 属性,存放他所依赖的 watcher(依赖收集),当属性变化后会通知自己对应的 watcher 去更新(派发更新)。相关代码如下

javascript
class Observer {
  // 观测值
  constructor(value) {
    this.walk(value);
  }
  walk(data) {
    // 对象上的所有属性依次进行观测
    let keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {
      let key = keys[i];
      let value = data[key];
      defineReactive(data, key, value);
    }
  }
}

// Object.defineProperty 数据劫持核心 兼容性在 ie9 以及以上
function defineReactive(data, key, value) {
  observe(value); // 递归关键
  // 如果 value 还是一个对象会继续走一遍 odefineReactive 层层遍历一直到 value 不是对象才停止
  Object.defineProperty(data, key, {
    get() {
      console.log("获取值");
      //需要做依赖收集过程 这里代码没写出来
      return value;
    },
    set(newValue) {
      if (newValue === value) return;
      console.log("设置值");
      //需要做派发更新过程 这里代码没写出来
      value = newValue;
    },
  });
}

export function observe(value) {
  // 如果传过来的是对象或者数组 进行属性劫持
  if (Object.prototype.toString.call(value) === "[object Object]" ||Array.isArray(value)) {
    return new Observer(value);
  }
}

2-Vue 如何检测数组变化

数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对 7 种数 pushshiftpopspliceunshiftsortreverse 方法进行重写(AOP 切片思想)

所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新

javascript
// src/obserber/array.js
// 先保留数组原型
const arrayProto = Array.prototype;

// 然后将 arrayMethods 继承自数组原型
// 这里是面向切片编程思想(AOP)--不破坏封装的前提下,动态的扩展功能
export const arrayMethods = Object.create(arrayProto);

let methodsToPatch = [
    "push",
    "pop",
    "shift",
    "unshift",
    "splice",
    "reverse",
    "sort",
];

methodsToPatch.forEach((method) => {
    arrayMethods[method] = function (...args) {
        // 这里保留原型方法的执行结果
        const result = arrayProto[method].apply(this, args);
        // this 代表的就是数据本身 比如数据是 {a:[1,2,3]} 那么使用 a.push(4)  this 就是 a  
        // ob 就是 a.__ob__ 这个属性就是上段代码增加的 代表的是该数据已经被响应式观察过了指向Observer实例
        const ob = this.__ob__;

        // 这里的标志就是代表数组有新增操作
        let inserted;
        switch (method) {
            case "push":
            case "unshift":
                inserted = args;
                break;
            case "splice":
                inserted = args.slice(2);
            default:
                break;
        }
        // 如果有新增的元素 inserted 是一个数组 调用 Observer 实例的 observeArray 对数组每一项进行观测
        if (inserted) {
            ob.observeArray(inserted)
        };
        // 之后还可以在这里检测到数组改变了之后从而触发视图更新的操作--后续源码会揭晓
        return result;
    };
});