JavaScript 中的字符串基元和字符串对象有什么区别?

IT技术 javascript string object
2021-01-23 02:29:17

摘自MDN

字符串文字(由双引号或单引号表示)和从非构造函数上下文中的 String 调用返回的字符串(即,不使用 new 关键字)是原始字符串。JavaScript 自动将原始类型转换为 String 对象,因此可以将 String 对象方法用于原始字符串。在要在原始字符串上调用方法或发生属性查找的上下文中,JavaScript 将自动包装字符串原始并调用方法或执行属性查找。

所以,我认为(逻辑上)对字符串基元的操作(方法调用)应该比对字符串对象的操作慢,因为method在应用于字符串之前,任何字符串基元都被转换为字符串对象(额外的工作)

但是在这个测试用例中,结果相反。所述码块1的运行速度比较快码块-2 两者的码块在下面给出:

代码块 1 :

var s = '0123456789';
for (var i = 0; i < s.length; i++) {
  s.charAt(i);
}

代码块 2 :

var s = new String('0123456789');
for (var i = 0; i < s.length; i++) {
    s.charAt(i);
}

结果因浏览器而异,但代码块 1总是更快。谁能解释一下,为什么代码块 1代码块 2

6个回答

JavaScript 有两个主要的类型类别,原语和对象。

var s = 'test';
var ss = new String('test');

单引号/双引号模式在功能方面是相同的。除此之外,您尝试命名的行为称为自动装箱。所以实际发生的是,当调用包装类型的方法时,原语被转换为其包装类型。简单说一下:

var s = 'test';

是一种原始数据类型。它没有方法,只不过是一个指向原始数据内存引用的指针,这解释了随机访问速度要快得多。

那么当你这样做时会发生什么s.charAt(i)

由于s不是 的实例String,JavaScript 将自动装箱stypeof string其包装类型必须为 、Stringtypeof object或更准确地说s.valueOf(s).prototype.toString.call = [object String]

自动装箱行为s根据需要来回转换为其包装器类型,但标准操作非常快,因为您正在处理更简单的数据类型。但是自动装箱和Object.prototype.valueOf有不同的效果。

如果要强制自动装箱或将原语转换为其包装器类型,则可以使用Object.prototype.valueOf,但行为不同。基于各种测试场景,自动装箱仅应用“必需”方法,而不会改变变量的原始性质。这就是为什么你获得更好的速度。

这相当依赖于实现,但我会试一试。我将以 V8 为例,但我假设其他引擎使用类似的方法。

字符串原语被解析为v8::String对象。因此,如jfriend00所述,可以直接在其上调用方法

另一方面,一个 String 对象被解析为一个v8::StringObject扩展,Object并且除了是一个完整的对象之外,还充当v8::String.

现在,它仅仅是逻辑上,呼叫到new String('').method()具有拆箱本v8::StringObjectv8::String执行该方法之前,因此它是较慢的。


在许多其他语言中,原始值没有方法。

MDN 的说法似乎是解释基元的自动装箱如何工作的最简单的方法(在flav的回答中也提到过),即 JavaScript 的基元 y值如何调用方法。

但是,智能引擎不会在每次需要调用方法时将字符串原语 y转换为 String 对象。这也在Annotated ES5 规范中提供了翔实的提及关于解析原始值的属性(和“方法”¹):

注意可能在步骤 1 中创建的对象在上述方法之外是不可访问的。实现可能会选择避免实际创建对象。[...]

在非常低的层次上,字符串通常被实现为不可变的标量值。示例包装器结构:

StringObject > String (> ...) > char[]

你离原始人越远,到达它所需的时间就越长。在实践中,String原语是不是更加频繁StringObjectS,因此它不是引擎的惊喜方法添加到字符串原语对应的(解释)对象的类,而不是之间来回转换的String,并StringObject为MDN的解释说明。


¹ 在 JavaScript 中,“方法”只是解析为函数类型值的属性的命名约定。

深入了解 V8 的实现。我要补充一点,拳击不仅仅是为了解决这个功能。也可以将 this 引用传递到方法中。现在我不确定 V8 是否会为内置方法跳过这个,但是如果你添加你自己的扩展来说 String.prototype 你会在每次调用它时得到一个字符串对象的盒装版本。
2021-03-28 02:29:17
别客气。=]现在我想知道 MDN 的解释是否仅仅因为它似乎是理解自动装箱的最简单方法,或者在 ES 规范中是否有任何对它的引用.. 阅读整个规范进行检查,会记得如果我找到参考,请更新答案。
2021-04-08 02:29:17

在字符串文字的情况下,我们不能分配属性

var x = "hello" ;
x.y = "world";
console.log(x.y); // this will print undefined

而在字符串对象的情况下,我们可以分配属性

var x = new String("hello");
x.y = "world";
console.log(x.y); // this will print world
为什么会有人需要这样做?
2021-03-26 02:29:17
终于有人提出了为什么我们也需要String对象。谢谢!
2021-04-05 02:29:17

字符串字面量:

字符串文字是不可变的,这意味着一旦它们被创建,它们的状态就不能改变,这也使它们成为线程安全的。

var a = 's';
var b = 's';

a==b 结果将是 'true' 两个字符串都指向同一个对象。

字符串对象:

这里创建了两个不同的对象,它们有不同的引用:

var a = new String("s");
var b = new String("s");

a==b 结果将是错误的,因为它们有不同的引用。

字符串对象也是不可变的吗?
2021-03-13 02:29:17
还有: var a = String("s"); var b = String("s"); console.log(a == b); // 返回真
2021-03-19 02:29:17
你写了“var a = 's'; var b = 's'; a==b 结果将是 'true' 两个字符串都引用了同一个对象。” 这是不正确的:a 和 b 不引用任何相同的对象,结果为真,因为它们具有相同的值。这些值存储在不同的内存位置,这就是为什么如果你改变另一个不会改变!
2021-04-01 02:29:17
@YangWang 这是一种愚蠢的语言,对于两者来说ab尝试分配a[0] = 'X'都会成功执行,但不会像您预期的那样工作
2021-04-03 02:29:17

如果使用new,则明确表示要创建Object的实例因此,new String正在生成一个包装String原语Object,这意味着对它的任何操作都涉及额外的工作层。

typeof new String(); // "object"
typeof '';           // "string"

由于它们的类型不同,您的JavaScript解释器也可能以不同的方式优化它们,如注释中所述