这是我参与更文挑战的第9天,活动详情查看:更文挑战
is-generator-function 是 Koa 的一个依赖包,用于判断是否是一个 Generator 函数,源码总共 38 行,逻辑并不复杂。
核心代码是下面这一块:
module.exports = function isGeneratorFunction(fn) {
if (typeof fn !== 'function') {
return false;
}
if (isFnRegex.test(fnToStr.call(fn))) {
return true;
}
if (!hasToStringTag) {
var str = toStr.call(fn);
return str === '[object GeneratorFunction]';
}
if (!getProto) {
return false;
}
if (typeof GeneratorFunction === 'undefined') {
var generatorFunc = getGeneratorFunc();
GeneratorFunction = generatorFunc ? getProto(generatorFunc) : false;
}
return getProto(fn) === GeneratorFunction;
};
首先使用 typeof 判断目标函数是否是函数类型,不是函数类型直接确定不是一个 Generator 函数。
是函数类型的话将该函数转成字符串,通过正则匹配判断,isFnRegex 跟 fnToStr 的定义如下所示:
var fnToStr = Function.prototype.toString;
var isFnRegex = /^\s*(?:function)?\*/;
Function.prototype.toString用于将整个函数的代码块转成字符串,我们做个简单测试如下所示:
let func1 = function* () {}
let func2 = function * () {}
let obj = {
*fn() {}
}
console.log(fnToStr.call(func1)) // function* () {}
console.log(isFnRegex.test(fnToStr.call(func1))) // true
console.log(fnToStr.call(func2)) // function * () {}
console.log(isFnRegex.test(fnToStr.call(func2))) // false
console.log(fnToStr.call(obj.fn)) // *fn() {}
console.log(isFnRegex.test(fnToStr.call(obj.fn))) // true
可以看到该正则表达式可以匹配出function* 和 *func方式定义的 Generator 函数,而function *方式定义的则无法判断。
所以如果正则匹配失败,就通过Object.prototype.toString方式判断。hasToStringTag 跟 toStr 代码如下所示:
var toStr = Object.prototype.toString;
var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
由于 ES6 增加了 Symbol.toStringTag,通过该属性能够修改Object.prototype.toString返回值,如下所示:
let user = {
[Symbol.toStringTag]: "User"
};
alert( {}.toString.call(user) ); // [object User]
所以只有在明确不存在 Symbol.toStringTag 的情况下,以 Object.prototype.toString得到的类型判断结果才可靠。
module.exports = function isGeneratorFunction(fn) {
// ......
if (!hasToStringTag) {
var str = toStr.call(fn);
return str === '[object GeneratorFunction]';
}
// ......
}
以上条件都不满足的情况下,进入最后一个步骤,判断该函数的 prototype 属性是否与 GeneratorFunction 的 prototype 属性一致。
首先判断环境是否存在Object.getPrototypeOf方法,如果不存在就直接返回false,如果存在Object.getPrototypeOf,就创建一个空的 Generator 函数,通过判断这个 Genertator 函数的 ptototype 与目标函数的 prototype 是否相等来判定目标函数是否是一个 Generator 函数。
var GeneratorFunction; // 用于存储 Generator 函数的 prototype
var getProto = Object.getPrototypeOf;
var getGeneratorFunc = function () { // 该函数用于创建一个空的 Generator 函数
if (!hasToStringTag) {
return false;
}
try {
return Function('return function*() {}')();
} catch (e) {
}
};
module.exports = function isGeneratorFunction(fn) {
//......
if (typeof GeneratorFunction === 'undefined') {
// 创建一个空的 Generator 函数
var generatorFunc = getGeneratorFunc();
// 获取到 Generator 函数的 prototype
GeneratorFunction = generatorFunc ? getProto(generatorFunc) : false;
}
// 根据 prototype 判断目标函数是否是 Generator 函数
return getProto(fn) === GeneratorFunction;
}
总结
Generator 函数的判断逻辑如下:
- 通过 typeof 判断是否是函数类型,符合则进行步骤 2;
- 转成字符串,通过正则匹配,判断是否符合 Generator 函数定义方式,符合返回 true,不符合进行步骤 3;
- 判断当前环境是否具有
Symbol.toString,没有就根据Object.prototype.toString方式判定是否是 Generator 函数,有Symbol.toString的话进行步骤 4; - 判断环境是否具有
Object.getPrototypeOf,没有就返回 false,有则通过当前函数的 prototype 是否与 Generator 函数的 prototype 一致来判定目标函数是否是 Generator 函数
对比
昨天写了co 源码解析一文,在 co 中也存在 Generator 函数的判断逻辑,实现思路与本文的思路还是不太一样,有兴趣的朋友可以对比看看。
// 判断 obj 是否是一个 Generator 函数
function isGeneratorFunction(obj) {
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName)
return true;
return isGenerator(constructor.prototype);
}
// 判断 obj 是否是一个 Generator 对象
function isGenerator(obj) {
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}