博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
jQuery源码解析之Data
阅读量:6207 次
发布时间:2019-06-21

本文共 12356 字,大约阅读时间需要 41 分钟。

jQuery源码学习之data

jQuery中有两个关于data操作的方法

$().data()$.data(elem);

内部其实现均离不开自定义类Data

内部类 Data

Datasrc/data/Data.js定义,构建时为实例添加expando属性,作为唯一标识

function Data() {    this.expando = jQuery.expando + Data.uid++;}

在原型上添加了多个方法

Data.prototype = {    cache: function(){        ...    },    set: function(){        ...    },    get: function(){        ...    },    access: function(){        ...    },    remove: function(){        ...    },    hasData: function(){        ...    }}

在jq内部,使用cache方法获取缓存的数据。传入一个参数owner,表示要获取缓存数据的对象。判断在owner上是否有expando属性,如果没有,说明这个owner是否第一次调用,需要在其初始化缓存数据对象。判断节点的类型,如果是元素节点或者document节点或者对象时,可以设置缓存数据。如果是元素节点或者document节点,直接使用对象字面量进行赋值,属性名是expando,值为空对象。如果是对象的话,使用Object.defineProperty为其定义数据,属性名也是expando,初始化为{},同时属性描述符可以更改,不可枚举。

Data.prototype.cache

cache: function( owner ) {        // Check if the owner object already has a cache        // 获取在owner的缓存值        var value = owner[ this.expando ];        // If not, create one        if ( !value ) {            value = {};            // We can accept data for non-element nodes in modern browsers,            // but we should not, see #8335.            // Always return an empty object.            // 判断owener类型 是否能在其上调用data            // 在元素节点或body或对象上可以设置data            // 其他节点不设置缓存数据            if ( acceptData( owner ) ) {                // If it is a node unlikely to be stringify-ed or looped over                // use plain assignment                // 此处为owner添加属性 key为Data对象的expando值 建立owner和Data对象之间的连接                // owner是元素节点或body                if ( owner.nodeType ) {                    owner[ this.expando ] = value;                // Otherwise secure it in a non-enumerable property                // configurable must be true to allow the property to be                // deleted when data is removed                // owner是对象                // 为owner添加expando属性 初始化为{},同时属性描述符可以更改,不可枚举                } else {                    Object.defineProperty( owner, this.expando, {                        value: value,                        configurable: true                    } );                }            }        }        return value;    }

使用set来更新缓存对象,分为data(key,value)data(obj)两种调用情况,保存时要将键名保存为驼峰命名法。

Data.prototype.set

set: function( owner, data, value ) {        var prop,            cache = this.cache( owner );        // Handle: [ owner, key, value ] args        // Always use camelCase key (gh-2257)        if ( typeof data === "string" ) {            cache[ jQuery.camelCase( data ) ] = value;        // Handle: [ owner, { properties } ] args        } else {            // Copy the properties one-by-one to the cache object            for ( prop in data ) {                cache[ jQuery.camelCase( prop ) ] = data[ prop ];            }        }        return cache;    }

使用get来获取缓存对象,调用时有data(key)data()。未指定key时直接返回整个cache对象,否则返回cache[key]。键名也要转为驼峰命名。

Data.prototype.get

get: function( owner, key ) {        return key === undefined ?            this.cache( owner ) :            // Always use camelCase key (gh-2257)            owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];    }

对调用的方式进行区分,内部调用 setget

通过参数的数量和类型进行区分:

  • key为空时,获取整个cache对象
  • key类型为stringvalue===undefined 对应获取指定值
  • 其他调用均为set,在set内部进行区分

Data.prototype.access

access: function( owner, key, value ) {        // In cases where either:        //        //   1. No key was specified        //   2. A string key was specified, but no value provided        //        // Take the "read" path and allow the get method to determine        // which value to return, respectively either:        //        //   1. The entire cache object        //   2. The data stored at the key        //        if ( key === undefined ||                ( ( key && typeof key === "string" ) && value === undefined ) ) {            return this.get( owner, key );        }        // When the key is not a string, or both a key and value        // are specified, set or extend (existing objects) with either:        //        //   1. An object of properties        //   2. A key and value        //        this.set( owner, key, value );        // Since the "set" path can have two possible entry points        // return the expected data based on which path was taken[*]        return value !== undefined ? value : key;    }

使用remove来删除缓存对象属性,调用时,可以传入一个string,表示要删除的键名,或者传入一个保存多个键名的string数组。键名也要转为驼峰命名。如果不传入出参数,则直接删除掉在owner上的缓存数据对象。

Data.prototype.remove

remove: function( owner, key ) {        var i,            cache = owner[ this.expando ];        if ( cache === undefined ) {            return;        }        if ( key !== undefined ) {            // Support array or space separated string of keys            if ( Array.isArray( key ) ) {                // If key is an array of keys...                // We always set camelCase keys, so remove that.                key = key.map( jQuery.camelCase );            } else {                key = jQuery.camelCase( key );                // If a key with the spaces exists, use it.                // Otherwise, create an array by matching non-whitespace                key = key in cache ?                    [ key ] :                    ( key.match( rnothtmlwhite ) || [] );            }            i = key.length;            while ( i-- ) {                delete cache[ key[ i ] ];            }        }        // Remove the expando if there's no more data        if ( key === undefined || jQuery.isEmptyObject( cache ) ) {            // Support: Chrome <=35 - 45            // Webkit & Blink performance suffers when deleting properties            // from DOM nodes, so set to undefined instead            // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)            if ( owner.nodeType ) {                owner[ this.expando ] = undefined;            } else {                delete owner[ this.expando ];            }        }    }

判断owner上是否有缓存数据。

Data.prototype.hasData

hasData: function( owner ) {        var cache = owner[ this.expando ];        return cache !== undefined && !jQuery.isEmptyObject( cache );    }

jQuery方法的定义

定义后内部类data后,在/src/data.js进行拓展。在jQuery添加了hasDatadataremoveData_data_removeData等方法,在jQuery.fn上添加了dataremoveData方法。

在jQuery拓展的方法,都是对Data方法的封装,在调用时$.data()时,并无对setget操作区分,在Data.prototype.access内部区分set和get。下面源码中,dataUserdataPrivData实例,分别为缓存数据和表示jq对象是否将元素的data属性添加到dataUser

// $.datajQuery.extend( {    hasData: function( elem ) {        return dataUser.hasData( elem ) || dataPriv.hasData( elem );    },    data: function( elem, name, data ) {        return dataUser.access( elem, name, data );    },    removeData: function( elem, name ) {        dataUser.remove( elem, name );    },    // TODO: Now that all calls to _data and _removeData have been replaced    // with direct calls to dataPriv methods, these can be deprecated.    _data: function( elem, name, data ) {        return dataPriv.access( elem, name, data );    },    _removeData: function( elem, name ) {        dataPriv.remove( elem, name );    }} );

jQuery.fn上拓展的方法只有dataremoveData。在data内,先对$().data()$().data({k:v})两个调用情况进行处理。如果是第一种情况,则返回在this[0]上的缓存数据对象,如果是第一次以$().data()的方式调用,同时还会将元素上的data属性添加dataUser中,并更新dataPriv。如果是$().data({k:v})的调用方式,则遍历jq对象,为每个节点更新缓存数据。其他调用方式如$().data(k)$().data(k,v)则调用access进行处理。此处的access并非Data.prototype.access

removeData则遍历jq对象,删除在每个节点上的缓存数据。

// $().datajQuery.fn.extend( {    data: function( key, value ) {        var i, name, data,            elem = this[ 0 ], // elem为dom对象            attrs = elem && elem.attributes; // 节点上的属性        // Gets all values        // $().data()        if ( key === undefined ) {            if ( this.length ) {                data = dataUser.get( elem );                // elem是元素节点,且dataPriv中无hasDataAttrs时执行这个代码块里的代码                // dataPriv上的hasDataAttrs表示elem是否有data-xxx属性                // 初始化dataPriv后花括号内的代码不再执行,即以下的if内的代码只执行一次                if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {                    i = attrs.length;                    while ( i-- ) {                        // Support: IE 11 only                        // The attrs elements can be null (#14894)                        // 将elem的data-*-*属性以*-*转为驼峰命名方式的值为键                        // 在dataAttr函数内保存到dataUser中                        // 所以用$().data可以获取元素的data-*属性 但修改后dom上的属性却不变化                        if ( attrs[ i ] ) {                            name = attrs[ i ].name;                            // name为data-xxx                            if ( name.indexOf( "data-" ) === 0 ) {                                // name为xxx                                name = jQuery.camelCase( name.slice( 5 ) );                                dataAttr( elem, name, data[ name ] );                            }                        }                    }                    // 同时将dataPriv的hasDataAttrs属性设置为真,表示已经将元素属性节点上的data属性保存到缓存对象中                    dataPriv.set( elem, "hasDataAttrs", true );                }            }            return data;        }                // Sets multiple values        //  $().data(obj) 此处遍历this,即遍历jq对象上所有的节点,并在其设置值        if ( typeof key === "object" ) {            return this.each( function() {                dataUser.set( this, key );            } );        }        // 除了$().data()和$().data({k:v})的其他情况        return access( this, function( value ) {            var data;            // The calling jQuery object (element matches) is not empty            // (and therefore has an element appears at this[ 0 ]) and the            // `value` parameter was not undefined. An empty jQuery object            // will result in `undefined` for elem = this[ 0 ] which will            // throw an exception if an attempt to read a data cache is made.            // value undefined说明是获取操作            // 调用$().data(k)            if ( elem && value === undefined ) {                // Attempt to get data from the cache                // The key will always be camelCased in Data                // 从dataUser中获取 非undefined时返回                data = dataUser.get( elem, key );                if ( data !== undefined ) {                    return data;                }                // Attempt to "discover" the data in                // HTML5 custom data-* attrs                // dataUser中不存在key,调用dataAttr查找元素的data-*属性                // 如果存在属性,更新dataUser并返回其值                data = dataAttr( elem, key );                if ( data !== undefined ) {                    return data;                }                // We tried really hard, but the data doesn't exist.                return;            }            // Set the data...            // jq对象长度 >= 1, 调用如$().data(k,v) 遍历jq对象,为每个节点设置缓存数据            this.each( function() {                // We always store the camelCased key                dataUser.set( this, key, value );            } );        }, null, value, arguments.length > 1, null, true );    },    // 遍历jq对象,删除各个元素上的缓存数据    removeData: function( key ) {        return this.each( function() {            dataUser.remove( this, key );        } );    }} );

其中,getData用于对元素上的data属性进行类型转换,dataAttr用于获取保存在元素节点上的data属性,并同时更新dataUser。需要注意的是,以$().data(k, v)方式调用时,如果在缓存数据上查找不到属性,则会调用dataAttr在元素查找属性。

// 属性值是string 进行类型转换function getData( data ) {    if ( data === "true" ) {        return true;    }    if ( data === "false" ) {        return false;    }    if ( data === "null" ) {        return null;    }    // Only convert to a number if it doesn't change the string    // data转化成number再转成string后仍严格等于data    if ( data === +data + "" ) {        return +data;    }    if ( rbrace.test( data ) ) {        return JSON.parse( data );    }    return data;}// 获取元素的dataset中的属性,并保存到dataUser中function dataAttr( elem, key, data ) {    var name;    // If nothing was found internally, try to fetch any    // data from the HTML5 data-* attribute    // 此处获取dataset里的值    if ( data === undefined && elem.nodeType === 1 ) {        name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); // 此处将驼峰命名方式的key转化为data-*-*        data = elem.getAttribute( name );        if ( typeof data === "string" ) {            try {                data = getData( data );            } catch ( e ) {}            // Make sure we set the data so it isn't changed later            // 将元素的data-*属性保存到dataUser中            dataUser.set( elem, key, data );        } else {            data = undefined;        }    }    return data;}

转载地址:http://crqca.baihongyu.com/

你可能感兴趣的文章
CODING 敏捷实践完全指南
查看>>
下MFC中对象、句柄、ID之间的区别.
查看>>
如何构建Win32汇编的编程环境(ONEPROBLEM个人推荐)
查看>>
Asp.Net MVC 分页、检索、排序整体实现
查看>>
Flymeos插桩适配教程
查看>>
还在用PS磨皮去皱?看看如何用神经网络高度还原你的年轻容貌!
查看>>
大端模式与小端模式、网络字节顺序与主机字节顺序
查看>>
微信支付申请90%的商户都卡在这儿了,申请微信支付,商户功能设置详细说明...
查看>>
制作一款微信表情
查看>>
高仿Instagram 页面效果android特效
查看>>
我的友情链接
查看>>
Juniper 基于路由的×××
查看>>
HDU - 2018 - 母牛的故事(dp)
查看>>
如何查找JSP页面中的错误
查看>>
2016 年总结
查看>>
Python学习开始
查看>>
Android应用程序消息处理机制(Looper、Handler)分析(4)
查看>>
C++ 类成员的构造和析构顺序
查看>>
将String转化成Stream,将Stream转换成String
查看>>
java路径Java开发中获得非Web项目的当前项目路径
查看>>