归纳面试题
配置 nginx
1 | server { |
配置 webpack
1 | const path = require("path"); |
配置 rollup
1 | import json form '@rollup/plugin-json'; |
React 问题
React.memo useMemo useCallback 都是什么,区别?应用场景?方法?
- React.memo 是高阶组件,是对整个组件进行包裹.作用类似于 class 组件中的
shouldComponentUpdate
,对 props 是否变化做浅层比较.
变化才会重新渲染.如果想深层比较,可以使用它的第二个参数.和shouldComponentUpdate
不同的地方是第二个参数的返回值,props 如果相等是返回 true,而shouldComponentUpdate
返回的是 false.
1 | export default React.memo(MyComponent, areEqual); // 第二个参数areEqual |
- React.memo 是对整个组件进行包裹,useMemo 和 useCallback 是对更细粒度的值或函数进行优化.
- useMemo 和 useCallback 差异在于返回值,一个是返回的 memorize 值,一个是返回 memorize 函数.
- useMemo 和 useCallback 需要配合 React.memo,如果不使用 memo,那么每次 prop 整体都是一个新的对象/
- Vue 会自动去做这件事,React 需要手动去做.
Hook 为啥不能变.不能在前面加判断语句?
要保证 hooks 是在最顶层调用.是为了保证多个 hooks 调用的顺序是一致.
Hooks 最终是构建了一个单项链表,每次执行都是按照同样的顺序被调用,如果放在循环,条件语句或者嵌套函数中肯定会破坏它的顺序型导致问题.
Fiber 算法
Fiber 是一个执行单元,一种数据结构.因为 React16 之前是将虚拟 DOM 树看出一个任务执行,递归处理,任务庞大且不能中断.
React16 之后,将整个任务分成一个个小的任务在空余时间里处理,主要是通过 requestIdleCallback 这个浏览器的 API 去获取浏览器的空余时间,如果有,就执行,没有就让出主线程.
useLayoutEffect,和 useEffect 区别?在哪个阶段执行? beginWork,commit Work
二者的函数签名是相同的,不同点在于触发的时机不同.useLayoutEffect
是同步处理副作用.useEffect
是异步处理.useLayoutEffect
在 DOM 更新后,浏览器渲染之前,useLayoutEffect
内部的更新将会同步刷新.useEffect
是在浏览器渲染后.useLayoutEffect
是在commitWork
阶段.
Redux 是双向数据绑定吗?
不是,是单向数据流.
redux 和 react-redux 区别
- redux 和组件进行对接的时候是直接在组件中进行创建。react-redux 是运用 Provider 将组件和 store 对接,使在 Provider 里的所有组件都能共享 store 里的数据,还要使用 connect 将组件和 react 连接.
- redux 获取 state 是直接通过 store.getState()。
react-redux 获取 state 是通过 mapStateToProps 函数,只要 state 数据变化就能获取最新数据 - redux 是使用 dispatch 直接触发,来操作 store 的数据。
react-redux 是使用 mapDispatchToProps 函数然后在调用 dispatch 进行触发
React vs Vue 区别
- 从写法上,react 推荐的是 jsx+内联样式,即
all in js
.Vue 是 template 的单文件格式,即 html,js,css 写在同一个文件中. - 虚拟 DOM 的区别,主要是 diff 算法的区别
- 数据驱动视图.
- Vue 是类似 MVVM 框架,数据响应式 => 转化 data 属性为 getter/setter =>watcher 实例对象进行依赖收集与监听 =>数据 setter 被调用时,watcher 对比前后两个值是否发生变化 => 通知视图是否进行渲染.
- React 通过
setState
实现数据驱动视图. setState 将接收的第一个参数 state 存储在pending
队列中,判断当前 React 是否处在批量更新状态,是的话就将需要更新的 state 组件添加到dirtyComponent
中,不是的话,就遍历dirtyComponent
中的所有组件,调用updateComponent
方法更新每个dirty
组件.
diff 算法区别
- dom 更新策略不同
react 会自顶而下全 diff,vue 会跟踪每个组件的依赖关系,不需要重新渲染整个组件树.
react 当状态发生改变会重新 render 页面,生成新的虚拟 dom 树,新旧 dom 树进行比较,进行打补丁的方式,局部更新 dom.
所以需要手动的对不需要重新渲染的组件或者值进行 memo 优化.
vue 是通过数据响应式的方式,通过Object.defineProperty
把 data 的属性转化为getter/setter
.同时进行依赖收集与监听,setter 被调用时进行组件更新. - diff 算法源码实现不同
vue 采用双端对比的算法,同时从新旧 children 的两端进行比较,借助 key 值找到可复用的节点,再进行相关操作.
react 为什么没有采用双指针?
因为目前 Fiber 上没有设置反向链表,单向链表无法使用双指针,所以无法对算法进行双指针优化.
react 异步组件
原理: 用 componentDidCatch 捕获异步请求,如果有异步请求渲染 fallback,等到异步请求执行完毕,渲染真实组件,借此整个异步流程完毕.
1 | const LazyComponent = React.lazy(() => import("./test.js")); |
setState 是异步还是同步
本质上是批量执行或非批量执行的问题.而且只在 React18 以前存在.
在 react17 中,setState 是批量执行的,因为执行前会设置 executionContext。但如果在 setTimeout、事件监听器等函数里,就不会设置 executionContext 了,这时候 setState 会同步执行。可以在外面包一层 batchUpdates 函数,手动设置下 executionContext 来切换成异步批量执行
在 react18 里面,如果用 createRoot 的 api,就不会有这种问题了,因为所有的 setState 都是异步批量执行了。
webpack
webpack plugin 原理
利用钩子机制,在生命周期的钩子上挂载函数实现扩展.
webpack 中 publicPath 作用?
静态资源的基础路径,一般在生成模式下使用.
tree shaking
作用是移除未引入的代码.
webpack 的 tree-shaking 在 production 下是默认开启的.
tree-shaking 需要依赖 ESModule 模块才能起作用.
在开发环境下可以通过设置optimization: { useExports: true; minimize: true}
开启.
这个和babel-loader
中preset-env
互斥,但是新版本的已经改为自动判断是否开启.所以不用担心.
Webpack => sideEffects
可以在optimization
中开启标识副作用,在package.json
中标识是否有副作用.
也可以在package.json
中标记哪些有副作用,不过在生产环境是默认开启的.
1 | //package.json |
直接导出构造函数会被 tree-shaking 吗
看导出的构造函数有没有被使用了
webpack 打包体积如何减小?
chunk 分包,import
动态导入,提取公共模块.
webpack 优化
在加快构建时间方面,作用最大的是配置 cache,可大大加快二次构建速度。
在减小打包体积方面,作用最大的是压缩代码、分离重复代码、Tree Shaking,可最大幅度减小打包体积。
在加快加载速度方面,按需加载、浏览器缓存、CDN 效果都很显著。
配置持久化 cache
1 | cache: { |
压缩代码:
- 压缩 js 代码:在 production 模式默认开启压缩.可以在 optimization 的 minimizer 中进行配置
- 压缩 html 代码: 在 HtmlWebpackPlugin 中 minify 开启压缩.
打包的代码加上 hash,实现缓存文件
开启 tree Shaking,移除未引用代码(production 默认开启)
开启方法: 在 package.json 配置 sideEffects: false(表示都没有副作用,可以清除所有未引用代码),如果有,写个数组[‘*.css’],一般都是 css 文件
按照路由拆分代码,实现按需加载
多页应用实现多入口打包,设置多个 entry 入口,htmlWebpackPlugin 也是多个
提取公共模块,也就是分离代码,在 optimization 中设置 splitChunks,
图片压缩
- 使用雪碧图,字体图标, 5k 一下使用 base64 编码
使用 cdn
使用预加载
script 标签中 rel=”preload”
- DNS 预解析 dns-prefetch
- 资源预加载 preload
- 预渲染 prerender
减少重排重绘
- 不使用 table 布局
- css 属性读写分离
- 样式批量修改
- 减少 dom 深度
- 将没用的元素设置为不可见 visibility: hidden
polyfill 按需加载 babel 典型配置
1 | module.exports = { |
postcss 什么作用?
处理 css 兼容性的工具,一般有多种工具,比如autoprefixer
处理不同浏览器的前缀.
sass-loader 用了什么方法
1 | const nodeSass = require("node-sass"); |
可以看到,sass-loader 使用了 node-sass 解析 sass 文件导出为 css 文件.输出为字符串交给下一个 loader.
Esm 是什么?
es6 module 是一种模块化开发的规范.设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系和导入导出的变量.
主要功能由两个命令构成: export
, import
.
- 只能作为模块顶层的语句出现
- import 的模块名只能是字符串常量
- import 之后是不可修改的 例如,在使用 CommonJS 时,必须导入完整的工具 (tool) 或库 (library) 对象,且可带有条件判断来决定是否导入。
Umi 了解吗
Element-ant 之类的是怎么实现响应式布局?
参照了 Bootstrap 的 响应式设计,预设了五个响应尺寸:xs、sm、md、lg 和 xl。
也就是通过类名.
在 less 或者 scss 文件中设置变量.媒体查询通过断点设置 css.
Vue
Vue 对数组是怎么处理的
- 先获取原生 Array 的原型方法,因为拦截后还是需要原生的方法帮我们实现数组的变化。
- 对 Array 的原型方法使用 Object.defineProperty 做一些拦截操作。
- 把需要被拦截的 Array 类型的数据原型指向改造后原型。
1 | import { def } from "../util/index"; |
$set 为什么可以检测数组变化?
如果 target 是一个数组且索引有效,就设置 length 的属性。
通过 splice 方法把 value 设置到 target 数组指定位置。
设置的时候,vue 会拦截到 target 发生变化,然后把新增的 value 也变成响应式
最后返回 value
1 | function set(target, key, val) { |
$nextTick 的作用和原理
Vue 中 DOM 更新是异步的,批量的.在修改 DOM 后立即获取,是获取不到的,需要等待 dom 更新后才能获取.
tis.$nextTick 接收一个函数,函数会在下一次循环 DOM 渲染完毕后执行,在这里也就得到了最新的值.
mutationObserver 了解吗
属于微任务,观察 dom 元素,并在检测到更改时触发回调.
1 | // 通过new创建实例,接收一个回调 |
父子组件的生命周期执行顺序?
执行顺序:父组件先创建,然后子组件创建;子组件先挂载,然后父组件挂载,
否则父组件先挂载就是结束了,子组件没地方挂载了.
大体流程: 先父后字,再先子后父.界限在挂载那里改为先子后父,前面的都是先父后子.
渲染阶段
父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount->子 mounted->父 mounted
卸载阶段
父 beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed
Http
axios 和 fetch 区别
- 传递数据方式.axios 放在 data 中,fetch 放在 body 中并且是 string 形式.
- 设置超时.axios 设置 timeout 即可,fetch 需要使用
AbortController
属性设置.
1 | const url = "https://jsonplaceholder.typicode.com/todos"; |
- 数据转化. 对于返回类型,axios 可以自动转化,默认是 json 类型.fetch 需要清楚后端传过来是什么类型,并手动转化.
- 拦截器.axios 自带,fetch 需要手动封装.
1 | // axios |
- 并发请求. 都支持.
1 | axios.all([axios.get("..."), axios.get("...")]).then( |
- 使用 fetch 封装,注意和 axios 的区别,无法捕获非网络异常导致的 error.必须手动 Promise.reject()抛出.
1 | const url = "https://jsonplaceholder.typicode.com/todos"; |
interceptor 原理
1、当使用 axios 时,Axios 被实例化,并内部分别实例化 request、response 拦截器
2、当使用 use 添加拦截器时,触发 InterceptorManager.prototype.use 方法,将传过来的 resolve、reject 回调,添加在 this.handlers[]中
3、使用 axios.get(‘https://xxxx‘) 时,触发 Axios.prototype.request ,内部触发 InterceptorManager.prototype.forEach
3.1 request 拦截器 和 response 拦截器 触发 Axios.prototype.request 内方法不一样 分别是:this.interceptors.request.forEach、this.interceptors.response.forEach 二者主要区别在于想队列 chain[] 中添加的位置不同,request 拦截器从头开始添加 和 response 拦截器从尾开始添加,而 chain[]本身就含有 xhr 的 promise 调用返回
3.2 从而实现:先触发 request 拦截器=>发请求,请求返回=>触发 response 拦截器
- Axios.prototype.request 最后 return promise,就是我们最后使用的 axios.get(‘’)这个方法的原样,所有 axios.get(‘’).then(res)会再被调用,res 就是请求返回的数据被 response 拦截器处理后的最终结果了
fetch 的 interceptor 原理
拦截器的原理很简单,原生 fetch,接受两个参数,一个是请求路径 resource,resource 可以是 url 也可以是 request 对象,另外一个参数是 config,可以配置 method,header 等请求参数。而拦截器则是通过替换 window.fetch,在其外面再套一层处理起 request 以及 response。
1 | function initFetchInterceptor() { |
request 拦截比较简单,就是直接获取 resource 以及 config 进行处理,替换为自己想要的参数再发起请求
1 | // request拦截 |
response 拦截
1 | const btn = document.getElementById("btn"); |
CORS 的请求头
CORS 是跨域资源共享.
当浏览器发现域是不同的,会向服务端发送一个 options 请求,检查请求是否允许.
前端可以在该方法中添加Access-Control-Request-Methods
,表头中提供一些信息,真实请求何时到来,数据类型是什么.
服务端响应发回Access-Control-Allow-Origin
,返回允许的地址.
ES6
Map,set, weakMap,weakSet 区别?
Set 是类数组的数据结构,内部存储的值并不重复.
weakSet 的成员只能是对象类型,而且是弱引用,就是如果没有对象引用内部的对象,那就可能被 GC,所以不能遍历.
Map 是键值对的组合,和 Object 的区别是 key 可以是任意类型,而对象只能是 string 和 symbol.
weakMap 类似于 Map,不过只接受对象做 key.
弱引用是所引用的对象的其他引用被清除,垃圾回收就会自动释放该对象占用的内存.(就是没被人用了,就清除了)
Proxy 和 Reflect?
Proxy 和 Reflect 是 ES6 推出的新特性.
Prxoy 可以代理对象的属性,内部有陷阱函数可以对代理的对象进行操作.通过 Rflect 可以将代理对象的方法返回.
1 | const obj = { |
receiver 表示代理对象或者继承对象
Promise 并发控制
1 | //自定义请求函数 |
Node
node 写过 cli 吗
简历项目
在项目中发现首屏加载时间 xx 秒,有很大优化空间,经过研究采用 xx 方案优化到了 xx 秒.提升了 60%性能.
借鉴 xx 项目中优秀的方案,讲解
竟可能多说解决方案