jquery底层架构
DESCRIPTION
TRANSCRIPT
INSIDE JQUERY市场开发部-前端 魏琪君
12年3月31⽇日星期六
⼤大纲1. 对象结构和构造成本2. Promise & Deferred3. jQuery.cache4. 事件5. Sizzle6. ⼀一些⽅方法和实现
12年3月31⽇日星期六
对象结构
12年3月31⽇日星期六
jQuery对象形成链表结构
12年3月31⽇日星期六
摘⾃自jQuery⽂文档$('ul.first').find('.foo').css('background-color', 'red') .end().find('.bar').css('background-color', 'green');
1. 不推荐过分地使⽤用链式调⽤用2. 同类型操作可以使⽤用链式调⽤用,以简化代码
12年3月31⽇日星期六
构造流程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. $(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⽇日星期六
应⽤用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⽇日星期六
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⽇日星期六
$.ajax// 通常$.ajax(url, { dataType: ‘jsonp’ success: function(o) { console.debug(o); }});
12年3月31⽇日星期六
通过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⽇日星期六
$.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⽇日星期六
Promise接⼝口另外⼀一半var Promise = { // 下⾯面3个⽅方法相当于bind done: function() {}, fail: function() {}, always: function() {} // 下⾯面两个⽅方法相当于trigger resolve: function() {}, reject: function() {}};
12年3月31⽇日星期六
Deferred对象⽤用于⽅方便我们实现promise接⼝口
12年3月31⽇日星期六
⼀一个⽰示例
12年3月31⽇日星期六
注意依赖关系
12年3月31⽇日星期六
应⽤用1. 使⽤用 $.when, promise.pipe等完成⼀一些稍复杂的异步请求需求
2. 组件开发者如有必要可以考虑提供promise接⼝口来统⼀一异步操作的使⽤用⽅方式
3. 使⽤用Deferred对象简化promise接⼝口的实现
4. 注意不能让Deferred侵⼊入业务代码(⻅见例⼦子)
5. 使⽤用Callbacks来简化observer模式的实现(1.7提供)
12年3月31⽇日星期六
$.cachejQuery使⽤用$.cache这个对象保存 $.data, 事件, 动画操作所需要的数据结构
所以了解它对于理解和调试代码有⾮非常⼤大的帮助
12年3月31⽇日星期六
12年3月31⽇日星期六
$.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⽇日星期六
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⽇日星期六
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⽇日星期六
事件
12年3月31⽇日星期六
12年3月31⽇日星期六
12年3月31⽇日星期六
12年3月31⽇日星期六
12年3月31⽇日星期六
事件源码导读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⽇日星期六
事件使⽤用技巧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⽇日星期六
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⽇日星期六
SizzlejQuery中的CSS3选择器引擎
12年3月31⽇日星期六
querySelectorAll在⾼高级浏览器中,我们可能根本不需要js来处理选择器,因为有querySelectorAll
12年3月31⽇日星期六
⾼高效的选择器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⽇日星期六
选择器使⽤用技巧1. 优先使⽤用简单选择器
2. 选取元素时,请指定合适的context(推荐为模块⼦子模块) 如 $(‘a.close’, panel)
3. class选择器请限定tag, 如tag.class
4. 适度使⽤用伪类
5. 选择器层级不要太深,⼀一般指定context后, selector不会超过2层
12年3月31⽇日星期六
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⽇日星期六
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⽇日星期六
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⽇日星期六
创建⾃自⼰己的选择器jQuery.expr[‘:’][‘mod’] = function(elem) { return $(elem).hasClass(‘.mod’);};
var mod = $(‘div:mod’);
组件编写者可以根据需要编写⾃自⼰己的伪类选择器
12年3月31⽇日星期六
轻松⼀一下〜~
12年3月31⽇日星期六
$.noop
12年3月31⽇日星期六
$.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⽇日星期六
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⽇日星期六
elm.remove() / elm.detach()
12年3月31⽇日星期六
elm.closest()
12年3月31⽇日星期六
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⽇日星期六
应⽤用所以在引⼊入ui/core的前提下:
$(elm).bind(‘remove’, function() { // 清理⼯工作 // 组件或框架设计的同学可以⽤用来做清理⼯工作});
12年3月31⽇日星期六
源码之前,了⽆无秘密!
12年3月31⽇日星期六
完
12年3月31⽇日星期六