JavaScript程序设计
2.3.8 Object 类型
ECMAScript 中的对象其实就是一组数据和功能的集合。对象通过 new 操作符后跟对象类型的名称来创建。开发者可以通过创建 Object 类型的实例来创建自己的对象,然后再给对象添加属性和方法:
let o = new Object();
这个语法类似 Java,但 ECMAScript 只要求在给构造函数提供参数时使用括号。如果没有参数,如上面的例子所示,那么完全可以省略括号(不推荐):
let o = new Object; // 合法,但不推荐
Object 的实例本身并不是很有用,但理解与它相关的概念非常重要。类似 Java 中的 java.lang.Object,ECMAScript 中的 Object 也是派生其他对象的基类(或者说在 ECMAScript 中 Object 是所有对象的基类)。Object 类型的所有属性和方法在派生的对象上同样存在。
每个 Object 实例都有如下属性和方法。
constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是Object()函数。hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如 o.hasOwnProperty(“name”))或符号。isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用for-in语句枚举。与hasOwnProperty()一样,属性名必须是字符串。toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。toString():返回对象的字符串表示。valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同。
Note: 严格来讲,ECMA-262 中对象的行为不一定适合 JavaScript 中的其他对象。比如浏览器环境中的 BOM 和 DOM 对象,都是由宿主环境定义和提供的宿主对象。而宿主对象不受 ECMA-262 约束,所以它们可能会也可能不会继承 Object。
2.4 操作符
ECMA-262 描述了一组可用于操作数据值的操作符,包括数学操作符(如加、减)、位操作符、关系操作符和相等操作符等。ECMAScript 中的操作符是独特的,因为它们可用于各种值,包括字符串、数值、布尔值,甚至还有对象。在应用给对象时,操作符通常会调用 valueOf()、toString()方法来取得可以计算的值。
2.4.1 一元操作符
只操作一个值的操作符叫一元操作符(unary operator)。一元操作符是 ECMAScript中最简单的操作符。
-
递增/递减操作符
类似 C 语言,递增/递减操作符分为前缀版和后缀版,具体如下:
let age = 29; ++age; // 前缀递增操作 age = age + 1; // 等同于上面一行语句 --age; // 前缀递减操作 age = age - 1; // 等同于上面一行语句 // 无论使用前缀递增还是前缀递减操作符,都是先改变变量的值,然后返回改变后的值: let x = 1; let a= ++x; // 先将 x 加1变成2,然后使用2赋值给a // 而对于后缀递增和后缀递减操作符,都是先返回变量被改变前的值,然后再改变变量的值: let y = 1; let b = y++; // 先返回y的当前值1赋值给b,然后将 y 加1变成2这4个操作符(前/后缀 + 递增/递减)可以作用于任何值,意思是不限于整数——字符串、布尔值、浮点值,甚至对象都可以。递增和递减操作符遵循如下规则。
- 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值。
- 对于字符串,如果不是有效的数值形式,则将变量的值设置为 NaN 。变量类型从字符串变成数值。
- 对于布尔值,如果是 false,则转换为 0 再应用改变。变量类型从布尔值变成数值。
- 对于布尔值,如果是 true,则转换为 1 再应用改变。变量类型从布尔值变成数值。
- 对于浮点值,加 1 或减 1。
- 如果是对象,则调用其
valueOf()方法取得可以操作的值。对得到的值应用上述规则。如果是NaN,则调用toString()并再次应用其他规则。变量类型从对象变成数值。
下面的例子演示了这些规则:
let s1 = "2"; let s2 = "z"; let b = false; let f = 1.1; let o = { valueOf() { return -1; } }; s1++; // 值变成数值 3 s2++; // 值变成 NaN b++; // 值变成数值 1 f--; // 值变成 0.10000000000000009(因为浮点数不精确) o--; // 值变成-2 -
一元加和减
一元加和减操作符对大多数开发者来说并不陌生,它们在 ECMAScript 中跟在高中数学中的用途一样。一元加由一个加号(+)表示,放在变量前头,对数值没有任何影响:
let num = 25; num = +num; console.log(num); // 25如果将一元加应用到非数值,则会执行与使用
Number()转型函数一样的类型转换:布尔值false和true转换为 0 和 1,字符串根据特殊规则进行解析,对象会调用它们的valueOf()、toString()方法以得到可以转换的值。下面的例子演示了一元加在应用到不同数据类型时的行为:
let s1 = "01"; let s2 = "1.1"; let s3 = "z"; let b = false; let f = 1.1; let o = { valueOf() { return -1; } }; s1 = +s1; // 值变成数值 1 s2 = +s2; // 值变成数值 1.1 s3 = +s3; // 值变成 NaN b = +b; // 值变成数值 0 f = +f; // 不变,还是 1.1 o = +o; // 值变成数值-1一元减由一个减号(-)表示,放在变量前头,主要用于把数值变成负值,如把 1 转换为-1。示例如下:
let num = 25; num = -num; console.log(num); // -25对数值使用一元减会将其变成相应的负值(如上面的例子所示)。在应用到非数值时,一元减会遵循与一元加同样的规则,先对它们进行转换,然后再取负值:
let s1 = "01"; let s2 = "1.1"; let s3 = "z"; let b = false; let f = 1.1; let o = { valueOf() { return -1; } }; s1 = -s1; // 值变成数值-1 s2 = -s2; // 值变成数值-1.1 s3 = -s3; // 值变成 NaN b = -b; // 值变成数值 0 f = -f; // 变成-1.1 o = -o; // 值变成数值 1一元加和减操作符主要用于基本的算术,但也可以像上面的例子那样,用于数据类型转换。
2.4.2 位操作符
接下来要介绍的操作符用于数值的底层操作,也就是操作内存中表示数据的比特(位)。ECMAScript中的所有数值都以 IEEE 754 64 位格式存储,但位操作并不直接应用到 64 位表示,而是先把值转换为32 位整数,再进行位操作,之后再把结果转换为 64 位。对开发者而言,就好像只有 32 位整数一样,因为 64 位整数存储格式是不可见的。既然知道了这些,就只需要考虑 32 位整数即可。
以下对有符号整数的讲解,详细请学习计算机组成原理。
有符号整数使用 32 位的前 31 位表示整数值。第 32 位表示数值的符号,如 0 表示正,1 表示负。这一位称为符号位(sign bit),它的值决定了数值其余部分的格式。
正值以真正的二进制格式存储,即 31位中的每一位都代表 2 的幂。第一位(称为第 0 位)表示 20 ,第二位表示 21 ,依此类推。如果一个位是空的,则以0填充,相当于忽略不计。比如,数值18的二进制格式为00000000000000000000000000010010,或更精简的 10010。后者是用到的 5 个有效位,决定了实际的值。
负值以补码存储。一个数值的补码通过如下 3 个步骤计算得到:
- 确定绝对值的二进制表示(如,对于-18,先确定 18 的二进制表示)
- 按位取反,换句话说,就是每个 0 都变成 1,每个 1 都变成 0
- 给结果加 1。
那么,-18 的二进制表示就是 11111111111111111111111111101110。要注意的是,在处理有符号整数时,我们无法访问第 31 位。
ECMAScript 会帮我们记录这些信息。在把负值输出为一个二进制字符串时,我们会得到一个前面加了减号的绝对值,如下所示:
let num = -18;
console.log(num.toString(2)); // "-10010"
Note: 默认情况下,ECMAScript 中的所有整数都表示为有符号数。不过,确实存在无符号整数。对无符号整数来说,第 32 位不表示符号,因为只有正值。无符号整数比有符号整数的范围更大,因为符号位被用来表示数值了。
在对 ECMAScript 中的数值应用位操作符时,后台会发生转换:64 位数值会转换为 32 位数值,然后执行位操作,最后再把结果从 32 位转换为 64 位存储起来。整个过程就像处理 32 位数值一样,这让二进制操作变得与其他语言中类似。但这个转换也导致了一个奇特的副作用,即特殊值NaN 和Infinity在位操作中都会被当成 0 处理。
如果将位操作符应用到非数值,那么首先会使用 Number()函数将该值转换为数值(这个过程是自动的),然后再应用位操作。最终结果是数值。
-
按位非
按位非操作符用波浪符(~)表示,它的作用是返回数值按位取反的结果。按位非是 ECMAScript 中为数不多的几个二进制数学操作符之一。看下面的例子:
let num1 = 25; // 二进制 00000000000000000000000000011001 let num2 = ~num1; // 二进制 11111111111111111111111111100110 console.log(num2); // -26这里,按位非操作符作用到了数值 25,得到的结果是-26。由此可以看出,按位非的最终效果是对数值取反并减 1,就像执行如下操作的结果一样:
let num1 = 25; let num2 = -num1 - 1; console.log(num2); // "-26"实际上,尽管两者返回的结果一样,但位操作的速度快得多。这是因为位操作是在数值的底层表示上完成的。
-
按位与
按位与操作符用和号(&)表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作。
第一个数值的位 第二个数值的位 结果 1 1 1 1 0 0 0 1 0 0 0 0 简单的说,按位与操作在两个位都是 1 时返回 1,在任何一位是 0 时返回 0。
let result = 25 & 3; console.log(result); // 输出 1 // 25 = 0000 0000 0000 0000 0000 0000 0001 1001 // 3 = 0000 0000 0000 0000 0000 0000 0000 0011 // -------------------------------------------- //AND = 0000 0000 0000 0000 0000 0000 0000 0001 -
按位或
按位或操作符用管道符(|)表示,同样有两个操作数。按位或遵循如下真值表:
第一个数值的位 第二个数值的位 结果 1 1 1 1 0 1 0 1 1 0 0 0 简单的说,按位或操作在至少一位是 1 时返回 1,在两位都是 0 时返回 0。
let result = 25 | 3; console.log(result); // 输出 27 // 25 = 0000 0000 0000 0000 0000 0000 0001 1001 // 3 = 0000 0000 0000 0000 0000 0000 0000 0011 // -------------------------------------------- // OR = 0000 0000 0000 0000 0000 0000 0001 1011 ---- 27 -
按位异或
按位异或用脱字符(^)表示,同样有两个操作数。下面是按位异或的真值表:
第一个数值的位 第二个数值的位 结果 1 1 0 1 0 1 0 1 1 0 0 0 简单的说,按位异或操作在两位相同时返回 0,在两位不同时返回 1。
let result = 25 ^ 3; console.log(result); // 输出 26 // 25 = 0000 0000 0000 0000 0000 0000 0001 1001 // 3 = 0000 0000 0000 0000 0000 0000 0000 0011 // -------------------------------------------- //XOR = 0000 0000 0000 0000 0000 0000 0001 1010 ---- 26 -
左移
左移操作符用两个小于号(<<)表示,会按照指定的位数将数值的所有位向左移动。比如,如果数值 2(二进制 10)向左移 5 位,就会得到 64(二进制 1000000),如下所示:
let oldValue = 2; // 等于二进制 10 let newValue = oldValue << 5; // 等于二进制 1000000,即十进制 64简单的说,左移操作是在对原数值做乘2操作(当左边没发生溢出时,每移1位乘1次)
在移位后,数值右端会空出 5 位。左移会以 0 填充这些空位,让结果是完整的 32 位数值,并且左移会保留它所操作数值的符号。比如,如果-2 左移 5 位,将得到-64,而不是正 64。
-
有符号右移
有符号右移由两个大于号(>>)表示,会将数值的所有 32 位都向右移,同时保留符号(正或负)。有符号右移实际上是左移的逆运算。比如,如果将 64 右移 5 位,那就是 2:
let oldValue = 64; // 等于二进制 1000000 let newValue = oldValue >> 5; // 等于二进制 10,即十进制 2简单的说,右移操作是在对原数值做除2操作(当右边没发生溢出时,每移1位除1次)
同样,移位后就会出现空位。不过,右移后空位会出现在左侧,且在符号位之后ECMAScript 会用符号位(正数为0,负数为1)的值来填充这些空位,以得到完整的数值。
-
无符号右移
无符号右移用 3 个大于号表示(>>>),会将数值的所有 32 位都向右移。
对于正数,无符号右移与有符号右移结果相同。仍然以前面有符号右移的例子为例,64 向右移动 5 位,会变成 2:
let oldValue = 64; // 等于二进制 1000000 let newValue = oldValue >>> 5; // 等于二进制 10,即十进制 2对于负数,有时候差异会非常大。与有符号右移不同,无符号右移会给空位补 0,而不管符号位是什么。对正数来说,这跟有符号右移效果相同。但对负数来说,结果就差太多了。无符号右移操作符将负数的二进制表示当成正数的二进制表示来处理。因为负数是其绝对值的二补数,所以右移之后结果变得非常之大,如下面的例子所示:
// 等于二进制 11111111111111111111111111000000 let oldValue = -64; let newValue = oldValue >>> 5; // 等于二进制 00000111111111111111111111111110 // 等于十进制 134217726

被折叠的 条评论
为什么被折叠?



