JavaScript对象浅拷贝与深拷贝原理及实现

作者:拾光时间:2026-10-30 13:22:02浏览:217

本文详细解析JavaScript中对象的浅拷贝与深拷贝机制。通过手动实现extendCopy和deepCopy函数,阐明基本类型与引用类型在内存地址上的区别。同时介绍jQuery中$.extend()方法的递归合并功能及其在插件开发中的应用,并探讨JS中对象相等的判断逻辑。

JavaScript对象浅拷贝与深拷贝原理及实现

本文分享了JavaScript中对象浅拷贝和深拷贝的具体实现代码,供读者参考。

浅拷贝机制与缺陷

1. 浅拷贝

拷贝操作的核心是将父对象的属性全部复制到子对象中。

以下函数展示了如何实现这一拷贝过程:


var Chinese = {
  nation:'中国'
}
var Doctor = {
  career:'医生'
}  
function extendCopy(p) {
    var c = {};
    for (var i in p) { 
      c[i] = p[i];
    }
    c.uber = p;
    return c;
 }

使用时,代码如下:


var Doctor = extendCopy(Chinese);
Doctor.career = '医生';
alert(Doctor.nation); // 中国

然而,这种拷贝方式存在一个缺陷:如果父对象的属性值是数组或另一个对象,子对象获得的仅仅是该属性的内存地址,而非真正的数据副本。因此,修改子对象中的引用类型属性可能会意外篡改父对象的数据。

例如,现在给Chinese对象添加一个名为“出生地”的属性,其值为一个数组。

Chinese.birthPlaces = ['北京','上海','香港'];

接着,通过extendCopy()函数让Doctor继承Chinese。

var Doctor = extendCopy(Chinese);

随后,向Doctor的“出生地”数组中添加一个城市:

Doctor.birthPlaces.push('厦门');

观察输出结果:

alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门 alert(Chinese.birthPlaces); //北京, 上海, 香港, 厦门

结果显示,Doctor和Chinese的出生地列表都被修改了。

由此可见,extendCopy()仅对基本类型数据进行了值拷贝,这种拷贝方式被称为“浅拷贝”。

深拷贝的实现方法

2. 深拷贝

鉴于浅拷贝存在上述弊端,接下来介绍深拷贝的实现方式。

深拷贝旨在实现真正意义上的数组和对象副本。其实现并不复杂,只需对浅拷贝逻辑进行递归调用即可。


function deepCopy(p, c) {
    var c = c || {};
    for (var i in p) {
      if (typeof p[i] === 'object') {
        c[i] = (p[i].constructor === Array) ? [] : {};
        deepCopy(p[i], c[i]);
      } else {
         c[i] = p[i];
      }
    }
    return c;
  }

使用方法如下:

var Doctor = deepCopy(Chinese);

现在,给父对象添加一个属性,其值为数组,并在子对象上修改该属性:


Chinese.birthPlaces = ['北京','上海','香港'];
Doctor.birthPlaces.push('厦门');

alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); //北京, 上海, 香港

这样便完成了深拷贝操作。

jQuery $.extend()方法详解

$.extend()方法

jQuery库中的$.extend()方法也具备类似功能。

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

•deep 类型: Boolean 如果是true,合并成为递归(又叫做深拷贝)。 •target 类型: Object 对象扩展。这将接收新的属性。 •object1 类型: Object 一个对象,它包含额外的属性合并到第一个参数. •objectN 类型: Object 包含额外的属性合并到第一个参数

当向$.extend()传递两个或多个对象时,所有对象的属性都会被添加到目标对象(即target参数)中。

如果仅向$.extend()传递一个参数,则意味着省略了目标参数。此时,jQuery对象本身被视为目标对象。这种用法常用于在jQuery命名空间下添加新功能,对于希望向jQuery扩展函数的插件开发者而言非常有用。

需要注意的是,目标对象(第一个参数)会被修改,并通过$.extend()返回。如果希望保留原对象不变,可以将一个空对象作为目标参数传入:

var object = $.extend({}, object1, object2);

默认情况下,$.extend()的合并操作不是递归的。如果第一个对象的某个属性本身是对象或数组,它将被第二个对象中具有相同key的属性完全覆盖,其内部值不会被合并。通过检查示例中banana的值即可理解这一点。然而,若将true作为函数的第一个参数,则会在对象层级进行递归合并。

警告:不支持第一个参数传递 false 。

1. 合并两个对象,并修改第一个对象。


var object1 = {
 apple: 0,
 banana: { weight: 52, price: 100 },
 cherry: 97
};
var object2 = {
 banana: { price: 200 },
 durian: 100
};

// Merge object2 into object1
$.extend( object1, object2 );

// Assuming JSON.stringify - not available in IE<8
console.log( JSON.stringify( object1 ) );
//{"apple":0,"banana":{"price":200},"cherry":97,"durian":100}

2. 采用递归方式合并两个对象,并修改第一个对象。


var object1 = {
 apple: 0,
 banana: { weight: 52, price: 100 },
 cherry: 97
};
var object2 = {
 banana: { price: 200 },
 durian: 100
};

// Merge object2 into object1, recursively
$.extend( true, object1, object2 );

// Assuming JSON.stringify - not available in IE<8
console.log( JSON.stringify( object1 ) );
//{"apple":0,"banana":{"weight":52,"price":200},"cherry":97,"durian":100}

3. 合并 defaults 和 options 对象,并且不修改 defaults 对象。这是常用的插件开发模式。


var defaults = { validate: false, limit: 5, name: "foo" };
var options = { validate: true, name: "bar" };

// Merge defaults and options, without modifying defaults
var settings = $.extend( {}, defaults, options );


console.log(JSON.stringify( defaults ));
console.log(JSON.stringify( options ));
console.log(JSON.stringify( settings ));
//defaults -- {"validate":false,"limit":5,"name":"foo"}
//options -- {"validate":true,"name":"bar"}
//settings -- {"validate":true,"limit":5,"name":"bar"}

JavaScript对象相等性判断

Javascript 判断对象是否相等

在JavaScript中,相等运算包括“==”和“===”(全等),两者的区别显而易见。本文将探讨如何判断两个对象是否相等。直觉上,如果两个对象拥有相同的属性且属性值也相同,它们可能被视为相等。但通过以下实例可以验证这一观点:


var obj1 = {
  name: "Benjamin",
  sex : "male"
}

var obj2 = {
  name: "Benjamin",
  sex : "male"
}

//Outputs: false
console.log(obj1 == obj2);

//Outputs: false
console.log(obj1 === obj2);

从上述例子可见,无论使用“==”还是“===”,结果均返回false。根本原因在于,基本类型(如string、number)通过值进行比较,而对象(包括Date、Array及普通对象)则通过比较指向内存中的地址来判断。参考以下示例:


var obj1 = {
  name: "Benjamin",
  sex : "male"
};

var obj2 = {
  name: "Benjamin",
  sex : "male"
};

var obj3 = obj1;

//Outputs: true
console.log(obj1 == obj3);

//Outputs: true
console.log(obj1 === obj3);

//Outputs: false
console.log(obj2 == obj3);

//Outputs: false
console.log(obj2 === obj3);

上例返回true,是因为obj1和obj3的指针指向了内存中的同一地址。这与Java/C++等面向对象语言中值传递和引用传递的概念相似。因此,在判断两个对象是否相等时,必须明确你是希望比较对象的属性结构,还是属性对应的值,抑或是其他逻辑。


function person(name) { 
  this.name=name; 
} 

var p1 = new person("p1"); 
var p2 = new person("p2"); 

console.log(p1 == p2); //false 

person.prototype.sayHi = function() { 
  // do sayHi here 
} 

console.log(p1.sayHi() == p2.sayHi()); //true 
console.log(p1.sayHi() === p2.sayHi()); //true

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

作者最新文章