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


116

取自MDN

字符串文字(用双引号或单引号引起来)和非构造函数上下文中从字符串调用返回的字符串(即,不使用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
使用new String引入了对象包装的另一个透明层。typeof new String(); //"object"
Paul S.

那又如何'0123456789'.charAt(i)呢?
Yuriy Galanter 2013年

@YuriyGalanter,这不是问题,但是我想问为什么code block-1更快?
阿尔法(Alpha)

2
字符串对象在现实生活中很少见,因此解释器优化字符串文字并不奇怪。如今,您的代码已不再是简单的解释,幕后发生了许多优化层。
法布里西奥磨砂

2
这很奇怪:修订版2
hjpotter92 2013年

Answers:


149

JavaScript有两个主要的类型类别,即素数和对象。

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

就功能而言,单引号/双引号模式是相同的。除此之外,您要命名的行为称为自动装箱。因此,实际上发生的是,当调用包装类型的方法时,原语将转换为其包装类型。简单地说:

var s = 'test';

是原始数据类型。它没有方法,只不过是指向原始数据存储器引用的指针,因此可以解释更快的随机访问速度。

那么s.charAt(i),例如当您这样做时会发生什么?

由于s不是的实例String,因此JavaScript会自动包装s(具有typeof string其包装器类型)Stringtypeof object或者更精确地选择s.valueOf(s).prototype.toString.call = [object String]

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

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


33

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

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

另一方面,String对象被解析为a v8::StringObject,后者延伸,Object并且除了是完整的对象之外,还用作的包装器v8::String

现在,这只是合乎逻辑的,在执行该方法之前,new String('').method()必须调用取消对此v8::StringObject的装箱v8::String,因此它的速度较慢。


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

MDN的表达方式似乎是解释基元自动装箱工作方式的最简单方法(也见flav的答案),即JavaScript的基元y值如何调用方法。

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

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

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

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

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


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


1
别客气。=]现在,我想知道是否存在MDN的解释仅仅是因为它似乎是理解自动装箱的最简单方法,还是在ES规范中是否有任何引用。如果我找到参考,请更新答案。
法布里西奥磨砂

深入了解V8的实施。我要补充一点,装箱不只是为了解决该功能。也可以将此引用传递给方法。现在,我不确定V8是否会针对内置方法跳过此操作,但是如果您添加自己的扩展名说String.prototype,则每次调用该字符串对象时,都会获得盒装版本的字符串对象。

17

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

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

而对于String Object,我们可以分配属性

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

1
最后有人激励我们为什么也需要String对象。谢谢!
西普里安·托莫阿格(CiprianTomoiagă)2016年

1
为什么有人需要这样做?
Aditya

11

字符串字面量:

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

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

a==b 结果将为“ true”,这两个字符串都引用相同的对象。

字符串对象:

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

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

a==b 结果将为假,因为它们具有不同的引用。


1
字符串对象也是不可变的吗?
Yang

@YangWang是一种愚蠢的语言,对于两者ab尝试分配a[0] = 'X'将成功执行,但不会如您期望的那样
起作用

您写了“ var a ='s'; var b ='s'; a == b结果将为'true',这两个字符串都引用相同的对象。” 这是不正确的:a和b没有引用任何相同的对象,结果是正确的,因为它们具有相同的值。这些值存储在不同的内存位置,这就是为什么如果您更改一个不变的原因!
SC1000

9

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

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

由于它们的类型不同,因此您的JavaScript解释器也可能会对其进行不同的优化,如comment中所述


5

当您声明:

var s = '0123456789';

您创建一个字符串原语。该字符串基元具有一些方法,使您可以在其上调用方法,而无需将该基元转换为第一类对象。因此,您认为这样做会更慢,因为必须将字符串转换为对象是不正确的。它不必转换为对象。原语本身可以调用方法。

将其转换为成熟的对象(允许您向其添加新属性)是一个额外的步骤,并且不会使字符串操作更快(事实上,您的测试表明它会使它们变得更慢)。


字符串基元如何继承所有原型属性,包括自定义属性String.prototype
尤里·加兰特

1
var s = '0123456789';是一个原始值,这个值怎么有方法,我很困惑!
2013年

2
@SheikhHeera-语言实现中内置了原语,因此解释器可以赋予它们特殊的功能。
jfriend00 2013年

1
@SheikhHeera-我不明白您的最后评论/问题。字符串基元本身不支持您向其添加自己的属性。为了实现这一点,javascript还具有一个String对象,该对象具有与字符串基元相同的所有方法,但是它是一个成熟的对象,您可以在所有方面将其视为对象。这种双重形式似乎有些混乱,但是我怀疑这样做是对性能的妥协,因为99%的情况是使用基元,并且它们可能比字符串对象更快,更高效地使用内存。
jfriend00

1
@SheikhHeera“转换为字符串对象”是MDN表示的,以解释原语如何调用方法。它们并没有从字面上转换为字符串对象。
法布里西奥磨砂

4

我可以看到这个问题早已解决,字符串文字和字符串对象之间还有另一个微妙的区别,因为似乎没有人接触过它,我想我只是为了完整而写。

基本上,两者之间的另一个区别是使用eval时。eval('1 + 1')给出2,而eval(new String('1 + 1'))给出'1 +1',因此,如果某些代码块既可以“正常”执行又可以使用eval执行,则可以导致奇怪的结果


感谢您的输入:-)
Alpha

哇,这真是个奇怪的行为。您应该在评论中添加一个小的在线演示以显示此行为-这是非常令人大开眼界的。
EyuelDK '19

如果您考虑一下,这是正常的。new String("")返回一个对象,只有EVAL评估字符串,返回等每别的东西,因为它是
费利克斯·布吕

3

对象的存在与ECMAScript / JavaScript引擎中String的实际行为无关,因为根作用域将仅为此包含函数对象。因此,如果是字符串文字,则将搜索并执行charAt(int)函数。

对于真实的对象,您需要再添加一层,其中在标准行为开始之前(与上面相同),还在对象本身上搜索charAt(int)方法。显然,在这种情况下完成了大量工作。

顺便说一句,我不认为基元实际上会转换为对象,但是脚本引擎会将这个变量简单地标记为字​​符串类型,因此它可以为它找到所有提供的函数,因此看起来就像调用一个对象。不要忘记,这是一个脚本运行时,其运行原理与OO运行时不同。


3

字符串基元和字符串对象之间的最大区别是,对象必须遵循以下规则进行==操作

仅当操作数引用同一对象时,比较对象的表达式才为true。

因此,尽管字符串原语有一个==比较值的便利,但是在使任何其他不可变对象类型(包括字符串对象)表现得像值类型时,您还是很不走运。

"hello" == "hello"
-> true
new String("hello") == new String("hello") // beware!
-> false

(其他人指出,字符串对象在技术上是可变的,因为可以向其添加属性。但是尚不清楚这对什么有用;字符串值本身不是可变的。)


感谢您在相当长的一段时间后为问题添加了价值:-)
Alpha

1

在由javascript引擎运行之前,对代码进行了优化。通常,微基准测试可能会产生误导,因为编译器和解释器会在代码的某些部分重新排列,修改,删除并执行其他技巧,以使其运行更快。换句话说,编写的代码说明了目标是什么,但是编译器和/或运行时将决定如何实现该目标。

块1更快,主要是因为:var s ='0123456789'; 总是比var s = new String('0123456789');快 由于对象创建的开销。

循环部分不是导致速度下降的部分,因为chartAt()可以由解释器内联。尝试移除回路并重新运行测试,您将看到速度比与未移除回路时相同。换句话说,对于这些测试,循环块在执行时具有完全相同的字节码/机器码。

对于这些类型的微基准测试,查看字节码或机器代码将提供更清晰的画面。


1
感谢您的回答。
Alpha

0

在Javascript中,原始数据类型(例如字符串)是非复合构造块。这意味着它们只是值,仅此而已: let a = "string value"; 默认情况下,没有诸如toUpperCase,toLowerCase等内置方法。

但是,如果您尝试编写:

console.log( a.toUpperCase() ); or console.log( a.toLowerCase() );

这不会引发任何错误,而是它们将按预期方式工作。

发生了什么 ?好吧,当您尝试访问字符串的属性时,aJavascript通过new String(a);称为包装器对象将字符串强制转换为对象

此过程链接到Javascript中称为函数构造函数的概念,其中函数用于创建新对象。

当您new String('String value');在此处键入String时,它是函数构造函数,它接受一个参数并在函数范围内创建一个空对象,该空对象将被分配给函数,在这种情况下,String提供了我们前面提到的所有已知的内置函数。并且一旦操作完成(例如执行大写操作),包装对象就会被丢弃。

为了证明这一点,让我们这样做:

let justString = 'Hello From String Value';
justString.addNewProperty = 'Added New Property';
console.log( justString );

在这里输出将是不确定的。为什么呢 在这种情况下,JavaScript创建包装器String对象,设置新属性addNewProperty并立即丢弃包装器对象。这就是为什么您不确定的原因。伪代码如下所示:

let justString = 'Hello From String Value';
let wrapperObject = new String( justString );
wrapperObject.addNewProperty = 'Added New Property'; //Do operation and discard

0

我们可以用3种方式定义String

  1. var a =“第一种方式”;
  2. var b = String(“ second way”);
  3. var c = new String(“ third way”);

//也可以使用4.创建var d = a +'';

检查使用typeof运算符创建的字符串的类型

  • // //“字符串”的类型
  • typeof b //“字符串”
  • typeof c //“对象”


当您比较a和b var a==b ( // yes)


当您比较String对象时

var StringObj = new String("third way")
var StringObj2 = new String("third way")
StringObj  == StringObj2 // no result will be false, because they have different references
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.