约翰·雷西格(John Resig)的面向对象JavaScript实现是否适用于生产环境?

24

长期以来,我一直在思考如何使我的JavaScript更加面向对象。我也看过几种不同的实现方法,但我无法决定是否有必要这样做。

我试图回答以下问题:

  • 约翰·雷西格(John Resig)的简单继承结构是否可以安全地用于生产环境?
  • 有没有办法能够判断它被测试得有多好?
  • 除了Joose之外,我还有哪些选择?我需要一个易于使用、快速、稳健的方案。它还需要与jQuery兼容。

22
我曾看过在生产环境中运行的JavaScript代码使得小猫天使们落泪,因此你需要自己决定它对单元/页面测试会有什么影响。虽然这看起来很简单,但很难想象它会带来严重的问题。 - Pointy
+1 Pointy。我刚才批评了下面的内容,但实际上99%的网站上都有比这更糟糕的代码...... - bobince
2
小心点,Tim,我们不要开始什么事情...! - bobince
@bobince: 目前为止,我似乎逃过了这个问题。 - Tim Down
3个回答

19

哎呀,对我来说它看起来比必要的要复杂很多。

仔细看看,我真的反感它在方法中提供this._super()来调用超类方法的做法。

该代码引入了对typeof==='function'(对于一些对象不可靠)、Function#toString(哎呀,函数分解也不可靠)的依赖,以及决定是否进行包装的方式是根据您在函数体中使用字节序列_super的顺序(即使您只在字符串中使用它。如果尝试例如this['_'+'super'],它将失败)。

如果您在函数对象上存储属性(例如 MyClass.myFunction.SOME_PRIVATE_CONSTANT ,您可能会这样做以保持命名空间的清洁),那么包装将阻止您访问这些属性。如果在一个对象的方法中抛出异常并在另一个方法中捕获,_super将指向错误的东西。

所有这些都只是为了更轻松地调用超级类的同名方法。但是我认为在JS中实现这个并不特别困难。它太聪明反被聪明误了,并因此使整个系统变得不够可靠。(噢,arguments.callee在严格模式下无效,尽管这并不是他的错,因为它发生在他发布之后。)

这是我目前使用的类。我不认为这是“最好”的JS类系统,因为有大量进行它和一堆您可能想要添加或不添加的不同功能的方法。但它非常轻巧,并旨在成为“ JavaScriptic”,如果这是一个词的话。(它不是。)

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw 'Constructor function requires new operator';
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    if (this!==Object) {
        Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
        Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    }
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

它提供了:

  1. 防止意外缺少 new。另一种选择是将 X() 静默重定向到 new X(),以便缺少 new 也能正常工作。这两种方法都有利弊;我选择明确的错误提示,避免习惯于编写没有 new 的代码,并在其他未定义为此类的对象上引起问题。无论哪种方式,都比JS默认的让 this. 属性落在 window 上并在后续出现神秘错误要好。

  2. 可继承的 _init 方法,因此您不必编写什么都不做,只调用超类构造函数的构造函数。

就是这样。

以下是如何使用它来实现 Resig 的示例:

var Person= Object.makeSubclass();
Person.prototype._init= function(isDancing) {
    this.dancing= isDancing;
};
Person.prototype.dance= function() {
    return this.dancing;
};

var Ninja = Person.makeSubclass();
Ninja.prototype._init= function() {
    Person.prototype._init.call(this, false);
};
Ninja.prototype.swingSword= function() {
    return true;
};

var p= new Person(true);
p.dance(); // => true

var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true

// Should all be true
p instanceof Person &&
n instanceof Ninja && n instanceof Person

通过特别指定你想要的方法并调用它来进行超类调用,有点像Python。如果你想避免再次命名Person,可以在构造函数中添加_super成员(这样你就可以说Ninja._super.prototype._init.call或者Ninja._base._init.call)。


如果你正在处理宿主对象,那么typeof对于函数来说只是不可靠的,而且你也不想子类化其中的一个。或者至少,你不应该想要这样做。我完全同意Resig的代码过于繁琐和脆弱,而且根本没有必要这样做。 - Tim Down
@Tim:是的,你不能从主机对象进行原型设计,但函数test所做的是检查每个成员,而不是超类对象。因此,作为您的类成员的主机对象可能会引起问题。(例如,在Firefox 3.0中,正则表达式变成了函数,因此在Class.extend中包装可能会尝试像函数一样包装它们,从而破坏正则表达式成员)。 - bobince
哦,我总是忘记正则表达式的事情。是的,你是对的;我没有像我本应该那样彻底阅读Resig先生的代码,现在它看起来比以前更脆弱了。顺便说一句,“nadger”这个词用得很好。在SO上很少用到。 - Tim Down
这是一个很好的例子,bobince......在我接受它之前,我只想等待看看是否还有其他建议。非常感谢你对此如此彻底。 - Metropolis
如果您关心性能,以下链接很有帮助。使用<BaseClass>.prototype.<Function>.call(this, ...)相当慢 :/ http://ajaxian.com/archives/javascript-inheritance-performance - BSick7

5

JavaScript是基于原型而不是类的。我的建议是不要与其抗争,而是以JS的方式声明子类型:

MyDerivedObj.prototype = new MySuperObj();
MyDerivedObj.prototype.constructor = MyDerivedObj;

3
问题在于调用new MySuperObj()会触发构造函数中的初始化代码,就像您实际上正在实例化一个MySuperObj一样。一旦构造函数中有任何非平凡的内容,这种方法就不再适用,因为您需要在构造MyDerivedObj实例时调用MySuperObj的初始化代码,而不是在定义时调用。 - bobince
1
我同意你或许想把初始化代码提取出来放到一个单独的方法里面,并从MyDerivedObj的构造函数中调用它... 更好的方法是,考虑使用依赖注入。我发现这样可以大大减少构造函数代码量。 - Christopher Hunt

5
看看你在完全不使用继承的情况下能走多远。把它当作一种性能优化(只有在真正必要的情况下才应该使用)而不是设计原则。
在像JS这样高度动态的语言中,很少需要知道一个对象是否是“Person”。你只需要知道它是否具有“firstName”属性或“eatFood”方法。通常不需要知道一个对象是否是数组;如果它具有长度属性和一些其他以整数命名的属性,那通常就足够了(例如Arguments对象)。“如果它像鸭子一样走路并像鸭子一样叫,那么它就是鸭子。”
// give back a duck
return { 
  walk: function() { ... }, 
  quack: function() { ... } 
};

是的,如果您正在创建大量具有数十个方法的小对象,则可以将这些方法分配给原型以避免在每个实例中创建数十个插槽的开销。但是,将其视为减少内存开销的一种方式 - 仅仅是优化。并通过某种工厂函数隐藏您对new的使用,以便为用户提供帮助,使他们甚至不需要知道对象是如何创建的。他们只需要知道它具有方法foo或属性bar
(请注意,在这种情况下,您实际上并没有建模经典继承。这只是定义一个单个类以获得共享虚表的效率的等效方法。)

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