JavaScript中的__proto__与prototype有什么区别?

993

这张图再次显示每个对象都有一个原型。构造函数 Foo 也有它自己的 __proto__,指向 Function.prototype,而 Function.prototype 再通过它自己的 __proto__ 属性引用 Object.prototype。因此可以重复强调,Foo.prototype 只是 Foo 的一个显式属性,它引用了 b 和 c 对象的原型。

var b = new Foo(20);
var c = new Foo(30);

__proto__和prototype有什么区别?

enter image description here

该图来自dmitrysoshnikov.com

注意:以上2010年文章已经有了第二版(2017年)


5
我认为自上而下或自下而上是个人偏好的问题。我个人更喜欢自上而下,这样我可以追踪图表,找到某件事情的起源。 - Mike Lippert
2
我喜欢JavaScript如何使用原型继承来将y.constructor解析为y.proto.constructor。我也喜欢Object.prototype位于原型继承链的顶部,而Object.prototype.__proto__设置为null。我还喜欢这个图表如何将程序员对对象的三列概念化可视化,即1.实例、2.构造函数、3.原型,当通过new关键字实例化时,构造函数将与这些实例相关联。 - John Sonderson
看完 http://www.youtube.com/watch?v=_JJgSbuj5VI 后,图表立刻就变得很清晰易懂。顺便说一下。 - mlvljr
现在,当我阅读了答案后,我感到有义务真正地推荐上面的视频,因为它确实清晰明了(而且没有令人困惑的部分)地解释了正在发生的事情 :) - mlvljr
34个回答

965

__proto__ 是用于查找链中解析方法的实际对象,而 prototype 则是创建一个带有 new 的对象时用来构建 __proto__ 的对象:

( new Foo ).__proto__ === Foo.prototype
( new Foo ).prototype === undefined

309
啊!所以“原型(prototype)”不可用于实例本身(或其他对象),而只能在构造函数上使用。 - rvighne
59
prototype 只能在函数上使用,因为它们是从 FunctionObject 派生的,但在其他任何地方都不能使用。然而,__proto__ 可以在任何地方使用。 - Tarik
32
因此,__proto__ 是保存并用作原型的实际对象,而 Myconstructure.prototype 只是 __proto__ 的蓝图,即实际保存并用作原型的对象。因此,myobject.prototype 不会成为实际对象的属性,因为它只是构造函数用于概述 myobject.__proto__ 应该看起来像什么的临时对象。 - Alex_Nabu
17
可以这样说,一个对象的 __proto__ 属性是指向该对象的构造函数的 prototype 属性的指针吗?即 foo.proto === foo.constructor.prototype。 - Niko Bellic
21
@Alex_Nabu 不完全正确。 newCar.__proto__ 就是 Car.prototype,而不是 Car.prototype 的一个实例。 而 Car.prototype 一个 object 的实例。 Car.prototype 并不提供任何属性或结构给 newCar,它只是 newCar 原型链中的下一个 objectCar.prototype 不是一个临时的 object。它是作为使用 Car 作为 constructor 创建新 object__proto__ 属性的值设置的 object。如果你想把任何东西都当作一个蓝图 object,那么可以将 Car 视为新车_ object 的蓝图。 - seangwright
显示剩余6条评论

439

prototype 是一个函数对象的属性。它是由该函数构造的对象的原型。

__proto__ 是一个对象的内部属性,指向其原型。当前标准提供了一个等效的 Object.getPrototypeOf(obj) 方法,但事实上通用的 __proto__ 更快。

您可以通过比较一个函数的 prototype 和一个对象的 __proto__ 链来查找 instanceof 关系,并且可以通过更改 prototype 来打破这些关系。

function Point(x, y) {
    this.x = x;
    this.y = y;
}

var myPoint = new Point();

// the following are all true
myPoint.__proto__ == Point.prototype
myPoint.__proto__.__proto__ == Object.prototype
myPoint instanceof Point;
myPoint instanceof Object;

这里的Point是一个构造函数,它按过程建立一个对象(数据结构)。myPoint是由Point()构建的对象,所以在那个时候Point.prototype被保存到了myPoint.__proto__


2
如果您更改对象的__proto__属性,则会更改执行原型查找的对象。例如,您可以将方法对象添加为函数的__proto__,以获得可调用实例对象的效果。 - kzh
2
myPoint.proto.constructor.prototype == Point.prototype - Francisco
@kzh 哈哈,这给了我一个有趣的结果 console.log(obj1.call) // [Function: call] obj1.call() // TypeError: obj1.call 不是一个函数。我做了 obj.__proto__ = Function.__proto__ - abhisekp
myFn.__proto__ = {foo: 'bar'} - kzh
2
我想我明白你的意思了。 - ComicScrip
.__proto__现在已经成为标准;尽管它并不总是可用(例如对于Object.create(null)),甚至对于普通对象也可以(部分地)禁用(delete Object.prototype.__proto__; - undefined

162

prototype 属性是在函数声明时创建的。

例如:

 function Person(dob){
    this.dob = dob
 }; 

Person.prototype 属性在你声明上面的函数后,会在内部创建。 可以向 Person.prototype 添加许多属性,这些属性将由使用 new Person() 创建的 Person 实例共享。

// adds a new method age to the Person.prototype Object.
Person.prototype.age = function(){return date-dob}; 
值得注意的是,默认情况下Person.prototype是一个对象字面量(可以根据需要进行更改)。 使用new Person()创建的每个实例都有一个__proto__属性,它指向Person.prototype。这是用于查找特定对象的属性的链条。
var person1 = new Person(somedate);
var person2 = new Person(somedate);

创建了两个 Person 实例,这两个对象可以调用 Person.prototypeage 方法,分别为 person1.ageperson2.age

从你的问题中的上图中可以看出,Foo 是一个 Function Object,因此它有一个指向 Function.prototype__proto__ 链接,而 Function.prototype 又是 Object 的实例,并且具有一个指向 Object.prototype__proto__ 链接。在 Object.prototype 中,__proto__ 指向 null,链接到此结束。

任何对象都可以通过其由 __proto__ 链接连接的原型链访问其原型链中的所有属性,这是原型继承的基础。

__proto__ 不是访问原型链的标准方式,标准但类似的方法是使用 Object.getPrototypeOf(obj)

下面的代码演示了 instanceof 运算符:

当一个对象是某个 Class 的实例时,即如果该对象的原型链中存在 Class.prototype,则 object instanceof Class 运算符返回 true,进而表示该对象是该 Class 的实例。

function instanceOf(Func){
  var obj = this;
  while(obj !== null){
    if(Object.getPrototypeOf(obj) === Func.prototype)
      return true;
    obj = Object.getPrototypeOf(obj);
  }
  return false;
}      
上述方法可以称为:instanceOf.call(object, Class),如果object是Class的实例,则返回true。

3
为什么要在内部创建 prototype 对象呢?我们是否可以直接将静态方法分配给函数对象本身?例如:function f(a){this.a = a}; f.increment = function(){return ++this.a}。为什么不选择这种方式而是要将方法添加到 prototype 对象中呢?如果 f.__proto__ = g,其中 g 是基类,这种方式也可以实现。 - abhisekp
也许选择prototype对象进行共享是因为只有独占的函数构造器属性可以存储在函数构造器对象中。 - abhisekp
2
实际上,这将是一团糟,因为如果删除了“prototype”属性,instanceof将导致 ({}) instanceof Function === true,并且没有办法区分原型之间的差异。 - abhisekp
@abhisekp 你的意思是什么:“如果f.proto = g,其中g是基类,那么这将起作用。”我不知道这是否有一些我不理解的含义,但如果您以这种方式添加属性和方法,那么当您使用“new”关键字创建实例时,属性和方法将不会被复制。 - doubleOrt

108
为了解释,让我们创建一个函数。
 function a (name) {
  this.name = name;
 }

当JavaScript执行这段代码时,它会向a添加prototype属性,prototype属性是一个具有两个属性的对象:

  1. constructor
  2. __proto__

因此,当我们执行

a.prototype时,它返回

     constructor: a  // function definition
    __proto__: Object

现在你可以看到,constructor其实就是函数本身a,而__proto__指向JavaScript的根级Object

让我们看看当我们使用带有new关键字的a函数时会发生什么。

var b = new a ('JavaScript');

当 JavaScript 执行此代码时,它会执行 4 个操作:

  1. 创建一个新的对象,一个空对象 // {}
  2. b 上创建 __proto__ 并将其指向 a.prototype,因此 b.__proto__ === a.prototype
  3. 使用新创建的对象(在步骤 #1 中创建)作为其上下文(即 this),执行 a.prototype.constructor(即函数 a 的定义),因此传递的 name 属性为“JavaScript”(添加到 this 中),并将其添加到新创建的对象中。
  4. 返回新创建的对象(在步骤 #1 中创建),因此将 var b 分配给新创建的对象。

现在,如果我们添加 a.prototype.car = "BMW" 并执行 b.car,则输出“BMW”。

这是因为当 JavaScript 执行此代码时,它在 b 上搜索 car 属性,未找到,然后 JavaScript 使用 b.__proto__(在步骤 #2 中指向 'a.prototype')并找到 car 属性,因此返回 “BMW”。


3
  1. constructor 不会返回 a()!它会返回 a
  2. __proto__ 会返回 Object.prototype,而不是 JavaScript 中的根对象。
- doubleOrt
4
这是一个很棒的回答! - john-raymon
6
+1 这是最好的解释原型实际上是什么(一个带有两个属性的对象)以及JavaScript如何执行每段代码。这些信息难以找到。 - java-addict301

84

一个好的理解方式是...

prototypeconstructor函数所使用。它实际上应该被称为"prototypeToInstall",因为这就是它的作用。

__proto__是那个“已安装的原型”,它位于对象上(该对象是从该constructor()函数创建/安装的)。


4
我已经点赞了,但也许downvote的原因是因为“prototype被constructor()函数使用”的说法听起来好像非构造函数没有这个功能,但事实并非如此。不过,除此之外,这也不是我们现在关注的重点,此外,如果使用new关键字调用,每个函数都潜在地可以成为一个构造函数... - yoel halb
2
请将“constructor()函数”更改为“构造函数”,因为可能会与“proto.constructor()函数”混淆。我认为这很重要,因为在使用new关键字时并不实际调用__proto__.constructor。 - Alexander Gonchiy
1
“_prototype被构造函数(constructor())使用_”这个说法只是重要事实的一部分,但它描述的方式很可能让读者认为这就是整个事实。在JavaScript中,无论将来如何调用该函数(使用或不使用new关键词),prototype都会在每个函数声明时内部创建;已声明函数的prototype指向一个对象文字。 - Yiling

67

原型 VS. __proto__ VS. [[Prototype]]

创建函数时,会自动创建一个名为prototype的属性对象(您不需要手动创建它),并将其附加到函数对象(constructor)上。
注意:这个新的prototype对象也指向或具有内部私有链接到原生 JavaScript Object。

示例:

function Foo () {
    this.name = 'John Doe';
}

// Foo has an object property called prototype.
// prototype was created automatically when we declared the function Foo.
Foo.hasOwnProperty('prototype'); // true

// Now, we can assign properties and methods to it:
Foo.prototype.myName = function () {
    return 'My name is ' + this.name;
}
如果你使用 new 关键字从 Foo 创建一个新对象,基本上你创建了一个新的对象(除其他事物之外),该对象具有与之前讨论过的函数 Foo 原型的内部或私有链接
var b = new Foo();

b.[[Prototype]] === Foo.prototype  // true


这个函数对象的私有链接被称为双括号原型或者[[Prototype]]。许多浏览器还提供了公共链接,称为__proto__

更具体地说,__proto__实际上是JavaScript原生对象的一个getter函数,它返回任何this绑定的内部私有原型链接(返回b[[Prototype]]):

b.__proto__ === Foo.prototype // true
值得注意的是,自ECMAScript5起,您还可以使用getPrototypeOf方法来获取内部私有链接。
Object.getPrototypeOf(b) === b.__proto__ // true


注意: 这个回答并不旨在涵盖创建新对象或新构造函数的整个过程,而是帮助更好地理解 __proto__prototype[[Prototype]] 以及它们的工作原理。


2
@Taurus,点击标题,它会带您到ECMAScript规范文档。请查看第9节(普通和异类对象行为),其中更详细地解释了它。 - Lior Elrom

42

为了让它更加清晰,除了以上优秀的回答:

function Person(name){
    this.name = name
 }; 

var eve = new Person("Eve");

eve.__proto__ == Person.prototype //true

eve.prototype  //undefined

实例__proto__prototype


1
构造函数和类也有一个__proto__属性,它不仅仅是实例的保留属性。构造函数和类都有一个__proto__属性和一个prototype属性。请参见https://dev59.com/4mkw5IYBdhLWcg3wV5ID#42002749。 - Sébastien
简短而精炼的翻译:简洁明了 - Vigneshwaran Chandrasekaran

20
在JavaScript中,函数可以用作构造函数。这意味着我们可以使用new关键字将它们创建为对象。每个构造函数都带有一个与之链接的内置对象。这个内置对象被称为原型。构造函数的实例使用__proto__来访问其构造函数的原型属性。

prototype diagram


  1. 首先我们创建了一个构造函数:function Foo(){}。明确一点,Foo只是另一个函数。但是我们可以使用new关键字从中创建一个对象。这就是为什么我们称它为构造函数。

  2. 每个函数都有一个唯一的属性,称为原型属性。所以,构造函数Foo具有指向其原型的原型属性,即Foo.prototype(见图像)。

  3. 构造函数本身是一个函数,是由称为[[Function]]构造函数的系统构造函数构造的。因此,我们可以说function Foo是由[[Function]]构造函数构造的。因此,我们的Foo函数__proto__将指向其构造函数的原型,即Function.prototype

  4. Function.prototype本身就是一个对象,是由另一个称为[[Object]]的系统构造函数构造的。因此,[[Object]]Function.prototype的构造函数。因此,我们可以说Function.prototype[[Object]]的一个实例。因此,Function.prototype__proto__指向Object.prototype

  5. Object.prototype是原型链中的最后一个对象。我的意思是它没有被构造。它已经存在于系统中。因此,它的__proto__指向null

  6. 现在我们来到Foo的实例。当我们使用new Foo()创建一个实例时,它会创建一个新的对象,该对象是Foo的一个实例。这意味着Foo是这些实例的构造函数。在这里,我们创建了两个实例(x和y)。因此,x和y的__proto__指向Foo.prototype


只是为了明确:实例没有 .prototype 属性?只有构造函数对吧?... 因此,实例和它的构造函数之间的区别在于:构造函数具有 1. proto 2. .prototype 对象,而实例只有 .proto 属性... 是这样吗? - Shaz
@Shaz 你是对的。 实例使用它们的__proto__来访问它们的构造函数的原型属性。 - AL-zami
@shaz,你能提供一个 JSFiddle 让我可以看到情况吗? - AL-zami
这里是https://jsfiddle.net/shahyan3/b5tr3zhk/6/ ...请查看car和car2作为示例。有什么想法吗? - Shaz
1
这里的car.prototype是一个继承属性。car从vehicle函数中继承了'prototype'属性。因此car.prototype === vehicle.prototype。 "prototype"属性是vehicle上的一个属性。car可以通过其原型链访问它。希望这能解决您的疑惑。 - AL-zami
显示剩余2条评论

17

我认为你需要知道 __proto__[[prototype]]prototype 之间的区别。

接受的答案很有帮助,但可能会不完美地暗示 __proto__ 是仅适用于使用构造函数 new 创建的对象的东西,这是不正确的。更准确地说:__proto__ 存在于每个对象上

  • 那么,__proto__ 到底是什么?

    • 好吧,它是一个对象,引用另一个对象,也是所有对象的属性,称为 [[prototype]]
    • 值得一提的是,[[prototype]] 是 JavaScript 内部处理的内容,无法被开发者访问
  • 为什么我们需要一个引用对象来访问所有对象的 [[prototype]] 属性?

    • 因为 JavaScript 不想允许直接获取 / 设置 [[prototype]],所以它通过一个中间层 __proto__ 允许它。因此,你可以将 __proto__ 视为 [[prototype]] 属性的 getter/setter。
  • 那么 prototype 是什么?

    • 它是一些特定于函数的东西(最初在 Function 中定义,即 Function.prototype,然后通过原型继承被新创建的函数继承,并且然后这些函数将其传给他们的子级,形成原型继承链)。

    • 当使用 new 运行父函数时,JavaScript 使用父函数的 prototype 来设置其子函数的 [[prototype]](记得我们说过所有对象都有 [[prototype]]吗? 好吧,函数也是对象,所以它们也有[[prototype]])。因此,当一个函数(子级)的 [[prototype]] 被设置为另一个函数(父级)的 prototype 时,最终你会得到这个:

      let child = new Parent();
      child.__proto__ === Parent.prototype // --> true.
      

      (记住child.[[prototype]]不可访问,所以我们使用__proto__进行了检查。)


注意1:每当一个属性不在子对象中时,它的__proto__将被“隐式”搜索。例如,如果child.myprop返回一个值,你无法确定“myprop”是子对象的属性还是其父原型链中某个原型的属性。这也意味着你永远不需要像这样做:child.__proto__.__proto__.myprop,只需使用child.myprop就可以自动完成。

注意2:即使父对象的原型中有项目,子对象自己的prototype最初也将是一个空对象。你可以手动添加或删除其中的项目,以扩展继承链(向子对象添加儿童)。或者可以通过隐式方式来操纵它,例如使用类语法。)

注意3:如果需要设置/获取[[prototype]],使用__proto__有点过时,现代的JavaScript建议使用Object.setPrototypeOfObject.getPrototypeOf


10

JavaScript prototype vs __prototype__

'use strict'
function A() {}
var a = new A();
class B extends A {}
var b = new B();
console.log('====='); // =====
console.log(B.__proto__ === A); // true
console.log(B.prototype.__proto__ === A.prototype); // true
console.log(b.__proto__ === B.prototype); // true
console.log(a.__proto__ === A.prototype); // true
console.log(A.__proto__ === Function.__proto__); // true
console.log(Object.__proto__ === Function.__proto__); // true
console.log(Object.prototype === Function.__proto__.__proto__); // true
console.log(Object.prototype.__proto__ === null); // true

在JavaScript中,每个对象(函数也是对象)都有一个__proto__属性,该属性是指向其原型的引用。 当我们使用构造函数和new操作符创建一个新对象时,新对象的__proto__属性将被设置为构造函数的prototype属性,然后构造函数将由新对象调用,在这个过程中,“this”将在构造函数作用域中引用新对象,最后返回新对象。 构造函数的原型是__proto__属性,构造函数的prototype属性与new操作符一起使用。 构造函数必须是一个函数,但即使它具有prototype属性,函数并不总是构造函数。 原型链实际上是对象的__proto__属性引用其原型,原型的__proto__属性引用原型的原型,以此类推,直到引用Object的原型的__proto__属性,该属性引用null。 例如:
console.log(a.constructor === A); // true
// "a" don't have constructor,
// so it reference to A.prototype by its ``__proto__`` property,
// and found constructor is reference to A

[[Prototype]]__proto__ 属性实际上是同一件事情。

我们可以使用 Object 的 getPrototypeOf 方法来获取某个对象的原型。

console.log(Object.getPrototypeOf(a) === a.__proto__); // true

任何我们编写的函数都可以用new运算符来创建对象,因此这些函数中的任何一个都可以作为构造函数。

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