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

2022-08-30 04:24: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快。


答案 1

JavaScript有两个主要的类型类别,基元和对象。

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

单引号/双引号模式在功能方面是相同的。除此之外,您尝试命名的行为称为自动装箱。因此,实际发生的情况是,当调用包装类型的方法时,基元将转换为其包装器类型。简单来说:

var s = 'test';

是基元数据类型。它没有方法,它只不过是指向原始数据存储器参考的指针,这解释了更快的随机访问速度。

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

由于 不是 的实例,JavaScript 将自动装箱 ,其包装器类型为 ,或者更准确地说。sStringstypeof stringStringtypeof objects.valueOf(s).prototype.toString.call = [object String]

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

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


答案 2

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

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

另一方面,String 对象被解析为 v8::StringObject,该对象扩展,除了是一个完整的对象之外,还充当 的包装器。Objectv8::String

现在它只是合乎逻辑的,在执行方法之前,调用必须取消框,因此它更慢。new String('').method()v8::StringObjectv8::String


在许多其他语言中,基元值没有方法。

MDN的说法似乎是解释基元的自动装箱工作原理的最简单方法(正如flav的答案中也提到的那样),也就是说,JavaScript的原语值如何调用方法。

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

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

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

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

你离原始的距离越远,到达它所需的时间就越长。在实践中,基元比s更频繁,因此对于引擎来说,将方法添加到字符串基元的相应(解释)对象的类中,而不是像MDN的解释所暗示的那样在它们之间来回转换也就不足为奇了。StringStringObjectStringStringObject


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