美文网首页复习基础JsWeb前端之路
前端面试题——对象的深浅拷贝

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

作者: ac68882199a1 | 来源:发表于2017-02-25 23:02 被阅读343次

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

    但是在 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();
    

    相关文章

      网友评论

      本文标题:前端面试题——对象的深浅拷贝

      本文链接:https://www.haomeiwen.com/subject/kynnwttx.html