JavaScript .prototype如何工作?


2041

我不喜欢动态编程语言,但是我写了相当一部分JavaScript代码。我从来没有真正了解过这种基于原型的编程,有人知道它是如何工作的吗?

var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

我记得很久以前与人们进行过多次讨论(我不确定自己在做什么),但据我了解,这里没有一个课堂的概念。这只是一个对象,这些对象的实例是原始对象的副本,对吗?

但是,此“ .prototype”属性在JavaScript中的确切目的是什么?它与实例化对象有何关系?

更新:正确的方法

var obj = new Object(); // not a functional object
obj.prototype.test = function() { alert('Hello?'); }; // this is wrong!

function MyObject() {} // a first class functional object
MyObject.prototype.test = function() { alert('OK'); } // OK

这些幻灯片也确实起到了很大作用。


78
John Resig上有一些功能原型的幻灯片,在研究主题时对我很有帮助(您也可以更改代码,看看会发生什么...)http://ejohn.org/apps/learn/#64
约翰·福斯特

5
很棒的参考资料,目的是为了使这个问题更有意义,或者在John的站点被更改而导致您的链接不再可用的情况下,在您的答案中添加来自John的站点的一些评论。两种方式+1都对我有所帮助。
克里斯(Chris)

95
+1链接到John Resig的JavaScript Ninja幻灯片#64。从那里开始确实很有帮助,我觉得我正确地理解了原型。
带薪的书呆子

4
我们真的需要一个功能对象来应用原型吗?如果是,为什么?
Anshul 2013年

6
这可能会帮助您:webdeveasy.com/javascript-prototype
Naor

Answers:


1007

每JavaScript对象具有一个内部“时隙”称为[[Prototype]]其值是任一null或一个object。您可以将插槽视为JavaScript引擎内部对象的属性,该属性对您编写的代码隐藏。方括号[[Prototype]]是有意的,并且是ECMAScript规范约定,用于表示内部插槽。

[[Prototype]]对象的指向的值俗称“该对象的原型”。

如果您通过点(obj.propName)或方括号(obj['propName'])表示法访问属性,而该对象没有直接具有这样的属性(即,自己的属性,可通过进行检查obj.hasOwnProperty('propName')),则运行时将在引用的对象上查找具有该名称的属性由[[Prototype]]代替。如果[[Prototype]] 没有这样的属性,[[Prototype]]则依次检查其,依此类推。这样,原始对象的原型链就会遍历,直到找到匹配项或到达末尾为止。null价值是原型链的顶部。

现代JavaScript实现允许[[Prototype]]通过以下方式对进行读取和/或写入访问:

  1. new操作者(设定从一个构造函数返回的默认对象上的原型链),
  2. extends关键字(使用类语法时配置原型链),
  3. Object.create将提供的参数设置为[[Prototype]]结果对象的,
  4. Object.getPrototypeOfObject.setPrototypeOf[[Prototype]] 创建对象获取/设置),以及
  5. 命名的标准化访问器(即getter / setter)属性__proto__(类似于4)。

Object.getPrototypeOf比之更Object.setPrototypeOf受推荐__proto__,部分原因o.__proto__ 当对象的原型为时,行为异常null

[[Prototype]]在创建对象时首先设置对象。

如果您通过创建新对象new Func(),则[[Prototype]]默认情况下,该对象的设置为所引用的对象Func.prototype

因此,请注意,所有类以及可与new运算符一起使用的所有函数.prototype除具有自己的[[Prototype]]内部插槽外,还具有命名的属性。“原型”一词的这种双重使用是该语言的新手之间不断产生混淆的根源。

new与构造函数一起使用可以让我们模拟JavaScript中的经典继承。尽管我们已经看到,JavaScript的继承系统是原型的,而不是基于类的。

在将类语法引入JavaScript之前,构造函数是模拟类的唯一方法。我们可以将构造函数的.prototype属性所引用的对象的属性视为共享成员。即。每个实例都相同的成员。在基于类的系统中,对每个实例都以相同的方式实现方法,因此在概念上将方法添加到.prototype属性中。但是,对象的字段是特定于实例的,因此在构造过程中会添加到对象本身。

没有类语法,开发人员必须手动配置原型链,以实现与经典继承类似的功能。这导致了许多实现此目的的不同方法。

这是一种方法:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
  return child;
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

...这是另一种方式:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
    function tmp() {}
    tmp.prototype = parent.prototype
    const proto = new tmp()
    proto.constructor = child
    child.prototype = proto
    return child
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

ES2015中引入的类语法通过提供extends“一种真正的方式”来配置原型链以模拟JavaScript中的经典继承,从而简化了事情。

因此,类似于上面的代码,如果您使用类语法创建新对象,如下所示:

class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}

const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

...结果对象[[Prototype]]将被设置为的实例Parent,其实例[[Prototype]]Parent.prototype

最后,如果您通过创建了一个新对象Object.create(foo),则生成的对象[[Prototype]]将设置为foo


1
所以,我在短片段中通过在prototype属性上定义新属性来做错什么吗?
约翰·莱德格伦

3
我认为这就是将功能对象作为一等公民的意义。
约翰·莱德格伦

8
我讨厌非标准的东西,尤其是在编程语言中,为什么当显然不需要它时,甚至还有一个原型
约翰·莱德格伦

1
@John __proto__仅在内部由JS解释器使用。每个对象都需要知道哪些属性和方法是原型的一部分,哪些是对象本身的一部分。JS解释器需要能够在对象上调用原型方法。
BMiner

17
请注意,使用的[[原型]是故意的-与双方括号内部的属性ECMA-262包围的名称
克里斯托弗

1798

在实现Java,C#或C ++之类的经典继承的语言中,您首先创建一个类(对象的蓝图),然后可以从该类中创建新对象,也可以扩展该类,定义一个新类以增强原来的课。

在JavaScript中,您首先创建一个对象(没有类的概念),然后可以扩充自己的对象或从中创建新对象。这并不困难,但是对于那些习惯了经典方式的人来说,却有点陌生和难以代谢。

例:

//Define a functional object to hold persons in JavaScript
var Person = function(name) {
  this.name = name;
};

//Add dynamically to the already defined object a new getter
Person.prototype.getName = function() {
  return this.name;
};

//Create a new object of type Person
var john = new Person("John");

//Try the getter
alert(john.getName());

//If now I modify person, also John gets the updates
Person.prototype.sayMyName = function() {
  alert('Hello, my name is ' + this.getName());
};

//Call the new method on john
john.sayMyName();

到目前为止,我一直在扩展基础对象,现在创建另一个对象,然后从Person继承。

//Create a new object of type Customer by defining its constructor. It's not 
//related to Person for now.
var Customer = function(name) {
    this.name = name;
};

//Now I link the objects and to do so, we link the prototype of Customer to 
//a new instance of Person. The prototype is the base that will be used to 
//construct all new instances and also, will modify dynamically all already 
//constructed objects because in JavaScript objects retain a pointer to the 
//prototype
Customer.prototype = new Person();     

//Now I can call the methods of Person on the Customer, let's try, first 
//I need to create a Customer.
var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();

//If I add new methods to Person, they will be added to Customer, but if I
//add new methods to Customer they won't be added to Person. Example:
Customer.prototype.setAmountDue = function(amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function() {
    return this.amountDue;
};

//Let's try:       
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

正如我所说的,我无法在Person上调用setAmountDue()和getAmountDue()。

//The following statement generates an error.
john.setAmountDue(1000);

352
我认为关于stackoverflow的答案不仅对于原始发布者而言很有趣,而且对于潜伏于搜索中或来自搜索的其他人群也是如此。我曾经是其中之一,并且从旧帖子中受益。我想我可以为其他答案添加一些代码示例做出贡献。关于您的问题:如果您遗漏了新内容,它将无法正常工作。当我调用myCustomer.sayMyName()时,它返回“ myCustomer.sayMyName不是函数”。最简单的方法是尝试使用Firebug,看看会发生什么。
stivlo 2011年

7
据我了解,var Person = function(name){...}; 正在定义一个能够构造Person对象的构造函数。因此,还没有对象,只有匿名构造函数分配给了Person。这是一个很好的解释: helephant.com/2008/08/how-javascript-objects-work
stivlo 2011年

17
警告:此答案忽略了以下事实:不基于每个实例调用父类构造函数。它起作用的唯一原因是因为他在子构造函数和父构造函数中所做的操作完全相同(设置名称)。有关在JavaScript中尝试继承时常见错误的更深入的解释(以及最终解决方案),请参见: 此堆栈溢出文章
Aaren Cordova

3
我注意到该答案也没有提到通过使用“ new Person()”作为原型,您实际上是将“ Person”的“ name”实例属性设置为“ Customer”的静态属性(因此所有Customer实例将具有相同的属性)。虽然这是一个很好的基本示例,但不要那样做。:)通过将原型的原型设置为“ Person.prototype”,创建一个新的匿名函数以充当“桥梁”,然后从中创建一个实例,并将“ Customer.prototype”设置为该匿名实例。
詹姆斯·威尔金斯

10
关于此Customer.prototype = new Person();行,MDN显示了一个使用的示例Customer.prototype = Object.create(Person.prototype),并指出“此处的常见错误是使用“ new Person()””来源
拉斐尔·埃因格2014年

186

这是一个非常简单的基于原型的对象模型,在解释过程中将其视为示例,但尚无注释:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    console.log(this.name);
}
var person = new Person("George");

在通过原型概念之前,我们必须考虑一些关键点。

1- JavaScript函数实际上如何工作:

第一步,我们必须弄清楚JavaScript函数实际上是如何工作的(作为类,类似于使用this关键字的函数),或者作为带有参数,其作用和返回结果的常规函数​​。

假设我们要创建一个Person对象模型。但在这一步中,我将尝试不使用prototypeand new关键字而做同样的事情

所以在这一步functionsobjectsthis关键字就是我们所拥有的。

第一个问题是如何this不使用newkeyword,关键字有用

因此,要回答这个问题,假设我们有一个空对象,并且两个函数类似:

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

function getName(){
    console.log(this.name);
}

现在 不用new关键字就可以使用这些功能。因此,JavaScript有3种不同的方法可以做到这一点:

一个。第一种方法是将函数作为常规函数调用:

Person("George");
getName();//would print the "George" in the console

在这种情况下,这将是当前上下文对象,通常是全局对象 window浏览器或GLOBAL中对象Node.js。这意味着我们将在浏览器中使用window.name或在Node.js中使用GLOBAL.name,其值为“乔治”。

b。我们可以它们附加到对象上,作为其属性

- 最简单的方法是修改空person对象,例如:

person.Person = Person;
person.getName = getName;

这样,我们可以像这样称呼他们:

person.Person("George");
person.getName();// -->"George"

现在person对象就像:

Object {Person: function, getName: function, name: "George"}

- 将属性附加到对象的另一种方法是使用该prototype对象的,该对象可以在名称为的任何JavaScript对象中找到__proto__,而我尝试在摘要部分进行一些解释。因此,我们可以通过执行以下操作获得类似的结果:

person.__proto__.Person = Person;
person.__proto__.getName = getName;

但是这种方式实际上是在修改Object.prototype,因为每当我们使用文字({ ... })创建JavaScript对象时,它都会基于创建Object.prototype,这意味着它会作为名为属性的属性附加到新创建的对象上__proto__,因此,如果我们更改它,就像我们在之前的代码片段中所做的那样,所有JavaScript对象都会更改,而不是一种好习惯。因此,现在最好的做法是:

person.__proto__ = {
    Person: Person,
    getName: getName
};

现在其他物体仍处于和平状态,但这似乎仍然不是一个好习惯。因此,我们还有另一个解决方案,但是要使用该解决方案,我们应该回到person创建对象的代码行(var person = {};),然后像下面这样更改它:

var propertiesObject = {
    Person: Person,
    getName: getName
};
var person = Object.create(propertiesObject);

它的作用是创建一个新的JavaScript Object并将其附加propertiesObject__proto__属性。因此,请确保您可以执行以下操作:

console.log(person.__proto__===propertiesObject); //true

但是,这里的棘手问题是您可以访问__proto__在第一层定义的所有属性。person对象(有关详细信息,请阅读摘要部分)。


如您所见,使用这两种方式中的任何一种 this都会精确地指向person对象。

C。JavaScript还有另一种向函数提供功能的方式this,即使用callapply来调用该函数。

apply()方法使用给定的此值和作为数组(或类似数组的对象)提供的参数调用函数。

call()方法使用给定的此值和单独提供的参数来调用函数。

这样,这是我的最爱,我们可以轻松地调用以下函数:

Person.call(person, "George");

要么

//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);

getName.call(person);   
getName.apply(person);

这3种方法是弄清.prototype功能的重要的初始步骤。


2- new关键字如何运作?

这是了解.prototype功能的第二步。这是我用来模拟过程的内容:

function Person(name){  this.name = name;  }
my_person_prototype = { getName: function(){ console.log(this.name); } };

在这一部分中,我将尝试采取JavaScript所采取的所有步骤,而在使用new关键字prototype时不使用关键字and new。因此,当我们执行时new Person("George")Person函数充当了构造函数,这就是JavaScript所要做的,一个接一个:

一个。首先,它生成一个空对象,基本上是一个空哈希,例如:

var newObject = {};

b。JavaScript的下一步是所有原型对象附加到新创建的对象

我们my_person_prototype这里有类似于原型对象的对象。

for(var key in my_person_prototype){
    newObject[key] = my_person_prototype[key];
}

JavaScript并不是真正地附加原型中定义的属性的方法。实际方式与原型链概念有关。


一个。&b。通过执行以下操作,您可以得到与上述两个步骤完全相同的结果:

var newObject = Object.create(my_person_prototype);
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype); //true
//and also check if you have access to your desired properties
console.log(typeof newObject.getName);//"function"

现在我们可以getName在我们的函数中调用该函数my_person_prototype

newObject.getName();

C。然后将该对象提供给构造函数,

我们可以使用我们的示例来做到这一点,例如:

Person.call(newObject, "George");

要么

Person.apply(newObject, ["George"]);

然后构造函数可以执行任何所需的操作,因为构造函数内部的对象是刚刚创建的对象。

现在,在模拟其他步骤之前为最终结果:对象{name:“ George”}


摘要:

基本上,当您在函数上使用new关键字时,您正在调用该函数,并且该函数用作构造函数,因此当您说:

new FunctionName()

JavaScript在内部创建一个对象,一个空散列,然后将其提供给构造函数,然后构造函数可以执行其所需的任何操作,因为构造函数内部的对象是刚刚创建的对象,然后为您提供了该对象如果您尚未在函数中使用return语句,或者return undefined;在函数主体的末尾放置了a 。

因此,当JavaScript在对象上查找属性时,它要做的第一件事就是在对象上查找属性。然后是[[prototype]]我们通常喜欢的一个秘密属性,__proto__而该属性就是JavaScript接下来要看的内容。并且,当它遍历时__proto__,就再次成为另一个JavaScript对象,它具有自己的__proto__属性,它会不断上升直到到达下一个__proto__为null 的地步。该点是JavaScript中唯一一个其__proto__属性为null的Object.prototype对象是object:

console.log(Object.prototype.__proto__===null);//true

这就是继承在JavaScript中的工作方式。

原型链

换句话说,当您在函数上具有原型属性并在其上调用一个新属性时,JavaScript完成对新创建的对象的属性查找后,它将去查看该函数的.prototype对象,并且该对象也可能具有其属性自己的内部原型。等等。


6
a)请不要通过复制属性来解释原型b)设置内部[[prototype]]会在构造函数应用到实例之前发生,请更改顺序c)jQuery在这个问题上完全
不合时宜

1
@Bergi:感谢您指出,如果您让我知道现在还可以,我将不胜感激。
Mehran Hatami 2014年

7
你能简单点吗?您在所有方面都是正确的,但阅读此说明的学生可能真的是第一次感到困惑。拿起任何更简单的示例,然后让代码自我解释或添加一堆注释以阐明您的意思。
2014年

2
@PM:感谢您的反馈。我已尝试使其尽可能简单,但我认为您是对的,但仍存在一些含糊之处。因此,我将尝试对其进行修改,并使其更具描述性。:)
Mehran Hatami 2014年

1
@AndreaMattioli,因为这样您就可以创建一个全新的对象,并覆盖其他对象也可以共享的旧对象。通过替换,__proto__您将首先清除所有顶级原型属性,然后拥有一个新的原型库,除非您共享它,否则将不再共享。
Mehran Hatami 2015年

77

原型的七个Koans

经过深思熟虑后,西罗·桑(Ciro San)下山降落在火狐山(Mount Fire Fox)时,他的头脑清晰而平静。

然而,他的手却焦躁不安,他自己握住了刷子,写下了以下笔记。


0)可以将两种不同的事物称为“原型”:

  • 原型属性,如 obj.prototype

  • 原型内部属性,[[Prototype]] 在ES5中表示。

    可以通过ES5检索它Object.getPrototypeOf()

    Firefox使该__proto__属性可以作为扩展访问。ES6现在提到的一些可选要求__proto__


1)存在这些概念可以回答以下问题:

当我这样做时obj.property,JS在哪里寻找.property

直观上,经典继承应该影响属性查找。


2)

  • __proto__用于点.属性查找,如中所述obj.property
  • .prototype用于直接查找,只是间接地因为它决定__proto__在对象创建与new

查找顺序为:

  • objobj.p = ...或添加的属性Object.defineProperty(obj, ...)
  • 的性质 obj.__proto__
  • 的性质 obj.__proto__.__proto__,等等
  • 如果一些__proto__就是null,返回undefined

这就是所谓的原型链

您可以避免.使用obj.hasOwnProperty('key')和查找Object.getOwnPropertyNames(f)


3)设置的主要方法有两种obj.__proto__

  • new

    var F = function() {}
    var f = new F()

    然后new设置:

    f.__proto__ === F.prototype

    .prototype被使用的地方。

  • Object.create

     f = Object.create(proto)

    设置:

    f.__proto__ === proto

4)代码:

var F = function(i) { this.i = i }
var f = new F(1)

对应于下图(Number省略了一些内容):

(Function)       (  F  )                                      (f)----->(1)
 |  ^             | | ^                                        |   i    |
 |  |             | | |                                        |        |
 |  |             | | +-------------------------+              |        |
 |  |constructor  | |                           |              |        |
 |  |             | +--------------+            |              |        |
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |[[Prototype]]   |[[Prototype]]   |prototype   |constructor   |[[Prototype]]
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |  |             |                | +----------+              |        |
 |  |             |                | |                         |        |
 |  |             |                | | +-----------------------+        |
 |  |             |                | | |                                |
 v  |             v                v | v                                |
(Function.prototype)              (F.prototype)                         |
 |                                 |                                    |
 |                                 |                                    |
 |[[Prototype]]                    |[[Prototype]]          [[Prototype]]|
 |                                 |                                    |
 |                                 |                                    |
 | +-------------------------------+                                    |
 | |                                                                    |
 v v                                                                    v
(Object.prototype)                                       (Number.prototype)
 | | ^
 | | |
 | | +---------------------------+
 | |                             |
 | +--------------+              |
 |                |              |
 |                |              |
 |[[Prototype]]   |constructor   |prototype
 |                |              |
 |                |              |
 |                | -------------+
 |                | |
 v                v |
(null)           (Object)

此图显示了许多语言预定义的对象节点:

  • null
  • Object
  • Object.prototype
  • Function
  • Function.prototype
  • 1
  • Number.prototype(可以找到(1).__proto__,必须使用圆括号来满足语法)

我们的两行代码仅创建了以下新对象:

  • f
  • F
  • F.prototype

i现在的属性是f因为当您执行以下操作:

var f = new F(1)

它的计算结果Fthis被该值new然后把它分配给要回来,f


5) .constructor通常来自F.prototype通过.查找:

f.constructor === F
!f.hasOwnProperty('constructor')
Object.getPrototypeOf(f) === F.prototype
F.prototype.hasOwnProperty('constructor')
F.prototype.constructor === f.constructor

在编写时f.constructor,JavaScript的.查找方式为:

  • f 不具有 .constructor
  • f.__proto__ === F.prototype.constructor === F,所以接受

该结果f.constructor == F在直观上是正确的,因为该结果F用于构造f(例如,设置字段),这与经典OOP语言中的情况非常相似。


6)经典的继承语法可以通过操纵原型链来实现。

ES6添加了classextends关键字,它们大多是语法糖,用于解决以前可能发生的原型操作混乱问题。

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
c = new C(1)
c.inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// http://stackoverflow.com/questions/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

没有所有预定义对象的简化图:

(c)----->(1)
 |   i
 |
 |
 |[[Prototype]]
 |
 |
 v    __proto__
(C)<--------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |[[Prototype]] 
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|[[Prototype]]    (D.prototype)--------> (inc2 function object)
| |                |             inc2
| |                |
| |                |[[Prototype]]
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)------->(inc function object)
|                inc
v
Function.prototype

让我们花点时间研究以下内容如何工作:

c = new C(1)
c.inc() === 2

第一行设置c.i,以1如在解释“4)”。

在第二行,当我们这样做时:

c.inc()
  • .inc通过[[Prototype]]链找到:c-> C-> C.prototype->inc
  • 当我们使用Javascript调用函数时X.Y(),JavaScript 在函数调用中会自动设置this为相等!XY()

完全相同的逻辑也可以解释d.incd.inc2

本文https://javascript.info/class#not-just-a-syntax-sugar提到了class值得了解的其他影响。如果没有class关键字(TODO检查哪个),则其中一些可能无法实现:

  • [[FunctionKind]]:"classConstructor",这会强制使用new调用构造函数: 为什么不能将ES6类构造函数称为普通函数?
  • 类方法是不可枚举的。可以完成Object.defineProperty
  • 总是上课use strict。可以use strict对每个功能都使用显式来完成,这无疑是乏味的。

1
@tomasb谢谢!“我不知道您从哪里得到的”:在我看过其中一些动态语言之后,我注意到它们的类系统最重要的是.查找的工作方式(以及创建了多少数据副本) 。所以我着手了解这一点。其余的是Google +博客文章+一个Js解释器。:)
Ciro Santilli冠状病毒审查六四事件法轮功

1
我仍然不明白为什么g.constructor ===对象,因为您说过“ 4)当您执行f = new F时,new还设置了f.constructor = F”。您能再跟我解释一下吗?无论如何,这是我要寻找的最佳答案。非常感谢!
nguyenngoc101 2015年

@ nguyenngoc101谢谢!该sets f.constructor = F部分是公然错误的,并且与其他部分相矛盾:.constructor通过.对原型链的查找可以找到。立即修复。
Ciro Santilli冠状病毒审查六四事件法轮功2015年

从所有讨论中,如果我创建构造函数并尝试使用new运算符创建它的实例,我将得到(从经典继承中得到)我将仅获得附加到原型对象的方法和属性,因此必须附加所有方法和属性,如果我们想继承原型对象,对吧?
blackawk

1
@CiroSantilli刘晓波死六四事件法轮功我不认为这是Chromium中的错误。我认为这只是一个症状,即f原型F仅在构建时设置;首次构建后,f不会F.prototype随时知道或关心。
约翰·格拉斯麦尔

76

prototype使您可以上课。如果您不使用,prototype则它将变为静态。

这是一个简短的例子。

var obj = new Object();
obj.test = function() { alert('Hello?'); };

在上述情况下,您具有静态功能调用测试。该函数只能由obj.test访问,您可以将obj想象成一个类。

在下面的代码中

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

obj已成为现在可以实例化的类。可以存在obj的多个实例,并且它们都具有test功能。

以上是我的理解。我将其设为社区Wiki,因此如果我错了,人们可以纠正我。


13
-1:prototype是构造函数的属性,而不是实例,即您的代码错误!也许您的意思__proto__是对象的非标准属性,但这是完全不同的野兽……
Christoph

@Christoph-感谢您指出。我已经更新了示例代码。
拉梅什(Ramesh)

3
不仅如此,而且还有更多内容……JavaScript不是基于类的语言-它通过原型处理继承,您需要更详细地介绍差异!
詹姆斯,

5
我认为这个答案有点误导。
Armin Cifuentes

也许答案是“错误的”,但解释了原型的用途,而在所有这些“答案”都获得了数百票赞成票的情况下,现在我已经很清楚了。谢谢。
亚历克斯(Aleks)2015年

66

阅读此主题后,我对JavaScript原型链感到困惑,然后我发现了这些图表

http://iwiki.readthedocs.org/en/latest/javascript/js_core.html#inheritance * [[protytype]] *和功能对象的<code> prototype </ code>属性

这是一个清晰的图表,以按原型链显示JavaScript继承

http://www.javascriptbank.com/javascript/article/JavaScript_Classical_Inheritance/

这个包含一个示例代码和一些漂亮的图表。

原型链最终归结为Object.prototype。

通过将子类的原型设置为与父类的对象相等,每次都可以在技术上根据需要扩展原型链。

希望对理解JavaScript原型链也有帮助。


是否可以在JavaScript上具有多个继承?

Foo是这里的对象文字还是函数对象?如果它是对象文字,我相信Foo.prototype不会通过构造函数指向Foo。
Madhur Ahuja 2014年

@ user3376708 JavaScript仅支持单一继承
Rafael Eyng 2014年

@ Nuno_147起初还不清楚,但是如果您看起来足够长,可能会从中得到一些帮助。
marcelocra

3
你能解释什么[[Prototype]]意思吗?
CodyBugstein 2014年

40

每个对象都有一个内部属性[[Prototype]],将其链接到另一个对象:

object [[Prototype]]  anotherObject

在传统的javascript中,链接对象是prototype函数的属性:

object [[Prototype]]  aFunction.prototype

某些环境将[[Prototype]]公开为__proto__

anObject.__proto__ === anotherObject

创建对象时,创建[[Prototype]]链接。

// (1) Object.create:
var object = Object.create(anotherObject)
// object.__proto__ = anotherObject

// (2) ES6 object initializer:
var object = { __proto__: anotherObject };
// object.__proto__ = anotherObject

// (3) Traditional JavaScript:
var object = new aFunction;
// object.__proto__ = aFunction.prototype

因此,这些语句是等效的:

var object = Object.create(Object.prototype);
var object = { __proto__: Object.prototype }; // ES6 only
var object = new Object;

您实际上无法Object.prototype语句中看到链接目标();相反,构造函数隐含了目标(Object)。

记得:

  • 每个对象都有一个链接[[Prototype]],有时显示为__proto__
  • 每个函数都有一个prototype属性,最初包含一个空对象。
  • new创建的对象链接到prototype其构造函数的属性。
  • 如果一个函数从不用作构造函数,则其prototype属性将不使用。
  • 如果不需要构造函数,请使用Object.create而不是new

1
修订版5删除了一些有用的信息,包括有关Object.create()的信息。参见修订版4
Palec

@Palec我应该加些什么?
2015年

2
IMO至少是Object.create()docs,@ sam 的链接。链接__proto__Object.prototype将是不错的增强。我喜欢您关于原型如何与构造函数和一起使用的示例Object.create(),但是它们可能是您想要摆脱的冗长而不太相关的部分。
Palec'7

从所有讨论中,如果我创建构造函数并尝试使用new运算符创建它的实例,我将得到(从经典继承中得到)我将仅获得附加到原型对象的方法和属性,因此必须附加所有方法和属性,如果我们想继承原型对象,对吧?
blackawk

29

Javascript在通常意义上没有继承,但是它具有原型链。

原型链

如果在对象中找不到对象的成员,则会在原型链中寻找它。链由其他对象组成。可以使用__proto__变量访问给定实例的原型。每个对象都有一个,因为javascript中的类和实例之间没有区别。

向原型添加功能/变量的优点是,它只能在内存中一次,而不是每个实例一次。

这对于继承也很有用,因为原型链可以包含许多其他对象。


1
FF和Chrome支持proto,但不支持IE或Opera。
一些

Georg,请澄清一下-“ JavaScript中的类和实例之间没有区别。” -您能详细说明吗?这是如何运作的?
Hamish Grubijan

从所有讨论中,如果我创建构造函数并尝试使用new运算符创建它的实例,我将得到(从经典继承中得到)我将仅获得附加到原型对象的方法和属性,因此必须附加所有方法和属性,如果我们想继承原型对象,对吧?
blackawk

28

这篇文章很长。但是,我敢肯定,它将清除您有关JavaScript继承的“原型”性质的大多数查询。甚至更多。请阅读全文。

JavaScript基本上有两种数据类型

  • 非物体
  • 对象

非物体

以下是非对象数据类型

  • 数字(包括NaN和Infinity)
  • 布尔值(true,false)
  • 未定义

这些数据类型在您使用 typeof运算符

typeof “字符串文字”(或包含字符串文字的变量)=== 'string'

typeof 5(或任何数字文字或包含数字文字或NaN或Infynity的变量)=== 'number'

typeof true(或false或包含truefalse的变量)=== 'boolean'

typeof 未定义(或未定义变量或包含未定义的变量)=== '未定义'

字符串号码布尔数据类型可以表示既作为对象非对象。当它们被表示为对象他们的typeof总是===“对象”。一旦了解了对象数据类型,我们将回到这一点。

对象

对象数据类型可以进一步分为两种类型

  1. 函数类型对象
  2. 非函数类型对象

功能类型的对象是返回字符串的那些“功能”typeof运算符。所有用户定义的函数以及所有可以使用new运算符创建新对象的JavaScript内置对象都属于此类别。例如。

  • 宾语
  • 布尔型
  • 数组
  • 类型数组
  • 正则表达式
  • 功能
  • 可以使用new运算符创建新对象的所有其他内置对象
  • 函数 UserDefinedFunction(){/ *用户定义的代码* /}

因此, typeof(Object) === typeof(String) === typeof(Number) === typeof(Boolean) === typeof(Array) === typeof(RegExp) === typeof(Function) == = typeof(UserDefinedFunction) === '功能'

所有的Function类型对象实际上都是内置JavaScript对象Function的实例(包括Function对象,即它是递归定义的)。好像这些对象是通过以下方式定义的

var Object= new Function ([native code for object Object])
var String= new Function ([native code for object String])
var Number= new Function ([native code for object Number])
var Boolean= new Function ([native code for object Boolean])
var Array= new Function ([native code for object Array])
var RegExp= new Function ([native code for object RegExp])
var Function= new Function ([native code  for object Function])
var UserDefinedFunction= new Function ("user defined code")

如前所述,功能类型对象可以使用new运算符进一步创建新对象。例如,可以使用以下方法创建类型为ObjectStringNumberBooleanArrayRegExpUserDefinedFunction的对象

var a=new Object() or var a=Object() or var a={} //Create object of type Object
var a=new String() //Create object of type String
var a=new Number() //Create object of type Number
var a=new Boolean() //Create object of type Boolean
var a=new Array() or var a=Array() or var a=[]  //Create object of type Array
var a=new RegExp() or var a=RegExp() //Create object of type RegExp
var a=new UserDefinedFunction() 

这样创建的对象都是Non Function类型的对象,并返回其typeof === 'object'。在所有这些情况下,对象“ a”无法使用运算符new进一步创建对象。所以以下是错误的

var b=new a() //error. a is not typeof==='function'

内置对象Mathtypeof === 'object'。因此,新运算符不能创建Math类型的新对象。

var b=new Math() //error. Math is not typeof==='function'

还要注意,ObjectArrayRegExp函数无需使用运算符new即可创建新对象。但是下面的人没有。

var a=String() // Create a new Non Object string. returns a typeof==='string' 
var a=Number() // Create a new Non Object Number. returns a typeof==='number'
var a=Boolean() //Create a new Non Object Boolean. returns a typeof==='boolean'

用户定义的功能是特殊情况。

var a=UserDefinedFunction() //may or may not create an object of type UserDefinedFunction() based on how it is defined.

由于Function类型的对象可以创建新对象,因此也称为Constructors

自动定义的每个构造函数/函数(无论是内置的还是用户定义的)都具有一个称为“原型”的属性,其默认值设置为对象。该对象本身具有一个称为“构造函数”的属性,默认情况下,该属性引用构造函数/函数

例如,当我们定义一个函数

function UserDefinedFunction()
{
}

以下自动发生

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

“原型”属性仅在函数类型对象中存在 (而在非函数类型对象中则不存在)。

这是因为创建新对象(使用new运算符)时,它将继承Constructor函数的当前原型对象的所有属性和方法,即 在新创建的对象中创建内部引用 ,该内部引用引用了Constructor函数的当前原型对象所引用的对象。

这个“内部参考”是在对象创建用于参考继承属性是被称为对象的原型(引用由构造的引用的对象“原型”属性,但不同的是从它)。对于任何对象(函数或非函数),都可以使用Object.getPrototypeOf()方法进行检索。使用这种方法,可以跟踪对象的原型链。

同样,每个创建的对象函数类型或非函数类型)都具有“构造函数”属性,该属性继承自构造函数的prototype属性所引用的对象。默认情况下,这个“构造”属性引用的构造函数创建它(如果构造函数的默认的“原型”没有变化)。

对于所有Function类型对象,构造函数始终为 Function Function(){}

对于非函数类型的对象(例如,Java内置的Math对象),构造函数是创建它的函数。对于Math对象,它是函数Object(){}

没有任何支持代码,上面解释的所有概念可能有些令人生畏。请逐行阅读以下代码以了解其概念。尝试执行它以具有更好的理解。

function UserDefinedFunction()
{ 

} 

/* creating the above function automatically does the following as mentioned earlier

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

*/


var newObj_1=new UserDefinedFunction()

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays true

alert(newObj_1.constructor) //Displays function UserDefinedFunction

//Create a new property in UserDefinedFunction.prototype object

UserDefinedFunction.prototype.TestProperty="test"

alert(newObj_1.TestProperty) //Displays "test"

alert(Object.getPrototypeOf(newObj_1).TestProperty)// Displays "test"

//Create a new Object

var objA = {
        property1 : "Property1",
        constructor:Array

}


//assign a new object to UserDefinedFunction.prototype
UserDefinedFunction.prototype=objA

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays false. The object referenced by UserDefinedFunction.prototype has changed

//The internal reference does not change
alert(newObj_1.constructor) // This shall still Display function UserDefinedFunction

alert(newObj_1.TestProperty) //This shall still Display "test" 

alert(Object.getPrototypeOf(newObj_1).TestProperty) //This shall still Display "test"


//Create another object of type UserDefinedFunction
var newObj_2= new UserDefinedFunction();

alert(Object.getPrototypeOf(newObj_2)===objA) //Displays true.

alert(newObj_2.constructor) //Displays function Array()

alert(newObj_2.property1) //Displays "Property1"

alert(Object.getPrototypeOf(newObj_2).property1) //Displays "Property1"

//Create a new property in objA
objA.property2="property2"

alert(objA.property2) //Displays "Property2"

alert(UserDefinedFunction.prototype.property2) //Displays "Property2"

alert(newObj_2.property2) // Displays Property2

alert(Object.getPrototypeOf(newObj_2).property2) //Displays  "Property2"

每个对象的原型链最终可以追溯到Object.prototype(它本身没有任何原型对象)。以下代码可用于跟踪对象的原型链

var o=Starting object;

do {
    alert(o + "\n" + Object.getOwnPropertyNames(o))

}while(o=Object.getPrototypeOf(o))

各种对象的原型链如下所示。

  • 每个功能对象(包括内置的功能对象)-> Function.prototype-> Object.prototype-> null
  • 简单对象(由new Object()或{}创建,包括内置的Math对象)-> Object.prototype-> null
  • 使用new或Object.create创建的对象->一个或多个原型链-> Object.prototype-> null

要创建没有任何原型的对象,请使用以下命令:

var o=Object.create(null)
alert(Object.getPrototypeOf(o)) //Displays null

可能有人认为,将构造函数的prototype属性设置为null会创建一个带有null原型的对象。但是,在这种情况下,新创建的对象的原型设置为Object.prototype,其构造函数设置为Object。以下代码演示了这一点

function UserDefinedFunction(){}
UserDefinedFunction.prototype=null// Can be set to any non object value (number,string,undefined etc.)

var o=new UserDefinedFunction()
alert(Object.getPrototypeOf(o)==Object.prototype)   //Displays true
alert(o.constructor)    //Displays Function Object

在本文的摘要中

  • 有两种类型的对象:功能类型非功能类型
  • 只有函数类型的对象才能使用运算符new创建新对象。这样创建的对象是非功能类型的对象。该非功能型对象使用不能再创建一个对象的operator new

  • 默认情况下,所有功能类型对象均具有“原型”属性。此“原型”属性引用具有“构造函数”属性的对象,该属性默认情况下引用“ 功能”类型对象本身。

  • 所有对象(“ 函数类型”和“ 非函数类型”)都具有“构造函数”属性,默认情况下会引用创建它的函数类型对象 / 构造函数

  • 在内部创建的每个对象都引用由创建它的构造方法的“ prototype”属性引用的对象 。该对象称为创建对象的原型(不同于它引用的Function类型对象的“ prototype”属性)。这样,创建的对象可以直接访问构造函数的“ prototype”属性所引用的对象中定义的方法和属性(在创建对象时)。

  • 一个对象的原型(并且因此其继承的属性名称)可以使用被检索Object.getPrototypeOf() 方法。实际上,该方法可用于导航对象的整个原型链。

  • 每个对象的原型链最终可以追溯到Object.prototype(除非使用Object.create(null)创建对象,否则对象将没有原型)。

  • typeof(new Array())==='object'是语言设计的,不是Douglas Crockford指出的错误

  • 将构造方法的prototype属性设置为null(或undefined,number,true,false,string)不应创建具有null原型的对象。在这种情况下,新创建的对象的原型设置为Object.prototype,其构造函数设置为功能Object。

希望这可以帮助。


24

prototypal对于许多开发人员来说,继承的概念是最复杂的概念之一。让我们尝试理解问题的根源,以便prototypal inheritance更好地理解。让我们从一个plain函数开始。

在此处输入图片说明

如果在上使用new运算符,则将Tree function其称为constructor函数。

在此处输入图片说明

每个JavaScript函数都有一个prototype。登录时Tree.prototype,您会得到...

在此处输入图片说明

如果查看上面的console.log()输出,则可以看到上的构造函数属性Tree.prototype和一个__proto__属性。的__proto__代表prototype,这function是基于关闭,因为这只是一个简单的JavaScript function没有inheritance建立的是,它指的是Object prototype这仅仅是内置于JavaScript的东西...

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

这有类似的东西.toString, .toValue, .hasOwnProperty...

__proto__我的mozilla带来了不推荐使用的Object.getPrototypeOf方法,该方法已替换为要获取的方法object's prototype

在此处输入图片说明

Object.getPrototypeOf(Tree.prototype); // Object {} 

让我们在中添加一个方法Tree prototype

在此处输入图片说明

我们已经修改了,Rootfunction为其添加了一个分支。

在此处输入图片说明

也就是说,当您创建的instanceTree,可以调用它的branch方法。

在此处输入图片说明

我们还可以添加primitivesobjects到我们Prototype

在此处输入图片说明

让我们添加一个child-tree到我们Tree

在此处输入图片说明

这里是从Tree Child继承prototype过来的,我们在这里所做的是使用Object.create()方法根据您传递的内容创建一个新对象,这里是Tree.prototype。在这种情况下,我们要做的是将Child的原型设置为一个看起来与Tree原型相同的新对象。接下来,我们将设置Child's constructor to Child,如果不这样做,它将指向Tree()

在此处输入图片说明

Child现在有它自己的prototype,它__proto__指向TreeTree's prototype指向基础Object

Child  
|
 \
  \
   Tree.prototype
   - branch
   |
   |
    \
     \
      Object.prototype
      -toString
      -valueOf
      -etc., etc.

现在你创建instanceChild和呼叫branch使本来可用Tree。我们实际上尚未在上定义我们branchChild prototype。但是,Root prototypeChild从中继承。

在此处输入图片说明

在JS中,一切都不是对象,一切都可以像对象一样工作。

Javascript具有像strings, number, booleans, undefined, null.他们不是这样的原语object(i.e reference types),但当然可以像普通话一样起作用object。让我们在这里看一个例子。

在此处输入图片说明

在此清单的第一行中,将一个primitive字符串值分配给name。第二行将名称视为,object然后charAt(0)使用点表示法进行调用。

这是幕后发生的事情:// JavaScript引擎做什么

在此处输入图片说明

String object之前它的破坏(这个过程被称为只存在一个语句autoboxing)。让我们再次回到我们的prototypal inheritance

  • Javascript支持delegation基于的 继承prototypes
  • 每个Function都有一个prototype属性,该属性引用另一个对象。
  • properties/functionsobject本身或通过 prototype链查看(如果不存在)

prototypeJS中的A 是一个对象,yields您是另一个对象的父对象object[即委派] Delegation表示如果您无法做某事,您会告诉其他人替您做。

在此处输入图片说明

https://jsfiddle.net/say0tzpL/1/

如果您查看上面的小提琴,则dog可以访问toStringmethod,但其中没有可用的方法,但是可以通过委托给它的原型链使用Object.prototype

在此处输入图片说明

如果您看下面的一个,我们正在尝试访问callevery中可用的方法function

在此处输入图片说明

https://jsfiddle.net/rknffckc/

如果您查看上面的小提琴,则ProfileFunction可以访问callmethod,但其中没有可用的方法,但是可以通过委托给它的原型链使用Function.prototype

在此处输入图片说明

注意: prototype是函数构造函数的属性,而是__proto__从函数构造函数构造的对象的属性。每个函数都有一个prototype值为空的属性object。创建函数的实例时,我们会获得内部属性,[[Prototype]]或者__proto__其引用是Function的原型constructor

在此处输入图片说明

上面的图看起来有点复杂,但是展示了如何 prototype chaining工作原理。让我们慢慢地看一下:

有两个实例b1b2,其构造函数为Bar,父对象为Foo,并且具有原型链identifyspeakvia Bar和中的两个方法。Foo

在此处输入图片说明

https://jsfiddle.net/kbp7jr7n/

如果你看看上面的代码,我们有Foo构造谁的方法identify()Bar具有构造speak方法。我们创建了两个Bar实例b1b2其父类型Foo。现在,在调用的speak方法时Bar,我们可以识别通过prototype链环进行通话的人。

在此处输入图片说明

Bar现在已在Foo中定义了所有方法prototype。让我们进一步深入理解Object.prototypeFunction.prototype和它们之间的关系。如果您查找和的构造函数Foo,则为。BarObjectFunction constructor

在此处输入图片说明

prototypeBarFooprototypeFoo就是Object,如果你仔细观察的prototypeFoo是有关Object.prototype

在此处输入图片说明

在结束之前,让我们在这里包装一小段代码来总结上面的所有内容。我们在instanceof这里使用运算符来检查an 链中是否object具有a prototypeprototype属性constructor,下面总结了整个大图。

在此处输入图片说明

我希望此添加了一些信息,我知道这可能有点难理解...用简单的话来说,它只是对象与对象的链接!!!


22

“ .prototype”属性的确切目的是什么?

标准类的接口变得可扩展。例如,您正在使用Array类,还需要为所有数组对象添加一个自定义序列化程序。您会花时间编码一个子类,还是使用合成或...原型属性通过让用户控制可用于类的成员/方法的确切集合来解决此问题。

将原型视为额外的vtable指针。当原始类中缺少某些成员时,将在运行时查找原型。


21

将原型链分为两类可能会有所帮助。

考虑构造函数:

 function Person() {}

的值Object.getPrototypeOf(Person)是一个函数。实际上是Function.prototype。自从Person作为函数创建以来,它与所有函数共享相同的原型函数对象。与相同Person.__proto__,但是不应使用该属性。无论如何,随着Object.getPrototypeOf(Person)您有效地走上了所谓的原型链的阶梯。

向上的链看起来像这样:

    Person→交通Function.prototype→交通Object.prototype(终点)

重要的是,该原型链与Person可以构造的对象无关。这些构造的对象具有自己的原型链,并且该链可能与上述的链没有共同的祖先。

以这个对象为例:

var p = new Person();

pPerson没有直接的原型链关系。他们的关系是不同的。对象p具有其自己的原型链。使用Object.getPrototypeOf,您会发现链条如下:

    p→交通Person.prototype→交通Object.prototype(终点)

该链中没有功能对象(尽管可以)。

因此Person似乎与两种链条有关,它们各自生活。要从一个链“跳”到另一个链,请使用:

  1. .prototype:从构造函数的链跳转到创建对象的链。因此,仅为函数对象定义该属性(因为new只能在函数上使用)。

  2. .constructor:从创建对象的链跳到构造函数的链。

这是涉及的两个原型链的可视化表示,以列表示:

在此处输入图片说明

总结一下:

prototype属性不提供对象的原型链的任何信息,而提供对象创建的对象的信息。

物业名称prototype会引起混乱也就不足为奇了。如果已命名此属性prototypeOfConstructedInstances或沿此名称命名,可能会更加清楚。

您可以在两个原型链之间来回跳转:

Person.prototype.constructor === Person

可以通过为对象明确分配其他对象来打破这种对称性。 prototype属性(稍后会对此进行更多介绍)。

创建一个函数,获取两个对象

Person.prototype是在创建函数的Person同时创建的对象。它具有Person构造函数,即使该构造函数尚未真正执行。因此,同时创建了两个对象:

  1. 功能 Person本身
  2. 当函数被调用为构造函数时将作为原型的对象

两者都是对象,但是它们具有不同的角色:函数对象构造,而另一个对象表示函数将构造的任何对象的原型。原型对象将成为其原型链中构造对象的父对象。

由于函数也是对象,因此它在自己的原型链中也有自己的父项,但请记住,这两个链是关于不同的事物的。

这里有一些平等之处可以帮助您解决问题-所有这些印刷品true

function Person() {};

// This is prototype chain info for the constructor (the function object):
console.log(Object.getPrototypeOf(Person) === Function.prototype);
// Step further up in the same hierarchy:
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype) === null);
console.log(Person.__proto__ === Function.prototype);
// Here we swap lanes, and look at the constructor of the constructor
console.log(Person.constructor === Function);
console.log(Person instanceof Function);

// Person.prototype was created by Person (at the time of its creation)
// Here we swap lanes back and forth:
console.log(Person.prototype.constructor === Person);
// Although it is not an instance of it:
console.log(!(Person.prototype instanceof Person));
// Instances are objects created by the constructor:
var p = new Person();
// Similarly to what was shown for the constructor, here we have
// the same for the object created by the constructor:
console.log(Object.getPrototypeOf(p) === Person.prototype);
console.log(p.__proto__ === Person.prototype);
// Here we swap lanes, and look at the constructor
console.log(p.constructor === Person);
console.log(p instanceof Person);

将水平添加到原型链

尽管在创建构造函数时创建了原型对象,但是您可以忽略该对象,并为该构造函数创建的任何后续实例分配另一个应用作原型的对象。

例如:

function Thief() { }
var p = new Person();
Thief.prototype = p; // this determines the prototype for any new Thief objects:
var t = new Thief();

现在t的原型链比p的原型链长了一个步骤:

    t→交通p→交通Person.prototype→交通Object.prototype(终点)

另外原型链是不再:ThiefPerson是兄弟姐妹共享相同的父在他们的原型链:

    Person}
    Thief  }→交通Function.prototype→交通Object.prototype(终点)

然后可以将先前显示的图形扩展为该图形(原始图形Thief.prototype被忽略):

在此处输入图片说明

蓝色线代表原型链,其他彩色线代表其他关系:

  • 在对象及其构造函数之间
  • 在构造函数和将用于构造对象的原型对象之间


16

我发现在obj_n.prop_X被引用时将“原型链”解释为递归约定很有帮助:

如果obj_n.prop_X不存在,检查obj_n+1.prop_X哪里obj_n+1 = obj_n.[[prototype]]

如果prop_X最终在第k个原型对象中找到,则

obj_1.prop_X = obj_1.[[prototype]].[[prototype]]..(k-times)..[[prototype]].prop_X

您可以在此处按其属性找到Javascript对象之间的关系图:

js对象图

http://jsobjects.org


14

当构造函数创建对象时,该对象隐式引用构造函数的“ prototype”属性,以解决属性引用的问题。构造表达式的“ prototype”属性可以由程序表达式的builder.prototype引用,添加到对象原型的属性通过继承由共享该原型的所有对象共享。


11

这里有两个不同但相关的实体需要说明:

  • .prototype函数的属性。
  • 所有对象[2][[Prototype]][1]属性。

这是两件事。

[[Prototype]]属性:

这是所有[2]对象上存在的属性。

这里存储的是另一个对象,该对象本身是一个对象,它[[Prototype]]指向另一个对象。另一个对象有[[Prototype]]其自己的。这个故事一直持续到您到达原型对象为止,该对象提供可在所有对象(例如.toString)上访问的方法。

[[Prototype]]物业是怎样形成的部分[[Prototype]]链条。此链[[Prototype]]的对象是当,例如,什么被检查[[Get]][[Set]]操作的对象执行:

var obj = {}
obj.a         // [[Get]] consults prototype chain
obj.b = 20    // [[Set]] consults prototype chain

.prototype属性:

这是仅在函数上找到的属性。使用一个非常简单的功能:

function Bar(){};

.prototype属性包含一个b.[[Prototype]]在执行操作时将分配给的对象var b = new Bar。您可以轻松检查以下内容:

// Both assign Bar.prototype to b1/b2[[Prototype]]
var b = new Bar;
// Object.getPrototypeOf grabs the objects [[Prototype]]
console.log(Object.getPrototypeOf(b) === Bar.prototype) // true

其中最重要的.prototype-是不是对的Object功能。该原型包含所有[[Prototype]]链包含的原型对象。在其上,定义了新对象的所有可用方法:

// Get properties that are defined on this object
console.log(Object.getOwnPropertyDescriptors(Object.prototype))

现在,由于.prototype是对象,因此具有[[Prototype]]属性。当您不对进行任何分配时Function.prototype.prototype[[Prototype]]点指向原型对象(Object.prototype)。每当您创建新功能时,都会自动执行此操作。

这样,只要您new Bar;为自己设置了原型链,就可以定义所有内容,Bar.prototype并定义所有内容Object.prototype

var b = new Bar;
// Get all Bar.prototype properties
console.log(b.__proto__ === Bar.prototype)
// Get all Object.prototype properties
console.log(b.__proto__.__proto__ === Object.prototype)

当你进行分配给Function.prototype所有你正在做的是延长了原型链包含另一个对象。这就像在单链表中的插入。

这基本上改变了[[Prototype]]链,从而允许在该对象上定义的属性Function.prototype可以被该函数创建的任何对象看到。


[1:这不会使任何人感到困惑;通过提供__proto__性能在许多实现。
[2]:除以外的所有内容null


10

让我告诉您我对原型的理解。我不会在这里将继承与其他语言进行比较。我希望人们不再比较语言,而只是理解语言本身。理解原型和原型继承非常简单,下面我将向您展示。

原型就像模型一样,您可以在此模型上创建产品。要理解的关键点是,当您使用另一个对象作为原型创建对象时,原型与产品之间的联系是永恒的。例如:

var model = {x:2};
var product = Object.create(model);
model.y = 5;
product.y
=>5

每个对象都包含一个称为[[prototype]]的内部属性,可以通过该Object.getPrototypeOf()函数进行访问。Object.create(model)创建一个新对象并将其[[prototype]]属性设置为对象模型。因此,当您这样做时Object.getPrototypeOf(product),将获得对象模型

产品中的属性按以下方式处理:

  • 当访问属性以仅读取其值时,将在范围链中查找该属性。从产品开始搜索变量到其原型。如果在搜索中找到了这样的变量,则搜索将在此处停止,并返回值。如果在范围链中找不到此类变量,则返回undefined。
  • 编写(更改)属性后,该属性将始终写在产品对象上。如果产品尚不具有此类属性,则将隐式创建和编写该产品。

使用prototype属性进行的对象链接称为原型继承。那里很简单,同意吗?


并非总是在作业时写在产品上。您并不是很清楚必须初始化实例特定成员,并且共享成员可以继续使用原型。尤其是当你拥有实例具体可变成员:stackoverflow.com/questions/16063394/...
HMR

HMR:在您的答案示例中,ben.food.push(“ Hamburger”); 这行代码由于以下原因而更改了原型对象的属性:1.)首先,对ben.food进行查找,任何查找操作都将仅查找范围链。2.)执行该ben.food对象的push函数。通过在答案中编写模式,我的意思是当您显式设置一个值时,例如:ben.food = ['Idly']; 这将始终在产品对象上创建一个新属性(如果尚不存在),然后为其分配值。
2014年

HMR:感谢您的评论,它使我思考并检验了我的理解。
Aravind

重新分配ben.food时,除非使用第二个参数(并非总是)使用Object.defineProperty,Object.defineProperties或Object.create创建食物,否则它将隐藏食物成员。创建getter设置程序时,甚至可以通过重新分配来更改原型。当涉及到继承模式时,我知道构造函数很难理解并且有一些主要问题,但是如果您理解它就很好。JavaScript的继承不是以设置原型为开始和结束的,初始化(构造函数)也要(重新)使用。
HMR 2014年

您的答案很好地解释了原型,但由于过度简化了JavaScript和实例特定成员的继承,可能会造成误解。已经提出了很多问题,为什么对一个实例进行原型成员更改会影响其他实例。
HMR 2014年


10

考虑以下keyValueStore对象:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
        this.get = function(key) { return this.data[key]; };
        this.set = function(key, value) { this.data[key] = value; };
        this.delete = function(key) { delete this.data[key]; };
        this.getLength = function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  { // Singleton public properties
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

我可以通过执行以下操作创建该对象的新实例:

kvs = keyValueStore.create();

该对象的每个实例将具有以下公共属性:

  • data
  • get
  • set
  • delete
  • getLength

现在,假设我们创建了该keyValueStore对象的100个实例。尽管getsetdeletegetLength会为每个100个实例的完全一样的东西,每个实例都有自己的这个函数的副本。

现在,想象一下,如果你可以有只是一个单一的getsetdeletegetLength复制,并且每个实例将引用相同的功能。这样可以提高性能,并减少内存需求。

这就是原型的来源。原型是继承但不被实例复制的属性的“蓝图”。因此,这意味着它在对象的所有实例中仅在内存中存在一次,并由所有这些实例共享。

现在,keyValueStore再次考虑该对象。我可以这样重写它:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
    };

    kvs.prototype = {
        'get' : function(key) { return this.data[key]; },
        'set' : function(key, value) { this.data[key] = value; },
        'delete' : function(key) { delete this.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  {
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

这与该keyValueStore对象的先前版本完全相同,只是其所有方法现在都放在原型中。这意味着现在所有100个实例共享这四种方法,而不是每个实例都有自己的副本。


9

摘要:

  • 函数是javascript中的对象,因此可以具有属性
  • (构造函数)函数始终具有原型属性
  • 当将函数用作new关键字的构造函数时,对象将获取原型。可以__proto__在新创建的对象的属性上找到对该原型的引用。
  • __proto__属性是指prototype构造函数的属性。

例:

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

let me = new Person('willem');

console.log(Person.prototype) // Person has a prototype property

console.log(Person.prototype === me.__proto__) // the __proto__ property of the instance refers to prototype property of the function.

为什么这样有用:

当在对象上查找属性时,JavaScript具有一种称为“原型继承”的机制,基本上是这样做的:

  • 首先检查属性是否位于对象本身上。如果是这样,则返回此属性。
  • 如果该属性不在对象本身上,它将“爬升原型链”。它基本上看一下由proto属性引用的对象。在那里,它检查属性是否在proto所引用的对象上可用
  • 如果该属性不在原型对象上,它将沿着原型链一直爬到对象对象。
  • 如果无法在对象及其原型链的任何地方找到该属性,它将返回未定义。

例如:

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

let mySelf = new Person('Willem');

console.log(mySelf.__proto__ === Person.prototype);

console.log(mySelf.__proto__.__proto__ === Object.prototype);

更新:

__proto__尽管已在大多数现代浏览器中实现了该属性,但已弃用该 属性,获取原型对象引用的更好方法是:

Object.getPrototypeOf()


7

在理解这类内容时,我总是喜欢类比。在我看来,与原型低音继承相比,“原型继承”非常令人困惑,尽管原型是更简单的范例。实际上,对于原型而言,确实没有继承,因此名称本身具有误导性,它更像是一种“委托”。

想象一下....

您正在读高中,正在上课,今天有一个测验,但是您没有笔来填写答案。h!

您正坐在可能有笔的朋友Finnius旁边。您问,他环顾四周办公桌没有成功,但他没有说“我没有笔”,而是一个好朋友,他与其他朋友Derp核对了他是否有笔。Derp确实有一支备用笔,并将其传递回Finnius,后者将其交给您以完成测验。Derp已将钢笔委托给Finnius,后者已将钢笔委托给您使用。

这里重要的是,Derp不会把笔给您,因为您与他没有直接关系

这是原型工作方式的简化示例,其中在数据树中搜索您要寻找的东西。


3

另一个显示__proto__原型构造函数关系的方案: 在此处输入图片说明


1

只是您已经有一个对象,Object.new但是使用构造函数语法时仍然没有对象。



0

原型创建新的对象通过克隆现有对象。因此,实际上,当我们考虑原型时,可以真正地考虑克隆或复制某些内容而不是进行组合。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.