前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >javascript经典面试题之拷贝

javascript经典面试题之拷贝

作者头像
挥刀北上
发布2019-07-19 15:38:50
2980
发布2019-07-19 15:38:50
举报
文章被收录于专栏:Node.js开发

今天和大家一起来探讨一下javascript中的拷贝,使用拷贝的情况,要根据javascript的数据类型来定,javascript的数据类型分为基础类型和引用类型,只有引用类型才会用到拷贝。

而引用类型中拷贝用的最多是对象类型。

而拷贝的层次又分为两种,浅拷贝与深拷贝:

首先是浅拷贝只会将对象的各个属性进行依次复制,并不会进行递归复制,也就是说只会复制目标对象的第一层属性。

深拷贝不同于浅拷贝,它不只拷贝目标对象的第一层属性,而是递归拷贝目标对象的所有属性。

浅拷贝对于目标对象第一层为基本数据类型的数据,就是直接赋值,即「传值」;而对于目标对象第一层为引用数据类型的数据,就是直接赋存于栈内存中的堆内存地址,即「传址」,并没有开辟新的栈,也就是复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变。

深拷贝的复制则是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

一句话总结,浅拷贝对于第一层属性值为引用类型数据的时候,不做处理,直接复制引用地址,深拷贝会开辟新的栈,生成新的数据。

先来看一下浅拷贝的几种实现方式:

第一种 for in循环,代码如下:

代码语言:javascript
复制
var obj  = {
    name:"zs",
    age:"18",
    gender:"男",
    hobby:["吃饭","唱歌","爬山"]
}

var newobj = {};
for(var key in obj){
    newobj[key] = obj[key];
}
console.log(newobj);
newobj.hobby.push("旅游");
console.log(obj)

打印结果:

可以看到实现了浅层的拷贝,当第一层的属性值为引用类型是,在拷贝时只是赋值了一个引用。修改newobj的hobby属性,obj的hobby属相也会发生变化。

接着看第二种实现方式,这次使用object.assign,代码如下:

代码语言:javascript
复制
var obj  = {
    name:"zs",
    age:"18",
    gender:"男",
    hobby:["吃饭","唱歌","爬山"]
}

var newobj = Object.assign({},obj)
console.log(newobj);
newobj.hobby.push("旅游")
console.log(obj)

打印结果:

查看输出结果发现同样实现了浅拷贝。

接着看第三种浅拷贝的实现方式,扩展运算符:

代码语言:javascript
复制
var obj  = {
    name:"zs",
    age:"18",
    gender:"男",
    hobby:["吃饭","唱歌","爬山"]
}

var newobj = {...obj}
console.log(newobj);
newobj.hobby.push("旅游")
console.log(obj)

打印结果:

以上的案例都是对象的浅拷贝的实现,除了对象还有数组,数组的浅拷贝有如下几种方式,for循环、concant、slice这里就不一一举例了。

说完浅拷贝来看一下深拷贝的实现。

先看第一种实现方式,利用JSON.parse和JSON.stringify,代码如下:

代码语言:javascript
复制
var obj  = {
    name:"zs",
    age:"18",
    gender:"男",
    hobby:["吃饭","唱歌","爬山"]
}

var newobj = JSON.parse(JSON.stringify(obj));
// 修改newobj中的hobby
newobj.hobby.push("旅游")
// 观察obj中的hobby的变化。
console.log(obj)

打印结果如下:

从结果我们可以看出,这次实现了深度拷贝,newobj上的hobby新开辟了一个空间,修改后不会影响obj的hobby属性了。

但是这种拷贝方式有几点问题需要注意:

  1. 会忽略 undefined
  2. 会忽略 symbol
  3. 不能序列化函数
  4. 不能解决循环引用的对象

只要碰到上面四种情况便不能使用JSON来实现深拷贝了。

第二种深拷贝的实现方式我们可以使用MessageChannel,代码如下:

代码语言:javascript
复制
function deepClone(obj) {
    return new Promise(resolve => {
      var channel = new MessageChannel()
      channel.port2.onmessage = ev => resolve(ev.data)
      channel.port1.postMessage(obj)
    })
  }
  
  var obj = {
    a: 1,
    b: {
      c: 2
    }
  }
// 添加一个循环引用;
  obj.z = obj;
//再添加一个循环引用;
  obj.b.d = obj.b
  
  // 注意该方法是异步的
  // 可以处理 undefined 和循环引用对象
  const test = async () => {
    const clone = await deepClone(obj)
    console.log(clone)
  }
  test()

使用MessageChannel需要注意的是这种处理方式是异步的,它可以处理内置类型,处理内部循环引用,比第一种更加强大,基本所有的深拷贝都能完成,不过可惜只能在浏览器端,只是不能处理函数。

最后一种就是用递归的方式实现深拷贝,但是自己实现的话需要考虑好多边界情况,例如碰见javascript内置对象(Date,正则)如何处理,碰见数组如何处理,碰见循环引用如何处理,等等。

我们先简单的实现一版,在进一步迭代,代码如下:

代码语言:javascript
复制
function copy(obj) {
    let res;
    if (typeof obj === "object") {
        let newobj = {};
        for (const key in obj) {
            newobj[key] = copy(obj[key])
        }
        res = newobj
    } else {
        res = obj;
    }
    return res
}

仔细观察上面代码,实现简单对象的克隆不成问题,但是并没有对数组、时间对象、正则对象进行处理,如果克隆的数据包含内置对象就不能满足需求了,所以我们需要处理这些边界情况,代码更改如下:

代码语言:javascript
复制
function copy(obj) {
    let res;
    if (typeof obj === "object") {
        if (Object.prototype.toString.call(obj) == "[object Date]") {
            res = obj;
            console.log("obj")
        }
        else if (Object.prototype.toString.call(obj) == "[object RegExp]") {
            res = obj;
        }
        else if (Object.prototype.toString.call(obj) == "[object Array]") {
            let arr = [];
            for (let i = 0; i < obj.length; i++) {
                arr[i] = copy(obj[i])
            }
            res = arr;
        } 
        else if (Object.prototype.toString.call(obj) == "[object Object]") {
            let newobj = {};
            for (const key in obj) {
                newobj[key] = copy(obj[key])
            }
            res = newobj
        }
    } else {
        res = obj;
    }
    return res
}

// 测试代码
var obj = {
    a:1,
    b:{name:2,hobby:[1,2,3,4]},
    c:"333",
    t:new Date()
}
let newobj = copy(obj);
obj.b.hobby.push(22222);
console.log(newobj);

上面的代码我们使用了Object.prototype.toString.call(obj)来判断数据的类型,从而对边界进行了处理。面试的时候也是主要考察参见面试人员如何对边界情况进行处理。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-03-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 nodejs全栈开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档