首页 > 代码库 > AMD and CMD are dead之js模块化黑魔法

AMD and CMD are dead之js模块化黑魔法

<script> var define, require, define2, require2; typeof JSON != "object" && (JSON = {}), function () { "use strict"; function i(n) { return n < 10 ? "0" + n : n } function o(n) { return u.lastIndex = 0, u.test(n) ? ‘"‘ + n.replace(u, function (n) { var t = s[n]; return typeof t == "string" ? t : "\\u" + ("0000" + n.charCodeAt(0).toString(16)).slice(-4) }) + ‘"‘ : ‘"‘ + n + ‘"‘ } function e(i, r) { var s, l, h, a, v = n, c, u = r[i]; u && typeof u == "object" && typeof u.toJSON == "function" && (u = u.toJSON(i)), typeof t == "function" && (u = t.call(r, i, u)); switch (typeof u) { case "string": return o(u); case "number": return isFinite(u) ? String(u) : "null"; case "boolean": case "null": return String(u); case "object": if (!u) return "null"; if (n += f, c = [], Object.prototype.toString.apply(u) === "[object Array]") { for (a = u.length, s = 0; s < a; s += 1) c[s] = e(s, u) || "null"; return h = c.length === 0 ? "[]" : n ? "[\n" + n + c.join(",\n" + n) + "\n" + v + "]" : "[" + c.join(",") + "]", n = v, h } if (t && typeof t == "object") for (a = t.length, s = 0; s < a; s += 1) typeof t[s] == "string" && (l = t[s], h = e(l, u), h && c.push(o(l) + (n ? ": " : ":") + h)); else for (l in u) Object.prototype.hasOwnProperty.call(u, l) && (h = e(l, u), h && c.push(o(l) + (n ? ": " : ":") + h)); return h = c.length === 0 ? "{}" : n ? "{\n" + n + c.join(",\n" + n) + "\n" + v + "}" : "{" + c.join(",") + "}", n = v, h } } typeof Date.prototype.toJSON != "function" && (Date.prototype.toJSON = function () { return isFinite(this.valueOf()) ? this.getUTCFullYear() + "-" + i(this.getUTCMonth() + 1) + "-" + i(this.getUTCDate()) + "T" + i(this.getUTCHours()) + ":" + i(this.getUTCMinutes()) + ":" + i(this.getUTCSeconds()) + "Z" : null }, String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function () { return this.valueOf() }); var r, u, n, f, s, t; typeof JSON.stringify != "function" && (u = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, s = { "\b": "\\b", "\t": "\\t", "\n": "\\n", "\f": "\\f", "\r": "\\r", ‘"‘: ‘\\"‘, "\\": "\\\\" }, JSON.stringify = function (i, r, u) { var o; if (n = "", f = "", typeof u == "number") for (o = 0; o < u; o += 1) f += " "; else typeof u == "string" && (f = u); if (t = r, r && typeof r != "function" && (typeof r != "object" || typeof r.length != "number")) throw new Error("JSON.stringify"); return e("", { "": i }) }), typeof JSON.parse != "function" && (r = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, JSON.parse = function (text, reviver) { function walk(n, t) { var r, u, i = n[t]; if (i && typeof i == "object") for (r in i) Object.prototype.hasOwnProperty.call(i, r) && (u = walk(i, r), u !== undefined ? i[r] = u : delete i[r]); return reviver.call(n, t, i) } var j; if (text = String(text), r.lastIndex = 0, r.test(text) && (text = text.replace(r, function (n) { return "\\u" + ("0000" + n.charCodeAt(0).toString(16)).slice(-4) })), /^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]").replace(/(?:^|:|,)(?:\s*\[)+/g, ""))) return j = eval("(" + text + ")"), typeof reviver == "function" ? walk({ "": j }, "") : j; throw new SyntaxError("JSON.parse"); }) }(), function () { var n = {}; define = function (t, i, r) { if (arguments.length === 1) throw "the module must take a name"; if (arguments.length === 2) if (t.match(".")) { var u = t.split("."), f = [u[0]]; _findRefrence(f, i, !0, u[1], t) } else n[t] = i(); else n[t] = _excuCallback(i, r) }, require = function (n, t) { _findRefrence(n, t) }, _excuCallback = function (t, i) { for (var f = t.length, u = [], r = 0; r < f; r++) u.push(n[t[r]]); return i.apply(null, u) }, _findRefrence = function (t, i, r, u, f) { for (var e = 0, a = t.length, h = [], c = [], o; e < a; e++) for (o in n) { var l = o.split("."), v = l[0], y = l[1]; v === t[e] && (c.push(y), h.push(n[o])) } var s = i.toString(), p = s.slice(s.indexOf("{") + 1, s.lastIndexOf("}")) + (r ? "return " + u + ";" : ""), w = new Function(c, p), b = w.apply(null, h); r && (n[f] = b) } }(), function () { function r(n) { Object.prototype.toJSON = function () { var t = {}, n; for (n in this) this.hasOwnProperty(n) && (t[n] = typeof this[n] == "function" ? this[n].toString() : this[n]); return t }; var t = JSON.stringify(n); return delete Object.prototype.toJSON, t } var t, i, n; log = function (n) { window.console && console.log(n) }, t = !1, i = /xyz/.test(function () { xyz }) ? /\b_super\b/ : /.*/, this.Class = function () { }, Class.extend = function (n) { function u() { !t && this.init && this.init.apply(this, arguments) } var e = this.prototype, f, r; t = !0, f = new this, t = !1; for (r in n) f[r] = typeof n[r] == "function" && typeof e[r] == "function" && i.test(n[r]) ? function (n, t) { return function () { var r = this._super, i; return this._super = e[n], i = t.apply(this, arguments), this._super = r, i } }(r, n[r]) : n[r]; return u.prototype = f, u.prototype.constructor = u, u.extend = arguments.callee, u }, n = {}, window.modules = n, define2 = function (t, i, u) { if (arguments.length === 1) throw "the module must take a name"; if (arguments.length === 2) if (_indexOf(t, ".") !== -1) { var f = t.split("."), e = [f[0]], o = "function(){ var " + f[1] + "=Class.extend(" + r(i) + ");}"; _findRefrence(e, o, !0, f[1], t) } else n[t] = Class.extend(i); else n[t] = _excuCallback(i, u) }, require2 = function (n, t) { _findRefrence(n, t) }, _excuCallback = function (t, i) { for (var f = t.length, u = [], r = 0; r < f; r++) u.push(n[t[r]]); return i.apply(null, u) }, _indexOf = function (n, t) { if (n.indexOf) return n.indexOf(t); for (var i = 0, r = n.length; i < r; i++) if (t === n.charAt(i)) return i; return -1 }, _findRefrence = function (t, i, r, u, f) { for (var s = 0, p = t.length, c = [], l = [], h, e, o, v, y; s < p; s++) for (h in n) { var a = h.split("."), w = a[0], b = a[1]; w === t[s] && (l.push(b), c.push(n[h])) } e = i.toString().replace(/"function[\s\S]*?\}"/g, function (n) { return n.substr(1, n.length - 2) }), o = e.slice(e.indexOf("{") + 1, e.lastIndexOf("}")) + (r ? "return " + u + ";" : ""), o = o.replace(/\\r\\n/g, "").replace(/\\n/g, "").replace(/\\/g, ""), v = new Function(l, o), y = v.apply(null, c), r && (n[f] = y) } }(); </script>

缘由

在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);})
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: Consolas, "Courier New", Courier, Monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>
<script> ; (function () { $("#excuCodeBtn").on("click", function () { 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 += "
" } var song = new Song("春天的故事"); text += "song title:" + song.title; text += "
"; text += "album first song:" + Album.songs[0].title; span.innerHTML = text; var resultShowPanel=document.getElementById("resultShowPanel"); resultShowPanel.innerHTML=""; resultShowPanel.appendChild(span);}) }) })(); </script>

可以在不同操作系统或浏览器环境测试,兼容到IE5.5+

从代码可以看出:

在Album中,不需要引用Song,就可以使用父AppName下的Song

在程序入口require下,直接引用top namespace就可以使用其下的Song和Album

原理

先看下图:

20140616084001

拿到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;    }}
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

此时该有掌声,但且慢着鼓掌,这是第一个版本,仅仅不够。再看下个栗子:

再举栗子

<script> ; (function () { $("#excuCodeBtn2").on("click", function () { eval($("#exampleCode2").val()); }) })(); </script>

现在可以看到,define的function没有了?全部成了{init:xxx,xxx:xxx}的JSON格式,require还保留了其回掉的function,这样是符合语义的。

简直是极简主义!简单就是美。但简单的背后做了大量的工作。

原理

看图:

20140616090652

相关代码:

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……

求砖和荐。