jquery底层架构

49
INSIDE JQUERY 市场开发部-前端 魏琪君 12年3月31星期六

Upload: fangdeng

Post on 18-Jan-2015

1.207 views

Category:

Documents


6 download

DESCRIPTION

 

TRANSCRIPT

Page 1: jQuery底层架构

INSIDE JQUERY市场开发部-前端 魏琪君

12年3月31⽇日星期六

Page 2: jQuery底层架构

⼤大纲1. 对象结构和构造成本2. Promise & Deferred3. jQuery.cache4. 事件5. Sizzle6. ⼀一些⽅方法和实现

12年3月31⽇日星期六

Page 3: jQuery底层架构

对象结构

12年3月31⽇日星期六

Page 4: jQuery底层架构

jQuery对象形成链表结构

12年3月31⽇日星期六

Page 5: jQuery底层架构

摘⾃自jQuery⽂文档$('ul.first').find('.foo').css('background-color', 'red') .end().find('.bar').css('background-color', 'green');

1. 不推荐过分地使⽤用链式调⽤用2. 同类型操作可以使⽤用链式调⽤用,以简化代码

12年3月31⽇日星期六

Page 6: jQuery底层架构

构造流程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⽇日星期六

Page 7: jQuery底层架构

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.buildFragment

12年3月31⽇日星期六

Page 8: jQuery底层架构

应⽤用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对象时,优先采⽤用 simpleTag

12年3月31⽇日星期六

Page 9: jQuery底层架构

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

Page 10: jQuery底层架构

$.ajax// 通常$.ajax(url, { dataType: ‘jsonp’ success: function(o) { console.debug(o); }});

12年3月31⽇日星期六

Page 11: jQuery底层架构

通过promise接⼝口使⽤用ajaxvar 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⽇日星期六

Page 12: jQuery底层架构

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

Page 13: jQuery底层架构

Promise接⼝口另外⼀一半var Promise = { // 下⾯面3个⽅方法相当于bind done: function() {}, fail: function() {}, always: function() {} // 下⾯面两个⽅方法相当于trigger resolve: function() {}, reject: function() {}};

12年3月31⽇日星期六

Page 14: jQuery底层架构

Deferred对象⽤用于⽅方便我们实现promise接⼝口

12年3月31⽇日星期六

Page 15: jQuery底层架构

⼀一个⽰示例

12年3月31⽇日星期六

Page 16: jQuery底层架构

注意依赖关系

12年3月31⽇日星期六

Page 17: jQuery底层架构

应⽤用1. 使⽤用 $.when, promise.pipe等完成⼀一些稍复杂的异步请求需求

2. 组件开发者如有必要可以考虑提供promise接⼝口来统⼀一异步操作的使⽤用⽅方式

3. 使⽤用Deferred对象简化promise接⼝口的实现

4. 注意不能让Deferred侵⼊入业务代码(⻅见例⼦子)

5. 使⽤用Callbacks来简化observer模式的实现(1.7提供)

12年3月31⽇日星期六

Page 18: jQuery底层架构

$.cachejQuery使⽤用$.cache这个对象保存 $.data, 事件, 动画操作所需要的数据结构

所以了解它对于理解和调试代码有⾮非常⼤大的帮助

12年3月31⽇日星期六

Page 19: jQuery底层架构

12年3月31⽇日星期六

Page 20: jQuery底层架构

$.data1. 我们使⽤用$(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⽇日星期六

Page 21: jQuery底层架构

data类型识别从data属性获取的值,会根据属性字符串的样式,⽽而进⾏行适当的类型转换

1. “true” --> true

2. “false” --> false

3. “null” --> null

4. $.isNumberic(data) --> parseFloat(data)

5. “{...}” 或 “[]” --> parseJSON, 如果parse失败,则返回字符串 这⾥里如果要正常parse出json,必须是严格的json格式才可以。

6. 直接返回字符串

具体过程看:function dataAttr(

12年3月31⽇日星期六

Page 22: jQuery底层架构

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

Page 23: jQuery底层架构

事件

12年3月31⽇日星期六

Page 24: jQuery底层架构

12年3月31⽇日星期六

Page 25: jQuery底层架构

12年3月31⽇日星期六

Page 26: jQuery底层架构

12年3月31⽇日星期六

Page 27: jQuery底层架构

12年3月31⽇日星期六

Page 28: jQuery底层架构

事件源码导读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⽇日星期六

Page 29: jQuery底层架构

事件使⽤用技巧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⽇日星期六

Page 30: jQuery底层架构

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

Page 31: jQuery底层架构

SizzlejQuery中的CSS3选择器引擎

12年3月31⽇日星期六

Page 32: jQuery底层架构

querySelectorAll在⾼高级浏览器中,我们可能根本不需要js来处理选择器,因为有querySelectorAll

12年3月31⽇日星期六

Page 33: jQuery底层架构

⾼高效的选择器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⽇日星期六

Page 34: jQuery底层架构

其他可参考:http://caniuse.com/

12年3月31⽇日星期六

Page 35: jQuery底层架构

选择器使⽤用技巧1. 优先使⽤用简单选择器

2. 选取元素时,请指定合适的context(推荐为模块⼦子模块) 如 $(‘a.close’, panel)

3. class选择器请限定tag, 如tag.class

4. 适度使⽤用伪类

5. 选择器层级不要太深,⼀一般指定context后, selector不会超过2层

12年3月31⽇日星期六

Page 36: jQuery底层架构

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

Page 37: jQuery底层架构

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

Page 38: jQuery底层架构

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

Page 39: jQuery底层架构

创建⾃自⼰己的选择器jQuery.expr[‘:’][‘mod’] = function(elem) { return $(elem).hasClass(‘.mod’);};

var mod = $(‘div:mod’);

组件编写者可以根据需要编写⾃自⼰己的伪类选择器

12年3月31⽇日星期六

Page 40: jQuery底层架构

轻松⼀一下〜~

12年3月31⽇日星期六

Page 41: jQuery底层架构

$.noop

12年3月31⽇日星期六

Page 42: jQuery底层架构

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

Page 43: jQuery底层架构

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/setAttribute

12年3月31⽇日星期六

Page 44: jQuery底层架构

elm.remove() / elm.detach()

12年3月31⽇日星期六

Page 45: jQuery底层架构

elm.closest()

12年3月31⽇日星期六

Page 46: jQuery底层架构

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

Page 47: jQuery底层架构

应⽤用所以在引⼊入ui/core的前提下:

$(elm).bind(‘remove’, function() { // 清理⼯工作 // 组件或框架设计的同学可以⽤用来做清理⼯工作});

12年3月31⽇日星期六

Page 48: jQuery底层架构

源码之前,了⽆无秘密!

12年3月31⽇日星期六

Page 49: jQuery底层架构

12年3月31⽇日星期六