装个b,贴一段English
A beginning programmer writes her programs like an ant builds her hill, one piece at a time, without thought for the bigger structure. Her programs will be like loose sand. They may stand for a while, but growing too big they fall apart.
Realizing this problem, the programmer will start to spend a lot of time thinking about structure. Her programs will be rigidly structured, like rock sculptures. They are solid, but when they must change, violence must be done to them.
The master programmer knows when to apply structure and when to leave things in their simple form. Her programs are like clay, solid yet malleable.
Master Yuan-Ma, The Book of Programming
以上基本上是为什么要模块化的,至于什么是模块化有好多好多种。
比如这样:
function dujia1(){ //.. } function dujia2(){ //... }
这样简单的放在一起也是模块化,只不过太挫,有些人不承认而已。
再比如这样
var page = { init: function(){ //.. }, getData:function(){ //.. }, bindEvent:function(){ //... }, __secret:”我不想让让人知道" }
这个就是我们比价常用的了。所有page相关的功能都作为属性包在page这个模块里面,基本上对全局没有污染。但是也没有保留,也就是对外部来说,所有的东西都是可以看到的。比如访问 page.__secret 你能获取这个秘密,这样的包装方式是包不住的。
再比如这样:
var page = (function(){ var __secret = ”我不想让让人知道”; var init = function(){ //.. }; var getData = function(){ //.. }; var bindEvent = function(){ //... }; return { init : init } })();
这样呢外吐的只有init了。是吧,其他都包住了。
再比如一下这个样子:
var page = (function(mod){ mod.xxx=yyy; return mod })(module);
好,这样就可以扩展module了。
其实以上都不是本篇的重点,本篇的重点是模块加载工具的简单实现。
以上大家都看到了,要实现模块化,我们要设计,要知道怎么搞好,然后呢用这个方法去实现,啪啪啪写好多代码去实现。
那么换个思路想想,我们为什么不在打包过程中编译过程中或者加载过程中由程序去做这个事情呢。对,fekit是这样实现,好多好多都是这样实现的,实现这样功能的东西就是模块加载器。
先说说,我们已经写的次数和名字差不多的 require是怎么实现的。
抛开一直写的那个require不讲,我们说的是一个简单的模块加载器的简单实现。
定义一下:这个加载器,可以通过require一个文件的方式,把里面的内容添加到require的文件中,并能够执行他。也就是通过传入模块名来取得该模块的调用。
实现一个readFile方法,返回对应文件的内容;
将返回的字符串作为代码进行执行。
readFile非常好实现,忽略不提。
然后就是把字符串转成可以执行的程序代码。eval是第一个冒出来的,但是,一般提到他都会是弃用的,会有安全的漏洞
更好的方案是Function构造器。
var plus = new Function("name", "return name + ‘ bigger'"); console.log(plus("Iphone6")); //Iphone6 bigger
两个参数,第一个是用逗号分隔的参数列表字符串,第二个是函数体字符串
有了这个我们就可以来实现require方法了
//module.js function require(name){ //调用一个模块,首先检查这个模块是否已经调用 if(name in require.cache){ return require.cache[name]; } //此处生成一个code的函数,参数为 exports 和 module, 函数体为readFile返回的js文件中的代码字符串 var code = new Function("exports, module", readFile(name)); //定义外吐内容 var exports = {}, module = {exports: exports}; //执行函数体,如果有定义外吐,既module.exports 或者 exports.***之类的,会改写上面的外吐内容 code(exports, module); require.cache[name] = module.exports; //返回exports return module.exports; } //缓存对象,为了应对重复调用 require.cache = Object.create(null); //读文件,返回结果 function readFile(fileName){ ... }
这就是一个简单的CommonJS模块风格的加载方式,也是node和fekit现在使用的加载方式。
亲测,不加demo很有可能看不懂
有一个文件 aaa.js,内容如下
function (){ console.log("this is aaa"); } exports.aaa = "aaa";
这是 var aaa = require('aaa.js')
会做以下事情
readFile("aaa.js")
以字符串形式返回aaa的所有函数体,也就是上面的哪些部分代码
通过new Function构造器,创造一个code函数,函数体是aaa.js里面的内容
执行code函数,log出结果,然后扩展了module.exports
返回这个module.exports 改外面的变量 aaa
我们在吐js模块的时候经常使用的方案是这样两种
# module.exports = a object;
# exports.aaa = function();
如果我们直接写 exports = a object 会怎么样,为什么?