Skip to main content

你不知道的JS(中)

2 值

2.1 数组

数组通过数字进行索引,但有趣的是它们也是对象,所以也可以包含字符串键值和属性 (但这些并不计算在数组长度内):

var a = [ ];
a[0] = 1;
a["foobar"] = 2;
a.length; // 1
a["foobar"]; // 2
a.foobar; // 2

这里有个问题需要特别注意,如果字符串键值能够被强制类型转换为十进制数字的话,它就会被当作数字索引来处理.

var a = [ ];
a["13"] = 42;
a.length; // 14

在数组中加入字符串键值/属性并不是一个好主意. 建议使用对象来存放键值/属性值,用数组来存放数字索引值.

类数组

有时需要将类数组(一组通过数字索引的值)转换为真正的数组,这一般通过数组工具函数(如 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"]

如上所示,slice() 返回参数列表(上例中是一个类数组)的一个数组复本。 用 ES6 中的内置工具函数 Array.from(..) 也能实现同样的功能

Array.from("alan")  // [ 'a', 'l', 'a', 'n' ]

2.2 字符串

数组的很多函数,字符串可以借用:

var a = "foo";
a.join; // undefined
a.map; // undefined
Array.prototype.join.call( a, "-" ); // "f-o-o"
Array.prototype.map.call( a, function(v){
return v.toUpperCase() + ".";
} ).join( "" ); // "F.O.O."

因为字符串是不可变对象,所以改变数组原始值的函数,在字符串上操作没有效果,比如reverse:

var a = "foo";
Array.prototype.reserve.call(a);
a //foo

字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。而数组的成员函数都是在其原始值上进行操作.

数字

小数点后小数部分最后面的 0 也可以省略, 所以以下都是合法声明:

var a = 42.0;
var b = 42.;

因为.它是一个有效的数字字符,会被优先识别为数字常量的一部分,然后才是对象属性访问运算符。所以:

// 无效语法:
42.toFixed( 3 ); // SyntaxError
// 下面的语法都有效:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"

tofixed(..) 方法可指定小数部分的显示位数,如果指定的小数部分的显示位数多于实际位数就用 0 补齐,函数返回结果是字符串。toPrecision(..) 方法用来指定有效数位的显示位数:

var a = 42.59;
a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"
a.toPrecision( 6 ); // "42.5900"

较小的数值

所有遵循 IEEE 754 规范的语言都无法精确表示二进制浮点数中的 0.1 和 0.2,它们相加的结果并非刚好等于 0.3,而是一个比较接近的数字 0.30000000000000004。 那么应该怎样来判断 0.1 + 0.2 和 0.3 是否相等呢? 最常见的方法是设置一个误差范围值,通常称为“机器精度”(machine epsilon),对 JavaScript 的数字来说,这个值通常是 2^-52 (2.220446049250313e-16)。 从 ES6 开始,该值定义在 Number.EPSILON 中,我们可以直接拿来用,也可以为 ES6 之前 的版本写 polyfill:

if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}

可以使用 Number.EPSILON 来比较两个数字是否相等(在指定的误差范围内):

function numbersCloseEnoughToEqual(n1,n2) {
return Math.abs( n1 - n2 ) < Number.EPSILON;
}
var a = 0.1 + 0.2;
var b = 0.3;
numbersCloseEnoughToEqual( a, b );
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // false

整数检测

要检测一个值是否是整数,可以使用 ES6 中的 Number.isInteger(..) 方法:

Number.isInteger( 42 );     // true
Number.isInteger( 42.000 ); // true
Number.isInteger( 42.3 ); // false

也可以为 ES6 之前的版本 polyfill Number.isInteger(..) 方法:

if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
};
}

要检测一个值是否是安全的整数,可以使用 ES6 中的 Number.isSafeInteger(..) 方法:

Number.isSafeInteger( Number.MAX_SAFE_INTEGER );    // true
Number.isSafeInteger( Math.pow( 2, 53 ) ); // false
Number.isSafeInteger( Math.pow( 2, 53 ) - 1 ); // true

可以为 ES6 之前的版本 polyfill Number.isSafeInteger(..) 方法:

if (!Number.isSafeInteger) {
Number.isSafeInteger = function(num) {
return Number.isInteger( num ) &&
Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
};
}

不是值的值

null 是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋值。然而 undefined 却是一个标识符,可以被当作变量来使用和赋值。

NaN

isNaN 会对输入参数进行类型转换(ToNumber), 然后再判断是否是 NaN, ToNumber转换规则参考ECMA规范:

isNaN(NaN)          // true
isNaN(undefined) // true, 因为 ToNumber(underfined) => NaN
isNaN(0) // false
isNaN(null) // false, 因为 ToNumber(null) => 0

Number.isNaN 会判断输入参数是否是number类型, 等同于如下代码:

if (!Number.isNaN) {
Number.isNaN = function(n) {
return (typeof n === "number" && window.isNaN( n ) );
};
}

// OR

if (!Number.isNaN) {
Number.isNaN = function(n) {
return n !== n;
};
}

Progress

  • 2018-12-09 P20,41