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文件,并以<style>便签将css-loader内部样式注入到我们的HTML页面
file-loader: 可以解析js中require的文件,输出到输出目录并返回 public URL
html-loader: 可以对HTML模板中指定哪个标签属性组合(tag-attribute combination)元素应该被此 loader 处理
less-loader: 依赖less,可以将less编译成css
postcss-loader: 配合一些plugin如cssnano,autoprefixer可以对css进行压缩,优化,自动补足前缀等
scss-loader: 配合node-scss,可以将scss编译成css
style-loader: 配合css-loader可以解析在js中引入的css文件,并以<style>便签将css-loader内部样式注入到我们的HTML页面
url-loader: url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL(base64)
10. 常见打包优化
使用dll
移除prefetch, preload,关闭sourceMap
webpack-bundle-analyzer打包分析,将大的模块可能的移至CDN。打包时间分析使用speed-measure-webpack-plugin
开启gzip,服务器需要支持
使用多线程:thread-loader或HappyPack
webpack4内置的terser启动多线程压缩
对项目进行拆分
11. 性能优化
-
webapck
优化与开启gzip
压缩
1.babel-loader
用 include
或 exclude
来帮我们避免不必要的转译,不转译node_moudules
中的js文件,其次在缓存当前转译的js文件,设置loader: 'babel-loader?cacheDirectory=true'
2.文件采用按需加载等等
3.具体的做法非常简单,只需要你在你的 request headers
中加上这么一句:
accept-encoding:gzip
,该功能需要服务器支持才能正常显示页面。
4.图片优化,采用svg
图片或者字体图标
5.浏览器缓存机制,它又分为强缓存和协商缓存
- 本地存储——从
Cookie
到Web Storage
、IndexedDB
说明一下SessionStorage
和localStorage
还有cookie
的区别和优缺点
- 代码优化
1.事件代理
2.事件的节流和防抖
3.页面的回流和重绘
4.EventLoop事件循环机制
5.代码优化等等
概念
1. MVVM
View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
2. 组件化思想
简单的说组件就是:将一段UI样式和其对应的功能作为独立的整体去看待,无论这个整体放在哪里去使用,它都具有一样的功能和样式,从而实现复用,这种整体化的思想就是组件化。
组件化设计就是为了增加复用性,灵活性,提高系统设计,从而提高开发效率。
3. 虚拟DOM
使用Javascript来操纵DOM,操作效率往往很低,由于DOM被表示为树结构,每次DOM中的某些内容都会发生变化,因此对DOM的更改非常快,但更改后的元素,并且它的子项必须经过Reflow / Layout阶段,然后浏览器必须重新绘制更改,这很慢的。
因此,回流/重绘的次数越多,您的应用程序就越卡顿。但是,Javascript运行速度很快,虚拟DOM是放在JS 和 HTML中间的一个层。它可以通过新旧DOM的对比,来获取对比之后的差异对象,然后有针对性的把差异部分真正地渲染到页面上,从而减少实际DOM操作,最终达到性能优化的目的。
4. SPA 和 多页面应用
单页面应用: 仅仅在web页面初始化时加载相应的HTML、JavaScript、CSS,一旦页面加载完成了,SPA不会因为用户的操作而进行页面的重新加载或跳转,而是利用 JavaScript 动态的变换HTML的内容,从而实现UI与用户的交互。
多页面应用: 多页面跳转刷新所有资源,每个公共资源(js、css等)需选择性重新加载,常用于 app 或 客户端
5. CDN
CDN的全称是Content Delivery Network,即内容分发网络。基本原理是在用户和服务器之间增加Cache层,主要是通过接管DNS实现,将用户的请求引导到Cache上获得源服务器的数据,从而降低网络的访问时间。CDN的关键技术主要有负载均衡,内容存储和分发技术。
负载均衡:使用整体性的网络负载均衡技术,通过内容路由器中的重定向(DNS)机制,在多个远程POP上均衡用户的请求,以使用户请求得到最近内容源的响应。
内容分发:借助于建立索引、缓存、流分裂、组播(Multicast)等技术,将内容发布或投递到距离用户最近的远程服务点(POP)处。
内容存储:在功能上包括对各种内容格式的支持,对部分缓存的支持;在性能上包括支持的容量、多文件吞吐率、可靠性、稳定性,都是存储需要考虑的问题。
6. 函数式编程
函数式编程是种编程方式,它将电脑运算视为函数的计算。在函数编程中,函数是第一等公民,且该函数应该是一个纯函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。
列如含有:
1.log
2.http请求
3.可变数据如new Date()
4.DOM操作
- 纯函数带来的好处就是:更好的进行单元测试和调试,一对一的数据关系可以便于缓存。函数式编程还有其他特性:
1.闭包和高阶函数
2.惰性计算
3.递归
- 函数式编程有两个最基本的运算:合成compose和柯里化curry。
结束语
2020前端面试就分享到这里,前端就是一个大杂烩,乱炖,需要会的、了解的东西太多了,学无止境,如果发现问题,欢迎评论区指正。