JavaScript 值

数组

数组可以容纳任何类型的值,可以是字符串、 数字、对象(object),甚至是其他数组(多维数组就是通过这种方式来实现的)

使用 delete 运算符可以将单元从数组中删除,但是请注意,单元删除后,数 组的 length 属性并不会发生变化。

可以通过下标对数组元素进行索引,但是同时,数组也是对象,所以可以通过点操作符以及[‘’]获取数组元素:

1
2
3
4
5
6
var a = [ ];
a[0] = 1;
a["foobar"] = 2;
a.length;
a["foobar"];
a.foobar;

需要注意的是,当使用[‘1’]数字字符串的性质,会被强制类型转换成十进制数字。

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

类数组

有时需要将类数组(一组通过数字索引的值)转换为真正的数组,这一般通过数组工具函数(如 indexOf(..)、concat(..)、forEach(..) 等)来实现。

Array.from() 方法返回一个数组复本

1
2
3
4
5

Array.from('dsdgarg')
(7) ["d", "s", "d", "g", "a", "r", "g"]
Array.from([1,2])
(2) [1, 2]

字符串

在JavaScript中字符串与字符串数组不是一回事,尽管这两者很相似。

1
2
var a = "foo";
var b = ["f","o","o"]

都有length属性以及indexOf()方法和concat(…) 方法

都可以通过b[1]获取元素, 但是低版本的IE中需要通过a. chartAt(1)方法获取指定下标的字符

一个有趣的事情,我们可以使用数组的方法来处理字符串

1
2
3
4
5
6
7
8
a.join;         // undefined
a.map; // undefined
var c = Array.prototype.join.call( a, "-" );
var d = Array.prototype.map.call( a, function(v){
return v.toUpperCase() + ".";
} ).join( "" );
c; // "f-o-o"
d; // "F.O.O."

字符串反转的例子,数组有一个方法reverse()

1
2
3
4
5
6
a.reverse;
b.reverse();
b;
// undefined
// ["!","o","O","f"]
// ["f","O","o","!"]

一个变通(破解)的办法是先将字符串转换为数组,待处理完后再将结果转换回字符串:

1
2
3
4
5
var c = a
// 将a的值转换为字符数组 .split( "" )
// 将数组中的字符进行倒转 .reverse()
// 将数组中的字符拼接回字符串 .join( "" );
c; // "oof"

这种方法的确简单粗暴,但对简单的字符串却完全适用。上述方法对于包含复杂字符(Unicode,如星号、多字节字符等)的 字符串并不适用。

数字

JavaScript 只有一种数值类型:number(数字),包括“整数”和带小数的十进制数

指数格式显示,toExponential() 函数

1
2
3
4
5
6
7
var a = 5E10;
a; // 50000000000
a.toExponential(); // "5e+10"
var b = a * a;
b; // 2.5e+21
var c = 1 / a;
c; // 2e-11

tofixed(..) 方法可指定小数部分的显示位数:

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

toPrecision(..) 方法用来指定有效数位的显示位数::

1
2
3
4
5
6
7
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"

较小的数值

0.1 + 0.2 === 0.3; // false

相加的结果是一个比较接近的数字0.30000000000000004,那么如何判断相加是相等的呢?

设置一个误差范围值:

从 ES6 开始,该值定义在 Number.EPSILON 中,我们可以直接拿来用,也可以为 ES6 之前
的版本写 polyfill:

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

使用 Number.EPSILON 来比较两个数字是否相等

1
2
3
4
5
6
7
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

数的极限范围

1.798e+308 5e-324(不是负数,但无限接近0)

整数的安全范围

能够被“安全”呈现的最大整数是 2^53 - 1,即 9007199254740991,在 ES6 中被定义为 Number.MAX_SAFE_INTEGER。最小整数是 -9007199254740991,在 ES6 中被定义为 Number. MIN_SAFE_INTEGER。

整数的检测

Number.isInteger(..)方法:

1
2
3
4
5
6
7
8
9
10
11
//ES6之前
if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
};
}

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

检测一个值是否是安全的整数,number.isSafeInteger(..) 方法:

1
2
3
4
5
6
7
8
9
10
11
12
if (!Number.isSafeInteger) {
Number.isSafeInteger = function(num) {
return Number.isInteger( num ) &&
Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
}; }

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

一些特殊的值

  • null:指的是空值,或者曾经赋值,但现在没有值,初始化时会赋值成null以便于和undefined区分

  • undefined:指的是没有值,从未赋值

  • void运算符:通过void运算符可以让表达式返回undefined

  • NaN:不是数字的数字,如果数学运算的操作数不是数字类型,就无法返回一个有效的数字,这种情况下返回值为NaN,推荐使用ES6中的Number.isNan(..)方法,以避免一些历史遗留的问题

  • -0+0: 转换让人迷惑,还是查文档吧

值和引用

引用就像一种特殊的指 针,是来指向变量的指针(别名)。如果参数不声明为引用的话,参数值总是通过值复制 的方式传递,即便对复杂的对象值也是如此。

简单值(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值/传递,包括 null、undefined、字符串、数字、布尔和 ES6 中的 symbol。

复合值(compound value)——对象(包括数组和封装对象,参见第3章)和函数,则总 是通过引用复制的方式来赋值 / 传递。

由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。

1
2
3
4
5
6
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]

引用指向的值本身,所以在函数参数传递的时候:

1
2
3
4
5
6
7
8
9
10
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]

我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。

值复制

如果通过值复制的方式来传递复合值(如数组),就需要为其创建一个复本,这样传递的 就不再是原始值。例如:

1
foo( a.slice() );

slice(..)不带参数会返回当前数组的一个浅副本,由于传递给函数的是指向该副本的引用,所以在函数中的操作不会影响到a指向的数组。

相反的,如果要将基本数据类型的值传递到函数内,并且修改之,就需要将该值封装套一个复合值(对象或者数组等)中,然后通过引用复制的方式传递

小结

null 类型只有一个值 null,undefined 类型也只有一个值 undefined。所有变量在赋值之 前默认值都是 undefined。void 运算符返回 undefined。

数字类型有几个特殊值,包括 NaN(意指“not a number”,更确切地说是“invalid number”)、+Infinity、-Infinity 和 -0。

简单标量基本类型值(字符串和数字等)通过值复制来赋值 / 传递,而复合值(对象等) 通过引用复制来赋值 / 传递。JavaScript 中的引用和其他语言中的引用 / 指针不同,它们不 能指向别的变量 / 引用,只能指向值。