Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript专题之从零实现jQuery的extend #33

Open
mqyqingfeng opened this issue Jul 14, 2017 · 43 comments
Open

JavaScript专题之从零实现jQuery的extend #33

mqyqingfeng opened this issue Jul 14, 2017 · 43 comments

Comments

@mqyqingfeng
Copy link
Owner

mqyqingfeng commented Jul 14, 2017

前言

jQuery 的 extend 是 jQuery 中应用非常多的一个函数,今天我们一边看 jQuery 的 extend 的特性,一边实现一个 extend!

extend 基本用法

先来看看 extend 的功能,引用 jQuery 官网:

Merge the contents of two or more objects together into the first object.

翻译过来就是,合并两个或者更多的对象的内容到第一个对象中。

让我们看看 extend 的用法:

jQuery.extend( target [, object1 ] [, objectN ] )

第一个参数 target,表示要拓展的目标,我们就称它为目标对象吧。

后面的参数,都传入对象,内容都会复制到目标对象中,我们就称它们为待复制对象吧。

举个例子:

var obj1 = {
    a: 1,
    b: { b1: 1, b2: 2 }
};

var obj2 = {
    b: { b1: 3, b3: 4 },
    c: 3
};

var obj3 = {
    d: 4
}

console.log($.extend(obj1, obj2, obj3));

// {
//    a: 1,
//    b: { b1: 3, b3: 4 },
//    c: 3,
//    d: 4
// }

当两个对象出现相同字段的时候,后者会覆盖前者,而不会进行深层次的覆盖。

extend 第一版

结合着上篇写得 《JavaScript专题之深浅拷贝》,我们尝试着自己写一个 extend 函数:

// 第一版
function extend() {
    var name, options, copy;
    var length = arguments.length;
    var i = 1;
    var target = arguments[0];

    for (; i < length; i++) {
        options = arguments[i];
        if (options != null) {
            for (name in options) {
                copy = options[name];
                if (copy !== undefined){
                    target[name] = copy;
                }
            }
        }
    }

    return target;
};

extend 深拷贝

那如何进行深层次的复制呢?jQuery v1.1.4 加入了一个新的用法:

jQuery.extend( [deep], target, object1 [, objectN ] )

也就是说,函数的第一个参数可以传一个布尔值,如果为 true,我们就会进行深拷贝,false 依然当做浅拷贝,这个时候,target 就往后移动到第二个参数。

还是举这个例子:

var obj1 = {
    a: 1,
    b: { b1: 1, b2: 2 }
};

var obj2 = {
    b: { b1: 3, b3: 4 },
    c: 3
};

var obj3 = {
    d: 4
}

console.log($.extend(true, obj1, obj2, obj3));

// {
//    a: 1,
//    b: { b1: 3, b2: 2, b3: 4 },
//    c: 3,
//    d: 4
// }

因为采用了深拷贝,会遍历到更深的层次进行添加和覆盖。

extend 第二版

我们来实现深拷贝的功能,值得注意的是:

  1. 需要根据第一个参数的类型,确定 target 和要合并的对象的下标起始值。
  2. 如果是深拷贝,根据 copy 的类型递归 extend。
// 第二版
function extend() {
    // 默认不进行深拷贝
    var deep = false;
    var name, options, src, copy;
    var length = arguments.length;
    // 记录要复制的对象的下标
    var i = 1;
    // 第一个参数不传布尔值的情况下,target默认是第一个参数
    var target = arguments[0] || {};
    // 如果第一个参数是布尔值,第二个参数是才是target
    if (typeof target == 'boolean') {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    // 如果target不是对象,我们是无法进行复制的,所以设为{}
    if (typeof target !== 'object') {
        target = {}
    }

    // 循环遍历要复制的对象们
    for (; i < length; i++) {
        // 获取当前对象
        options = arguments[i];
        // 要求不能为空 避免extend(a,,b)这种情况
        if (options != null) {
            for (name in options) {
                // 目标属性值
                src = target[name];
                // 要复制的对象的属性值
                copy = options[name];

                if (deep && copy && typeof copy == 'object') {
                    // 递归调用
                    target[name] = extend(deep, src, copy);
                }
                else if (copy !== undefined){
                    target[name] = copy;
                }
            }
        }
    }

    return target;
};

在实现上,核心的部分还是跟上篇实现的深浅拷贝函数一致,如果要复制的对象的属性值是一个对象,就递归调用 extend。不过 extend 的实现中,多了很多细节上的判断,比如第一个参数是否是布尔值,target 是否是一个对象,不传参数时的默认值等。

接下来,我们看几个 jQuery 的 extend 使用效果:

target 是函数

在我们的实现中,typeof target 必须等于 object,我们才会在这个 target 基础上进行拓展,然而我们用 typeof 判断一个函数时,会返回function,也就是说,我们无法在一个函数上进行拓展!

什么,我们还能在一个函数上进行拓展!!

当然啦,毕竟函数也是一种对象嘛,让我们看个例子:

function a() {}

a.target = 'b';

console.log(a.target); // b

实际上,在 underscore 的实现中,underscore 的各种方法便是挂在了函数上!

所以在这里我们还要判断是不是函数,这时候我们便可以使用《JavaScript专题之类型判断(上)》中写得 isFunction 函数

我们这样修改:

if (typeof target !== "object" && !isFunction(target)) {
    target = {};
}

类型不一致

其实我们实现的方法有个小 bug ,不信我们写个 demo:

var obj1 = {
    a: 1,
    b: {
        c: 2
    }
}

var obj2 = {
    b: {
        c: [5],

    }
}

var d = extend(true, obj1, obj2)
console.log(d);

我们预期会返回这样一个对象:

{
    a: 1,
    b: {
        c: [5]
    }
}

然而返回了这样一个对象:

{
    a: 1,
    b: {
        c: {
            0: 5
        }
    }
}

让我们细细分析为什么会导致这种情况:

首先我们在函数的开始写一个 console 函数比如:console.log(1),然后以上面这个 demo 为例,执行一下,我们会发现 1 打印了三次,这就是说 extend 函数执行了三遍,让我们捋一捋这三遍传入的参数:

第一遍执行到递归调用时:

var src = { c: 2 };
var copy = { c: [5]};

target[name] = extend(true, src, copy);

第二遍执行到递归调用时:

var src = 2;
var copy = [5];

target[name] = extend(true, src, copy);

第三遍进行最终的赋值,因为 src 是一个基本类型,我们默认使用一个空对象作为目标值,所以最终的结果就变成了对象的属性!

为了解决这个问题,我们需要对目标属性值和待复制对象的属性值进行判断:

判断目标属性值跟要复制的对象的属性值类型是否一致:

  • 如果待复制对象属性值类型为数组,目标属性值类型不为数组的话,目标属性值就设为 []

  • 如果待复制对象属性值类型为对象,目标属性值类型不为对象的话,目标属性值就设为 {}

结合着《JavaScript专题之类型判断(下)》中的 isPlainObject 函数,我们可以对类型进行更细致的划分:

var clone, copyIsArray;

...

if (deep && copy && (isPlainObject(copy) ||
        (copyIsArray = Array.isArray(copy)))) {

    if (copyIsArray) {
        copyIsArray = false;
        clone = src && Array.isArray(src) ? src : [];

    } else {
        clone = src && isPlainObject(src) ? src : {};
    }

    target[name] = extend(deep, clone, copy);

} else if (copy !== undefined) {
    target[name] = copy;
}

循环引用

实际上,我们还可能遇到一个循环引用的问题,举个例子:

var a = {name : b};
var b = {name : a}
var c = extend(a, b);
console.log(c);

我们会得到一个可以无限展开的对象,类似于这样:

循环引用对象

为了避免这个问题,我们需要判断要复制的对象属性是否等于 target,如果等于,我们就跳过:

...
src = target[name];
copy = options[name];

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

如果加上这句,结果就会是:

{name: undefined}

最终代码

// isPlainObject 函数来自于  [JavaScript专题之类型判断(下) ](https://github.com/mqyqingfeng/Blog/issues/30)
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;

function isPlainObject(obj) {
    var proto, Ctor;
    if (!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }
    proto = Object.getPrototypeOf(obj);
    if (!proto) {
        return true;
    }
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
    return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}


function extend() {
    // 默认不进行深拷贝
    var deep = false;
    var name, options, src, copy, clone, copyIsArray;
    var length = arguments.length;
    // 记录要复制的对象的下标
    var i = 1;
    // 第一个参数不传布尔值的情况下,target 默认是第一个参数
    var target = arguments[0] || {};
    // 如果第一个参数是布尔值,第二个参数是 target
    if (typeof target == 'boolean') {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    // 如果target不是对象,我们是无法进行复制的,所以设为 {}
    if (typeof target !== "object" && !isFunction(target)) {
        target = {};
    }

    // 循环遍历要复制的对象们
    for (; i < length; i++) {
        // 获取当前对象
        options = arguments[i];
        // 要求不能为空 避免 extend(a,,b) 这种情况
        if (options != null) {
            for (name in options) {
                // 目标属性值
                src = target[name];
                // 要复制的对象的属性值
                copy = options[name];

                // 解决循环引用
                if (target === copy) {
                    continue;
                }

                // 要递归的对象必须是 plainObject 或者数组
                if (deep && copy && (isPlainObject(copy) ||
                        (copyIsArray = Array.isArray(copy)))) {
                    // 要复制的对象属性值类型需要与目标属性值相同
                    if (copyIsArray) {
                        copyIsArray = false;
                        clone = src && Array.isArray(src) ? src : [];

                    } else {
                        clone = src && isPlainObject(src) ? src : {};
                    }

                    target[name] = extend(deep, clone, copy);

                } else if (copy !== undefined) {
                    target[name] = copy;
                }
            }
        }
    }

    return target;
};

思考题

如果觉得看明白了上面的代码,想想下面两个 demo 的结果:

var a = extend(true, [4, 5, 6, 7, 8, 9], [1, 2, 3]);
console.log(a) // ???
var obj1 = {
    value: {
        3: 1
    }
}

var obj2 = {
    value: [5, 6, 7],

}

var b = extend(true, obj1, obj2) // ???
var c = extend(true, obj2, obj1) // ???

专题系列

JavaScript专题系列目录地址:https://github.com/mqyqingfeng/Blog

JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

@TaurusWood
Copy link

var isObject = function (data) {
  return Object.prototype.toString.call(data) === '[object Object]'
}

var extend = function(deep) {
  var sources = typeof deep === 'boolean' && deep ? Array.prototype.slice.call(arguments, 1) : Array.prototype.slice.call(arguments)
  var i = 0;
  var obj = {};
  for (; i < sources.length; i++) {
    if (!isObject(sources[i])) {
      console.error("Function[extend] parmas must be Object")
      return false
    }
    for (var key in sources[i]) {
      if (deep === true && isObject(sources[i][key]) && obj[key]) {
        obj[key] = extend(deep, obj[key], sources[i][key])
        continue
      }
      if (sources[i].hasOwnProperty(key)) {
        obj[key] = sources[i][key]
      }

    }
  }
  return obj;
}

我没有考虑数组对象的情况,只是一个简单的对象的复制。我只是觉得没必要写的你那么繁琐,比如target,i,我觉得这些变量必要性都不大的,可以像我这样简写,不知道你对这种写法有啥看法,或者改进的建议

@mqyqingfeng
Copy link
Owner Author

@TaurusWood 我觉得写得很好呀,文章中的写法是抽自 jQuery 的 extend 的写法,考虑到了很多情况,比如不同类型之间的拷贝、循环引用,甚至是性能上的考虑,比如 for 循环高于 for in ,直接遍历 arguments 对象而非先转换成数组再遍历等,你的写法相对简化,但相对的,实现的功能也少了一点。我觉得这个看需求吧,如果现在的可以满足你的需求,那就是很好的~

@zhanba
Copy link

zhanba commented Aug 31, 2017

楼主的深复制方法只支持json对象,并不完美啊。可以参考http://jerryzou.com/posts/dive-into-deep-clone-in-javascript/

@fondadam
Copy link

fondadam commented Sep 1, 2017

为什么
var c = extend(true, obj2, obj1) 答案会是

c = {
    value: [5, 6, 7]
}

感觉应该是

c = {
     value: {
         3: 1
    }
}

@mqyqingfeng
Copy link
Owner Author

@fondadam 单独看这个结果,当然是 c = { value: { 3: 1 }},然而这其实是个陷阱,因为当执行前一句 var b = extend(true, obj1, obj2)的时候, obj1 的值已经发生了改变~

@fondadam
Copy link

fondadam commented Sep 1, 2017

@mqyqingfeng 哎呀 掉坑里了

话说你的blog都讲的好详细,谢谢分享。

@cobish
Copy link

cobish commented Sep 4, 2017

extend 第一版中的 src 变量好像赋值后没用到哦

@mqyqingfeng
Copy link
Owner Author

@cobish 哈哈,确实如此,感谢指出~

@1391020381
Copy link

1391020381 commented Oct 25, 2017

function extend() {
    // 默认不进行深拷贝
    var deep = false;
    var name, options, src, copy;
    var length = arguments.length;
    // 记录要复制的对象的下标
    var i = 1;
    // 第一个参数不传布尔值的情况下,target默认是第一个参数
    var target = arguments[0] || {};
    // 如果第一个参数是布尔值,第二个参数才是target
    if (typeof target == 'boolean') {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    // 如果target不是对象,我们无法进行复制的所以设为{}
    if (typeof target !== 'object') {
        target = {}
    }
    for (; i < length; i++) { // 从i 开始 没有deep i = 1 有deep i = 2
        options = arguments[i]
        if (options !== null) { //不为null和undefined
            for (name in options) {

                src = target[name]
                copy = options[name]

                if (deep && copy && typeof copy == 'object') {
                	console.log(deep, src, copy)
                    src = extend(deep, src, copy)
                    //  target[name] = extend(deep,src,copy)
                } else if (copy !== undefined) {
                    target[name] = copy
                }
            }
        }
    }
    return target

}

var obj1 = {
    a: 1,
    b: {
        c: 2
    }
}

var obj2 = {
    b: {
        c: [5],

    }

}

var d = extend(true, obj1, obj2)
console.log(d); // {a:1,b:{c:2}}

只是在深拷贝的时候把 target[name]换成了 src
怎么会这样?

@mqyqingfeng
Copy link
Owner Author

mqyqingfeng commented Oct 25, 2017

@1391020381 我搞错了,第二版是没有这个问题的,这是因为:

if (deep && copy && typeof copy == 'object') {
     console.log(deep, src, copy)
    // wrong 这样并不会修改 target[name]的值
     src = extend(deep, src, copy)
    // right
    target[name] = extend(deep,src,copy)
 } else if (copy !== undefined) {
      target[name] = copy
}

@1391020381
Copy link

1391020381 commented Oct 25, 2017

@mqyqingfeng 是不是当 target[name]是对象的时候,src保存的是target[name]的指针副本,
当再次给它赋值时,只是改变了src的指向,而target[name]没有变化。即前面参数传递的意思是一样的。

@mqyqingfeng
Copy link
Owner Author

@1391020381 正是如此,不过无论 target[name] 是不是对象,修改 src 的值都不会改变 target[name] 的值,举个例子:

// 不是对象
var value = {
	a: 1
}
var src = value.a;

src = 2;

console.log(value.a); // 1

// 是对象
var value = {
	a: {
		num: 1
	}
}
var src = value.a;

src = 2;

console.log(value.a); // {num: 1}

@1391020381
Copy link

1391020381 commented Oct 25, 2017

对的不是对象的时候,也是重新给src赋值,不会影响value.a。大神回复,我受宠若惊,我会一直关注大神的 @mqyqingfeng

@mqyqingfeng
Copy link
Owner Author

@1391020381 过奖了,大家一起相互交流讨论,有的时候,我可能因为某些原因没能及时回复,还请不要介意~

@zzzzzyb
Copy link

zzzzzyb commented Nov 2, 2017

// 要求不能为空 避免 extend(a,,b) 这种情况
if (options != null) {
........
}
参数如果是空的,按道理获取值是undefined,这个不应该判断是options!=undefined么,怎么是null,这个没看明白哈,求指教

@mqyqingfeng
Copy link
Owner Author

@zzzzzyb 因为 null == nullundefined == null 的结果都为 true,所以 options != null ,如果 options 为 null 或者 undefine,结果都为 false,所以无论是 extend(a,,b) 或者你传 extend(a, null, b)都是可以跳过这个参数的

@zzzzzyb
Copy link

zzzzzyb commented Nov 2, 2017

不严格的话,undefined==undefined,也是true,就结果来说用undefined来判断也是一样的吧@mqyqingfeng

@mqyqingfeng
Copy link
Owner Author

@zzzzzyb 所以也可以啦~ 不过 null 更短呀~ <( ̄︶ ̄)>
除此之外,值得一提的是,在低版本的浏览器中,undefined 的值是可以被更改的,举个例子:

window.undefined = null;
console.log(window.abc === undefined); // false

当然大多时候,不会出现这样的问题,但是有可能曲折的出现:

var n = window.abc; // 实际上 abc 并没有被设置,n 为 undefined
window[n]='text'; // 不小心更改了 undefined 的值
console.log(window.abc === undefined); // false

所以有些开发者可能更倾向于使用 null 进行判断

@zzzzzyb
Copy link

zzzzzyb commented Nov 2, 2017

@mqyqingfeng 确实更严谨点,谢谢大佬指点

@liujuntao123
Copy link

liujuntao123 commented Nov 14, 2017

感觉ES6的Object.assign函数和extend很相似,不过assign使用的也是浅拷贝

@mqyqingfeng
Copy link
Owner Author

@liujuntao123 确实是这样的,我觉得正是 extend 这种函数的广泛应用才导致了 assign 函数的诞生~

@lizhongzhen11
Copy link

非常感谢作者的讲解,一开始看有点不懂,后来带着敲,自己理解懂了点,然后自己仿着写了一遍,也弄了个深拷贝,只支持数组和普通对象,循环引用没过滤,技术比较菜写这个都写了很久,害羞的丢个链接让前辈们帮我看看不足之处,我感觉我写的 i f判断太多。
https://github.com/lizhongzhen11/myStudy/blob/master/jq/extend.js

@mqyqingfeng
Copy link
Owner Author

@lizhongzhen11 写得很不错哈~ 直接看确实会有点难度啦,最好还是自己敲一遍,边敲边理解,效果会更好。写代码的时候注意哪些东西是重复书写的,就可以将其提取出来,也算是一个优化,比如 Object.prototype.toString.call(source) === '[object Object]' 和 Object.prototype.toString.call(source) === '[object Array]' 被重复的用到,就可以提取成 isObject 和 isArray 函数,这样代码的语义会更好~

@lizhongzhen11
Copy link

哈哈哈哈哈,好开心能得到前辈的点评,我会继续努力的!!!

@xunan007
Copy link

xunan007 commented Feb 25, 2018

jQuery中$.extend没有办法深拷贝存在自身引用的对象,一旦出现自身引用会爆栈的。$.extend对于循环引用的处理并不彻底。
这是我自己的实现方式,比$.extend简单,但是加入了对象自环的处理。

/**
 * obj: 待拷贝的对象
 * target: 目标对象
 * parent: target的各个父级
 * _target: 用来分解target,如果有值,必有_target.target[_target.key] === target,主要用来while赋值用。(出现自引用)
 **/
function deepCopy(obj, target, parent = null, _target = null) {
    target = target || {};

    let _parent = parent;
    while (_parent) {
        if (_parent.parentObj === obj) {
            _target.target[_target.key] = obj;
            // 不能直接给target赋值,否则引用的指针丢失,只能给target的属性赋值
            // target = obj (错!!!)
            // 所以才要把target[key]拆分成target和key
            return;
        }
        _parent = _parent.parent;
    }

    Object.keys(obj).forEach(function(key) {
        let currentCopy = obj[key];
        if (typeof currentCopy === 'object' && currentCopy !== null) {
            target[key] = currentCopy.constructor === Array ? [] : {};
            deepCopy(
                currentCopy,
                target[key],
                {
                    parentObj: obj, // 当前target的直接父级
                    parent: parent // 保存target的所有非直接父级
                },
                {
                    // 把target[key]拆分成target和key,供while赋值,否则对传入的target直接赋值会导致指针丢失
                    target: target,
                    key: key
                }
            );
        } else {
            target[key] = currentCopy;
        }
    });

    return target;
}

@Tan90Qian
Copy link

@DEVINNN dalao的思路非常赞~不过这种实现的话,每次只能从一个对象继承属性吧?需要继承多个对象的时候需要多次执行该方法。有个简单的修改:既然已经用了es6的默认参数功能,那么for (let key in obj) { if (obj.hasOwnProperty(key)) {xxx},这一段在没有else分支的情况下,可以用Object.keys(obj).forEach(key => {xxx})来代替吧(不过不确定会不会性能较差)。

@xunan007
Copy link

@Tan90Qian 大佬谈不上,你写的文章质量都很高😄
代码是参考了知乎上一篇深拷贝“找爸爸”的思路。写的不那么仔细,感谢提出不足。
这个代码确实是只考虑拷贝一个对象的属性。

@Tan90Qian
Copy link

@DEVINNN 认错人了吧233 我只是一个读者,不是作者,还没毕业的萌新一枚

@xunan007
Copy link

@Tan90Qian 同,大三哈~

@mqyqingfeng
Copy link
Owner Author

@Tan90Qian @StevenXN 后生可畏呀,两位同学!(๑•̀ㅂ•́)و✧

@tr2v
Copy link

tr2v commented Jun 1, 2018

求解释一下最终版里的clone有什么用直接这样写不行吗

if (copyIsArray) {
    copyIsArray = false;
    src = src && Array.isArray(src) ? src : [];

} else {
    src = src && isPlainObject(src) ? src : {};
}
target[name] = extend(deep, src, copy);

@onexwolf
Copy link

// 第一个参数不传布尔值的情况下,target 默认是第一个参数
var target = arguments[0] || {};

对于这句注释,如果第一个参数传入false,得到的结果是{},所以是不是可以理解成如果传入false,就不会把第二个参数当成target,而是直接使用{},然后从第二个参数开始copy呢。

@gxr404
Copy link

gxr404 commented Feb 25, 2019

if (copyIsArray) {
    copyIsArray = false; 
    src = src && Array.isArray(src) ? src : [];

}

大佬 这段代码中 copyIsArray 重新赋值false是 有必要的吗? 求解

@NameWjp
Copy link

NameWjp commented Jul 9, 2019

@gxr404 要的,如果这里不重新赋值的话会影响下一次for循环数组的判断

@xianping-yan
Copy link

使用Object.create({})创建的对象通过isPlainObject函数后返回false,也就会导致这个对象不会递归调用extend方法。这样是不是就没有达到深度拷贝的效果呢?

@wweggplant
Copy link

有个问题,检测循环引用的部分,感觉有问题

var a = {
    a:{
        b:{
            c:{
                d:b
            }
        }
    }
}
var b = {
    a:{
        b:{
            c:{
                d:a
            }
        }
    }
}
var c = extend(a, b);

结果是
image

不知道是否是因为对于循环检测的定义不一样造成的

@lazyken
Copy link

lazyken commented Sep 14, 2020

if (target === copy) {continue; }是直接跳过当前循环,那么最后name:undefined是什么时候变成undefined的呢?这个地方有点糊涂,想不明白

@Richard-zhao93
Copy link

if (target === copy) {continue; }是直接跳过当前循环,那么最后name:undefined是什么时候变成undefined的呢?这个地方有点糊涂,想不明白

我的理解是

var  a = {
    b: 1
}
console.log(a.c) // undefined

对象中没有赋值的属性,取值都为 undefined

@lazyken
Copy link

lazyken commented Oct 14, 2020

if (target === copy) {continue; }是直接跳过当前循环,那么最后name:undefined是什么时候变成undefined的呢?这个地方有点糊涂,想不明白

我的理解是

var  a = {
    b: 1
}
console.log(a.c) // undefined

对象中没有赋值的属性,取值都为 undefined

var a = {name : b};
var b = {name : a}

原本不是循环引用嘛,name都是有值的,extend后name都变成undefined了,它们是在哪里变成undefined的呢?
后来我想明白了,请看我下面的评论

@lazyken
Copy link

lazyken commented Oct 14, 2020

大佬,你的循环引用的例子有点问题:
var a = {name : b};
var b = {name : a}
var c = extend(a, b);
console.log(c);
---------------------------------
虽然var a的时候,b变量提升了,但是此时b是undefined。所以,这个例子里并不是循环引用。
实际结果是:
a = {name: undefined}
b= {name: {name: undefined}}

@bingyuea
Copy link

大佬,这篇所讲的extend函数 感觉和上篇所说的深拷贝是一回事吧,这样理解对吧

@gdmec07150725
Copy link

gdmec07150725 commented May 11, 2023

ES6的Object.assign好像就是extend 第一版的实现

@muyudou
Copy link

muyudou commented Jun 9, 2023

if (target === copy) {continue; } 感觉进不去,这里是不是有问题啊?无法判断循环引用
截屏2023-06-09 19 22 44

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests