Vue 面试题汇总

什么是vue生命周期

Vue 实例从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期,共八个阶段。

作用: 生命周期中有多个事件钩子,在控制整个 Vue实例 的过程时更容易形成好的逻辑。

  • beforeCreate: 完成实例初始化,this 指向被创建的实例,data,computed,watch,mothods方法 和 数据都不可以访问,数据观测之前(data observer)被调用。
  • created: 实例创建完成,data,computed,watch,methods 可被访问,未挂载Dom,可对 data进行操作,操作 Dom 需放到 nextTick中。
    -beforeMount: 有了el,找到对应的template 编译成 render 函数
    -mounted: 完成挂载 Dom 和 渲染,可对 Dom 进行获取节点等操作,可发起后端请求拿到数据。
  • beforeUpdate: 数据更新时调用,发生在虚拟Dom 重新渲染 和 打补丁之前之调用。
  • updated: 组件 Dom 已完成更新,可执行依赖的 Dom 操作,不要操作数据会陷入死循环。
  • beforeDestroy: 实例销毁之前调用,可进行优化操作,如销毁定时器,解除绑定事件。
  • destroyed: 组件已经被销毁,事件监听器和子实例都会被移除销毁。

首次页面加载会触发四个钩子函数: beforeCreate, created, beforeMount, mounted
且 DMO 渲染在 mounted 中就已经完成了。

可以使用 on('hook:') 或once('hook:') 来简化生命周期的注册

谈谈 MVVM 模式

Model: 代表数据模型,也可以在 Model 中定义 数据修改 和 操作 的业务逻辑。
View: 代表 UI 组件,它负责将 数据模型 转化成 UI 展现出来。
ViewModel: 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步 View 和 Model 的对象,连接ModelView

在 MVVM 架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewMode 进行交互,Model 和 ViewModel 之间的交互是双向自动的, 因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。而开发者只需关注业务逻辑,不需要手动操作 DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

MVVM 和 MVC区别?

mvcmvvm其实区别并不大。都是一种设计思想。主要就是 mvcController 演变成mvvm 中的 viewModelmvvm 主要解决了mvc 中大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到 View 。

请说一下 Vue 响应式数据的原理是什么?

在 Vue 初始化数据时, 使用Object.defineProperty重新定义 data 中所有属性,增加了数据 获取(getter)/ 设置(setter)的拦截功能。在 获取 / 设置 时可增加一些逻辑,这个逻辑交叫作 依赖收集。当页面取到对应属性时会进行依赖收集, 如果属性发生变化, 则会通知收集的依赖进行更新,而负责收集的就是watcher
如负责渲染的watcher 会在页面渲染的时候对数据进行取值,并把当前watcher先存起来对应到数据上,当更新数据的时候告诉对应的 watcher去更新, 从而实现了数据响应式。

data 一般分为两大类: 对象类型 和 数组:

对象:

在 Vue 初始化的时候,会调用 initData 方法初始化 data,它会拿到当前用户传入的数据。判断如果已经被观测过则不在观测,如果没有观测过则利用new Observer 创建一个实例用来观测数据。如果数据是对象类型非数组的话会调用this.walk(value)方法把数据进行遍历,在内部使用 definReactive方法重新定义( definReactive 是比较核心的方法: 定义响应式 ),而重新定义采用的就是 Object.defineProperty。如当前对象的值还是个对象,会自动调用递归观测。当用户取值的时候会调用 get 方法并收集当前的wacther 。在 set方法里,数据变化时会调用 notify方法触发数据对应的依赖进行更新。

数组:

使用函数劫持的方式重写了数组的方法,并进行了原型链重写。使 data 中的数组指向了自己定义的数组原型方法。这样的话,当调用数组API 时,可以通知依赖更新。如果数组中包含着引用类型,则会对数组中的引用类型进行再次监控。

也就是当创建了Observer观测实例后,如果数据是数组的话,判断是否支持自己原型链,如果不支持则调用protoAugment方法使目标指向arrayMethods方法。arrayMethods 就是重写的数组方法,包括 push、pop、shift 、unshift、splice、sort 和 reverse共七个可以改变数组的方法,内部采用函数劫持的方式。在数组调用重写的方法之后,还是会调用原数组方法去更新数组。只不过重写的方法会通知视图更新。如果使用push、unshift 和 splice 等方法新增数据,会调用 observeArray 方法对插入的数据再次进行观测。

如果数组中有引用类型,则继续调用 observeArray方法循环遍历每一项,继续深度观测。前提是每一项必须是对象类型, 否则observe 方法会直接return

为何 Vue 采用异步渲染?

如不采用异步更新, 则每次更新数据都会对当前组件进行重新渲染, 因此为了性能考虑 Vue 在本轮数据更新结束后,再去异步更新视图。
当数据变化之后, 会调用 notify方法去通知watcher 进行数据更新。而watcher 会调用 update 方法进行更新( 这里就是发布订阅模式 )。更新时并不是让wathcer立即执行,而是放在一个queueWatcher队列里进行过滤,相同的 watcher只存一个。最后在调用nextTick方法通过flushSchedulerQueue 异步清空 watcher队列。

.nextTick 实现原理?

nextTick方法主要是使用了 宏任务 和 微任务 定义了一个异步方法。多次调用 nextTick 会将方法存入队列中,通过这个异步方法清空当前队列。所以 nextTick 方法就是异步方法。
默认在内部调用 nextTick 时会传入 flushSchedulerQueue方法, 存在一个数组里并让它执行。用户有时也会调用 nextTick,调用时把用户传过来的 cb 也放在数组里,都是同一个数组callbacks 。多次调用 nextTick只会执行一次, 等到代码都执行完毕后,会调用 timerFunc 这个异步方法依次进行判断所支持的类型:

  1. 如支持 Promise则把timerFunc 包裹在了 Promise 中并把flushCallbacks放在了then 中, 相当于异步执行了lushCallBacksflushCallBacks 函数作用就是让传过来的方法依次执行。

  2. 如不是 IE 、支持 Mutationobserve 并且是原生的 Mutationobserve。首先声明一个变量并创建一个文本节点。接着创建 Mutationobserve实例并把flushCallBacks传入, 调用observe 方法去观测每一个节点。如果节点变化会异步执行 flushCallBacks方法。

  3. 如果支持setImmediate , 则调用 setImmediate 传入flushCallBacks 异步执行。

  4. 以上都不支持就只能调用setTimeout 传入flushCallBacks

作用:$nextTick是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用$nextTick,则可以在回调中获取更新后的DOM

.请说一下 Vue 中 Computed 和 watch ?

默认computedwatch 内部都是用一个watcher实现的 。
computed有缓存功能, 不会先执行,只有当依赖的属性发生变化才会通知视图跟新。
watcher没有缓存,默认会先执行,只要监听的属性发生变化就会更新视图。

computed
调用initComputed方法初始化计算属性时,会获取到用户定义的方法,并创建一个 watcher把用户定义传进去, 这个 watcher 有个标识:lazy = true,默认不会执行用户定义的函数。还有个标识 dirty = true 默认去求值 。watcher内部调用 defineComputed 方法将计算属性定义在实例上,其底层也是用的 Object.defineProperty。并且传入了createComputedGetter 方法定义一个计算属性。在用户取值时,调用的是createComputedGetter 返回函数 computedGetter。判断当前的watcher.dirty是否为true。如果为 true则调用watcher.evaluate 方法求值。在求值时是调用的 this.get()方法。其实 this.get()就是用户传入的方法,执行时会把方法里的属性依次取值。而在取值前调用了pushTarget方法将watcher 放在了全局上,当取值时会进行依赖收集,把当前的计算属性的 watcher 收集起来。等数据变化则通知 watcher 重新执行,也就是进入到了update方法中。update 并没有直接让watcher执行,而是将 dirty = true。这样的好处就是,如果 dirty = true,就进行求值,否则就返回上次计算后的值,从而实现了缓存的机制。

watch
调用initWatch方法初始化 watch 的时候,内部传入用户定义的方法调用了createWatcher 方法。在 createWatcher 方法中比较核心的就是$watch方法,内部调用了 new Watcher 并传入了expOrFn 和 回调函数。expOrFn如果是个字符串的话, 会包装成一个函数并返回这个字符串。这时 lazy = false了, 则直接调用了this.get() 方法取属性的值。同computed在取值前也执行 pushTarget 方法将 watcher 放在了全局上, 当用户取值时就收集了watcher。 因此当属性值发生改变时,watcher就会更新。
如果监听的属性值是个对象,则取对象里的值就不会更新了,因为默认只能对属性进行依赖收集,不能对属性值是对象的进行依赖收集。想要不管属性值是否是对象都能求值进行收集依赖,可设置 deep = true。如设置了deep = true ,则会调用 traverse方法进行递归遍历。

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

因为 js 本身的特性带来的,同一个组件被复用多次,会创建多个实例。这些实例是同一个构造函数。如果 data是一个对象的话,那么所有组件都共享了同一个对象。为了保证组件中数据的独立性要求每个组件必须通过 data 函数返回一个对象作为组件的状态。
Vue通过extend创建子类之后,会调用 mergeOptions方法合并父类和子类的选项,选中就包括 data。在循环完父类和子类之后调用 mergeField函数的中的strat 方法去合并data,如果data 不是函数而是个对象,则会报错提示data应该是个函数。

Vue中的 v-show 和 v-if 是做什么用的, 两者有什么区别?

  • v-if:会在 with 方法里进行判断,如果条件为true 则创建相应的虚拟节点,否则就创建一个空的虚拟节点也就是不会渲染 DOM

  • v-show: 会在with方法里创建了一个指令就v-show,在运行的时候处理指令,添加了style: display = none / originalDisplay

  • v-if 才是“真正的”条件渲染, 因为它会确保在切换过程中条件块内的事件监听器和子组件适当的被销毁和重建。

  • v-if也是惰性的, 如果在初次渲染时条件为假, 则什么也不做,一直到条件第一次变为真时, 才会渲染条件块。

  • 相比之下,v-show就简单的多,不管初始条件是什么,元素总会被渲染, 并且只是简单的基于css 进行切换。

  • 一般来说,v-if 有更高的切换开销,v-show有更高的初始渲染开销。
    因此,如需要频繁的切换则使用v-show 较好,如在运行时条件不大可能改变则使用 v-if 较好。

v-if 和 v-for 为什么不能连用?

  • v-for 的优先级会比v-if 要高, 在 调用with方法编译时会先进行循环, 然后再去做 v-if的条件判断, 因此性能不高。
  • 因此一般会把v-if 提出来放在v-for外层, 或者想要连用把渲染数据放在计算属性里进行过滤。

Vue 中的 v-html 会导致哪些问题

v-html 其原理就是用 innerHtml实现的的, 如果不能保证内容是完全可以被依赖的, 则可能会导致xxs 攻击。
在运行的时候, 调用 updateDOMProps 方法或解析配置的属性, 如果判断属性是innerHTML的话, 会清除所有的子元素。

Vue 中父子组件的调用顺序

组件的调用都是先父后子,渲染完成的过程顺序都是先子后父

组件的销毁操作是先父后子,销毁完成的顺序是先子后父

在页面渲染的时候,先执行父组件的 beforeCreate -> created -> befroreMount,当父组件实例化完成的时候会调用 rander 方法,判断组件是不是有子组件,如果有子组件则继续渲染子组件以此类推。当子组件实例化完成时候,会把子组件的插入方法先存起来放到instertedVNodeQueue队列里, 最后会调用 invokeIntertHook 方法把当前的队列依次执行。

更新也是一样,先父beforeUpdate-> 子beforeUpdate 再到 子updated -> 父 updated

  1. 加载渲染过程
    beforeCreate-> 父created-> 父beforeMount-> 子beforeCreate-> 子created-> 子beforeMount- > 子mounted-> 父mounted

  2. 子组件更新过程
    beforeUpdate-> 子beforeUpdate-> 子updated-> 父updated

  3. 父组件更新过程
    beforeUpdate -> 父updated

  4. 销毁过程
    beforeDestroy-> 子beforeDestroy-> 子destroyed-> 父destroyed

Vue中父组件能监听到子组件的生命周期吗

父组件通过@hook: 能够监听到子组件的生命周期,举个栗子:

// 这里是父组件
<template>
    <child @hook:mounted="getChildMounted"  />
</template>
<script>
method: {
    getChildMounted () {
        // 这里可以获取到子组件mounted的信息
    }
}
</script>

为什么使用异步组件?

可使用异步的方式加载组件,减少打包体积,主要依赖 import() 语法,可实现文件的分割加载

components:{
  testCpt: (resove) => import("../components/testCpt")  或
  testCpt: r => require(['@/views/assetsInfo/assetsProofList'],r)
}

加载组件的时候,如果组件是个函数会调用resolveAsyncComponent方法, 并传入组件定义的函数 asyncFactory , 并让其马上执行。因为是异步的所以执行后并不会马上返回结果。而返回的是一个 promise,因此没有返回值, 返回的是一个占位符。
加载完成后,会执行factory函数并传入了成功/失败的回调。在回调 resolve 成功的回调时会调用 forceRander 方法, 内部调用 $forceUpdate 强制刷新。之后resolveAsyncComponent判断已经执行成功,就是去创建组件、初始化组件和渲染组件。

作者:酷酷的凯先生
链接:https://www.jianshu.com/p/14e9a060af8a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容

  • .什么是vue生命周期 Vue 实例从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过...
    酷酷的凯先生阅读 4,328评论 0 3
  • 谈谈你对MVVM开发模式的理解 MVVM分为Model、View、ViewModel三者。 Model 代表数据模...
    廖若晨阅读 1,287评论 0 4
  • 毕业之后就在一直合肥小公司工作,没有老司机、没有技术氛围,在技术的道路上我只能独自摸索。老板也只会画饼充饥,前途一...
    前端小酱阅读 4,104评论 5 56
  • 1、V-if和V-show的区别 答案:区别就是dom元素是否挂载了,v-show是dom树上有内容,不显示,di...
    cj_jax阅读 20,118评论 2 22
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,517评论 16 22