jquery构造器的实现代码小结

  显然,能做到这一步,其实现是相当的复杂,这个实现就是它的init方法,jQuery的真实构造器。它功能也随着版本的升级而升级,越来越长。

  2009-01-13发布的1.3版

  

复制代码 代码如下:

  init: function( selector, context ) {

  // Make sure that a selection was provided

  selector = selector || document;

  // 处理节点参数,直接添加属性到新实例上

  if ( selector.nodeType ) {

  this[0] = selector;

  this.length = 1;

  this.context = selector;

  return this;

  }

  // 处理字符串参数

  if ( typeof selector === "string" ) {

  // 判定是否为HTML片断还是ID

  var match = quickExpr.exec( selector );

  if ( match && (match[1] || !context) ) {

  // 如果是HTML片断,转换一个由节点构造的数组

  if ( match[1] )

  selector = jQuery.clean( [ match[1] ], context );

  // 如果是ID,则查找此元素,如果找到放进空数组中

  else {

  var elem = document.getElementById( match[3] );

  // Make sure an element was located

  if ( elem ){

  // 处理 IE and Opera 混淆ID与NAME的bug

  if ( elem.id != match[3] )

  return jQuery().find( selector );

  var ret = jQuery( elem );

  ret.context = document;

  ret.selector = selector;

  return ret;

  }

  selector = [];

  }

  } else

  //使用Sizzle处理其他CSS表达式,生成实例并返回

  return jQuery( context ).find( selector );

  // 处理函数参数,直接domReady

  } else if ( jQuery.isFunction( selector ) )

  return jQuery( document ).ready( selector );

  //处理jQuery对象参数,简单地将其两个属性赋给新实例

  if ( selector.selector && selector.context ) {

  this.selector = selector.selector;

  this.context = selector.context;

  }

  //将上面得到节点数组,用setArray方法把它们变成实例的元素

  return this.setArray(jQuery.makeArray(selector));

  },

  2009-02-19发布的1.32版

  

复制代码 代码如下:

  init: function( selector, context ) {

  // Make sure that a selection was provided

  selector = selector || document;

  // 处理节点参数,直接添加属性到新实例上

  if ( selector.nodeType ) {

  this[0] = selector;

  this.length = 1;

  this.context = selector;

  return this;

  }

  //处理字符串参数

  if ( typeof selector === "string" ) {

  //判定是否为HTML片断还是ID

  var match = quickExpr.exec( selector );

  if ( match && (match[1] || !context) ) {

  // 如果是HTML片断,转换一个由节点构造的数组

  if ( match[1] )

  selector = jQuery.clean( [ match[1] ], context );

  else {

  var elem = document.getElementById( match[3] );

  // 如果是ID,则查找此元素,如果找到放进空数组中

  if ( elem && elem.id != match[3] )

  return jQuery().find( selector );

  //这里对1.3版做了些优化,更简洁

  var ret = jQuery( elem || [] );

  ret.context = document;

  ret.selector = selector;

  return ret;

  }

  } else

  //使用Sizzle处理其他CSS表达式,生成实例并返回

  return jQuery( context ).find( selector );

  // 处理函数参数,进行domReady操作

  } else if ( jQuery.isFunction( selector ) )

  return jQuery( document ).ready( selector );

  //处理jQuery对象参数,简单地将其两个属性赋给新实例

  if ( selector.selector && selector.context ) {

  this.selector = selector.selector;

  this.context = selector.context;

  }

  //这里对1.3版做了些扩展,允许传珍上元素集合(HTMLCollection)与节点集合(NodeList),

  //元素数组可能是我们用字符串转换过来的,也可以是用户直接传进来的

  return this.setArray(jQuery.isArray( selector ) ? selector : jQuery.makeArray(selector));

  },

  2010-01-13发布的1.4版

  

复制代码 代码如下:

  init: function( selector, context ) {

  var match, elem, ret, doc;

  //处理空白字符串,null,undefined参数(新增),返回一个非常纯净的实例

  if ( !selector ) {

  return this;

  }

  // 处理节点参数,直接添加属性到新实例上

  if ( selector.nodeType ) {

  this.context = this[0] = selector;//写法上优化

  this.length = 1;

  return this;

  }

  //处理字符串参数

  if ( typeof selector === "string" ) {

  // 判定是否为HTML片断还是ID

  match = quickExpr.exec( selector );

  if ( match && (match[1] || !context) ) {

  //如果是HTML片断

  if ( match[1] ) {

  //取得文档对象

  doc = (context ? context.ownerDocument || context : document);

  // 如果是单个标签,直接使用 document.createElement创建此节点并放入数组中

  ret = rsingleTag.exec( selector );

  if ( ret ) {

  //如果后面跟着一个纯净的JS对象,则为此节点添加相应的属性或样式

  if ( jQuery.isPlainObject( context ) ) {

  selector = [ document.createElement( ret[1] ) ];

  jQuery.fn.attr.call( selector, context, true );

  } else {

  selector = [ doc.createElement( ret[1] ) ];

  }

  } else {

  //改由buildFragment来生成节点集合(NodeList)

  ret = buildFragment( [ match[1] ], [ doc ] );

  selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;

  }

  } else {

  // 如果是ID,则查找此元素,如果找到放进空数组中

  elem = document.getElementById( match[2] );

  if ( elem ) {

  // 处理 IE and Opera 混淆ID与NAME的bug

  if ( elem.id !== match[2] ) {

  return rootjQuery.find( selector );

  }

  //这里也做了一些优化,原来是很傻地再生成一个jQuery实例

  this.length = 1;

  this[0] = elem;

  }

  this.context = document;

  this.selector = selector;

  return this;

  }

  // 如果字符是很简单的标签选择器,那基本没有必要走Sizzle路线,直接getElementsByTagName,很好的优化

  } else if ( !context && /^\w+$/.test( selector ) ) {

  this.selector = selector;

  this.context = document;

  selector = document.getElementsByTagName( selector );

  // 如果第二个参数不存在或者是jQuery对象,那么用它或rootjQuery调用find查找目标节点(走Sizzle路线)

  } else if ( !context || context.jquery ) {

  return (context || rootjQuery).find( selector );

  // HANDLE: $(expr, context)

  // (which is just equivalent to: $(context).find(expr)

  } else {

  //如果第二个参数已指定为某元素节点,转为jQuery对象,走Sizzle路线

  return jQuery( context ).find( selector );

  }

  // 处理函数参数,直接domReady

  } else if ( jQuery.isFunction( selector ) ) {

  return rootjQuery.ready( selector );

  }

  //处理jQuery对象参数,简单地将其两个属性赋给新实例

  if (selector.selector !== undefined) {

  this.selector = selector.selector;

  this.context = selector.context;

  }

  //这里又做了些许修改,缘于makeArray可以接受第二个参数(可以是数组或类数组,这时相当合并操作)

  return jQuery.isArray( selector ) ?

  this.setArray( selector ) ://内部用push方法,迅速将一个普通对象变成类数组对象

  jQuery.makeArray( selector, this );

  },

  接着是广受欢迎的2010-02-13发布的1.42版

  

复制代码 代码如下:

  init: function( selector, context ) {

  var match, elem, ret, doc;

  // 处理空白字符串,null,undefined参数

  if ( !selector ) {

  return this;

  }

  // 处理节点参数

  if ( selector.nodeType ) {

  this.context = this[0] = selector;

  this.length = 1;

  return this;

  }

  // 处理body参数(新增)

  if ( selector === "body" && !context ) {

  this.context = document;

  this[0] = document.body;

  this.selector = "body";

  this.length = 1;

  return this;

  }

  // 处理字符串参数,分七种情形:

  //①单个标签,带对象属性包 ---> jQuery.merge

  //②单个标签,不带对象属性包 ---> attr + jQuery.merge

  //③复杂的HTML片断 ---> buildFragment + jQuery.merge

  //④ID选择器,与找到的元素的ID不同 ---> getElementById + Sizzle + pushStack

  //⑤ID选择器,与找到的元素的ID相同 ---> getElementById + 简单属性添加

  //⑥标签选择器 ---> getElementsByTagName + jQuery.merge

  //⑦其他CSS表达式 ---> Sizzle + pushStack

  if ( typeof selector === "string" ) {

  match = quickExpr.exec( selector );

  if ( match && (match[1] || !context) ) {

  if ( match[1] ) {

  doc = (context ? context.ownerDocument || context : document);

  ret = rsingleTag.exec( selector );

  if ( ret ) {

  if ( jQuery.isPlainObject( context ) ) {

  selector = [ document.createElement( ret[1] ) ];

  jQuery.fn.attr.call( selector, context, true );

  } else {

  selector = [ doc.createElement( ret[1] ) ];

  }

  } else {

  ret = buildFragment( [ match[1] ], [ doc ] );

  selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;

  }

  return jQuery.merge( this, selector );

  } else {

  elem = document.getElementById( match[2] );

  if ( elem ) {

  if ( elem.id !== match[2] ) {

  return rootjQuery.find( selector );

  }

  this.length = 1;

  this[0] = elem;

  }

  this.context = document;

  this.selector = selector;

  return this;

  }

  } else if ( !context && /^\w+$/.test( selector ) ) {

  this.selector = selector;

  this.context = document;

  selector = document.getElementsByTagName( selector );

  return jQuery.merge( this, selector );

  } else if ( !context || context.jquery ) {

  return (context || rootjQuery).find( selector );

  } else {

  return jQuery( context ).find( selector );

  }

  // 处理函数参数,直接domReady

  } else if ( jQuery.isFunction( selector ) ) {

  return rootjQuery.ready( selector );

  }

  //处理jQuery对象参数

  if (selector.selector !== undefined) {

  this.selector = selector.selector;

  this.context = selector.context;

  }

  //无论是数组还是类数组(如NodeList),统统使用jQuery.makeArray来为实例添加新的元素

  return jQuery.makeArray( selector, this );

  },

  另附上makeArray方法与merge方法,merge方法好神奇啊,

  

复制代码 代码如下:

  makeArray: function( array, results ) {

  var ret = results || [];

  if ( array != null ) {

  // The window, strings (and functions) also have 'length'

  // The extra typeof function check is to prevent crashes

  // in Safari 2 (See: #3039)

  if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) {

  push.call( ret, array );

  } else {

  jQuery.merge( ret, array );

  }

  }

  return ret;

  },

  merge: function( first, second ) {

  var i = first.length, j = 0;

  if ( typeof second.length === "number" ) {

  for ( var l = second.length; j < l; j++ ) {

  first[ i++ ] = second[ j ];

  }

  } else {

  while ( second[j] !== undefined ) {

  first[ i++ ] = second[ j++ ];

  }

  }

  first.length = i;

  return first;

  },

  2011-01-23发布的1.5版,其init方法与1.42的变化不大:只有两处做了改动:

  

复制代码 代码如下:

  //1.42

  - ret = buildFragment( [ match[1] ], [ doc ] );

  - selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;

  //1.5

  + ret = jQuery.buildFragment( [ match[1] ], [ doc ] );

  + selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;

  //1.42

  - return jQuery( context ).find( selector );

  //1.5

  + return this.constructor( context ).find( selector );//目的就是为了不再生成新实例

  2011-05-02发布的jquery1.6,变化不大,只是对HTML片断进行了更严密的判定:

  

复制代码 代码如下:

  // Are we dealing with HTML string or an ID?

  if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {

  // Assume that strings that start and end with <> are HTML and skip the regex check

  match = [ null, selector, null ];

  } else {

  match = quickExpr.exec( selector );

  }

  总体来说,jQuery的构造器已经做得非常之完美,基本上达到“改无可改”的地步了。但是要保证其高效运作,我们还需要一点选择器的知识与了解buildFragment方法的运作,因为这两个实在太常用了,但也是最耗性能的。