前几天看了下webpack打包出来的js,豁然开朗以为实现一个模块化工具稳稳的,真开始写的时候才发现too young。javascript
// 定义模块apple:
define('apple',['orange'],function(orange){
return orange
})
// 定义模块orange:
define('orange',[],function(){
return {
name:'orange',
color:'white',
size:'small',
}
})
// 使用定义好的模块:
var a = require(['apple','jquery'],function(apple,$){
console.log(apple)
console.log($('<div>123</div>'))
})
===>输出
{
name:'orange',
color:'white',
size:'small',
},
n.fn.init [div]
复制代码
let paths = {
apple:'./apple.js',
orange:'./orange.js',
jquery:'https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js',
}
复制代码
// 存放全部注册require的模块,收集他们的依赖,以及回调
let reqs = {}
// 保存加载好的模块
let modules = {}
复制代码
function require(deps,callback) {
...
}
复制代码
将本次执行require看作一个任务,并在reqs对象中注册下,模块加载完成后将执行本reqs中的全部任务。css
// 任务名称从0开始,最先注册的任务为reqs[0],随后++
let id = 0
// 建立执行模块
reqs[id] = {
deps,
id,
callback,
}
id++
复制代码
第一个require执行完后reqs对象将变为html
{
0:{
callback:function(){..},
id:0,
deps:['apple','jquery']
}
}
复制代码
而后循环deps数组,建立script标签依次加载依赖的模块:java
for(let item of deps) {
// 若是modules变量中尚未保存本模块,首先在模块中初始化本模块:1.建立模块name,2.建立watcher属性用于记录是哪一个注册reqs任务引用了我这个模块。而后建立script标签异步加载本模块。加载完成以后执行loadComplete方法。若是本模块在modoles里已存在,说明本模块已加载过,那么直接把reqs的任务id push到watchers里。
if(!modules[name]) {
// 初始化模块,并记住哪一个reqs任务引用了本模块。
modules[name] = {
// 存放依赖此模块的模块名
watchers:[id],
name:name,
}
var node = document.createElement('script');
node.type = 'text/javascript';
node.charset = 'utf-8';
node.setAttribute('data-requiremodule', name);
node.async = true;
document.body.appendChild(node)
node.addEventListener('load', loadComplete, false);
node.src = paths[name]
}else{
modules[name].watchers.push(id)
}
}
复制代码
但须要注意的是,node.load方法会在下载好的js执行完以后才会执行。意思就是说若是加载的apple.js里有console.log("apple模块加载好了")
,而loadComplete里有console.log("执行script的onload方法")
,那么执行顺序是1.apple模块加载好了;2.执行script的onload方法。由于apple模块里执行了define方法,因此先看define的定义。node
本方法采用amd规范,接收三个变量:1.本模块名称,2.本模块的依赖模块,3.本模块的执行结果。jquery
function define(name, deps, callback){
...
}
复制代码
在modules变量中注册本模块,若是本模块有依赖,执行require方法先加载依赖,等依赖加载完只有执行callback获取模块的结果;若是本模块没有依赖,执行本模块的callback方法获得本模块的结果。webpack
modules[name].callback = callback
if(deps.length === 0) {
modules[name].result = callback()
}else{
// 若是有依赖,要先执行依赖
require(deps,function(){
modules[name].result = callback(...arguments)
})
}
复制代码
以orange模块为例,define方法执行完以后modules变量为git
{
orange:{
result:{
name:'orange',
color:'white',
size:'small',
}
}
}
复制代码
定义模块是amd规范:define.amd = true
github
下面真正到了loadComplete方法。也就是script的onload回调。web
本方法主要的任务是:执行之前注册的那些依赖本模块的reqs任务。若是reqs任务的finish=true,说明模块已经执行过了,跳过。若是reqs任务没有执行过,那么拿到reqs任务deps属性,也就是依赖哪些模块,若是全部的模块都有result(结果),执行本任务的callback,并将finish置为true.
function loadComplete(evt){
var node = evt.currentTarget || evt.srcElement;
node.removeEventListener('load', loadComplete);
let name = node.getAttribute('data-requiremodule')
modules[name].watchers.map((item)=>{
if(reqs[item].finish) return
let completed = true
let args = []
reqs[item].deps.map(item2=>{
if(!modules[item2].result) {
completed = false
}else{
args.push(modules[item2].result)
}
})
if(completed) {
reqs[item].callback(...args)
reqs[item].finish = true
reqs[item].completed = true
}
})
}
复制代码
参考:
仿照require1k实现相似requirejs的模块加载库。代码
果真复杂的多,根据注释走了一遍流程,基本上流程走的通。
参考: