乐趣区

关于前端:超全面总结Vue面试知识点助力金三银四

前言

本文会对 Vue 中一些常见的重要知识点以及框架原理进行整顿汇总,意在帮忙作者以及读者自测 Vue 的熟练度以及不便查问与温习。金三银四的到来,想必 vue 会是很多面试官的重点考核内容,心愿小伙伴们读完本文,可能有肯定自我晋升,也心愿这篇文章可能为大家的面试的保驾护航~

如果这篇文章有帮忙到你,❤️关注 + 点赞❤️激励一下作者,文章公众号首发,关注 前端南玖 第一工夫获取最新文章~

文章收录于 github,欢送 star❤️❤️

1.MVC 与 MVVM 的区别

MVC

MVC 全名是 Model View Controller,是模型 (model)-视图(view)-控制器(controller) 的缩写,一种软件设计榜样

  • Model(模型):是应用程序中用于解决应用程序数据逻辑的局部。通常模型对象负责在数据库中存取数据
  • View(视图):是应用程序中解决数据显示的局部。通常视图是根据模型数据创立的
  • Controller(控制器):是应用程序中解决用户交互的局部。通常控制器负责从视图读取数据,管制用户输出,并向模型发送数据

上面看斯坦福大学公开课上的这幅图来阐明,这能够说是最经典和最标准的 MVC 规范

简直所有的 App 都只干这么一件事:将数据展现给用户看,并解决用户对界面的操作。
MVC 的思维:一句话形容就是 Controller 负责将 Model 的数据用 View 显示进去,换句话说就是在 Controller 外面把 Model 的数据赋值给 View。

MVVM

MVVM:Model、View、ViewModel。

你会下意识地把它和 MVC 来比照,你会发现,MVVM 多了一个 ViewModel 而少了 Controller。

首先说一下多进去的 ViewModel(VM,不是显存)。
VM 的意义,和 Model 一样,在于数据。
Model 负责对数据进行取和存,然而咱们对数据的操作除了取和存以外,还有一个十分重要的操作:解析

M:对应于 MVC 的 M

V:对应于 MVC 的 V

VM:ViewModel,是把 MVC 里的 controller 的数据加载,加工性能分离出来

区别

MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的主动同步,也就是当 Model 的属性扭转时,咱们不必再本人手动操作 Dom 元素,来扭转 View 的显示,而是扭转属性后该属性对应 View 层显示会主动扭转(对应 Vue 数据驱动的思维)

Vue 并没有齐全遵循 MVVM 的思维

这一点 Vue 官网本人也有阐明

这是因为从严格意义上来讲,MVVM 要求 View 与 Model 是不能间接通信的,而 Vue 提供了 $refs 这个属性,让 Model 能够间接操作 View,违反了这一规定,所以说 Vue 没有齐全遵循 MVVM。

2. 为什么 data 须要是一个函数?

这个说法次要是在组件中呈现,因为组件是能够复用的,js 里对象是援用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相净化,产生副作用。如果组件中 data 选项是一个函数,那么每个实例能够保护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会相互影响;而 new Vue 的实例,是不会被复用的,因而不存在援用对象的问题。

3.v-if 与 v -show

v-if 与 v -show 的区别

v-if 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是 惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的“display”属性进行切换。

所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。

v-show 指令算是重排吗?

v-show 实质是通过元素 css 的 display 属性来管制是否显示,在 DOM 渲染时依然会先渲染元素,而后才会进行判断是否显示(通过 display 属性),而对于重排的定义是渲染树中的节点信息产生了大小、边距等扭转,要从新计算各节点和 css 具体的大小和地位。
当用 display 来管制元素的显示和暗藏时,会扭转节点的大小和渲染树的布局,导致产生重排,因而 v -show 指令算是重排。

4.v-for

v-if 与 v-for 为什么不倡议一起应用?

首先,对于 v -if 和 v -for 的优先级,能够在源码 compiler/codegen/index.js 中找到 genElement 办法,外面的 if else 判断,能够分明看到 for 的判断在 if 判断之上,由此,可证实 v -for 的优先级高于 v -if

如果 v -if 和 v -for 同时呈现,分两种状况:

  • 当同时呈现在同一标签内, 能够通过 vue.$options.render 打印出渲染函数,能够清晰的看到会优先执行 for 循环,再执行 if 判断
  • 当 v -if 呈现在父级中,子级有 v -for,此时再打印vue.$options.render,会发现会优先执行 if 判断。

若想优化,晋升性能,v-if 须要优先执行,能够在 v -for 外层加一层 template 搭配 v -if 应用。
若是 v -if 与 v -for 必须呈现在同一层或 v -if 为 v -for 的子级的状况下,优化的形式能够将 for 循环的数组提前通过计算属性解决,尽量减少过多渲染导致的性能耗费。

v-for 中的 key 有什么作用?为什么在 v -for 中的 key 不举荐应用随机数或者 index?

key 的作用: 能够使 vue 的 diff 操作更加精确和疾速

如果不应用 key,Vue 会应用一种最大限度缩小动静元素并且尽可能的尝试就地批改 / 复用雷同类型元素的算法。key 是为 Vue 中 vnode 的惟一标记,通过这个 key,咱们的 diff 操作能够更精确、更疾速

更精确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 比照中能够防止就地复用的状况。所以会更加精确。

更疾速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历形式更快

为什么在 v -for 中的 key 不举荐应用随机数或者 index?

因为在插入数据或者删除数据的时候,会导致前面的数据的 key 绑定的 index 变动,进而导致从新渲染,效率会升高,同时也会导致渲染出错;当数据进行更改的时候,会通过 key 来判断虚构 dom 树是否进行了更改。如果发现了雷同的 dom-key 就能够间接复用。缩小了渲染的性能损耗。所以应用随机数或 index 作为 key 会导致性能节约,并且应用 index 作为 key 可能会导致渲染出错。

v-for 遍历对象时,是按什么程序遍历的?如何保障程序?

1、会先判断是否有 iterator 接口,如果有循环执行 next()办法

2、没有 iterator 的状况下,会调用 Object.keys()办法,在不同浏览器中,JS 引擎不能保障输入程序统一

3、保障对象的输入程序能够把对象放在数组中,作为数组的元素

5. 常见的 Vue 内置指令

6.Vue 组件通信的几种形式

props/$emit

这个个别用于父子组件之间的通信,父组件通过 props 的形式向子组件传递数据,子组件能够通过 $emit 的形式向父组件进行通信。

<!-- 父组件 -->
<template>
 <div class="section">
 <childItem :list="list" @update="update"></childItem>
 </div>
</template>
 
<script>
import childItem from './childItem'
export default {
 name: 'parent',
 components: {childItem},
 data() {
 return {
   currentIndex: 0,
   list: [{id:1,title: 'nanjiu'}, {id:2, title:'FE'}, {id:3, title:'boy'}]
   }
 },
  methods: {update(index) {this.currentIndex = index}
  }
}
</script>
<!-- 子组件 -->
<template>
 <div>
 <span v-for="(item, index) in list" :key="item.id" @click="update(index)">{{item}}</span>
 </div>
</template>
 
<script>
export default {
 props: {list: Array},
  methods: {update(index) {this.$emit('update', index)
    }
  }
}
</script>

总结:

  • props 只能够从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且是 props 只读的,不可被批改,所有批改都会生效并正告。
  • $emit绑定一个自定义事件, 当这个语句被执行时, 就会将参数传递给父组件, 父组件通过 v-on 监听并接管参数。

$parent/$Children

咱们来看看官网是怎么解释这两个 API 的:

从下面具体中咱们能够晓得,通过 $parent$children就能够拜访到对应组件的实例,既然都拜访到组件实例了,那么组件内的所有内容(datamethods等)就都可能拜访到了。

<!-- 父组件 -->
<template>
  <div class="section">
    <childItem></childItem>
  </div>
</template>

<script>
import childItem from "./components/item";
export default {
  name: "parent",
  components: {childItem},
  data() {
    return {
      currentIndex: 0,
      msg: "父组件",
    };
  },
  mounted() {console.log(` 我是父组件, 我正在拜访 ${this.$children[0].msg}...`);
  }
};
</script>
<!-- 子组件 --> 
<template>
  <div class="item">
  </div>
</template>

<script>
export default {
  name: "item",
  data() {
    return {msg: "子组件"};
  },
  mounted() {console.log(` 我是子组件, 我正在拜访 ${this.$parent.msg}...`);
  }
};
</script>

ref

如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例

<!-- 子组件 -->
export default {data () {
   return {name: 'nanjiu'}
 },
 methods: {sayHello () {console.log('hello, I am nanjiu')
   }
 }
}
<!-- 父组件 -->
<template>
 <child ref="child"></child>
</template>
<script>
  import child from "./child"
 export default {components: {child},
   mounted () {
    const child = this.$refs.child;
    console.log(child.name); // nanjiu
    child.sayHello(); // hello, I am nanjiu}
 }
</script>

provide/inject

provide/ inject 是 vue2.2.0 新增的 api, 简略来说就是父组件中通过 provide 来提供变量, 而后再子组件中通过 inject 来注入变量。并且不管子组件嵌套有多深, 只有调用了 inject 那么就能够注入 provide 中的数据

<!-- 根组件 -->
<template>
  <div class="section">
    <childItem></childItem>
  </div>
</template>

<script>
import childItem from "./components/item";
export default {
  name: "parent",
  components: {childItem},
  data() {
    return {
      currentIndex: 0,
      msg: "根组件"
    };
  },
  provide() {
    return {rootMsg: this.msg};
  },
  mounted() {console.log(` 我是父组件, 我正在拜访 ${this.$children[0].msg}...`);
  }
};
</script>
<!-- 子组件 -->
<template>
  <div class="item">
    <subItem />
  </div>
</template>
<script>
import subItem from "./subItem.vue";
export default {
  name: "item",
  components: {subItem},
  inject: ["rootMsg"],
  data() {
    return {msg: "子组件"};
  },
  mounted() {console.log(` 我是子组件, 我正在拜访 ${this.rootMsg}...`); // 我是子组件, 我正在拜访根组件...
  }
};
</script>
<!-- 孙子组件 -->
<template>
  <div class="sub_item"></div>
</template>

<script>
export default {
  name: "subItem",
  inject: ["rootMsg"],
  mounted() {console.log(` 我是孙子组件,我正在拜访 ${this.rootMsg}`); // 我是孙子组件,我正在拜访根组件
  }
};
</script>

eventBus

eventBus 又称为事件总线,在 vue 中能够应用它来作为沟通桥梁的概念, 就像是所有组件共用雷同的事件核心,能够向该核心注册发送事件或接管事件,所以组件都能够告诉其余组件。应用 $on 订阅事件,$emit公布事件

// index.js
Vue.prototype.$bus = new Vue() // 应用一个 vue 实例来承载地方事件

// 订阅事件
<template>
  <div class="section">
    <childItem :list="list" @update="update"></childItem>
  </div>
</template>

<script>
import childItem from "./components/item";
export default {
  name: "parent",
  components: {childItem},
  mounted() {
    // 订阅事件
    this.$bus.$on("childLoad", () => {console.log(` 我是父组件,已监测到子组件加载实现 `); // 我是父组件,已监测到子组件加载实现
    });
  }
};
</script>

// 公布事件
<template>
  <div class="item">
    child
  </div>
</template>

<script>
export default {
  name: "item",
  mounted() {
    window.addEventListener("load", e => {this.$bus.$emit("childLoad");
    });
  }
};
</script>

$attrs/$listeners

如果遇到跨级组件应用 props 与 emit 来通信的话,那么就须要将数据与事件一层一层往下传递,这样做太麻烦了。所以在 vue2.4 中,为了解决该需要,引入了 $attrs$listeners,新增了inheritAttrs 选项。在版本 2.4 以前,默认状况下, 父作用域中不作为 props 被辨认 (且获取) 的个性绑定 (class 和 style 除外),将会“回退”且作为一般的 HTML 个性利用在子组件的根元素上。

// 根组件
<template>
  <div class="section">
    <childItem
      :list="list"
      :msg="msg"
      @update="update"
      @rootFun="rootFun"
    ></childItem>
  </div>
</template>
<script>
import childItem from "./components/item";
export default {
  name: "parent",
  components: {childItem},

  data() {
    return {
      currentIndex: 0,
      msg: "根组件",
      list: [{ id: 1, title: "nanjiu"},
        {id: 2, title: "FE"},
        {id: 3, title: "boy"}
      ]
    };
  },
  methods: {rootFun() {console.log("我是根组件的办法");
    },
    update(index) {this.currentIndex = index;}
  }
};
</script>

// 子组件
<template>
  <div class="item">
    <subItem v-bind="$attrs" v-on="$listeners" />
  </div>
</template>

<script>
import subItem from "./subItem.vue";
export default {
  name: "item",
  components: {subItem}
};
</script>
// 孙子组件
<template>
  <div class="sub_item"></div>
</template>

<script>
export default {
  name: "subItem",
  mounted() {console.log(this.$attrs); // {list: Array(3), msg: '根组件'}
    console.log(this.$listeners); //{update: ƒ, rootFun: ƒ}
  }
};
</script> 

Vuex

1. Vuex 介绍

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

Vuex 解决了多个视图依赖于同一状态和来自不同视图的行为须要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上

2. Vuex 各个模块

  1. state:用于数据的存储,是 store 中的惟一数据源
  2. getters:如 vue 中的计算属性一样,基于 state 数据的二次包装,罕用于数据的筛选和多个数据的相关性计算
  3. mutations:相似函数,扭转 state 数据的惟一路径,且不能用于解决异步事件
  4. actions:相似于 mutation,用于提交 mutation 来扭转状态,而不间接变更状态,能够蕴含任意异步操作
  5. modules:相似于命名空间,用于我的项目中将各个模块的状态离开定义和操作,便于保护

localStorage/sessionStorage

应用 localStorage/sessionStorage 也可能进行通信,毛病就是不易保护

总结

  1. 父子组件通信: props; $parent / $children; provide / inject ; ref ; $attrs / $listeners
  2. 兄弟组件通信: eventBus ; vuex
  3. 跨级通信: eventBus;Vuex;provide / inject、$attrs / $listeners

7. 怎么了解 Vue 的单向数据流?

在 Vue 中,所有的 prop 都使得其父子 prop 之间造成了一个 单向数据流:父级 prop 的更新会向下流动到子组件中,然而反过来则不行。这样会避免从子组件意外扭转父级组件的状态,从而导致你的利用的数据流向难以了解。

额定的,每次父级组件产生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件外部扭转 prop。如果你这样做了,Vue 会在浏览器的控制台中收回正告。子组件想批改时,只能通过 $emit 派发一个自定义事件,父组件接管到后,由父组件批改。

8.computed 和 watch 的区别和使用的场景?

区别:

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值;

  • 反对缓存,只有依赖数据产生扭转,才会从新进行计算
  • 不反对异步,当 computed 内有异步操作时有效,无奈监听数据的变动
  • computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于 data 中申明过或者父组件传递的 props 中的数据通过计算失去的值
  • 如果一个属性是由其余属性计算而来的,这个属性依赖其余属性,是一个多对一或者一对一,个别用 computed
  • 如果 computed 属性属性值是函数,那么默认会走 get 办法;函数的返回值就是属性的属性值;在 computed 中的,属性都有一个 get 和一个 set 办法,当数据变动时,调用 set 办法。

watch: 更多的是「察看」的作用,相似于某些数据的监听回调,每当监听的数据变动时都会执行回调进行后续操作;

  • 不反对缓存,数据变,间接会触发相应的操作;
  • watch 反对异步;
  • 监听的函数接管两个参数,第一个参数是最新的值;第二个参数是输出之前的值;
  • 当一个属性发生变化时,须要执行对应的操作;一对多;
  • 监听数据必须是 data 中申明过或者父组件传递过去的 props 中的数据,当数据变动时,触发其余操作,

watch 和 computed 各自解决的数据关系场景不同:

  • watch 善于解决的场景:一个数据影响多个数据
  • computed 善于解决的场景:一个数据受多个数据影响

9.Vue2 最低兼容到 IE 几?

vue2 兼容 IE8 以上版本,IE8 及以下版本不反对 Object.defineProperty 办法,但这个是 vue 实现响应式的所必须的。

10. 说说 Vue 的生命周期,个别在哪个钩子发申请?

beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在以后阶段 data、methods、computed 以及 watch 上的数据和办法都不能被拜访

created 实例曾经创立实现之后被调用。在这一步,实例已实现以下的配置:数据观测(data observer),属性和办法的运算,watch/event 事件回调。这里没有$el, 如果非要想与 Dom 进行交互,能够通过 $nextTick 来拜访 Dom

beforeMount 在挂载开始之前被调用:相干的 render 函数首次被调用。

mounted 在挂载实现后产生,在以后阶段,实在的 Dom 挂载结束,数据实现双向绑定,能够拜访到 Dom 节点

beforeUpdate 数据更新时调用,产生在虚构 DOM 从新渲染和打补丁(patch)之前。能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程

updated 产生在更新实现之后,以后阶段组件 Dom 已实现更新。要留神的是防止在此期间更改数据,因为这可能会导致有限循环的更新,该钩子在服务器端渲染期间不被调用。

beforeDestroy 实例销毁之前调用。在这一步,实例依然齐全可用。咱们能够在这时进行善后收尾工作,比方革除计时器。

destroyed Vue 实例销毁后调用。调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

activated keep-alive 专属,组件被激活时调用

deactivated keep-alive 专属,组件被销毁时调用

异步申请在哪一步发动?

能够在钩子函数 created、beforeMount、mounted 中进行异步申请,因为在这三个钩子函数中 data 曾经创立,能够将服务端返回的数据进行赋值。

如果异步申请不须要依赖 Dom 举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处:

  • 能更快获取到服务端数据,缩小页面 loading 工夫;
  • ssr 不反对 beforeMount、mounted 钩子函数,所以放在 created 中有助于一致性;

11. 说说 Vue2 的数据响应式原理

举荐浏览

【Vue 源码学习】响应式原理探秘

【Vue 源码学习】依赖收集

Vue2 与 Vue3 的数据响应式原理有什么区别

  • Vue2 中的变动侦测实现对 Object 及 Array 别离进行了不同的解决,Objcet 应用了Object.defineProperty API,Array 应用了拦截器对 Array 原型上的可能扭转数据的办法进行拦挡。尽管也实现了数据的变动侦测,但存在很多局限,比方对象新增属性无奈被侦测,以及通过数组下边批改数组内容,也因而在 Vue2 中常常会应用到 $set 这个办法对数据批改,以保障依赖更新。
  • Vue3 中应用了 es6 的 Proxy API 对数据代理,没有像 Vue2 中对原数据进行批改,只是加了代理包装,因而首先性能上会有所改善。其次解决了 Vue2 中变动侦测的局限性,能够不应用 $set 新增的对象属性及通过下标批改数组都能被侦测到。

12.Vue 的父组件和子组件生命周期钩子函数执行程序?

  • 加载渲染过程

    父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

  • 子组件更新过程

    父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

  • 父组件更新过程

    父 beforeUpdate -> 父 updated

  • 销毁过程

    父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

13.Vue 事件绑定原理

原生事件绑定是通过 addEventListener 绑定给实在元素的,组件事件绑定是通过 Vue 自定义的 $on 实现的。如果要在组件上应用原生事件,须要加.native 修饰符,这样就相当于在父组件中把子组件当做一般 html 标签,而后加上原生事件。

1. 原生 dom 事件的绑定, 采纳的是 addEventListener 实现

  • Vue在创立真是 dom 时会调用createElm, 默认会调用invokeCreateHooks
  • 会遍历以后平台下绝对的属性解决代码, 其中就有 updateDOMListeners 办法, 外部会传入 add 办法
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {return}
  const on = vnode.data.on || {}
  const oldOn = oldVnode.data.on || {}
  target = vnode.elm
  normalizeEvents(on)
  updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
  target = undefined
}
function add (
  name: string,
  handler: Function,
  capture: boolean,
  passive: boolean
) {
  target.addEventListener( // 给以后的 dom 增加事件
    name,
    handler,
    supportsPassive
      ? {capture, passive}
      : capture
  )
}

2. 组件绑定事件采纳的是 $on 办法

export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}
function add (event, fn) {target.$on(event, fn)
}

14. 间接给一个数组项赋值,Vue 能检测到变动吗?

看过源码应该都晓得这是不能的,因为 Vue2 对数组的劫持实质上是劫持数组原型上的那七个办法,所以只有通过调用这七个办法中的其中一个 Vue 才可能监测到变动。

但咱们能够通过调用 set 或 $set 来来触发视图更新

mounted() {this.$set(this.list, this.list[0], {id: 1, title: "南玖"});
  },

vm.$set 的实现原理是:

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

15.vue 中 data 的属性能够和 methods 中的办法同名吗?为什么?

这个通过看源码也能晓得这个不行的,vue 在初始化过程中会先进行初始化 props(initProps),而后会初始化methods(initMethods),这里会判断是否与 props 中有重名的 key,有的话会告警,而后再初始化data(initData),这里又会判断是否有与propsmethods重名的属性,有的话会告警。

16. 父组件能够监听到子组件的生命周期吗?

答案是能够的。

第一种能够应用 $emit 来实现

// Parent.vue
<Child @mounted="doSomething"/>

// Child.vue
mounted() {this.$emit("mounted");
}

第二种能够应用 @hook 来实现

// Parent.vue
<template>
  <div class="section">
    <childItem
      @hook:mounted="childMounted"
    ></childItem>
  </div>
</template>
<script>
export default {
  name: "parent",
  components: {childItem},
  mounted() {console.log("父组件 mounted");
  },
  methods: {childMounted() {console.log("父组件监听到子组件 mounted");
    }
  }
};
</script>

// Child.vue
mounted() {console.log("子组件 mounted");
}

打印程序应该是:子组件 mounted –> 父组件监听到子组件 mounted –> 父组件 mounted

17. 虚构 DOM

虚构 DOM 是什么?

虚构 DOM 是将状态映射成试图的泛滥解决方案之一。页面交互的实质还是通过扭转状态 (变量) 来扭转试图渲染,而框架(像支流框架 vue、react、angular)的利用能够让咱们把关注的焦点更多的放在状态上,省略对 DOM 的操作(框架外部曾经帮咱们实现了)。
而虚构 DOM 映射视图的形式是通过状态生成一个虚构节点树,而后通过虚构节点树进行渲染。虚构 DOM 实质上是一个一般的 js 对象,它蕴含了创立一个 DOM 元素所须要的属性。

虚构 DOM 的优缺点

长处:

  • 保障性能上限:框架的虚构 DOM 须要适配任何下层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;然而比起粗犷的 DOM 操作性能要好很多,因而框架的虚构 DOM 至多能够保障在你不须要手动优化的状况下,仍然能够提供还不错的性能,即保障性能的上限;
  • 无需手动操作 DOM:咱们不再须要手动去操作 DOM,只须要写好 View-Model 的代码逻辑,框架会依据虚构 DOM 和 数据双向绑定,帮咱们以可预期的形式更新视图,极大进步咱们的开发效率;
  • 具备跨平台的劣势:因为 虚构 DOM 是以 JavaScript 对象为根底而不依赖实在平台环境,所以使它具备了跨平台的能力,比如说浏览器平台、Weex、Node 等。

毛病:

  • 首次渲染大量 DOM 时,因为多了一层虚构 DOM 的计算,会比 innerHTML 插入慢。

18.Vue-router 的路由钩子函数是什么,以及执行程序是怎么的?

vue-router 提供的导航守卫次要用来通过 跳转 勾销 的形式 守卫导航 ,Vue 的路由钩子分为: 全局守卫、路由守卫、组件守卫

执行程序

  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 的回调函数,创立好的组件实例会作为回调函数的参数传入。

19. 说说你对 slot 的了解

Slot 名为插槽,咱们能够了解为 solt 在组件模板中占好了地位,当应用该组件标签时候,组件标签外面的内容就会主动填坑(替换组件模板中 slot 地位),作为承载散发内容的进口

slot 能够分为三类

默认插槽

子组件用 <slot> 标签来确定渲染的地位,标签外面能够放 DOM 构造,当父组件应用的时候没有往插槽传入内容,标签内 DOM 构造就会显示在页面

父组件在应用的时候,间接在子组件的标签内写入内容即可

<!-- 父组件 -->
<Child>
  <div> 默认插槽 </div>  
</Child>

<!-- 子组件 -->
<template>
    <slot>
      <p> 插槽后备的内容 </p>
    </slot>
</template>

具名插槽

子组件用 name 属性来示意插槽的名字,不传为默认插槽

父组件中在应用时在默认插槽的根底上加上 slot 属性,值为子组件插槽 name 属性值

<!-- 父组件 -->
<child>
    <template v-slot:default> 具名插槽 </template>
    <!-- 具名插槽⽤插槽名做参数 -->
    <template v-slot:content> 内容...</template>
</child>

<!-- 子组件 -->
<template>
    <slot> 插槽后备的内容 </slot>
    <slot name="nanjiu"> 插槽后备的内容 </slot>
</template>

作用域插槽

子组件在作用域上绑定属性来将子组件的信息传给父组件应用,这些属性会被挂在父组件 v-slot 承受的对象上

父组件中在应用时通过v-slot:(简写:#)获取子组件的信息,在内容中应用

<!-- 父组件 -->
<child> 
    <!-- 把 v -slot 的值指定为作⽤域高低⽂对象 -->
    <template v-slot:default="slotProps">
      来⾃⼦组件数据:{{slotProps.testProps}}
    </template>
  <template #default="slotProps">
      来⾃⼦组件数据:{{slotProps.testProps}}
    </template>
</child>

<!-- 子组件 -->
<template> 
  <slot name="footer" testProps="子组件的值">
          <h3> 没传 footer 插槽 </h3>
    </slot>
</template>

小结:

  • v-slot 属性只能在 <template> 上应用,但在只有默认插槽时能够在组件标签上应用
  • 默认插槽名为 default,能够省略 default 间接写 v -slot
  • 缩写为 #时不能不写参数,写成#default
  • 应用作用域插槽时能够通过解构获取 v-slot={msg},还能够重命名v-slot="{msg: newMsg}" 和定义默认值v-slot="{msg =' 默认值 '}"

插槽的原理:

slot 实质上是返回 VNode 的函数,个别状况下,Vue 中的组件要渲染到页面上须要通过 template >> render function >> VNode >> DOM 过程。组件挂载的实质就是执行渲染函数失去 VNode,至于 data/props/computed 这些属性都是给 VNode 提供数据起源。

在 2.5 之前,如果是一般插槽就 间接是 VNode的模式了,而如果是作用域插槽,因为子组件须要在父组件拜访子组件的数据,所以父组件下是一个 未执行的函数 (slotScope) => return h('div', slotScope.msg),承受子组件的 slotProps 参数,在子组件渲染实例时会调用该函数传入数据。

在 2.6 之后,两者合并,一般插槽也变成一个函数,只是不承受参数了。

20. 说说 Vue 的 $nextTick 的原理

Vue 在更新 DOM 时是异步执行的。只有侦听到数据变动,Vue 将开启一个队列,并缓冲在同一事件循环中产生的所有数据变更。如果同一个 watcher 被屡次触发,只会被推入到队列中一次。这种在缓冲时去除反复数据对于防止不必要的计算和 DOM 操作是十分重要的。而后,在下一个的事件循环“tick”中,Vue 刷新队列并执行理论 (已去重的) 工作。Vue 在外部对异步队列尝试应用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不反对,则会采纳 setTimeout(fn, 0) 代替。(前面会写一篇对于 nextTick 的源码的文章)

21. 说说你对函数式组件的了解

1. 函数式组件须要在申明组件是指定 functional:true

2. 不须要实例化,所以没有 this,this 通过 render 函数的第二个参数 context 来代替

3. 没有生命周期钩子函数,不能应用计算属性,watch

4. 不能通过$emit 对外裸露事件,调用事件只能通过 context.listeners.click 的形式调用内部传入的事件

5. 因为函数式组件是没有实例化的,所以在内部通过 ref 去援用组件时,理论援用的是 HTMLElement 6. 函数式组件的 props 能够不必显示申明,所以没有在 props 外面申明的属性都会被主动隐式解析为 prop, 而一般组件所有未声明的属性都解析到 $attrs 外面,并主动挂载到组件根元素下面(能够通过 inheritAttrs 属性禁止)

长处

  • 1. 因为函数式组件不须要实例化,无状态,没有生命周期,所以渲染性能要好于一般组件
  • 2. 函数式组件构造比较简单,代码构造更清晰

22. 常见的 Vue 性能优化有哪些?

  • 响应式数据对象层级不要过深,非响应式数据不要放在 data 外面或者应用Object.freeze() 解冻数据
  • 正当应用 v -if 和 v-show,v-if 实用于切换不频繁的场景,v-show 实用于切换频繁的场景
  • computed 和 watch 辨别应用场景,能应用 computed 实现的就不必 watch(computed 具备缓存成果)
  • v-for 遍历必须加 key,key 最好是 id 值,并且防止同时应用 v-if
  • 大数据列表和表格性能优化 - 虚构列表 / 虚构表格
  • 避免外部透露,组件销毁后还应该把全局变量和事件销毁
  • 图片懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 适当采纳 keep-alive 缓存组件
  • 防抖、节流使用
  • 服务端渲染 SSR 或者 预渲染

23.Vue.mixin 的应用场景及原理

mixin(混入),提供了一种非常灵活的形式,来散发 Vue 组件中的可复用性能。混入对象能够蕴含任意组件选项。当组件应用混入对象时,所有混入对象的选项将被混入该组件自身的选项。混入也能够进行全局注册。应用时分外小心!一旦应用全局混入,它将影响每一个之后创立的 Vue 实例。

// mixin.js
export default {data() {
    return {title: '我是 mixin 中的 title'}
  },
  
}
<template>
  <div class="section">
  </div>
</template>

<script>
import mixin from "./mixin";
export default {
  name: "parent",
  mixins: [mixin],
  data() {
    return {
      currentIndex: 0,
      msg: "根组件",
    };
  },
  created() {console.log(this.$data);
    console.log("我是根组件中的 created");
  },
</script>

先打印出 我是 mixin 中的 created,而后接着打印合并后的 data(蕴含根组件的 data 与 mixin 中的 data),再打印出 我是根组件中的 created

应用场景:

在日常的开发中,咱们常常会遇到在不同的组件中常常会须要用到一些雷同或者类似的代码,这些代码的性能绝对独立
这时,能够通过 Vue 的 mixin 性能将雷同或者类似的代码提出来。

原理:

次要原理就在于这个 merOptions 办法

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {if (child.mixins) { // 判断有没有 mixin 有的话递归进行合并
    for (let i = 0, l = child.mixins.length; i < l; i++) {parent = mergeOptions(parent, child.mixins[i], vm)
    }
}
  const options = {} 
  let key
  for (key in parent) {mergeField(key) // 先遍历 parent 的 key
  }
  for (key in child) {if (!hasOwn(parent, key)) { // 如果 parent 曾经解决过某个 key 就不解决了
      mergeField(key) // 解决 child 中的 key 也就 parent 中没有解决过的 key
    }
  }
  function mergeField (key) {const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key) // 依据不同类型的 options 调用 strats 中不同的办法进行合并
  }
  return options
}

优先递归解决 mixins,先遍历合并 parent 中的 key,调用 mergeField 办法进行合并,而后保留在变量 options,再遍历 child,合并补上 parent 中没有的 key,调用 mergeField 办法进行合并,保留在变量 options。

24.Vue.extend 的作用与原理

应用根底 Vue 结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。

data 选项是特例,须要留神 – 在 Vue.extend() 中它必须是函数,该办法返回一个与 Vue 具备雷同性能的构造函数(其实为创立了一个组件)- 属性 options 是 合并 根底 Vue 结构器 与 extend 的参数 的对象,

原理:

Vue.extend 外围思路就是新建一个 VueComponent 构造函数 命名为 Sub,通过将 VueComponent 的原型指向 Vue 构造函数 的原型的形式继承 Vue 构造函数 原型上所有的属性和办法,接着查看传入的 extendOptions 是否领有 props 和 computed 属性,如果有就进行初始化。如果检测出传入的 extendOptions 中含有 name 属性,则将其主动放入 VueComponent 的全局组件中。而后将 Vue.options 保留到 VueComponent.superOptions 属性中,将传入的 extendOptions 保留到 VueComponent.extendOptions 属性中,并将传入的 extendOptions 封存一份保留到 VueComponent.sealedOptions 中。最初将 VueComponent 返回,这就是继承了 Vue 构造函数 的 Vue 组件 的构造函数。(前面会写文章细讲)

与 mixin 的区别:

  • mixin 是对 Vue 类的 options 进行混入。所有 Vue 的实例对象都会具备混入进来的配置行为。
  • extend 是产生一个继承自 Vue 类的子类,只会影响这个子类的实例对象,不会对 Vue 类自身以及 Vue 类的实例对象产生影响。

25. 既然 vue 通过数据劫持能够精准的探测数据变动,为什么还要进行 diff 检测差别?

当初前端框架有两种数据变动侦测形式,一种是 pull,一种是 push.

  • pull 的代表是 React,在进行 setState 操作后显示更新数据,React 会应用 diff 算法一层层找出差别,而后 patch 到 DOM 树上,React 一开始不晓得那里变动了,只是晓得变动了,而后暴力进行查找那变动了,另一个代表是 Angular 的脏查看。
  • Vue 的响应式零碎就是 Push 的代表,Vue 初始化的时候就会对 data 的数据进行依赖收集,因而 Vue 能实时晓得那里产生了变动,个别绑定的细粒度过高,会生成大量的 Watcher 实例,则会造成过大的内存和依赖追踪的开销,而细粒度过低无奈侦测到变动。因而,Vue 采纳的是中等细粒度的计划,只针对组件级别的进行响应式监听也就是 push,这样能够晓得那个组件产生了变动,再对组件进行 diff 算法找到具体变动的地位,这是 pull 操作,vue 是 pull + push 联合进行变动侦测的。

举荐浏览

  • 【面试必备】前端常见的排序算法
  • 前端常见的平安问题及防范措施
  • 为什么大厂前端监控都在用 GIF 做埋点?
  • 前端人员不要只晓得 KFC,你应该理解 BFC、IFC、GFC 和 FFC
  • Promise、Generator、Async 有什么区别?
  • 2022 年了你还不理解箭头函数与一般函数的区别吗?
  • 从如何应用到如何实现一个 Promise
  • 超具体解说页面加载过程

原文首发地址点这里,欢送大家关注公众号 「前端南玖」,如果你想进前端交换群一起学习,请点这里

我是南玖,咱们下期见!!!

退出移动版