Vue3响应式原理

253 阅读3分钟

Vue2.x响应式分析:

核心API:Object.defineProperty

响应式原理:数据改变的时候,视图会跟着更新

简单分析:

  1. 遍历data对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter
  2. 对象:递归遍历
  3. 数组:函数拦截,覆盖数组原型方法

缺点:

  • 深度监听,需要递归到底,一次性计算量大
  • 无法原生监听数组,需要特殊处理
  • 无法监听新增属性和删除属性需要使用Vue.set、Vue.delete处理

代码简单演示:

// 发布订阅模式
class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  // 通知更新视图
  notify() {
    this.subs.forEach(item => {
      item.update();
    });
  }
}
// 观察者
class Watch {
  constructor() {
    Dep.target = this;
  }
  update() {
    console.log("更新视图");
  }
}
function render() {
  console.log("更新视图");
}

function defineReactive(obj, key, val) {
  const dep = new Dep();
  observal(val);
  Object.defineProperty(obj, key, {
    get: function() {
      console.log("get", val);
      dep.addSub(Dep.target);
      return val;
    },
    set: function(newVal) {
      if (newVal != val) {
        console.log("set", val);
        // 新设置的值也需要深度监听
        observal(val);
        val = newVal;
        dep.notify();
      }
    }
  });
}
// 重写原型
const nativeArrayProperty = Array.prototype;
// 新创建一个对象且不影响原型
const arrayPro = Object.create(nativeArrayProperty);
const methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse"
];
methodsToPatch.forEach(method => {
  arrayPro[method] = function() {
    nativeArrayProperty[method].call(this, ...arguments);
    // 更新视图
    render();
  };
});
// 监听对象属性
function observal(target) {
  if (typeof target != "object" || target == null) {
    return target;
  }
  if (Array.isArray(target)) {
    target.__proto__ = arrayPro;
  }
  for (let key in target) {
    defineReactive(target, key, target[key]);
  }
}

class TVue {
  constructor(options) {
    this.data = options.data;
    observal(this.data);
    new Watch();
  }
}

let vue = new TVue({
  data: {
    leftNode: 1,
    rightNode: "20",
    info: {
      // 深度监听
      name: "luck",
      age: 10
    },
    nums: [10, 20, 30]
  }
});

console.log(vue.data.leftNode);
vue.data.info.age = 20
vue.data.nums.push(40);

Vue3.0响应式原理

vue3中使用ES6的Proxy特性来实现响应式

Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

proxy基本应用:

const data = {
  name: "luck",
  age: 21
};
// 数组操作 
// const data = [1,2,3,4]  
/**
 * get(target, propKey, receiver) 拦截对象属性的读取
 * set(target, propKey, value, receiver):拦截对象属性的设置
 * has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值
 * deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
 * ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、
   Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名
 * ...
 */

let proxy = new Proxy(data, {
  /**
   *
   * @param {*} target:目标对象
   * @param {*} key:属性名
   * @param {*} receiver:proxy 实例本身(可选)
   * @returns
   */
  get(target, key, receiver) {
    const result = Reflect.get(target, key, receiver);
    // const result = target[key]
    return result;
  },
  set(target, key, val, receiver) {
    // 重复的数据,不处理
    if (val === target[key]) {
      return true;
    }
    const result = Reflect.set(target, key, val, receiver);
    // const result =  target[key] = value;
    console.log("set", key, val);
    return result;
  },
  deleteProperty(target, key) {
    const result = Reflect.deleteProperty(target, key);
    console.log("delete property", key);
    return result;
  }
});
// get
const age = proxy.age;
// set
proxy.name = "code";
// delete
const result = delete proxy.name;
// push
proxy.push(5)

Reflect

  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法
  • 让Object操作更加规范化、标准化、函数式
  • 将Object对象的一些工具函数(比如Object.defineProperty),放到Reflect对象上。
// 老写法
'name' in data // true
delete data.name
// 新写法
Reflect.has(data, 'name') // true
Reflect.deleteProperty(data,'name')

proxy总结:

优点:能规避Object.defineProperty的问题

  • 深度监听:性能更好--不会递归到底而是在get到这个层级才会去递归
  • 可监听数组变化
  • 可监听新增、删除属性

缺点:无法兼容所有浏览器,并无法polyfill

响应式简单处理:

function reactive(target = {}) {
  if (typeof target !== "object" || target == null) {
    return target;
  }

  // 代理配置
  const proxyConf = {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver);
      // 深度监听
      // 性能提升: 什么时候get到这个层级才会去递归
      return reactive(result);
    },
    set(target, key, val, receiver) {
      // 重复的数据,不处理
      if (val === target[key]) {
        return true;
      }
      const ownKeys = Reflect.ownKeys(target);
      if (ownKeys.includes(key)) {
        console.log("已有的 key", key);
      } else {
        console.log("新增的 key", key);
      }

      const result = Reflect.set(target, key, val, receiver);
      console.log("set", key, val);
      return result;
    },
    deleteProperty(target, key) {
      const result = Reflect.deleteProperty(target, key);
      console.log("delete property", key);
      return result; // 是否删除成功
    }
  };

  // 生成代理对象
  const observed = new Proxy(target, proxyConf);
  return observed;
}

let data = {
  name: "luck",
  age: 33,
  address: {
    city: "sz"
  },
  arr: [1, 2, 3, 4]
};

const proxyData = reactive(data);