js 中我们创建字符串的方式有三种,如下。其中str、str1为基本字符串
,str2为字符串对象
,Boolean、Number 类似。
var str = 'test'
var str1 = String('test')
var str2 = new String('test')
我们使用 typeof 判断其类型,可以得到如下结果。
typeof str // string
typeof str1 // string
typeof str2 // object
当我们对他们进行比较时,会发现使用全等符去判断基本字符串和字符串对象会返回 false。
var str = 'test'
var str1 = String('test')
var str2 = new String('test')
str === str1 // true
str === str2 // false
// 双等时相等
str == str1 // true
str == str2 // true
那么为什么他们不相等呢??
js 的内存空间分为三种,栈(stack)
、堆(heap)
、池
(一般也会归类为栈中)。栈是一种先进后出的列表结构,如一个水杯中放入鸡蛋,我们需要先取出后放入的鸡蛋才能去取先放入的鸡蛋。堆是一种经过排序的树形数据结构。
其中栈存放变量
,堆存放复杂对象
,池存放常量
,所以也叫常量池。
上面的三种字符串,会有不同的存储过程。
- 基本字符串会先在
栈
中创建变量 str,然后在常量池
中寻找内容为 test 的对象,如果找到就将 str 指向 test,如果没有找到就在常量池中创建 test 然后 str 指向 test。当我们执行 str = ‘test2’ 重新赋值时,会重新执行上面的过程,寻找 test2,如果找到就将 str 指向 test2 否则重新创建然后指向,这也是为什么会说字符串是不可以改变的。 - 字符串对象则会在
堆
中创建一个字符串对象,然后将栈中创建的变量指向对应的对象,当我们新创建一个同样的字符串 var str3 = new String(‘test’) 时,会重新创建,所以 str3 === str2 会返回 false
var str = 'test'
var str1 = String('test')
var str2 = new String('test')
var str3 = new String('test')
str3 === str2 // false
str3 == str2 // false
str3 == str1 // true
'1' == new String('1') // true
new String('1') == new String('1') // false
可能有人会疑问,上面的代码中,str3 == str1 会返回 true 为什么 str3 == str2 会返回 false呢?
我们解析一下他的过程
- 当 str3 == str1 执行时,这里是基本类型和引用类型的比较,此时双等号下会执行隐式类型转换。
- 引用类型转换为基本类型会使用 ToPrimitive(input, PreferredType?) 抽象规则,这里会先调用 valueOf 方法。
- 此时 str2 会转换为基本字符串,此时比较结果相等。
- 而 str2 == str3 ,是引用类型和引用类型的比较,会比较其对象的地址,所以会返回 false。
注: 实际上,js 有三种特殊的引用类型:String、Number、Boolean 。被称为 基本包装类型
他们和普通的引用类型有一些差异,详情。
那为什么我们在使用基本字符串时也可以使用字符串对象的方法呢?
MDN - String 一节 中有相关的描述。
请注意区分 JavaScript 字符串对象和基本字符串值 . ( 对于 Boolean 和Numbers 也同样如此.)
字符串字面量 (通过单引号或双引号定义) 和 直接调用 String 方法(没有通过 new 生成字符串对象实例)的字符串都是基本字符串。JavaScript会自动将基本字符串转换为字符串对象,只有将基本字符串转化为字符串对象之后才可以使用字符串对象的方法。当基本字符串需要调用一个字符串对象才有的方法或者查询值的时候(基本字符串是没有这些方法的),JavaScript 会自动将基本字符串转化为字符串对象并且调用相应的方法或者执行查询
。
所以我们的基本字符串可以调用字符串对象的方法。
这里特别说明一下 Boolean 的基本包装类型。逻辑判断中,new Boolean(false)
会被当做 true 去处理,所以判断时最好使用 valueOf
去返回基本类型的值。示例如下