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()) 等于,那么instanceof 怎么可能知道new c()c 的一个实例?

http://jsfiddle.net/ezZr5/

【问题讨论】:

有什么理由需要更新构造函数?如果我只是假装财产不存在,我会发现我的生活会更轻松。 我很难将其作为副本关闭 - 所有其他 questinos 都如此冗长...... c.prototypeb()b.prototypea(),因此 c.prototypea() @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 属性只是原型的另一种方法,如上例中的areaconstructor 属性的特别之处在于它用于初始化原型的实例。否则它与原型的任何其他方法完全相同。

在原型上定义constructor 属性是有利的,原因如下:

    逻辑上是正确的。例如考虑Object.prototypeObject.prototypeconstructor 属性指向 Object。如果在实例上定义了constructor 属性,那么Object.prototype.constructor 将是undefined,因为Object.prototypenull 的一个实例。 它的处理方式与其他原型方法没有区别。这使得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 继承和构造函数属性的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript对象继承方式

javascript 重置继承的构造函数属性

JavaScript构造函数和原型原型链及this指向

Javascript中实现继承的方式

JavaScript继承的6种方式

JavaScript 的对象继承方式,有几种写法?