• jQuery1.9.1--结构及$方法的工作原理源码分析


    jQuery的$方法使用起来非常的多样式,接口实在太灵活了,有点违反设计模式的原则职责单一。但是用户却非常喜欢这种方式,因为不用记那么多名称,我只要记住一个$就可以实现许多功能,这个$简直就像个万能的魔术师,想要什么就变出来。其实当我们拆穿了这个魔术的表象,你会看到一个混乱的内部,jQuery内部做了太多的事了,而能够将这个混乱一一理清,并且毫无问题的运行起来,John Resig的能力不禁让人敬佩。

    下面先来看看jQuery中的$方法(又叫jQuery)的几种用途或者调用方式:

    1.$(document)  
    2.$(‘<div>’)
    3.$(‘div’)
    4.$(‘#test’)
    5.$(function(){})
    6.$("input:radio", document.forms[0]);
    7.$(‘input’, $(‘div’))
    8.$()
    9.$("<div>", {
             "class": "test",
             text: "Click me!",
             click: function(){ $(this).toggleClass("test"); }
          }).appendTo("body");
    10$($(‘.test’))

    当我们执行一个$方法时,例如:$('div'),在控制台console.log($('div'))看看出来的是什么东西:

    1. 0: div
    2. context: document
    3. length: 1
    4. prevObject: jQuery.fn.jQuery.init[1]
    5. selector: "div"
    6. __proto__: Object[0]
      1. add: function ( selector, context ) {
      2. addBack: function ( selector ) {
      3. addClass: function ( value ) {
      4. after: function () {
      5. ajaxComplete: function ( fn ){
      6. ajaxError: function ( fn ){
      7. ajaxSend: function ( fn ){
      8. ajaxStart: function ( fn ){
      9. ajaxStop: function ( fn ){
      10. ajaxSuccess: function ( fn ){
      11. andSelf: function ( selector ) {
      12. animate: function ( prop, speed, easing, callback ) {
      13. append: function () {

    我们看到一个对象有0?, length, (这不是数组吗?),preObject, selector这些属性,而且这是个实例对象,其隐藏属性__proto__指向一个原型对象,那个原型对象有一堆方法,那堆方法正是jQuery手册的方法。

    现在就让我们进入$的世界,看看它究竟是何方神圣

    在jQuery源码中看到jQuery(即$的定义方式),返回的是一个jQuery.fn.init这个构造函数的实例,这个jQuery方法就相当于是个简单的工厂方法,rootjQuery也是一个jQuery方法返回的实例,只是其参数是document,这里作为jQuery的根对象

    var jQuery = function( selector, context ) {
            return new jQuery.fn.init( selector, context, rootjQuery );
    }

    //...

    rootjQuery = jQuery(document);

    接下来往源码下面看:

    jQuery.fn = jQuery.prototype = {
        jQuery: core_version,
        constructor: jQuery,
        // 构造函数
        init: function (selector, context, rootjQuery) {
            //…
        }
    }

    将jQuery的prototype对象的引用指向jQuery.fn,当两者其中一个发生改变,另一个也会随之改变。 jQuery.fn相当于是jQuery.prototype的简写。

    然后init这个构造函数了,$的万能钥匙就在这个构造函数里:

    // 构造函数
            init: function (selector, context, rootjQuery) {
                var match, elem;
                // 处理 $(""), $(null), $(undefined), $(false)
                // 返回的是jQuery()方法实例
                if (!selector) {
                    return this;
                }
    
                // 处理HTML字符串
                if (typeof selector === 'string') {
                    if (selector.charAt(0) === '<' && selector.charAt(selector.length - 1) === '>' && selector.length >= 3) {
                        // 假设字符串以“<”开始且“>”结束
                        // 说明是HTML,略过正则检查
                        match = [null, selector, null];
                    } else {
                        match = rquickExpr.exec(selector);
                    }
    
                    // 匹配HTML或者确保#id的上下文没被指定
                    if (match && (match[1] || !context)) {
                        // 处理 $(html) -> $(array)
                        if (match[1]) {
                            context = context instanceof jQuery ? context[0] : context;
    
                            // 向后兼容
                            jQuery.merge(this, jQuery.parseHTML(
                                match[1],
                                context && context.nodeType ? context.ownerDocument || context : document,
                                true
                            ));
    
                            // 处理 $(html, props)
                            if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
                                for (match in context) {
                                    //
                                    if (jQuery.isFunction(this[match])) {
                                        this[match](context[match]);
                                    } else {
                                        this.attr(match, context[match]);
                                    }
                                }
                            }
    
                            return this;
    
                            // 处理 $(#id)
                        } else {
                            elem = document.getElementById(match[2]);
    
                            // 检查parentNode,因为Blackberry 4.6
                            // 返回的节点不在document中
                            if (elem && elem.parentNode) {
                                // 处理Opera返回的是name而不是id
                                if (elem.id !== match[2]) {
                                    return rootjQuery.find(selector);
                                }
    
                                // 给this实例对象添加类似数组的属性
                                this.length = 1;
                                this[0] = elem;
                            }
    
                            // 再添加上下文,选择器属性,最后返回this,结束函数
                            this.context = document;
                            this.selector = selector;
                            return this;
                        }
    
                        // 处理 $(expr, [$(...)])
                    } else if (!context || context.jQuery) {
                        // 返回jQuery.fn.find()获取的匹配元素,
                        // 该方法会使用jQuery.find方法(即Sizzle),
                        // 然后通过jQuery.fn.pushStack和merge方法附加元素集及合并
                        return (context || rootjQuery).find(selector);
    
                        // 处理 $(expr, context)
                        // 即 $(context).find(expr)
                    } else {
                        return this.constructor(context).find(selector);
                    }
    
                    // 处理$(DOMElement)
                } else if (selector.nodeType) {
                    this.context = this[0] = selector;
                    this.length = 1;
                    return this;
    
                    // 处理$(function)
                    // jQUery(document) ready的简写
                } else if (jQuery.isFunction(selector)) {
                    // 调用jQuery.fn.ready方法
                    return rootjQuery.ready(selector);
                }
    
                // 处理$($(...))
                if (selector.selector !== undefined) {
                    this.selector = selector.selector;
                    this.context = selector.context;
                }
    
                // 返回伪数组对象
                return jQuery.makeArray(selector, this);
            }
    

      看完了init构造函数大概知道了$方法的多样式原来是这样来的,这么多条件判断。现在我们仅仅知道$()方法是怎么判断参数,却对生成的相应的元素集原理很模糊。我们还是以$('div')为例,当被实例化后会执行到这个分支的内容:

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

    这里调用了jQuery.fn.find方法,让我们来看看该部分的源码:

    find: function (selector) {
                var i, ret, self,
                    len = this.length;
    
                if (typeof selector !== 'string') {
                    self = this;
                    return this.pushStack(jQuery(selector).filter(function () {
                        for (i = 0; i < len; i++) {
                            if (jQuery.contains(self[i], this)) {
                                return true;
                            }
                        }
                    }));
                }
    
                ret = [];
                for (i = 0; i < len; i++) {
                    jQuery.find(selector, this[i], ret);
                }
    
                // Needed because $( selector, context ) becomes $( context ).find( selector )
                ret = this.pushStack(len > 1 ? jQuery.unique(ret) : ret);
                ret.selector = (this.selector ? this.selector + ' ' : '') + selector;
                return ret;
    }
    

      代码比较简单,没怎么注释,这里遍历jQuery元素集然后使用jQuery.find静态方法(同时也就是Sizzle方法),先暂时不讲解Sizzle选择器的工作原理,因为这不是一个篇幅可以讲完的事。Sizzle方法会将匹配到的元素数组返回给ret。此时ret已经有了我们希望操作的元素了,接下来要给实例对象添加数组特性和context, selector属性。jQuery.fn.pushStack方法会新实例化一个jQuery对象,并且合并元素到该新实例化对象中,再给新实例对象添加prevObject(保存着当前实例化对象的引用,非新实例化对象)和context属性

    // 使用传入的元素生成一个新的jQuery元素,(
            // 将元素数组合并到this对象中)
            // 并将这个对象的prevObject设置成当
            // 前这个实例对象(this).最后将这个新生成的jQuery对象返回
            // 把当前的jQuery对象缓存起来,
            // 以便以后使用end方法恢复这个jQuery对象
            pushStack: function (elems) {
                // 新建一个新的jQuery匹配元素集
                // this.constructor === jQuery
                // jQuery()返回的是this
                // 通过将elems数组merge到this中,使this也具有类似数组的特性,
                // 这就是使用选择器匹配到的元素被合并到this中的原因
                var ret = jQuery.merge(this.constructor(), elems);
    
                // 把旧对象保存在prevObject属性上
                ret.prevObject = this;
                ret.context = this.context;
    
                // 返回新的元素集
                return ret;
            }
    

      jQuery.merge:

    /**
             * 合并两个数组(或类数组)
             * 返回合并后的第一个内容
             */
            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;
            }
    

      

    至此我们的$方法已经获取了我们希望匹配的元素了,那么是怎么使用jQuery的其他方法的呢?

    // 延迟实例化
        /*
         这里将init的构造函数原型指向jQuery.fn(即jQuery原型),
         当我们给jQuery.fn扩展方法或属性的时候,实际上就是给init.prototype,
         而jQuery()方法返回的是init构造函数的实例化对象,所以jQuery()就是其实例对象,
         具有了其方法和属性。
         */
        jQuery.fn.init.prototype = jQuery.fn;
    

      此时我们就可以使用jQuery.fn中的方法了,为了简便扩展方法,jQuery用了extend方法:

    /*
         用一个或多个其他对象来扩展一个对象,返回被扩展的对象
         */
        // jQuery.extend(target, [object1], [objectN])
        // jQuery.extend([deep], target, object1, [objectN])
        // jQuery.fn.extend就是jQuery.fn.init.prototype.extend,
        // 所以this就是init的实例化对象,即jQuery(..)
        jQuery.extend = jQuery.fn.extend = function () {
            var src, copyIsArray, copy, name, options, clone,
                target = arguments[0] || {},
                i = 1,
                length = arguments.length,
                deep = false;
    
            // 处理 深拷贝的情况
            if (typeof target === 'boolean') {
                deep = target;
                target = arguments[1] || {};
                // 略过布尔值
                i = 2;
            }
    
            // target非对象或函数则强制转换为空对象
            if (typeof target !== 'object' && !jQuery.isFunction(target)) {
                target = {};
            }
    
            // 当只有一个参数或者深度拷贝的两个参数时说明是扩展jQuery或者jQuery.fn
            if (length === i) {
                target = this;
                --i;
            }
    
            for (; i < length; i++) {
                if ((options = arguments[i]) != null) {
                    for (name in options) {
                        src = target[name];
                        copy = options[name];
    
                        // 避免循环递归, 不把自己的引用作为自己的一个成员
                        if (target === copy) {
                            continue;
                        }
    
                        // 递归深度拷贝的对象或数组
                        if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
                            if (copyIsArray) {
                                copyIsArray = false;
                                clone = src && jQuery.isArray(src) ? src : [];
                            } else {
                                clone = src && jQuery.isPlainObject(src) ? src : {};
                            }
    
                            // 递归调用
                            target[name] = jQuery.extend(deep, clone, copy);
                        } else if (copy !== undefined) {
                            target[name] = copy;
                        }
                    }
                }
            }
    
            // 返回被修改的对象
            return target;
        }
    

      $.extend与$.fn.extend用法

    1. $.extend({
            min: function(a, b) { return a < b ? a : b; },
            max: function(a, b) { return a > b ? a : b; }
        });
    $.min(2,3); // => 2 
    $.max(4,5); // => 5
    
    2. $.fn.extend({
            check: function() {
                return this.each(function() { this.checked = true; });
            },
            uncheck: function() {
                return this.each(function() { this.checked = false; });
            }
        });
    $("input[type=checkbox]").check(); 
    $("input[type=radio]").uncheck();
    

      终于到结尾了,如果有什么讲错的地方,希望大家提出来。

  • 相关阅读:
    JAVA中堆和栈的区别
    怎么回答面试官:你对Spring的理解?
    如何设计一个高可用、高并发秒杀系统
    这应该是把Java内存区域讲的最清楚的一篇文章
    Spring Cloud底层原理解析
    Spring事务管理详解
    选择合适Redis数据结构,减少80%的内存占用
    最强Java并发编程详解:知识点梳理,BAT面试题等
    深入理解HashMap
    Springboot 优雅停止服务的几种方法
  • 原文地址:https://www.cnblogs.com/webFrontDev/p/3183066.html
Copyright © 2020-2023  润新知