从jQuery.extend()中学习对象的扩展和深遍历

由于本人水平有限,若有误导嫌疑,请不吝赐教!

$.extend是jQuery中极为重要的一个方法,它是jQ内置的第一个方法,其他所有方法都在它的基础上进行扩展的,所以非常有必要学习这个API的实现。

用法非常简单,可以参照菜鸟教程的示例:菜鸟教程 $.extend 示例

$.extend( [deep ], target, object1 [, objectN ] )
  1. deep: 是否深度合并
  2. target: 需要合并的源对象
  3. object: 需要合并的目标对象
    注意,合并后,target会被修改!


一、知识准备(参考《javascript高级程序设计》第三版)

1.基础类型的复制
基础类型包括:String、Number、Boolean、undefined、null。它们的复制在代码上体现为赋值:

var num1 = 5;
var num2 = num1;
num2 = 10;
console.log(num1)  //5
console.log(num2)  //10
修改num2后,对num1无影响。

这是因为基础类型的赋值,会在对象变量上创建一个新值,并把该值放到新变量的位置上。如下图所示:

复制前
复制后

2.引用类型的复制
引用类型包括: Object、Array

var obj1 = new Object()
obj1.name = '电光毒龙';
var obj2 = obj1;
obj2.name = '钻';
console.log(obj1.name)  // '钻'
修改obj2后,也改变了obj1

说明obj1和obj2引用的是同一个对象,我们可以通过obj2改变obj1。所以引用类型的复制不能通过简单的赋值实现。变量和堆栈中的对象关系如下图:

3.判断数据类型:
可以参照我的上一篇文章:从jQuery.type()中学习如何判断数据类型
jQ中还用到了 isPlainObject() 判断对象,文章结尾会进行介绍和说明


二、核心代码

根据上述,对象的合并,实际上是对象(引用类型)深遍历时进行基础数据赋值的过程。我们将第一个传入的对象称为 源对象,之后传入的统称为目标对象

jQuery.extend = jQuery.fn.extend = function() {
    var options,          //目标对象,指向参数
        name,             //目标对象中的属性(键)
        src,              //源对象的属性值(值)
        copy,             //目标对象的属性值
        copyIsArray,      //判断属性值是否为数组(布尔值)
        clone,            //源对象的属性值(值)
        target = arguments[ 0 ] || {},//源对象
        i = 1,        
        length = arguments.length,//参数数量
        deep = false;     //是否深度合并,默认false

    // 处理深拷贝
    if ( typeof target === "boolean" ) {
        deep = target;

        //源对象指向第二个参数, 遍历时跳过第一个参数
        target = arguments[ i ] || {};
        i++;
    }
    // Handle case when target is a string or something (possible in deep copy)
    // 当源对象为字符串或其他非基础数据类型时执行(此时可能在深拷贝过程中)
    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
        target = {};
    }

    // 如果只传递一个参数,则扩展到jQuery本身。此时jQuery为源对象
    if ( i === length ) {
        target = this;
        i--;
    }

    for ( ; i < length; i++ ) {

        // 程序不处理 null或undefined 参数
        if ( ( options = arguments[ i ] ) != null ) {

            // 遍历目标对象
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                // 防止无限循环
                if ( target === copy ) {
                    continue;
                }

                // 目标对象的属性值若为 Object 或 Array ,进行递归 
                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 : {};
                    }

                    // 递归遍历
                    target[ name ] = jQuery.extend( deep, clone, copy );

                // 若为基础数据类型且不为undefined,赋值即可
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }

    // 返回修改后的源对象,若不想修改传入的对象,可以把 {} 作为源对象传入
    return target;
};


三、分析

有以下几处值得品味

  1. 防止基础类型出现
if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
    target = {};
}

源码中注释说到,这里是为了深拷贝中重置string or something。但是 target 只可能是$.extend([deep ], target, object1 [, objectN ]) 中传入的target,或者clone,而 clone必定是 [] 或 {} ,所以不可能出现注释中所说的情况,还请 读者们能够赐教

2.防止无限循环

 if ( target === copy ) {
    continue;
 }

这是为了防止以下情况出现

var obj1 = {name: '电光毒龙'};
var obj2 = {name: obj1};


四、jQuery.isPlainObject和jQuery.isArray分析

从字面意思可以看出,这是判断数据是否为对象和是否为数组
1.jQuery.isArray

jQuery.extend( {
    isArray: Array.isArray
})

这段代码在 jQuery-3.1.1 第282行,其方法直接调用原生数组的isArray方法,此方法是ECMA5.1版发布的标准,不支持IE8及以下低版本浏览器。

2.jQuery.isPlainObject

isPlainObject: function( obj ) {
    var proto, Ctor;

    // Detect obvious negatives
    // Use toString instead of jQuery.type to catch host objects
    if ( !obj || toString.call( obj ) !== "[object Object]" ) {
        return false;
    }

    proto = getProto( obj );

    // Objects with no prototype (e.g., `Object.create( null )`) are plain
    if ( !proto ) {
        return true;
    }

    // 判断传入对象的原型链是否存在"constructor" ,若有则赋值给Ctor
    Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
    // 判断传入对象的构造函数是否为 Object
    return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
}

这段代码在 jQuery-3.1.1 第302行。其中:
toString === Object.prototype.toString
getProto === Object.getPrototypeOf
hasOwn === Object.prototype.hasOwnProperty
fnToString === Object.prototype.hasOwnProperty.toString
ObjectFunctionString === "function Object() { [native code] }"

此方法是用于判断传入对象是否继承原生Object,因为实例化函数构造器所得的对象(在JAVA中如同class的概念)不应该被当做普通对象进行扩展。eg:

//构造函数与普通函数没有任何区别,仅仅是调用方法不同,它就起到不同的作用。约定上我们用首字母大写表示此函数为构造函数
function Human() {};
var human = new Human();
$.isPlainObject(human ) //返回false
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,380评论 25 707
  • 四季转换,白驹过隙。树叶从枯萎的枝桠飘落,将一片霜意带进初冬。十月的荏苒已成过往,而十一月,打马而来。莫道岁月晚,...
    江山如画5088阅读 481评论 0 1
  • 1、人是无法做到换位思考的,因为思想、经历、感官、全都不一样,就像我说大海很漂亮,你却说淹死过很多人。 2、相信人...
    3500f3ef60b3阅读 207评论 0 1
  • 可选链 官方的说法是:由多个可选类型组成的一个链条被称为可选链 然而他的可选性实际上体现在:我们当前调用的目标可能...
    S_Lyu阅读 682评论 0 3