拟类继承与“JavaScript 方式”的区别

18
刚刚读完Crockford的“JavaScript: The Good Parts”,我对他关于伪经典和原型式方法的立场有疑问。实际上,我并不是很关心他的立场;我只是想理解他的论点,以便能够建立自己的立场。
在这本书中,Crockford似乎暗示JavaScript中不应该使用构造函数和“所有那些东西”,他提到了“new”关键字的实现不好-即非构造函数可以用“new”关键字调用,反之亦然(可能会引起问题)。
我以为我理解他的观点,但我想我没有理解清楚。
当我需要创建一个新模块时,我通常会像这样开始:
function MyModule(something) {
    this.something = something || {};
}

然后我将会向它的原型中添加一些方法:

MyModule.prototype = {
    setSomething : function(){},
    getSomething : function(){},
    doSomething : function(){}
}

我喜欢这个模型;这意味着我可以在需要时创建一个新的实例,它拥有自己的属性和方法:

var foo = new MyModule({option1: 'bar'});
// Foo is an object; I can do anything to it; all methods of the "class"
// are available to this instance.

我的问题是:如何使用更适合JavaScript的方法来实现上述目标?换句话说,如果“JavaScript”是一个人,她会建议什么?
此外:Crockford说一个特定的设计模式“更具表现力”,是什么意思?
4个回答

10

参考:JavaScript的“new”关键字是否有害?

需要记住的是,像Crockford这样的众多JavaScript程序员最初都是希望通过“修复”语言的方式——使其更像其他(所谓的“经典”的)面向对象语言来接近它。因此,他们编写了大量的结构代码,构建了库和框架,然后他们开始意识到这并不是真正必要的;如果你按照JS本身的规则来处理它,你也可以很好地使用。


谢谢!阅读那个帖子给了我所需的所有信息(尤其是你的回答)! - James

4
你所提供的示例代码,在我看来是这样的原型版本:
Object.beget = function (o) { /* Crockfords replacement for the new */ }

var myModule = {
    something : null,
    getSomething : function () {},
    setSomething : function () {},
    doSomething : function () {}
};

然后你可以执行以下操作:

var foo = Object.beget(myModule);
foo.something = bar;

更新:您也可以使用构建器模式来替换像这样的构造函数:

var myModuleBuilder = {
    buildMyModule : function (something) {
        var m = Object.beget(myModule);
        m.something = something || {};
        return m;
    }
}

那么你可以这样做:

var foo = myModuleBuilder.buildMyModule(something);

2

您的实现存在问题,因为您替换了整个原型对象,丢失了继承的函数原型的属性,并且如果您以相同的方式编写其他类,则会破坏或至少使后续使用继承的能力更加困难。

更适合JavaScript的方法是:

var MyClass = function (storeThis) {
 this.storage = storeThis
}

MyClass.prototype.getStorage = function (){
   return this.storage;
}

MyClass.prototype.setStorage = function (newStorage){
  this.storage = newStorage;
}

使用方法:

var myInstance = new MyClass("sup");
alert("myInstance storage: " + myInstance.getStorage());
myInstance.setStroage("something else");

关于'new'关键字和Crawford对它的问题,我无法回答,因为我没有读过这本书,但我可以看出,您可以通过使用new关键字调用任何函数来创建一个新对象,包括那些被认为是类方法的函数。
当有人说某个设计模式更具“表达力”时,通常意味着该设计模式清晰简单,易于理解其实现方式和目的。

谢谢你的回答。我的例子并不是很重要,我关心的是设计模式本身。感谢您解释了“表达性” :) - James
你的第一个观点含糊不清,似乎误导了。那个被替换的原型上有什么“与每个对象相关联的内置功能”,而J-P的替代原型却没有呢? - Tim Down
我表达得不好,但是从函数派生出来的原型具有属性和特性,而对象字面量没有。例如toString。 - Bjorn
不是这样的。函数的原型起初就像使用 new Object() 创建的对象一样,是一个对象。而使用 {} 创建的对象确实有一个 toString 方法。例如尝试执行 ({}).toString()(在对象字面量周围加上括号以确保它被解释为对象而不是块)。 - Tim Down
嗯,我知道它们都有一个toString方法。我的意思是它们做了不同的事情。我刚试着将一个对象字面量设置为函数的原型,但它并没有像我想象的那样被覆盖。 - Bjorn

0

Javascript不是人,所以她不能真正建议你做什么。

以上没有任何答案提到我倾向于发现最简单的普通函数式继承方式。

function myModuleMaker(someProperty){
  var module = {}; //define your new instance manually

  module.property = someProperty; //set your properties

  module.methodOne = function(someExternalArgument){
    //do stuff to module.property, which you can, since you have closure scope access
  return module;
  }
}

现在来创建一个新实例:
var newModule = myModuleMaker(someProperty);

你仍然可以通过伪经典方式获得所有的好处,但是每次实例化时你都会遇到一个缺点,那就是你要复制所有实例方法的新副本。这可能只有在你开始拥有许多(甚至成千上万)实例时才会有影响,这是大多数人很少遇到的问题。如果你正在创建真正巨大的数据结构或者使用许多实例进行硬核动画,那么你最好使用伪经典方式。

我认为很难争辩说其中任何一种方法本质上是“不良实践”。


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