Vue3.0 和 2.0 的响应式原理区别

Vue3.x 改用 Proxy 代替 Object.defineProperty。因为 Proxy 能够间接监听对象和数组的变动,并且有多达 13 种拦挡办法。

相干代码如下

import { mutableHandlers } from "./baseHandlers"; // 代理相干逻辑import { isObject } from "./util"; // 工具办法export function reactive(target) {  // 依据不同参数创立不同响应式对象  return createReactiveObject(target, mutableHandlers);}function createReactiveObject(target, baseHandler) {  if (!isObject(target)) {    return target;  }  const observed = new Proxy(target, baseHandler);  return observed;}const get = createGetter();const set = createSetter();function createGetter() {  return function get(target, key, receiver) {    // 对获取的值进行喷射    const res = Reflect.get(target, key, receiver);    console.log("属性获取", key);    if (isObject(res)) {      // 如果获取的值是对象类型,则返回以后对象的代理对象      return reactive(res);    }    return res;  };}function createSetter() {  return function set(target, key, value, receiver) {    const oldValue = target[key];    const hadKey = hasOwn(target, key);    const result = Reflect.set(target, key, value, receiver);    if (!hadKey) {      console.log("属性新增", key, value);    } else if (hasChanged(value, oldValue)) {      console.log("属性值被批改", key, value);    }    return result;  };}export const mutableHandlers = {  get, // 当获取属性时调用此办法  set, // 当批改属性时调用此办法};

mixin 和 mixins 区别

mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。

Vue.mixin({  beforeCreate() {    // ...逻辑        // 这种形式会影响到每个组件的 beforeCreate 钩子函数  },});

尽管文档不倡议在利用中间接应用 mixin,然而如果不滥用的话也是很有帮忙的,比方能够全局混入封装好的 ajax 或者一些工具函数等等。

mixins 应该是最常应用的扩大组件的形式了。如果多个组件中有雷同的业务逻辑,就能够将这些逻辑剥离进去,通过 mixins 混入代码,比方上拉下拉加载数据这种逻辑等等。
另外须要留神的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。

delete和Vue.delete删除数组的区别

  • delete 只是被删除的元素变成了 empty/undefined 其余的元素的键值还是不变。
  • Vue.delete 间接删除了数组 扭转了数组的键值。

Vue 子组件和父组件执行程序

加载渲染过程:

  1. 父组件 beforeCreate
  2. 父组件 created
  3. 父组件 beforeMount
  4. 子组件 beforeCreate
  5. 子组件 created
  6. 子组件 beforeMount
  7. 子组件 mounted
  8. 父组件 mounted

更新过程:

  1. 父组件 beforeUpdate
  2. 子组件 beforeUpdate
  3. 子组件 updated
  4. 父组件 updated

销毁过程:

  1. 父组件 beforeDestroy
  2. 子组件 beforeDestroy
  3. 子组件 destroyed
  4. 父组件 destoryed

Vue模版编译原理晓得吗,能简略说一下吗?

简略说,Vue的编译过程就是将template转化为render函数的过程。会经验以下阶段:

  • 生成AST树
  • 优化
  • codegen

首先解析模版,生成AST语法树(一种用JavaScript对象的模式来形容整个模板)。 应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。

Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的DOM也不会变动。那么优化过程就是深度遍历AST树,依照相干条件对树节点进行标记。这些被标记的节点(动态节点)咱们就能够跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最初一步是将优化后的AST树转换为可执行的代码

Vue组件data为什么必须是个函数?

  • 根实例对象data能够是对象也能够是函数 (根实例是单例),不会产生数据净化状况
  • 组件实例对象data必须为函数 一个组件被复用屡次的话,也就会创立多个实例。实质上,这些实例用的都是同一个构造函数。如果data是对象的话,对象属于援用类型,会影响到所有的实例。所以为了保障组件不同的实例之间data不抵触,data必须是一个函数,

简版了解

// 1.组件的渲染流程 调用Vue.component -> Vue.extend -> 子类 -> new 子类// Vue.extend 依据用户定义产生一个新的类function Vue() {}function Sub() { // 会将data存起来    this.data = this.constructor.options.data();}Vue.extend = function(options) {    Sub.options = options; // 动态属性    return Sub;}let Child = Vue.extend({    data:()=>( { name: 'zf' })});// 两个组件就是两个实例, 心愿数据互不感化let child1 = new Child();let child2 = new Child();console.log(child1.data.name);child1.data.name = 'poetry';console.log(child2.data.name);// 根不须要 任何的合并操作   根才有vm属性 所以他能够是函数和对象  然而组件mixin他们都没有vm 所以我就能够判断 以后data是不是个函数

相干源码

// 源码地位 src/core/global-api/extend.jsexport function initExtend (Vue: GlobalAPI) {  Vue.extend = function (extendOptions: Object): Function {    extendOptions = extendOptions || {}    const Super = this    const SuperId = Super.cid    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})    if (cachedCtors[SuperId]) {      return cachedCtors[SuperId]    }    const name = extendOptions.name || Super.options.name    if (process.env.NODE_ENV !== 'production' && name) {      validateComponentName(name)    }    const Sub = function VueComponent (options) {      this._init(options)    }    // 子类继承大Vue父类的原型    Sub.prototype = Object.create(Super.prototype)    Sub.prototype.constructor = Sub    Sub.cid = cid++    Sub.options = mergeOptions(      Super.options,      extendOptions    )    Sub['super'] = Super    // For props and computed properties, we define the proxy getters on    // the Vue instances at extension time, on the extended prototype. This    // avoids Object.defineProperty calls for each instance created.    if (Sub.options.props) {      initProps(Sub)    }    if (Sub.options.computed) {      initComputed(Sub)    }    // allow further extension/mixin/plugin usage    Sub.extend = Super.extend    Sub.mixin = Super.mixin    Sub.use = Super.use    // create asset registers, so extended classes    // can have their private assets too.    ASSET_TYPES.forEach(function (type) {      Sub[type] = Super[type]    })    // enable recursive self-lookup    if (name) {       Sub.options.components[name] = Sub // 记录本人 在组件中递归本人  -> jsx    }    // keep a reference to the super options at extension time.    // later at instantiation we can check if Super's options have    // been updated.    Sub.superOptions = Super.options    Sub.extendOptions = extendOptions    Sub.sealedOptions = extend({}, Sub.options)    // cache constructor    cachedCtors[SuperId] = Sub    return Sub  }}

Vue模版编译原理

vue中的模板template无奈被浏览器解析并渲染,因为这不属于浏览器的规范,不是正确的HTML语法,所有须要将template转化成一个JavaScript函数,这样浏览器就能够执行这一个函数并渲染出对应的HTML元素,就能够让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析parse,优化optimize,生成generate,最终生成可执行函数render。

  • 解析阶段:应用大量的正则表达式对template字符串进行解析,将标签、指令、属性等转化为形象语法树AST。
  • 优化阶段:遍历AST,找到其中的一些动态节点并进行标记,不便在页面重渲染的时候进行diff比拟时,间接跳过这一些动态节点,优化runtime的性能。
  • 生成阶段:将最终的AST转化为render函数字符串。

什么是 mixin ?

  • Mixin 使咱们可能为 Vue 组件编写可插拔和可重用的性能。
  • 如果心愿在多个组件之间重用一组组件选项,例如生命周期 hook、 办法等,则能够将其编写为 mixin,并在组件中简略的援用它。
  • 而后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

双向数据绑定的原理

Vue.js 是采纳数据劫持联合发布者-订阅者模式的形式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时公布音讯给订阅者,触发相应的监听回调。次要分为以下几个步骤:

  1. 须要observe的数据对象进行递归遍历,包含子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变动
  2. compile解析模板指令,将模板中的变量替换成数据,而后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,增加监听数据的订阅者,一旦数据有变动,收到告诉,更新视图
  3. Watcher订阅者是Observer和Compile之间通信的桥梁,次要做的事件是: ①在本身实例化时往属性订阅器(dep)外面增加本人 ②本身必须有一个update()办法 ③待属性变动dep.notice()告诉时,能调用本身的update()办法,并触发Compile中绑定的回调,则功成身退。
  4. MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听本人的model数据变动,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变动 -> 视图更新;视图交互变动(input) -> 数据model变更的双向绑定成果。

参考:前端vue面试题具体解答

Vue中的key到底有什么用?

key是为Vue中的vnode标记的惟一id,通过这个key,咱们的diff操作能够更精确、更疾速

diff算法的过程中,先会进行新旧节点的首尾穿插比照,当无奈匹配的时候会用新节点的key与旧节点进行比对,而后超出差别.

diff程能够概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量互相比拟,一共有4种比拟形式。如果4种比拟都没匹配,如果设置了key,就会用key进行比拟,在比拟的过程中,变量会往两头靠,一旦StartIdx>EndIdx表明oldCh和newCh至多有一个曾经遍历完了,就会完结比拟,这四种比拟形式就是首、尾、旧尾新头、旧头新尾.
  • 精确: 如果不加key,那么vue会抉择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug.
  • 疾速: key的唯一性能够被Map数据结构充分利用,相比于遍历查找的工夫复杂度O(n),Map的工夫复杂度仅仅为O(1).

应用 Object.defineProperty() 来进行数据劫持有什么毛病?

在对一些属性进行操作时,应用这种办法无奈拦挡,比方通过下标形式批改数组数据或者给对象新增属性,这都不能触发组件的从新渲染,因为 Object.defineProperty 不能拦挡到这些操作。更准确的来说,对于数组而言,大部分操作都是拦挡不到的,只是 Vue 外部通过重写函数的形式解决了这个问题。

在 Vue3.0 中曾经不应用这种形式了,而是通过应用 Proxy 对对象进行代理,从而实现数据劫持。应用Proxy 的益处是它能够完满的监听到任何形式的数据扭转,惟一的毛病是兼容性的问题,因为 Proxy 是 ES6 的语法。

vue-router 路由钩子函数是什么 执行程序是什么

路由钩子的执行流程, 钩子函数品种有:全局守卫、路由守卫、组件守卫

残缺的导航解析流程:

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创立好的组件实例会作为回调函数的参数传入。

用过pinia吗?有什么长处?

1. pinia是什么?

  • Vue3中,能够应用传统的Vuex来实现状态治理,也能够应用最新的pinia来实现状态治理,咱们来看看官网如何解释pinia的:PiniaVue 的存储库,它容许您跨组件/页面共享状态。
  • 实际上,pinia就是Vuex的升级版,官网也说过,为了尊重原作者,所以取名pinia,而没有取名Vuex,所以大家能够间接将pinia比作为Vue3Vuex

2. 为什么要应用pinia?

  • Vue2Vue3都反对,这让咱们同时应用Vue2Vue3的小伙伴都能很快上手。
  • pinia中只有stategetteraction,摈弃了Vuex中的MutationVuexmutation始终都不太受小伙伴们的待见,pinia间接摈弃它了,这无疑缩小了咱们工作量。
  • piniaaction反对同步和异步,Vuex不反对
  • 良好的Typescript反对,毕竟咱们Vue3都举荐应用TS来编写,这个时候应用pinia就十分适合了
  • 无需再创立各个模块嵌套了,Vuex中如果数据过多,咱们通常分模块来进行治理,稍显麻烦,而pinia中每个store都是独立的,相互不影响。
  • 体积十分小,只有1KB左右。
  • pinia反对插件来扩大本身性能。
  • 反对服务端渲染

3. pinna应用

pinna文档(opens new window)

  1. 筹备工作

咱们这里搭建一个最新的Vue3 + TS + Vite我的项目

npm create [email protected] my-vite-app --template vue-ts
  1. pinia根底应用
yarn add pinia
// main.tsimport { createApp } from "vue";import App from "./App.vue";import { createPinia } from "pinia";const pinia = createPinia();const app = createApp(App);app.use(pinia);app.mount("#app");

2.1 创立store

//sbinsrc/store/user.tsimport { defineStore } from 'pinia'// 第一个参数是应用程序中 store 的惟一 idexport const useUsersStore = defineStore('users', {  // 其它配置项})

创立store很简略,调用pinia中的defineStore函数即可,该函数接管两个参数:

  • name:一个字符串,必传项,该store的惟一id
  • options:一个对象,store的配置项,比方配置store内的数据,批改数据的办法等等。

咱们能够定义任意数量的store,因为咱们其实一个store就是一个函数,这也是pinia的益处之一,让咱们的代码扁平化了,这和Vue3的实现思维是一样的

2.2 应用store

<!-- src/App.vue --><script setup lang="ts">import { useUsersStore } from "../src/store/user";const store = useUsersStore();console.log(store);</script>

2.3 增加state

export const useUsersStore = defineStore("users", {  state: () => {    return {      name: "test",      age: 20,      sex: "男",    };  },});

2.4 读取state数据

<template>  <img alt="Vue logo" src="./assets/logo.png" />  <p>姓名:{{ name }}</p>  <p>年龄:{{ age }}</p>  <p>性别:{{ sex }}</p></template><script setup lang="ts">import { ref } from "vue";import { useUsersStore } from "../src/store/user";const store = useUsersStore();const name = ref<string>(store.name);const age = ref<number>(store.age);const sex = ref<string>(store.sex);</script>

上段代码中咱们间接通过store.age等形式获取到了store存储的值,然而大家有没有发现,这样比拟繁琐,咱们其实能够用解构的形式来获取值,使得代码更简洁一点

import { useUsersStore, storeToRefs } from "../src/store/user";const store = useUsersStore();const { name, age, sex } = storeToRefs(store); // storeToRefs获取的值是响应式的

2.5 批改state数据

<template>  <img alt="Vue logo" src="./assets/logo.png" />  <p>姓名:{{ name }}</p>  <p>年龄:{{ age }}</p>  <p>性别:{{ sex }}</p>  <button @click="changeName">更改姓名</button></template><script setup lang="ts">import child from './child.vue';import { useUsersStore, storeToRefs } from "../src/store/user";const store = useUsersStore();const { name, age, sex } = storeToRefs(store);const changeName = () => {  store.name = "张三";  console.log(store);};</script>

2.6 重置state

  • 有时候咱们批改了state数据,想要将它还原,这个时候该怎么做呢?就比方用户填写了一部分表单,忽然想重置为最初始的状态。
  • 此时,咱们间接调用store$reset()办法即可,持续应用咱们的例子,增加一个重置按钮
<button @click="reset">重置store</button>// 重置storeconst reset = () => {  store.$reset();};

当咱们点击重置按钮时,store中的数据会变为初始状态,页面也会更新

2.7 批量更改state数据

如果咱们一次性须要批改很多条数据的话,有更加简便的办法,应用store$patch办法,批改app.vue代码,增加一个批量更改数据的办法

<button @click="patchStore">批量批改数据</button>// 批量批改数据const patchStore = () => {  store.$patch({    name: "张三",    age: 100,    sex: "女",  });};
  • 有教训的小伙伴可能发现了,咱们采纳这种批量更改的形式仿佛代价有一点大,如果咱们state中有些字段无需更改,然而依照上段代码的写法,咱们必须要将state中的所有字段例举出了。
  • 为了解决该问题,pinia提供的$patch办法还能够接管一个回调函数,它的用法有点像咱们的数组循环回调函数了。
store.$patch((state) => {  state.items.push({ name: 'shoes', quantity: 1 })  state.hasChanged = true})

2.8 间接替换整个state

pinia提供了办法让咱们间接替换整个state对象,应用store$state办法

store.$state = { counter: 666, name: '张三' }

上段代码会将咱们提前申明的state替换为新的对象,可能这种场景用得比拟少

  1. getters属性
  2. gettersdefineStore参数配置项外面的另一个属性
  3. 能够把getter设想成Vue中的计算属性,它的作用就是返回一个新的后果,既然它和Vue中的计算属性相似,那么它必定也是会被缓存的,就和computed一样

3.1 增加getter

export const useUsersStore = defineStore("users", {  state: () => {    return {      name: "test",      age: 10,      sex: "男",    };  },  getters: {    getAddAge: (state) => {      return state.age + 100;    },  },})

上段代码中咱们在配置项参数中增加了getter属性,该属性对象中定义了一个getAddAge办法,该办法会默认接管一个state参数,也就是state对象,而后该办法返回的是一个新的数据

3.2 应用getter

<template>  <p>新年龄:{{ store.getAddAge }}</p>  <button @click="patchStore">批量批改数据</button></template><script setup lang="ts">import { useUsersStore } from "../src/store/user";const store = useUsersStore();// 批量批改数据const patchStore = () => {  store.$patch({    name: "张三",    age: 100,    sex: "女",  });};</script>

上段代码中咱们间接在标签上应用了store.gettAddAge办法,这样能够保障响应式,其实咱们state中的name等属性也能够以此种形式间接在标签上应用,也能够放弃响应式

3.3 getter中调用其它getter

export const useUsersStore = defineStore("users", {  state: () => {    return {      name: "test",      age: 20,      sex: "男",    };  },  getters: {    getAddAge: (state) => {      return state.age + 100;    },    getNameAndAge(): string {      return this.name + this.getAddAge; // 调用其它getter    },  },});

3.3 getter传参

export const useUsersStore = defineStore("users", {  state: () => {    return {      name: "test",      age: 20,      sex: "男",    };  },  getters: {    getAddAge: (state) => {      return (num: number) => state.age + num;    },    getNameAndAge(): string {      return this.name + this.getAddAge; // 调用其它getter    },  },});
<p>新年龄:{{ store.getAddAge(1100) }}</p>
  1. actions属性
  2. 后面咱们提到的stategetters属性都次要是数据层面的,并没有具体的业务逻辑代码,它们两个就和咱们组件代码中的data数据和computed计算属性一样。
  3. 那么,如果咱们有业务代码的话,最好就是卸载actions属性外面,该属性就和咱们组件代码中的methods类似,用来搁置一些解决业务逻辑的办法。
  4. actions属性值同样是一个对象,该对象外面也是存储的各种各样的办法,包含同步办法和异步办法

4.1 增加actions

export const useUsersStore = defineStore("users", {  state: () => {    return {      name: "test",      age: 20,      sex: "男",    };  },  getters: {    getAddAge: (state) => {      return (num: number) => state.age + num;    },    getNameAndAge(): string {      return this.name + this.getAddAge; // 调用其它getter    },  },  actions: {    // 在理论场景中,该办法能够是任何逻辑,比方发送申请、存储token等等。大家把actions办法当作一个一般的办法即可,非凡之处在于该办法外部的this指向的是以后store    saveName(name: string) {      this.name = name;    },  },});

4.2 应用actions

应用actions中的办法也非常简单,比方咱们在App.vue中想要调用该办法

const saveName = () => {  store.saveName("poetries");};

总结

pinia的知识点很少,如果你有Vuex根底,那么学起来更是大海捞针

pinia无非就是以下3个大点:

  • state
  • getters
  • actions

Vue性能优化

编码优化

  • 事件代理
  • keep-alive
  • 拆分组件
  • key 保障唯一性
  • 路由懒加载、异步组件
  • 防抖节流

Vue加载性能优化

  • 第三方模块按需导入( babel-plugin-component
  • 图片懒加载

用户体验

  • app-skeleton 骨架屏
  • shellap p壳
  • pwa

SEO优化

  • 预渲染

谈谈对keep-alive的理解

keep-alive 能够实现组件的缓存,当组件切换时不会对以后组件进行卸载。罕用的2个属性 include/exclude ,2个生命周期 activated deactivated

理解nextTick吗?

异步办法,异步渲染最初一步,与JS事件循环分割严密。次要应用了宏工作微工作(setTimeoutpromise那些),定义了一个异步办法,屡次调用nextTick会将办法存入队列,通过异步办法清空以后队列。

Vue的长处

  • 轻量级框架:只关注视图层,是一个构建数据的视图汇合,大小只有几十 kb
  • 简略易学:国人开发,中文文档,不存在语言障碍 ,易于了解和学习;
  • 双向数据绑定:保留了 angular 的特点,在数据操作方面更为简略;
  • 组件化:保留了 react 的长处,实现了 html 的封装和重用,在构建单页面利用方面有着独特的劣势;
  • 视图,数据,构造拆散:使数据的更改更为简略,不须要进行逻辑代码的批改,只须要操作数据就能实现相干操作;
  • 虚构DOM:dom 操作是十分消耗性能的,不再应用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种形式;
  • 运行速度更快:相比拟于 react 而言,同样是操作虚构 dom,就性能而言, vue 存在很大的劣势。

如何从实在DOM到虚构DOM

波及到Vue中的模板编译原理,次要过程:

  1. 将模板转换成 ast 树, ast 用对象来形容实在的JS语法(将实在DOM转换成虚构DOM)
  2. 优化树
  3. ast 树生成代码

Vue3.0有什么更新

(1)监测机制的扭转

  • 3.0 将带来基于代理 Proxy的 observer 实现,提供全语言笼罩的反馈性跟踪。
  • 打消了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限度:

(2)只能监测属性,不能监测对象

  • 检测属性的增加和删除;
  • 检测数组索引和长度的变更;
  • 反对 Map、Set、WeakMap 和 WeakSet。

(3)模板

  • 作用域插槽,2.x 的机制导致作用域插槽变了,父组件会从新渲染,而 3.0 把作用域插槽改成了函数的形式,这样只会影响子组件的从新渲染,晋升了渲染的性能。
  • 同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来不便习惯间接应用 api 来生成 vdom 。

(4)对象式的组件申明形式

  • vue2.x 中的组件是通过申明的形式传入一系列 option,和 TypeScript 的联合须要通过一些装璜器的形式来做,尽管能实现性能,然而比拟麻烦。
  • 3.0 批改了组件的申明形式,改成了类式的写法,这样使得和 TypeScript 的联合变得很容易

(5)其它方面的更改

  • 反对自定义渲染器,从而使得 weex 能够通过自定义渲染器的形式来扩大,而不是间接 fork 源码来改的形式。
  • 反对 Fragment(多个根节点)和 Protal(在 dom 其余局部渲染组建内容)组件,针对一些非凡的场景做了解决。
  • 基于 tree shaking 优化,提供了更多的内置性能。

v-on能够监听多个办法吗?

能够监听多个办法

<input type="text" :value="name" @input="onInput" @focus="onFocus" @blur="onBlur" />

v-on 罕用修饰符

  • .stop 该修饰符将阻止事件向上冒泡。同理于调用 event.stopPropagation() 办法
  • .prevent 该修饰符会阻止以后事件的默认行为。同理于调用 event.preventDefault() 办法
  • .self 该指令只当事件是从事件绑定的元素自身触发时才触发回调
  • .once 该修饰符示意绑定的事件只会被触发一次