对象和引用类型的赋值都是通过引用传递的方式进行的,这意味着变量实际上存储的是对象的引用,而不是对象本身的副本

这篇博客我主要想解释一下这句话:对象和引用类型的赋值都是通过引用传递的方式进行的,这意味着变量实际上存储的是对象的引用,而不是对象本身的副本。

其实这段话早在学习JS的时候就接触过,只是被我丢进了“记忆垃圾桶”,但是昨天遇到的一个bug,让死去的回忆突然攻击我

问题引出

起因是我在编写一个酒店住客管理的代码(纯前端项目,Pinia持久化存储)

首先我将Pinia仓库中的住客信息渲染在表格中

 

然后,利用element-ui组件库的表格组件渲染数据,并利用该组件的行数据对象scope.row获取一行的数据赋值给drawerMessage.value

目的是通过表格的“查看/编辑”按钮去打开一个右侧弹出框,并渲染对应的行数据

然而神奇的事情发生了,我将檀健次改成多多,对应的表格数据竟然也同步变化了,更神奇的是,Pinia仓库中的数据也改变了

 

为了便于大家理解,我把相关代码放在这

<script setup>
import { ref } from 'vue'

import { useHotelStore } from '@/stores'
const hotelStore = useHotelStore()

// 右侧弹出框
const drawer = ref(false)
const drawerMessage = ref()

const openDraw = scope => {
  drawer.value = true
  drawerMessage.value = scope
}

</script>

<template>
  <!-- 住客列表 -->
  <div class="bigContainer">

      <el-table :data="hotelStore.guestsList" height="100%" stripe style="width: 100%">
        <el-table-column label="编号" width="100">
          <template #default="scope">
            <span>{{ scope.$index + 1 }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="guestsName" label="姓名" width="180" />
        <el-table-column prop="guestsGender" label="性别" />
        <el-table-column prop="guestsId" label="身份证号" />
        <el-table-column prop="guestsPhone" label="手机号" />
        <el-table-column prop="guestsCheckIn" label="入住时间" />
        <el-table-column prop="guestsCheckOut" label="退房时间" />
        <el-table-column prop="guestsRoomType" label="房间类型" />
        <el-table-column prop="guestsRoomNumber" label="房间号" />
        <el-table-column label="操作">
          <template #default="scope">
            <el-button @click="openDraw(scope.row)" type="primary" drawer>查看/编辑</el-button>
          </template>
        </el-table-column>
      </el-table>


    <!-- 右侧弹出框 -->
    <el-drawer v-model="drawer" :direction="direction">
      <template #header>
        <h4>住客信息</h4>
      </template>
      <template #default>
        <div class="messageFlex">
          <div>
            <span class="drawerMessage">姓名</span>
            <el-input v-model=drawerMessage.guestsName style="width: 240px" placeholder='姓名' />
          </div>

          <div>
            <span class="drawerMessage">性别</span>
            <el-input v-model=drawerMessage.guestsGender style="width: 240px" placeholder='性别' />
          </div>

          <div>
            <span class="drawerMessage">身份证号</span>
            <el-input v-model=drawerMessage.guestsId style="width: 240px" placeholder='身份证号' />
          </div>

          <div>
            <span class="drawerMessage">手机号</span>
            <el-input v-model=drawerMessage.guestsPhone style="width: 240px" placeholder='手机号' />
          </div>

          <div>
            <span class="drawerMessage">入住时间</span>
            <el-input v-model=drawerMessage.guestsCheckIn style="width: 240px" placeholder='入住时间' />
          </div>

          <div>
            <span class="drawerMessage">退房时间</span>
            <el-input v-model=drawerMessage.guestsCheckOut style="width: 240px" placeholder='退房时间' />
          </div>

          <div>
            <span class="drawerMessage">房间类型</span>
            <el-input v-model=drawerMessage.guestsRoomType style="width: 240px" placeholder='房间类型' />
          </div>

          <div>
            <span class="drawerMessage">房间号</span>
            <el-input v-model=drawerMessage.guestsRoomNumber style="width: 240px" placeholder='房间号' />
          </div>
        </div>
      </template>
      <template #footer>
        <div style="flex: auto">
          <el-button @click="handleDelete">删除该住客</el-button>
          <el-button type="primary" @click="handleSave">保存修改</el-button>
        </div>
      </template>
    </el-drawer>

  </div>
</template>

明明是相互赋值的关系,为什么数据的变化却是绑定的呢?

接下来的内容由AI提供

问题解答

在你的代码中,drawerMessage 被设置为表格中某一行的数据引用。因此,当你打开抽屉并编辑输入框中的数据时,实际上是在直接修改该行的数据。由于 drawerMessage 引用了表格数据中的某一行,这些更改会自动反映在表格数据中。

以下是一些关键点,帮助解释为什么会发生这种情况:

数据引用

当你在 openDraw 方法中将 scope.row 赋值给 drawerMessage 时:

const openDraw = scope => {
  drawer.value = true
  drawerMessage.value = scope // 这里将当前行的引用赋值给 drawerMessage
}

drawerMessage 并不是一个新的对象,而是对 scope.row 所指向的对象的引用。因此,当你修改 drawerMessage 中的属性时,实际上是在修改 scope.row 的属性,即修改了 hotelStore.roomList 中对应行的数据

双向绑定

在模板中,你使用了 v-model 指令来绑定输入框与 drawerMessage 的属性:

<el-input v-model="drawerMessage.roomType" style="width: 240px" placeholder="房间类型" />
<el-input v-model="drawerMessage.roomNum" style="width: 240px" placeholder="房间号" />
<el-input v-model="drawerMessage.roomImg" style="width: 240px" placeholder="图片地址" />
<el-input v-model="drawerMessage.roomState" style="width: 240px" placeholder="状态" />

v-model 实现了双向绑定,当输入框的值改变时,相应的 drawerMessage 属性也会随之改变。而因为 drawerMessage 引用的是 scope.row,这意味着 scope.row 的对应属性也会被修改。

Pinia 数据更新

假设 hotelStore 使用 Pinia 来管理状态,当你修改表格数据中的某一行时,Pinia 的状态也会被更新。这是因为 Pinia 存储的数据是响应式的,任何变更都会自动传播到依赖这些数据的地方。

解决方案

如果你不希望修改 drawerMessage 时直接影响到 hotelStore 中的数据,可以克隆一份对象,以避免直接引用。你可以使用 Object.assign 或者 JSON.parse(JSON.stringify()) 来创建一个新对象:

const openDraw = scope => {
  drawer.value = true
  drawerMessage.value = { ...scope.row } // 使用扩展运算符克隆对象
}

这样,你在 drawerMessage 上的更改不会立即反映在 hotelStore 的数据上,直到你显式地保存更改为止。下面是一个简化的示例:

<el-button type="primary" @click="handleSave">保存修改</el-button>

handleSave 方法中,你可以将 drawerMessage 的数据同步回 hotelStore

const handleSave = () => {
  // 查找当前正在编辑的房间在 hotelStore.roomList 中的位置
  const index = hotelStore.roomList.findIndex(room => room.id === drawerMessage.value.id)
  
  // 如果找到了对应的房间(即 index 不为 -1)
  if (index !== -1) {
    // 用抽屉中的数据更新 hotelStore.roomList 中的对应房间的数据
    hotelStore.roomList[index] = { ...drawerMessage.value }
  }
  
  // 关闭抽屉
  drawer.value = false
}

这种方式确保只有在用户点击“保存修改”按钮时,才会将更改应用到 hotelStore 中的数据,从而避免了实时同步带来的问题。

原理解释

所以什么数据类型会出现上述情况?

在 JavaScript 中,主要是对象(包括数组、函数等)以及其他引用类型会出现上述情况。这是因为在 JavaScript 中,对象和引用类型的赋值都是通过引用传递的方式进行的,这意味着变量实际上存储的是对象的引用,而不是对象本身的副本。

除了对象和引用类型之外,基本数据类型(如数字、字符串、布尔值、null 和 undefined)在赋值时会创建副本,并且对副本的更改不会影响原始值,因为它们在内存中是独立存储的。这种赋值方式被称为值传递。

因此,只有对象和引用类型会出现上述描述的情况,而基本数据类型则不会。

代码示例1:对象赋值

const obj1 = { a: 1, b: 2 };
const obj2 = obj1;

obj1.c = 3;

console.log(obj2); // { a: 1, b: 2, c: 3 }

代码示例2:数组赋值

const array1 = [1, 2, 3];
const array2 = array1;

array1.push(4);

console.log(array2); // [1, 2, 3, 4]

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-15 22:14:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-15 22:14:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-06-15 22:14:04       82 阅读
  4. Python语言-面向对象

    2024-06-15 22:14:04       91 阅读

热门阅读

  1. 计算机类期刊含金量横纵向对比(二)

    2024-06-15 22:14:04       35 阅读
  2. ubuntu20.0.4下安装PyTorch

    2024-06-15 22:14:04       30 阅读
  3. 钉钉Stream模式推送程序环境部署

    2024-06-15 22:14:04       26 阅读
  4. 图神经网络工具篇

    2024-06-15 22:14:04       25 阅读
  5. C#多线程与函数对象的实例

    2024-06-15 22:14:04       22 阅读
  6. Elasticsearch与Kafka集成:实现数据流处理

    2024-06-15 22:14:04       30 阅读
  7. 代码随想录Day59

    2024-06-15 22:14:04       28 阅读
  8. lvgl手势事件判断为点击事件问题

    2024-06-15 22:14:04       27 阅读
  9. 解决测试类Class not found报错

    2024-06-15 22:14:04       31 阅读
  10. 区块链之快照

    2024-06-15 22:14:04       28 阅读
  11. web开发的尽头是servlet

    2024-06-15 22:14:04       32 阅读