一个后端眼中的jQuery的extend方法

概述

我看的是3.1.0版本的,先上一段代码吧,不要版本都不同那就尴尬了,这段代码看着没有多少,但我相信这基本上是这个世界上执行的最多的代码了,再不济也是一个之一。json

直接看代码有一点绕,因此咱们经过先黑盒的方式,先看实现的功能,在来分析功能实现的代码是怎么实现的。后端

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[ 0 ] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    // 若是第一个参数类型是boolean类型,处理深拷贝
    if ( typeof target === "boolean" ) {
        deep = target;

        // 跳过第一个Boolean参数
        target = arguments[ i ] || {};
        i++;
    }

    // 若是target类型不是Object或者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;
                }

                // 递归拷贝 plain objects({},new Object()) or arrays(数组)
                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 );

                // 不拷贝undefined值
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }
    return target;
};

添加jQuery静态属性和实例属性

这个标题可能有一些迷惑,那咱们可使用另外一个说法,为jQuery添加属性和为jQuery的原型prototype添加属性(这个属性多是一个function类型的方法) 。数组

假设咱们都知道jQuery是一个函数对象,因此它是有prototype属性的,也知道它们有什么区别,若是不知道能够先看一下一个后端眼中的js对象和原型链这篇文章。框架

js不讲道理的地方就在于它太灵活了,以致于不少库你不看源码根本不知作了什么处理,好比jQuery.extend 和 jQuery.fn.extend这个方法,参数个数,参数类型不同处理都不同,而后对于我这样的后端来讲,每一次用就看一下,结果每一次可能都不同,结果接混乱了。函数

这里顺便提一下为何是这个不是这2个,是由于它们方法体是同一个,js就是这么神奇。优化

这里就总结一下:ui

  1. 若是jQuery.extend只有一个参数就是给jQuery添加静态属性(通常这个属性是一个或者一下函数),就是给jQuery函数对象添加属性。
  2. 若是jQuery.fn.extend只有一个参数就是给jQuery添加实例属性(通常这个属性是一个或者一下函数),就是给jQuery函数对象的prototype属性添加属性。

下面咱们就来从源码的角度来分析一下是怎样实现的: 关键就在于下面的代码:this

if ( i === length ) {
    target = this;
    i--;
}

根据上下文很容易判读要知足i===length 那么i===length === 1,接下来就好理解了,只有一个参数的时候target就是this。jQuery.extend调用的时候this是jQuery函数对象,jQuery.fn.extend调用的时候this是jQuery.prototype由于jQuery.fn == jQuery.prototype.net

因此很容易就能得出上面的结论了。prototype

深拷贝与浅拷贝

deep = false;
if ( typeof target === "boolean" ) {
    deep = target;
    target = arguments[ i ] || {};
    i++;
}

从上下文能够看出默认的是浅拷贝方式,若是第一个参数是一个boolean值,那么就是指示的是深拷贝或者浅拷贝,第二个参数就变为了目标对象target。

咱们先来看浅拷贝,拷贝操做的核心代码就是for循环了,咱们先来看一下浅拷贝,若是只是浅拷贝的话,咱们能够把代码简化为下面的样子:

for ( ; i < length; i++ ) {

        // 只处理非空值
        if ( ( options = arguments[ i ] ) != null ) {

            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                // 避免无限循环
                if ( target === copy ) {
                    continue;
                }if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }

这个是否是好理解多了,就是把参数对象中的全部属性设置到target中,target已经有的就执行的是覆盖操做。

再来看一下深拷贝的核心代码:

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 );
}

这个其实也还好理解,就是对于被拷贝对象copy若是是数组和PlainObject(就是{}和new Object方式生成的对象)就再对这个个copy对象执行一下依次拷贝(递归)。

举个简单的例子:若是有2个对象:

{ name: "tim", location:{city: "Boston",county:"USA"} }
{ name: "tom",age:20, location: {state: "MA",county:"China"}}
jQuery.extend({ name: "tim", location: { city: "Boston", county: "USA" } }, { name: "tom", age: 20, location: { state: "MA", county: "China" } });

的结果是:

{"name":"tom","location":{"state":"MA","county":"China"},"age":20}
jQuery.extend(true,{ name: "tim", location: { city: "Boston", county: "USA" } }, { name: "tom", age: 20, location: { state: "MA", county: "China" } });

的结果是:

{"name":"tom","location":{"city":"Boston","county":"China","state":"MA"},"age":20}

因此能够总结一下:浅拷贝只是简单的执行的是添加或者覆盖。而深拷贝对于PlainObject或者是数组对象就再递归的执行添加拷贝操做,而不是简单的覆盖。

jQuery的extend方法的应用

说了这么多,主要仍是看应用了,我遇到最多的就是插件中了。咱们知道jQuery插件的开发通常是下面这个套路:

;(function ($, window) {
  $.fn.myPlugin = function() {
    //实现代码
  };
}(jQuery, window));

;分号是避免多个文件压缩前一个文件中的代码最后一个语句没有添加;引发的错误。最外层的()小括号是匿名函数自执行。传入jQuery和window是避免每一次在函数内部使用jQuery和window的时候都要依次查找到最顶层,优化速度。

至于:

$.fn.myPlugin = function() {
    //实现代码
  };

这个套路,你若是仔细读了一个后端眼中的js对象和原型链这篇文章,应该就能理解,其实本质上就是给jQuery的函数对象的prototype属性添加方法。

既然说道extend那么固然除了上面的套路还有extend的方式了:

(function($){
    $.fn.extend({
        f1: function(op){},
        f2:function(op){}
        })
})(jQuery);

国产的ui框架jui就是使用的这个套路。其实本质到同样,就是给jQuery的函数对象的prototype属性添加方法。

除了$.fn.extned还有$.extend方法了,好比要扩展String类型的方法就能够:

$.extend(String.prototype, {
    isPositiveInteger:function(){
        return (new RegExp(/^[1-9]\d*$/).test(this));
    },
    isInteger:function(){
        return (new RegExp(/^\d+$/).test(this));
    }})

其实本质都是同样的,最重要的是弄清楚js的函数对象,prototype,原型链和对象属性。其余的基本万变不离其宗。

相关文章
相关标签/搜索