金三银四的 Vue 面试准备

前言
为了金三银四的跳槽季做准备,并且我是 vue 技术栈的,所以整理了若干个 vue 的面试题。
每次看别人的博客,都会不自主的去看答案,为了方便检验自己的掌握程度,我特意将答案折叠起来,大家可以先看题目,在脑海中想象一下如果你被问到会怎么回答,然后再展开答案看看和自己的答案有什么不同。
答案非官方,仁者见仁智者见智,仅供参考。
基础使用
MVVM、MVC 有什么区别
MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。
其中
View负责页面的显示逻辑,Model负责存储页面的业务数据,以及对相应数据的操作。Controller层是View层和Model层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller中的事件触发器就开始工作了,通过调用Model层,来完成对Model的修改,然后Model层再去通知View层更新。
MVVM 分为 Model、View、ViewModel。
Model代表数据模型,数据和业务逻辑都在Model层中定义;View代表UI视图,负责数据的展示;ViewMode负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。 这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作 DOM。
Vue 并没有完全遵循 MVVM 思想呢?
严格的
MVVM要求View不能和Model直接通信,而Vue提供了$refs这个属性,让Model可以直接操作View,违反了这一规定,所以说Vue没有完全遵循MVVM。
Vue 的优点
渐进式框架:可以在任何项目中轻易的引入;
轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十
kb;简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
双向数据绑定:在数据操作方面更为简单;
组件化:很大程度上实现了逻辑的封装和重用,在构建单页面应用方面有着独特的优势;
视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
对 SPA 单页面的理解,它的优缺点分别是什么?
SPA 仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
有利于前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
初次加载耗时多:为实现单页
Web应用功能及显示效果,需要在加载页面的时候将JavaScript、CSS统一加载,部分页面按需加载;不利于
SEO:由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势。
怎样理解 Vue 的单向数据流?
父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。在子组件内部改变 prop 的时候 , Vue 会在浏览器的控制台中发出警告。
子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
有两种常见的试图改变一个 prop 的情形 :
这个
prop用来传递一个初始值;这个子组件接下来希望将其作为一个本地的prop数据来使用。 在这种情况下,最好定义一个本地的data属性并将这个prop用作其初始值:这个
prop以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个prop的值来定义一个计算属性
Data 为什么是一个函数?
因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
Computed 和 Watch 有什么区别?
对于 Computed:
它支持缓存,只有依赖的数据发生了变化,才会重新计算
不支持异步,当
Computed中有异步操作时,无法监听数据的变化如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用 computed
如果
computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。
对于 Watch:
它不支持缓存,当一个属性发生变化时,它就会触发相应的操作
支持异步监听
监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
监听数据必须是
data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作函数有两个的参数:immediate:组件加载立即触发回调函数 deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。
Computed 和 Methods 的区别
共同点:
可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的。
不同点:
computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值;
method: 调用总会执行该函数。
.Sync 的作用是什么?
vue 修饰符 sync 的功能是:当父组件提供了一个数据,而子组件想要去更改这个数据,但是 Vue 的规则不能让子组件去修改父组件的数据,就需要通过 this.$emit 和 $event,来实现数据修改的目的。
绑定事件 @click=handler 和 @click=handler() 那个正确?有什么区别?
都可以,不带括号会传进来一个事件对象,带括号的不会
事件有哪些修饰符?
「事件修饰符」
.stop 阻止事件继续传播
.prevent 阻止标签默认行为
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
.self 只当在
event.target是当前元素自身时触发处理函数.once 事件将只会触发一次
.passive 告诉浏览器你不想阻止事件的默认行为
「v-model 的修饰符」
.lazy 通过这个修饰符,转变为在
change事件再同步.number 自动将用户的输入值转化为数值类型
.trim 自动过滤用户输入的首尾空格
「键盘事件的修饰符」
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
「系统修饰键」
.ctrl
.alt
.shift
.meta
「鼠标按钮修饰符」
.left
.right
.middle
什么是插槽?具名插槽?作用域插槽?原理是什么?
slot 又名插槽,是 Vue 的内容分发机制,插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot 又分三类,默认插槽,具名插槽和作用域插槽。
默认插槽:又名匿名插槽,当
slot没有指定name属性值的时候,默认显示的插槽,一个组件内只允许有一个匿名插槽。具名插槽:带有具体名字的插槽,也就是带有
name属性的slot,一个组件可以出现多个具名插槽。作用域插槽:可以是匿名插槽,也可以是具名插槽,该插槽在渲染时,父组件可以使用子组件内部的数据。
实现原理:当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,存放在 vm.$slot 中,默认插槽为 vm.$slot.default,具名插槽为 vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用 $slot 中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
Vue 中如何实现过度效果?如何实现列表过度?
过渡效果,当然只有 dom 从显示到隐藏或隐藏到显示才能用
Vue.js 为我们提供了内置的过渡组件 transition 和 transition-group
Vue 将元素的过渡分为四个阶段,进入前,进入后,消失前,消失后
支持 mode 属性,可选值:
in-out:要进入的先进入,然后要消失的再消失out-in:要消失的先消失,然后要进入的再进入
多个元素需要加上过渡效果可以使用 name 属性进行区分。
可以配合 animate.css 实现更多的动画效果。
过滤器的作用,如何实现一个过滤器
过滤器是用来过滤数据的,在 Vue 选项中声明 filters 来实现一个过滤器,filters不会修改数据,而是处理数据,改变用户看到的输出。
使用场景:
需要格式化数据的情况,比如需要处理时间、价格等数据格式的输出 / 显示。
比如后端返回一个 年月日的日期字符串,前端需要展示为 多少天前 的数据格式,此时就可以用
fliters过滤器来处理数据。
过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在 插值表达式 {{ }} 和 v-bind 表达式 中,然后放在操作符 | 后面进行指示。
例如,在显示金额,给商品价格添加单位:
assets 和 static 的区别
相同点: assets 和 static 两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下,这是相同点
不相同点: assets 中存放的静态资源文件在项目打包时,也就是运行 npm run build 时会将 assets 中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在 static 文件中跟着 index.html 一同上传至服务器。static 中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是 static 中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于 assets 中打包后的文件提交较大点。在服务器中就会占据更大的空间。
建议: 将项目中 template需要的样式文件 js 文件等都可以放置在 assets 中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css 等文件可以放置在 static 中,因为这些引入的第三方文件已经经过处理,我们不再需要处理,直接上传。
对 SSR 的理解
SSR 大致的意思就是 vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端,这个过程就叫做服务端渲染。
(1)服务端渲染的优点:
更好的
SEO:SSR是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;首屏加载更快:
SPA会等待所有Vue编译后的js文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR直接由服务端渲染好页面直接返回显示,无需等待下载js文件及再去渲染等,所以SSR有更快的内容到达时间;
(2) 服务端渲染的缺点:
更多的开发条件限制:例如服务端渲染只支持
beforCreate和created两个钩子函数,不能进行
dom操作。
这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行。
Vue 的性能优化有哪些
(1)代码层面的优化
v-if和v-show区分使用场景computed和watch区分使用场景v-for遍历必须为item添加key,且避免同时使用v-if长列表性能优化
事件的销毁
图片资源懒加载
路由懒加载
第三方插件的按需引入
优化无限列表性能
服务端渲染
(2)Webpack 层面的优化
Webpack对图片进行压缩减少
ES6转为ES5的冗余代码提取公共代码
模板预编译
提取组件的
CSS优化
SourceMap构建结果输出分析
Vue项目的编译优化
(3)基础的 Web 技术的优化
开启
gzip压缩浏览器缓存
CDN的使用使用
Chrome Performance查找性能瓶颈
Vue 的首屏加载性能优化有哪些
优化前的大小
1.图片优化
之前为了方便开法, 背景图片直接在 assets 里面扔了一个 jpg , 导致加载这张图片的时候就用了十几秒, 于是乎我就把图片上传空间了, 然后改用网络地址。
2.禁止生成.map 文件
build 出来的 dist 文件夹里面有很多的 .map 文件,这些文件主要是帮助线上调试代码,禁止生成这些文件.
在 vue.config.js 里面加上这句。
3.路由懒加载
4.cdn 引入公共库
网上说可以把 import 注释掉,亲自操作会报错,也有资料说不用注释也不会打包。
一顿操作最后的文件,效果显著,app.js 还是很大
5.终极法宝 GZIP 压缩
做完这个感觉前四步都是小菜一碟,直接把 1.4m 的 app.js 干成一百多 kb ,其他的都不足挂齿了。
服务端也要配,不然不认识 GZIP 文件。
最垃圾的服务器通过以上几个优化,一样飞起来了!!!
vue 初始化页面闪动问题
使用 vue 开发时,在 vue 初始化之前,由于 div 是不归 vue 管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于 {{message}} 的字样,虽然一般情况下这个时间很短暂,但是我们还是有必要让解决这个问题的。
首先:在 css 里加上 [v-cloak] { display: none; } 。如果没有彻底解决问题,则在根元素加上 style="display: none;" :style="{display: block }"
Class 与 Style 如何动态绑定?
Class 可以通过对象语法和数组语法进行动态绑定:
对象语法:
数组语法:
Style 也可以通过对象语法和数组语法进行动态绑定:
对象语法:
数组语法:
如何让 CSS 只在当前组件中起作用?
在组件中的 style 标签中加上 scoped
如何获取 dom
ref="domName" 用法:this.$refs.domName
vue-loader 是什么?使用它的用途有哪些?
vue 文件的一个加载器,把 template/js/style转换成 js 模块。
生命周期
Vue 有哪些生命周期钩子?
Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)。
beforeCreate:是new Vue()之后触发的第一个钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问。created:在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom。beforeMount:发生在挂载之前,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。mounted:在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作。beforeUpdate:发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。updated:发生在更新完成之后,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。beforeDestroy:发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。destroyed:发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
如果需要发送异步请求,最好放在哪个钩子内?
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
推荐在 created 钩子函数中调用异步请求,有以下优点:
能更快获取到服务端数据,减少页面
loading时间;ssr不支持beforeMount、mounted钩子函数,所以放在created中有助于一致性;
第一次页面加载会触发哪几个钩子?
beforeCreate,created,beforeMount,mounted
哪个钩子可以进行 dom 操作?
在钩子函数 mounted 被调用前,Vue 已经将编译好的模板挂载到页面上,所以在 mounted 中可以访问操作 DOM。
父子组件嵌套时,父组件和子组件生命周期钩子执行顺序是什么?
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated父组件更新过程
父beforeUpdate->父updated销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
父子组件嵌套时,父组件视图和子组件视图谁先完成渲染?
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
可知子组件先完成渲染
keep-alive 中的生命周期哪些
对应两个钩子函数
activated和deactivated,当组件被激活时,触发钩子函数activated,当组件被移除时,触发钩子函数deactivated。
指令相关
说说 vue 内置指令
什么是自定义指令?有哪些生命周期?
是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。
自定义指令有五个生命周期
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
unbind:只调用一次,指令与元素解绑时调用。
v-text 和 v-html 有什么区别?
v-text和{{}}表达式渲染数据,不解析标签。v-html不仅可以渲染数据,而且可以解析标签。
v-if 和 v-for 的优先级
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐 v-if 和 v-for 同时使用。如果 v-if 和 v-for 一起用的话,vue 中的的会自动提示 v-if 应该放到外层去。
V-if 和 v-show 有什么区别?
手段:
v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐;编译过程:
v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换;编译条件:
v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译;v-show是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留;性能消耗:
v-if有更高的切换消耗;v-show有更高的初始渲染消耗;使用场景:
v-if适合运营条件不大可能改变;v-show适合频繁切换。
组件的 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 表单元素为例:
v-model 可以被用在自定义组件上吗?如果可以,如何使用?
如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:
v-on 可以监听多个方法吗?
可以
组件相关
组件通信的 N 种方式
(1)props / $emit 适用 父子组件通信
(2)ref 适用 父子组件通信
ref:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例
(3)$parent / $children / $root:访问父 / 子实例 / 根实例
(4)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
(5)$attrs/$listeners 适用于 隔代组件通信
$attrs:包含了父作用域中不被prop所识别 (且获取) 的特性绑定 (class和style除外 )。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定 (class和style除外 ),并且可以通过v-bind="$attrs"传入内部组件。通常配合inheritAttrs选项一起使用。$listeners:包含了父作用域中的 (不含.native修饰器的)v-on事件监听器。它可以通过v-on="$listeners"传入内部组件
(6)provide / inject 适用于 隔代组件通信
祖先组件中通过 provide 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(7)Vuex 适用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。store 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。改变
store中的状态的唯一途径就是显式地提交(commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
(8)插槽
Vue3 可以通过 usesolt 获取插槽数据。
(9)mitt.js 适用于任意组件通信
Vue3 中移除了 $on,$off等方法,所以 EventBus 不再使用,相应的替换方案就是 mitt.js
Vue3 和 vue2 全局组件和局部组件注册的方式?
Vue2:
Vue.component()Vue3:
app.component()
什么是动态组件?动态组件的钩子如何执行?
让多个组件使用同一个挂载点,并动态切换,这就是动态组件
简单的说,动态组件就是将几个组件放在一个挂载点下,这个挂载点就是标签,其需要绑定 is 属性,属性值为父组件中的变量,变量对应的值为要挂载的组件的组件名,然后根据父组件里某个变量来动态显示哪个,也可以都不显示。
缓存 <keep-alive>
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
可以将动态组件放到组件内对动态组件进行缓存,这样动态组件进行切换的时候,就不会每次都重新创建了。
Keep-alive 的作用?使用 keep-alive 的组件如何监控组件切换?
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
一般结合路由和动态组件一起使用,用于缓存组件;
提供
include和exclude属性,两者都支持字符串或正则表达式,include表示只有名称匹配的组件会被缓存,exclude表示任何名称匹配的组件都不会被缓存 ,其中exclude的优先级比include高;对应两个钩子函数
activated和deactivated,当组件被激活时,触发钩子函数activated,当组件被移除时,触发钩子函数deactivated。
父组件如何监听子组件的生命周期?
比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
原理相关
Vue 初始化时都做了什么?
第一部分
⭐ 每个 vue 实例都有一个 _uid,并且是依次递增的,确保唯一性。
⭐ vue 实例不应该是一个响应式的,做个标记。
第二部分
⭐ 如果是子组件,将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率。
⭐ 如果是根组件,对 options 进行合并,vue 会将相关的属性和方法都统一放到 vm.$options 中。vm.$options 的属性来自两个方面,一个是 Vue 的构造函数 vm.constructor 预先定义的,一个是 new Vue 时传入的入参对象。
第三部分
⭐ initProxy / vm._renderProxy 在非生产环境下执行了 initProxy 函数,参数是实例;在生产环境下设置了实例的 _renderProxy 属性为实例自身。
⭐ 设置了实例的 _self 属性为实例自身。
⭐ initLifecycle 初始化组件实例关系属性 , 比如 $parent、$children、$root、$refs 等 (不是组件生命周期 mounted , created...)
⭐ initEvents 初始化自定义事件。
⭐ initRender 初始化插槽 , 获取 this.slots , 定义 this._c , 也就是 createElement 方法 , 平时使用的 h 函数。
⭐ callHook 执行 beforeCreate 生命周期函数。
⭐ initInjections 初始化 inject 选项
⭐ initState 响应式原理的核心 , 处理 props、methods、computed、data、watch 等。
⭐ initProvide 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上。
⭐ callHook 执行 created 生命周期函数。
第四部分
⭐ 如果有 el 属性,则调用 vm.$mount 方法挂载 vm ,挂载的目标就是把模板渲染成最终的 DOM。
⭐ 不存在 el 的时候不挂载 , 需要手动挂载。
数据响应式的原理
Vue.js 是采用 数据劫持 结合 发布者-订阅者模式 的方式,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
使用
observe对需要响应式的数据进行递归,将对像的所有属性及其子属性,都加上setter和getter这样的话,给这个对象的某个属性赋值的时候,就会触发setter,那么就能监听到了数据变化。compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
在自身实例化时往属性订阅器(
dep)里面添加自己自身必须有一个
update()方法待属性变动触发
dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,完成视图更新。
总结:通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到一个数据响应式的效果。
使用 Object.defineProperty() 来进行数据劫持有什么缺点?
无法劫持以下操作
给对象新增属性
给对象删除属性
大部分的操作数组
Vue 框架怎么实现对象和数组的监听?
Vue 框架是通过 遍历数组 和 递归遍历对象,从而达到利用 Object.defineProperty() 对对象和数组的部分方法的操作进行监听。
Vue 中给 data 中的对象属性添加一个新的属性或删除一个属性时会发生什么?如何解决?
什么都不会发生,因为 Object.defineProperty() 监听不到这类变化。
可以使用 vm.$set 和 Vue.set 方法去添加一个属性。
可以使用 vm.$delete 和 Vue.delete 方法去删除一个属性。
如何解决索引赋值或者修改数组长度无法改变视图?
由于 Vue 只改写了 7 种修改数组的方法,所以 Vue 不能检测到以下数组的变动:
当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue当你修改数组的长度时,例如:
vm.items.length = newLength
为了解决第一个问题,Vue 提供了以下操作方法:
为了解决第二个问题,Vue 提供了以下操作方法:
数组的响应式是怎么实现的?
择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写
所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新
$nextTick 的原理是什么?nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法。
简单的理解是:当数据更新了,在 dom 中渲染后, 自动执行该函数。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,Vue 是异步执行 DOM 更新的。created 钩子函数进行的 DOM 操作一定要放在 Vue.nextTick() 的回调函数中,原因是在函数执行的时候 DOM 其实并未进行任何渲染。常用的场景是在进行获取数据后,需要对新视图进行下一步操作或者其他操作时,发现获取不到 dom。因为赋值操作只完成了数据模型的改变并没有完成视图更新。
列表循环时 key 有什么作用?key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有 4 种比较方式:newStartIndex 和 oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了 key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。
所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速,因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,源码如下:
为什么不建议用 index 作为 key?
使用 index 作为 key 和没写基本上没区别,因为不管数组的顺序怎么颠倒,index 都是 0, 1, 2... 这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作。
v-if、v-show、v-html 的原理
v-if 会调用
addIfCondition方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染;v-show 会生成
vnode,render的时候也会渲染成真实节点,只是在render过程中会在节点的属性中修改show属性值,也就是常说的display;v-html 会先移除节点下的所有节点,设置
innerHTML为v-html的值。
Vue 中封装的数组方法有哪些,其如何实现页面更新
数组就是使用 object.defineProperty 重新定义数组的每一项,那能引起数组变化的方法我们都是知道的,
pop、push、shift、unshift、splice、sort、reverse
这七种,只要这些方法执行改了数组内容,我就更新内容就好了,是不是很好理解。
是用来函数劫持的方式,重写了数组方法,具体呢就是更改了数组的原型,更改成自己的,用户调数组的一些方法的时候,走的就是自己的方法,然后通知视图去更新。
数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象才能进行观测,观测过的也不会进行观测)
vue3:改用 proxy ,可直接监听对象数组的变化。
Vue 模板渲染的原理是什么?
vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。
模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。
parse 阶段:使用大量的正则表达式对
template字符串进行解析,将标签、指令、属性等转化为抽象语法树AST。optimize 阶段:遍历
AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行diff比较时,直接跳过这一些静态节点,优化runtime的性能。generate 阶段:将最终的
AST转化为render函数字符串。
说一下什么是 Virtual DOM
Virtual DOM 是 DOM 节点在 JavaScript 中的一种抽象数据结构,之所以需要虚拟 DOM,是因为浏览器中操作 DOM 的代价比较昂贵,频繁操作 DOM 会产生性能问题。虚拟 DOM 的作用是在每一次响应式数据发生变化引起页面重渲染时,Vue 对比更新前后的虚拟 DOM,匹配找出尽可能少的需要更新的真实 DOM,从而达到提升性能的目的。
Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?
Vue是异步执行DOM更新。只要观察到数据变化,
Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个
watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个的事件循环
tick中,Vue刷新队列并执行实际 (已去重的) 工作。Vue在内部尝试对异步队列使用原生的Promise.then和MessageChannel,如果执行环境不支持,会采用setTimeout(fn, 0)代替。
例如,当你设置 vm.someData = 'new value' ,该组件不会立即重新渲染。
当刷新队列时,组件会在事件循环队列清空时的下一个
tick更新。多数情况我们不需要关心这个过程,但是如果你想在
DOM状态更新后做点什么,这就可能会有些棘手。虽然
Vue.js通常鼓励开发人员沿着 “数据驱动” 的方式思考,避免直接接触DOM,但是有时我们确实要这么做。为了在数据变化之后等待Vue完成更新DOM,可以在数据变化之后立即使用Vue.nextTick(callback)。这样回调函数在DOM更新完成后就会调用。
Vue.mixin 的使用场景和原理
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立,可以通过 Vue 的 mixin 功能抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
Vue.extend 作用和原理
其实就是一个子类构造器 ,是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并。
Vue 事件绑定原理
原生事件绑定是通过 addEventListener 绑定给真实元素的,组件事件绑定是通过 Vue 自定义的 $on 实现的。如果要在组件上使用原生事件,需要加 .native 修饰符,这样就相当于在父组件中把子组件当做普通 html 标签,然后加上原生事件。
on、emit 是基于发布订阅模式的,维护一个事件中心,on 的时候将事件按名称存在事件中心里,称之为订阅者,然后 emit 将对应的事件进行发布,去执行事件中心里的对应的监听器。
虚拟 DOM 实现原理?
虚拟 DOM 的实现原理主要包括以下 3 部分:
用
JavaScript对象模拟真实DOM树,对真实DOM进行抽象;diff算法 — 比较两棵虚拟DOM树的差异;pach算法 — 将两个虚拟DOM对象的差异应用到真正的DOM树。
虚拟 dom 和真实 dom 的区别
虚拟
DOM不会进行排版与重绘操作虚拟
DOM就是把真实DOM转换为Javascript代码虚拟
DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗
生态相关
vue-router 路由模式有几种?
vue-router 有 3 种路由模式:hash、history、abstract:
hash: 使用
URL hash值来作路由。支持所有浏览器,包括不支持HTML5 History Api的浏览器;history : 依赖
HTML5 History API和服务器配置。abstract : 支持所有
JavaScript运行环境,如Node.js服务器端。如果发现没有浏览器的API,路由会自动强制进入这个模式.
路由的 hash 和 history 模式的区别
(1)hash 模式的实现原理
早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 #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 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:
history 路由模式的实现主要基于存在下面几个特性:
pushState和repalceState两个API来操作实现URL的变化 ;我们可以使用
popstate事件来监听url的变化,从而对页面进行跳转(渲染);history.pushState()或history.replaceState()不会触发popstate事件,这时我们需要手动触发页面跳转(渲染)。
如何获取页面的 hash 变化
监听 $route 对象
route 和 router 的区别
$router 是 VueRouter 的实例,在 script 标签中想要导航到不同的 URL,使用 $router.push 方法。返回上一个历史 history 用 $router.to(-1)
$route 为当前 router 跳转对象。里面可以获取当前路由的 name,path,query,parmas 等。
如何定义动态路由?如何获取传过来的动态参数?
可以通过query,param两种方式
区别:query通过url传参,刷新页面还在;params属性页面不在
params的类型:
配置路由格式:
/router/:id传递的方式:在
path后面跟上对应的值传递后形成的路径:
/router/123通过
$route.params.id获取传递的值
query 的类类型
配置路由格式
:/router也就是普通配置传递的方式:对象中使用
query的key作为传递方式传递后形成的路径
:/route?id=123通过
$route.query获取传递的值
Vue-router 导航守卫有哪些
Vue-router 跳转和 location.href 有什么区别
使用 location.href= /url 来跳转,简单方便,但是刷新了页面;
使用 history.pushState( /url ) ,无刷新页面,静态跳转;
引进 router ,然后使用 router.push( /url ) 来跳转,使用了 diff 算法,实现了按需加载,减少了 dom 的消耗。
其实使用 router 跳转和使用 history.pushState() 没什么差别的,因为 vue-router 就是用了 history.pushState() ,尤其是在 history 模式下。
params 和 query 的区别
用法:query 要用 path 来引入,params 要用 name 来引入,接收参数都是类似的,分别是 this.$route.query.name 和 this.$route.params.name 。
url 地址显示:query 更加类似于我们 ajax 中 get 传参,params 则类似于 post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示。
注意点:query 刷新不会丢失 query 里面的数据, params 刷新会丢失 params 里面的数据。
Vuex 的原理
Vue组件会触发(dispatch)一些事件或动作,也就是Actions;在组件中发出的动作,肯定是想获取或者改变数据的,但是在
vuex中,数据是集中管理的,不能直接去更改数据,所以会把这个动作提交(Commit)到Mutations中;然后
Mutations就去改变State中的数据;当
State中的数据被改变之后,就会重新渲染(Render)到Vue Components中去,组件展示更新后的数据,完成一个流程。
Vuex 有哪几种属性
有五种,分别
State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
Getter:允许组件从
Store中获取数据,mapGetters辅助函数仅仅是将store中的getter映射到局部计算属性。Mutation:是唯一更改
store中状态的方法,且必须是同步函数。Action:用于提交
mutation,而不是直接变更状态,可以包含任意异步操作。Module:允许将单一的
Store拆分为多个store且同时保存在单一的状态树中。
Vuex 和单纯的全局对象有什么区别?
Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。不能直接改变
store中的状态。改变store中的状态的唯一途径就是显式地提交mutation。
Vuex 中 action 和 mutation 的区别
Mutation专注于修改State,理论上是修改State的唯一途径;Action业务代码、异步请求。Mutation:必须同步执行;Action:可以异步,但不能直接操作State。在视图更新时,先触发
actions,actions再触发mutationmutation的参数是state,它包含store中的数据;action的参数是context,它是state的父级,包含state、getters等。
为什么 Vuex 的 mutation 中不能做异步操作?
Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过Action来提交mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。每个
mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。
Vuex 和 localStorage 的区别
(1)最重要的区别
vuex存储在内存中localstorage则以文件的方式存储在本地,只能存储字符串类型的数据,存储对象需要JSON的stringify和parse方法进行处理。 读取内存比读取硬盘速度要快
(2)应用场景
Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex用于组件之间的传值。localstorage是本地存储,是将数据存储到浏览器的方法,一般是在跨页面传递数据时使用 。Vuex能做到数据的响应式,localstorage不能
(3)永久性
刷新页面时 vuex 存储的值会丢失,localstorage 不会,对于不变的数据可以用 localstorage 可以代替 vuex。
Vuex 的严格模式是什么,有什么作用,如何开启?
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
在 Vuex.Store 构造器选项中开启,如下
如何在组件中批量使用 Vuex 的 getter 属性
使用 mapGetters 辅助函数, 利用对象展开运算符将 getter 混入 computed 对象中
如何在组件中重复使用 Vuex 的 mutation
使用 mapMutations 辅助函数,在组件中这么使用
Vuex 页面刷新数据丢失怎么解决
在
created周期中读取sessionstorage中的数据存储在store中,此时用vuex.store的replaceState方法,替换store的根状态在
beforeunload方法中将store.state存储到sessionstorage中。
3.0 相关
Vue3.0 有什么更新
Vue3.0 defineProperty 和 proxy 的区别
Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。
Proxy 与 Object.defineProperty 优劣对比
Proxy 的优势如下:
Proxy可以直接监听对象而非属性;Proxy可以直接监听数组的变化;Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改;Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
Object.defineProperty 的优势如下:
兼容性好,支持
IE9。
Vue 3.0 生命周期有哪些变化?
注意:3.0中的生命周期钩子要比2.X中相同生命周期的钩子要快
Composition API还新增了以下调试钩子函数:但是不怎么常用
onRenderTracked
onRenderTriggered
Vue 3.0 自定义指令有哪些变化?
先看看Vue2自定义指令的钩子
bind:当指令绑定在对应元素时触发。只会触发一次。
inserted:当对应元素被插入到
DOM的父元素时触发。update:当元素更新时,这个钩子会被触发(此时元素的后代元素还没有触发更新)。
componentUpdated:当整个组件(包括子组件)完成更新后,这个钩子触发。
unbind:当指令被从元素上移除时,这个钩子会被触发。也只触发一次。
在 Vue3 中,官方为了更有助于代码的可读性和风格统一,把自定义指令的钩子名称改的更像是组件生命周期,尽管他们是两回事
bind => beforeMount
inserted => mounted
beforeUpdate: 新的钩子,会在元素自身更新前触发
update => 移除!
componentUpdated => updated
beforeUnmount: 新的钩子,当元素自身被卸载前触发
unbind => unmounted
后语
最后祝大家在新的一年里,都能找到满意的工作,升职加薪,赚的盆满钵满!
最后
如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163 相互学习,我们会有专业的技术答疑解惑
如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点 star:http://github.crmeb.net/u/defu不胜感激 !
PHP 学习手册:https://doc.crmeb.com
技术交流论坛:https://q.crmeb.com











评论