简体   繁体   English

澄清javascript原型命名和机制

[英]Clarifying javascript prototype nomenclature and mechanism

Recognizing that JavaScript doesn't have the concept of class per se, and that the "type" of all objects is "object", I'm trying to get my head around just what a "prototype" consists of and, in particular, how its "name" is associated with it. 认识到JavaScript没有类本身的概念,并且所有对象的“类型”都是“对象”,我试图了解“原型”包含的内容,特别是它的“名称”如何与之相关联。 For example, in the following: 例如,在以下内容中:

function Foo(){};
console.log(Foo.prototype);                // => "Foo {}"

How does console.log know to output Foo before the braces and what is that name referring to? console.log如何知道在大括号之前输出Foo以及该名称是指什么?

(Note: I'm aware that in the above, I'm referring to the prototype property of functions and not the prototype per se (ie not the thing accessible by __proto__ ), but the same question applies to the actual prototype objects. I just used the prototype property to simplify my example.) (注意:我知道在上面,我指的是函数的原型属性而不是原型本身(即不是__proto__可以访问的东西),但同样的问题适用于实际的原型对象。只是使用了prototype属性来简化我的例子。)

Update: Based on the comment thread, this question is really focused on what Chrome is doing and, in particular, rationalizing its behavior in the following: 更新:基于评论主题,这个问题实际上集中在Chrome正在做什么,特别是在以下方面合理化其行为:

function Foo(){};
Foo.prototype.constructor = function Bar(){};
f = new Foo();
console.log(f);              // => Foo{} (remembering that f created by Foo, ignoring constructor)
console.log(Foo.prototype)   // => Bar{} (reporting constructor value)

See https://gist.github.com/getify/5793213 for more discussion. 有关更多讨论,请参阅https://gist.github.com/getify/5793213

JavaScript has a very twisted form of prototypal inheritance. JavaScript具有非常扭曲的原型继承形式。 I like to call it the constructor pattern of prototypal inheritance . 我喜欢称它为原型继承构造函数模式 There is another pattern of prototypal inheritance as well - the prototypal pattern of prototypal inheritance . 还有另一种原型继承模式 - 原型继承的原型模式 I'll explain the latter first. 我先解释后者。

In JavaScript objects inherit from objects. 在JavaScript中,对象继承自对象。 There's no need for classes. 没有必要上课。 This is a good thing. 这是一件好事。 It makes life easier. 它让生活更轻松。 For example say we have a class for lines: 例如,假设我们有一个行类:

class Line {
    int x1, y1, x2, y2;

    public:

    Line(int x1, int y1, int x2, int y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    int length() {
        int dx = x2 - x1;
        int dy = y2 - y1;
        return sqrt(dx * dx + dy * dy);
    }
}

Yes, this is C++. 是的,这是C ++。 Now that we created a class we may now create objects: 现在我们创建了一个类,现在我们可以创建对象:

Line line1(0, 0, 0, 100);
Line line2(0, 100, 100, 100);
Line line3(100, 100, 100, 0);
Line line4(100, 0, 0, 0);

These four lines form a square. 这四条线形成一个正方形。

JavaScript doesn't have any classes. JavaScript没有任何类。 It has prototypal inheritance. 它具有原型继承。 If you wanted to do the same thing using the prototypal pattern you would do this: 如果你想使用原型模式做同样的事情,你会这样做:

var line = {
    create: function (x1, y1, x2, y2) {
        var line = Object.create(this);
        line.x1 = x1;
        line.y1 = y1;
        line.x2 = x2;
        line.y2 = y2;
        return line;
    },
    length: function () {
        var dx = this.x2 - this.x1;
        var dy = this.y2 - this.y1;
        return Math.sqrt(dx * dx + dy * dy);
    }
};

Then you create instances of the object line as follows: 然后,您创建对象line实例,如下所示:

var line1 = line.create(0, 0, 0, 100);
var line2 = line.create(0, 100, 100, 100);
var line3 = line.create(100, 100, 100, 0);
var line4 = line.create(100, 0, 0, 0);

That's all there is to it. 这里的所有都是它的。 No confusing constructor functions with prototype properties. 没有令人困惑的构造函数与prototype属性。 The only function needed for inheritance is Object.create . 继承所需的唯一功能是Object.create This function takes an object (the prototype) and returns another object which inherits from the prototype. 此函数接受一个对象(原型)并返回另一个继承自原型的对象。

Unfortunately, unlike Lua, JavaScript endorses the constructor pattern of prototypal inheritance which makes it more difficult to understand prototypal inheritance. 不幸的是,与Lua不同,JavaScript支持原型继承的构造函数模式,这使得理解原型继承更加困难。 The constructor pattern is the inverse of the prototypal pattern. 构造函数模式是原型模式的反转。

  1. In the prototypal pattern objects are given the most importance. 在原型模式中,对象被赋予最重要的意义。 Hence it's easy to see that objects inherit from other objects. 因此,很容易看到对象继承自其他对象。
  2. In the constructor pattern functions are given the most importance. 在构造函数模式中,函数最重要。 Hence people tend to think that constructors inherit from other constructors. 因此人们倾向于认为构造函数继承自其他构造函数。 This is wrong. 这是错的。

The above program would look like this when written using the constructor pattern: 使用构造函数模式编写时,上面的程序看起来像这样:

function Line(x1, y1, x2, y2) {
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
}

Line.prototype.length = function () {
    var dx = this.x2 - this.x1;
    var dy = this.y2 - this.y1;
    return Math.sqrt(dx * dx + dy * dy);
};

You may now create instances of Line.prototype as follows: 您现在可以创建Line.prototype实例,如下所示:

var line1 = new Line(0, 0, 0, 100);
var line2 = new Line(0, 100, 100, 100);
var line3 = new Line(100, 100, 100, 0);
var line4 = new Line(100, 0, 0, 0);

Notice the similarity between the constructor pattern and the prototypal pattern? 注意构造函数模式和原型模式之间的相似性?

  1. In the prototypal pattern we simply create an object which has a create method. 在原型模式中,我们只需创建一个具有create方法的对象。 In the constructor pattern we create a function and JavaScript automatically creates a prototype object for us. 在构造函数模式中,我们创建了一个函数,JavaScript自动为我们创建了一个prototype对象。
  2. In the prototypal pattern we have two methods - create and length . 在原型模式中,我们有两种方法 - createlength In the constructor pattern too we have two methods - constructor and length . 在构造函数模式中,我们也有两个方法 - constructorlength

The constructor pattern is the inverse of the prototypal pattern because when you create a function JavaScript automatically creates a prototype object for the function. 构造函数模式是原型模式的反转,因为当您创建函数时,JavaScript会自动为函数创建prototype对象。 The prototype object has a property called constructor which points back to the function itself : prototype对象有一个名为constructor的属性,它指向函数本身

As Eric said, the reason console.log knows to output Foo is because when you pass Foo.prototype to console.log : 正如Eric所说, console.log知道输出Foo的原因是因为当你将Foo.prototype传递给console.log

  1. It finds Foo.prototype.constructor which is Foo itself. 它找到了Foo.prototype.constructor ,它本身就是Foo
  2. Every named function in JavaScript has a property called name . JavaScript中的每个命名函数都有一个名为name的属性。
  3. Hence Foo.name is "Foo" . 因此Foo.name"Foo" So it finds the string "Foo" on Foo.prototype.constructor.name . 所以它在Foo.prototype.constructor.name上找到字符串"Foo"

Edit: Alright, I understand that you have a problem with the redefining the prototype.constructor property in JavaScript. 编辑:好的,我知道您在JavaScript中重新定义prototype.constructor属性时遇到问题。 To understand the problem let's first understand how the new operator works. 要理解这个问题,我们首先要了解new运算符的工作原理。

  1. First, I want you to take a good look at the diagram I showed you above. 首先,我想让你好好看看我在上面展示的图表。
  2. In the above diagram we have a constructor function, a prototype object and an instance. 在上图中,我们有一个构造函数,一个原型对象和一个实例。
  3. When we create an instance using the new keyword before a constructor JS creates a new object. 当我们在构造函数JS创建新对象之前使用new关键字创建实例时。
  4. The internal [[proto]] property of this new object is set to point to whatever constructor.prototype points to at the time of object creation . 此新对象的内部[[proto]]属性设置为指向在创建对象时指向的constructor.prototype

What does this imply? 这意味着什么? Consider the following program: 考虑以下程序:

function Foo() {}
function Bar() {}

var foo = new Foo;

Foo.prototype = Bar.prototype;

var bar = new Foo;

alert(foo.constructor.name); // Foo
alert(bar.constructor.name); // Bar

See the output here: http://jsfiddle.net/z6b8w/ 请参阅此处的输出: http//jsfiddle.net/z6b8w/

  1. The instance foo inherits from Foo.prototype . 实例foo继承自Foo.prototype
  2. Hence foo.constructor.name displays "Foo" . 因此foo.constructor.name显示"Foo"
  3. Then we set Foo.prototype to Bar.prototype . 然后我们将Foo.prototype设置为Bar.prototype
  4. Hence bar inherits from Bar.prototype although it was created by new Foo . 因此bar继承自Bar.prototype尽管它是由new Foo创建的。
  5. Thus bar.constructor.name is "Bar" . 因此bar.constructor.name"Bar"

In the JS fiddle you provided you created a function Foo and then set Foo.prototype.constructor to function Bar() {} : 在你提供的JS小提示中 ,你创建了一个函数Foo ,然后将Foo.prototype.constructor设置为function Bar() {}

function Foo() {}
Foo.prototype.constructor = function Bar() {};
var f = new Foo;
console.log(f.hasOwnProperty("constructor"));
console.log(f.constructor);
console.log(f);

Because you modified a property of Foo.prototype every instance of Foo.prototype will reflect this change. 因为你修改的属性Foo.prototype的每个实例Foo.prototype将反映这一变化。 Hence f.constructor is function Bar() {} . 因此f.constructorfunction Bar() {} Thus f.constructor.name is "Bar" , not "Foo" . 因此f.constructor.name"Bar" ,而不是"Foo"

See it for yourself - f.constructor.name is "Bar" . 亲自看看 - f.constructor.name"Bar"


Chrome is known to do weird things like that. 众所周知,Chrome会做出类似奇怪的事情。 What's important to understand is that Chrome is a debugging utility and console.log is primarily used for debugging purposes. 重要的是要理解Chrome是一个调试实用程序, console.log主要用于调试目的。

Hence when you create a new instance Chrome probably records the original constructor in an internal property which is accessed by console.log . 因此,当您创建新实例时,Chrome可能会将原始构造函数记录在console.log访问的内部属性中。 Thus it displays Foo , not Bar . 因此它显示Foo ,而不是Bar

This is not actual JavaScript behavior. 这不是实际的JavaScript行为。 According to the specification when you overwrite the prototype.constructor property there's no link between the instance and the original constructor. 根据规范,当你覆盖prototype.constructor属性时,实例和原始构造函数之间没有链接。

Other JavaScript implementations (like the Opera console, node.js and RingoJS) do the right thing and display Bar . 其他JavaScript实现(如Opera控制台,node.js和RingoJS)做正确的事情并显示Bar Hence Chrome's behavior is non-standard and browser-specific, so don't panic. 因此Chrome的行为是非标准的,特定于浏览器,所以不要惊慌。

What's important to understand is that even though Chrome displays Foo instead of Bar the constructor property of the object is still function Bar() {} as with other implementations: 重要的是要理解,即使Chrome显示Foo而不是Bar ,对象的constructor属性仍然是function Bar() {} ,与其他实现一样:

The constructor property (which refers to a function originally used as a generator of the corresponding objects) is used to give a name to a prototype object in the console log. constructor属性(引用最初用作相应对象的生成器的函数)用于为控制台日志中的prototype对象指定名称。 Consider the following: 考虑以下:

function Foo() { 
  this.x = 1; 
}
console.log(Foo.prototype);  // Foo {}
Foo.prototype.constructor = function Bar() {  
  this.y = 2 
}
console.log(Foo.prototype);  // Bar {}        
var f = new Foo();
console.log(f.constructor);  // function Bar() { this.y = 2}

console.log(f.x);            // 1
console.log(f.y);            // undefined
console.log(f);              // Foo {x:1}

Here we've switched constructor to another function, giving a new name to prototype object. 在这里,我们将constructor函数切换到另一个函数,为prototype对象赋予一个新名称。 Note that the same function is returned when constructor property is queried directly from an object , created with Foo() function (as we go up the inheritance chain). 请注意,直接从使用Foo()函数创建的对象查询constructor属性时会返回相同的函数(因为我们继承了继承链)。

Still, it doesn't mean that another function ( Bar() ) was actually used to create the corresponding objects; 但是,这并不意味着另一个函数( Bar() )实际上用于创建相应的对象; it's still Foo() , and you can see it both by querying properties -- and f directly. 它仍然是Foo() ,你可以通过查询属性 - 和f直接看到它。 Basically, objects remember the function that was used to create them, even if constructor property of prototype was "redirected". 基本上,对象记住用于创建它们的函数,即使prototype constructor属性被“重定向”。

function Foo(){};

Working down the chain: 在链条上工作:

console.log(Foo.prototype);
console.log(Foo.prototype.constructor);
console.log(Foo.prototype.constructor.name);

Took a bit of digging online but I found this article that really illustrates how prototypes and other key core javascript functionality works: 在网上进行了一些挖掘,但我发现这篇文章真正说明了原型和其他关键核心JavaScript功能的工作原理:

http://dmitrysoshnikov.com/ecmascript/javascript-the-core/ http://dmitrysoshnikov.com/ecmascript/javascript-the-core/

I particularly like the diagram on how the prototype chain looks like. 我特别喜欢关于原型链如何看起来的图表。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM