共计 9405 个字符,预计需要花费 24 分钟才能阅读完成。
2021 秋招 vue 面试题 + 答案
vue 视频教程系列:
Vue3.0 新个性教程
视频教程:点击观看
残缺教程目录:点击查看
Vue 源码解析系列
视频教程:点击观看
残缺教程目录:点击查看
闲云游览我的项目(vue+element-ui)
视频教程:点击观看
残缺教程目录:点击查看
前端 Vue3.0 从 0 到 1 手把手撸码搭建治理后盾零碎
视频教程:点击观看
残缺教程目录:点击查看
你有对 Vue 我的项目进行哪些优化?
如果没有对 Vue 我的项目没有进行过优化总结的同学,能够参考本文作者的另一篇文章《Vue 我的项目性能优化 — 实际指南》,文章次要介绍从 3 个大方面,22 个小方面具体解说如何进行 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 查找性能瓶颈
vue3.0 个性你有什么理解的吗?
Vue 3.0 正走在公布的路上,Vue 3.0 的指标是让 Vue 外围变得更小、更快、更弱小,因而 Vue 3.0 减少以下这些新个性:
(1)监测机制的扭转
3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言笼罩的反馈性跟踪。这打消了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限度:
- 只能监测属性,不能监测对象
- 检测属性的增加和删除;
- 检测数组索引和长度的变更;
- 反对 Map、Set、WeakMap 和 WeakSet。
新的 observer 还提供了以下个性:
- 用于创立 observable 的公开 API。这为中小规模场景提供了简略轻量级的跨组件状态治理解决方案。
- 默认采纳惰性察看。在 2.x 中,不论反应式数据有多大,都会在启动时被察看到。如果你的数据集很大,这可能会在利用启动时带来显著的开销。在 3.x 中,只察看用于渲染应用程序最后可见局部的数据。
- 更准确的变更告诉。在 2.x 中,通过 Vue.set 强制增加新属性将导致依赖于该对象的 watcher 收到变更告诉。在 3.x 中,只有依赖于特定属性的 watcher 才会收到告诉。
- 不可变的 observable:咱们能够创立值的“不可变”版本(即便是嵌套属性),除非零碎在外部临时将其“解禁”。这个机制可用于解冻 prop 传递或 Vuex 状态树以外的变动。
- 更好的调试性能:咱们能够应用新的 renderTracked 和 renderTriggered 钩子准确地跟踪组件在什么时候以及为什么从新渲染。
(2)模板
模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会从新渲染,而 3.0 把作用域插槽改成了函数的形式,这样只会影响子组件的从新渲染,晋升了渲染的性能。
同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来不便习惯间接应用 api 来生成 vdom。
(3)对象式的组件申明形式
vue2.x 中的组件是通过申明的形式传入一系列 option,和 TypeScript 的联合须要通过一些装璜器的形式来做,尽管能实现性能,然而比拟麻烦。3.0 批改了组件的申明形式,改成了类式的写法,这样使得和 TypeScript 的联合变得很容易。
此外,vue 的源码也改用了 TypeScript 来写。其实当代码的性能简单之后,必须有一个动态类型零碎来做一些辅助治理。当初 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外裸露的 api 更容易联合 TypeScript。动态类型零碎对于简单代码的保护的确很有必要。
(4)其它方面的更改
vue3.0 的扭转是全面的,下面只波及到次要的 3 个方面,还有一些其余的更改:
- 反对自定义渲染器,从而使得 weex 能够通过自定义渲染器的形式来扩大,而不是间接 fork 源码来改的形式。
- 反对 Fragment(多个根节点)和 Protal(在 dom 其余局部渲染组建内容)组件,针对一些非凡的场景做了解决。
- 基于 treeshaking 优化,提供了更多的内置性能。
vue 中应用了哪些设计模式
1. 工厂模式 – 传入参数即可创立实例
虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode
2. 单例模式 – 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉
3. 公布 - 订阅模式 (vue 事件机制)
4. 观察者模式 (响应式数据原理)
5. 装璜模式: (@装璜器的用法)
6. 策略模式 策略模式指对象有某个行为, 然而在不同的场景中, 该行为有不同的实现计划 - 比方选项的合并策略
… 其余模式欢送补充
Vue 模板编译原理
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)第二步是对 AST 进行动态节点标记,次要用来做虚构 DOM 的渲染优化(优化器)第三步是 应用 element ASTs 生成 render 函数代码字符串(代码生成器)
相干代码如下
export function compileToFunctions(template) {
// 咱们须要把 html 字符串变成 render 函数
// 1. 把 html 代码转成 ast 语法树 ast 用来形容代码自身造成树结构 不仅能够形容 html 也能形容 css 以及 js 语法
// 很多库都使用到了 ast 比方 webpack babel eslint 等等
let ast = parse(template);
// 2. 优化动态节点
// 这个有趣味的能够去看源码 不影响外围性能就不实现了
// if (options.optimize !== false) {// optimize(ast, options);
// }
// 3. 通过 ast 从新生成代码
// 咱们最初生成的代码须要和 render 函数一样
// 相似_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))
// _c 代表创立元素 _v 代表创立文本 _s 代表文 Json.stringify-- 把对象解析成文本
let code = generate(ast);
// 应用 with 语法扭转作用域为 this 之后调用 render 函数能够应用 call 扭转 this 不便 code 外面的变量取值
let renderFn = new Function(`with(this){return ${code}}`);
return renderFn;
}
模板编译原理详解 传送门
Vue.extend 作用和原理
官网解释:Vue.extend 应用根底 Vue 结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。
其实就是一个子类结构器 是 Vue 组件的外围 api 实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并
相干代码如下
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;
};
}
Vue 组件原理详解 传送门
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();}
}
nextTick 原理详解 传送门
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 模版编译原理晓得吗,能简略说一下吗?
简略说,Vue 的编译过程就是将 template
转化为 render
函数的过程。会经验以下阶段:
- 生成 AST 树
- 优化
- codegen
首先解析模版,生成AST 语法树
(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是 将优化后的 AST 树转换为可执行的代码
。
Vue 中组件生命周期调用程序说一下
组件的调用程序都是 先父后子
, 渲染实现的程序是 先子后父
。
组件的销毁操作是 先父后子
,销毁实现的程序是 先子后父
。
加载渲染过程
父 beforeCreate-> 父 created-> 父 beforeMount-> 子 beforeCreate-> 子 created-> 子 beforeMount- > 子 mounted-> 父 mounted
子组件更新过程
父 beforeUpdate-> 子 beforeUpdate-> 子 updated-> 父 updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父 beforeDestroy-> 子 beforeDestroy-> 子 destroyed-> 父 destroyed
Vue 组件间通信有哪几种形式?
Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。
(1)props / $emit
实用 父子组件通信 这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。
(2)ref 与 $parent / $children
实用 父子组件通信
ref
:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例$parent / $children
:拜访父 / 子实例
(3)EventBus($emit / $on)
实用于 父子、隔代、兄弟组件通信 这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。
(4)$attrs/$listeners
实用于 隔代组件通信
$attrs
:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 (class 和 style 除外)。当一个组件没有申明任何prop
时,这里会蕴含所有父作用域的绑定 (class 和 style 除外),并且能够通过v-bind="$attrs"
传入外部组件。通常配合inheritAttrs
选项一起应用。$listeners
:蕴含了父作用域中的 (不含 .native 润饰器的)v-on
事件监听器。它能够通过v-on="$listeners"
传入外部组件
(5)provide / inject
实用于 隔代组件通信 先人组件中通过 provider
来提供变量,而后在子孙组件中通过 inject
来注入变量。provide / inject API
次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。(6)Vuex
实用于 父子、隔代、兄弟组件通信 Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state)。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
- 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。
能说下 vue-router 中罕用的 hash 和 history 路由模式实现原理吗?
(1)hash 模式的实现原理
晚期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简略,location.hash 的值就是 URL 中 # 前面的内容。比方上面这个网站,它的 location.hash 的值为 ‘#search’:
https://www.word.com#search
hash 路由模式的实现次要是基于上面几个个性:
- URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 局部不会被发送;
hash 值的扭转,都会在浏览器的拜访历史中减少一个记录。因而咱们能通过浏览器的回退、后退按钮管制 hash 的切换;
- 能够通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会产生扭转;或者应用 JavaScript 来对 loaction.hash 进行赋值,扭转 URL 的 hash 值;
- 咱们能够应用 hashchange 事件来监听 hash 值的变动,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理
HTML5 提供了 History API 来实现 URL 的变动。其中做最次要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 能够在不进行刷新的状况下,操作浏览器的历史纪录。惟一不同的是,前者是新增一个历史记录,后者是间接替换以后的历史记录,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
history 路由模式的实现次要基于存在上面几个个性:
- pushState 和 repalceState 两个 API 来操作实现 URL 的变动;
- 咱们能够应用 popstate 事件来监听 url 的变动,从而对页面进行跳转(渲染);
- history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时咱们须要手动触发页面跳转(渲染)。
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 和 React 组件化的思维
- 1. 咱们在各个页面开发的时候,会产生很多反复的性能,比方 element 中的 xxxx。像这种纯正非页面的 UI,便成为咱们罕用的 UI 组件,最后的前端组件也就仅仅指的是 UI 组件
- 2. 随着业务逻辑变得越来多是,咱们就想要咱们的组件能够解决很多事,这就是咱们常说的组件化,这个组件就不是 UI 组件了,而是包具体业务的业务组件
- 3. 这种开发思维就是分而治之。最大水平的升高开发难度和保护老本的成果。并且能够多人合作,每个人写不同的组件,最初像撘积木一样的把它形成一个页面
写过自定义指令吗 原理是什么
指令实质上是装璜器,是 vue 对 HTML 元素的扩大,给 HTML 元素减少自定义性能。vue 编译 DOM 时,会找到指令对象,执行指令的相干办法。
自定义指令有五个生命周期(也叫钩子函数),别离是 bind、inserted、update、componentUpdated、unbind
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。2. inserted:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。4. componentUpdated:被绑定元素所在模板实现一次更新周期时调用。5. unbind:只调用一次,指令与元素解绑时调用。
原理
1. 在生成 ast 语法树时,遇到指令会给以后元素增加 directives 属性
2. 通过 genDirectives 生成指令代码
3. 在 patch 前将指令的钩子提取到 cbs 中, 在 patch 过程中调用对应的钩子
4. 当执行指令对应钩子函数时,调用对应指令定义的办法
生命周期钩子是如何实现的
Vue 的生命周期钩子外围实现是利用公布订阅模式先把用户传入的的生命周期钩子订阅好(外部采纳数组的形式存储)而后在创立组件实例的过程中会一次执行对应的钩子办法(公布)
相干代码如下
export function callHook(vm, hook) {
// 顺次执行生命周期对应的办法
const handlers = vm.$options[hook];
if (handlers) {for (let i = 0; i < handlers.length; i++) {handlers[i].call(vm); // 生命周期外面的 this 指向以后实例
}
}
}
// 调用的时候
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = mergeOptions(vm.constructor.options, options);
callHook(vm, "beforeCreate"); // 初始化数据之前
// 初始化状态
initState(vm);
callHook(vm, "created"); // 初始化数据之后
if (vm.$options.el) {vm.$mount(vm.$options.el);
}
};