koa源码之 is-generator-function 包解析

2240 4 年前
查看koa源码,第一项就是引及 is-generator-function 包,其作用就是判断一个是否为generator函数

is-generator-function 源码

is-generator-function 源码(version: 1.0.7)

'use strict';

var toStr = Object.prototype.toString;
var fnToStr = Function.prototype.toString;
var isFnRegex = /^\s*(?:function)?\*/;
var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
var getProto = Object.getPrototypeOf;
var getGeneratorFunc = function () { // eslint-disable-line consistent-return
	if (!hasToStringTag) {
		return false;
	}
	try {
		return Function('return function*() {}')();
	} catch (e) {
	}
};
var generatorFunc = getGeneratorFunc();
var GeneratorFunction = generatorFunc ? getProto(generatorFunc) : {};

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]';
	}
	return getProto(fn) === GeneratorFunction;
};

解析

源码一:

var toStr = Object.prototype.toString;

使用Object.prototype.toString来判断对象的类型,输出[object xxx],如对一个数组调用此方法

toStr.call([])

返回[object Array]jquery里使用此方法来判断对象类型,但,ES2015以后,这方法不再可靠,因为ES2015为一些对象设置了Symbol.toStringTag属性,此属性的值可以自定义,Object.prototype.toString方法返回的值为Symbol.toStringTag属性的值。

function Teacher(name) {
  this.name = name
}
Teacher.prototype[Symbol.toStringTag] = 'Teacher'

const teacher = new Teacher('Jason')
console.log(teacher.toString()) //[object Teacher]
console.log(Object.prototype.toString.call(teacher)) //[object Teacher]

源码二:

var fnToStr = Function.prototype.toString;

函数类自定义了自己的toString方法,此方法返回函数的原代码。

const fnToStr = Function.prototype.toString;

function fn(){return "function*"}
let obj = {
    getName(){return 'http://www.tensweets.com'}
};

console.log(fnToStr.call(obj.getName));
//getName(){return 'http://www.tensweets.com'}
console.log(fnToStr.call(fn));
//function fn(){return "function*"}

对于一个generator函数,在node版本小于10时,Function.prototype.toString总是返回function**funName的形式;但在node版本大于等于10时,其返回的是原generator函数原始内容的样子。

node小于10:

const fnToStr = Function.prototype.toString;
let obj = {
    *getName(){},
    *getAge(){}
};
let fa = function* (){};
function* fb(){}
let fn = Function('return function *() {}')();

console.log(fnToStr.call(obj.getName));
//*getName(){}
console.log(fnToStr.call(obj.getAge));
//*getAge(){}
console.log(fnToStr.call(fa));
//function* (){}
console.log(fnToStr.call(fb));
//function* fb(){}
console.log(fnToStr.call(fn));
//function* () {}

node大于等于10:

const fnToStr = Function.prototype.toString;
let obj = {
    *getName(){},
    *getAge(){}
};
let fa = function* (){};
function* fb(){}
let fn = Function('return function *() {}')();

console.log(fnToStr.call(obj.getName));
//*getName(){}
console.log(fnToStr.call(obj.getAge));
//* getAge(){}
console.log(fnToStr.call(fa));
//function* (){}
console.log(fnToStr.call(fb));
//function* fb(){}
console.log(fnToStr.call(fn));
//function * () {}

源码三:

var isFnRegex = /^\s*(?:function)?\*/;

用于匹配generator函数通过Function.prototype.toString执行后返回的字符串,表示的意思是:以0个或多个空格开头,后面紧接0个或1个不捕获的function字符串,再后面紧接一个*字符。

显然,当node>=10时这个正则匹配不能完全匹配generator函数的字符串原码(有function *这种情况)。

源码四:

var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';

这段代码码用于判断当前环境是否支持对象Symbol.toStringTag属性,Object.prototype.toString返回值不一定可靠了。

var getProto = Object.getPrototypeOf;

取得对象原型的函数。

源码五:

var getGeneratorFunc = function () { // eslint-disable-line consistent-return
	if (!hasToStringTag) {
		return false;
	}
	try {
		return Function('return function*() {}')();
	} catch (e) {
	}
};

定义一个函数,如果环境不支持Symbol.toStringTag返回false,反之试图返回一个generator函数。

var generatorFunc = getGeneratorFunc();
var GeneratorFunction = generatorFunc ? getProto(generatorFunc) : {};

如果不支持Symbol.toStringTag或者无法创建generator函数GeneratorFunction值为空对象{},反之GeneratorFunction值为新建generator函数的原型。

源码六:

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]';
	}
	return getProto(fn) === GeneratorFunction;
};

模块导出为一个函数,接收一个要检测的参数。

  1. 如果参数不是函数类型,肯定也不是generator函数。
  2. 参数是函数了,将这函数换成字符串,如果字符串以*function*开头,则是一个generator函数,个判断是很不严谨的, 因为function *无法匹配
  3. 如果上面的方法不行, 就尝试利用 toString 的方法,当环境不支持Symbol.toStringTagFunction.prototype.toString方法返回的类型字符串是可信的,对于一个generator函数,其执行Function.prototype.toString方法,返回的是[object GeneratorFunction]字符串。
  4. 上面还是没结果的话,判断参数函数的原型跟我们创建的generator函数的原型是否指向同一个对象。

以下是在node>=10的环境测试输出的结果

var toStr = Object.prototype.toString;
var fnToStr = Function.prototype.toString;
var isFnRegex = /^\s*(?:function)?\*/;
var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
var getProto = Object.getPrototypeOf;
var getGeneratorFunc = function () { // eslint-disable-line consistent-return
    if (!hasToStringTag) {
        return false;
    }
    try {
        return Function('return function*() {}')();
    } catch (e) {
    }
};
var generatorFunc = getGeneratorFunc();
var GeneratorFunction = generatorFunc ? getProto(generatorFunc) : {};

function isGeneratorFunction(fn) {
    if (typeof fn !== 'function') {
		console.log("11")
        return false;
    }
    if (isFnRegex.test(fnToStr.call(fn))) {
		console.log("22")
        return true;
    }
    if (!hasToStringTag) {
		console.log("33")
        var str = toStr.call(fn);
        return str === '[object GeneratorFunction]';
    }
	console.log("44")
    return getProto(fn) === GeneratorFunction;
};


let fn = Function('return function *() {}')();
console.log(fnToStr.call(fn));
console.log(isGeneratorFunction(fn));
function *() {}
44
true
© 2018邮箱:11407215#qq.comGitHub沪ICP备12039518号-6