jQuery.extend()方法和jQuery.fn.extend()方法源码分析

这两个方法用的是相同的代码,一个用于给jQuery对象或者普通对象合并属性和方法一个是针对jQuery对象的实例,对于基本用法举几个例子:

html代码如下:

<!doctype html>
<html>
   <head>
      <title></title>
        <script src=‘jquery-1.7.1.js‘></script>
   </head>
   <body>

    <img src=‘‘/>

   </body>
</html>

下面写js里面的用法:

合并两个普通对象

      //给两个普通对象合并属性
      var obj1={name:‘Tom‘,age:22};
      var obj2={name:‘Jack‘,height:180};
      console.log($.extend(obj1,obj2));  //Object {name: "Jack", age: 22, height: 180}

给jQuery对象添加属性或者方法

$.extend({hehe:function(){alert(‘hehe‘);}});

 $.hehe();  //alert(‘hehe‘)

这个用法很重要,是jQuery内部添加实例属性和方法以及原型属性和方法的实现方法也是编写jQuery插件的方法,下面是jQuery1.7.1中使用extend方法扩展自己的方法和属性

jQuery.extend({
    noConflict: function( deep ) {
        if ( window.$ === jQuery ) {
            window.$ = _$;
        }

        if ( deep && window.jQuery === jQuery ) {
            window.jQuery = _jQuery;
        }

        return jQuery;
    },

    // Is the DOM ready to be used? Set to true once it occurs.
    isReady: false,

    // A counter to track how many items to wait for before
    // the ready event fires. See #6781
    readyWait: 1,    .....

在这个例子中只传入了一个对象参数,那么默认就把this当做待合并修改的对象

给jQuery对象实例添加属性或者方法

 //针对jQuery实例扩展合并
      console.log($(‘img‘).extend({‘title‘:‘img‘}));//[img, img#img.img, prevObject: jQuery.fn.jQuery.init[1], context: document, selector: "img", title: "img", constructor: function…]

只合并不修改待合并对象

      var obj1={name:‘Tom‘,age:22};
      var obj2={name:‘Jack‘,height:180};
      console.log($.extend(obj1,obj2));   //Object {name: "Jack", age: 22, height: 180}
      console.log(obj1);                  //Object {name: "Jack", age: 22, height: 180}

默认情况下,待合并对象跟返回结果一样是被修改了的,如果仅仅想得到一个合并后的对象又不想破坏任何一个原来的对象可以使用此方法

  var obj1={name:‘Tom‘,age:22};
  var obj2={name:‘Jack‘,height:180};
  var empty={};
  console.log($.extend(empty,obj1,obj2));   //Object {name: "Jack", age: 22, height: 180}
  console.log(obj1);                  //Object {name: "Tom", age: 22}

使用则递归合并或者叫深度拷贝

 var obj1={name:‘Tom‘,love:{drink:‘milk‘,eat:‘bread‘}};
 var obj2={name:‘Jack‘,love:{drink:‘water‘,sport:‘football‘}};

 console.log(($.extend(false,obj1,obj2)).love);   //Object {drink: "water", sport: "football"}
 console.log(($.extend(true,obj1,obj2)).love);    //Object {drink: "water", eat: "bread", sport: "football"}

详细的使用方法可以看参考手册http://www.w3cschool.cc/manual/jquery/

下面来分析下1.7.1源码中是怎么实现的:

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

首先是定义了一组变量,因为参数个数不确定所以就直接调用arguments对象访问传递的参数

变量 options:指向某个源对象。
? ? 变量 name:表示某个源对象的某个属性名。
? ? 变量 src:表示目标对象的某个属性的原始值。
? ? 变量 copy:表示某个源对象的某个属性的值。
? ? 变量 copyIsArray:指示变量 copy 是否是数组。
? ? 变量 clone:表示深度复制时原始值的修正值。
? ? 变量 target:指向目标对象。
? ? 变量 i:表示源对象的起始下标。
? ? 变量 length:表示参数的个数,用于修正变量 target。
? ? 变量 deep:指示是否执行深度复制,默认为 false。

为了更好地了解代码实现这里以上面举的一个例子作为演示观察源代码执行情况

      var obj1={name:‘Tom‘,love:{drink:‘milk‘,eat:‘bread‘}};
      var obj2={name:‘Jack‘,love:{drink:‘water‘,sport:‘football‘}};
      $.extend(true,obj1,obj2)

源码分析

// Handle a deep copy situation
    if ( typeof target === "boolean" ) {
        deep = target;
        target = arguments[1] || {};
        // skip the boolean and the target
        i = 2;
    }

判断是不是深度复制,如果第一个参数是布尔值那么就把第一个参数的值给deep,然后把第二个参数作为目标对象,如果第二个参数不存在就赋值为一个空对象,把源对象的下标改为2,在这个例子里面  是走这里的因为第一个参数是ture然后把deep变成了true ,target被修正成了第二个参数也即是obj1,源对象的起始下标为2就是从第三个开始作为源对象也就是本例中的obj2

// Handle case when target is a string or something (possible in deep copy)
    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
        target = {};
    }

这里对target又进一步进行了处理对于非对象和函数的数据类型而言增加自定义属性是无效的比如字符串自能调用自带的方法和属性

// extend jQuery itself if only one argument is passed
    if ( length === i ) {
        target = this;
        --i;
    }

如果length属性等于i的值那就表示没有目标对象存在,正常情况下length应该是大于i的值的 ,那么这个时候就把this作为目标对象把i值减一实现length值大于i值(比i大1)

这个就是jQuery给自己扩展属性的方法的实现原理,只要不传入目标对象就可以啦

两种可能的情况:$.extend(obj)    或者  $.extend(false/true,obj);

    for ( ; i < length; i++ ) {
        // Only deal with non-null/undefined values
        if ( (options = arguments[ i ]) != null ) {
            // Extend the base object
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                // Prevent never-ending loop
                if ( target === copy ) {
                    continue;
                }

                // Recurse if we‘re merging plain objects 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 : {};
                    }

                    // Never move original objects, clone them
                    target[ name ] = jQuery.extend( deep, clone, copy );

                // Don‘t bring in undefined values
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }

这个部分就是此方法的核心了,从arguements对象的第i个下标值开始循环操作首先过滤掉源对象是null或者是undefined的情况可以看到其实

源对象不一定真的就是对像,也可以是其他类型的值比如字符串比如这样写:

console.log($.extend({‘name‘:‘tom‘},‘aa‘));   //Object {0: "a", 1: "a", name: "tom"}

是不是感觉很奇怪啊?究竟是怎么实现的呢?下面接着看

过滤完之后开始进行for循环 src保存的是目标对象的某个键的值,copy属性保存的源对象的某个键的值,这两个键都是一样的

// Prevent never-ending loop
                if ( target === copy ) {
                    continue;
                }

如果源对象的某个属性值就是目标对象可能会造成死循环导致程序崩溃所以这里做了一个限制让其跳过此次循环例如:

var o = {};
o.n1 = o;
$.extend( true, o, { n2: o } );
// 抛出异常:
// Uncaught RangeError: Maximum call stack size exceeded

但是这样做也会冤枉一些正常的情况比如:

 var obj1={a:‘a‘}
 var obj2={a:obj1};
 console.log($.extend(obj1,obj2)); //Object {a: "a"}

这种情况也是满足源对象值等于目标对象的但是结果发现obj1的a的属性值并没有被修改,就是因为执行了continue,下面把源码的这段话注释掉在执行

Object {a: Object}

这个时候就是正常被修改了个人感觉这个地方需要改进;

接着就是一个if判断就是区分是不是进行深度复制的先不看深度复制的先看一般的

target[ name ] = copy;

很简单就是只要copy有值就直接复制给目标对象,目标对象有的就修改没有就增加,这样就实现了合并啦。

for循环之后在把新的目标对象返回,所以目标对象最后是被修改的,而且结果和返回的结果是一样的。

// Return the modified object
    return target;};

下面再来说说深度复制了怎么去处理

首先保证deep是true,copy有值并且是对象或者数组(如果不是对象和数组深度复制也就无从谈起)然后再分数组和对象来处理,先来看数组的情况:

if ( copyIsArray ) {
         copyIsArray = false;
         clone = src && jQuery.isArray(src) ? src : [];

} else {
        clone = src && jQuery.isPlainObject(src) ? src : {};
}

如果是数组copyIsArray的值为真然后走里面的 把值改成false ,针对当前循环的源对象属性,目标对象可能有也可能没有,有的话判断一下是不是数组是的话就是原来的数组不变不是的话就让它变成一个数组,因为既然源对象的当前属性是数组最后目标元素也必须是数组。不是数组就是对象把目标对象当前属性改成对象。

    // Never move original objects, clone them
     target[ name ] = jQuery.extend( deep, clone, copy );

然后把源对象的当前属性值(是数组或对象)和已经被改造过的目标对象的当前属性进行递归合并最后返回的新的数组或者对象赋值给目标对象,最终实现了深度复制。

但是这里面还有一个比较奇怪的现象,比如这样操作:

      console.log($.extend({a:1},‘aa‘)); //Object {0: "a", 1: "a", a: 1}

原来源对象不一定真的是对象e而且居然可以把字符串拆开跟目标对象合并,原来for...in循环是操作字符串的

      var str=‘aa‘;
      for(var name in str){
         console.log(name);
         console.log(str[name])
      }

这样也是可以的,会把字符串拆开按数字下标读取,但是在源码中

if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) )

是有数组和对象限制的,那么深度复制的时候是不是就没有效果了呢?

经过我测试深度复制也是可以的,因为在源码里面copy的值竟然变成了匿名函数函数

 alert(jQuery.isPlainObject(copy)); //true

至于为什么是函数笔者还没搞清楚留待以后解决吧!

时间: 2024-10-14 05:11:28

jQuery.extend()方法和jQuery.fn.extend()方法源码分析的相关文章

jQuery.extend()方法和jQuery.fn.extend()方法

jQuery.extend()方法和jQuery.fn.extend()方法源码分析 这两个方法用的是相同的代码,一个用于给jQuery对象或者普通对象合并属性和方法一个是针对jQuery对象的实例,对于基本用法举几个例子: html代码如下: <!doctype html> <html> <head> <title></title> <script src='jquery-1.7.1.js'></script> <

jQuery hover()方法和jQuery toggle()方法用法示例

jQuery hover()方法和jQuery toggle()方法是两个合成事件,类似ready()方法,都属于jQuery自定义的方法.下面来讲解这两个方法的属性和如何使用. 一.hover()方法:语法结构为: hover(enter,leave).用于模拟光标悬停事件.当光标移动到目标元素上时,会触发指定第1个函数(enter);当移出这个元素时,会触发第2个函数(leave). 下面是一段示例代码: $(function(){ $("#panel h5.head").hove

Java split方法源码分析

Java split方法源码分析 1 public String[] split(CharSequence input [, int limit]) { 2 int index = 0; // 指针 3 boolean matchLimited = limit > 0; // 是否限制匹配个数 4 ArrayList<String> matchList = new ArrayList<String>(); // 匹配结果队列 5 Matcher m = matcher(inp

invalidate和requestLayout方法源码分析

invalidate方法源码分析 在之前分析View的绘制流程中,最后都有调用一个叫invalidate的方法,这个方法是啥玩意?我们来看一下View类中invalidate系列方法的源码(ViewGroup没有重写这些方法),如下: /**  * Mark the area defined by dirty as needing to be drawn. dirty代表需要重新绘制的脏的区域  * If the view is visible, onDraw(Canvas) will be c

jQuery实现DOM加载方法源码分析

传统的判断dom加载的方法 使用 dom0级 onload事件来进行触发所有浏览器都支持在最初是很流行的写法 我们都熟悉这种写法: window.onload=function(){ ... }  但是onload事件触发过于缓慢,尤其是在存在很多外部图片或者视频文件的时候,为了更好的了解这一点有必要知道一个html文档是如何进行加载的,这里引用一个园友的表述: 1.用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件: 2.浏览器开始载入htm

jQuery.pushStack()原型方法源码分析

这次分析的方法跟前面不同,虽然pushStack也是原型方法之一,但是我们几乎从不用在页面调用,在参考手册里面也没有这个方法的使用说明,但是这个方法还是非常重要的,在使用很多jQuery的其他方式都会隐式的调用此方法: 它为以下方法提供支持:? ? jQuery 对象遍历:.eq()..first()..last()..slice()..map().? ? DOM 查找.过滤:.find()..not()..filter()..closest()..add()..andSelf().? ? DO

JDK中ArrayList、HashMap和HashSet的equals方法源码分析

最近遇到个坑,在分别对ArrayList.HashMap等数据类型进行比较时,发现数据一样,但equals一直返回false.于是乎看了一下ArrayList和HashMap的源码,才恍然大悟.本文的代码摘自JDK 1.7.0. ArrayList的equals方法: public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof List)) return false; ListIterator<

常见方法源码分析:HashMap的put(K k,V v)

version:JDK1.8 一.调用内部的另一个方法: final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) 将结果直接返回 源码: 1 public V put(K key, V value) { 2 return putVal(hash(key), key, value, false, true); 3 } 二.那么进入putVal这个方法看看内部是怎么实现的 注:当前对象就是需要插入的H

String源码分析之Java中的String为什么是不可变的以及replace方法源码分析

什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的.不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变. 区分对象和对象的引用 对于Java初学者, 对于String是不可变对象总是存有疑惑.看下面代码: String s = "ABCabc";