前端面试系列之工程化篇

如果对前端八股文感兴趣,可以留意公重号:码农补给站,总有你要的干货。

前端工程化

Webpack

  • 概念

    • 本质上,webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
  • 有哪些常见的Loader?你用过哪些Loader?

    • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
    • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
    • postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀
    • eslint-loader:通过 ESLint 检查 JavaScript 代码
    • tslint-loader:通过 TSLint检查 TypeScript 代码
    • vue-loader:加载 Vue.js 单文件组件
    • ts-loader: 将 TypeScript 转换成 JavaScript
    • babel-loader:把 ES6 转换成 ES5
    • ...
  • 有哪些常见的Plugin?你用过哪些Plugin?

    • html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
    • define-plugin:定义环境变量 (Webpack4 之后指定 mode 会自动配置)
    • uglifyjs-webpack-plugin:不支持 ES6 压缩 (Webpack4 以前)
    • webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
    • mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)
    • speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
    • webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)
    • happy-pack plugin
    • ...
  • Webpack构建流程简单说一下

    • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数

    • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译

    • 确定入口:根据配置中的 entry 找出所有的入口文件

    • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理

    • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系

    • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会

    • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

    • 总结版

      • 始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
      • 编译:从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理
      • 输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中
  • loader 与 plugin

    • Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

      • Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
    • Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

      • Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。
  • 热更新原理

    • Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
    • HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),【实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。】
    • 后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。
  • Source Map 使用以及原理

    • source map 是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map。

    • 线上环境一般有三种处理方案:

      • hidden-source-map:借助第三方错误监控平台 Sentry 使用
      • nosources-source-map:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高
      • sourcemap:通过 nginx 设置将 .map 文件只对白名单开放(公司内网)
      • 注意:避免在生产中使用 inline- 和 eval-,因为它们会增加 bundle 体积大小,并降低整体性能。
  • 模块打包原理知道吗?

    • Webpack 实际上为每个模块创造了一个可以导出和导入的环境,本质上并没有修改 代码的执行逻辑,代码执行顺序与模块加载顺序也完全一致。
  • 文件指纹是什么?怎么用?

    • 文件指纹是打包后输出的文件名的后缀。

    • Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改

    • Chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash

    • Contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变

    • 实例

      • 设置 output 的 filename,用 chunkhash。
      • 设置 MiniCssExtractPlugin 的 filename,使用 contenthash。
      • 设置file-loader的name,使用hash。
  • 如何保证各个loader按照预想方式工作?

    • 可以使用 enforce 强制执行 loader 的作用顺序,pre 代表在所有正常 loader 之前执行,post 是所有 loader 之后执行。(inline 官方不推荐使用)
  • 是否写过Loader?简单描述一下编写loader的思路?

    • Loader 支持链式调用,所以开发上需要严格遵循"单一职责",每个 Loader 只负责自己需要负责的事情。

    • 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
  • 简单描述一下编写Plugin的思路?

    • webpack在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在特定的阶段钩入想要添加的自定义功能。Webpack 的 Tapable 事件流机制保证了插件的有序性,使得整个系统扩展性良好。

      • compiler 暴露了和 Webpack 整个生命周期相关的钩子

      • compilation 暴露了与模块和依赖有关的粒度更小的事件钩子

      • 插件需要在其原型上绑定apply方法,才能访问 compiler 实例

      • 传给每个插件的 compiler 和 compilation对象都是同一个引用,若在一个插件中修改了它们身上的属性,会影响后面的插件

      • 找出合适的事件点去完成想要的功能

        • emit 事件发生时,可以读取到最终输出的资源、代码块、模块及其依赖,并进行修改(emit 事件是修改 Webpack 输出资源的最后时机)
        • watch-run 当依赖的文件发生变化时会触发
      • 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住

ESLint

  • 概念

    • ESLint是一个用来识别 ECMAScript 并且按照规则给出报告的代码检测工具,使用它可以避免低级错误和统一代码的风格。如果每次在代码提交之前都进行一次eslint代码检查,就不会因为某个字段未定义为undefined或null这样的错误而导致服务崩溃,可以有效的控制项目代码的质量。
  • 原理

    • ESLint 使用 Espree 解析 JavaScript。
    • ESLint 使用 AST 去分析代码中的模式。
    • ESLint 是完全插件化的。每一个规则都是一个插件并且你可以在运行时添加更多的规则。

Babel

  • 概念

    • Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
  • 原理

    • 大多数JavaScript Parser遵循 estree 规范,Babel 最初基于 acorn 项目(轻量级现代 JavaScript 解析器) Babel大概分为三大部分:

      • 解析:将代码转换成 AST
      • 词法分析:将代码(字符串)分割为token流,即语法单元成的数组语法分析:分析token流(上面生成的数组)并生成 AST
    • 转换:访问 AST 的节点进行变换操作生产新的 AST

      • Taro就是利用 babel 完成的小程序语法转换
    • 生成:以新的 AST 为基础生成代码

TypeScript

  • 概念
  • 原理

单元测试 Jest

  • 有没有使用过?

包管理器

  • npm run start 的整个过程?

    • 运行 npm run xxx的时候,npm 会先在当前目录的 node_modules/.bin 查找要执行的程序,如果找到则运行;
    • 没有找到则从全局的 node_modules/.bin 中查找,npm i -g xxx就是安装到到全局目录;
    • 如果全局目录还是没找到,那么就从 path 环境变量中查找有没有其他同名的可执行程序。
  • npm/yarn/pnpm

    • pnpm 通过巧妙硬链接 + 软链接结合的方式完全实现了依赖树结构的 node_modules,并且严格遵循了 Node.js 的模块解析标准,解决了幻影依赖和 npm 分身的问题。并且通过全局只保存一份在 ~/.pnpm-store 的方式,在不同的项目中进行 install 的速度也会变得更快,也解决了磁盘空间占用的问题
  • npm install 的执行过程

    • npm 模块安装机制

      • 发出npm install命令
      • 查询 node_modules 目录之中是否已经存在指定模块
      • 若存在,不再重新安装
      • 若不存在
      • npm 向 registry 查询模块压缩包的网址
      • 下载压缩包,存放在根目录下的.npm目录里
      • 解压压缩包到当前项目的 node_modules 目录
    • npm 实现原理

Git

  • rebase 和 merge 的区别?

    • merge

      • 通过merge合并分支会新增一个merge commit,然后将两个分支的历史联系起来
      • 其实是一种非破坏性的操作,对现有分支不会以任何方式被更改,但是会导致历史记录相对复杂
    • rebase

      • rebase会将整个分支移动到另一个分支上,有效地整合了所有分支上的提交
      • 主要的好处是历史记录更加清晰,是在原有提交的基础上将差异内容反映进去,消除了 git merge所需的不必要的合并提交

CSS 工程化

  • Sass、Less 是什么?为什么要使用他们?

    • 它们都是 CSS 预处理器,是 CSS 上的一种抽象层。它们是一种特殊的语法/语言,最终编译成 CSS。
    • 例如 Less 是一种动态样式语言,将 CSS 赋予了动态语言的特性,如变量,继承,运算, 函数。
    • 为什么要使用它们?
    • 结构清晰,便于扩展。 可以方便地屏蔽浏览器私有语法差异。封装对浏览器语法差异的重复处理, 减少无意义的机械劳动。
    • 可以轻松实现多重继承。 完全兼容 CSS 代码,可以方便地应用到老项目中。LESS 只是在 CSS 语法上做了扩展,所以老的 CSS 代码也可以与 LESS 代码一同编译。
  • CSS预处理器/后处理器是什么?为什么要使用它们?

    • 预处理器, 如:less,sass,stylus,用来预编译sass或者less,增加了css代码的复用性。层级,mixin, 变量,循环, 函数等对编写以及开发UI组件都极为方便。

    • 后处理器, 如: postCss,通常是在完成的样式表中根据css规范处理css,让其更加有效。目前最常做的是给css属性添加浏览器私有前缀,实现跨浏览器兼容性的问题。

    • css预处理器为css增加一些编程特性,无需考虑浏览器的兼容问题,可以在CSS中使用变量,简单的逻辑程序,函数等在编程语言中的一些基本的性能,可以让css更加的简洁,增加适应性以及可读性,可维护性等。

    • 使用原因:

      • 结构清晰, 便于扩展
      • 可以很方便的屏蔽浏览器私有语法的差异
      • 可以轻松实现多重继承
      • 完美的兼容了CSS代码,可以应用到老项目中
  • 对 CSS 工程化的理解

    • 解决的问题

      • 宏观设计:CSS 代码如何组织、如何拆分、模块结构怎样设计?
      • 编码优化:怎样写出更好的 CSS?
      • 构建:如何处理我的 CSS,才能让它的打包结果最优?
      • 可维护性:代码写完了,如何最小化它后续的变更成本?如何确保任何一个同事都能轻松接手?
    • 三个方向

      • 预处理器:Less、 Sass 等;
      • 重要的工程化插件: PostCss;
      • Webpack loader 等 。
相关推荐
吃杠碰小鸡6 分钟前
commitlint校验git提交信息
前端
虾球xz37 分钟前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇42 分钟前
HTML常用表格与标签
前端·html
疯狂的沙粒1 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员1 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐1 小时前
前端图像处理(一)
前端
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒1 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪1 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背1 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript