webpack大杂烩,自品一枚

547 阅读15分钟

webpack

最近针对webpack进行了一些系统的学习,将一些学习的基础知识点做成脑图,方便自己记忆,也同时给大家分享一下自己的学习过程,有遗漏补充的地方请各方大牛补充一二,其中包含了各地的砖头,🙏,阿弥陀佛,勿怪勿怪!!!!!!!!!

webpack之热更新原理

启动服务阶段(webpack-dev-server/lib/Server.js)

  1. 运行npm run bin初始化一个webpack的实例,启动webpack的编译
  2. addEnties函数,处理生成一个新的webpackentry,便于生成websocket客户端代码:node_modules/webpack-dev-server/client/index.js以及热更新代码:webpack/hot/dev-server.js
  3. 完成后可以让这两块代码能够被打包到本地bundlejs,植入到浏览器中去,最后在浏览器运行的时候,可以找到
  4. 接下来是监听webpack阶段(下面统一总结)
  5. 调用express生成一个静态文件能够使用的服务器,便于用ip来访问文件
  6. 创建server之后,紧接着会通过ws或者socketjs创建一个websocket长链接

监听webpack阶段

  1. 调用setUpHooks函数,对webpack编译完成阶段进行监听:done.tap('webpack-dev-server',function(stats){...})

  2. 等监听到webpack编译完成之后就调用回调:this._sendStats(this.sockets,this.getStats(stats))

  3. _sendStats通过websocket,写入hashok两个指令,向websocket client发送,hash向浏览器端发送最新的 hash(这是一个很重要的东西),ok指令通知客户端webpack编译完成了。

  4. 接下来通过setupDevMiddleware方法监听文件的变化,,在这里是利用webpack-dev-middleware这个库来进行文件的操作(编译,输出以及监听),以便让webpack-dev-server功能分离出来,专心做启动服务和准备工作

  5. webpack-dev-middle调用compiler.watch方法对本地文件的变化进行监听(主要通过文件的改变时间,这就是为什么文件发生改变,就能继续启动webpack的编译),同时将编译打包完的代码通过memory-fs库的setFs(context,compiler)写入内存中,这就是为什么dist目录找不到热更新的代码的原因

浏览器收到热更新通知的阶段

  1. 利用webpack-dev-server/client/index.js内的websocket 服务器可以收到hashok指令
  2. 将最新的hash值保存在currentHash中,ok方法在内部调用了reloadApp(options,status)
  3. reloadApp利用webpack/hot/emitteremit('webpackHotUpdate')去触发webpackHotUpdate
  4. 这个webpackHotUpdate函数的注册在那里呢?答案就是node_modules/webpack/hot/dev-server.js'一开始和webpack client函数一起写进浏览器的。在一步中,webpackHotUpdate回调函数currentHash存放在了lastHash中,同时调用了check(),这个check方法由module.hot.check提供,这个module.hot.check是那里出来的呢?答案就是:hotModuleReplacementPlugin

HotModuleReplacement检查代码更新阶段

执行了module.hot.check

  1. 利用之前得到的最新的hash,调用hotDownloadManifest请求hash.hot-update.json,获取到要更新的代码的信息,并进入到热更新准备
  2. 然后调用hotDownloadUpdateChunk发送hash.hot-update.js,通过JSONP方式,获取到最新的代码,然后根据JSONP的特性,直接执行了最新的代码块的代码中的webpackHotUpdate方法

代码替换阶段(window['webpackHotUpdate'])

  1. 将要更新的模块moreModules保存到全局对象hotUpdate,然后调用 **hotApply**开始进行模块的替换
  2. 删除过期模块,并将模块id存储下来,以防止更新失败,可以进行回退
  3. 将新的模块添加到modules里面
  4. 要知道webpack打包后要执行代码都是通过bundle里面的__webpack__require__执行,在hotApply的最后,调用__webpack__require__,将moduleId传入,执行 modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));

结束阶段

  1. 然后执行了module.hot.check的回调,为了容错执行window.location.reload(),这时候结束了整个过程.打印出[HMR] App is up to date

以下地址包含了具体源代码的分析(砖🧱)

大型同性交友网站:github

webpack运行流程原理

[学习中。。。]

plugin原理

pluginwebpack中非常重要和功能,plugin提供了loaders无法做到的事,包括环境变量的增加,代码压缩和打包的优化等等。

一个plugin应该至少具有以下内容

  1. 一个JS的类或者一个函数
  2. 在原型上有一个apply的函数,负责在webpack在编译的时候,执行该函数
  3. apply需要有一个compiler的参数,便于plugin能获取到编译过程中的一些资源
  4. 使用compiler提供的hook,注册一个回调函数(想要知道具体可以看文章内的关于Tapable的介绍)
  5. 必须调用webpack回调函数提供的 callback,即回调的第二个参数,必须在回调代码执行的最后调用,否则下一个plugin无法继续进行
  6. 绑定的回调函数除了有callback这个参数,还需要一个非常重要的compilation,负责当前的模块资源,编译生成资源,变化的文件,以及跟踪以来的状态信息

总结 继承自tapable的两个实例:compilercompilationwebpack为了区分两者的工作范围,compiler主要负责的是源码编译,而compilation负责文件输出

自定义实现一个简单的plugin

当然plugin没有loader那么方便,可以直接脱离webpack执行(loader可以使用loader-runner),所以要创建一个plugin需要依赖webpackplugins数组

// webpack.dev.js
const simpleWebpackPlugin = require('../plugins/simple-webpack-plugin.js')
plugins: [
    new simpleWebpackPlugin({name: '新建Plugin'})
  ],

module.exports = class SimpleWebpackPlugin{
    constructor(options) {
        this.options = options;
    }
    apply(compiler) {
        console.log(this.options)
        const options = this.options
        compiler.hooks.emit.tapAsync('ImgPlugin', (compilation, callback) => {
            compilation.assets['readme.txt'] = {
                source:function(){
                    return options.name
                },
                size:function(){
                    return 6
                }
            }
            callback()
        })
    }
}

在这里我们最后使用complation生成文件的过程,最后可以在打包的dist文件中生成一个readme.text文件,并被写入:新建Plugin

loaders的学习

loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,比如编译,压缩等等,并最终打包到指定的文件中,处理一个文件能够使用多个loader,loader的执行顺序和配置中的顺序是相反的(从右往左执行

原因:是因为webpack采用的是compose,类似于a(b)的时候,它会从从右到左的:b->a的执行 也就是说loader的执行接收前一个执行的loader的返回值作为参数,最后执行的loader会 返回此模块的此模块的javascript源码

  1. loader就是一个简单的函数
  2. 一个loader的职责是单一的,只能进行一种转化,如果一个源文件需要多个转化才能正常使用,那就要借助多个loader,如sass和less文件的执行

loader-utils

官方提供的可以通过loaderUtils.getOptions(this)来获取webpack的配置参数,然后进行自己的处理

loader的异常处理和结果的回调处理

  1. 可以直接通过throw来抛出错误
  2. 通过this.callback传递错误(同步处理方式)
this.callback({error,content,sourceMap,meta})
// error可以为null,表示没有错误
  1. this.async来处理异步loader

loader缓存的开启

webpack默认开启了loader的缓存 可以使用this.cachable(false)关掉缓存

pitch钩子

loader文件中,pitch钩子的注册,可以让这个函数在所有的loader前被调用

  1. data设置的值,可以在loader里面获取到,在每个loader里面都可以通过this.data获取到这些值
module.exports.pitch = (remaining, preceding, data) => {
    data.value = "bajiu"
}

module.export = function(data){
console.log('data',this.data.value) // bajiu
} 

手动实现一个loader

简单参考

tapable学习

webpack是基于事件驱动,类似于nodeevent emitter:注册事件,然后触发事件,但是Tapable要比Event Emitter更加的强大,webpack的整个工作流程就是将所有的插件串联起来,而保证这一切能够实现的的核心就是Tapable

Tapable的核心

  1. 负责编译的Compiler
  2. 负责创建bundlescomplation
const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook 
 } = require("tapable");

All Hook constructors take one optional argument, which is a list of argument names as strings.

const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// ["arg1","arg2","arg3"] 控制触发事件,最多能传入多少个参数

Tapable主要钩子hook

主要有以下几种钩子

  1. 大体分为同步和异步的钩子
  2. 异步又分为:异步串行钩子和异步并行钩子
  3. 而根据事件终止条件的不同,可以分为Bail/waterfall/Loop
名称注册事件的方法回调时间的方法作用
Hooktap,tapAsync,tapPromise---钩子基类
SyncHooktapcall同步钩子,顺序执行
SyncBailHooktapcall同步钩子,只要执行的监听函数handler有返回值不为undefined,剩余的handler就不会执行
SyncLoopHooktapcall同步循环钩子,只要执行的监听函数handler有返回值为true,那么就会一直执行这个钩子,如果返回undefined则表示会退出循环
SyncWaterfallHooktapcall同步流水钩子,上一个监听函数handler的返回值作为下一个handler的参数
AsyncParallelBailHooktap,tapAsync,tapPromisecall,callAsync,promise异步并行熔断钩子,监听函数handler并行触发,但是跟内部调用逻辑的快慢有关联
AsyncParallelHooktap,tapAsync,tapPromisecall,callAsync,promise异步并行钩子,不关心返回值
AsyncSeriesBailHooktap,tapAsync,tapPromisecall,callAsync,promise异步串行钩子熔断钩子,handler串行触发,但是跟handler内部调用回调的逻辑有关
AsyncSeriesHooktap,tapAsync,tapPromisecall,callAsync,promise异步串行钩子,handler串行触发

源码分析及原理分析

任何不拿源码分析的文章都是太简单了,接下来所有的分析只针对于SyncHook来分析的,因为大部分过程是一致的

package.json文件的描述中可以看到Just a little module for plugins.

入口文件:"main": "lib/index.js"

从源码分析可以发现:

  1. 存在两个基础类:Hook以及HookCodeFactory工厂类
  2. Hook有一下几个子类:SyncHook,SyncBailHook,SyncLoopHook,SyncWaterfallHook等。在父类Hook上可以发现两个最为主要的方法:tapcall,webpack围绕这两个方法,构建出一个基于plugin的工作方式
  3. HookCodeFactory产生了对应于Hook的几个子工厂类:SyncHookCodeFactory,SyncBailHOokCodeFactory
  4. 通过const factory = new SyncHookCodeFactory(),在生成对应的工厂实例:factory
  5. 然后在SyncHook的方法compile,调用了factory.setUp以及返回了factory.create(options)
  6. 然后根据options内部的type,通过Function构建出可执行的函数,最后在调用call的时候被调用compile

tap的实现

tapable的使用中,tap函数是一个非常重要的内容。

const {
    SyncHook
  } = require('tapable')

  const hook = new SyncHook(['aa','bb'])
  hook.tap('a',function(arg,arg1,arg2){
      console.log('arg1',arg,arg1,arg2)
  })

  hook.tap({name: 'b',before: 'a'},function(arg,arg1,arg2){
    console.log('arg2',arg,arg1,arg2)
})
hook.tap({name: 'c',stage:-1},function(arg,arg1,arg2){
    console.log('arg3',arg,arg1,arg2)
})
// stage的大小决定了它执行的顺序
// before决定了这个回调函数要在那个回调函数前执行
hook.call('b','c','d')

tap函数的核心概念就是:将所有绑定的回调函数,都存放在taps这么样一个数组里面。

在上文提到了,所有的Hook都继承与唯一的父类Hook,既然在SyncHook等并没有发现tap函数的重载或者重写,那么就需要在父类Hook找到tap方法

	// lib/Hook.js
	tap(options, fn) {
		options = this._runRegisterInterceptors(options);
		this._insert(options);
	}

_insert函数

	_insert(item) {
		// 在_resetCompilation,将call,promise,callSync等
		this._resetCompilation();
		let before;
		if (typeof item.before === "string") before = new Set([item.before]);
		else if (Array.isArray(item.before)) {
			before = new Set(item.before);
		}
		let stage = 0;
		if (typeof item.stage === "number") stage = item.stage;
		let i = this.taps.length;
		while (i > 0) {
			i--;
			const x = this.taps[i];
			this.taps[i + 1] = x;
			const xStage = x.stage || 0;
			if (before) {
				if (before.has(x.name)) {
					before.delete(x.name);
					continue;
				}
				if (before.size > 0) {
					continue;
				}
			}
			if (xStage > stage) {
				continue;
			}
			i++;
			break;
		}
		this.taps[i] = item;
	}
	
	// _resetCompilation
	_resetCompilation() {
		this.call = this._call;
		this.callAsync = this._callAsync;
		this.promise = this._promise;
	}

_insert根据beforestage确定这个回调函数在taps位置。

call的实现

同样的,calltap一样是在父类Hook中实现

// lib/Hook.js
this.call = this._call
Object.defineProperties(Hook.prototype, {
	_call: {
		value: createCompileDelegate("call", "sync"),
		configurable: true,
		writable: true
	},
	_promise: {
		value: createCompileDelegate("promise", "promise"),
		configurable: true,
		writable: true
	},
	_callAsync: {
		value: createCompileDelegate("callAsync", "async"),
		configurable: true,
		writable: true
	}
});

使用了Object.defineProperties创建了_call方法,之后调用_call也就是相当于调用了value属性的createCompileDelegate,然后最终调用了_createCall

this.compile({
			taps: this.taps,
			interceptors: this.interceptors,
			args: this._args,
			type: type
		});

但是在_createCall调用了this.compile,在父类Hook中,compile是一个空的实现,所以得看子类hook得实现

	compile(options) {
		// 在call的时候被调用 _createCall taps interceptors _args type
		factory.setup(this, options);
		return factory.create(options);
	}

然后再看HookCodeFactory中,生成Function的部分代码

case "sync":
				fn = new Function(
					this.args(),
					'"use strict";\n' +
						this.header() +
						this.content({
							onError: err => `throw ${err};\n`,
							onResult: result => `return ${result};\n`,
							resultReturns: true,
							onDone: () => "",
							rethrowIfPossible: true
						})
				);
				break;

来看看Function构造函数的语法: new Function(arg1, arg2........argN, body);

可以知道this.args()生成的是所有的参数,然后紧接着的是body

可以看到,主要的是this.header,this.content的内容

来看看最后生成的fn是什么

console.log(fn)
// [Function: anonymous] fn

一个闭包

"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(aa, bb);
var _fn1 = _x[1];
_fn1(aa, bb);
var _fn2 = _x[2];
_fn2(aa, bb);

this._x就是之前的taps数组,这个闭包功能就是将taps里面的回调函数拿出来执行aabb是一开始初始化SyncHook传入的参数

疑问

  1. 为什么源码要采用这样一个一个函数拿出来执行的过程,而不直接采用循环来执行这些回调函数?

content

在生成的过程中

  1. this.header负责处理全局的变量
  2. this.args()负责处理call过程中的参数
  3. this.content()负责处理核心的流程

this.content是由每一个子类工厂实现的,如:SyncHookCodeFactory

content({ onError, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
			onDone,
			rethrowIfPossible
		});
	}

在这里调用了callTapsSeries,在这个函数里面最核心的内容就是

			const content = this.callTap(i, {
				onError: error => onError(i, error, done, doneBreak),
				onResult:
					onResult &&
					(result => {
						return onResult(i, result, done, doneBreak);
					}),
				onDone: !onResult && done,
				rethrowIfPossible:
					rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
			});

来大概看一下超长待机callTap的内容

		let code = "";
		let hasTapCached = false;
		for (let i = 0; i < this.options.interceptors.length; i++) {
			const interceptor = this.options.interceptors[i];
			if (interceptor.tap) {
				if (!hasTapCached) {
					code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
					hasTapCached = true;
				}
				code += `${this.getInterceptor(i)}.tap(${
					interceptor.context ? "_context, " : ""
				}_tap${tapIndex});\n`;
			}
		}
		code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
		const tap = this.options.taps[tapIndex];
		switch (tap.type) {
			case "sync":
				if (!rethrowIfPossible) {
					code += `var _hasError${tapIndex} = false;\n`;
					code += "try {\n";
				}
				if (onResult) {
					code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
						before: tap.context ? "_context" : undefined
					})});\n`;
				} else {
					code += `_fn${tapIndex}(${this.args({
						before: tap.context ? "_context" : undefined
					})});\n`;
				}
				if (!rethrowIfPossible) {
					code += "} catch(_err) {\n";
					code += `_hasError${tapIndex} = true;\n`;
					code += onError("_err");
					code += "}\n";
					code += `if(!_hasError${tapIndex}) {\n`;
				}
				if (onResult) {
					code += onResult(`_result${tapIndex}`);
				}
				if (onDone) {
					code += onDone();
				}
				if (!rethrowIfPossible) {
					code += "}\n";
				}
				
				break;
			case "async":
				let cbCode = "";
				if (onResult) cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;
				else cbCode += `_err${tapIndex} => {\n`;
				cbCode += `if(_err${tapIndex}) {\n`;
				cbCode += onError(`_err${tapIndex}`);
				cbCode += "} else {\n";
				if (onResult) {
					cbCode += onResult(`_result${tapIndex}`);
				}
				if (onDone) {
					cbCode += onDone();
				}
				cbCode += "}\n";
				cbCode += "}";
				code += `_fn${tapIndex}(${this.args({
					before: tap.context ? "_context" : undefined,
					after: cbCode
				})});\n`;
				break;
			case "promise":
				code += `var _hasResult${tapIndex} = false;\n`;
				code += `var _promise${tapIndex} = _fn${tapIndex}(${this.args({
					before: tap.context ? "_context" : undefined
				})});\n`;
				code += `if (!_promise${tapIndex} || !_promise${tapIndex}.then)\n`;
				code += `  throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise${tapIndex} + ')');\n`;
				code += `_promise${tapIndex}.then(_result${tapIndex} => {\n`;
				code += `_hasResult${tapIndex} = true;\n`;
				if (onResult) {
					code += onResult(`_result${tapIndex}`);
				}
				if (onDone) {
					code += onDone();
				}
				code += `}, _err${tapIndex} => {\n`;
				code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`;
				code += onError(`_err${tapIndex}`);
				code += "});\n";
				break;
		}
		console.log(code,'code')
		return code;

这代码有点长,直接分析太过空洞,所以结合生成的代码来看是比较合适的

工厂类创建的执行代码对比

syncHook

"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(aa, bb);
var _fn1 = _x[1];
_fn1(aa, bb);
var _fn2 = _x[2];
_fn2(aa, bb);

可以看到没有任何限制,直接将taps里面所有的

SyncBailHook

"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0(aa, bb);
if(_result0 !== undefined) {
return _result0;
;
} else {
var _fn1 = _x[1];
var _result1 = _fn1(aa, bb);
if(_result1 !== undefined) {
return _result1;
;
} else {
var _fn2 = _x[2];
var _result2 = _fn2(aa, bb);
if(_result2 !== undefined) {
return _result2;
;
} else {
}
}
}

可以看到当回调函数的返回值不为undefined的时候,就会停止向下执行,并且返回当前的回调函数的返回值

SyncLoopHook

"use strict";
var _context;
var _x = this._x;
var _loop;
do {
_loop = false;
var _fn0 = _x[0];
var _result0 = _fn0(aa, bb);
if(_result0 !== undefined) {
_loop = true;
} else {
var _fn1 = _x[1];
var _result1 = _fn1(aa, bb);
if(_result1 !== undefined) {
_loop = true;
} else {
var _fn2 = _x[2];
var _result2 = _fn2(aa, bb);
if(_result2 !== undefined) {
_loop = true;
} else {
if(!_loop) {
}
}
}
}
} while(_loop);

定义了一个为_loop的变量控制了do-while的执行,当回调函数的返回值不为undefined的时候,就会重复该回调函数的执行,直到返回undefined,才会执行下一个回调函数callback

SyncWaterfallHook

"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0(aa, bb);
if(_result0 !== undefined) {
aa = _result0;
}
var _fn1 = _x[1];
var _result1 = _fn1(aa, bb);
if(_result1 !== undefined) {
aa = _result1;
}
var _fn2 = _x[2];
var _result2 = _fn2(aa, bb);
if(_result2 !== undefined) {
aa = _result2;
}
return aa;

可以看到当前SyncWaterfallHook如果,回调函数中的返回值不为undefined,那么这个返回值将成为下一个回调函数执行时的参数

接下来是异步hook的分析

demo代码:

const {
    AsyncParallelHook
  } = require('tapable')

  const hook = new AsyncParallelHook(['aa'])
  hook.tap('a',function(arg){
      console.log('arg1',arg)
  })

  hook.tap({name: 'b',before: 'a'},function(arg){
    console.log('arg2',arg)
})
hook.tap({name: 'c',stage:-1},function(arg){
    console.log('arg3',arg)
})

hook.callAsync('b',function(data){
    console.log('这是响应回调',data)
})

AsyncParallelHook

"use strict";
var _context;
var _x = this._x;
do {
var _counter = 3;
var _done = () => {
_callback();
};
if(_counter <= 0) break;
var _fn0 = _x[0];
var _hasError0 = false;
try {
_fn0(aa);
} catch(_err) {
_hasError0 = true;
if(_counter > 0) {
_callback(_err);
_counter = 0;
}
}
if(!_hasError0) {
if(--_counter === 0) _done();
}
if(_counter <= 0) break;
var _fn1 = _x[1];
var _hasError1 = false;
try {
_fn1(aa);
} catch(_err) {
_hasError1 = true;
if(_counter > 0) {
_callback(_err);
_counter = 0;
}
}
if(!_hasError1) {
if(--_counter === 0) _done();
}
if(_counter <= 0) break;
var _fn2 = _x[2];
var _hasError2 = false;
try {
_fn2(aa);
} catch(_err) {
_hasError2 = true;
if(_counter > 0) {
_callback(_err);
_counter = 0;
}
}
if(!_hasError2) {
if(--_counter === 0) _done();
}
} while(false);(--_counter === 0) _done();
}
  1. _counter是表示绑定的回调函数的个数
  2. _callback代表调用的回调函数
  3. 当执行时候报错了,同时当前执行的_counter还存在的话,那么会执行回调,同时传入_err
  4. 当最后一个函数执行完成,也会执行_callback

AsyncParallelBailHook

"use strict";
var _context;
var _x = this._x;
var _results = new Array(3);
var _checkDone = () => {
for(var i = 0; i < _results.length; i++) {
var item = _results[i];
if(item === undefined) return false;
if(item.result !== undefined) {
_callback(null, item.result);
return true;
}
if(item.error) {
_callback(item.error);
return true;
}
}
return false;
}
do {
var _counter = 3;
var _done = () => {
_callback();
};
if(_counter <= 0) break;
var _fn0 = _x[0];
var _hasError0 = false;
try {
var _result0 = _fn0(aa);
} catch(_err) {
_hasError0 = true;
if(_counter > 0) {
if(0 < _results.length && ((_results.length = 1), (_results[0] = { error: _err }), _checkDone())) {
_counter = 0;
} else {
if(--_counter === 0) _done();
}
}
}
if(!_hasError0) {
if(_counter > 0) {
if(0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = { result: _result0 }), _checkDone())) {
_counter = 0;
} else {
if(--_counter === 0) _done();
}
}
}
/**重复逻辑代码省略**/
} while(false);
  1. _checkdone函数利用循环,只要注册的回调函数taps,里面有一个返回值不为undefined,那么就会执行_callback(注册callAsync注册的回调)
  2. 执行错误或者执行完成的时候

AsyncSeriesHook

"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _hasError0 = false;
try {
_fn0(aa);
} catch(_err) {
_hasError0 = true;
_callback(_err);
}
if(!_hasError0) {
var _fn1 = _x[1];
var _hasError1 = false;
try {
_fn1(aa);
} catch(_err) {
_hasError1 = true;
_callback(_err);
}
if(!_hasError1) {
var _fn2 = _x[2];
var _hasError2 = false;
try {
_fn2(aa);
} catch(_err) {
_hasError2 = true;
_callback(_err);
}
if(!_hasError2) {
_callback();
}
}
}

异步串行钩子

  1. 不会关注任何回调函数的返回值,只会在报错的时候或者执行了最后一个回调函数且不报错的时候,会执行callAsync绑定的回调

AsyncSeriesBailHook

"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _hasError0 = false;
try {
var _result0 = _fn0(aa);
} catch(_err) {
_hasError0 = true;
_callback(_err);
}
if(!_hasError0) {
if(_result0 !== undefined) {
_callback(null, _result0);
;
} else {
var _fn1 = _x[1];
var _hasError1 = false;
try {
var _result1 = _fn1(aa);
} catch(_err) {
_hasError1 = true;
_callback(_err);
}
if(!_hasError1) {
if(_result1 !== undefined) {
_callback(null, _result1);
;
} else {
var _fn2 = _x[2];
var _hasError2 = false;
try {
var _result2 = _fn2(aa);
} catch(_err) {
_hasError2 = true;
_callback(_err);
}
if(!_hasError2) {
if(_result2 !== undefined) {
_callback(null, _result2);
;
} else {
_callback();
}
}
}
}
}
}
  1. 返回值不为undefined会直接触发tapAsync的参数

AsyncSeriesWaterfallHook

"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _hasError0 = false;
try {
var _result0 = _fn0(aa);
} catch(_err) {
_hasError0 = true;
_callback(_err);
}
if(!_hasError0) {
if(_result0 !== undefined) {
aa = _result0;
}
var _fn1 = _x[1];
var _hasError1 = false;
try {
var _result1 = _fn1(aa);
} catch(_err) {
_hasError1 = true;
_callback(_err);
}
if(!_hasError1) {
if(_result1 !== undefined) {
aa = _result1;
}
var _fn2 = _x[2];
var _hasError2 = false;
try {
var _result2 = _fn2(aa);
} catch(_err) {
_hasError2 = true;
_callback(_err);
}
if(!_hasError2) {
if(_result2 !== undefined) {
aa = _result2;
}
_callback(null, aa);
}
}
}
  1. 下一个回调函数接收上一个回调函数的返回值(返回值不为undefined)

总结

钩子执行条件
syncHook直接将taps里面所有的回调函数执行了
SyncBailHook当回调函数的返回值不为undefined的时候,就会停止向下执行,并且返回当前的回调函数的返回值
SyncLoopHook当回调函数的返回值不为undefined的时候,就会重复该回调函数的执行,直到返回undefined,才会执行下一个回调函数callback
SyncWaterfallHook回调函数中的返回值不为undefined,那么这个返回值将成为下一个回调函数执行时的参数
AsyncParallelHooktaps里面的回调函数按照顺序执行,并不关心返回值
AsyncParalleBailHook里面有一个返回值不为undefined,那么就会执行_callback(注册callAsync注册的回调)
AsyncSeriesHook不会关注任何回调函数的返回值
AsyncSeriesBailHook返回值不为undefined会直接触发tapAsync的参数
AsyncSeriesWaterfallHook下一个回调函数接收上一个回调函数的返回值(返回值不为undefined)

个人最近学习关于原理方面的知识,有文章介绍的大佬吗?望推荐!!!