精华帖 (3) :: 良好帖 (11) :: 新手帖 (1) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-02-20
对于函数这一级的分析,上流程图或者时序图更清晰一点。
继续,加油。 |
|
返回顶楼 | |
发表时间:2012-02-20
补充一下: extend : function() { // Object的构造函数 var oc = Object.prototype.constructor;//(私有对象,仅下面的return function内部可以使用) // override函数,用来覆盖prototype上的属性的(私有方法,仅下面的return function内部可以使用) var io = function( o ) { for ( var m in o ) { this[m] = o[m]; } }; return function( sb , sp , overrides ) { //......... return sb; } }()
|
|
返回顶楼 | |
发表时间:2012-02-20
adamed 写道
补充一下: extend : function() { // Object的构造函数 var oc = Object.prototype.constructor;//(私有对象,仅下面的return function内部可以使用) // override函数,用来覆盖prototype上的属性的(私有方法,仅下面的return function内部可以使用) var io = function( o ) { for ( var m in o ) { this[m] = o[m]; } }; return function( sb , sp , overrides ) { //......... return sb; } }()
已追加~ |
|
返回顶楼 | |
发表时间:2012-02-20
目测要火,果断留名。
不错 |
|
返回顶楼 | |
发表时间:2012-02-20
牛逼,是不是要想真正精通就必须去看一遍源码呢?那jquery有没有看一遍的必要?
|
|
返回顶楼 | |
发表时间:2012-02-21
sniper114713 写道
牛逼,是不是要想真正精通就必须去看一遍源码呢?那jquery有没有看一遍的必要?
我觉得要是有时间的话,可以看看源代码。 |
|
返回顶楼 | |
发表时间:2012-02-21
最后修改:2012-02-21
接着分析ext-base文件,这个文件稍长,所以根据模块分成多个帖子。
接下来的代码主要处理:
一、IE的事件模型和W3C的事件模型之间的接口不同,就是兼容一下各大浏览器,让接口统一。
二、实现一个Dom加载完成的事件,这个事件触发要在window的onload前面。现在的浏览器都有DomContentLoaded事件,早期应该是没有这个事件。
下面的代码是用了setInterval来反复判断document,document.body,el.nextSibling这些变量,如果这些变量有值了,就表示Dom已经加载好了。
再阅读下面代码,主要有三个核心数据结构一个是listeners和unloadListeners,一个是onAvailStack。这块代码的入口函数是_tryPreloadAttach(),建议从这个函数开始阅读。 onAvailStack的意义是保存要执行的函数队列,实现了类似jQuery的ready事件。
Ext.lib.Event = function() { // loadComplete指的是window的onload事件是否触发,触发后就会执行_load函数,loadComplete就变成了true。 var loadComplete = false; // listeners这个变量用来存放你注册事件时的参数,因为你删除事件的时候,也需要这些参数。 var listeners = []; // unloadListeners这个变量中存放的事件句柄,在window的onunload事件触发时会执行 var unloadListeners = []; // 重试的次数,_tryPreloadAttach函数中会使用它,每执行一次_tryPreloadAttach函数,这个值就减一,这个值的初始值是POLL_RETRYS var retryCount = 0; // 这个数组中存放的是当Dom加载完成时,要执行的一些函数 var onAvailStack = []; // 这个变量有可能是在别的地方使用,这个文件中是没有使用过。也有可能是多写了。 var counter = 0; // 这个变量的意义不大,在getTime中使用了一次。 var lastError = null; return { POLL_RETRYS: 200, POLL_INTERVAL: 20, EL: 0, TYPE: 1, FN: 2, WFN: 3, OBJ: 3, ADJ_SCOPE: 4, _interval: null, // 定时函数,用来执行_tryPreloadAttach startInterval: function() { if (!this._interval) { var self = this; var callback = function() { self._tryPreloadAttach(); }; this._interval = setInterval(callback, this.POLL_INTERVAL); } }, // 当id为p_id的元素加载好了,就执行p_fn函数,p_obj是p_fn的第一个参数,如果p_override没有,则p_fn函数的this指向p_id元素 // 如果p_override为true,则this指向p_obj,其他则this指向p_override onAvailable: function(p_id, p_fn, p_obj, p_override) { onAvailStack.push({ id: p_id, fn: p_fn, obj: p_obj, override: p_override, checkReady: false }); retryCount = this.POLL_RETRYS; this.startInterval(); }, // 注册事件函数,返回是否注册成功 addListener: function(el, eventName, fn) { el = Ext.getDom(el); // 参数检查 if (!el || !fn) { return false; } // 如果是unload事件,那么直接添加在unloadListeners数组中,当window的onunload事件触发时执行 if ("unload" == eventName) { unloadListeners[unloadListeners.length] = [el, eventName, fn]; return true; } // 封装函数,处理event变量不兼容的问题 var wrappedFn = function(e) { return typeof Ext != 'undefined' ? fn(Ext.lib.Event.getEvent(e)) : false; }; var li = [el, eventName, fn, wrappedFn]; var index = listeners.length; listeners[index] = li; // 下面语句就直接注册事件了 this.doAdd(el, eventName, wrappedFn, false); return true; }, // 删除注册事件 removeListener: function(el, eventName, fn) { var i, len; el = Ext.getDom(el); if(!fn) { // 如果没有传入fn变量,那么直接删除el元素所有eventName事件的注册函数 return this.purgeElement(el, false, eventName); } // 如果是unload事件,那么直接从unloadListeners中清除掉就可以了 if ("unload" == eventName) { for (i = 0,len = unloadListeners.length; i < len; i++) { var li = unloadListeners[i]; if (li && li[0] == el && li[1] == eventName && li[2] == fn) { unloadListeners.splice(i, 1); return true; } } return false; } // 下面的代码就是从listeners中找出,要删除的事件函数,然后清除之 var cacheItem = null; var index = arguments[3]; if ("undefined" == typeof index) { index = this._getCacheIndex(el, eventName, fn); } if (index >= 0) { cacheItem = listeners[index]; } if (!el || !cacheItem) { return false; } // 这个函数调用removeLisenre函数 this.doRemove(el, eventName, cacheItem[this.WFN], false); delete listeners[index][this.WFN]; delete listeners[index][this.FN]; listeners.splice(index, 1); return true; }, // 取得触发事件的dom元素 getTarget: function(ev, resolveTextNode) { // 这个browserEvent不知道怎么设置的,应该不是W3C的标准,貌似也不属于IE系列的 ev = ev.browserEvent || ev; var t = ev.target || ev.srcElement; return this.resolveTextNode(t); }, // 取得文本节点 resolveTextNode: function(node) { if (Ext.isWebKit && node && 3 == node.nodeType) { return node.parentNode; } else { return node; } }, // getPageX就是取得当前鼠标相对文档的横坐标位置 getPageX: function(ev) { ev = ev.browserEvent || ev; var x = ev.pageX; if (!x && 0 !== x) { x = ev.clientX || 0; if (Ext.isIE) { x += this.getScroll()[1]; } } return x; }, // getPageY就是取得当前鼠标相对于文档的纵坐标位置 getPageY: function(ev) { ev = ev.browserEvent || ev; var y = ev.pageY; if (!y && 0 !== y) { y = ev.clientY || 0; if (Ext.isIE) { y += this.getScroll()[0]; } } return y; }, // 取得横纵坐标位置 getXY: function(ev) { ev = ev.browserEvent || ev; return [this.getPageX(ev), this.getPageY(ev)]; }, // 取得相关的的事件源,针对mouseout和mouseover两个事件 getRelatedTarget: function(ev) { ev = ev.browserEvent || ev; var t = ev.relatedTarget; if (!t) { if (ev.type == "mouseout") { t = ev.toElement; } else if (ev.type == "mouseover") { t = ev.fromElement; } } return this.resolveTextNode(t); }, // 取得当前执行时间 getTime: function(ev) { ev = ev.browserEvent || ev; if (!ev.time) { var t = new Date().getTime(); try { ev.time = t; } catch(ex) { this.lastError = ex; return t; } } return ev.time; }, // 停止事件传播,并且阻止默认行为 stopEvent: function(ev) { this.stopPropagation(ev); this.preventDefault(ev); }, // 停止传播 stopPropagation: function(ev) { ev = ev.browserEvent || ev; if (ev.stopPropagation) { ev.stopPropagation(); } else { ev.cancelBubble = true; } }, // 阻止默认行为 preventDefault: function(ev) { ev = ev.browserEvent || ev; if(ev.preventDefault) { ev.preventDefault(); } else { ev.returnValue = false; } }, // 取得event变量,IE是window.event,W3C是函数的第一参数 getEvent: function(e) { // 实际上下面这句代码就可以正确取得event对象了,因为wrappedFn中传入了参数e var ev = e || window.event; // 下面这段代码是,当ev没有取到值,那么就找父函数的第一个参数,如果第一个参数不是Event类型,那么接着向上找父函数 if (!ev) { var c = this.getEvent.caller; while (c) { ev = c.arguments[0]; if (ev && Event == ev.constructor) { break; } c = c.caller; } } return ev; }, // 取得键盘代码 getCharCode: function(ev) { ev = ev.browserEvent || ev; return ev.charCode || ev.keyCode || 0; }, // 从listeners中找出注册时间函数的索引 _getCacheIndex: function(el, eventName, fn) { for (var i = 0,len = listeners.length; i < len; ++i) { var li = listeners[i]; if (li && li[this.FN] == fn && li[this.EL] == el && li[this.TYPE] == eventName) { return i; } } return -1; }, // 未使用 elCache: {}, // 取到dom元素 getEl: function(id) { return document.getElementById(id); }, // 这个应该是还没有完成 clearCache: function() { }, // 当window的load事件触发时,就会执行此函数 _load: function(e) { loadComplete = true; var EU = Ext.lib.Event; // 清除_load函数,防止内存泄露 if (Ext.isIE) { EU.doRemove(window, "load", EU._load); } }, // 这个函数的主要功能就是执行onAvailStack中的函数。 _tryPreloadAttach: function() { // 此函数可以多次运行,但是onAvailStack是共享的,所以要锁一下 if (this.locked) { return false; } this.locked = true; // 这几行代码挺绕的,意思是这样的 // loadComplete代表window的onload事件触发,默认值是false,这个前面说过 // tryAgain主要是依据window是否load完成,和notAvail中是否还有未执行完成的 // notAvail如果有没有需要执行的了话,retryCount等于0 var tryAgain = !loadComplete; if (!tryAgain) { tryAgain = (retryCount > 0); } // 这个表示还未执行的函数,因为那个Dom元素还没有加载好,所以叫notAvail var notAvail = []; // 遍历onAvialStack,尝试执行一遍里面的函数 for (var i = 0,len = onAvailStack.length; i < len; ++i) { var item = onAvailStack[i]; // 执行完的选项为null,所以这里要判断一下,执行过得就不再执行了 if (item) { // 根据id获取Dom节点元素 var el = this.getEl(item.id); if (el) { // 如果节点元素存在,下面是连续的短路判断 // checkReady默认是false,其意思就是是否等Dom加载好了,再执行 // loadComplete开始是false,表示window的load事件是否加载好了 // el.nextSibling引用的是该节点的下一个兄弟节点 // document && document.body这个表示的body标签是否都加载好了 if (!item.checkReady || loadComplete || el.nextSibling || (document && document.body)) { // 经过上面层层判断,如果Dom确实已经加载好了,那么就开始准备执行了 // 首先就是scope,默认是el,这个就是函数执行时的this值 var scope = el; // 下面是可以重新设置这个scope的值,需要设置一下override的值,这个在前面讲过 if (item.override) { if (item.override === true) { scope = item.obj; } else { scope = item.override; } } // 最后是函数执行,执行完后,把该项引用设置成null item.fn.call(scope, item.obj); onAvailStack[i] = null; } } else { // 如果el不存在,表示Dom还没有加载好,那么就push到notAvail中去 notAvail.push(item); } } } // 如果都执行成功,则retryCount为0,否则,retryCount每次减1,初始值是200 retryCount = (notAvail.length === 0) ? 0 : retryCount - 1; if (tryAgain) { // startInterval函数还会调用此函数,继续轮回 this.startInterval(); } else { // 不需要再执行了,就清除了,就可以 clearInterval(this._interval); this._interval = null; } this.locked = false; return true; }, // 删除一个元素事件函数,还支持递归删除子元素的,顶 purgeElement: function(el, recurse, eventName) { var elListeners = this.getListeners(el, eventName); if (elListeners) { for (var i = 0,len = elListeners.length; i < len; ++i) { var l = elListeners[i]; this.removeListener(el, l.type, l.fn); } } if (recurse && el && el.childNodes) { for (i = 0,len = el.childNodes.length; i < len; ++i) { this.purgeElement(el.childNodes[i], recurse, eventName); } } }, // 根据el和eventName取得监听的时间函数结果集 getListeners: function(el, eventName) { var results = [], searchLists; if (!eventName) { searchLists = [listeners, unloadListeners]; } else if (eventName == "unload") { searchLists = [unloadListeners]; } else { searchLists = [listeners]; } for (var j = 0; j < searchLists.length; ++j) { var searchList = searchLists[j]; if (searchList && searchList.length > 0) { for (var i = 0,len = searchList.length; i < len; ++i) { var l = searchList[i]; if (l && l[this.EL] === el && (!eventName || eventName === l[this.TYPE])) { results.push({ type: l[this.TYPE], fn: l[this.FN], obj: l[this.OBJ], adjust: l[this.ADJ_SCOPE], index: i }); } } } } return (results.length) ? results : null; }, // 当window的unload事件触发时,执行此函数,并且触发unloadListeners中的所有函数 _unload: function(e) { var EU = Ext.lib.Event, i, j, l, len, index; for (i = 0,len = unloadListeners.length; i < len; ++i) { l = unloadListeners[i]; if (l) { var scope = window; if (l[EU.ADJ_SCOPE]) { if (l[EU.ADJ_SCOPE] === true) { scope = l[EU.OBJ]; } else { scope = l[EU.ADJ_SCOPE]; } } l[EU.FN].call(scope, EU.getEvent(e), l[EU.OBJ]); unloadListeners[i] = null; l = null; scope = null; } } unloadListeners = null; if (listeners && listeners.length > 0) { j = listeners.length; while (j) { index = j - 1; l = listeners[index]; if (l) { EU.removeListener(l[EU.EL], l[EU.TYPE], l[EU.FN], index); } j = j - 1; } l = null; EU.clearCache(); } EU.doRemove(window, "unload", EU._unload); }, // 取得文档的滚动距离 getScroll: function() { var dd = document.documentElement, db = document.body; if (dd && (dd.scrollTop || dd.scrollLeft)) { return [dd.scrollTop, dd.scrollLeft]; } else if (db) { return [db.scrollTop, db.scrollLeft]; } else { return [0, 0]; } }, // 注册事件 doAdd: function () { if (window.addEventListener) { return function(el, eventName, fn, capture) { el.addEventListener(eventName, fn, (capture)); }; } else if (window.attachEvent) { return function(el, eventName, fn, capture) { el.attachEvent("on" + eventName, fn); }; } else { return function() { }; } }(), // 删除事件 doRemove: function() { if (window.removeEventListener) { return function (el, eventName, fn, capture) { el.removeEventListener(eventName, fn, (capture)); }; } else if (window.detachEvent) { return function (el, eventName, fn) { el.detachEvent("on" + eventName, fn); }; } else { return function() { }; } }() }; }(); // 把addlistener和removeListener注册到on和un上,方便使用 var E = Ext.lib.Event; E.on = E.addListener; E.un = E.removeListener; // 判断一下Dom是否加载完成 if(document && document.body) { // 如果已经加载好了,就执行一下_load函数 E._load(); } else { // 如果还没有加载好,就注册到window的load事件上 E.doAdd(window, "load", E._load); } // 注册unload事件 E.doAdd(window, "unload", E._unload); // 这个_tryPreloadAttach函数应该是比_load函数执行的早,可以看做是入口函数 E._tryPreloadAttach(); |
|
返回顶楼 | |
发表时间:2012-02-21
这样的帖子 非常不错
|
|
返回顶楼 | |
发表时间:2012-02-21
最后修改:2012-02-21
临时详解下下面的代码: // 这个函数可以在你执行完原函数以后,执行一下自定义的函数。 createSequence : function( fcn , scope ) { if ( typeof fcn != "function" ) { return this; } var method = this; return function() { var retval = method.apply( this || window , arguments ); fcn.apply( scope || this || window , arguments ); return retval; }; } 这个代码为什么要这么设计呢? 先看API中关于这个方法的使用方法:
var sayHi = function(name){ alert('Hi, ' + name);} sayHi('Fred'); // alerts "Hi, Fred" var sayGoodbye = sayHi.createSequence(function(name){ alert('Bye, ' + name); }); sayGoodbye('Fred'); // both alerts show 最终createSequence需要返回一个function类型的方法所以在方法的定义中return 了一个function。 且根据方法定义,该方法的第一个参数必须是一个function类型的对象,所以在方法的最开始首先判断该方法第一个参数的参数类型若不为function则放弃后续操作并将createSequence方法的调用者返回去。这样类似于var sayGoodbye = sayHi.createSequence({});在执行sayGoodbye();时等价于调用sayHi();
这里还有一个待解决的问题那就是如何将sayHi保存起来,因为根据createSequence的定义。该方法需要先调用sayHi()而后调用里面的那个匿名function。 且因为一定要返回一个function所以下面必须renturn function(){。。。。。}这里请注意一点,返回的function中的this和return外面的this并不是同一个对象。 我们看代码:var mathod = this; 返回的function中: var retval = method.apply( this || window , arguments ); 这2个this是不一样的,return外面的this表示调用createSequence的对象,根据上面的例子就是sayHi这个function。而return中的this代表调用return的function的那个对象,也就是调用sayHi的那个对象而不是sayHi本身。 所以必须在return function之前先把代表sayHi的function也就是那个this用一个内部变量缓存起来利用闭包供return的function执行时使用。 所以才有了var retval = method.apply( this || window , arguments );的写法。根据上面的DEMO我们重新给方法定义添加一些注释以帮助理解2个不同的this:
// 这个函数可以在你执行完原函数以后,执行一下自定义的函数。 createSequence : function( fcn , scope ) { //这里的fcn就是DEMO中的那个function(name){alert('Bye, ' + name);} if ( typeof fcn != "function" ) { return this; } var method = this;//这个this为sayHi (类型为function) return function() { var retval = method.apply( this || window , arguments );//这个this为调用sayHi的对象也就是window //上面那一句相当于调用sayHi(/* 这个地方应该是arguments 也就是 'Fred' */); fcn.apply( scope || this || window , arguments );//这一句相当于执行function(name){alert('Bye, ' + name);}('Fred') return retval;//根据定义返回sayHi执行结果 }; } |
|
返回顶楼 | |
发表时间:2012-02-21
Sequence字面意思是“序列”的意思,所以简而言之,createSequence就是一种把函数按顺序调用的效果。
|
|
返回顶楼 | |