小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
我们上一篇已经讲了两种深复制对象的方法,分别是
-
JSON.parse(JSON.stringify(obj))- 只能复制普通键的属性,Symbol类型的无能为力
- 循环引用对象,比如window不能复制
- 函数不能复制
- Date, Regex, Blob等类型不能有效复制
-
MessageEvent系列 包含MessageChannel,BroadCastChannel等等
- 循环引用对象,比如window不能复制
- 函数不能复制
- 变为了异步
都不完美,今天我们就来一起学习用传统的遍历方式进行深拷贝。
难点
我们自己实现深拷贝的难点是什么?
- 区分值是引用类型还是非引用类型
- 递归(递归就可能爆栈)
- 特殊类型的处理
- 循环引用
简单版本的实现
- 判断传入的是不是对象或者数组, 如果不是直接返回
- 如果是,再区分数据和对象分别遍历属性
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;
}
简单的版本能满足一般的需求了,但是问题多多!!!
- 循坏引用
- 爆栈
- 特殊类型
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;
};
其余的操作也很普通,有两项增强:
- 其对
__proto__进行了单独的判断,防止原型污染 - target和copy进行了比对,简单的防止循环引用
小结
很多时候,我们不一定需要写出这么完美的代码,但是脑海中一定要知道写出来的代码还存在什么问题。 如果在你的场景下满足需求,又有什么问题? 何必太认真。
今天你收获了吗?