Skip to content

tapable 拦截器

钩子都提供额外的拦截器 API,在实例对象上使用 hook.intercept() 去添加一个钩子对象。

  1. context : 如果定义了 那么 call、tap、loop 的第一个参数就是 _context, 且有一个事件流中如果定义了 context,那么这个 _context = {}否则为 _context = undefined

  2. register : 在每一个事件流通过 tap、tapAsync、tapPromise 添加一个事件流的时候执行,参数为当前事件流处理后的 option 对象,可以修改此对象去自定义 option

  3. call : 在每一个钩子函数执行的时候执行,参数为当前钩子函数的参数,如果定义了 context 第一个参数会变成 _context

  4. tap : 在每一个事件流执行的时候执行,参数为当前当前的事件流对象(tap : {name , type,}),如果定义了 context 第一个参数会变成 _context

  5. loop : Loop 类钩子函数独有,在每一个钩子函数循环执行的时候触发,参数为当前钩子函数的参数,如果定义了 context 第一个参数会变成 _context

register

在每一个事件流添加的时候执行,其参数为事件流的 option,需要 return 一个新的 option(可以在 register 中修改事件流的 option 对象)

源码
js
class Hook {
	constructor(args) {}
	_createCompileDelegate(name, type) {}
	tap(options, fn) {
		options = this._runRegisterInterceptors(options);
		// 添加事件流
		this._insert(options);
	}

	tapAsync(options, fn) {
		options = this._runRegisterInterceptors(options);
		this._insert(options);
	}

	tapPromise(options, fn) {
		options = this._runRegisterInterceptors(options);
		this._insert(options);
	}

	/**
	 * 回调每一个钩子函数中定义的register函数,参数为当前事件流的处理后的option 返回一个新的option
	 */
	_runRegisterInterceptors(options) {
		for (const interceptor of this.interceptors) {
			if (interceptor.register) {
				// 就是执行register然后获得新的option 返回
				const newOptions = interceptor.register(options);
				if (newOptions !== undefined) options = newOptions;
			}
		}
		return options;
	}
}

可以看到 register 的作用就是在每一个事件流添加的时候可以让用户处理 option

call

我们看一下 call 的渲染

js
class HookCodeFactory {
	header() {
		let code = '';
		if (this.needContext()) {
			code += 'var _context = {};\n';
		} else {
			code += 'var _context;\n';
		}
		// 定义所有的回调函数
		code += 'var _x = this._x;\n';
		if (this.options.interceptors.length > 0) {
			code += 'var _taps = this.taps;\n';
			code += 'var _interceptors = this.interceptors;\n';
		}

		for (let i = 0; i < this.options.interceptors.length; i++) {
			const interceptor = this.options.interceptors[i];
			// 拦截器是否定义了 call
			if (interceptor.call) {
				// 处理拦截器的call,这个说拦截器的call是执行的时候才会执行
				// 其入参也是
				code += `${this.getInterceptor(i)}.call(${this.args({
					before: interceptor.context ? '_context' : undefined,
				})});\n`;
			}
		}
		return code;
	}
}

其是在每一个钩子函数执行生成伪代码的时候,在 this.header()中进行处理的,所以其不会因为多个事件流而执行多次

我们看他的渲染结果

js
'use strict';
var _context;
var _x = this._x;
var _taps = this.taps;

var _interceptors = this.interceptors;
// 处理call
_interceptors[0].call(_context, xxx, arg2);
_interceptors[1].call(xxx, arg2);

var _tap0 = _taps[0];
_interceptors[0].tap(_context, _tap0);
_interceptors[1].tap(_tap0);
var _fn0 = _x[0];
_fn0(xxx, arg2);
var _tap1 = _taps[1];
_interceptors[0].tap(_context, _tap1);
_interceptors[1].tap(_tap1);
var _fn1 = _x[1];
_fn1(xxx, arg2);

从上面可以看出 call 是在事件流执行之前执行,其参数是,如果定义了钩子函数 before 参数 context 那么就形参就是 _interceptors[0].call(context的值, h1.定义的时候传入的参数)

tap

在每一个事件流执行的时候先执行 tap 函数,传入当前事件流的 taps 对象({name, type , interceptors ,args})

源码

js
class HookCodeFactory {
	callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
		debugger;
		let code = '';
		let hasTapCached = false;

		// 处理拦截器中 的 interceptor.tap
		/*

			var _context;
			var _x = this._x;

			var _taps = this.taps;

			var _interceptors = this.interceptors;
			_interceptors[0].call(xxx, arg2);

			var _tap0 = _taps[0];
			_interceptors[0].tap(_tap0);
			var _fn0 = _x[0];
			_fn0(xxx, arg2);

			var _tap1 = _taps[1];
			_interceptors[0].tap(_tap1);
			var _fn1 = _x[1];
			_fn1(xxx, arg2);

			我们看上面生成的结果可以看出 tap 在生成可执行的字符串的时候是 在每一个事件流回调执行之前执行执行当前tap拦截,参数为每一个的事件流的 tap对象 其实就是 { name , fn ...}
			var _tap0 = _taps[0];
			_interceptors[0].tap(_tap0);

		*/
		for (let i = 0; i < this.options.interceptors.length; i++) {
			const interceptor = this.options.interceptors[i];
			if (interceptor.tap) {
				// 原来所有的 taps是存放在 this._taps 中,下面需要多次访问那么为了提高性能就缓存一份 var _tap0 = _taps[0];
				// 在上面的例子中可能不能看出问题,主要是当我们注册很多拦截器的时候
				// 这时候就是
				/*
					var _tap0 = _taps[0];
					_interceptors[0].tap(_tap0);
					_interceptors[1].tap(_tap0);
					_interceptors[2].tap(_tap0);
					_interceptors[3].tap(_tap0);

					var _fn0 = _x[0];
					_fn0(xxx, arg2);

				*/

				if (!hasTapCached) {
					code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
					hasTapCached = true;
				}
				code += `${this.getInterceptor(i)}.tap(${interceptor.context ? '_context, ' : ''}_tap${tapIndex});\n`;
			}
		}
		// 生成  var _fn0 = _x[0];
		code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
		const tap = this.options.taps[tapIndex];

		// 下面生成 事件流的回调函数执行字符串
		// 最简单的是 _fn0(xxx, arg2);
		switch (tap.type) {

		return code;
	}
}

我们看他的渲染结果

js
'use strict';
var _context;
var _x = this._x;
var _taps = this.taps;

var _interceptors = this.interceptors;
// 处理call
_interceptors[0].call(_context, xxx, arg2);
_interceptors[1].call(xxx, arg2);

var _tap0 = _taps[0];

// 处理tap
_interceptors[0].tap(_context, _tap0);
_interceptors[1].tap(_tap0);
var _fn0 = _x[0];
_fn0(xxx, arg2);
var _tap1 = _taps[1];
_interceptors[0].tap(_context, _tap1);
_interceptors[1].tap(_tap1);
var _fn1 = _x[1];
_fn1(xxx, arg2);

loop

loop 是 syncLoopHook 独有的一个拦截器属性,其是每一次循环的时候执行一次(call 是每一次启动的时候调用一次,tap 是每一个事件流执行都执行一次)

js
'use strict';
var _context;
var _x = this._x;
var _taps = this.taps;
var _interceptors = this.interceptors;
_interceptors[0].call(_context, xxx, arg2);
_interceptors[1].call(xxx, arg2);
var _loop;
do {
	_loop = false;
	_interceptors[0].loop(_context, xxx, arg2);
	var _tap0 = _taps[0];
	_interceptors[0].tap(_context, _tap0);
	_interceptors[1].tap(_tap0);
	var _fn0 = _x[0];
	var _result0 = _fn0(xxx, arg2);
	if (_result0 !== undefined) {
		_loop = true;
	} else {
		var _tap1 = _taps[1];
		_interceptors[0].tap(_context, _tap1);
		_interceptors[1].tap(_tap1);
		var _fn1 = _x[1];
		var _result1 = _fn1(xxx, arg2);
		if (_result1 !== undefined) {
			_loop = true;
		} else {
			if (!_loop) {
			}
		}
	}
} while (_loop);

上次更新: