JavaScript继承和构造函数属性

JavaScript继承和构造函数属性,javascript,inheritance,constructor,instanceof,Javascript,Inheritance,Constructor,Instanceof,考虑以下代码 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的构造函数没有更新 我做错

考虑以下代码

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()
    对象。getPrototypeOf(new c())
    a{}
    ,那么
    instanceof
    怎么可能知道
    new c()
    c
    的实例呢

默认情况下

function b() {}
然后
b.prototype
有一个
.constructor
属性,该属性自动设置为
b
。但是,您当前正在覆盖原型,从而丢弃该变量:

b.prototype = new a;
然后
b.prototype
不再具有
.constructor
属性;它被覆盖删除了。它确实继承自
a
,并且
(新的a)。构造函数===a
,因此
(新的b)。构造函数===a
(它在原型链中引用相同的属性)

最好的方法是在之后手动设置:

b.prototype.constructor = b;
您还可以为此制作一个小功能:

function inherit(what, from) {
    what.prototype = new from;
    what.prototype.constructor = what;
}

构造函数
是函数对象的
原型
属性的默认值的常规、不可枚举属性。因此,分配给
prototype
将丢失该属性

instanceof
仍将工作,因为它不使用
构造函数
,而是扫描对象的原型链,查找函数的
prototype
属性的(当前)值,即
foo instanceof foo
等价于

var proto = Object.getPrototypeOf(foo);
for(; proto !== null; proto = Object.getPrototypeOf(proto)) {
    if(proto === Foo.prototype)
        return true;
}
return false;
在ECMAScript 3中,无法设置与内置属性行为相同的
构造函数
属性,因为用户定义的属性始终是可枚举的(即对
中的..可见)

ECMAScript 5改变了这一点。但是,即使您手动设置
构造函数
,您的代码仍然存在问题:特别是,将
原型
设置为父类的实例是一个坏主意-定义子类时不应调用父类构造函数,而应在创建子实例时调用父类构造函数

下面是一些ECMAScript 5示例代码,介绍了如何执行此操作:

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.';
};
如果坚持使用ECMAScript 3,则需要使用自定义对象,而不是
对象。create()
,并且无法使
构造函数
不可枚举:

Cat.prototype = clone(Pet.prototype);
Cat.prototype.constructor = Cat;

好的,让我们玩一个小智力游戏:

从上图中我们可以看到:

  • 当我们创建像
    function Foo(){}
    这样的函数时,JavaScript会创建一个
    function
    实例
  • 每个
    函数
    实例(构造函数)都有一个属性
    原型
    ,它是一个指针
  • 构造函数的
    prototype
    属性指向它的prototype对象
  • 原型对象有一个属性
    构造函数
    ,它也是一个指针
  • 原型对象的
    构造函数
    属性指向其构造函数
  • 当我们创建
    Foo
    的新实例时,比如
    newfoo()
    ,JavaScript会创建一个新对象
  • 实例的内部
    [[proto]]
    属性指向构造函数的原型
  • 现在,问题出现了,为什么JavaScript不将
    构造函数
    属性附加到实例对象而不是原型。考虑:

    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
    
    正如您所看到的,
    构造函数
    属性只是原型的另一种方法,就像上面示例中的
    区域
    构造函数
    属性的特殊之处在于它用于初始化原型的实例。否则,它与原型的任何其他方法完全相同

    在原型上定义
    构造函数
    属性是有利的,原因如下:

  • 逻辑上是正确的。例如,考虑<代码>对象。原型< /COD>。
    对象的
    构造函数
    属性。原型
    指向
    对象
    。如果在实例上定义了
    constructor
    属性,则
    Object.prototype.constructor
    将是
    未定义的
    ,因为
    Object.prototype
    null
    的实例
  • 它与其他原型方法没有区别。这使得
    new
    的工作更容易,因为它不需要在每个实例上定义
    constructor
    属性
  • 每个实例共享相同的
    构造函数
    属性。因此它是有效的
  • 现在,当我们谈论继承时,我们有以下场景:

    从上图中我们可以看到:

  • 派生构造函数的
    prototype
    属性设置为基本构造函数的实例
  • 因此,派生构造函数实例的内部
    [[proto]]
    属性也指向它
  • 因此,派生构造函数实例的
    构造函数
    属性现在指向基构造函数
  • 至于
    instanceof
    操作符,与流行的观点相反,它不依赖于实例的
    构造函数
    属性。从上面可以看出,这将导致错误的结果

    instanceof
    运算符是一个二进制运算符(它有两个操作数)。它对实例对象和构造函数进行操作。如前所述,它只执行以下操作:

    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.\uuuu proto\uuuu==foo.prototype
  • foo.\uuuuuuuuuuuuuuuuuuuuuuuuu==Bar.prototype
  • foo.\uuuuu proto.\uuuuu proto.\uuuuuu proto.\uuuuuuuu__