之前写的都是Dojo的基本使用教程,整体的讲解了Dojo AMD加载器使用及原理, DOM 操作, 事件,动画,这些都是平常工作中会经常用到的, 但对于真正的提升还是要分析源代码,了解整个的框架设计,同时针对每个函数或每行代码,看看大师们是怎么写的,有助于自己提写自己的编码水平。
我们先从 dojo 包中 dojo.js 开始讲解。
(function(userConfig, defaultConfig){
//主体代码
})(
// 传入用户配置
function(){
return this.dojoConfig || this.djConfig || this.require || {};
},
//传入默认配置
{
// 传入浏览器环境参数(通过has.js特征检测确定运行环境), 在其它环境,如nodejs时,会修改这些配置, 大部分选项都是用于build做优化的,这里有详细介绍has选项,http://dojotoolkit.org/reference-guide/1.9/dojo/has.html
hasCache:{
"host-browser": 1, //浏览器环境下使用dojo
"dom":1, //支持浏览器文档对像
"dojo-amd-factory-scan":1, // loader选项,不是has.add 特征检测,默认情况是CommonJs的模块请求 define(["require","export","module",function(require,export,module){}), 如果指定为true,使用AMD扫描factory, 会自动设置这三个模块。即直接定义 define([],function(){})
"dojo-loader":1, //表明使用dojo loader, 而不是requirejs等三方的。在_base/loader.js会使用它个选项来判断加载器是不是dojo 本身加载器,如果是第三方的,则无法加载老版本的Dojo.
"dojo-has-api":1, //应用在has.js里, 主用于 has.js 里检测是否之前已经定义了has, has.add方法,如果使用了 dojo-loader,那么loader已经定义了这两个方法,在has.js里无需在定义这两个方法。如果使用第三方的加载器,则 dojo-has-api 默认为没有定义,那么在has.js里会重新定义
"dojo-inject-api":1, // 应用在dojo.js, 表示是否支持跨域加载模块。
"dojo-timeout-api":1, // 应用在dojo.js, 配合waitSeconds, 提供一个加载超时的API. 如是不设这个为true, 那么waitSeconds 也是无效的,因为没有一个计时器的功能。
"dojo-log-api":1, // dojo.js, 主要用于提供日志功能,req.log('a','b'), 会调用console.log函数,并且把每个参数按行来输出。arguments[0], arguments[1]。
"dojo-dom-ready-api":1, //dojo.js 提供一个监听器,当dom加载完成时,会通知这个监听器。
"dojo-publish-privates":1, // dojo.js, 是否允许公开loader 的私有方法或者变量。 build时设置为 0
"dojo-config-api":1, //dojo.js 确保在 dojo在 build时可配置
"dojo-sniff":1, //dojo.js,允许扫描dojo.js 角本中的 data-dojo-config 和 djConfig 标签
"dojo-sync-loader":1, // dojo.js ,是否可以使用老版本的加载器
"dojo-test-sniff":1, // dojo.js, 是否需要侦测 Doh(单元测试)模块的测试配置 testConfig, build时为0
"config-deferredInstrumentation":1, // deferred.js, 加载dojo/promise/instrumentation模块. 该模块用于监测被拒绝的承诺,将末被处理的错误输出到控制台. build 设置为0
"config-useDeferredInstrumentation":"report-unhandled-rejections", // dojo/promise/instrumentation.js, 只将末被处理的拒绝,输出到控制台。 如果为report-rejections, 输出所有被拒绝的Promise 的信息
"config-tlmSiblingOfDojo":1 // dojo.js ,是否允许运行非标准化的模块名解析, 模块名解析就是根据字符串,解析为正确的模块路径
},
packages:[{
// 如果在dojoConfig中没有设置baseURL, 引导程序会计算baseUrl的路径为dojo文件夹,即包含 dojo.js的文夹件
name:'dojo', // 例如, 加载 dojo/dom, dojo代表包名,包的地址为baseUrl的文件夹,模块的地址为 baserUrl/dom.js
location:'.'
},{
name:'tests',
location:'./tests'
},{
name:'dijit',
location:'../dijit'
},{
name:'build',
location:'../util/build'
},{
name:'doh',
location:'../util/doh'
},{
name:'dojox',
location:'../dojox'
},{
name:'demos',
location:'../demos'
}],
trace:{
// 在加载一个模块时,需要跟踪哪些信息, 先设置以下的项,在调用require.trace.on
"loader-inject":0, // 输出模块加入到应用程序时的信息
"loader-define":0,
"loader-exec-module":0,
"loader-run-factory":0, //运行模块的factory时的信息
"loader-finish-exec":0,
"loader-define-module":0,
"loader-circular-dependency":0,
"loader-define-nonmodule":0
},
asycn:0, //默认为0, 以同步的方法加载,而不是AMD,这种加载会先把所有的基础模块如dojo/_base/kernel. 所以在用户配置里最好设置asycn:1. 这样就能按需加载。
waitSeconds:15 //加载一个模块的时间为15 秒,如果模块没有在规定时间内加载完成,触发加载错误,这样Dojo就能知道如何处理,而不是一直等待
}
)
// 实现一个内部会使用到的小型库, 提供基础的方法
var noop=function(){
}, // 空函数
isEmpty=function(it){
for(var p in it){
return 0;
}
return 1;
}, //对像是否为空
toString={}.toString,
isFunction = function(it){
return toString.call(it) == "[object Function]";
}, // 对像是否为函数
isString = function(it){
return toString.call(it) == "[object String]";
},
isArray = function(it){
return toString.call(it) == "[object Array]";
},
forEach = function(vector, callback){
if(vector){
for(var i=0; i
global= this,
doc = global.document,
element = doc && doc.createElement("Div"),
has = req.has = function(name){ // 添加 has方法, 并将方法附加到 require 上。 内部可以直接通过 has(" feature name" ) 外部可以通过 require.has(" feature Name")
return isFunction(hasCache[name]) ? hasCache[name] = hasCache[name](global, doc, element) : hasCache[name]; // 如果hasCach缓存的是一个测试函数,则调用这个测试函数,并将结果hasCache对应的项,如果不是,则直接返回相应的值
},
hasCache = has.cache = defaultConfig.hasCache;
has.add = function(name, test, now,force){
(hasCache[name] === undefined || force) && (hasCache[name] = test); //整个表达式是的意思是: 如果没有定义name的特征或者强制覆盖,则进行赋值。 第一个括号用于两个表达式的共同的结果。
return now && has(name); //当指定了now时,立即运行特侦测试,并返回结果。
};
/*
用于检测rhino环境
*/
has.add("host-rhino", userConfig.has && "host-rhino" in userConfig.has ?
userConfig.has["host-rhino"] :
(typeof load == "function" && (typeof Packages == "function" || typeof Packages == "object")));
if(has("host-rhino")){
// owing to rhino's lame feature that hides the source of the script, give the user a way to specify the baseUrl...
for(var baseUrl = userConfig.baseUrl || ".", arg, rhinoArgs = this.arguments, i = 0; i < rhinoArgs.length;){
arg = (rhinoArgs[i++] + "").split("=");
if(arg[0] == "baseUrl"){
baseUrl = arg[1];
break;
}
}
load(baseUrl + "/_base/configRhino.js");
rhinoDojoConfig(defaultConfig, baseUrl, rhinoArgs);
}
//loader使用环境后,用 userConfig中的has 替换defaultConfig指定的has测试
for(var p in userConfig.has){
has.add(p, userConfig.has[p], 0, 1); //强制替换
}
var requested = 1, //请求已经发出的状态
arrived = 2, //请求的模块已经到达
nonmodule = 3, //请求的不是一个模块
executing = 4, // 模块正在执行
executed =5; // 模块执行完成
if(has("dojo-trace-api")){
// 如果在 hasCache有指定dojo-trace-api(用于调试追踪模块的加载情况, 输出相应的信息), 则把数字替换为更友好的文字
requested = "requested";
arrived = "arrived";
nonmodule = "not-a-module";
executing = "executing";
executed = "executed";
}
// 使用同步模式加载的代码, 这段代码可以不用理会
var legacyMode = 0,
sync = "sync",
xd = "xd",
syncExecStack = [],
dojoRequirePlugin = [],
checkDojoRequirePlugin = noop,
transformToAmd = noop,
getXhr;
if(has("dojo-sync-loader")){
req.isXdUrl = noop;
req.initSyncLoader = function(dojoRequirePlugin_, checkDojoRequirePlugin_, transformToAmd_){
// dojo/_base/loader.js 加载后会调用这个方法,来初始化同步加载器
if(!dojoRequirePlugin){
dojoRequirePlugin = dojoRequirePlugin_;
checkDojoRequirePlugin = checkDojoRequirePlugin_;
transformToAmd = transformToAmd_;
}
return {
sync:sync,
requested:requested,
arrived:arrived,
nonmodule:nonmodule,
executing:executing,
executed:executed,
syncExecStack:syncExecStack,
modules:modules,
execQ:execQ,
getModule:getModule,
injectModule:injectModule,
setArrived:setArrived,
signal:signal,
finishExec:finishExec,
execModule:execModule,
dojoRequirePlugin:dojoRequirePlugin,
getLegacyMode:function(){return legacyMode;},
guardCheckComplete:guardCheckComplete
};
};
if(has("dom")){
// 如果采用传统的同步加载器方式, loader需要定义一个小型的Xhr 库。
var locationProtocol = location.protocol,
locationHost = location.host;
req.isXdUrl = function(url){
if(/^\./.test(url)){
// begins with a dot is always relative to page URL; therefore not xdomain
return false;
}
if(/^\/\//.test(url)){
// for v1.6- backcompat, url starting with // indicates xdomain
return true;
}
// get protocol and host
// \/+ takes care of the typical file protocol that looks like file:///drive/path/to/file
// locationHost is falsy if file protocol => if locationProtocol matches and is "file:", || will return false
var match = url.match(/^([^\/\:]+\:)\/+([^\/]+)/);
return match && (match[1] != locationProtocol || (locationHost && match[2] != locationHost));
};
// note: to get the file:// protocol to work in FF, you must set security.fileuri.strict_origin_policy to false in about:config
has.add("dojo-xhr-factory", 1);
has.add("dojo-force-activex-xhr", has("host-browser") && !doc.addEventListener && window.location.protocol == "file:");
has.add("native-xhr", typeof XMLHttpRequest != "undefined");
if(has("native-xhr") && !has("dojo-force-activex-xhr")){
getXhr = function(){
return new XMLHttpRequest();
};
}else{
// if in the browser an old IE; find an xhr
for(var XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], progid, i = 0; i < 3;){
try{
progid = XMLHTTP_PROGIDS[i++];
if(new ActiveXObject(progid)){
// this progid works; therefore, use it from now on
break;
}
}catch(e){
// squelch; we're just trying to find a good ActiveX progid
// if they all fail, then progid ends up as the last attempt and that will signal the error
// the first time the client actually tries to exec an xhr
}
}
getXhr = function(){
return new ActiveXObject(progid);
};
}
req.getXhr = getXhr;
has.add("dojo-gettext-api", 1);
req.getText = function(url, async, onLoad){
var xhr = getXhr();
xhr.open('GET', fixupUrl(url), false);
xhr.send(null);
if(xhr.status == 200 || (!location.host && !xhr.status)){
if(onLoad){
onLoad(xhr.responseText, async);
}
}else{
throw makeError("xhrFailed", xhr.status);
}
return xhr.responseText;
};
} // 结束对同步或者跨域加载的数据或才方法定义
}else{
req.async = 1;
}
var eval_ = new Function('return eval(argument[0]);'); // 使用构造函数, 这样我们的 eval_ 函数就能形成一个闭包,不会污染全局作用域
req.eval = function(text,hint){
return eval_(text + "\r\n@ sourceURL=" + hint);
}
// loader 事件 API
var listenerQueues = {}, //监听器队列,用于保存所有的事件类型及对应的监听器
error = "error",//因为错误类型的事件会在loader多次监听或者触发,所以定义一个error变量,可以直接在on(error, function(){}), 而不需要每次 on("error",function(){})
on = req.on = function(type,listener){
var queue = listenerQueues[type] || (listenerQueues[type]=[]); //如果没有存在相应的事件类型,则创建一个事件队列,并赋值。
queue.push(listener);
// 返回一个对像,用于删除监听器, 如 a=on('error',handler), 可以调用 a.remove() 删除listenerQueues中 error队列中的 handler 监听器。
return{
remove: function(){ //闭包函数,可以引用外围函数中的变量及参数
for(var i=0;i
// no config API, assume defaultConfig has everything the loader needs...for the entire lifetime of the application
paths = defaultConfig.paths;
pathsMapProg = defaultConfig.pathsMapProg;
packs = defaultConfig.packs;
aliases = defaultConfig.aliases;
mapProgs = defaultConfig.mapProgs;
modules = defaultConfig.modules;
cache = defaultConfig.cache;
cacheBust = defaultConfig.cacheBust;
// remember the default config for other processes (e.g., dojo/config)
req.rawConfig = defaultConfig;
if(has("dojo-config-api")){
var consumePendingCacheInsert = function(referenceModule){},
escapeString = function(s){
},
computeMapProg = function(map, dest){
},
computeAliases = function(config, dest){
},
fixupPackageInfo = function(packageInfo){
},
delayedModuleConfig
= [],
config = function(config, booting, referenceModule){
};
if(has("dojo-cdn") || has("dojo-sniff")){
/*
1. 如果在script中有指定dojo.js, 则获取 dojo.js的文件夹路径,做为baseUrl. 并获得data-dojo-config 或者 djConfig的数据。
2. 记录script节点, 并赋值给变量insertPointSibling. 用于定位之后的角本注入位置。
*/
}
if(has("dojo-test-sniff")){
/*
在DOH单元测试时,将测试的配置添加到dojoSniffConfig对像中
*/
}
req.rawConfig = {}; //原生的配置
config(defaultConfig, 1); //进行默认配置
if(has("dojo-cdn")){
//如果dojo.js在CDN上,指定dojo, dijit, dojox等包的文件路径.
}
config(userConfig, 1); //进行用户配置
config(dojoSniffConfig, 1); //对侦测到的配置选项,进行处理
}