1. 问题描述

今天在写一个代码题时候, 有一个BUG 导致自己停滞好久, 该BUG 可以描述为如下代码:

PS: 由于原题是算法题, 为了叙述方便以及展示重点考虑, 这里只复现BUG, 不提供原场景.

const log = console.log.bind(console)

let obj = {}
let list = [1, 2, 3] obj.array_1 = list
obj.array_2 = list
obj.array_1.push(4) log(obj)
// {
// array_1: [ 1, 2, 3, 4 ],
// array_2: [ 1, 2, 3, 4 ]
// }

场景说明:

在第8行代码: obj.array_1.push(4) 中, 目的是将obj.array_1 值改变, 而obj.array_2 值期望是不要改变, 但是结果事与愿违, obj.array_1, obj.array_2 的值均发生了改变.

2. 原因分析

要理解上面代码, 我们先来了解一下JavaScript 的数据类型, JavaScript 的数据类型可分为:

  1. 基本数据类型: Undefined, Null, Number, String, Boolean
  2. 引用数据类型: Object(array, object, set 等数据类型)

JavaScript 对于数据类型值操作的方式如下:

  1. 基本数据类型: 按值访问, 因此可以操作保存在变量中的实际值;
  2. 引用数据类型: 引用类型的值是保存在内存中的对象, 和其他语言不通, JavaScript 不允许直接访问内存中的位置, 也就是说不能直接操作对象的内存空间, 我们在操作对象时实际上是操作对象的引用而不是实际的对象, 为此 引用类型的值是按引用访问的.

简而言之, 当一个变量 a 是引用数据类型时, a 保存的是一个地址, 我们操作 a 来达到改变地址值的目的; 当变量 b 和 a 指向同一个地址时, 他们的操作会互相影响;

在上面的例子中, obj.array_1 和 obj.array_2 指向的是同一个引用数据类型 list, 因此它们之间的操作会互相影响, 如果要消除这种影响, 我们将obj.array_1 和 obj.array_2 指向不同的引用数据类型即可

const log = console.log.bind(console)

let obj = {}
let list = [1, 2, 3] obj.array_1 = list
// 通过Array.prototype.slice() 方法完成数组的拷贝
obj.array_2 = list.slice()
obj.array_1.push(4) log(obj)
// {
// array_1: [ 1, 2, 3, 4 ],
// array_2: [ 1, 2, 3 ]
// } log(typeof 'here')

3. React 中的引用数据类型

在React 中, 引用数据也具有这种性质(因为React 本身支持JavaScript, JSX 也是JavaScript 语法的升级), 如下面例子:

import React from "react";

const log = console.log.bind(console)

class ReactObjectAdress extends React.Component{
constructor(props) {
super(props)
this.state = {
arrayOne: [],
arrayTwo: []
}
} componentDidMount() {
let array = [1, 2, 3, 4]
this.setState({
arrayOne: array,
arrayTwo: array
})
} onClickEvent = () => {
let array = this.state.arrayOne
array.push(5)
this.setState({
arrayOne: array
})
} render() {
let { arrayOne, arrayTwo } = this.state
log('arrayOne: ', arrayOne)
log('arrayTwo: ', arrayTwo)
return (
<>
<button onClick={this.onClickEvent}>click Me!</button>
</>
)
}
} export default ReactObjectAdress

描述:

当我们点击 'click Me!' 按钮触发 onClickEvent 实践时, 可以在控制台看到 this.state.arrayOne 和 this.state.arrayTwo 这两个数组发生了改变: 数组被添加了一个新数据 5

4. 业务场景

根据上面React 框架的例子, 我们来思考这样一个业务场景:

现在需要在ReactObjectAdress 组件中添加两个组件: (array 表示在componentDidMount 中的值, 可以理解出初始数据)

  1. 组件ShowArray, 用于展示数组 array
  2. 组件UpdateArray, 用于更新数组 array, 且更新需要向后端确认

根据上面的场景, 我们可能会写如下的代码:

import React from "react";

const log = console.log.bind(console)

class ReactObjectAdress extends React.Component{
constructor(props) {
super(props)
this.state = {
arrayOne: [],
arrayTwo: []
}
} componentDidMount() {
let array = [1, 2, 3, 4]
this.setState({
arrayOne: array,
arrayTwo: array
})
} onClickEvent = () => {
let array = this.state.arrayOne
array.push(5)
this.setState({
arrayOne: array
})
} render() {
let { arrayOne, arrayTwo } = this.state
// log('arrayOne: ', arrayOne)
// log('arrayTwo: ', arrayTwo)
return (
<>
<button onClick={this.onClickEvent}>click Me!</button>
<ShowArray array={arrayOne} />
<UpdateArray array={arrayTwo} />
</>
)
}
} export default ReactObjectAdress

描述:

上面代码中, ShowArray UpdateArray 的参数分别为arrayOne, arrayTwo, 这样会存在一个问题:

arrayOne, arrayTwo 指向的是同一个地址, 如果我们在UpdateArray 中操作this.props.array 参数, 导致的结果就是arrayOne, arrayTwo 这两个数会马上改变; ShowArray, UpdateArray这两个组件会立即更新显示!

但正确的逻辑应该是先等到UpdateArray 和后端确认之后, ShowArray 组件才完成更新, 因此这种场景下: 显示的数据和更改的数据不应该使用同一个地址存储, 上面的代码需要进行如下的变更:

componentDidMount() {
let array = [1, 2, 3, 4]
this.setState({
arrayOne: array,
arrayTwo: array.slice()
})
}

5. 参考资料

  1. JavaScript 高级程序设计第4章 - 变量、作用域和内存问题(第三版)

JavaScript 引用数据类型的更多相关文章

  1. JS 基本数据类型和引用数据类型

    本文章已收录于:   .embody { padding: 10px 10px 10px; margin: 0 -20px; border-bottom: solid 1px #ededed } .e ...

  2. JavaScript系列----数据类型以及传值和传引用

    1.简单数据类型 在JavaScript中简单数据类型分为5种.分别为 Undefined, Null,Boolean,Number,String. Undefined类型Undefined类型只有一 ...

  3. JavaScript中基本数据类型和引用数据类型的区别

    1.基本数据类型和引用数据类型 ECMAScript包括两个不同类型的值:基本数据类型和引用数据类型. 基本数据类型指的是简单的数据段,引用数据类型指的是有多个值构成的对象. 当我们把变量赋值给一个变 ...

  4. javascript数据基本类型和引用数据类型区别

    基本类型和引用数据类型区别 1.基本数据类型和引用数据类型 javascript中有两种数据类型,分别是基本数据类型和引用数据类型: 基本数据(原始数据)类型指的是简单的数据段,而引用数据类型则指的是 ...

  5. Web | JavaScript的引用数据类型强制转换类型

    我在这里主要的想提下的是JavaScript中的引用类型进行强制转换类型.因为对于基本数据类型的变换大多都是雷同的,很容易熟知,但是引用数据类型有一点小插曲. JavaScript的引用类型主要为对象 ...

  6. javascript 基本数据类型、引用数据类型

    阅读目录 数据类型 两种访问方式 两种类型复制 函数参数的传递 两种变量类型检测 回到目录   数据类型 1.   ECMAScript变量包含两种不同类型的值:基本类型值.引用类型值: 2.   基 ...

  7. JavaScript中的基本数据类型和引用数据类型

    ECMAScript变量包括了两种不同的数据类型 在学习JavaScript的数据类型时,我们通常会把数据类型分成六中(官方认为)Object.String.Boolean.Number.Undefi ...

  8. JavaScript中基本数据类型和引用数据类型的区别(栈——堆)

    JavaScript中基本数据类型和引用数据类型的区别 1.基本数据类型和引用数据类型 ECMAScript包括两个不同类型的值:基本数据类型和引用数据类型. 基本数据类型指的是简单的数据段,引用数据 ...

  9. JavaScript基本数据类型和引用数据类型详解

    数据类型小知识 JavaScript主要数据类型共有7种,有string.number.boolean.undefined.null.symbol.object.其余7种可以笼统的分为两大类:基本数据 ...

随机推荐

  1. Pycharm修改HTML模板

  2. JavaWebCase

    目录 案例:用户登录 用户登录案例需求 分析 开发步骤 创建项目 创建数据库环境 创建包 com.my.domain,创建类User 创建包 com.my.dao,创建类UsesrDao,提供logi ...

  3. (三)Redis &分布式锁

    1 Redis使用中的常见问题和解决办法 1.1 缓存穿透 定义:缓存系统都是按照key去缓存查询,如果不存在对应的value,就应该去DB查找.一些恶意的请求会故意查询不存在的key,请求量很大,就 ...

  4. 【Hadoop离线基础总结】MapReduce案例之自定义groupingComparator

    MapReduce案例之自定义groupingComparator 求取Top 1的数据 需求 求出每一个订单中成交金额最大的一笔交易 订单id 商品id 成交金额 Order_0000005 Pdt ...

  5. 【Poj-3693】Maximum repetition substring 后缀数组 连续重复子串

    POJ - 3693 题意 SPOJ - REPEATS的进阶版,在这题的基础上输出字典序最小的重复字串. 思路 跟上题一样,先求出最长的重复次数,在求的过程中顺便纪录最多次数可能的长度. 因为sa数 ...

  6. python的进栈出栈遍历

    python实现出栈进栈 要求: 进栈 出栈 遍历所有 退出 stack = [] #创建列表 #进栈 def pushstack(): stack.append(input('Enter a nub ...

  7. 搭建私有镜像仓库registry 2.0

    搭建 docker run -d -p 5000:5000 --restart=always --name registry2 registry:2 就可以将自己的镜像 push到这个私有的镜像仓库 ...

  8. java ->IO流_File类

    IO概述 回想之前写过的程序,数据都是在内存中,一旦程序运行结束,这些数据都没有了,等下次再想使用这些数据,可是已经没有了.那怎么办呢?能不能把运算完的数据都保存下来,下次程序启动的时候,再把这些数据 ...

  9. Postman学习之Postman简介

    前言:对于测试人员来说,接口测试是必须掌握的一个技能:在工作中掌握了接口自动化测试无疑是如虎添翼,那么怎么开展接口测试呢?下面将介绍一款接口测试的神器——postman 1.postman背景介绍 p ...

  10. Java openrasp学习记录(一)

    前言: 最近一直在做学校实验室安排的项目,太惨了,没多少时间学习新知识,不过rasp还是要挤挤时间学的,先从小例子的分析开始,了解rasp的基本设计思路,后面详细阅读openrasp的源码进行学习!欢 ...