校招前端一面必会 vue 面试题指南
写过自定义指令吗?原理是什么
回答范例
Vue有一组默认指令,比如v-model或v-for,同时Vue也允许用户注册自定义指令来扩展 Vue 能力自定义指令主要完成一些可复用低层级
DOM操作使用自定义指令分为定义、注册和使用三步:
定义自定义指令有两种方式:对象和函数形式,前者类似组件定义,有各种生命周期;后者只会在
mounted 和updated时执行注册自定义指令类似组件,可以使用
app.directive()全局注册,使用{directives:{xxx}}局部注册使用时在注册名称前加上
v-即可,比如v-focus
我在项目中常用到一些自定义指令,例如:
复制粘贴
v-copy长按
v-longpress防抖
v-debounce图片懒加载
v-lazy按钮权限
v-premission页面水印
v-waterMarker拖拽指令
v-draggable
vue3中指令定义发生了比较大的变化,主要是钩子的名称保持和组件一致,这样开发人员容易记忆,不易犯错。另外在v3.2之后,可以在setup中以一个小写v开头方便的定义自定义指令,更简单了
基本使用
当 Vue 中的核心内置指令不能够满足我们的需求时,我们可以定制自定义的指令用来满足开发的需求
我们看到的v-开头的行内属性,都是指令,不同的指令可以完成或实现不同的功能,对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令
注册一个自定义指令有全局注册与局部注册
钩子函数
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。componentUpdated:被绑定元素所在模板完成一次更新周期时调用。unbind:只调用一次,指令与元素解绑时调用。
所有的钩子函数的参数都有以下:
el:指令所绑定的元素,可以用来直接操作 DOMbinding:一个对象,包含以下property:name:指令名,不包括v-前缀。value:指令的绑定值,例如:v-my-directive="1 + 1"中,绑定值为2。oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子中可用。无论值是否改变都可用。expression:字符串形式的指令表达式。例如v-my-directive="1 + 1"中,表达式为"1 + 1"。arg:传给指令的参数,可选。例如v-my-directive:foo中,参数为"foo"。modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar中,修饰符对象为{ foo: true, bar: true }vnode:Vue编译生成的虚拟节点oldVnode:上一个虚拟节点,仅在update和componentUpdated钩子中可用
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行
应用场景
使用自定义组件组件可以满足我们日常一些场景,这里给出几个自定义组件的案例:
防抖
图片懒加载
设置一个v-lazy自定义组件完成图片懒加载
一键 Copy 的功能
拖拽
原理
指令本质上是装饰器,是
vue对HTML元素的扩展,给HTML元素增加自定义功能。vue编译DOM时,会找到指令对象,执行指令的相关方法。自定义指令有五个生命周期(也叫钩子函数),分别是
bind、inserted、update、componentUpdated、unbind
原理
在生成
ast语法树时,遇到指令会给当前元素添加directives属性通过
genDirectives生成指令代码在
patch前将指令的钩子提取到cbs中,在patch过程中调用对应的钩子当执行指令对应钩子函数时,调用对应指令定义的方法
说一下 Vue 的生命周期
Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。
beforeCreate(创建前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到 data、computed、watch、methods 上的方法和数据。
created(创建后) :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到
$el属性。beforeMount(挂载前):在挂载开始之前被调用,相关的 render 函数首次被调用。实例已完成以下的配置:编译模板,把 data 里面的数据和模板生成 html。此时还没有挂载 html 到页面上。
mounted(挂载后):在 el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的 html 内容替换 el 属性指向的 DOM 对象。完成模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。
beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。
updated(更新后) :在由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM 已经更新,所以可以执行依赖于 DOM 的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,
this仍能获取到实例。destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
另外还有 keep-alive 独有的生命周期,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。
Vue 中 computed 和 watch 有什么区别?
计算属性 computed: (1)支持缓存,只有依赖数据发生变化时,才会重新进行计算函数; (2)计算属性内不支持异步操作; (3)计算属性的函数中都有一个 get(默认具有,获取计算属性)和 set(手动添加,设置计算属性)方法; (4)计算属性是自动监听依赖值的变化,从而动态返回内容。
侦听属性 watch: (1)不支持缓存,只要数据发生变化,就会执行侦听函数; (2)侦听属性内支持异步操作; (3)侦听属性的值可以是一个对象,接收 handler 回调,deep,immediate 三个属性; (3)监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些其他事情。
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 适合频繁切换。
对 React 和 Vue 的理解,它们的异同
相似之处:
都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库;
都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板;
都使用了 Virtual DOM(虚拟 DOM)提高重绘性能;
都有 props 的概念,允许组件间的数据传递;
都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性。
不同之处 :
1)数据流
Vue 默认支持数据双向绑定,而 React 一直提倡单向数据流
2)虚拟 DOM
Vue2.x 开始引入"Virtual DOM",消除了和 React 在这方面的差异,但是在具体的细节还是有各自的特点。
Vue 宣称可以更快地计算出 Virtual DOM 的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
对于 React 而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate 这个生命周期方法来进行控制,但 Vue 将此视为默认的优化。
3)组件化
React 与 Vue 最大的不同是模板的编写。
Vue 鼓励写近似常规 HTML 的模板。写起来很接近标准 HTML 元素,只是多了一些属性。
React 推荐你所有的模板通用 JavaScript 的语法扩展——JSX 书写。
具体来讲:React 中 render 函数是支持闭包特性的,所以 import 的组件在 render 中可以直接调用。但是在 Vue 中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 一个组件完了之后,还需要在 components 中再声明下。 4)监听数据变化的实现原理不同
Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的 vDOM 的重新渲染。这是因为 Vue 使用的是可变数据,而 React 更强调数据的不可变。
5)高阶组件
react 可以通过高阶组件(HOC)来扩展,而 Vue 需要通过 mixins 来扩展。
高阶组件就是高阶函数,而 React 的组件本身就是纯粹的函数,所以高阶函数对 React 来说易如反掌。相反 Vue.js 使用 HTML 模板创建视图组件,这时模板无法有效的编译,因此 Vue 不能采用 HOC 来实现。
6)构建工具
两者都有自己的构建工具:
React ==> Create React APP
Vue ==> vue-cli
7)跨平台
React ==> React Native
Vue ==> Weex
如何从真实 DOM 到虚拟 DOM
涉及到 Vue 中的模板编译原理,主要过程:
将模板转换成
ast树,ast用对象来描述真实的 JS 语法(将真实 DOM 转换成虚拟 DOM)优化树
将
ast树生成代码
参考 前端进阶面试题详细解答
过滤器的作用,如何实现一个过滤器
根据过滤器的名称,过滤器是用来过滤数据的,在 Vue 中使用filters来过滤数据,filters不会修改数据,而是过滤数据,改变用户看到的输出(计算属性 computed ,方法 methods 都是通过修改数据来处理数据格式的输出显示)。
使用场景:
需要格式化数据的情况,比如需要处理时间、价格等数据格式的输出 / 显示。
比如后端返回一个 年月日的日期字符串,前端需要展示为 多少天前 的数据格式,此时就可以用
fliters过滤器来处理数据。
过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在插值表达式 {{ }} 和 v-bind 表达式 中,然后放在操作符“ | ”后面进行指示。
例如,在显示金额,给商品价格添加单位:
Vue 中封装的数组方法有哪些,其如何实现页面更新
在 Vue 中,对响应式处理利用的是 Object.defineProperty 对数据进行拦截,而这个方法并不能监听到数组内部变化,数组长度变化,数组的截取变化等,所以需要对这些操作进行 hack,让 Vue 能监听到其中的变化。 那 Vue 是如何实现让这些数组方法实现元素的实时更新的呢,下面是 Vue 中对这些方法的封装:
简单来说就是,重写了数组中的那些原生方法,首先获取到这个数组的__ob__,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 继续对新的值观察变化(也就是通过target__proto__ == arrayMethods来改变了数组实例的型),然后手动调用 notify,通知渲染 watcher,执行 update。
slot 是什么?有什么作用?原理是什么?
slot 又名插槽,是 Vue 的内容分发机制,组件内部的模板引擎使用 slot 元素作为承载分发内容的出口。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot 又分三类,默认插槽,具名插槽和作用域插槽。
默认插槽:又名匿名查抄,当 slot 没有指定 name 属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
具名插槽:带有具体名字的插槽,也就是带有 name 属性的 slot,一个组件可以出现多个具名插槽。
作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
实现原理:当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,存放在vm.$slot中,默认插槽为vm.$slot.default,具名插槽为vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用$slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
Vue 单页应用与多页应用的区别
概念:
SPA 单页面应用(SinglePage Web Application),指只有一个主页面的应用,一开始只需要加载一次 js、css 等相关资源。所有内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅仅刷新局部资源。
MPA 多页面应用 (MultiPage Application),指有多个独立页面的应用,每个页面必须重复加载 js、css 等相关资源。多页应用跳转,需要整页资源刷新。
说说 Vue 的生命周期吧
什么时候被调用?
beforeCreate :实例初始化之后,数据观测之前调用
created:实例创建万之后调用。实例完成:数据观测、属性和方法的运算、
watch/event事件回调。无$el.beforeMount:在挂载之前调用,相关
render函数首次被调用mounted:了被新创建的
vm.$el替换,并挂载到实例上去之后调用改钩子。beforeUpdate:数据更新前调用,发生在虚拟 DOM 重新渲染和打补丁,在这之后会调用改钩子。
updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用改钩子。
beforeDestroy:实例销毁前调用,实例仍然可用。
destroyed:实例销毁之后调用,调用后,Vue 实例指示的所有东西都会解绑,所有事件监听器和所有子实例都会被移除
每个生命周期内部可以做什么?
created:实例已经创建完成,因为他是最早触发的,所以可以进行一些数据、资源的请求。
mounted:实例已经挂载完成,可以进行一些 DOM 操作。
beforeUpdate:可以在这个钩子中进一步的更改状态,不会触发重渲染。
updated:可以执行依赖于 DOM 的操作,但是要避免更改状态,可能会导致更新无线循环。
destroyed:可以执行一些优化操作,清空计时器,解除绑定事件。
ajax 放在哪个生命周期?:一般放在 mounted 中,保证逻辑统一性,因为生命周期是同步执行的, ajax 是异步执行的。单数服务端渲染 ssr 同一放在 created 中,因为服务端渲染不支持 mounted 方法。 什么时候使用 beforeDestroy?:当前页面使用 $on ,需要解绑事件。清楚定时器。解除事件绑定, scroll mousemove 。
MVC 和 MVVM 区别
MVC
MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范
Model(模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据
View(视图):是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的
Controller(控制器):是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据
MVC 的思想:一句话描述就是 Controller 负责将 Model 的数据用 View 显示出来,换句话说就是在 Controller 里面把 Model 的数据赋值给 View。
MVVM
MVVM 新增了 VM 类
ViewModel 层:做了两件事达到了数据的双向绑定 一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。
MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的自动同步,也就是当 Model 的属性改变时,我们不用再自己手动操作 Dom 元素,来改变 View 的显示,而是改变属性后该属性对应 View 层显示会自动改变(对应 Vue 数据驱动的思想)
整体看来,MVVM 比 MVC 精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作 DOM 元素。因为在 MVVM 中,View 不知道 Model 的存在,Model 和 ViewModel 也观察不到 View,这种低耦合模式提高代码的可重用性
注意:Vue 并没有完全遵循 MVVM 的思想 这一点官网自己也有说明
那么问题来了 为什么官方要说 Vue 没有完全遵循 MVVM 思想呢?
严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了 $refs 这个属性,让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM。
$nextTick 是什么?
Vue 实现响应式并不是在数据发生后立即更新 DOM,使用 vm.$nextTick 是在下次 DOM 更新循环结束之后立即执行延迟回调。在修改数据之后使用,则可以在回调中获取更新后的 DOM。
那 vue 中是如何检测数组变化的呢?
数组就是使用 object.defineProperty 重新定义数组的每一项,那能引起数组变化的方法我们都是知道的, pop 、 push 、 shift 、 unshift 、 splice 、 sort 、 reverse 这七种,只要这些方法执行改了数组内容,我就更新内容就好了,是不是很好理解。
是用来函数劫持的方式,重写了数组方法,具体呢就是更改了数组的原型,更改成自己的,用户调数组的一些方法的时候,走的就是自己的方法,然后通知视图去更新。
数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象才能进行观测,观测过的也不会进行观测)
vue3:改用 proxy ,可直接监听对象数组的变化。
vue 如何监听对象或者数组某个属性的变化
当在项目中直接设置数组的某一项的值,或者直接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为 Object.defineProperty()限制,监听不到变化。
解决方式:
this.$set(你要改变的数组/对象,你要改变的位置/key,你要改成什么 value)
调用以下几个数组的方法
vue 源码里缓存了 array 的原型链,然后重写了这几个方法,触发这几个方法的时候会 observer 数据,意思是使用这些方法不用再进行额外的操作,视图自动进行更新。 推荐使用 splice 方法会比较好自定义,因为 splice 可以在数组的任何位置进行删除/添加操作
vm.$set 的实现原理是:
如果目标是数组,直接使用数组的 splice 方法触发相应式;
如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)
v-model 可以被用在自定义组件上吗?如果可以,如何使用?
可以。v-model 实际上是一个语法糖,如:
实际上相当于:
用在自定义组件上也是同理:
相当于:
显然,custom-input 与父组件的交互如下:
父组件将
searchText变量传入 custom-input 组件,使用的 prop 名为value;custom-input 组件向父组件传出名为
input的事件,父组件将接收到的值赋值给searchText;
所以,custom-input 组件的实现应该类似于这样:
Vue 的基本原理
当一个 Vue 实例创建时,Vue 会遍历 data 中的属性,用 Object.defineProperty(vue3.0 使用 proxy )将它们转为 getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
描述下 Vue 自定义指令
在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。一般需要对 DOM 元素进行底层操作时使用,尽量只用来操作 DOM 展示,不修改内部的值。当使用自定义指令直接修改 value 值时绑定 v-model 的值也不会同步更新;如必须修改可以在自定义指令中使用 keydown 事件,在 vue 组件中使用 change 事件,回调中修改 vue 数据;
(1)自定义指令基本内容
全局定义:
Vue.directive("focus",{})局部定义:
directives:{focus:{}}钩子函数:指令定义对象提供钩子函数
o bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
o inSerted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。
o update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前调用。指令的值可能发生了改变,也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。
o ComponentUpdate:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
o unbind:只调用一次,指令与元素解绑时调用。
钩子函数参数 o el:绑定元素
o bing: 指令核心对象,描述指令全部信息属性
o name
o value
o oldValue
o expression
o arg
o modifers
o vnode 虚拟节点
o oldVnode:上一个虚拟节点(更新钩子函数中才有用)
(2)使用场景
普通 DOM 元素进行底层操作的时候,可以使用自定义指令
自定义指令是用来操作 DOM 的。尽管 Vue 推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的 DOM 操作,并且是可复用的。
(3)使用案例
初级应用:
鼠标聚焦
下拉菜单
相对时间转换
滚动动画
高级应用:
自定义指令实现图片懒加载
自定义指令集成第三方插件
Vue 为什么没有类似于 React 中 shouldComponentUpdate 的生命周期?
考点: Vue 的变化侦测原理
前置知识: 依赖收集、虚拟 DOM、响应式系统
根本原因是 Vue 与 React 的变化侦测方式有所不同
React 是 pull 的方式侦测变化,当 React 知道发生变化后,会使用 Virtual Dom Diff 进行差异检测,但是很多组件实际上是肯定不会发生变化的,这个时候需要用 shouldComponentUpdate 进行手动操作来减少 diff,从而提高程序整体的性能.
Vue 是 pull+push 的方式侦测变化的,在一开始就知道那个组件发生了变化,因此在 push 的阶段并不需要手动控制 diff,而组件内部采用的 diff 方式实际上是可以引入类似于 shouldComponentUpdate 相关生命周期的,但是通常合理大小的组件不会有过量的 diff,手动优化的价值有限,因此目前 Vue 并没有考虑引入 shouldComponentUpdate 这种手动优化的生命周期.
父子组件生命周期调用顺序(简单)
渲染顺序:先父后子,完成顺序:先子后父
更新顺序:父更新导致子更新,子更新完成后父
销毁顺序:先父后子,完成顺序:先子后父










评论