string 和 String 到底有什么区别?

TypeScript1周前发布 admin
262 0

写 JS 代码的人,大概率都干过这两件事:

js:
let a = "hello";
let b = new String("hello");

一个用小写 string,一个用大写 String,看起来好像差不多,打印出来也都是 hello。但如果你哪天不小心写出了 new String(...),代码可能会在一些很隐蔽的地方给你使绊子。今天就把这两者的关系彻底捋清楚。

先说结论:一个是”纸条”,一个是”盒子”

想象一下:

  • 小写的 string 是一张写着文字的纸条。纸条本身就是内容,你拿这张纸条给别人看,别人看到的就是上面写的字。两张写着同样内容的纸条,可以看作是”一样的”。
  • 大写的 String 是一个盒子,盒子里面装着一张写着文字的纸条。哪怕两个盒子里装的纸条内容一模一样,这也是两个不同的盒子,摆在桌子的不同位置。

这个比喻对应到技术上,就是 JS 里非常核心的一个概念:原始值(primitive)对象(object) 的区别。

"hello" 这种写法创建的是原始值类型的字符串,而 new String("hello") 创建的是一个 String 对象,这个对象内部包了一层,才装着真正的字符串内容。

用代码验证一下

先看类型:

js:
typeof "hello";              // "string"
typeof new String("hello");  // "object"

typeof 已经很诚实地告诉你了:一个是字符串类型,另一个压根就是对象类型,只是这个对象比较特殊,专门用来装字符串。

再看 instanceof

js:
"hello" instanceof String;              // false
new String("hello") instanceof String;  // true

纸条不是盒子,纸条永远不会通过”是不是盒子”这个检测;只有真正被装进盒子里的那个东西,才会通过。

疑问来了:既然是原始值,为什么还能调用方法?

这是很多人困惑的地方。原始值不是对象,按理说不应该有方法可以调用,但我们天天写 "hello".toUpperCase()"hello".length,用得好好的,这是为什么?

答案是 JS 引擎在背后偷偷帮你做了一件事,叫自动装箱(autoboxing)

当你对一个原始字符串调用方法或访问属性时,JS 引擎会:

  1. 临时把这张”纸条”塞进一个一次性的 String 盒子里;
  2. 调用盒子上的方法,拿到结果;
  3. 用完之后立刻把这个临时盒子扔掉。

整个过程快到你感觉不到,好像纸条自己就会做事一样。但本质上,方法是长在”盒子”这个对象上的,纸条只是被临时装了一下、借用了一下盒子的能力。

这也是为什么 String.prototype 上挂着一堆方法(sliceindexOfreplace 等等),而你用的却是原始字符串——你调用方法的那一瞬间,引擎已经悄悄帮你完成了”纸条进盒子”的临时转换。

坑一:相等比较会翻车

这是 new String() 最容易埋雷的地方:

js:
"hello" === "hello";                       // true
new String("hello") === new String("hello"); // false
new String("hello") === "hello";           // false

原因很直接:=== 对原始值比较的是内容,两张纸条只要写的字一样就算相等;但对对象比较的是引用,也就是是不是同一个盒子。

两个盒子哪怕装的纸条内容完全相同,它们也是摆在内存里不同位置的两个盒子,天生就不可能相等。

如果非要比较 String 对象的内容,得先把纸条从盒子里拿出来:

js:
new String("hello").valueOf() === "hello"; // true

坑二:if 判断也会翻车

这个坑更隐蔽,杀伤力也更大:

js:
if (new String("")) {
  console.log("居然进来了?!");
}

按常理,空字符串 "" 在 JS 里是”假值”(falsy),if ("") 是不会执行的。但 new String("") 是一个对象,而所有对象在布尔判断里都是”真值”(truthy),哪怕这个对象装的是个空纸条,盒子本身依然被判定为”存在”,于是上面这段代码真的会打印出那句话。

这种坑一旦出现在真实项目里,排查起来会很让人头疼,因为表面上看变量的”内容”是空的,逻辑却走向了相反的分支。

那为什么 String 对象还存在?

既然处处是坑,为什么 JS 还留着 String 这个构造函数,不干脆去掉?

一方面是历史遗留:JS 早期的设计里,需要有一种方式把字符串包装成对象,才能挂载方法、实现前面说的自动装箱机制,StringNumberBoolean 这三个包装对象就是干这个用的。另一方面,String 也确实有它正经的用途,比如判断类型时用 instanceof,或者极少数需要给字符串”对象”额外挂自定义属性的场景。但这些场景在日常业务代码里非常罕见。

结论

日常写代码,记住一句话就够了:

永远用小写的 "字符串"String("hello")(不带 new),不要用 new String("hello")

String("hello") 不带 new 调用时,只是做类型转换,返回的还是原始字符串,跟 new String("hello") 完全是两码事,这点也值得单独留意。

小写 string 简单、直接、可预测,绝大多数场景下都是你真正想要的东西;大写 String 对象更多时候只会在相等比较和真假值判断上给你埋雷。

搞清楚”纸条”和”盒子”的区别之后,再看到这两者,就不会再傻傻分不清楚了。

© 版权声明

相关文章

暂无评论

none
暂无评论...