jQuery底层架构

1,566 views

Published on

0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,566
On SlideShare
0
From Embeds
0
Number of Embeds
141
Actions
Shares
0
Downloads
27
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

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⽇日星期六

×