几个关于vue的面试题

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) {

return _c(‘div’, [_c(‘Counter’), _c(‘Counter’)], 1)

}

}

每一个 Counter 会被 _c 所调用,也就是 createElement,想象一下 createElement 内部会发生什么,它会直接拿着 Counter 上的 data 这个引用去创建一个组件。也就是所有的 Counter 组件实例上的 data 都指向同一个引用。

此时假如 id 为 a 的 Counter 组件内部调用了 count++,会去对 data 这个引用上的 count 属性赋值,那么此时由于 id 为 b 的 Counter 组件内部也是引用的同一份 data,它也会感觉到变化而更新组件,这就造成了多个组件之间的数据混乱了。

那么如果换成函数的情况呢?每创建一次组件实例就执行一次 data() 函数:

function data() {

return { count: 0 }

}

// 组件a创建一份data

const a = data()

// 组件b创建一份data

const b = data()

a === b // false

是不是一目了然,每个组件拥有了自己的一份全新的 data,再也不会互相污染数据了。

computed 和 watch 有什么区别


???计算属性是基于他们的响应式依赖进行缓存的,只有在依赖发生变化时,才会计算求值,而使用 methods,每次都会执行相应的方法

这也是一个一问就倒的回答,依赖变化是计算属性就重新求值吗?中间经历了什么过程,为什么说 computed 是有缓存值的?随便挑一个点深入问下去就站不住。事实上 computed 会拥有自己的 watcher,它内部有个属性 dirty 开关来决定 computed 的值是需要重新计算还是直接复用之前的值。

以这样的一个例子来说:

computed: {

sum() {

return this.count + 1

}

}

首先明确两个关键字:

dirty 从字面意义来讲就是 的意思,这个开关开启了,就意味着这个数据是脏数据,需要重新求值了拿到最新值。

求值 的意思的对用户传入的函数进行执行,也就是执行 sum 这个函数。

  1. sum 第一次进行求值的时候会读取响应式属性 count,收集到这个响应式数据作为依赖。并且计算出一个值来保存在自身的 value 上,把 dirty 设为 false,接下来在模板里再访问 sum 就直接返回这个求好的值 value,并不进行重新的求值。

  2. count 发生变化了以后会通知 sum 所对应的 watcher 把自身的 dirty 属性设置成 true,这也就相当于把重新求值的开关打开来了。这个很好理解,只有 count 变化了, sum 才需要重新去求值。

  3. 那么下次模板中再访问到 this.sum 的时候,才会真正的去重新调用 sum 函数求值,并且再次把 dirty 设置为 false,等待下次的开启……

具体的原理解析,我在Vue 的计算属性如何实现缓存?这篇文章里很详细的讲解了。

Watch 中的 deep:true 是如何实现的


???当用户指定了 watch 中的 deep 属性为 true 时,如果当前监控的值是数组类型,会对对象中的每一项进行求值,此时会将当前 watcher 存入到对应属性的依赖中,这样数组中的对象发生变化时也会通知数据更新。

不光是数组类型,对象类型也会对深层属性进行 依赖收集,比如deep watchobj,那么对 obj.a.b.c = 5 这样深层次的修改也一样会触发 watch 的回调函数。本质上是因为 Vue 内部对需要 deep watch 的属性会进行递归的访问,而在此过程中也会不断发生依赖收集。(只要此属性也是响应式属性

在回答这道题的时候,同样也要考虑到 递归收集依赖 对性能上的损耗和权衡,这样才是一份合格的回答。

action 和 mutation 区别


???mutation 是同步更新数据(内部会进行是否为异步方式更新数据的检测)

内部并不能检测到是否异步更新,而是实例上有一个开关变量 _committing

  1. 只有在 mutation 执行之前才会把开关打开,允许修改 state 上的属性。

  2. 并且在 mutation 同步执行完成后立刻关闭。

  3. 异步更新的话由于已经出了 mutation 的调用栈,此时的开关已经是关上的,自然能检测到对 state 的修改并报错。具体可以查看源码中的 withCommit 函数。这是一种很经典对于 js单线程机制 的利用。

Store.prototype._withCommit = function _withCommit(fn) {

var committing = this._committing

this._committing = true

fn()

this._committing = committing

}

总结

关于面经,面经其实是一个挺不错的文章形式,它可以让你在不去参与面试的情况下也可以得知目前国内的大厂主要在技术上关注哪些重点。但是如果你用面经下面的简略的答案去作为你的学习材料,那我觉得就本末倒置了。正确的方式是去针对每一个重难点,结合你自己目前的技术水平和方向去深入学习和研究。

比如面试官问你 Vue 的原理,其实是想考察你对平常使用的框架是否有探索底层原理的兴趣和热情,相信有这份热情的人他的技术积累和潜力一定也不会差。但是很多人现在为了应付面试,就直接按照本文所说的比较水的面试文章里简略版答案去背,大厂面试官一定会针对每一个点深入挖掘,挖到你说不出来为止,这样真的是很不推荐的一种行为。

如果你真的想掌握好 Vue 的原理,并且作为你简历中的一个亮点,那么你就自己打开源码一点点花时间去研究。

如果你目前的基础不够,那也可以辅助以一些优秀的的视频教程或者文章。我始终觉得,纸上得来终觉浅,如果你不能去深入源码一步步调试,你对它的认知总归是比较浅层的。

一些小秘密


如果你还是很迷茫该如何进阶 Vue 原理的话,关注我的公众号,我已经精心为你挑选好了「Vue 进阶精选」系列,从实现一个精简但非玩具的 Vue 开始,逐个击破各 Computed、Diff 算法等各个难点…

当然,最近的 Vue3 可是很火,它的响应式原理,我也准备好手把手带你实现一个麻雀虽小,五脏俱全的版本了。

至于真的掌握原理的区分度有多明显,我只能告诉你,我已经有好几个读者通过了滴滴、字节、百度等大厂的面试了,我觉得下一个就是你。

参考资料

[1]

Vue源码详解之nextTick:MutationObserver只是浮云,microtask才是核心!: https://segmentfault.com/a/1190000008589736

[2]

网上都说操作真实 DOM 慢,但测试结果却比 React 更快,为什么?: https://www.zhihu.com/question/31809713/answer/53544875

[3]

vue-template-explorer: https://template-explorer.vuejs.org/

[4]
尤大说只是为了性能的权衡才不去监听: https://segmentfault.com/a/1190000015783546
[5]
编译成什么样: https://template-explorer.vuejs.org/#%3Cdiv%3E%0A%20%3CCounter%20%2F%3E%0A%20%3CCounter%20%2F%3E%0A%3C%2Fdiv%3E

总结一下

面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

  • 15
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值