jQuery源码学习笔记(二)

一、jQuery选择器

通过jQuery选择器获取的DOM元素以类数组的形式,作为jQuery对象的一部分返回。实际上jQuery对象是通过new jQuery.fn.init(selector,context)实例化的一个对象,此对象通过jQuery.fn.init.prototype=jQuery.fn继承了jQuery的方法。在init()构造器函数中,大致的处理思路如下:

  1. 是否为空字符串或者为null、undefined等情况,是则直接返回;
  2. 是否为DOM元素,如果是则直接把该对象存储到jQuery对象集合中并返回;
  3. 是否为body,是则返回document.body;
  4. 是否为HTML string或者为ID:

    HTML标签:则创建相应的对象并返回;
    ID:找到这个元素并返回;

  5. 是否为选择器表达式,是则调用find函数找到相应对象并返回;
  6. 是否为function,是则return rootjQuery.ready( selector )
  7. 是否为DOM数组,是则依次存储到jQuery对象集合中并返回。

jQuery内部的选择器是用的Sizzle引擎,主要包括matches()find()filter()方法,这些接口传递给jQuery的find,filter,expr等公共函数。下面是几点使用选择器时可以提高性能表现的建议:

  • 多用ID选择器,因为它是最快的,在选择器父级元素中增加ID选择器可以缩短节点访问路程;
  • 少用直接的Class选择器,因为它是最慢的,可以考虑使用复合选择器代替;
  • 多用父子关系,少用嵌套关系,比如parent>child就要比parent child快;
  • 缓存jQuery对象。

使用jQuery选择器可以比较好的解决兼容性问题,能够有更优雅的语法,但是其缺点也是明显的:相比原生,jQuery的部分选择器效率非常低。

二、DOM操作

jQuery封装的DOM操作非常多,语法简洁,功能强大,但是同样存在效率低下的问题,实际中要根据需要选择。DOM不同操作之间的联系不想选择器那么紧密,下面仅选择几个小点来解读jQuery源码。

删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// keepData is for internal use only--do not document
remove: function( selector, keepData ) { //删除工作会首先根据选择器选择备选元素
for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { //遍历备选元素
if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { //找到了需要删除的元素
if ( !keepData && elem.nodeType === 1 ) {
jQuery.cleanData( elem.getElementsByTagName("*") );
jQuery.cleanData( [ elem ] ); //删除一些绑定的事件
}

if ( elem.parentNode ) { //删除元素
elem.parentNode.removeChild( elem );
}
}
}

return this;
},

empty: function() {
for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
// Remove element nodes and prevent memory leaks
if ( elem.nodeType === 1 ) {
jQuery.cleanData( elem.getElementsByTagName("*") );
}

// Remove any remaining nodes
while ( elem.firstChild ) { //清空所有节点
elem.removeChild( elem.firstChild );
}
}

return this;
}

attr()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
attr: function( name, value ) {  //access中会判断value是否为对象,是则递归调用真正的attr函数
return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
}

//真正的attr函数
attr: function( elem, name, value, pass ) {
// 忽略文字注释等节点的属性
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
return;
}
//做一些兼容性工作

if ( value !== undefined ) {

if ( value === null ) {
jQuery.removeAttr( elem, name );
return; //为空时则移除移除属性
} else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
return ret;

} else {
elem.setAttribute( name, "" + value );
return value; //设置属性
}

} else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
return ret;

} else {
ret = elem.getAttribute( name ); //获取属性值
// Non-existent attributes return null, we normalize to undefined
return ret === null ?
undefined :
ret;
}
}

这里需要说明的是,两个attr()方法并不是挂在相同对象下,第一个是使用的jQuery.fn.extend()扩展的,第二个是使用jQuery.extend()扩展的,两个方法的区别:

  • jQuery.fn.extend():在原型链上扩展,所有jQuery对象都能使用,使用方法为:jQuery对象.handler()
  • jQuery.extend():在jQuery对象上扩展,也就是说扩展的方法是全局的,jQuery对象是访问不到的,使用方法为:jQuery.handler()