JavaScript 继承和构造函数属性
Posted
技术标签:
【中文标题】JavaScript 继承和构造函数属性【英文标题】:JavaScript inheritance and the constructor property 【发布时间】:2011-12-26 22:36:39 【问题描述】:考虑下面的代码。
function a()
function b()
function c()
b.prototype = new a();
c.prototype = new b();
console.log((new a()).constructor); //a()
console.log((new b()).constructor); //a()
console.log((new c()).constructor); //a()
为什么没有为 b 和 c 更新构造函数?
我是不是继承错了?
更新构造函数的最佳方法是什么?
此外,请考虑以下事项。
console.log(new a() instanceof a); //true
console.log(new b() instanceof b); //true
console.log(new c() instanceof c); //true
假设(new c()).constructor
等于a()
并且Object.getPrototypeOf(new c())
等于a
,那么instanceof
怎么可能知道new c()
是c
的一个实例?
http://jsfiddle.net/ezZr5/
【问题讨论】:
有什么理由需要更新构造函数?如果我只是假装财产不存在,我会发现我的生活会更轻松。 我很难将其作为副本关闭 - 所有其他 questinos 都如此冗长......c.prototype
是 b()
和 b.prototype
是 a()
,因此 c.prototype
是 a()
@pimvdb 是对的,需要手动设置构造函数。为了子孙后代 - 这是一个基于 Stoyan Stefanovs “Javacript Patterns”的伪经典继承模式的小提琴:jsfiddle.net/sunwukung/YXxG6
这是一个有用的小提琴,但你的 Child.hasOwnProperty('name')
有点模棱两可 - 它返回 true
因为函数总是有一个 .name
属性(在这种情况下,"Child"
)。我不确定这是否是您的意思(不是Parent
设置的name
,因为构造函数没有设置该属性)。
【参考方案1】:
好吧,我们来玩个小游戏:
从上图我们可以看出:
-
当我们创建像
function Foo()
这样的函数时,javascript 会创建一个 Function
实例。
每个Function
实例(构造函数)都有一个属性prototype
,它是一个指针。
构造函数的prototype
属性指向其原型对象。
原型对象有一个属性constructor
,它也是一个指针。
原型对象的constructor
属性指向其构造函数。
当我们创建 Foo
的新实例(如 new Foo()
)时,JavaScript 会创建一个新对象。
实例的内部[[proto]]
属性指向构造函数的原型。
现在,问题出现了,为什么 JavaScript 不将 constructor
属性附加到实例对象而不是原型。考虑:
function defclass(prototype)
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
var Square = defclass(
constructor: function (side)
this.side = side;
,
area: function ()
return this.side * this.side;
);
var square = new Square(10);
alert(square.area()); // 100
如您所见,constructor
属性只是原型的另一种方法,如上例中的area
。 constructor
属性的特别之处在于它用于初始化原型的实例。否则它与原型的任何其他方法完全相同。
在原型上定义constructor
属性是有利的,原因如下:
-
逻辑上是正确的。例如考虑
Object.prototype
。 Object.prototype
的 constructor
属性指向 Object
。如果在实例上定义了constructor
属性,那么Object.prototype.constructor
将是undefined
,因为Object.prototype
是null
的一个实例。
它的处理方式与其他原型方法没有区别。这使得new
的工作更容易,因为它不需要在每个实例上定义constructor
属性。
每个实例共享相同的constructor
属性。因此它很有效。
现在当我们谈论继承时,我们有以下场景:
从上图我们可以看出:
-
派生构造函数的
prototype
属性设置为基构造函数的实例。
因此派生构造函数实例的内部[[proto]]
属性也指向它。
因此,派生构造函数实例的constructor
属性现在指向基构造函数。
至于instanceof
运算符,与普遍的看法相反,它不依赖于实例的constructor
属性。从上面我们可以看出,这会导致错误的结果。
instanceof
运算符是二元运算符(它有两个操作数)。它对实例对象和构造函数进行操作。正如Mozilla Developer Network 上的解释,它只是执行以下操作:
function instanceOf(object, constructor)
while (object != null)
if (object == constructor.prototype) //object is instanceof constructor
return true;
else if (typeof object == 'xml') //workaround for XML objects
return constructor.prototype == XML.prototype;
object = object.__proto__; //traverse the prototype chain
return false; //object is not instanceof constructor
简单地说,如果Foo
继承自Bar
,那么Foo
实例的原型链将是:
foo.__proto__ === Foo.prototype
foo.__proto__.__proto__ === Bar.prototype
foo.__proto__.__proto__.__proto__ === Object.prototype
foo.__proto__.__proto__.__proto__.__proto__ === null
如您所见,每个对象都继承自 Object
构造函数。当内部 [[proto]]
属性指向 null
时,原型链结束。
instanceof
函数简单地遍历实例对象的原型链(第一个操作数)并将每个对象的内部[[proto]]
属性与构造函数的prototype
属性(第二个操作数)进行比较。如果它们匹配,则返回true
;否则如果原型链结束,则返回false
。
【讨论】:
+1 我更喜欢Object.getPrototypeOf
而不是.__proto__
。
为了解释,我只使用了__proto__
属性。这就是 Mozilla Developer Network 上的解释方式。尽管如此,__proto__
属性确实比Object.getPrototypeOf
方法的优势在于它更快(没有函数调用开销)并且它被所有主要浏览器实现。我使用Object.getPrototypeOf
的唯一原因是为了解决像Rhino 这样不支持__proto__
属性的实现。我们都有自己的喜好。我更喜欢__proto__
属性,因为它更具可读性。干杯。 =)
2 个问题 - 1. 基本构造函数总是对象吗?我很难在代码中证明这一点。 :( 它总是指向 Object 之后的第一个构造函数。 2. 当我们链接 proto 时,我们正在解除对原始原型对象的引用。难道我们不会从这些原型对象中收到任何属性,除了对象原型?在所有原型都附加到实例之后。
@CodeHater 为什么箭头会指向相反的方向?【参考方案2】:
默认情况下,
function b()
那么b.prototype
有一个.constructor
属性,该属性自动设置为b
。但是,您当前正在覆盖原型并因此丢弃该变量:
b.prototype = new a;
然后b.prototype
不再有.constructor
属性;它被覆盖删除。它确实继承自 a
和 (new a).constructor === a
,因此继承自 (new b).constructor === a
(它指的是原型链中的相同属性)。
最好的办法是事后手动设置:
b.prototype.constructor = b;
你也可以为此做一个小函数:
function inherit(what, from)
what.prototype = new from;
what.prototype.constructor = what;
http://jsfiddle.net/79xTg/5/
【讨论】:
没错。但是你不会知道某个对象是继承的。当您使用$.extend
时,new b() instanceof a
返回 false
,如果他需要检查继承,在 OP 的情况下这可能是不希望的。
@Robert Koritnik:好点,但b instanceof b
总是错误的,因为构造函数不是它自己的实例。扩展功能似乎确实适用于new b instanceof b
,除非我误解了你:jsfiddle.net/79xTg/3。
我编辑了我的评论。这当然是一个错字。让我编辑你的例子:jsfiddle.net/79xTg/4$.extend
不做继承,而是prototype
复制。因此,您无法使用它检查继承。
@Robert Koritnik:你是对的。由于扩展不是为了继承目的,我删除了它。【参考方案3】:
constructor
是函数对象prototype
属性默认值的常规、不可枚举属性。因此,分配给prototype
将失去该属性。
instanceof
仍然可以工作,因为它不使用constructor
,而是扫描对象的原型链以查找函数的prototype
属性的(当前)值,即foo instanceof Foo
等效于
var proto = Object.getPrototypeOf(foo);
for(; proto !== null; proto = Object.getPrototypeOf(proto))
if(proto === Foo.prototype)
return true;
return false;
在 ECMAScript3 中,无法设置行为与内置属性相同的 constructor
属性,因为用户定义的属性始终是可枚举的(即对 for..in
可见)。
这在 ECMAScript5 中有所改变。但是,即使您手动设置 constructor
,您的代码仍然存在问题:特别是,将 prototype
设置为父类的实例是一个坏主意 - 当child-'class' 是定义的,而是在创建子实例时定义的。
这里有一些 ECMAScript5 示例代码,说明应该如何完成:
function Pet(name)
this.name = name;
Pet.prototype.feed = function(food)
return this.name + ' ate ' + food + '.';
;
function Cat(name)
Pet.call(this, name);
Cat.prototype = Object.create(Pet.prototype,
constructor :
value : Cat,
writable : true,
enumerable : false,
configurable : true
);
Cat.prototype.caress = function()
return this.name + ' purrs.';
;
如果您无法使用 ECMAScript3,则需要使用自定义 clone()
function 而不是 Object.create()
,并且无法使 constructor
不可枚举:
Cat.prototype = clone(Pet.prototype);
Cat.prototype.constructor = Cat;
【讨论】:
以上是关于JavaScript 继承和构造函数属性的主要内容,如果未能解决你的问题,请参考以下文章