• jQuery 3.0 的 Data 浅析


    jQuery 3.0 在6月9日正式发布了,3.0 也被称为下一代的 jQuery 。这个版本从14年10月开始,其中发布过一次beta 版(2016/1/14,)和候选版(2016/05/20)。一路走来,颇为不易。

    文章目录

    1. Data浅析
    2. Data在jQuery内部的使用
    3. 1.x.x 和 2.x.x 的比较

    一、Data浅析

    jQuery 3.0 中的 Data 是内部使用的,定义为一个“类”。一共用它创建了两个对象,dataPriv 和 dataUser。Data 有 1 个对象属性(expando)和类属性(uid),有 6 个方法,如下

    下面分别解读

    1、Data.uid

    这是一个从 1 开始用来自增的数字。

    2、expando

    由 jQuery.expando 和 uid 组合而成,它用来作为元素(如DOM元素)的key,是唯一的。jQuery.expando 的生成如下

    jQuery.expando = "jQuery" + ( version + Math.random() ).replace( /D/g, "" )

    即 'jQuery' + (版本号 + 随机数),然后把非数字的都去掉,比如

    "jQuery" + "3.0.0" + 0.129896303388626 == "jQuery3.0.00.129896303388626"

    去掉非数字变为

    "jQuery30009423638425146147"
    

      

    jQuery 3.0 内部变量 dataPriv 和 dataUser 生成 expando 如下

    jQuery 300 024727210109188635 1
    jQuery 300 024727210109188635 2

    第三部分是随机数,每次刷新都会变,其它部分的不变。

    3、cache

    cache 方法会给 owner 上绑定一个对象作为存储,owner 必须满足 acceptData 的,cache 会以 this.expando 为线索 key。
    owner 有两种,一中是DOM元素(nodeType为1和9),另一种则是普通的JS对象。诸如 文本节点(nodeType=3)、注释节点(nodeType=8) 一律不添加。

    acceptData 的定义如下

    var acceptData = function( owner ) {
    
    	// Accepts only:
    	//  - Node
    	//    - Node.ELEMENT_NODE
    	//    - Node.DOCUMENT_NODE
    	//  - Object
    	//    - Any
    	/* jshint -W018 */
    	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
    };
    

    acceptData 在 3.0 中一共有 3 处使用,分别为

    1. Data 类的 cache 方法,为私有方法不提供给程序员使用。
    2. $.cleanData 方法,清空元素关联的所有缓存数据。为公开方法,但很少使用。该方法被用在 empty、html、replaceWith、remove 方法中。
    3. $().trigger 方法,主动派发事件,为公开方法。

    如果是 DOM 元素,则直接使用点操作符赋值,如果是普通 JS 对象则使用 ES5 的 Object.defineProperty 方法,这也是 jQuery 3.0 会使用新 API 的体现。

    // If it is a node unlikely to be stringify-ed or looped over
    // use plain assignment
    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
    } else {
    	Object.defineProperty( owner, this.expando, {
    		value: value,
    		configurable: true
    	} );
    }
    

    转换成如下代码

    elem['jQuery3000247272101091886351'] = dataObj;
    
    var person = {name: 'John', age: 30};
    Object.defineProperty( person, 'jQuery3000247272101091886351', {
    	value: dataObj,
    	configurable: true
    } );
    

    cache 方法就是这样,传入 owner,只有第一次会 set ,返回 value (一个空对象),之后取到 value 后直接返回。

    源码

    cache: function( owner ) {
    
    	// Check if the owner object already has a cache
    	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.
    		if ( acceptData( owner ) ) {
    
    			// If it is a node unlikely to be stringify-ed or looped over
    			// use plain assignment
    			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
    			} else {
    				Object.defineProperty( owner, this.expando, {
    					value: value,
    					configurable: true
    				} );
    			}
    		}
    	}
    
    	return value;
    },
    

    4、set

    上面的 cache 方法为 owner 建立一个以 expando 为 key 的空对象,后面所有的方法都围绕这个空对象来展开,这个空对象就被称为缓存对象,后面所有的数据都添加到它上面。set 就是给这个对象来添砖加瓦,set 每次都是先取回 cache ,再给其添加新的属性及数据。如果 data 是字符串,则以它为 key 添加,如果是对象,则遍历它添加。只需注意一点,横线连接符内部会被转成驼峰格式,这也是为了对 H5 data-xxx 的兼容 。

    源码

    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;
    },
    

      

    5、get

    get 简单至极,传 key 则从 cache 上取回该 key 的值,无则取回整个 cache。

    源码

    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 ) ];
    },
    

      

    6、access

    这个方法即时 getter,也是 setter,如此而已。它存在的原因是由于 jQuery 的 API 有很多是身兼 setter 和 getter 的功能,比如 .data、.html、.text。有了 access 可以少写很多 if / else。

    getter 条件

    1. key 是 undefined,这时取整个 cache
    2. key 是字符串且value 是undefined,这是取指定 key 的值


    setter 条件

    1. owner、key、value 这三个参数都传

    源码

    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;
    },
    

    7、remove

    清空绑定元素(owner)上面的缓存对象,依然需要先通过 this.expando 拿到 cache,如果传了 key 则删除指定 key 的值(key自身也被删除)。
    当然 jQuery API 保持已有的方便性,key 可以为一个数组,这样可以批量删除多个 key。如果 key 没传则将整个 cache 删除,这里区分了 DOM 和普通 JS 对象,DOM 对象使用undefined赋值,JS 对象则使用 delete。

    源码

    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 ( jQuery.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( rnotwhite ) || [] );
    		}
    
    		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 ];
    		}
    	}
    }, 

    8、hasData

    用来判断 owner 上是否有缓存数据,返回 true 或 false。

    源码

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

    二、Data在jQuery内部的使用

    以上解读完了 Data 的所有方法,上面也提到 Data 类是在 jQuery 内部使用的,一共创建了它的两个对象:dataPriv 和 dataUser。

    这两个对象在 3.0.0 中有着明确的分工,dataPriv 可以猜测到是 “data” 和 “private” 两个单词的组合后简写。即 dataPriv 是私有的用来服务 jQuery 内部方法,dataUser 用来服务那些公开给用户使用的方法。

    下面看下这两个对象分布在哪些模块中使用。

    完整版点击展开可查看

    dataPriv
    	
    	公共
    		$.hasData
    		$.cleanData
    		cloneCopyEvent
    
    	队列
    		$().queue
    		$()._queueHooks
    		$().promise
    
    	动画
    		$().animate
    		$().stop
    		$().finish
    		showHide
    
    	事件
    		$.event.add
    		$.event.remove
    		$.event.dispatch
    		$.event.trigger
    	
    	其它
    		setGlobalEval
    		domManip
    		defaultPrefilter
    		$().toggleClass
    
    dataUser
    
    	公共
    		$.hasData
    		$.cleanData
    		cloneCopyEvent
    
    	数据缓存
    		$.data
    		$.removeData
    		$().data
    		$().removeData
    
    	其它
    		dataAttr

    以上可以看到,除了“公共”,DataPriv 用在了 jQuery 的 队列、动画、事件等模块;dataUser 用在了数据缓存及 dataAttr 模块。

    “公共” 是指这三个方法内都用到了 dataPriv 和 dataUser

    $.hasData(elem)

    用来判断 elem 上是否绑定了相关的数据缓存,返回 true 和false,只有 dataPriv 和 dataUser 上都没有才返回 false

    源码

    hasData: function( elem ) {
    return dataUser.hasData( elem ) || dataPriv.hasData( elem );
    },
    

    $.cleanData(elems)

    清空 elem 上绑定的所有数据缓存,理所当然的需要同时清空 dataPriv 和 dataUser 上的。
    注意:虽然这个方法在公开暴露在了 $ 上, 但官网API上却没有该方法的介绍。另使用不当会造成严重后果,比如绑定了事件后(.on),调用该方法,绑定的事件将全部失效。因为会清空 dataPriv 内的所有数据。


    cloneCopyEvent(src, dest)

    这是一个内部方法,$.clone 会使用到它。克隆元素时除了会克隆 node 节点外,绑定在 node 上的数据也会被克隆过去。比如

    var cloneNode = $.clone(elem);

    把 elem 克隆给 cloneNode,此时 elem 上添加的事件 cloneNode 上也会有。

    三、1.x.x 和 2.x.x 的比较

    jQuery 1.x 系列 和 2.x 系列的版本对 数据缓存模块 的实现差异还是很大的。大家可以对比我11年的这篇文章

    1. 缓存的数据结构

    1.x (直到1.11.2) 缓存都是存储在 jQuery.cache 上的,2.x(包括3.x) 则使用了一个内部类 Data 做缓存,其主要用到了两个对象 dataPriv 和 dataUser。很明显 2.x 做的更好,它所有的缓存数据都是私有的,不会存在被误写的风险,而 1.x 的 jQuery.cache 是公开的,如果被误写(比如某个同学想当然的给$上添加一个cache对象)后果不堪设想。


    2. jQuery._data

    看到这个下划线就知道是私有的(约定式),在 1.x 中是仅在内部使用的,不提供给开发者。以 1.11.2 示例、这个方法被事件模块、队列模块、动画模块、setGlobalEval、cloneCopyEvent、fixCloneNodeIssues、domManip、showHide、defaultPrefilter、toggleClass 使用。3.x 则使用 dataPriv 和 dataUser 替代,大家可以对比看看。

    (2/3).x 相比 1.x 明显更优,dataPriv 和 dataUser 是真正的私有的(封装的更好,更安全),比起 1.x 约定式的私有 jQuery._data。虽然 3.0.0 还保守的兼容了 jQuery._data,相信过不了多久的后续版本就会剔除。


    3. 重构

    1.x 以 $._data 为中心,以它来辅助实现其它 API, (2/3).x 以 dataPriv/dataUser 为中心来实现。(2/3).x 将代码重构后提取出了 Data 类,更加清晰。

    相关:

    http://naotu.baidu.com/file/c287195ae96011f7511571a4280042c7?token=ddb7c115786ff90f

    http://naotu.baidu.com/file/186bfe75ebe2878fa4d70856f0f33672?token=c8b112b939e0f65c

    http://www.cnblogs.com/snandy/archive/2011/06/10/2077298.html

    http://www.cnblogs.com/snandy/p/4650599.html

  • 相关阅读:
    [Python 多线程] 详解daemon属性值None,False,True的区别 (五)
    Python 多线程 线程安全、daemon简介 (四)
    Python 多线程 start()和run()方法的区别(三)
    jstack的使用方法
    java中的fork-join框架
    RabbitMQ:消息发送确认 与 消息接收确认(ACK)
    vue项目搭建
    Node.js安装及环境配置之Windows篇
    Storm里面fieldsGrouping和Field的概念详解
    Java8 如何正确使用 Optional
  • 原文地址:https://www.cnblogs.com/snandy/p/5578467.html
Copyright © 2020-2023  润新知