在原型继承中实现实例方法/变量

5

我在阅读了http://javascript.crockford.com/prototypal.html之后,对原型继承进行了一些尝试,并且在理解如何在类似于经典继承的方式中使用它方面遇到了一些问题。也就是说,除非子对象覆盖它们,否则由原型继承的所有函数和变量本质上都变成了静态内容。请考虑以下代码段:

var Depot = {   
    stockpile : [],
    loadAmmo : function (ammoType) {
        this.stockpile.push(ammoType);
    }
};

var MissileDepot = Object.create(Depot);
var GunDepot = Object.create(Depot);

stockpileloadAmmo应该放在原型中,因为MissileDepot和GunDepot都有它们。然后我们运行:

MissileDepot.loadAmmo("ICBM");
MissileDepot.loadAmmo("Photon Torpedo");

alert(MissileDepot.stockpile); // outputs "ICBM,Photon Torpedo"
alert(GunDepot.stockpile); // outputs "ICBM,Photon Torpedo"

这是预期的,因为 MissileDepotGunDepot 都没有 stockpileloadAmmo 属性,所以 JavaScript 查找它们的共同祖先来继承这些属性。
当然,我可以手动设置 GunDepot 的 stockpile 属性,正如预期的那样,解释器就不再需要查找继承链了。
GunDepot.stockpile = ["Super Nailgun", "Boomstick"];
alert(GunDepot.stockpile); // outputs "Super Nailgun,Boomstick"

但这不是我想要的。如果这是经典继承(比如Java),loadAmmo将独立地在MissileDepot和GunDepot的库存中操作,作为实例方法和实例变量。我希望我的原型声明对子对象通用的内容,而不是被它们共享的。 因此,也许我完全误解了原型继承背后的设计原则,但我不知道如何实现我刚才描述的内容。有什么提示吗?提前致谢!

你不应该在对象名称 Depot、MissileDepot 和 GunDepot 中使用大写字母,这违反了常规约定并阻碍了理解。 - Eric Bréchemier
谢谢Eric - 我用它来表示一个类,就像Ruby/Java/php/任何语言一样。那么对于JavaScript,我们所有对象都使用驼峰命名法吗?有什么经验法则吗? - ambertch
抱歉,我的标点符号有误,我是想问“有什么经验法则吗?” - ambertch
类名应使用大写驼峰命名法,而类的实例则应使用小写驼峰命名法。 - Daniel X Moore
5个回答

8
Javascript提供了一种您熟悉的方式来实现这一点 :) 尝试这样做:
function Depot() {   
    this.stockpile = [],
    this.loadAmmo = function (ammoType) {
        this.stockpile.push(ammoType);
    }
};

var MissileDepot = new Depot();
var GunDepot = new Depot();


MissileDepot.loadAmmo("ICBM");
MissileDepot.loadAmmo("Photon Torpedo");

alert(MissileDepot.stockpile); // outputs "ICBM,Photon Torpedo"
alert(GunDepot.stockpile); // outputs ""

您可以随时添加功能:

MissileDepot.blow = function(){alert('kaboom');}

使用另一个对象扩展对象也是一种选择,但您想要的是,在JavaScript中进行面向对象编程是通过函数而不是带有{}的对象完成的;)

编辑:

我写下这段话时感到内疚,没有提到:JavaScript中的“new”关键字仅用于使面向对象的老手更容易。请深入了解原型继承和动态对象创建,其中包含真正的魔力!:)


1
loadAmmo() 应该位于 Depot.prototype 中,因为它可以在实例之间共享;你现在的做法是为每个 Depot 实例创建一个新的函数对象。 - Christoph
我不认为它会影响这个问题,但它确实很有趣,可能会有用。您能详细说明一下吗?或者给个小例子来看看区别? - naugtur
Depot.prototype.loadAmmo = function () { ... }; 这样,就只有一个函数实例在 Depot 实例之间共享。如果你在构造函数内部使用 this.loadAmmo = function () { ... };,那么你会为每个 Depot 实例分配一个新的函数实例。 - Ates Goral

3

对于这个方法,一切都按预期进行。只需注意您需要处理的字段即可。

在YUI中,我经常看到构造函数分配实例变量。从父类继承的“类”调用其父类的构造函数。请看这里:http://developer.yahoo.com/yui/docs/DataSource.js.html

示例基类:

util.DataSourceBase = function(oLiveData, oConfigs) {
    ...   
    this.liveData = oLiveData;

    ... more initialization...
}

示例子类:

util.FunctionDataSource = function(oLiveData, oConfigs) {
    this.dataType = DS.TYPE_JSFUNCTION;
    oLiveData = oLiveData || function() {};

    util.FunctionDataSource.superclass.constructor.call(this, oLiveData, oConfigs);   
};

// FunctionDataSource extends DataSourceBase
lang.extend(util.FunctionDataSource, util.DataSourceBase, {
    ...prototype of the subclass...
});

1

那是因为你试图让猫喵叫!Douglas Crockford很不错,但你使用的脚本实质上是通过循环遍历父对象并将其所有属性复制到原型链中来工作的--这不是你想要的。当你把东西放在原型链中时,它们会被该对象的所有实例共享--对于成员函数来说是理想的,但对于数据成员来说则不理想,因为你希望每个对象实例都有自己的数据成员集合。

John Resig编写了一个用于模拟经典继承的小脚本。你可能想要查看一下。


这个小脚本看起来让人感到非常熟悉! - Alsciende

1
为了实现你想要的功能,你需要一个克隆方法。你不需要继承原型,而是需要一个克隆原型。可以看一下已经实现的 Object.clone() 函数之一,比如 prototypejs 的一个:http://api.prototypejs.org/language/object.html#clone-class_method 如果你想坚持某种原型设计,你需要实现一个 initialize() 方法,将一个“stockpile”属性赋给你新创建的 Depots。这就是 prototypejs 类的定义方式:一个克隆的原型和一个 initialize() 方法:http://prototypejs.org/learn/class-inheritance

0
JavaScript 中实例变量的秘密在于它们在超类或包含模块中定义的方法之间共享。语言本身并没有提供这样的功能,而且可能无法与原型继承相融合,因为每个实例都需要自己的实例变量容器,但是通过使用纪律和约定,实现起来相当简单。
// Class Depot
function Depot(I) {
  // JavaScript instance variables
  I = I || {};

  // Initialize default values
  Object.reverseMerge(I, {
    stockpile: []
  });

  return {
    // Public loadAmmo method
    loadAmmo: function(ammoType) {
      I.stockpile.push(ammoType);
    },
    // Public getter for stockpile
    stockpile: function() {
      return I.stockpile;
    }
  };
}

// Create a couple of Depot instances
var missileDepot = Depot();
var gunDepot = Depot();

missileDepot.loadAmmo("ICBM");
missileDepot.loadAmmo("Photon Torpedo");

alert(missileDepot.stockpile()); // outputs "ICBM,Photon Torpedo"
alert(gunDepot.stockpile()); // outputs ""

// Class NonWeaponDepot
function NonWeaponDepot(I) {
  I = I || {};

  // Private method
  function nonWeapon(ammoType) {
    // returns true or false based on ammoType
  }

  // Make NonWeaponDepot a subclass of Depot and inherit it's methods
  // Note how we pass in `I` to have shared instance variables
  return Object.extend(Depot(I), {
    loadAmmo: function(ammoType) {
      if(nonWeapon(ammoType)) {
        // Here I.stockpile is the same reference an in the Depot superclass
        I.stockpile.push(ammoType);
      }
    }
  });
}

var nonWeaponDepot = NonWeaponDepot();
nonWeaponDepot.loadAmmo("Nuclear Bombs");

alert(nonWeaponDepot.stockpile()); // outputs ""

这就是如何在JavaScript中使用实例变量。使用相同技术的另一个实例变量示例


1
谢谢 Daniel - "Object.reverseMerge" 是什么?我在浏览器中找不到它,它是一个库方法吗? - ambertch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接