博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
jQuery 源码系列(八)data 缓存机制
阅读量:6606 次
发布时间:2019-06-24

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

欢迎来查看系列文章。

不打算介绍 deferred,或者放到后面以后去介绍,因为我对于 js 的异步存在着恐惧,看了半天代码,发现,用挺好用的,一看源码,全傻眼了。如果你感兴趣,这边,。

数据缓存

jQuery 最初以便捷 DOM 操作而流行,而 DOM 的本质其实就是对象,开发者们又习惯性的将一些标志直接扔给 DOM 本事,这会带来内存泄漏的问题。

比如对于斐波那契数列,方法可以有递归,有迭代,如果用 js 来写的话有一个比较有意思的方法,就是用缓存来实现:

function fib(n){  if(n == 1 || n == 0)    return n;  if(!fib[n-1]){    fib[n-1] = fib(n-1);  }  if(!fib[n-2]){    fib[n-2] = fib(n-2);  }  return fib[n-1] + fib[n-2];}

因为 fib 不仅是函数,而且是对象,JS 中万物都是对象,所以才有了这种缓存的解决办法,这就是前面所说的,不过是在 DOM 上实现的。

当然这种方法也会有弊端,造成内存泄漏。现代的浏览器有自动回收内存的机制,但当出现循环引用或闭包的时候,就会产生内存泄漏问题

就不深入去讨论了。

jQuery 的缓存机制

来看看 jQuery 中提高的数据缓存机制,有两个函数,分别是 jQuery.data()jQuery.fn.data(),可以看出来,一个是在 jQuery 对象上,一个是在 jQuery 生成的对象上。如果仔细阅读的话,你会发现 jQuery 中很多函数都有两个,原型上一个,jQuery 上一个。

jQuery.data() 有两种使用,一个用于绑定,一个用于查询:

  1. jQuery.data( element, key, value )

  2. jQuery.data( element, key )

上面的 element 参数表示 DOM 元素,比如一个例子如下:

jQuery.data(document.body, 'foo', 52);jQuery.data(document.body, 'bar', 'test');jQuery.data(document.body, 'foo'); // 52jQuery.data(document.body, 'bar'); // "test"

还有 .data() 方法,,这个函数就直接在 jquery 对象上实行绑定 data:

$("body").data("foo", 52);$("body").data("bar", { myType: "test", count: 40 });$("body").data({ baz: [ 1, 2, 3 ] }); $("body").data("foo"); // 52$("body").data(); // { foo: 52, bar: { myType: "test", count: 40 }, baz: [ 1, 2, 3 ] }

这边有一个小细节:

var jq1 = $("body");var jq2 = $("body");jq1.data('a', 1);jq2.data('a', 2);jq1.data('a'); //2jq2.data('a'); //2// 数据被覆盖$.data(jq1, 'b', 3);$.data(jq2, 'b', 4);$.data(jq1, 'b'); //3$.data(jq2, 'b'); //4// 不会被覆盖

可以看出来,通过这两种方法绑定的数据,其实是不一样的,前者会被覆盖,而后者不会,说明在 cache 中肯定有某种神秘的力量将他们区别开来

源码

在 jQuery 中的源码,大致是这样的结构:

function Data(){...}Data.prototype = {  cache: function(){...},  set: function(){...},  get: function(){...},  access: function(){...},  remove: function(){...},  hasData: function(){...}}var dataUser = new Data();jQuery.extend({  data: function( elem, name, data ) {    return dataUser.access( elem, name, data );  },  hasData: function( elem ) {    return dataUser.hasData( elem ) || dataPriv.hasData( elem );  },  removeData: function( elem, name ) {    dataUser.remove( elem, name );  }})jQuery.fn.extend({  data: function(){    ...    dataUser...    ...  },  removeData: function(){...}})

由于之前已经弄懂 jQuery 内部结构,对于这个一点也不惊讶,在 jQuery 和 jQuery 的原型上分别有一个 data 函数,用来处理各自的情况。

既然已经知道了 data 的基本结构,我们来各个击破,先来看一下 function Data()

function Data() {  // jQuery.expando 是 jQuery 的标识  this.expando = jQuery.expando + Data.uid++;}Data.uid = 1;jQuery.expando = ('3.1.1' + Math.random()).replace( /\D/g, "" )// "3.1.10.9610206515567563".replace( /\D/g, "" )// "31109610206515567563"

接着是 prototype

Data.prototype = {  // 建立一个 cache  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 ) ) {        // 判断 owner 是一个合格者后        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;  },  // set 函数就是为 dom 设置 key,value  set: function( owner, data, value ) {    var prop,      cache = this.cache( owner );    if ( typeof data === "string" ) {      cache[ jQuery.camelCase( data ) ] = value;    // 处理 data 为这种情况: [ owner, { properties } ]    } else {      // Copy the properties one-by-one to the cache object      for ( prop in data ) {        cache[ jQuery.camelCase( prop ) ] = data[ prop ];      }    }    return 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 ) ];  },  // 用来访问,将 get、set 结合到一起,并对 underfined 判断  access: function( owner, key, value ) {    if ( key === undefined ||        ( ( key && typeof key === "string" ) && value === undefined ) ) {      return this.get( owner, key );    }    this.set( owner, key, value );    return value !== undefined ? value : key;  },  // 用于移除 cache  remove: function( owner, key ) {    var i,      cache = owner[ this.expando ];    if ( cache === undefined ) {      return;    }    if ( key !== undefined ) {      // 支持删除数组格式的 key      if ( jQuery.isArray( key ) ) {        key = key.map( jQuery.camelCase );      } else {        key = jQuery.camelCase( key );        // 为了保持一致,强行的构造了一个 数组        key = key in cache ?          [ key ] :          ( key.match( rnothtmlwhite ) || [] );      }      i = key.length;      // 删      while ( i-- ) {        delete cache[ key[ i ] ];      }    }    // cache 为空的时候,删除整个缓存    if ( key === undefined || jQuery.isEmptyObject( cache ) ) {      if ( owner.nodeType ) {        owner[ this.expando ] = undefined;      } else {        delete owner[ this.expando ];      }    }  },  hasData: function( owner ) {    var cache = owner[ this.expando ];    return cache !== undefined && !jQuery.isEmptyObject( cache );  }};

然后是 jQuery.data()

var dataPriv = new Data(); //以后会讲到var dataUser = new Data();jQuery.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 );  }} );

源码里面有 dataPriv 和 dataUser,作者做了一个 TODO 标记,

接着是 jQuery.fn.data()

jQuery.fn.extend( {  data: function( key, value ) {    var i, name, data,      // 将第一个 dom 赋给 elem      elem = this[ 0 ],      attrs = elem && elem.attributes;    // key 为 underfined,表示参数空,获取全部    if ( key === undefined ) {      if ( this.length ) {        data = dataUser.get( elem );        if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {          i = attrs.length;          while ( i-- ) {            // 这里面从 dom 的 attribute 中搜索 data- 开通的属性            if ( attrs[ i ] ) {              name = attrs[ i ].name;              if ( name.indexOf( "data-" ) === 0 ) {                name = jQuery.camelCase( name.slice( 5 ) );                dataAttr( elem, name, data[ name ] );              }            }          }          dataPriv.set( elem, "hasDataAttrs", true );        }      }      return data;    }    // object 类型    if ( typeof key === "object" ) {      return this.each( function() {        dataUser.set( this, key );      } );    }    // key value 的情况,利用 access 函数    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.      if ( elem && value === undefined ) {        // Attempt to get data from the cache        // The key will always be camelCased in Data        data = dataUser.get( elem, key );        if ( data !== undefined ) {          return data;        }        // Attempt to "discover" the data in        // HTML5 custom data-* attrs        data = dataAttr( elem, key );        if ( data !== undefined ) {          return data;        }        // We tried really hard, but the data doesn't exist.        return;      }      // Set the data...      this.each( function() {        // We always store the camelCased key        dataUser.set( this, key, value );      } );    }, null, value, arguments.length > 1, null, true );  },  removeData: function( key ) {    return this.each( function() {      dataUser.remove( this, key );    } );  }} );

data 函数略有不同,但思路也很清晰。

有几个要提一下的函数

其中,有几个函数,也来介绍一下,acceptData

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

acceptData 是判断 owner 的类型,具体关于 nodeType,去看看吧。

jQuery.camelCase

jQuery.camelCase = function (string) {  return string.replace(/^-ms-/, "ms-").replace(/-([a-z])/g, function (all, letter) {    return letter.toUpperCase();  });}

这个函数就是做了一些特殊字符串的 replace,具体有啥用,我也不是很清楚。

isEmptyObject 是判断一个 Object 是否为空的函数,挺有意思的,可以借鉴:

jQuery.isEmptyObject = function (obj) {    var name;    for (name in obj) {        return false;    }    return true;}

dataAttr 是一个从 DOM 中搜索以 data- 开头属性的函数:

function dataAttr( elem, key, data ) {  var name;  // If nothing was found internally, try to fetch any  // data from the HTML5 data-* attribute  if ( data === undefined && elem.nodeType === 1 ) {    name = "data-" + key.replace( /[A-Z]/g, "-$&" ).toLowerCase();    // 利用 dom 自身的 get 操作    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      dataUser.set( elem, key, data );    } else {      data = undefined;    }  }  return data;}

总结

jQuery 的 data 缓存从源码来看的话,真的不是很难,而且不难发现,jQuery 缓存的实质,其实就是在内部先弄一个 Object,然后和缓存体(DOM)建立一对一的联系,所有增删改查的操作,都是围绕着 jQuery 内部来的,不直接对 DOM 操作,这样就可以避免内存泄漏。而且从源码来看,jQuery 的缓存机制自带清内存操作,更是锦上添花呀。

参考

本文在 github 上的,欢迎来 star。

欢迎来交流。

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

你可能感兴趣的文章
vue学习:10、第一个项目,实践中遇到的问题
查看>>
sed的基本用法
查看>>
一个不错的shell 脚本入门教程
查看>>
JVM、GC相关资料
查看>>
dell r620装cenots7遇到的问题
查看>>
Ansible之playbook的使用
查看>>
ansible模块批量管理
查看>>
redis命令 - GET
查看>>
[Maven问题总结]Jetty9的Maven配置——嵌入式服务器
查看>>
httpd.conf的基本设置
查看>>
RHEL/Centos7新功能
查看>>
第一部分 思科九年 一(1)
查看>>
DBA日常工作职责
查看>>
Redis的持久化
查看>>
linux安装NFS服务器学习
查看>>
Planner .NET日历日程控件能给你的应用程序提供多种日历日程功能
查看>>
我的友情链接
查看>>
Linux压力测试
查看>>
JAVA中的线程机制(二)
查看>>
nginx安装与配置2(转载)
查看>>