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 相关文章推荐
jQuery选择器的工作原理和优化分析
Jul 25 Javascript
html5的自定义data-*属性和jquery的data()方法的使用示例
Aug 21 Javascript
IE网页js语法错误2行字符1、FF中正常的解决方法
Sep 09 Javascript
基于javascript的COOkie的操作实现只能点一次
Dec 26 Javascript
javascript点击按钮实现隐藏显示切换效果
Feb 03 Javascript
使用基于Node.js的构建工具Grunt来发布ASP.NET MVC项目
Feb 15 Javascript
JS绘制微信小程序画布时钟
Dec 24 Javascript
详解基于Angular4+ server render(服务端渲染)开发教程
Aug 28 Javascript
js动态设置select下拉菜单的默认选中项实例
Aug 21 Javascript
JavaScript生成一个不重复的ID的方法示例
Sep 16 Javascript
基于canvas实现手写签名(vue)
May 21 Javascript
JS call()及apply()方法使用实例汇总
Jul 11 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
在 PHP 中使用随机数的三个步骤
2006/10/09 PHP
杏林同学录(一)
2006/10/09 PHP
Laravel 4 初级教程之Pages、表单验证
2014/10/30 PHP
php+mysql大量用户登录解决方案分析
2014/12/29 PHP
php安装扩展mysqli的实现步骤及报错解决办法
2017/09/23 PHP
Laravel 实现在Blade模版中使用全局变量代替路径的例子
2019/10/22 PHP
javascript 网页跳转的方法
2008/12/24 Javascript
js实现鼠标经过表格行变色的方法
2015/05/12 Javascript
使用JSON作为函数的参数的优缺点
2016/10/27 Javascript
AngularJS使用angular.bootstrap完成模块手动加载的方法分析
2017/01/19 Javascript
Angular实现购物车计算示例代码
2017/02/21 Javascript
jQuery实现遍历复选框的方法示例
2017/03/06 Javascript
@ResponseBody 和 @RequestBody 注解的区别
2017/03/08 Javascript
微信小程序—setTimeOut定时器的问题及解决
2019/07/26 Javascript
Vue数字输入框组件示例代码详解
2020/01/15 Javascript
node.JS的crypto加密模块使用方法详解(MD5,AES,Hmac,Diffie-Hellman加密)
2020/02/06 Javascript
[01:14:05]《加油DOTA》第四期
2014/08/25 DOTA
python网络编程之UDP通信实例(含服务器端、客户端、UDP广播例子)
2014/04/25 Python
Python探索之创建二叉树
2017/10/25 Python
Python无损音乐搜索引擎实现代码
2018/02/02 Python
解决pycharm安装后代码区不能编辑的问题
2018/10/28 Python
基于Python实现迪杰斯特拉和弗洛伊德算法
2020/05/27 Python
用Python实现二叉树、二叉树非递归遍历及绘制的例子
2019/08/09 Python
python 下 CMake 安装配置 OPENCV 4.1.1的方法
2019/09/30 Python
pycharm实现print输出保存到txt文件
2020/06/01 Python
一款纯css3制作的2015年元旦雪人动画特效教程
2014/12/29 HTML / CSS
测量实习生自我鉴定
2013/09/19 职场文书
个人简历中的自我评价怎么写
2014/01/26 职场文书
绿色出行口号
2014/06/18 职场文书
2014年销售工作总结
2014/12/01 职场文书
大学生村官个人总结
2015/02/15 职场文书
2016年大学生党员公开承诺书
2016/03/24 职场文书
java多态注意项小结
2021/10/16 Java/Android
Pygame Draw绘图函数的具体使用
2021/11/17 Python
python获取带有返回值的多线程
2022/05/02 Python
vue el-table实现递归嵌套的示例代码
2022/08/14 Vue.js