`
up2pu
  • 浏览: 219025 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

【翻译】简单的JavaScript继承

阅读更多
原文地址:http://ejohn.org/blog/simple-javascript-inheritance/
译者注:cocos2d-html5使用了这种类继承方式

简单的JavaScript继承


关于JavaScript继承,最近我做了很多事,主要是为了我的JavaScript书能顺利出版,同时也检验了多种JavaScript经典继承方式的模拟技术。其中,我最感兴趣的是base2(http://code.google.com/p/base2/)和Prototype(http://prototypejs.org/)。

我想把这几种技术的精华以简单、可复用、容易理解并没有任何依赖的形式展现出来。并且,我希望结果简单而高可用。下面是一个例子:
var Person = Class.extend({
  init: function(isDancing){
    this.dancing = isDancing;
  },
  dance: function(){
    return this.dancing;
  }
});
var Ninja = Person.extend({
  init: function(){
    this._super( false );
  },
  dance: function(){
    //调用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

//应该都是true
p instanceof Person && p instanceof Class &&
n instanceof Ninja && n instanceof Person && n instanceof Class



接下来对这种实现方式做一些解释:
1.创建构造函数很简单(本例中通过使用init方法实现)
2.为了创建一个新的“类”,你需要extend一个已经存在的类
3.所有类继承自同一祖先:Class。所以,如果你想创建一个新类,那它必须是Class的子类
4.最有挑战的是:这里提供了重载方法实现。参考上面的代码,可以通过在init()和dance()中调用this._super()实现。

我对结果很满意:它强制类为一个结构,维护简单的继承,并且可以调用父类方法。

简单的类创建和继承
下面是具体实现(做了格式化并添加了注释)——大概25行,欢迎反馈。
/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// 受base2和Prototype启发
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  //基类实现(什么都没做)
  this.Class = function(){};
  
  //创建一个新类,继承自class
  Class.extend = function(prop) {
    var _super = this.prototype;
    
    // 初始化一个基类(但是只创建了实例,没有调用init构造函数)
    initializing = true;
    var prototype = new this();
    initializing = false;
    
    //将属性复制到新的prototype
    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() {
      //所有构造过程在init方法中完成
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }
    
    //我们构造的protype对象
    Class.prototype = prototype;
    
    //强制构造函数为我们需要的内容
    Class.prototype.constructor = Class;

    //然后使该类可继承
    Class.extend = arguments.callee;
    
    return Class;
  };
})();


我觉得最有技巧的地方是“初始化/不要调用init”和“创建_super方法”。我想简要地介绍一下这些部分,这样你就可以更好地理解我们在这个方法中实现了什么。

初始化
为了使用一个函数prototype模拟继承,我们使用传统的技术来创建父类方法的实例,并且把它赋给prototype。不使用以上技术它应该看起来像这样:
function Person(){}
function Ninja(){}
Ninja.prototype = new Person();
//允许instanceof可用
(new Ninja()) instanceof Person


然而挑战在哪里,我们需要的就是“instanceof”的好处吗,这不值得初始化一个Person对象并运行它的构造函数。为了抵消损失,我在代码中用了一个变量,initializing,在我们仅想初始化一个类用于prototype时,设置为true。
if ( !initializing )  
    this.init.apply(this, arguments);


尤其重要的是init方法可以运行任何耗费资源的启动代码(连接服务器,创建DOM对象,谁知道呢)可以有效避免无法正常工作的。

Super方法
当你继承的时候,创建一个类,然后从父类继承一些功能,大家最希望有的功能就是可以访问被覆盖的父类方法。最终,在这个实现方案中是通过一个临时方法(._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实例中(构建过程前面我们讲了)。在合并过程中,我们做个简单的检查:我们要合并的属性是个方法吗,我们要覆盖的也是个方法吗?如果是这样,我们需要找到一种方法使父类方法可用。

我们创建了一个匿名的闭包(返回一个方法)来封装新的加强后的父类方法。我们需要保存原来的this._super(不管是否真的存在)的引用,完成后再恢复。这样会帮助我们解决同名变量已经存在的问题(而不是意外地弄丢它)。

接下来,我们创建一个新的_super方法,只是对父类prototype已经存在方法的一个引用。谢天谢地,我们不用再做其他改变或者修改作用域之类的,如果它是我们对象的一个属性,功能的上下文会自动设置(this是指与父类相对的实例)。

最后,我们调用最开始的方法,它做自己的事(很可能也是利用_super),之后我们恢复_super到原始状态并从方法返回。

现在有很多方法实现上面的类似功能(我见过另外一种实现方法是将super方法绑定到方法本身,从过arguments.callee访问),但是我认为这种方法实现了可用性和简单性最好的结合。

我会在我的书中深入介绍JavaScript prototype的本质,但是我希望把这个类实现方式单独剥离出来分享给大家。在简化代码上还有很多可以讨论的地方(容易学,容易扩展,下载量下),所以这个实现是个很好的开始用来学习JavaScript类的创建和继承。
分享到:
评论

相关推荐

    JavaScript忍者禁术

    Secrets of the JavaScript Ninja(翻译:JavaScript忍者禁术)适用有JS基础的,这本书以函数为中心(函数也确实是js的核心),对函数的讲解非常全面细致,涉及到函数的定义、扩展、重载、curry化、闭包、重定向、继承...

    《javascript设计模式》学习笔记二:Javascript面向对象程序设计继承用法分析

    javascript中的继承有三种(不同的书上对这三中的名称翻译都不一样,我按照我的理解来命名吧): a.构造函数继承(类式继承,组合继承,伪经典继承) b.原型继承 c.原型赋值(遍历)继承(寄生式继承) 2.构造函数...

    学用 JavaScript 设计模式.pdf

    外文翻译:学用JavaScript设计模式,pdf版本,中文版 设计模式是可重用的用于解决软件设计中一般问题的方案。设计模式如此让人着迷,以至在任何编程语言中都有对其进行的探索。 其中一个原因是它可以让我们站在巨人...

    讲解JavaScript的面向对象的编程

    本人一行注释一行代码翻译了该大师的艺术作品--目的说明它是在第1,2阶段文档演示的JavaScript面向对象的书写方式的进一步改进,它是现代JavaScript面向对象编程方式(使用基本类来编码)的过渡代码--没有它就没有当今...

    关于JavaScript的面向对象和继承有利新手学习

    这是一篇关于JavaScript的面向对象和继承的文章,写于1年前,作者循序渐进,对想学习JavaScript中面向对象的同学来说是很有帮助的,因此试着翻译一下,不妥之处,请指正。原文链接Objects and Inheritance in ...

    JavaScript面向对象之Prototypes和继承

    一、前言 本文翻译自微软的牛人Scott Allen Prototypes and Inheritance in JavaScript ,本文对到底什么是Prototype和为什么通过Prototype能实现继承做了详细的分析和阐述,是理解JS OO 的佳作之一。翻译不好的地方...

    解析John Resig Simple JavaScript Inheritance代码

     John Resig 写了一篇关于 JavaScript 里 类似其它语言的 “继承”, 灵感来自于 base2 and PrototypeJS. 他为文章起名为”Simple JavaScript Inheritance” . 他使用的一些很巧妙的技术来实现 super 方法. 你还...

    how-javascript-works:有关javascript工作原理,事件循环,服务工作者等的知识

    本文是翻译介绍 JavaScript 的工作原理的,该系列原文还在更新中,原文见。 目录结构 第一章: 第二章: 第三章: 第四章: 第五章: 第六章: 第七章: 第八章: 第九章: 第十章: 第十一章: 第十二章:网络层...

    图像矩阵matlab代码-isat:卫星轨道跟踪器:在JavaScript中实现最新的SGP模型和算法,然后用于通过任何浏览器可视化数百颗卫星

    因此,我JavaScript代码在结构,模块化,加载,继承等方面未遵循良好做法。在使用jQuery,RequireJS,AMD等之前,我先使用裸露JavaScript。确实是一个很好的范例。 概述 我已经从MATLAB翻译了SGP算法,我们认为这是...

    javascript中的prototype属性实例分析说明

    一说到prototype很多人可能第一个想到的是著名的prototype.js框架,当然我们今天说的不是它,而是Javascript中的prototype属性,一般都被翻译为“原型”。这是一个比较特殊的属性,Javascript中的继承一般都依赖这...

    js-deep:js深入学习

    前端进阶JavaScript基础:JavaScript数据类型JavaScript代码运行机制作用域和作用域链let/const/var的区别JavaScript高阶编程技巧原型和原型链的底层运行机制this指向数据类型检测JavaScript类的继承方案JavaScript...

    Ajax完全自学手册PPT和源代码(ptt格式)

    第5章 JavaScript中的继承 Test4OO.htm 什么是对象 Test4OO.htm 对象分类 TestStatic.htm 作用域、静态作用域、this关键字 Test4Factory.htm 工厂方式 test4Constract.htm 构造函数 Test4Fixed.htm 混合方式 dynamic...

    深入浅出ES6 简体中文

    类的加入虽然有助于其它语言的使用者开始使用JavaScript,但是却无法发挥出JavaScript原型继承的巨大优势);以及为了保持非侵入式弥补其它新特性而诞生的Symbols。 其它的新特性也相当诱人,熟练掌握可以大幅度提升...

    Ajax完全自学手册(源代码).rar

    第5章 JavaScript中的继承 Test4OO.htm 什么是对象 Test4OO.htm 对象分类 TestStatic.htm 作用域、静态作用域、this关键字 Test4Factory.htm 工厂方式 test4Constract.htm 构造函数 Test4Fixed.htm ...

    matlab代码替换-my_isat:my_isat

    因此,我JavaScript代码在结构,模块化,加载,继承等方面未遵循良好做法。在使用jQuery,RequireJS,AMD等之前,我先使用裸露JavaScript。确实是一个很好的范例。 概述 我已经从MATLAB翻译了SGP算法,我们认为这是...

    Wicket 8.X开发文档(中文翻译+英文原版)

    使用wicket标记继承:扩展标记 5.5。摘要 6.保持对HTML的控制 6.1。隐藏或禁用组件 6.2。修改标签属性 6.3。生成标记属性“id” 6.4。使用WebMarkupContainer创建内嵌面板 6.5。使用标记片段 6.6。将标题内容添加到...

    agent:用于构建物联网的Agent框架

    抽象,继承和接口 属性,注释和模板 被动和单例 简单,系列,并行,队列,MapReduce和Waker基本行为 HRRN计划,微型容器和审核 异步消息传递,选择器和过滤器 JSON-LD本体和消息翻译 WoT描述和动作 MQTT传感器,执行...

    liberapay.com:经常性捐赠平台Liberapay的源代码

    支付宝 是一个经常性的捐赠平台。 我们帮助您资助您欣赏的...Liberapay 最初是从Gratipay分叉出来的,并继承了它的 web 微框架Pando ( né Aspen),它基于文件系统路由和simplates 。 别担心,这很简单。 例如,要让

Global site tag (gtag.js) - Google Analytics