jquery 這些小技巧你懂嗎

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 、$ 的控制權不代表不能使用 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, "" );
}

 

 

參考文章:

【深入淺出jQuery】源碼淺析--整體架構

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章