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章 - 变量、作用域和内存问题(第三版)

最新文章

  1. 基于ASP.NET MVC(C#)和Quartz.Net组件实现的定时执行任务调度
  2. nginx负载均衡基于ip_hash的session粘帖
  3. CUtilityCode
  4. Java工程师三大框架面试题汇总
  5. hdu 1874(Dijkstra + Floyd)
  6. MYSQL中delete删除多表数据
  7. php更新修改excel中的内容例子
  8. hihocoder 1043 完全背包
  9. gdb显示内存命令用法简介
  10. 省市联级菜单--js+html
  11. 学习笔记TF018:词向量、维基百科语料库训练词向量模型
  12. java学习笔记10-方法
  13. PowerShell执行脚本时“系统上禁止运行脚本”问题解决
  14. Redis常见面试题
  15. angularjs的一些问题
  16. 50个常用的Linux命令(二)sed
  17. Oracle下载汇聚
  18. Leetcode 1023. Camelcase Matching
  19. linking against a dylib which is not safe for use in application extensions
  20. jmeter的Include Controller控件和Test Fragment控件和Module Controller控件

热门文章

  1. Bootstrap初识
  2. Excel 函数 常见错误
  3. MATLAB矩阵处理—特殊矩阵
  4. VA01信贷使用
  5. STM32 标准库3.5修改默认外部8M晶振为16M晶振
  6. flush方法和close方法的区别
  7. [hdu5200]离线+标记
  8. 小心了!Kubernetes自动化操作工具将让你失去工作
  9. mysql5.7 derived_merge=on 影响你的查询了吗?
  10. 一分钟掌握MySQL的InnoDB引擎B+树索引