tapable最详细源码分析及手写实现

tapable整体介绍

tapable 是一个类似于 Node.js 中的 EventEmitter的库,但更专注于自定义事件的触发和处理。

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook,
	AsyncSeriesLoopHook
 } = require("tapable");

这些钩子有同步钩子和异步钩子之分。
Sync-开头的是同步钩子,
Async-开头的是异步钩子。
异步钩子又分为异步串行钩子(AsyncSeries)和异步并行钩子(AsyncParallel)。
*Hook:基础钩子,单纯的调用注册的事件回调,并不关心其内部的运行逻辑
*BailHook:熔断钩子,当一个事件回调运行时返回的值不为undefined时,停止后面事件回调的执行
*WaterfallHook: 瀑布钩子,如果当前执行的事件回调返回值不为undefined时,那么就把下一个事件回调的第一个参数替换成这个值
*LoopHook:循环钩子,如果当前执行的事件回调的返回值不是undefined,重新从第一个注册的事件回调处执行,直到当前执行的事件回调没有返回值

tapable最详细源码分析及手写实现_第1张图片
tapable最详细源码分析及手写实现_第2张图片

使用方法

1. install
npm install --save tapable
2. 使用
  1. 实例化hook,比如new SyncHook()
  2. 注册回调函数 .tap(‘vue’, () => {})
  3. 触发回调: .call()
const {
    SyncHook,
} = require("tapable");
// 实例化hook
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// 注册回调事件
hook.tap("vue", function (name) {
	console.log("vue", name);
	return "vue-book";
});

hook.tap("react", function (name) {
	console.log("react:", name);
	return "react";
});
// 触发回调
hook.call('ac')

源码分析

  1. new SyncHook():
    首先看一下SyncHook类的源码
    ./lib/SyncHook.js
function SyncHook(args = [], name = undefined) {
	const hook = new Hook(args, name);
	hook.constructor = SyncHook;
	hook.tapAsync = TAP_ASYNC;
	hook.tapPromise = TAP_PROMISE;
	hook.compile = COMPILE;
	return hook;
}

SyncHook.prototype = null;

我们可以看到SyncHook是Hook的一种形式,可以把Hook看成抽象类,看一下基础Hook的实现:
./lib/Hook.js

...
class Hook {
	constructor(args = [], name = undefined) {
		this._args = args;
		this.name = name;
		this.taps = [];
		this.interceptors = [];
		this._call = CALL_DELEGATE;
		this.call = CALL_DELEGATE;
		this._callAsync = CALL_ASYNC_DELEGATE;
		this.callAsync = CALL_ASYNC_DELEGATE;
		this._promise = PROMISE_DELEGATE;
		this.promise = PROMISE_DELEGATE;
		this._x = undefined;

		this.compile = this.compile;
		this.tap = this.tap;
		this.tapAsync = this.tapAsync;
		this.tapPromise = this.tapPromise;
	}
	_createCall(type) {
		...
	}

	_tap(type, options, fn) {
		...
	}
	_insert(item) {
		...
	}
	...
}

Object.setPrototypeOf(Hook.prototype, null);

module.exports = Hook;

实例化之后hook实例具有如下属性和方法:
tapable最详细源码分析及手写实现_第3张图片
2. 注册回调函数

hook.tap("vue", function (name) {
	console.log("vue", name);
	return "vue-book";
});

hook.tap("react", function (name) {
	console.log("react:", name);
	return "react";
});

执行这一步的,会直接执行hook的tap方法,hook的tap方法我们看到在Hook类实现的
./lib/Hook.js

	_tap(type, options, fn) {
		if (typeof options === "string") {
			options = {
				name: options.trim()
			};
		} else if (typeof options !== "object" || options === null) {
			throw new Error("Invalid tap options");
		}
	    ...
		this._insert(options);
	}

	tap(options, fn) {
		this._tap("sync", options, fn);
	}

	tapAsync(options, fn) {
		this._tap("async", options, fn);
	}

	tapPromise(options, fn) {
		this._tap("promise", options, fn);
	}

tap方法会执行_insert方法

	_insert(item) {
		...
		this.taps[i] = item;
	}

至此,回调函数注册完毕,结果如下:
tapable最详细源码分析及手写实现_第4张图片
3. 调用回调函数 hook.call()

tapable最详细源码分析及手写实现_第5张图片
执行hook.call()会执行下面代码(./lib/Hook.js):

const CALL_DELEGATE = function(...args) {
	this.call = this._createCall("sync");
	return this.call(...args);
};

接着执行_createCall方法(./lib/Hook.js)

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

然后会执行compile方法,compile方法在SyncHook里面定义的(./lib/SyncHook.js)

const COMPILE = function(options) {
	factory.setup(this, options);
	return factory.create(options);
};

这里的factory指的是什么呢?接着向下看:

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

const factory = new SyncHookCodeFactory();

factory是SyncHookCodeFactory的实例,SyncHookCodeFactory这类继承了HookCodeFactory,HookCodeFactory这个类就实现了根据不同的参数生成不同的函数体,所以我们执行compile中的factory.setup()方法走到了HookCodeFactory中的setup(./lib/HookCodeFactory.js)

setup(instance, options) {
	instance._x = options.taps.map(t => t.fn);
}

这个方法就是把我们注册的回调函数赋给了this._x,此时的hook实例如下:
tapable最详细源码分析及手写实现_第6张图片
接着执行factory.create(),这个方法通过new Function()根据不同的参数生成不同的函数体,

create(options) {
		this.init(options);
		let fn;
		switch (this.options.type) {
			case "sync":
				fn = new Function(
					this.args(),
					'"use strict";\n' +
						this.header() +
						this.contentWithInterceptors({
							onError: err => `throw ${err};\n`,
							onResult: result => `return ${result};\n`,
							resultReturns: true,
							onDone: () => "",
							rethrowIfPossible: true
						})
				);
				break;
			case "async":
				fn = new Function(
					this.args({
						after: "_callback"
					}),
					'"use strict";\n' +
						this.header() +
						this.contentWithInterceptors({
							onError: err => `_callback(${err});\n`,
							onResult: result => `_callback(null, ${result});\n`,
							onDone: () => "_callback();\n"
						})
				);

				break;
			case "promise":
				let errorHelperUsed = false;
				const content = this.contentWithInterceptors({
					onError: err => {
						errorHelperUsed = true;
						return `_error(${err});\n`;
					},
					onResult: result => `_resolve(${result});\n`,
					onDone: () => "_resolve();\n"
				});
				let code = "";
				code += '"use strict";\n';
				code += this.header();
				code += "return new Promise((function(_resolve, _reject) {\n";
				if (errorHelperUsed) {
					code += "var _sync = true;\n";
					code += "function _error(_err) {\n";
					code += "if(_sync)\n";
					code +=
						"_resolve(Promise.resolve().then((function() { throw _err; })));\n";
					code += "else\n";
					code += "_reject(_err);\n";
					code += "};\n";
				}
				code += content;
				if (errorHelperUsed) {
					code += "_sync = false;\n";
				}
				code += "}));\n";
				fn = new Function(this.args(), code);
				break;
		}
		this.deinit();
		console.log(fn.toString())
		return fn;
	}

最终create方法返回的函数是:

function anonymous(arg1, arg2, arg3) {
	"use strict";
	var _context;
	var _x = this._x;
	var _fn0 = _x[0];
	_fn0(arg1, arg2, arg3);
	var _fn1 = _x[1];
	_fn1(arg1, arg2, arg3);
}

这个也就是this._createCall()的结果,然后执行this.call()调用我们这个函数。

const CALL_DELEGATE = function(...args) {
	this.call = this._createCall("sync");
	return this.call(...args);
};

最终得出结果:

vue ac
react: ac

至此源码解析完毕,下面我们看一下同步和异步怎么实现的

手写实现同步异步

你可能感兴趣的:(js,javascript)