这是我参与8月更文挑战的第5天,活动详情查看8月更文挑战
前言
上次我们对 JavaScript 中的变量,在内存中的存储形式有了一定的了解,内存空间分为两种:栈内存与堆内存。
下面我们从一个问题出发,引出我们的主题“基本数据与引用类型”,开启我们的探索之旅吧。
字符串是否可以改变的?
首先我们先回顾一下,基本数据类型和引用数据类型:
- 基本数据类型:number、string、boolean、null、undefined、还有 ES6 新语法规范中的 Symbol、BigInt
- 引用数据类型:对象(普通对象、数组对象、正则对象、日期对象、Math、Date)、函数
上面所提到的基本数据类型,在
ECMAScript标准中,它们被定义为primitive values,即原始值,代表值本身是不可被改变的。
虽然标准是这么说,我们还是应该保持好奇心,去尝试验证,字符串是否可以被改变?
我们调用操作字符串的方式是,看下是否可以改变字符串,可以运行下下面代码测试下
var str = 'Dream';
str.toLowerCase(1);
str[0] = 'd';
str.slice(1);
console.log(str); // Dream
输出结果为 “Dream”,也就是印证了我们调用字符串的相关方法,并没有修改到原有字符串,而是在原字符串的基础上产生一个新的字符串,这说明了字符串的不可变性,也就是基本数据类型的值是不可以变的。
我们继续调用下面的代码:
str += 'er';
console.log(str); // Dreamer
你发现 str 的值改变了,那是不是标准说的字符串本身是不可以改变的,有问题呢?不慌。
说明一下,不可变的意思,是一旦创建,它们的值就不能变了。
var str = 'Dream'; // 执行这行代码的时候,整个过程会分配一个足够容纳 5 个字符串的控件,然后填充上
我们结合这张图说明一下:
我们看到进行字符串拼接之后,str 的值是 "Dreamer",而 "Dream" 被划掉了,字符串拼接,也相当于修改 str 变量中的字符串值,所以是必须先销毁原始的字符串,然后将新的字符串保存到 str 变量中。
执行 str += 'er'; 的操作之后,实际上是在栈中又开辟了一块内存空间用于存储 'Dreamer',然后将变量 str 指向这块空间。
如何理解基本类型与引用类型?
看到一篇文章,里面说到一个比喻,比较贴切,跟大家分享一下
用“连锁店”和“连锁店钥匙”来理解
基本类型
变量的交换等于在一个新的地方按照连锁店的规范标准(统一店面理解为相同的变量内容)新开一个分店,这样新开的店与其它旧店互不相关、各自运营。
这其实就是按值访问。
引用类型
变量的交换等同于把现有一间店的钥匙(变量引用地址)复制一把给了另外一个老板,此时两个老板同时管理一间店,两个老板的行为都有可能对一间店的运营造成影响。
这其实就是按引用访问。钥匙,就相当于变量的引用地址。
是否可变
在上面我们已经了解到基本数据类型的值,本身是不可以被改变的,而引用数据类型的值,是可以被改变的。
这怎么理解呢?比如,大家工作中经常使用到数组吧,我们可以对数组进行增删查改的操作。
var arr = [];
arr.push('张三');
arr.push('李四');
arr.pop();
console.log(arr); // ['张三']
复制
我们将一个变量的值复制到另一个变量上时,基本类型和引用类型的效果是不一样的。
先看看基本类型:
var name = '追梦玩家';
var name2 = name;
name2 = '砖家';
console.log(name); // 追梦玩家
输出的是 '追梦玩家',我们结合下图来理解下
内存中有一个变量name,值为追梦玩家。我们从变量name复制出一个变量name2,会在栈内存中创建一个新空间,存储 追梦玩家,赋值给变量 name2,虽然值是相同的,但是从图中,我们可以看出两者指向的内存空间完全没有关系,操作这两个变量,都不会有影响。
引用数据类型,我们看下这个代码
var obj = { name: '追梦玩家' };
var obj2 = obj;
obj2.name = '砖家';
console.log(obj.name); // 砖家
当我们复制引用类型的变量时,其实是复制的是栈中存的地址,所以复制出来的 obj2 和 实际上和 obj 指向同一个堆内存的实际值。所以我们修改了 obj2,obj 这个变量也会受影响,反之,也是一样的。
比较
当我们在对两个变量进行比较时,不同类型的变量的表现也是不同的。
// 基本数据类型
var name = '追梦玩家';
var name2 = '追梦玩家';
console.log(name === name2); // true
// 引用数据类型
var obj = { name:'追梦玩家' };
var obj2 = { name:'追梦玩家' };
console.log(obj === obj2); // false
基本类型之间的比较,是直接比较它们的值,如果值相等,即返回 true。上面代码的 name 和 name2的,其实 name 的第一个字符串跟 name2 的第一个字符串是否相等,依次进行比较。
对于引用类型,比较时,会比较它们的引用地址,虽然两个变量在堆中存储的对象具有的属性值都是相等的,但是它们被存储在了不同的存储空间,因此比较值为false。
怎么理解引用类型的比较呢?大白话来讲,就是 obj 这个对象,假设在栈内存中存储的内存地址是 0x00ff,对应的一把仓库钥匙,obj2 这个对象,假设再栈内存中存储的内存地址是 0x11ff,对应的是另一个仓库的钥匙,也就是说,你两把仓库钥匙,是不一样的,所以结果是 false。