亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

從零開(kāi)始,DIY一個(gè)jQuery(3)

Original 2016-11-11 17:40:42 373
abstrakt:在前兩篇,為了方便調(diào)試,我們寫(xiě)了一個(gè)非常簡(jiǎn)單的 jQuery.fn.init 方法:jQuery.fn.init = function (selector, context, root) {         if (!selector)&n

在前兩篇,為了方便調(diào)試,我們寫(xiě)了一個(gè)非常簡(jiǎn)單的 jQuery.fn.init 方法:

jQuery.fn.init = function (selector, context, root) {
        if (!selector) {
            return this;
        } else {
            var elem = document.querySelector(selector);
            if (elem) {
                this[0] = elem;
                this.length = 1;
            }
            return this;
        }
    };

因此我們?cè)?demo 里執(zhí)行 $('div') 時(shí)可以取得這么一個(gè)類數(shù)組對(duì)象:

561179-20160808221819496-2900624.png

在完整的 jQuery 中通過(guò) $(selector) 的形式獲取的對(duì)象也基本如此 —— 它是一個(gè)對(duì)象而非數(shù)組,但可以通過(guò)下標(biāo)(如 $div[index] )或 .get(index) 接口來(lái)獲取到相應(yīng)的 DOM 對(duì)象,也可以直接通過(guò) .length 來(lái)獲取匹配到的 DOM 對(duì)象總數(shù)。

這么實(shí)現(xiàn)的原因是 —— 方便,該對(duì)象畢竟是 jQuery 實(shí)例,繼承了所有的實(shí)例方法,同時(shí)又直接是所檢索到的DOM集合(而不需要通過(guò) $div.getDOMList() 之類的方法來(lái)獲?。?jiǎn)直一石二鳥(niǎo)。

如下圖所示便是一個(gè)很尋常的 JQ 類數(shù)組對(duì)象(初始化執(zhí)行的代碼是 $('div') ):

2.gif

1. Sizzle 引入

在 jQuery 中,檢索DOM的能力來(lái)自于 Sizzle 引擎,它是 JQ 最核心也是最復(fù)雜的部分,在后續(xù)有機(jī)會(huì)我們?cè)賹?duì)其作詳細(xì)介紹,當(dāng)前階段,我們只需要直接“獲取”并“使用”它即可。

Sizzle 是開(kāi)源的選擇器引擎,其官網(wǎng)是 http://sizzlejs.com/ ,直接在首頁(yè)便能下載到最新版本。

我們?cè)?src 目錄下新增一個(gè) /sizzle 文件夾,并把下載到的 sizzle.js 放進(jìn)去(即存放為 src/sizzle/sizzle.js ),接著得對(duì)其做點(diǎn)小修改,使其得以適應(yīng)我們 rollup 的打包模式。

其原先代碼為:

(function( window ) {

var i,
    support,

//...省略一大堆有的沒(méi)的

Sizzle.noConflict = function() {
    if ( window.Sizzle === Sizzle ) {
        window.Sizzle = _sizzle;
    }

    return Sizzle;
};

if ( typeof define === "function" && define.amd ) {
    define(function() { return Sizzle; });
// Sizzle requires that there be a global window in Common-JS like environments
} else if ( typeof module !== "undefined" && module.exports ) {
    module.exports = Sizzle;
} else {
    window.Sizzle = Sizzle;
}
// EXPOSE

})( window );

將這段代碼的頭和尾替換為:

var i,
    support,

//...省略

Sizzle.noConflict = function() {
    if ( window.Sizzle === Sizzle ) {
        window.Sizzle = _sizzle;
    }

    return Sizzle;
};

export default Sizzle;

同時(shí)新增一個(gè)初始化文件 src/sizzle/init.js ,用于把 Sizzle 賦予靜態(tài)接口 jQuery.find:

import Sizzle from './sizzle.js';

var selectorInit = function(jQuery){
    jQuery.find = Sizzle;
};



export default selectorInit;

別忘了在打包的入口文件里引入該模塊并執(zhí)行:

import jQuery from './core';
import global from './global';
import init from './init';
import sizzleInit from './sizzle/init';  //新增global(jQuery);
init(jQuery);
sizzleInit(jQuery);  //新增
export default jQuery;


打包后我們就能愉快地通過(guò) jQuery.find 接口來(lái)使用 Sizzle 的各種能力了(使用方式可以參考 Sizzle 的API文檔):

2.png

留意 $.find(XXX) 返回的是一個(gè)匹配到的 DOM 集合的數(shù)組(注意類型直接就是Array,不是 document.querySelectorAll 那樣返回的 nodeList )。

我們需要多做一點(diǎn)處理,來(lái)將這個(gè)數(shù)組轉(zhuǎn)換為前頭提到的類數(shù)組JQ對(duì)象。

另外,雖然現(xiàn)在 JQ 的工具方法有了檢索DOM的能力,但其實(shí)例方法是木有的,鑒于構(gòu)造器的靜態(tài)屬性不會(huì)繼承給實(shí)例,會(huì)導(dǎo)致我們沒(méi)法鏈?zhǔn)降貋?lái)支持 find,比如:

$('div').find('p').find('span')

很明顯,這可以在 jQuery.fn.extend 里多加一個(gè) find 接口來(lái)實(shí)現(xiàn),不過(guò)不著急,咱們一步一步來(lái)。

2. $.merge 方法

針對(duì)上述的第一個(gè)需求點(diǎn),我們修改下 src/core.js ,往 jQuery.extend 里新增一個(gè) jQuery.merge 靜態(tài)方法,方便把檢索到的 DOM 集合數(shù)組轉(zhuǎn)換為類數(shù)組對(duì)象:

jQuery.fn = jQuery.prototype = {
    jquery: version,
    length: 0,  // 修改點(diǎn)1,JQ實(shí)例.length 默認(rèn)為0
    //...
}

jQuery.extend( {
    merge: function( first, second ) {  //修改點(diǎn)2,新增 merge 工具接口
        var len = +second.length,
            j = 0,
            i = first.length;

        for ( ; j < len; j++ ) {
            first[ i++ ] = second[ j ];
        }

        first.length = i;

        return first;
    },
    //...
});

merge 的代碼段太好理解了,其實(shí)現(xiàn)的能力為:

<div>hello</div>
<div>world</div>

<script>
    var divs = $.find('div'); //純數(shù)組
    var $div1 = $.merge( ['hi'], divs); //右邊的數(shù)組合并到左邊的數(shù)組,形成一個(gè)新數(shù)組
    var $div2 = $.merge( {0: 'hi', length: 1}, divs); //右邊的數(shù)組合并到左邊的對(duì)象,形成一個(gè)新的類數(shù)組對(duì)象

    console.log($div1);
    console.log($div2);
</script>

運(yùn)行輸出:

2.png

因此,如果我們?cè)?jQuery.fn.init 中,把 this 傳入為 $.merge 的 first 參數(shù)(留意這里this為JQ實(shí)例對(duì)象自身,默認(rèn) length 實(shí)例屬性為0),再把檢索到的 DOM 集合數(shù)組作為 second 參數(shù)傳入,那么就能愉快地得到我們想要的 JQ 類數(shù)組對(duì)象了。

我們簡(jiǎn)單地修改下 src/init.js :

jQuery.fn.init = function (selector, context, root) {
        if (!selector) {
            return this;
        } else {
            var elemList = jQuery.find(selector);
            if (elemList.length) {
                jQuery.merge( this, elemList );  //this是JQ實(shí)例,默認(rèn)實(shí)例屬性 .length 為0
            }
            return this;
        }
    };

我們打包后執(zhí)行:

<div>hello</div>
<div>world</div>

<script>
    var $div = $('div');
    console.log($div);
</script>

輸出正是我們所想要的類數(shù)組對(duì)象:

3.gif

3. 擴(kuò)展 $.fn.find

針對(duì)第二個(gè)需求點(diǎn) —— 鏈?zhǔn)街С?find 接口,我們需要給 $.fn 擴(kuò)展一個(gè) find 方法:

jQuery.fn.extend({
    find: function( selector ) {  //鏈?zhǔn)街С謋ind
        var i, ret,
            len = this.length,
            self = this;
        ret = [];        for ( i = 0; i < len; i++ ) {  //遍歷
            jQuery.find( selector, self[ i ], ret );  //直接利用 Sizzle 接口,把結(jié)果注入到 ret 數(shù)組中去        }        return ret;
    }
});

這里我們依舊直接使用了 Sizzle 接口 —— 當(dāng)帶上了第三個(gè)參數(shù)(數(shù)組類型)時(shí),Sizzle 會(huì)把檢索到的 DOM 集合注入到該參數(shù)中去(API文檔)。

我們打包后執(zhí)行下方代碼:

<div><span>hi</span><b>hello</b></div>
<div><span>你好</span></div>

<script>
    var $span = $('div').find('span');
    console.log($span);
</script>

效果如下:

4.gif

可以看到,我們要的子元素是出來(lái)了,不過(guò)呢,這里獲取到的是純數(shù)組,而非 JQ 對(duì)象,處理方法很簡(jiǎn)單 —— 直接調(diào)用前面剛加上的 $.merge 方法即可。

另外也有個(gè)問(wèn)題,一旦咱們獲取到了子孫元素(如上方代碼中的span),那么如果我們需要重新取到其祖先元素(如上方代碼中的div),就又得重新去走 $('div') 來(lái)檢索了,這樣麻煩且效率不高。

而我們知道,在 jQuery 中是有一個(gè) $.fn.end 方法可以返回上一次檢索到的 JQ 對(duì)象的:

$('div').find('span').end()  //返回$('div')對(duì)象

處理方法也很簡(jiǎn)單,參考瀏覽器的歷史記錄棧,我們也來(lái)寫(xiě)一個(gè)遵循后進(jìn)先出的棧操作方法。

可能你在第一時(shí)間會(huì)想到,是否使用一個(gè)數(shù)組,通過(guò) push 和 pop 來(lái)實(shí)現(xiàn)入棧和出棧的功能。

事實(shí)上我們有更簡(jiǎn)單的形式 —— 給新的 JQ 對(duì)象新增一個(gè) .prevObject 屬性并指向舊 JQ 對(duì)象,這樣一來(lái),我們想獲取當(dāng)前 JQ 對(duì)象之前的一次 JQ 對(duì)象,通過(guò)該屬性就能直接取到了:

jQuery.fn = jQuery.prototype = {
    jquery: version,
    length: 0, 
    constructor: jQuery,
    /**
     * 入棧操作
     * @param elems {Array}
     * @returns {*}
     */
    pushStack: function( elems ) {  //elems是數(shù)組

        // 將檢索到的DOM集合轉(zhuǎn)換為JQ類數(shù)組對(duì)象
        var ret = jQuery.merge( this.constructor(), elems );  //this.constructor() 返回了一個(gè) length 為0的JQ對(duì)象

        // 添加關(guān)系鏈,新JQ對(duì)象的prevObject屬性指向舊JQ對(duì)象
        ret.prevObject = this;

        return ret;
    }
    //省略...
}

這樣通過(guò) pushStack 接口包裝下,就解決了上面說(shuō)的兩個(gè)問(wèn)題,我們改下 $.fn.find 代碼:

jQuery.fn.extend({
    find: function( selector ) {  //鏈?zhǔn)街С謋ind
        var i, ret,
            len = this.length,
            self = this;
        ret = [];        for ( i = 0; i < len; i++ ) {  //遍歷
            jQuery.find( selector, self[ i ], ret );  //直接利用 Sizzle 接口,把結(jié)果注入到 ret 數(shù)組中去        }        return this.pushStack( ret );  //轉(zhuǎn)為JQ對(duì)象    }
});

從性能上考慮,我們這樣寫(xiě)會(huì)更好一些(減少一些merge里的遍歷):

jQuery.fn.extend({
    find: function( selector ) {  //鏈?zhǔn)街С謋ind
        var i, ret,
            len = this.length,
            self = this;
        ret = this.pushStack( [] ); //轉(zhuǎn)為JQ對(duì)象
        for ( i = 0; i < len; i++ ) {  //遍歷
            jQuery.find( selector, self[ i ], ret );  //直接利用 Sizzle 接口,把結(jié)果注入到 ret 數(shù)組中去        }        return ret
    }
});

4. $.fn.end、$.fn.eq 和 $.fn.get

鑒于我們?cè)?pushStack 中加上了 oldJQ.prevObject 的關(guān)系鏈,那么 $.fn.end 接口的實(shí)現(xiàn)就太簡(jiǎn)單了:

jQuery.fn.extend({
    end: function() {        return this.prevObject || this.constructor();
    }
});

直接返回上一次檢索到的JQ對(duì)象(如果木有,則返回一個(gè)空的JQ對(duì)象)。

這里順便再多添加兩個(gè)大家熟悉的不能再熟悉的 $.fn.eq 和 $.fn.get 工具方法,代碼非常的簡(jiǎn)單:

jQuery.fn.extend({
    end: function() {
        return this.prevObject || this.constructor();
    },
    eq: function( i ) {
        var len = this.length,
            j = +i + ( i < 0 ? len : 0 );  //支持倒序搜索,i可以是負(fù)數(shù)
        return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); //容錯(cuò)處理,若i過(guò)大或過(guò)小,返回空數(shù)組
    },
    get: function( num ) {
        return num != null ?

            // 支持倒序搜索,num可以是負(fù)數(shù)
            ( num < 0 ? this[ num + this.length ] : this[ num ] ) :

            // 克隆一個(gè)新數(shù)組,避免指向相同
            [].slice.call( this );  //建議把 [].slice 封裝到 var.js 中去復(fù)用
    }
});


通過(guò) eq 接口我們可以知道,后續(xù)任何方法,如果要返回一個(gè) JQ 對(duì)象,基本都需要裹一層 pushStack 做處理,來(lái)確保 prevObject 的正確引用。

當(dāng)然,這也輕松衍生了 $.fn.first 和 $.fn.last 兩個(gè)工具方法:

jQuery.fn.extend({
    first: function() {
        return this.eq( 0 );
    },
    last: function() {
        return this.eq( -1 );
    }
});

本手記就先寫(xiě)到這里,避免太多內(nèi)容難消化。事實(shí)上,我們的 $.fn.init 、$.find 和 $.fn.find 都還有一些不完善的地方:

1. $.fn.init 方法沒(méi)有兼顧到各種參數(shù)類型的情況,也還沒(méi)有加上第二個(gè)參數(shù) context 來(lái)做上下文預(yù)設(shè);

2. 同上,$.fn.find 也未對(duì)兼顧到各種參數(shù)類型的情況;

3. $.fn.find 返回結(jié)果有可能帶有重復(fù)的 DOM

例如:

<div><div><span>hi</span></div></div>

<script>
    var $span = $('div').find('span');
    console.log($span);  //重復(fù)了
</script>



Versionshinweise

Beliebte Eintr?ge