jQuery方法源码解析--jQuery($)方法(一)

jQuery方法源码解析--jQuery($)方法

注:

1.本文分析的代码为jQuery.1.11.1版本,在官网上下载未压缩版即可

2.转载请注明出处

jQuery方法:

这个方法大家都不陌生,在使用过程中,它还有另外一个名字,美元符号:$,$(...)其实就是jQuery(...);

它有很多种用法,通常都返回一个jquery对象,也可以作为$(document).ready(...);的简写形式,分析之前先看一下jQuery都有什么用法。

1.jQuery( selector [, context ] )

selector:String;

context:Element or jQuery

在context中返回一个符合selecttor表达式的jquery对象,context默认为$(document),如果找不到自然返回一个空的jquery对象,也就是length为0的jquery对象;

eg:

$( "div.foo" ).click(function() {

$( "span", this ).addClass( "bar" );

});

2.jQuery( element ),jQuery( elementArray )

element :Element

elementArray : Array of Element

将DOM元素或数组转化(包装)为一个jquery对象。PS:从实现的原理上来说,称之为包装更合适。

eg:$(document),$(this),$(document.getElementsByTagName("div"));

3.jQuery( object )

object:PlainObject

将一个普通对象转为jQuery对象,普通对象:PlainObject,就是一个仅有键值对的对象;

这有什么用处?!暂时不大了解,官网给出解释:

At present, the only operations supported on plain JavaScript objects wrapped in jQuery are:

.data(),.prop(),.on(), .off(), .trigger() and .triggerHandler().

The use of .data() (or any method requiring .data()) on a plain object will result in a new

property on the object called jQuery{randomNumber} (eg. jQuery123456789).

4.jQuery( selection )

selection :jQuery

复制一个jQuery对象

When a jQuery object is passed to the $() function,
a clone of the object is created. This new jQuery object references the same DOM elements as the initial one.

5.jQuery()

返回一个空的jQuery对象

6.jQuery( html [, ownerDocument ] )

html:htmlString

ownerDocument:document

创建一个新的dom元素,第二个参数指定创建元素所使用的document;当有多个iframe场景可能会用到第二个元素

eg:$( "<p id=‘test‘>My <em>new</em> text</p>" ).appendTo( "body" );

7.jQuery( html, attributes )

html: htmlString,该场景下必须是单标签

attributes : PlainObject,一个使用键值对的形式描述元素属性的对象

eg:

$( "<div></div>", {

"class": "my-div",

on: {

touchstart: function( event ) {

// Do something}

}}).appendTo( "body" );

如果第二个对象的属性名字在jQuery中是一个函数(还可能是内部函数),会调用函数,属性值会被当做参数。如果attributes过于复杂,个人不建议这么写,写成下面这样比较清晰。

$( "<div></div>" )

.addClass( "my-div" )

.on({

touchstart: function( event ) {

// Do something

}

})

.appendTo( "body" );

8.jQuery( callback )

callback:function

没什么好说的,就是$(document).ready(callback)的简写形式。


what is jQuery Object??

jQuery方法,通常返回的是一个jQuery对象,那么,首先我们必须要知道jQuery对象到底是什么,它大致长成什么样子.

在html写几个li:

<body>

<ol>

<li></li>

<li></li>

<li></li>

<li></li>

</ol>

</body>

创建jQuery对象,并输出出来:

console.log($("li"));

在chrome中运行如下:

可以看出来jQuery对象,具有类数组的结构,有着从0到length-1的索引和length属性(但没有数组的concat或者slice等其他方法),而每个li,就是element DOM对象.这个类数组的结构,可以看做是原生的js对象数组,除了这个类数组的结构外,其他的就是一些jQuery属性和方法,打开指向prototype的隐性指针,就可以看到很多我们常用的jQuery方法.

总结一下,jQuery对象,就是在js对象的基础上,包装了一些jQuery方法的对象,有一个英文词形容的比较贴切,就是wrap(包裹).


jQuery方法的结构

在jQuery1.11.1的源码中,搜索jQuery,在源码开始的不远处,就可以看到如下的代码:

// Define a local copy of jQuery

jQuery = function( selector, context ) {

// The jQuery object is actually just the init constructor ‘enhanced‘

// Need init if jQuery is called (just allow error to be thrown if not included)

return new jQuery.fn.init( selector, context );

}

可以看到,jQuery就是一个函数,返回了一个对象.

== ,what?! 居然返回的是一个 jQuery.fn.init对象?!

我们搜一下jQuery.fn.init这货:

init = jQuery.fn.init = function( selector, context ) {

var match, elem;

......

};

在这个函数结束之后,下面这个代码扑面而来(不同版本这些代码的位置不一定一样,还是搜索比较靠谱):

// Give the init function the jQuery prototype for later instantiation

init.prototype = jQuery.fn;

再搜索"jQuery.fn =",可以看到jQuery.fn = jQuery.prototype = {...};

这回明了了,jQuery.fn就是jQuery这个函数的prototype,而init.prototype = jQuery.fn,就是说:

jQuery.fn.init.prototype = jQuery.prototype

那么通过jQuery.fn.init.prototype的这个函数构造出来的对象,就可以通过指向jQuery.fn.init.prototype的引用,找到jQuery的prototype下的方法,从某种意义上来说,通过jQuery.fn.init构造出来的方法,就是jQuery对象了.

总结一下,jQuery对象的构造其实是通过jQuery.fn.init来实现的,通过init.prototype = jQuery.prototype,令jQuery.fn.init对象可以使用jQuery.prototype的方法属性.


jquery.init方法

在分析init方法之前,必须认识该方法用到的两个正则表达式:

第一个:

// A simple way to check for HTML strings

// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)

// Strict HTML recognition (#11290: must start with <)

rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/

从注释上看也知道,这是匹配html的正则表达式,因为某个bug的原因,还要同时匹配#id形式的字符串,进行优先处理.

/^(...)$/-->包裹着开始和结束定位符,匹配整个字符串;

(?:...) --> ?:表示这个分组不捕获,仅仅是起分组的作用,可以提高性能;

\s*(<[\w\W]+>[^>]*  -->匹配可以空白字符开头,可以任意非‘>‘结尾的<tag>形式的字符串

#([\w-]*)  -->匹配以#开头,跟着任意字母或减号的字符串

整体翻译过来,就是匹配<tag>或#id形式的字符串

执行一下:

rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/;

console.log(rquickExpr.exec("<tag>"));

输出:["<tag>", "<tag>", undefined, index: 0, input: "<tag>"]

另外一个正则:

var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);

看名字可以猜到,这是个匹配单标签的形式。

<(\w+)\s*\/?> -->匹配一个标签,可以使<tag>或<tag />的形式

(?:<\/\1>|) -->\1表示分组1中捕获的字符串,也就是匹配和前面一样的闭合标签</tag>或者为空字符串(注意后面的或者符号什么也没跟,就是或者是空字符串)

合起来就是匹配<tag /> 或者是 <tag></tag>;

不难发现,这个正则匹配不了<tag attr="..."></tag>这种带属性的单标签,也匹配不了<tag>asdf</tag>这种带文本节点的标签,跟确切的说,这个正则只匹配单的空标签.

jQuery方法可以说是JQ最核心的方法了,而它通过init方法实现,那么它是不是很长呢?!

还真不是,也就100行,因为它还调用了jQuery的其他方法,下面单就这个init方法结构,进行分析。

直接搜索jQuery.fn.init,很快就能找到init函数的源码,大致过一下,发现很多if else,这和jQuery这个函数功能的多样性相符合;

下面直接在代码中写注释来解析这段代码

init = jQuery.fn.init = function( selector, context ) {
          var match, elem;

          //处理jQuery()形式,返回空的jQuery对象
          // HANDLE: $(""), $(null), $(undefined), $(false)
          if ( !selector ) {
               return this;
          }

          // Handle HTML strings
          /*#源码分析
           *匹配html(以<开头,以>结尾,或者是<tag>...形式,推荐写严格的html),或者是#id形式,并且确保,#id情况下,没有context
           */
          if ( typeof selector === "string" ) {
               if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
                    /*#源码分析
                    *假设以<开头,以>结尾的,并且长度大于3的String都是htmlString,然后存到match中
                    */
                    // Assume that strings that start and end with <> are HTML and skip the regex check
                    match = [ null, selector, null ];

               } else {
                    match = rquickExpr.exec( selector );
               }
              /*#源码分析
               *what is match?
               *我们分析过rquickExpr这个正则表达式,它有两个分组(最外面的那个分组不捕获,没算),
               *这样使用exec方法后,如果匹配到了,则应该返回一个长度为3,包括index和input属性的数组。
               *那么match[0]是整体匹配的数组,match[1]是匹配的<tag>标签(如果有),match[2]匹配#id(如果有)
               *显然match[1],match[2]只能有一个被捕获
               */

               // Match html or make sure no context is specified for #id
               if ( match && (match[1] || !context) ) {
                    //处理jQuery( html [, ownerDocument ] ),根据html生成dom元素,返回jQuery对象,就像下面的注释$(html) -> $(array)
                    //match[1]
                    // HANDLE: $(html) -> $(array)
                    if ( match[1] ) {
                        /*#源码分析
                         *如果context是jQuery对象,转为js原生对象.
                         *我们说过,jQuery对象就是给js原生对象包装了了一些方法的对象,而原生对象以类数组的形式存在jQuery中,
                         *所以context[0],就是去jQuery对象中的第一个原生对象,在这里context期待传入的就是document或者$(document)
                         */
                         context = context instanceof jQuery ? context[0] : context;

                        /*#源码分析
                         *这里调用了jQuery.parseHTML方法,就是把htmlString转为dom数组
                         *还调用了jQuery.merge(first,second),接收两个"类数组"参数,
                         *这个方法是把第二个数组追加到第一个数组尾部,会改变第一个数组
                         *前面讲过,jQuery对象具有类数组结构,当前还没有操作this,它的length = 0
                         *所以下面这段代码,就是把htmlString转为dom数组并追加到this的尾部。
                         */
                         // scripts is true for back-compat
                         // Intentionally let the error be thrown if parseHTML is not present
                         jQuery.merge( this, jQuery.parseHTML(
                              match[1],
                              context && context.nodeType ? context.ownerDocument || context : document,
                              true
                         ) );

                        /*#源码分析
                         *处理jQuery( html, attributes )这种用法
                         *第一个参数必须是单标签,第二个参数是一个普通对象,类似{html:"hello world",id:"test"}
                         *注意,这种情况下,会走上面的分支,已经把单标签转为dom并拼接到了this中了
                         */
                         // HANDLE: $(html, props)
                         if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
                              for ( match in context ) {
                                  /*#源码分析
                                   *这里的match是context的属性
                                   *如果有match的方法,就会调用match方法
                                   *比如{html:"hello world",id:"test"},就会调用this.html("hello world")方法
                                   *否则按照属性处理
                                   */
                                   // Properties of context are called as methods if possible
                                   if ( jQuery.isFunction( this[ match ] ) ) {
                                        this[ match ]( context[ match ] );

                                   // ...and otherwise set as attributes
                                   } else {
                                        this.attr( match, context[ match ] );
                                   }
                              }
                         }

                         return this;
                   /*#源码分析
                    *处理match[2]被捕获到的情况,也就是#id的情况
                    */
                    // HANDLE: $(#id)
                    } else {
                         elem = document.getElementById( match[2] );

                         // Check parentNode to catch when Blackberry 4.6 returns
                         // nodes that are no longer in the document #6963
                         // 为了兼容Blackberry 4.6中的一个bug,不但判断element,还要判断elem.parentNode
                         if ( elem && elem.parentNode ) {
                              // Handle the case where IE and Opera return items
                              // by name instead of ID
                             /*#源码分析
                              *早期的IE的document.getElementById()有bug,在查找元素时,会把表单的元素的name也看成是id,
                              *这时候使用find方法来兼容,搜索一下"rootjQuery =",会发现rootjQuery = jQuery( document );
                              */
                              if ( elem.id !== match[2] ) {
                                   return rootjQuery.find( selector );
                              }
                             /*#源码分析
                              *否则直接放到this[0]中
                              */
                              // Otherwise, we inject the element directly into the jQuery object
                              this.length = 1;
                              this[0] = elem;
                         }

                         this.context = document;
                         this.selector = selector;
                         return this;
                    }
              /*#源码分析
               *处理$(复杂选择器的情况),采用find方法处理并返回,
               *这里两个else的判断,就是为了确保是jQuery对象调用find方法,
               *如果context不是jQuery对象,使用constructor构造一个
               *context.jquery-->jquery是jQuery.fn上的一个版本属性,在此用这个来判断是否是jQuery对象。
               */
               // HANDLE: $(expr, $(...))
               } else if ( !context || context.jquery ) {
                    return ( context || rootjQuery ).find( selector );

               // HANDLE: $(expr, context)
               // (which is just equivalent to: $(context).find(expr)
               } else {
                    return this.constructor( context ).find( selector );
               }
         /*#源码分析
          *else selector不是String类型的
          */
          // HANDLE: $(DOMElement)
          } else if ( selector.nodeType ) {
              /*#源码分析
               *处理$(DOMElement)形式,这个比较简单,就是加到this[0]上
               */
               this.context = this[0] = selector;
               this.length = 1;
               return this;

          // HANDLE: $(function)
          // Shortcut for document ready
          } else if ( jQuery.isFunction( selector ) ) {
              /*#源码分析
               *$(function)形式,就是调用了$(document).ready()方法
               */
               return typeof rootjQuery.ready !== "undefined" ?
                    rootjQuery.ready( selector ) :
                    // Execute immediately if ready is not present
                    selector( jQuery );
          }
         /*#源码分析
          *如果selector.selector !== undefined,那么selector本身可能就是一个jQuery对象,做了如下处理,为什么呢?!不知道
          */
          if ( selector.selector !== undefined ) {
               this.selector = selector.selector;
               this.context = selector.context;
          }
         /*#源码分析
          *jQuery.makeArray(arr)是把类数组,转为纯数组形式,API是这么说的.
          *实际上makeArray还有第二个参数,但仅限内部使用,就是现在这种情况
          *jQuery.makeArray( arr, results ) -->把调用jQuery.merge把arr数组追加到results尾部
          *这里会处理jQuery( elementArray )、jQuery( object )这两种情况
          */
          return jQuery.makeArray( selector, this );
};

下集预告

经过分析之后大概对jQuery这个函数有了大致的了解,这个函数还调用了其他的jQuery的工具函数:

比如jQuery.merge、 jQuery.parseHTML、jQuery.isPlainObject、find、$(document).ready()、jQuery.makeArray方法

下一篇文章对以上的方法中选几个简单的进行分析,太复杂的,先放一放吧。

jQuery方法源码解析--jQuery($)方法(一)

时间: 2024-12-27 20:59:12

jQuery方法源码解析--jQuery($)方法(一)的相关文章

erlang下lists模块sort(排序)方法源码解析(二)

上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel模块,因为我们先前主要分析的split_1_*对应的是rmergel,我们先从rmergel查看,如下 ....................................................... split_1(X, Y, [], R, Rs) -> rmergel([[Y, X

Spring5源码解析2-register方法注册配置类

接上回已经讲完了this()方法,现在来看register(annotatedClasses);方法. // new AnnotationConfigApplicationContext(AppConfig.class); 源码 public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) { //调用默认无参构造器,里面有一大堆初始化逻辑 this(); //把传入的Class进行注册,Class既可以有@Co

TreeSet集合的add()方法源码解析(01.Integer自然排序)

>TreeSet集合使用实例 >TreeSet集合的红黑树 存储与取出(图) >TreeSet的add()方法源码     TreeSet集合使用实例 package cn.itcast_05; import java.util.TreeSet; /* * TreeSet:能够对元素按照某种规则进行排序. * 排序有两种方式 * A:自然排序 * B:比较器排序 * * TreeSet集合的特点:排序和唯一 * * 通过观察TreeSet的add()方法,我们知道最终要看TreeMap的

RestTemplate post请求使用map传参 Controller 接收不到值的解决方案 postForObject方法源码解析.md

结论 post方法中如果使用map传参,需要使用MultiValueMap来传递 RestTemplate 的 postForObject 方法有四个参数 String url => 顾名思义 这个参数是请求的url路径 Object request => 请求的body 这个参数需要再controller类用 @RequestBody 注解接收 Class responseType => 接收响应体的类型 第四个参数?postForObject 方法多种重构 Map<String

Flask源码解析:字符串方法endswith与os.path.splittext()

1.字符串方法endswith endswith方法: def endswith(self, suffix, start=None, end=None): # real signature unknown; restored from __doc__ """ S.endswith(suffix[, start[, end]]) -> bool Return True if S ends with the specified suffix, False otherwise

数组sort方法源码解析

我们提到数组排序都会想到数组的sort方法,这个方法用起来的确很方便,其原理还是用到了我们的冒泡排序,sort函数接受一个参数,参数为一个函数,如果不指定参数,则按照则按unicode码顺序排列. var arr=[7,3,6,1,5,12]; console.log(arr.sort());//[1, 12, 3, 5, 6, 7] console.log(arr.sort(function(a,b){ return a-b; })); //[1, 3, 5, 6, 7, 12] 其实源码实现

java常用关键词关键字,方法源码解析

transient volatile native final Integer String Class &&Object newInstance Class.forName,ClassLoader.loadClass ClassLoader .getResources(), ClassLoader.getSystemResources() ClassLoader .getResources(), ClassLoader.getSystemResources() public Enumer

jQuery offset()源码解析

首先是原型上的offset方法,根据arguments判断到底是取值还是设值.如果是设置,就遍历调用静态方法jQuery.offset.setOffset 如果是取值.那么就是从"var docElem,win"这里开始了. jQuery.fn.offset = function( options ) { if ( arguments.length ) {//设置元素坐标 return options === undefined ?//如果传入的参数是undefined,直接返回 th

jquery extend源码解析

$.extend(obj1,0bj2,{"name":"s","age":22}) //target 要拷贝到哪个对象上 // i 要执行拷贝的次数 // length 要拷贝的参数的长度 // name 对象参数中属性值 // options 对象参数 // clone 深度拷贝时重名对象属性的拷贝 // target 要拓展的对象 jQuery.extend = jQuery.fn.extend = function() { var src,