淘宝Kissy框架分析【二】
2010-06-10 22:25 BlueDream 阅读(6743) 评论(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 (!s || !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 (!s || !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, this, true, ['_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 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实例传入到回调函数中.
测试用例:
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函数给修正了.
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;
}
},
测试用例:
<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个对象.进行的是浅拷贝. 一般用于设置默认参数时候使用:
测试用例:
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 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
})();