原文: http://exploringjs.com/impatient-js/ch_values.html
贡献者:lq920320
在本章中,我们将研究 JavaScript 具有哪些值。
在本章中,我们偶尔会使用严格相等运算符。如果a
和b
相等,a === b
结果为true
。具体含义在12.4 严格相等详细解释。
在本章中,我将类型视为值的集合。例如,类型 boolean
是集 {false
,true
} 。
图 6: JavaScript 类型的部分层次结构。缺少的是错误类,与基元类型相关的类等等。此图暗示了并非所有的对象都是 Object
的实例。
图 6 显示了 JavaScript 的类型层次结构。我们从该图中学到了什么?
- JavaScript 区分两种值:原始值和对象。我们很快就会看到有什么区别。
- 该图区分了类
Object
的对象和实例。Object
的每个实例也是一个对象,但反之亦然。但是,实际上你在实践中遇到的所有对象都是Object
的实例。例如,通过对象字面值创建的对象。关于该主题的更多细节在26.4.3.4 对象并非Object
的实例中进行解释。
ECMAScript 规范已知的总共 7 种类型。这些类型的名称是(我使用 TypeScript 的名称,而不是规范的名称):
undefined
:唯一元素undefined
。null
:唯一元素null
。boolean
:包含false
和true
元素。number
:所有数字的类型(例如-123
,3.141
)。string
:所有字符串的类型(例如'abc'
)。symbol
:所有符号的类型(例如Symbol('My Symbol')
)。object
:所有对象的类型(与Object
不同,类Object
及其子类的所有实例的类型)。
规范对值进行了重要区分:
- 原始值是
undefined
,null
,boolean
,number
,string
,symbol
类型的元素。 - 所有其他值都是对象。
与 Java 相比(启发了 JavaScript 语言),原始值不是二等公民。它们和对象之间的区别更加微妙。简而言之,它是:
- 原始值:是 JavaScript 中的原子数据块。
- 它们是值传递的:当原始值分配给变量或传递给函数时,它们的内容被复制。
- 它们按值进行比较:比较两个原始值时,比较它们的内容。
- 对象:是复合数据。
- 它们是通过标识(我的术语)传递:当对象被分配给变量或传递给函数时,它们的标识(想一下指针)被复制。
- 它们是通过标识(我的术语)进行比较的:当比较两个对象时,他们的标识进行比较。
除此之外,原始值和对象非常相似:它们都具有属性(键值条目),并且可以在相同的位置使用。
接下来,我们将更深入地研究原始值和对象。
您无法更改,添加或删除基元的属性:
let str = 'abc';
assert.equal(str.length, 3);
assert.throws(
() => { str.length = 1 },
/^TypeError: Cannot assign to read only property 'length'/
);
基元是值传递的:变量(包括参数)存储基元的内容。将原始值分配给变量或将其作为参数传递给函数时,会复制其内容。
let x = 123;
let y = x;
assert.equal(y, 123);
基元按值进行比较:当比较两个原始值时,我们比较它们的内容。
assert.equal(123 === 123, true);
assert.equal('abc' === 'abc', true);
要了解这种比较方式有什么特别之处,请继续阅读并找出对象的比较方式。
对象的内容将会在第25章 “单个对象”以及接下来的介绍更多细节。这里我们主要集中于其与原始值有哪些不同。
让我们首先探讨创建对象的两种常见方法:
-
对象值:
const obj = { first: 'Jane', last: 'Doe', };
对象的值以花括号
{}
开头和结尾。它创建了一个具有两个属性的对象。第一个属性具有键'first'
(字符串)和值'Jane'
。第二个属性具有键'last'
和值'Doe'
。有关对象字值的更多信息,请参阅25.2.1 对象值:属性的章节。 -
数组值:
const arr = ['foo', 'bar'];
Array 值以方括号
[]
开头和结尾。它创建了一个带有两个元素的数组:'foo'
和'bar'
。有关数组值的更多信息,请参阅28.2.1 创建、读取、写入数组。
默认情况下,您可以自由更改,添加和删除对象的属性:
const obj = {};
obj.foo = 'abc'; // 添加一个属性
assert.equal(obj.foo, 'abc');
obj.foo = 'def'; // 改变某个属性的值
assert.equal(obj.foo, 'def');
对象是通过标识传递(我的术语):变量(包括参数)存储对象的标识。
对象的标识就像是堆(想一下 JavaScript 引擎的共享主内存)上的对象实际数据的指针(或透明引用)。
将对象分配给变量或将其作为参数传递给函数时,会复制其标识。每个对象字面值在堆上创建一个新对象并返回其标识。
const a = {}; // fresh empty object
// Pass the identity in `a` to `b`:
const b = a;
// Now `a` and `b` point to the same object
// (they “share” that object):
assert.equal(a === b, true);
// Changing `a` also changes `b`:
a.foo = 123;
assert.equal(b.foo, 123);
JavaScript 使用垃圾回收自动管理内存:
let obj = { prop: 'value' };
obj = {};
现在obj
的旧值{ prop: 'value' }
是垃圾(不再使用)。在某个时间点(如果有足够的可用内存,可能永远不会), JavaScript 会自动将它进行垃圾回收(从内存中删除)。
“通过标识传递”意味着对象(透明引用)的标识按值传递。这种方法也称为“通过共享传递”。
通过标识(我的术语)比较对象:如果两个变量包含相同的对象标识,则它们仅相等。如果它们引用具有相同内容的不同对象,则它们不相等。
const obj = {}; // fresh empty object
assert.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same content
typeof
和instanceof
这两个运算符可以确定给定值x
的类型:
if (typeof x === 'string') ···
if (x instanceof Array) ···
他们有什么不同?
typeof
区分规范的 7 种类型(减去一个遗漏,加上一个补充)。instanceof
测试哪个类创建了给定值。
表 2: typeof
操作符的结果集
x |
typeof x |
---|---|
undefined |
'undefined' |
null |
'object' |
布尔值 | 'boolean' |
数字 | 'number' |
字符串 | 'string' |
符号 | 'symbol' |
函数 | 'function' |
所有其他对象 | 'object' |
表 2 列出typeof
的所有结果。它们大致对应于语言规范的 7 种类型。唉,有两个不同之处,它们是语言怪癖:
typeof null
返回'object'
而不是'null'
。那是一个错误。不幸的是,它无法修复。 TC39 尝试这样做,但它在网络上打破了太多代码。- 函数的
typeof
应该是'object'
(函数是对象)。为功能引入单独的类别令人困惑。
exercises/operators/typeof_exrc.js
- 额外:
exercises/operators/is_object_test.js
该运算符回答了问题:是否有一个类C
创建了值x
?
x instanceof C
例如:
> (function() {}) instanceof Function
true
> ({}) instanceof Object
true
> [] instanceof Array
true
原始值不是任何实例:
> 123 instanceof Number
false
> '' instanceof String
false
> '' instanceof Object
false
exercises/operators/instanceof_exrc.js
JavaScript 的对象原始工厂是构造函数:如果通过 new
操作符调用它们,则返回自身的“实例”的普通函数。
ES6 引入了构造函数最好的语法,类。
在本书中,我可以互换地使用术语构造函数和类。
类可以看作是将规范的单一类型 object
划分为子类型——它们给出了比规范中有限的 7 种类型更多的类型。每个类都是由它创建的对象的类型。
每个基本类型(undefined
和null
的规范内部类型除外)都有一个关联的构造函数(想一下类):
- 构造函数
Boolean
与布尔值相关联。 - 构造函数
Number
与数字相关联。 - 构造函数
String
与字符串相关联。 - 构造函数
Symbol
与符号相关联。
每个函数都扮演着几个角色。例如,Number
:
-
可以将其用作函数并将值转换为数字:
assert.equal(Number('123'), 123);
-
Number.prototype
提供数字的属性。例如,方法.toString()
:assert.equal((123).toString, Number.prototype.toString);
-
Number
是数字工具函数的命名空间/容器对象。例如:assert.equal(Number.isInteger(123), true);
-
最后,你还可以将
Number
用作类并创建数字对象。这些对象与实数不同,应该避免。assert.notEqual(new Number(123), 123); assert.equal(new Number(123).valueOf(), 123);
与基本类型相关的构造函数也称为包装类型,因为它们提供了将原始值转换为对象的规范方法。在此过程中,原始值被“包装”在对象中。
const prim = true;
assert.equal(typeof prim, 'boolean');
assert.equal(prim instanceof Boolean, false);
const wrapped = Object(prim);
assert.equal(typeof wrapped, 'object');
assert.equal(wrapped instanceof Boolean, true);
assert.equal(wrapped.valueOf(), prim); // unwrap
包装在实践中很少有用,但它在语言规范内部使用,以提供原语属性。
在 JavaScript 中,有两种方法可以将值转换为其他类型:
- 显式转换:通过
String()
等功能。 - 强制(自动转换):当操作接收到无法使用的操作数/参数时发生。
与基本类型关联的(构造)函数显式地将值转换为该类型:
> Boolean(0)
false
> Number('123')
123
> String(123)
'123'
你还可以使用Object()
将值转换为对象:
> typeof Object(123)
'object'
对于许多操作,如果操作数/参数的类型不匹配,JavaScript 会自动转换它们。这种自动转换称为强制。
例如,乘法运算符将其操作数强制转换为数字:
> '7' * '3'
21
许多内置函数也强制执行。例如,parseInt()
将其参数强制转换为字符串(解析在第一个不是数字的字符处停止):
> parseInt(123.45)
123
exercises/values/conversion_exrc.js
参见测验应用程序。