成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

cordova研習(xí)筆記(二) —— cordova 6.X 源碼解讀(上)

Java_oldboy / 1432人閱讀

摘要:本文源碼為版本。的代碼結(jié)構(gòu)也是一個(gè)很經(jīng)典的定義結(jié)構(gòu)構(gòu)造函數(shù)實(shí)例修改函數(shù)原型共享實(shí)例方法,它提供事件通道上事件的訂閱撤消訂閱調(diào)用。

前言

cordova(PhoneGap) 是一個(gè)優(yōu)秀的經(jīng)典的中間件框架,網(wǎng)上對(duì)其源代碼解讀的文章確實(shí)不多,本系列文章試著解讀一下,以便對(duì)cordova 框架的原理理解得更深入。本文源碼為cordova android版本6.1.2。

源碼結(jié)構(gòu)

我們使用IDE的代碼折疊功能先從整體上把握代碼結(jié)構(gòu)。

/*
* 版權(quán)申明及注釋部分
*/
;(function() {
  ...
})();

;是保證導(dǎo)入的其它js腳本,使用工具壓縮js文件時(shí)不出錯(cuò)。一個(gè)自執(zhí)行匿名函數(shù)包裹,防止內(nèi)部變量污染到外部命名空間。閱讀過(guò)jQuery源碼的人都知道,jQuery的也是相同的結(jié)構(gòu),只是jQuery定義的匿名函數(shù)多了兩個(gè)參數(shù)window和undefined,然后調(diào)用的時(shí)候只傳入window,這樣,window可以在jQuery內(nèi)部安全使用,而undefined也的確表示未定義(有些瀏覽器實(shí)現(xiàn)允許重定義undefined)。

繼續(xù)展開(kāi)代碼,可以看到如下的結(jié)構(gòu):

;(function() {
var PLATFORM_VERSION_BUILD_LABEL = "6.1.2";

// 模塊化系統(tǒng)
/* ------------------------------------------------------------- */
var require, // 加載使用module
    define;  // 定義注冊(cè)module

// require|define 的邏輯
(function () {
  ...
})();

// Export for use in node
if (typeof module === "object" && typeof require === "function") {
    module.exports.require = require;
    module.exports.define = define;
}
/* ------------------------------------------------------------- */

// 事件的處理和回調(diào),外部訪問(wèn)cordova.js的入口
define("cordova", function(require, exports, module) { ... }

// JS->Native的具體交互形式
define("cordova/android/nativeapiprovider", function(require, exports, module) { ... }

// 通過(guò)prompt()和Native交互
define("cordova/android/promptbasednativeapi", function(require, exports, module)  { ... }

// 用于plugin中校驗(yàn)參數(shù),比如argscheck.checkArgs("fFO", "Camera.getPicture", arguments); 參數(shù)應(yīng)該是2個(gè)函數(shù)1個(gè)對(duì)象
define("cordova/argscheck", function(require, exports, module) { ... }

// JS->Native交互時(shí)對(duì)ArrayBuffer進(jìn)行uint8ToBase64(WebSockets二進(jìn)制流)
define("cordova/base64", function(require, exports, module) { ... }

// 對(duì)象屬性操作,比如把一個(gè)對(duì)象的屬性Merge到另外一個(gè)對(duì)象
define("cordova/builder", function(require, exports, module) { ... }

// 事件通道
define("cordova/channel", function(require, exports, module) { ... }

// 執(zhí)行JS->Native交互
define("cordova/exec", function(require, exports, module) { ... }

// 用于Plugin中往已經(jīng)有的模塊上添加方法
define("cordova/exec/proxy", function(require, exports, module) { ... }

// 初始化處理
define("cordova/init", function(require, exports, module) { ... }
define("cordova/init_b", function(require, exports, module) { ... }

// 把定義的模塊clobber到一個(gè)對(duì)象,在初始化的時(shí)候會(huì)賦給window
define("cordova/modulemapper", function(require, exports, module) { ... }
define("cordova/modulemapper_b", function(require, exports, module) { ... }

// 平臺(tái)啟動(dòng)處理
define("cordova/platform", function(require, exports, module) { ... }

// 清緩存、loadUrl、退出程序等
define("cordova/plugin/android/app", function(require, exports, module) { ... }

// 載所有cordova_plugins.js中定義的模塊,執(zhí)行完成后會(huì)觸發(fā)
define("cordova/pluginloader", function(require, exports, module) { ... }
define("cordova/pluginloader_b", function(require, exports, module) { ... }

// 獲取絕對(duì)URL,InAppBrowser中會(huì)用到
define("cordova/urlutil", function(require, exports, module) { ... }

// 工具類
define("cordova/utils", function(require, exports, module) { ... }

// 所有模塊注冊(cè)完之后,導(dǎo)入cordova至全局環(huán)境中
window.cordova = require("cordova");

// 初始化啟動(dòng)
require("cordova/init");

})();

從上可以清晰的看出,在cordova內(nèi)部,首先是定義了兩個(gè)公共的require和define函數(shù),然后是使用define注冊(cè)所有模塊,再通過(guò)window.cordova=require("cordova")導(dǎo)入庫(kù)文件至全局執(zhí)行環(huán)境中。

模塊機(jī)制

類似于Java的package/import,在JavaScript中也有類似的define/require,它用來(lái)異步加載module化的js,從而提高運(yùn)行效率。模塊化加載的必要性,起源于nodejs的出現(xiàn)。但是JavaScript并沒(méi)有內(nèi)置模塊系統(tǒng),所以就出現(xiàn)了很多規(guī)范。?主要有2種:CommonJS 和 AMD(Asynchronous Module Definition)。還有國(guó)內(nèi)興起的CMD(Common Module Definition)?。CommonJS主要面對(duì)的是服務(wù)器,代表是Node.js;AMD針對(duì)瀏覽器進(jìn)行了優(yōu)化,主要實(shí)現(xiàn)require.js;CMD是seajs。?

cordova-js最開(kāi)始采用的是require.js作者寫的almond.js(兼容AMD和CommonJS),但之后由于特殊需求(比如模塊不存在的時(shí)候要throw異常),最終從almond.js fork過(guò)來(lái)實(shí)現(xiàn)了一個(gè)簡(jiǎn)易CommonJS風(fēng)格的模塊系統(tǒng),同時(shí)提供了和nodejs之間很好的交互。在cordova.js中可以直接使用define()和require(),在其他文件可以通過(guò)cordova.define()和cordova.require()來(lái)調(diào)用。所以src/scripts/require.js中定義的就是一個(gè)精簡(jiǎn)的JavaScript模塊系統(tǒng)。?

// cordova.js內(nèi)部使用的全局函數(shù)require/define
var require,
    define;

(function () {
    // 初始化一個(gè)空對(duì)象,緩存所有的模塊
    var modules = {},
    // 正在build中的模塊ID的棧
        requireStack = [],
    // 標(biāo)示正在build中模塊ID的Map
        inProgressModules = {},
        SEPARATOR = ".";

    // 模塊build
    function build(module) {
        // 備份工廠方法
        var factory = module.factory,
        // 對(duì)require對(duì)象進(jìn)行特殊處理 
            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);
            };
        // 給模塊定義一個(gè)空的exports對(duì)象,防止工廠類方法中的空引用
        module.exports = {};
        // 刪除工廠方法
        delete module.factory;
        // 調(diào)用備份的工廠方法(參數(shù)必須是require,exports,module)  
        factory(localRequire, module.exports, module);
        // 返回工廠方法中實(shí)現(xiàn)的module.exports對(duì)象
        return module.exports;
    }
    
    // 加載使用模塊
    require = function (id) {
        // 如果模塊不存在拋出異常 
        if (!modules[id]) {
            throw "module " + id + " not found";
        // 如果模塊正在build中拋出異常
        } else if (id in inProgressModules) {
            var cycle = requireStack.slice(inProgressModules[id]).join("->") + "->" + id;
            throw "Cycle in require graph: " + cycle;
        }
        // 如果模塊存在工廠方法說(shuō)明還未進(jìn)行build(require嵌套)
        if (modules[id].factory) {
            try {
                // 標(biāo)示該模塊正在build
                inProgressModules[id] = requireStack.length;
                // 將該模塊壓入請(qǐng)求棧
                requireStack.push(id);
                // 模塊build,成功后返回module.exports
                return build(modules[id]);
            } finally {
                // build完成后刪除當(dāng)前請(qǐng)求
                delete inProgressModules[id];
                requireStack.pop();
            }
        }
        // build完的模塊直接返回module.exports  
        return modules[id].exports;
    };
    
    // 定義注冊(cè)模塊
    define = function (id, factory) {
        // 如果已經(jīng)存在拋出異常
        if (modules[id]) {
            throw "module " + id + " already defined";
        }
        // 模塊以ID為索引包含ID和工廠方法
        modules[id] = {
            id: id,
            factory: factory
        };
    };
    
    // 移除模塊
    define.remove = function (id) {
        delete modules[id];
    };
    
    // 返回所有模塊
    define.moduleMap = modules;
})();

首先在外部cordova環(huán)境中定義require和define兩個(gè)變量,用來(lái)存儲(chǔ)實(shí)現(xiàn)導(dǎo)入功能的函數(shù)和實(shí)現(xiàn)注冊(cè)功能的函數(shù)。然后用一個(gè)立即調(diào)用的匿名函數(shù)來(lái)實(shí)例化這兩個(gè)變量,在這個(gè)匿名函數(shù)內(nèi)部,緩存了所有的功能模塊。注冊(cè)模塊時(shí),如果已經(jīng)注冊(cè)了,就直接拋出異常,防止無(wú)意中重定義,如確實(shí)需要重定義,可先調(diào)用define.remove。

從內(nèi)部私有函數(shù)build中,可以看出,調(diào)用工廠函數(shù)時(shí), factory(localRequire, module.exports, module); 第一個(gè)參數(shù)localRequire實(shí)質(zhì)還是調(diào)用全局的require()函數(shù),只是把ID稍微加工了一下支持相對(duì)路徑。cordova.js沒(méi)有用到相對(duì)路徑的require,但在一些Plugin的js中有,比如Contact.jsContactError = require("./ContactError");

這里我們寫個(gè)測(cè)試用例:


注:module.js為上述cordova的模塊代碼。

上面例子中我們定義了兩個(gè)模塊,這里是寫在同一個(gè)頁(yè)面下,在實(shí)際中我們自然希望寫在兩個(gè)不同的文件中,然后按需加載。我們上一篇文章中說(shuō)明了cordova的插件使用方法,我們會(huì)發(fā)現(xiàn)cordova_plugins.js中定義了cordova插件的id、路徑等變量,并且該文件定義了一個(gè)id為cordova/plugin_list的模塊,我們?cè)赾ordova.js中可以看到有這個(gè)模塊的引用。

定義了require和define并賦值后,是將cordova所有模塊一一注冊(cè),例如:

define("cordova",function(require,exports,module){
  // 工廠函數(shù)內(nèi)部實(shí)現(xiàn)代碼
});

這里需要注意的是,define只是注冊(cè)模塊,不會(huì)調(diào)用其factory。factory函數(shù)在這個(gè)時(shí)候并沒(méi)有實(shí)際執(zhí)行,而只是定義,并作為一個(gè)參數(shù)傳遞給define函數(shù)。所有模塊注冊(cè)完之后,通過(guò):

window.cordova = require("cordova");

導(dǎo)入至全局環(huán)境。

因?yàn)槭亲?cè)后第一次導(dǎo)入,所以在執(zhí)行require("cordova")時(shí),modules["cordova"].factory的值是注冊(cè)時(shí)的工廠函數(shù),轉(zhuǎn)變?yōu)閎oolean值時(shí)為true,從而在這里會(huì)通過(guò)build調(diào)用這個(gè)工廠函數(shù),并將這個(gè)工廠函數(shù)從注冊(cè)緩存里面刪除,接下來(lái)的就是去執(zhí)行cordova的這個(gè)factory函數(shù)了。

事件通道

作為觀察者模式(Observer)的一種變形,很多MV*框架(比如:Vue.js、Backbone.js)中都提供發(fā)布/訂閱模型來(lái)對(duì)代碼進(jìn)行解耦。cordova.js中也提供了一個(gè)自定義的pub-sub模型,基于該模型提供了一些事件通道,用來(lái)控制通道中的事件什么時(shí)候以什么樣的順序被調(diào)用,以及各個(gè)事件通道的調(diào)用。

src/common/channel.js的代碼結(jié)構(gòu)也是一個(gè)很經(jīng)典的定義結(jié)構(gòu)(構(gòu)造函數(shù)、實(shí)例、修改函數(shù)原型共享實(shí)例方法),它提供事件通道上事件的訂閱(subscribe)、撤消訂閱(unsubscribe)、調(diào)用(fire)。pub-sub模型用于定義和控制對(duì)cordova初始化的事件的觸發(fā)以及此后的自定義事件。

頁(yè)面加載和Cordova啟動(dòng)期間的事件順序如下:

onDOMContentLoaded ——(內(nèi)部事件通道)頁(yè)面加載后DOM解析完成

onNativeReady ——(內(nèi)部事件通道)Cordova的native準(zhǔn)備完成

onCordovaReady ——(內(nèi)部事件通道)所有Cordova的JavaScript對(duì)象被創(chuàng)建完成可以開(kāi)始加載插件

onDeviceReady —— Cordova全部準(zhǔn)備完成

onResume —— 應(yīng)用重新返回前臺(tái)

onPause —— 應(yīng)用暫停退到后臺(tái)

可以通過(guò)下面的事件進(jìn)行監(jiān)聽(tīng):

document.addEventListener("deviceready", myDeviceReadyListener, false);
document.addEventListener("resume", myResumeListener, false);
document.addEventListener("pause", myPauseListener, false);

DOM生命周期事件應(yīng)用于保存和恢復(fù)狀態(tài):

window.onload

window.onunload

define("cordova/channel", function(require, exports, module) {

    var utils = require("cordova/utils"),
        nextGuid = 1;

    // 事件通道的構(gòu)造函數(shù)
    var Channel = function(type, sticky) {
        // 通道名稱
        this.type = type;
        // 通道上的所有事件處理函數(shù)Map(索引為guid)
        this.handlers = {};
        // 通道的狀態(tài)(0:非sticky, 1:sticky但未調(diào)用, 2:sticky已調(diào)用)  
        this.state = sticky ? 1 : 0;
        // 對(duì)于sticky事件通道備份傳給fire()的參數(shù) 
        this.fireArgs = null;
        // 當(dāng)前通道上的事件處理函數(shù)的個(gè)數(shù)
        this.numHandlers = 0;
        // 訂閱第一個(gè)事件或者取消訂閱最后一個(gè)事件時(shí)調(diào)用自定義的處理
        this.onHasSubscribersChange = null;
    },
    // 事件通道外部接口
    channel = {
        // 把指定的函數(shù)h訂閱到c的各個(gè)通道上,保證h在每個(gè)通道的最后被執(zhí)行  
        join: function(h, c) {
            var len = c.length,
                i = len,
                f = function() {
                    if (!(--i)) h();
                };
            for (var j=0; j

我們可以寫一個(gè)測(cè)試用例:


但是很多時(shí)候我們希望能夠傳遞參數(shù),通過(guò)閱讀上面的源碼可以得知:

if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") {
    // 接收到一個(gè)實(shí)現(xiàn)handleEvent接口的EventListener對(duì)象
    handleEvent = eventListenerOrFunction.handleEvent;
   eventListener = eventListenerOrFunction;
} else {
   // 接收到處理事件的回調(diào)函數(shù)
   handleEvent = eventListenerOrFunction;
}

我們上面的例子中我們傳遞的是一個(gè)方法,這里我們也可以傳遞一個(gè)EventListener對(duì)象。

// 創(chuàng)建事件通道
channel.create("onTest");

// 訂閱事件
channel.onTest.subscribe(function (event) {
  console.log(event);
  console.log(event.data.name+" fire");
});

// 創(chuàng)建 Event 對(duì)象
var event = document.createEvent("Events");
// 初始化事件
event.initEvent("onTest", false, false);
// 綁定數(shù)據(jù)
event.data = {name: "test"};
// 觸發(fā)事件
channel.onTest.fire(event);
工具模塊

我們?cè)趯懖寮臅r(shí)候如果熟悉cordova自帶的工具函數(shù),可以更加方便的拓展自己的插件。

define("cordova/utils", function(require, exports, module) {
    var utils = exports;
    // 定義對(duì)象屬性(或方法)的setter/getter
    utils.defineGetterSetter = function(obj, key, getFunc, opt_setFunc) {...}
    // 定義對(duì)象屬性(或方法)的getter
    utils.defineGetter = utils.defineGetterSetter;
    // Array IndexOf 方法
    utils.arrayIndexOf = function(a, item) {...}
    // Array remove 方法
    utils.arrayRemove = function(a, item) {...}
    // 類型判斷
    utils.typeName = function(val) {...}
    // 數(shù)組判斷
    utils.isArray = Array.isArray ||
        function(a) {return utils.typeName(a) == "Array";};
    // Date判斷
    utils.isDate = function(d) {...}
    // 深度拷貝
    utils.clone = function(obj) {...}
    // 函數(shù)包裝調(diào)用
    utils.close = function(context, func, params) {...}
    // 內(nèi)部私有函數(shù),產(chǎn)生隨機(jī)數(shù)
    function UUIDcreatePart(length) {...}
    // 創(chuàng)建 UUID (通用唯一識(shí)別碼)
    utils.createUUID = function() {...}
    // 繼承
    utils.extend = function() {...}
    // 調(diào)試
    utils.alert = function(msg) {...}
});

UUIDcreatePart函數(shù)用來(lái)隨機(jī)產(chǎn)生一個(gè)16進(jìn)制的號(hào)碼,接受一個(gè)表示號(hào)碼長(zhǎng)度的參數(shù)(實(shí)際上是最終號(hào)碼長(zhǎng)度的一半),一般用途是做為元素的唯一ID。

utils.isArray 在這里不使用instanceof來(lái)判斷是不是Array類型,主要是考慮到跨域或者多個(gè)frame的情況,多個(gè)frame時(shí)每個(gè)frame都會(huì)有自己的Array構(gòu)造函數(shù),從而得出不正確的結(jié)論。使用"[object Array]"來(lái)判斷是根據(jù)ECMA標(biāo)準(zhǔn)中的返回值來(lái)進(jìn)行的,事實(shí)上,這里不需要類型轉(zhuǎn)換,而可以用全等“===”來(lái)判斷。

utils.close函數(shù),封裝函數(shù)的調(diào)用,將執(zhí)行環(huán)境作為一個(gè)參數(shù),調(diào)用的函數(shù)為第二個(gè)參數(shù),調(diào)用函數(shù)本身的參數(shù)為后續(xù)參數(shù)。

原型繼承實(shí)現(xiàn)詳解

utils.extend = (function() {
    // proxy used to establish prototype chain
    var F = function() {};
    // extend Child from Parent
    return function(Child, Parent) {
        F.prototype = Parent.prototype;
        Child.prototype = new F();
        Child.__super__ = Parent.prototype;
        Child.prototype.constructor = Child;
    };
}());

這里的繼承是通過(guò)原型鏈的方式實(shí)現(xiàn),我們可以通過(guò)下述方式調(diào)用:

var Parent = function () {
    this.name = "Parent";
}
Parent.prototype.getName = function () {
    return this.name;
}
var Child = function () {
    this.name = "Child";
}

utils.extend(Child, Parent);

var child = new Child();
console.log(child.getName())

ES5中有一個(gè)Object.create方法,我們可以使用這個(gè)函數(shù)實(shí)現(xiàn)繼承:

// 創(chuàng)建一個(gè)新的對(duì)象
Object.create = Object.create || function (proto) {
    var F = function () {};
    F.prototype = proto;
    return new F();
}

// 實(shí)現(xiàn)繼承
var extend = function(Child, Parent) {
    // 拷貝Parent原型對(duì)象
    Child.prototype = Object.create(Parent.prototype);
    // 將Child構(gòu)造函數(shù)賦值給Child的原型對(duì)象
    Child.prototype.constructor = Child;
}

// 實(shí)例
var Parent = function () {
    this.name = "Parent";
}
Parent.prototype.getName = function () {
    return this.name;
}
var Child = function () {
    this.name = "Child";
}
extend(Child, Parent);
var child = new Child();
console.log(child.getName())

原型鏈的概念對(duì)于初學(xué)者而言可能有點(diǎn)繞,但是我們把握構(gòu)造函數(shù)、實(shí)例化對(duì)象、原型對(duì)象三者的關(guān)系就很簡(jiǎn)單了。我們以此為例說(shuō)明:

// 構(gòu)造函數(shù)
var Child = function () {
    this.name = "Child";
}
// 原型對(duì)象Child.prototype
Child.prototype.getName = function () {
    return this.name;
}
// 實(shí)例化對(duì)象
var child = new Child();

原型對(duì)象是構(gòu)造函數(shù)的prototype屬性,是所有實(shí)例化對(duì)象共享屬性和方法的原型對(duì)象。

實(shí)例化對(duì)象通過(guò)new構(gòu)造函數(shù)得到,都繼承了原型對(duì)象的屬性和方法。

如何訪(qiu)問(wèn)(jie)原型對(duì)象?若已知構(gòu)造函數(shù)Child,則可以通過(guò)Child.prototype得到;若已知實(shí)例化對(duì)象child,則可以通過(guò)child.__proto__或者Object.getPrototypeOf(child)得到,也通過(guò)Object.setPrototypeOf方法來(lái)重寫對(duì)象的原型。

Child.prototype === child.__proto__  // true
child.__proto__ === Object.getPrototypeOf(child) // true

原型對(duì)象中有個(gè)隱式的constructor,指向了構(gòu)造函數(shù)本身,也就是我們可以通過(guò)Child.prototype.constructor(雖然看似多此一舉,但是經(jīng)常需要重新設(shè)置構(gòu)造函數(shù))或child.__proto__.constructor或者Object.getPrototypeOf(child).constructor得到構(gòu)造函數(shù)。

instanceof和Object.isPrototypeOf()可以判斷兩個(gè)對(duì)象是否是繼承關(guān)系

child instanceof Child  // true
Child.prototype.isPrototypeOf(child) // true

至此構(gòu)造函數(shù)、實(shí)例化對(duì)象、原型對(duì)象三者的關(guān)系我們已經(jīng)很清除了,再回過(guò)頭看看上面繼承的實(shí)現(xiàn)就很簡(jiǎn)單了。

我們可以通過(guò)instanceof來(lái)檢驗(yàn)是否滿足繼承關(guān)系:

child instanceof Child && child instanceof Parent  // true

其實(shí)上述繼承的思路很簡(jiǎn)單:
1.首先獲得父類原型對(duì)象的方法,這里的F對(duì)象作為中間變量引用拷貝Parent.prototype對(duì)象(即和Parent.prototype共享同一內(nèi)存空間);例如我們修改上述的Object.create為:

Object.create = function (proto) {
    var F = function () {};
    F.prototype = proto;
    F.prototype.setName = function(name){
        this.name = name;
    }
    return new F();
}

此時(shí)Parent.prototype、Child.prototype、child都擁有的setName方法,但是我們應(yīng)當(dāng)避免這樣做,這也是為什么我們不直接通過(guò)Child.prototype = Parent.prototype獲得;通過(guò)實(shí)例化中間對(duì)象F間接得到Parent.prototype的方法,此時(shí)通過(guò)Object.create方法獲得的對(duì)象和Parent.prototype不再是共享內(nèi)存空間。Child通過(guò)extend(Child, Parent)從Parent.prototype對(duì)象獲得一份新的拷貝。實(shí)質(zhì)是因?yàn)槲覀兺ㄟ^(guò)new一個(gè)構(gòu)造函數(shù)獲得的實(shí)例化對(duì)象是獲得了一個(gè)新的內(nèi)存空間,子對(duì)象互不影響;
2.對(duì)子類進(jìn)行修正,我們通過(guò)拷貝獲得了父類的一個(gè)備份,此時(shí)子類原型對(duì)象下的constructor屬性依然是父類的構(gòu)造函數(shù),顯然不符合我們的要求,我們需要重置,同時(shí)有時(shí)候我們希望保留對(duì)父類的引用,如cordova這里用一個(gè)__super__屬性保存。

Child.__super__ = Parent.prototype;
Child.prototype.constructor = Child;

其實(shí)繼承的本質(zhì)我們是希望能實(shí)現(xiàn)以下功能:

父類有的我都有,我也能重載,但不至于影響到父類的屬性和方法

除了繼承之外,我也能添加自己的方法和屬性

我們可以利用es6新特性實(shí)現(xiàn)同樣的效果:

class Parent {
    constructor () {
        this.name = "Parent";
    }
    getName () {
        return this.name;
    }
}

class Child extends Parent {
    constructor () {
        super();
        this.name = "Child";
    }
}

var child = new Child();
console.log(child.getName())

super關(guān)鍵字在這里表示父類的構(gòu)造函數(shù),用來(lái)新建父類的this對(duì)象。在子類的構(gòu)造函數(shù)中,只有調(diào)用super之后,才可以使用this關(guān)鍵字,否則會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇悓?shí)例的構(gòu)建,是基于對(duì)父類實(shí)例加工,只有super方法才能返回父類實(shí)例。

cordova 模塊

本文最后一部分我們來(lái)看看cordova模塊,cordova模塊是事件的處理和回調(diào),外部訪問(wèn)cordova.js的入口。

define("cordova", function(require, exports, module) {
  if (window.cordova && !(window.cordova instanceof HTMLElement)) {
    throw new Error("cordova already defined");
  }
  // 導(dǎo)入事件通道模塊
  var channel = require("cordova/channel");
  // 導(dǎo)入平臺(tái)模塊
  var platform = require("cordova/platform");
  // 保存addEventListener、removeEventListener的原生實(shí)現(xiàn)
  var m_document_addEventListener = document.addEventListener;
  var m_document_removeEventListener = document.removeEventListener;
  var m_window_addEventListener = window.addEventListener;
  var m_window_removeEventListener = window.removeEventListener;

  // 緩存所有的事件處理函數(shù)
  var documentEventHandlers = {},
      windowEventHandlers = {};
  
  // 重新定義addEventListener、removeEventListener,方便后面注冊(cè)添加pause、resume、deviceReady等事件
  document.addEventListener = function(evt, handler, capture) {...}
  window.addEventListener = function(evt, handler, capture) {...}
  document.removeEventListener = function(evt, handler, capture) {...}
  window.removeEventListener = function(evt, handler, capture) {...}
  function createEvent(type, data) {...}

  var cordova = {
    define: define,
    require: require,
    version: PLATFORM_VERSION_BUILD_LABEL,
    platformVersion: PLATFORM_VERSION_BUILD_LABEL,
    platformId: platform.id,
    addWindowEventHandler: function(event) {...},
    addStickyDocumentEventHandler: function(event) {...},
    addDocumentEventHandler: function(event) {...},
    removeWindowEventHandler: function(event) {...},
    removeDocumentEventHandler: function(event) {...},
    getOriginalHandlers: function() {...},
    fireDocumentEvent: function(type, data, bNoDetach) {...},
    fireWindowEvent: function(type, data) {...},
    callbackId: Math.floor(Math.random() * 2000000000),
    callbacks: {},
    callbackStatus: {},
    callbackSuccess: function(callbackId, args) {...},
    callbackError: function(callbackId, args) {...},
    callbackFromNative: function(callbackId, isSuccess, status, args, keepCallback) {...},
    addConstructor: function(func) {...}
  }
  // 暴露cordova對(duì)象給外部
  module.exports = cordova;
});

這里我們以document Event為例說(shuō)明一下cordova模塊中關(guān)于事件的處理:

// 保存addEventListener、removeEventListener的原生實(shí)現(xiàn)
var m_document_addEventListener = document.addEventListener;
var m_document_removeEventListener = document.removeEventListener;
// 緩存事件處理函數(shù)
var documentEventHandlers = {};
// 重新定義addEventListener
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);
    }
};
// 重新定義removeEventListener
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);
    }
};
// 創(chuàng)建 Event 對(duì)象
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;
}
var codova = {
    ...
    // 創(chuàng)建事件通道
    addStickyDocumentEventHandler:function(event) {
        return (documentEventHandlers[event] = channel.createSticky(event));
    },
    addDocumentEventHandler:function(event) {
        return (documentEventHandlers[event] = channel.create(event));
    },
    // 取消事件通道
    removeDocumentEventHandler:function(event) {
        delete documentEventHandlers[event];
    },
    // 發(fā)布事件消息
    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);
        }
    },
    ...
}
module.exports = cordova;

在初始化啟動(dòng)模塊cordova/init中有這樣的代碼:

// 注冊(cè)pause、resume、deviceReady事件
channel.onPause = cordova.addDocumentEventHandler("pause");
channel.onResume = cordova.addDocumentEventHandler("resume");
channel.onActivated = cordova.addDocumentEventHandler("activated");
channel.onDeviceReady = cordova.addStickyDocumentEventHandler("deviceready");

// 監(jiān)聽(tīng)DOMContentLoaded事件并發(fā)布事件消息
if (document.readyState == "complete" || document.readyState == "interactive") {
    channel.onDOMContentLoaded.fire();
} else {
    document.addEventListener("DOMContentLoaded", function() {
        channel.onDOMContentLoaded.fire();
    }, false);
}

// 原生層加載完成事件
if (window._nativeReady) {
    channel.onNativeReady.fire();
}

// 加載完成發(fā)布時(shí)間事件消息
channel.join(function() {
    modulemapper.mapModules(window);
    platform.initialize && platform.initialize();
    channel.onCordovaReady.fire();
    channel.join(function() {
        require("cordova").fireDocumentEvent("deviceready");
    }, channel.deviceReadyChannelsArray);
}, platformInitChannelsArray);

這里通過(guò)addDocumentEventHandler及addStickyDocumentEventHandler創(chuàng)建了事件通道,并通過(guò)fireDocumentEvent或者fire發(fā)布事件消息,這樣我們就可以通過(guò)document.addEventListener訂閱監(jiān)聽(tīng)事件了。

如果我們要?jiǎng)?chuàng)建一個(gè)自定義事件Test,我們可以這樣做:

// 創(chuàng)建事件通道
cordova.addWindowEventHandler("Test");

// 發(fā)布事件消息
cordova.fireWindowEvent("Test", {
    name: "test",
    data: {
        time: new Date()
    }
})

// 訂閱事件消息
window.addEventListener("Test", function (evt) {
     console.log(evt);
});
參考

Cordova 3.x 入門 - 目錄
PhoneGap源碼分析

寫在后面

本文至此已經(jīng)說(shuō)完了cordova的模塊機(jī)制和事件機(jī)制,已經(jīng)cordova的工具模塊,了解這些后寫起插件來(lái)才能得心應(yīng)手,對(duì)于原理實(shí)現(xiàn)部分不屬于本文的范疇,下一篇會(huì)詳細(xì)講解cordova原理實(shí)現(xiàn)。敬請(qǐng)關(guān)注,不過(guò)近來(lái)在寫畢設(shè),估計(jì)一時(shí)半會(huì)兒也不會(huì)寫完,本文前前后后已是拖了半個(gè)月。如果覺(jué)得本文對(duì)您有幫助,不妨打賞支持以此鼓勵(lì)。

轉(zhuǎn)載需標(biāo)注本文原始地址:https://zhaomenghuan.github.io/

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/82904.html

相關(guān)文章

  • cordova研習(xí)筆記(一) —— 初試牛刀之cordova.js概要

    摘要:任何初始化任務(wù)應(yīng)該在文件中的事件的事件處理函數(shù)中。這個(gè)配置文件有幾個(gè)地方很關(guān)鍵,一開(kāi)始沒(méi)有認(rèn)真看,將插件導(dǎo)進(jìn)工程跑的時(shí)候各種問(wèn)題,十分頭痛,不得不重新認(rèn)真看看文檔。 前言 來(lái)新公司的第一個(gè)任務(wù),研究hybrid App中間層實(shí)現(xiàn)原理,做中間層插件開(kāi)發(fā)。這個(gè)任務(wù)挺有意思,也很有挑戰(zhàn)性,之前在DCloud雖然做過(guò)5+ App開(kāi)發(fā),但是中間層的東西確實(shí)涉及不多。本系列文章屬于系列開(kāi)篇cord...

    buildupchao 評(píng)論0 收藏0
  • [筆記]React+Cordova踩坑

    摘要:之前做過(guò)一點(diǎn)前端的小項(xiàng)目所以前端還算熟練因?yàn)樽罱跍?zhǔn)備所以想能不能寫一個(gè)背單詞軟件正好這學(xué)期有個(gè)開(kāi)發(fā)課,就用來(lái)當(dāng)大作業(yè)了前端后端如何在下調(diào)試當(dāng)然是代理啦在之前兩個(gè)項(xiàng)目中為了不用代理強(qiáng)行在后端啟用了事實(shí)證明這是個(gè)愚蠢的決定因?yàn)橥耆贿m合做后端 之前做過(guò)一點(diǎn)前端的小項(xiàng)目所以前端還算熟練因?yàn)樽罱跍?zhǔn)備GRE所以想能不能寫一個(gè)背單詞軟件正好這學(xué)期有個(gè)Android開(kāi)發(fā)課,就用來(lái)當(dāng)大作業(yè)了 前端...

    shadajin 評(píng)論0 收藏0
  • ReactJS新聞 #22 Next.js發(fā)布2.0

    摘要:第期新聞發(fā)布是熱門的通用服務(wù)器端的延伸框架,近日發(fā)布了版本。官方博客相關(guān)報(bào)導(dǎo)官網(wǎng)全新改版工具的官網(wǎng)近日全新改版上線,文檔與首頁(yè)都有全新的版面與改善。官方網(wǎng)站研習(xí)會(huì)報(bào)導(dǎo)是在月底,于歐洲的社群的一個(gè)研習(xí)會(huì)活動(dòng)。 第022期 (2017.04.02) 新聞 Next.js發(fā)布2.0 Next.js是熱門的通用(服務(wù)器端)的React延伸框架,近日發(fā)布了2.0版本。2.0的目標(biāo)有三個(gè),是針對(duì)...

    or0fun 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<