- 面试题
- Vue
- Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
- Vue 项目时为什么要在列表组件中写 key,其作用是什么?
- 为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作?
- 在 Vue 中,子组件为何不可以修改父组件传递的 Prop,如果修改了,Vue 是如何监控到属性的修改并给出警告?
- 双向绑定和 vuex 是否会冲突?
- Vue 的父组件和子组件生命周期钩子执行顺序是什么?
- vue 在 v-for 时给每项元素绑定事件需要用事件代理吗?为什么?
- MVC、MVP、MVVM 模式的区别是什么?
- Vue 组件间如何通信?
- new Vue以后会发什么事情?
- React
- 部署
- 算法
- Vue
面试题
[toc]
备注:大部分从面试题从晒兜斯收录
Vue
Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
Object.defineProperty无法低耗费的监听到数组下标的变化,导致通过数组下标添加元素,不能实时响应;Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历。如果属性值是对象,还需要深度遍历。Proxy可以劫持整个对象, 并返回一个新的对象。Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
Vue 项目时为什么要在列表组件中写 key,其作用是什么?
vue 和 react 都是采用 diff算法来对比新旧虚拟节点,从而更新节点。在 vue 的 diff 函数交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的 key 去对比旧节点数组中的 key,从而找到相应旧节点(这里对应的是一个 key => index 的 map 映射)。如果没有找到就认为是一个新增节点。而如果没有 key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个 map映射,另一种是遍历查找。相比而言,map映射的速度更快。
为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作?
纯函数,给定同样的输入返回同样的输出,可预测性。
在 Vue 中,子组件为何不可以修改父组件传递的 Prop,如果修改了,Vue 是如何监控到属性的修改并给出警告?
- 因为
Vue是单项数据流,易于检测数据的流动,出现了错误可以更加迅速的定位到错误发生的位置; - 通过
setter属性进行检测,修改值将会触发setter,从而触发警告;
双向绑定和 vuex 是否会冲突?
当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 v-model 会导致出错。
解决方案:
- 给
<Input>中绑定value,然后侦听input或者change事件,在事件回调中调用一个方法; - 使用带有
setter的双向绑定计算属性;
Vue 的父组件和子组件生命周期钩子执行顺序是什么?
- 加载渲染过程:
父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount->子 mounted->父 mounted; - 子组件更新过程:
父 beforeUpdate->子 beforeUpdate->子 updated->父 updated; - 父组件更新过程:
父 beforeUpdate->父 updated; - 销毁过程:
父 beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed;
vue 在 v-for 时给每项元素绑定事件需要用事件代理吗?为什么?
在 v-for 中使用事件代理可以使监听器数量和内存占用率都减少,vue 内部并不会自动做事件代理,所以在 v-for 上使用事件代理在性能上会更优。
MVC、MVP、MVVM 模式的区别是什么?
在开发图形界面应用程序的时候,会把管理用户界面的层次称为 View,应用程序的数据为 Model,Model 提供数据操作的接口,执行相应的业务逻辑。
MVC 除了把应用程序分为 View、Model层,还额外的加了一个 Controller层,它的职责是进行 Model 和 View 之间的协作(路由、输入预处理等)的应由逻辑(application logic);Model 进行处理业务逻辑。
用户对 View 操作以后,View 捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller 会对来自 View 数据进行预处理、决定调用哪个 Model 的接口;然后由 Model 执行相关的业务逻辑;当Model 变更了以后,会通过观察者模式(Observer Pattern)通知 View;View 通过观察者模式收到 Model 变更的消息以后,会向 Model 请求最新的数据,然后重新更新界面。
MVP和MVC 模式一样,用户对 View 的操作都会从 View 交易给 Presenter。Presenter 会执行相应的应用程序逻辑,并且会对 Model 进行相应的操作;而这时候 Model 执行业务逻辑以后,也是通过观察者模式把自己变更的消息传递出去,但是是传给 Presenter 而不是 View。Presenter 获取到 Model变更的消息以后,通过 View 提供的接口更新界面。
MVVM 可以看做是一种特殊的 MVP(Passive View)模式,或者说是对 MVP 模式的一种改良。
MVVM 代表的是 Model-View-ViewModel,可以简单把 ViewModel 理解为页面上所显示内容的数据抽象,和 Domain Model 不一样,ViewModel 更适合用来描述 View。 MVVM 的依赖关系和 MVP 依赖关系一致,只不过是把 P 换成了 VM。
MVVM 的调用关系和 MVP 一样。但是,在 ViewModel 当中会有一个叫 Binder,或者是 Data-binding engine 的东西。以前全部由 Presenter 负责的 View 和 Model 之间数据同步操作交由给 Binder 处理。你只需要在View 的模板语法当中,指令式声明 View 上的显示的内容是和 Model 的哪一块数据绑定的。当 ViewModel 对进行 Model 更新的时候,Binder 会自动把数据更新到 View 上,当用户对 View 进行操作(例如表单输入),Binder 也会自动把数据更新到 Model 上。这种方式称为:Two-way data-binding,双向数据绑定。可以简单而不恰当地理解为一个模板引擎,但是会根据数据变更实时渲染。
Vue 组件间如何通信?
父子组件通信
- props + emit
- $refs + $parent
- provider/inject
兄弟组件通信
- eventBus
refs
new Vue以后会发什么事情?
new Vue会调用Vue原型链上的_init方法对Vue实例进行初始化;- 首先是
initLifecycle初始化生命周期,对 Vue 实例内部的一些属性(如children、parent、isMounted)进行初始化; initEvents,初始化当前实例上的一些自定义事件(Vue.$on);initRender,解析slots绑定在Vue实例上,绑定createElement方法在实例上;- 完成对生命周期、自定义事件等一系列属性的初始化后,触发生命周期钩子
beforeCreate; initInjections,在初始化data和props之前完成依赖注入(类似于React.Context);initState,完成对data和props的初始化,同时对属性完成数据劫持内部,启用监听者对数据进行监听(更改);initProvide,对依赖注入进行解析;- 完成对数据(
state状态)的初始化后,触发生命周期钩子created; - 进入挂载阶段,将 vue 模板语法通过
vue-loader解析成虚拟 DOM 树,虚拟 DOM 树与数据完成双向绑定,触发生命周期钩子beforeMount; - 将解析好的虚拟 DOM 树通过 vue 渲染成真实 DOM,触发生命周期钩子
mounted;
React
redux 为什么要把 reducer 设计成纯函数?
redux 的设计思想就是不产生副作用,数据更改的状态可回溯,所以 redux 中处处都是纯函数。
react-router 里的 标签和 标签有什么区别?
- 有
onClick则执行OnClick; - 阻止
a标签默认事件(跳转页面); - 在取得跳转
href(to属性值),用history/hash跳转,此时只是链接发现改变,并没有刷新页面;
react 组件的生命周期有哪些?
初始化阶段
constructor(): 用于绑定事件,初始化statecomponentWillMount():组件将要挂载,在render之前调用,可以在服务端调用。render():用作渲染dom;componentDidMount():在render之后,而且是所有子组件都render之后才调用。
更新阶段
getDerivedStateFromProps:getDerivedStateFromProps会在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容;componentWillReceiveProps(nextProps): 在这里可以拿到即将改变的状态,可以在这里通过setState方法设置stateshouldComponentUpdate(nextProps, nextState): 他的返回值决定了接下来的声明周期是否会被调用,默认返回truecomponentWillUpdate(): 不能在这里改变state,否则会陷入死循环componentDidUpdate(): 和componentDidMount()类似,在这里执行Dom操作以及发起网络请求
析构阶段
componentWillUnmount():主要执行清除工作,比如取消网络请求,清除事件监听。
如何源码实现Redux源码?
原理就是创建了一个状态中心(state),通过纯函数来对状态中心的数据进行修改,然后将修改后的结果以发布订阅的方式通知到所有订阅者,从而达到一个共享状态的效果。核心是函数式编程,使用无副作用的同步函数 action 来触发 reducer 对数据进行修改。Redux 的创建函数是 createStore,这个函数的作用是创建一个 store 对象,其中关键步骤是
- 将
reducer利用闭包存储在函数内; - 导出了
subscribe函数,subscribe函数内部将订阅者添加到一个订阅者列表中,等待通知; - 导出了
dispatch函数,在dispatch内部执行了reducer函数,并将action作为参数传入到reducer中; - 将
reducer函数的返回结果(新的state)存在currentState中,同时通知所有的订阅者state已经更新; - 订阅者可以通过
getState得到最新的state;
Redux 中间件的原理就是 Decorator 装饰器模式,将中间件经过一些装饰器(中间件)装饰以后,返回一个增强型的 dispatch。
redux-thunk 是如何实现异步 action 的?
在 redux-thunk 中会判断 action 的类型,如果 action 的类型为函数,则执行该 action 函数,并且将 dispatch 作为参数,将自身的 dispatch 操作延迟到 action 函数中执行,由 action 函数决定何时(可能是异步操作后)执行 dispatch
如何简述React源码实现?
- React 的实现主要分为
Component和Element; Component属于 React 实例,在创建实例的过程中会在实例中注册state和props属性,还会依次调用内置的生命周期函数;Component中有一个render函数,render函数要求返回一个Element对象(或null);Element对象分为原生Element对象和组件式对象,原生Element+ 组件式对象会被一起解析成虚拟 DOM 树,并且内部使用的state和props也以 AST 的形式注入到这棵虚拟 DOM 树之中;- 在渲染虚拟 DOM 树的前后,会触发 React Component 的一些生命周期钩子函数,比如
componentWillMount和componentDidMount,在虚拟 DOM 树解析完成后将被渲染成真实 DOM 树; - 调用
setState时,会调用更新函数更新Component的state,并且触发内部的一个updater,调用render生成新的虚拟 DOM 树,利用 diff 算法与旧的虚拟 DOM 树进行比对,比对以后利用最优的方案进行 DOM 节点的更新,这也是 React 单向数据流的原理(与 Vue 的 MVVM 不同之处)。
部署
如何简述Git-Rebase?
- 可以合并多次提交记录,减少无用的提交信息;
- 合并分支并且减少 commit 记录;
算法
深度优先遍历和广度优先遍历,如何实现?
深度优先遍历(Depth-First-Search),是搜索算法的一种,它沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点 v 的所有边都已被探寻过,将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已探寻源节点到其他所有节点为止,如果还有未被发现的节点,则选择其中一个未被发现的节点为源节点并重复以上操作,直到所有节点都被探寻完成。
简单的说,DFS 就是从图中的一个节点开始追溯,直到最后一个节点,然后回溯,继续追溯下一条路径,直到到达所有的节点,如此往复,直到没有路径为止。
DFS 可以产生相应图的拓扑排序表,利用拓扑排序表可以解决很多问题,例如最大路径问题。一般用堆数据结构来辅助实现 DFS 算法。
注意:深度 DFS 属于盲目搜索,无法保证搜索到的路径为最短路径,也不是在搜索特定的路径,而是通过搜索来查看图中有哪些路径可以选择。
广度优先遍历(Breadth-First-Search)是从根节点开始,沿着图的宽度遍历节点,如果所有的节点均被访问过,则算法终止,BFS 同样属于盲目搜索,一般用队列数据结构来辅助实现 BFS。
BFS 从一个节点开始,尝试访问尽可能接近它的目标节点。本质上这种遍历在图上是逐层移动的,首先检查最靠近第一个节点的层,再逐渐向下移动到离起始节点最远的层。
