jQuery源码:核心函数extend

307 阅读3分钟

一、jQuery无new化构建实例

1、$就是jQuery的别称

$就是jQuery的别称,它们指向同一个地址:

console.log($ === jQuery); // true

$jQuery都是挂载在window上的,以便实现全局引用:

window.$ = window.jQuery = jQuery;

2、$()就是在创建jQuery的实例

(function(window){
    var jQuery = function(){
        // return 一个实例对象
    };
    window.$ = window.jQuery = jQuery;
})(window);

二、共享原型设计

jQuery的共享原型设计真的是一个非常棒的设计。

1、错误示范

按照平常的思维,一开始我们可能会想直接retur一个new jQuery():

var jQuery = function(){
    return new jQuery();
};

但是这样函数不断地调用本身创建一个新实例,会造成死循环的问题,这样是不行的。

2、jQuery的共享原型设计

jQuery的共享原型是怎么设计的呢?先来看一张图:

我们在调用$时,会去找到jQuery原型上的init方法,把init当做一个构造函数,然后返回init的实例对象。但我们实际是要创建jQuery对象,并访问到jQuery原型上扩展的属性和方法。所以jQuery是这样做的:将jQuery原型上的init构造函数和jQuery本身共享一个原型。

具体实现如下:

(function (window) {
    var jQuery = function () {
        // return new jQuery(); // 不合理的实例对象创建,会造成死循环
        return new jQuery.prototype.init();
    };
    jQuery.prototype = {
        init: function () {},
        // 可以扩展其他方法
        css: function () {},
    };
    jQuery.prototype.init.prototype = jQuery.prototype; // 共享原型对象
    window.$ = window.jQuery = jQuery;
})(window);

三、jQuery的核心函数extend()

1、extend的功能

extend可以做任意对象的扩展,jQuery内部是使用extend方法来进行自身属性和方法扩展,我们也可以利用extend方法来自定义一些属性或者方法。

// 1、对对象进行扩展
$.extend({}, { name: 'vincent'});
// 2、对jQuery对象本身扩展属性或者方法
$.extend({
    work: function () {
        console.log('work');
    },
});
$.work(); // work
// 3、给jQuery的实例对象扩展属性或者方法
$.fn.extend({
    sex: '男'
});
console.log($().sex); // 男

2、jQuery的实例对象fn

jQuery扩展一个实例对象fn,使它等于jQuery的原型:

jQuery.fn = jQuery.prototype = {
    init: function () {},
};

共享原型也可以写成以下格式:

jQuery.fn.init.prototype = jQuery.fn;

3、extend的实现

$$.fn都可以调用到extend这个方法,那么这个extend是同一个方法吗?是的,确实是的。在jQuery里面是这样实现的:

jQuery.fn.extend = jQuery.extend = function() {};

我们来分析一下,extend可以对一个对象进行扩展;也可以对jQuery本身或者jQuery的实例对象fn进行扩展。既然这样我们就可以通过extend传入的第一个参数进行处理了:

1、如果有多个对象,第一个参数是一个对象,那么我们可以从第二个参数开始遍历,把后面的对象扩展到第一个对象里面。

2、如果参数只有一个,并且是对象,那么就是给jQuery本身或者jQuery的实例对象fn进行扩展了。

代码实现:

jQuery.fn.extend = jQuery.extend = function () {
    // console.log(arguments);
    var target = arguments[0] || {}; // target是需要扩展的对象
    var length = arguments.length;
    var i = 1;
    var option, name;
    if (typeof target !== 'object') {
        target = {};
    }
    // 如果参数的个数等于1时 要么给jQuery本身扩展方法,要么给jQuery的实例对象扩展方法
    if (length === i) {
        target = this; // this: Query本身 or jQuery的实例对象fn
        i--;
    }
   for(; i < length; i++) {
        if ((option = arguments[i]) !== null) {
            for (name in option) {
                target[name] = option[name];
            }
        }
    }
    return target; // 返回扩展的对象
};

以上代码简单地实现了extend的功能,但是这个只是浅拷贝,仅仅是浅拷贝是不够的,那么应该如何实现深拷贝呢?

我们调用extend时可以传入一个参数作为判断是否需要做深拷贝的操作:

var ret = { name: 'vincent', age: 18, list: { status: 'good'} };
var res = { list: { sex: '男' }};
// 第一个参数为true即要做深拷贝的操作
var obj = $.extend(true, {}, ret, res);
console.log(obj)

那么extend方法也要做相应的处理

var deep = false; // 定义一个deep来判断是深拷贝 or 浅拷贝
// 传入第一个参数如果为boolean,则是判断是否需要做深拷贝的操作
if (typeof target === 'boolean') {
    deep = target; // 第一个参数为target
    target = arguments[1]; // 第二个参数才是需要扩展的对象
    i = 2; // i要加1
}

深拷贝的操作是只有对象或者数组才需要做的,所以我们可以给jQuery扩展判断类型的方法:

jQuery.extend({
    // 类型检测
    isObject: function(obj){
        return toString.call(obj) === '[object Object]';
    },
    isArray: function(obj){
        return toString.call(obj) === '[object Array]';
    },
});

extend方法的完整实现如下:

jQuery.fn.extend = jQuery.extend = function () {
    // console.log(arguments);
    var target = arguments[0] || {}; // target是需要扩展的对象
    var length = arguments.length;
    var i = 1;
    var deep = false; // 定义一个deep来判断是深拷贝 or 浅拷贝
    var option, name, copy, src, copyIsArray, clone;
    // 传入第一个参数如果为boolean,则是判断是否需要做深拷贝的操作
    if (typeof target === 'boolean') {
        deep = target; // 第一个参数为target
        target = arguments[1]; // 第二个参数才是需要扩展的对象
        i = 2; // i要加1
    }
    if (typeof target !== 'object') {
        target = {};
    }
    // 如果参数的个数等于1时 要么给jQuery本身扩展方法,要么给jQuery的实例对象扩展方法
    if (length === i) {
        target = this; // this: Query本身 or jQuery的实例对象fn
        i--;
    }

    // 浅拷贝  深拷贝
    for (; i < length; i++) {
        if ((option =arguments[i]) != null) { // i=1;对第一个对象扩展 第一个对象不要动;
            for (name in option){
                // target[name] = option[name];
                // console.log(name);
                copy = option[name]; // option 当前遍历对象的值
                src = target[name]; // 第一个为 {} src为undefined
                if (deep && ($.isObject(copy) || (copyIsArray = $.isArray(copy)))) { 
                    // 深拷贝  copy需要是Object或者Array
                    if (copyIsArray){
                        copyIsArray = false;
                        clone = src && $.isArray(src) ? src : [];
                    } else {
                        clone = src && $.isObject(src) ? src : {};
                    }
                    // console.log(clone, copy);
                    target[name] = $.extend(deep, clone, copy); // 核心代码
                } else if (copy != undefined) { // 浅拷贝
                    target[name] = copy;
                }
            }
        }
    }
    return target; // 返回扩展的对象
};

核心代码是target[name] = $.extend(deep, clone, copy);这一句,jQuery的核心功能函数extend就实现啦。

四、完整代码

最后贴上这个实现的完整代码:

(function (window) {
    var jQuery = function () {
        // return new jQuery(); // 不合理的实例对象创建,会造成死循环
        return new jQuery.prototype.init();
    };
    jQuery.fn = jQuery.prototype = {
        init: function () {

        },
        // 可以扩展其他方法
        css: function () {

        }
    };
    // jQuery核心功能函数:extend,可以在外部或内部使用
    // 细节:需要扩展的对象必须是Object
    jQuery.fn.extend = jQuery.extend = function () {
        // console.log(arguments);
        var target = arguments[0] || {}; // target是需要扩展的对象
        var length = arguments.length;
        var i = 1;
        var deep = false; // 定义一个deep来判断是深拷贝 or 浅拷贝
        var option, name, copy, src, copyIsArray, clone;
        // 传入第一个参数如果为boolean,则是判断是否需要做深拷贝的操作
        if (typeof target === 'boolean') {
            deep = target; // 第一个参数为target
            target = arguments[1]; // 第二个参数才是需要扩展的对象
            i = 2; // i要加1
        }
        if (typeof target !== 'object') {
            target = {};
        }
        // 如果参数的个数等于1时 要么给jQuery本身扩展方法,要么给jQuery的实例对象扩展方法
        if (length === i) {
            target = this; // this: Query本身 or jQuery的实例对象fn
            i--;
        }

        // 浅拷贝  深拷贝
        for (; i < length; i++) {
            if ((option =arguments[i]) != null) { // i=1;对第一个对象扩展 第一个对象不要动;
                for (name in option){
                    // target[name] = option[name];
                    // console.log(name);
                    copy = option[name]; // option 当前遍历对象的值
                    src = target[name]; // 第一个为 {} src为undefined
                    if (deep && ($.isObject(copy) || (copyIsArray = $.isArray(copy)))) { 
                        // 深拷贝  copy需要是Object或者Array
                        if (copyIsArray){
                            copyIsArray = false;
                            clone = src && $.isArray(src) ? src : [];
                        } else {
                            clone = src && $.isObject(src) ? src : {};
                        }
                        // console.log(clone, copy);
                        target[name] = $.extend(deep, clone, copy); // 核心代码
                    } else if (copy != undefined) { // 浅拷贝
                        target[name] = copy;
                    }
                }
            }
        }
        return target; // 返回扩展的对象
    };

    // 共享原型设计
    // 调用$时,会去找到jQuery原型上的init方法,把init当做一个构造函数,然后返回init的实例对象
    // jQuery原型上的init的构造函数跟jQuery本身共享一个原型
    // jQuery.prototype.init.prototype = jQuery.prototype; // 共享原型对象
    jQuery.fn.init.prototype = jQuery.fn;

    // 给jQuery扩展
    jQuery.extend({
        // 类型检测
        isObject: function(obj){
            return toString.call(obj) === '[object Object]';
        },
        isArray: function(obj){
            return toString.call(obj) === '[object Array]';
        },
    });
    window.$ = window.jQuery = jQuery; // 要创建一个$实例
})(window);