面试题

[toc]

备注:大部分从面试题从晒兜斯收录

Vue

Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

  1. Object.defineProperty 无法低耗费的监听到数组下标的变化,导致通过数组下标添加元素,不能实时响应;
  2. Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历。如果属性值是对象,还需要深度遍历。 Proxy 可以劫持整个对象, 并返回一个新的对象。
  3. Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

Vue 项目时为什么要在列表组件中写 key,其作用是什么?

vuereact 都是采用 diff算法来对比新旧虚拟节点,从而更新节点。在 vue 的 diff 函数交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的 key 去对比旧节点数组中的 key,从而找到相应旧节点(这里对应的是一个 key => indexmap 映射)。如果没有找到就认为是一个新增节点。而如果没有 key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个 map映射,另一种是遍历查找。相比而言,map映射的速度更快。

为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作?

纯函数,给定同样的输入返回同样的输出,可预测性。

在 Vue 中,子组件为何不可以修改父组件传递的 Prop,如果修改了,Vue 是如何监控到属性的修改并给出警告?

  • 因为 Vue 是单项数据流,易于检测数据的流动,出现了错误可以更加迅速的定位到错误发生的位置;
  • 通过 setter 属性进行检测,修改值将会触发 setter,从而触发警告;

双向绑定和 vuex 是否会冲突?

当在严格模式中使用 Vuex 时,在属于 Vuexstate 上使用 v-model 会导致出错。

解决方案:

  1. <Input> 中绑定 value,然后侦听 input 或者 change 事件,在事件回调中调用一个方法;
  2. 使用带有 setter 的双向绑定计算属性;

Vue 的父组件和子组件生命周期钩子执行顺序是什么?

  1. 加载渲染过程:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
  2. 子组件更新过程:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
  3. 父组件更新过程:父 beforeUpdate -> 父 updated
  4. 销毁过程:父 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
    • Vue&React&部署&算法 - 图1refs

new Vue以后会发什么事情?

  1. new Vue 会调用 Vue原型链上的 _init 方法对 Vue实例进行初始化;
  2. 首先是 initLifecycle 初始化生命周期,对 Vue 实例内部的一些属性(如 childrenparentisMounted)进行初始化;
  3. initEvents,初始化当前实例上的一些自定义事件(Vue.$on);
  4. initRender,解析 slots 绑定在 Vue实例上,绑定 createElement 方法在实例上;
  5. 完成对生命周期、自定义事件等一系列属性的初始化后,触发生命周期钩子 beforeCreate
  6. initInjections,在初始化 dataprops 之前完成依赖注入(类似于 React.Context);
  7. initState,完成对 dataprops 的初始化,同时对属性完成数据劫持内部,启用监听者对数据进行监听(更改);
  8. initProvide,对依赖注入进行解析;
  9. 完成对数据(state状态)的初始化后,触发生命周期钩子 created
  10. 进入挂载阶段,将 vue 模板语法通过 vue-loader 解析成虚拟 DOM 树,虚拟 DOM 树与数据完成双向绑定,触发生命周期钩子 beforeMount
  11. 将解析好的虚拟 DOM 树通过 vue 渲染成真实 DOM,触发生命周期钩子 mounted

React

redux 为什么要把 reducer 设计成纯函数?

redux 的设计思想就是不产生副作用,数据更改的状态可回溯,所以 redux 中处处都是纯函数。

react-router 里的 标签和 标签有什么区别?

  • onClick 则执行 OnClick
  • 阻止 a 标签默认事件(跳转页面);
  • 在取得跳转 hrefto 属性值),用history/hash 跳转,此时只是链接发现改变,并没有刷新页面;

react 组件的生命周期有哪些?

初始化阶段

  1. constructor(): 用于绑定事件,初始化 state
  2. componentWillMount():组件将要挂载,在 render之前调用,可以在服务端调用。
  3. render():用作渲染 dom
  4. componentDidMount():在 render之后,而且是所有子组件都 render之后才调用。

更新阶段

  1. getDerivedStateFromPropsgetDerivedStateFromProps会在调用 render方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容;
  2. componentWillReceiveProps(nextProps): 在这里可以拿到即将改变的状态,可以在这里通过 setState方法设置 state
  3. shouldComponentUpdate(nextProps, nextState): 他的返回值决定了接下来的声明周期是否会被调用,默认返回 true
  4. componentWillUpdate(): 不能在这里改变 state,否则会陷入死循环
  5. componentDidUpdate(): 和componentDidMount()类似,在这里执行 Dom操作以及发起网络请求

析构阶段

  • componentWillUnmount():主要执行清除工作,比如取消网络请求,清除事件监听。

如何源码实现Redux源码?

原理就是创建了一个状态中心(state),通过纯函数来对状态中心的数据进行修改,然后将修改后的结果以发布订阅的方式通知到所有订阅者,从而达到一个共享状态的效果。核心是函数式编程,使用无副作用的同步函数 action 来触发 reducer 对数据进行修改。Redux 的创建函数是 createStore,这个函数的作用是创建一个 store 对象,其中关键步骤是

  1. reducer 利用闭包存储在函数内;
  2. 导出了 subscribe 函数,subscribe 函数内部将订阅者添加到一个订阅者列表中,等待通知;
  3. 导出了 dispatch 函数,在 dispatch 内部执行了 reducer 函数,并将 action 作为参数传入到 reducer 中;
  4. reducer 函数的返回结果(新的 state)存在 currentState 中,同时通知所有的订阅者 state 已经更新;
  5. 订阅者可以通过 getState 得到最新的 state

Redux 中间件的原理就是 Decorator 装饰器模式,将中间件经过一些装饰器(中间件)装饰以后,返回一个增强型dispatch

redux-thunk 是如何实现异步 action 的?

redux-thunk 中会判断 action 的类型,如果 action 的类型为函数,则执行该 action 函数,并且将 dispatch 作为参数,将自身的 dispatch 操作延迟到 action 函数中执行,由 action 函数决定何时(可能是异步操作后)执行 dispatch

如何简述React源码实现?

  1. React 的实现主要分为 ComponentElement
  2. Component 属于 React 实例,在创建实例的过程中会在实例中注册 stateprops 属性,还会依次调用内置的生命周期函数;
  3. Component 中有一个 render 函数,render 函数要求返回一个 Element对象(或 null);
  4. Element 对象分为原生 Element 对象和组件式对象,原生 Element + 组件式对象会被一起解析成虚拟 DOM 树,并且内部使用的 stateprops 也以 AST 的形式注入到这棵虚拟 DOM 树之中;
  5. 在渲染虚拟 DOM 树的前后,会触发 React Component 的一些生命周期钩子函数,比如 componentWillMountcomponentDidMount,在虚拟 DOM 树解析完成后将被渲染成真实 DOM 树;
  6. 调用 setState 时,会调用更新函数更新 Componentstate,并且触发内部的一个 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 从一个节点开始,尝试访问尽可能接近它的目标节点。本质上这种遍历在图上是逐层移动的,首先检查最靠近第一个节点的层,再逐渐向下移动到离起始节点最远的层。