{"action":"closed","issue":{"url":"https://api.github.com/repos/chunpu/blog/issues/5","labels_url":"https://api.github.com/repos/chunpu/blog/issues/5/labels{/name}","comments_url":"https://api.github.com/repos/chunpu/blog/issues/5/comments","events_url":"https://api.github.com/repos/chunpu/blog/issues/5/events","html_url":"https://github.com/chunpu/blog/issues/5","id":53943587,"number":5,"title":"深入理解jQuery中的data(data_user, data_priv)","user":{"login":"chunpu","id":4565306,"avatar_url":"https://avatars.githubusercontent.com/u/4565306?v=3","gravatar_id":"","url":"https://api.github.com/users/chunpu","html_url":"https://github.com/chunpu","followers_url":"https://api.github.com/users/chunpu/followers","following_url":"https://api.github.com/users/chunpu/following{/other_user}","gists_url":"https://api.github.com/users/chunpu/gists{/gist_id}","starred_url":"https://api.github.com/users/chunpu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chunpu/subscriptions","organizations_url":"https://api.github.com/users/chunpu/orgs","repos_url":"https://api.github.com/users/chunpu/repos","events_url":"https://api.github.com/users/chunpu/events{/privacy}","received_events_url":"https://api.github.com/users/chunpu/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/chunpu/blog/labels/javascript","name":"javascript","color":"ededed"}],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-01-10T03:47:42Z","updated_at":"2015-01-10T03:48:36Z","closed_at":"2015-01-10T03:48:36Z","body":"data函数在jQuery中看起来很不起眼, 就像沙滩上一颗平凡的沙子, 但仔细一瞅, 却惊讶的发现data是jQuery中无比重要的一环, 甚至jQuery中各种事件都基于此\n\n---\n\ndata在jQuery中有两种\n\n一个是用来存数据的, 对应的对象分别是\n\n- 存储对象: `data_user`\n\n- 获取与设置方法: `$.data(el, key, value)`\n\n另一个是用来存事件的, 如click事件那种, 对应的对象分别是\n\n- 存储对象: `data_priv`\n\n- 获取与设置方法: `$._data(el, key, value)`\n\n\n`data_user`和`data_priv`, 就如其名, 一个是用户用的, 一个是jQuery私有的, 他们都是一个叫Data的实例对象\n\n---\n\n\n### 存数据的 *data_user*\n\ndata的使用方法简单无比\n\n```javascript\n$('body').data('key', 'value')\n// 或者\n$.data(document.body, 'key', 'value')\n```\n\n你可能会说, 这函数也太弱了吧, 自己分分钟就可以写一个\n\n```javascript\n$.fn.data = function(k, v) {\n  if (arguments.length === 2) {\n    return this.each(function() {\n      this[key] = v\n    })\n  }\n  return this[0][k]\n}\n```\n\n这样写看上去没啥问题, 其实却有很多隐患, 比如这样\n\n```javascript\n// case1\n$('body').data('tagName', 'somevalue')\n// case2\n$('body').data('key', $('body'))\n```\n\n直接套在node对象上碰到原生的属性只会失效. 又如果值存为自己, 谁保不准会引用来引用去出问题呢, 而且直接放在节点对象上, 容易暴露逻辑, 被轻易修改\n\n我们来看看jQuery怎么做的\n\n```javascript\n$(document.body).data('aaa', 'value-aaa')\nconsole.dir(document.body)\n```\n![document.body](http://fimg.qiniudn.com/jquery-data-0.png)\n\n我们看到多出来暗色的属性-->jQuery加一串随机数\n\n首先是暗色, 我们就知道此属性不能被枚举(enumerable为false), 是用definePropert*定义的属性\n\n其次可以看出这个属性和jQuery.expando很像\n\nexpando是一个jQuery的唯一标示, 这个多出来的属性就是jQuery.expando再加上一串随机数产生的\n\n> 为啥费这么大劲, 直接叫`jquery-data`啥的不好嘛? 这是因为一个网页能共存多个jQuery, 用[$.noConflict](https://api.jquery.com/jQuery.noConflict/)来实现不冲突共存\n\n同时我们能看到这个属性的值为`3`\n\n我们再试试修改body上的data值\n\n```javascript\n$(document.body).data('aaa', 'value-bbb')\n$(document.body).add(document).data('bbb', 'value-xxx')\n```\n\n再看看这个属性的值, 还是3, 根本没有变\n\n聪明的同学一定知道了, jQuery存data是用一个数组一样的东西存的, dom上只存这个数据的位置\n\n而这个数组(array-like), 就是本文一开始提到的data_user的一个属性\n\n先打印一下data_user存的东西\n\n![data_user](http://fimg.qiniudn.com/data_user.png)\n\n可以看到这里面存了2个属性, 一个就是expando, 也就是节点元素上多出来的那个奇怪属性, 另一个是cache, 不像数组, 但存着刚才我们看到的`3`, 3属性的值就是之前存入的data\n\n知道这些我们就可以写出一个最简单的类似jQuery的data函数\n\n```javascript\n;(function() {\n  $.fn.data = dataFn\n  var data = {\n    expando: jQuery.expando + Math.random(),\n    cache: []\n  }\n  var eo = data.expando\n  function dataFn(k, v) {\n    if (arguments.length === 2) {\n      return this.each(function() {\n        if (this[eo] >= 0) {\n          data.cache[this[eo]][k] = v\n        } else {\n          var len = data.cache.length\n          this[eo] = len\n          data.cache[len] = {}\n          data.cache[len][k] = v\n        }\n      })\n    }\n    var o = data.cache[this[0][eo]] || {}\n    if (arguments.length === 0) {\n      return o\n    }\n    return o[k]\n  }\n})()\n```\n\n---\n\n### 存事件的 *data_priv*\n\n那为啥jQuery的事件也和它有关呢?\n\n了解jQuery事件的同学一定知道, 不管怎么给dom加事件, 在chrome的调试板上只能看到dom被绑了一个很奇怪的匿名函数\n\n因为所有事件都被jQuery托管了, 但是都绑同一个函数, jQuery是怎么知道哪个handler对应哪个dom呢?\n\n我们不妨再打印一下dom元素\n\n```javascript\n$('a').click(alert)\nconsole.dir($('a')[0])\n```\n\n![](http://fimg.qiniudn.com/data_priv.jpg)\n\n我去, 怎么看上去和存了data一模一样啊, 也多了一个暗色奇怪字符串, 值也是3\n\n我们也给a存下data看下\n\n`$('a').data('aaa', 222)`\n\n![](http://fimg.qiniudn.com/data-user-priv.png)\n\n这下有俩奇怪字符串了!\n\n其实它们就是`data_user`和`data_priv`这俩货各自的expando\n\n事实上data和events本来就是完全一样的东西, 只是一个存在`data_user`中, 一个存在`data_priv`中\n\n打印一下`data_priv`\n\n![data_priv](http://fimg.qiniudn.com/data-priv-obj.jpg)\n\n可以看到`data_priv`确实和`data_user`一样, 只是存放的数据比较复杂, 是一些jQuery的events标示\n\njQuery提供了`$._data`这个方法(以后会被删掉), 让我们可以像`$.data`那样轻松的获取绑定在某节点上的handlers\n\n比如我们看一下github在document上绑定的事件\n\n![github-data-priv](http://fimg.qiniudn.com/github-data-priv.png)\n\n知道jQuery的event handlers存放和data一样, 借助data, 那我们自己模仿jQuery实现一个最简单最简陋的`$.fn.on`函数就像玩一样\n\n```javascript\n;(function() {\n  $.fn.on = onFn\n  function onFn(ev, sel, handler) {\n    return this.each(function() {\n      this.addEventListener(ev, _handler)\n      var o = {}\n      o[ev] = {\n        sel: sel,\n        handler: handler\n      }\n      $.data(this, o)\n    })\n  }\n  function _handler(e) {\n    var _event = $.data(this)[e.type]\n    var sel = _event.sel\n    var elements = $(this).find(sel)\n    var handlerQueue = []\n    elements.each(function() {\n      var cur = e.target\n      if (cur === this || $(this).has(cur).length) {\n        _event.handler.call(this, e)\n      }\n    })\n  }\n})()\n```"}}