看这个文章之前,看一下这个作者的文档,讲了一下堆和栈、基本数据和引用数据,比较基础的一些东西:https://www.cnblogs.com/echolun/p/7889848.html
如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。
浅拷贝:复制对象的时候,只对第一层键值对进行独立的复制
简单的实现如下 es5
// 浅拷贝实现
function shadowCopy(target, source){
if( !source || typeof source !== 'object'){
return;
}
// 这个方法有点小trick,target一定得事先定义好,不然就不能改变实参了。
// 具体原因解释可以看参考资料中 JS是值传递还是引用传递
if( !target || typeof target !== 'object'){
return;
}
// 这边最好区别一下对象和数组的复制
for(var key in source){
if(source.hasOwnProperty(key)){
target[key] = source[key];
}
}
}
es6
注意,在属性拷贝过程中可能会产生异常,比如目标对象的某个只读属性和源对象的某个属性同名,这时该方法会抛出一个 TypeError 异常,拷贝过程中断,已经拷贝成功的属性不会受到影响,还未拷贝的属性将不会再被拷贝。
注意, Object.assign 会跳过那些值为 null 或 undefined 的源对象。
Object.assign(target, ...sources)
var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }
深拷贝: 递归属性遍历
es5
一般来说,在JavaScript中考虑复合类型的深层复制的时候,往往就是指对于Date、Object与Array这三个复合类型的处理。我们能想到的最常用的方法就是先创建一个空的新对象,然后递归遍历旧对象,直到发现基础类型的子节点才赋予到新对象对应的位置。不过这种方法会存在一个问题,就是JavaScript中存在着神奇的原型机制,并且这个原型会在遍历的时候出现,然后原型不应该被赋予给新对象。那么在遍历的过程中,我们应该考虑使用hasOenProperty方法来过滤掉那些继承自原型链上的属性:
function clone(obj) {
var copy;
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
JQ前拷贝神拷贝的实现
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {}, // 常见用法 jQuery.extend( obj1, obj2 ),此时,target为arguments[0]
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) { // 如果第一个参数为true,即 jQuery.extend( true, obj1, obj2 ); 的情况
deep = target; // 此时target是true
target = arguments[1] || {}; // target改为 obj1
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) { // 处理奇怪的情况,比如 jQuery.extend( 'hello' , {nick: 'casper})~~
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) { // 处理这种情况 jQuery.extend(obj),或 jQuery.fn.extend( obj )
target = this; // jQuery.extend时,this指的是jQuery;jQuery.fn.extend时,this指的是jQuery.fn
--i;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) { // 比如 jQuery.extend( obj1, obj2, obj3, ojb4 ),options则为 obj2、obj3...
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) { // 防止自引用,不赘述
continue;
}
// Recurse if we're merging plain objects or arrays
// 如果是深拷贝,且被拷贝的属性值本身是个对象
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) { // 被拷贝的属性值是个数组
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else { 被拷贝的属性值是个plainObject,比如{ nick: 'casper' }
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy ); // 递归~
// Don't bring in undefined values
} else if ( copy !== undefined ) { // 浅拷贝,且属性值不为undefined
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
利用JSON深拷贝
对于一般的需求是可以满足的,但是它有缺点。下例中,可以看到JSON复制会忽略掉值为undefined以及函数表达式。
JSON.parse(JSON.stringify(obj));