前端面试题——对象的深浅拷贝

在实际项目中,我们往往需要对一个对象进行拷贝,其目的可能是为了拷贝一个对象做其他处理,可能是为了扩充一个对象使其拥有另一个对象的属性,也可能是为了其他的目的

但是在 js 中,对象的拷贝分为深浅两种,而不仅仅是将这个对象的属性赋给另一个对象那么简单。同时对象的深浅拷贝也是很对前端面试中常见的一题,所以下面就来详细说说 js 中的 对象深浅拷贝,如果有误,还请多多指正,共同进步

值类型与引用类型

首先理解一下两个概念:值类型引用类型

  • 值类型

简单来说,值类型就是将一个变量赋值给另一个变量后,两个变量完全独立,改变其中的一个并不会影响另一个

var a = 1;
var b = a; // b = 1
a = 2; // a = 2   b = 1

像上面的例子中,虽然后声明的变量b赋予了a的值,但是改变a的值,b却没有改变

除了数值类型,与此类似的 js 中的值类型还有布尔值字符串nullundefined

  • 引用类型

引用类型刚好与值类型相反,原始的变量被改变后,被赋值的变量也会被改变

引用类型会在内存中开辟一块区域保存它的值,而被赋予了这个值的原始变量本质上是将指向了这块内存,而被赋值的另一个变量获得的也只是这个指向而已

所以一旦内存上的值改变,所有只想这块内存的变量的值都会被改变

var c = [1,2,3];
var d = c; // d = [1,2,3]
c[0] = 0; // c = [0,2,3]   d = [0,2,3]

可能有的小伙伴会说,不对呀,下面这种情况 d 并没有被改变

var c = [1,2,3];
var d = c; // d = [1,2,3]
c = [4,5,6]; // c = [4,5,6]   d = [1,2,3]

其实并没有不对,上面例子中的 c[0] 改变的是原内存地址中存储的值,因为c、d指向相同,所有都被改变;而后一个例子中,为 c 重新赋值,相当于是在内存中重新开辟了一块区域存储新值,改变了 c 原来的指向,但 d 的指向却没有改变,所以我们看到的值也就没变

js 中常见引用类型有 数组对象函数

了解了值类型和引用类型,下面我们开始进入正题

对象的浅拷贝

对象的浅拷贝简单,就是将一个变量赋给另一个变量

var obj1 = {
    name: 'test name',
    age: 18
}

var obj2 = obj1;

上面的例子中 obj2 经过浅拷贝拥有了 obj1 的属性

封装浅拷贝方法

    var easyCopy = function ( extendObj ) {
        var newObj = extendObj.constructor === Array ? [] : {};
        if (typeof extendObj != 'object') return;
        for (var key in extendObj) {
            if (extendObj.hasOwnProperty(key)) {
                newObj[key] = extendObj[key];
            }
        }
        return newObj
    };

    var obj2 = {
        tall: 1.8,
        weight: 75
    }

    var obj1 = easyCopy( obj2 );

    console.log( obj1 );

浅拷贝存在的问题

我们知道引用类型的赋值其实是改变了变量的指向,那么如果在需要拷贝的对象中存在某个属性的值是引用类型,如数组或子对象,那么浅拷贝后的原对象的属性获得的也只是这个指向

所以如果改变被拷贝对象的属性值,那么原对象的相应属性也会跟着改变

    var obj2 = {
        names: ['test0', 'test1', 'test3']
    }

    obj1 = easyCopy( obj2 );

    console.log( obj1, obj2 );

    obj2.names[1] = 'test0';

    console.log( obj1, obj2 );

    // 打印结果为:obj1.name[1] 的值从原来的 'test1' 变成了 'test0'

日常项目中使用比较多的是浅拷贝,但是如果某些情况下使用了浅拷贝,可能会产生一些极不容易发现的bug,所以这时候就需要用到深拷贝了

对象的深拷贝

深拷贝其实就是将对象中的数组、子对象进行深度递归遍历,直到其不是引用类型位置再进行复制,这样即使改变了其中一个的值,也不会影响到另一个

深拷贝的封装

    var deepCopy = function( extendObj ){
        var str, newObj = extendObj.constructor === Array ? [] : {};
        if(typeof extendObj !== 'object'){
            return;
        } else if(window.JSON){
            str = JSON.stringify(extendObj);
            newObj = JSON.parse(str);
        } else {
            for(var key in extendObj){
              if (!extendObj.hasOwnProperty(key)) return;
                newObj[key] = typeof extendObj[key] === 'object' ?
                        cloneObj(extendObj[key]) : extendObj[key];
            }
        }
        return newObj;
    };

    var obj2 = {
        names: ['test0', 'test1', 'test3']
    }

    var obj1 = deepCopy( obj2 );

    console.log( obj1, obj2 );

    obj2.names[1] = 'test0';

    console.log( obj1, obj2 );

深拷贝的缺点

虽然深拷贝能够避免浅拷贝出现的问题,但是却会带来性能上的问题,如果一个对象非常复杂或数据庞大,所消耗的性能将会是很可观的

补充

** for … in**

for … in 可以用来遍历任何一个对象,它会将该对象上的所有属性全部遍历出来,包括原型链上的属性

由于可以遍历出原型链上的属性,所以需要使用 hasOwnProperty 这个方法来判断到底是不是这个对象自身的属性

由于数组也是对象,for … in 也可以用来遍历数组,但是 for … in 损耗性能较多,所以如果是遍历数组的话最好使用 for 语句

递归调用

一个方法重复调用自身的情况叫做递归,但是需要注意的是,一定要有一个条件来结束递归,否则将会陷入无限的循环

var index = 1;
function fuckSelf() {
    if (index < 100) {
        index++;
        fuckSelf();
    }
} 
fuckSelf();

你可能感兴趣的:(前端面试题——对象的深浅拷贝)