相關文章:
? jQuery插件開發全解析 讀jq之四 jquery1.43源碼分析之核心部分 推薦圈子:
Jquery 更多相關推薦
這個部分是jquery一些常用的工具方法. 包括為jquery對象擴展了一些數組里的方法.一些測試方法,函數代理和瀏覽器的特性檢測. 數組和對象操作.這部分的很多方法都已經成為javascript1.6的標準. 這部分包括一些原型函數,靜態函數,內部函數. 原型函數主要通過api暴露給外界. 靜態方法主要包含了原型方法的具體邏輯實現. 內部函數主要供內部調用. prototype上的方法一般設計得比較簡單, 主要充當一個控制層的作用. 而具體的實現邏輯, 大多數都放在了底層的靜態方法中. 這樣將來版本升級的時候, 一般只要修改靜態方法就可以了,跟api緊密耦合的prototype方法不會受到太多牽連. 這部分其中有一些方法已經在jquery1.43源碼分析之核心部分寫過了, 看看另外一些方法的實現. jQuery.isArray
isArray: function( obj ) {return toString.call(obj) === "[object Array]";}
判斷對象是不是Array類型.這個簡單的需求經歷了漫長的演變過程. 一般我們判斷對象類型, 會想到下面幾種方式. 1 typeof??? typeof? 只能判斷大概區分是不是對象類型, 不論是Array還是Date還是null,還是通過自定義構造函數生成的對象.返回的都是object 2 instanceof和constructor? 都非常容易在原型繼承中出現問題, 比如
var A = function(){}
A.prototype = new Array; var a = new A;alert (a instanceof Array) //true
而且在不同的iframe中, 判斷也不準確, 因為不同的iframe不共享原型鏈. instanceof和constructor自然也會失效. 3 后來就有了流行一時的鴨式變型. 如果一只鴨子, 它會呱呱叫, 也會像鴨子一樣走路. 那么就認為它就是鴨子.
isArray:?function(object)?{???
return?object?!=?null?&&?typeof?object?==?"object"?&&??'splice'?in?object?&&?'join'?in?object;???
}??
不過也許有那么一只聰明的雞學會了呱呱叫, 也學會了像鴨子一樣走路. 比如 var a = {}; a.splice = 1; a.join = 2; 前面的方法顯然都不完美, 直到有個人發現了Object.prototype.toString.call(obj) === "[object Array]", 世界才變美好.其實就是利用toString方法來得到一個包含這個對象內部屬性的字符串,這個字符串就包含了此對象構造器的信息. jQuery.prototype.each 顧名思義, each方法是對數組或者對象中的每個元素都做一些類似的操作. 我們在看源碼之前先自己實現一個Array.prototype.each.
Array.prototype.each = function(fn){for (var i = 0, l = this.length; i < l; i++){fn(this[i]); //當前元素作為回調函數的參數.}
};[1, 2, 3].each(function(i){alert (i); //i 就是上面的 this[i]
})
很簡陋, 然后想想它有哪些不足. 1, 在回調函數里我不知道當前循環到了第幾個元素. 2, 回調函數里的this指向了window, 這個沒任何意義. 所以現在來稍微改一下.
Array.prototype.each = function(fn){for (var i = 0, l = this.length; i < l; i++){fn.call(this[i], i, this[i]);}
};[1, 2, 3].each(function(i, n){alert ([this, i, n]);
})
現在在回調函數里可以取到3個值, this指向當前元素. i表示循環到了第幾個. n也表示當前元素. 當然你也可以把this指向別的東西. jquery就指向了原始元素. 不過如果我想在循環之中退出怎么辦, 比如我找到了2, 就想退出循環.那么再修改一下.
Array.prototype.each = function(fn){for (var i = 0, l = this.length; i < l; i++){if ( fn.call(this[i], i, this[i]) === false ) break; //當回調函數返回false的時候, 中止循環.}
};[1, 2, 3].each(function(i, n){if (n >= 2) {return false;}alert (n)
})
現在已經基本達到目的了, 再來看看jquery里each的實現.
each: function( callback, args ) {return jQuery.each( this, callback, args );},
直接交給靜態方法jQuery.each來操作. 看看jQuery.each
jQuery.each
each: function( object, callback, args ) {
/*
object為目標jquery對象, callback為回調函數, args表示callback方法里的參數, 如果不傳, 就為默認的i(下標), n(當前元素).
*/var name, i = 0, //對象屬性名、 數組下標length = object.length,isObj = length === undefined || jQuery.isFunction(object);
/*
isObj判斷object是單個對象還是數組, 如果是單個對象, 就遍歷它的屬性, 如是數組, 就循環每個元素.
*/if ( args ) { //如果給callback傳遞了參數if ( isObj ) { //如果是單個對象for ( name in object ) { //遍歷屬性if ( callback.apply( object[ name ], args ) === false ) {
/*
執行回調函數, 如果回調函數返回false, 退出循環. 回調函數里的參數為自己傳遞的args.
*/break;}}} else { //如果是數組for ( ; i < length; ) { //循環數組if ( callback.apply( object[ i++ ], args ) === false ) { //執行回調函數, 如果回調函數返回false, 退出循環.break;}}}} else { //基本同上, 除了用i和value代替參數argsif ( isObj ) {for ( name in object ) {if ( callback.call( object[ name ], name, object[ name ] ) === false ) {break;}}} else {for ( var value = object[0];i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}}}return object;},
其實each方法還有一個小小的缺陷. 當要遍歷一個對象的時候. 比如 { name: "John", lang: "JS" }這個對象. 這樣寫是沒問題的.
$.each( { name: "John", lang: "JS" }, function(i, n){alert( "Name: " + i + ", Value: " + n );})
但其實很多時候我們可能希望這樣寫
$({ name: "John", lang: "JS" }).each(function(i, n){alert( "Name: " + i + ", Value: " + n );})
但這樣寫不行. 因為現在的目標對象object實際上是一個經過包裝了的jquery對象. 即使里面是單個元素, 它的length也為1. isObj為false. jquery不會遍歷它的屬性. 這個方法里的代碼其實也可以更精簡一點,2個大的條件分支完全可以合并成一個. jQuery.prototype.map 將一組元素轉換成其他數組(不論是否是元素數組) 其實很容易聯想到, map方法就是讓集合里的每個元素都執行一次同一個函數, 把返回值填充到一個數組并返回這個新的數組. 看個例子
var ary = $.map( [0,1,2], function(n){return n + 4;
}); alert (ary);
ary已經被轉化成為[4,5,6]. 再看源碼
map: function( callback ) {return this.pushStack( jQuery.map(this, function( elem, i ) {return callback.call( elem, i, elem );}));},
看起來有一點點復雜, 其實callback.call( elem, i, elem )得到的是每個元素通過轉化之后的值.在jQuery.map里,這些值都會被填充進一個新的數組.最后把原來的引用存入pushStack,方便回溯. 看看jquery.map
map: function( elems, callback, arg ) {var ret = [], value; //ret是待填充的數組.//value引用回調函數的返回值for ( var i = 0, length = elems.length; i < length; i++ ) { value = callback( elems[ i ], i, arg );
//執行回調函數. elems[ i ]表示當前元素值, i表示當前循環到了
//第幾個元素. arg參數是1.26版本里沒有的.只供內部使用if ( value != null ) { //當返回值不為空時. ret[ ret.length ] = value; //push進新數組.}}return ret.concat.apply( [], ret ); },
注意value != null, 這里只有2個等號, 意味著可以也過濾掉返回值為undefined的元素, 比如在map一堆dom節點的子節點時, 如果某個元素沒有子節點, 那個元素就會被過濾掉. 可以看測試代碼
<body><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div></div>
</body><script>var ary = $.map( document.getElementsByTagName('div'), function(n){return n.childNodes[0];}); alert (ary.length)</script>
jQuery.prototype.grep 使用一個過濾函數閉包來按照某種條件來過濾數組
grep: function( elems, callback, inv ) { //至少需要前2個參數, 待過濾的數組和過濾函數. 第3個參數沒有什么必要.默認為false. 如果填true 就在過濾函數返回false的時候選擇那個元素.var ret = []; //空數組, 待會裝載過濾后的元素.for ( var i = 0, length = elems.length; i < length; i++ ) { //循環待過濾數組.if ( !inv !== !callback( elems[ i ], i ) ) {
//inv默認為undefined, callback也有可能返回undefined. 等號2端前面都加個!是為了讓inv和//callback統一轉化為boolean類型進行===的判斷.其實可以簡單看成//inv !== callback( elems[ i ], i ) )ret.push( elems[ i ] ); //符合條件的塞進數組}}return ret;},
當過濾函數的返回結果不為undefined, "", 0, false, null這5種情況之一時, 通過!轉化恰好不等于默認的inv(默認為false). 條件成立,這個就是需要的元素. 不過一般情況下我們讓過濾函數返回true就可以了. 注意并沒有用callback.call的形式來調用callback. 所以callback里的this是指向window的. jQuery.prototype.ready 用來替代window.onload. 每個主流庫中都有這個方法的相應實現. 如果使用window.onload, 你必須得等到頁面的所有圖片,視頻等都加載完,才會觸發window.onload里面的方法. 下面是頁面加載的具體順序. onContentReady,這時DOM樹完成 script defer 這時開始執行設定了defer屬性的script. ???????????? 某些庫比如ext用到了這個屬性 ondocumentready complete這時可以使用HTC組件與XHR? html.doScroll 這時可以讓HTML元素使用doScroll方法 window.onload 這時圖片flash等資源都加載完畢 來自http://www.cnblogs.com/rubylouvre/archive/2009/12/30/1635645.html jQuery分別用到了onContentReady, html.doScroll, window.onload來確認dom最早加載完成的時間. jQuery的實現可以大致分為這幾個步驟. 1首先把需要在頁面加載完成后執行的函數都存到一個list中, 加載完成后再依次觸發.并設置一個控制器, 保證一個頁面只有一個監聽函數在執行. 2判斷document.readyState是不是為complete.表示dom是否加載完畢 3 如果2步驟失敗, 根據瀏覽器的不同,給document增加DOMContentLoaded或者onreadystatechange事件,當該事件被觸發的時候執行readyList里的方法. 4在IE瀏覽器中, 設置一個定時器,不停的查看是否已經可以執行這個操作.document.documentElement.doScroll("left");如果執行這個操作時不再拋出異常,說明dom已經加載完畢了.這個辦法可能比步驟3更快. 看看代碼
ready: function( fn ) {jQuery.bindReady(); //添加監聽函數if ( jQuery.isReady ) { //如果dom加載完成了fn.call( document, jQuery ); //立刻執行函數} else if ( readyList ) {readyList.push( fn ); //否則把函數添加進readyList}return this;},
這里的邏輯并不復雜, 當調用$().ready(fn) 時, 先通過jQuery.isReady看dom有沒有加載完成(jQuery.isReady默認是false, 當加載完成時會被設置為true).如果dom已經加載完成了,就立刻執行fn. 否則, 把fn添加進待執行的數組.等dom加載完成時再執行. 看看bindReady的實現
bindReady: function() {if ( readyBound ) { //默認為falsereturn;}readyBound = true;if ( document.readyState === "complete" ) {
//如果$().ready()的時候,document已經加載完成了.return jQuery.ready(); //執行readyList里的方法}if ( document.addEventListener ) { //如果支持w3c標準事件模型, 如firefox opera, safari
document.addEventListener("DOMContentLoaded",DOMContentLoaded, false );
//當dom加載完成時, 觸發DOMContentLoaded方法. DOMContentLoaded方
//法可查看源碼742行.window.addEventListener( "load", jQuery.ready, false ); //保險起見, 給window.onload上面也綁定jQuery.ready, 這是其它方法都 //失效,逼不得以的情況.} else if ( document.attachEvent ) { //如果是IE事件模型
document.attachEvent("onreadystatechange", DOMContentLoaded); //實際上是判斷當document.readyState === "complete"時, 執行
// readyList里的函數, 見源碼754行. window.attachEvent( "onload", jQuery.ready ); //同上面的window.addEventListener/*
下面的代碼只針對IE瀏覽器和頁面不在iframe之中的情況, 當頁面處在iframe中, 好像有時候用doScroll()會出問題 (不過我測試了幾次, 似乎沒有發現這個問題 - -!).*/var toplevel = false;try {toplevel = window.frameElement == null; //判斷是IE并且頁面不在iframe當中} catch(e) {}if ( document.documentElement.doScroll && toplevel ) {doScrollCheck();
//不停的執行document.documentElement.doScroll("left"); 直到不報異常
}}}
再看看bindReady方法中涉及到的幾個方法.
jQuery.readyready: function() {if ( !jQuery.isReady ) { if ( !document.body ) { //至少要保證document.body存在. 似乎又是為IE做的hackreturn setTimeout( jQuery.ready, 13 ); //每隔13ms調用, resig似乎對13這個數字情有獨鐘, 是因為比較接近cpu//平均一幀的時間?}jQuery.isReady = true; //設置isReadyif ( readyList ) {var fn, i = 0;while ( (fn = readyList[ i++ ]) ) {fn.call( document, jQuery ); //依次執行readyList里的方法.}readyList = null;//清空readyList,盡早釋放內存. 因為當isReady為true時,//$().ready().對于參數里方法,采取的是來一個執行一個.已經無須// readyList了.}if ( jQuery.fn.triggerHandler ) { //觸發document上綁定的事件jQuery( document ).triggerHandler( "ready" );}}}
doScrollCheck
function doScrollCheck() {if ( jQuery.isReady ) {return;}try {document.documentElement.doScroll("left");} catch( error ) {setTimeout( doScrollCheck, 1 ); return;}//不停的執行document.documentElement.doScroll("left")//直到沒有異常拋出jQuery.ready();
}
jQuery.proxy proxy: function( fn, proxy, thisObject ) 返回一個新函數,這個函數的this指向你指定的對象. jquery1.4總算提供了this代理的方法了, 以前總是要自己實現一個Function.prototype.bind方法. 可能很多同學對this的調用和指向還是有點模糊. 那在此之前,先講一下this的幾種指向情況. 1 普通函數調用, this指向window. 比如 var fn = function(){ alert (this === window) }; fn(); 結果為true 2 對象屬性調用, this指向擁有這個屬性的對象. 比如 var obj = { fn: function(){ alert (this === obj); } obj.fn(); } 結果為true 或者 document.getElementById("id1").onclick = function(){ alert (this.id); } 點擊后彈出id1, 此時this指向onclick的擁有者id1. 3 通過構造函數調用this時,this指向通過構造函數生成的對象. 比如 function A(){ this.b = 1; } var a = new A(); 當a去調用A的構造函數時, this是指的a. 4 call或者apply, 這里的this是由自己指定. 比如
(function(){alert(this.name);
}).call({name: "__游樂場"})
這里需要一個括號把function(){alert(this.name)}包圍起來是為了讓引擎把括號里面的語句當成一個表達式而不是函數聲明, 函數聲明是不能調用方法的. 編譯期進行語法檢測的時候就會報錯. 其實我們在開發很容易就不知不覺弄丟了this.舉個例子.我要點擊一個div的時候,彈出這個div的id.
document.getElementById('div1').onclick = function(){~function(){alert (this.id);}()
}
這個方法里的this就已經是window了. 一般我們可以改成這樣
document.getElementById('div1').onclick = function(){var self = this;~function(){alert (self.id);}()
}
也許你不喜歡self這個臨時變量.那換一種方法.我們擴展一下Function的原型,實現一個最簡單的bind方法.
Function.prototype.bind = function(obj){var self = this; return function(){ //返回一個閉包, 調用的時候把obj當成this.self.call(obj); }
}
document.getElementById('div1').onclick = function(){~function(){alert (this.id);}.bind(this)()
}
現在已經OK了,沒有了討厭的臨時變量. 這里的proxy方法肯定也是利用call或者apply方法來指定this. 看看api上的例子
var obj = {name: "John",test: function() {alert( this.name );$("#test").unbind("click", obj.test);} };$("#test").click( jQuery.proxy( obj, "test" ) ); // 以下代碼跟上面那句是等價的: // $("#test").click( jQuery.proxy( obj.test, obj ) );// 可以與單獨執行下面這句做個比較。// $("#test").click( obj.test );
proxy根據參數傳遞的不同有2種調用方式, 1, 參數分別為obj對象, 被代理函數(必須是obj對象的屬性). 結果是返回obj.test函數, this指向obj. 2, 參數分別為被代理函數(obj.test), this指向obj. 第二種方式看來順眼得多.再看看源碼的具體實現
proxy: function( fn, proxy, thisObject ) { //參數分別為被代理函數, this代理, thisObject是內部用的變量, 用來修正被代理函數if ( arguments.length === 2 ) {if ( typeof proxy === "string" ) { //如果第二個參數為字符型, 參看上面的第一種調用方式.thisObject = fn; //修正thisObject指向obj對象. 這里代碼雖然寫的fn, 看起來像一個函數//實際上是第一種調用方式傳遞進來的某個對象. fn = thisObject[ proxy ]; //被代理函數修正為obj對象的某個屬性函數.proxy = undefined;
//清空proxy, 跟下面的if ( !proxy && fn ) 統一處理只有一個參數fn的情況.} else if ( proxy && !jQuery.isFunction( proxy ) ) { //上面第二種調用方式thisObject = proxy;
//修正thisObject指向obj對象.proxy = undefined;
//清空proxy, 跟下面的 if ( !proxy && fn ) 統一處理只有一個參數fn的情況.}}if ( !proxy && fn ) { // 如果只有一個參數, proxy為undefined. 有2個參數的絕大部分情況下, proxy也已經被設置為undefined. 這里的條件判斷都為trueproxy = function() { return fn.apply( thisObject || this, arguments );
//進行代理, 如果只有一個參數, thisObject顯然是undefined, 這里的代理對象還是原來的this. 注意proxy返回一個函數, 這里的第二個參數arguments是以后調用proxy函數時候的arguments};}if ( fn ) {proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; //設置一個全局標識符, 在event系統中用到.}return proxy;}
瀏覽器特性檢測 從jquery1.3版本開始, 寫不同瀏覽器的兼容代碼之前, 不贊成再去判斷瀏覽器的類型, 而是直接判斷支不支持某個特性, 比如盒模型. 透明度這些. 就像一個老外向你問路的時候, 他肯定是先說can you speak English. 而不是are you Amercan. 關于特性檢測的具體實現, 可以參考下面文章. http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting http://yura.thinkweb2.com/cft/ http://www.jibbering.com/faq/faq_notes/not_browser_detect.html
總結
以上是生活随笔 為你收集整理的jquery1.43源码分析之工具方法 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。