jQuery初始化

  • jQuery(selector, context)构造器
    前面介绍了jQuery内部全局变量和构造函数,从正则表达式和核心函数的定义中可以看出,jQuery在框架封装上追求的是简洁、实用。在构造jQuery对象中,同样可以看到jQuery的风格。首先,看下jQuery是如何构造对象?jQuery.fn.init(selector, context, rootjQuery)这个函数具有三个参数,那么jQuery的使用者只能传递两个参数,因为jQuery(seletcor, context)构造器只提供了selector、context这两个形参。从selector、context字面上理解为选择器、上下文。
  • jQuery选择器
    jQuery的api中选择器提供了五中结构:#id、element、.class、*和selector1,selector2,…,selectorN联合形式。先来看下jQuery.init初始化中源码
jQuery.fn = jQuery.prototype = {
        // The current version of jQuery being used
        jquery: core_version,
        constructor: jQuery,
        init: function(selector, context, rootjQuery) {
            var match, elem;
            // HANDLE: $(""), $(null), $(undefined), $(false)
            if (!selector) {
                return this;
            }
            // Handle HTML strings
            if (typeof selector === "string") {
                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 = rquickExpr.exec(selector);
                }
                // Match html or make sure no context is specified for #id
                if (match && (match[1] || !context)) {
                    // HANDLE: $(html) -> $(array)
                    if (match[1]) {
                        context = context instanceof jQuery ? context[0] : context;
                        var aaa = jQuery.parseHTML(
                            match[1],
                            context && context.nodeType ? context.ownerDocument || context : document,
                            true
                        )
                        // scripts is true for back-compat
                        jQuery.merge(this, aaa);
                        // HANDLE: $(html, props)
                        if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
                            for (match in context) {
                                // 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;
                        // 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
                        if (elem && elem.parentNode) {
                            // Inject the element directly into the jQuery object
                            this.length = 1;
                            this[0] = elem;
                        }
                        this.context = document;
                        this.selector = selector;
                        return this;
                    }
                    // 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);
                }
                // HANDLE: $(DOMElement)
            } else if (selector.nodeType) {
                this.context = this[0] = selector;
                this.length = 1;
                return this;
                // HANDLE: $(function)
                // Shortcut for document ready
            } else if (jQuery.isFunction(selector)) {
                return rootjQuery.ready(selector);
            }
            if (selector.selector !== undefined) {
                this.selector = selector.selector;
                this.context = selector.context;
            }
            return jQuery.makeArray(selector, this);
        },
... ...

jQuery.prototype.init函数体可以拆分为几个if结构:!selector、typeof selector === “string”和selector.selector !== undefined、selector.nodeType和jQuery.isFunction( selector )。
- !selector

// HANDLE: $(""), $(null), $(undefined), $(false)
    if (!selector) {
        return this;
    }

内容一目了然,就是传入:”“、null、undefined和false形式的选择器时,返回一个空的jQuery对象。
- selector.selector !== undefined

if (selector.selector !== undefined) {
         this.selector = selector.selector;
         this.context = selector.context;
    }
    //将selector中的属性按数组下标形式组装到this对象属性中,如this[0]、this[1]、this[2]...
    return jQuery.makeArray(selector, this);

如果选择器是一个对象,并且该selector对象包含属性selector,则将该对象包装成jQuery对象,同时由jQuery.makeArray工具方法返回selector和this属性的[]。

jQuery.extend({
    ... ...
    // results is for internal usage only
    makeArray: function(arr, results) {
        var ret = results || [];
        if (arr != null) {
            //如果Object(arr)为类数组对象,那么将arr合并到ret返回数组中
           if (isArraylike(Object(arr))) {
               jQuery.merge(ret, 
                    typeof arr === "string" ? [arr] : arr);
           } else {
                //否则,[].push(arr)直接存入数组中
               core_push.call(ret, arr);
           }
       }
       return ret;
    },
    /*
     * 将数组second合并到first数组中,并返回first,也就是first结构会改变。
     */
    merge: function(first, second) {
        var l = second.length,
            i = first.length,
            j = 0;
        if (typeof l === "number") {
            for (; j < l; j++) {
                first[i++] = second[j];
            }
        } else {
           while (second[j] !== undefined) {
               first[i++] = second[j++];
           }
        }
        first.length = i;
        return first;
     },
    ... ...
});
/**
 -  判断obj是否为数组的依据是length属性和类型,处理window对象外都可以组装成数组。
 -  对于每一个页面都只能有一个window对象。
 */
function isArraylike(obj) {
    var length = obj.length,
        type = jQuery.type(obj);
    if (jQuery.isWindow(obj)) {
       return false;
    }
    if (obj.nodeType === 1 && length) {
       return true;
    }
    return type === "array" || type !== "function" &&
        (length === 0 ||
           typeof length === "number" && length > 0 && (length - 1) in obj);
}   
  • selector.nodeType
    如果选择器为HTMLElement元素,则将该元素包装为jQuery对象,并返回。此时,this[0]属性和this.context都设置为selector,并且this.length为1,也就是说jQuery对象是一个isArraylike检查为true的类数组对象。
  • jQuery.isFunction( selector )
    如果选择器为函数,则将该函数作为ready事件的绑定函数,也就是document.onload的加载处理器。
  • typeof selector === “string”
    字符类型的选择器可以分为#id、<*>类型的选择器,同时context有无对元素定位也是不同。所以将该类型的选择器切分为几个分支结构:
  • 单标签的html(<input>、 <img>和<link>等等)
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 ]; 
// 如果selector是aa<....>这种形式的标签, 则html字符检查正则表达式/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/ 
} else {
     // 这个正则表达式可以检查<...>html字符,和#...这种Id字符,并且match匹配只会有两种  [<aa>dd,<aa>,undefined]或[#dd,undefined,dd] 
     match = rquickExpr.exec( selector ); }
  • <*>*</*>、#id选择器
// Match html or make sure no context is specified for #id,如果match有匹配结果,则进行处理
if ( match && (match[1] || !context) ) {
    // HANDLE: $(html) -> $(array),如果匹配的是html元素选择器,如<div>、aa<div>、......
    if ( match[1] ) {
  //判断当前Element上下文是否jQuery上下文,如果是则去[0]的元素
       context = context instanceof jQuery ? context[0] : context;
  //返回Element元素数组
       var aaa = jQuery.parseHTML(match[1],
       context && context.nodeType ? context.ownerDocument || context : document, true)
//合并aaa到this上下文中
        // scripts is true for back-compat
        jQuery.merge( this,  aaa);
        // HANDLE: $(html, props)
        if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
           for ( match in context ) {
             // 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;
       // HANDLE: $(#id),如果选择器是按id来进行,那么调用document原生getElementById来实现
    } else {
       elem = document.getElementById( match[2] );
       // Check parentNode to catch when Blackberry 4.6 returns
       // nodes that are no longer in the document #6963,
   //因为在黑莓4.6版本中存在Element节点没有parentNode属性情况,所以这里单独处理
       if ( elem && elem.parentNode ) {
          // Inject the element directly into the jQuery object
          this.length = 1;
          this[0] = elem;
        }
//如果元素没有parentNode,那么this对象中就没有元素,而只是一个空的jQuery对象
        this.context = document;
        this.selector = selector;
       return this;
     }
// Match html or make sure no context is specified for #id,如果match有匹配结果,则进行处理
    if ( match && (match[1] || !context) ) {
       // HANDLE: $(html) -> $(array),如果匹配的是html元素选择器,如<div>、aa<div>、......
       if ( match[1] ) { 
//判断当前Element上下文是否jQuery上下文,如果是则去[0]的元素
       context = context instanceof jQuery ? context[0] : context;
//返回Element元素数组
           var aaa = jQuery.parseHTML(match[1],
               context && context.nodeType ? context.ownerDocument || context : document, true)
//合并aaa到this上下文中
            // scripts is true for back-compat
            jQuery.merge( this,  aaa);
            // HANDLE: $(html, props)
            if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
               for ( match in context ) {
            // 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;
// HANDLE: $(#id),如果选择器是按id来进行,那么调用document原生getElementById来实现
} else {
elem = document.getElementById( match[2] );
// Check parentNode to catch when Blackberry 4.6 returns
        // nodes that are no longer in the document #6963,
//因为在黑莓4.6版本中存在Element节点没有parentNode属性情况,所以这里单独处理
        if ( elem && elem.parentNode ) {
        // Inject the element directly into the jQuery object
            this.length = 1;
             this[0] = elem;
         }
//如果元素没有parentNode,那么this对象中就没有元素,而只是一个空的jQuery对象
         this.context = document;
         this.selector = selector;
         return this;
      }
// HANDLE: $(expr, $(...)),如果为selector指定了context(jQuery对象),那么直接调用find方法筛选对象
}

这一块包含的内容可以从context有没有进行区分,因为从match = rquickExpr.exec( selector )开始,就像match数组的match[1]有没有值进行判断<div>和#id选择器。首先!context为真、match[1]为真时document.createElement(*)创建HTMLElement元素,也就是$(<div>)创建元素。其次!context为真、match[1]为假时document.getElementById(match[2])先获取元素,然后包装成jQuery对象。然后,当!context为真、match为false,也即形如div、.class、parent>child和selector1…这种选择器,由( context || rootjQuery ).find( selector )筛选出符合条件的元素。最后,构造this.constructor( context ).find( selector )jQuery对象,同时查找符合selector条件的元素。
从源码中看到jQuery.prototype原型中修正了constructor构造器,这样做的目的是因为jQuery.prototype原型重新赋值了Object字面量{},那么jQuery的constructor就不在指向本身,而是指向了Object,所以通过人为的给{}添加constructor属性,并让它指向jQuery来修正constructor。
- 结论
从上面看出,jQuery初始化函数init中实现了jQuery选择器selector,对于简单的element、#id和<\w\W+>形式的元素选择,可以直接包装jQuery对象返回,此外jQuery对象本身是一个类数组的对象,它方招数组下标访问形式this[0]、this[1]… … 和this.length属性,可以for(var)下标遍历,而不只是传统对象{}的foreach遍历。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值