vue3源码学习(2)--手写vue2/vue3响应式系统的简单实现-以及二者数据劫持的区别

续上文:
Vue3源码学习之旅(1)–自己实现一个简单的渲染系统-render/h/patch函数

依赖收集系统

// 收集依赖
/**
 * 收集依赖的类
 */
class Dep {
  constructor() {
    // 发布订阅模式
    // 订阅者 将其都加入进来
    this.subscribers = new Set();
  }
  // 收集副作用
  // addEffect(effect) {
  //   this.subscribers.add(effect);
  // }

  /* 自动收集副作用 */
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect)
    }
  }

  // 通知,进行回调执行
  notify() {
    this.subscribers.forEach(effect => {
      effect();
    })
  }
}

let activeEffect = null;
/**
 * 帮助收集依赖
 * @param {*} effect 数据发送改变后要执行的副作用函数
 */
function watchEffect(effect) {
  activeEffect = effect;
  // 用原始数据执行一次
  effect()
  activeEffect = null;
}
/* 
  Map: {key:value} key: 字符串 value任意
  WeakMap: key 是一个对象,弱引用  value任意
*/
const targetMap = new WeakMap();

/**
 * 
 * @param {*} target 目标对象
 * @param {*} key 目标对象的属性
 */
function getDep(target, key) {
  // 1. 根据目标对象 取出对应的Map对象
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  // 取出具体的依赖对象
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Dep();
    depsMap.set(key, dep);
  }
  return dep;
}

数据劫持

响应式系统Vue2实现
**
 * vue2对raw进行数据劫持
 * @param {*} raw 原始数据
 */
function reactive(raw) {
  Object.keys(raw).forEach(key => {
    // 收集依赖
    const dep = getDep(raw, key);
    let value = raw[key];
    // 劫持的对象,对象的属性,属性的描述
    Object.defineProperty(raw, key, {
      // 获取数据 会执行get函数
      get() {
        // 添加依赖
        dep.depend()
        return value;
      },
      // 设置数据
      set(newValue) {
        if (value !== newValue) {
          value = newValue;
          dep.notify();
        }
      }
    })
  })
  return raw;
}
响应式系统Vue3实现
**
 * vue3对raw进行数据劫持
 * @param {*} raw 原始数据
 */
function reactive(raw) {
  // 劫持的对象 对代理对象的数据劫持
  return new Proxy(raw, {
    // 获取数据时调用
    // target 参数 就是 我们劫持的对象 raw
    get(target, key) {
      const dep = getDep(target, key);
      dep.depend();
      // 这里没有考虑属性值还是对象的情况
      return target[key];
    },
    // 设置数据时调用
    set(target, key, newValue) {
      const dep = getDep(target, key);
      target[key] = newValue;
      dep.notify();
    }
  });
}
响应式数据的测试
// ----------------测试----------------------

// 数据
const info = reactive({ name: '毛毛', age: 21 })
// 会自动收集依赖
watchEffect(() => {
  console.log(info.age * 2);
})
// 数据发送改变 自动执行副作用函数
info.age++;

let obj  = reactive({name:'hah ',counter:1});
watchEffect(()=>{
  console.log("name:",obj.name);
})
watchEffect(()=>{
  console.log("counter:",obj.counter);
})
obj.counter++
为什么Vue3选择Proxy呢?

Object.definedProperty 是劫持对象的属性时,如果新增元素:

那么Vue2需要再次 调用definedProperty,而 Proxy 劫持的是整个对象,不需要做特殊处理;

修改对象的不同:

  • 使用 defineProperty 时,我们修改原来的 obj 对象就可以触发拦截;
  • 而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截;

Proxy 能观察的类型比 defineProperty 更丰富

  • has:in操作符的捕获器;
  • deleteProperty:delete 操作符的捕捉器;
  • 等等其他操作;

框架外层API设计

这样我们就知道了,从框架的层面来说,我们需要 有两部分内容:

  • createApp用于创建一个app对象;

  • 该app对象有一个mount方法,可以将根组件挂 载到某一个dom元素上;

/**
 * 
 * @param {*} rootComponent 根组件(元素)
 * @returns 
 */
function createApp(rootComponent) {
  return {
    mount(selector) {
      const container = document.querySelector(selector);
      // 是否挂载过
      let isMounted = false;
      // 数据未更新前的vnode
      let oldVNode = null;
      watchEffect(() => {
        if (!isMounted) {
          // 没有挂载过
          //  进行挂载
          oldVNode = rootComponent.render();
          mount(oldVNode, container);
          isMounted = true;
        } else {
          // 已经挂载过
          // 进行patch比对
          const newVNode = rootComponent.render()
          patch(oldVNode, newVNode);
          // 保存这次新的vnode
          oldVNode = newVNode;
        }
      })
    }
  }
}

测试

// 根组件
    const App = {
      data: reactive({
        counter: 0
      }),
      render() {
        return h("div", { style: "color:red" }, [
          h("h2", null, `当前计数:${this.data.counter}`),
          h("button", {
            onClick: () => {
              this.data.counter++
            }
          }, "+1")
        ])
      }
    }
    // 挂载根组件
    const app = createApp(App)
    app.mount("#app")
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尤雨东

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值