Vue响应式原理

2021-02-17  本文已影响0人  _royalpioneer

前言の前言:写得极其不通顺,只是快速做个零碎总结,后续会不断润色

vue2与vue3在实现上的区别

vue2实现数据劫持使用的是Object.defineProperty, Vue3使用的是Proxy

共同需要解决的问题

  1. 响应式的对象
  2. 响应式的数组

看看vue响应式的表现

在了解这些vue2的表现后,我们往原理层面看看,到底为什么会是这样

使用Object.defineProperty

表面上,这个Object的API Object.defineProperty 和直接使用字面量给对象赋值的效果貌似是一样的,那么实际上他们到底有什么区别呢
(1)定义方式

// 使用Object.defineProperty给对象定义属性
let obj = {};
// 参数: 对象、属性名/Symbol、描述符(里面的叫做键、值)
Object.defineProperty( obj, "name", {
    configurable: false,   // 1.是否能删除 2.描述符是否能修改
    enumerable: false, // 是否可枚举
    value: undefined, // 值
    writable: false, // 能否被赋值运算符=赋值
    get: undefined,  // 访问该属性时调用,传入this,返回结果用作属性值
    set: undefined  // 修改属性值时调用,传入修改值和this
} )

// 使用字面量的方式
obj.name = undefined;

很显然,Object.defineProperty可以使用描述来控制该属性的配置、枚举、修改,拦截get和set的过程,而这正是我们实现数据劫持所需要的特性。

此外,如果想要批量添加属性的话,可以使用Object.defineProperties,示例

const obj = {};
Object.defineProperties(obj, {
    name: {
        value: "123",
        writable: true
    },
    name2: {
        value: "456"
    }
});

基本的响应式实现

// 数据
const data = {
    name: "123",
    name2: "456"
};

// 将data变成响应式
observer(data);

function observer(target) {
    // 非数组、对象 直接返回
    if(typeof target !== 'object' || typeof target !== null) {
        return target;
    }

    // 数组、对象实现数据劫持
    for(let key in target) {
        defineReactive(target, key, target[key]);
    }
}

// 使用defineProperty实现简单的数据劫持
function defineReactive(target, key, value) {
    Object.defineProperty(target, key, {
        value,
        get: function() {
            return value;
        },
        set: function(newVal) {
            value = newVal;
            // todo: 更新视图
        }
    })
}

这里补一张图:


在这里插入图片描述

vue实际上是通过发布订阅者模式进行数据驱动视图更新的。
在我理解,发布订阅者模式可以高度概括为一句话:控制并发布数据的Subject会在数据发生变动时通知所有注册&订阅了的Observer进行更新。

细化.复杂对象

const data = {
    name: "123",
    name2: "456",
    name3: {
        firstName: "c",
        lastName: "xk"
    }
};

// 将data变成响应式的数据
observer(data);

// observer的实现:递归侦听
function observer(target) {
    if(typeof target !== 'object' || typeof target !== null) {
        return target;
    }

    // s数组、对象使用defineProperty实现简单的数据劫持
    for(let key in target) {
        observer(newVal);
        Object.defineProperty(target, key, {
            value,
            get: function() {
                return value;
            },
            set: function(newVal) {
                observer(newVal);
                value = newVal;
                // todo: 更新视图
            }
        })
    }
}

当数据是多层级对象时,之前的实现就无法侦听到深层次属性的变化了,所以需要使用递归进行优化;另外,如果set的时候,给定的也是个对象 如 obj.name = { name }, 那么也无法监听,所以这里也需要递归优化下,递归时间复杂度很高,所以在vue2在遇到复杂对象时性能不会很好,vue3使用proxy解决了这个问题。

细化.数组

const {
  arguments
} = require("file-loader");
const {
  method
} = require("lodash");

const data = {
  name: "123",
  name2: "456",
  name3: {
    firstName: "c",
    lastName: "xk"
  },
  names: ['1', '2', '3'];
};

// 利用Array原型创建新原型
const oldArrayProto = Array.prototype;
const newArrayProto = Object.create(oldArrayProto);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
  newArrayProto[methodName] = function () {
    // todo: 更新视图
    oldArrayProto[methodName].call(this, ...arguments);
  }
});

// 将data变成响应式的数据
observer(data);

// observer的实现:递归侦听
function observer(target) {
  if (typeof target !== 'object' || typeof target !== null) {
    return target;
  }

  // 将_proto_指向新原型
  if (Array.isArray(target)) {
    target._proto_ = newArrayProto;
  }

  // s数组、对象使用defineProperty实现简单的数据劫持
  for (let key in target) {
    observer(newVal);
    Object.defineProperty(target, key, {
      value,
      get: function () {
        return value;
      },
      set: function (newVal) {
        observer(newVal);
        value = newVal;
        // todo: 更新视图
      }
    })
  }
}

Object.create方法的作用是Creates an object that has the specified prototype or that has null prototype,即创建有原型指向旧对象的新对象。

后续将继续补充完善

上一篇 下一篇

猜你喜欢

热点阅读