JavaScript和Lua的類繼承
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