数组
和其他强类型语言不同,在 JavaScript 中,数组可以容纳任何类型的值
使用 delete
运算符可以将单元从数组中删除,但是,单元删除后,数组的 length
属性并不会发生变化
// 稀疏数组
var a = []
a[0] = 1
a[2] = [3]
a[1] === "undefined"
a.length === 3
复制代码
数组通过数字进行索引,但有趣的是它们也是对象,所以也可以包含字符串键值和属性 (但这些并不计算在数组长度内)
类数组
有时候需要将类数组(一组通过数字索引的值)转换为真正的数组,这一般通过数组工具函数 (如 indexOf(…)、concat(…)、forEach(…)等)来实现
例如,一些 DOM 查询操作会返回 DOM 元素列表,它们并非真正意义上的数组,但十分类似,另一个例子是通过 arguments
对象(类数组),将函数的参数当作列表来访问(ES6 开始已废止)
工具函数 slice(...)
经常被用于这类转换:
function foo () {
var arr = Array.prototype.slice.call(arguments)
arr.push("bam")
console.log(arr)
}
foo("bar", "baz"); // ["bar", "baz", "bam"]
// 用 ES6 中的内置工具函数 `Array.from(...)` 也能实现同样的功能
var arr = Array.from(arguments)
复制代码
字符串
字符串和数组的确很相似,它们都是类数组,都有length
属性以及 indexOf(...)
和 concat(...)
方法
JavaScript 中的字符串和字符数组并不是一回事,
var a = "foo"
var b = ["f", "o", "o"]
a.length === 3
b.length === 3
a.indexOf("o") === 1
b.indexOf("o") === 1
var c = a.concat("bar") // "foobar"
var d = b.concat(["b", "a", "r"]) // ["f", "o", "o", "b", "a", "r"]
a === c // false
b === d // false
// 但这并不意味着它们都是 字符数组
a[1] = 'O'
b[1] = 'O'
a // "foo"
b // ["f", "O", "o"]
复制代码
JavaScript 中字符串是不可变的,而数组是可变的,并且 a[1]
在JavaScript 中并非总是合法语法,正确的方法应该是 a.charAt(1)
字符串不可变是指字符串的成员函数和不会改变其原始值,而是创建并返回一个新的字符串,而数组的成员函数都是在其原始值上进行操作
许多数组函数用来处理字符串很方便,虽然字符串没有这些函数,但可以通过 ”借用“ 数组的非变更方法
来处理字符串
var a = "foo"
a.join // undefined
a.map // undefined
var c = Array.prototype.join.call(a, '-')
var d = Array.propotype.map.call(a, v => {
return v.toUpperCase() + "."
})
复制代码
数组有一个字符串没有的可变更成员函数 reverse()
可惜我们无法借用数组的 可变更成员函数
,因为字符串是不可变的
所以,我们需要用一个比较粗暴的方法:
var a = "foo"
var c = a.split("").reverse().join("")
c // "oof"
复制代码
这种方法虽然粗暴,但对简单的字符串却完全适用
数字
JavaScript 只有一种数值类型: number
,包括”整数“ 和带小数的十进制数,此处”整数“之所以加引号是因为和其他语言不同,JavaScript没有真正意义上的整数
与大部分现代编程语言一样,JavaScript 中的数字类型是基于 IEEE 754
标准来实现的,该标准通常被称为”浮点数“,JavaScript 使用的是 ”双精度“格式(64位二进制)
数字的语法
// 数字常量一般使用十进制表示
var a = 42
var b = 42.3
// 数字前的 0 可以省略
var c = 0.42
var d = .42
// 小数点后小数部分最后的 0 也可以省略
var f = 42.0
var g = 42.
// 特别大和特别小的数字默认用指数格式显示,与 `toExponential()` 函数的输出结果相同
var a = 5E10
a // 50000000000
a.toExponential() // 5e+10
var b = a * a
b // 2.5e+21
var c = 1/a
c // 2e-11
复制代码
由于数字值可以使用 Number
对象进行封装,因此数字值可以调用 Number.prototype
中的方法
var a = 42.59
a.toFixed(0) // 43
a.toFixed(1) // 42.6
a.toFixed(2) // 42.59
a.toFixed(3) // 42.590
a.toPrecision(1) // 4e+1
a.toPrecision(2) // 43
a.toPrecision(3) // 42.6
a.toPrecision(4) // 42.59
a.toPrecision(5) // 42.590
复制代码
这些方法也适用于数字常量,不过
// 无效语法
42.toFixed(3)
// 有效语法
(42).toFixed(3)
0.42.toFixed(3)
42..toFixed(3)
42 .toFixed(3)
复制代码
因为数字常量是一个有效的数字字符,会被优先识别为数字常量的一部分,然后才是对象属性访问运算符
较小的数值
二进制浮点数最大的问题:
0.1 + 0.2 === 0.3 // false
复制代码
最常见的方法是设置一个误差范围值,通常称为”机器精度“,对JavaScript的数字来说,这个值通常是2^-52
从 ES6 开始,该值定义在 Number.EPSILON
中,我们可以直接拿来用,也可以为 ES6 之前的版本写 polyfill:
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2, -52)
}
function numbersCloseEnoughToEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON
}
var a = 0.1 + 0.2
var b = 0.3
numbersCloseEnoughToEqual(a, b) // true
复制代码
能够呈现的最大浮点数约 1.798e+308
,它定义在Number.MAX_VALUE
中
最小浮点数定义在Number.MIN_VALUE
中,大约是 5e-324
,它不是负数,但无限接近于 0
整数的安全范围
数字的呈现方式决定了”整数“的安全值范围远远小于 Number.MAX_VALUE
能够被”安全“呈现的最大整数是 2^53 – 1,在 ES6 中被定义为 Number.MAX_SAFE_INTEGER
, 最小整数是 -(2^53 – 1),在 ES6 中被定义为 Number.MIN_SAFE_INTEGER
整数检测
//ES6
Number.isInteger(42) // true
Number.isInteger(42.000) // true
Number.isInteger(42.3) // false
// before ES6 polyfill
if (!Number.isInteger) {
Number.isInteger = function (num) {
return typeof num === "number" && num % 1 === 0
}
}
// ES6
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
Number.isSafeInteger(Math.pow(2, 53)) // false
Number.isSafeInteger(Math.pow(2, 53) - 1) // false
// before ES6 polyfill
if (!Number.isSafeInteger) {
Number.isSafeInteger = function(num) {
return Number.isInteger(num) &&
Math.abs(num) <= Number.MAX_SAFE_INTEGER
}
}
复制代码
32位有符号整数
虽然整数最大能够达到 53 位,但是有些数字操作(如数位操作)只适用于 32 位数字,所以这些操作中数字的安全范围就要小很多,变成从 Math.pow(-2,31)(-2147483648, 约-21 亿)到 Math.pow(2,31) – 1(2147483647,约 21 亿)。
a | 0
可以将变量 a 中的数值转换为 32 位有符号整数,因为数位运算符 | 只适用于 32 位整数(它只关心 32 位以内的值,其他的数位将被忽略),因此与 0 进行操作即可截取 a 中 的 32 位数位
特殊数值
不是值的值
undefined 类型只有一个值,即 undefined。null 类型也只有一个值,即 null。它们的名
称既是类型也是值
undefined 和 null 常被用来表示“空的”值或“不是值”的值。二者之间有一些细微的差
别。例如:
- null 指空值
- undefined 指没有值(missing value)
null 是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋值。然而
undefined 却是一个标识符,可以被当作变量来使用和赋值(非严格模式)
void 运算符
var a = 42
void a // undefined
a // 42
复制代码
特殊的数字
- 不是数字的数字
NaN
var a = 2 / "foo"
a === NaN // false
NaN !== NaN // true
复制代码
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反的值
var a = 2 / "foo"
var b = "foo"
a // NaN
b // "foo"
window.isNaN(a) // true
window.isNaN(b) // true ————晕了!
复制代码
很明显 “foo” 不是一个数字,但是它也不是 NaN。这个 bug 自 JavaScript 问世以来就一直存在,至今已超过 19 年
不过从 ES6 开始我们可以使用工具函数 Number.isNaN(..)
// before ES6 polyfill
if (!Number.isNaN) {
Number.isNaN = function(n) {
return (
typeof n === "number" && window.isNaN(n)
)
}
}
var a = 2 / "foo";
var b = "foo";
Number.isNaN( a ); // true
Number.isNaN( b ); // false——好!
// 还有一个更简单的方法
if (Number.isNaN) {
Number.isNaN = function(n) {
return n !== n
}
}
复制代码
- 无穷数
Infinity
var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity
var a = Number.MAX_VALUE; // 1.7976931348623157e+308
a + a; // Infinity
a + Math.pow( 2, 970 ); // Infinity
a + Math.pow( 2, 969 ); // 1.7976931348623157e+308
复制代码
Number.POSITIVE_INfiNITY
Number.NEGATIVE_INfiNITY
- 零值
var a = 0 / -3 // -0
var b * -3 // -0
// 加减法运算不会得到负零
// 对负零进行字符串化会返回 ‘0’
a.toString() // "0"
a + "" // "0"
String(a) // "0"
JSON.stringify(a) // "0"
// 反过来是准确的
+ "-0" // -0
Number("-0") // -0
JSON.parse("-0") // -0
// 比较
-0 === 0 // true
// 区分
function isNegZero (n) {
n = Number(n)
return (n === 0) && (1/n === -Infinity)
}
复制代码
特殊等式
// ES6
var a = 2 / "foo";
var b = -3 * 0;
Object.is( a, NaN ); // true
Object.is( b, -0 ); // true
Object.is( b, 0 ); // false
// before ES6 polyfill
if (!Object.is) {
Object.is = function(v1, v2) {
// 判断是否是-0
if (v1 === 0 && v2 === 0) {
return 1 / v1 === 1 / v2;
}
// 判断是否是NaN
if (v1 !== v1) {
return v2 !== v2;
}
// 其他情况
return v1 === v2;
};
}
复制代码
值和引用
var a = 2;
var b = a; // b是a的值的一个副本
b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // d是[1,2,3]的一个引用
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
复制代码
简单值总是通过值复制的方式来赋值/传递
复合值————对象、函数,总是通过引用复制的方式来赋值/传递
由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向
var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// 然后
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
复制代码
函数参数经常让人产生这样的困惑:
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// 然后
x = [4,5,6];
x.push( 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[1,2,3,4],不是[4,5,6,7]
//如果要将 a 的值变为 [4,5,6,7],必须更改 x 指向的数组,而不是为 x 赋值一个新的数组
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// 然后
x.length = 0; // 清空数组
x.push( 4, 5, 6, 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[4,5,6,7],不是[1,2,3,4]
复制代码
我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定
JavaScript 中的引用和其他语言中的引用 / 指针不同,它们不
能指向别的变量 / 引用,只能指向值