### 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` 是递归向下的进行 `reconciler`,`React` 引入了 `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` 文件里写的 `template` 和 `HTML` 本质上没有关系。
>
> 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` 组件:
我们从原理出发,先看看它被[编译成什么样]( )的 `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 watch`了 `obj`,那么对 `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; };
### 关于知识变现
我一直觉得知识变现不可耻,这是一个「自媒体」流行的时代,认真输出自己观点并且影响他人的人理应获得自己的收益,我并不觉得这有什么丢人的,
我在 [写给初中级前端的高级进阶指南]( ) 这篇文章里放了几个掘金小册的推广码,是我认真读过以后真心想推荐给大家的,这也是掘金官方提供的一种变现机制。我真心不觉得这有什么不对。知识是有价值的,前提是你不要输出二手错误百出的知识。甚至在大家的公众号上看到广告的时候,我也是会心一笑,因为这个作者曾经或「原创」或「转载」的优质文章给我带来了很大的收益……
水平明显还不足以给社区的新人一些启发,这样子为了变现而影响社区环境的吃相我就接受不了了。
再不济,你还可以学习某不愿提及姓名的「闰土大叔」,写些心灵鸡汤做一个教父,也一样可以赚的盆满钵满,毕竟人家没误导人。只是人家是真的不会技术,那就曲线救国而已。
### 总结
关于面经,面经其实是一个挺不错的文章形式,它可以让你在不去参与面试的情况下也可以得知目前国内的大厂主要在技术上关注哪些重点。但是如果你用面经下面的简略的答案去作为你的学习材料,那我觉得就本末倒置了。正确的方式是去针对每一个重难点,结合你自己目前的技术水平和方向去深入学习和研究。
### 最后
总的来说,面试官要是考察思路就会从你实际做过的项目入手,考察你实际编码能力,就会让你在电脑敲代码,看你用什么编辑器、插件、编码习惯等。所以我们在回答面试官问题时,有一个清晰的逻辑思路,清楚知道自己在和面试官说项目说技术时的话就好了


**开源分享:https://docs.qq.com/doc/DSmRnRGxvUkxTREhO**