jquery有很多奇淫技巧,它的結構清晰,高內聚、低耦合,兼具優秀的性能與便利的擴展性。
這裏僅對其中的一些小技巧做個總結。
1、jquery閉包
把當前沙箱需要的外部變量通過函數參數引入進來
只要保證參數對內提供的接口的一致性,你可以隨意替換傳進來的這個參數
(function(window, undefined) {
// jQuery 代碼
undefined = 20;
console.log(undefined); // 20
})(window);
(function(w, u) {
// jQuery 代碼
console.log(w); // window
u = 20;
console.log(u); // 20
})(window);
(function(window,undefined) {
// jQuery 代碼
undefined = 20;
console.log(undefined); // 20
})(window, undefined);
undefined = 20;
console.warn(undefined); // undefined
2、jquery 無 new 構造
// 無 new 構造
// s也能訪問jq原型鏈上的所有方法
var s = $('#test');
// 當然也可以使用 new
// q能訪問jq原型鏈上的所有方法
var q = new $('#test');
(function (window, undefined) {
var
// ...
jQuery = function (selector, context) {
// 實例化方法 jQuery() 實際上是調用了其拓展的原型方法 jQuery.fn.init
return new jQuery.fn.init(selector, context, rootjQuery);
},
// jQuery.prototype 即是 jQuery 的原型,掛載在上面的方法,即可讓所有生成的 jQuery 對象使用
jQuery.fn = jQuery.prototype = {
// 實例化化方法,這個方法可以稱作 jQuery 對象構造器
init: function (selector, context, rootjQuery) {
// ...
}
},
jQuery.fn.init.prototype = jQuery.fn;
})(window);
1)使用 $('xxx') 這種實例化方式,其內部調用的是 return new jQuery.fn.init(selector, context, rootjQuery) ,即構造實例是交給 jQuery.fn.init() 方法去完成。
2)將 jQuery.fn.init 的 prototype 屬性設置爲 jQuery.fn,那麼使用 new jQuery.fn.init() 生成的對象的原型對象就是 jQuery.fn ,所以掛載到 jQuery.fn 上面的函數就相當於掛載到 jQuery.fn.init() 生成的 jQuery 對象上,所有使用 new jQuery.fn.init() 生成的對象也能夠訪問到 jQuery.fn 上的所有原型方法。
3)也就是實例化方法存在這麼一個關係鏈
- jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype ;
- new jQuery.fn.init() 相當於 new jQuery() ;
- var obj1 = jQuery() 返回的是 new jQuery.fn.init(),var obj2 = new jQuery(),obj1和obj2是相當的,所以我們可以無 new 實例化 jQuery 對象。
3、jQuery.fn.extend 與 jQuery.extend
jQuey 內部用extend 方法來擴展靜態方法或實例方法, jQuery 插件開發的時候也會用到它。
1)jQuery.extend(object) 爲擴展 jQuery 類本身,爲類添加新的靜態方法;
2)jQuery.fn.extend(object) 給 jQuery 對象添加實例方法,也就是通過這個 extend 添加的新方法,實例化的 jQuery 對象都能使用,因爲它是掛載在 jQuery.fn 上的方法(jQuery.fn = jQuery.prototype )。
1)jQuery.extend(): 把兩個或者更多的對象合併到第一個當中,
2)jQuery.fn.extend():把對象掛載到 jQuery 的 prototype 屬性,來擴展一個新的 jQuery 實例方法。
也就是說,使用 jQuery.extend() 拓展的靜態方法,我們可以直接使用 $.xxx 進行調用(xxx是拓展的方法名),
而使用 jQuery.fn.extend() 拓展的實例方法,需要使用 $().xxx 調用。
jQuery.fn.extend({
a: function() {
console.log('使用$(xx).xxx調用');
}
});
jQuery.extend({
b: '使用$.xxx調用'
})
$().a();
$.b;
需要注意的是這一句 jQuery.extend = jQuery.fn.extend = function() {} ,也就是 jQuery.extend 的實現和 jQuery.fn.extend 的實現共用了同一個方法,但是爲什麼能夠實現不同的功能了,這就要歸功於 Javascript 強大(怪異?)的 this 了。
1)在 jQuery.extend() 中,this 的指向是 jQuery 對象(或者說是 jQuery 類),所以這裏擴展在 jQuery 上;
2)在 jQuery.fn.extend() 中,this 的指向是 fn 對象,前面有提到 jQuery.fn = jQuery.prototype ,也就是這裏增加的是原型方法,也就是對象方法。
4、jQuery 的鏈式調用及回溯
只需要在要實現鏈式調用的方法的返回結果裏,返回 this ,就能夠實現鏈式調用了。
var s = {
a: function() {
console.log(1);
return this;
},
b: function() {
console.log(2);
return this;
},
c: function() {
console.log(3);
return this;
}
};
s.a().b().c();
jQuery 完整的鏈式調用、增棧、回溯通過 return this 、 return this.pushStack() 、return this.prevObject 實現
jQuery.fn = jQuery.prototype = {
// 將一個 DOM 元素集合加入到 jQuery 棧
// 此方法在 jQuery 的 DOM 操作中被頻繁的使用, 如在 parent(), find(), filter() 中
// pushStack() 方法通過改變一個 jQuery 對象的 prevObject 屬性來跟蹤鏈式調用中前一個方法返回的 DOM 結果集合
// 當我們在鏈式調用 end() 方法後, 內部就返回當前 jQuery 對象的 prevObject 屬性
pushStack: function (elems) {
// 構建一個新的jQuery對象,無參的 this.constructor(),只是返回引用this
// jQuery.merge 把 elems 節點合併到新的 jQuery 對象
// this.constructor 就是 jQuery 的構造函數 jQuery.fn.init,所以 this.constructor() 返回一個 jQuery 對象
// 由於 jQuery.merge 函數返回的對象是第二個函數附加到第一個上面,所以 ret 也是一個 jQuery 對象,這裏可以解釋爲什麼 pushStack 出入的 DOM 對象也可以用 CSS 方法進行操作
var ret = jQuery.merge(this.constructor(), elems);
// 給返回的新 jQuery 對象添加屬性 prevObject
// 所以也就是爲什麼通過 prevObject 能取到上一個合集的引用了
ret.prevObject = this;
ret.context = this.context;
// Return the newly-formed element set
return ret;
},
// 回溯鏈式調用的上一個對象
end: function () {
// 回溯的關鍵是返回 prevObject 屬性
// 而 prevObject 屬性保存了上一步操作的 jQuery 對象集合
return this.prevObject || this.constructor(null);
},
// 取當前 jQuery 對象的第 i 個
eq: function (i) {
// jQuery 對象集合的長度
var len = this.length,
j = +i + (i < 0 ? len : 0);
// 利用 pushStack 返回
return this.pushStack(j >= 0 && j < len ? [this[j]] : []);
}
}
總的來說,
1)end() 方法返回 prevObject 屬性,這個屬性記錄了上一步操作的 jQuery 對象合集;
2)而 prevObject 屬性由 pushStack() 方法生成,該方法將一個 DOM 元素集合加入到 jQuery 內部管理的一個棧中,通過改變 jQuery 對象的 prevObject 屬性來跟蹤鏈式調用中前一個方法返回的 DOM 結果集合
3)當我們在鏈式調用 end() 方法後,內部就返回當前 jQuery 對象的 prevObject 屬性,完成回溯。
5、jQuery方法的重載
// 獲取 title 屬性的值
$('#id').attr('title');
// 設置 title 屬性的值
$('#id').attr('title','jQuery');
function addMethod(obj, name, f) {
var old = obj[name];
obj[name] = function () {
if (f.length === arguments.length) {
return f.apply(this, arguments);
} else {
return old.apply(this, arguments);
}
}
}
var people = {
name: ["張三", "李四", "王五"]
};
var find0 = function () {
return this.name;
};
var find1 = function (name) {
var arr = this.name;
for (var i = 0; i < arr.length; i++) {
if (arr[i] === name) {
return arr[i] + "在" + i + "位";
}
}
}
function test(a) {
console.log('形參個數', test.length); // 1
console.log('實參個數', arguments.length); // 0
}
test();
addMethod(people, 'find', find0);
addMethod(people, 'find', find1);
console.log(people.find("王五"));
6、jQuery 變量衝突處理
當需要處理衝突的時候,調用靜態方法 noConflict(),讓出變量的控制權
(function(window, undefined) {
var
// Map over jQuery in case of overwrite
// 設置別名,通過兩個私有變量映射了 window 環境下的 jQuery 和 $ 兩個對象,以防止變量被強行覆蓋
_jQuery = window.jQuery,
_$ = window.$;
jQuery.extend({
// noConflict() 方法讓出變量 $ 的 jQuery 控制權,這樣其他腳本就可以使用它了
// 通過全名替代簡寫的方式來使用 jQuery
// deep -- 布爾值,指示是否允許徹底將 jQuery 變量還原(移交 $ 引用的同時是否移交 jQuery 對象本身)
noConflict: function(deep) {
// 判斷全局 $ 變量是否等於 jQuery 變量
// 如果等於,則重新還原全局變量 $ 爲 jQuery 運行之前的變量(存儲在內部變量 _$ 中)
if (window.$ === jQuery) {
// 此時 jQuery 別名 $ 失效
window.$ = _$;
}
// 當開啓深度衝突處理並且全局變量 jQuery 等於內部 jQuery,則把全局 jQuery 還原成之前的狀況
if (deep && window.jQuery === jQuery) {
// 如果 deep 爲 true,此時 jQuery 失效
window.jQuery = _jQuery;
}
// 這裏返回的是 jQuery 庫內部的 jQuery 構造函數(new jQuery.fn.init())
// 像使用 $ 一樣盡情使用它吧
return jQuery;
}
})
}(window)
那麼讓出了這兩個符號之後,是否就不能在我們的代碼中使用 jQuery 或者呢 $ 呢?莫慌,還是可以使用的:
// 讓出 jQuery 、$ 的控制權不代表不能使用 jQuery 和 $ ,方法如下:
var query = jQuery.noConflict(true);
(function($) {
// 插件或其他形式的代碼,也可以將參數設爲 jQuery
})(query);
// ... 其他用 $ 作爲別名的庫的代碼
7、短路表達式
var a = 1, b = 0, c = 3;
var foo = a && b && c, // 0 ,相當於 a && (b && c)
bar = a || b || c; // 1
1、在 Javascript 的邏輯運算中,0、""、null、false、undefined、NaN 都會判定爲 false ,而其他都爲 true ;
2、因爲 Javascript 的內置弱類型域 (weak-typing domain),所以對嚴格的輸入驗證這一點不太在意,即便使用 && 或者 || 運算符的運算數不是布爾值,仍然可以將它看作布爾運算。雖然如此,還是建議如下:
if(foo){ ... } //不夠嚴謹
if(!!foo){ ... } //更爲嚴謹,!!可將其他類型的值轉換爲boolean類型
8、$.trim
let core_version = "1.19.2";
let core_Trim = core_version.trim;
function trim(data) {
return core_Trim.call(data);
}
console.log(trim(" abds "));
// 源碼
core_version = "1.9.0",
core_trim = core_version.trim,
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
function( text ) {
return text == null ?
"" :
core_trim.call( text );
} :
// Otherwise use our own trimming functionality
function( text ) {
return text == null ?
"" :
( text + "" ).replace( rtrim, "" );
}
var core_trim = String.prototype.trim; if (core_trim && !core_trim.call("\uFEFF\xA0")) 相當於: if (String.prototype.trim && "\uFEFF\xA0".trim() !== "") 高級的瀏覽器已經支持原生的String的trim方法,但是jQuery還爲了避免它沒法解析全角空白,所以加多了一個判斷:"\uFEFF\xA0".trim() !== ""
\uFEFF是utf8的字節序標記,詳見:字節順序標記 "\xA0"是全角空格 如果以上條件成立了,那就直接用原生的trim函數就好了,展開也即是:
$.trim = function( text ) {
return text == null ?
"" :
text.trim();
}
如果上述條件不成立了,那jQuery就自己實現一個trim方法:
$.trim = function( text ) {
return text == null ?
"" :
( text + "" ).replace( rtrim, "" );
}
參考文章: