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

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

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

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

更新:正确方式

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

这些幻灯片也帮了大忙。


当前回答

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

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

这是两件不同的事情。

[[Prototype]]属性:

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

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

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

var obj = {}
obj.a         // [[Get]] consults prototype chain
obj.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

最重要的原型之一是Object函数的原型。此原型包含所有[[prototype]]链包含的原型对象。在上面,定义了新对象的所有可用方法:

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

现在,由于.prototype是一个对象,它有一个[[prototype]]属性。如果您没有对Function.prototype进行任何赋值,.prototype的[[prototype]]将指向原型对象(object.prototype)。这将在您创建新函数时自动执行。

这样,任何时候你做新酒吧;原型链已为您设置,您将获得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] :除空值外的所有值。

其他回答

这个“.prototype”属性的确切用途是什么?

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

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

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

http://iwiki.readthedocs.org/en/latest/javascript/js_core.html#inheritance

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

and

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

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

原型链最终返回到Object.prototype。原型链可以根据您的需要进行技术上的扩展,每次都将子类的原型设置为父类的对象。

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

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

想象一下。。。。

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

你坐在你的朋友芬尼乌斯旁边,他可能有一支笔。你问,他环顾了一下他的桌子,但没有成功,但他不是说“我没有笔”,而是一个很好的朋友,他会和他的另一个朋友Derp检查是否有笔。德普确实有一支备用笔,并将其交给芬尼乌斯,后者将其交由你完成测验。德普已将笔交给芬尼乌斯,后者已将笔委托给你使用。

这里重要的是,德普不会把笔交给你,因为你和他没有直接关系。

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

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

如果obj_n.prop_X不存在,请检查obj_n+1.prop_X,其中obj_n+1=obj_n。[[原型]]

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

obj_1.prop_X=obj_1.[[原型]].[[原型]]..(k次)..[[原型]].prop_X

您可以在此处通过Javascript对象的财产找到它们之间的关系图:

http://jsobjects.org

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

如果我们在Tree函数上使用一个新运算符,我们将其作为构造函数调用。

每个JavaScript函数都有一个原型。当你记录Tree.prototype时,你会。。。

如果查看上面的console.log()输出,您可以看到Tree.prototype上的构造函数属性和__proto__属性。__proto__表示这个函数所基于的原型,由于这只是一个普通的JavaScript函数,还没有设置继承,所以它引用的是Object原型,它是JavaScript中刚刚内置的东西。。。

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

这包括.toString、.toValue、.hasOwnProperty等。。。

__我的mozilla带来的proto__已被弃用,取而代之的是Object.getPrototypeOf方法来获取对象的原型。

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

让我们向Tree原型添加一个方法。

我们修改了Root并向其添加了一个函数分支。

这意味着当您创建Tree实例时,可以调用它的分支方法。

我们还可以向原型中添加原语或对象。

让我们在树中添加一个子树。

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

孩子现在有了自己的原型,它的__proto__指向Tree,Tree的原型指向base Object。

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

现在您创建了一个最初在Tree中可用的Child和call分支的实例。我们还没有在Child原型上定义分支。但是,在Child继承的Root原型中。

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

Javascript有字符串、数字、布尔值、未定义、null等原语。它们不是对象(即引用类型),但肯定可以充当对象。让我们看一个例子。

在该列表的第一行中,为name指定了一个基本字符串值。第二行将名称视为对象,并使用点表示法调用charAt(0)。

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

String对象在被销毁之前只存在一个语句(一个称为自动装箱的过程)。让我们再次回到我们的原型继承。

Javascript支持基于原型。每个函数都有一个原型属性,该属性引用另一个对象财产/函数是从对象本身或通过原型链(如果不存在)

JS中的原型是一个对象,它使您成为另一个对象的父对象。授权意味着如果你不能做某事,你会告诉别人帮你做。

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

如果您查找上面的fiddle,dog可以访问toString方法,但它在其中不可用,但可以通过委托给Object.prototype的原型链使用

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

https://jsfiddle.net/rknffckc/

如果您查找上面的fiddle,Profile Function可以访问调用方法,但它在其中不可用,但可以通过委托给Function.prototype的原型链使用

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

上图看起来有点复杂,但展示了原型链接工作原理的全貌。让我们慢慢来:

有两个实例b1和b2,其构造函数是Bar,父实例是Foo,并且有两个来自原型链的方法通过Bar和Foo识别和说话

https://jsfiddle.net/kbp7jr7n/

如果你查阅上面的代码,我们有Foo构造函数,它有方法identify(),Bar构造函数有speak方法。我们创建了两个Bar实例b1和b2,它们的父类型是Foo。现在,在调用Bar的speak方法时,我们能够通过原型链识别谁在调用speak。

Bar现在拥有Foo的所有方法,这些方法都在其原型中定义。让我们进一步了解Object.prototype和Function.prototype以及它们之间的关系。如果您查找Foo的构造函数,Bar和Object是Function构造函数。

Bar的原型是Foo,Foo的原型是Object,如果仔细观察,Foo原型与Object.prototype相关。

在结束之前,让我们在这里用一小段代码来总结上面的内容。我们在这里使用instanceof运算符来检查对象的原型链中是否包含构造函数的prototype属性,该属性在下面概括了整个大型图。

我希望这是一些信息,我知道这类信息可能很难掌握。。。简单地说,它只是链接到对象的对象!!!!