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

这些幻灯片也帮了大忙。


当前回答

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

其他回答

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

考虑构造函数:

 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.原型):

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

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

考虑以下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();

此对象的每个实例都将具有以下公共财产:

数据收到设置删去获取长度

现在,假设我们创建了这个keyValueStore对象的100个实例。尽管get、set、delete、getLength将对这100个实例中的每一个执行完全相同的操作,但每个实例都有自己的函数副本。

现在,想象一下,如果您只有一个get、set、delete和getLength副本,并且每个实例都引用相同的函数。这将更好地提高性能,并且需要更少的内存。

原型就是这样产生的。原型是财产的“蓝图”,它是继承而不是被实例复制的。因此,这意味着对于一个对象的所有实例,它只在内存中存在一次,并且由所有这些实例共享。

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

看完这篇文章后,我对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原型链也有帮助。

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

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

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

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

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