写在前面
本周有位群友说我不应该为了面试而背题,我很认同他的说法,这里想表达下我建立这个专栏的初衷吧。
- 前端领域知识点繁杂分散,我认为再基础的点,不回顾也有可能忘记,所以我希望这个专栏可以像基地一样,我们常回来看看
- 大家也能发现,我这里主要是收集或修改网络上的回答,是因为我真的觉得这些回答很好,这也引出第二点:看看别人是怎么表述这个观点的。
- emm...确实也在背题
当然,以上只是我个人观点。
值新的一岁之际,希望自己可以:勤思考,多动手,善总结,能坚持。
建了一个公众号,每周周一 至 周五,每天发布若干道面试题,并奉上个人觉得还行的解答,周五(周末发好像点击率真的有点低)、周六或周天发一遍汇总~。
希望大家有所得,希望自己有所得。下面是本周的汇总(2020.08.03 - 2020.08.07)。
目录
- 是否写过 Loader?简单描述一下编写 loader 的思路?
- 是否写过 Plugin?简单描述一下编写 Plugin 的思路?
- 聊一聊 Babel 原理吧
- 在项目中,如何优化 Webpack
- CDN 是什么?描述下 CDN 原理?为什么要用 CDN?
- 说一下正向代理和反向代理
- 说一下事件模型、事件流和事件代理
- 「react」React 中函数组件和普通组件有什么区别
- 「react」说一下 React 生命周期,以及新版本中都有哪些改变,为什么去掉了那几个旧的生命周期
- 「react」知道 Portal 吗,有哪些使用场景?
- 「react」 React 中 StrictMode (严格模式) 是什么?
- 「react」什么是纯函数 && 什么是纯组件
- 「react」请求接口放在哪个生命周期
- 「react」React 事件合成机制是什么?为什么要合成?
详情
一、是否写过 Loader?简单描述一下编写 loader 的思路?
所谓 loader 只是一个导出为函数的 JavaScript 模块。 webpack 中的 loader runner 会调用这个函数,然后把上一个 loader 产生的结果或者资源文件(resource file)传入进去。函数的 this 上下文将由 webpack 填充,并且 webpack 的 loader runner 具有一些有用方法,可以使 loader 改变为异步调用方式,或者获取 query 参数。
编写 Loader 时要遵循单一 z 职责,每个 Loader 只做一种"转义"工作。 每个 Loader 的拿到的是源文件内容(source),可以通过返回值的方式将处理后的内容输出,也可以调用 this.callback()方法,将内容返回给 webpack。 还可以通过 this.async()生成一个 callback 函数,再用这个 callback 将处理后的内容输出出去。 此外 webpack 还为开发者准备了开发 loader 的工具函数集——loader-utils。
- Loader 运行在 Node.js 中,我们可以调用任意 Node.js 自带的 API 或者安装第三方模块进行调用
- Webpack 传给 Loader 的源内容默认都是 UTF-8 格式编码的字符串,当某些场景下 Loader 处理二进制文件时,需要通过 exports.raw = true 告诉 Webpack 该 Loader 是否需要二进制数据
- 尽可能的异步化 Loader,如果计算量很小,同步也可以
- Loader 是无状态的,我们不应该在 Loader 中保留状态
- 使用 loader-utils 和 schema-utils 为我们提供的实用工具
- 加载本地 Loader 方法
- Npm link
- ResolveLoader
参考 / 来源:
官网
关于 webpack 的面试题
「吐血整理」再来一打 Webpack 面试题
二、是否写过 Plugin?简单描述一下编写 Plugin 的思路?
webpack 在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。
- compiler 暴露了和 Webpack 整个生命周期相关的钩子
- compilation 暴露了与模块和依赖有关的粒度更小的事件钩子
- 插件需要在其原型上绑定 apply 方法,才能访问 compiler 实例传给每个插件的 compiler 和 compilation 对象都是同一个引用,若在一个插件中修改了它们身上的属性,会影响后面的插件
- 找出合适的事件点去完成想要的功能
- emit 事件发生时,可以读取到最终输出的资源、代码块、模块及其依赖,并进行修改(emit 事件是修改 Webpack 输出资源的最后时机)
- watch-run 当依赖的文件发生变化时会触发
- 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住
官网
「吐血整理」再来一打 Webpack 面试题
三、聊一聊 Babel 原理吧
大多数 JavaScript Parser 遵循 estree 规范,Babel 最初基于 acorn 项目(轻量级现代 JavaScript 解析器)Babel 大概分为三大部分:
解析:将代码(其实就是字符串)转换成 AST
转换:访问 AST 的节点进行变换操作生产新的 AST
生成:以新的 AST 为基础生成代码
详细一点:
ES6、7 代码输? -> babylon 进?解析 -> 得到 AST (抽象语法树)->plugin ? babel-traverse 对 AST 树进?遍历转译 -> 得到新的 AST 树 -> ? babel-generator 通过 AST 树?成 ES5 代码
面试官: 聊一聊 Babel
「吐血整理」再来一打 Webpack 面试题
四、在项目中,如何优化 Webpack
优化开发体验
优化构建速度
首先说下衡量手段:speed-measure-webpack-plugin,监控构建过程中,loader 和 plugin 的执行时长
使用 高版本 的 webpack 和 node.js
缩小文件搜索范围
- 配置 loader 时,通过 include 和 exclude 缩小命中范围
- 优化 resolve.modules 配置,指明存放第三方模块的绝对路径,减少寻找时间。
- 优化 resolve.mainFields 配置: 只采用 main 字段作为入口文件描述字段 (减少搜索步骤,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段)
- 优化 resolve.alias 配置
- 优化 resolve.extensions 配置
- 优化 module.noParse 配置: 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
使用 DLLPlugin
- 使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。
- HashedModuleIdsPlugin 可以解决模块数字 id 问题
使用 HappyPack(不维护了)、thread-loader
使用 ParallelUglifyPlugin
充分利用缓存提升二次构建速度
- babel-loader 开启缓存
- terser-webpack-plugin 开启缓存
- 使用 cache-loader 或者 hard-source-webpack-plugin
优化使用体验
- 开启自动刷新
- 开启模块热替换(HMR)
- 展示构建进度:progress-bar-webpack-plugin
- 优化 webpack 原始的构建输出:webpack-dashboard
优化输出质量
减少用户能感知到的加载时机,也就是首屏加载速度
区分环境,不同环境使用的配置不相同
压缩代码:
多进程并行压缩
- webpack-paralle-uglify-plugin
- uglifyjs-webpack-plugin 开启 parallel 参数 (不支持 ES6)
- terser-webpack-plugin 开启 parallel 参数
通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。
Tree Shaking:删除没用到的代码
- 打包过程中检测工程中没有引用过的模块并进行标记,在资源压缩时将它们从最终的 bundle 中去掉(只能对 ES6 Modlue 生效) 开发中尽可能使用 ES6 Module 的模块,提高 tree shaking 效率- 禁用 babel-loader 的模块依赖解析,否则 Webpack 接收到的就都是转换过的 CommonJS 形式的模块,无法进行 tree-shaking- 使用 PurifyCSS(不在维护) 或者 uncss 去除无用 CSS 代码 - purgecss-webpack-plugin 和 mini-css-extract-plugin 配合使用(建议)复制代码
提取公共代码
基础包分离:
- 使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中
- 使用 SplitChunksPlugin 进行(公共脚本、基础包、页面公共文件)分离(Webpack4 内置) ,替代了 CommonsChunkPlugin 插件
按需加载
提升流畅度,也就是提升代码性能
prepack
Scope Hoisting
- 构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突
- 必须是 ES6 的语法,因为有很多第三方库仍采用 CommonJS 语法,为了充分发挥 Scope hoisting 的作用,需要配置 mainFields 对第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法
图片压缩: imagemin、image-webpack-loader
参考 / 来源:
官网Webpack 优化
「吐血整理」再来一打 Webpack 面试题
Webpack 优化——将你的构建效率提速翻倍
五、CDN 是什么?描述下 CDN 原理?为什么要用 CDN?
CDN 的全称是 Content Delivery Network,即 内容分发网络。
工作原理:将源码的资源缓存到位于全国各地的 CDN 节点上,用户请求资源时,就近返回节点上缓存的资源,而不需要每个用户的请求都回源站点获取,避免网络拥塞、分担源站压力,保证用户访问资源的速度和体验。
为什么要用 CDN
- 为了加速网站的访问。
- 为了实现跨运营商、跨地域的全网覆盖
- 为了保障网站安全
- 为了异地备源
- 为了节约成本投入
- 为了更专注业务本身
六、说一下正向代理和反向代理
代理是在服务器和客户端之间假设的一层服务器,代理将接收客户端的请求并将它转发给服务器,然后将服务端的响应转发给客户端。
不管是正向代理还是反向代理,实现的都是上面的功能。
正向代理(VPN)
正向代理,意思是一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并制定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。
正向代理 是为我们服务的,即为客户端服务,客户端可以根据正向代理访问到它本身无法访问到的服务器资源。
正向代理 是对我们是透明的,对服务端是非透明的,即服务端并不知道自己收到的是来自代理的访问还是来自真实客户端的访问。
反向代理(Nginx)
反向代理 方式是指以代理服务器来接受连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给客户端。
反向代理 是为服务端服务的,反向代理可以帮助服务器接收来自客户端的请求,帮助服务器做请求转发,负载均衡等。
反向代理 对服务端是透明的,对客户端是非透明的,即客户端并不知道自己访问的是代理服务器,而服务器知道反向代理在为他服务。
参考 / 来源:
公众号:code 秘密花园 - 前端开发者必备的 nginx 知识
七、说一下事件模型、事件流和事件代理
js 事件模型主要分为 3 种:原始事件模型、DOM2 事件模型、IE 事件模型
原始事件模型(DOM0 级)
这是一种被所有浏览器都支持的事件模型,对于原始事件而言,没有事件流,事件一旦发生将马上进行处理,有两种方式可以实现原始事件:
<!-- 在 html 代码中直接指定属性值 --><button onclick="doSomeTing()">test</button><script> // 在 js 代码中 document.getElementsById('demo').onclick = doSomeTine();</script>复制代码
优点:所有浏览器都兼容缺点:
- 逻辑与显示没有分离
- 相同事件的监听函数只能绑定一个,后面绑定的会覆盖掉前面的
- 无法通过事件的冒泡、委托等机制完成更多事情。
DOM2 事件模型
此模型是 W3C 指定的标准模型,现代浏览器(IE6 ~ IE8 除外)都已经遵循这个规范。 W3C 制定的事件模型中,一次事件的发生包含三个过程:
- 事件捕获阶段(从上到下)
- 事件目标阶段
- 事件冒泡阶段(从下到上)
事件捕获:当某个元素触发某个事件(如 onclick),顶层对象 document 就会发出一个事件流,随着 DOM 树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会触发的。
事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。
事件冒泡:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被依次触发。
所有的事件类型都会经历事件捕获,但是只有部分事件会经历事件冒泡阶段,例如 submit 事件就不会冒泡。
在 W3C 中,使用 stopPropagation() 方法阻止事件传播,在捕获的过程中 stopPropagation() 后,后面的冒泡过程就不会发生了。
用法:
// eventType: 事件类型,例如 click// 第二个参数,处理函数 handler// 第三个参数用来指定是否在捕获阶段进行处理,一般设为 false 或不传addEventListener('eventType', 'handler', 'true|false');复制代码
IE 事件模型
IE 不把该对象传入事件处理函数,由于在任意时刻只会存在一个事件,所以 IE 把它作为全局对象 window 的一个属性。
用法
document.getElementById('demo').attachEvent('onclick', func);复制代码
事件代理
传统的事件处理中,我们为每一个需要触发事件的元素添加事件处理器,但是这种方法将可能会导致内存泄露或者性能下降(特别是通过 ajax 获取数据后重复绑定事件,总之,越频繁风险越大)。事件代理在 js 中是一个非常有用的功能,我们将事件处理器绑定到一个父级元素上,避免了频繁的绑定多个子级元素,依靠事件冒泡机制与事件捕获机制,子元素的事件将委托给父级元素。
八、「react」React 中函数组件和普通组件有什么区别
- 类组件: 可以使用其他特性,如状态和生命周期函数
- 函数组件:也称为哑组件、展示组件、无状态组件,当组件只接收 props 渲染到页面时,就是无状态组件,称为函数组件
函数组件的性能比类组件性能高,因为类组件在使用时要实例化,而函数组件之间调用函数取返回结果即可。
函数组件没有 this、生命周期、state
九、「react」说一下 React 生命周期,以及新版本中都有哪些改变,为什么去掉了那几个旧的生命周期
初始化阶段
- constructor : class 的构造函数
挂载阶段
- componentWillMount: 组件初始化渲染前执行 16设为不安全 17弃用
- static getDerivedStateFromProps: 16 新增
- render: 组件渲染
- componentDidMount: 组件被挂载到 DOM 后触发
更新阶段
- componentWillReceiveProps: 组件将要接收新的 prop 前执行 16设为不安全 17弃用
- static getDerivedStateFromProps: 替代 componentWillReceiveProps 16 新增
- shouldComponentUpdate: 组件是否需要更新
- componentWillUpdate: 组件更新前执行 16设为不安全 17弃用
- render 组件渲染
- getSnapshotBeforeUpdate: render 后,可以读取但无法使用 DOM 的时候 16 新增
- componentDidUpdate: 组件更新后执行
组件卸载
- componentWillUnmount: 组件卸载前执行
错误处理
- componentDidCatch: 16新增
废弃的原因主要是因为 react 在 16 版本重构了调度算法,新的调度可能会导致一些生命周期被反复调用,所以在 16 中就不建议使用了,而改在其他时机中暴露出其他生命周期钩子用来替代。
十、「react」知道 Portal 吗,有哪些使用场景?
Portal(传送门),提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的方案。
常见使用场景
- 父组件 overflow: hidden, 但是子组件又想展示
- 父组件的 z-index 太小
- fixed 需要放在 body 第一层
- Modal 模态框、Popover 弹出的 Drawer 抽屉等
使用方法
render() { return ReactDOM.createProtal( <div>{this.props.children}</div>, body )}复制代码
十一、「react」 React 中 StrictMode (严格模式) 是什么?
React 的 StrictMode 是一种辅助组件,可以帮助我们编写更好的 React 组件,可以使用 <StrictMode />包装一组组件,并且可以帮我们做一下检查:
- 验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告
- 验证是否使用了已经废弃的方法,如果有,会在控制台给出警告,例如:
- 失败不安全的生命周期
- 过时的字符串 ref API 警告
- 废弃的 findDOMNode 方法警告
- 过时的 context API 警告
- 通过识别一些潜在的风险预防一些副作用
十二、「react」什么是纯函数 && 什么是纯组件
纯函数是不依赖并且不会在其作用域外修改变量状态的函数。本质上,纯函数始终在给定相同参数的情况下返回相同的结果。
纯组件是通过控制 sholdComponentUpdate 生命周期函数,减少 render 渲染次数来减少性能损耗的。
- 优点:这相对于 Component 来说,减少了手动判断 state 变化的繁琐操作。
- 缺点:以为 PureComponent 只进行了一层浅比较,简单来说,它只比较了 props 和 state 的内存地址,如果内存地址相同,则 shouldComponentUpdate 就会返回 false
- PureComponent 应用场景:局部数据发生改变的时候,比如带有 输入框、switch 开关等的 UI 组件就可以使用 PureComponent 组件封装, PureComponent 中如果有数据操作最好配合一个第三方组件——Immutable 一起使用,Immutable 可以保证数据的不变性。
PureComponent 浅比较出现的问题的解决方案:
- 当知道有深度数据结构更新时,可以直接调用 forceUpdate 强制更新
- 考虑使用 immutable objects,实现快速的比较对象。
十三、「react」请求接口放在哪个生命周期
建议放在 componentDidMount() 中。
不能放在 render() 中,因为会重复调用
不能放在 componentWillMount() 中,会与 RN 等其它框架冲突,而且该生命周期已经不安全,即将弃用。
也可以写在构造函数 constructor() 之中,但是不建议这样做
十四、「react」React 事件合成机制是什么?为什么要合成?
React 根据 W3C 规范定义了每个事件处理函数的参数,即事件合成。
- div 或其他元素触发事件,该事件会冒泡到 document,然后被 React 的事件处理程序捕获
- 事件处理程序随后将事件传递给 SyntheticEvent 的实例,这是一个跨浏览器原生事件包装器。
- SyntheticEvent 触发 dispatchEvent,将 event 对象交由对应的处理器执行。
为什么要合成事件机制?
- 更好的兼容性和跨平台,摆脱传统 DOM 事件
- react 合成的 SyntheticEvent 采用了事件池,这样可以大大节省内存,而不会频繁的创建和销毁事件对象。
- 方便事件的统一管理,如:事务机制
写在最后
前端的世界纷繁复杂,远非笔者所能勾画,部分面试题不是靠背诵记忆就能掌握的,希望我摘录的、总结的简单答案可以引起读者的兴趣,闲暇时可以自己深入总结,如果读者有更好的答案,或有想了解的题目,欢迎留言。
笔者新建了一个 github 仓库,对公众号内发布的面试题做进一步分类,同时也会在周六同步本周题目、公布下周问题,欢迎大家关注~
想要实时关注笔者最新的文章和最新的文档更新请关注公众号前端地基,后续的文章会优先在公众号更新.
github:关注笔者最新的仓库
作者:同学请留步
链接:https://juejin.im/post/6858139075807805453
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。