JavaScript是个动态语言,它的变量是没有类型的,但变量代表的值是有类型的。不同的类型值使得变量拥有特定的能力。

JavaScript内置7种数据类型:

  • null — 空值
  • undefined — 未定义
  • boolean — 布尔值
  • number — 数字
  • string —字符串
  • object — 对象
  • symbol — 符号

注意:数组和方法都属于object

通过 typeof可以获得一个变量的值类型。

1
2
3
4
5
6
7
8
typeof undefined     === "undefined"; // true
typeof true === "boolean"; // true
typeof 42 === "number"; // true
typeof "42" === "string"; // true
typeof { life: 42 } === "object"; // true
typeof Symbol() === "symbol"; // true

typeof null === "object"; // true

null值类型是object这是个JS语言的bug。完整验证一个值类型是否是null可以用:

1
2
3
var a = null;

(!a && typeof a === "object"); // true

函数对象的 length 属性是其声明的参数的个数。

数组

JavaScript的数组可以保存任何类型的值,可以是字符串数字对象数组

JavaScript中的数组的索引可以不是数字,字符串也是可以的。但它不算数组的长度。

1
2
3
4
5
6
7
8
var a = [];

a[0] = 1;
a["foobar"] = 2;

a.length; // 1
a["foobar"]; // 2
a.foobar; // 2

如果索引是可以转换成十进制数字的类型,它会被当作数字索引处理。

1
2
3
4
5
var a = [];

a["13"] = 42;

a.length; // 14

JavaScript中有一些类数组(一组通过数字索引的值),例如一些 DOM 查询操作会返回 DOM 元素列表,它们并非真正意义上的数组,但十分类似。可以通过var arr = Array.prototype.slice.call( arguments );或ES6中的var arr = Array.from( arguments );

字符串

JavaScript中的字符串看起来和数组很相像,它也可以通过索引下标取具体位置的字符(IE可能不兼容),它拥有length属性和indexOf()concat()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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; // "foo"
b; // ["f","o","o"]

a[1] = "O";
b[1] = "O";

a; // "foo" 字符串是不可变的
b; // ["f","O","o"]
// 字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。
c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"

b.push( "!" );
b; // ["f","O","o","!"]

//许多数组函数用来处理字符串很方便。虽然字符串没有这些函数,但可以通过“借用”数组的非变更方法来处理字符串:
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."

不建议把字符串当做数组使用,因为字符串在JavaScript是不可变的。它是保存在内存栈中,这些值在内存分配时有固定的大小。而数组在内存中是引用类型,它的值保存在堆内存中,它的大小是不固定的。

如果需要经常使用数组的方法,就不如开始就定义为数组最后通过join('')转换成字符串。

数字

JavaScript中数字包括,整数和带小数的十进制数。如果整数为是0,着可以省略。

通过tofixed(..)你可以指定小数部分显示的位数。

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

注意: 42.toFixed(3) 是无效语法,因为 . 被视为常量 42. 的一部分(如前所述),所以没有 . 属性访问运算符来调用 tofixed 方法。

42..toFixed(3) 则没有问题,因为第一个 . 被视为 number 的一部分,第二个 . 是属性访问运算符。

ES6中提供了Number.isInteger(..)用于判断一个数字是否是整数:

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

它的polyfill版本是:

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

NaN是JavaScript中很特殊的一个值,它表示的意思是:不是数字的数字。它与自己都不相等。一般在两种无法进行转换的值类型进行计算时会出现它。

1
2
3
4
5
var a = 2 / "foo";      // NaN

typeof a === "number"; // true
a == NaN; // false
a === NaN; // false

ES6 开始我们可以使用工具函数 Number.isNaN(..)检测一个值是否是NaN。ES6 之前的浏览器的 polyfill 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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——好!

类型转换

toString

1
2
3
4
5
6
7
8
/** 数字转换成 字符串 **/
var s =28;
s+'' // '28' string
var s1 = 28;
String(s1) // '28' string
new String(s1) // '28' Object new String() 会做一次封装操作,返回的是个对象
s1.toSting() // '28' string
JSON.stringify(28) // '28' string

对于普通对象而言,转换成字符串是调用内部的toString()方法,如果没有被定义则返回内部的[[class]]的值。

数组的默认 toString() 方法经过了重新定义,将所有单元字符串化以后再用 "," 连接起来:

1
2
var a = [1,2,3];
a.toString(); // "1,2,3" 和调用join()效果一样

工具函数 JSON.stringify(..) 在将 JSON 对象序列化为字符串时也用到了 ToString。非字符串的基本类型转换它和直接调用toSting()没啥区别。

1
2
3
4
JSON.stringify( 42 );   // "42"
JSON.stringify( "42" ); // ""42"" (含有双引号的字符串)
JSON.stringify( null ); // "null"
JSON.stringify( true ); // "true"

JSON.stringify(..) 在对象中遇到 undefinedfunctionsymbol 时会自动将其忽略,在数组中则会返回 null(以保证单元位置不变)。

1
2
3
4
5
6
7
8
9
JSON.stringify( undefined );      // undefined
JSON.stringify( function(){} ); // undefined

JSON.stringify(
[1,undefined,function(){},4]
); // "[1,null,null,4]"
JSON.stringify(
{ a:2, b:function(){} }
);

如果对象中定义了toJSON()方法,JSON.stringify()会去执行该方法进行序列话。这样就可以避免循环引用的对象执行JSON.Stringify()方法时报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var o = { };

var a = {
b: 42,
c: o,
d: function(){}
};

// 在a中创建一个循环引用
o.e = a;

// 循环引用在这里会产生错误
// JSON.stringify( a );

// 自定义的JSON序列化
a.toJSON = function() {
// 序列化仅包含b
return { b: this.b };
};

JSON.stringify( a ); // "{"b":42}"

我们可以向 JSON.stringify(..) 传递一个可选参数 replacer,它可以是数组或者函数,用来指定对象序列化过程中哪些属性应该被处理,哪些应该被排除,和 toJSON() 很像。

如果 replacer 是一个数组,那么它必须是一个字符串数组,其中包含序列化要处理的对象的属性名称,除此之外其他的属性则被忽略。

如果 replacer 是一个函数,它会对对象本身调用一次,然后对对象中的每个属性各调用一次,每次传递两个参数,键和值。如果要忽略某个键就返回 undefined,否则返回指定的值。

1
2
3
4
5
6
7
8
9
10
11
12
var a = {
b: 42,
c: "42",
d: [1,2,3]
};

JSON.stringify( a, ["b","c"] ); // "{"b":42,"c":"42"}"

JSON.stringify( a, function(k,v){
if (k !== "c") return v;
} );
// "{"b":42,"d":[1,2,3]}"

(1) 字符串、数字、布尔值和 nullJSON.stringify(..) 规则与 ToString 基本相同。

(2) 如果传递给 JSON.stringify(..) 的对象中定义了 toJSON() 方法,那么该方法会在字符串化前调用,以便将对象转换为安全的 JSON 值。

toNumber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Number(true) // 1
Number(false) // 0
Number(undefined) // NaN
Number(null) // 0
Number('ggg') // NaN

var c = "3.14";
var d = +c;
d // 3.14 number

var timestamp = +new Date(); // 获取当前时间戳 Date.now();

var a = "42";
var b = "42px";

Number( a ); // 42
parseInt( a ); // 42

Number( b ); // NaN
parseInt( b ); // 42

对于对象转换成数字,JavaScript会将如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。

如果 valueOf()toString() 均不返回基本类型值,会产生 TypeError 错误。

从 ES5 开始,使用 Object.create(null) 创建的对象 [[Prototype]] 属性为 null,并且没有 valueOf()toString() 方法,因此无法进行强制类型转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var a = {
valueOf: function(){
return "42";
}
};

var b = {
toString: function(){
return "42";
}
};

var c = [4,2];
c.toString = function(){
return this.join( "" ); // "42"
};

Number( a ); // 42
Number( b ); // 42
Number( c ); // 42
Number( "" ); // 0
Number( [] ); // 0
Number( [ "abc" ] ); // NaN

ToBoolean

假值:

1
2
3
4
5
6
7
!!undefined // false
!!null // false
!!+0 // false
!!-0 // false
!!NaN // false
!!'' // false
!!false // false

注意: 经过new Boolean()封装后,上面的假植会变成真值。理论上出了价值之外的值都将是真值。

其它奇怪的转换

~字位操作“非”,用于字符反转。

~x当会x将类型转换成数字类型,最后进行-(x+1)。如果x不能转换成正常的数字类型,就会报错。

-1的在JavaSript是个很常见的数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = "Hello World";

~a.indexOf( "lo" ); // -4 <-- 真值!

if (~a.indexOf( "lo" )) { // true
// 找到匹配!
}

~a.indexOf( "ol" ); // 0 <-- 假值!
!~a.indexOf( "ol" ); // true

if (!~a.indexOf( "ol" )) { // true
// 没有找到匹配!
}

parseInt

解析字符串中的数字和将字符串强制类型转换为数字的返回结果都是数字。但解析和转换两者之间还是有明显的差别。

1
2
3
4
5
6
7
8
var a = "42";
var b = "42px";

Number( a ); // 42
parseInt( a ); // 42

Number( b ); // NaN
parseInt( b ); // 42

解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回 NaN

|| 和 &&

&&|| 运算符的返回值并不一定是布尔类型,而是两个操作数其中一个的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = 42;
var b = "abc";
var c = null;

a || b; // 42
a && b; // "abc"

c || b; // "abc"
c && b; // null

function foo() {
console.log( a );
}

var a = 42;

a && foo(); // 42 foo() 只有在条件判断 a 通过时才会被调用。如果条件判断未通过,a && foo() 就会戛然终止(也叫作“短路”,short circuiting),foo() 不会被调用。

参考

你不知道的JavaScript(中卷)