Composition API 与 Options API 有什么不同

剖析

Vue3最重要更新之一就是Composition API,它具备一些列长处,其中不少是针对Options API裸露的一些问题量身打造。是Vue3举荐的写法,因而把握好Composition API利用对把握好Vue3至关重要

What is Composition API?(opens new window)

  • Composition API呈现就是为了解决Options API导致雷同性能代码扩散的景象

体验

Composition API能更好的组织代码,上面用composition api能够提取为useCount(),用于组合、复用

compositon api提供了以下几个函数:

  • setup
  • ref
  • reactive
  • watchEffect
  • watch
  • computed
  • toRefs
  • 生命周期的hooks

答复范例

  1. Composition API是一组API,包含:Reactivity API生命周期钩子依赖注入,使用户能够通过导入函数形式编写vue组件。而Options API则通过申明组件选项的对象模式编写组件
  2. Composition API最次要作用是可能简洁、高效复用逻辑。解决了过来Options APImixins的各种毛病;另外Composition API具备更加麻利的代码组织能力,很多用户喜爱Options API,认为所有货色都有固定地位的选项搁置代码,然而单个组件增长过大之后这反而成为限度,一个逻辑关注点扩散在组件各处,造成代码碎片,保护时须要重复横跳,Composition API则能够将它们无效组织在一起。最初Composition API领有更好的类型推断,对ts反对更敌对,Options API在设计之初并未思考类型推断因素,尽管官网为此做了很多简单的类型体操,确保用户能够在应用Options API时取得类型推断,然而还是没方法用在mixinsprovide/inject
  3. Vue3首推Composition API,然而这会让咱们在代码组织上多花点心理,因而在抉择上,如果咱们我的项目属于中低复杂度的场景,Options API仍是一个好抉择。对于那些大型,高扩大,强保护的我的项目上,Composition API会取得更大收益

可能的诘问

  1. Composition API是否和Options API一起应用?

能够在同一个组件中应用两个script标签,一个应用vue3,一个应用vue2写法,一起应用没有问题

<!-- vue3 --><script setup>  // vue3写法</script><!-- 降级vue2 --><script>  export default {    data() {},    methods: {}  }</script>

v-if和v-show区别

  • v-show暗藏则是为该元素增加css--display:nonedom元素仍旧还在。v-if显示暗藏是将dom元素整个增加或删除
  • 编译过程:v-if切换有一个部分编译/卸载的过程,切换过程中适合地销毁和重建外部的事件监听和子组件;v-show只是简略的基于css切换
  • 编译条件:v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染
  • v-showfalse变为true的时候不会触发组件的生命周期
  • v-iffalse变为true的时候,触发组件的beforeCreatecreatebeforeMountmounted钩子,由true变为false的时候触发组件的beforeDestorydestoryed办法
  • 性能耗费:v-if有更高的切换耗费;v-show有更高的初始渲染耗费

v-show与v-if的应用场景

  • v-ifv-show 都能管制dom元素在页面的显示
  • v-if 相比 v-show 开销更大的(间接操作dom节点减少与删除)
  • 如果须要十分频繁地切换,则应用 v-show 较好
  • 如果在运行时条件很少扭转,则应用 v-if 较好

v-show与v-if原理剖析

  1. v-show原理

不论初始条件是什么,元素总是会被渲染

咱们看一下在vue中是如何实现的

代码很好了解,有transition就执行transition,没有就间接设置display属性

// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vShow.tsexport const vShow: ObjectDirective<VShowElement> = {  beforeMount(el, { value }, { transition }) {    el._vod = el.style.display === 'none' ? '' : el.style.display    if (transition && value) {      transition.beforeEnter(el)    } else {      setDisplay(el, value)    }  },  mounted(el, { value }, { transition }) {    if (transition && value) {      transition.enter(el)    }  },  updated(el, { value, oldValue }, { transition }) {    // ...  },  beforeUnmount(el, { value }) {    setDisplay(el, value)  }}
  1. v-if原理

v-if在实现上比v-show要简单的多,因为还有else else-if 等条件须要解决,这里咱们也只摘抄源码中解决 v-if 的一小部分

返回一个node节点,render函数通过表达式的值来决定是否生成DOM

// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.tsexport const transformIf = createStructuralDirectiveTransform(  /^(if|else|else-if)$/,  (node, dir, context) => {    return processIf(node, dir, context, (ifNode, branch, isRoot) => {      // ...      return () => {        if (isRoot) {          ifNode.codegenNode = createCodegenNodeForBranch(            branch,            key,            context          ) as IfConditionalExpression        } else {          // attach this branch's codegen node to the v-if root.          const parentCondition = getParentCondition(ifNode.codegenNode!)          parentCondition.alternate = createCodegenNodeForBranch(            branch,            key + ifNode.branches.length - 1,            context          )        }      }    })  })

v-if和v-for哪个优先级更高

  • 实际中不应该把v-forv-if放一起
  • vue2中,v-for的优先级是高于v-if,把它们放在一起,输入的渲染函数中能够看出会先执行循环再判断条件,哪怕咱们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比拟节约;另外须要留神的是在vue3中则齐全相同,v-if的优先级高于v-for,所以v-if执行时,它调用的变量还不存在,就会导致异样
  • 通常有两种状况下导致咱们这样做:

    • 为了过滤列表中的我的项目 (比方 v-for="user in users" v-if="user.isActive")。此时定义一个计算属性 (比方 activeUsers),让其返回过滤后的列表即可(比方users.filter(u=>u.isActive)
    • 为了防止渲染本应该被暗藏的列表 (比方 v-for="user in users" v-if="shouldShowUsers")。此时把 v-if 挪动至容器元素上 (比方 ulol)或者外面包一层template即可
  • 文档中明确指出永远不要把 v-ifv-for 同时用在同一个元素上,显然这是一个重要的注意事项
  • 源码外面对于代码生成的局部,可能清晰的看到是先解决v-if还是v-for,程序上vue2vue3正好相同,因而产生了一些症状的不同,然而不管怎样都是不能把它们写在一起的

vue2.x源码剖析

在vue模板编译的时候,会将指令系统转化成可执行的render函数

编写一个p标签,同时应用v-ifv-for

<div id="app">  <p v-if="isShow" v-for="item in items">    {{ item.title }}  </p></div>

创立vue实例,寄存isShowitems数据

const app = new Vue({  el: "#app",  data() {    return {      items: [        { title: "foo" },        { title: "baz" }]    }  },  computed: {    isShow() {      return this.items && this.items.length > 0    }  }})

模板指令的代码都会生成在render函数中,通过app.$options.render就能失去渲染函数

ƒ anonymous() {  with (this) { return     _c('div', { attrs: { "id": "app" } },     _l((items), function (item)     { return (isShow) ? _c('p', [_v("\n" + _s(item.title) + "\n")]) : _e() }), 0) }}
  • _lvue的列表渲染函数,函数外部都会进行一次if判断
  • 初步失去论断:v-for优先级是比v-if高
  • 再将v-forv-if置于不同标签
<div id="app">  <template v-if="isShow">    <p v-for="item in items">{{item.title}}</p>  </template></div>

再输入下render函数

ƒ anonymous() {  with(this){return     _c('div',{attrs:{"id":"app"}},    [(isShow)?[_v("\n"),    _l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}}

这时候咱们能够看到,v-forv-if作用在不同标签时候,是先进行判断,再进行列表的渲染

咱们再在查看下vue源码

源码地位:\vue-dev\src\compiler\codegen\index.js

export function genElement (el: ASTElement, state: CodegenState): string {  if (el.parent) {    el.pre = el.pre || el.parent.pre  }  if (el.staticRoot && !el.staticProcessed) {    return genStatic(el, state)  } else if (el.once && !el.onceProcessed) {    return genOnce(el, state)  } else if (el.for && !el.forProcessed) {    return genFor(el, state)  } else if (el.if && !el.ifProcessed) {    return genIf(el, state)  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {    return genChildren(el, state) || 'void 0'  } else if (el.tag === 'slot') {    return genSlot(el, state)  } else {    // component or element    ...}

在进行if判断的时候,v-for是比v-if先进行判断

最终论断:v-for优先级比v-if

watch 原理

watch 实质上是为每个监听属性 setter 创立了一个 watcher,当被监听的属性更新时,调用传入的回调函数。常见的配置选项有 deepimmediate,对应原理如下

  • deep:深度监听对象,为对象的每一个属性创立一个 watcher,从而确保对象的每一个属性更新时都会触发传入的回调函数。次要起因在于对象属于援用类型,单个属性的更新并不会触发对象 setter,因而引入 deep 可能很好地解决监听对象的问题。同时也会引入判断机制,确保在多个属性更新时回调函数仅触发一次,防止性能节约。
  • immediate:在初始化时间接调用回调函数,能够通过在 created 阶段手动调用回调函数实现雷同的成果

用过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 vite@latest 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

Vue3.0 为什么要用 proxy?

在 Vue2 中, 0bject.defineProperty 会扭转原始数据,而 Proxy 是创建对象的虚构示意,并提供 set 、get 和 deleteProperty 等处理器,这些处理器可在拜访或批改原始对象上的属性时进行拦挡,有以下特点∶

  • 不需用应用 Vue.$setVue.$delete 触发响应式。
  • 全方位的数组变化检测,打消了Vue2 有效的边界状况。
  • 反对 Map,Set,WeakMap 和 WeakSet。

Proxy 实现的响应式原理与 Vue2的实现原理雷同,实现形式大同小异∶

  • get 收集依赖
  • Set、delete 等触发依赖
  • 对于汇合类型,就是对汇合对象的办法做一层包装:原办法执行后执行依赖相干的收集或触发逻辑。

参考 前端进阶面试题具体解答

$route$router的区别

  • $route是“路由信息对象”,包含pathparamshashqueryfullPathmatchedname等路由信息参数。
  • $router是“路由实例”对象包含了路由的跳转办法,钩子函数等

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

  • delete只是被删除的元素变成了 empty/undefined 其余的元素的键值还是不变。
  • Vue.delete间接删除了数组 扭转了数组的键值。
var a=[1,2,3,4]var b=[1,2,3,4]delete a[0]console.log(a)  //[empty,2,3,4]this.$delete(b,0)console.log(b)  //[2,3,4]

Vue 修饰符有哪些

vue中修饰符分为以下五种

  • 表单修饰符
  • 事件修饰符
  • 鼠标按键修饰符
  • 键值修饰符
  • v-bind修饰符

1. 表单修饰符

在咱们填写表单的时候用得最多的是input标签,指令用得最多的是v-model

对于表单的修饰符有如下:

  • lazy

在咱们填完信息,光标来到标签的时候,才会将值赋予给value,也就是在change事件之后再进行信息同步

<input type="text" v-model.lazy="value"><p>{{value}}</p>
  • trim

主动过滤用户输出的首空格字符,而两头的空格不会过滤

<input type="text" v-model.trim="value">
  • number

主动将用户的输出值转为数值类型,但如果这个值无奈被parseFloat解析,则会返回原来的值

<input v-model.number="age" type="number">

2. 事件修饰符

事件修饰符是对事件捕捉以及指标进行了解决,有如下修饰符

  • .stop 阻止了事件冒泡,相当于调用了event.stopPropagation办法
<div @click="shout(2)">  <button @click.stop="shout(1)">ok</button></div>//只输入1
  • .prevent 阻止了事件的默认行为,相当于调用了event.preventDefault办法
<form v-on:submit.prevent="onSubmit"></form>
  • .capture 应用事件捕捉模式,使事件触发从蕴含这个元素的顶层开始往下触发
<div @click.capture="shout(1)">    obj1<div @click.capture="shout(2)">    obj2<div @click="shout(3)">    obj3<div @click="shout(4)">    obj4</div></div></div></div>// 输入构造: 1 2 4 3 
  • .self 只当在 event.target 是以后元素本身时触发处理函数
<div v-on:click.self="doThat">...</div>
应用修饰符时,程序很重要;相应的代码会以同样的程序产生。因而,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素本身的点击
  • .once 绑定了事件当前只能触发一次,第二次就不会触发
<button @click.once="shout(1)">ok</button>
  • .passive 通知浏览器你不想阻止事件的默认行为

在挪动端,当咱们在监听元素滚动事件的时候,会始终触发onscroll事件会让咱们的网页变卡,因而咱们应用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符

<!-- 滚动事件的默认行为 (即滚动行为) 将会立刻触发 --><!-- 而不会期待 `onScroll` 实现  --><!-- 这其中蕴含 `event.preventDefault()` 的状况 --><div v-on:scroll.passive="onScroll">...</div>
  • 不要把 .passive.prevent 一起应用,因为 .prevent 将会被疏忽,同时浏览器可能会向你展现一个正告。
  • passive 会通知浏览器你不想阻止事件的默认行为
  • native 让组件变成像html内置标签那样监听根元素的原生事件,否则组件上应用 v-on 只会监听自定义事件
<my-component v-on:click.native="doSomething"></my-component><!-- 应用.native修饰符来操作一般HTML标签是会令事件生效的 -->

3. 鼠标按钮修饰符

鼠标按钮修饰符针对的就是左键、右键、中键点击,有如下:

  • .left 左键点击
  • .right 右键点击
  • .middle 中键点击
<button @click.left="shout(1)">ok</button><button @click.right="shout(1)">ok</button><button @click.middle="shout(1)">ok</button>

4. 键盘事件的修饰符

键盘修饰符是用来润饰键盘事件(onkeyuponkeydown)的,有如下:

keyCode存在很多,但vue为咱们提供了别名,分为以下两种:

  • 一般键entertabdeletespaceescupdownleftright...)
  • 零碎润饰键ctrlaltmetashift...)
<!-- 只有按键为keyCode的时候才触发 --><input type="text" @keyup.keyCode="shout()">

还能够通过以下形式自定义一些全局的键盘码别名

Vue.config.keyCodes.f2 = 113

5. v-bind修饰符

v-bind修饰符次要是为属性进行操作,用来别离有如下:

  • async 能对props进行一个双向绑定
//父组件<comp :myMessage.sync="bar"></comp> //子组件this.$emit('update:myMessage',params);

以上这种办法相当于以下的简写

//父亲组件<comp :myMessage="bar" @update:myMessage="func"></comp>func(e){ this.bar = e;}//子组件jsfunc2(){  this.$emit('update:myMessage',params);}

应用async须要留神以下两点:

  • 应用sync的时候,子组件传递的事件名格局必须为update:value,其中value必须与子组件中props中申明的名称完全一致
  • 留神带有 .sync 修饰符的 v-bind 不能和表达式一起应用
  • prop 设置自定义标签属性,防止裸露数据,避免净化HTML构造
<input id="uid" title="title1" value="1" :index.prop="index">
  • camel 将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox
<svg :viewBox="viewBox"></svg>

利用场景

依据每一个修饰符的性能,咱们能够失去以下修饰符的利用场景:

  • .stop:阻止事件冒泡
  • .native:绑定原生事件
  • .once:事件只执行一次
  • .self :将事件绑定在本身身上,相当于阻止事件冒泡
  • .prevent:阻止默认事件
  • .caption:用于事件捕捉
  • .once:只触发一次
  • .keyCode:监听特定键盘按下
  • .right:右键

如果让你从零开始写一个vuex,说说你的思路

思路剖析

这个题目很有难度,首先思考vuex解决的问题:存储用户全局状态并提供治理状态API。

  • vuex需要剖析
  • 如何实现这些需要

答复范例

  1. 官网说vuex是一个状态管理模式和库,并确保这些状态以可预期的形式变更。可见要实现一个vuex
  2. 要实现一个Store存储全局状态
  3. 要提供批改状态所需API:commit(type, payload), dispatch(type, payload)
  4. 实现Store时,能够定义Store类,构造函数接管选项options,设置属性state对外裸露状态,提供commitdispatch批改属性state。这里须要设置state为响应式对象,同时将Store定义为一个Vue插件
  5. commit(type, payload)办法中能够获取用户传入mutations并执行它,这样能够按用户提供的办法批改状态。 dispatch(type, payload)相似,但须要留神它可能是异步的,须要返回一个Promise给用户以解决异步后果

实际

Store的实现:

class Store {    constructor(options) {        this.state = reactive(options.state)        this.options = options    }    commit(type, payload) {        this.options.mutations[type].call(this, this.state, payload)    }}

vuex简易版

/** * 1 实现插件,挂载$store * 2 实现store */let Vue;class Store {  constructor(options) {    // state响应式解决    // 内部拜访: this.$store.state.***    // 第一种写法    // this.state = new Vue({    //   data: options.state    // })    // 第二种写法:避免外界间接接触外部vue实例,避免内部强行变更    this._vm = new Vue({      data: {        $$state: options.state      }    })    this._mutations = options.mutations    this._actions = options.actions    this.getters = {}    options.getters && this.handleGetters(options.getters)    this.commit = this.commit.bind(this)    this.dispatch = this.dispatch.bind(this)  }  get state () {    return this._vm._data.$$state  }  set state (val) {    return new Error('Please use replaceState to reset state')  }  handleGetters (getters) {    Object.keys(getters).map(key => {      Object.defineProperty(this.getters, key, {        get: () => getters[key](this.state)      })    })  }  commit (type, payload) {    let entry = this._mutations[type]    if (!entry) {      return new Error(`${type} is not defined`)    }    entry(this.state, payload)  }  dispatch (type, payload) {    let entry = this._actions[type]    if (!entry) {      return new Error(`${type} is not defined`)    }    entry(this, payload)  }}const install = (_Vue) => {  Vue = _Vue  Vue.mixin({    beforeCreate () {      if (this.$options.store) {        Vue.prototype.$store = this.$options.store      }    },  })}export default { Store, install }

验证形式

import Vue from 'vue'import Vuex from './vuex'// this.$storeVue.use(Vuex)export default new Vuex.Store({  state: {    counter: 0  },  mutations: {    // state从哪里来的    add (state) {      state.counter++    }  },  getters: {    doubleCounter (state) {      return state.counter * 2    }  },  actions: {    add ({ commit }) {      setTimeout(() => {        commit('add')      }, 1000)    }  },  modules: {  }})

怎么缓存以后的组件?缓存后怎么更新

缓存组件应用keep-alive组件,这是一个十分常见且有用的优化伎俩,vue3keep-alive有比拟大的更新,能说的点比拟多

思路

  • 缓存用keep-alive,它的作用与用法
  • 应用细节,例如缓存指定/排除、联合routertransition
  • 组件缓存后更新能够利用activated或者beforeRouteEnter
  • 原理论述

答复范例

  1. 开发中缓存组件应用keep-alive组件,keep-alivevue内置组件,keep-alive包裹动静组件component时,会缓存不流动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,避免反复渲染DOM
<keep-alive>  <component :is="view"></component></keep-alive>
  1. 联合属性includeexclude能够明确指定缓存哪些组件或排除缓存指定组件。vue3中联合vue-router时变动较大,之前是keep-alive包裹router-view,当初须要反过来用router-view包裹keep-alive
<router-view v-slot="{ Component }">  <keep-alive>    <component :is="Component"></component>  </keep-alive></router-view>
  1. 缓存后如果要获取数据,解决方案能够有以下两种
  2. beforeRouteEnter:在有vue-router的我的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){  next(vm=>{    console.log(vm)    // 每次进入路由执行    vm.getData()  // 获取数据  })},
  • actived:在keep-alive缓存的组件被激活的时候,都会执行actived钩子
activated(){    this.getData() // 获取数据},
  1. keep-alive是一个通用组件,它外部定义了一个map,缓存创立过的组件实例,它返回的渲染函数外部会查找内嵌的component组件对应组件的vnode,如果该组件在map中存在就间接返回它。因为componentis属性是个响应式数据,因而只有它变动,keep-aliverender函数就会从新执行

Vue为什么没有相似于React中shouldComponentUpdate的生命周期

  • 考点: Vue的变动侦测原理
  • 前置常识: 依赖收集、虚构DOM、响应式零碎
根本原因是VueReact的变动侦测形式有所不同
  • 当React晓得发生变化后,会应用Virtual Dom Diff进行差别检测,然而很多组件实际上是必定不会发生变化的,这个时候须要 shouldComponentUpdate 进行手动操作来缩小diff,从而进步程序整体的性能
  • Vue在一开始就晓得那个组件产生了变动,不须要手动管制diff,而组件外部采纳的diff形式实际上是能够引入相似于shouldComponentUpdate相干生命周期的,然而通常正当大小的组件不会有适量的diff,手动优化的价值无限,因而目前Vue并没有思考引入shouldComponentUpdate这种手动优化的生命周期

Vue中的过滤器理解吗?过滤器的利用场景有哪些?

过滤器本质不扭转原始数据,只是对数据进行加工解决后返回过滤后的数据再进行调用解决,咱们也能够了解其为一个纯函数

Vue 容许你自定义过滤器,可被用于一些常见的文本格式化

ps: Vue3中已废除filter

如何用

vue中的过滤器能够用在两个中央:双花括号插值和 v-bind 表达式,过滤器应该被增加在 JavaScript表达式的尾部,由“管道”符号批示:

<!-- 在双花括号中 -->{ message | capitalize }<!-- 在 `v-bind` 中 --><div v-bind:id="rawId | formatId"></div>

定义filter

在组件的选项中定义本地的过滤器

filters: {  capitalize: function (value) {    if (!value) return ''    value = value.toString()    return value.charAt(0).toUpperCase() + value.slice(1)  }}

定义全局过滤器:

Vue.filter('capitalize', function (value) {  if (!value) return ''  value = value.toString()  return value.charAt(0).toUpperCase() + value.slice(1)})new Vue({  // ...})

留神:当全局过滤器和部分过滤器重名时,会采纳部分过滤器

过滤器函数总接管表达式的值 (之前的操作链的后果) 作为第一个参数。在上述例子中,capitalize 过滤器函数将会收到 message 的值作为第一个参数

过滤器能够串联:

{ message | filterA | filterB }

在这个例子中,filterA 被定义为接管单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。而后持续调用同样被定义为接管单个参数的过滤器函数 filterB,将 filterA 的后果传递到 filterB 中。

过滤器是 JavaScript函数,因而能够接管参数:

{{ message | filterA('arg1', arg2) }}

这里,filterA 被定义为接管三个参数的过滤器函数。

其中 message 的值作为第一个参数,一般字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数

举个例子:

<div id="app">  <p>{{ msg | msgFormat('疯狂','--')}}</p></div><script>    // 定义一个 Vue 全局的过滤器,名字叫做  msgFormat    Vue.filter('msgFormat', function(msg, arg, arg2) {        // 字符串的  replace 办法,第一个参数,除了可写一个 字符串之外,还能够定义一个正则        return msg.replace(/单纯/g, arg+arg2)    })</script>

小结:

  • 部过滤器优先于全局过滤器被调用
  • 一个表达式能够应用多个过滤器。过滤器之间须要用管道符“|”隔开。其执行程序从左往右

利用场景

平时开发中,须要用到过滤器的中央有很多,比方单位转换数字打点文本格式化工夫格式化之类的等

比方咱们要实现将30000 => 30,000,这时候咱们就须要应用过滤器

Vue.filter('toThousandFilter', function (value) {  if (!value) return ''  value = value.toString()  return .replace(str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g, '$1,')})

原理剖析

应用过滤器

{{ message | capitalize }}

在模板编译阶段过滤器表达式将会被编译为过滤器函数,次要是用过parseFilters,咱们放到最初讲

_s(_f('filterFormat')(message))

首先剖析一下_f

_f 函数全名是:resolveFilter,这个函数的作用是从this.$options.filters中找出注册的过滤器并返回

// 变为this.$options.filters['filterFormat'](message) // message为参数

对于resolveFilter

import { indentity,resolveAsset } from 'core/util/index' export function resolveFilter(id){  return resolveAsset(this.$options,'filters',id,true) || identity}

外部间接调用resolveAsset,将option对象,类型,过滤器id,以及一个触发正告的标记作为参数传递,如果找到,则返回过滤器;

resolveAsset的代码如下:

export function resolveAsset(options,type,id,warnMissing){ // 因为咱们找的是过滤器,所以在 resolveFilter函数中调用时 type 的值间接给的 'filters',理论这个函数还能够拿到其余很多货色  if(typeof id !== 'string'){ // 判断传递的过滤器id 是不是字符串,不是则间接返回      return   }  const assets = options[type]  // 将咱们注册的所有过滤器保留在变量中  // 接下来的逻辑便是判断id是否在assets中存在,即进行匹配  if(hasOwn(assets,id)) return assets[id] // 如找到,间接返回过滤器  // 没有找到,代码继续执行  const camelizedId  = camelize(id) // 万一你是驼峰的呢  if(hasOwn(assets,camelizedId)) return assets[camelizedId]  // 没找到,继续执行  const PascalCaseId = capitalize(camelizedId) // 万一你是首字母大写的驼峰呢  if(hasOwn(assets,PascalCaseId)) return assets[PascalCaseId]  // 如果还是没找到,则查看原型链(即拜访属性)  const result = assets[id] || assets[camelizedId] || assets[PascalCaseId]  // 如果仍然没找到,则在非生产环境的控制台打印正告  if(process.env.NODE_ENV !== 'production' && warnMissing && !result){    warn('Failed to resolve ' + type.slice(0,-1) + ': ' + id, options)  }  // 无论是否找到,都返回查找后果  return result}

上面再来剖析一下_s

_s 函数的全称是 toString,过滤器解决后的后果会当作参数传递给 toString函数,最终 toString函数执行后的后果会保留到Vnode中的text属性中,渲染到视图中

function toString(value){  return value == null  ? ''  : typeof value === 'object'    ? JSON.stringify(value,null,2)// JSON.stringify()第三个参数可用来管制字符串外面的间距    : String(value)}

最初,在剖析下parseFilters,在模板编译阶段应用该函数阶段将模板过滤器解析为过滤器函数调用表达式

function parseFilters (filter) {    let filters = filter.split('|')    let expression = filters.shift().trim() // shift()删除数组第一个元素并将其返回,该办法会更改原数组    let i    if (filters) {        for(i = 0;i < filters.length;i++){            experssion = warpFilter(expression,filters[i].trim()) // 这里传进去的expression实际上是管道符号后面的字符串,即过滤器的第一个参数        }    }    return expression}// warpFilter函数实现function warpFilter(exp,filter){    // 首先判断过滤器是否有其余参数    const i = filter.indexof('(')    if(i<0){ // 不含其余参数,间接进行过滤器表达式字符串的拼接        return `_f("${filter}")(${exp})`    }else{        const name = filter.slice(0,i) // 过滤器名称        const args = filter.slice(i+1) // 参数,但还多了 ‘)’        return `_f('${name}')(${exp},${args}` // 留神这一步少给了一个 ')'    }}

小结:

  • 在编译阶段通过parseFilters将过滤器编译成函数调用(串联过滤器则是一个嵌套的函数调用,前一个过滤器执行的后果是后一个过滤器函数的参数)
  • 编译后通过调用resolveFilter函数找到对应过滤器并返回后果
  • 执行后果作为参数传递给toString函数,而toString执行后,其后果会保留在Vnodetext属性中,渲染到视图

Vue中常见性能优化

编码优化

  1. 应用v-show复用DOM:防止反复创立组件
<template>  <div class="cell">    <!-- 这种状况用v-show复用DOM,比v-if成果好 -->    <div v-show="value" class="on">      <Heavy :n="10000"/>    </div>    <section v-show="!value" class="off">      <Heavy :n="10000"/>    </section>  </div></template>
  1. 正当应用路由懒加载、异步组件,无效拆分App尺寸,拜访时才异步加载
const router = createRouter({  routes: [    // 借助webpack的import()实现异步组件    { path: '/foo', component: () => import('./Foo.vue') }  ]})
  1. keep-alive缓存页面:防止反复创立组件实例,且能保留缓存组件状态
<router-view v-slot="{ Component }">    <keep-alive>    <component :is="Component"></component>  </keep-alive></router-view>
  1. v-oncev-memo:不再变动的数据应用v-once
<!-- single element --><span v-once>This will never change: {{msg}}</span><!-- the element have children --><div v-once>  <h1>comment</h1>  <p>{{msg}}</p></div><!-- component --><my-component v-once :comment="msg"></my-component><!-- `v-for` directive --><ul>  <li v-for="i in list" v-once>{{i}}</li></ul>

按条件跳过更新时应用v-momo:上面这个列表只会更新选中状态变动项

<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">  <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>  <p>...more child nodes</p></div>
  1. 长列表性能优化:如果是大数据长列表,可采纳虚构滚动,只渲染少部分区域的内容
<recycle-scroller  class="items"  :items="items"  :item-size="24">  <template v-slot="{ item }">    <FetchItemView      :item="item"      @vote="voteItem(item)"    />  </template></recycle-scroller>
  1. 避免外部透露,组件销毁后把全局变量和事件销毁:Vue 组件销毁时,会主动解绑它的全副指令及事件监听器,然而仅限于组件自身的事件
export default {  created() {    this.timer = setInterval(this.refresh, 2000)  },  beforeUnmount() {    clearInterval(this.timer)  }}
  1. 图片懒加载

对于图片过多的页面,为了减速页面加载速度,所以很多时候咱们须要将页面内未呈现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载

<!-- 参考 https://github.com/hilongjw/vue-lazyload --><img v-lazy="/static/img/1.png">
  1. 滚动到可视区域动静加载

https://tangbc.github.io/vue-virtual-scroll-list(opens new window)

  1. 第三方插件按需引入:(babel-plugin-component

element-plus这样的第三方组件库能够按需引入防止体积太大

import { createApp } from 'vue';import { Button, Select } from 'element-plus';const app = createApp()app.use(Button)app.use(Select)
  1. 服务端渲染:SSR

如果SPA利用有首屏渲染慢的问题,能够思考SSR

以及上面的其余办法

  • 不要将所有的数据都放在data中,data中的数据都会减少gettersetter,会收集对应的watcher
  • v-for 遍历为 item 增加 key
  • v-for 遍历防止同时应用 v-if
  • 辨别 computedwatch 的应用
  • 拆分组件(进步复用性、减少代码的可维护性,缩小不必要的渲染 )
  • 防抖、节流

用户体验

  • app-skeleton 骨架屏
  • pwa serviceworker

SEO优化

  • 预渲染插件 prerender-spa-plugin
  • 服务端渲染 ssr

打包优化

  • Webpack 对图片进行压缩
  • 应用 cdn 的形式加载第三方模块
  • 多线程打包 happypack
  • splitChunks 抽离公共文件
  • 优化 SourceMap
  • 构建后果输入剖析,利用 webpack-bundle-analyzer 可视化剖析工具

根底的 Web 技术的优化

  • 服务端 gzip 压缩
  • 浏览器缓存
  • CDN 的应用
  • 应用 Chrome Performance 查找性能瓶颈

请说出vue cli我的项目中src目录每个文件夹和文件的用法

  • assets文件夹是放动态资源;
  • components是放组件;
  • router是定义路由相干的配置;
  • view视图;
  • app.vue是一个利用主组件;
  • main.js是入口文件

为什么Vue采纳异步渲染

Vue 是组件级更新,如果不采纳异步更新,那么每次更新数据都会对以后组件进行从新渲染,所以为了性能, Vue 会在本轮数据更新后,在异步更新视图。核心思想 nextTick

源码相干

dep.notify() 告诉 watcher进行更新, subs[i].update 顺次调用 watcherupdatequeueWatcherwatcher 去重放入队列, nextTickflushSchedulerQueue )在下一tick中刷新watcher队列(异步)
update () { /* istanbul ignore else */     if (this.lazy) {         this.dirty = true     }     else if (this.sync) {         this.run()     }     else {         queueWatcher(this); // 当数据发生变化时会将watcher放到一个队列中批量更新     }}export function queueWatcher (watcher: Watcher) {     const id = watcher.id // 会对雷同的watcher进行过滤     if (has[id] == null) {         has[id] = true         if (!flushing) {             queue.push(watcher)         } else {             let i = queue.length - 1             while (i > index && queue[i].id > watcher.id) {                 i--             }            queue.splice(i + 1, 0, watcher)         }        // queue the flush         if (!waiting) {             waiting = true             if (process.env.NODE_ENV !== 'production' && !config.async) {                 flushSchedulerQueue()                 return             }            nextTick(flushSchedulerQueue) // 调用nextTick办法 批量的进行更新         }     } }

Vue.set的实现原理

  • 给对应和数组自身都减少了dep属性
  • 当给对象新增不存在的属性则触发对象依赖的watcher去更新
  • 当批改数组索引时,咱们调用数组自身的splice去更新数组(数组的响应式原理就是从新了splice等办法,调用splice就会触发视图更新)

根本应用

以下办法调用会扭转原始数组:push(), pop(), shift(), unshift(), splice(), sort(), reverse(),Vue.set( target, key, value )
  • 调用办法:Vue.set(target, key, value )

    • target:要更改的数据源(能够是对象或者数组)
    • key:要更改的具体数据
    • value :从新赋的值
<div id="app">{{user.name}} {{user.age}}</div><div id="app"></div><script>    // 1. 依赖收集的特点:给每个属性都减少一个dep属性,dep属性会进行收集,收集的是watcher    // 2. vue会给每个对象也减少一个dep属性    const vm = new Vue({        el: '#app',        data: { // vm._data              user: {name:'poetry'}        }    });    // 对象的话:调用defineReactive在user对象上定义一个age属性,减少到响应式数据中,触发对象自身的watcher,ob.dep.notify()更新     // 如果是数组 通过调用 splice办法,触发视图更新    vm.$set(vm.user, 'age', 20); // 不能给根属性增加,因为给根增加属性 性能耗费太大,须要做很多解决    // 批改必定是同步的 -> 更新都是一步的  queuewatcher</script>

相干源码

// src/core/observer/index.js 44export class Observer { // new Observer(value)  value: any;  dep: Dep;  vmCount: number; // number of vms that have this object as root $data  constructor (value: any) {    this.value = value    this.dep = new Dep() // 给所有对象类型减少dep属性  }}
// src/core/observer/index.js 201export function set (target: Array<any> | Object, key: any, val: any): any {  // 1.是开发环境 target 没定义或者是根底类型则报错  if (process.env.NODE_ENV !== 'production' &&    (isUndef(target) || isPrimitive(target))  ) {    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)  }  // 2.如果是数组 Vue.set(array,1,100); 调用咱们重写的splice办法 (这样能够更新视图)  if (Array.isArray(target) && isValidArrayIndex(key)) {    target.length = Math.max(target.length, key)    // 利用数组的splice变异办法触发响应式      target.splice(key, 1, val)    return val  }  // 3.如果是对象自身的属性,则间接增加即可  if (key in target && !(key in Object.prototype)) {    target[key] = val // 间接批改属性值      return val  }  // 4.如果是Vue实例 或 根数据data时 报错,(更新_data 无意义)  const ob = (target: any).__ob__  if (target._isVue || (ob && ob.vmCount)) {    process.env.NODE_ENV !== 'production' && warn(      'Avoid adding reactive properties to a Vue instance or its root $data ' +      'at runtime - declare it upfront in the data option.'    )    return val  }  // 5.如果不是响应式的也不须要将其定义成响应式属性  if (!ob) {    target[key] = val    return val  }  // 6.将属性定义成响应式的  defineReactive(ob.value, key, val)  // 告诉视图更新  ob.dep.notify()  return val}

咱们浏览以上源码可知,vm.$set 的实现原理是:

  • 如果指标是数组 ,间接应用数组的 splice 办法触发相应式;
  • 如果指标是对象 ,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决( defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 gettersetter 的性能所调用的办法)

构建的 vue-cli 工程都到了哪些技术,它们的作用别离是什么

  • vue.jsvue-cli工程的外围,次要特点是 双向数据绑定 和 组件零碎。
  • vue-routervue官网举荐应用的路由框架。
  • vuex:专为 Vue.js 利用我的项目开发的状态管理器,次要用于保护vue组件间共用的一些 变量 和 办法。
  • axios( 或者 fetchajax ):用于发动 GET 、或 POSThttp申请,基于 Promise 设计。
  • vuex等:一个专为vue设计的挪动端UI组件库。
  • 创立一个emit.js文件,用于vue事件机制的治理。
  • webpack:模块加载和vue-cli工程打包器。

实现双向绑定

咱们还是以Vue为例,先来看看Vue中的双向绑定流程是什么的

  1. new Vue()首先执行初始化,对data执行响应化解决,这个过程产生Observe
  2. 同时对模板执行编译,找到其中动静绑定的数据,从data中获取并初始化视图,这个过程产生在Compile
  3. 同时定义⼀个更新函数和Watcher,未来对应数据变动时Watcher会调用更新函数
  4. 因为data的某个key在⼀个视图中可能呈现屡次,所以每个key都须要⼀个管家Dep来治理多个Watcher
  5. 未来data中数据⼀旦发生变化,会首先找到对应的Dep,告诉所有Watcher执行更新函数

流程图如下:

先来一个构造函数:执行初始化,对data执行响应化解决

class Vue {    constructor(options) {      this.$options = options;      this.$data = options.data;      // 对data选项做响应式解决      observe(this.$data);      // 代理data到vm上      proxy(this);      // 执行编译      new Compile(options.el, this);    }  }  

data选项执行响应化具体操作

function observe(obj) {    if (typeof obj !== "object" || obj == null) {      return;    }    new Observer(obj);  }  class Observer {    constructor(value) {      this.value = value;      this.walk(value);    }    walk(obj) {      Object.keys(obj).forEach((key) => {        defineReactive(obj, key, obj[key]);      });    }  }  

编译Compile

对每个元素节点的指令进行扫描跟解析,依据指令模板替换数据,以及绑定相应的更新函数

class Compile {    constructor(el, vm) {      this.$vm = vm;      this.$el = document.querySelector(el);  // 获取dom      if (this.$el) {        this.compile(this.$el);      }    }    compile(el) {      const childNodes = el.childNodes;       Array.from(childNodes).forEach((node) => { // 遍历子元素        if (this.isElement(node)) {   // 判断是否为节点          console.log("编译元素" + node.nodeName);        } else if (this.isInterpolation(node)) {          console.log("编译插值⽂本" + node.textContent);  // 判断是否为插值文本 {{}}        }        if (node.childNodes && node.childNodes.length > 0) {  // 判断是否有子元素          this.compile(node);  // 对子元素进行递归遍历        }      });    }    isElement(node) {      return node.nodeType == 1;    }    isInterpolation(node) {      return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent);    }  }  

依赖收集

视图中会用到data中某key,这称为依赖。同⼀个key可能呈现屡次,每次都须要收集进去用⼀个Watcher来保护它们,此过程称为依赖收集多个Watcher须要⼀个Dep来治理,须要更新时由Dep统⼀告诉

实现思路

  1. defineReactive时为每⼀个key创立⼀个Dep实例
  2. 初始化视图时读取某个key,例如name1,创立⼀个watcher1
  3. 因为触发name1getter办法,便将watcher1增加到name1对应的Dep
  4. name1更新,setter触发时,便可通过对应Dep告诉其治理所有Watcher更新
// 负责更新视图  class Watcher {    constructor(vm, key, updater) {      this.vm = vm      this.key = key      this.updaterFn = updater      // 创立实例时,把以后实例指定到Dep.target动态属性上      Dep.target = this      // 读一下key,触发get      vm[key]      // 置空      Dep.target = null    }    // 将来执行dom更新函数,由dep调用的    update() {      this.updaterFn.call(this.vm, this.vm[this.key])    }  }  

申明Dep

class Dep {    constructor() {      this.deps = [];  // 依赖治理    }    addDep(dep) {      this.deps.push(dep);    }    notify() {       this.deps.forEach((dep) => dep.update());    }  } 

创立watcher时触发getter

class Watcher {    constructor(vm, key, updateFn) {      Dep.target = this;      this.vm[this.key];      Dep.target = null;    }  }  

依赖收集,创立Dep实例

function defineReactive(obj, key, val) {    this.observe(val);    const dep = new Dep();    Object.defineProperty(obj, key, {      get() {        Dep.target && dep.addDep(Dep.target);// Dep.target也就是Watcher实例        return val;      },      set(newVal) {        if (newVal === val) return;        dep.notify(); // 告诉dep执行更新办法      },    });  }  

Vue-router 除了 router-link 怎么实现跳转

申明式导航

<router-link to="/about">Go to About</router-link>

编程式导航

// literal string pathrouter.push('/users/1')// object with pathrouter.push({ path: '/users/1' })// named route with params to let the router build the urlrouter.push({ name: 'user', params: { username: 'test' } })

答复范例

  • vue-router导航有两种形式:申明式导航和编程形式导航
  • 申明式导航形式应用router-link组件,增加to属性导航;编程形式导航更加灵便,可传递调用router.push(),并传递path字符串或者RouteLocationRaw对象,指定pathnameparams等信息
  • 如果页面中简略示意跳转链接,应用router-link最快捷,会渲染一个a标签;如果页面是个简单的内容,比方商品信息,能够增加点击事件,应用编程式导航
  • 实际上外部两者调用的导航函数是一样的