← 编程学习中心
📖

Preparation

8 个课程
1
如何学习编程
2
计算机基础
3
键盘初识与基准键位
4
字母与数字基础
5
Shift键魔法
6
编程符号专精
7
功能键与修饰键
8
键盘学习总结与进阶
📚

Basics

22 个课程
1
什么是编程?
2
Hello World - 你的第一个程序
3
变量基础概念 - 给信息贴标签
4
变量赋值和操作 - 变量的高级用法
5
数据类型基础 - 数字和字符串
6
基础运算符 - 数字运算王国
7
布尔类型与比较运算符 - 程序的判断基础
8
条件判断基础 - 让程序学会做决定
9
变量作用域 - 变量的生存范围
10
条件语句进阶 - 复杂判断与嵌套结构
11
逻辑运算符 - 简化复杂条件判断
12
循环语句 - 程序的重复机器
13
循环进阶 - break、continue与循环控制
14
嵌套循环与作用域
15
循环调试实战技巧
16
循环不变量 - 理解循环的数学规律
17
数组基础 - 数据的收纳盒
18
数组操作方法 - 添加、删除和管理
19
数组高级 - 探索数组的强大功能
20
引用概念 - 变量的不同行为
21
函数基础 - 解决重复代码的秘密武器
22
函数实践与应用 - 巩固基础,实战应用
🚀

Intermediate

11 个课程
1
从控制台到画布 - p5.js初体验
2
图形绘制工具箱 - 基础图形函数
3
让世界多彩 - 颜色系统详解
4
文字的艺术 - 文本绘制与样式
5
球体动起来 - 动画基础入门
6
球体的交互魔法 - 鼠标与条件判断
7
面向对象编程 - Class类与实例
8
球的家族 - 循环与数组应用
9
球的个性 - 函数封装与参数化
10
球的变身 - 形状模式与高级绘制
11
球的世界 - 综合创作项目
🎯

Advanced

6 个课程
1
对象类型
2
对象类型应用
3
自定义类型
4
函数进阶 - 箭头函数与高级编程技巧
5
内置方法 - 字符串与数组的魔法工具
6
Switch语句与字符串模板 - 更优雅的代码表达
← 编程学习中心
📖

Preparation

8 个课程
1
如何学习编程
2
计算机基础
3
键盘初识与基准键位
4
字母与数字基础
5
Shift键魔法
6
编程符号专精
7
功能键与修饰键
8
键盘学习总结与进阶
📚

Basics

22 个课程
1
什么是编程?
2
Hello World - 你的第一个程序
3
变量基础概念 - 给信息贴标签
4
变量赋值和操作 - 变量的高级用法
5
数据类型基础 - 数字和字符串
6
基础运算符 - 数字运算王国
7
布尔类型与比较运算符 - 程序的判断基础
8
条件判断基础 - 让程序学会做决定
9
变量作用域 - 变量的生存范围
10
条件语句进阶 - 复杂判断与嵌套结构
11
逻辑运算符 - 简化复杂条件判断
12
循环语句 - 程序的重复机器
13
循环进阶 - break、continue与循环控制
14
嵌套循环与作用域
15
循环调试实战技巧
16
循环不变量 - 理解循环的数学规律
17
数组基础 - 数据的收纳盒
18
数组操作方法 - 添加、删除和管理
19
数组高级 - 探索数组的强大功能
20
引用概念 - 变量的不同行为
21
函数基础 - 解决重复代码的秘密武器
22
函数实践与应用 - 巩固基础,实战应用
🚀

Intermediate

11 个课程
1
从控制台到画布 - p5.js初体验
2
图形绘制工具箱 - 基础图形函数
3
让世界多彩 - 颜色系统详解
4
文字的艺术 - 文本绘制与样式
5
球体动起来 - 动画基础入门
6
球体的交互魔法 - 鼠标与条件判断
7
面向对象编程 - Class类与实例
8
球的家族 - 循环与数组应用
9
球的个性 - 函数封装与参数化
10
球的变身 - 形状模式与高级绘制
11
球的世界 - 综合创作项目
🎯

Advanced

6 个课程
1
对象类型
2
对象类型应用
3
自定义类型
4
函数进阶 - 箭头函数与高级编程技巧
5
内置方法 - 字符串与数组的魔法工具
6
Switch语句与字符串模板 - 更优雅的代码表达

引用概念 - 变量的不同行为

重要概念:数组与基础类型的关键区别

在我们深入学习数组的高级功能之前,需要理解一个重要的概念:数组变量和基础类型变量的行为不同。

什么是基础类型变量?

在我们学习编程时,会遇到不同类型的数据。基础类型变量是最简单的数据类型,它们每次只能存储一个单一的数据值。

基础类型(Primitive Types)是编程中最基本的数据类型

常用的3种基础类型:

  • 数字 (number):如 100、3.14、-5
  • 字符串 (string):如 "你好"、"Hello"、'编程'
  • 布尔值 (boolean):只有两个值:true(真)和 false(假)
加载代码编辑器...

回顾:基础类型变量的行为

加载代码编辑器...

重要观察:

  • 赋值就是复制值:执行 score2 = score1 时,系统将 score1 存储的值100完整复制给 score2
  • 赋值后的状态:赋值完成后,两个变量都有相同的值(100
  • 修改的独立性:当我们改变 score2 的值时,score1 保持不变,这证明了两个变量是完全独立的

理解基础类型变量的独立性:

  • 基础类型变量就像是独立的储物盒
  • 赋值是把一个盒子里的东西复制一份放到另一个盒子里
  • 改变一个盒子的内容不会影响另一个盒子
  • 这是基础类型变量(数字、字符串、布尔值等)的核心特征

技术概念:值复制 (Value Copy)

什么是值复制?

值复制 (Value Copy)是编程中的一个重要概念。当我们在基础类型变量之间进行赋值时,发生的是值复制:

  • 复制的是值:将变量的实际数值(如100、"你好")复制给另一个变量,这被称为值复制的过程
  • 创建新副本:接收方变量得到一个完全独立的数据副本,我们称之为数据副本,它与原数据相同但独立
  • 变量独立性:对其中一个变量的修改不会影响另一个变量,这种互不影响的关系就是变量独立性的特征

数组变量的行为

重要提示:值复制是基础类型变量的行为特征。而我们前面刚刚学习的数组类型将展示完全不同的行为——引用复制,这是我们接下来要深入理解的关键概念。

🔍 让我们先观察一个奇怪的现象

加载代码编辑器...

停下来思考一下: 我们明明只给 inventory2 添加了物品,为什么 inventory1 的内容也跟着变化了?这和我们之前学习的基础类型完全不同!

🗝️ 钥匙和储物柜的比喻

让我们回顾一下在数组基础课程中学到的比喻,然后深入理解:

回顾:基础类型的独立储物盒:

  • 📦 单个数据 → 单个储物盒(比如 let score = 100;)
  • 基础类型变量就像是独立的储物盒,每个变量有自己的存储空间
  • 赋值是把一个盒子里的东西复制一份放到另一个盒子里

现在学习数组的储物柜系统:

  • 🗄️ 多个数据 → 多格的储物柜(比如 let scores = [100, 85, 92];)
  • 每个储物柜都有配一把钥匙,打开特定的储物柜(这就是变量,可以用来访问数组)

理解这个比喻:

  • 储物柜(数组本身):实际存储物品的地方
  • 钥匙(变量):只是指向储物柜的工具
  • 多把钥匙:多个变量可以指向同一个储物柜
  • 用钥匙操作:通过任何一把钥匙都能修改储物柜内容

重要对比:

  • 基础类型:每个变量都有自己的储物盒,相互独立
  • 数组类型:多个变量可以共享同一个储物柜,通过不同的钥匙访问

现在让我们回到原始的例子,用储物柜比喻来理解:

加载代码编辑器...

关键理解:

  • inventory1 和 inventory2 都是我们的"钥匙"(变量)
  • ["铁剑", "盾牌", "药水"] 是储物柜本身(数组)
  • 添加面包后变为 ["铁剑", "盾牌", "药水", "面包"] 表示储物柜里面的物品变了
  • 两把钥匙对应同一个储物柜,所以储物柜物品改变后,用任何一把钥匙都能看到变化!

🔧 技术概念:引用 (Reference)

现在我们引入专业的技术术语:

引用 (Reference) 就是我们比喻中的"钥匙",它是:

  • 变量存储的不是数组内容本身,而是存储了指向数组的引用(对应钥匙)
  • 引用不是数组本身,而是用来访问数组的一个方式(对应钥匙不是储物柜,而是访问储物柜的工具)
  • 当我们复制数组变量时,复制的是引用,不是数组内容(对应复制了一份钥匙,指向同一个数组)
加载代码编辑器...

💡 为什么这样设计?

你可能会问:为什么要这样复杂的设计?直接复制内容不是更简单吗?

在解释这个之前,我们需要理解什么是内存。

🧠 什么是内存?

内存(Memory)是计算机的短期记忆系统,就像我们人类的短期记忆能力:

🧠 人类记忆 vs 计算机内存的类比:

  • 短期记忆有限:我们一次只能记住有限的信息,比如记住一个电话号码,但很难同时记住20个号码
  • 内存空间有限:计算机的内存容量也是有限的,不能无限存储数据
  • 信息占用空间:不同的信息占用不同的记忆空间,简单数字容易记住,复杂的长篇文章需要更多记忆空间

📊 计算机内存的特点:

  • 数据需要内存空间:任何数据存储在内存中都会占用空间
  • 内存容量是有限的:就像我们能同时记住的事情数量有限
  • 不同数据占用不同空间:
    • 基础类型数据(数字、字符串、布尔值):占用空间小
    • 数组数据:可以包含多个元素,占用空间更大

💡 为什么这个概念重要? 理解内存有限性有助于我们:

  • 知道为什么大数组需要更多内存
  • 理解为什么引用复制更节省内存
  • 学会编写更高效的程序

🎯 引用设计的优势

现在我们理解了内存的概念,就能明白引用设计的优势了:

1. 内存效率:大数组只需要在内存中存储一份

想象一下:

加载代码编辑器...

内存使用情况:

  • 使用引用:1个大储物柜 + 3把小钥匙(占用空间很少)
  • 如果复制内容:3个相同的大储物柜(占用3倍空间!)

2. 同步更新:多个"观察者"看到的都是最新的数据

因为所有变量都指向同一个数组:

  • 任何一个变量修改数组,其他变量都能立即看到变化
  • 就像多把钥匙开同一个储物柜,任何人放入新物品,其他人都能看到

3. 性能优化:复制引用比复制大量数据快得多

  • 复制引用:就像复制一把钥匙,很快完成
  • 复制大量数据:就像把整个储物柜里的所有东西都复制一遍,很慢

总结:引用设计是一种聪明的方法,让我们用最少的内存空间,实现最快的访问速度,还能保证所有变量都能看到最新的数据!

🔍 数组变量的相等判断

让我们深入理解 === 这个相等运算符在不同类型数据上的行为差异。

基础类型的相等判断:比较值

还记得在数据类型基础课程中,我们学习了基础类型的相等判断 === 吗?

加载代码编辑器...

基础类型相等判断的特点:

  • 比较的是值是否相同
  • 只要值相同,就认为相等(不管它们存储在哪里)

数组类型的相等判断:比较引用

现在让我们看看数组类型的相等判断,你会发现完全不同的结果:

加载代码编辑器...

停下来思考一下:

  • 为什么 10 === 10 返回 true,但 [10] === [10] 返回 false?
  • 为什么 array1 和 array2 内容相同,但 array1 === array2 返回 false?
  • 为什么 array1 和 array3 的相等判断返回 true?

核心原理:值比较 vs 引用比较

基础类型(数字、字符串、布尔值):

  • 10 === 10:比较值是否相同
  • 只要是相同的值,就认为相等
  • 就像问"这两个苹果都是10克的苹果吗?" → 是

数组类型:

  • [10] === [10] 的左边和右边实际上是两个完全不同的数组,就像两个不同的储物柜
  • [10] === [10]:比较引用是否相同(是否指向同一个数组)
  • 即使两个数组的内容都是 [10],但它们是独立创建的,所以不相等
  • 就像问"这两把钥匙开的是同一个储物柜吗?" → 不是,因为它们开的是两个不同的储物柜

用储物柜比喻深入理解

场景1:不同的储物柜,内容相同

加载代码编辑器...
  • arrayA 和 arrayB 就像两个不同的储物柜
  • 虽然里面都放着数字10,但是它们是不同的储物柜
  • 所以 arrayA === arrayB 返回 false

场景2:同一把钥匙的不同拷贝

加载代码编辑器...
  • arrayC 和 arrayD 就像两把开同一个储物柜的钥匙
  • 它们指向同一个储物柜
  • 所以 arrayC === arrayD 返回 true

重要发现:两种不同的比较方式

记住这个关键区别:

数据类型=== 比较的是什么?例子结果
基础类型值是否相同10 === 10true
数组类型引用是否相同[10] === [10]false

为什么这样设计?

  • 基础类型:数据小,直接存储值,比较值很高效
  • 数组类型:数据可能很大,存储引用更高效,比较引用也更快

这就是为什么我们需要理解引用概念的原因!数组的相等判断逻辑与基础类型完全不同。

🔧 如何判断两个数组的内容是否相同

既然 === 不能比较数组内容,那我们如何判断两个数组的内容是否相同呢?

基本思路:

  1. 先比较长度:长度不同,内容肯定不同
  2. 逐个比较元素:长度相同,再逐个比较每个位置的元素
  3. 使用布尔变量记录结果:一旦发现不同元素,立即确定为不同
加载代码编辑器...

理解这个算法:

  1. 长度优先检查:

    • 如果长度不同,直接确定为不同,不用比较具体内容
    • 这样可以提高效率,避免不必要的循环
  2. 逐个元素比较:

    • 使用布尔变量 arraysEqual 记录比较结果,初始为 true
    • 使用 for 循环遍历数组的每个位置
    • 使用 array1[i] !== array2[i] 比较对应位置的元素
    • 一旦发现不同的元素,将 arraysEqual 改为 false,并用 break 跳出循环
  3. 最终结果:

    • 如果循环完成后 arraysEqual 仍然是 true,说明所有元素都相同
    • 如果在循环中发现不同元素,arraysEqual 会被改为 false

✅ 如何创建真正的独立副本

理解了数组相等判断后,我们现在知道:如果我们需要两个内容相同但独立的数组,需要创建真正的独立副本(两个不同的储物柜)。

方法1:使用 for 循环 + push 从头构建

这是最基础的方法,让我们一步步手动创建副本:

加载代码编辑器...

理解手动构建的过程:

  1. 创建空数组 manualCopy = []
  2. 遍历原数组,逐个复制元素
  3. 得到一个完全独立的新数组

方法2:slice() 方法(传统方法)

slice 是什么意思? slice 在英语中是"切片"的意思,就像切面包片一样,可以从数组中切出一片。

slice() 方法的基本用法:

例子1:不写参数 - 复制整个数组

加载代码编辑器...

例子2:一个参数 - 从指定索引复制到末尾

加载代码编辑器...

数学概念:开区间和闭区间

在编程中,我们经常使用数学中的区间概念来表示范围。

什么是区间? 区间就是表示一段范围的符号,就像说"从第1页到第3页"一样。

两种区间类型:

  • 闭区间 [ ]:包括两端的值,比如 [1, 3] 表示"从1到3,包括1和3"
  • 开区间 ( ):不包括两端的值,比如 (1, 3) 表示"大于1且小于3,不包括1和3"

半开半闭区间:

  • [1, 3):表示"从1到3,包括1但不包括3"
  • (1, 3]:表示"从1到3,不包括1但包括3"

例子3:两个参数 - 复制指定索引范围(使用半开区间)

加载代码编辑器...

为什么编程中常用半开区间 [开始, 结束)?

  1. 计算长度方便:结束 - 开始 = 元素个数,比如 3 - 1 = 2 个元素
  2. 避免重复:多个区间可以无缝连接,如 [0, 3) 和 [3, 6]
  3. 国际惯例:大多数编程语言都采用这种设计

重要说明:

  • 不写参数:slice() 复制整个数组
  • 一个参数:slice(开始索引) 从指定索引复制到末尾
  • 两个参数:slice(开始索引, 结束索引) 复制从开始索引到结束索引(不包括结束索引)的元素

使用 slice() 创建独立副本:

加载代码编辑器...

三种方法的对比

方法语法优点缺点推荐场景
for循环 + push代码较长,逻辑清晰理解底层原理,适合学习代码复杂学习和理解原理时
slice()array.slice()传统语法,兼容性好语法稍长,功能丰富需要部分复制时

🎯 什么时候共享?什么时候独立?

使用共享的情况(多个变量指向同一个数组):

加载代码编辑器...

使用独立副本的情况(每个变量有自己的数组):

加载代码编辑器...

总结

核心区别:值复制 vs 引用复制

数据类型赋值行为修改影响相等判断
基础类型(数字、字符串、布尔值)创建独立副本互不影响比较值:10 === 10 → true
数组类型共享同一个数组相互影响比较引用:[10] === [10] → false

关键概念

引用 vs 值的核心区别:

基础类型变量(数字、字符串、布尔值):

  • 变量直接存储值本身
  • 赋值时创建独立副本
  • 修改一个变量不影响其他变量
  • 就像每个人都有自己的独立储物盒

数组变量:

  • 变量存储的不是数组内容,而是数组的引用
  • 赋值时复制引用,不是数组内容
  • 多个变量可以指向同一个数组
  • 就像多把钥匙开同一个储物柜

实用方法

1. 比较数组内容是否相同

加载代码编辑器...

2. 创建数组独立副本的两种方法

  • for循环 + push:手动构建,适合理解原理
  • slice():传统方法,array.slice() 复制整个数组(推荐)

选择建议:

  • 需要共享:多个变量访问同一个数组(如游戏地图)
  • 需要独立:每个变量有自己的数组(如玩家背包)

引用概念是编程的重要基础,理解了它你就掌握了数据在程序中的工作方式!

Previous lesson
Previous
数组高级 - 探索数组的强大功能
Next
函数基础 - 解决重复代码的秘密武器
Next lesson