jQuery 源码分析2: jQuery.fn.init
1 //jQuery.fn.intit 中使用到的外部变量: 2 3 // 判断是否为HTML标签或#id 4 rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/; 5 6 // window.document的jQuery对象 7 rootjQuery = jQuery(window.document); 8 9 // 判断是否为HTML标签 10 rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); 11 12 init = jQuery.fn.init = function( selector, context ) { 13 var match, elem; 14 15 // 处理空selector $(""), $(null), $(undefined), $(false) 16 if ( !selector ) { 17 return this; // 返回空jQuery对象 18 } 19 // 处理 HTML 字符串 20 if ( typeof selector === "string" ) { 21 if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { 22 // 如果头尾含有 "<", ">"则跳过正则表达式检查 23 match = [ null, selector, null ]; 24 } else { 25 match = rquickExpr.exec( selector ); // 使用正则表达式检查是否为#id或HTML标签 26 } 27 // 确定是一个html标签,或为#id时没有特定的上下文 28 if ( match && (match[1] || !context) ) { 29 // HANDLE: $(html) -> $(array) 30 if ( match[1] ) { 31 context = context instanceof jQuery ? context[0] : context; 32 // jQuery.merge把jQuery.parseHTML的返回值合并到this上 33 // jQuery.parseHTML将HTML字符串转换为一个DOM节点的集合 34 // 如果context不为空则以conext节点为上下文来创建HTML片段 35 // jQuery.parseHTML中的参数true表明保留HTML字符串中的脚本 36 // 如果parseHTML方法不存在则会抛出异常 37 jQuery.merge( this, jQuery.parseHTML( 38 match[1], 39 context && context.nodeType ? context.ownerDocument || context : document, 40 true 41 ) ); 42 // jQuery(html, attributes),example 43 // $( "<div></div>", { 44 // "class": "my-div", 45 // on: { touchstart: function( event ) {// Do something} 46 // } 47 // }) 48 // rsingleTag检测字符串时候为一个HTML tag 49 // jQuery.isPlainObject 判断context是否为纯粹的对象字面量 50 // 纯粹对象字面量表明这个对象是有new或{}创建的,它有构造器,有原型链继承,但要排除window或DOM对象 51 52 // 如果context不是纯粹对象字面量,for in 语句操作可能会出错 53 if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { 54 for ( match in context ) { 55 // 调用自身的方法 56 if ( jQuery.isFunction( this[ match ] ) ) { 57 this[ match ]( context[ match ] ); 58 // 设置属性 59 } else { 60 this.attr( match, context[ match ] ); 61 } 62 } 63 } 64 return this; 65 // $('#id') 选择id 66 } else { 67 68 // 直接利用document的方法来说实现 69 elem = document.getElementById( match[2] ); 70 // Check parentNode to catch when Blackberry 4.6 returns 71 // nodes that are no longer in the document #6963 72 // Blackberry4.6 返回时有parentNode 73 if ( elem && elem.parentNode ) { 74 75 // IE和Opera的返回对象是第一个name符合的对象 76 if ( elem.id !== match[2] ) { // 如果ID错误,在rootjQuery中用find查找 77 return rootjQuery.find( selector ); 78 } 79 // 如果不存在这个节点,则插入一个 80 this.length = 1; 81 this[0] = elem; 82 } 83 this.context = document; // 添加上下文 84 this.selector = selector; // 更改选择器为id 85 return this; 86 } 87 // 选择器表达式: $(expr, $(...)) 88 89 // 如果context为空或context是一个jQuery对象,jQuery.jquery存放version信息 90 } else if ( !context || context.jquery ) { 91 return ( context || rootjQuery ).find( selector ); 92 // 选择器表达式: $(expr, context) 93 // 等同于: $(context).find(expr) 94 } else { // 利用jQuery构造器调用context 95 return this.constructor( context ).find( selector ); 96 } 97 // $(a_node), 一个DOM节点元素 98 } else if ( selector.nodeType ) { // 直接把该节点添加到this上 99 this.context = this[0] = selector; 100 this.length = 1; 101 return this; 102 // selector 是一个function, 调用jQuery.ready(func) 的快捷方式 103 } else if ( jQuery.isFunction( selector ) ) { 104 return typeof rootjQuery.ready !== "undefined" ? 105 rootjQuery.ready( selector ) : 106 // 如果ready方法不存在,则立即执行 107 selector( jQuery ); 108 } 109 if ( selector.selector !== undefined ) { // 如果选择器包含其他属性,赋值给this的同名属性 110 this.selector = selector.selector; 111 this.context = selector.context; 112 } 113 114 115 // 将selector合并到this上 116 return jQuery.makeArray( selector, this ); 117 };
init执行过程:
- 处理空selector,相当于返回jQuery方法接口
- 判断HTML字符串
-
- HTML字符或#id
-
- 单独HTML标签,创建DOM对象并插入
- #id,使用document.getElementById获取DOM对象
- 选择器表达式,使用$.find
- 函数,注册到ready或立即执行
- DOM对象,直接返回
可以看到init初始化一个jQuery对象,根据形参来搜索或建立一个DOM对象(又或者是执行jQuery.ready方法),并将该对象返回。
jQuery.fn.init的实现十分精明,在JavaScript中实现了方法的重载,让API调用变得更为简单方便。
过程中有针对浏览器兼容性的处理、纯粹对象判断,用来保证运行的可靠性和稳定性,这部分十分值得研读和学习。