本文已参与「新人创作礼」活动,一起开启掘金创作之路
前言
在面试和复习过程中总结的一些前端知识点,记录下来,风格较简洁,尽量涵盖内容要点和简单例子。本文是前端面试系列第五篇:前端工程化,持续更新...
一、前端模块化机制
- ES6 模块化(前提是要用babel);
- CommonJS(node常用);
1. ES6 模块化 和 CommonJS 的区别:
- 前者不支持动态导入,后者支持;
- 前者是异步导入,后者是同步导入;
- 前者是编译时加载,后者运行时加载;
- 前者是引用拷贝,导入导出的值指向同一地址,后者是值拷贝,导入导出的值不会互相影响;
2. 浏览器不适合用 CommonJS 的原因:
服务端加载一个模块,直接就从硬盘或者内存中读取了,消耗时间可以忽略不计,浏览器需要从服务端下载这个文件,等代码模块下载完毕,并运行之后才能得到所需要的API,会阻塞浏览器渲染页面。
3. require 文件加载流程:
require 加载的模块分为三类:
- 核心模块:如 fs、path 等,这些文件提前已编译成二进制文件,加载速度最快;
- 文件模块:即自己写的模块,已 ./ ,../ 或 / 开头,先找到真实的路径,编译,再将结果缓存起来
- 第三方模块:通过 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,并利用插件优化构建过程。
- 初始化参数:解析 webpack 配置参数,合并 shell 传入和 webpack.config.js 文件配置的参数,形成最后的配置结果;
- 开始编译:用上一步得到的参数初始化 compiler 对象,注册所有配置的插件,插件监听 webpack 构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译;
- 确定入口:从配置的 entry 入口,开始解析文件构建 AST,找出依赖,递归下去;
- 编译模块:递归中根据文件类型和 loader 配置,调用所有配置的 loader 对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
- 完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据 entry 或分包配置生成代码块 chunk;
- 输出完成:生成 bundle 文件;
三、webpack loader
loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中。
常见的 loader:
source-map-loader:加载额外的 Source Map ⽂件,以⽅便断点调试image-loader:加载并且压缩图⽚⽂件babel-loader:把 ES6 转换成 ES5css-loader:加载 CSS,⽀持模块化、压缩、⽂件导⼊等特性eslint-loader:通过 ESLint 检查 JavaScript 代码
四、webpack plugin
plugin 功能更强大,loader 不能做的都是它做。webpack 在编译过程中留下一系列生命周期的钩子,plugin 通过调用这些钩子来实现在不同编译结果时对源模块进行处理。
插件的工作流程:
- 读取配置的过程中会先 new 一个插件的实例;
- 初始化 compiler 对象后调用插件的 apply 方法给插件实例传入 compiler 对象;
- 插件实例在获取到 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 转化的过程:
- parse:第一步使用
babelon将原始代码转换为抽象语法树 - transform:第二步通过
babel-traverse对前面的抽象语法树进行遍历修改并获得新的抽象语法树 - generator:第三步使用
babel-generator将抽象语法树转换为代码
注:这三个操作通过
babel-core合成一个对外的 api 供外界使用
八、前端微服务
- 微服务本是后端的概念,就是把后端的服务拆分成几个服务,各自负责各自的功能,分而治之,互不影响。
- 前端借鉴后端微服务的概念,也做拆分实现逻辑解耦,成为前端微服务,也叫微前端。
- 微前端的好处:各个服务分开打包、分开上线,提高开发效率,降低相互之间的影响。
- 微前端的应用场景:按照菜单来划分子应用,即路由分发应用。
- 微前端的流程:加载主应用 -> 监听路由的变化 -> 匹配子应用路由 -> 加载子应用。
- 沙箱:可以理解为作用域,即在一个沙箱内的任何操作不会影响到外部,微前端的各个子应用也需要沙箱隔离机制。
- 关键内容:注册子应用,异步加载子应用,预加载(记录用户习惯,减少应用的切换时间),子应用之间通信、依赖共享(减少重复依赖)