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>
   </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-03 01:21:15

jQuery.extend()方法和jQuery.fn.extend()方法的相关文章

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

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

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=''/>

jquery循环方法和attr,prop方法

$()——>$()[0] jquery对象转化为dom对象 $.each(arr,function(x,y){ }) $("p').each(function(x,y)){ }) this表示调用方法的元素 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <

Servlet的Service方法和doget 和 dopost方法的区别,常见的错误解析

package com.sxt.in; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Service方法和doget 和 dopost方

$.extend()方法和(function($){...})(jQuery)详解

1.    JS中substring与substr的区别 之前在项目中用到substring方法,因为C#中也有字符串的截取方法Substring方法,当时也没有多想就误以为这两种方法的使用时一样的.这样就直接按照在C#中使用Substring的方式,直接在js中用了substring,在实际的项目中,有时候能得到正确的结果,但是有时候得到的结果却让人匪夷所思,后来仔细的调试跟踪之后才发现,原来是Js中substring的方法跟C#中的Substring方法不一样.关于C# 中的字符串截取方法已

每日学习心得:$.extend()方法和(function($){...})(jQuery)详解

1.    JS中substring与substr的区别 之前在项目中用到substring方法,因为C#中也有字符串的截取方法Substring方法,当时也没有多想就误以为这两种方法的使用时一样的.这样就直接按照在C#中使用Substring的方式,直接在js中用了substring,在实际的项目中,有时候能得到正确的结果,但是有时候得到的结果却让人匪夷所思,后来仔细的调试跟踪之后才发现,原来是Js中substring的方法跟C#中的Substring方法不一样.关于C# 中的字符串截取方法已

$.extend()方法和(function($){...})(jQuery)详解 关于js 的 深入用法。“use strict”

这一次release的内容终于做完了,可以研究一下用到的东西. 以前写js 都是基本的写法, 写个function, 这次看到同事用jquery 加js 写的插件 用了自己不知道的新知识,觉得得有必要学习一下了. 1. "use  strict";   可以在写js 的文件中把这句话放上,不知道是什么意思 ,查了一下,应该是让js写法更严谨,当出现不符合要求的js,系统会报错 看解释给的一个例子  foo = ’aa': 当加了这句话就会报错.  大约就是语法更严谨了. 可以写到fun

jquery attr方法和prop方法获取input的checked属性问题

问题:经常使用jQuery插件的attr方法获取checked属性值,获取的值的大小为未定义,此时可以用prop方法获取其真实值,下面介绍这两种方法的区别: 1.通过prop方法获取checked属性,获取的checked返回值为boolean,选中为true,否则为flase <input type="checkbox" id="selectAll" onclick="checkAll()">全选 function checkAll

jquery call方法和apply方法接触

call方法: 语法:call([thisObj[,arg1[, arg2[,   [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象. 说明: call 方法可以用来代替另一个对象调用一个方法.call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象. 如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj. apply方法: 语法:apply([thisObj[,argArray]]) 定义:应用某