javascript 自己雖是一門面向對象的編程語言, 但並無明確提供繼承方式.二十多年間,衆多高手提供不少模擬繼承的實現,javascript
主要的有:對象冒充,call/apply,prototype,以及深複製等. 網上有不少此類教程,在這裏就再也不贅述這些實現.我所在的團隊正在作的項目,須要使用js和lua實現同一份API接口,已達到js和lua的無縫切換.因此,實現類的繼承方案相當重要. 接下來,就是具體實現過程, 若有不到之處,還望你們指正.java
Lua ,是一門很優秀的動態語言,爲嵌入而生,若是隻是當成腳本使用的話,類的概念其實並沒必要要, 可是若是想要構成龐大規模的代碼量,這就一個亟待的問題,由於Lua 的操做符重載的支持, 使得這一過程得以實現.編程
javascript:app
function Class(Super, init ){ //do }
lua : 編程語言
function Class(Super , init ) --[[do]] end
*Super , 是新生成的類須要繼承的父類.函數
*init , 是新生成的類的構造函數. this
下面, 咱們就來實現這個兩個Class函數.lua
1.在js當中,調用new算符實際就是複製當前構造函數原型.由於Lua中並無new算符,因此應該將其屏蔽.spa
2.在lua中想要實現方法的關聯,主要使用兩種方案, 一是複製,二是覆蓋元表__index索引,複製是一個很不錯的想法,或者說是一個極其通用的思想, 繼承的本質就是讓一個實例能夠調用另一個類實例的方法.若是是這樣的的話,複製是一個很完美的方案,簡單粗暴,簡單就是穩定,粗暴就是直接;穩定直接的方案每每是實現邏輯的最佳選擇,可是想要這個過程高效,那麼就須要深厚的功力,我自認爲尚未達到這樣的水平, 因此,lua的實現機制仍是選擇覆蓋元表__index索引實現.prototype
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 版本 同樣的調用方式......