jQ的数据管理【中篇】
发布在# 菜鸟解读 jQuery #2014年5月27日view:3735
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

本篇会讲解的api是: $.data(), $._data(), $.removeData()
本篇代码较长,但是逻辑很清晰,应该没有什么特绕的地方,
请大家直接阅读代码吧,小编已备好注释

jQuery.extend({
    //....
    //这里我们对pvt参数说明下,这个参数为true的时候是进行系统数据设置
    //对于外部使用来说不需要传递,jQ系统内需要绑定数据时会传入true
    data: function( elem, name, data, pvt ) {
        // 检测元素是否可以绑定数据
        if ( !jQuery.acceptData( elem ) ) {
            return;
        }
        var privateCache, thisCache, ret,
            //jQ的唯一标示
            internalKey = jQuery.expando,
            //是否是字符串
            getByName = typeof name === "string",
            // 是否是节点,节点类型 
            isNode = elem.nodeType,
            // 如果是dom元素呢就指向jQ的全局cache变量,否则就指向参数自身
            cache = isNode ? jQuery.cache : elem,
            // 获取标示符
            // 如果是dom元素的话 则试着取出其身上的jQ标示对应的id,没有就是 undefined
            // 如果是js对象的话有则直接赋值 jQueryId
            id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
            // 是否是事件对象,jQ内部进行使用的,
            // 请先跳过关于事件的代码,小编暂时还解释不了.
            isEvents = name === "events";
        // 如果读取数据 && 没有数据 那就可以结束了
        if( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data))&& getByName && data === undefined ){
            return;
        }
        // 如果元素身上没有id则为元素创建一个id
        // 比如第一次添加数据
        if ( !id ) {
            if ( isNode ) {
                // 为dom元素对象附加一个id,同时给id进行了赋值
                // uuid这个在前一篇
                elem[ internalKey ] = id = ++jQuery.uuid;
            } else {
                // js对象的话 直接给予当前id即可
                id = internalKey;
            }
        }
        //缓存的数据不存在的时候制空
        if ( !cache[ id ] ) {
            cache[ id ] = {};
            if ( !isNode ) {
                // toJSON,当对象被 JSON.stringify(),如果存在toJSON方法则会直接输出 return 值
                // jQ在这里将toJSON设置成了空,这就导致无法会数据缓存对象使用stringify
                // jQuery.noop这个是早之前就出现过的,就是一个空函数 function(){};
                cache[ id ].toJSON = jQuery.noop;
            }
        }

        // 这里执行的是批量赋值 这种情况是处理 $.data(dom,{...});
        if ( typeof name === "object" || typeof name === "function" ) {
            // 内部数据的,则绑定到关联的缓存对象上
            if ( pvt ) {
                cache[ id ] = jQuery.extend( cache[ id ], name );
            // 如果是用户自定义数据,则绑定到关联的缓存对象的.data对象上
            } else {
                cache[ id ].data = jQuery.extend( cache[ id ].data, name );
            }
        }

        //这是缓存后的数据
        privateCache = thisCache = cache[ id ];

        //如果是自定义数据 则将 thisCache变量指向到 .data 对象中,如果为空则创建一个 空对象。
        //这里是个重点,很简单的代码,这里改变了将数据存储的位置。
        //小编到以为这段代码其实可以移动到上个if之前。
        if ( !pvt ) {
            if ( !thisCache.data ) {
                thisCache.data = {};
            }
            // 修改了thisCache变量的指向
            thisCache = thisCache.data;
        }

        //设置值 key-value
        if ( data !== undefined ) {
            //camelCase这个函数小编可能没有讲到过
            //他的作用就是把 a-name-b 这种形式的字符串拼接成 aNameB 驼峰命名规则的一个函数
            thisCache[ jQuery.camelCase( name ) ] = data;
        }

        //events事件特殊处理 ,请忽略阅读
        if ( isEvents && !thisCache[ name ] ) {
            return privateCache.events;
        }

        // 到这这里就是获取返回值了 $.data(dom,'key')
        if ( getByName ) {
            //如果取不到则转换一下命名规则再取一次, 等价于一个, 三元运算符操作
            ret = thisCache[ name ];
            if ( ret == null ) {
                ret = thisCache[ jQuery.camelCase( name ) ];
            }
        } else {
            //返回用户自定义属性对象
            ret = thisCache;
        }
        return ret;
    },
    //移除对象某个属性
    //这里其实很多变量表示和语句都类似于data()
    removeData: function( elem, name, pvt ) {
        //检查对象是否可以被绑定数据
        if ( !jQuery.acceptData( elem ) ) {
            return;
        }
        var thisCache, i, l,
            internalKey = jQuery.expando,
            isNode = elem.nodeType,
            cache = isNode ? jQuery.cache : elem,
            id = isNode ? elem[ internalKey ] : internalKey;
        //如果没有数据那也就不用删除了
        if ( !cache[ id ] ) {
            return;
        }
        if ( name ) {
            //指向私有对象还是指向用户自定义的data
            thisCache = pvt ? cache[ id ] : cache[ id ].data;
            if ( thisCache ) {
                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( " " );
                        }
                    }
                }
                //经过上面的处理我们看到jQ兼容了很多形式上的参数
                // [key1,key2] "key1 key2" "key1" "key1-name"
                //上边的一顿整理 到了这里都是一个数组,执行删除操作
                for ( i = 0, l = name.length; i < l; i++ ) {
                    delete thisCache[ name[i] ];
                }

                //如果数据对象中还有剩余数据则函数执行完毕 return;
                //isEmptyDataObject监测的是js数据对象,【因为自定义的数据对象是绑定在了该对象上】
                //这个函数小编安排在了下一篇,大家可以到源码中搜索下,没有几行,很好理解
                //isEmptyObject我们在前文中介绍过,就是监测一个普通对象是否是空对象
                if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
                    return;
                }
            }
        }

        /*
            代码执行到这里的时候有两种情况:
                1.没有传name参数 //意味着要删除所有数据
                2.按照传递的name参数删除后,没有数据了
        */

        // 如果是来删除自定义的数据
        if ( !pvt ) {
            //删除.data
            delete cache[ id ].data;
            //删除后检测到 数据缓存对象还有剩余数据 则返回
            if ( !isEmptyDataObject(cache[ id ]) ) {
                return;
            }
        }

        /*
            代码执行到这里时:
                1.删除的是系统级别数据, 
                2.已经清空完了用户的缓存数据,而且数据缓存对象还不是空的时候
        */

        // 如果浏览器支持删除dom上的属性
        // 如果不支持删除dom上的属性 && cache指向的不是window对象
        if ( jQuery.support.deleteExpando || !cache.setInterval ) {
            delete cache[ id ];
        //如果浏览器不支持删除dom扩展属性 && cache指向window对象,则设置为null
        } else {
            cache[ id ] = null;
        }

        //该对象是dom元素
        if ( isNode ) {
            // 浏览器支持删除属性的话则直接使用delete
            if ( jQuery.support.deleteExpando ) {
                delete elem[ internalKey ];
            //如果支持原生remove则使用原生remove进行删除
            } else if ( elem.removeAttribute ) {
                elem.removeAttribute( internalKey );
            //设置为null
            } else {
                elem[ internalKey ] = null;
            }
        }
    },

    // 添加一个内部数据
    _data: function( elem, name, data ) {
        return jQuery.data( elem, name, data, true );
    },

    // 检测一个属性是否可以绑定数据
    acceptData: function( elem ) {
        if ( elem.nodeName ) {
            // jQuery.noData这是一个对象,在前一篇我们提到过的,
            // 存储的是不可被添加的元素对象标签名
            var match = jQuery.noData[ elem.nodeName.toLPowerCase() ];
            // 这里就是针对之前的noData里的几个元素进行的检测,如果是则返回了false
            if ( match ) {
                // 这里在再次针对 object 标签进行一次检测,看看是不是非flash
                return !(match === true || elem.getAttribute("classid") !== match);
            }
        }
        return true;
    }
});

本篇基本全是代码,本篇就介绍到这里.
下一篇应该会是在周五为您奉上

感谢您的阅读,欢迎留言:提问,交流,斧正。

评论
发表评论
暂无评论
WRITTEN BY
前端狮子
JS前端开发工程师 :喜欢研究js,nodejs,html5; 希望结交更多朋友~
TA的新浪微博
PUBLISHED IN
# 菜鸟解读 jQuery #

本栏解读的jQ为1.7.2版本。 本人也是刚开始读起源码,在这里分享下成长的心得。 本人能力有限,也是接触JS不久的初学者,定会有不少解析不全不够明朗【甚至BUG】的地方, 希望各位牛牛多多留言斧正 感谢阅读 ps:由于工作不定时繁忙,本人也无法定期更新,但是会尽量抽时间学习,分享给大家

我的收藏