Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jQuery源码阅读 #23

Open
wuxianqiang opened this issue Jan 5, 2018 · 0 comments
Open

jQuery源码阅读 #23

wuxianqiang opened this issue Jan 5, 2018 · 0 comments
Labels

Comments

@wuxianqiang
Copy link
Owner

wuxianqiang commented Jan 5, 2018

在jQuery中我们通常通过$()传入一个选择器来获取一个元素,然后会返回一个jQuery对象,把匹配的元素放到一个类似数组的集合中,而且执行完了还可以执行下一个函数,jQuery如此强大,我们就来分析一下它背后的原理。

理解jQuery的原型

先看看$()执行都经历了哪些过程,下面是经过删减后的代码

var jQuery = function () {
    return new jQuery.fn.init()
};
jQuery.fn = jQuery.prototype = {
    constructor: jQuery,
    length: 0
}
var init = jQuery.fn.init = function () {
    // 初始化函数
}
init.prototype = jQuery.fn

通过上面的代码我们可以得到下面这样的原型图
图片
我们发现jQuery函数和init函数共享同一个原型,这就是jQuery支持链式写法的原因

提示:每次执行$()都会初始化一个实例,如果你每次获取一个元素都初始化一个实例,那么就会消耗更多的性能,建议用变量保存这个值方便以后使用。

理解扩展函数extend

这是 jQeury 核心中很重要的一个函数 . 通过它我们就可以轻松地在 jQuery 或者 jQuery 原型对象中随意扩展自己想要的方法

jQuery.extend = jQuery.fn.extend = function () {
   var options, name, src, copy, copyIsArray, clone,
       target = arguments[0] || {},
       i = 1,
       length = arguments.length,
       deep = false;
   // 如果传进来的首个参数是一个 boolean 类型的变量,那就证明要进行深度拷贝。而这时传进来的 argumens[1] 就是要拷贝的对象
   if (typeof target === "boolean") {
       deep = target;
       target = arguments[i] || {};
       i++;
   }
   //  如果 target 不是 objet  并且也不是 function  就默认设置它为 {}
   if (typeof target !== "object" && !jQuery.isFunction(target)) {
       target = {};
   }
   //如果只传入了一个参数 ,  那么扩展的就是 jQuery 自身 
   if (i === length) {
       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;
               }
               // options 是扩展对象 ,  它的方法或属性将会被扩展到 target 上
               if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
                   if (copyIsArray) {
                       copyIsArray = false;
                       clone = src && Array.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源码我们发现,不仅可以通过$.extend()在jQuery中添加静态方法和通过$.fn.extend()在jQuery原型中添加公共方法之外,而且还实现了像ES6中Object.assign()方法的对象合并功能,而且jQuery更强大,支持深浅拷贝,只需在第一个参数中传入true/false,ES6的那个方法是不支持深拷贝的

$.extend({},{name: "张三", age: 18})
Object.assign({},{name: "张三", age: 18})

理解初始化函数init

  init = jQuery.fn.init = function (selector, context, root) {
    var match, elem;
    // 没有传入参数会返回一个空对象,如:$()
    if (!selector) {
        return this;
    }
    root = root || rootjQuery;
    // 传入字符串的时候分别对有上下文和没有上下文做了处理
    if (typeof selector === "string") {
        if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
            match = [null, selector, null];
        } else {
            match = rquickExpr.exec(selector);
        }
        if (match && (match[1] || !context)) {
            if (match[1]) {
                context = context instanceof jQuery ? context[0] : context;
                // 调用 jQuery.merge 把两个数组拼接起来 ( 将第二个数组接到第一个数组的尾巴上 )
                // 调用 jQuery.parseHTML 将字符串转化成真正的 DOM 元素然后放在一个数组里面 
                jQuery.merge(this, jQuery.parseHTML(
                    match[1],
                    context && context.nodeType ? context.ownerDocument || context : document,
                    true
                ));
                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;
            } else {
                // 推荐使用ID选择器来匹配元素从而提高性能!
                elem = document.getElementById(match[2]);
                if (elem) {
                    this[0] = elem;
                    this.length = 1;
                }
                return this;
            }
            // 参数还可以传入一个表达式哦!如:$("#"+"box") 相当于 $("#box")
        } else if (!context || context.jquery) {
            return (context || root).find(selector);
        } else {
            return this.constructor(context).find(selector);
        }
    } else if (selector.nodeType) {
        // 参数可以传入一个DOM元素,如:$(document.getElementById('box')) 相当于 $("#box")
        this[0] = selector;
        this.length = 1;
        return this;
    // 参数可以传入一个函数,如:$(function (){}) 相当于 window.onload = function() {}
    } else if (jQuery.isFunction(selector)) {
        return root.ready !== undefined ?
            root.ready(selector) :
            selector(jQuery);
    }
    // 最后这条语句是将传入的一个参数转换为jQuery对象返回,如:$(this)
    return jQuery.makeArray(selector, this);
};
  • 直接的一个 Dom 元素类型
  • 数组类型
  • 函数类型
  • jQuery 或者其他的类数组对象类型
  • string 类型
    • 没有 context 的情况
    • 有 context 的情况

链式写法的原因

支持链式写法和下面连个函数有关,我们来看看

jQuery.fn = jQuery.prototype = {
    pushStack: function (elems) {
        var ret = jQuery.merge(this.constructor(), elems);
        ret.prevObject = this;
        return ret;
    },
    end: function () {
        return this.prevObject || this.constructor();
    }
}

pushStack使用传入的元素生成一个新的 jQuery 元素 , 并将这个对象的 prevObject 设置成当前这个对象 (this). 最后将这个新生成的 jQuery 对象返回,end方法是我返回之前设置的对象prevObject

有很多的函数 return this,这就是为什么支持链式写法的原因,但是每次都链式调用的同时都不能修改我之前匹配到的那个元素集合,如果某一个方法真的要修改匹配元素集合 , 那么它就会调用 pushStack 方法 , 把当前的 jQuery 对象保存起来 , 以便以后使用 end方法恢复这个 jQuery 对象 。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant