×

前端面试题:聊聊你对深拷贝和浅拷贝的理解以及它们之间的区别

作者:Terry2023.02.03来源:Web前端之家浏览:2665评论:0
关键词:深拷贝浅拷贝

500.jpg

面试的时候,经常会被问到深拷贝和浅拷贝的问题,所以今天小编来分享下关于深拷贝和浅拷贝的理解以及它们之间的区别。我们继续往下看吧。

深浅拷贝是什么?

首先我们要明白一点,js中数据类型分为:

基本数据类型 (Number, String, Boolean, Null, Undefined, Symbol)

对象数据类型 ( Object )

引用数据类型的值是保存在栈内存和堆内存中的对象。栈区内存保存变量标识符和指向堆内存中该对象的指针。当寻找引用值时,解释器会先寻找栈中的地址。然后根据地址找到堆内存的实体。

浅拷贝:

浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。

可以使用 for in、 Object.assign、 扩展运算符 ... 、Array.prototype.slice()、Array.prototype.concat() 等。

深拷贝:

深拷贝和浅拷贝是针对复杂数据类型(对象及数组)来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。

深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。 深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。

深浅拷贝基础(数据类型)

1.基础数据类型(值传递)

//基本数据类型的拷贝(复制copy)  深拷贝和浅拷贝
 
//前提:需要理解  值传递   地址传递 基本数据类型    值传递    number  string  boolean null  undefined   Sysmbol  bigInt       
    // Number
         a = 1.1;b = a;b = 2; console.log(a,b)
    // String
         a = 'hello';b = a;b = 3; console.log(a,b)
    // Boolean
         a = false;b = a;b = 'sss'; console.log(a,b)
    // Undefined
         a = undefined;b = a;b = false; console.log(a,b)
    // Null
         a = null;b = a;b = undefined; console.log(a,b)

2.复杂数据类型(地址传递)

//复杂数据类型(object) 的拷贝     地址传递  注意 常用的复杂数据类型包括:{} 、[] 、function(){} 、Date 、RegExp 、null(这个比较特殊)等
  
   //1、我们依然用一的简单赋值(=)来进行一遍操作(赋值)
  // 经过实践我们会发现:
   //  1、当类型为{}、[]的时候,改变b的值,a也会跟着一起变化。
    // 2、当类型为Date、function、RegExp的时候,a保持不变。
     //总结:
     //我们发现{}或者[]时,简单的赋值操作并不能实现它们的拷贝,只是改了b的指向,使a和b都指向同一个引用,随意改变一个,都会影响另外一个的值。   
 
     {}
     a = {name: 'abc'};b = a;b.name = 'sss'; 
     console.log(a,b)
     // []
     a = ['a','b','c'];b = a;b[1] = 'd'; 
     console.log(a,b)
     // function
     a = function(){ alert('aaa'); };b = a;b = function(){ alert('bbb'); }; 
     console.log(a.toString(),b.toString())
     // Date
     a = new Date('2018-10-11 00:00:00');b = a;b = new Date('1970-01-01 00:00:00');
     console.log(a,b)
     // RegExp
     a = new RegExp('abc');b = a;b = new RegExp('aaa'); 
     console.log(a,b)

深浅拷贝怎样操作(代码示例)

 1.浅拷贝:

 var array = [
        { number: 1 },
        { number: 2 },
        { number: 3 }
     ];
     var copyArray = array.slice();
     copyArray[0].number = 100;
     console.log(array); 
     console.log(copyArray); 
 
 
 
 
     let obj = {
         name: 'Yvette',
         age: 18,
         hobbies: ['reading', 'photography']
     }
     let obj2 = Object.assign({}, obj);
     let obj3 = {...obj};
 
     obj.name = 'Jack';
     obj.hobbies.push('coding');
     console.log(obj);
     console.log(obj2);
     console.log(obj3);
 
 
 
    // 可以看出浅拷贝只最第一层属性进行了拷贝,当第一层的属性值是基本数据类型时,新的对象和原对象互不影响,但是如果第一层的属性值是复杂数据类型,那么新对象和原对象的属性值其指向的是同一块内存
通过Object.assign     for   in  进行浅拷贝 
//Object.assign 和 for in进行{}和[]的拷贝(浅拷贝--只能对象拷贝一层最外层)
     //Object.assign  对象的复制		
     a = {name: 'aaa'};
     //地址引用
     var b=a;
     // Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
     文件浅拷贝
     var b = Object.assign({},a)
     console.log(a,b)
     b.name = 'bbb';  
     console.log(a,b)
     a = [1,2,3,4,5]; 
     // b=a; 
     b = Object.assign([],a);
     b[1]=9;
    // console.log(a,b)
 
    // for in
	// 封装的函数  遍历对象或数组  在遍历中赋值给另外个对象
    var copy = function (obj){
       var res =  obj.constructor();
       console.log(res)
       // 遍历对象中的属性及方法
       for(var key in  obj){
         // 判断对象有没有相应的key
         if (obj.hasOwnProperty(key)) {
           // 把对象相应key对象的值赋值给另外一个对象相应的key对应的值
           res[key] = obj[key];
         }
       }
       return  res
    }
 
 
	
     
     a = {name: 'aaa'};b = copy(a);b.name = 'bbb';  
     console.log(a,b)
     a = [1,2,3];b = copy(a);b[1] = 4;  
     console.log(a,b)
 
   // 浅拷贝只能拷贝一层
   a = {name:'aaa',people:{name: 'abc'}};b = Object.assign({}, a);b.people.name = 'def'; 
     console.log(a,b)                        
   // for in
     var copy = function(a) {
      var res = a.constructor();
      for(var key in a) {
       if(a.hasOwnProperty(key)) {
         res[key] = a[key];
       }
      }
      return res;
     }
   a = {name:'aaa',people:{name: 'abc'}};b = copy(a);b.people.name = 'def'; 
   console.log(a,b)
   a = [1,2, {name: 'aaa'}];b = Object.assign([], a);b[2].name = 'bbb'; 
   console.log(a,b)
   a = [1,2, {name: 'aaa'}];b = copy(a);b[2].name = 'bbb'; 
   console.log(a,b)

深拷贝:

深拷贝的实现   JSON.parse(JSON.stringify(obj))   完美   递归实现

1)深拷贝最简单的实现是: JSON.parse(JSON.stringify(obj))

    function deepCopy(obj){
 
             return JSON.parse(JSON.stringify(obj))
 
           }
 
 
           a = {name:'aaa',people:{name: 'abc'}};b = deepCopy(a);b.people.name = 'def';  
           console.log(a,b)
           a = [1,2, {name: 'aaa'}];b = deepCopy(a);b[2].name = 'bbb';  
           console.log(a,b)
 
           a = {name:'aaa',fun:function(){console.log('fun');},nn: undefined};
           b = deepCopy(a); 
           console.log(a,b)  
 
          // 1.上述的方法会忽略值为function以及undefined的字段,而且对date类型的支持也不太友好
          // 2.上述方法只能克隆原始对象自身的值,不能克隆它继承的值 
           function Person (name) {
             this.name = name
           }
           var a = new Person('王二');
           var b = deepCopy(a);
           console.log(a.constructor == Person); // true
           console.log(b.constructor == Object); // true

注意:

  JSON.parse(JSON.stringify(obj)) 是最简单的实现方式,但是有一些缺陷:

  • 对象的属性值是函数时,无法拷贝。

  • 原型链上的属性无法拷贝

  • 不能正确的处理 Date 类型的数据

  • 不能处理 RegExp

  • 会忽略 symbol

  • 会忽略 undefined

 2)实现一个 deepClone 函数  (深拷贝,完美)

 // 如果是基本数据类型,直接返回
      // 如果是 RegExp 或者 Date 类型,返回对应类型
      // 如果是复杂数据类型,递归。
      // 考虑循环引用的问题
    var show={
        btn:"btn",
        init:function(){
            var that=this;
            alert(this);
            this.btn.click(function(){
                    that.change();
                    alert(this);
            })            
        },
        change:function(){
            this.btn.css({'background':'green'});
            person={
              name:"king",
              show:function(){
                console.log(this.name)
              }
            }
        }
    }

3)递归拷贝

function deepClone(obj, hash = new WeakMap()) { //递归拷贝
        if (obj instanceof RegExp) return new RegExp(obj);
        if (obj instanceof Date) return new Date(obj);
        if (obj === null || typeof obj !== 'object') {
            //如果不是复杂数据类型,直接返回
            return obj;
        }
        if (hash.has(obj)) {
            return hash.get(obj);
        }
        // *
        //  * 如果obj是数组,那么 obj.constructor 是 [Function: Array]
        //  * 如果obj是对象,那么 obj.constructor 是 [Function: Object]
         
        let t = new obj.constructor();
        hash.set(obj, t);
        for (let key in obj) {
            //递归
            if (obj.hasOwnProperty(key)) {//是否是自身的属性
                t[key] = deepClone(obj[key], hash);
            }
        }
        return t;
    }
    
    // var show2 = deepClone(show)
    // var show2 =  cloneObject(show)
    console.log(show,show2)
	
	// 	//递归函数   笔试或面试最后一道题   使用递归函数实现对象的深拷贝
		function cloneObject(obj){
			// 临时对象  暂时存放属性 及属性值
			var newObj = {}
			// obj如果是基本数据类型  直接返回
			if (typeof (obj) !== 'object') {
        return obj;
      }
			//如果是引用类型,遍历属性
			else{
        for(var attr in obj){
          // 如果某个属性还是对象 递归调用
          newObj[attr] = cloneObject(obj[attr])
        }
      }
      return newObj;				
}

您的支持是我们创作的动力!
温馨提示:本文作者系Terry ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://jiangweishan.com/article/shenqiancopy.html

网友评论文明上网理性发言 已有0人参与

发表评论: