简单的JavaScript继承(译)

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

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

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

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).app

  • 全部的“类”都继承于一个祖先:Class。所以,若是要建立一个新类,它必须是Class的子类。函数

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

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

简单的类建立与继承this

这里是该内容的实现(合理的大小而且有备注) 大概有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做者写的),所以在这里我向各位初学者推荐这本书,但愿对你们有所帮助。

相关文章
相关标签/搜索