cordova.js 文件分析(1)
为啥上来先分析这个类呢?其实是因为我们创建的index.html文件中只对该文件做了引用,因此猜测,该文件应该是cordova的入口文件了.
文件解析
第一步: 整体语法
;(function() {
})()
这个是js自执行语法 .大神解释———给自执行的代码块的中的变量保护,防止变量污染.
第一步:声明变量
var PLATFORM_VERSION_BUILD_LABEL = '5.0.1';
var require;
var define;
这里声明三个变量
第二步:自执行程序
(function () {
var modules = {};
// Stack of moduleIds currently being built.*
var requireStack = [];
// Map of module ID -> index into requireStack of modules currently being built.*
var inProgressModules = {};
var SEPARATOR = '.';
function build (module) {
var factory = module.factory;
var localRequire = function (id) {
var resultantId = id;
// Its a relative path, so lop off the last portion and add the id (minus "./")*
if (id.charAt(0) === '.') {
resultantId = module.id.slice(0, module.id.lastIndexOf(SEPARATOR)) + SEPARATOR + id.slice(2);
}
return require(resultantId);
};
module.exports = {};
delete module.factory;
factory(localRequire, module.exports, module);
return module.exports;
}
require = function (id) {
if (!modules[id]) {
throw 'module ' + id + ' not found';
} else if (id in inProgressModules) {
var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id;
throw 'Cycle in require graph: ' + cycle;
}
if (modules[id].factory) {
try {
inProgressModules[id] = requireStack.length;
requireStack.push(id);
return build(modules[id]);
} finally {
delete inProgressModules[id];
requireStack.pop();
}
}
return modules[id].exports;
};
define = function (id, factory) {
if (modules[id]) {
throw 'module ' + id + ' already defined';
}
modules[id] = {
id: id,
factory: factory
};
};
define.remove = function (id) {
delete modules[id];
};
define.moduleMap = modules;
})();
这也是自执行程序.为了防止该执行程序内的变量泄露给外界.
自执行程序分析
- 声明变量 modules,requireStack,inProgressModules,SEPARATOR
- 声明函数 build (module)
- 给外界的require变量赋值. (这里需要注意的是,该变量指向的是一个函数)
- 给外界的define变量赋值(同理,该变量指向的是一个函数)
- 给变量define 增加属性remove.该属性指向一个函数
- 给变量define 增加属性moduleMap ,该属性指向变量modules
define变量分析

从这个结构体的api能看出来,define相当于字典.
define自身指向的函数相当于向字典中添加数据
define的remove属性指向的函数相当于从字典中删除数据
define中的moduleMap相当于全局变量
modules[id] = {
id: id,
factory: factory
};
从这里我们也能看出modules中的数据结构是

require变量分析
该变量指向一个函数,该函数传入一个参数 id.
throw 相当于返回
slice() 方法可从已有的数组中返回选定的元素。
语法
start 必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。
end 可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。
join() 方法用于把数组中的所有元素放入一个字符串。
语法
arrayObject.join(separator)
separator 可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符。
- 正常检查modules 是否包含id 不包含直接抛出异常
- 要是modules 中包含id的数值,那么检查是否加载过该id值.加载过就抛出异常(inProgressModules 是map,requireStack是list. inProgressModules 中key 对应的值就是存入在requireStack的下标)
- 判断modules 中id 对应的字典是不是包含factory字段,包含.执行4,否则执行6
- 将该id 保存在inProgressModules 和requireStack 中(防止多次加载), 调用build函数
- 要是调用build函数出现异常,那么说明加载的数据是错误的,将数据从加载数据中清除掉
- 返回modules[id].对应的exports字段(这里的exports字段是在build中加入的)
inProgressModules 和 requireStack 就是判断是否成功调用了build函数.防止多次调用.
这里的的modules 就是define 中moduleMap 指向数据。
这里我们要理解require的作用:
我们知道我们通过define加入到全局变量define指向的moduleMap中的factory是没有进行调用过的,只是一个指针。
这里require 左右就是调用factory函数指针,将moduleMap[key]指向exports
reuire变化.png
build 函数
这里我们知道build函数传入的是map .结构{id:id,factory:factory}
- 获取factory字段
- 生成localRequire 函数指针
- 给module 增加exports属性.exports 是map
- 删除掉module的factory字段
- 调用factory 函数
- 返回module 的exports属性(是个字典)
这里的factory的数据结构能看出来了.
第一个参数是一个函数指针,
第二个参数是一个map
第三个参数是module
从上面的操作,我们知道了.modules 经过build的数据结构如下

localRequire 函数
这里有个循环,require ->build > factory->localRequire->require 类似递归
这里这样写的目的是:我们可能会定义很多factory,而每个factory可能会依赖其他的factory,因此,我们在factory 中传入localRequire 就是让当前factory初始化以前保证依赖的factory已经进行了初始化
node 赋值
// Export for use in node*
if (typeof module === 'object' && typeof require === 'function') {
module.exports.require = require;
module.exports.define = define;
}
define 的moduleMap加入数据
下面就向define的moduleMap中加入数据
moduleMap 中的数据有key值有
- cordova
- cordova/argscheck
- cordova/base64
- cordova/builder
- cordova/channel
- cordova/exec
- cordova/exec/proxy
- cordova/init
- cordova/modulemapper
- cordova/platform
- cordova/plugin/ios/console
- cordova/plugin/ios/logger
- cordova/pluginloader
- cordova/urlutil
- cordova/utils
这里我们知道我们是通过define的形式将 以上key加入到全局modules中的,因此这些key 对应的factory都没有进行初始化.只是暴露给外界一个引用.这样其实是有好处的.因为有的factory可能加载时很耗费时间的,这样写可以在需要加载factory模块的时候在加载.而不需要都加载所有模块.
接下来我们具体分析每个模块
加载cordova 的factory模块
window.cordova = require('cordova');
我们知道require 返回的一定是一个map .这里是给window添加一个属性cordova属性,是map类型的.
由于factory比较长,因此这里分段分析
if (window.cordova && !(window.cordova instanceof HTMLElement)) { *// eslint-disable-line no-undef*
throw new Error('cordova already defined');
}
这里就是判断window是否加载了cordova属性,防止重复加载
var channel = require('cordova/channel');
var platform = require('cordova/platform');
需要依赖的模块
var m_document_addEventListener = document.addEventListener;
var m_document_removeEventListener = document.removeEventListener;
var m_window_addEventListener = window.addEventListener;
var m_window_removeEventListener = window.removeEventListener;
声明四个变量指针,指向document 和window的监听事件
document.addEventListener
addEventListener里最后一个参数决定该事件的响应顺序;
如果为true事件执行顺序为 addEventListener—> 标签的onclick事件—> document.onclick
如果为false事件的顺序为 标签的onclick事件—>document.onclick ------> addEventListener
这里牵扯到“事件流”的概念。侦听器在侦听时有三个阶段:捕获阶段、目标阶段和冒泡阶段。顺序 为:捕获阶段(根节点到子节点检查是否调用了监听函数)→目标阶段(目标本身)→冒泡阶段(目标本身到根节点)。此处的参数确定侦听器是运行于捕获阶段、 目标阶段还是冒泡阶段。 如果将 useCapture 设置为 true,则侦听器只在捕获阶段处理事件,而不在目标或冒泡阶段处理事件。 如果useCapture 为 false,则侦听器只在目标或冒泡阶段处理事件。 要在所有三个阶段都侦听事件,请调用两次 addEventListener,一次将 useCapture 设置为 true,第二次再将useCapture 设置为 false。
document.removeEventListener 同上
window.addEventListener 同上
window.removeEventListener 同上
这里需要知道window 是在最顶层,而 docment是window之下
捕获: window 先于document
冒泡:document 先于 window
true 代表捕获阶段
false 代表冒泡阶段
默认是false
这里可以理解可以拦截所有的事件
这里我们有必要了解下window和docment的区别
Window -- 代表浏览器中一个打开的窗口:
对象属性
window //窗口自身
window.self //引用本窗户window=window.self
window.name //为窗口命名
window.defaultStatus //设定窗户状态栏信息
window.location //URL地址,配备布置这个属性可以打开新的页面对象方法
window.alert("text") //提示信息会话框
window.confirm("text") //确认会话框
window.prompt("text") //要求键盘输入会话框
window.setIntervel("action",time) //每一隔指定的时间(毫秒)就执行一次操作
window.clearInterval() //清除时间配备布置作用就是终止轮回
window.setTimeout(action,time) //隔了指定的时间(毫秒)执行一次操作
window.open() //打开新的窗口
window.close() //关闭窗口成员对象
window.event
window.document //见document对象详解
window.history
window.screen
window.navigator
window.external
window.history对象
window.history.length //浏览过的页面数
history.back() //后退
history.forward() //前进
history.go(i) //前进或后退到历史记录的第i个页面
//i>0进步,i<0 后退
window.screen对象
window.screen.width //屏幕宽度
window.screen.height //屏幕高度
window.screen.colorDepth //屏幕色深
window.screen.availWidth //可用宽度
window.screen.availHeight //可用高度(除去任务栏的高度)
window.external对象
window.external.AddFavorite("地址","标题" ) //把网站新增到保藏夹
window.navigator对象
window.navigator.appCodeName //浏览器代码名
window.navigator.appName //浏览器应用程序名
window.navigator.appMinorVersion //浏览器补丁版本
window.navigator.cpuClass //cpu类型 x86
window.navigator.platform //操作体系类型 win32
window.navigator.plugins
window.navigator.opsProfile
window.navigator.userProfile
window.navigator.systemLanguage //客户体系语言 zh-cn简体中文
window.navigator.userLanguage //用户语言,同上
window.navigator.appVersion //浏览器版本
window.navigator.userAgent
window.navigator.onLine //用户否在线
window.navigator.cookieEnabled //浏览器是否撑持cookie
window.navigator.mimeTypes
document对象 -- 代表整个HTML 文档,可用来访问页面中的所有元素:
对象属性
document.title //设置文档标题等价于HTML的
document.bgColor //设置页面背景色
document.fgColor //设置前景色(文本颜色)
document.linkColor //未点击过的链接颜色
document.alinkColor //激活链接(焦点在此链接上)的颜色
document.vlinkColor //已点击过的链接颜色
document.URL //设置URL属性从而在同一窗口打开另一网页
document.fileCreatedDate //文件建立日期,只读属性
document.fileModifiedDate //文件修改日期,只读属性
document.fileSize //文件大小,只读属性
document.cookie //设置和读出cookie
document.charset //设置字符集 简体中文:gb2312常用对象方法
document.write() //动态向页面写入内容
document.createElement_x(Tag) //创建一个html标签对象
document.getElementByIdx_x(ID) //获得指定ID值的对象
document.getElementsByName(Name) //获得指定Name值的对象
document.body.appendChild(oTag)body-主体子对象
document.body //指定文档主体的开始和结束等价于
document.body.bgColor //设置或获取对象后面的背景颜色
document.body.link //未点击过的链接颜色
document.body.alink //激活链接(焦点在此链接上)的颜色
document.body.vlink //已点击过的链接颜色
document.body.text //文本色
document.body.innerText //设置...之间的文本
document.body.innerHTML //设置...之间的HTML代码
document.body.topMargin //页面上边距
document.body.leftMargin //页面左边距
document.body.rightMargin //页面右边距
document.body.bottomMargin //页面下边距
document.body.background //背景图片
document.body.appendChild(oTag) //动态生成一个HTML对象常用对象事件
document.body.onclick="func()" //鼠标指针单击对象是触发
document.body.onmouseover="func()" //鼠标指针移到对象时触发
document.body.onmouseout="func()" //鼠标指针移出对象时触发location-位置子对象
document.location.hash // #号后的部分
document.location.host // 域名+端口号
document.location.hostname // 域名
document.location.href // 完整URL
document.location.pathname // 目录部分
document.location.port // 端口号
document.location.protocol // 网络协议(http:)
document.location.search // ?号后的部分常用对象事件
documeny.location.reload() //刷新网页
document.location.reload(URL) //打开新的网页
document.location.assign(URL) //打开新的网页document.location.replace(URL) //打开新的网页
selection-选区子对象
document.selection
images集合(页面中的图象):
----------------------------
a)通过集合引用
document.images //对应页面上的标签
document.images.length //对应页面上标签的个数
document.images[0] //第1个标签
document.images[i] //第i-1个标签
----------------------------
b)通过nane属性直接引用document.images.oImage //document.images.name属性
----------------------------
c)引用图片的src属性
document.images.oImage.src //document.images.name属性.src
var documentEventHandlers = {};
var windowEventHandlers = {};
声明两个事件处理变量
document.addEventListener = function (evt, handler, capture) {
var e = evt.toLowerCase();
if (typeof documentEventHandlers[e] !== 'undefined') {
documentEventHandlers[e].subscribe(handler);
} else {
m_document_addEventListener.call(document, evt, handler, capture);
}
};
给document增加具体监听行为
- 将事件变成小写的赋值给变量e
- 判断变量documentEventHandlers 是否存入了e,存入,那么订阅handle
- 没有存入e,那么,m_document_addEventListener 进行call函数
subscribe 订阅
这是一种观察者模式,和publish(发布对应使用) 其实就是观察者模式了.
call 方法
语法 定义 说明 call(thisObj,Object) 调用一个对象的一个方法,以另一个对象替换当前对象。 call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象.如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj apply(thisObj,[argArray]) 应用某一对象的一个方法,用另一个对象替换当前对象。 如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数
这个函数说明要是在documentEventHandlers 没有需要拦截的事件,那么我们就执行该事件.依次传递下去
该函数是给documentEventHandlers中添加的数据增加订阅事件的功能.
window.addEventListener = function (evt, handler, capture) {
var e = evt.toLowerCase();
if (typeof windowEventHandlers[e] !== 'undefined') {
windowEventHandlers[e].subscribe(handler);
} else {
m_window_addEventListener.call(window, evt, handler, capture);
}
};
同理window增加事件
document.removeEventListener = **function** (evt, handler, capture) {
**var** e = evt.toLowerCase();
*// If unsubscribing from an event that is handled by a plugin*
**if** (**typeof** documentEventHandlers[e] !== 'undefined') {
documentEventHandlers[e].unsubscribe(handler);
} **else** {
m_document_removeEventListener.call(document, evt, handler, capture);
}
};
window.removeEventListener = **function** (evt, handler, capture) {
**var** e = evt.toLowerCase();
*// If unsubscribing from an event that is handled by a plugin*
**if** (**typeof** windowEventHandlers[e] !== 'undefined') {
windowEventHandlers[e].unsubscribe(handler);
} **else** {
m_window_removeEventListener.call(window, evt, handler, capture);
}
};
这里和增加事件是相对的.
function createEvent (type, data) {
var event = document.createEvent('Events');
event.initEvent(type, false, false);
if (data) {
for (var i in data) {
if (data.hasOwnProperty(i)) {
event[i] = data[I];
}
}
}
return event;
}
创建event ,并且该event 设置上所有传入的data属性
这里大概是自定义事件
对于标准浏览器,其提供了可供元素触发的方法:
element.dispatchEvent()
. 不过,在使用该方法之前,我们还需要做其他两件事,及创建和初始化。因此,总结说来就是:document.createEvent() event.initEvent() element.dispatchEvent()
举个板栗:
$(dom).addEvent("alert", function() { alert("弹弹弹,弹走鱼尾纹~~"); }); // 创建 var evt = document.createEvent("HTMLEvents"); // 初始化 evt.initEvent("alert", false, false); // 触发, 即弹出文字 dom.dispatchEvent(evt);
接下来就是定义cordova对象并赋值给module.exports = cordova;
var cordova = {
};
module.exports = cordova;
cordova 结构
define: define,
require: require,
version: PLATFORM_VERSION_BUILD_LABEL,
platformVersion: PLATFORM_VERSION_BUILD_LABEL,
platformId: platform.id,
这几个就是简单赋值
addWindowEventHandler: function (event) {
return (windowEventHandlers[event] = channel.create(event));
},
addStickyDocumentEventHandler: function (event) {
return (documentEventHandlers[event] = channel.createSticky(event));
},
addDocumentEventHandler: function (event) {
return (documentEventHandlers[event] = channel.create(event));
},
removeWindowEventHandler: function (event) {
delete windowEventHandlers[event];
},
removeDocumentEventHandler: function (event) {
delete documentEventHandlers[event];
},
这里我们知道上文的windowEventHandlers 对象 和 documentEventHandlers 对象是添加数据是通过这几个api添加的.这几个api添加事件拦截.算是切面技术吧
从这里我们应该能看出来,这里添加的事件都是需要拦截的事件.
getOriginalHandlers: function () {
return { 'document': { 'addEventListener': m_document_addEventListener, 'removeEventListener': m_document_removeEventListener },
'window': { 'addEventListener': m_window_addEventListener, 'removeEventListener': m_window_removeEventListener } };
},
这里就是返回一个对象,包含window 和document 对象的监听和移出对象api.
fireDocumentEvent: function (type, data, bNoDetach) {
var evt = createEvent(type, data);
if (typeof documentEventHandlers[type] !== 'undefined') {
if (bNoDetach) {
documentEventHandlers[type].fire(evt);
} else {
setTimeout(function () {
// Fire deviceready on listeners that were registered before cordova.js was loaded.
if (type === 'deviceready') {
document.dispatchEvent(evt);
}
documentEventHandlers[type].fire(evt);
}, 0);
}
} else {
document.dispatchEvent(evt);
}
},
fireWindowEvent: function (type, data) {
var evt = createEvent(type, data);
if (typeof windowEventHandlers[type] !== 'undefined') {
setTimeout(function () {
windowEventHandlers[type].fire(evt);
}, 0);
} else {
window.dispatchEvent(evt);
}
},
这两个函数分别是针对window 和document 两个对象的.因此拿出一个来看就可以了
这里以document 为例
- 创建event对象
- 判断documentEventHandlers 要是没有注册该事件,那么久直接调用该事件
- 要是documentEventHandlers 中注册了该事件,那么就fire 下该事件.
- 要是type 是deviceready 那么,就直接执行该事件(这里需要注意,这是我们注册过的事件,这里应该就会直接执行我们定义的回调函数 )
疑问
这里fire 干嘛用的暂时不知道.(答案需要看完channel对象才知道)
setTimeout(0)的作用
function a() { setTimeout( function(){ alert(1) }, 0); alert(2); } a();
代码中的
setTimeout
设为 0,也就是延迟 0ms,看上去是不做任何延迟立刻执行,即依次弹出 “1”、“2”。但实际的执行结果确是 “2”、“1”。其中的原因得从setTimeout
的原理说起:JavaScript 是单线程执行的,也就是无法同时执行多段代码,当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个队列,一旦当前任务执行完毕,再从队列中取出下一个任务。这也常被称为 “阻塞式执行”。所以一次鼠标点击,或是计时器到达时间点,或是 Ajax 请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执行。假如当前 JavaScript 进程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个
setTimeout
,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以setTimeout
并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。settimeout(0)就起到了一个将事件加入到队列中,待执行的一个功能效果!
callbackId: Math.floor(Math.random() * 2000000000),
callbacks: {},
callbackStatus: {
NO_RESULT: 0,
OK: 1,
CLASS_NOT_FOUND_EXCEPTION: 2,
ILLEGAL_ACCESS_EXCEPTION: 3,
INSTANTIATION_EXCEPTION: 4,
MALFORMED_URL_EXCEPTION: 5,
IO_EXCEPTION: 6,
INVALID_ACTION: 7,
JSON_EXCEPTION: 8,
ERROR: 9
},
callbackId :cordova 对象的id ,我们这里能看出来,这个id很大,一般情况下是不可能出现重复的
callbackStatus: 返回对象的状态
callbackSuccess: function (callbackId, args) {
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
},
成功回调
callbackError: function (callbackId, args) {
// TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative.
// Derive success from status.
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
},
失败回调
callbackFromNative: function (callbackId, isSuccess, status, args, keepCallback) {
try {
var callback = cordova.callbacks[callbackId];
if (callback) {
if (isSuccess && status === cordova.callbackStatus.OK) {
callback.success && callback.success.apply(null, args);
} else if (!isSuccess) {
callback.fail && callback.fail.apply(null, args);
}
/*
else
Note, this case is intentionally not caught.
this can happen if isSuccess is true, but callbackStatus is NO_RESULT
which is used to remove a callback from the list without calling the callbacks
typically keepCallback is false in this case
*/
// Clear callback if not expecting any more results
if (!keepCallback) {
delete cordova.callbacks[callbackId];
}
}
} catch (err) {
var msg = 'Error in ' + (isSuccess ? 'Success' : 'Error') + ' callbackId: ' + callbackId + ' : ' + err;
console && console.log && console.log(msg);
console && console.log && err.stack && console.log(err.stack);
cordova.fireWindowEvent('cordovacallbackerror', { 'message': msg });
throw err;
}
},
该函数分析执行逻辑分析
- 获取callbackId对应的callBack函数
- 要是没有callback,那么就调用window的cordovacallbackerror事件
- 要是有callback事件,那么调用callback事件
- 不需要callback事件,就删除该事件
从这里我们大概能猜出callback事件的结构

addConstructor: function (func) {
channel.onCordovaReady.subscribe(function () {
try {
func();
} catch (e) {
console.log('Failed to run constructor: ' + e);
}
});
}
这里操作的是channel对象.这里暂时不详解.下节详细介绍
module.exports = cordova;
这里给module的exports 赋值
这里其实就是将全局变量modules 中的cordova 相当于给实例化了.(factory变成了exports)
网友评论