我不太喜欢动态编程语言,但我已经编写了相当多的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”属性的确切用途是什么?

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

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

其他回答

面向对象JavaScript的最终指南-一个非常简洁和清晰的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();

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

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

现在,假设我们创建了这个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个实例现在都共享这四个方法,而不是每个实例都有自己的副本。

另一个显示__proto__、原型和构造函数关系的方案:

这个“.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] :除空值外的所有值。