MooTools最重要的兩個核心模塊,一個是Type,另一個就是Class,Type的源碼分析已經有棍子上的蘿蔔分析了1.3版本的了,Class的源碼分析網上只有1.2版本的,在1.3版本已經有了大的改變,現在把1.4版的Class嘗試分析下,如理解有誤歡迎指正:
- /*
- ---
- name: Class
- description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
- license: MIT-style license.
- requires: [Array, String, Function, Number]
- provides: Class
- 源碼分析: 苦苦的苦瓜(http://hmking.blog.51cto.com)
- ...
- */
- (function () {
- // #region Class
- var Class = this.Class = new Type('Class', function (params) {
- // 如果參數是一個函數,當作構造函數處理,自動變爲一個對象字面量,例如:
- // var Barn = new Class(function(name){ this.fowl = name; });
- if (instanceOf(params, Function)) {
- params = { initialize: params };
- }
- // 先新建一個函數作爲新建的類的原型
- // 然後調用extend函數把Class所有的特性複製給newClass
- // 然後params對象implement到newClass類中,這裏調用的implement是Class的implement方法
- var newClass = function () {
- // 複製前先解除關聯,爲什麼要剝離?因爲原型繼承,包含引用類型的原型屬性會被所有實例共享啊......
- reset(this);
- // 判斷是否處於類的設計階段
- if (newClass.$prototyping) { return this; }
- this.$caller = null;
- // 類的實例運行階段調用類本身的構造函數,將參數原樣傳遞
- var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
- this.$caller = this.caller = null;
- return value;
- } .extend(this).implement(params);
- // 將newClass的構造函數設爲Class
- newClass.$constructor = Class;
- // 指定newClass的原型的$constructor,使之可以正確的instanceOf
- newClass.prototype.$constructor = newClass;
- // this.parent可以訪問父類的被覆蓋的方法
- newClass.prototype.parent = parent;
- // 返回了這個被包裝過的類
- return newClass;
- });
- // this.parent可以訪問父類的被覆蓋的方法
- var parent = function () {
- if (!this.$caller) {
- throw new Error('The method "parent" cannot be called.');
- }
- // 通過$name屬性取得類方法名稱
- var name = this.$caller.$name,
- // 通過$owner屬性得到類對象(不是類的實例),調用類的靜態屬性parent的到父類對象
- parent = this.$caller.$owner.parent,
- // 取得父類原型中的同名方法
- previous = (parent) ? parent.prototype[name] : null;
- // 如果父類中不存在同名的方法,它就拋出一個錯誤
- if (!previous) {
- throw new Error('The method "' + name + '" has no parent.');
- }
- // 調用父類中的方法
- return previous.apply(this, arguments);
- };
- /**
- * 對象的剝離(也就是clone),這裏要詳細說明一下reset函數的工作原理:
- * 首先創建了一個新的空函數F,然後將F的prototype屬性設置爲作爲參數object傳入的原型對象,prototype屬性就是用來指向原型對象的,通過原型鏈機制,
- * 它提供了到所有繼承而來的成員的鏈接,最後通過new運算符作用於F創建出一個新對象返回。這個新的對象就是一個以給定對象爲原型對象的空對象,
- * 以下面的例子來解說,先執行reset(b)語句,然後讀取b.ref.x的值,這時你得到的是其原型對象的同名屬性值,其實是一個返指最初的a.x的鏈接,
- * 而在這之後你寫入b.ref.x一個新值,也就是直接爲b.ref對象定義了一個新的屬性x,這時你再讀取b.ref.x就不是指向a.x了
- * 如果想詳細瞭解原型式繼承可翻閱JavaScript設計模式一書,非常棒的一本書,真的很棒!!!哈哈......
- * var a = { x: 1 };
- * var b = { y: 2, ref: a };
- * log.info('b.ref == a : ' + (b.ref == a)); //輸出true
- * log.info(b.y); // 輸出2
- * log.info(b.ref.x); // 輸出1
- * reset(b); //解除引用
- * log.info('b.ref == a : ' + (b.ref == a)); //輸出false
- * log.info(b.y); // 輸出2
- * log.info(b.ref.x); // 輸出1
- * b.ref.x = 10;
- * log.info(b.ref.x); // 輸出10
- * log.info(a.x); // 輸出1
- **/
- var reset = function (object) {
- for (var key in object) {
- var value = object[key];
- switch (typeOf(value)) {
- case 'object':
- var F = function () { };
- F.prototype = value;
- object[key] = reset(new F);
- break;
- case 'array':
- object[key] = value.clone();
- break;
- }
- }
- return object;
- };
- /**
- * @function: wrap
- * @description: 將一個方法用wrapper函數重新包裝,添加下面幾個靜態屬性
- * @$owner - 類本身
- * @$origin - 指向未被包裝的函數
- * @$name - 類的方法名稱
- * @returns: (funciton) 包裝過後的函數
- **/
- var wrap = function (self, key, method) {
- // 如果函數已被父類包裝過,則調用最初未被包裝的函數(函數的原始形態,呵呵)
- if (method.$origin) {
- method = method.$origin;
- }
- var wrapper = function () {
- // 如果方法設置了$protected屬性,說明此方法不希望在類外面被調用,也就是類的實例不能調用類中設置了$protected屬性的方法,只可遠觀而不可褻玩也,呵呵......
- // 但是如果一個類繼承另一個類,同時在子類中覆蓋了父類中設置了$protected屬性的方法,在子類的實例中調用此方法就不會彈出錯誤警告了。
- // 當然如果子類的同名方法同樣設置了$protected屬性,那麼子類的實例同樣不能調用此方法。
- if (method.$protected && this.$caller == null) {
- throw new Error('The method "' + key + '" cannot be called.');
- }
- // 緩存類實例的caller和$caller兩個屬性
- var caller = this.caller,
- current = this.$caller;
- this.caller = current;
- // 將類實例的$caller屬性指向調用的方法本身,Function的caller屬性在Class中的完美模擬,這樣parent函數纔可以運行啊,呵呵......
- this.$caller = wrapper;
- // 掛爲原型上的方法執行
- var result = method.apply(this, arguments);
- // 方法執行完畢將類實例caller和$caller兩個屬性值還原
- this.$caller = current;
- this.caller = caller;
- return result;
- } .extend({ $owner: self, $origin: method, $name: key });
- return wrapper;
- };
- // 又見implement函數,第三次了,呵呵,這裏是擴展類的方法屬性
- var implement = function (key, value, retain) {
- // 首先檢查類的的每一個屬性和方法在Class.Mutators對象的是不是有mutator函數的對應的名字在裏面。
- // 如果找到了,它就調用這個函數並且把鍵的值傳給它做處理。
- if (Class.Mutators.hasOwnProperty(key)) {
- value = Class.Mutators[key].call(this, value);
- // 判斷mutator函數有沒有返回值,如果沒有則退出。
- if (value == null) { return this; }
- }
- if (typeOf(value) == 'function') {
- // $hidden屬性表明此函數無法被其他對象implement
- if (value.$hidden) { return this; }
- // Implements mutator調用本函數時retain參數設爲ture,表明只是合併方法到原型中
- // 而在創建類時(前面建立newclass)retain參數沒有賦值,執行wrap函數包裝方法到原型
- this.prototype[key] = (retain) ? value : wrap(this, key, value);
- } else {
- // 合併屬性到原型
- Object.merge(this.prototype, key, value);
- }
- return this;
- };
- /**
- * @functoin: getInstance
- * @param klass - (class) 要繼承的類
- * @description: 得到父類的一個實例
- **/
- var getInstance = function (klass) {
- // 設置標記,說明Class在設計階段,不會執行構造函數
- klass.$prototyping = true;
- var proto = new klass;
- // 刪除標記
- delete klass.$prototyping;
- return proto;
- };
- // 暴露implement方法
- Class.implement('implement', implement.overloadSetter());
- // #endregion Class
- // #region Mutators
- /**
- * 好了,接下來着重介紹一下Class.Mutators對象:
- *
- * Mutator是一個可以改變你的類的結構的一個很特殊的函數,它們是產生特別功能和優雅化繼承和摻元的的有力工具。
- *
- * 建立一個Mutatorr有二個部分:mutator的關鍵字 和mutator的實際函數,關鍵字既是mutator的名字,
- * 也是在構建類時候的keyword。Mootools把mutators 儲存在Class.Mutators對象中。
- *
- * 當你傳一個對象給Class構造函數的時候,Mootools檢查這個對象的的每一個鍵在Class.Mutators對象的是不是有
- * mutator函數的對應的名字在裏面。如果找到了,它就調用這個函數並且把鍵的值傳給它做處理。
- *
- * Class.Mutators對象包含了兩個內建的Mutator: Extends 和 Implements,分別實現原型式繼承和多親繼承。
- *
- * MooTools在Class.Extras模塊中提供了三個摻元類Chain、Events、Options,至於作用就不用多說了吧,呵呵。
- **/
- Class.Mutators = {
- // 取得傳送給它的class的名字後,直接繼承這個class
- Extends: function (parent) {
- // 靜態屬性,存儲父類對象
- this.parent = parent;
- // 原型式繼承
- this.prototype = getInstance(parent);
- },
- // Implements mutator取得傳送給它的class的名字後,把它們的方法和屬性添加到新類。
- // 利用摻元類實現多親繼承
- Implements: function (items) {
- Array.from(items).each(function (item) {
- var instance = new item;
- for (var key in instance) {
- implement.call(this, key, instance[key], true);
- }
- }, this);
- }
- };
- // #endregion
- })();