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

时间:2013-06-22 23:08:33

标签: javascript string object

取自MDN

  

字符串文字(用双引号或单引号表示)和字符串   在非构造函数上下文中从String调用返回(即,没有   使用new关键字)是原始字符串。 JavaScript自动   将基元转换为String对象,以便可以使用它   原始字符串的字符串对象方法。在某种情况下   方法将在原始字符串或属性查找上调用   发生时,JavaScript会自动换行字符串原语和   调用方法或执行属性查找。

因此,我认为(逻辑上)字符串基元上的操作(方法调用)应该比字符串对象上的操作慢,因为在method上应用var s = '0123456789'; for (var i = 0; i < s.length; i++) { s.charAt(i); } 之前,任何字符串基元都会转换为字符串对象(额外工作)。字符串。

但是在这个test case中,结果是相反的。 代码块-1 的运行速度比代码块-2 快,两个代码块如下所示:

代码块-1:

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

代码块-2:

{{1}}

结果因浏览器而异,但代码块-1 总是更快。任何人都可以解释一下,为什么代码块-1 代码块-2 更快。

12 个答案:

答案 0 :(得分:125)

JavaScript有两个主要类型类别:primivites和objects。

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

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

var s = 'test';

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

那么当您执行s.charAt(i)时会发生什么?

由于s不是String的实例,因此JavaScript会自动将s设置为typeof string,其包装类型为String,{ typeof object或更准确地说s.valueOf(s).prototype.toString.call = [object String]

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

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

答案 1 :(得分:31)

这是依赖于实现的,但我会尝试一下。我将用V8举例说明,但我假设其他引擎使用类似的方法。

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

另一方面,String对象被解析为扩展Object的{​​{3}},除了是一个完整的对象之外,还充当v8::String的包装器。 / p>

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


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

MDN的方式似乎是解释原语自动装箱如何工作的最简单方法(也在 flav 的答案中也有提到),也就是说,JavaScript的原语是怎样的-y 值可以调用方法。

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

  

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

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

v8::String

你离原始的距离越远,达到它的时间就越长。在实践中,StringObject > String (> ...) > char[] 原语比String更频繁,因此引擎向String原语'对应(解释)对象'类添加方法而不是来回转换并不奇怪MDN的解释表明,在StringObjectString之间。


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

答案 2 :(得分:13)

如果是字符串文字,我们无法分配属性

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

答案 3 :(得分:9)

如果您使用new,则明确声明您要创建 Object 的实例。因此,new String正在生成包含 String 原语的 Object ,这意味着对它的任何操作都需要额外的工作层。

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

由于它们属于不同类型,您的 JavaScript 解释程序也可能采用不同的方式优化它们,as mentioned in comments

答案 4 :(得分:9)

字符串文字:

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

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

a==b结果将是&#39; true&#39;两个字符串都引用相同的对象。

字符串对象:

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

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

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

答案 5 :(得分:5)

宣布:

var s = '0123456789';

你创建一个字符串原语。该字符串原语具有允许您在其上调用方法而无需将原语转换为第一类对象的方法。所以你的假设是因为字符串必须转换为一个对象会更慢,这是不正确的。它不必转换为对象。原语本身可以调用方法。

将它转换为一个完整的对象(允许你向它添加新的属性)是一个额外的步骤,并不会使字符串更快(事实上你的测试显示它使它们变慢)。

答案 6 :(得分:3)

对象的存在与ECMAScript / JavaScript引擎中String的实际行为几乎没有关系,因为根作用域将只包含函数对象。因此,搜索并执行字符串文字时的charAt(int)函数。

使用真实对象,您可以添加一个图层,在标准行为启动之前,还会在对象本身上搜索charAt(int)方法(与上面相同)。显然,在这种情况下完成了大量工作。

BTW我不认为原语实际上是转换为对象,但脚本引擎只是将这个变量标记为字符串类型,因此它可以找到所有提供的函数,因此看起来你调用了一个对象。不要忘记这是一个脚本运行时,它的工作原理与OO运行时不同。

答案 7 :(得分:3)

我可以看到这个问题很久以前就已经解决了,字符串文字和字符串对象之间还有另一个细微的区别,因为似乎没有人触及它,我想我只是为了完整性而写它。 / p>

基本上两者之间的另一个区别是使用eval时。 eval(&#39; 1 + 1&#39;)给出2,而eval(new String(&#39; 1 + 1&#39;))给出&#39; 1 + 1&#39 ;,所以如果某些块代码可以正常执行&#39;或者使用eval,可能会导致奇怪的结果

答案 8 :(得分:1)

字符串基元和字符串对象之间的最大区别是对象必须遵循this rule for the == operator

  

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

因此,尽管字符串基元具有方便的==来比较值,但是要使任何其他不可变对象类型(包括字符串对象)的行为类似于值类型,您还是很不走运。 / p>

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

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

答案 9 :(得分:1)

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

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

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

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

答案 10 :(得分:0)

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

但是,如果您尝试写:

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

这不会引发任何错误,而是它们将按预期运行。

发生了什么事? 好吧,当您尝试访问字符串a的属性时,JavaScript会通过称为{strong> wrapper对象的new String(a);将字符串强制转换为对象。

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

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

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

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

答案 11 :(得分: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