jQuery源代码解析(1)—— jq基础、data缓存系统

闲话

jquery 的源代码已经到了1.12.0版本号。据官网说1版本号和2版本号若无意外将不再更新,3版本号将做一个架构上大的调整。但预计能兼容IE6-8的。或许这已经是最后的样子了。

我学习jq的时间非常短,应该在1月。那时的版本号还是1.11.3,通过看妙味课堂的公开课视频和文档里的全部api的注解学习。

源代码则是近期些日子直接生啃。跳过了sizzle和文档处理的部分(待业狗压力大。工作以后再看)。关注datareadyeventqueueDefferred(jq的promise编程)、ajaxanimation的处理,初看甚至有点恶心。耐着性子坚持却叹其精妙。在这里记录下来加深印象。

(本文採用 1.12.0 版本号进行解说,用 #number 来标注行号)

jQuery初始化

总体封装上,考虑了两点(写轮子的重要參考)

模块化支持:通过 noGlobal 变量来决定是否绑定到全局变量,返回值为jQuery

冲突避免:保存window.$ window.jQuery,能够调用noConflict 还原,返回jQuery对象。(noGlobal为true时不须要)

主体使用了例如以下结构。抽离 if 分支。仅仅关注主体逻辑的书写

(function( a, fun ) {
    // 推断是否调用、怎样调用fun
})(a, function( _a, _b ) {
    // 详细逻辑
});

/* ---- 差别于例如以下模式 ----*/
(function( _a, _b) {
    if (推断A) {
        // 调整參数或退出
    } else if (推断B) {
        // 调整參数或退出
    } ...
    // 这里開始时详细逻辑
})( a );

[源代码]

(function( global, factory ) {

    if ( typeof module === "object" && typeof module.exports === "object" ) {
        module.exports = global.document ?
            factory( global, true ) :
            // 若无window,则模块存为函数,可取出通过传入window来调用一次
            // noGlobal为false,一定会污染全局
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }
})(typeof window !== "undefined" ?

window : this, function( window, noGlobal ) {
    /* ---- jQuery详细逻辑 ----*/
    ...
    // 对AMD的支持,#10991
    if ( typeof define === "function" && define.amd ) {
        define( "jquery", [], function() {
            return jQuery;
        } );
    }
    // 保存之前的 window.$   window.jQuery
    var _jQuery = window.jQuery,
        _$ = window.$;

    jQuery.noConflict = function() {
        if ( window.$ === jQuery ) {
            window.$ = _$;
        }
        if ( deep && window.jQuery === jQuery ) {
            window.jQuery = _jQuery;
        }
        return jQuery;
    };
    // 模块化时,不设置全局
    if ( !noGlobal ) {
        window.jQuery = window.$ = jQuery;
    }
    return jQuery;
});

jQuery工厂结构

jq为了能有$.func、 $().func两种调用方法,选择了共享 jQuery.prototypereturn new jQuery.fn.init

这并非唯一的方式(能够例如以下),之所以选择如此,个人觉得应该是使用频率太高,这样每次能够省掉两次类型推断。

jQuery.fn 我想也是起到简写、别名的作用

jQuery.extend/fn.extend 则决定了jq重要的插件扩展机制

var jQuery = function( selector, context ) {
    if ( this instanceof jQuery) {
        return new jQuery( selector, context );
    }
    // 详细逻辑
}

[源代码]

// #71
var jQuery = function( selector, context ) {
    return new jQuery.fn.init( selector, context );
};
// #91, 原型的别名 fn,省字符
jQuery.fn = jQuery.prototype = {
    ...
};
// #175, jQuery 扩展方法extend定义,仅仅填一个參数表示对this进行扩展
jQuery.extend = jQuery.fn.extend = function(){
    ...
};
// #2866, 支持选择器,节点html,element,jq对象,函数
var init = jQuery.init = function( selector, context, root ) {
    ...
};
// #2982, 跟jQuery共享原型对象
init.prototype = jQuery.fn;

jQuery链式调用

精髓:通过 return this , return this.pushStack() , return this.prevObject 实现链式调用、增栈、回溯

[源代码]

// # 122, 创建一层新的堆栈, 并引用 prevObject
pushStack: function( elems ) {
    // merge -> #433, 支持把数组、类数组的0-length项加入到第一个參数
    var ret = jQuery.merge( this.constructor(), elems );

    ret.prevObject = this;
    ret.context = this.context;

    return ret;
};

// #164, 能够回溯上一级 preObject
end: function() {
    return this.preObject || this.constructor();
}

// #3063, 加入到新层 this.constructor()
add: function( selector, context ) {
    return this.pushStack(
        // 去重排序
        jQuery.uniqueSort(
            // 合并到 this.get()
            jQuery.merge( this.get(), jQuery( selector, context ) )
        )
    );
},
addBack: function( selector ) {
    // add将把结果pushStack
    return this.add( selector == null ?

this.preObject :
        // 可做一次过滤
        this.prevObject.filter( selector )
    );
}


看到这,说明你真的是个有耐心的boy了。我们上主菜!


IE内存泄露

讲data缓存前,首先要必须介绍一下IE6-8的内存泄露问题。

IE低版本号dom採用COM组件的形式编写,垃圾清理机制。使用的引用计数的方式,无法正确的处理包括dom元素的环状的循环引用。

即使刷新页面内存也不会被释放。

一般是怎样产生循环引用的呢?

此例中。当前作用域里包括变量a (引用了dom元素),通过绑定事件onclick,使dom引用了事件函数。

可是函数在声明时,会把当前所在活动对象的作用域链保存在自身的scope属性。从而引用了当前环境定义的全部变量。而变量a指向dom。从而产生循环引用。须要手动把变量对dom的引用解除

{ // 某个scope作用域
    var a = document.getElementById(‘id_a‘);
    // dom -> fun
    a.onclick = function(e) {
    };
    // fun -> a -> dom , 解除对dom元素的引用, fun -> a -X- dom
    a = null;
}

jQuery.data原理

jq内部实现了一个缓存机制,用于分离dom对函数等对象的引用(函数对dom的引用取决于函数本身的特性、定义的位置,这个没法子)。怎样能做到呢?与 策略模式 的思路一致——字符串约定。

jQuery.extend({
    // #247, 构造一个基本不可能被占用的属性, 除去"."
    expando: "jQuery" + ( version + Math.random() ).replace(/\D/g, ""),
    // #507, 全局使用的 guid 号码
    guid: 1,
    // #4014, 缓存空间
    cache: {}
});

/* ---- 原理示意 ---- */
var a = document.getElementById("id_a");

// 每一个元素的 jQuery.expando 分到唯一的 guid 号码
a[jQuery.expando] = a[jQuery.expando] || jQuery.guid++;

// jQuery.cache 上元素的 guid 相应的位置为该对象的缓存空间
jQuery.cache[a[jQuery.expando]] = {};

通过 jQuery.data 的封装能够轻松实现数据的存取,可是这样就结束了么?太天真了!

这样做也会产生衍生的问题。尽管不产生循环引用,但对象绑定在jQuery.cache上。即使dom对象被移除。数据仍然不会因此消失。并且假设是函数,仍然会通过引用环境的变量单向引用着dom,导致dom元素也不消失。尽管刷新后内存会清出,不如循环引用严重,但也是问题了。

看起来仍然须要手动设置变量为null,仿佛回到原点,可是数据还在,比之前更不如。解决方法事实上非常easy,就是移除节点时调用 jQuery.cleanData ,data没有被不论什么对象引用,自然的回收。

可是问题仍然没解决,由于比如绑定事件。即使函数放在jQuery.cache中,也至少有一个触发函数绑定在dom上。因此 jQuery.event.add( elem, types, handler, data, selector ) 中的elem返回前被设为null ,见源代码 #4961。所以如非事事通明。尽量使用jq提供的方式删除节点、绑定事件等

function remove( elem, selector, keepData ) { // #6107, #6255为正式方法
    var node,
        elems = selector ?

jQuery.filter( selector, elem ) : elem,
        i = 0;

    for ( ; ( node = elems[ i ] ) != null; i++ ) {

        if ( !keepData && node.nodeType === 1 ) {
            // 清除数据
            jQuery.cleanData( getAll( node ) );
        }

        if ( node.parentNode ) {
            if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
                setGlobalEval( getAll( node, "script" ) );
            }
            // 删除节点
            node.parentNode.removeChild( node );
        }
    }

    return elem;
}

jQuery架构方法

jQuery在此之上考虑了几点:

核心

  1. 对普通对象和dom对象做了区分。由于普通对象的垃圾回收机制(GC)使用的不是引用计数,不会内存泄露。

    其次data机制本身添加了复杂度不说,对象解除引用后绑定的数据还须要手动销毁。反而造成内存的无端占用。

    普通对象绑定在自身的 jQuery.expando 属性上。初始化为 { toJSON: jQuery.noop },防止序列化时暴露数据。

    dom对象绑定在 jQuery.cache[ ele[jQuery.expando] ] ,初始化为 {}

  2. 私用和公用隔离,如 jQuery.cache[ guid ]、jQuery.cache[ guid ].data。内部的事件、队列等等都使用的私有空间调用 _data 方法,而用户储存数据调用的 data 方法则是私有,由參数 pvt 决定,true代表私有
  3. 当移除数据后。jQuery.cache[ guid ]或 jQuery.cache[ guid ].data对象不再有数据。则移除该对象释放内存。

    且当移除缓存对象时,绑定在elem的事件函数也将被移除

特性

  1. 支持通过简单的自己定义配置来添加不支持缓存的特例类型
  2. 扩展支持读取 elem.attributes 中 data- 前缀定义的数据
  3. 以小驼峰书写为标准读取方式,内部进行相应转换

jQuery.data结构

使用常见的外观模式。定义核心功能。通过再封装实现个性化的使用规则,以供其它模块或外部调用

  • 核心功能:(特点:參数多而全,逻辑负责全面)

    jQuery.internalData( elem, name, data, pvt ) 为dom和对象设置data数据。支持 -> 核心12
    jQuery.internalRemoveData( elem, name, pvt ) 移除dom或对象上指定属性的data数据-> 核心3
    jQuery.cleanData( elems, forceAcceptData ) 由于删除data时还要删除elem本身上绑定的触发事件函数,因此不能简单 delete cache[id]。
    单独封装,被事件系统和 jQuery.internalRemoveData使用 ->核心3
    
  • 外观:(工具方法、实例方法)
    jQuery.hasData( elem ) 直接在相应的 `cache` 中查找。
    jQuery._data( elem, name, data )  jQuery.\_removeData( elem, name )`用于内部私有调用。封装了核心方法,简化了传參数量。
    jQuery.data( elem, name, data )  jQuery.removeData( elem, name ) 用于外部公共调用。封装了核心方法,简化了传參数量。
    
    jQuery.fn.data( key, value )  jQuery.fn.removeData( key, value ) 封装了 jQuery.data  jQuery.removeData ,遍历this来分别调用
    
  • 钩子:埋在某个环节直接运行,依据须要实现终止流程、改变特性的功能,或不产生不论什么影响
    acceptData( elem ) #3762,特性1。过滤
    dataAttr( elem, key, data ) #3780。特性2,data的值不存在则会訪问元素的attribute。返回data,且值会缓存到cache
    jQuery.camelCase( string ) #356,特性3。小驼峰
    

[核心 + 外观 + 钩子] 感觉应该算一种常见好用的架构方式了。

jq中用到了非常多 jQuery.some() -> 核心, jQuery.fn.some() -> 外观 的形式,也都几乎相同。

这里提一下我所理解的钩子,Callback的四个字符串特性就相似于四个钩子。在最常见的几个需求分歧上埋设,发展了观察者模式的4种特性支持,event的众多钩子 标准方式转换 + 个例bug修正 完毕了兼容的大业与主逻辑的统一。

另外不得不提一个在此维度之外的优化方案 —— 提炼反复代码

核心逻辑是不可省的。通常支持数种方式定义參数,把其变化为某种固定形式的參数传入来运行递归是分离函数内部个性化与核心逻辑的第一步,还能够进一步,抽出此部分逻辑。保持主逻辑的纯粹,变成外观部分内的逻辑进行 功能增强。jq再进了一步,由于大量方法都支持參数推断存取、对象或字符串均可、map映射,分离出 access( elems, fn, key, value, chainable, emptyGet, raw ) #4376,核心 jQuery.xxx(),外观jQuery.fn.xxx() 里调用 access`。

// access( elems, fn, key, value, chainable, emptyGet, raw )
// 直观语义是让forEach( elems, fn( elem, key, value ) ), 支持类型推断实现映射等功能

css: function( name, value ) {  // #7340, 任意举的样例

        // 第二个參数要么是 jQuery.someThing
        // 或者是封装了 jQuery.someThing.apply( ... ) 的函数。内含
        return access( this, function( elem, name, value ) {
            var styles, len,
                map = {},
                i = 0;

            // 新增一样仅仅针对css的个性化參数处理
            if ( jQuery.isArray( name ) ) {
                styles = getStyles( elem );
                len = name.length;

                for ( ; i < len; i++ ) {
                    map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
                }

                return map;
            }

            return value !== undefined ?
                jQuery.style( elem, name, value ) :
                jQuery.css( elem, name );
        }, name, value, arguments.length > 1 );
    }

可是,data缓存系统并没有这样使用。

原因也非常easy,基础的共性部分支持不同不支持映射,如上面的css是共性同样的情况下能够添加个性,但不同的情况就要又一次抽出、或写在外观里、或写在核心代码里使用递归。

[源代码]

/* ---------------------------------- 1. 相关代码(扫一下) ---------------------------------- */

jQuery.extend({
    // #247, 构造一个基本不可能被占用的属性, 除去"."
    expando: "jQuery" + ( version + Math.random() ).replace(/\D/g, ""),
    // #507, 全局使用的 guid 号码
    guid: 1,
    // #4014, 缓存空间
    cache: {},
    noData: {
        // 为 true 的不能够
        "applet ": true,
        "embed ": true,

        // ...but Flash objects (which have this classid) *can* handle expandos
        "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
    },
});

// #3748, support.deleteExpando
( function() {
    var div = document.createElement( "div" );

    // Support: IE<9
    support.deleteExpando = true;
    try {
        delete div.test;
    } catch ( e ) {
        support.deleteExpando = false;
    }

    // Null elements to avoid leaks in IE.
    div = null;
} )();

// #284, 可用于检測 elem 的公有 cache.data 是否已空
jQuery.isEmptyObject = function( obj ) {
    var name;
    for ( name in obj ) {
        return false;
    }
    return true;
};

// #3814, 检測 elem 的私有 cache 缓存是否已空
function isEmptyDataObject( obj ) {
    var name;
    for ( name in obj ) {

        // if the public data object is empty, the private is still empty
        if ( name === "data" && jQuery.isEmptyObject( obj[ name ] ) ) {
            continue;
        }
        if ( name !== "toJSON" ) {
            return false;
        }
    }

    return true;
}

/* ---------------------------------- 2. 钩子(瞅瞅) ---------------------------------- */

// #3762
var acceptData = function( elem ) {

    // 比对禁止列表 jQuery.noData, 为 true 必定不能
    var noData = jQuery.noData[ ( elem.nodeName + " " ).toLowerCase() ],
        // 普通对象都会按 nodeType = 1 处理,通过筛选
        nodeType = +elem.nodeType || 1;

    return nodeType !== 1 && nodeType !== 9 ?

false :

        // noData不存在(黑名单) 或 存在且classid与noDta同样但不为true(白名单)
        !noData || noData !== true && elem.getAttribute( "classid" ) === noData;
};

function dataAttr( elem, key, data ) {

    // 正确姿势: dataAttr( elem, key, jQuery.data( elem )[ key ] );
    // data 不存在。则查找 HTML5 data-* attribute,并存入 jQuery.data( elem, key, data )
    // 返回 data
    if ( data === undefined && elem.nodeType === 1 ) {

        var name = "data-" + key.replace( /([A-Z])/g, "-$1" ).toLowerCase();

        data = elem.getAttribute( name );

        if ( typeof data === "string" ) {
            try {
                // "true" -> true , "false" -> false , "23" -> 23
                // "{ \"a\": 1}" -> {"a": 1}, "[1, ‘2‘]" -> [1, ‘2‘]
                // 或 data 本身
                data = data === "true" ?

true :
                    data === "false" ?

false :
                    data === "null" ? null :
                    +data + "" === data ? +data :
                    /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/.test( data ) ?

jQuery.parseJSON( data ) :
                    data;
            } catch ( e ) {}

            // 保存
            jQuery.data( elem, key, data );

        } else {
            data = undefined;
        }
    }

    return data;
}

// #356, 正则变量替换了,本身在 #80
jQuery.camelCase = function( string ) {
    // -ms-abc -> msAbc , a-abc -> aAbc
    return string.replace( /^-ms-/, "ms-" ).replace( /-([\da-z])/gi, fcamelCase );
};

/* ---------------------------------- 3. 核心(关键) ---------------------------------- */

// #3830, 加入缓存
function internalData( elem, name, data, pvt /* true 为私,存cache;false 为公。存cache.data */ ) {
    // 钩子。黑白名单
    if ( !acceptData( elem ) ) {
        return;
    }

    var ret, thisCache,
        internalKey = jQuery.expando,

        // dom 和 object 区分对待
        isNode = elem.nodeType,

        // object 不缓存,存自身 jQuery.expando 属性上
        cache = isNode ?

jQuery.cache : elem,

        // 没设置过说明 第一次
        id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;

    // 1. 第一次仅仅能写,不能读。否则 return
    if ( ( !id || !cache[ id ] || ( !pvt && !cache[ id ].data ) ) &&
        data === undefined && typeof name === "string" ) {
        return;
    }

    // 2. 第一次写。先初始化 [ 属性、缓存对象 cache ]
    // dom -> jQuery.cache[ elem[ internalKey ] ]。object -> object[internalKey]
    if ( !id ) {

        // Only DOM nodes need a new unique ID for each element since their data
        // ends up in the global cache
        if ( isNode ) {
            id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
        } else {
            id = internalKey;
        }
    }

    if ( !cache[ id ] ) {
        cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
    }

    // 3. 写入与读取
    // write特例:支持 name 參数为 { "key" : value } 或 函数。存入数据
    if ( typeof name === "object" || typeof name === "function" ) {
        // 公私不同
        if ( pvt ) {
            cache[ id ] = jQuery.extend( cache[ id ], name );
        } else {
            cache[ id ].data = jQuery.extend( cache[ id ].data, name );
        }
    }

    thisCache = cache[ id ];

    // thisCache 依据pvt索引到了应该被赋值的对象
    if ( !pvt ) {
        if ( !thisCache.data ) {
            thisCache.data = {};
        }

        thisCache = thisCache.data;
    }

    // 写入,小驼峰为标准
    if ( data !== undefined ) {
        thisCache[ jQuery.camelCase( name ) ] = data;
    }

    // Check for both converted-to-camel and non-converted data property names
    // If a data property was specified
    if ( typeof name === "string" ) {

        // 这不是重点。也能够读非驼峰。正常使用并不会有
        ret = thisCache[ name ];

        if ( ret == null ) {

            // 读这里。不管读写,都要读一次
            ret = thisCache[ jQuery.camelCase( name ) ];
        }
    } else {
        // 无 name , 直接读 cache
        ret = thisCache;
    }

    return ret;
}

// #3922, 删除 公/私 缓存属性 或 缓存。

// 删完属性若变空cache,将移去。会处理掉 elem 上 绑定的 event
function internalRemoveData( elem, name, pvt ) {

    // 钩子,黑名单者,直接 return
    if ( !acceptData( elem ) ) {
        return;
    }

    var thisCache, i,
        isNode = elem.nodeType,
        cache = isNode ?

jQuery.cache : elem,
        id = isNode ?

elem[ jQuery.expando ] : jQuery.expando;

    // 缓存空间未初始化,return
    if ( !cache[ id ] ) {
        return;
    }

    // 1. name 存在,删除 name 属性值
    if ( name ) {

        thisCache = pvt ? cache[ id ] : cache[ id ].data;

        if ( thisCache ) {

            // 1.1 支持数组定义多属性,此处把字符串形式也转为数组[name]
            // next step: 统一迭代删除
            if ( !jQuery.isArray( name ) ) {

                // 这不是重点。也能够读非驼峰。

正常使用并不会有
                if ( name in thisCache ) {
                    name = [ name ];
                } else {

                    // 看这里,转换为小驼峰读
                    name = jQuery.camelCase( name );
                    if ( name in thisCache ) {
                        name = [ name ];
                    } else {
                        // 能够字符串空格隔开多个,均变成小驼峰
                        name = name.split( " " );
                    }
                }
            } else {

                // If "name" is an array of keys...
                // When data is initially created, via ("key", "val") signature,
                // keys will be converted to camelCase.
                // Since there is no way to tell _how_ a key was added, remove
                // both plain key and camelCase key. #12786
                // This will only penalize the array argument path.
                name = name.concat( jQuery.map( name, jQuery.camelCase ) );
            }

            // 1.2 删
            i = name.length;
            while ( i-- ) {
                delete thisCache[ name[ i ] ];
            }

            // 1.3 假设 cache 删除后没空。结束 return
            // 假设空了, 与 name 不存在的情况一样直接删除 data
            if ( pvt ? !isEmptyDataObject( thisCache ) : !jQuery.isEmptyObject( thisCache ) ) {
                return;
            }
        }
    }

    // 2. 依据 pvt 推断,false 删除公有
    if ( !pvt ) {
        delete cache[ id ].data;

        // cache 还没空,能够闪了,return
        // cache 空了,合并到 pvt 为true,私有cache 删除
        if ( !isEmptyDataObject( cache[ id ] ) ) {
            return;
        }
    }

    // 3. pvt 为 true, 删除私有 cache
    // 3.1 为节点时。若cache[events]里还有事件,把 elem 绑定的事件函数删除
    if ( isNode ) {
        jQuery.cleanData( [ elem ], true );

    // 3.2 普通对象时
    } else if ( support.deleteExpando || cache != cache.window ) {
        // 能删则 delete
        delete cache[ id ];

    // 不能删, undefined
    } else {
        cache[ id ] = undefined;
    }
}

// #6192, 函数内包括 删除事件队列时删除elem相应type的绑定事件 的功能
// cleanData 不仅被 internalRemoveData 内部调用,remove节点的时候也$().remove调用,因此支持了elems 数组
jquery.cleanData = function( elems, /* internal */ forceAcceptData ) {
    var elem, type, id, data,
        i = 0,
        internalKey = jQuery.expando,
        cache = jQuery.cache,
        attributes = support.attributes,
        special = jQuery.event.special;

    // 支持 elems 数组迭代
    for ( ; ( elem = elems[ i ] ) != null; i++ ) {
        // 钩子
        if ( forceAcceptData || acceptData( elem ) ) {

            id = elem[ internalKey ];
            data = id && cache[ id ];

            if ( data ) {
                // 1. 存在事件队列
                if ( data.events ) {

                    // 迭代,删除绑在 elem 上的触发函数
                    for ( type in data.events ) {

                        // 尽管绑定在data上的事件。都转换成标准的 eventType
                        // 但标准的 eventtype 可能不被兼容
                        // special.setup 钩子在绑定触发函数时会hack一次
                        // 须要该方法找到绑定在elem上事件触发函数的真正类型并删除
                        if ( special[ type ] ) {
                            jQuery.event.remove( elem, type );

                        // 普通情况,直接删除
                        } else {
                            jQuery.removeEvent( elem, type, data.handle );
                        }
                    }
                }

                // 2. 不存在(或已删去)事件队列
                if ( cache[ id ] ) {

                    delete cache[ id ];

                    // Support: IE<9
                    // IE does not allow us to delete expando properties from nodes
                    // IE creates expando attributes along with the property
                    // IE does not have a removeAttribute function on Document nodes
                    if ( !attributes && typeof elem.removeAttribute !== "undefined" ) {
                        elem.removeAttribute( internalKey );

                    } else {
                        elem[ internalKey ] = undefined;
                    }

                    deletedIds.push( id );
                }
            }
        }
    }
}

/* ---------------------------------- 4. 外观(接口API) ---------------------------------- */

// #4013
jQuery.extend( {
    // cache、noData 上面已经提前写了
    cache: {},
    noData: {
        "applet ": true,
        "embed ": true,
        "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
    },

    // 直接从缓存 cache 查找推断。dom 与 object差别对待
    hasData: function( elem ) {
        elem = elem.nodeType ?

jQuery.cache[ elem[ jQuery.expando ] ] : elem[ jQuery.expando ];
        return !!elem && !isEmptyDataObject( elem );
    },

    // 公用。pvt 无
    data: function( elem, name, data ) {
        return internalData( elem, name, data );
    },

    removeData: function( elem, name ) {
        return internalRemoveData( elem, name );
    },

    // 私用,pvt true
    _data: function( elem, name, data ) {
        return internalData( elem, name, data, true );
    },

    _removeData: function( elem, name ) {
        return internalRemoveData( elem, name, true );
    }
} );

// #4049,实例方法
jQuery.fn.extend( {
    data: function( key, value ) {
        var i, name, data,
            elem = this[ 0 ],
            attrs = elem && elem.attributes;

        // Special expections of .data basically thwart jQuery.access,
        // so implement the relevant behavior ourselves

        // 1. key不存在。

获得 data缓存 全部值
        if ( key === undefined ) {
            if ( this.length ) {
                data = jQuery.data( elem );

                if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
                    i = attrs.length;
                    while ( i-- ) {

                        // Support: IE11+
                        // The attrs elements can be null (#14894)
                        if ( attrs[ i ] ) {
                            name = attrs[ i ].name;
                            if ( name.indexOf( "data-" ) === 0 ) {
                                name = jQuery.camelCase( name.slice( 5 ) );

                                // 钩子,data[name]若无。则搜索data- attribute,并赋值给 data[ name ]
                                dataAttr( elem, name, data[ name ] );
                            }
                        }
                    }
                    jQuery._data( elem, "parsedAttrs", true );
                }
            }

            return data;
        }

        // 2. key 是 "object"
        // internalData 已经支持了 "object" 參数。因此直接迭代
        if ( typeof key === "object" ) {
            return this.each( function() {
                jQuery.data( this, key );
            } );
        }

        return arguments.length > 1 ?

// 3. 迭代写入 key -> value
            this.each( function() {
                jQuery.data( this, key, value );
            } ) :

            // 4. 读取 key 属性值, 没有则尝试读data- attribute,并赋值给 data[ name ]
            elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
    },

    removeData: function( key ) {
        return this.each( function() {
            jQuery.removeData( this, key );
        } );
    }
} );

最后再说几点:

  1. $(elem).data("key", "name")$.data($(elem), "key", "name") 的差别是前者内部元素被迭代。绑定在元素(dom)上,而后者绑定在 $(elem) 对象(object)上,差别不言自明。
  2. 对于支持对象等多种參数形式的逻辑本身很多其它放在外观里。这里在 internalData。由于公有私有不止一个外观,避免反复要么抽出相似 access 使用。要么放到公共方法中。而之所以不放在终于的实例 data 方法中,由于工具方法已经在jq模块内部被多次使用了,这样能够有效简化内部操作。
  3. dataAtrr 之所以在实例 data 方法中才出现被使用。是由于仅仅实用户调用的时候dom才载入完呀,才会产生这个须要。
时间: 2024-11-04 17:25:07

jQuery源代码解析(1)—— jq基础、data缓存系统的相关文章

jQuery源代码解析(3)—— ready载入、queue队列

ready.queue放在一块写,没有特殊的意思,仅仅是相对来说它俩可能源代码是最简单的了.ready是在dom载入完毕后.以最高速度触发,非常实用. queue是队列.比方动画的顺序触发就是通过默认队列'fx'处理的. (本文採用 1.12.0 版本号进行解说,用 #number 来标注行号) ready 非常多时候,我们须要尽快的载入一个函数,假设里面含有操作dom的逻辑,那么最好在dom刚刚载入完毕时调用.window的load事件会在页面中的一切都载入完毕时(图像.js文件.css文件.

Spark MLlib LDA 源代码解析

1.Spark MLlib LDA源代码解析 http://blog.csdn.net/sunbow0 Spark MLlib LDA 应该算是比較难理解的,当中涉及到大量的概率与统计的相关知识,并且还涉及到了Spark GraphX图计算方面的知识.要想明确当中的原理得要下一番功夫. LDA源代码解析前的基础知识: 1)LDA主题模型的理论知识 參照:LDA数学八卦 2)SparkGraphX 基础知识 http://blog.csdn.net/sunbow0/article/details/

jQuery源代码学习之六——jQuery数据缓存Data

一.jQuery数据缓存基本原理 jQuery数据缓存就两个全局Data对象,data_user以及data_priv; 这两个对象分别用于缓存用户自定义数据和内部数据: 以data_user为例,所有用户自定义数据都被保存在这个对象的cache属性下,cache在此姑且称之为自定义数据缓存: 自定义数据缓存和DOM元素/javascript对象通过id建立关联,id的查找通过DOM元素/javascript元素下挂载的expando属性获得 话不多说,直接上代码.相关思路在代码注释中都有讲解

jQuery.data的是jQuery的数据缓存系统

jQuery.Data源码 jQuery.data的是jQuery的数据缓存系统.它的主要作用就是为普通对象或者DOM元素添加数据. 1 内部存储原理 这个原理很简单,原本要添加在DOM元素本身的数据,现在被集中的存储在cache集合中.它们之间靠一个从1开始的数字键来联系着.这样DOM元素就不会像以前那么笨重了,更不会出现以前那种循环引用而引起的内存泄漏.现在DOM只需要保存好这个数字键值即可.这个属性值被保存在DOM元素的一个属性里,该属性名是由jQuery.expando生成的. 2 Da

jQuery源码解读 - 数据缓存系统:jQuery.data

jQuery在1.2后引入jQuery.data(数据缓存系统),主要的作用是让一组自定义的数据可以DOM元素相关联——浅显的说:就是让一个对象和一组数据一对一的关联. 一组和Element相关的数据如何关联着这个Element一直是web前端的大姨妈,而最初的jQuery事件系统照搬Dean Edwards的addEvent.js:将回调挂载在EventTarget上,这样下来,循环引用是不可忽视的问题.而在web前端中,数据和DOM的关系太过基情和紧张,于是jQuery在1.2中,正式缔造了

读jQuery源码之六(Data缓存和Queue队列)

源码3523-3706 Data对象及原型定义 1.accessData方法和Data函数.代码如下: jQuery.acceptData = function( owner ) { // Accepts only: // - Node // - Node.ELEMENT_NODE // - Node.DOCUMENT_NODE // - Object // - Any /* jshint -W018 */ return owner.nodeType === 1 || owner.nodeTyp

jquery源码之缓存系统--$.data

jQuery内置了一个缓存系统,它做的事情可就多了,动画模块,事件模块等非常有用的模块都是依赖它实现的. 其实说到底,就是在jQuery的命名空间下开辟了一个cache的对象. 将数据以一定得规则存放的cache对象中. 首先我们来看看内部实现: jQuery.extend({ cache: {}, deletedIds: [], // Remove at next major release (1.9/2.0) uuid: 0, // Unique for each copy of jQuer

Android源代码解析之(七)--&amp;gt;LruCache缓存类

转载请标明出处:一片枫叶的专栏 android开发过程中常常会用到缓存.如今主流的app中图片等资源的缓存策略通常是分两级.一个是内存级别的缓存,一个是磁盘级别的缓存. 作为android系统的维护者google也开源了其缓存方案,LruCache和DiskLruCache.从android3.1開始LruCache已经作为android源代码的一部分维护在android系统中.为了兼容曾经的版本号android的support-v4包也提供了LruCache的维护,假设App须要兼容到andr

大数据基础(1)zookeeper源代码解析

五 源代码解析 public enum ServerState { LOOKING, FOLLOWING, LEADING, OBSERVING;}zookeeper服务器状态:刚启动LOOKING,follower是FOLLOWING,leader是LEADING,observer是OBSERVING: public enum LearnerType { PARTICIPANT, OBSERVER;} 简单来说,zookeeper启动的核心类是QuorumPeerMain,启动之后会加载配置,