如一个工程的文件结构如下:
project/
├── css/
│ └── main.css
├── js/
│ ├── require.js
│ └── modlues/
│ ├── a.js
│ ├── b.js
│ └── c.js
└── index.html
而这里几个模块的依赖关系是:
┌> a.js -> b.js
index.html -|
└> c.js
// a.js
require("./js/test/b.js");
// b.js
console.log("i am b");
// c.js
console.log("i am c");
我们要从 index.html 中利用 require.js 获取这一连串的依赖关系,一般采用的方式就是正则匹配。
如下:先拿到 function 的代码,然后正则匹配出第一层的依赖关系,接着加载匹配到关系的代码,继续匹配。
// index.html
<script type="text/javascript" src="./js/require.js"></script>
<script type="text/javascript">
function test(){
var a = require("./js/modlues/a.js");
var c = require("./js/modlues/c.js");
}
// toString 方法可以拿到 test 函数的 code
start(test.toString());
</script>
整个函数的入口是 start,正则表达式为:
var r = /require\((.*)\)/g;
var start = function(str){
while(match = r.exec(str)) {
console.log(match[1]);
}
};
由此我们拿到了第一层的依赖关系:
["./js/modlues/a.js", "./js/modlues/c.js"]
接着要拿到 a.js 和 b.js 的文件层次依赖,之前我们写了一个 require 函数,
这个函数可以拿到脚本的代码内容,不过这个 require 函数要稍微修改下,递归去查询和下载代码。
var cache = {};
var start = function(str){
while(match = r.exec(str)) {
console.log(match && match[1]);
// 如果匹配到了内容,下载 path 对应的源码
match && match[1] && require(match[1]);
}
};
var require = function(path){
var xhr = new XMLHttpRequest(), res;
xhr.open("GET", path, true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
res = xhr.responseText;
// 缓存文件
cache[path] = res;
// 继续递归匹配
start(res);
}
}
xhr.send();
};
- 最早我们这样写代码
function foo(){
//...
}
function bar(){
//...
}
总结:
- Global 被污染,很容易命名冲突
- 简单封装:Namespace 模式
var MYAPP = {
foo: function(){},
bar: function(){}
}
MYAPP.foo();
总结:
- 减少 Global 上的变量数目
- 本质是对象,一点都不安全
- 匿名闭包 :IIFE 模式
var Module = (function(){
var _private = "safe now";
var foo = function(){
console.log(_private)
}
return {
foo: foo
}
})()
Module.foo(); // safe now
Module._private; // undefined
总结:
- 函数是 JavaScript 唯一的 Local Scope
- 再增强一点 :引入依赖
var Module = (function($){
var _$body = $("body"); // we can use jQuery now!
var foo = function(){
console.log(_$body); // 特权方法
}
// Revelation Pattern
return {
foo: foo
}
})(jQuery)
Module.foo();
总结:
- 这就是模块模式,也是现代模块实现的基石
只有封装性可不够,我们还需要加载
- 问题:
body
script(src="zepto.js")
script(src="jhash.js")
script(src="fastClick.js")
script(src="iScroll.js")
script(src="underscore.js")
script(src="handlebar.js")
script(src="datacenter.js")
script(src="deferred.js")
script(src="util/wxbridge.js")
script(src="util/login.js")
script(src="util/base.js")
script(src="util/city.js")
script(src="util/date.js")
script(src="util/cookie.js")
script(src="app.js")
- 难以维护
- 依赖模糊
- 请求过多 2.解决方案:Script Loader 如何工作?
$LAB.script("framework.js").wait()
.script("plugin.framework.js")
.script("myplugin.framework.js").wait()
.script("init.js");
3.基于文件的依赖管理
$LAB
.script( [ "script1.js", "script2.js", "script3.js"] )
.wait(function(){ // wait for all scripts to execute first
script1Func();
script2Func();
script3Func();
});
- 特点 同步/阻塞式加载
1.AMD(Async Module Definition)
RequireJS 对模块定义的规范化产出
2.CMD(Common Module Definition)
SeaJS 对模块定义的规范化产出
3.AMD vs CommonJS
a. 书写风格
// Module/1.0
var a = require("./a"); // 依赖就近
a.doSomething();
var b = require("./b")
b.doSomething();
// AMD recommended style
define(["a", "b"], function(a, b){ // 依赖前置
a.doSomething();
b.doSomething();
})
b. 执行时机
- 浏览器加载
1.1 <script>标签打开defer或async属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。
1.2 defer 和 async 区别 defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染
- defer是“渲染完再执行”
- async是“下载完就执行”