如何判断一个函数的意图是被用作构造器,也就是可视为“类”
前提是不要求做什么特殊标记。只是最大可能的猜测函数的作用大概是当“类”使用。
我想了一个方式:
function isClass(f) {
var names = Object.keys(f.prototype)
return names.indexOf('constructor') === -1 || names.length > 1
}
这里的假设是,作为构造器的函数会在其prototype上加公共方法。
prototype上默认是有 constructor 属性(指向函数自身)。那么如果keys(prototype上的属性名列表)的长度大于1,意味着作者执行过 f.prototype.xxx = ... ,或者keys不包含constructor属性,意味着作者很可能执行过 f.prototype = { xxx:... } 。这样我们就认为这个函数的意图是被当作构造器(类)来用。
各位如何看?
有什么别的idea么?
【7月24日更新】
之前的方法有一些小问题,根据标准,constructor属性应是不可enumerable的。所以Object.keys并不会包含constructor属性(感谢jk和luolonghao指出问题)。上面的代码可直接改为:
function isClass(f) {
var names = Object.keys(f.prototype)
return names.length > 0
}
但考虑旧引擎,则其相关ES5方法是由如es5-shim之类的库补上,行为不是最标准,比如无法知道一个属性是否是enumerable的(这就是jk怀疑的什么引擎会返回constructor,呵呵)。
另外,若f.prototype上存在不可枚举属性,说明什么呢?难道就是不想作为类吗?一个case是我可能希望方法不可枚举。所以可修改如下:
function isClass(f) {
var names = Object.getOwnPropertyNames(f.prototype)
return names.length > 1 || names[0] !== 'constructor'
}
这里还存在一个问题。prototype可以是原型继承得来的,上面own properties没有,但是有许多其他继承来的property,那么我们可能需要上溯所有原型(感谢limu提出这个问题)。
不过我直觉上溯并不是好办法,所以我重新考虑我的基本出发点:
原始function(){}的prototype值是一个new Object()结果,且其上定义了constructor属性指向该函数自身。
基本想法是:我们判断当前函数的情况是否和上面一致,如果不一致,则其目的很可能是为了将该函数作为一个类构造器来使用。
所以,最后得到这个版本:
function Person(name){this.name = name;}alert(likeConstructor(Person));//chrome falsevar p1 = new Person("a");