我不太喜欢动态编程语言,但我已经编写了相当多的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”属性以解析属性引用。构造函数的“prototype”属性可以由程序表达式constructor.prototype引用,添加到对象原型中的财产通过继承由共享原型的所有对象共享。

我发现在引用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

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

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

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

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

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

当访问属性以读取其值时,将在范围链中查找该属性。变量的搜索从产品开始,一直到其原型。如果在搜索中找到了这样的变量,搜索将立即停止,并返回值。如果在作用域链中找不到这样的变量,则返回undefined。当写入(更改)属性时,该属性将始终写入产品对象。如果产品还没有这样的属性,则会隐式创建和编写它。

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

原型允许您创建类。如果不使用原型,那么它将成为静态的。

这里有一个简短的例子。

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实例,它们都具有测试功能。

以上是我的理解。我正在把它做成一个社区维基,所以如果我错了,人们可以纠正我。

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

原型链

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

向原型中添加函数/变量的优点在于,它只需在内存中存在一次,而不是针对每个实例。

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