Javascript 构造函数属性的意义是什么?

试图绕过 Javascript 的面向对象... ... 并且,像许多其他人一样,陷入了关于 constructor属性的混乱。特别是 constructor属性的重要性,因为我似乎不能使它产生任何效果。例如:

function Foo(age) {
this.age = age;
}


function Bar() {
Foo.call(this, 42);
this.name = "baz";
}


Bar.prototype = Object.create(Foo.prototype);
var b = new Bar;


alert(b.constructor); // "Foo". That's OK because we inherit `Foo`'s prototype.
alert(b.name);        // "baz". Shows that Bar() was called as constructor.
alert(b.age);         // "42", inherited from `Foo`.

In the above example, the object b seems to have had the right constructor called (Bar) – and it inherits the age property from Foo. So why do many people suggest this as a necessary step:

Bar.prototype.constructor = Bar;

显然,正确的 Bar构造函数 曾经是在构造 b时调用,那么这个原型属性有什么影响呢?我很想知道“正确”设置构造函数属性实际上有什么不同ーー因为我看不出它对创建对象后实际调用哪个构造函数有任何影响。

19151 次浏览

2020年9月最新情况

下面的答案来自 ECMAScript 3的时代,第一句话已经不再正确,因为自从 ECMAScript 6以来,constructor属性只在少数地方使用。然而,我认为总的要点仍然适用。感谢 T.J。 Crowder 在评论中指出了这一点,请阅读他的答案,以便更全面地了解当前的形势。

原始答案

constructor属性对内部的任何东西都没有实际的影响。只有在代码显式使用它时,它才有用。例如,您可能决定需要每个对象都有一个对创建它的实际构造函数的引用; 如果是这样的话,您需要在设置继承时显式地设置 constructor属性,方法是将一个对象分配给构造函数的 prototype属性,如您的示例所示。

第一步是了解 constructorprototype的全部内容。这并不困难,但人们必须放弃传统意义上的“继承”。

构造函数

constructor属性 没有会在程序中产生任何特定的效果,但是您可以查看它,看看是哪个函数与操作符 new一起使用来创建对象。如果你输入 new Bar(),它将是 Bar,你输入 new Foo,它将是 Foo

原型机

如果有问题的对象没有要求的属性,则使用 prototype属性进行查找。如果编写 x.attr,JavaScript 将尝试在 x的属性中找到 attr。如果它找不到它,它将在 x.__proto__中寻找。如果它也不存在,那么只要定义了 __proto__,它就会在 x.__proto__.__proto__中查找,以此类推。

那么什么是 __proto__? 它和 prototype有什么关系?简而言之,prototype表示“类型”,而 __proto__表示“实例”。(我之所以这样说,是因为类型和实例之间实际上没有什么区别)。当您编写 x = new MyType()时,发生的(除了其他事情之外)是 x.__proto___被设置为 MyType.prototype

那个问题

Now, the above should be all you need to derive what your own example means, but to try and answer your actual question; "why write something like":

Bar.prototype.constructor = Bar;

我个人从来没有见过它,我觉得它有点傻,但在上下文中,你已经给它将意味着 Bar.prototype-对象(创建使用 new Foo(42))将作为已创建的 Bar而不是 Foo。我认为这个想法是创建一些类似于 C + +/Java/C # 的语言,其中类型查找(constructor属性)总是产生最具体的类型,而不是原型链中更上一层的更通用对象的类型。

我的建议是: 不要过多考虑 JavaScript 中的“继承”。接口和混合的概念更有意义。不要检查对象的类型。而是检查所需的属性(“如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那它就是鸭子”)。

试图强制 JavaScript 进入经典的继承模型,而它所拥有的只是上面描述的原型机制,这就是导致混乱的原因。许多建议手动设置 constructor属性的人可能正在尝试这样做。抽象是可以的,但是这种构造函数属性的手动分配并不是 JavaScript 的惯用方法。

使用构造函数的一种情况:

  1. 这是继承的共同实现之一:

    Function.prototype.extend = function(superClass,override) {
    var f = new Function();
    f.prototype = superClass.prototype;
    var p = this.prototype = new f();
    p.constructor = this;
    this.superclass = superClass.prototype;
    ...
    };
    
  2. this new f() would not call the constructor of superClass,so when you create a subClass,maybe you need call the superClass at first,like this:

    SubClass = function() {
    SubClass.superClass.constructor.call(this);
    };
    

so the constructor property make sense here.

构造函数属性指向用于创建对象实例的构造函数。如果你输入‘ new Bar ()’,它就是‘ Bar’,而你输入‘ new Foo ()’,它就是‘ Foo’。

但是如果你设置了原型而没有设置构造函数,你会得到这样的结果:

function Foo(age) {
this.age = age;
}


function Bar() {
this.name = "baz";
}


Bar.prototype = new Foo(42);
var one = new Bar();
console.log(one.constructor);   // 'Foo'
var two = new Foo();
console.log(two.constructor);   // 'Foo'

要将构造函数实际设置为用于创建对象的构造函数,我们还需要设置构造函数,同时设置原型如下:

function Foo(age) {
this.age = age;
}


function Bar() {
this.name = "baz";
}


Bar.prototype = new Foo(42);
Bar.prototype.constructor = Bar;
var one = new Bar();
console.log(one.constructor);   // 'Bar'
var two = new Foo();
console.log(two.constructor);   // 'Foo'

希望 prototype.constructor属性在 prototype属性重新分配后仍然存在的用例之一是,在 prototype上定义一个方法,该方法生成与给定实例相同类型的新实例。例如:

function Car() { }
Car.prototype.orderOneLikeThis = function() {  // Clone producing function
return new this.constructor();
}
Car.prototype.advertise = function () {
console.log("I am a generic car.");
}


function BMW() { }
BMW.prototype = Object.create(Car.prototype);
BMW.prototype.constructor = BMW;              // Resetting the constructor property
BMW.prototype.advertise = function () {
console.log("I am BMW with lots of uber features.");
}


var x5 = new BMW();


var myNewToy = x5.orderOneLikeThis();


myNewToy.advertise(); // => "I am BMW ..." if `BMW.prototype.constructor = BMW;` is not
// commented; "I am a generic car." otherwise.

这里前面的答案(以不同的方式)表明,在 JavaScript 中,constructor属性的值不会被任何东西使用。在写这些答案的时候确实是这样,但是 ES2015以及以后的课程已经开始使用 constructor了。

函数的 prototype属性的 constructor属性意味着指向该函数,以便您可以询问对象是如何构造它的。它是作为创建传统函数对象或类构造函数对象(details)的一部分自动设置的。

function TraditionalFunction() {
}


console.log(TraditionalFunction.prototype.constructor === TraditionalFunction); // true


class ExampleClass {
}


console.log(ExampleClass.prototype.constructor === ExampleClass); // true

箭头函数没有 prototype属性,所以它们没有 prototype.constructor

For years the JavaScript specification only said that the constructor property would be there and have that value (a link back to the function) by default. But starting in ES2015, that changed, and various operations in the specification now actually use the constructor property, such as 这个, 这个, 这个, and 这个.

因此,在设置构造函数来构建继承链时,最好确保 constructor属性引用适当的函数。例如,请参阅 我的答案就在这里