目录
计算机程序通过操作值来工作,例如数字3.14或文本“Hello World”。可以在编程语言中表示和操作的这类值称为类型,而编程语言最基本的特征之一是它支持的类型集。当程序需要保留一个值以备将来使用时,它会将该值赋给(或“存储”该值到)变量中。变量有名称,它们允许在程序中使用这些名称来引用值。变量的工作方式是任何编程语言的另一个基本特征。本章介绍JavaScript中的类型、值和变量。它从概述和一些定义开始。
3.1 概述和定义
JavaScript类型可以分为两类:原始类型和对象类型。JavaScript的基本类型包括数字、文本字符串(称为字符串)和布尔真值(称为布尔值)。本章的大部分内容都致力于详细解释JavaScript中的数值(§3.2)和字符串(§3.3)类型。布尔值包含在§3.4中。
特殊的JavaScript值null
和undefined
是原始值,但它们不是数字、字符串或布尔值。每个值通常被认为是其自身特殊类型的唯一成员。§3.5有更多关于null
和undefined
的内容。ES6添加了一个新的特殊用途类型,即Symbol
,它可以定义语言扩展而不损害向后兼容性。§3.6简要介绍了Symbol
。
任何不是数字、字符串、布尔值、符号、null
或undefined
的JavaScript值都是一个对象。对象是属性的集合,其中每个属性都有一个名称和一个值(原始类型或另一个对象)。第3.7节涵盖了一个非常特殊的对象,即全局对象,但第6章会有更详细的介绍。
普通JavaScript对象是命名值的无序集合。该语言还定义了一种特殊类型的对象,称为数组,它表示编号值的有序集合。JavaScript语言包含用于处理数组的特殊语法,数组有一些特殊的行为,这些行为将它们与普通对象区分开来。数组是第7章的主题。
除了基本对象和数组之外,JavaScript还定义了许多其他有用的对象类型。Set
对象表示一组值。Map
对象表示从键到值的映射。各种“类型化数组”类型有助于对字节数组和其他二进制数据进行操作。RegExp
类型表示文本模式,并支持对字符串进行复杂的匹配、搜索和替换操作。Date
类型表示日期和时间,并支持基本的日期算术。Error
及其子类型表示执行JavaScript代码时可能出现的错误。所有这些类型都在第11章中介绍。
JavaScript与静态语言的不同之处在于,函数和类不仅仅是语言语法的一部分:它们本身就是可以由JavaScript程序操纵的值。与任何不是原始值的JavaScript值一样,函数和类是一种特殊的对象。在第8章和第9章中有详细介绍。
JavaScript解释器为内存管理执行自动垃圾收集。这意味着JavaScript程序员通常不需要担心对象或其他值的破坏或释放。当一个程序无法再引用某个值时,该值不再可访问时,解释器知道该值不能再使用,并自动回收它占用的内存。(JavaScript程序员有时确实需要注意确保值不会在无意中保持可访问性,从而导致长时间不可回收)
JavaScript支持面向对象的编程风格。简单地说,这意味着类型本身定义了处理值的方法,而不是使用全局定义的函数来操作各种类型的值。例如,要对数组a的元素进行排序,我们不将a传递给sort()
函数。相反,我们调用的sort()
方法是:
a.sort(); // 面向对象版本的 sort(a)。
方法定义见第9章。从技术上讲,只有JavaScript对象有方法。但是数字、字符串、布尔值和符号值的行为就像它们有方法一样。在JavaScript中,null
和undefined
是唯一不能调用方法的值。
JavaScript的对象类型是可变的,其原始类型是不可变的。可变类型的值可以更改:JavaScript程序可以更改对象属性和数组元素的值。数字、布尔值、符号、null
和undefined
都是不可变的,例如,讨论更改数字的值都没有意义。字符串可以看作是字符数组,您可能希望它们是可变的。然而,在JavaScript中,字符串是不可变的:您可以访问字符串的任何索引处的文本,但是JavaScript没有提供更改现有字符串文本的方法。可变值和不变值之间的差异将在§3.8中进一步探讨。
JavaScript可以自由地将值从一种类型转换为另一种类型。例如,如果一个程序需要一个字符串,而你给了它一个数字,它会自动将这个数字转换成字符串。如果在需要布尔值的地方使用非布尔值,JavaScript将相应地进行转换。类型转换规则在§3.9中进行了解释。JavaScript的自由类型转换规则影响其相等性的定义,==
相等运算符执行§3.9.1中描述的类型转换。(但是,在实践中,==
相等运算符被弃用,取而代之的是严格的相等运算符===
,后者不进行类型转换。有关两个操作符的更多信息,请参见§4.9.1。)
常量和变量允许您使用名称来引用程序中的值。常量用const
声明,变量用let
声明(在旧的JavaScript代码中用var
声明)。JavaScript常量和变量是动态类型的:声明没有指定将分配什么类型的值。变量声明和赋值包含在§3.10中。
从这篇冗长的介绍中可以看出,这是一个内容广泛的章节,解释了JavaScript中如何表示和操作数据的许多基本细节。我们将从深入了解JavaScript数字和文本的细节开始。
3.2 数字
JavaScript的主要数字类型Number
用于表示整数和近似实数。JavaScript使用IEEE754标准定义的64位浮点格式表示数字,这意味着它可以表示大到±1.7976931348623157×10308和小到±5×10−324的数字。
JavaScript数字格式允许您精确地表示−9007199254740992( −253 )和9007199254740992(253)之间的所有整数。如果使用大于此值的整数值,则可能会丢失后面数字的精度。但是请注意,JavaScript中的某些操作(如数组索引和第4章中描述的按位运算符)是用32位整数执行的。如果需要精确表示较大的整数,请参见§3.2.5。
当一个数字直接出现在JavaScript程序中时,它被称为数字字面量。JavaScript支持多种格式的数字字面量,如下节所述。请注意,任何数字前面都可以加一个减号(-)使数字为负数。
3.2.1 数字字面值
在JavaScript程序中,以10为基数的整数被写成一系列数字。例如:
0
3
10000000
除了以10为基数的整数文本外,JavaScript还可以识别十六进制(基数16)值。十六进制文本以0x
或0X
开头,后跟一个十六进制数字字符串。十六进制数字是0到9或字母A(或A)到f(或f)中的一个,代表10到15的值。以下是十六进制整数文本的示例:
0xff // => 255: (15*16 + 15)
0xBADCAFE // => 195939070
在ES6及更高版本中,还可以使用前缀0b
和0o
(或0B
和0O
)而不是0x
以二进制(以2为基数)或八进制(以8为基)表示整数:
0b10101 // => 21: (1*16 + 0*8 + 1*4 + 0*2 + 1*1)
0o377 // => 255: (3*64 + 7*8 + 7*1)
3.2.2 浮点字面值
浮点字面值可以有小数点;它们对实数使用传统语法。实值表示为数字的整数部分,后跟小数点和小数部分。
浮点字面值也可以用指数表示法表示:实数后跟字母e(或E),后跟可选的加号或减号,然后是整数指数。这个符号表示实数乘以10的指数幂。
更简洁地说,语法是:
[digits][.digits][(E|e)[(+|-)]digits]
例如:
3.14
2345.6789
.333333333333333333
6.02e23 // 6.02 × 10²³
1.4738223E-32 // 1.4738223 × 10⁻³²
数字文字中的分隔符
您可以在数字文本中使用下划线将长文本拆分为更易于阅读的块:let billion = 1_000_000_000;//下划线为千位分隔符。 let bytes = 0x89_AB_CD_EF;//作为字节分隔符。 let bits = 0b0001_1101_0111;//作为半字节分隔符。 let fraction = 0.123_456_789;//也适用于小数部分。
在2020年初撰写本文时,数字文本中的下划线尚未作为JavaScript的一部分正式标准化。但它们处于标准化过程的高级阶段,并且由所有主流浏览器和 Node 实现。
3.2.3 JavaScript中的算术
JavaScript程序使用语言所提供的算术运算符处理数字。其中包括 + 表示加法,- 表示减法,* 表示乘法,/ 表示除,以及 % 表示模(除后的余数)。ES2016为指数增加了 **。有关这些和其他操作员的详细信息,请参阅第4章。
除了这些基本的算术运算符之外,JavaScript还通过一组定义为Math
对象属性的函数和常量来支持更复杂的数学运算:
Math.pow(2,53)//=>9007199254740992:2到53的幂
Math.round(.6)//=>1.0:四舍五入到最接近的整数
Math.ceil(.6)//=>1.0:向上取整为整数
Math.floor(.6)//=>0.0:向下舍入为整数
Math.abs(-5)//=>5:绝对值
Math.max(x, y, z)//返回最大参数
Math.min(x, y, z)//返回最小参数
Math.random()//伪随机数x,其中0<=x<1.0
Math.PI //π:圆的周长/直径
Math.E // 自然对数的底
Math.sqrt(3) //=>3**0.5:3的平方根
Math.pow(3, 1/3)//=>3**(1/3):3的立方根
Math.sin(0)//三角法:同时Math.cos, Math.atan等等。
Math.log(10) //10的自然对数
Math.log(100)/Math.LN10 //100的以10为底的对数
Math.log(512)/Math.LN2 //以2为底的512对数
Math.exp(3) //Math.E立方
ES6 在 Math
对象上定义了更多函数:
Math.cbrt(27)//=>3:立方根
Math.hypot(3, 4)//=>5:所有参数平方和的平方根
Math.log10(100)//=>2:以10为底的对数
Math.log2(1024)//=>10:以2为底的对数
Math.log1p(x) //自然对数(1+x);非常小的x精确
Math.expm1(x) //Math.exp(x) -1;与Math.log1p()相反
Math.sign(x) //-1、0或1表示参数<、==或>0
Math.imul(2,3)//=>6:32位整数的优化乘法
Math.clz32(0xf)//=>28:32位整数中前导零位的数目
Math.trunc(3.9)//=>3:通过截断小数部分转换为整数
Math.fround(x) //四舍五入到最接近的32位浮点数
Math.sinh(x) //双曲正弦。也Math.cosh(), Math.tanh()
Math.asinh(x) //双曲反正弦。也Math.acosh(), Math.atanh()
JavaScript中的算术在溢出、下溢或被零除的情况下不会引发错误。当数值运算的结果大于最大可表示数(溢出)时,结果是一个特殊的无穷大值,即Infinity
。同样,当一个负值的绝对值大于最大可表示负数的绝对值时,结果是负无穷大,-Infinity
。无穷大值 的行为与您预期的一样:加、减、乘或除以任何值都会得到一个无穷大的值(可能正负号会变反)。
当数值运算的结果比最小的可表示数字更接近于零时,就会发生下溢。在本例中,JavaScript返回0。如果下溢是由负数引起的,JavaScript会返回一个称为“负零”的特殊值,这个值与普通的零几乎没有区别,JavaScript程序员很少需要注意到它。
在JavaScript中被零除不是错误:它只返回无穷大或负无穷大。但是有一个例外:零除以零没有一个定义良好的值,这个操作的结果是一个特殊的非数字值NaN。如果试图用无穷大除以无穷大,取负数的平方根,或者使用算术运算符来处理无法转换为数字的非数字操作数,也会出现NaN
。
JavaScript预定义全局常量 Infinity
和 NaN
来保存正无穷大和非数字值,这些值也可用作 Number
对象的属性:
Infinity // 正无穷大
Number.POSITIVE_INFINITY // 同样是正无穷大
1/0 // => Infinity
Number.MAX_VALUE * 2 // => Infinity; 溢出
-Infinity // 负无穷大
Number.NEGATIVE_INFINITY // 负无穷大
-1/0 // => -Infinity
-Number.MAX_VALUE * 2 // => -Infinity
NaN // 非数字值
Number.NaN // NaN的另一种写法
0/0 // => NaN
Infinity/Infinity // => NaN
Number.MIN_VALUE/2 // => 0: 下溢
-Number.MIN_VALUE/2 // => -0: 负零
-1/Infinity // -> -0: 负零
-0
//以下 Number 属性在ES6中定义
Number.parseInt() // 相当于全局的 parseInt() 函数
Number.parseFloat() // 相当于全局的 parseFloat() 函数
Number.isNaN(x) // x 是否是 NaN?
Number.isFinite(x) // x 是否是一个无穷大的数字?
Number.isInteger(x) // x 是否是个整数?
Number.isSafeInteger(x) // x 是否是个整数,且 -(2**53) < x < 2**53?
Number.MIN_SAFE_INTEGER // => -(2**53 - 1)
Number.MAX_SAFE_INTEGER // => 2**53 - 1
Number.EPSILON // => 2**-52: 数字之间的最小差异
NaN
值在JavaScript中有一个不寻常的特性:它与任何其他值(包括它本身)相比都不相等。这意味着您不能编写x === NaN
来确定变量x的值是否为NaN
。相反,你必须写x != x
或者 Number.isNaN(x)
。只有 x 确实是 NaN的时候,这些表达式的值才为真。
全局函数isNaN()
类似于 Number.isNaN()
. 如果其参数为NaN
,或者该参数是无法转换为数字的非数字值,则返回true。相关函数 Number.isFinite()
如果其参数不是NaN
、Infinity
或-Infinity
的数字,则返回true
。全局isFinite()
函数的参数是或可以转换为有限数,则返回true
。
译者注
,isNaN功能类似就不看了,看看isFinite有什么区别吧:console.log(Number.isFinite('5')) // false 虽然'5'可以转换为数字,但是仍然返回false console.log(isFinite('5')) // true, 全局的isFinite,如果参数可以转换为数字,返回true
负零值也有点不寻常。它与正零比较是相等的(甚至使用JavaScript的严格相等性测试 ===
),这意味着这两个值几乎无法区分,除非用作除数:
let zero = 0; // 普通0
let negz = -0; // 负0
zero === negz // => true: 正0与负0相等
1/zero === 1/negz // => false: Infinity 和 -Infinity 不相等
3.2.4 二进制浮点和舍入错误
实数有无穷多,但只有有限个数(确切地说是18437736874454810627)可以用JavaScript浮点格式精确地表示。这意味着在JavaScript中使用实数时,数字的表示通常是实际数字的近似值。
JavaScript(以及几乎所有其他现代编程语言)使用的IEEE-754浮点表示是一种二进制表示,它可以精确地表示1/2、1/8和1/1024等分数。不幸的是,我们最常用的分数(尤其是在进行财务计算时)是小数:1/10、1/100,等等。二进制浮点表示法不能精确表示0.1这样简单的数字。
JavaScript数字有足够的精度,可以非常接近0.1。但是,这个数字不能准确地表示出来,这可能会导致问题。考虑以下代码:
let x = .3 - .2; // 30美分减去20美分
let y = .2 - .1; // 20美分减去10美分
x === y // => false: 这两个值不一样!
x === .1 // => false: .3-.2不等于.1
y === .1 // => true: .2-.1等于.1
由于舍入误差,.3和.2近似值之间的差异与.2和.1近似值之间的差异不完全相同。重要的是要理解这种问题不只是Javascript才有的。另外,请注意这里显示的代码中的值x
和y
非常接近,并且接近正确的值。计算出的值对于几乎任何目的都是足够的;只有当我们试图比较相等性时才会出现问题。
如果这些浮点近似值不适合你的程序,请考虑使用放大后的整数。例如,您可以将货币值表示为整数美分,而不是分数美元。
3.2.5 具有任意精度的整数BigInt
JavaScript在ES2020中定义的最新特性之一是一种称为BigInt的新数值类型。到2020年初,它已经在Chrome、Firefox、Edge和Node中实现,Safari也在进行中。顾名思义,BigInt是一种数值类型,其值为整数。JavaScript中添加该类型主要是为了允许表示64位整数,这是与许多其他编程语言和api兼容所必需的。但是BigInt
值可以有数千甚至数百万个数字,如果您需要处理那么大的数字。(但是请注意,BigInt
实现不适合加密因为它们不试图阻止时序攻击。)
BigInt
文本以数字字符串形式写入,后跟小写字母n
。默认情况下,以10为基数,但可以使用0b
、0o
和0x
前缀表示二进制、八进制和十六进制BigInt
:
1234n //一个不大的BigInt文本
0b111111n //二进制BigInt
0o7777n //八进制BigInt
0x80000000000000n //=>2n**63n:64位整数
可以将BigInt()
用作将常规JavaScript数字或字符串转换为BigInt
值的函数:
BigInt(Number.MAX_SAFE_INTEGER) // => 9007199254740991n
let string = "1" + "0".repeat(100); // 1 后面跟100个0.
BigInt(string) // => 10n**100n: 一个古戈尔
BigInt
值的算术与使用常规JavaScript数字的算术类似,只是除法会删除任何余数并向下取整(接近零):
1000n + 2000n // => 3000n
3000n - 2000n // => 1000n
2000n * 3000n // => 6000000n
3000n / 997n // => 3n: 商是3
3000n % 997n // => 9n: 倒数 9
(2n **