我不太喜欢动态编程语言,但我已经编写了相当多的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

这些幻灯片也帮了大忙。


当前回答

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

这里有一个简短的例子。

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

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

其他回答

我发现在引用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。当写入(更改)属性时,该属性将始终写入产品对象。如果产品还没有这样的属性,则会隐式创建和编写它。

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

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

考虑构造函数:

 function Person() {}

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

向上的链条如下所示:

人员→ 功能.原型→ Object.prototype(终点)

重要的是,这个原型链与Person可以构造的对象关系不大。这些构造的对象有自己的原型链,而这个链可能和上面提到的对象没有共同的祖先。

以该对象为例:

var p = new Person();

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

p型→ 人物原型→ Object.prototype(终点)

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

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

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

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

总结如下:

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

属性原型的名称会导致混淆,这并不奇怪。如果将此属性命名为prototypeOfConstructedInstance或类似的名称,可能会更清楚。

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

Person.prototype.constructor === Person

通过将不同的对象显式指定给原型属性,可以打破这种对称性(稍后将详细介绍)。

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

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

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

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

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

以下是有助于把握问题的一些等式——所有这些都是正确的:

函数Person(){};//这是构造函数(函数对象)的原型链信息:console.log(Object.getPrototypeOf(Person)==Function.prototype);//在同一层次结构中更进一步:console.log(Object.getPrototypeOf(Function.prototype)==Object.proto原型);console.log(Object.getPrototypeOf(Object.prototype)==null);console.log(Person.__proto__==Function.prototype);//这里我们交换通道,并查看构造函数的构造函数console.log(Person.constructor==函数);console.log(函数的个人实例);//Person.prototype由Person创建(创建时)//这里我们来回交换车道:console.log(Person.prototype.constructor==人员);//虽然它不是它的一个实例:console.log(!(Person.prototype instanceof Person));//实例是由构造函数创建的对象:var p=新角色();//类似于为构造函数显示的内容,这里我们有//构造函数创建的对象也是如此:console.log(Object.getPrototypeOf(p)==Person.prototype);console.log(p.__proto__==Person.prototype);//在这里,我们交换通道,并查看构造函数console.log(p.constructor==人员);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长一步:

吨→ p→ 人物原型→ Object.prototype(终点)

另一个原型链不再长:Thief和Person是兄弟姐妹,在原型链中共享同一个父级:

个人}小偷}→ 功能.原型→ Object.prototype(终点)

之前展示的图形可以扩展到这一点(省略了最初的Thief.原型):

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

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

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

函数的.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] :除空值外的所有值。

面向对象JavaScript的最终指南-一个非常简洁和清晰的30分钟视频解释(原型继承主题从5:45开始,尽管我更愿意听整个视频)。这段视频的作者还制作了JavaScript对象可视化工具网站http://www.objectplayground.com/.