Vue相关源码

ref和reactive

ref 定义单个响应式数据

  • 数据类型可以是任意类型。它通常用于定义原始数据类型为响应式数据。
  • 返回一个响应式对象,该对象包含一个 .value 属性,可用于获取和设置数据。
  • 底层采用Object.defineProperty()实现,如果数据类型是对象,会转化为reactive
  • 源码:
function ref(value) {
  return createRef(value, false);
}

function createRef(rawValue, shallow) {
  if (isRef(rawValue)) {
    return rawValue;
  }
  return new RefImpl(rawValue, shallow);
}

class RefImpl {
  constructor(value, __v_isShallow) { // __v_isShallow 一般为false
    this.__v_isShallow = __v_isShallow;
    this.dep = void 0;
    this.__v_isRef = true;
    this._rawValue = __v_isShallow ? value : toRaw(value);
    this._value = __v_isShallow ? value : toReactive(value);
  }
  get value() {
    trackRefValue(this);
    return this._value;
  }
  set value(newVal) {
    const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
    newVal = useDirectValue ? newVal : toRaw(newVal);
    if (shared.hasChanged(newVal, this._rawValue)) {
      const oldVal = this._rawValue;
      this._rawValue = newVal;
      this._value = useDirectValue ? newVal : toReactive(newVal);
      triggerRefValue(this, 4, newVal, oldVal);
    }
  }
}
// 判断是否是对象,是的话转reactive
const toReactive = (value) => shared.isObject(value) ? reactive(value) : value;

reactive 定义多个响应式数据

  • 数据类型必须是对象
  • 返回一个响应式对象,必须使用响应式对象来获取属性和设置数据
  • 底层采用的是new Proxy()
function reactive(target) {
  if (isReadonly(target)) {
    return target;
  }
  return createReactiveObject( // 创建reactive对象
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  );
}

function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {
  if (!shared.isObject(target)) {
    {
      warn(
        `value cannot be made ${isReadonly2 ? "readonly" : "reactive"}: ${String(
          target
        )}`
      );
    }
    return target;
  }
  if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) {
    return target;
  }
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  const targetType = getTargetType(target);
  if (targetType === 0 /* INVALID */) {
    return target;
  }
  const proxy = new Proxy(
    target,
    targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
  );
  proxyMap.set(target, proxy);
  return proxy;
}

computed源码分析

dirty是监听的一个属性,
如果dirty是true的时候,读取computed的值就会重新计算
如果dirty是false的时候,就会使用缓存

const computed = (getterOrOptions, debugOptions) => {
  const c = computed$1(getterOrOptions, debugOptions, isInSSRComponentSetup);
  {
    const i = getCurrentInstance();
    if (i && i.appContext.config.warnRecursiveComputed) {
      c._warnRecursive = true;
    }
  }
  return c;
};

function computed$1(getterOrOptions, debugOptions, isSSR = false) {
  let getter;
  let setter;
  const onlyGetter = isFunction(getterOrOptions);
  if (onlyGetter) {
    getter = getterOrOptions;
    setter = () => {
      warn$2("Write operation failed: computed value is readonly");
    } ;
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }
  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR);
  if (debugOptions && !isSSR) {
    cRef.effect.onTrack = debugOptions.onTrack;
    cRef.effect.onTrigger = debugOptions.onTrigger;
  }
  return cRef;
}

class ComputedRefImpl {
    constructor(getter, _setter, isReadonly, isSSR) {
      this.getter = getter;
      this._setter = _setter;
      this.dep = void 0;
      this.__v_isRef = true;
      this["__v_isReadonly"] = false;
      this.effect = new ReactiveEffect(
        () => getter(this._value),
        () => triggerRefValue(
          this,
          this.effect._dirtyLevel === 2 ? 2 : 3
        )
      );
      this.effect.computed = this;
      this.effect.active = this._cacheable = !isSSR;
      this["__v_isReadonly"] = isReadonly;
    }
    get value() {
      const self = toRaw(this);
      if ((!self._cacheable || self.effect.dirty) && hasChanged(self._value, self._value = self.effect.run())) {
        triggerRefValue(self, 4);
      }
      trackRefValue(self);
      if (self.effect._dirtyLevel >= 2) {
        if (this._warnRecursive) {
          warn$2(COMPUTED_SIDE_EFFECT_WARN, `

getter: `, this.getter);
        }
        triggerRefValue(self, 2);
      }
      return self._value;
    }
    set value(newValue) {
      this._setter(newValue);
    }
    // #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.x
    get _dirty() {
      return this.effect.dirty;
    }
    set _dirty(v) {
      this.effect.dirty = v;
    }
    // #endregion
  }

vue2和vue3的区别

写法

vue2是Options Api,vue3是Composition API

响应式

vue2的响应式原理基于object.defineProperty,而vue3则基于proxy对象(ref使用的也是object.defineProperty,如果是引用对象类型才会转为reactive,而reactive是proxy), object.defineProperty只是对属性进行拦截,会去遍历一个对象的每一个属性去添加getter和setter,如果遇到对属性的删除增加,或者对数组下标进行修改,就拿不到响应式数据(可以使用vue自带的set方法去重新设置响应式)
proxy就代理的是整个对象,proxy针对于对象或者数组都有更多的api,可以深度监听对象的变化,实现对嵌套对象的深度监听

diff算法

vue2的diff算法基于虚拟dom采用双指针算法和同级比较,vue3基于proxy和patch flag,引用的patch flag概念精确判断更新节点,采用proxy对象来跟踪动态节点的变化,提高diff算法的效率

打包体积

Vue.js 2 的打包体积通常比较大,原因在于它内置了很多功能和组件,Vue.js 3 采用了Tree-shaking,更好的组件化支持,更好的模板编译来优化打包体积,使打包体积更小

Tree-Shaking

Tree-Shaking最先在rollup.js中应用,后来webpack,vite。简单来说就是移除掉项目中永远不会被执行的代码(dead code),即代码虽然依赖某个模块,但只使用其中的某些功能,通过Tree-shaking,将没有使用的模块代码移除掉,削减项目的体积
tree-shaking是依赖于ES6的模块特性,即模块必须是ESM(ES Module),但是vue2是基于es5搭建的

vuex和pinia的区别

设计和使用

  • Vuex:采用全局单例模式,通过一个store对象来管理所有状态
  • Pinia:采用分离模式,每个组件都有自己的store实例

数据修改

  • Vuex:包含mutations和actions,mutations用于同步修改状态,actions用于处理异步操作
  • Pinia:没有mutations,只有state、getters和actions1。Pinia的actions可以直接修改状态,而不需要像Vuex那样通过mutations来修改状态。

语法

  • Pinia:语法上比Vuex更容易理解和使用,提供了更好的TypeScript支持。Pinia与Vue 3的Composition API紧密结合,允许将状态逻辑作为可复用的函数组合起来。
  • Vuex:Vuex的语法和用法相对传统,对于熟悉Vue 2的开发者来说可能更加熟悉

体积和性能

  • Pinia:体积较小,约1KB,且性能较好,因为它使用了新的ES6语法和新的数据处理方式。
  • Vuex:体积相对较大,但性能稳定可靠,是Vue.js官方提供的状态管理库

keep-alive

keep-alive组件的主要作用就是将需要缓存的组件进行缓存,当组件被切换时,它会将之前缓存的组件重新渲染到页面上,而不会再重新创建新的组件实例。这种缓存机制可以极大地提高页面的加载速度和响应速度

keep-alive组件利用了其中的两个生命周期钩子函数:activated和deactivated

  • activated:在activated函数中,keep-alive组件会将之前缓存的组件重新渲染到页面上,而不会重新创建实例。这是因为keep-alive组件使用了LRU(Least Recently Used)算法来管理缓存的组件实例,当缓存的组件数量超过一定的阈值时,较早使用的组件会被销毁,释放内存空间。
  • deactivated:在deactivated函数中,keep-alive组件会将当前的组件实例保存到缓存中,不会被销毁。这样当组件再次被激活时,可以直接从缓存中取出组件实例,而不需要重新创建。

effect

响应式API,用于追踪响应式依赖项

import { reactive, effect } from 'vue';

const state = reactive({
  count: 0,
});

// 创建一个 effect 函数
const countEffect = effect(() => {
  console.log(`Count is: ${state.count}`);
});

// 修改状态
state.count++; // 触发 countEffect 函数重新执行
// 输出:Count is: 1

diff算法

diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom

  • 只会同级比较,不会跨层级比较
  • 在diff比较的过程中,循环从两边向中间比较

diff 算法的在很多场景下都有应用,在 vue 中,作用于虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较
image.png

vue2和vue3diff算法的不同

1.Virtual DOM的优化

Vue 2 中的 diff 算法针对整个 Virtual DOM 树进行了完整的比较,导致在大型应用中可能存在性能问题。
Vue 3 中通过静态分析和标记,将组件标记为静态、动态或稳定,从而避免不必要的 Virtual DOM 比较,提高了渲染性能。

2.动态指令的优化

Vue 2 中动态指令的 diff 算法在某些情况下效率不高,可能会导致不必要的重新渲染。
Vue 3 中通过优化动态指令的处理方式,提高了动态指令的 diff 效率,减少了不必要的更新操作,提升了性能。

3.事件侦听器的优化

在 Vue 2 中,每次更新都会重新设置事件侦听器,存在一定性能损耗。
Vue 3 中通过事件侦听器的缓存和重用,减少了事件侦听器的重复创建和销毁,提高了事件的处理效率。

4.静态树的处理

Vue 2 中没有对静态树(即不会发生变化的部分)做特殊处理,仍然会进行完整的 diff 操作。
Vue 3 中对静态树进行了优化处理,避免了不必要的比较和更新,提高了整体渲染性能。

5.Fragments的处理

在 Vue 2 中,使用 Fragments 时会引入额外的 Virtual DOM 节点,导致在 diff 过程中产生额外的开销。
Vue 3 中通过优化 Fragments 的处理方式,减少了额外节点的创建和比较,提高了对 Fragments 的 diff 效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值