JavaScript构造函数与constructor属性深度解析及Ext.extend陷阱
本文深入解析JavaScript中constructor属性的指向机制,指出其实际指向构造函数的prototype.constructor。结合Ext.extend源码分析,揭示重写constructor参数在不同版本下的行为差异,提醒开发者注意继承中constructor指向维护及API使用细节,避免常见误区。
JavaScript构造函数与constructor属性深度解析及Ext.extend陷阱
例如,在Chrome浏览器中调试以下程序,可以清晰地展示这一现象:
然而,情况并非如此简单。请观察下面的代码:
constructor属性指向机制与继承问题
显然,此时obj的constructor属性已不再指向创建它的函数。注意到obj.name同样为undefined,这表明直接修改构造函数的prototype的constructor属性,并不会影响由该构造函数生成的对象。根本原因在于:对象的constructor属性指向其构造函数的prototype.constructor。每个函数都拥有一个prototype对象,默认情况下,该prototype包含一个constructor属性,指向函数自身。JavaScript的设计初衷似乎是让每个对象的constructor都指向其构造函数,但上述示例破坏了这一规则。此外,这种设计在继承场景中并不完美,主要问题在于必须谨慎维护constructor的指向。在最简单的继承模式中,若将子类的prototype设置为父类的一个实例,由于父类实例的constructor指向父类构造函数,导致子类的prototype的constructor也指向父类构造函数,进而使子类的每个实例的constructor都指向父类构造函数。这极易引发混淆。
最后,回顾上一篇遗留的问题,即Extjs官网给出的一个继承Observable的例子:
代码如下:
Employee = Ext.extend(Ext.util.Observable, {
constructor: function(config){
this.name = config.name;
this.addEvents({
"fired" : true,
"quit" : true
});
// Copy configured listeners into *this* object so that the base class's
// constructor will add them.
this.listeners = config.listeners;
// Call our superclass constructor to complete construction process.
Employee.superclass.constructor.call(config)
}
}); Ext.extend源码分析与API使用陷阱
这个例子容易给人造成一种错觉,即可以通过重写父类的constructor属性来改变子类的构造函数行为。这对JavaScript基础不深的开发者而言是一种误导。让我们仔细查看Ext.extend的源代码:
代码如下:
extend : function(){
// inline overrides
var io = function(o){
for(var m in o){
this[m] = o[m];
}
};
var oc = Object.prototype.constructor;
return function(sb, sp, overrides){
if(Ext.isObject(sp)){
overrides = sp;
sp = sb;
sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);}; //注意这里 }
//以下省略………}(), 请注意加注释的那一行。如果extend检测到overrides参数中包含constructor属性,即子类试图重写父类prototype的constructor,它会将子类直接设置为该函数!从而达到预期效果。但我立即发现,此检测逻辑仅存在于if语句块内,即仅适用于extend的两参数版本。若使用extend的三参数版本,这种设置应无效。编写代码进行测试:
<head>
<title></title>
<script type="text/javascript" src="ext-3.1.0/src/core/core/Ext.js"></script>
<script type="text/javascript">
function MyClass() {
this.id = 'class';
}
function SubClass() { }
// SubClass=Ext.extend(SubClass, MyClass, { constructor: function() {
// this.overrideProperty = 'OK';
// }
// });
SubClass = Ext.extend(MyClass, { constructor: function() {
this.overrideProperty = 'OK';
}
});
var obj = new SubClass();
alert(obj.overrideProperty);
</script>
</head> 两种写法的运行结果证实:第一种返回undefined,第二种返回OK。名称相同的函数仅因参数写法不同,执行效果却大相径庭,这出乎意料。且Extjs官方文档对此未作任何说明。建议今后尽量使用两参数版本。
Function构造函数的自我引用与总结
最后,再观察几个表达式:
第一个指出函数的prototype的constructor指向自身,前文已述,无误;第二个说明函数也是对象,它是Function构造函数的一个实例,易于理解;第三个指出Function构造函数本身也是对象,同样是Function构造函数的一个实例;最后一个与第三个等价,指出Function对象的constructor指向其自身…… 我确实难以理解Function是如何自我构造的?这仿佛是鸡生蛋、蛋生鸡的问题。若要深入探究,可能需涉及JavaScript语言的具体实现,此处暂且搁置。
唉,JavaScript语言本身确实复杂。若非XMLHttpObject使其大红大紫,否则它可能仍在角落中默默无闻。
作为应用程序开发人员,或许我们日常编码较少触及这些底层细节,但JS框架中大量使用了这些高级特性。再详尽的文档也无法逐一阐明所有方法的作用与影响,因此开发人员仍需尽量理解这些机制,以免在遇到问题时不知所措。上述Ext.extend函数即为典型例子。
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
