Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

jQuery底层架构

1,689 views

Published on

  • Be the first to comment

jQuery底层架构

  1. 1. INSIDE JQUERY 市场开发部-前端 魏琪君12年3月31⽇日星期六
  2. 2. ⼤大纲 1. 对象结构和构造成本 2. Promise & Deferred 3. jQuery.cache 4. 事件 5. Sizzle 6. ⼀一些⽅方法和实现12年3月31⽇日星期六
  3. 3. 对象结构12年3月31⽇日星期六
  4. 4. jQuery对象形成链表结构12年3月31⽇日星期六
  5. 5. 摘⾃自jQuery⽂文档 $(ul.first).find(.foo).css(background-color, red) .end().find(.bar).css(background-color, green); 1. 不推荐过分地使⽤用链式调⽤用 2. 同类型操作可以使⽤用链式调⽤用,以简化代码12年3月31⽇日星期六
  6. 6. 构造流程 1. $(element) this.context = this[0] = element this.length = 1 return; 2. $(‘body’) 3. $(‘#id’) document.getElementById( 4. $(selector) $(document).find(selector) $(selector, jq) jq.find(‘selector’) $(‘selector’, context) $(context).find(‘selector’) 5. $(jquery) 6. $(func) $(document).ready(func)12年3月31⽇日星期六
  7. 7. 7. $(html) 1. simple tag $(‘<div>’), $(‘<div />’), $(‘<div></div>’) 1.1 document.createElement( 1.2? $(simpleTag, attrs) jQuery.fn.attr.call(elm, attrs) 2. else jQuery.buildFragment12年3月31⽇日星期六
  8. 8. 应⽤用 1. 构造jQuery对象的空间成本很低,正常情况下10000个也不会超过1M, 旺铺DIY后台打开 渲染完毕⼤大约构造800个jQuery对象。⼀一般⻚页⾯面初始化时构造数量应该< 500 2. 构造jQuery对象的时间成本通常也很低 如从 dom节点,jquery节点,‘body’, id选择器 构造jQuery都不需要Sizzle参于 3. 如果明确知道是jQuery对象,就不需要再次包装(⼀一般新⼿手会出现,喜欢任何变量都套个$) 4. 使⽤用其他选择器,如果没有context, 相当于 $(document).find(... 所以建议任何使⽤用css3选择器进⾏行构造jQuery对象,请带上相应模块的context节点进⾏行限 制 5. 从html构造jQuery对象时,优先采⽤用 simpleTag12年3月31⽇日星期六
  9. 9. Promise接⼝口 promise接⼝口为监异步操作定义了统⼀一的api 它是commonjs规范中的内容⻅见 http://wiki.commonjs.org/wiki/Promises/A var Promise = { done: function(callback), fail: function(callback), always: function(callback), .... };12年3月31⽇日星期六
  10. 10. $.ajax // 通常 $.ajax(url, { dataType: ‘jsonp’ success: function(o) { console.debug(o); } });12年3月31⽇日星期六
  11. 11. 通过promise接⼝口使⽤用ajax var o = $.ajax(mock.php, { dataType: jsonp }); o.done(function(o) { // 相当于success console.debug(success1, o); }); o.fail(function(e) { // 相当于 error console.debug(error, e); }); o.always(function() { // 相当于complete console.debug(complete); }); var o = $.ajax(mock.php, { dataType: jsonp }); o.then(function() { console.debug(success); }, function() { console.debug(error); });12年3月31⽇日星期六
  12. 12. $.when var url = mock.php a1 = $.ajax(url, { dataType: jsonp }), a2 = $.ajax(url, { dataType: jsonp }), a3 = $.ajax(url, { dataType: jsonp }); $.when(a1, a2, a3).done(function() { console.debug(all complete); }); 额外阅读: promise.pipe()12年3月31⽇日星期六
  13. 13. Promise接⼝口另外⼀一半 var Promise = { // 下⾯面3个⽅方法相当于bind done: function() {}, fail: function() {}, always: function() {} // 下⾯面两个⽅方法相当于trigger resolve: function() {}, reject: function() {} };12年3月31⽇日星期六
  14. 14. Deferred对象 ⽤用于⽅方便我们实现promise接⼝口12年3月31⽇日星期六
  15. 15. ⼀一个⽰示例12年3月31⽇日星期六
  16. 16. 注意依赖关系12年3月31⽇日星期六
  17. 17. 应⽤用 1. 使⽤用 $.when, promise.pipe等完成⼀一些稍复杂的异步请求需求 2. 组件开发者如有必要可以考虑提供promise接⼝口来统⼀一异步操作的使⽤用⽅方式 3. 使⽤用Deferred对象简化promise接⼝口的实现 4. 注意不能让Deferred侵⼊入业务代码(⻅见例⼦子) 5. 使⽤用Callbacks来简化observer模式的实现(1.7提供)12年3月31⽇日星期六
  18. 18. $.cache jQuery使⽤用$.cache这个对象保存 $.data, 事件, 动画操作所需要的数据结构 所以了解它对于理解和调试代码有⾮非常⼤大的帮助12年3月31⽇日星期六
  19. 19. 12年3月31⽇日星期六
  20. 20. $.data 1. 我们使⽤用$(elm).data(‘key’, value) 设置节点相关数据 2. 有三种元素不能设置data: embed, ⾮非flash的object, applet. 查看jQuery.noData获得列表 3. 设置时key字符串会被转成 camelCase 如 $(elm).data(‘a-b’, ‘hello’) 相当于 $(elm).data(‘aB’, ‘hello’) 我们应该全部使⽤用camelCase⽅方式进⾏行设置 4. 获取时 使⽤用key尝试获取,如果获取不到,会把key转化成 camelCase再次获取。 why? 因为允许 $(elm).data({ ‘a-b’: ‘hello’, ‘c-d’: ‘hello2’ }); 这种⽅方式进⾏行⼀一次设置多个data 但是内部没做转换(估计后续会优化) 我们应该全部使⽤用camelCase⽅方式进⾏行获取 5. 获取data时,如果从cache中获取不到,则会尝试从data属性中获取 <div data-config=’{ “requestUrl”: “url” }’>... 可以使⽤用 $(elm).data(‘config’) 进⾏行读取配置信息 ⼀一次读取后,这个值就会被cache,下次从cache中直接获取 注:设置时,是不会更改节点属性的, 如果需要可以使⽤用 $#attr(...12年3月31⽇日星期六
  21. 21. data类型识别从data属性获取的值,会根据属性字符串的样式,⽽而进⾏行适当的类型转换1. “true” --> true2. “false” --> false3. “null” --> null4. $.isNumberic(data) --> parseFloat(data)5. “{...}” 或 “[]” --> parseJSON, 如果parse失败,则返回字符串 这⾥里如果要正常parse出json,必须是严格的json格式才可以。6. 直接返回字符串具体过程看:function dataAttr(12年3月31⽇日星期六
  22. 22. data事件 var elm = $(#mydiv1); elm.bind(getData, function(value, name) { // 取得数据前触发,可以拦截哦 console.debug(getData, name, value); return new data; }); elm.bind(setData, function(value, name) { // 设置数据前触发, 可惜这个⽅方法没有提供拦截 console.debug(setData, name, value); }); elm.bind(changeData, function(value, name) { // 设置数据后触发 console.debug(changeData, name, value) }); 注: 这⼏几个⽅方法并未出现在⽂文档中(可能是我没发现),但这⼏几个事件出现在很早的 jQuery版本中 1.4就有了12年3月31⽇日星期六
  23. 23. 事件12年3月31⽇日星期六
  24. 24. 12年3月31⽇日星期六
  25. 25. 12年3月31⽇日星期六
  26. 26. 12年3月31⽇日星期六
  27. 27. 12年3月31⽇日星期六
  28. 28. 事件源码导读 1. 在理解上述结构后,看源码将会⾮非常⽅方便 2. 源码中如果有结构和逻辑两部分, 要先弄懂数据结构。 3. 数据结构是静⽌止的算法 事件内部⼯工作流程: 1. 当调⽤用elm.on/bind/delete时, 内部调⽤用 jQuery.event.add(elem, types, handler, data, selector) 完成上述结构的构建 2. 事件触发时,将会调⽤用相应节点 data events.handle ⽅方法 3. events.handle 仅仅调⽤用 jQuery.event.dispatch(event) [备注,⽼老版本这个⽅方法叫 jQuery.event.handle] 4. 当调⽤用trigger⼈人为触发事件时, 内部调⽤用 jQuery.event.trigger(event, data, elem, onlyHandlers) 后者调⽤用 节点data events.handle⽅方法12年3月31⽇日星期六
  29. 29. 事件使⽤用技巧 0. 从原理上可以看出,⾃自定义事件和原⽣生事件使⽤用的是同⼀一套逻辑,和浏览器⽆无关 1. jQuery 1.7开始使⽤用on/off 来代替原来的 bind, delegate, one 等事件,以规范化事件的使⽤用 2. 如需要对很多相同性质的节点,或动态产⽣生的节点进⾏行事件绑定,请使⽤用 delegate 3. delegate请尽量减少作⽤用域,如有可能,使⽤用 tag#id.cass来作为selecotr, 这样会直接使⽤用正 则式进⾏行⽐比较 4. 可以使⽤用事件数据来传递额外信息 5. e.preventDefault/e.stopPropagation, return false相当于两者 6. e.stopImmediatePropagation(), 节点事件数组中后⾯面的事件都不再执⾏行12年3月31⽇日星期六
  30. 30. 7. trigger/ triggerHandler区别 trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, triggerHandler: function( type, data ) { if ( this[0] ) { return jQuery.event.trigger( type, data, this[0], true ); } } 8. 在某些情况下可以使⽤用namespace事件来完成⼀一些要求 如在组件中,对该组件所有的事件给个namespace, 以⽅方便清理 我们绑定事件时,可以给⼀一个namespace, 以便在不保存事件引⽤用的情况下移除事件 9. 注意api的⽅方便性, 如⼀一次可以添加多个事件哦, 还有事件函数可以直接为false link.on(‘click’, false); 10. one / toggle 类型事件 在某些情况下可以帮助我们不要重新发明轮⼦子12年3月31⽇日星期六
  31. 31. Sizzle jQuery中的CSS3选择器引擎12年3月31⽇日星期六
  32. 32. querySelectorAll 在⾼高级浏览器中,我们可能根本不需要js来处理选择器,因为有querySelectorAll12年3月31⽇日星期六
  33. 33. ⾼高效的选择器 1. 即使有querySelectorAll, 也不⼀一定⽀支持任何复杂(⻅见jQuery⽂文档)的选择器 2. 经过我的测试,以下选择器⼀一般都是⽀支持的,所以优先采⽤用(正常应⽤用中⾜足够了) - ⼦子代选择器,邻代选择器等都是⽀支持的 div ul, div>ul, div+ul, div~ul - 属性选择器 input[name], input[name=username], div[data-tracelog] - class选择器 div.mod - 不⽀支持伪类选择器, 只⽀支持hover, active等⼏几个伪类选择器, 如 :first, :last是不⽀支持 的 3. 以下⼏几种情况直接使⽤用dom api直接获取, ⽽而不需要 querySelectorAll ‘body‘ document.body #id document.getElementById tag getElementsByTagName .class getElementsByClass (如果⽀支持的话)12年3月31⽇日星期六
  34. 34. 其他可参考:http://caniuse.com/12年3月31⽇日星期六
  35. 35. 选择器使⽤用技巧 1. 优先使⽤用简单选择器 2. 选取元素时,请指定合适的context(推荐为模块⼦子模块) 如 $(‘a.close’, panel) 3. class选择器请限定tag, 如tag.class 4. 适度使⽤用伪类 5. 选择器层级不要太深,⼀一般指定context后, selector不会超过2层12年3月31⽇日星期六
  36. 36. Sizzle流程 0. jQuery.find = Sizzle 1. 处理选择器 -》 数组 jQuery.find(#doc div.mylist ul>li) -》 ["#doc", "div.mylist", "ul", ">", "li"] 2. 根据类别采⽤用不同的处理顺序 - 选择器中包含位置相关的伪类,如 :first, :last, :even, :odd ⻅见 Sizzle.selectors.match.POS 这个正则表达式 如果存在, 则采⽤用⾃自左向右的顺序进⾏行处理 $(‘div ul li:first’)-> $(‘div’).find(‘ul’).find(‘li:first’) 如果不存在,则采⽤用⾃自右向左的顺序进⾏行处理[1] $(‘div ul li’) -> $(‘li’), 再过滤出所有符合条件的li, 处理是从右⾃自左的 [1] 如果第⼀一个为id选择器,则先处理它,再使⽤用这个节点为context查找12年3月31⽇日星期六
  37. 37. Sizzle(‘ul li’) - 分割选择器 [‘ul’, ‘li’] - Sizzle.find(‘li’) - Sizzle.selector.find.TAG ---> getElementsByTagName(li) - Sizzle.relative(lis, ‘ul’); -> 看看是否在ul下⾯面 Sizzle(‘ul li:first’) - 分割选择器[‘ul’, ‘li:first’] - Sizzle.find(‘ul’) - 分析选择器 [‘ul’] - Sizzle.find(‘ul’) Sizzle.selector.find.TAG ---> getElementsByTagName - Sizzle(‘li’, ul) - Sizzle.filter(‘:first’, lis)12年3月31⽇日星期六
  38. 38. Sizzle源码导读 1. 正则式 chunker ⽤用于分解选择器 2. 正则式Sizzle.selectors.match.POS ⽤用于判断是否有位置类型的选择器 3. Sizzle.selectors.find ⽤用于查找节点(对应于原⽣生⽅方法, getElementById, getElementsByTagName 等) 4. Sizzle.selectors.relative 过滤出符合要求的⽗父节点 5. Sizzle.selectors.filters 伪类过滤器 (我们可以扩展这个实现⾃自⼰己的伪类语法哦) 6. Sizzle.selectors.filter 各种类型的过滤器逻辑12年3月31⽇日星期六
  39. 39. 创建⾃自⼰己的选择器 jQuery.expr[‘:’][‘mod’] = function(elem) { return $(elem).hasClass(‘.mod’); }; var mod = $(‘div:mod’); 组件编写者可以根据需要编写⾃自⼰己的伪类选择器12年3月31⽇日星期六
  40. 40. 轻松⼀一下〜~12年3月31⽇日星期六
  41. 41. $.noop12年3月31⽇日星期六
  42. 42. $.proxy ⽅方便我们创建特定context和参数的closure var A = { init: function() { var div = $(‘div.mydiv’); $.ajax(url, { success: $.proxy(this, ‘success’, div); // 代替 function(o) { self.success(div, o); } }); }, success: function(div, o) { this... } };12年3月31⽇日星期六
  43. 43. elm.attr() / elm.prop() 1. jQuery 1.6之后有了prop, 即把 attribute和 property分开 2. 区分attribute 和 property 前者相当于 elm.getAttribute 后者相当于 elm[key], 如 elm.checked, sel.selectedIndex 3. checkbox.prop(‘checked’), select.prop(‘selectedIndex’) 4. 尽管jQuery在1.64及以后的版本中做了兼容性,但是不要依赖于这个特性 1. 存在jQuery.attrFn⾥里⾯面的,则直接调⽤用相应⽅方法 2. 返回undefined的,尝试使⽤用prop获取 3. 存在于jQuery.attrHook的字段,特殊处理 4. 最后使⽤用getAttribute/setAttribute12年3月31⽇日星期六
  44. 44. elm.remove() / elm.detach()12年3月31⽇日星期六
  45. 45. elm.closest()12年3月31⽇日星期六
  46. 46. remove event 在ui/core.js中 var _cleanData = $.cleanData; $.cleanData = function(elems){ for (var i = 0, elem; (elem = elems[i]) != null; i++) { $(elem).triggerHandler(remove); } _cleanData(elems); };12年3月31⽇日星期六
  47. 47. 应⽤用 所以在引⼊入ui/core的前提下: $(elm).bind(‘remove’, function() { // 清理⼯工作 // 组件或框架设计的同学可以⽤用来做清理⼯工作 });12年3月31⽇日星期六
  48. 48. 源码之前,了⽆无秘密!12年3月31⽇日星期六
  49. 49. 完12年3月31⽇日星期六

×