JavaScript.原型如何工作?

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

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

我记得不久前我和人们进行了很多讨论(我不确定我在做什么),但据我所知,没有类的概念。它只是一个对象,这些对象的实例是原始对象的克隆,对吧?

但是JavaScript中这个“.原型”属性的确切目的是什么?它与实例化对象有什么关系?

更新:正确的方式

var obj = new Object(); // not a functional objectobj.prototype.test = function() { alert('Hello?'); }; // this is wrong!
function MyObject() {} // a first class functional objectMyObject.prototype.test = function() { alert('OK'); } // OK

这些幻灯片真的帮助了很多。

508597 次浏览

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,所以如果我错了,人们可以纠正我。

这个“原型”属性的确切目的是什么?

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

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

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

原型链

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

将函数/变量添加到原型的优点是它必须只在内存中出现一次,而不是每个实例。

它对继承也很有用,因为原型链可以由许多其他对象组成。

每个JavaScript对象有一个内部“插槽”称为[[Prototype]],其值为nullobject。您可以将插槽视为对象上的属性,它是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. 名为__proto__(类似于4)的标准化访问器(即getter/setter)属性。

Object.getPrototypeOfObject.setPrototypeOf优于__proto__,部分原因是当对象具有null的原型时o.__proto__是不寻常的行为。

对象的[[Prototype]]最初是在对象创建期间设置的。

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

请注意,因此,所有类以及所有可以与#0运算符一起使用的函数,除了它们自己的#2内部插槽外,还有一个名为#1的属性。“原型”这个词的双重使用是该语言新手无休止混淆的根源。

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 = childreturn child;}
Child = inherit(Child, Parent)const o = new Childconsole.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.prototypeconst proto = new tmp()proto.constructor = childchild.prototype = protoreturn child}
Child = inherit(Child, Parent)const o = new Childconsole.log(o.inheritedMethod()) // 'this is inherited'

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

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

class Parent { inheritedMethod() { return 'this is inherited' } }class Child extends Parent {}
const o = new Childconsole.log(o.inheritedMethod()) // 'this is inherited'

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

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

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

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

在JavaScript中,你首先创建一个对象(没有类的概念),然后你可以增强你自己的对象或从中创建新对象。这并不难,但对于习惯经典方式的人来说有点陌生和难以新陈代谢。

示例:

//Define a functional object to hold persons in JavaScriptvar Person = function(name) {this.name = name;};
//Add dynamically to the already defined object a new getterPerson.prototype.getName = function() {return this.name;};
//Create a new object of type Personvar john = new Person("John");
//Try the getteralert(john.getName());
//If now I modify person, also John gets the updatesPerson.prototype.sayMyName = function() {alert('Hello, my name is ' + this.getName());};
//Call the new method on johnjohn.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//prototypeCustomer.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());

var Person = function (name) {this.name = name;};Person.prototype.getName = function () {return this.name;};var john = new Person("John");alert(john.getName());Person.prototype.sayMyName = function () {alert('Hello, my name is ' + this.getName());};john.sayMyName();var Customer = function (name) {this.name = name;};Customer.prototype = new Person();
var myCustomer = new Customer('Dream Inc.');myCustomer.sayMyName();Customer.prototype.setAmountDue = function (amountDue) {this.amountDue = amountDue;};Customer.prototype.getAmountDue = function () {return this.amountDue;};myCustomer.setAmountDue(2000);alert(myCustomer.getAmountDue());

正如我所说,我不能调用setAmount(),而在Person上调用getAmount()。

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

看完这篇文章后,我对JavaScript原型链感到困惑,然后我发现了这些图表

http://iwiki.readthedocs.org/en/latest/javascript/js_core.html#inheritance*[[protytype]]*和<code>原型</code>函数对象的属性

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

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

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

原型链最终会回到Object.prototype.

原型链可以在技术上扩展,只要你愿意,每次都通过将子类的原型设置为等于父类的对象。

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

每个对象都有一个内部属性原型,将其链接到另一个对象:

object [[Prototype]] → anotherObject

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

object [[Prototype]] → aFunction.prototype

一些环境将原型暴露为__proto__

anObject.__proto__ === anotherObject

您在创建对象时创建原型链接。

// (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 onlyvar object = new Object;

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

记住:

  • 每个对象都有一个链接原型,有时显示为__proto__
  • 每个函数都有一个prototype属性,最初持有一个空对象。
  • 使用新的创建的对象链接到其构造函数的prototype属性。
  • 如果一个函数从未被用作构造函数,它的prototype属性将被闲置。
  • 如果您不需要构造函数,请使用Object.create而不是new

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

如果obj_n.prop_X不存在,请选中obj_n+1.prop_Xobj_n+1 = obj_n.[[prototype]]

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

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

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

js对象图

http://jsobjects.org

这是一个非常简单的基于原型的对象模型,在解释期间将被视为示例,尚未发表评论:

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

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

1. JavaScript函数实际上是如何工作的:

为了迈出第一步,我们必须弄清楚JavaScript函数实际上是如何工作的,作为一个使用#0关键字的类函数,或者只是作为一个带有参数的常规函数,它做什么和它返回什么。

假设我们想要创建一个Person对象模型。但在这一步中,我将尝试在不使用#1和#2关键字的情况下做同样的事情

所以在这一步#0#1#2关键字,是我们所拥有的一切。

第一个问题是#0关键字如何在不使用#1关键字的情况下有用

因此,为了回答这个问题,假设我们有一个空对象和两个函数,例如:

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

现在不使用#0关键字我们如何使用这些函数。所以JavaScript有3种不同的方法来做到这一点:

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

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

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

我们可以附上它们到一个对象,作为它的属性

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

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

我们可以这样称呼它们:

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

现在person对象就像:

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

-附加财产的另一种方式到一个对象使用了该对象的prototype,该对象可以在任何名称为__proto__的JavaScript对象中找到,我试图在摘要部分解释它。所以我们可以通过这样做得到类似的结果:

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

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

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

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

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

它所做的是创建一个新的JavaScriptObject并将propertiesObject附加到__proto__属性。所以为了确保你可以这样做:

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

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


正如您所看到的,使用这两种方式中的任何一种,this都将准确地指向person对象。

c。JavaScript还有另一种方式为函数提供this,即使用打电话适用来调用函数。

应用()方法调用一个给定this值的函数作为数组(或类似数组的对象)提供的参数。

call()方法调用具有给定this值的函数,并且单独提供的参数。

这是我最喜欢的方式,我们可以轻松地调用我们的函数,例如:

Person.call(person, "George");

//apply is more useful when params count is not fixedPerson.apply(person, ["George"]);
getName.call(person);getName.apply(person);

这3个方法是找出.原型功能的重要初始步骤。


new关键字是如何工作的?

这是理解.prototype的第二步functionality.this是我用来模拟过程的:

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

在这一部分中,我将尝试采取JavaScript所采取的所有步骤,而不使用new关键字和prototype,当你使用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实际附加原型中定义的属性的方式。实际的方式与原型链概念有关。


a.&b.而不是这两个步骤,您可以通过执行以下操作获得完全相同的结果:

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

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

newObject.getName();

c.然后它将该对象提供给构造函数,

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

Person.call(newObject, "George");

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

然后构造函数可以做任何它想做的事情,因为该构造函数内部的这个是刚刚创建的对象。

现在是模拟其他步骤之前的最终结果:对象{name:"George"}


总结:

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

new FunctionName()

JavaScript在内部创建一个对象,一个空哈希,然后它将该对象提供给构造函数,然后构造函数可以做任何它想做的事情,因为该构造函数内部的这个是刚刚创建的对象,然后它会给你那个对象,当然,如果你没有在函数中使用返回语句,或者如果你在函数体的末尾放了一个return undefined;

所以当JavaScript去查找一个对象的属性时,它做的第一件事,就是在那个对象上查找它。然后有一个秘密属性#0,我们通常把它像#1一样,这个属性就是JavaScript接下来要查看的。当它查看#1时,只要它是另一个JavaScript对象,它有自己的#1属性,它就会不断上升,直到它到达下一个#1为空的点。这一点是JavaScript中唯一一个#1属性为空的对象是Object.prototype对象:

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

这就是继承在JavaScript中的工作原理。

原型链

换句话说,当你在一个函数上有一个原型属性并且你调用了一个new,在JavaScript完成查看新创建的对象的属性后,它会查看函数的.prototype,并且这个对象可能有自己的内部原型。

原型的七个Koans

当西罗圣经过深思后下火狐山时,他的头脑清晰而平静。

然而,他的手不安分,自己抓起画笔,记下下面的笔记。


0)两种不同的东西可以称为“原型”:

  • 原型属性,如obj.prototype

  • 原型内部属性,表示为[[Prototype]]在es5

    它可以通过ES5Object.getPrototypeOf()检索。

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


1)这些概念的存在是为了回答这个问题:

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

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


2)

  • __proto__用于点.属性查找,如obj.property所示。
  • .prototype没有直接用于查找,只是间接地,因为它在使用new创建对象时确定__proto__

查找顺序是:

  • obj添加了obj.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) The code:

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__找到,括号必须满足语法)

我们的2行代码只创建了以下新对象:

  • f
  • F
  • F.prototype

i现在是f的属性,因为当你这样做时:

var f = new F(1)

它计算Fthisnew将返回的值,然后将其分配给f


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

f.constructor === F!f.hasOwnProperty('constructor')Object.getPrototypeOf(f) === F.prototypeF.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 === FunctionC.__proto__ === Function.prototypeD.constructor === Function// D is a function "indirectly" through the chain.D.__proto__ === CD.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class// lookups will work as expectedvar d = new D(1)d.__proto__ === D.prototypeD.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-alternativesC.c = 1C.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)|                incvFunction.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会在Y()函数调用中自动将this设置为等于X

同样的逻辑也解释了d.incd.inc2

本文https://javascript.info/class#not-just-a-syntax-sugar提到了class值得了解的进一步效果。如果没有class关键字,其中一些可能无法实现(待办事项请检查哪个):

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

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

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

每个对象都包含一个名为[[原型]]的内部属性,可以由Object.getPrototypeOf()函数访问。Object.create(model)创建一个新对象并将其[[原型]]属性设置为对象模型。因此,当您执行Object.getPrototypeOf(product)时,您将获得对象模型

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

  • 当一个属性被访问只是为了读取它的值时,它会在作用域链中查找。对变量的搜索从产品开始,一直到它的原型。如果在搜索中找到这样的变量,搜索就会停止,并返回值。如果在作用域链中找不到这样的变量,则返回未定义。
  • 写入(更改)属性时,该属性始终写入产品对象。如果产品还没有这样的属性,则会隐式创建和写入它。

这种使用原型属性的对象链接称为原型继承。在那里,它很简单,同意吗?

另一个尝试用更好的图片解释基于JavaScript原型的继承

简单对象继承

这篇文章很长。但我相信它会清除你的大部分疑问关于JavaScript继承的“原型”性质。甚至更多。请阅读完整的文章。

JavaScript基本上有两种数据类型

  • 非对象
  • 对象

非对象

以下是非对象数据类型

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

当您使用typeof运算符时,这些数据类型返回以下内容

typeof"字符串文字"(或包含字符串文字的变量) === 字符串

typeof5(或任何数字文字或包含数字文字的变量或NaN或Infynity) === 数字

typeof真正(或虚假或包含真正虚假的变量) === 布尔型

typeof未定义(或未定义的变量或包含未定义的变量) === “不确定”

字符串数量布尔数据类型可以表示为对象非对象。当它们表示为对象时,它们的类型总是==='对象'。一旦我们理解了对象数据类型,我们将回到这个问题。

对象

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

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

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

  • 对象
  • String
  • 数量
  • 布尔
  • 数组
  • 类型化数组
  • RegExp
  • 函数
  • 所有其他可以使用new运算符创建新对象的内置对象
  • 函数用户定义参数(){/*用户自定义代码*/}

所以,typeof(对象) === typeof(字符串)===typeof(数量)===typeof(布尔值)===typeof(数组)===typeof(RegExp)===typeof(函数)===类型(用户定义函数)===函数

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

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")

如前所述,函数类型对象可以使用新运营商进一步创建新对象。例如,可以使用以下命令创建对象String数量布尔数组RegExp用户定义参数类型的对象

var a=new Object() or var a=Object() or var a={} //Create object of type Objectvar a=new String() //Create object of type Stringvar a=new Number() //Create object of type Numbervar a=new Boolean() //Create object of type Booleanvar a=new Array() or var a=Array() or var a=[]  //Create object of type Arrayvar a=new RegExp() or var a=RegExp() //Create object of type RegExpvar a=new UserDefinedFunction()

这样创建的对象都是非函数类型对象并返回它们的typeof===对象。在所有这些情况下,对象“a”都不能进一步创建使用运算符new的对象。所以下面是错误的

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

内置对象Mathtypeof===对象。因此,new运算符无法创建Math类型的新对象。

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

另请注意,对象数组RegExp函数甚至可以在不使用运营商新的情况下创建一个新对象。但是下面的函数没有。

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 UserDefinedFunction(){}

以下自动发生

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

“原型”属性仅存在于函数类型对象中(永远不会在非函数类型对象)。

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

在对象中创建的用于引用继承属性的“内部参考”称为对象的原型(它引用了构造函数“原型”属性引用的对象,但与之不同)。对于任何对象(函数或非函数),都可以使用Object.get原型()方法检索。使用此方法可以跟踪对象的原型链。

此外,创建的每个对象函数类型非函数类型)具有构造函数属性,该属性继承自构造函数的原型属性引用的对象。默认情况下,此构造函数属性引用创建它的构造函数(如果构造函数的默认“原型”未更改)。

对于所有函数类型对象,构造函数总是函数Function(){}

对于非函数类型对象(例如Javascript Built in 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.prototypeUserDefinedFunction.prototype=objA
alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays false. The object referenced by UserDefinedFunction.prototype has changed
//The internal reference does not changealert(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 UserDefinedFunctionvar 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 objAobjA.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对象(包括内置的Function对象)->Function.prototype->Object.prototype->null
  • 简单对象(由new Object()或{}创建,包括内置的Math对象)->Object.prototype->null
  • 使用新的或Object.create创建的对象->一个或多个原型链->Object.prototype->null

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

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

有人可能会认为将构造函数的原型属性设置为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 truealert(o.constructor)    //Displays Function Object

以下是本文的摘要

  • 有两种类型的对象函数类型非函数类型
  • 只有函数类型对象可以使用运算符new创建新对象。这样创建的对象是非函数类型对象。非函数类型对象不能使用运算符new进一步创建对象。

  • 默认情况下,所有函数类型对象都有一个“原型”属性。这个“原型”属性引用了一个对象,该对象有一个“构造函数”属性,默认情况下引用了函数类型对象本身。

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

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

  • 可以使用Object.get原型()方法检索对象的原型(以及它的继承属性名称)。事实上,这个方法可以用于导航对象的整个原型链。

  • 每个对象的原型链最终都可以追溯到Object.prototype(除非对象是使用Object.create(null)创建的,在这种情况下,对象没有原型)。

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

  • 将构造函数的原型属性设置为空(或未定义、数字、真、假、字符串)不会创建具有空原型的对象。在这种情况下,新创建的对象的原型设置为Object.prototype,其构造函数设置为函数Object。

希望这有帮助。

这段30分钟的视频解释了提出的问题(原型继承的话题从5点45分开始,虽然我宁愿听整个视频)。这段视频的作者还制作了JavaScript对象可视化器网站http://www.objectplayground.com/在此处输入图像描述输入图片描述

考虑以下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个实例现在共享这四个方法,而不是每个实例都有自己的副本。

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

在此处输入图片描述

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

在此处输入图片描述

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

在此处输入图片描述

如果你看一下上面的console.log()输出,你可以看到Tree.prototype上的构造函数属性和__proto__属性。__proto__代表function所基于的prototype,由于这只是一个普通的JavaScript function,还没有设置inheritance,它指的是Object prototype,这是JavaScript中刚刚内置的东西…

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

这有.toString, .toValue, .hasOwnProperty等…

__proto__,这是带来了我的mozilla被弃用,并被Object.getPrototypeOf方法来代替,以获得object's prototype

在此处输入图片描述

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

让我们在Treeprototype中添加一个方法。

在此处输入图片描述

我们修改了Root并添加了function分支。

在此处输入图片描述

这意味着当你创建Treeinstance时,你可以调用它的branch方法。

在此处输入图片描述

我们也可以将primitivesobjects添加到我们的Prototype

在此处输入图片描述

让我们将child-tree添加到我们的Tree

在此处输入图片描述

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

在此处输入图片描述

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

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

现在您创建Childinstance并调用最初在Tree中可用的branch。我们实际上没有在Child prototype上定义我们的branch。但是,在孩子继承的Root prototype中。

在此处输入图片描述

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

Javascript有像strings, number, booleans, undefined, null.这样的原语,它们不是object(i.e reference types),但肯定可以像object一样。让我们在这里看一个例子。

在此处输入图片描述

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

这是幕后发生的事情://JavaScript引擎的作用

在此处输入图片描述

String object在被销毁之前只存在一个语句(一个称为autoboxing的过程)。让我们再次回到我们的prototypalinheritance

  • Javascript支持通过delegation继承,基于prototypes.
  • 每个Function都有一个prototype属性,它引用了另一个对象。
  • properties/functions是从object本身或通过prototype链,如果它不存在

JS中的prototype是一个对象,yields是你另一个object的父对象。[即代表团]Delegation意味着如果你不能做某事,你会告诉别人为你做。

在此处输入图片描述

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

如果你查看上面的小提琴,狗可以访问toString方法,但它在其中不可用,但可以通过委托给Object.prototype的原型链获得

在此处输入图片描述

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

在此处输入图片描述

https://jsfiddle.net/rknffckc/

如果你查看上面的小提琴,Profile函数可以访问call方法,但它在其中不可用,但可以通过委托给Function.prototype的原型链使用

在此处输入图片描述

备注:prototype是函数构造器的属性,而__proto__是由函数构造器构造的对象的属性。每个函数都带有一个prototype属性,其值为空object。当我们创建函数的实例时,我们得到一个内部属性[[Prototype]]__proto__,其引用是函数constructor的原型。

在此处输入图片描述

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

有两个实例b1b2,其构造函数是Bar,父级是Foo,并且有两个来自原型链identifyspeak的方法,通过BarFoo

在此处输入图片描述

https://jsfiddle.net/kbp7jr7n/

如果您查看上面的代码,我们有Foo构造函数,它具有方法identify()Bar构造函数,它具有speak方法。我们创建了两个Bar实例b1b2,其父类型为Foo。现在在调用Barspeak方法时,我们能够通过identify()0链识别谁在调用语音。

在此处输入图片描述

Bar现在拥有Foo的所有方法,这些方法在其prototype中定义。让我们进一步了解Object.prototypeFunction.prototype以及它们是如何相关的。如果你查找Foo的构造函数,BarObjectFunction constructor

在此处输入图片描述

BarprototypeFooFooprototypeObject,如果你仔细看,FooprototypeObject.prototype有关。

在此处输入图片描述

在我们结束之前,让我们在这里用一小块代码包装到总结上面的一切。我们在这里使用instanceof运算符来检查object在其prototype链中是否具有constructorprototype属性,下面总结了整个大图。

在此处输入图片描述

我希望这个添加的一些信息,我知道这有点可能是大的把握……简单地说,它的它只是对象链接到对象!!!!

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

考虑构造函数:

 function Person() {}

Object.getPrototypeOf(Person)的值是一个函数。事实上,它是Function.prototype。由于Person是作为一个函数创建的,它共享所有函数都具有的相同原型函数对象。它与Person.__proto__相同,但该属性不应该被使用。无论如何,有了Object.getPrototypeOf(Person),你有效地走上了所谓的原型链的阶梯。

向上的链条看起来像这样:

PersonFunction.prototypeObject.prototype(终端)

重要的是,这个原型链与Person可以构建的对象几乎没有关系。那些构造的对象有自己的原型链,这个链可能与上面提到的没有共同的密切祖先。

以这个对象为例:

var p = new Person();

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

pPerson.prototypeObject.prototype(终端)

这个链中没有函数对象(尽管可能是)。

所以Person似乎与两种链条有关,它们过着自己的生活。要从一条链“跳转”到另一条链,您可以使用:

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

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

以下是所涉及的两个原型链的可视化演示,表示为列:

在此处输入图片描述

总结如下:

prototype属性不提供对象的原型链的信息,但提供对象创建主题的信息。

毫不奇怪,属性prototype的名称会导致混淆。如果这个属性被命名为prototypeOfConstructedInstances或类似的东西,可能会更清楚。

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

Person.prototype.constructor === Person

这种对称性可以通过显式地将不同的对象分配给prototype属性来打破(稍后会详细介绍)。

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

Person.prototype是在创建函数Person的同时创建的对象。它有Person作为构造函数,尽管该构造函数实际上还没有执行。所以同时创建了两个对象:

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

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

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

以下是一些可以帮助理解问题的等式-所有这些printtrue

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 constructorconsole.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 constructorconsole.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长了一步:

tpPerson.prototypeObject.prototype(终端)

另一个原型链不再:ThiefPerson是在其原型链中共享相同父链的兄弟姐妹:

Person}
Thief; } → Function.prototypeObject.prototype(终端)

然后可以将前面呈现的图形扩展为(原始Thief.prototype被省略):

在此处输入图片描述

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

  • 在一个对象和它的构造函数之间
  • 在构造函数和将用于构造对象的原型对象之间

在理解这类东西时,我总是喜欢类比。在我看来,与类低音继承相比,“原型继承”相当令人困惑,尽管原型是更简单的范式。事实上,有了原型,真的没有继承,所以名字本身就具有误导性,它更多的是一种“委托”。

想象一下……

你在上高中,你在上课,今天有一个测验,但是你没有笔来填写你的答案。

你坐在你的朋友Finnius旁边,他可能有一支笔。你问他,他环顾了一下自己的办公桌,但没有成功,但他是一个很好的朋友,他没有说“我没有笔”,而是和他的另一个朋友Derp一起检查他是否有笔。Derp确实有一支备用笔,并把它传回给Finnius,Finnius把它交给你来完成你的测验。Derp把笔委托给Finnius,他把笔委托给你使用。

这里重要的是Derp不会把笔给你,因为你和他没有直接的0。

这是原型如何工作的一个简化示例,其中搜索数据树以查找您要查找的内容。

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

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

这是两件不同的事情。

[[Prototype]]属性:

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

这里存储的是另一个对象,作为一个对象本身,它有自己的[[Prototype]]指向另一个对象。那个其他对象有自己的[[Prototype]]。这个故事一直持续到你到达原型对象,它提供了所有对象都可以访问的方法(比如#2)。

[[Prototype]]属性是构成[[Prototype]]链的一部分。例如,当对对象执行[[Get]][[Set]]操作时,会检查这个[[Prototype]]对象链:

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

.prototype属性:

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

function Bar(){};

.prototype属性持有物体将在您执行var b = new Bar时分配给b.[[Prototype]]。您可以轻松检查:

// 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之一是#1函数的。这个原型包含所有[[Prototype]]链包含的原型对象。在上面,定义了新对象的所有可用方法:

// Get properties that are defined on this objectconsole.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 propertiesconsole.log(b.__proto__ === Bar.prototype)// Get all Object.prototype propertiesconsole.log(b.__proto__.__proto__ === Object.prototype)

当您Function.prototype赋值时,您所做的只是扩展原型链以包含另一个对象。这就像在单链表中的插入。

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


[1:这不会让任何人感到困惑;通过#0属性在许多实现中。
[2]:除了#0之外的所有。

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

另一个显示__proto__原型构造函数关系的方案:输入图片描述

总结:

  • 函数是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在查找对象属性时有一种机制,称为原型继承,以下是基本上的作用:

  • 首先检查属性是否位于对象本身上。如果是,则返回此属性。
  • 如果属性不位于对象本身,它将“爬上原型链”。它基本上查看原型属性引用的对象。在那里,它检查原型引用的对象上是否有该属性可用
  • 如果属性不位于原型对象上,它将沿着原型链向上爬到Object对象。
  • 如果它在对象及其原型链上找不到属性,它将返回未定义。

例如:

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()

原型通过克隆现有的对象来创建新对象。所以当我们考虑原型时,我们真的可以认为克隆或制造是某个东西的副本,而不是编造它。

重要的是要理解对象的原型(可通过Object.getPrototypeOf(obj)或通过已弃用的__proto__属性获得)和构造函数上的prototype属性之间存在区别。前者是每个实例上的属性,后者是构造函数上的属性。也就是说,Object.getPrototypeOf(new Foobar())引用了与Foobar.prototype相同的对象。

参考:https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes

如果你想从基础知识中理解原型和基于原型的继承的概念,请查看官方的MDN文档,他们解释得很好。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

在继承方面,JavaScript只有一个构造:对象。每个对象都有一个私有属性,其中包含指向另一个对象称为它的原型。该原型对象有一个它自己的原型,依此类推,直到使用null到达对象作为其原型。根据定义,null没有原型,并充当这个原型链的最后一个链接。

此外,这里还有另一个很好的资源,可以使用简单的示例进行解释-https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes