JavaScript和Lua的类继承

别说谁变了你拦得住时间么 提交于 2019-12-09 11:08:25

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


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!