深入理解jquery中extend的实现


Posted in Javascript onDecember 22, 2016

Jquery的扩展方法extend是我们在写插件的过程中常用的方法,该方法有一些重载原型,下面来看看详细的介绍吧。

通常我们使用jquery的extend时,大都是为了实现默认字段的覆盖,即若传入某个字段的值,则使用传入值,否则使用默认值。

如下面的代码:

function getOpt(option){
 var _default = {
 name : 'wenzi',
 age : '25',
 sex : 'male'
 }
 $.extend(_default, option);
 return _default;
}
getOpt(); // {name: "wenzi", age: "25", sex: "male"}
getOpt({name:'bing'}); // {name: "bing", age: "25", sex: "male"}
getOpt({name:'bing', age:36, sex:'female'}); // {name: "bing", age: 36, sex: "female"}

那现在我们就得需要知道这个extend具体是怎么实现的了,除了实现上面的功能,还有其他作用么?那肯定是有的啦,否则我也不会问那句话了((⊙?⊙)b)。我们先来看看extend主要有哪些功能,然后再看实现这些功能的原理。

1. extend能实现的功能

其实从extend的含义里,我们就能知道extend是做什么的了。extend翻译成汉语后就是:延伸、扩展、推广。

1.1 将两个或更多对象的内容合并到第一个对象

我们来看看$.extend()提供的参数:jQuery.extend( target [, object1 ] [, objectN ] ),extend方法需要至少传入一个参数,第一个必需,后面的都是可选参数。若传给extend是两个或两个以上的参数都是对象类型,那么就会把后面所有对象的内容合并给target(第一个对象)上。

我们再来看看上面的例子:

function getOpt(option){
 var _default = {
 name : 'wenzi',
 age : '25',
 sex : 'male'
 }
 `$.extend(_default, option);`
 return _default;
}

$.extend()中接收了两个参数_default和option,那么extend方法执行时,就会把option对象上字段的值全给了_default。于是_default上设置的默认值就会被option上的值覆盖。当然,若option上没有这个字段,就不会覆盖_default上字段的值。
上面函数中的extend,只是传入了两个参数,那传的参数再更多一些呢:

function getOpt(target, obj1, obj2, obj3){
 $.extend(target, obj1, obj2, obj3);
 return target;
}

var _default = {
 name : 'wenzi',
 age : '25',
 sex : 'male'
}
var obj1 = {
 name : 'obj1'
}
var obj2 = {
 name : 'obj2',
 age : '36'
}
var obj3 = {
 age : '67',
 sex : {'error':'sorry, I dont\'t kown'}
}
getOpt(_default, obj1, obj2, obj3); // {name: "obj2", age: "67", sex: {error: "sorry, I dont't kown"}}

这里我们传入了4个参数,然后getOpt()返回第一个参数的值。从运行的得到结果我们可以看到,属性值永远是最后一个属性的值。

还有很重要的一点,$.extend()其实是有返回值的,返回的就是修改后的第一个参数的值。如我们可以把上面的函数修改成这样:

function getOpt(target, obj1, obj2, obj3){
 var result = $.extend(target, obj1, obj2, obj3); 
 return result; // // result即修改后的target值
}

若我们传入的参数不想被修改,我们可以用一个空对象来作为第一个参数,然后获取$.extend()的返回值:

function getOpt(target, obj1, obj2, obj3){
 var result = $.extend({}, target, obj1, obj2, obj3); 
 return result; // // result即为{}修改后的值
}

1.2 为JQUERY扩展方法或属性

刚才我们在1.1中讲的$.extend()的例子都是传了两个或两个以上的参数,但其实只有一个参数是必须的。若只传一个参数会怎样呢。

如果只有一个参数提供给$.extend() ,这意味着目标参数被省略。在这种情况下,jQuery对象本身被默认为目标对象。这样,我们可以在jQuery的命名空间下添加新的功能。这对于插件开发者希望向 jQuery 中添加新函数时是很有用的。

$.extend({
 _name : 'wenzi',
 _getName : function(){
 return this._name;
 }
})

$._name; // wenzi
$._getName(); // wenzi

这样我们就为jQuery扩展了_name属性和_getName方法。

1.3 深度拷贝和浅度拷贝

针对什么是深度拷贝,什么是浅度拷贝,我们先来看一个简单的例子。

var obj = {name:'wenzi', sex:'male'};
var obj1 = obj; // 赋值
obj1.name = 'bing';
console.log(obj.name); // bing

我们修改了obj1中的name值,结果obj中的值也跟着发生了变化,这是为什么呢。其实这就是浅度拷贝:这仅仅是将obj对象的引用地址简单的复制了一份给予变量 obj1,而并不是将真正的对象克隆了一份,因此obj和obj1指向的都是同一个地址。当修改obj1的属性或给obj1添加新属性时,obj都会受到影响。

可是如果变量的值不是对象和数组,修改后面的变量是不会影响到前面的变量:

var s = 'hello';
var t = s;
t = 'world';
console.log(s); // hello

那么深度拷贝就不是拷贝引用地址,而是实实在在的复制一份新对象给新的变量。 在上面使用$.extend()中,都是使用的浅度拷贝,因此若后面的参数值是object类型或array类型,修改_default(target)的值,就会影响后面参数的值。

如我们使用getOpt(_default, obj1, obj2, obj3);得到的_default值是{name: “obj2”, age: “67”, sex: {error: “sorry, I dont't kown”}},可是若:

_default.sex.error = 'hello world';

那么obj3.sex.error也会跟着修改,因为obj3.sex是一个object类型。

不过$.extend()也提供了深度拷贝的方法:jQuery.extend( [deep ], target, object1 [, objectN ] ) 。若第一个参数是boolean类型,且值是true,那么就会把第二个参数作为目标参数进行合并。

var obj = {name:'wenzi', score:80};
var obj1 = {score:{english:80, math:90}}
$.extend(true, obj, obj1);
obj.score.english = 10;
console.log(obj.score.english); // 10
console.log(obj1.score.english); // 80

执行后我们发现,无论怎么修改obj.score里的值,都不会影响到obj1.score了。

2. jQuery中extend实现原理

其实不看源码,对extend大致的过程应该也是了解的:对后一个参数进行循环,然后把后面参数上所有的字段都给了第一个字段,若第一个参数里有相同的字段,则进行覆盖操作,否则就添加一个新的字段。

下面是jQuery中关于extend的源码,我就在源码上进行注释讲解了,随后再在后面进行总结:

// 为与源码的下标对应上,我们把第一个参数称为`第0个参数`,依次类推
jQuery.extend = jQuery.fn.extend = function() {
 var options, name, src, copy, copyIsArray, clone,
 target = arguments[0] || {}, // 默认第0个参数为目标参数
 i = 1, // i表示从第几个参数凯斯想目标参数进行合并,默认从第1个参数开始向第0个参数进行合并
 length = arguments.length,
 deep = false; // 默认为浅度拷贝

 // 判断第0个参数的类型,若第0个参数是boolean类型,则获取其为true还是false
 // 同时将第1个参数作为目标参数,i从当前目标参数的下一个
 // Handle a deep copy situation
 if ( typeof target === "boolean" ) {
 deep = target;

 // Skip the boolean and the target
 target = arguments[ i ] || {};
 i++;
 }

 // 判断目标参数的类型,若目标参数既不是object类型,也不是function类型,则为目标参数重新赋值 
 // Handle case when target is a string or something (possible in deep copy)
 if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
 target = {};
 }

 // 若目标参数后面没有参数了,如$.extend({_name:'wenzi'}), $.extend(true, {_name:'wenzi'})
 // 则目标参数即为jQuery本身,而target表示的参数不再为目标参数
 // Extend jQuery itself if only one argument is passed
 if ( i === length ) {
 target = this;
 i--;
 }

 // 从第i个参数开始
 for ( ; i < length; i++ ) {
 // 获取第i个参数,且该参数不为null和undefind,在js中null和undefined,如果不区分类型,是相等的,null==undefined为true,
 // 因此可以用null来同时过滤掉null和undefind
 // 比如$.extend(target, {}, null);中的第2个参数null是不参与合并的
 // Only deal with non-null/undefined values
 if ( (options = arguments[ i ]) != null ) {
 
 // 使用for~in获取该参数中所有的字段
 // Extend the base object
 for ( name in options ) {
 src = target[ name ]; // 目标参数中name字段的值
 copy = options[ name ]; // 当前参数中name字段的值

 // 若参数中字段的值就是目标参数,停止赋值,进行下一个字段的赋值
 // 这是为了防止无限的循环嵌套,我们把这个称为,在下面进行比较详细的讲解
 // Prevent never-ending loop
 if ( target === copy ) {
  continue;
 }

 // 若deep为true,且当前参数中name字段的值存在且为object类型或Array类型,则进行深度赋值
 // Recurse if we're merging plain objects or arrays
 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
  // 若当前参数中name字段的值为Array类型
  // 判断目标参数中name字段的值是否存在,若存在则使用原来的,否则进行初始化
  if ( copyIsArray ) {
  copyIsArray = false;
  clone = src && jQuery.isArray(src) ? src : [];

  } else {
  // 若原对象存在,则直接进行使用,而不是创建
  clone = src && jQuery.isPlainObject(src) ? src : {};
  }

  // 递归处理,此处为2.2
  // Never move original objects, clone them  
  target[ name ] = jQuery.extend( deep, clone, copy );

 // deep为false,则表示浅度拷贝,直接进行赋值
 // 若copy是简单的类型且存在值,则直接进行赋值
 // Don't bring in undefined values
 } else if ( copy !== undefined ) {
  // 若原对象存在name属性,则直接覆盖掉;若不存在,则创建新的属性
  target[ name ] = copy;
 }
 }
 }
 }

 // 返回修改后的目标参数
 // Return the modified object
 return target;
};

源码分析完了,下面我们来讲解下源码中存在的几个难点和重点。

2.1 若参数中字段的值就是目标参数,停止赋值

在源码中进行了一下这样的判断:

// Prevent never-ending loop
if ( target === copy ) {
 continue;
}

为什么要有这样的判断,我们来看一个简单的例子,如果没有这个判断会怎么样:

var _default = {name : 'wenzi'};
var obj = {name : _default}
$.extend(_default, obj);
console.log(_default);

输出的_default是什么呢:

_default = {name : _default};

_default是object类型,里面有个字段name,值是_default,而_default是object类型,里面有个字段name,值是_default……,无限的循环下去。于是jQuery中直接不进行操作,跳过这个字段,进行下一个字段的操作。

2.2 深度拷贝时进行递归处理

我们在前面稍微的讲解了一下,变量值为简单类型(如number, string, boolean)进行赋值时是不会影响上一个变量的值的,因此,如果当前字段的值为Object或Array类型,需要对其进行拆分,直到字段的值为简单类型(如number, string, boolean)时才进行赋值操作。

3. $.extend()与$.fn.extend()

上面讲解的全都是$.extend(),根本就没讲$.fn.extend() 。可是,你有没有发现一个细节,在这段代码的第一行是怎么写的:

jQuery.extend = jQuery.fn.extend = function(){}

也就是说$.extend()$.fn.extend()共用的是同一个函数体,所有的操作都是一样的,只不过两个extend使用的对象不同罢了:$.extend()是在jQuery($)上进行操作的;而$.fn.extend()是在jQuery对象上进行操作的,如$(‘div').extend()

4. 总结

这就是jQuery中extend的实现,以后若我们需要用到上面的功能时,除了使用$.extend() ,我们也可以在不引入jQuery框架的情况下,自己写一个简单的extend()来实现上面的功能。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

Javascript 相关文章推荐
select组合框option的捕捉实例代码
Sep 30 Javascript
JS和jquery获取各种屏幕的宽度和高度的代码
Aug 02 Javascript
js中的scroll和offset 使用比较的实例与分析
Sep 29 Javascript
js获取当前时间显示在页面上并每秒刷新
Dec 24 Javascript
jQuery调用ajax请求的常见方法汇总
Mar 24 Javascript
JavaScript+CSS实现仿天猫侧边网页菜单效果
Aug 25 Javascript
AngularJS手动表单验证
Feb 01 Javascript
jQuery on()方法绑定动态元素的点击事件实例代码浅析
Jun 16 Javascript
js鼠标按键事件和键盘按键事件用法实例汇总
Oct 03 Javascript
记一次webapck4 配置文件无效的解决历程
Sep 19 Javascript
Vue最新防抖方案(必看篇)
Oct 30 Javascript
Node.js fs模块原理及常见用途
Oct 22 Javascript
jQuery插件DataTable使用方法详解(.Net平台)
Dec 22 #Javascript
JS实现间歇滚动的运动效果实例
Dec 22 #Javascript
javascript-解决mongoose数据查询的异步操作
Dec 22 #Javascript
Bootstrap popover用法详解
Dec 22 #Javascript
深入学习jQuery中的data()
Dec 22 #Javascript
读Javascript高性能编程重点笔记
Dec 21 #Javascript
解决拦截器对ajax请求的拦截实例详解
Dec 21 #Javascript
You might like
Bo-Blog专用的给Windows服务器的IIS Rewrite程序
2007/08/26 PHP
PHP CodeIgniter框架的工作原理研究
2015/03/30 PHP
PHP代码维护,重构变困难的4种原因分析
2016/01/25 PHP
PHP 将dataurl转成图片image方法总结
2016/10/14 PHP
php使用PDO下exec()函数查询执行后受影响行数的方法
2017/03/28 PHP
JS上传图片前的限制包括(jpg jpg gif及大小高宽)等
2012/12/19 Javascript
一个奇葩的最短的 IE 版本判断JS脚本
2014/05/28 Javascript
script标签属性用type还是language
2015/01/21 Javascript
JavaScript计算某一天是星期几的方法
2015/08/05 Javascript
JS实现复制内容到剪贴板功能兼容所有浏览器(推荐)
2016/06/17 Javascript
AngularJS 基础ng-class-even指令用法
2016/08/01 Javascript
jQuery post数据至ashx实例详解
2016/11/18 Javascript
JS实现搜索框文字可删除功能
2016/12/28 Javascript
js仿微信抢红包功能
2020/09/25 Javascript
vue-cli webpack2项目打包优化分享
2018/02/07 Javascript
vue中如何动态绑定图片,vue中通过data返回图片路径的方法
2018/02/07 Javascript
Vue中父子组件通讯之todolist组件功能开发
2018/05/21 Javascript
使用mpvue搭建一个初始小程序及项目配置方法
2018/12/03 Javascript
vue生命周期与钩子函数简单示例
2019/03/13 Javascript
vue 列表页跳转详情页获取id以及详情页通过id获取数据
2019/03/27 Javascript
跟老齐学Python之编写类之四再论继承
2014/10/11 Python
Python手机号码归属地查询代码
2016/05/04 Python
python Pandas 读取txt表格的实例
2018/04/29 Python
使用python实现简单五子棋游戏
2019/06/18 Python
实例详解Python模块decimal
2019/06/26 Python
Python如何给你的程序做性能测试
2020/07/29 Python
CSS实现聊天气泡效果
2020/04/26 HTML / CSS
科技工作者先进事迹
2014/08/16 职场文书
大学生赌博检讨书
2014/09/22 职场文书
2014七年级班主任工作总结
2014/12/05 职场文书
党员个人自我评价
2015/03/03 职场文书
活动总结模板大全
2015/05/11 职场文书
2015年教研工作总结
2015/05/23 职场文书
Mysql binlog日志文件过大的解决
2021/10/05 MySQL
基于redis+lua进行限流的方法
2022/07/23 Redis
不想升级Win11?教你彻底锁定老版Windows系统的方法(附下载地址)
2022/09/23 数码科技