JavaScript和Lua的类继承

jopen 10年前

javascript 本身虽是一门面向对象的编程语言, 但并没有明确提供继承方式.二十多年间,众多高手提供很多模拟继承的实现,

主要的有:对象冒充,call/apply,prototype,以及深复制等. 网上有很多此类教程,在这里就不再赘述这些实现.我所在的团队正在做的项目,需要使用js和lua实现同一份API接口,已达到js和lua的无缝切换.所以,实现类的继承方案至关重要. 接下来,就是具体实现过程, 如有不到之处,还望大家指正.

Lua ,是一门很优秀的动态语言,为嵌入而生,如果只是当成脚本使用的话,类的概念其实并不必要, 但是如果想要构成庞大规模的代码量,这就一个亟待的问题,因为Lua 的操作符重载的支持, 使得这一过程得以实现.

javascript:

function Class(Super, init ){  //do  }

lua :   

function Class(Super , init ) --[[do]] end

*Super , 是新生成的类需要继承的父类.

*init , 是新生成的类的构造函数. 

下面, 我们就来实现这个两个Class函数.

  1.在js当中,调用new算符实际就是复制当前构造函数原型.因为Lua中并没有new算符,所以应该将其屏蔽.

  2.在lua中想要实现方法的关联,主要使用两种方案, 一是复制,二是覆盖元表__index索引,复制是一个很不错的想法,或者说是一个极其通用的思想, 继承的本质就是让一个实例可以调用另外一个类实例的方法.如果是这样的的话,复制是一个很完美的方案,简单粗暴,简单就是稳定,粗暴就是直接;稳定直接的方案往往是实现逻辑的最佳选择,但是想要这个过程高效,那么就需要深厚的功力,我自认为还没有达到这样的水平, 所以,lua的实现机制还是选择覆盖元表__index索引实现.

  3. lua元表的__index索引,仔细想来,它的机制很像js的原型链, 也就是说让lua模拟js的原型链还是比较容易的.而原型链的方式实现javascript的继承也非常容易.

基于上述3点,下面贴出代码:

lua

local setmetatable, pairs = setmetatable, pairs;    function Class(Super, init)      local NEW = { fz = {} };---新的类定义, fz 就是实例方法所在的域 ,相当于js的prototype域.      NEW.__initialize = function(self, ...) ---构造方法.          local this = {}            for i, v in pairs(self) do    ---- 复制的 self 副本              this[i] = v          end                     if init then              init(this, ...);  ---执行初始化方法          end                    return setmetatable(this, {     ---建立关联.. 如果在NEW的实例域上没有搜索到存在的域,那么              __index = function(_, key)  ------就在NEW的fz下寻找.                   return NEW.fz[key]              end          });      end            setmetatable(NEW.fz, {            ---建立关联 .. 如果在NEW.fz上没有找到存在的域,那么          __index = function(_, key)    ------就在Super.fz 域上寻找, 如果找不到,就返回nil.              return Super.fz and Super.fz[key] or nil;          end      });      return setmetatable(NEW, {        ---设置元表的__call 域, 使得 NEW 这个hash表能够被调用.          __call = function(...)              return (...).__initialize(...); --- 调用的时候直接转到 初始化方法..          end      });  end

调用 : 

local M = Class({}, function(self , a , b ) ---  定义了类M , 继承了table      self.a = a      self.b = b  end)  M.fz.geta = function(self)                  ---   定义实例方法 geta .      return self.a;  end  local MM = Class(M , function(self, a , b)  ---  定义了类MM, 继承了 M      self.a = a      self.b = b  end);  MM.fz.getb = function(self)                  --- 定义实例方法 getb.      return self.b  end  local mm = MM( "AAA" , "BBB");              --- 获得 MM 的实例 mm   print(mm:geta())                            --- "AAA"  print(mm:getb())                            --- "BBB"

 

上述 代码实现还是比较简单的. 子类可以继承父类  fz 下面的所有字段...

接下来就是js 的实现了..

因为 Lua 没有关键字'new' , 所以js 的实现我将new 关键字做了屏蔽, 此处的参考了jQuery的实现,在此对john表示敬意.. 下面就是具体代码:

function Class(Super, init) {      var N = function () {           //创建一个空的函数N 做一个中间层.      };                                    N.prototype = Super.prototype;  // 将N的原型 指向 Super 的原型      var New = function () {         //  要生成的类定义 NEW           return new New.fz.initialize(arguments);        };      New.fz = New.prototype = new N();  //将 N的实例 关联到 NEW 的原型上,并取一个别名 fz.       New.fz.initialize = function (M) {  //初始化方法.          if (init) init.apply(this, M);          return this;      };      New.fz.constructor = New;        // 将New.fz上的constructor域重定向到 NEW..      New.fz.initialize.prototype = New.fz;  // 很高妙的处理,jQuery的实现.      return New;  }

说明一下, 为什么会有一个 function  N :  其实是一个隔离考量. 前文提到的, 继承 只是 prototype 关联, 对其他域应该予以屏蔽.

所以给定一个  N , 这个 N 没有任何实现,也没挂载任何域, 只是将Super.prototype挂载到N.prototype上, 所以,new N() , 其实只是相当于一个指向Super.prototype的指针而已,

内存上几乎没有占用.. 至于隐藏 new 关键字,并没有选择工厂方法的实现, 而是直接使用jQuery 的实现方案..

调用:

var M = Class({}, function (a, b) {      this.a = a;      this.b = b;  });  M.fz.geta = function () {      return this.a;  };  var MM = Class(M, function (a, b) {      this.a = a;      this.b = b;  });  MM.fz.getb = function () {      return this.b  };  var mm = MM("AAA", "BBB");  print(mm.geta())  print(mm.getb())

依然是跟 lua 版本 一样的调用方式......


来自:http://my.oschina.net/nanlou/blog/339255