AMD and CMD are dead之js模块化黑魔法
缘由
在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);
})
.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; }
可以在不同操作系统或浏览器环境测试,兼容到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;
}
}
.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; }
此时该有掌声,但且慢着鼓掌,这是第一个版本,仅仅不够。再看下个栗子:
再举栗子
define2("NS.Song", {
init: function (title) {
this.title = title;
var aa = "xxxxxxxxxxxx";
},
play: function () {
alert(this.title);
}
})
define2("NS.Album", {
init: function (title,songs) {
this.title = title || "当耐特专辑";
this.songs =songs|| [new Song("当耐特进行曲"), new Song("当耐特荡起双桨")];
},
createSong: function (name) {
var song = new Song(name);
this.songs.push(song);
}
})
require2(["NS"], 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/>";
var album = new Album();
text += "album first song:" + album.songs[0].title;
span.innerHTML = text;
var resultShowPanel2 = document.getElementById("resultShowPanel2");
resultShowPanel2.innerHTML="";
resultShowPanel2.appendChild(span);
})
现在可以看到,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……
求砖和荐。
AMD and CMD are dead之js模块化黑魔法的更多相关文章
- AMD and CMD are dead之JS工程化终极解决方案KMD.js版本0.0.1发布
回顾 经过两天晚上疯狂的开发调试,伴随着大量掉落的头发和酸痛的颈椎,KMD.js赢来了第一个稳定版本.在此期间KMD规范也有所修改和完善. 这两天主要完成的功能有: 按需加载 版本控制 模块管理 便捷 ...
- AMD and CMD are dead之KMD规范
What's KMD? 乱世出英雄,KMD名字的由来充满了杀气. Kill AMD and CMD KMD为替代混乱的AMD和CMD世界而生,一统天下.或者让这个混乱的世界更加混乱,导致: KMD A ...
- amd、cmd、CommonJS以及ES6模块化
AMD.CMD.CommonJs.ES6的对比 他们都是用于在模块化定义中使用的,AMD.CMD.CommonJs是ES5中提供的模块化编程的方案,import/export是ES6中定义新增的 什么 ...
- AMD and CMD are dead之KMD.js之懒
缘由 "懒"在软件设计中,有着重大的意义.最常见的两种"懒",便是: 懒得计算 懒得加载 "懒得计算"常见于服务器端: 比如Multipla ...
- AMD and CMD are dead之KMD.js版本0.0.2发布
更新 正式从UglifyJS切换至UglifyJS2 增加依赖可视化功能 压缩代码更加方便 统一风格:如main的class名也不能省略 优化了kmdjs管道 修复了无数bug 通过src开启debu ...
- 该如何理解AMD ,CMD,CommonJS规范--javascript模块化加载学习总结
是一篇关于javascript模块化AMD,CMD,CommonJS的学习总结,作为记录也给同样对三种方式有疑问的童鞋们,有不对或者偏差之处,望各位大神指出,不胜感激. 本篇默认读者大概知道requi ...
- AMD and CMD are dead之KMDjs集成Blob一键下载全部build包
更新 不zuo,[A/C]MD就不会死,所以kmdjs赢来来其伟大的版本0.0.6,该版本主要的更新有: 移除去了kmdjs.get(..).then的支持,只支持kmdjs.get(-,functi ...
- AMD and CMD are dead之KMDjs在JS工程化的努力
总览 kmdjs发布了最接近最终版本的0.0.4版本https://github.com/kmdjs/kmdjs,你已经完全可以在项目中使用.我已经无法用语言形容其完美程度.借用我发的微博: 模块 ...
- AMD and CMD are dead之KMD.js依赖可视化工具发布
使用 require("MyAapp.DepTree", function (DepTree) { DepTree(({ renderTo: "holder", ...
随机推荐
- AOP详解
什么是AOP AOP Aspect Oriented Programing 面向切面编程 AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视.事务管理.安全检查.缓存) Spring ...
- [算法总结]three-way partition
procedure three-way-partition(A : array of values, mid : value): i ← 0 j ← 0 n ← size of A - 1 while ...
- 如何使用android百度地图离线地图
1.首先把离线地图放在android工程下的assets里面. 注意:建议离线地图下载通过百度地图APIDEMO去下载,因为到官网上下载的离线地图文件格式不一样,APIDEMO的格式是.dat,而官网 ...
- sql union和union all的用法及效率
UNION指令的目的是将两个SQL语句的结果合并起来.从这个角度来看, 我们会产生这样的感觉,UNION跟JOIN似乎有些许类似,因为这两个指令都可以由多个表格中撷取资料. UNION的一个限制是两个 ...
- 从OOP的角度看Golang
资料来源 https://github.com/luciotato/golang-notes/blob/master/OOP.md?hmsr=toutiao.io&utm_medium=tou ...
- solr schema.xml文档节点配置
首先,讲解一下/usr/local/solr/collection1/conf/schema.xml的配置,此文档功能类似于配置索引数据库. Field:类似于数据库字段的属性(此文统一使用用“字段” ...
- Perforce: 常用功能。
1. checkout fatherDir下所有一级子目录下名称为text.txt的文件到change list 1234: p4 edit -c 1234 -t text+k "fathe ...
- ECF R9(632E) & DP
Description: 给你$n$个数可以任取$k$个(可重复取),输出所有可能的和. $n \leq 1000,a_i \leq 1000$ Solution: 好神的DP,我们排序后把每个数都减 ...
- [机器学习] 深度学习之caffe1——软件配置与测试
caffe的编译配置真的是很让人头疼啊,不知道试过多少次了~~~ 重装系统了七八次,搞得linux的一些常用命令倒是很熟悉了~~~ 我有洁癖~~~某一个点上出了错,我一定要把它搞好了,再重新来一次,我 ...
- Thinkphp的初级注意点
开头话: 网站,说实话,是第一次做,也就直接选择了ThinkPHP这个开源框架.选择这个框架的原因...已经不记得了 貌似在我当时的认知中只有这个了,其它更优秀的框架也是这个毕业设计做到后期再去了解的 ...