首先,我们先来简单说一下,require.js的原理:
1、载入模块
2、通过模块名解析出模块信息,以及计算出URL
3、通过创建SCRIPT的形式把模块加载到页面中。
4、判断被加载的脚本,如果发现它有依赖就去加载依赖模块。如果不依赖其它模块,就直接执行factory方法
5、等所有脚本都被加载完毕就执行加载完成之后的回调函数。
从今天起,我们跟着我们简单的例子,通过跟踪代码,来了解require.js的源码。
1 DOCTYPE html> 2 <html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <title>Documenttitle> 7 8 <script data-main="./test.js" src="./require.js">script> 9 script> 10 <script> 11 require(['a', 'b'], function(a, b) { 12 a.printA(); 13 b.printB(); 14 }); 15 script> 16 head> 17 18 <body> 19 body> 20 21 html>
执行网页时,如果需要使用require.js作为模块加载器时,我们首先需要引入require.js。
1 2 <script data-main="./test.js" src="./require.js">script>
1 // a.js 2 define(function() { 3 return { 4 printA: console.log("I am A") 5 } 6 }); 7 8 // b.js 9 define(function() { 10 return { 11 printB: console.log("I am B") 12 } 13 });
打开require.js文件我们可以发现,文件中首先定义了三个全局变量,之后便是一个自执行函数。因此在加载require.js文件时,自动执行自执行函数中的内容。
1 var requirejs, require, define; 2 (function(global, setTimeout) { 3 // 此处省略... 4 }(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)));
首先我们,可以将require.js分为三个部分,
1)定义全局变量和帮助函数
2)模块加载核心部分
3)定义require和define两个方法,以及项目入口。
如果我们使用跟踪代码的话,会发现文件一直都在定义变量和方法,一直执行到req({});这个语句调用req方法,传入一个空对象作为参数,用于创建一个默认上下文。现在我们跟踪代码,简单了解如何创建一个默认上下文。执行下面这段代码
1 req = requirejs = function(deps, callback, errback, optional) { 2 // 此时传入的参数时空对象 3 4 //Find the right context, use default 5 var context, config, 6 contextName = defContextName; // 默认上下文名为_ 7 8 // Determine if have config object in the call. 9 // 此时传入一个空对象作为配置对象 10 if (!isArray(deps) && typeof deps !== 'string') { 11 // deps is a config object 12 config = deps; 13 if (isArray(callback)) { 14 // Adjust args if there are dependencies 15 deps = callback; 16 callback = errback; 17 errback = optional; 18 } else { 19 deps = []; 20 } 21 } 22 // config为空对象,没有设置context属性,因此不设置上下文名 23 if (config && config.context) { 24 contextName = config.context; 25 } 26 27 // getOwn函数:帮助函数,用于获取对象中对应属性的值,此时返回值为undefined 28 context = getOwn(contexts, contextName); 29 if (!context) { 30 /* 31 s = req.s = { 32 contexts: contexts, 33 newContext: newContext 34 }; 35 */ 36 // 初始化时,执行下面这段代码,调用req.s中的newContext方法创建默认上下文 37 // newContext方法时require.js的核心,此时我们只需要了解,它创建了一个默认上下文 38 // 并且这个函数,只执行一次,之后的上下文,都是通过闭包,在闭包中,修改默认上下文的信息 39 // 保存自己的上下文信息 40 context = contexts[contextName] = req.s.newContext(contextName); 41 } 42 43 // 此时config为空对象,执行configure方法几乎没什么作用 44 if (config) { 45 context.configure(config); 46 } 47 48 // 跟踪代码可以发现调用context.require=>context.makeRequire()=>localRequire 49 // context.require = context.makeRequire(); 50 // function localRequire(deps, callback, errback) 51 // 返回一个localRequire 52 return context.require(deps, callback, errback); 53 };
到这里,以及完成了context初始化。
然后继续执行require({});下面的语句
1 // 重置列出的函数 2 //Exports some context-sensitive methods on global require. 3 each([ 4 'toUrl', 5 'undef', 6 'defined', 7 'specified' 8 ], function(prop) { 9 //Reference from contexts instead of early binding to default context, 10 //so that during builds, the latest instance of the default context 11 //with its config gets used. 12 req[prop] = function() { 13 var ctx = contexts[defContextName]; 14 return ctx.require[prop].apply(ctx, arguments); 15 }; 16 }); 17 18 // 获取添加脚本的父亲节点 19 if (isBrowser) { 20 head = s.head = document.getElementsByTagName('head')[0]; 21 //If BASE tag is in play, using appendChild is a problem for IE6. 22 //When that browser dies, this can be removed. Details in this jQuery bug: 23 //http://dev.jquery.com/ticket/2709 24 baseElement = document.getElementsByTagName('base')[0]; 25 if (baseElement) { 26 head = s.head = baseElement.parentNode; 27 } 28 }
以上仅仅是创建了默认上下文,并进行简单的处理。