简单的JavaScript继承

作者: 王兴欣 | 来源:发表于2017-10-16 18:14 被阅读0次

    这篇文章翻译自John Resig(jQuery的作者)的博客,原文地址

    为了正在写的这本书(译者注:这本书是《忍者秘籍》),我最近做了许多关于JavaScript继承的工作,并在此基础上研究了几种不同的JavaScript经典继承模拟技术。在我所有看过的研究中,我最推崇的是base2Prototype这两个库的实现。

    我想要提取这些技术的精华,以一个简单的、可复用的方式进行展示,以便使这些特性更容易不依赖其他的内容而被理解。此外我想要使其可以被简单的、高效的被使用。这里展示了一个可以使用完成后的结果来实现的实例。(译者注:既完成后的代码可以用于实现下面这个功能)

    var Person = Class.extend({
      init: function(isDancing){
        this.dancing = isDancing;
      },
      dance: function(){
        return this.dancing;
      }
    });
    
    var Ninja = Person.extend({y
      init: function(){
        this._super( false );
      },
      dance: function(){
        // Call the inherited version of dance()
        return this._super();
      },
      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 && p instanceof Class &&
    n instanceof Ninja && n instanceof Person && n instanceof Class
    
    

    关于本例,有几点重要的注意事项。

    • 让构造器的创建更加简单(在这个例子中仅仅使用init方法来创建)

    • 为了创建一个新的‘class’,你必须要继承一个已经存在的类(sub-class).

    • 所有的“类”都继承于一个祖先:Class。因此,如果要创建一个新类,它必须是Class的子类。

    • 该语法最大的挑战是访问被覆盖的方法,而且有时这些方法的上下文也有可能被修改了。通过this._super()调用Person超类的原始init()dance()方法

    本例的代码使我很愉快:它使得“类”的概念作为一种结构,保持继承简单,并且允许调用超类方法。

    简单的类创建与继承

    这里是该内容的实现(合理的大小并且有备注) 大概有25行。 欢迎并感谢提出建议。

    /* Simple JavaScript Inheritance
     * By John Resig https://johnresig.com/
     * MIT Licensed.
     */
    //从base2与Prototype这2个库中受到启发。
    (function(){
      var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
    
     //基础的class实现 没有做任何事情
      this.Class = function(){};
    
      //  创建一个新的类继承这个Class
      Class.extend = function(prop) {
        var _super = this.prototype;
    
        //   实例化一个基础类(仅仅是创建实例,并没有运行初始化构造器)
        initializing = true;
        var prototype = new this();
        initializing = false;
    
        // 复制属性到新的原型上
        for (var name in prop) {
          // 检查我们是否覆盖了一个已经存在的方法
          prototype[name] = typeof prop[name] == "function" && 
            typeof _super[name] == "function" && fnTest.test(prop[name]) ?
            (function(name, fn){
              return function() {
                var tmp = this._super;
    
                // 添加._super()方法,该方法与超类的方法相同
                this._super = _super[name];
    
                //  该方法只需要临时存在,所以在执行完之后移除该方法
                var ret = fn.apply(this, arguments);        
                this._super = tmp;
    
                return ret;
              };
            })(name, prop[name]) :
            prop[name];
        }
    
        //  仿真的类构造器
        function Class() {
          // All construction is actually done in the init method  所有的创建工作都会在init方法里完成
          if ( !initializing && this.init )
            this.init.apply(this, arguments);
        }
    
        // 设置类的原型
        Class.prototype = prototype;
    
        //重载构造器的引用
        Class.prototype.constructor = Class;
    
        //让类可以继续扩展
        Class.extend = arguments.callee;
    
        return Class;
      };
    })();
    
    

    在我看来,最难的两个部分是“初始化/不调用init方法”和“创建_super方法”。我想要简要的介绍这部分以便于理解整个代码的实现。

    子类的实例化

    为了用函数原型模拟继承,我们使用传统的创建父类的实例,并将其赋值给子类的原型。如果不使用之前的实现,其实现代码类似如下:

    function Person(){}
    function Ninja(){}
    Ninja.prototype = new Person();
    // Allows for instanceof to work:
    (new Ninja()) instanceof Person
    
    

    该代码的挑战在于我们想从instanceof中受益,而不是实例化Person对象并运行其构造器。为了抵消这一点,我们在代码中定义了initialozing变量,当我们想使用原型实例化一个类的时候,都将该变量设置为true。

    因此,在构造实例的时候,我们可以确保不在实例化模式下进行构建实例,并且可以相应的运行或者跳过init()方法。

    if ( !initializing )
      this.init.apply(this, arguments);
    
    

    尤其重要的是,init()方法可以运行启动各种昂贵的启动代码(链接到一个服务器,创建DOM元素等等),所以如果只是创建一个实例作为原型的话,我们要避免任何不必要的昂贵代码。

    保留父级方法

    当你正在实例化的时候,创建一个类并且继承超类的方法,我们保留了访问被覆盖方法的能力,最后在这个特别的实现中,使用了一个新的临时方法(._super)来访问父类的相关方法,该方法只能从子类方法内部进行访问,并且该方法引用的是父类中原有方法。

    例如,如果你想要调用父类的同名的方法,你可以这样做。

    var Person = Class.extend({
      init: function(isDancing){
        this.dancing = isDancing;
      }
    });
    
    var Ninja = Person.extend({
      init: function(){
        this._super( false );
      }
    });
    
    var p = new Person(true);
    p.dancing; // => true
    
    var n = new Ninja();
    n.dancing; // => false
    
    

    实施这个功能需要多个步骤。首先,注意我们用于继承一个已经存在类的对象(例如被传入Person.extend的这个)需要与基础的new Person的实例合并(Person类之前已经被创建了)。在合并过程中我们做了简单的检查:子类属性是否是一个函数、超类属性是否是一个函数、子类函数是否包含了super引用。

    注意,我们创建了一个匿名的闭包(返回了一个构造函数),将会封装并执行子类的函数。首先,作为优秀的开发人员,需要保持旧的this._super引用(不管它是否存在),处理完了以后再恢复该引用。这在同名变量已经存在的情况下会很有用(我们不想意外的失去它)。

    接下来,我们创建了新的_super方法,新的方法保持了对存在于父类方法的引用。值得庆幸的是,我们不需要做任何额外的代码修改或者作用域的修改,当函数成为我们对象的一个属性时,该函数的上下文会自动设置(this引用的是当前的子类实例,而不是父类实例)。

    最后我们调用原始的子类方法执行自己的工作(也有可能使用了_super),然后将_super恢复成原来的状态,并返回调用结果。

    有很多方式可以达到类似的结果(有的实现,会通过访问arguments.callee,将_super方法绑定到方法自身),但是该特定技术提供了良好的可用性和简便性。

    我会在我写的书中覆盖更多的JavaScript原型系统背后的真相,我只是想把这个类实现放到这里,让每个人都尝试使用它。我认为这个简单的代码可以说明很多的事情(更容易去学习,去继承,更少的下载),因此我认为这个实现是开始和学习JavaScript类构造和继承的基础的好地方。


    其实我是拜读过忍者秘籍的,这个例子在忍者秘籍中的第六章 - 原型与面向对象中做了更加详细的讲解,此外本书全面且详细的讲解了javascript的基础部分(函数、执行上下文、闭包等等),并且书中有部分例子实际上有些深奥,部分例子在我当初第一次阅读的时候并没有完全理解,于是我就把该页折叠起来,日后再次阅读理解更为透彻一点。 由于我是认真阅读过忍者秘籍的,我认为这本书非常的不错(毕竟是jQuery作者写的),因此在这里我向各位初学者推荐这本书,希望对大家有所帮助。

    相关文章

      网友评论

        本文标题:简单的JavaScript继承

        本文链接:https://www.haomeiwen.com/subject/hzlluxtx.html