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 版本 一样的调用方式......