几个关于vue的面试题

  • ??? 正文部分是笔者的回应。

友情提示,文章中宝藏链接过多,很难一口气消化。

为了不迷路,建议关注公众号,点顶部「前端从进阶到入院」。

我为你精心挑选了**「Vue 进阶精选」**专题文章,帮你逐个击破难点。

请说一下响应式数据的原理


???默认 Vue 在初始化数据时,会给 data 中的属性使用 Object.defineProperty 重新定义所有属性,当页面到对应属性时,会进行依赖收集(收集当前组件中的 watcher)如果属性发生变化会通知相关依赖进行更新操作

收集当前组件中的 watcher,我会进一步问你什么叫当前组件的 watcher?我面试时经常听到这种模糊的说法,感觉就是看了些造玩具的文章就说熟悉响应式原理了,起码的流程要清晰一些:

  1. 由于 Vue 执行一个组件的 render 函数是由 Watcher 去代理执行的,Watcher 在执行前会把 Watcher 自身先赋值给 Dep.target 这个全局变量,等待响应式属性去收集它

  2. 这样在哪个组件执行 render 函数时访问了响应式属性,响应式属性就会精确的收集到当前全局存在的 Dep.target 作为自身的依赖

  3. 在响应式属性发生更新时通知 Watcher 去重新调用 vm._update(vm._render()) 进行组件的视图更新

关于这个问题,有一个比较有意思的经历是,有一位同学前面部分都答得很好,但是我问他 watcher 是利用了什么数据结构去存储的时候,他就不太能答得出来了。所以是否真的阅读过源码,可以通过类似只要你看过,就一定印象深刻的细节来试探。

响应式部分,如果你想在简历上写熟悉的话,还是要抽时间好好的去看一下源码中真正的实现,而不是看这种模棱两可的说法就觉得自己熟练掌握了。

为什么 Vue 采用异步渲染


???因为如果不采用异步更新,那么每次更新数据都会对当前租金按进行重新渲染,所以为了性能考虑,Vue 会在本轮数据更新后,再去异步更新数据

什么叫本轮数据更新后,再去异步更新数据?

轮指的是什么,在 eventLoop 里的 taskmicroTask,他们分别的执行时机是什么样的,为什么优先选用 microTask,这都是值得深思的好问题。

建议看看这篇文章:Vue源码详解之nextTick:MutationObserver只是浮云,microtask才是核心![1]

nextTick 实现原理


???nextTick 方法主要是使用了宏任务和微任务,定义一个异步方法,多次调用 nextTick 会将方法存在队列中,通过这个异步方法清空当前队列。所以这个 nextTick 方法就是异步方法

这句话说的很乱,典型的让面试官忍不住想要深挖一探究竟的回答。(因为一听你就不是真的懂)

正确的流程应该是先去 嗅探环境,依次去检测:

Promise 的 then -> ???MutationObserver 的回调函数 -> ???setImmediate -> ???setTimeout 是否存在,找到存在的就使用它,以此来确定回调函数队列是以哪个 api 来异步执行。

nextTick 函数接受到一个 callback 函数的时候,先不去调用它,而是把它 push 到一个全局的 queue 队列中,等待下一个任务队列的时候再一次性的把这个 queue 里的函数依次执行。

这个队列可能是 microTask 队列,也可能是 macroTask 队列,前两个 api 属于微任务队列,后两个 api 属于宏任务队列。

简化实现一个异步合并任务队列:

let pending = false

// 存放需要异步调用的任务

const callbacks = []

function flushCallbacks() {

pending = false

// 循环执行队列

for (let i = 0; i < callbacks.length; i++) {

callbacksi

}

// 清空

callbacks.length = 0

}

function nextTick(cb) {

callbacks.push(cb)

if (!pending) {

pending = true

// 利用Promise的then方法 在下一个微任务队列中把函数全部执行

// 在微任务开始之前 依然可以往callbacks里放入新的回调函数

Promise.resolve().then(flushCallbacks)

}

}

测试一下:

// 第一次调用 then方法已经被调用了 但是 flushCallbacks 还没执行

nextTick(() => ???console.log(1))

// callbacks里push这个函数

nextTick(() => ???console.log(2))

// callbacks里push这个 《大厂前端面试题解析+Web核心总结学习笔记+企业项目实战源码+最新高清讲解视频》无偿开源 徽信搜索公众号【编程进阶路】 函数

nextTick(() => ???console.log(3))

// 同步函数优先执行

console.log(4)

// 此时调用栈清空了,浏览器开始检查微任务队列,发现了 flushCallbacks 方法,执行。

// 此时 callbacks 里的 3 个函数被依次执行。

// 4

// 1

// 2

// 3

Vue 优点


???虚拟 DOM 把最终的 DOM 操作计算出来并优化,由于这个 DOM 操作属于预处理操作,并没有真实的操作 DOM,所以叫做虚拟 DOM。最后在计算完毕才真正将 DOM 操作提交,将 DOM 操作变化反映到 DOM 树上

看起来说的很厉害,其实也没说到点上。关于虚拟 DOM 的优缺点,直接看 Vue 作者尤雨溪本人的知乎回答,你会对它有进一步的理解:

网上都说操作真实 DOM 慢,但测试结果却比 React 更快,为什么?[2]

???双向数据绑定通过 MVVM 思想实现数据的双向绑定,让开发者不用再操作 dom 对象,有更多的时间去思考业务逻辑

开发者不操作 dom 对象,和双向绑定没太大关系。React 不提供双向绑定,开发者照样不需要操作 dom。双向绑定只是一种语法糖,在表单元素上绑定 value 并且监听 onChange 事件去修改 value 触发响应式更新。

我建议真正想看模板被编译后的原理的同学,可以去尤大开源的 vue-template-explorer[3]  网站输入对应的模板,就会展示出对应的 render 函数。

???运行速度更快,像比较与 react 而言,同样都是操作虚拟 dom,就性能而言,vue 存在很大的优势

为什么快,快在哪里,什么情况下快,有数据支持吗?事实上在初始化数据量不同的场景是不好比较的,React 不需要对数据递归的进行 响应式定义

而在更新的场景下 Vue 可能更快一些,因为 Vue 的更新粒度是组件级别的,而 React 是递归向下的进行 reconcilerReact 引入了 Fiber 架构和异步更新,目的也是为了让这个工作可以分在不同的 时间片 中进行,不要去阻塞用户高优先级的操作。

???Proxy 是 es6 提供的新特性,兼容性不好,所以导致 Vue3 一致没有正式发布让开发者使用

Vue3 没发布不是因为兼容性不好,工作正在有序推进中,新的语法也在不断迭代,并且发布 rfc 征求社区意见。

???Object.defineProperty 的缺点:无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应

事实上可以,并且尤大说只是为了性能的权衡才不去监听[4]。数组下标本质上也就是对象的一个属性。

React 和 Vue 的比较


???React 默认是通过比较引用的方式(diff)进行的,React 不精确监听数据变化。

比较引用和 diff 有什么关系,难道 Vue 就不 diff 了吗。

???Vue2.0 可以通过 props 实现双向绑定,用 vuex 单向数据流的状态管理框架

双向绑定是 v-model 吧,面试的时候不要把双向绑定和响应式数据给搞混。

???Vue 父组件通过 props 向子组件传递数据或回调

Vue 虽然可以传递回调,但是一般来说还是通过 @change 这样的方式去绑定事件吧,这和回调是两套机制。深入的话可以从 Vue 内部实现的 eventEmitter 事件总线机制来回答。

???模板渲染方式不同,Vue 通过 HTML 进行渲染

事实上 Vue 是自己实现了一套模板引擎系统,HTML 可以被利用为模板的而已,你在 .vue 文件里写的 templateHTML 本质上没有关系。

???React 组合不同功能方式是通过 HoC(高阶组件),本质是高阶函数

事实上高阶函数只是社区提出的一种方案被 React 所采纳而已,其他的方案还有 renderProps 和 最近流行的Hook

[Vue 也可以利用高阶函数](() 实现组合和复用。

diff 算法的时间复杂度


???两个数的完全的 diff 算法是一个时间复杂度为 o(n3), ???Vue 进行了优化 O(n3)复杂度的问题转换成 O(n)复杂度的问题(只比较同级不考虑跨级问题)在前端当中,你很少会跨级层级地移动 Dom 元素,所以 Virtual Dom 只会对同一个层级地元素进行对比

听这个描述来说,React 没有对 O(n3) 的复杂度进行优化?事实上 React 和 Vue 都只会对 tag 相同的同级节点进行 diff,如果不同则直接销毁重建,都是 O(n) 的复杂度。

谈谈你对作用域插槽的理解


???单个插槽当子组件模板只有一个没有属性的插槽时, 父组件传入的整个内容片段将插入到插槽所在的 DOM 位置, 并替换掉插槽标签本身。

跟 DOM 没关系,是在虚拟节点树的插槽位置替换。

Vue 中 key 的作用


???如果不加 key,那么 vue 会选择复用节点(Vue 的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的 bug

不加 key 也不一定就会复用,关于 diff 和 key 的使用,建议大家还是找一些非造玩具的文章真正深入的看一下原理。

[为什么 Vue 中不要用 index 作为 key?(diff 算法详解)](()

组件中的 data 为什么是函数


???因为组件是用来复用的,JS 里对象是引用关系,这样作用域没有隔离,而 new Vue 的实例,是不会被复用的,因此不存在引用对象问题

这句话反正我压根没听懂,事实上如果组件里 data 直接写了一个对象的话,那么如果你在模板中多次声明这个组件,组件中的 data 会指向同一个引用。

此时如果在某个组件中对 data 进行修改,会导致其他组件里的 data 也被污染。而如果使用函数的话,每个组件里的 data 会有单独的引用,这个问题就可以避免了。

这个问题我同样举个例子来方便理解,假设我们有这样的一个组件,其中的 data 直接使用了对象而不是函数:

var Counter = {

template: <span @click="count++"></span>

data: {

count: 0

}

}

注意,这里的 Counter.data 仅仅是一个对象而已,它 是一个引用,也就是它是在当前的运行环境下全局唯一的,它真正的值在堆内存中占用了一部分空间。

也就是说,不管利用这份 data 数据创建了多少个组件实例,这个组件实例内部的 data 都指向这一个唯一的对象。

然后我们在模板中调用两次 Counter 组件:

我们从原理出发,先看看它被编译成什么样[5]的 render 函数:

function render() {

with (this) {

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值