Vue原理篇

1、vue2响应式原理?

源码:
let obj = { name: 'poetry', age: 20 };

class Observer {
  // 观测值
  constructor(value) {
    this.walk(value);
  }
  walk(data) {
    // 对象上的所有属性依次进行观测
    let keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {
      let key = keys[i];
      let value = data[key];
      defineReactive(data, key, value);
    }
  }
}
// Object.defineProperty数据劫持核心 兼容性在ie9以及以上
function defineReactive(obj, key, value) {
  // 创建一个dep
  let dep = new Dep();

  // 递归观察子属性
  observe(value);

  Object.defineProperty(obj, key, {
    get() { // 收集对应的key 在哪个方法(组件)中被使用
      if (Dep.target) { // watcher
        dep.depend(); // 这里会建立 dep 和watcher的关系
      }
      return value;
    },
    set(newValue) {
      if (newValue !== value) {
        observer(newValue);
        value = newValue; // 让key对应的方法(组件重新渲染)重新执行
        dep.notify()
      }
    }
  })
}
export function observe(value) {
  // 如果传过来的是对象或者数组 进行属性劫持
  if (
    Object.prototype.toString.call(value) === "[object Object]" ||
    Array.isArray(value)
  ) {
    return new Observer(value);
  }
}


class Dep {
  constructor() {
    this.subs = [] // subs [watcher]
  }
  depend() {
    this.subs.push(Dep.target)
  }
  notify() {
    this.subs.forEach(watcher => watcher.update())
  }
}
Dep.target = null;
observer(obj); // 响应式属性劫持

// 依赖收集  所有属性都会增加一个dep属性,
// 当渲染的时候取值了 ,这个dep属性 就会将渲染的watcher收集起来
// 数据更新 会让watcher重新执行

// 观察者模式

// 渲染组件时 会创建watcher
class Watcher {
  constructor(render) {
    this.get();
  }
  get() {
    Dep.target = this;
    render(); // 执行render
    Dep.target = null;
  }
  update() {
    this.get();
  }
}
const render = () => {
  console.log(obj.name); // obj.name => get方法
}

// 组件是watcher、计算属性是watcher
const watcher = new Watcher(render);

// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
    arrProto[methodName] = function () {
        Watcher.update() // 触发视图更新
        oldArrayProperty[methodName].call(this, ...arguments)
        // Array.prototype.push.call(this, ...arguments)
    }
})

// 模拟数据获取,触发getter
obj.name = 'poetries'

// 一个属性一个dep,一个属性可以对应多个watcher(一个属性可以在任何组件中使用、在多个组件中使用)
// 一个dep 对应多个watcher 
// 一个watcher 对应多个dep (一个视图对应多个属性)
// dep 和 watcher是多对多的关系

2、vue3响应式原理?

Vue3的响应式原理是什么-Vue.js-PHP中文网


//定义一个 reactive 函数,用于将普通对象转换为响应式对象
function reactive(obj) {
  // 创建一个 WeakMap,用于存储原始对象和代理对象的映射关系
  const weakMap = new WeakMap();

  // 定义一个代理处理程序
  const handler = {
    get(target, key) {
      // 获取原始对象的属性值
      const value = Reflect.get(target, key);

      // 如果属性值是对象,则递归调用 reactive 函数
      if (typeof value === 'object' && value !== null) {
        if (!weakMap.has(value)) {
          weakMap.set(value, reactive(value));
        }
        return weakMap.get(value);
      }

      // 返回属性值
      return value;
    },
    set(target, key, value) {
      // 设置原始对象的属性值
      const oldValue = Reflect.get(target, key);
      if (oldValue !== value) {
        Reflect.set(target, key, value);

        // 触发更新
        trigger(target, key, oldValue, value);
      }
    },
  };

  // 使用 Proxy 对象创建代理对象
  return new Proxy(obj, handler);
}

// 定义一个 effect 函数,用于收集依赖
function effect(fn) {
  const effectStack = [];

  function runEffect() {
    // 保存当前 effect 到栈中
    effectStack.push(runEffect);

    // 执行传入的函数
    fn();

    // 移除当前 effect 从栈中
    effectStack.pop();
  }

  // 立即执行 effect
  runEffect();
}

// 定义一个 trigger 函数,用于触发更新
function trigger(target, key, oldValue, newValue) {
  // 遍历所有依赖,执行 effect 函数
  for (const effect of effectStack) {
    effect();
  }
}

3、nextTick的原理?

定义:

nextTick是表示下一次dom更新结束之后延迟执行回调

作用:

Vue有个异步更新策略,意思是如果数据变化,Vue不会立刻更新DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一事件循环中发生的所有数据变更会异步的批量更新。这一策略导致我们对数据的修改不会立刻体现在DOM上,此时如果想要获取更新后的DOM状态,就需要使用nextTick

使用场景:

DOM 更新后立即执行代码、获取dom元素的总数量等等

实现原理:

1、首先定义回调函数执行的数组以及判断函数执行的状态

callbacks数组以及pending状态

2、然后定义事件处理函数,flushCallbacks,使用for循环依次调用callbacks数组内的每一项并执行

3、定义timerFunc异步方法,采用优雅降级判断执行

4、使用多层判断进行优雅降级

1.判断是否支持promise使用isNative判断浏览器是否支持

2、使用isNative方法判断是否支持MutationObserver

MutationObserver是在dom更新完毕之后执行

设置状态并创建一个文本节点,执行过程中判断1和0的状态,总是在0和1状态中变换依旧保持执行阶段

3、判断是否支持setImmediate

4、如果都不支持的话就执行setTimeout

源码:
// src/core/utils/nextTick
let callbacks = [];
let pending = false;
function flushCallbacks() {
  pending = false; //把标志还原为false
  // 依次执行回调
  for (let i = 0; i < callbacks.length; i++) {
    callbacks[i]();
  }
}
let timerFunc; //定义异步方法  采用优雅降级
if (typeof Promise !== "undefined") {
  // 如果支持promise
  const p = Promise.resolve();
  timerFunc = () => {
    p.then(flushCallbacks);
  };
} else if (typeof MutationObserver !== "undefined") {
  // MutationObserver 主要是监听dom变化 也是一个异步方法
  let counter = 1;
  const observer = new MutationObserver(flushCallbacks);
  const textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true,
  });
  timerFunc = () => {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
} else if (typeof setImmediate !== "undefined") {
  // 如果前面都不支持 判断setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks);
  };
} else {
  // 最后降级采用setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0);
  };
}

export function nextTick(cb) {
  // 除了渲染watcher  还有用户自己手动调用的nextTick 一起被收集到数组
  callbacks.push(cb);
  if (!pending) {
    // 如果多次调用nextTick  只会执行一次异步 等异步队列清空之后再把标志变为false
    pending = true;
    timerFunc();
  }
}

4、keepalive的原理?

定义:

keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM

keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

作用:

keep-alive用于缓存组件状态,避免组件的反复渲染,减少浏览器的重排和重绘;

属性:
    • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
    • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
    • max - 数字。最多可以缓存多少组件实例
实现原理:

1、首先在created生命周期内创建缓存对象以及缓存组件的keys集合

2、然后在执行render函数进行组件的渲染

1.获取默认组件 this.$slots.default ;默认缓存第一个子组件

2.把include和exclude结构出来然后判断是否要进行缓存

如果没有缓存返回当前虚拟节点

如果有缓存接着解构缓存对象cache以及key集合,使用cache[key]判断是否在缓存集合中,然后将原有缓存删除并且将新的缓存添加至末尾;如果没有缓存就直接缓存下来,最后进行判断max是否存在,存在即 将下标为0的删除并且添加到最后一项;

3、在mounted里监听include和exclude在该函数内部进行遍历缓存对象(cache),取出每一项的值并与新的缓存规则进行匹配,匹配不上则被删除;

源码:
export default {
  name: "keep-alive",
  abstract: true, //抽象组件

  props: {
    include: patternTypes, //要缓存的组件
    exclude: patternTypes, //要排除的组件
    max: [String, Number], //最大缓存数
  },

  created() {
    this.cache = Object.create(null); //缓存对象  {a:vNode,b:vNode}
    this.keys = []; //缓存组件的key集合 [a,b]
  },

  destroyed() {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys);
    }
  },

  mounted() {
    //动态监听include  exclude
    this.$watch("include", (val) => {
      pruneCache(this, (name) => matches(val, name));
    });
    this.$watch("exclude", (val) => {
      pruneCache(this, (name) => !matches(val, name));
    });
  },

  render() {
    const slot = this.$slots.default; //获取包裹的插槽默认值 获取默认插槽中的第一个组件节点
    const vnode: VNode = getFirstComponentChild(slot); //获取第一个子组件
    // 获取该组件节点的componentOptions
    const componentOptions: ?VNodeComponentOptions =
      vnode && vnode.componentOptions;
    if (componentOptions) {
      // 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag
      const name: ?string = getComponentName(componentOptions);
      const { include, exclude } = this;
      // 不走缓存 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode
      if (
        // not included  不包含
        (include && (!name || !matches(include, name))) ||
        // excluded  排除里面
        (exclude && name && matches(exclude, name))
      ) {
        //返回虚拟节点
        return vnode;
      }

      const { cache, keys } = this;
      // 获取组件的key值
      const key: ?string =
        vnode.key == null
        ? // same constructor may get registered as different local components
        // so cid alone is not enough (#3269)
        componentOptions.Ctor.cid +
        (componentOptions.tag ? `::${componentOptions.tag}` : "")
        : vnode.key;
      // 拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存
      if (cache[key]) {
        //通过key 找到缓存 获取实例
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        remove(keys, key); //通过LRU算法把数组里面的key删掉
        keys.push(key); //把它放在数组末尾
      } else {
        cache[key] = vnode; //没找到就换存下来
        keys.push(key); //把它放在数组末尾
        // prune oldest entry  //如果超过最大值就把数组第0项删掉
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }

      vnode.data.keepAlive = true; //标记虚拟节点已经被缓存
    }
    // 返回虚拟节点
    return vnode || (slot && slot[0]);
  },
};

5、vue-router的原理?

定义:

Vue Router是Vue.js官方提供的路由管理库,用于实现前端路由。它通过改变URL来实现不同页面之间的切换,同时还提供了导航守卫、动态路由、嵌套路由等功能。路由分为hash路由和history路由

hash路由:

1.不美观 由端口号+/#/表示

2.可以通过location.hash获取hash值并且可以通过浏览器监听onHashChange事件来进行路由跳转/切换

3.hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制 hash 的切换

history路由:

1.通过popstate方法来监听路由的变化,

2.pushState 和 repalceState 两个 API 来操作实现 URL的切换

实现原理:

hash路由实现

定义插件并实现全局组件:Router类并挂载实例,然后实现router-link(跳转)和router-view(占位/渲染)组件

vuerouter插件/install全局插件:

        • 在constructor构造函数中缓存path和route的映射关系;
          • 定义一个响应式的current ,vue.util.defineReactive(this,'current',' ') ;
          • 然后监听hashchange和load(页面刷新)事件,事件处理函数都为this.onHashChange.bind(this)修改onHashChange的this指向
          • onHashchange事件处理函数内保存了当前的路由信息('/'或者当前hash值的#后的信息)
        • install使用mixin混入将this.$options.router挂载到vue原型上;然后在进行注册router-link和router-view全局组件
          • router-link内部为跳转到指定路由
          • router-view
            • 内部先创建要渲染的组件component
            • 将路由映射对象和当前路由进行判断是否存在,存在则保存当前对应的组件
            • 最后return返回 当前组件
源码:
// 我们的插件:
// 1.实现一个Router类并挂载期实例
// 2.实现两个全局组件router-link和router-view
let Vue;

class VueRouter {
  // 核心任务:
  // 1.监听url变化
  constructor(options) {
    this.$options = options;

    // 缓存path和route映射关系
    // 这样找组件更快
    this.routeMap = {}
    this.$options.routes.forEach(route => {
      this.routeMap[route.path] = route
    })

    // 数据响应式
    // 定义一个响应式的current,则如果他变了,那么使用它的组件会rerender
    Vue.util.defineReactive(this, 'current', '')

    // 请确保onHashChange中this指向当前实例
    window.addEventListener('hashchange', this.onHashChange.bind(this))
    window.addEventListener('load', this.onHashChange.bind(this))
  }

  onHashChange() {
    // console.log(window.location.hash);
    this.current = window.location.hash.slice(1) || '/'
  }
}

// 插件需要实现install方法
// 接收一个参数,Vue构造函数,主要用于数据响应式
VueRouter.install = function (_Vue) {
  // 保存Vue构造函数在VueRouter中使用
  Vue = _Vue

  // 任务1:使用混入来做router挂载这件事情
  Vue.mixin({
    beforeCreate() {
      // 只有根实例才有router选项
      if (this.$options.router) {
        Vue.prototype.$router = this.$options.router
      }

    }
  })

  // 任务2:实现两个全局组件
  // router-link: 生成一个a标签,在url后面添加#
  // <a href="#/about">aaaa</a>
  // <router-link to="/about">aaa</router-link>
  Vue.component('router-link', {
    props: {
      to: {
        type: String,
        required: true
      },
    },
    render(h) {
      // h(tag, props, children)
      return h('a',
               { attrs: { href: '#' + this.to } },
               this.$slots.default
              )
      // 使用jsx
      // return <a href={'#'+this.to}>{this.$slots.default}</a>
    }
  })
  Vue.component('router-view', {
    render(h) {
      // 根据current获取组件并render
      // current怎么获取?
      // console.log('render',this.$router.current);
      // 获取要渲染的组件
      let component = null
      const { routeMap, current } = this.$router
      if (routeMap[current]) {
        component = routeMap[current].component
      }
      return h(component)
    }
  })
}

export default VueRouter

6、vuex的原理?

定义:

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化

五大核心属性:
      1. state:基本数据(数据源存放地)
      2. getters:从基本数据派生出来的数据;
      3. mutations:提交更改数据的方法,同步
      4. action:像一个装饰器,包裹mutations,使之可以异步
      5. modules:模块化Vuex
优/缺点:

统一管理数据;

如果使用不当就会造成全局数据紊乱;

实现原理:

使用mixin混入将this.options.$store方法挂载到vue原型上(vue.prototype.$store)

1.首先定义响应式的state

****options 为 this.$store**

data:{ $$state:options.state }

2.将mutations和actions挂载到this上,指定commit和dispatch的this执行,都为this

3.使用get/set state()将state变为只读形式不可以直接被修改.

4.定义commit和dispatch方法,实现思路基本一样只不过dispatch执行时最好在内部写一个定时器

commit :接收两个参数,第一个参数为type(函数名称)第二个参数为payload(传递的参数); 使用this._mutations[type]判断是否存在,不存在抛出错误,存在则执行回调并且传入两个参数(this.state,payload)

dispatch::接收两个参数,第一个参数为type(函数名称)第二个参数为payload(传递的参数); 使用this._actions[type]判断是否存在,不存在抛出错误,存在则执行回调并且传入两个参数(this,payload)

源码:
// 目标1:实现Store类,管理state(响应式的),commit方法和dispatch方法
// 目标2:封装一个插件,使用更容易使用
let Vue;

class Store {
  constructor(options) {
    // 定义响应式的state
    // this.$store.state.xx
    // 借鸡生蛋
    this._vm = new Vue({
      data: {
        $$state: options.state
      }
    })

    this._mutations = options.mutations
    this._actions = options.actions

    // 绑定this指向
    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)
  }

  // 只读
  get state() {
    return this._vm._data.$$state
  }

  set state(val) {
    console.error('不能直接赋值呀,请换别的方式!!天王盖地虎!!');

  }

  // 实现commit方法,可以修改state
  commit(type, payload) {
    // 拿出mutations中的处理函数执行它
    const entry = this._mutations[type]
    if (!entry) {
      console.error('未知mutaion类型');
      return
    }

    entry(this.state, payload)
  }

  dispatch(type, payload) {
    const entry = this._actions[type]

    if (!entry) {
      console.error('未知action类型');
      return
    }

    // 上下文可以传递当前store实例进去即可
    entry(this, payload)
  }
}

function install(_Vue){
  Vue = _Vue

  // 混入store实例
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    }
  })
}

// { Store, install }相当于Vuex
// 它必须实现install方法
export default { Store, install }

7、Pinia的原理?

pinia是vue3用来实现全局状态管理;

相比于vuex来说pinia使用起来比较灵活;

pinia中只有state、getters和actions;

state是一个回调函数。用于存放数据;

actions同时支持异步和同步任务;

getters:计算属性用于计算和获取state的数据

源码:
// /store/index.js
import createPinia from "./createPinia";
import defineStore from "./defineStore";

export { createPinia, defineStore }

// main.js
import {createApp} from 'vue'
import {createPinia} from './store/index.js'
let app = createApp()
app.use(createPinia())


//store/useStore.js
import {defineStore} from 'pinia';
export const useStore = defineStore('main',{
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
  },
})
  
// todolist.vue
<template>
    <div>12456--{{ store.todos }}</div>
    <button @click="store.addTodo('1234')">++++</button>  
</template>
<script setup>
import { ref, reactive } from 'vue';
import useStore from '../stores/index.js';
let store = useStore();
</script>

// /store/createPinia.js
import { reactive } from 'vue'; // 导入reactive函数,用于创建响应式对象
import patch from './apis'; // 导入patch模块

export default () => {
    const piniaStore = reactive({}); // 创建一个响应式对象piniaStore

    function setSubStore(name, store) { // 定义一个setSubStore函数,用于设置子存储
        if (!piniaStore[name]) { // 如果piniaStore中不存在该名称的子存储
            piniaStore[name] = store; // 将store赋值给piniaStore的name属性
            piniaStore[name].$patch = patch; 
          // 将patch赋值给piniaStore的name属性的$patch属性
        }
        return piniaStore // 返回piniaStore
    };
    function install(app) { // 定义一个install函数,用于安装插件
        // 安装插件
        app.provide('setSubStore', setSubStore); 
      // 将setSubStore函数提供给app的provide方法向全局注入
    };

    return {
        install, // 返回install函数
    }
}

// /store/defineStore.js
import { reactive, toRef, computed, inject } from 'vue'; // 导入Vue相关函数
// 导出一个函数,接收三个参数:name, state, actions, getters
export default function (name, { state, actions, getters }) { 
    let store = {}; // 定义一个空对象store

    // 定义state
    if (state && typeof state === 'function') { // 如果state存在且是一个函数
        const _state = state() // 调用state函数并将结果赋值给_state
        store.$state = reactive(_state) 
        // 将_state转换为响应式对象并赋值给store的$state属性
        for (let key in _state) { // 遍历_state的属性
            store[key] = toRef(store.$state, key); 
          // 将store.$state的属性转换为ref对象并赋值给store的同名属性
        }
    }
    if (actions && Object.keys(actions).length > 0) { // 如果actions存在且有属性
        for (let method in actions) { // 遍历actions的属性
            store[method] = actions[method] // 将actions的属性赋值给store的同名属性
        }
    }
  // 如果getters存在且有属性
    if (getters && Object.keys(getters).length > 0) { 
       // 遍历getters的属性
        for (let key in getters) {
          // 将getters的属性转换为计算属性并赋值给store的同名属性
            store[key] = computed(() => getters[key].call(store.$state)) 
          // 将store的计算属性赋值给store.$state的同名属性
            store.$state[key] = store[key] 
        }
    }
    console.log(store) // 打印store对象
    return () => { // 返回一个函数
      // 使用inject获取全局属性
        const setSubStore = inject('setSubStore'); 
      // 调用setSubStore函数并传入name和store,将返回值赋值给piniaStore
        const piniaStore = setSubStore(name, store) 
      // 返回piniaStore的name属性
        return piniaStore[name]; 
    };

}

  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值