phonegap中模塊數目有幾十個,並且按模塊功能分成了不同的層次,如果完全手動請求構建會出現大量的類似window.myphonegap=require(“myphonegap”)的語句。
phonegap的設計者採用了配置構建的方式,即用一個common對象對所有通用的模塊進行配置,用一個platform對象對所有與平臺相關的模塊進行配置,builder對象提供的函數則根據這兩個配置對象作爲參數構建對應的對象模塊。
先來看common對象,因爲它是理解builder對象的基礎。
define("myphonegap/common",function(require,exports,module){
module.exports = {
defaults: {
myphonegap: {
path: 'myphonegap',
children: {
exec: {
path: 'myphonegap/exec'
},
logger: {
path: 'myphonegap/plugin/logger'
}
}
},
Myphonegap: {
children: {
exec: {
path: 'myphonegap/exec'
}
}
},
open : {
path: 'myphonegap/plugin/InAppBrowser'
},
Acceleration: {
path: 'myphonegap/plugin/Acceleration'
},
Camera:{
path: 'myphonegap/plugin/CameraConstants'
}
},
//省略若干屬性
clobbers: {
navigator: {
children: {
connection: {
path: 'myphonegap/plugin/network'
}
}
}
}
};
});
上面的代碼看出,這個導出模塊較爲簡單,沒有任何的函數對象;整個導出對象就是一個對象字面量定義的。這個字面量就是所有通用模塊的配置信息。
下面分析builder模塊,builder主要代碼結構如下:
define("myphonegap/builder", function(require, exports, module) {
一
var utils = require('myphonegap/utils');
function each(objects, func, context) {
}
function clobber(obj, key, value) {
}
function assignOrWrapInDeprecateGetter(obj, key, value, message) {
}
function include(parent, objects, clobber, merge) {
}
function recursiveMerge(target, src) {
}
module.exports = {
buildIntoButDoNotClobber: function(objects, target) {
include(target, objects, false, false);
},
buildIntoAndClobber: function(objects, target) {
include(target, objects, true, false);
},
buildIntoAndMerge: function(objects, target) {
include(target, objects, true, true);
},
recursiveMerge: recursiveMerge,
assignOrWrapInDeprecateGetter: assignOrWrapInDeprecateGetter
};
});
先分析each函數:
function each(objects, func, context) {
for (var prop in objects) {
if (objects.hasOwnProperty(prop)) {
func.apply(context, [objects[prop], prop]);
}
}
}
for循環遍歷objects中的屬性,if判斷條件選出了非原型對象中的屬性,func函數處理屬性,context作爲執行環境,屬性和屬性名作爲參數。這應該屬於一個可複用性較強的函數,對於新手來說理解的難點在Object原型對象提供的兩個函數hasOwnProperty和apply,弄懂了這兩個函數的用法就容易了。
function clobber(obj, key, value) {
obj[key] = value;
// Getters can only be overridden by getters.
if (obj[key] !== value) {
utils.defineGetter(obj, key, function() {
return value;
});
}
}
上面這個函數是給obj對象插入一個屬性,屬性名爲key,值爲value。和下面的函數一起替代了之前版本的obj[key] = value 這樣簡單的表達。目的是讓程序更健壯,但是代碼對初學者來說比較難懂了。
function assignOrWrapInDeprecateGetter(obj, key, value, message) {
if (message) {
utils.defineGetter(obj, key, function() {
console.log(message);
delete obj[key];
clobber(obj, key, value);
return value;
});
} else {
clobber(obj, key, value);
}
}
下面看看include函數,each函數將調用作爲參數的匿名函數並以objects的屬性和屬性名作爲參數,全局環境作爲執行環境。
assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated)函數的本質其實就是插入屬性值,之前的版本有更簡單的實現parent[key] = result;如果覺得下面的代碼難理解就將所有的assignOrWrapInDeprecateGetter函數和clobber函數用parent[key]=result替換。
clobber是指定合併規則,是以parent爲標準還是以objects爲標準,clobber爲true表示以objects爲準,如果parent中不存在的屬性就添加該屬性,存在的屬性則覆蓋或者遞歸添加。Clobber爲false時表示以parent爲準,如果該屬性在parent中不存在,則直接添加,如果存在則將該屬性作爲下一次遞歸的目標屬性。
function include(parent, objects, clobber, merge) {
each(objects, function (obj, key) {
try {
var result = obj.path ? require(obj.path) : {};
if (clobber) {
////如果目標對象不存在該屬性,直接賦值爲導入的模塊
if (typeof parent[key] === 'undefined') {
assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated);
} else if (typeof obj.path !== 'undefined') {
// 如果存在該屬性,合併則遞歸合併,否則覆蓋
if (merge) {
recursiveMerge(parent[key], result);
} else {
assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated);
}
}
result = parent[key];
} else {
if (typeof parent[key] == 'undefined') {
assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated);
} else {
// 目標對象已有該屬性,使用該屬性作爲下一次遞歸處理的參數
result = parent[key];
}
}
//遞歸處理
if (obj.children) {
include(result, obj.children, clobber, merge);
}
} catch(e) {
utils.alert('Exception building cordova JS globals: ' + e + ' for key "' + key + '"');
}
});
}
上面使用了函數recursizveMerge遞歸合併對象,src對象中屬性合併到target屬性中。這個函數的可複用性較強。
function recursiveMerge(target, src) {
for (var prop in src) {
if (src.hasOwnProperty(prop)) {
if (target.prototype && target.prototype.constructor === target) {
// If the target object is a constructor override off prototype.
clobber(target.prototype, prop, src[prop]);
} else {
if (typeof src[prop] === 'object' && typeof target[prop] === 'object') {
recursiveMerge(target[prop], src[prop]);
} else {
clobber(target, prop, src[prop]);
}
}
}
}
}
現在來測試下,先讓myphonegap模塊中導出一個Hello函數,exec模塊直接導出函數對象。然後我們通過builder和common模塊來構建myphonegap和exec模塊。不明白的話看代碼。
先是myphonegap模塊,前面說過define函數,define函數的第二個參數是一個工廠函數,用來創建最終的模塊對象。這裏module是形參,module.exports就是最終的模塊對象。如果對此有不理解的回頭看第三節。
//註冊myphonegap模塊
define("myphonegap", function(require, exports, module){
console.info("create myphonegap module");
module.exports = {
Hello:function(name){
console.info("hello, "+name +" !");
}
};
});
exec本來是執行本地API的函數借口,這裏我們先用一個簡單的函數替代。
define("myphonegap/exec", function(require, exports, module){
module.exports= function(name){
console.info("Exec native function " + name+" !");
};
});
接下來上測試代碼,先將之前的測試代碼註釋,當然也可以不註釋掉,之後的window對象用一個任意的自定義對象替換。
//window.myphonegap = require('myphonegap');
var builder = require("myphonegap/builder");
var common = require("myphonegap/common");
builder.buildIntoButDoNotClobber( common.defaults,window);
window.myphonegap.Hello("Jack");
window.myphonegap.exec("myfunc");
執行代碼,console輸出結果如下:
注:本節中用到utils模塊的內容,uitls只是一個簡單的工具模塊,從源碼拷貝過來改名即可。
myphonegap.js 本例完整源碼:
;(function(){
var require,//myphonegap內部的工具函數,用來導入相關的模塊
define;//在myphonegap註冊相關的模塊
//通過一個立即調用的匿名函數,來給require和define賦上實際的函數
(function(){
var modules={}; // 模塊數組,添加模塊類似給這個對象添加了屬性,模塊名爲屬性名,模塊對象爲屬性值,或者說是鍵值對
build = function(module){ //根據模塊對象構造模塊導出對象,模塊導出對象存儲在modules這個對象數組內
var factory = module.factory;
module.exports = {}; //給當前模塊加入了一個exports屬性
delete module.factory; //刪除了module的屬性
factory(require,module.exports,module); //構建導出模塊,module.exports是傳出參數(實參,引用傳遞)
return module.exports;
}
require = function(id){ //根據模塊名稱/id請求模塊對象,如果是第一次請求,就構建對象
if(!modules[id]){
throw "module " + id + " not found!";
}
return modules[id].factory?build(modules[id]):modules[id].exports;
}
define = function(id,factory){ //定義模塊,模塊名稱、構建模塊對象的工廠方法。
if(modules[id]){
throw "module " + id + " is exist!";
}
modules[id] = { //定義模塊對象,左邊的值爲屬性名,右邊的值爲傳入的參數
id:id,
factory:factory
};
}
})();
//註冊myphonegap模塊
define("myphonegap", function(require, exports, module){
console.info("create myphonegap module");
var myphonegap = {
Hello:function(name){
console.info("hello, "+name +" !");
}
};
module.exports = myphonegap;
});
//註冊myphonegap/builder模塊
define("myphonegap/builder", function(require, exports, module) {
var utils = require('myphonegap/utils');
function each(objects, func, context) {
for (var prop in objects) {
if (objects.hasOwnProperty(prop)) {
//console.info(prop);
func.apply(context, [objects[prop], prop]);
}
}
}
function clobber(obj, key, value) {
obj[key] = value;
// Getters can only be overridden by getters.
if (obj[key] !== value) {
utils.defineGetter(obj, key, function() {
return value;
});
}
}
function assignOrWrapInDeprecateGetter(obj, key, value, message) {
if (message) {
utils.defineGetter(obj, key, function() {
console.log(message);
delete obj[key];
clobber(obj, key, value);
return value;
});
} else {
clobber(obj, key, value);
}
}
function include(parent, objects, clobber, merge) {
each(objects, function (obj, key) {
try {
var result = obj.path ? require(obj.path) : {};
if (clobber) {
// Clobber if it doesn't exist.
if (typeof parent[key] === 'undefined') {
assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated);
} else if (typeof obj.path !== 'undefined') {
// If merging, merge properties onto parent, otherwise, clobber.
if (merge) {
recursiveMerge(parent[key], result);
} else {
assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated);
}
}
result = parent[key];
} else {
// Overwrite if not currently defined.
if (typeof parent[key] == 'undefined') {
assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated);
} else {
// Set result to what already exists, so we can build children into it if they exist.
result = parent[key];
}
}
if (obj.children) {
include(result, obj.children, clobber, merge);
}
} catch(e) {
utils.alert('Exception building myphonegap JS globals: ' + e + ' for key "' + key + '"');
}
});
}
function recursiveMerge(target, src) {
for (var prop in src) {
if (src.hasOwnProperty(prop)) {
if (target.prototype && target.prototype.constructor === target) {
// If the target object is a constructor override off prototype.
clobber(target.prototype, prop, src[prop]);
} else {
if (typeof src[prop] === 'object' && typeof target[prop] === 'object') {
recursiveMerge(target[prop], src[prop]);
} else {
clobber(target, prop, src[prop]);
}
}
}
}
}
module.exports = {
buildIntoButDoNotClobber: function(objects, target) {
include(target, objects, false, false);
},
buildIntoAndClobber: function(objects, target) {
include(target, objects, true, false);
},
buildIntoAndMerge: function(objects, target) {
include(target, objects, true, true);
},
recursiveMerge: recursiveMerge,
assignOrWrapInDeprecateGetter: assignOrWrapInDeprecateGetter
};
});
define("myphonegap/channel",function(require,exports,module){
var utils = require("myphonegap/utils"),
nextGuid = 1;
//典型的創建對象方法:通過構造器初始化變量,從而讓各個實例相互獨立;之後通過修改函數原型共享實例方法。
var Channel = function(type,sticky){
this.type = type;
//map of guid -> function
this.handlers = {};
// 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired.
this.state = sticky?1:0;
// Used in sticky mode to remember args passed to fire().
this.fireArgs = null;
// Used by onHasSubscribersChange to know if there are any listeners.
this.numHandlers = 0;
// Function that is called when the first listener is subscribed, or when
// the last listener is unsubscribed.
this.onHasSubscribersChange = null;
}, channel={
create:function(type){
channel[type] = new Channel(type,false);
},
createSticky:function(type){
channel[type] = new Channel(type,true);
},
deviceReadyChannelsArray: [],
deviceReadyChannelsMap: {},
waitForInitialization: function(feature) {
if (feature) {
var c = channel[feature] || this.createSticky(feature);
this.deviceReadyChannelsMap[feature] = c;
this.deviceReadyChannelsArray.push(c);
}
},
initializationComplete: function(feature) {
var c = this.deviceReadyChannelsMap[feature];
if (c) {
c.fire();
}
},
join: function (h, c) {//join也就是將一組Channel連接起來,並注入一個在所有Channel上只執行一次的公共處理函數
var i = c.length;
var len = i;
var f = function() {
if (!(--i)) h();
};
for (var j=0; j<len; j++) {
!c[j].fired?c[j].subscribe(f):i--;
}
if (!i) h();
}
};
function forceFunction(f){
if (f === null || f === undefined || typeof f != 'function') throw "Function required as first argument!";
}
//給對象Channel的原型對象添加訂閱函數,參數:函數對象、上下文、全局唯一ID
Channel.prototype.subscribe = function(f,c,g){
forceFunction(f);//確保f爲函數
if(this.state==2){ //apply方法能劫持另外一個對象的方法,繼承另外一個對象的屬性;f裏面的指針爲c(Channel的實例)
f.apply(c||this,this.fireArgs);
}
var func = f,
guid = f.observer_guid;
if(typeof f == "object"){ func = utils.close(c,f);}
if(!guid){
guid = ''+ nextGuid++;
}
f.observer_guid = guid;
func.observer_guid = guid;
//防止重複添加
if(!this.handlers[guid]){
this.handlers[guid] = func;
this.numHandlers++;
if(this.numHandlers===1){
this.onHasSubscribersChange&&this.onHasSubscribersChange();
}
}
};
Channel.prototype.unsubscribe = function(f){
forceFunction(f);
var guid = f.observer_guid;
if(this.handlers[guid]){
delete this.handlers[guid];
this.numHandlers--;
if(numHandlers===0){
this.onHasSubscribersChange&&this.onHasSubscribersChange();
}
}
};
//訂閱一次
Channel.prototype.subscribeOnce = function(f,c,g){
};
//調用了在通道訂閱的所有函數
Channel.prototype.fire = function(e){
//console.info('fire start:type/'+this.type + ' numHandlers/' +this.numHandlers);
var fail = false,
fireArgs = Array.prototype.slice.call(arguments);
// Apply stickiness.
if (this.state == 1) {
this.state = 2;
this.fireArgs = fireArgs;
}
if (this.numHandlers) {
// Copy the values first so that it is safe to modify it from within
// callbacks.
var toCall = [];
for (var item in this.handlers) {
toCall.push(this.handlers[item]);
}
for (var i = 0; i < toCall.length; ++i) {
toCall[i].apply(this, fireArgs);
// console.info(this.type+' enter func fire ');
}
if (this.state == 2 && this.numHandlers) {
this.numHandlers = 0;
this.handlers = {};
this.onHasSubscribersChange && this.onHasSubscribersChange();
}
}
};
channel.create('onDOMContentLoaded');
channel.create('onNativeReady');
channel.create('onDeviceReady');
channel.waitForInitialization('onMyphonegapReady');
channel.waitForInitialization('onMyphoneConnectionReady');
module.exports = channel;
//console.info('define myphonegap/channel completed');
});
//註冊myphonegap/common模塊
//配置對象,將公共模塊組織起來
define("myphonegap/common",function(require,exports,module){
module.exports = {
defaults: {
myphonegap: {
path: 'myphonegap',
children: {
exec: {
path: 'myphonegap/exec'
}
}
},
Myphonegap: {
children: {
exec: {
path: 'myphonegap/exec'
}
}
}
},
clobbers: {
navigator: {
children: {
connection: {
path: 'myphonegap/plugin/network'
}
}
}
}
};
});
define("myphonegap/exec", function(require, exports, module) {
module.exports= function(name){
console.info("Exec native function " + name+" !");
};
});
//註冊myphonegap/platform模塊
define("myphonegap/platform", function(require, exports, module){
});
// 這裏省略了其它插件的註冊
//註冊myphonegap/utils模塊
define("myphonegap/utils", function(require, exports, module){
var utils = exports;
utils.defineGetterSetter = function(obj, key, getFunc, opt_setFunc) {
if (Object.defineProperty) {
var desc = {
get: getFunc,
configurable: true
};
if (opt_setFunc) {
desc.set = opt_setFunc;
}
Object.defineProperty(obj, key, desc);
} else {
obj.__defineGetter__(key, getFunc);
if (opt_setFunc) {
obj.__defineSetter__(key, opt_setFunc);
}
}
};
utils.defineGetter = utils.defineGetterSetter;
utils.alert = function(msg) {
if (window.alert) {
window.alert(msg);
} else if (console && console.log) {
console.log(msg);
}
};
});;
(function (context) {
}(window));
//所有模塊註冊完之後,再導入myphonegap至全局環境中
//window.myphonegap = require('myphonegap');
//window.myphonegap.Hello("wen");
//測試channel模塊
/*
var channel = require("myphonegap/channel");
channel.onNativeReady.subscribe(function(){
console.info("onNativeReady");
}
);
console.info("before native ready");
channel.onNativeReady.fire();
console.info("after native ready");
*/
//測試common模塊與builder模塊
var builder = require("myphonegap/builder");
var common = require("myphonegap/common");
builder.buildIntoButDoNotClobber( common.defaults,window);
window.myphonegap.Hello("Jack");
window.myphonegap.exec("myfunc");
})();