JavaScript中的string文字和string对象有什么区别?

采取MDN

在非构造函数上下文中(即,不使用new关键字)从String调用返回的string文字(用双引号或单引号表示)和string是原始string。 JavaScript会自动将基元转换为string对象,以便可以为基元string使用string对象方法。 在一个方法被调用的原始string或属性查找的上下文中,JavaScript将自动包装string原语并调用方法或执行属性查找。

所以,我认为(逻辑上)对string文字的操作(方法调用)应该比对string对象的操作要慢,因为在将string应用到method之前,任何string文字都被转换为string对象(额外工作)。

但是在这个testing用例中 ,结果是相反的。 代码块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更快。

JavaScript有两个主要types类别,即原始对象和对象。

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

单引号/双引号模式在function上是相同的。 除此之外,你试图命名的行为被称为自动装箱。 所以实际上发生的是当一个包装types的方法被调用时,一个原语被转换为它的包装types。 简单的说:

 var s = 'test'; 

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

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

由于s不是String一个实例,所以JavaScript会自动将s s.valueOf(s).prototype.toString.call = [object String]给它的包装typesString ,其中包含typeof object ,更确切的说是s.valueOf(s).prototype.toString.call = [object String]

自动装箱行为根据需要来回转换为其包装types,但是由于您正在处理更简单的数据types,所以标准操作的速度非常快。 但是自动装箱和Object.prototype.valueOf有不同的效果。

如果你想强制自动装箱或者将一个图元转换为包装types,你可以使用Object.prototype.valueOf ,但是行为是不同的。 基于各种各样的testing场景,自动装箱只应用“必需的”方法,而不改变variables的原始性质。 这就是为什么你获得更好的速度。

这是相当实施,但我会拍。 我将以V8为例,但我假设其他引擎使用类似的方法。

一个string原语被parsing为一个v8::String对象。 因此,可以像jfriend00所提到的那样直接调用方法。

另一方面,String对象被parsing为一个扩展了Objectv8::StringObject ,除了是一个完整的对象之外, v8::StringObject可以作为v8::String的包装器。

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


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

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

但是,每次需要调用方法时,智能引擎都不会将stringprimitive-y转换为String对象。 这也在注释的ES5规范中提到。 关于解决原始价值的性质(和“方法”¹):

注意步骤1中可能创build的对象不能在以上方法之外访问。 实现可能会select避免实际创build对象。 […]

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

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

你离原始的距离越远,所需的时间就越长。 实际上, String基元比StringObject更为频繁,因此引擎将方法添加到String基元的相应(解释的)对象的Class中并不StringObject ,而StringObject像MDN的解释所暗示的那样在StringStringObject之间来回转换。


¹在JavaScript中,“method”只是一个属性的命名约定,该属性可以parsing为函数types的值。

如果使用new ,则明确指出要创buildObject的实例。 因此, new String产生了一个封装了String原语的对象 ,这意味着它的任何操作都涉及到额外的工作层。

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

由于它们的types不同,因此您的JavaScript解释器也可能会对它们进行不同的优化, 正如评论中所述 。

string字面量:

string文字是不可变的,这意味着一旦创build了它们的状态就不能改变,这也使得它们是线程安全的。

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

a==b结果将是'true',这两个string都是引用同一个对象。

string对象:

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

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

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

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

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

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

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

当你声明:

 var s = '0123456789'; 

你创build一个string原语。 这个string原始的方法可以让你调用方法,而不需要把原语转换成第一个类对象。 所以你的假设,这将是慢的,因为string已被转换为一个对象是不正确的。 它不必转换成一个对象。 基元本身可以调用这些方法。

把它转换成一个完整的对象(允许你添加新的属性)是一个额外的步骤,并不会使string操作更快(实际上你的testing显示它使得它们变慢)。

在ECMAScript / JavaScript引擎中,对象的存在与string的实际行为没有多大关系,因为根作用域将仅包含用于此的函数对象。 所以charAt(int)函数在string文字的情况下将被search并执行。

使用一个真实的对象,你可以在标准行为开始之前添加一个charAt(int)方法在对象本身上进行search的层(与上面相同)。 显然在这种情况下完成了大量的工作。

顺便说一句,我不认为原语实际上被转换成对象,但脚本引擎将简单地标记为字​​符串types的variables,因此它可以find所有提供的function,因此它看起来像你调用一个对象。 不要忘记这是一个脚本运行时,它运行在不同于OO运行时的原理上。