vue2响应式原理

Object.defineProperty()

Object JavaScript参考

Object.defineProperty() 静态方法 会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。

const object = {};

// 简单设置新属性和值
Object.defineProperty(object, "age", {
  value: 42,
  writable: false,
});

object.age = 77;
// Throws an error in strict mode

console.log(object.age);
// Expected output: 42

let name = "xiaoming";

// 重写对象的get和set方法 在get和set操作时 可以设置一些操作
Object.defineProperty(object, "name", {
  get() {
    console.log("get!");
    return name;
  },
  set(value) {
    console.log("set!" + value);
    name = value;
  },
});

console.log(object.name);
// Expected output: get!
//                  xiaoming

object.name = "daming";
// Expected output: set!daming

由上图可以得知
当用户通过修改object.name = 'xxx'时 触发了 set 方法 可以知道属性的值被修改了 在使用{{ object.name }}的时候 触发了 get 方法 可以知道这个值被使用了

响应式原理

vue 采用数据代理、数据挟持、发布订阅模式实现响应式

数据挟持 + 发布订阅(重点)

响应式流程 在 vue 初始化的时候 会将 data 里的数据进行递归遍历 将每一个未被冻结对象里的字段使用 Object.defineProperty 挟持一遍 通过自定义每个属性的 get、set 方法(也可以说 getter、setter) 当视图中使用了对象的某一项数据时 触发了对象的 get方法 我们可以知道这个数据会影响视图 所以要收集他的依赖 当用户修改对象的数据时 触发了 set 方法 我们可以知道数据发生了变化 此时就需要判断这个数据会不会影响视图 也就是去依赖里去找这个数据的依赖 如果找到了就通知视图更新 从而达到数据响应式驱动视图更新
进阶(面试时回答到重写 get、set 方法即可,如果了解可以多说说):我们在 get 方法里增加了 Dep 依赖收集 收集了系统里被使用到的数据的依赖 当用户设置被挟持的对象的属性值时 set 会触发当前对象的依赖收集(Dep)的 notify 方法 通知页面需要更新

然后vue使用新的虚拟dom和旧的虚拟dom进行对比 操作真实dom 对真实dom进行修改 (这里修改的地方不止一处时 会对nodify 通知更新进行一次批处理更新 也就是一段时间内处理一部分更新 而不是每次有通知更新就立即更新一次)

数据代理(了解)

在 vue 里 我们的 vm (vm = new Vue()) 会代理 data 里的数据
比如 通过访问 vm.name(或者是 this.name) 就可以访问到 data 里的 name 当然这并不是响应式的逻辑 只是为了方便取值 将 data 里的数据代理到了 vm 上 如果通过其他方式去取值的话 就是 this.$options.data().name 需要从传入的选项里获取值 是不是很复杂

vue2 源码解读

!!! success TIPS
tip: 只包含了关键代码
!!!

/**
 * Define a reactive property on an Object.
 */
// 因为是ts版本的 如果不会ts的话 可以忽略类型
export function defineReactive(
  obj: object, // 需要被响应式的对象
  key: string, // 对象的键
  val?: any, // 需要设置的值 ?的意思是可以传入变量作为初始值 也可以不传入 简单理解为非必填 非必填参数一般都在必填参数后面
  customSetter?: Function | null, // 自定义set 方法
  shallow?: boolean, // 是否是浅的 就是对象最外层是响应式的 里面嵌套的不去挟持
  mock?: boolean, // 暂时不知道是干嘛的 看样子应该是和ssr有关
  observeEvenIfShallow = false // 这个变量没有使用到
) {
  // const dep = new Dep() 这个是依赖收集 可以先了解 从这可以看到 没个对象都有自己的dep

  const property = Object.getOwnPropertyDescriptor(obj, key);
  // 先看看这个属性是否是可以设置的 如果对象被冻结了 或者是属性被设置为不可配置 就不可以挟持了 对象冻结方法 Object.freeze()
  if (property && property.configurable === false) {
    return;
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && (val === NO_INITIAL_VALUE || arguments.length === 2)) {
    // 获取到初始值
    val = obj[key];
  }

  // 如果是shadow 表示浅代理 内层嵌套的childOb就直接挂一个__ob__ 属性 表示已经被些吃过了 不需要些吃了 相对于是一个假的挟持
  // 否则就表示需要递归挟持
  let childOb = shallow ? val && val.__ob__ : observe(val, false, mock);
  // observe 其实就是一个可以对数组、嵌套对象进行数据挟持的一个 defineReactive 就是加了些判读、循环、递归
  // 然后再调defineReactive去一个一个对象、数组去劫持
  // v2 最新源码地址 https://github.com/vuejs/vue

  Object.defineProperty(obj, key, {
    enumerable: true, // 可枚举
    configurable: true, // 可配置
    get: function reactiveGetter() {
      // 有get 方法就用get取值 没有就用obj[key] 取值
      const value = getter ? getter.call(obj) : val;

      dep.depend(); // depend()就是依赖收集的方法 这里是简写的 感兴趣的可以自己去翻看源码
      // 简单来说就是这里在收集 需要使用到的数据的依赖 然后数据变化了就可以通过这些记录
      // 知道哪个数据变化了 是否需要更新页面

      // 这里使用了 isRef 应该是为了兼容V3
      return isRef(value) && !shallow ? value.value : value;
    },
    set: function reactiveSetter(newVal) {
      // 同理 也是获取值
      const value = getter ? getter.call(obj) : val;

      // 如果没变 直接就不修改数据了
      if (!hasChanged(value, newVal)) {
        return;
      }
      if (__DEV__ && customSetter) {
        customSetter();
      }
      // 有setter就直接设置值
      if (setter) {
        setter.call(obj, newVal);
      } else if (getter) {
        // #7981: for accessor properties without setter
        return;
      } else if (!shallow && isRef(value) && !isRef(newVal)) {
        // 兼容V3吧
        value.value = newVal;
        return;
      } else {
        val = newVal;
      }
      // 这里可能会出现 用户添加了一些新的属性 这些属性没在data里 是后续通过方法调用加的 可能没被代理到 要进行重新代理
      childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock);

      dep.notify(); // notify()就是通知数据更新了 需要更新页面
    },
  });

  return dep; // 返回收集的依赖
}
  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值