jQuery名望的John Resig提供了Simple JavaScript Inheritance的简洁实现。他的方法激发了我进一步改进工作的尝试。我已经重写了Resig的原始Class.extend函数,以包括以下优点:

  • 性能 –在类定义,对象构造和基类方法调用
  • 期间开销较小
  • 灵活性 –已针对兼容ECMAScript 5的新型浏览器(例如Chrome)进行了优化,但为较旧的浏览器(例如IE6)提供了等效的“填充”
  • 兼容性 –在严格模式下进行验证并提供更好的工具兼容性(例如VSDoc / JSDoc注释,Visual Studio IntelliSense等)。
  • 简单 –您不必是“忍者”即可理解源代码(如果丢失ECMAScript 5功能,它甚至会更简单)
  • 健壮性 –通过更多的“corner case”单元测试(例如,在IE中覆盖toString)

  • 因为这似乎太真实了,所以我想确保我的逻辑没有任何基本的缺陷或错误,并查看是否有人可以提出改进建议或反驳代码。这样,我介绍了classify函数:
    function classify(base, properties)
    {
        /// <summary>Creates a type (i.e. class) that supports prototype-chaining (i.e. inheritance).</summary>
        /// <param name="base" type="Function" optional="true">The base class to extend.</param>
        /// <param name="properties" type="Object" optional="true">The properties of the class, including its constructor and members.</param>
        /// <returns type="Function">The class.</returns>
    
        // quick-and-dirty method overloading
        properties = (typeof(base) === "object") ? base : properties || {};
        base = (typeof(base) === "function") ? base : Object;
    
        var basePrototype = base.prototype;
        var derivedPrototype;
    
        if (Object.create)
        {
            // allow newer browsers to leverage ECMAScript 5 features
            var propertyNames = Object.getOwnPropertyNames(properties);
            var propertyDescriptors = {};
    
            for (var i = 0, p; p = propertyNames[i]; i++)
                propertyDescriptors[p] = Object.getOwnPropertyDescriptor(properties, p);
    
            derivedPrototype = Object.create(basePrototype, propertyDescriptors);
        }
        else
        {
            // provide "shim" for older browsers
            var baseType = function() {};
            baseType.prototype = basePrototype;
            derivedPrototype = new baseType;
    
            // add enumerable properties
            for (var p in properties)
                if (properties.hasOwnProperty(p))
                    derivedPrototype[p] = properties[p];
    
            // add non-enumerable properties (see https://developer.mozilla.org/en/ECMAScript_DontEnum_attribute)
            if (!{ constructor: true }.propertyIsEnumerable("constructor"))
                for (var i = 0, a = [ "constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf" ], p; p = a[i]; i++)
                    if (properties.hasOwnProperty(p))
                        derivedPrototype[p] = properties[p];
        }
    
        // build the class
        var derived = properties.hasOwnProperty("constructor") ? properties.constructor : function() { base.apply(this, arguments); };
        derived.prototype = derivedPrototype;
        derived.prototype.constructor = derived;
        derived.prototype.base = derived.base = basePrototype;
    
        return derived;
    }
    

    除了构造函数名称(constructorinit)以及基类方法调用的语法外,用法与Resig几乎相同。
    /* Example 1: Define a minimal class */
    var Minimal = classify();
    
    /* Example 2a: Define a "plain old" class (without using the classify function) */
    var Class = function()
    {
        this.name = "John";
    };
    
    Class.prototype.count = function()
    {
        return this.name + ": One. Two. Three.";
    };
    
    /* Example 2b: Define a derived class that extends a "plain old" base class */
    var SpanishClass = classify(Class,
    {
        constructor: function()
        {
            this.name = "Juan";
        },
        count: function()
        {
            return this.name + ": Uno. Dos. Tres.";
        }
    });
    
    /* Example 3: Define a Person class that extends Object by default */
    var Person = classify(
    {
        constructor: function(name, isQuiet)
        {
            this.name = name;
            this.isQuiet = isQuiet;
        },
        canSing: function()
        {
            return !this.isQuiet;
        },
        sing: function()
        {
            return this.canSing() ? "Figaro!" : "Shh!";
        },
        toString: function()
        {
            return "Hello, " + this.name + "!";
        }
    });
    
    /* Example 4: Define a Ninja class that extends Person */
    var Ninja = classify(Person,
    {
        constructor: function(name, skillLevel)
        {
            Ninja.base.constructor.call(this, name, true);
            this.skillLevel = skillLevel;
        },
        canSing: function()
        {
            return Ninja.base.canSing.call(this) || this.skillLevel > 200;
        },
        attack: function()
        {
            return "Chop!";
        }
    });
    
    /* Example 4: Define an ExtremeNinja class that extends Ninja that extends Person */
    var ExtremeNinja = classify(Ninja,
    {
        attack: function()
        {
            return "Chop! Chop!";
        },
        backflip: function()
        {
            this.skillLevel++;
            return "Woosh!";
        }
    });
    
    var m = new Minimal();
    var c = new Class();
    var s = new SpanishClass();
    var p = new Person("Mary", false);
    var n = new Ninja("John", 100);
    var e = new ExtremeNinja("World", 200);
    

    这是我所有通过的QUnit测试:
    equals(m instanceof Object && m instanceof Minimal && m.constructor === Minimal, true);
    equals(c instanceof Object && c instanceof Class && c.constructor === Class, true);
    equals(s instanceof Object && s instanceof Class && s instanceof SpanishClass && s.constructor === SpanishClass, true);
    equals(p instanceof Object && p instanceof Person && p.constructor === Person, true);
    equals(n instanceof Object && n instanceof Person && n instanceof Ninja && n.constructor === Ninja, true);
    equals(e instanceof Object && e instanceof Person && e instanceof Ninja && e instanceof ExtremeNinja && e.constructor === ExtremeNinja, true);
    
    equals(c.count(), "John: One. Two. Three.");
    equals(s.count(), "Juan: Uno. Dos. Tres.");
    
    equals(p.isQuiet, false);
    equals(p.canSing(), true);
    equals(p.sing(), "Figaro!");
    
    equals(n.isQuiet, true);
    equals(n.skillLevel, 100);
    equals(n.canSing(), false);
    equals(n.sing(), "Shh!");
    equals(n.attack(), "Chop!");
    
    equals(e.isQuiet, true);
    equals(e.skillLevel, 200);
    equals(e.canSing(), false);
    equals(e.sing(), "Shh!");
    equals(e.attack(), "Chop! Chop!");
    equals(e.backflip(), "Woosh!");
    equals(e.skillLevel, 201);
    equals(e.canSing(), true);
    equals(e.sing(), "Figaro!");
    equals(e.toString(), "Hello, World!");
    

    与John Resig的original approach相比,有人对我的方法有什么看法吗?欢迎提出建议和反馈!

    注意:自从我最初发布此问题以来,以上代码已被重大修改。以上是最新版本。要查看其发展情况,请查看修订历史记录。

    最佳答案

    前一段时间,我查看了JS的多个对象系统,甚至实现了自己的一些对象系统,例如class.js(ES5 version)和proto.js

    我从未使用过它们的原因:您最终将编写相同数量的代码。恰当的例子:Resig的Ninja示例(仅添加了一些空白):

    var Person = Class.extend({
        init: function(isDancing) {
            this.dancing = isDancing;
        },
    
        dance: function() {
            return this.dancing;
        }
    });
    
    var Ninja = Person.extend({
        init: function() {
            this._super(false);
        },
    
        swingSword: function() {
            return true;
        }
    });
    

    19行,264字节。

    具有Object.create()的标准JS(这是ECMAScript 5函数,但是出于我们的目的,可以用自定义ES3 clone() 实现代替):
    function Person(isDancing) {
        this.dancing = isDancing;
    }
    
    Person.prototype.dance = function() {
        return this.dancing;
    };
    
    function Ninja() {
        Person.call(this, false);
    }
    
    Ninja.prototype = Object.create(Person.prototype);
    
    Ninja.prototype.swingSword = function() {
        return true;
    };
    

    17行,282字节。另外,额外的字节并不能解决单独对象系统增加的复杂性。通过添加一些自定义函数来简化标准示例很容易,但是再次:这样做确实不值得。

    关于javascript - 改善简单的JavaScript继承,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2510342/

    10-09 17:26