jQuery源码学习7——实例成员

选择器部分的代码实在很复杂,过后再看jQuery.init用到了一些实例方法,因此先看一下实例方法再回过头看init

源码中jQuery构造函数定义完之后添加的实例成员有:

jquery size get each index attr css text wrap append prepend before after end find clone filter not add is domManip pushStack

(1)jquery:存储当前jQuery的版本

(2)size:存储当前实力化对象的length属性,感觉应该是存放选取到的元素的个数

(3)get

    get: function( num ) {
        if ( num && num.constructor == Array ) {
            this.length = 0;
            [].push.apply( this, num );
            return this;
        } else
            return num == undefined ?
                jQuery.map( this, function(a){ return a } ) :
                this[num];
    },

if里面很明显是数组的情况,刚开始实在搞不清楚什么时候能进这个分支

看了好久才发现在jQuery构造函数定义的时候里面调用get方法时会传入数组:

    // Watch for when an array is passed in
    this.get( a.constructor == Array || a.length && !a.nodeType && a[0] != undefined && a[0].nodeType ?
        // Assume that it is an array of DOM Elements
        jQuery.merge( a, [] ) :

        // Find the matching elements and save them for later
        jQuery.find( a, c ) );

在这个if分支里面先初始化了length属性为0

接下来的这句有点意思:[].push.apply( this, num )

其实是把数组push方法内部的this强制改为了当前环境下的this

而apply的第二个参数是一个数组形式,所以把document放入数组中就是num

最后this就变成了

    {
        "0":document,
        "length":1
    }

如果num传入的就是数字类型的话那就好办了

直接走else分支返回对应的对象

如果直接$(".div1").get()就会得到$(".div1")选中的元素的数组

(4)each 实际上是调用了静态方法$.each 第一个参数是选择器选中的元素

(5)index

    index: function( obj ) {
        var pos = -1;
        this.each(function(i){
            if ( this == obj ) pos = i;
        });
        return pos;
    },

得到传入的obj在当前选中的元素集合中(this)的位置

由于each方法在定义的时候改变了其传入的参数中的this的指向,所以each的函数参数的函数体里面的this指向的是当前遍历的元素

(6)attr

    attr: function( key, value, type ) {
        // Check to see if we're setting style values
        return key.constructor != String || value != undefined ?
            this.each(function(){
                // See if we're setting a hash of styles
                if ( value == undefined )
                    // Set all the styles
                    for ( var prop in key )
                        jQuery.attr(
                            type ? this.style : this,
                            prop, key[prop]
                        );
                
                // See if we're setting a single key/value style
                else
                    jQuery.attr(
                        type ? this.style : this,
                        key, value
                    );
            }) :
            
            // Look for the case where we're accessing a style value
            jQuery[ type || "attr" ]( this[0], key );
    },

这个方法的用法貌似有以下几种

$(".div").attr("abc")            //设置有.div类的abc属性      //直接走else 而且type不存在所以就调用jQuery.attr($(".div")[0],"key")得到其key属性

$(".div").attr("abc","123")          //设置有.div类的abc属性值为"123"  //走if 内层判断走else 设置单个属性值

$(".div").attr({"abc":"123","index":"456"}) //设置多个属性            //走if 内层判断走if 设置多个属性值

此外在源码中还发现一种用法:

$(".div").attr("style","width:100px; height:100px; background:red;","curCSS") //设置style属性 走if 内层判断走else 设置多个样式值

而且貌似最后一个参数type和设置style属性有关

(7)css 直接调用了attr方法

(8)text

    text: function(e) {
        e = e || this;
        var t = "";
        for ( var j = 0; j < e.length; j++ ) {
            var r = e[j].childNodes;
            for ( var i = 0; i < r.length; i++ )
                t += r[i].nodeType != 1 ?
                    r[i].nodeValue : jQuery.fn.text([ r[i] ]);
        }
        return t;
    },

这个text方法貌似只能获取一个元素里面的值而不能设置

目测参数e得是类数组对象,而且这个数组对象里面的每一项都是DOM元素

所以希望通过$("#div .div").text(123)来将#div下的.div标签里面的值改为123是不太可能的

如果HTML是

    <div id="div1">
        <div class="div1">abc</div>
    </div>

那通过$("#div1").text()得到的将是#div1里面所有文本节点拼接起来的字符串

但是个人感觉一般来讲,这个字符串没什么太大意义

(9)wrap

    wrap: function() {
        var a = jQuery.clean(arguments);
        return this.each(function(){
            var b = a[0].cloneNode(true);
            this.parentNode.insertBefore( b, this );
            while ( b.firstChild )
                b = b.firstChild;
            b.appendChild( this );
        });
    },

wrap方法貌似是将选择器选到的元素都用传入的DOM对象包围

例如,HTML是

    <div id="div1">
        <div class="div1"></div>
        <div class="div1"></div>
        <div class="div1"></div>
        <div class="div1"></div>
    </div>
    <div class="div2"></div>

执行$("#div1 .div1").wrap($(".div2"));之后就得到了

    <div id="div1">
        <div class="div2"><div class="div1"></div></div>
        <div class="div2"><div class="div1"></div></div>
        <div class="div2"><div class="div1"></div></div>
        <div class="div2"><div class="div1"></div></div>
    </div>
    <div class="div2"></div>

由于wrap内部调用了$.clean来处理传进来的参数

因此$.clean能处理的参数形式wrap也能处理

但是传进来的参数一定不能是带有文本节点的,即不能传进来类似

<div>
    abc
</div>

因为wrap内部拿到这个DOM参数对象之后会一直遍历到这个DOM元素的最里层

在最里层append调用wrap方法的选择器中的对象

如果传入文本节点,肯定没办法append了

(10)domManip append prepend before after

append prepend before after都是调用了domManip,所以这是一个核心方法

    domManip: function(args, table, dir, fn){
        var clone = this.size() > 1;
        var a = jQuery.clean(args);
        
        return this.each(function(){
            var obj = this;
            
            if ( table && this.nodeName == "TABLE" && a[0].nodeName != "THEAD" ) {
                var tbody = this.getElementsByTagName("tbody");

                if ( !tbody.length ) {
                    obj = document.createElement("tbody");
                    this.appendChild( obj );
                } else
                    obj = tbody[0];
            }

            for ( var i = ( dir < 0 ? a.length - 1 : 0 );
                i != ( dir < 0 ? dir : a.length ); i += dir ) {
                    fn.apply( obj, [ clone ? a[i].cloneNode(true) : a[i] ] );
            }
        });
    },

再看它的调用形式

    append: function() {
        return this.domManip(arguments, true, 1, function(a){
            this.appendChild( a );
        });
    },

参数arguments应该是一个形如["<div><div>"]或者[oDiv]或者[$("#div")]的数组

再通过clean方法的处理变成一个纯粹的数组

第二个参数true/false代表是否对<table>做处理

dir代表domManip里面的循环是正序还是倒序

最后一个参数function的话是对当前调用domManip的this实例化对象选择到的元素集合的各项做的操作

从append里面调用domManip的最后一个参数的情况来看

这个函数将来在调用的时候一定会通过apply或call改变其内部this的指向

因为这个function直接调用的话,this必然指向window

但是window是没有appendChild这个方法的

因此this的指向一定会改成遍历到的DOM对象

再看domManip的源码

这个domManip的第一行就让我废了半天劲去看clone这个变量到底有什么用

结果也没有发现,目前猜测是为了兼容低版本浏览器

因为clone里面存储的是要执行append的jQuery对象选择到的元素的长度

而这个clone变量到了最后遍历数组a,将a中的每个元素都添加到每个元素下面的时候才用到

如果clone是true的话(即选择器至少2个元素)就将待添加的节点新克隆一个再append进去

如果clone是false的话(即选择器只有1个元素)就将待添加的节点直接append进去

经过测试没什么区别

中间对args中的表格元素的情况作了处理

再回头来看append和prepend

append内部调用的是appendChild方法,是直接往后面添加的

而prepend内部调用的是insertBefore方法,是往最前面添加的

挨个添加时append顺序添加就可以了,所以参数dir为1

而prepend的时候要想按顺序添加上去就需要先将最后一个元素insertBefore到最前面

再将倒数第二个元素insertBefore到最前面

以此类推,知道添加完

before和after也是差不多的

(11)end 字面上看起来好像是获取选择器中所有元素的最后一个元素,不过直接调用的话会报错,从源码中也很容易看出错误的原因:this.stack没有值

所以目测end方法是要和其他方法配合使用

(12)pushStack

pushStack是实例化方法里面继domManip之后的另一个重要的核心方法

    pushStack: function(a,args) {
        var fn = args && args[args.length-1];

        if ( !fn || fn.constructor != Function ) {
            if ( !this.stack ) this.stack = [];
            this.stack.push( this.get() );
            this.get( a );
        } else {
            var old = this.get();
            this.get( a );
            if ( fn.constructor == Function )
                return this.each( fn );
            this.get( old );
        }

        return this;
    }

 分析了一下pushStack的调用方式,大概见到以下几种:

find方法中:this.pushStack([oDiv],[".div1"]);

clone方法中:this.pushStack([oDiv],[true]);

filter方法中:this.pushStack([oDiv]);

not方法中:this.pushStack([oDiv],".div1");以及this.pushStack([oDiv],document.getElementById("div8"));

add方法中:this.pushStack([oDiv],[[$("#div1"),$("#div2")]]);

通过以上分析,发现pushStack的功能正如它的名字——入栈

每次在一个jQuery对象上进行add find filter等操作时,都会把当前的jQuery对象push到stack属性中

stack属性值中的最后一项就是最近一次操作的效果

不过里面调用的this.get(0)和this.get(a)不太清楚是做什么的,而且经过this.get(0)这么一处理,上面的not调用方式也是有问题的

从pushStack这个方法的else分支中发现貌似第二个参数还可以传入函数

但是这种方式目前还没有见过在哪里用到过

至此为止的话,最开始初始化的这些实例化方法基本上都分析的差不多了

posted on 2015-11-24 11:43  特拉法尔加  阅读(203)  评论(0编辑  收藏  举报