`
hax
  • 浏览: 962415 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

如何判断一个函数的意图是被用作构造器,也就是可视为“类”

    博客分类:
  • JS
阅读更多
前提是不要求做什么特殊标记。只是最大可能的猜测函数的作用大概是当“类”使用。


我想了一个方式:

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属性指向该函数自身。

基本想法是:我们判断当前函数的情况是否和上面一致,如果不一致,则其目的很可能是为了将该函数作为一个类构造器来使用。

所以,最后得到这个版本:

// 感谢jk指出isClass这个名字的问题,所以将名称修改为likeContrcutor
// 以更好的反映意图。
// 就这个函数的行为而言,最准确的名字或许是isPrototypeModified(),
// 不过这样的名字无法反映高阶的意图。
function likeConstructor(f) { 
  return 'prototype' in f && (
         Object.getPrototypeOf(f.prototype) !== Object.prototype ||
         Object.getOwnPropertyNames(f.prototype).length > 1 ||
         !('constructor' in f.prototype))
}


'prototype' in f 检测排除了那些没有prototype属性的函数,比如通过Function.prototype.bind调用产生的函数。这样的函数不会被用作constructor。

Object.getPrototypeOf(f.prototype) !== Object.prototype ,如果f.prototype的原型不是Object.prototype,就说明是执行了 f.prototype = new Class() 或者是 f.prototype = Object.create(proto)。因此可以认定f是要被用做constructor。

剩下f.prototype的原型确实是Object.prototype的情形。而Object.prototype上属性的增减与我们的判断无关。这样我们就不必上溯原型链,依旧使用之前的方法。

Object.getOwnPropertyNames(f.prototype).length > 1 ,即如果有1个以上的own properties。

!('constructor' in f.prototype)) ,即如果有1个own property但不是'constructor',或者干脆1个own property也没有。


上述所有情况排除之后剩下的就是:

原始function(){}的prototype值是一个new Object()结果,且其上定义了constructor属性。

唯一没考虑的是仅仅修改了constructor值使其不指向自身的情况。

考虑这样的代码:
function F() {}
F.prototype = {constructor: A}
var x = new F()

看上去也是要作为构造器,可是什么场景下需要把constructor设为另一个函数A呢?

(注:我们可以忽略constructor值被设为非函数的情形,因为那样是break了JS的惯例,所以也不必将其视作合理的Constructor)

我想大概是这样的场景,就是给client code一个指示,如果你要创建一个类似对象x的对象,应该调用new A()。这样的场景在涉及元编程时确实存在。

对于这样的情况,我觉得likeConstructor返回false是合理的,因为F在这里扮演的角色只是一个代孕妈妈(代理对象生产这一任务),当然更好的方式实际是用Object.create()。

那么是否要把 f.prototype.constructor === f 作为一个强制约定呢。经过一番思考,我决定不这样做。因为这会导致一些设置B.prototype = new A()后忘记设置B.prototype.constructor = B的代码被认为likeConstructor(B)为false。而这样的可能性是挺大的。无法表明自己是妈妈身份(黑户),和故意放弃妈妈身份(代孕)还是要区别对待的。

以上。欢迎大家继续提意见。




分享到:
评论
11 楼 enix2212 2011-07-27  
instaceof  不能么?
10 楼 yiminghe 2011-07-25  
hax 写道
yiminghe 写道
bind 构造器还是挺好玩的,this 不起作用,但是参数还是可以的


没有this还哪里有新对象呀!这咋做构造器呢?


指 bind 的参数 thisArg没用
9 楼 hax 2011-07-25  
yiminghe 写道
bind 构造器还是挺好玩的,this 不起作用,但是参数还是可以的


没有this还哪里有新对象呀!这咋做构造器呢?
8 楼 yiminghe 2011-07-25  
bind 构造器还是挺好玩的,this 不起作用,但是参数还是可以的
7 楼 yiminghe 2011-07-25  
bind 的为啥不能作为构造器(不推荐?),mdc上有个构造器的例子
6 楼 hax 2011-07-25  
光写代码,还没有来得及测试,呵呵。可能in的那个判断是有问题,我再推敲推敲。

limu 写道

function Person(name){
	this.name = name;
}
alert(likeConstructor(Person));//chrome false
var p1 = new Person("a");

这是个小问题,如果没有prototype上的动作应该直接var p2 = {name:"a"}.
但也不能说这个写法完全没有意义.一方面,可能后续会添加OwnProperty,
另一方面,console.log(p1)控制台会明确告诉p1是一个Person,而log(p2)则肯定是Object.

之所以测试了这个,你在最初提出这个问题的时候,我在想constructor的一个特点,构造函数体内常常有this引用.而普通function一般不会.


你说的这个情况,一开始就考虑过。如果不修改prototype,全部在构造器里通过this引用添加属性,确实也是可行的,检测不出来。但这样的构造器本质上就无法准确检测出来,因为它和一般的method无法区分(method里有都有this引用),除非你认为函数名大写可以表明它是“构造器”,这又回到了命名约定的路子上去了。

或许还是应该把名字改回为likeClass()?这样可认为不带有prototype的不like Class?
5 楼 limu 2011-07-24  
----汗:有句话乱了,重新发下----

按你的思路似乎最后一个语句应该改成.
!(f.prototype.hasOwnProperty("constructor")).
来说明只有一个OwnProperty且不是constructor.
是这样不?

4 楼 limu 2011-07-24  
function Person(name){
	this.name = name;
}

Person.prototype = {
	getName : function(){
		return this.name;
	}
}
//Person.prototype.constructor = Person;//注释掉这一行
alert(likeConstructor(Person));//chrome false

上面的测试没有成功,Object.getOwnPropertyNames(f.prototype)返回["getName"].length=1而不是>1.
看起来('constructor' in f.prototype)这句即使constructor不能enumerable但这个语句依然返回真.
别的浏览器没有试过,在chrome下,按你的思路似乎这最后的语句应该改成,来说明只有一个OwnProperty且不是constructor:!(f.prototype.hasOwnProperty("constructor")).
是这样么?
==============
function Person(name){
	this.name = name;
}
alert(likeConstructor(Person));//chrome false
var p1 = new Person("a");

这是个小问题,如果没有prototype上的动作应该直接var p2 = {name:"a"}.
但也不能说这个写法完全没有意义.一方面,可能后续会添加OwnProperty,
另一方面,console.log(p1)控制台会明确告诉p1是一个Person,而log(p2)则肯定是Object.

之所以测试了这个,你在最初提出这个问题的时候,我在想constructor的一个特点,构造函数体内常常有this引用.而普通function一般不会.

3 楼 jkisjk 2011-07-21  
isClass看起来是个严谨的方法,但是由于js里Class是个没有规范的模糊的东东,所以叫isClass让人不大明白目的是什么。
----如果没Class基类,这名字含义模糊;如果已有Class基类,判断也不应该是这么写。

可以换一个没歧义的名字,如
var hasProtoProperty=function(f){
for(var i in f.prototype) return true;
return false;
};
alert(hasProtoProperty(hasProtoProperty));
alert(hasProtoProperty(document.body.constructor));
2 楼 jkisjk 2011-07-21  
js里Class是个模糊的东东,还没有规范起来。isClass
1 楼 jkisjk 2011-07-21  
keys得不到'constructor'吧,莫非是什么神奇的浏览器?

相关推荐

    构建一个类Point,它提供两个公有的构造函数,一个没有参数的Point构造函数和一个有两个double参数的构造函数。

    构建一个类Point,它提供两个公有的构造函数,一个没有参数的Point构造函数和一个有两个double参数的构造函数。另外在该类中提供一个静态方法计算两个点的直线距离,传入参数为两个Point类实例。然后设计一个测试类...

    继承类的构造函数

    也就是说,希望在执行派生类的构造函数时,使派生类的数据成员和基类的数据成员同时都被初始化。解决这个问题的思路是,在执行派生类的构造函数时,调用基类的构造函数。 在简单的派生类中,定义构造函数时需要考虑...

    构造函数与析构函数

    构造函数是一个与类同名的方法,可以没有参数,有一个参数或多个参数,但是构造函数没有返回值。如果构造函数没有参数,该函数被称为类的默认构造函数。 (1) 一个类可以包含多个构造函数,各个构造函数之间通过...

    判断是否为闰年,包含两个不同的构造函数

    根据给定的信息,本文将详细解释“判断是否为闰年,包含两个不同的构造函数”的相关知识点,包括如何定义一个日期类、如何实现读取和设置年、月、日的方法、如何设计不同类型的构造函数以及如何编写一个判断是否为...

    详解C++中构造函数,拷贝构造函数和赋值函数的区别和实现

    构造函数是一种特殊的类成员函数,是当创建一个类的对象时,它被调用来对类的数据成员进行初始化和分配内存。(构造函数的命名必须和类名完全相同) 首先说一下一个C++的空类,编译器会加入哪些默认的成员函数 默认...

    构造函数不能为虚函数的理由

    这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。 但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置 V PTR 指向它自己的 V TABLE。如果函数调用使用虚机制,它将只产生通过它自己的 ...

    聚合中类的构造函数调用顺序

    在聚合关系中,如果一个类A包含一个类B的对象作为其成员变量,那么在创建类A的对象时,类B的构造函数也会被调用。这个调用顺序遵循一定的规则: 1. **基类构造器的调用**:如果类A继承自类B,那么在类A的构造函数...

    C++简单类(构造函数,析构函数以及拷贝构造函数)的实现

    本文将详细解析一个简单的C++类`cPerson`的实现,重点关注其构造函数、析构函数以及拷贝构造函数。 ### 构造函数 构造函数是在创建类的对象时自动调用的一种特殊成员函数,主要用于初始化对象的数据成员。`cPerson...

    构造、析构函数

    例如,考虑`Location`类,它定义了三个构造函数:无参构造函数、一个接受单个整数参数的构造函数以及一个接受两个整数参数的构造函数。 ```cpp class Location { private: int X, Y; public: Location(); // 无参...

    通讯录 c++ 构造函数 类

    构造函数是类的一个特殊函数,用于初始化新创建的对象。在通讯录类中,构造函数可以初始化空的联系人列表,如下所示: ```cpp AddressBook() : contacts{} {} ``` 这个构造函数不接受任何参数,但确保`contacts...

    c#构造函数的调用过程

    接下来,定义了一个派生自`A`的类`B`,它增加了一个新的整型成员变量`y`,并且也提供了三个构造函数:默认构造函数、带一个整型参数的构造函数以及同时接受两个整型参数的构造函数。最后一个构造函数调用了基类的...

    定义一个名为complex 的复数类,其属性数据为复数的实部和虚部,要求构造函数和拷贝构造函数,并能打印复数的值

    以上就是定义一个`complex`复数类的基本步骤,包括构造函数、拷贝构造函数和打印功能。在实际项目中,我们可能还需要为这个类添加其他功能,比如复数的加法、减法、乘法和除法等操作,以及重载相应的运算符,以提高...

    C++类对象的拷贝构造函数

    拷贝构造函数的工作过程可以理解为:当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。例如,在以下代码中,CA(const CA& C)就是我们自定义的拷贝构造函数: ...

    包含构造函数和析构函数的C++程序

    构造函数可以被重载,这意味着一个类可以有多个构造函数,但每个构造函数的参数列表必须不同。 在提供的代码示例中,定义了一个名为`Student`的类,其中包含了一个构造函数: ```cpp Student(int n, string nam, ...

    C++实现 类string的 普通构造函数, 拷贝构造函数 析构函数 和赋值函数

    拷贝构造函数是一个特殊的构造函数,当一个新对象被创建为已存在对象的副本时调用。对于`std::string`类,拷贝构造函数将确保源对象的所有数据都被深拷贝到新对象中,防止浅拷贝导致的数据共享问题。例如: ```cpp ...

    析构或构造函数声明为保护或私有成员

    但是,类 A 的静态成员函数 Instance 可以调用构造函数,并返回一个单例对象。 4. 禁止对象的复制 将拷贝构造函数和 operator=(赋值操作符重载)声明为私有成员,可以禁止外部用户对对象进行复制动作。例如: ```...

    C++复制构造函数详解

    当一个已存在的对象被用作创建新对象的初始值时,复制构造函数会被调用。理解复制构造函数的工作原理及其应用是C++程序员必须掌握的基础知识。 复制构造函数的定义通常具有以下形式: ```cpp ClassName(const ...

    C++派生类的构造函数

    7. 多重继承与构造函数:如果派生类从多个基类派生,那么在派生类的构造函数中需要为每一个基类调用相应的构造函数。 8. 虚构造函数:C++不支持虚构造函数,这意味着在派生类的指针或引用上调用构造函数将不会调用...

    类构造函数与析构函数

    在C++中,每个类都必须有一个默认的构造函数,如果没有提供构造函数,那么C++将自动提供一个默认的构造函数。构造函数的主要作用是将类对象的成员变量初始化为默认值或指定值。 在C++中,构造函数的定义必须和类名...

Global site tag (gtag.js) - Google Analytics