介绍一个简单的JavaScript类框架


Posted in Javascript onJune 24, 2015

 在写work-in-progress JavaScript book一书时,对于javascript继承体系,我花费了相当的时间,并在该过程中研究了各种不同的模拟经典类继承的方案。这些技术方案中,我最为推崇的是base2与Prototype的实现。

从这些方案中,应该能提炼出一个具有其思想内涵的框架,该框架须具有简单、可重用、易于理解并无依赖等特点,其中简单性与可用性是重点。以下是使用示例:
 

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(){
  // 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,因此如果要创建一个全新的类,该类必须为Class的子类,
  •     最具挑战的一点:父类的被覆写方法必须能访问到(通过配置上下文环境)。
  •     在上面的示例中,你能发现通过this._super()来调用Person父类的init()和dance()方法。

对结果相当满意:使类的定义结构化,保持单一继承,并且能够调用超类方法。

简单的类创建与继承

下面为其实现(便于阅读并有注释),大概25行左右。欢迎并感谢提出建议。
 

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
( function ( ) {
 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
 // The base Class implementation (does nothing)
 this.Class = function(){};
  
 // Create a new Class that inherits from this class
 Class.extend = function(prop) {
  var _super = this.prototype;
   
  // Instantiate a base class (but only create the instance,
  // don't run the init constructor)
  initializing = true;
  var prototype = new this();
  initializing = false;
   
  // Copy the properties over onto the new prototype
  for (var name in prop) {
   // Check if we're overwriting an existing function
   prototype[name] = typeof prop[name] == "function" &&
    typeof _super[name] == "function" && fnTest.test(prop[name]) ?
    (function(name, fn){
     return function() {
      var tmp = this._super;
       
      // Add a new ._super() method that is the same method
      // but on the super-class
      this._super = _super[name];
       
      // The method only need to be bound temporarily, so we
      // remove it when we're done executing
      var ret = fn.apply(this, arguments);    
      this._super = tmp;
       
      return ret;
     };
    })(name, prop[name]) :
    prop[name];
  }
   
  // The dummy class constructor
  function Class() {
   // All construction is actually done in the init method
   if ( !initializing && this.init )
    this.init.apply(this, arguments);
  }
   
  // Populate our constructed prototype object
  Class.prototype = prototype;
   
  // Enforce the constructor to be what we expect
  Class.prototype.constructor = Class;
  // And make this class extendable
  Class.extend = arguments.callee;
   
  return Class;
 };
})();

其中  “初始化(initializing/don't call init)”与“创建_super方法”最为棘手。接下来,我会对此做简要的介绍,使得大家对其实现机制能更好的理解。

初始化

    为了说明函数原型式的继承方式,首先来看传统的实现过程,即将子类的prototype属性指向父类的一个实例。如下所示:

 

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

然而,这里具有挑战性的一点,便是我们只想要得到‘是否实例(instatnceOf)'的效果,而不需要实例一个 Person并调用其构造函数所带来的后果。为防止这一点,在代码中设置一个bool参数initializing,只有在实例化父类并将其配置到子类的prototype属性时, 其值才为true。这样处理的目的是区分开真正的实例化与设计继承时这两种调用构造函数之间的区别,进而在真正实例化时调用init方法:
 

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

    值得特别注意的是,因为在init函数中可能会运行相当费资源的代码(如连接服务器,创建DOM元素等,谁也无法预测),所以做出区分是完全必要的。

超类方法(Super Method)

当使用继承时,最常见的需求便是子类能访问超类被覆写的方法。在该实现下,最终的方案便是提供一个临时方法(._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

实现这一功能需要几步处理。首先,我们使用extend来合并基本的Person实例(类实例,上面我们提到过其构造过程)与字面对象(Person.extend()的函数参数)。在合并过程中,做了简单的检查:首先检查将被合并的的属性是否为函数,如为函数,然后检查将被覆写的超类属性是否也为函数?如果这两个检查都为true,则需要为该属性准备_super方法。

注意,在这里创建了一个匿名闭包(返回的是函数对象)来封装增加的super方法。基于维护运行环境的需要,我们应该将旧的this._super(不管其是否存在)保存起来以备函数运行后重置,这有助于在有相同名称(不想偶然丢失对象指针)的情况下发生不可预知的问题。

然后,创建新的_super方法,该方法对象仅指向超类中被覆写的方法。谢天谢地,不用对_super做任何改动或变更作用域,因为函数的执行环境会随着函数调用对象自动变更(指针this会指向超类).

最后,调用字面量对象的方法,方法执行中可能会使用this._super(),方法执行后,将属性_super重置回其原来状态,之后return退出函数。

以上可以有许多种方案能达到相同的效果(我之前曾见过将super绑定到其自身,然后用arguments.callee访问),但是感觉还是这种方法最能能体现可用性与简洁性的特点。

在我已完成的多个基于javascript原型的工作中,只有这个类继承实现方案是我发表出来与大家分享的。我认为,简洁的代码(易于学习,易于继承,更少下载)更需要提出来让大家探讨,因此,对于学习javascript类构造与继承的人们来说,这套实现方案是一个好的开始。

Javascript 相关文章推荐
jquery调用asp.net 页面后台的实现代码
Apr 27 Javascript
js获取视频时长代码
Apr 10 Javascript
jquery validate demo 基础
Oct 29 Javascript
谈谈我对JavaScript原型和闭包系列理解(随手笔记6)
Dec 20 Javascript
AngularJS仿苹果滑屏删除控件
Jan 18 Javascript
EXT中单击button按钮grid添加一行(光标位置可设置)的实例代码
Jun 02 Javascript
jQuery leonaScroll 1.1 自定义滚动条插件(推荐)
Sep 17 Javascript
基于Node.js + WebSocket打造即时聊天程序嗨聊
Nov 29 Javascript
微信分享调用jssdk实例
Jun 08 Javascript
基于vue.js无缝滚动效果
Jan 25 Javascript
解决vue单页面应用中动态修改title问题
Jun 09 Javascript
vue-router中hash模式与history模式的区别
Jun 23 Vue.js
jquery分割字符串的方法
Jun 24 #Javascript
jQuery根据元素值删除数组元素的方法
Jun 24 #Javascript
举例详解JavaScript中Promise的使用
Jun 24 #Javascript
jQuery对JSON数据进行排序输出的方法
Jun 24 #Javascript
jQuery中$.extend()用法实例
Jun 24 #Javascript
深入理解JavaScript编程中的同步与异步机制
Jun 24 #Javascript
详解JavaScript中的客户端消息框架设计原理
Jun 24 #Javascript
You might like
用cookies来跟踪识别用户
2006/10/09 PHP
PHP与MySQL开发中页面乱码的产生与解决
2008/03/27 PHP
php5 图片验证码实现代码
2009/12/11 PHP
PHP中的str_repeat函数在JavaScript中的实现
2013/09/16 PHP
php 实现一个字符串加密解密的函数实例代码
2016/11/01 PHP
JS window.opener返回父页面的应用
2009/10/24 Javascript
javascript 获取元素位置的快速方法 getBoundingClientRect()
2009/11/26 Javascript
让你的博文自动带上缩址的实现代码,方便发到微博客上
2010/12/28 Javascript
js解决弹窗问题实现班级跳转DIV示例
2014/01/06 Javascript
window.open 以post方式传递参数示例代码
2014/02/27 Javascript
JavaScript基础教程之alert弹出提示框实例
2014/10/16 Javascript
JavaScript模拟重力状态下抛物运动的方法
2015/03/03 Javascript
JavaScript中的small()方法使用详解
2015/06/08 Javascript
AngularJS全局scope与Isolate scope通信用法示例
2016/11/22 Javascript
jquery实现刷新随机变化样式特效(tag标签样式)
2017/02/03 Javascript
解决JS内存泄露之js对象和dom对象互相引用问题
2017/06/25 Javascript
Javascript别踩白块儿(钢琴块儿)小游戏实现代码
2017/07/20 Javascript
基于 Immutable.js 实现撤销重做功能的实例代码
2018/03/01 Javascript
关于js的三种使用方式(行内js、内部js、外部js)的程序代码
2018/05/05 Javascript
Angular6 发送手机验证码按钮倒计时效果实现方法
2019/01/08 Javascript
vue生命周期的探索
2019/04/03 Javascript
微信小程序多列表渲染数据开关互不影响的实现
2020/06/05 Javascript
[02:11]2016国际邀请赛中国区预选赛最美TA采访现场玩家
2016/06/28 DOTA
[01:34]DAC2018主赛事第四日五佳镜头 Gh巨牙海民助Miracle-死里逃生
2018/04/07 DOTA
python实现可将字符转换成大写的tcp服务器实例
2015/04/29 Python
Python学生成绩管理系统简洁版
2020/04/05 Python
python之array赋值技巧分享
2019/11/28 Python
Python实现CAN报文转换工具教程
2020/05/05 Python
分别介绍一下Session Bean和Entity Bean
2015/03/13 面试题
MYSQL基础面试题
2012/05/13 面试题
物业电工岗位职责
2013/11/20 职场文书
自主招生自荐书
2013/11/29 职场文书
合作意向书模板
2014/03/31 职场文书
股份转让协议书
2014/04/12 职场文书
厉行勤俭节约倡议书
2014/05/16 职场文书
办公室岗位职责范本
2015/04/11 职场文书