轻松搞懂前端面试题系列(vue篇三)
一、react 和 vue 有什么区别?
React
是由Facebook创建的JavaScript UI
框架,React
推广了 Virtual DOM
( 虚拟 DOM )并创造了 JSX
语法。JSX
语法的出现允许我们在 javascript 中书写 HTML 代码。
VUE
是由尤雨溪开发的,VUE
使用了模板系统而不是JSX
,因其实模板系统都是用的普通的 HTML,所以对应用的升级更方便、更容易,而不需要整体重构。
相同点:
vue
和react
都借鉴了mvvm
的概念思想。- 都提供了响应式和组件化的视图组件。
Vue
和React
实现原理和流程基本一致,都是使用Virtual DOM + Diff
算法。- 都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关库。
不同点主要体现在以下几个方面:
核心思想不同
Vue早期定位是尽可能的降低前端开发的门槛(这跟Vue作者是独立开发者也有关系)。所以Vue推崇灵活易用(渐进式开发体验),数据可变,双向数据绑定(依赖收集)。
React早期口号是Rethinking Best Practices
。背靠大公司Facebook的React
,从开始起就不缺关注和用户,而且React
想要做的是用更好的方式去颠覆前端开发方式(事实上跟早期jquery
称霸前端,的确是颠覆了)。所以React推崇函数式编程(纯组件),数据不可变以及单向数据流。函数式编程最大的好处是其稳定性(无副作用)和可测试性(输入相同,输出一定相同),所以通常大家说的React
适合大型应用,根本原因还是在于其函数式编程。
由于两者核心思想的不同,所以导致Vue和React许多外在表现不同:
核心思想不同导致写法差异
Vue推崇template(简单易懂,从传统前端转过来易于理解)、单文件vue。而且虽然Vue2.0以后使用了Virtual DOM,使得Vue也可以使用JSX(bebel工具转换支持),但Vue官方依然首先推荐template,这跟Vue的核心思想和定位有一定关系。
React推崇JSX、HOC、all in js。
核心思想不同导致api差异
Vue定位简单易上手,基于template模板 + options API,所以不可避免的有较多的概念和api。比如template模板中需要理解slot、filter、指令等概念和api,options API中需要理解watch、computed(依赖收集)等概念和api。
React本质上核心只有一个Virtual DOM + Diff算法,所以API非常少,知道setState就能开始开发了。
核心思想不同导致社区差异
由于Vue定义简单易上手,能快速解决问题,所以很多常见的解决方案,是Vue官方主导开发和维护。比如状态管理库Vuex
、路由库Vue-Router
、脚手架Vue-CLI
、Vutur
工具等。属于那种大包大揽,遇到某类通用问题,只需要使用官方给出的解决方案即可。
React只关注底层,上层应用解决方案基本不插手,连最基础的状态管理早期也只是给出flow
单向数据流思想,大部分都丢给社区去解决。比如状态管理库方面,有redux、mobx、redux-sage、dva
等一大堆(选择困难症犯了),所以这也造就了React
社区非常繁荣。同时由于有社区做上层应用解决方案,所以React
团队有更多时间专注于底层升级,比如花了近2年时间把底层架构改为Fiber
架构,以及创造出React Hooks
来替换HOC
。
核心思想不同导致未来升级方向不同
核心思想不同,决定了Vue和React未来不管怎么升级变化,Vue和React考虑的基本盘不变。
Vue依然会定位简单易上手(渐进式开发),依然是考虑通过依赖收集来实现数据可变。这点从Vue3核心更新内容可以看到:template
语法基本不变、options api
只增加了setup
选项(composition api
)、基于依赖收集(Proxy
)的数据可变。
React的函数式编程这个基本盘不会变。React
核心思想,是把UI
作为Basic Type
,比如String、Array
类型,然后经过render
处理,转换为另外一个value
(纯函数)。从React Hooks
可以看出,React
团队致力于组件函数式编程,(纯组件,无class
组件),尽量减少副作用(减少this
,this
会引起副作用)。
组件实现不同
Vue源码实现是把options挂载到Vue核心类上,然后再new Vue({options})拿到实例(vue
组件的script
导出的是一个挂满options
的纯对象而已)。所以options api
中的this
指向内部Vue实例,对用户是不透明的,所以需要文档去说明this.slot、this.slot、this.slot、this.xxx
这些api。另外Vue
插件都是基于Vue
原型类基础之上建立的,这也是Vue
插件使用Vue.install
的原因,因为要确保第三方库的Vue
和当前应用的Vue
对象是同一个。
React
内部实现比较简单,直接定义render
函数以生成VNode
,而React内部使用了四大组件类包装VNode,不同类型的VNode
使用相应的组件类处理,职责划分清晰明了(后面的Diff
算法也非常清晰)。React
类组件都是继承自React.Component
类,其this
指向用户自定义的类,对用户来说是透明的。
响应式原理不同
Vue
- Vue依赖收集,自动优化,数据可变。
Vue
递归监听data
的所有属性,直接修改。- 当数据改变时,自动找到引用组件重新渲染。
React
- React基于状态机,手动优化,数据不可变,需要
setState
驱动新的State
替换老的State
。 - 当数据改变时,以组件为根目录,默认全部重新渲染
diff算法不同
Vue
基于snabbdom
库,它有较好的速度以及模块机制。Vue Diff使用双向指针,边对比,边更新DOM。
React主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。
事件机制不同
Vue
- Vue原生事件使用标准Web事件。
Vue
组件自定义事件机制,是父子组件通信基础。Vue
合理利用了snabbdom
库的模块插件。
React
- React原生事件被包装,所有事件都冒泡到顶层
document
监听,然后在这里合成事件下发。基于这套,可以跨端使用事件机制,而不是和Web DOM
强绑定。 React
组件上无事件,父子组件通信使用props
。
二、vue中computed和watch区别
watch和computed都是以Vue的依赖追踪机制为基础的,当某一个依赖型数据(依赖型数据:简单理解即放在 data
等对象下的实例数据)发生变化的时候,所有依赖这个数据的相关数据会自动发生变化,即自动调用相关的函数,来实现数据的变动。
当依赖的值变化时,在watch中,是可以做一些复杂的操作的,而computed中的依赖,仅仅是一个值依赖于另一个值,是值上的依赖。
应用场景
computed
:用于处理复杂的逻辑运算;一个数据受一个或多个数据影响;用来处理watch
和methods
无法处理的,或处理起来不方便的情况。例如处理模板中的复杂表达式、购物车里面的商品数量和总金额之间的变化关系等。
watch
:用来处理当一个属性发生变化时,需要执行某些具体的业务逻辑操作,或要在数据变化时执行异步或开销较大的操作;一个数据改变影响多个数据。例如用来监控路由、input 输入框值的特殊处理等。
区别
computed
- 初始化显示或者相关的
data、props
等属性数据发生变化的时候调用; - 计算属性不在
data
中,它是基于data
或props
中的数据通过计算得到的一个新值,这个新值根据已知值的变化而变化; - 在
computed
属性对象中定义计算属性的方法,和取data
对象里的数据属性一样,以属性访问的形式调用; - 如果
computed
属性值是函数,那么默认会走get
方法,必须要有一个返回值,函数的返回值就是属性的属性值; computed
属性值默认会缓存计算结果,在重复的调用中,只要依赖数据不变,直接取缓存中的计算结果,只有依赖型数据发生改变,computed
才会重新计算;- 在
computed
中的,属性都有一个get
和一个set
方法,当数据变化时,调用set
方法。
watch
- 主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,可以看作是
computed
和methods
的结合体; - 可以监听的数据来源:
data,props,computed
内的数据; watch
支持异步;- 不支持缓存,监听的数据改变,直接会触发相应的操作;
- 监听函数有两个参数,第一个参数是最新的值,第二个参数是输入之前的值,顺序一定是新值,旧值。
三、computed怎么实现的缓存?
整个流程为下面几步:
- 当组件初始化的时候,
computed
和data
会分别建立各自的响应系统,Observer
遍历data
中每个属性设置get/set
数据拦截 - 初始化
computed
会调用initComputed
函数
- 注册一个
watcher
实例,并在内实例化一个Dep
消息订阅器用作后续收集依赖(比如渲染函数的watcher
或者其他观察该计算属性变化的watcher
) - 调用计算属性时会触发其
Object.defineProperty
的get
访问器函数 - 调用
watcher.depend()
方法向自身的消息订阅器dep
的subs
中添加其他属性的watcher
- 调用
watcher
的evaluate
方法(进而调用watcher
的get
方法)让自身成为其他watcher
的消息订阅器的订阅者,首先将watcher
赋给Dep.target
,然后执行getter
求值函数,当访问求值函数里面的属性(比如来自data
、props
或其他computed
)时,会同样触发它们的get
访问器函数从而将该计算属性的watcher
添加到求值函数中属性的watcher
的消息订阅器dep
中,当这些操作完成,最后关闭Dep.target
赋为null
并返回求值函数结果。
- 当某个属性发生变化,触发
set
拦截函数,然后调用自身消息订阅器dep
的notify
方法,遍历当前dep
中保存着所有订阅者wathcer
的subs
数组,并逐个调用watcher
的update
方法,完成响应更新。
这里面涉及到源码的阅读,有打破砂锅问到底精神的朋友可以自行查询资料了解细节。
推荐文章:Vue computed是如何实现的?