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

取自 MDN

字符串文字(由双引号或单引号表示)和字符串 从非构造函数上下文中的 String 调用返回(即,没有 (使用 new 关键字)是基元字符串 将原语转换为 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快吗。

61687 次浏览

当你声明:

var s = '0123456789';

创建一个字符串原语。该字符串原语具有一些方法,可以在不将该原语转换为第一类对象的情况下调用它的方法。因此,您认为这样做会慢一些,因为字符串必须转换为对象,这种假设是不正确的。它不必转换为对象。原语本身可以调用这些方法。

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

如果您使用 new,那么您就是在明确地声明您想要创建一个 反对的实例。因此,new String正在产生一个包裹 绳子原语的 反对,这意味着对它的任何操作都涉及到额外的工作层。

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

由于它们的类型不同,您的 JavaScript解释器也可能会以不同的方式优化它们,即 正如评论中提到的

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

对于一个实际的对象,您需要添加一个新的层,在标准行为生效之前,charAt (int)方法也将在该对象本身上进行搜索(与上面相同)。显然,在这种情况下有大量的工作要做。

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

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

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

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

var s = 'test';

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

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

由于 s不是 String的实例,JavaScript 将自动对 s进行包装,s的包装类型为 typeof stringStringtypeof object或更精确地说是 s.valueOf(s).prototype.toString.call = [object String]

自动装箱行为根据需要将 s来回转换为其包装器类型,但是标准操作的速度令人难以置信,因为您处理的是一种更简单的数据类型。然而,自动装箱和 Object.prototype.valueOf有不同的效果。

如果希望强制自动装箱或将基元强制转换为其包装类型,可以使用 Object.prototype.valueOf,但行为是不同的。基于各种各样的测试场景,自动装箱只应用“必需的”方法,而不改变变量的原始特性。所以你的速度更快。

这完全取决于实现,但我还是要试一试。我将以 V8为例,但我假设其他引擎也使用类似的方法。

字符串原语被解析为 v8::String对象。因此,就像 朋友00提到的那样,可以直接对它调用方法。

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

现在它只是逻辑上的,对 new String('').method()的调用必须在执行该方法之前解压这个 v8::StringObjectv8::String,因此它比较慢。


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

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

但是,智能引擎不会在每次需要调用方法时将字符串 很原始转换为 String 对象。关于解析基元值的属性(和“方法”1) ,这也在 注释 ES5规格。中有所提及:

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

在非常低的级别上,字符串通常被实现为不可变的标量值:

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

你离原始世界越远,到达那里的时间就越长。实际上,String原语比 StringObject原语频繁得多,因此引擎向 String 原语的对应(解释)对象的 Class 中添加方法而不是像 MDN 解释的那样在 StringStringObject之间来回转换也就不足为奇了。


1在 JavaScript 中,“ method”只是一个属性的变数命名原则,该属性解析为一个类型为 function 的值。

字符串字面意义:

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

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

a==b结果将是‘ true’两个字符串引用的同一个对象。

字符串对象:

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

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

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

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

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

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

基本上,两者之间的另一个区别是在使用 eval 时。Eval (’1 + 1’)给出2,而 eval (new String (’1 + 1’)给出’1 + 1’,所以如果某个代码块既可以“正常”执行,也可以用 eval 执行,那么它可能会导致奇怪的结果

字符串原语和字符串对象之间最大的区别是对象必须遵循 ==操作符的此规则:

比较 Objects 的表达式只有在操作数引用时为 true 同一物体。

因此,虽然字符串原语有一个方便的 ==来比较值,但是当涉及到使任何其他不可变物件类型(包括字符串对象)表现得像一个值类型时,你就不走运了。

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

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

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

第1项之所以较快,主要是因为: Var s =’0123456789’; 总是比 Var s = new String (’0123456789’) ; 因为对象创建的开销。

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

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

在 Javascript 中,像 绳子这样的基本数据类型是非复合构建块。这意味着它们只是价值观,仅此而已: let a = "string value"; 默认情况下,不存在 toUpperCase、 toLowerCase 等内置方法。

但是,如果你试图写:

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

这不会抛出任何错误,相反,它们将按照应该的方式工作。

发生什么事了? 那么,当您尝试访问字符串的属性 a Javascript 强制字符串到对象的 new String(a);称为 包装器对象包装器对象

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

当你在这里输入 new String('String value');字符串是函数构造函数,它接受一个参数并在函数范围内创建一个空对象,这个空对象被分配给 这个,在这个例子中,字符串提供了我们之前提到的所有已知的内置函数。一旦操作完成,例如做大写操作,包装器对象被丢弃。

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

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

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

  1. Var a = “第一条路”;
  2. Var b = String (“第二条路”) ;
  3. Var c = new String (“ Third way”) ;

//也可以使用 4. var d = a +”;

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

  • A//“字符串”类型
  • B//“字符串”类型
  • C//“对象”类型


当你比较 a 和 b 变量时 a==b ( // yes)


当比较 String 对象 < br >

var StringObj = new String("third way")
var StringObj2 = new String("third way")
StringObj  == StringObj2 // no result will be false, because they have different references