前端面试要点(五):工程化

162 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

前言

在面试和复习过程中总结的一些前端知识点,记录下来,风格较简洁,尽量涵盖内容要点和简单例子。本文是前端面试系列第五篇:前端工程化,持续更新...

一、前端模块化机制

  • ES6 模块化(前提是要用babel);
  • CommonJS(node常用);

1. ES6 模块化 和 CommonJS 的区别:

  • 前者不支持动态导入,后者支持;
  • 前者是异步导入,后者是同步导入;
  • 前者是编译时加载,后者运行时加载;
  • 前者是引用拷贝,导入导出的值指向同一地址,后者是值拷贝,导入导出的值不会互相影响;

2. 浏览器不适合用 CommonJS 的原因:

服务端加载一个模块,直接就从硬盘或者内存中读取了,消耗时间可以忽略不计,浏览器需要从服务端下载这个文件,等代码模块下载完毕,并运行之后才能得到所需要的API,会阻塞浏览器渲染页面。

3. require 文件加载流程:

require 加载的模块分为三类:

  1. 核心模块:如 fs、path 等,这些文件提前已编译成二进制文件,加载速度最快;
  2. 文件模块:即自己写的模块,已 ./ ,../ 或 / 开头,先找到真实的路径,编译,再将结果缓存起来
  3. 第三方模块:通过 npm 安装的模块,会在当前目录下的 node_modules 目录查找,如果没有,在父级目录的 node_modules 查找,如此递归下去

require 加载文件时先分析有没有缓存,如果有直接返回缓存结果,并不会重新执行文件,如果没有缓存,则先把文件加入缓存,在执行文件,返回结果。所以,require 的缓存机制解决了重复加载和循环引用的问题

4. exports 和 module.exports 的区别:

  • exports 和 module.exports 指向同样的引用;
  • exports 只能操作属性,如 exports.a = 1,不能直接赋值如 exports = { a: 1 },而 module.exports 的用法是直接赋值如 module.exports = { a: 1 },这是因为对 exports 直接赋值会改变原引用;
  • exports 导出的只有对象,module.exports 可以导出函数或类

5. import 语句会提升到文件的最顶部开始执行

如下代码会先执行 import 再执行 console:

console.log('main.js开始执行’);
import say from ‘./a’;

使用 import 被导入的变量是只读的,可以理解默认为 const 装饰,无法被赋值

6. import() 动态加载

  • import() 返回一个 Promise 对象, 返回的 Promise 的 then 成功回调中,可以获取模块的加载成功信息。
  • import() 动态加载一些内容,可以放在条件语句或者函数执行上下文中,而静态加载则不行。

二、webpack 工作流程

主线就是从读取配置文件中的 entry 开始,到最后输出 bundle 的过程。

Compiler 对象 包含了当前运行 webpack 的配置,包括 entry、output、loaders 等配置。

Compilation 对象 代表了一次资源版本构建,就是构建模块和 Chunk,并利用插件优化构建过程。

  1. 初始化参数:解析 webpack 配置参数,合并 shell 传入和 webpack.config.js 文件配置的参数,形成最后的配置结果;
  2. 开始编译:用上一步得到的参数初始化 compiler 对象,注册所有配置的插件,插件监听 webpack 构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译;
  3. 确定入口:从配置的 entry 入口,开始解析文件构建 AST,找出依赖,递归下去;
  4. 编译模块:递归中根据文件类型和 loader 配置,调用所有配置的 loader 对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  5. 完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据 entry 或分包配置生成代码块 chunk;
  6. 输出完成:生成 bundle 文件;

三、webpack loader

loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中。

常见的 loader

  • source-map-loader:加载额外的 Source Map ⽂件,以⽅便断点调试
  • image-loader:加载并且压缩图⽚⽂件
  • babel-loader:把 ES6 转换成 ES5
  • css-loader:加载 CSS,⽀持模块化、压缩、⽂件导⼊等特性
  • eslint-loader:通过 ESLint 检查 JavaScript 代码

四、webpack plugin

plugin 功能更强大,loader 不能做的都是它做。webpack 在编译过程中留下一系列生命周期的钩子,plugin 通过调用这些钩子来实现在不同编译结果时对源模块进行处理。

插件的工作流程

  1. 读取配置的过程中会先 new 一个插件的实例;
  2. 初始化 compiler 对象后调用插件的 apply 方法给插件实例传入 compiler 对象;
  3. 插件实例在获取到 compiler 对象后,就可以通过 compiler.plugin(事件名称, 回调函数) 监听到 webpack 广播出来的事件,并且可以通过 compiler 对象去操作 webpack。

五、tree shaking

  • 在 webpack 项目中,有一个入口文件,入口文件有很多依赖的模块,实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能,通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。
  • tree-shaking 的消除原理依赖于 ES6 的模块特性。ES6 模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析。
  • 不足:比如一个类被 export 了,虽然没有地方引用它,但是在它的当前文件有个实例化的对象,这时是不能把这个类摇掉的。

六、uglify

uglify 是一种代码压缩解决方案,去除一些不必要的字符,如空白字符、换行符、注释等。

七、babel

它是一个 JS 编译器,能帮我们在旧的浏览器环境中将 ES6+ 代码转换成向后兼容版本的 JS 代码。

plugins 可以设定我们想要转换功能的一些插件,如转换箭头函数插件 @babel/plugin-transform-arrow-functions

presets 相当于一组插件的集合,作为预设,如 @babel/preset-env 包括支持 ES6+ 的所有插件

babel 转化的过程

  1. parse:第一步使用 babelon 将原始代码转换为抽象语法树
  2. transform:第二步通过 babel-traverse 对前面的抽象语法树进行遍历修改并获得新的抽象语法树
  3. generator:第三步使用 babel-generator 将抽象语法树转换为代码

注:这三个操作通过 babel-core 合成一个对外的 api 供外界使用

八、前端微服务

  • 微服务本是后端的概念,就是把后端的服务拆分成几个服务,各自负责各自的功能,分而治之,互不影响。
  • 前端借鉴后端微服务的概念,也做拆分实现逻辑解耦,成为前端微服务,也叫微前端。
  • 微前端的好处:各个服务分开打包、分开上线,提高开发效率,降低相互之间的影响。
  • 微前端的应用场景:按照菜单来划分子应用,即路由分发应用。
  • 微前端的流程:加载主应用 -> 监听路由的变化 -> 匹配子应用路由 -> 加载子应用。
  • 沙箱:可以理解为作用域,即在一个沙箱内的任何操作不会影响到外部,微前端的各个子应用也需要沙箱隔离机制。
  • 关键内容:注册子应用,异步加载子应用,预加载(记录用户习惯,减少应用的切换时间),子应用之间通信、依赖共享(减少重复依赖)