驳《前端常见的Vue面试题目汇总》

本着对社区的小伙伴们负责的态度,有些文章里应付面试用的一些讲解实在是看不下去。前端

本文针对 @小明同窗哟 的 《前端常见的Vue面试题目汇总》 这篇文章,提出一些错误。vue

先放一张大图,有兴趣的同窗能够点开图片看一下原文,简单来讲就是写了不少不知道从哪里收集来的劣质总结,而后底下放个公众号骗粉丝。react

且不说原文中每一个答案都过于简略,并不能达到面试官的要求,其中还有不少错误的地方会误导读者,接下来我重点指出一下错误的地方。es6

这里不放原文连接的缘由是我但愿抵制这样的做者,这个做者的掘力值快要 5000 了,而掘金会对掘力值 5000 以上的做者进行文章首页推荐。若是之后首页都是这样的低质量文章,那真的很让人绝望。面试

另外比较好笑的是,昨天在这篇文章下提出了一些反驳的观点,今早一看这篇文章的评论区,已经被做者删的一干二净,只留下她的「水军号」的一条评论了。不由唏嘘,直接删掉文章的反对观点来掩耳盗铃。算法

准备开始

接下来开始针对做者文章中的观点进行逐条的反驳,注意「引用」 中的文字的便是做者原文,错别字我也原样保留了。vuex

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

默认Vue在初始化数据时,会给data中的属性使用Object.defineProperty从新定义全部属性,当页面到对应属性时,会进行依赖收集(收集当前组件中的watcher)若是属性发生变化会通知相关依赖进行更新操做segmentfault

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

  1. 因为 Vue 执行一个组件的 render 函数是由 Watcher 去代理执行的,Watcher 在执行前会把 Watcher 自身先赋值给 Dep.target 这个全局变量,等待响应式属性去收集它
  2. 这样在哪一个组件执行 render 函数时访问了响应式属性,响应式属性就会精确的收集到当前全局存在的 Dep.target 做为自身的依赖
  3. 在响应式属性发生更新时通知 Watcher 去从新调用 vm._update(vm._render()) 进行组件的视图更新

响应式部分,若是你想在简历上写熟悉的话,仍是要抽时间好好的去看一下源码中真正的实现,而不是看这种模棱两可的说法就以为本身熟练掌握了。数组

为何Vue采用异步渲染

由于若是不采用异步更新,那么每次更新数据都会对当前租金按进行从新渲染,因此为了性能考虑,Vue会在本轮数据更新后,再去异步更新数据

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

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

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

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++) {
    callbacks[i]()
  }
  // 清空
  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这个函数
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 更快,为何?

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

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

我建议真正想看模板被编译后的原理的同窗,能够去尤大开源的vue-template-explorer 网站输入对应的模板,就会展现出对应的 render 函数。

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

为何快,快在哪里,什么状况下快,有数据支持吗?事实上在初始化数据量不一样的场景是很差比较的,React 不须要对数据递归的进行 响应式定义

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

Proxy是es6提供的新特性,兼容性很差,因此致使Vue3一致没有正式发布让开发者使用

Vue3 没发布不是由于兼容性很差,工做正在有序推动中,新的语法也在不断迭代,而且发布 rfc 征求社区意见。

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

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

React和Vue的比较

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

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

Vue2.0能够经过props实现双向绑定,用vuex单向数据流的状态管理框架

双向绑定是 v-model 吧。

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

Vue 虽然能够传递回调,可是通常来讲仍是经过 v-on:change 或者 @change 的方式去绑定事件吧,这和回调是两套机制。

模板渲染方式不一样,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 组件:

<div>
  <Counter id="a" />
  <Counter id="b" />
</div>
复制代码

咱们从原理出发,先看看它被编译成什么样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」 从字面意义来说就是 的意思,这个开关开启了,就意味着这个数据是脏数据,须要从新求值了拿到最新值。

「求值」 的意思的对用户传入的函数进行执行,也就是执行 return this.count + 1

  1. sum 第一次进行求值的时候会读取响应式属性 count,收集到这个响应式数据做为依赖。而且计算出一个值来保存在自身的 value 上,把 dirty 设为 false,接下来在模板里再访问 sum 就直接返回这个求好的值 value,并不进行从新的求值。
  2. count 发生变化了之后会通知 sum 所对应的 watcher 把自身的 dirty 属性设置成 true,这也就至关于把从新求值的开关打开来了。这个很好理解,只有 count 变化了, sum 才须要从新去求值。
  3. 那么下次模板中再访问到 this.sum 的时候,才会真正的去从新调用 sum 函数求值,而且再次把 dirty 设置为 false,等待下次的开启……

后续我会考虑单独出一篇文章进行详细讲解。

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;
};
复制代码

关于重复发文章

此外 @小明同窗哟 这个做者和 @小梦哟 这两个做者之间有说不清道不明的关系(以前看好像是情侣头像,而后常常互动,而且两我的分别著有《一个湖北女生的总结》、《一个湖北男生的总结》)。

两个做者之间把同一篇低质量文章来回发,都是那种评论区能指出特别多错误的水文。

来波 diff 算法

这是 @小明同窗哟 的 《前端面试大厂手写源码系列(上)》:

《前端面试大厂手写源码系列(上)》

这是 @小梦哟 的 《面试时,你被要求手写常见原理了吗?》

面试时,你被要求手写常见原理了吗?

基本上就是顺序调换一下,内容彻底重复的文章,阅读量还不低。

关于发课程文章不注明出处

最开始接触到这个做者,是由于她写了一篇 《Vue仿饿了么app项目总结》,我正好在这个项目的做者黄轶老师的群里,群友很是愤慨的来评论区讨公道后她才在评论区里声明这是和慕课网的黄轶老师学习课程后进行的总结。

我能够理解为若是没人说的话,她就想瞒混过去做为本身的项目了,惋惜她不了解行情,这门课早就在几年前就家喻户晓,成为 Vue 面试必备的实战项目了。

申请水军号

他们的文章其实挺难得到好评的,毕竟真的挺水的。可是这个用户却时常在他们的文章下抢沙发。点进去仔细一看,只关注了这俩人,点赞的也全是这俩人……

关于知识变现

我一直以为知识变现不可耻,这是一个「自媒体」流行的时代,认真输出本身观点而且影响他人的人理应得到本身的收益,我并不以为这有什么丢人的,

我在 写给初中级前端的高级进阶指南 这篇文章里放了几个掘金小册的推广码,是我认真读过之后真心想推荐给你们的,这也是掘金官方提供的一种变现机制。我真心不以为这有什么不对。知识是有价值的,前提是你不要输出二手错误百出的知识。甚至在你们的公众号上看到广告的时候,我也是会心一笑,由于这个做者曾经或「原创」或「转载」的优质文章给我带来了很大的收益……

而原做者 @小明同窗哟 的水平明显还不足以给社区的新人一些启发,甚至我感受大概至关于某c字开头的论坛上面充斥着的新手学习笔记,这样子为了变现而影响社区环境的吃相我就接受不了了。

再不济,你还能够学习某不肯说起姓名的「闰土大叔」,写些心灵鸡汤作一个教父,也同样能够赚的盆满钵满,毕竟人家没误导人。只是人家是真的不会技术,那就曲线救国而已。

总结

总而言之,我关注了这个做者和她的搭档 @小梦哟 挺久了,不知道这些做者为何这么拼命的想火起来,不惜重复发文章,不惜借用别人的课程成果而不声明,这对社区的进步来讲没有任何好处。

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

好比面试官问你 Vue 的原理,实际上是想考察你对日常使用的框架是否有探索底层原理的兴趣和热情,相信有这份热情的人他的技术积累和潜力必定也不会差。可是不少人如今为了应付面试,就直接按照本文所说的《前端常见的Vue面试题目汇总》这种文章一个个简略版答案去背(况且大厂面试官必定会针对每个点深刻挖掘,挖到你说不出来为止),这样真的是很不推荐的一种行为。

若是你真的想掌握好 Vue 的原理,而且做为你简历中的一个亮点,那么你就本身打开源码一点点花时间去研究,若是你目前的基础不够,那也能够辅助以一些优秀的的视频教程或者文章。可是我始终以为,纸上得来终觉浅,若是你不能去深刻源码一点点调试,你对它的认知总归是比较浅层的。

我坚持在掘金发文章其实有一个缘由,就是我也但愿中文社区能慢慢发展出相似 medium 那样高质量的前端交流社区(虽然它是付费制的,有难度),而掘金是我前端最开始就接触到的社区,内心也颇有感情,看着首页混杂着这种错误百出的低质量文章,我内心真的是百感交集,为何明明是不少未经考证,甚至连本身都说服不了的观点,也要整理成文章也要急着发出来吸引流量呢?

总之,真心但愿掘金能少一些不负责任的水文,一些摘抄搬运官方文档的东西。你们都认真的输出本身去证明过,或者真正理解的总结,慢慢的让掘金、甚至国内的前端氛围可以造成一个良性氛围,前端的明天愈来愈美好。

相关文章
相关标签/搜索