为什么字符串文字在 JavaScript 中被视为原始类型?
Why is the string literal considered a primitive type in JavaScript?
官方文档以及互联网上的大量文章都说 'some string'
是一个原始值,这意味着每次我们将它分配给一个变量时它都会创建一个副本。
但是,这个问题(及其答案) 表明实际上 V8 即使在 substr
方法上也不会复制字符串。
每次将字符串传递给函数时都复制字符串也是很疯狂的,而且没有意义。在C#、Java、Python等语言中,String数据类型绝对是引用类型。
此外,这个link显示了层次结构,毕竟我们可以看到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都说是。
从根本上说,因为 the specification says so:
string value
primitive value that is a finite ordered sequence of zero or more 16-bit unsigned integer values
规范还定义了 String 对象,与原始字符串不同。 (同样有原始 number
、boolean
和 symbol
类型,以及 Number 和 Boolean 和 Symbol 对象。)
原始字符串遵循其他原始字符串的所有规则。在语言级别,它们的处理方式与原始数字和布尔值完全相同。就所有意图和目的而言,它们 是 原始值。但是正如您所说,a = b
将 b
中的字符串从字面上复制 复制 并将该副本放入 a
中,这将是疯狂的。实现不必这样做,因为原始字符串值是 immutable(就像原始数值一样)。您不能更改字符串中的任何字符,只能创建一个新字符串。如果字符串是可变的,实现 将 必须在您执行 a = b
时进行复制(但如果它们是可变的,规范将以不同的方式编写)。
请注意原始字符串和字符串对象确实是不同的东西:
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 operation),然后在该对象上查找 属性 toUpperCase
并(在上面)调用。因此,原始字符串从 String.prototype
和 Object.prototype
获得它们的 toUpperCase
(和其他标准方法)。但是除了在某些边缘情况之外,代码无法访问创建的临时对象,并且 JavaScript 引擎可以避免在这些边缘情况之外按字面意思创建对象。 that 的优点是可以将新方法添加到 String.prototype
并用于原始字符串。
¹ "What edge cases?" 我听到你问了。我能想到的最常见的一种是当您在松散模式代码中将自己的方法添加到 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
官方文档以及互联网上的大量文章都说 'some string'
是一个原始值,这意味着每次我们将它分配给一个变量时它都会创建一个副本。
但是,这个问题(及其答案)substr
方法上也不会复制字符串。
每次将字符串传递给函数时都复制字符串也是很疯狂的,而且没有意义。在C#、Java、Python等语言中,String数据类型绝对是引用类型。
此外,这个link显示了层次结构,毕竟我们可以看到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都说是。
从根本上说,因为 the specification says so:
string value
primitive value that is a finite ordered sequence of zero or more 16-bit unsigned integer values
规范还定义了 String 对象,与原始字符串不同。 (同样有原始 number
、boolean
和 symbol
类型,以及 Number 和 Boolean 和 Symbol 对象。)
原始字符串遵循其他原始字符串的所有规则。在语言级别,它们的处理方式与原始数字和布尔值完全相同。就所有意图和目的而言,它们 是 原始值。但是正如您所说,a = b
将 b
中的字符串从字面上复制 复制 并将该副本放入 a
中,这将是疯狂的。实现不必这样做,因为原始字符串值是 immutable(就像原始数值一样)。您不能更改字符串中的任何字符,只能创建一个新字符串。如果字符串是可变的,实现 将 必须在您执行 a = b
时进行复制(但如果它们是可变的,规范将以不同的方式编写)。
请注意原始字符串和字符串对象确实是不同的东西:
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 operation),然后在该对象上查找 属性 toUpperCase
并(在上面)调用。因此,原始字符串从 String.prototype
和 Object.prototype
获得它们的 toUpperCase
(和其他标准方法)。但是除了在某些边缘情况之外,代码无法访问创建的临时对象,并且 JavaScript 引擎可以避免在这些边缘情况之外按字面意思创建对象。 that 的优点是可以将新方法添加到 String.prototype
并用于原始字符串。
¹ "What edge cases?" 我听到你问了。我能想到的最常见的一种是当您在松散模式代码中将自己的方法添加到 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