緣由
在2013-03-06 13:58的時候,曾甩下一片文章叫:《為什麼不使用requirejs和seajs》,並放下豪言說釋出一款完美的模組化庫,再後來就把那篇文章刪了,再然後就沒有然後。該用seajs還用seajs,甚至我碼的SCJ都是用requirejs組織起來的。
時光飛逝,歲月流轉。彈指間,來到了2014年6月15日,也就是昨日,突然碼興大發,一發不可收拾,也許跟最近小說和詩寫得比較猛,把碼意給壓抑了,便有了這次噴發。
js問題
作為一名前MS必應團隊資深當耐特(.NET)石專家,拿js與C#開發應用開發做個對比,js主要暴露的問題有:
1.沒有class關鍵字來定義類
2.沒有namespace關鍵字來定義名稱空間
3.沒有using/require/import/include關鍵字來處理依賴
4.繼承、partial class、static、private、protected、publish等都要通過小技巧或者特定約定規範且手段太多
AMD和CMD的問題
為什麼要define(function(){return xx})?
為什麼要本是同根生,還要deps?
為什麼要module.export?
為什麼要define(function(require, exports, module) {})?
為什麼所有模組都需要require deps才能使用?
別看多隻多寫了幾個單詞,但這絕對是掙扎糾結之後妥協的結果。
你要推翻它?那請制定一個更好的規範,OK?沒有就別瞎嚷嚷,OK?
規範
js裡一切define的東西皆class建立出來的
js中一切class都在namespace下
js中define("namespace.class",[namespaces],factory)用於把namespace和class名定義好,並可引用依賴的namespace,類似C#using
js中require用於引用依賴,類似於C#using
js中同一namespace下,依賴的模組不需要引用,如define("namespace.classA",factory)不再需要define("namespace.classA",["namespace.classB"],factory)
js中繼承直接通過冒號:define("namespace.class:base",[namespaces],factory)
js中部分類直接通過partial關鍵字define("partial namespace.class",[namespaces],factory)
ps:尼瑪!要求這麼多,那還是js了嗎?一定要把js改成C#一樣嗎?直接去用cs和ts算了?規範有可行性嗎?能實現嗎?
恩!js是個可塑性很強的小子,你想把他塑造成什麼形象,他就成什麼樣子。
舉個栗子
define("AppName.Song", function () { var Song = function (title) { this.title = title; } }) define("AppName.Album", function () { var Album = {}; Album.title = "當耐特專輯"; Album.songs = [new Song("當耐特進行曲"), new Song("當耐特蕩起雙槳")]; }) require(["AppName"], function () { var span = document.createElement("span"), text = ""; for (var i = 0, len = arguments.length; i < len; i++) { text += "第" + i + "個引數:" + arguments[i].toString(); text += "<br/>" } var song = new Song("春天的故事"); text += "song title:" + song.title; text += "<br/>"; text += "album first song:" + Album.songs[0].title; span.innerHTML = text; var resultShowPanel=document.getElementById("resultShowPanel"); resultShowPanel.innerHTML=""; resultShowPanel.appendChild(span); })
可以在不同作業系統或瀏覽器環境測試,相容到IE5.5+
從程式碼可以看出:
在Album中,不需要引用Song,就可以使用父AppName下的Song
在程式入口require下,直接引用top namespace就可以使用其下的Song和Album
原理
先看下圖:
拿到function之後進行toString,再重構該string,然後建立新的Function,再apply執行,把賴的模組傳給apply的第二個引數。有碼有真相:
_findRefrence = function (deps, callback, isDefine, className, mdName) { var i = 0, len = deps.length, moduleArr = [], moduleNameArr = []; for (; i < len; i++) { for (var key in modules) { var arr = key.split("."), ns = arr[0], cl = arr[1]; if (ns === deps[i]) { moduleNameArr.push(cl); moduleArr.push(modules[key]); } } } var entire = callback.toString(); var body = entire.slice(entire.indexOf("{") + 1, entire.lastIndexOf("}")) + (isDefine ? ("return " + className + ";") : ""); var fn = new Function(moduleNameArr, body); var obj = fn.apply(null, moduleArr); if (isDefine) { modules[mdName] = obj; } }
此時該有掌聲,但且慢著鼓掌,這是第一個版本,僅僅不夠。再看下個栗子:
再舉栗子
現在可以看到,define的function沒有了?全部成了{init:xxx,xxx:xxx}的JSON格式,require還保留了其回掉的function,這樣是符合語義的。
簡直是極簡主義!簡單就是美。但簡單的背後做了大量的工作。
原理
看圖:
相關程式碼:
function JSONstringifyWithFuncs(obj) { Object.prototype.toJSON = function () { var sobj = {}, i; for (i in this) if (this.hasOwnProperty(i)) sobj[i] = typeof this[i] == 'function' ? this[i].toString() : this[i]; return sobj; } }
這樣,json裡面function的資訊也不回丟失。
Class使用的是John Resig的Class,init為建構函式,使用_super可以呼叫父類方法很方便。
總結
有些好的東西,由於歷史原因可能會遭受大量的反對,但這就是我心目中,理想規範方便極簡的模組化開發方式,後續釋出並支援指令碼載入和namespace樹,如:
system
system.web
system.web.ui
system.web.ui.control
system.web.ui.control.xx.xxx.xxx.xxx……
求磚和薦。