在本章中,我们将研究 JavaScript 具有哪些值。
在本章中,我们偶尔会使用严格相等运算符。如果a
和b
相等,a === b
将评估为true
。具体含义在关于运算符的章节中有所解释。
在本章中,我将类型视为值集。例如,类型boolean
是集{false
,true
}。
Figure 6: A partial hierarchy of JavaScript’s types. Missing are the classes for errors, the classes associated with primitive types, and more. The diagram hints at the fact that not all objects are instances of Object
.
图 6 显示了 JavaScript 的类型层次结构。我们从该图中学到了什么?
Object
的对象和实例。 Object
的每个实例也是一个对象,但反之亦然。但是,实际上您在实践中遇到的所有对象都是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),原始值不是二等公民。它们和对象之间的区别更加微妙。简而言之,它是:
除此之外,原始值和对象非常相似:它们都具有 _ 属性 _(键值条目),并且可以在相同的位置使用。
接下来,我们将更深入地研究原始值和对象。
您无法更改,添加或删除基元的属性:
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);
要了解这种比较方式有什么特别之处,请继续阅读并找出对象的比较方式。
关于 OOP 的两章详细介绍了对象。在这里,我们主要关注它们与原始值的区别。
让我们首先探讨创建对象的两种常见方法:
对象字面值:
const obj = {
first: 'Jane',
last: 'Doe',
};
对象字面值以花括号{}
开头和结尾。它创建一个具有两个属性的对象。第一个属性具有键'first'
(字符串)和值'Jane'
。第二个属性具有键'last'
和值'Doe'
。有关对象字面值的更多信息,请参阅关于单个对象的章节。
数组字面值:
const arr = ['foo', 'bar'];
Array 字面值以方括号[]
开头和结尾。它创建一个带有两个 _ 元素 _ 的数组:'foo'
和'bar'
。有关数组字面值的更多信息,请参阅关于数组的章节。
默认情况下,您可以自由更改,添加和删除对象的属性:
const obj = {};
obj.foo = 'abc'; // add a property
assert.equal(obj.foo, 'abc');
obj.foo = 'def'; // change a property
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
:值的类型是什么?两个运算符typeof
和instanceof
可以确定给定值x
的类型:
if (typeof x === 'string') ···
if (x instanceof Array) ···
他们有什么不同?
typeof
区分规范的 7 种类型(减去一个遗漏,加上一个补充)。instanceof
测试哪个类创建了给定值。 经验法则:typeof
用于原始值,instanceof
用于对象
typeof
Table 2: The results of the typeof
operator.
x |
typeof x |
---|---|
undefined |
'undefined' |
null |
'object' |
布尔 | 'boolean' |
数 | 'number' |
串 | 'string' |
符号 | 'symbol' |
功能 | 'function' |
所有其他对象 | 'object' |
TBL。 2 列出typeof
的所有结果。它们大致对应于语言规范的 7 种类型。唉,有两个不同之处,它们是语言怪癖:
typeof null
返回'object'
而不是'null'
。那是一个错误。不幸的是,它无法修复。 TC39 尝试这样做,但它在网络上打破了太多代码。typeof
应该是'object'
(函数是对象)。为功能引入单独的类别令人困惑。exercises/operators/typeof_exrc.js
奖金:exercises/operators/is_object_test.js
instanceof
该运算符回答了问题:是否有x
类创建了值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
参见测验应用程序。