前言:
2020年是多灾多难的一年,疫情持续至今,到目前,全世界的经济都受到不同程序的影响,各大公司裁员,在这样一片严峻的形式下,找工作更是难上加难。
企业的门槛提高,第一,对于学历的要求,必须学信网可查的统招本科;第二,对于技术的掌握程序,更多的是底层原理,项目经验,等等。
下面是面试几周以来,总结的一些面试中常被问到的题目,还有吸取的一些前辈们分享的贴子,全部系统的罗列出来,希望能够帮到正在面试的人。
Webpack
1. 简介
- 本质上,
webpack
是一个现代JavaScript
应用程序的静态模块打包器(module bundler
)。当webpack
处理应用程序时,它会递归地构建一个依赖关系图(dependency graph
),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle
.
2. 核心概念
入口(
entry
)
1.指示webpack
应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack
会找出有哪些模块和库是入口起点(直接和间接)依赖的。
2.可以通过在webpack
配置中配置entry
属性,来指定一个入口起点(或多个入口起点)。默认值为./src
。输出(
output
)
output
属性告诉webpack
在哪里输出它所创建的bundles
,以及如何命名这些文件,默认值为./dist
。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。你可以通过在配置中指定一个output
字段,来配置这些处理过程加载(
loader
)loader
让webpack
能够去处理那些非JavaScript
文件(webpack
自身只理解JavaScript
)。loader
可以将所有类型的文件转换为webpack
能够处理的有效模块在更高层面,在
webpack
的配置中loader
有两个目标:
1.test
属性,用于标识出应该被对应的loader
进行转换的某个或某些文件。
2.use
属性,表示进行转换时,应该使用哪个loader
。
注意:Webpack选择了compose方式,即从右到左执行loader
插件(
plugins
)
1.插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。
2.plugins
需要暴露出一个class
, 在new WebpackPlugin()
的时候通过构造函数传入这个插件需要的参数,在webpack
启动的时候会先实例化plugin
再调用plugin.apply()
方法,插件需要在apply
函数里监听webpack
生命周期里的事件,做相应的处理模式(
mode
)
通过选择development
或production
之中的一个,来设置mode
参数,你可以启用相应模式下的webpack
内置的优化
// 多个入口
module.exports = {
mode: 'production',
entry: {
index: ["./src/index.js"],
main: ["./src/main.js"]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[hash:8].js'
},
module: {
rules: [{
test: /\.js$/, // 正则匹配文件名
exclude: '/node_modules/', // 排除
use: ['babel-loader']
}
},
plugins: [ // 插件
new copyWebpackPlugin([{
from: path.resolve(__dirname, 'public/static'),
to: path.resolve(__dirname, 'dist'),
ignore: ['index.html']
}])
}
3. 基本流程
- 解析
shell
和config
中的配置项,用于激活webpack
的加载项和插件 -
webpack
初始化工作,包括构建compiler
对象,初始化compiler
的上下文,loader
和file
的输入输出环境 - 解析入口
js
文件,通过对应的工厂方法创建模块,使用acron
生成AST树并且遍历AST,处理require
的module
,如果依赖中包含依赖则遍历build module
,在遍历过程中会根据文件类型和loader
配置找出合适的loader
用来对文件进行转换 - 调用
seal
方法,封装,逐次对每一个module
,chunk
进行整理,生成编辑后的代码
4. 模块打包
- 通过
fs
将模块读取成字符串,然后用warp
包裹一下,使之成为一个字符串形式的的函数然后调用vm.runInNewContext
这样类型的方法,这个字符串会变成一个函数。 - 这些模块的函数会被存放在数组里,然后进行解析执行。
module
和export
都是传入的对象,webpack
会实现require
函数,去加载其他模块。 - 如果是异步模块,则会通过
jsonp
的形式去加载该模块打包好生成的chunk
。异步加载模块可以使用import
和require.ensure
函数,函数将会返回一个promise
。 - 上面方法都是公共的,可以抽离成模板的js文件,
webpack
负责做依赖分析,并将模块读成函数填充入数组。(这里说的只是js的模块)
var moduleDepList = [
{'./moduleA': 1}, // module[0] 的依赖 他依赖moduleA 且 moduleA的下标在moduleList 中 为 1
{}
]
function require(id, parentId) {
var currentModlueId = parentId !== undefined ? moduleDepList[parentId][id] : id
var module = {exports: {}}
var moduleFunc = moduleList[currentModlueId]
moduleFunc(id => require(id, currentModlueId), module, module.exports)
return module.exports
}
var cache = {}
window.__jsonp = function(chunkId, moduleFunc) {
var chunk = cache[chunkId]
var resolve = chunk[0]
var module = {exports: {}}
moduleFunc(require, module, module.exports)
resolve(module.exports)
}
require.ensure = function(chunkId, parentId) {
var currentModlueId = parentId !== undefined ? moduleDepList[parentId][chunkId] : chunkId
var currentChunk = cache[currentModlueId]
if (currentChunk === undefined) {
var $script = document.createElement('script')
$script.src = `chunk.${chunkId}.js`
document.body.appendChild($script)
var promise = new Promise(function(resolve) {
var chunkCache = [resolve] // 数组形式是为了保存promise
chunkCache.status = true // 异步模块加载中 如果有别的包 在 异步加载在模块 那么下面的
cache[chunkId] = chunkCache
})
cache[chunkId].push(promise)
return promise
}
if (currentChunk.status) {
return currentChunk[1] // 这里的promise 这里的就直接返回promise 这样模块只会加载一次
}
return currentChunk
}
5. 热更新
client
和server
建立一个websocket
通信当有文件发生变动(如
fs.watchFile
)的时候,webpack
编译文件,并通过websocket
向client
发送一条更新消息client
根据收到的hash
值,通过ajax
获取一个manifest
描述文件client
根据manifest
获取新的JS
模块的代码当取到新的
JS
代码之后,会更新modules tree
,(installedModules
)调用之前通过module.hot.accept
注册好的回调,可能是loader
提供的,也可能是你自己写的manifest
: 描述资源文件对应关系如下,打包后的文件拥有了hash
值,所以需要进行映射。
{
"a.js": "a.41231243.js"
}
6. 如何开发一个plugin
- 一个 JavaScript 命名函数。
- 在插件函数的 prototype 上定义一个 apply 方法。
- 指定一个绑定到 webpack 自身的事件钩子。
- 处理 webpack 内部实例的特定数据。
- 功能完成后调用 webpack 提供的回调。
tapable 工具,它提供了 webpack 插件接口的支柱
// 一个 JavaScript 命名函数。
function plugin() {};
// 在插件函数的 prototype 上定义一个 `apply` 方法。
plugin.prototype.apply = function(compiler) {
// 指定一个挂载到 webpack 自身的事件钩子。
compiler.plugin('webpacksEventHook', function(compilation, callback) {
callback();
});
// 使用taptable的写法
//基本写法
compiler.hooks.someHook.tap(...)
//如果希望在entry配置完毕后执行某个功能
compiler.hooks.entryOption.tap(...)
//如果希望在生成的资源输出到output指定目录之前执行某个功能
compiler.hooks.emit.tap(...)
};
7. Compiler和Compliation 对象和钩子
对象
1.compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。
2.compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用钩子:总体分成两大类:Compiler和Compliation
1.Compiler暴露了和webpack整个生命周期相关的钩子
2.Compilation暴露了与模块和依赖有关的粒度更小的事件钩子,官方文档中的说法是模块会经历加载(loaded),封存(sealed),优化(optimized),分块(chunked),哈希(hashed)和重新创建(restored)这几个典型步骤,从上面的示例可以看到,compilation是Compiler生命周期中的一个步骤,使用compilation相关钩子的通用写法为:
compiler.hooks.compilation.tap('SomePlugin',function(compilation, callback){
compilation.hooks.someOtherHook.tap('SomeOtherPlugin',function(){
....
})
});
- 钩子的类型
1.同步钩子
(1)syncHook: 不关心返回值
(2)syncBailHook: 有一个返回值不为null就跳过剩下的逻辑
(3)SyncWaterfallHook: 下一个任务要拿到上一个任务的返回值
(4)SyncLoopHook: 监听函数返回true表示继续循环,返回undefine表示结束循环
2.异步钩子
(1)AsyncParallelHook: 异步并发执行,仍是单线程
(2)AsyncParallelBailHook: 异步并发执行,有一个失败了,其他的都不用走了
(3)AsyncSeriesHook: 异步串行执行
(4)AsyncSeriesBailHook: 异步串行执行,有一个返回值不为null就跳过剩下的逻辑
(5)AsyncSeriesWaterfallHook: 异步串行执行,下一个任务要拿到上一个任务的返回值
8. 常见plugin
clean-webpack-plugin
: 在构建之前删除上一次build的文件夹copy-webpack-plugin
: 复制文件或文件夹到生成后的目录extract-text-webpack | mini-css-extract-plugin
: 将所有入口的chunk(entry chunks)
中引用的*.css
,移动到独立分离的 CSS 文件html-webpack-plugin
: 将build后生成的资源以标签的形式嵌入到HTML模板内hot-module-replacement
: 模块热更新
9. 常见loader
babel-loader: 语法,源码转换以便能够运行在当前和旧版本的浏览器或其他环境中
css-loader: 配合style-loader可以解析在js中引入的css文件,并以