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最新动画教程+iso光盘下载
Jan 22 Javascript
JQuery 构建客户/服务分离的链接模型中Table分页代码效率初探
Jan 22 Javascript
ASP.NET jQuery 实例11 通过使用jQuery validation插件简单实现用户登录页面验证功能
Feb 03 Javascript
不使用XMLHttpRequest实现异步加载 Iframe和script
Oct 29 Javascript
JQuery中根据属性或属性值获得元素(6种情况获取方法)
Jan 17 Javascript
Jquery带搜索框的下拉菜单
May 06 Javascript
js判断手机访问或者PC的几个例子(常用于手机跳转)
Dec 15 Javascript
轻松掌握JavaScript中的Math object数学对象
May 26 Javascript
原生js实现放大镜效果
Jan 11 Javascript
基于JavaScript实现图片连播和联级菜单实例代码
Jul 28 Javascript
解决vue2.0路由跳转未匹配相应用路由避免出现空白页面的问题
Aug 24 Javascript
react-router4按需加载(踩坑填坑)
Jan 06 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内核探索:变量概述
2014/01/30 PHP
PHP调用wsdl文件类型的接口代码分享
2014/11/19 PHP
thinkphp模板继承实例简述
2014/11/26 PHP
JavaScript游戏之优化篇
2010/11/08 Javascript
JavaScript字符串插入、删除、替换函数使用示例
2013/07/25 Javascript
javascript 判断字符串是否包含某字符串及indexOf使用示例
2013/10/18 Javascript
JQuery插件fancybox无法在弹出层使用左右键的解决办法
2013/12/25 Javascript
Bootstrap响应式侧边栏改进版
2016/09/17 Javascript
AngularJS过滤器filter用法分析
2016/12/11 Javascript
详解webpack分离css单独打包
2017/06/21 Javascript
基于JavaScript实现五子棋游戏
2020/08/26 Javascript
详解vuex中mapState,mapGetters,mapMutations,mapActions的作用
2018/04/13 Javascript
element-ui 关于获取select 的label值方法
2018/08/24 Javascript
layui 动态设置checbox 选中状态的例子
2019/09/02 Javascript
js实现计算器功能
2020/08/10 Javascript
解决ant design vue 表格a-table二次封装,slots渲染的问题
2020/10/28 Javascript
vue组件添加事件@click.native操作
2020/10/30 Javascript
vue+element实现动态加载表单
2020/12/13 Vue.js
[01:05]DOTA2完美大师赛趣味视频之选手教你打职业
2017/11/23 DOTA
python在windows下实现ping操作并接收返回信息的方法
2015/03/20 Python
numpy排序与集合运算用法示例
2017/12/15 Python
Python pymongo模块常用操作分析
2018/09/01 Python
Flask实现跨域请求的处理方法
2018/09/27 Python
Python使用get_text()方法从大段html中提取文本的实例
2019/08/27 Python
python区分不同数据类型的方法
2019/10/14 Python
python实现批量文件重命名
2019/10/31 Python
python学生信息管理系统实现代码
2019/12/17 Python
python为Django项目上的每个应用程序创建不同的自定义404页面(最佳答案)
2020/03/09 Python
Python eval函数介绍及用法
2020/11/09 Python
css3实现一款模仿iphone样式的注册表单
2013/03/20 HTML / CSS
一个精品风格的世界:Atterley
2019/05/01 全球购物
致跳远、跳高运动员广播稿
2014/01/09 职场文书
理想演讲稿范文
2014/05/21 职场文书
校长师德师风自我剖析材料
2014/09/29 职场文书
手把手教你使用TensorFlow2实现RNN
2021/07/15 Python
Flask使用SQLAlchemy实现持久化数据
2021/07/16 Python