jQuery中extend()和fn.extend()方法详解


Posted in Javascript onJune 03, 2015

这两个方法用的是相同的代码,一个用于给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

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

Javascript 相关文章推荐
javascript 面向对象思想 附源码
Jul 07 Javascript
js生成随机数之random函数随机示例
Dec 20 Javascript
如何利用Promises编写更优雅的JavaScript代码
May 17 Javascript
微信公众号支付H5调用支付解析
Nov 04 Javascript
从零开始做一个pagination分页组件
Mar 15 Javascript
vue.js 上传图片实例代码
Jun 22 Javascript
BootStrap入门学习第一篇
Aug 28 Javascript
探索webpack模块及webpack3新特性
Sep 18 Javascript
vue实现通讯录功能
Jul 14 Javascript
JavaScript 点击触发复制功能实例详解
Nov 02 Javascript
JS前端面试必备——基本排序算法原理与实现方法详解【插入/选择/归并/冒泡/快速排序】
Feb 24 Javascript
JavaScript 中的执行上下文和执行栈实例讲解
Feb 25 Javascript
JavaScript中switch语句的用法详解
Jun 03 #Javascript
jquery中添加属性和删除属性
Jun 03 #Javascript
JavaScript中的条件判断语句使用详解
Jun 03 #Javascript
简单介绍JavaScript的变量和数据类型
Jun 03 #Javascript
在HTML中插入JavaScript代码的示例
Jun 03 #Javascript
jQuery事件绑定on()、bind()与delegate() 方法详解
Jun 03 #Javascript
在浏览器中打开或关闭JavaScript的方法
Jun 03 #Javascript
You might like
mysql建立外键
2006/11/25 PHP
php初学者写及时补给skype用户充话费的小程序
2008/11/02 PHP
javascript之函数直接量(function(){})()
2007/06/29 Javascript
Jquery中对数组的操作代码
2011/08/12 Javascript
jQuery实现友好的轮播图片特效
2015/01/12 Javascript
Augularjs-起步详解
2016/07/08 Javascript
js 实现数值的千分位及保存小数方法(推荐)
2016/08/01 Javascript
利用Node.js+Koa框架实现前后端交互的方法
2017/02/27 Javascript
Angular实现一个简单的多选复选框的弹出框指令实例
2017/04/25 Javascript
微信小程序访问node.js接口服务器搭建教程
2017/04/25 Javascript
详解angularjs 关于ui-router分层使用
2017/06/12 Javascript
深入理解vue Render函数
2017/07/19 Javascript
vue2.0开发入门笔记之.vue文件的生成和使用
2017/09/19 Javascript
angular.js和vue.js中实现函数去抖示例(debounce)
2018/01/18 Javascript
layui switch 开关监听 弹出确定状态转换的例子
2019/09/21 Javascript
webpack的pitching loader详解
2019/09/23 Javascript
基于aotu.js实现微信自动添加通讯录中的联系人功能
2020/05/28 Javascript
详细分析Node.js 模块系统
2020/06/28 Javascript
python解析发往本机的数据包示例 (解析数据包)
2014/01/16 Python
python读取html中指定元素生成excle文件示例
2014/04/03 Python
Python实现栈的方法
2015/05/26 Python
Python中的zipfile模块使用详解
2015/06/25 Python
python数据处理实战(必看篇)
2017/06/11 Python
python实现守护进程、守护线程、守护非守护并行
2018/05/05 Python
Django模板语言 Tags使用详解
2019/09/09 Python
python getpass模块用法及实例详解
2019/10/07 Python
JD Sports瑞典:英国领先的运动时尚商店
2018/01/28 全球购物
后勤园长自我鉴定
2013/10/17 职场文书
音乐专业应届生教师求职信
2013/11/04 职场文书
总经理司机岗位职责
2014/02/06 职场文书
给老师的检讨书
2014/02/11 职场文书
学雷锋活动总结范文
2014/04/25 职场文书
2015年幼儿园个人工作总结
2015/04/25 职场文书
2016教师校本培训心得体会
2016/01/08 职场文书
Django实现聊天机器人
2021/05/31 Python
通过shell脚本对mysql的增删改查及my.cnf的配置
2021/07/07 MySQL