首页 > 解决方案 > 为什么字符串文字在 JavaScript 中被视为原始类型?

问题描述

官方文档以及互联网上的大量文章都说这'some string'是一个原始值,这意味着每次我们将它分配给一个变量时它都会创建一个副本。

但是,这个问题(及其答案)How to force JavaScript to deep copy a string? substr演示了实际上 V8 甚至在方法上也不会复制字符串。

每次将字符串传递给函数时都复制字符串也是很疯狂的,而且没有意义。在 C#、Java 或 Python 等语言中,String 数据类型绝对是一种引用类型。

此外,这个链接显示了层次结构,我们毕竟可以看到 HeapObject。 https://thlorenz.com/v8-dox/build/v8-3.25.30/html/d7/da4/classv8_1_1internal_1_1_sliced_string.html 在此处输入图像描述

最后,经过检查

let copy = someStringInitializedAbove

Devtools中,很明显尚未创建该字符串的新副本!

所以我很确定在分配时不会复制字符串。但是我仍然不明白为什么这么多像JS Primitives vs Reference这样的文章说它们是。

标签: javascriptlanguage-designlow-levelvalue-typereference-type

解决方案


从根本上说,因为规范是这样说的

字符串值

原始值是零个或多个 16 位无符号整数值的有限有序序列

该规范还定义了 String 对象,与原始字符串不同。(类似地,还有原始numberbooleansymbol类型,以及 Number 和 Boolean 和 Symbol 对象。)

原语字符串遵循其他原语的所有规则。在语言级别上,它们的处理方式与原始数字和布尔值完全相同。出于所有意图和目的,它们都是原始值。但是正如您所说,如果a = b从字面上制作字符串的副本b并将该副本放入a. 实现不必这样做,因为原始字符串值是不可变的(就像原始数值一样)。您不能更改字符串中的任何字符,只能创建一个新字符串。如果字符串是可变的,则实现必须复制一份a = b(但如果它们是可变的,则规范会以不同的方式编写)。

请注意,原始字符串和 String 对象确实是不同的东西:

const s = "hey";
const o = new String("hey");

// Here, the string `s` refers to is temporarily
// converted to a string object so we can perform an
// object operation on it (setting a property).
s.foo = "bar";
// But that temporary object is never stored anywhere,
// `s` still just contains the primitive, so getting
// the property won't find it:
console.log(s.foo); // undefined

// `o` is a String object, which means it can have properties
o.foo = "bar";
console.log(o.foo); // "bar"

那么为什么要有原始字符串呢?你得问 Brendan Eich(他在 Twitter 上的反应很合理),但我怀疑这是因为等价运算符(=====!=!==)的定义不必是可能被用于其自身目的的对象类型,或用于字符串的特殊情况。

那么为什么要有字符串对象呢?拥有 String 对象(以及 Number 对象、Boolean 对象和 Symbol 对象)以及说明何时创建原语的临时对象版本的规则使得在原语上定义方法成为可能。当你这样做时:

console.log("example".toUpperCase());

在规范术语中,创建一个 String 对象(通过GetValue 操作),然后toUpperCase在该对象上查找属性并(在上面)调用。因此,原始字符串从和获取它们的toUpperCase(和其他标准方法)。但是除非在某些边缘情况下,否则代码无法访问创建的临时对象,并且 JavaScript 引擎可以避免在这些边缘情况之外创建对象。这样做的好处可以在原始字符串中添加和使用新方法。String.prototypeObject.prototypeString.prototype


¹“什么边缘情况?” 我听到你问。我能想到的最常见的一种方法是当您String.prototype在松散模式代码中添加自己的方法(或类似方法)时:

Object.defineProperty(String.prototype, "example", {
    value() {
        console.log(`typeof this: ${typeof this}`);
        console.log(`this instance of String: ${this instanceof String}`);
    },
    writable: true,
    configurable: true
});

"foo".example();
// typeof this: object
// this instance of String: true

在那里,JavaScript 引擎被迫创建 String 对象,因为this在松散模式下不能是原语。

严格模式可以避免创建对象,因为在严格模式下this不需要是对象类型,它可以是原始类型(在本例中为原始字符串):

"use strict";
Object.defineProperty(String.prototype, "example", {
    value() {
        console.log(`typeof this: ${typeof this}`);
        console.log(`this instance of String: ${this instanceof String}`);
    },
    writable: true,
    configurable: true
});

"foo".example();
// typeof this: string
// this instanceof String: false


推荐阅读