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 相关文章推荐
use jscript Create a SQL Server database
Jun 16 Javascript
JS 事件绑定函数代码
Apr 28 Javascript
基于jquery的cookie的用法
Jan 10 Javascript
Javascript模块化编程(三)require.js的用法及功能介绍
Jan 17 Javascript
对frameset、frame、iframe的js操作示例代码
Aug 16 Javascript
基于jQuery仿淘宝产品图片放大镜代码分享
Jun 23 Javascript
轻松掌握JavaScript策略模式
Aug 25 Javascript
基于jQuery实现表格的排序
Dec 02 Javascript
jQuery实现的简单在线计算器功能
May 11 jQuery
Ant Design Vue 添加区分中英文的长度校验功能
Jan 21 Javascript
vue 弹出遮罩层样式实例
Jul 22 Javascript
Vue3.0中Ref与Reactive的区别示例详析
Jul 07 Vue.js
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一些常用的正则表达式字符的一些转换
2008/07/29 PHP
Windows Apache2.2.11及Php5.2.9-1的安装与配置方法
2009/06/08 PHP
url decode problem 解决方法
2011/12/26 PHP
采用header定义为文件然后readfile下载(隐藏下载地址)
2014/01/31 PHP
php防止站外远程提交表单的方法
2014/10/20 PHP
php绘制圆形的方法
2015/01/24 PHP
解析WordPress中函数钩子hook的作用及基本用法
2015/12/22 PHP
Yii中实现处理前后台登录的新方法
2015/12/28 PHP
php简单处理XML数据的方法示例
2017/05/19 PHP
详解PHP 二维数组排序保持键名不变
2019/03/06 PHP
CSDN轮换广告图片轮换效果
2007/03/27 Javascript
Jquery作者John Resig自己封装的javascript 常用函数
2009/11/09 Javascript
关于javascript中this关键字(翻译+自我理解)
2010/10/20 Javascript
javascript如何判断输入的url是否正确
2014/04/11 Javascript
jQuery针对input的class属性写了多个值情况下的选择方法
2016/06/03 Javascript
Koa代理Http请求的示例代码
2018/10/10 Javascript
NodeJS 将文件夹按照存放路径变成一个对应的JSON的方法
2018/10/17 NodeJs
jQuery实现的简单日历组件定义与用法示例
2018/12/24 jQuery
ES6 Promise对象概念及用法实例详解
2019/10/15 Javascript
[02:04]2020年夜魇暗潮预告片
2020/10/30 DOTA
python 文件转成16进制数组的实例
2018/07/09 Python
用pycharm开发django项目示例代码
2018/10/24 Python
Python 获取主机ip与hostname的方法
2018/12/17 Python
连接pandas以及数组转pandas的方法
2019/06/28 Python
将数据集制作成VOC数据集格式的实例
2020/02/17 Python
python使用pandas抽样训练数据中某个类别实例
2020/02/28 Python
Html5新特性用canvas标签画多条直线附效果截图
2014/06/30 HTML / CSS
第二层交换机和路由器的区别?第三层交换机和路由器的区别?
2013/05/23 面试题
成都思必达公司C#程序员招聘面试题
2013/06/26 面试题
如何开启linux的ssh服务
2013/06/03 面试题
Set里的元素是不能重复的,那么用什么方法来区分重复与否呢?
2016/08/18 面试题
高一家长会邀请函
2014/01/12 职场文书
酒店保安领班职务说明书
2014/03/04 职场文书
2014年基建工作总结
2014/12/12 职场文书
2015年监理工作总结范文
2015/04/07 职场文书
CSS实现切角+边框+投影+内容背景色渐变效果
2021/11/01 HTML / CSS