__proto__和constructor.prototype有什么不同?

180
function Gadget(name, color)
{
   this.name = name;
   this.color = color;
}

Gadget.prototype.rating = 3

var newtoy = new Gadget("webcam", "black")

newtoy.constructor.prototype.constructor.prototype.constructor.prototype 

它总是返回评分等于3的对象。

但如果我执行以下操作:

newtoy.__proto__.__proto__.__proto__

该链最终返回 null

在Internet Explorer中,如果没有__proto__属性,我该如何检查 null 呢?


35
这个图示可以帮助你了解原型(prototype)和__proto__之间的区别。你可以从newtoy对象开始跟随__proto__链,然后你会明白为什么newtoy的第三个__Proto__是null。 - bits
@bits 你用了哪个工具来制作这个图表? - Royi Namir
@bits 我很想看到这个的JS(浏览器)版本。你可以在我的个人资料中看到我的电子邮件。 - Royi Namir
1
@Royi Namir,我已经在Github上上传了jsViz。这是demo网站。请不要介意实际代码的维护情况(和脏乱程度)。这是一个非常古老的项目,我已经很久没有碰它了。 - bits
显示剩余5条评论
9个回答

222

最近我一直在试图理解这个问题,最终得出了我认为可以完全解决问题的这个"映射"。

http://i.stack.imgur.com/KFzI3.png enter image description here

我知道我不是第一个想出这个东西的人,但是通过自己探索搞明白它还是更有趣 :-)。总之,在那之后,我找到了另一个基本上说同样的东西,如下所示:

Javascript object layout

对我来说最令人惊讶的事情是发现Object.__proto__指向Function.prototype而不是Object.prototype,但我相信这其中一定有一个很好的原因 :-)

我也在此处贴出了图中提到的代码,以便任何人都可以测试它。请注意,某些属性被添加到对象中,以便在跳转后容易了解我们所处的位置:

Object.O1='';
Object.prototype.Op1='';

Function.F1 = '';
Function.prototype.Fp1 = '';

Cat = function(){};
Cat.C1 = '';
Cat.prototype.Cp1 = '';

mycat = new Cat();
o = {};

// EDITED: using console.dir now instead of console.log
console.dir(mycat);
console.dir(o);

2
@utsaina 非常酷。看看 OP 发布的代码的另一个图形表示。我认为我们的图表在技术细节方面是一致的。 - bits
49
Object.__proto__ 指向 Function.prototype 的原因是因为 Object() 本身是一个原生函数,用于实例化一个空对象。因此,Object() 是一个函数。你会发现所有其他主要的原生类型的 __proto__ 属性都指向 Function.prototypeObjectFunctionStringNumberArray 都继承了 Function 原型。 - Swivel
@drodsou,你的第二个链接太棒了。请现在就去查看一下吧 ;) http://www.mollypages.org/misc/js.mp 解释得非常好 :D - abhisekp
2
@GiorgiMoniava 正确。Object 本身就是一个函数;执行可调用的 Object(即运行 Object() 的返回值)的结果不是一个函数。 - Swivel
谢谢,你的映射真的很有帮助!我创建了一个gist,基于你的语义,展示1.当猫从动物继承时关系如何行为2. ES2015/ES6类和ES5对象之间的等价性: https://gist.github.com/philipstanislaus/bad92373edee1d45887f06ce6fa260eb - Philip Stanislaus
显示剩余2条评论

69

constructor是函数对象的prototype属性所指向对象的预定义[[DontEnum]]属性,最初将指向函数对象本身。

__proto__相当于对象的内部[[Prototype]]属性,即其实际原型。

当您使用new运算符创建一个对象时,它的内部[[Prototype]]属性将设置为构造函数的prototype属性所指向的对象。

这意味着.constructor将计算为.__proto__.constructor,即用于创建对象的构造函数,并且正如我们所学到的,该函数的protoype属性用于设置对象的[[Prototype]]。

因此,.constructor.prototype.constructor.constructor相同(只要这些属性没有被覆盖);有关更详细的解释,请参见这里

如果__proto__可用,则可以遍历对象的实际原型链。在纯ECMAScript3中没有办法做到这一点,因为JavaScript并不是为深层继承层次结构而设计的。


3
那个“这里”的链接是黄金标准。如果您想要完整的描述,请前往那里。 - Ricalsin
使用.constructor.prototype链式调用是个好主意。在我没有看到.constructor等于.__proto__.constructor之前,我也不太清楚。这意味着可以在构造函数和它的原型之间进行循环。 - Johnny_D

32
JavaScript中的原型继承是基于__proto__属性的,每个对象都会继承其__proto__属性所引用对象的内容。 prototype属性仅对Function对象特殊,并且只在使用new运算符调用Function作为构造函数时才会特殊。此时,创建的对象的__proto__将被设置为构造函数的Function.prototype
这意味着添加到Function.prototype将自动反映在所有__proto__引用Function.prototype的对象上。
使用其他对象替换构造函数的Function.prototype不会更新任何已经存在的对象的__proto__属性。
请注意,不应直接访问__proto__属性,而应该使用Object.getPrototypeOf(object)
为了回答第一个问题,我制作了一张关于__proto__prototype引用的专门图表。不幸的是,stackoverflow不允许我添加带有“少于10个声望”的图像。也许另一次吧。
[编辑] 该图使用[[Prototype]]代替__proto__,因为这是ECMAScript规范引用内部对象的方式。希望你能够理解其中的所有内容。
以下是一些提示,可以帮助您理解该图:
red    = JavaScript Function constructor and its prototype
violet = JavaScript Object constructor and its prototype
green  = user-created objects
         (first created using Object constructor or object literal {},
          second using user-defined constructor function)
blue   = user-defined function and its prototype
         (when you create a function, two objects are created in memory:
          the function and its prototype)
请注意,constructor属性不存在于创建的对象中,但是从原型继承而来。

enter image description here


@xorcus 请问您能解释一下这个吗:new MyFunction()会创建一个对象实例,其__proto__应该引用其构造函数原型MyFunction.prototype。那么为什么 MyFunction.prototype.__proto__ 指向 Object.prototype 呢?它应该像我的第一个示例一样指向它的构造函数原型 MyFunction.prototype(注意 MyFunction.prototypeMyfunction 的一个实例)。 - Royi Namir
@Royi Namir:MyFunction.prototype.__proto__指的是Object.prototype,因为MyFunction.prototype是一个对象。Object.prototype被所有对象继承(通常情况下,这就是继承原型链的终点)。我不同意MyFunction.prototype是MyFunction的实例。obj instanceof MyFunction <=> MyFunction.prototype.isPrototypeOf(obj) <=> MyFunction.prototype存在于obj的原型链中。但对于MyFunction.prototype对象来说并非如此。 - xorcus

15
Object 代表夏娃,Function 代表亚当,亚当(Function)使用他的骨头(Function.prototype)来创造夏娃(Object)。 那么谁创造了亚当(Function)呢? -- JavaScript 语言的发明者 :-).
根据 utsaina 的回答,我想添加更多有用的信息。
最令我惊讶的是发现 Object.__proto__ 指向 Function.prototype,而不是指向 Object.prototype,但是我相信这其中一定有一个好的原因 :-)。
事实上应该不是这样的。 Object.__proto__ 不应该指向 Object.prototype,而是 Object 实例 oo.__proto__ 应该指向 Object.prototype
(请原谅我在 JavaScript 中使用 "class" 和 "instance" 这些术语,但您知道的 :-))
我认为类 Object 本身就是 Function 的一个实例,这就是为什么 Object.__proto__ === Function.prototype。 因此:Object 代表夏娃,Function 代表亚当,亚当(Function)使用他的骨头(Function.prototype)来创造夏娃(Object)。
而且,即使类 Function 本身也是 Function 的一个实例,也就是说 Function.__proto__ === Function.prototype,这也是为什么 Function === Function.constructor
更进一步,普通的类 CatFunction 的一个实例,也就是说 Cat.__proto__ === Function.prototype
以上的原因是,当我们在 JavaScript 中创建一个类时,实际上我们只是创建了一个函数,这个函数应该是 Function 的一个实例。 ObjectFunction 只是特殊的情况,但它们仍然是类,而 Cat 则是一个普通的类。
事实上,在 Google Chrome 的 JavaScript 引擎中,以下 4 个:
  • Function.prototype
  • Function.__proto__
  • Object.__proto__
  • Cat.__proto__
它们都是 ===(绝对相等)于另外 3 个,它们的值是 function Empty() {}
> Function.prototype
  function Empty() {}
> Function.__proto__
  function Empty() {}
> Object.__proto__
  function Empty() {}
> Cat.__proto__
  function Empty() {}
> Function.prototype === Function.__proto__
  true
> Function.__proto__ === Object.__proto__
  true
> Object.__proto__ === Cat.__proto__
  true

好的,那么是谁创建了特殊的function Empty() {}Function.prototype)?想一想:-)


同意,除了最后一件事:你所指的function Empty() {}等于Function.prototype是什么?你在Chrome控制台中使用的代码是什么? - drodsou
2
我纠正了你指出的最后一件事。在Google Chrome中,它们的值为function Empty() {}。我还添加了控制台输出。 - Peter Lee
所有的函数都是 Function 的实例,因此所有的函数都继承自 Function.prototype(_ proto _)。就这么简单 :) - xorcus
抱歉评论了旧帖子。但是它们是由语言的发明者创建的吗? - Patel Parth

7
我真不知道为什么大家没有纠正你对问题的理解上实际存在的错误。这会让你更容易发现问题。那么,让我们看看到底发生了什么:
var newtoy = new Gadget("webcam", "black")

newtoy 
  .constructor //newtoy's constructor function is newtoy ( the function itself)
    .prototype // the function has a prototype property.( all functions has)
      .constructor // constructor here is a **property** (why ? becuase you just did `prototype.constructor`... see the dot ? )  ! it is not(!) the constructor function  !!! this is where your mess begins. it points back to the constructor function itself ( newtoy function)
         .prototype // so again we are at line 3 of this code snippet
            .constructor //same as line 4 ...
                .prototype 
                 rating = 3

太好了,现在让我们来看看__proto__

在此之前,请记住与__proto__相关的2件事:

  1. 当您使用new操作符创建对象时,它的内部[[Prototype]]/proto__属性将设置为其constructor function或者说是"creator"的prototype属性(1)。

  2. 在JS中硬编码:Object.prototype.__proto__null

让我们把这两点称为“bill

newtoy
     .__proto__ // When `newtoy` was created , Js put __proto__'s value equal to the value of the cunstructor's prototype value. which is `Gadget.prototype`.
       .__proto__ // Ok so now our starting point is `Gadget.prototype`. so  regarding "bill" who is the constructor function now? watch out !! it's a simple object ! a regular object ! prototype is a regular object!! so who is the constructor function of that object ? Right , it's the `function Object(){...}`.  Ok .( continuing "bill" ) does it has a `prototype` property ? sure. all function has. it's `Object.prototype`. just remember that when Gadget.prototype was created , it's internal `__proto__` was refered to `Object.prototype` becuase as "bill" says :"..will be set to the `prototype` property of   its `constructor function`"
          .__proto__ // Ok so now our satrting point is `Object.prototype`. STOP. read bullet 2.Object.prototype.__proto__ is null by definition. when Object.prototype ( as an object) was created , they SET THE __PROTO__ AS NULL HARDCODED

更好了吗?

3
简短回答: __proto__ 是一个引用,指向创建对象的构造函数的 prototype 属性。
JavaScript 中的对象是一种内置类型,用于表示零个或多个属性的集合。属性是容器,可以包含其他对象、原始值或函数。
函数是常规对象(在 ECMA-262 术语中实现了 [[Call]]),但在 JavaScript 中扮演另一个角色:如果通过 new 运算符调用,则成为构造函数(对象的工厂)。因此,构造函数大致类似于其他语言中的类。
每个 JavaScript 函数实际上都是 Function 内置函数对象的实例,它具有一个名为 prototype 的特殊属性,用于实现基于原型的继承和共享属性。由构造函数创建的每个对象都有一个隐式引用(称为原型或 __proto__)指向其构造函数 prototype 的值。
构造函数的 prototype 是一种构建对象的蓝图,因为由构造函数创建的每个对象都继承了对其 prototype 的引用。
对象通过内部属性 [[Prototype]]__proto__ 指定其原型。两个对象之间的原型关系涉及继承:每个对象都可以将另一个对象作为其原型。原型可以是 null 值。
__proto__ 属性连接的对象链称为原型链。当在对象中引用属性时,引用是对在包含该名称属性的原型链中的第一个对象遇到的属性的引用。原型链的行为就像它是单个对象一样。
每当您尝试访问对象中的属性时,JavaScript 都会从该对象开始搜索,并继续使用其原型、原型的原型等,直到找到该属性或者如果 __proto__ 的值为 null
这种使用原型链进行的继承通常称为委托,以避免与使用类链的其他语言混淆。
几乎所有对象都是 Object 的实例,因为 Object.prototype 是它们原型链中的最后一个。但是,Object.prototype 不是 Object 的实例,因为 Object.prototype.__proto__ 的值为 null
您还可以像这样创建具有 null 原型的对象:
var dict = Object.create(null);

这种对象比字面对象更好的地方在于它是一个更好的映射(字典),这就是为什么有时候将该模式称为“dict”模式(“dict”代表字典)的原因。
注意:使用{}创建的字面对象是Object的实例,因为({}).__proto__是对Object.prototype的引用。

请引用您使用的引语和文物的来源。这张图片似乎来自https://giamir.com/pseudoclasses-and-prototypal-inheritance-in-JS,您对此有版权吗? - Bergi

2
如果所有这些数字令人不知所措,让我们看看这些属性的含义。

STH.prototype

创建一个新函数时,同时会创建一个空对象,并使用[[Prototype]]链将其链接到该函数。要访问此对象,我们使用函数的prototype属性。

function Gadget() {}
// in background, new object has been created
// we can access it with Gadget.prototype
// it looks somewhat like {constructor: Gadget}

请记住,prototype属性仅适用于函数。

STH.constructor

上述提到的原型对象除了一个constructor属性外没有其他属性。此属性表示创建原型对象的函数。

var toy = new Gadget();

创建 Gadget 函数时,我们还创建了一个像 {constructor: Gadget} 的对象 - 这与 Gadget.prototype 没有任何关系。由于 constructor 是指创建对象原型的函数,所以 toy.constructor 表示 Gadget 函数。我们写 toy.constructor.prototype,我们再次得到 {constructor: Gadget}
因此,存在一个恶性循环:您可以使用 toy.constructor.prototype.constructor.prototype.constructor.prototype.constructor.prototype.constructor.prototype.constructor.prototype.constructor.prototype.constructor.prototype,它始终是 Gadget.prototype
toy
.constructor    // Gadget
.prototype    // {constructor: Gadget}
.constructor    // Gadget
.prototype    // {constructor: Gadget}
// ...

STH.__proto__

prototype是函数特有的属性,而__proto__则适用于所有对象,因为它位于Object.prototype中。它指的是能够创建对象的函数的原型。

[].__proto__ === Array.prototype
// true

({}).__proto === Object.prototype
// true

在这里,toy.__proto__Gadget.prototype。由于Gadget.prototype是一个对象({}),而且对象是使用Object函数创建的(请参见上面的例子),因此我们得到了Object.prototype。这是JavaScript中更高级的对象,它的__proto__只能指示null
toy
.__proto__    // Gadget.prototype (object looking like {constructor: Gadget})
.__proto__    // Object.prototype (topmost object in JS)
.__proto__    // null - Object.prototype is the end of any chain

2

每个函数都会创建它自己的原型。 当我们使用该函数构造函数创建对象时,我的对象的__proto__属性将开始指向该函数的原型。


1
我认为你的意思是 __proto__ 属性。 - demisx
是的,我指的是对象的__proto__属性。希望这些信息对您有用。 - Apoorv Nag

0

JavaScript中的原型对于每个人都很困惑

任何类型(Object、String、Array等)上的构造函数最初都与创建它们的Function Object相关联。一旦对象类型的值/对象被创建,它们才会被分配自己的原型,这是一个唯一的属性和对象,当每个值被创建时,函数构造函数创建它们。但是,JavaScript中所有对象/类型(Object、String、Array等)的原型起始都是Function.prototype。它们都源自函数及其构造函数,用于在内存中创建对象和基本值的实例!只有在它们的值由它们的函数构造函数创建后,它们才会被分配自己的独特原型,包括它们继承的"prototype"属性和Object原型。

这就是99%的互联网网页没有告诉你的内容!

例如,Number(或String Array、Boolean等)类型始终具有构造函数,即Number.constructor,它派生自分配给“Number”类型的“Function Object”。这就是为什么“Number”被称为“构造函数”的原因。因此,在检查时,它的原型是Function.prototype。一旦它的函数或构造函数构建了一个真正的Number原始值或类型,它就会分配自己独特的原型Number.prototype。让我们在下面证明这一点!
以下是更简单的解释。下面是JavaScript中大多数对象从null开始继承到对象类型的方式:
String   < Function < Object < null
Array    < Function < Object < null
Object   < Function < Object < null
Function < Function < Object < null

这里有证据! 下面我只是在询问每个对象找到的原型。注意:Object.prototype.toString.call() 只告诉我们原型的字符串名称:

Object.prototype.toString.call(String);// [object Function]
Object.prototype.toString.call(Array);// [object Function]
Object.prototype.toString.call(Object);// [object Function]
Object.prototype.toString.call(Function);// [object Function]

Object.prototype.toString.call(String.__proto__);// [object Function]
Object.prototype.toString.call(Array.__proto__);// [object Function]
Object.prototype.toString.call(Object.__proto__);// [object Function]
Object.prototype.toString.call(Function.__proto__);// [object Function]

Object.prototype.toString.call(String.__proto__.__proto__);// [object Object]
Object.prototype.toString.call(Array.__proto__.__proto__);// [object Object]
Object.prototype.toString.call(Object.__proto__.__proto__);// [object Object]
Object.prototype.toString.call(Function.__proto__.__proto__);// [object Object]

Object.prototype.toString.call(String.__proto__.__proto__.__proto__);// [object Null]
Object.prototype.toString.call(Array.__proto__.__proto__.__proto__);// [object Null]
Object.prototype.toString.call(Object.__proto__.__proto__.__proto__);// [object Null]
Object.prototype.toString.call(Function.__proto__.__proto__.__proto__);// [object Null]

请注意,字符串“[object Function]”表示类型的“prototype”或父对象为“Function.prototype”。因此,它是在每个级别分配的基础原型父对象的表示。现在让我们更深入地解释一下...
JavaScript中的原型是一个词,意思如下:
  • JavaScript中的所有对象最终都从一系列原型或“基类”继承其各种属性和特性。这沿着树向下级联到底部的子级。在JavaScript中,所有对象最终都从接近该继承树顶部的Object.prototype继承。
  • 术语“原型”表示具有由子对象继承的属性和方法的特殊对象
  • “原型”也是JavaScript中赋予所有对象的特殊属性,将给定对象分配为父原型到子级,但还授予访问更改原型的权限。它控制分配给子对象的实际原型,但也像真正的类属性一样运作,因为您可以使用它来操作子对象的原型。我不建议您这样做,但您可以通过简单的属性添加或通过包含属性的对象文字添加属性来修改所有对象继承的原始Object.prototype:
    Object.prototype.myproperty = "Hello World";
    Object.prototype.myobjectproperties = {text1: "Hello", text2: "World"};
  • "prototype"属性与子对象名称组合表示为"MyObjectType.prototype"。这个新名称现在既是父原型的标识符,也是改变它的工具。但它不是指向实际原型对象的引用!(使用__proto__进行下面的操作)。当创建该类型的所有新对象时,它被分配给它们。它首先分配给构建对象的函数构造器,然后传递给函数构造器创建的对象。

  • "__proto__"是分配给子对象的实际原型对象的引用。它也是一个属性,但它是一个引用。因此,它用于沿着子对象继承的原型对象树向上移动并访问它们及其属性。下面的示例从创建的对象文字向上遍历树,并以"null"为顶部结束:

    alert({}.__proto__.__proto__);// null

原型中的怪异现象

在JavaScript继承中,一切都始于函数类型!为什么呢?因为你不能创建任何对象“类型”(Object、Array、Function、String等)而没有一个函数。当你这样做时,它们仍然会从某个函数中调用的“构造函数”中构建。函数及其构造函数不仅可以从类型创建新对象,还可以分配“prototype”属性、“__proto__”属性和实际继承的原型树或子对象将使用的对象。

在JavaScript中,有两种对象状态,即“类型”和实际实例化的对象。这就是为什么“Object”与“const x = {}”创建的对象不同的原因。这也是为什么“类型”从其最终继承或原型开始时具有不同的继承或原型的原因。

看看这个!

// The Array type has a prototype of "Function.prototype"
alert(Object.prototype.toString.call(Array));// [object Function]

// But an instance of an Array object has a NEW prototype of "Array.prototype" that the function prototype's constructor created for the object!
const myarray = [];
alert(Object.prototype.toString.call(myarray));// [object Array]

发生了什么事?

事实证明,当对象被创建时,FUNCTION CONSTRUCTOR创建并分配最终的prototype。但是,在使用许多其他属性、对象等在数组对象创建之前和之后,可以修改自定义原型。因此,由Function对象的构造函数设置了最终的分配的原型,如上所示,这是Array类型的初始原型。

因此,请注意,Function.prototype是JavaScript中所有Object类型的主要原型!它位于所有对象下面,但是是创建分配其自己的原型的最终实例化对象的工具。请注意,“Array.prototype”具有父原型Object.prototype,其父原型为“null”。因此,Object.prototype仍然是所有这些对象继承的顶级父级,但在创建它们时,构造函数会更改所有子对象的直接父级,当新对象被创建时。

请注意,Function.prototype 从其自身的 Object.prototype 中继承了许多功能。它为您创建的对象构建的原型也是从此父原型中制作的。因此,最终,Object.prototype 提供了创建和管理分配给它们的原型所需的好东西,无论是 Function 还是 Object 都是具有特殊工具和功能的特殊预构建类型,用于创建所有类型的对象!请记住这一点。
最后一个测试... 让我们看看我们创建的自定义对象如何使用原型。下面的示例证明函数构造器(Function.prototype 的一部分)将"prototype"属性分配给创建的对象,但可以在分配给对象的MyCustomObject.prototype原型之前或之后使用各种属性和方法进行自定义。这表明,您的对象的最终原型不必是Object.prototype继承属性的静态副本,而可以是您创建的全新内容!
let newPet;
function Pet() {
  this.fourlegs = true;
}

var Cat = {
  type : "cat"
}

var Dog = {
  type : "dog"
}

// We can see the prototype our constructor created for us
// and modify it as we like! Here we assigned it to an object
// which only means the prototype will merge "Cat" object's
// properties into the Pet.prototype.
Pet.prototype = Cat;

newPet = new Pet();
alert(newPet.type);// cat - inherited the Cat Object's properties in the prototype

Pet.prototype = Dog;

newPet = new Pet();
alert(newPet.type);// dog - inherited the Dog Object's properties in the prototype

alert(newPet.fourlegs);// true - this shows, even though you replace prototype, it ADDs the new types but does NOT erase the existing object properties! This must mean "prototype" is dynamically additive and rebuilt until the final "Pet" prototype is complete.

// Now change the "Pet.prototype" so all new objects have a new property.
Pet.prototype.furcolor = "white";
newPet = new Pet();
alert(newPet.furcolor);// "white"

// So you can see the "Pet.prototype" is dynamic, something you can tell the function constructor to modify!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接