关于js中的对象的深拷贝常用的方式

401 阅读4分钟

对于js中的对象的深拷贝在项目的开发中比较常用到,本篇文章举例说明常用的js对象的深拷贝方式。以供开发中使用。废话不多说,先上常用深拷贝方式结论,结论后面的是对应分析

常用深拷贝方式

  • JSON.parse(JSON.stringify())
  • Object.assign
  • ...拓展运算符
  • lodash函数库

关于js中变量(数据类型)的复制

我们知道,js语言中常用的有基本数据类型和引用数据类型

基本数据类型

基本数据类型分类

  • string
  • number
  • boolean
  • null
  • undefined

基本数据类型复制

由于基本数据类型的值存在栈里面,所以直接使用一个等于号=就可以复制,但是引用数据类型栈里面保存的只是一个指针地址,指针指向的是堆里面的数据。所以关于引用数据类型的复制,会稍微麻烦一点。

引用数据类型

引用数据类型分类

  • 对象
  • 数组
  • 函数

引用数据类型复制

我们知道,数据的复制(拷贝)其实本质上就是单独找一个内存块来存放对应的特定信息,而且这部分的信息是与原来的独立分开的。 对于引用数据类型的复制,如果直接使用等于号来赋值拷贝的话,只是把引用数据类型的指针给了该变量,所以就要说说引用数据类型的拷贝。函数一般不需要做拷贝,因为函数在js中是一等公民,哪里需要使用直接调用一下函数即可,所以谈到深拷贝或浅拷贝这个词的时候,一般指的是对象或者数据的拷贝,对象的复制略微多用一点

对象的深拷贝

浅拷贝,直接等于号=赋值,这里就不赘述了

场景假设

假设我们的项目页面上有一个表格,表格中每一行都是对应的数据,在表格的最右侧有一个编辑按钮,点击编辑按钮,出现一个弹出框,弹出框中有表单,表单中出现表格中对应行的数据,以供我们编辑修改保存。具体逻辑也很简单,即点击某一行编辑按钮,拿到对应行的数据,将对应行的数据赋到表单中。下面两张图分别呈现对应不使用深拷贝和使用深拷贝的效果区别

不使用深拷贝效果图

no.gif 不使用深拷贝,直接将rowData赋值到表单中去,我们会发现,当我们修改表单中的数据的时候,表格中对应行的数据居然也会被修改,因为我们赋值过去的只是对象的指针引用地址,所以出现这样的效果。这显然不是我们想要的,所以这种方式,一般不可行。

使用深拷贝效果图

123654.gif 我们发现使用深拷贝,修改表单中的数据,倒是没有改变原表格中对应行的数据,这才是我们想要的。

对应代码

<template>
  <div id="app">

    <!-- 表格部分 -->
    <el-table :data="tableData" border style="width: 100%">
      <el-table-column prop="name" label="姓名" width="180"> </el-table-column>
      <el-table-column prop="age" label="年龄" width="180"> </el-table-column>
      <el-table-column prop="home" label="家乡"> </el-table-column>
      <el-table-column fixed="right" label="操作" width="100">
        <template slot-scope="scope">
          <el-button type="primary" plain size="small" @click="editRow(scope.row)"
            >编辑</el-button
          >
        </template>
      </el-table-column>
    </el-table>

    <!-- 弹框中 表单部分 -->
    <el-dialog
      title="编辑表格"
      append-to-body
      :close-on-click-modal="false"
      :visible.sync="dialogVisible"
      width="30%"
    >
      <el-form ref="form" :model="form" label-width="80px">
        <el-form-item label="姓名">
          <el-input v-model.trim="form.name"></el-input>
        </el-form-item>
        <el-form-item label="年龄">
          <el-input v-model.trim="form.age"></el-input>
        </el-form-item>
        <el-form-item label="家乡">
          <el-input v-model.trim="form.home"></el-input>
        </el-form-item>
      </el-form>
    </el-dialog>

  </div>
</template>

<script>
// 引入lodash函数工具库
import _ from 'lodash'
export default {
  components: {},
  data() {
    return {
      // 表格的数据
      tableData: [
        {
          name: "孙悟空",
          age: "500",
          home: "花果山水帘洞",
        },
        {
          name: "猪八戒",
          age: "88",
          home: "高老庄",
        },
      ],
      dialogVisible: false,
      // 表单的数据
      form: {
        name: "",
        age: "",
        home: "",
      },
    };
  },
  methods: {
    editRow(rowData) {
      console.log("rowData", this.form);
      this.dialogVisible = true;

      // // 不用深拷贝,直接赋值
      // this.form = rowData

      // // 深拷贝方式一 之 JSON方法
      // this.form = JSON.parse(JSON.stringify(rowData));

      // // 深拷贝方式二 之 Object.assign方法
      // this.form = Object.assign({},rowData)

      // // 深拷贝方式三 之 拓展运算符
      // let { ...newObj } = rowData
      // this.form = newObj

      // // 深拷贝方式四 之 lodash函数库
      this.form = _.cloneDeep(rowData) // 调用lodash的cloneDeep方法也可以做深拷贝

    },
  },
};
</script>

<style lang="less" scoped>
#app {
  width: 100%;
  height: 100vh;
  box-sizing: border-box;
  padding: 50px;
}
/deep/ .el-dialog {
  margin-top: 30vh !important;
}
</style>

补充~数组的深拷贝

对于数组的拷贝复制而言,如果直接使用等于号=来进行拷贝复制,那只是浅拷贝,浅拷贝拷贝的是地址,所以源数组改变,拷贝的数组也会跟着改变

常见方式

  • slice
  • concat
  • ...拓展
  • JSON.parse(JSON.stringify())
let arr = ['孙悟空','猪八戒','沙和尚','唐僧']

// 方式一slice
let newArr = arr.slice(0)

// 方式二concat
let newArr = arr.concat()

// 方式三...拓展运算符
let [ ...newArr ] = arr

// 方式四
let newArr = JSON.parse(JSON.stringify(arr))

当然也可以使用lodash的函数工具库

手写一个深拷贝

 let arr = [
    {
        name: '孙悟空',
        age: 500,
        skill: ['筋斗云', '火眼金睛']
    },
    {
        name: '猪八戒',
        age: 88,
        skill: ['九齿钉耙', '36变']
    },
]

function deepClone(params) {
    // 如果数组类型数据
    if (Array.isArray(params)) {
        let newnew = []
        for (let i = 0; i < params.length; i++) {
            newnew[i] = deepClone(params[i]) // 递归调用克隆
        }
        return newnew // 克隆完以后,再返回出结果
    }
    // 如果是对象类型数据
    if (Object.prototype.toString.call(params) === '[object Object]') {
        let newnew = {}
        for (const key in params) {
            newnew[key] = deepClone(params[key]) // 递归调用克隆
        }
        return newnew // 克隆完以后,再返回出结果
    }
    // 如果是普通数据类型
    return params 
}
console.log( deepClone(arr) );