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 Event学习补遗 addEventSimple
Feb 11 Javascript
基于Jquery的温度计动画效果
Jun 18 Javascript
jquery 元素相对定位代码
Oct 15 Javascript
基于jQuery的message插件实现右下角弹出消息框
Jan 11 Javascript
js Date概念详细介绍
Nov 22 Javascript
node+express+ejs制作简单页面上手指南
Nov 26 Javascript
javascript 构造函数方式定义对象
Jan 02 Javascript
Vue.js bootstrap前端实现分页和排序
Mar 10 Javascript
WebSocket实现简单客服聊天系统
May 12 Javascript
浅谈Node 异步IO和事件循环
May 05 Javascript
快速解决Vue、element-ui的resetFields()方法重置表单无效的问题
Aug 12 Javascript
React自定义hook的方法
Jun 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
yii实现使用CUploadedFile上传文件的方法
2015/12/28 PHP
javascript 常用关键字列表集合
2007/12/04 Javascript
判断客户端浏览器是否安装了Flash插件的多种方法
2010/08/11 Javascript
JQuery FlexiGrid的asp.net完美解决方案 dotNetFlexGrid-.Net原生的异步表格控件
2010/09/12 Javascript
jquery 学习之二 属性相关
2010/11/23 Javascript
js 判断计算字符串长度/判断空的简单方法
2013/08/05 Javascript
jquery中的$(document).ready()使用小结
2014/02/14 Javascript
javascript制作的简单注册模块表单验证
2015/04/13 Javascript
jQuery实现仿路边灯箱广告图片轮播效果
2015/04/15 Javascript
jquery插件star-rating.js实现星级评分特效
2015/04/15 Javascript
Jquery zTree 树控件异步加载操作
2016/02/25 Javascript
JS中取二维数组中最大值的方法汇总
2016/04/17 Javascript
Bootstrap每天必学之轮播(Carousel)插件
2016/04/25 Javascript
Javascript highcharts 饼图显示数量和百分比实例代码
2016/12/06 Javascript
基于JavaScript实现屏幕滚动效果
2017/01/18 Javascript
详解Angualr 组件间通信
2017/01/21 Javascript
javascript设计模式之模块模式学习笔记
2017/02/15 Javascript
jQuery复合事件结合toggle()方法的用法示例
2017/06/10 jQuery
AngularJS service之select下拉菜单效果
2017/07/28 Javascript
详解vue axios中文文档
2017/09/12 Javascript
mint-ui 时间插件使用及获取选择值的方法
2018/02/09 Javascript
解决Vue+Element ui开发中碰到的IE问题
2018/09/03 Javascript
浅析vue-router中params和query的区别
2019/12/24 Javascript
解决VUE项目localhost端口服务器拒绝连接,只能用127.0.0.1的问题
2020/08/14 Javascript
Python基于OpenCV库Adaboost实现人脸识别功能详解
2018/08/25 Python
详解Python正则表达式re模块
2019/03/19 Python
Python函数式编程实例详解
2020/01/17 Python
django日志默认打印request请求信息的方法示例
2020/05/17 Python
静态变量和实例变量的区别
2015/07/07 面试题
教师求职推荐信范文
2013/11/20 职场文书
房产继承公证书
2014/04/09 职场文书
民警个人对照检查剖析材料
2014/09/17 职场文书
管辖权异议上诉状
2015/05/23 职场文书
Oracle更换为MySQL遇到的问题及解决
2021/05/21 Oracle
python读取mnist数据集方法案例详解
2021/09/04 Python
MySQL的InnoDB存储引擎的数据页结构详解
2022/03/03 MySQL