代码改变世界

淘宝Kissy框架分析【二】

2010-06-10 22:25  BlueDream  阅读(6733)  评论(0编辑  收藏  举报

首先,让我们从kissy核心文件夹开始. 第一个文件kissy.js也是主架构文件.

源码如下:

/**
 * @module j1616
 * @author liangchaoyjs@163.com
 
*/
 (
function(win, J, undefined) {
     
if (win[J] === undefined) win[J] = {};
     J 
= win[J];

     
var doc = win.document,

         mix 
= function(r, s, ov, wl) {
            
if (!|| !r) return r;
            
if (ov === undefined) ov = true;
            
var i, p, l;

            
if (wl && (l = wl.length)) {
                
for (i = 0; i < l; i++) {
                    p 
= wl[i];
                    
if (p in s) {
                        
if (ov || !(p in r)) {
                            r[p] 
= s[p];
                        } 
                    } 
                }
            } 
else {
                
for (p in s) {
                    
if (ov || !(p in r)) {
                        r[p] 
= s[p];
                    } 
                }
            }
            
return r;
        
        },
        
        isReady 
= false,

        readyList 
= [],
        
        readyBound 
= false;

    mix(J, {
        version: 
'@VERSION@',
        
        _init: 
function() {
            
this.Env = { mods: {} };
        },

        add: 
function(name, fn) {
            
var self = this;

            self.Env.mods[name] 
= {
                name: name,
                fn: fn
            }

            fn(self);
            
return self;
        },

        ready: 
function(fn) {
            
if (!readyBound) this._bindReady(); 
            
            
if (isReady) {
                fn.call(win, 
this);
            } 
else {
                readyList.push(fn);
            }

            
return this;
        },

        _bindReady: 
function() {
            
var self = this,
                doScroll 
= doc.documentElement.doScroll,
                eventType 
= doScroll ? 'onreadystatechange' : 'DOMContentLoaded',
                COMPLETE 
= 'complete';

            readyBound 
= true;

            
if (doc.readyState === COMPLETE) {
                
return self._fireReady();
            } 

            
//w3c mode
            if (doc.addEventListener) {
                
function domReady() {
                    doc.removeEventListener(eventType, domReady, 
false);
                    self._fireReady();
                }
                doc.addEventListener(eventType, domReady, 
false);
            }
            
else {
                
// IE mode
                if (win != win.top) { // iframe
                    function stateChange() {
                        
if (doc.readyState === COMPLETE) {
                            doc.detachEvent(eventType, stateChange);
                            self._fireReady();
                        } 
                    }
                    doc.attachEvent(eventType, stateChange);
                } 
                
else {
                    
function readyScroll() {
                        
try {
                            doScroll(
'left');
                            self._fireReady();
                        } 
catch(e) {
                            setTimeout(readyScroll, 
1);
                        }
                    }
                    readyScroll();
                }

                win.attachEvent(
'onload'function() {
                    self._fireReady()
                });
            }
        },
        
        _fireReady: 
function() {
            
if (isReady) return;
            
            isReady 
= true;
            
if (readyList) {
                
var fn, i = 0;
                
while (fn = readyList[i++]) {
                    fn.call(win, 
this);
                }
                
                readyList 
= null;
            } 
        },

        mix: mix,

        merge: 
function() {
            
var o = {}, i, l = arguments.length;
            
for (i = 0; i < l; ++i) {
                mix(o, arguments[i]);        
            }
            
return o;
        },

        augment: 
function(r, s, ov, wl) {
            mix(r.prototype, s.prototype 
|| s, ov, wl);
            
return r;
        },

        extend: 
function(r, s, px, sx) {
            
if (!|| !r) return r;
            
            
var OP = Object.prototype,
                O 
= function(o) {
                        
function F() {}
                        F.prototype 
= o;
                        
return new F();
                    },
                sp 
= s.prototype,
                rp 
= O(sp);

            r.prototype 
= rp;
            rp.constructor 
= r;
            r.superclass 
= sp;

            
if (s !== Object && sp.constructor === OP.constructor) {
                sp.constructor 
= s;
            } 

            
if (px) {
                mix(rp, px);
            } 

            
if (sx) {
                mix(r, sx);
            } 

            
return r;
        },

        namespace: 
function() {
            
var l = arguments.length, o = null, i, j, p;

            
for (i = 0; i < l; ++i) {
                p 
= ('' + arguments[i]).split('.');
                [
'app''Shop']
                o 
= this;
                
for (j = (win[p[0]] === o) ? 1 : 0; j < p.length; ++j) {
                    o 
= o[p[j]] = o[p[j]] || {};
                }
            }
            
return o;
        },

        app: 
function(name, sx) {
            
var O = win[name] || {};
            
            mix(O, 
thistrue, ['_init''add''namespace']);

            O._init();

            
return mix((win[name] = O), typeof sx === 'function' ? sx() : sx);
        },

        log: 
function(msg, cat, src) {
            
if (this.Config.debug) {
                
if (src) {
                    msg 
= src + '' + msg;
                } 
                
if (win['console'!== undefined && console.log) {
                    console[cat 
&& console[cat] ? cat : 'log'](msg);
                } 
            } 
            
return this;
        },

        error: 
function(msg) {
            
if (this.Config.debug) {
                
throw msg;
            } 
        }
    
    });

    J._init();
        
    J.Config 
= { debug: '@DEBUG@' };
        
 })(window, 
'J1616');




首先整个函数通过简单的闭包机制实现了沙箱.然后将win[J]暴露给全局.所以我们就可以J1616.xx引用属性了.

1. mix函数

作用:将s的属性拷贝给r. ov(默认为true)为true则属性覆盖,为false则不覆盖. wl如果定义了.那么只有当s中含有wl定义的属性才会进行属性拷贝.

测试用例:

var r = {'version''beta0.0.1'};
var s = {
    version: 
'beta0.0.2',
    _init: 
function() {
        alert(
'init');
    },
    _login: 
function() {
        alert(
'login');
    },
    _logout: 
function() {
        alert(
'logout');
    }
};

var J = J1616;
//////////////////////////////
//
J.mix(r, s);
//
alert(r.version);  // beta0.0.2
//
r._logout();       // logout
//
///////////////////////////
//
J.mix(r, s, false); 
//
alert(r.version);  // beta 0.0.1
//
///////////////////////////
//
J.mix(r, s, true, ['_init', '_login']);
//
r._init();   // init
//
r._login();  // login
//
r._logout(); // error
//
///////////////////////////

紧接着便用mix(J, {'_init':xx}); 实现了将属性赋予J上 其实就 等价于var J = {'_init':xxx};

2._init和add函数 

作用: 

当j1616.js一加载的时候便会执行_init函数. 这个函数给J维护了一个Env对象. 目前没太大作用.但应该是为了以后扩展接口方便.

add函数. 是注册一个模块. 并将J1616实例传入到回调函数中.

测试用例: 

(function() {
    
var J = J1616;
    J.add(
'module-name'function(J) {
        alert(J.version); 
// 可以获取实例
        J.DOM = {
            info: 
function() {
                alert(
'注册了一个DOM模块');
            }
        };
    });
    
// 使用定义的module
    J.DOM.info();
})();

3. ready函数

作用: 替代onload函数. onload函数需要图片等资源完全加载完毕才调用. 而ready在document构建完毕就开始执行.要更快一些. 

原理:

两个标志: (1) isReady记录document是不是已经ready完毕.  (2)readyBound记录是不是已经绑定了ready函数.

一个数组: readyList用来存储排队的需ready执行的函数.

整个流程就是:

如果没有绑定ready 就开始绑定domReady函数. 如果已经绑定并且isReady为true 即文档已经ready完毕.那么就直接执行. 否则就放入readyList列表中存储. 一旦ready完毕 就循环列表,依次执行存储在数组中的函数.

监听domReady的方法: 

w3c 使用DOMContentLoaded IE下如果是在iframe中使用onreadystatechange, 非iframe中使用doScroll('left')去检测. 最后使用onload函数兜底.防止onreadystatechange和doScroll晚于onload执行. 由于fireReady中有if (isReady) return;所以保证了函数只会运行一次. 

注意: Kissy源码有些问题,在IE下.如果没有图片等资源.页面很快就加载完毕. load函数要早于ready函数.... 所以这里我将ready函数给修正了.

_bindReady: function() {
    
var self = this, DOMContentLoaded = null;
        doScroll 
= doc.documentElement.doScroll,
        eventType 
= doScroll ? 'onreadystatechange' : 'DOMContentLoaded',
        COMPLETE 
= 'complete';
    
    readyBound 
= true;

    
if (doc.readyState === COMPLETE) {
        
return self._fireReady();
    } 

    
//w3c model
    if (doc.addEventListener) {
        DOMContentedLoaded 
= function() {
            doc.removeEventListener(eventType, DOMContentedLoaded, 
false);
            self._fireReady();
        }
        doc.addEventListener(eventType, DOMContentedLoaded, 
false);
    }
    
// IE model
    else {
        DOMContentedLoaded 
= function() {
            
if (doc.readyState === COMPLETE) {
                    doc.detachEvent(eventType, DOMContentedLoaded);
                    self._fireReady();
            }                 
        }
        
// 主要是这句 onreadystatechange会确保在onload之前执行.而kissy只用在iframe分支里
        // 所以会出现onload要早于domready执行
        doc.attachEvent(eventType, DOMContentedLoaded);
        win.attachEvent(
'onload'function() {
            self._fireReady()
        });

        
var toplevel = false;

        
try {
            toplevel 
= win.frameElement == null;
        } 
catch(e) {}
        
        
if (doScroll && toplevel) { 
            
function readyScroll() {
                
try {
                    doScroll(
'left');
                } 
catch(e) {
                    setTimeout(readyScroll, 
1);
                    
return;
                }
                self._fireReady();
            }
            readyScroll();
        }
    }
},

_fireReady: 
function() {
    
if (isReady) return;

    isReady 
= true;
    
if (readyList) {
        
var fn, i = 0;
        
while (fn = readyList[i++]) {
            fn.call(win, 
this);
        }
        
        readyList 
= null;
    } 
},

测试用例:

<body>
<img id="inline-image" src="http://veimages.gsfc.nasa.gov/2429/globe_west_2048.jpg"/>
<script>
(
function() {
    
var J = J1616;
    J.ready(
function(J) {
        alert(
'domReady: ' + J.version);
    });
    window.onload 
= function() {
        alert(
'onload: ' + J.version);
    }
})();
</script>
</body>

4.merge函数 

作用: 将多个对象合并为1个对象.进行的是浅拷贝. 一般用于设置默认参数时候使用:

测试用例:

(function() {
    J1616.ready(
function(J) {
        
var source = {
            version: 
'beta0.0.1',
            name: 
'kissy'
        };

        
var property = {
            version: 
'beta0.0.2'
        };

        
var prototype = {
            name: 
'j1616'
        }
        
        
var config = J.merge(source, property, prototype);

        alert([config.version, config.name]); 
// version: 'beta0.0.2' name: 'j1616'
    });
})();

5.augment函数

作用: 将一个函数的原型和另一个函数的原型或者一个对象合并. 然后也有ov和wl属性, 相当于改进版的mix函数.

测试用例:

(function() {
    
function Base() {};
    Base.prototype 
= {
        name: 
'kissy',
        show: 
function() {
            alert(
this.name);
        }
    };

    
var Base2 = {
        name: 
'j1616',
    };

    
function Child() {};
    Child.prototype 
= {
        version: 
'beta0.0.1',
        show: 
function() {
            alert(
this.version);
        }
    };
    
// 1.用Base的prototype覆盖Child的prototype. 覆盖的函数列表为show
    // 2.用Base2的属性去覆盖Child的prototype.
    //J1616.augment(Child, Base, true, ['show']);
    //J1616.augment(Child, Base2);
    //new Child().show(); // j1616

    
// 1.将Base的prototype和Child的prototype.属性合并但不覆盖
    // 2.用Base2的属性去覆盖Child的prototype.
    //J1616.augment(Child, Base, false);
    //J1616.augment(Child, Base2);
    //new Child().show(); // beta0.0.1
})();