对象的深拷贝 中

164 阅读1分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

我们上一篇已经讲了两种深复制对象的方法,分别是

  1. JSON.parse(JSON.stringify(obj))

    1. 只能复制普通键的属性,Symbol类型的无能为力
    2. 循环引用对象,比如window不能复制
    3. 函数不能复制
    4. Date, Regex, Blob等类型不能有效复制
  2. MessageEvent系列 包含MessageChannel,BroadCastChannel等等

    1. 循环引用对象,比如window不能复制
    2. 函数不能复制
    3. 变为了异步

都不完美,今天我们就来一起学习用传统的遍历方式进行深拷贝。

难点

我们自己实现深拷贝的难点是什么?

  1. 区分值是引用类型还是非引用类型
  2. 递归(递归就可能爆栈
  3. 特殊类型的处理
  4. 循环引用

简单版本的实现

  1. 判断传入的是不是对象或者数组, 如果不是直接返回
  2. 如果是,再区分数据和对象分别遍历属性
const { hasOwnProperty } = Object.prototype;

function isObject(obj) {
    return obj !== null && typeof obj == "object";
}

function isArray(obj) {
    return Array.isArray(obj)
}

function hasOwn(obj, key) {
    return hasOwnProperty.call(obj, key)
}

function deepClone(obj) {
    if (!isObject(obj)) return obj;
    let data;

    if (isArray(obj)) {
        data = [];
        for (let i = 0; i < obj.length; i++) {
            data[i] = deepClone(obj[i]);
        }
    } else if (isObject(obj)) {
        data = {};
        for (let key in obj) {
            if (hasOwn(obj, key)) {
                data[key] = deepClone(obj[key]);
            }
        }
    }
    return data;
}

简单的版本能满足一般的需求了,但是问题多多!!!

  1. 循坏引用
  2. 爆栈
  3. 特殊类型

jQuery.extend

一起看看经典jquery的

jQuery.extend = jQuery.fn.extend = function() {
	var options, name, src, copy, copyIsArray, clone,
		target = arguments[ 0 ] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;

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

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && typeof target !== "function" ) {
		target = {};
	}

	// Extend jQuery itself if only one argument is passed
	if ( i === length ) {
		target = this;
		i--;
	}

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

		// Only deal with non-null/undefined values
		if ( ( options = arguments[ i ] ) != null ) {

			// Extend the base object
			for ( name in options ) {
				copy = options[ name ];

				// Prevent Object.prototype pollution
				// Prevent never-ending loop
				if ( name === "__proto__" || target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
					( copyIsArray = Array.isArray( copy ) ) ) ) {
					src = target[ name ];

					// Ensure proper type for the source value
					if ( copyIsArray && !Array.isArray( src ) ) {
						clone = [];
					} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
						clone = {};
					} else {
						clone = src;
					}
					copyIsArray = false;

					// 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;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

其余的操作也很普通,有两项增强:

  1. 其对__proto__进行了单独的判断,防止原型污染
  2. target和copy进行了比对,简单的防止循环引用

小结

很多时候,我们不一定需要写出这么完美的代码,但是脑海中一定要知道写出来的代码还存在什么问题。 如果在你的场景下满足需求,又有什么问题? 何必太认真。

今天你收获了吗?

引用

深拷贝的终极探索(99%的人都不知道)