乐趣区

关于vue.js:前端二面经典vue面试题指南

v-model 的原理?

咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:

  • text 和 textarea 元素应用 value 属性和 input 事件;
  • checkbox 和 radio 应用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例:

<input v-model='something'>

相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:<ModelChild v-model="message"></ModelChild>

子组件:<div>{{value}}</div>

props:{value: String},
methods: {test1(){this.$emit('input', '小红')
  },
},

Vue.extend 作用和原理

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

其实就是一个子类结构器 是 Vue 组件的外围 api 实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并

  • extend是结构一个组件的语法器。而后这个组件你能够作用到 Vue.component 这个全局注册办法里还能够在任意 vue 模板里应用组件。也能够作用到 vue 实例或者某个组件中的 components 属性中并在外部应用 apple 组件。
  • Vue.component你能够创立,也能够取组件。

相干代码如下

export default function initExtend(Vue) {
  let cid = 0; // 组件的惟一标识
  // 创立子类继承 Vue 父类 便于属性扩大
  Vue.extend = function (extendOptions) {
    // 创立子类的构造函数 并且调用初始化办法
    const Sub = function VueComponent(options) {this._init(options); // 调用 Vue 初始化办法
    };
    Sub.cid = cid++;
    Sub.prototype = Object.create(this.prototype); // 子类原型指向父类
    Sub.prototype.constructor = Sub; //constructor 指向本人
    Sub.options = mergeOptions(this.options, extendOptions); // 合并本人的 options 和父类的 options
    return Sub;
  };
}

MVVM、MVC、MVP 的区别

MVC、MVP 和 MVVM 是三种常见的软件架构设计模式,次要通过拆散关注点的形式来组织代码构造,优化开发效率。

在开发单页面利用时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文件里。页面的渲染、数据的获取,对用户事件的响应所有的应用逻辑都混合在一起,这样在开发简略我的项目时,可能看不出什么问题,如果我的项目变得复杂,那么整个文件就会变得简短、凌乱,这样对我的项目开发和前期的我的项目保护是十分不利的。

(1)MVC

MVC 通过拆散 Model、View 和 Controller 的形式来组织代码构造。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 利用了观察者模式,当 Model 层产生扭转的时候它会告诉无关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它次要负责用户与利用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来实现对 Model 的批改,而后 Model 层再去告诉 View 层更新。

(2)MVVM

MVVM 分为 Model、View、ViewModel:

  • Model 代表数据模型,数据和业务逻辑都在 Model 层中定义;
  • View 代表 UI 视图,负责数据的展现;
  • ViewModel 负责监听 Model 中数据的扭转并且管制视图的更新,解决用户交互操作;

Model 和 View 并无间接关联,而是通过 ViewModel 来进行分割的,Model 和 ViewModel 之间有着双向数据绑定的分割。因而当 Model 中的数据扭转时会触发 View 层的刷新,View 中因为用户交互操作而扭转的数据也会在 Model 中同步。

这种模式实现了 Model 和 View 的数据主动同步,因而开发者只须要专一于数据的保护操作即可,而不须要本人操作 DOM。

(3)MVP

MVP 模式与 MVC 惟一不同的在于 Presenter 和 Controller。在 MVC 模式中应用观察者模式,来实现当 Model 层数据发生变化的时候,告诉 View 层的更新。这样 View 层和 Model 层耦合在一起,当我的项目逻辑变得复杂的时候,可能会造成代码的凌乱,并且可能会对代码的复用性造成一些问题。MVP 的模式通过应用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的 Controller 只晓得 Model 的接口,因而它没有方法管制 View 层的更新,MVP 模式中,View 层的接口裸露给了 Presenter 因而能够在 Presenter 中将 Model 的变动和 View 的变动绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还蕴含了其余的响应逻辑。

Vue 组件如何通信?

Vue 组件通信的办法如下:

  • props/$emit+v-on: 通过 props 将数据自上而下传递,而通过 $emit 和 v -on 来向上传递信息。
  • EventBus: 通过 EventBus 进行信息的公布与订阅
  • vuex: 是全局数据管理库,能够通过 vuex 治理全局的数据流
  • $attrs/$listeners: Vue2.4 中退出的 $attrs/$listeners 能够进行跨级的组件通信
  • provide/inject:以容许一个先人组件向其所有子孙后代注入一个依赖,不管组件档次有多深,并在起上下游关系成立的工夫里始终失效,这成为了跨组件通信的根底

还有一些用 solt 插槽或者 ref 实例进行通信的,应用场景过于无限就不赘述了。

v-if 和 v-show 的区别

v-if 在编译过程中会被转化成三元表达式, 条件不满足时不渲染此节点。

v-show 会被编译成指令,条件不满足时管制款式将对应节点暗藏(display:none)

nextTick 应用场景和原理

nextTick 中的回调是在下次 DOM 更新循环完结之后执行的提早回调。在批改数据之后立刻应用这个办法,获取更新后的 DOM。次要思路就是采纳微工作优先的形式调用异步办法去执行 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();}
}

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

你有对 Vue 我的项目进行哪些优化?

(1)代码层面的优化

  • v-if 和 v-show 辨别应用场景
  • computed 和 watch 辨别应用场景
  • v-for 遍历必须为 item 增加 key,且防止同时应用 v-if
  • 长列表性能优化
  • 事件的销毁
  • 图片资源懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 优化有限列表性能
  • 服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化

  • Webpack 对图片进行压缩
  • 缩小 ES6 转为 ES5 的冗余代码
  • 提取公共代码
  • 模板预编译
  • 提取组件的 CSS
  • 优化 SourceMap
  • 构建后果输入剖析
  • Vue 我的项目的编译优化

(3)根底的 Web 技术的优化

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

为什么 vue 组件中 data 必须是一个函数?

对象为援用类型,当复用组件时,因为数据对象都指向同一个 data 对象,当在一个组件中批改 data 时,其余重用的组件中的 data 会同时被批改;而应用返回对象的函数,因为每次返回的都是一个新对象(Object 的实例),援用地址不同,则不会呈现这个问题。

Proxy 与 Object.defineProperty 优劣比照

Proxy 的劣势如下:

  • Proxy 能够间接监听对象而非属性;
  • Proxy 能够间接监听数组的变动;
  • Proxy 有多达 13 种拦挡办法, 不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
  • Proxy 返回的是一个新对象, 咱们能够只操作新的对象达到目标, 而 Object.defineProperty 只能遍历对象属性间接批改;

Proxy 作为新规范将受到浏览器厂商重点继续的性能优化,也就是传说中的新规范的性能红利;

Object.defineProperty 的劣势如下:

  • 兼容性好,反对 IE9,而 Proxy 的存在浏览器兼容性问题, 而且无奈用 polyfill 磨平,因而 Vue 的作者才申明须要等到下个大版本 (3.0) 能力用 Proxy 重写。

为什么 Vue 采纳异步渲染呢?

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

dep.notify() 告诉 watcher 进行更新, subs[i].update 顺次调用 watcher 的 update queueWatcher 将 watcher 去重放入队列,nextTick( flushSchedulerQueue)在下一 tick 中刷新 watcher 队列(异步)。

computed 和 watch 有什么区别?

computed:

  1. computed是计算属性, 也就是计算值, 它更多用于计算值的场景
  2. computed具备缓存性,computed 的值在 getter 执行后是会缓存的,只有在它依赖的属性值扭转之后,下一次获取 computed 的值时才会从新调用对应的 getter 来计算
  3. computed实用于计算比拟耗费性能的计算场景

watch:

  1. 更多的是「察看」的作用, 相似于某些数据的监听回调, 用于察看 props $emit 或者本组件的值, 当数据变动时来执行回调进行后续操作
  2. 无缓存性,页面从新渲染时值不变动也会执行

小结:

  1. 当咱们要进行数值计算, 而且依赖于其余数据,那么把这个数据设计为 computed
  2. 如果你须要在某个数据变动时做一些事件,应用 watch 来察看这个数据变动

用 VNode 来形容一个 DOM 构造

虚构节点就是用一个对象来形容一个实在的 DOM 元素。首先将 template(实在 DOM)先转成 ast ast 树通过 codegen 生成 render 函数, render 函数里的 _c 办法将它转为虚构 dom

虚构 DOM 实现原理?

虚构 DOM 的实现原理次要包含以下 3 局部:

  • 用 JavaScript 对象模仿实在 DOM 树,对实在 DOM 进行形象;
  • diff 算法 — 比拟两棵虚构 DOM 树的差别;
  • pach 算法 — 将两个虚构 DOM 对象的差别利用到真正的 DOM 树。

vue-loader 是什么?它有什么作用?

答复范例

  1. vue-loader是用于解决单文件组件(SFCSingle-File Component)的webpack loader
  2. 因为有了 vue-loader,咱们就能够在我的项目中编写SFC 格局的 Vue 组件,咱们能够把代码宰割为 <template><script><style>,代码会异样清晰。联合其余 loader 咱们还能够用 Pug 编写 <template>,用SASS 编写 <style>,用TS 编写 <script>。咱们的<style> 还能够独自作用以后组件
  3. webpack打包时,会以 loader 的形式调用vue-loader
  4. vue-loader被执行时,它会对 SFC 中的每个语言块用独自的 loader 链解决。最初将这些独自的块装配成最终的组件模块

原理

vue-loader会调用 @vue/compiler-sfc 模块解析 SFC 源码为一个描述符(Descriptor),而后为每个语言块生成 import 代码,返回的代码相似上面

// source.vue 被 vue-loader 解决之后返回的代码
​
// import the <template> block
import render from 'source.vue?vue&type=template'
// import the <script> block
import script from 'source.vue?vue&type=script'
export * from 'source.vue?vue&type=script'
// import <style> blocks
import 'source.vue?vue&type=style&index=1'
​
script.render = render
export default script

咱们想要 script 块中的内容被作为 js 解决(当然如果是 <script lang="ts"> 被作为 ts 理),这样咱们想要 webpack 把配置中跟 .js 匹配的规定都利用到形如 source.vue?vue&type=script 的这个申请上。例如咱们对所有 *.js 配置了babel-loader,这个规定将被克隆并利用到所在Vue SFC

import script from 'source.vue?vue&type=script

将被开展为:

import script from 'babel-loader!vue-loader!source.vue?vue&type=script'

相似的,如果咱们对 .sass 文件配置了style-loader + css-loader + sass-loader,对上面的代码

<style scoped lang="scss">

vue-loader将会返回给咱们上面后果:

import 'source.vue?vue&type=style&index=1&scoped&lang=scss'

而后 webpack 会开展如下:

import 'style-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'
  • 当解决开展申请时,vue-loader将被再次调用。这次,loader将会关注那些有查问串的申请,且仅针对特定块,它会选中特定块外部的内容并传递给前面匹配的loader
  • 对于 <script> 块,解决到这就能够了,然而 <template><style> 还有一些额定工作要做,比方

    • 须要用 Vue 模板编译器编译 template,从而失去render 函数
    • 须要对 <style scoped>中的 CSS 做后处理(post-process),该操作在 css-loader 之后但在 style-loader 之前

实现上这些附加的 loader 须要被注入到曾经开展的 loader 链上,最终的申请会像上面这样:

// <template lang="pug">
import 'vue-loader/template-loader!pug-loader!source.vue?vue&type=template'
​
// <style scoped lang="scss">
import 'style-loader!vue-loader/style-post-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'

虚构 DOM 的优缺点?

长处:

  • 保障性能上限: 框架的虚构 DOM 须要适配任何下层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;然而比起粗犷的 DOM 操作性能要好很多,因而框架的虚构 DOM 至多能够保障在你不须要手动优化的状况下,仍然能够提供还不错的性能,即保障性能的上限;
  • 无需手动操作 DOM: 咱们不再须要手动去操作 DOM,只须要写好 View-Model 的代码逻辑,框架会依据虚构 DOM 和 数据双向绑定,帮咱们以可预期的形式更新视图,极大进步咱们的开发效率;
  • 跨平台: 虚构 DOM 实质上是 JavaScript 对象, 而 DOM 与平台强相干,相比之下虚构 DOM 能够进行更不便地跨平台操作,例如服务器渲染、weex 开发等等。

毛病:

  • 无奈进行极致优化: 尽管虚构 DOM + 正当的优化,足以应答绝大部分利用的性能需求,但在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化。

watch 原理

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

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

diff 算法

<details open=””><summary>答案 </summary>
<p>
</p><p> 工夫复杂度: 个树的齐全 diff 算法是一个工夫复杂度为 O(n*3),vue 进行优化转化成 O(n)。</p>
<p> 了解:</p>
<ul>
<li>
<p> 最小量更新, key 很重要。这个能够是这个节点的惟一标识,通知 diff 算法,在更改前后它们是同一个 DOM 节点 </p>
<ul>
<li> 扩大 v-for 为什么要有 key,没有 key 会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加 key 只会挪动缩小操作 DOM。</li>
</ul>
</li>
<li>
<p> 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。</p>
</li>
<li>
<p> 只进行同层比拟,不会进行跨层比拟。</p>
</li>
</ul>
<p>diff 算法的优化策略:四种命中查找,四个指针 </p>
<ol>
<li>
<p> 旧前与新前(先比结尾,后插入和删除节点的这种状况)</p>
</li>
<li>
<p> 旧后与新后(比结尾,前插入或删除的状况)</p>
</li>
<li>
<p> 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)</p>
</li>
<li>
<p> 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)</p>
</li>
</ol>
<p></p>
</details>

— 问完下面这些如果都能很分明的话,根本 O 了 —

以下的这些简略的概念,你必定也是没有问题的啦😉

Vue computed 实现

  • 建设与其余属性(如:dataStore)的分割;
  • 属性扭转后,告诉计算属性从新计算

实现时,次要如下

  • 初始化 data,应用 Object.defineProperty 把这些属性全副转为 getter/setter
  • 初始化 computed, 遍历 computed 里的每个属性,每个 computed 属性都是一个 watch 实例。每个属性提供的函数作为属性的 getter,应用 Object.defineProperty 转化。
  • Object.defineProperty getter 依赖收集。用于依赖发生变化时,触发属性从新计算。
  • 若呈现以后 computed 计算属性嵌套其余 computed 计算属性时,先进行其余的依赖收集

双向绑定的原理是什么

咱们都晓得 Vue 是数据双向绑定的框架,双向绑定由三个重要局部形成

  • 数据层(Model):利用的数据及业务逻辑
  • 视图层(View):利用的展现成果,各类 UI 组件
  • 业务逻辑层(ViewModel):框架封装的外围,它负责将数据与视图关联起来

而下面的这个分层的架构计划,能够用一个专业术语进行称说:MVVM这里的管制层的外围性能便是“数据双向绑定”。天然,咱们只需弄懂它是什么,便能够进一步理解数据绑定的原理

了解 ViewModel

它的主要职责就是:

  • 数据变动后更新视图
  • 视图变动后更新数据

当然,它还有两个次要局部组成

  • 监听器(Observer):对所有数据的属性进行监听
  • 解析器(Compiler):对每个元素节点的指令进行扫描跟解析, 依据指令模板替换数据, 以及绑定相应的更新函数

v-show 与 v-if 有什么区别?

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

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

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

退出移动版