基本类型

JavaScript 的每一个都属于某一种数据类型

  1. Undefined
  2. Null
  3. Boolean
  4. String
  5. Number
  6. Symbol – ES6
  7. Object

Undefined + Null

Undefined

编程规范:使用 void 0 代替 undefined

  1. Undefined 代表未定义,只有一个值undefined
    • undefined全局变量,但并非关键字 – 语言设计缺陷
  2. 任何变量在赋值前的类型为是 Undefined,值为 undefined
  3. void 运算可以将任意表达式变成 undefined
1
console.log(void 0 === undefined) // true

Null

Null 代表定义了但为空,只有一个值null,null 是关键字

Boolean

Boolean 只有两个值,truefalse,且均为关键字

String

Unicode + UTF

  1. Unicode 为字符集,每一个 Unicode 码点表示一个字符
    • U+???
    • 基本字符区域(BMP):U+0000 ~ U+FFFF
  2. UTF 为 Unicode 的编码方式规定码点在计算机中的表示方式,常见为 UTF-8UTF-16

String

  1. String 的最大长度为2^53-1编码长度)
  2. String 为值类型,永远无法变更
  3. String 把每个 UTF-16 单元当作一个字符来处理 – From Java

Number

对应数学中的有理数,但在计算机中,有一定的精度限制

  1. NaN:占用特定数字
  2. Infinity:无穷大;-Infinity:负无穷大
    • 1/0 ==> Infinity
    • 1/-0 ==> -Infinity
  3. 浮点数存在精度误差
1
2
3
console.log(0.1 + 0.2 == 0.3) // false
console.log(0.1 + 0.2 === 0.3) // false
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON) // true

Symbol

ES6 引入,是一切非字符串的对象Key的集合,整个对象系统被 Symbol 重塑

Symbol() 函数前不能使用 new,因为生成的 Symbol 是一个原始类型的值,不是对象
Symbol值不是对象,所以也不能添加属性,类似于字符串的值类型

1
2
3
let a1 = Symbol('A');
let a2 = Symbol('A');
console.log(a1 === a2) // false

样例:使用 Symbol.iterator 来定义 for...of对象上的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let o = {}

o[Symbol.iterator] = function () {
let v = 0;
return {
next: function () {
return {
value: v++,
done: v > 3
}
}
}
}

for (let v of o) {
console.log(v); // 0 1 2
}

如果 Symbol 的参数是一个对象,会调用该对象的 toString()方法,将其转换为字符串,然后生成一个 Symbol 值

1
2
3
4
5
6
7
8
const obj = {
toString() {
return "abc";
}
};

const sym = Symbol(obj)
console.log(sym) // Symbol("abc")

Symbol() 函数的参数只是表示对当前 Symbol 值的描述,相同参数的 Symbol 函数的返回值是不相等

1
2
3
4
5
6
7
let s1 = Symbol();
let s2 = Symbol();
console.log(s1 === s2) // false

let a1 = Symbol('A')
let a2 = Symbol('A')
console.log(a1 === a2) // false

Symbol 不能与其他类型的值进行运算

1
2
3
let s = Symbol();
console.log('symbol: ' + s) // TypeError: can't convert symbol to string
console.log(`symbol: ${s}`) // TypeError: can't convert symbol to string

Symbol 可以显式转成字符串

1
2
3
let s = Symbol('A');
console.log(String(s)) // Symbol(A)
console.log(String(s).toString()) // Symbol(A)

Symbol 可以转成布尔值

1
2
3
4
5
let s = Symbol('A');
console.log(Boolean(s)) // true

Number(s) // TypeError: can't convert symbol to number
s + 2 // TypeError: can't convert symbol to number

Object

对象的定义为属性的集合,属性分为数据属性访问器属性,都是 KV 结构,Key 可以为 String 或者 Symbol

JavaScript 是无法自定义类型的,『类』仅仅只是运行时对象的一个私有属性

NumberStringBoolean,这三个构造器是两用的

  1. new 搭配:产生对象
  2. 直接调用:表示强制类型转换
1
2
3
console.log(typeof 3) // number
console.log(typeof Number(3)) // number
console.log(typeof new Number(3)) // object

Symbol 如果与 new 搭配,会直接报错

1
2
// console.log(typeof new Symbol('A')) // Uncaught TypeError: Symbol is not a constructor
console.log(typeof Symbol('A')) // symbol

JavaScript 试图模糊对象基本类型之间的关系:可以在基本类型上使用对象的方法 - 装箱转换

1
2
3
let s = 'abc'
console.log(typeof s) // string
console.log(s.charAt(0)) // a

可以在原型上添加方法,并应用于基本类型

1
2
3
4
5
Symbol.prototype.hello = () => console.log('hello js')

let symbol = Symbol('A');
console.log(typeof symbol); // symbol,并非 object
symbol.hello(); // hello js

根因:.运算符提供了装箱操作,会根据基础类型构造一个临时对象,使得能在基础类型上调用对应对象的方法

类型转换

JavaScript 为弱类型动态语言,发生类型转换非常频繁

71bafbd2404dc3ffa5ccf5d0ba077720

==

语言设计缺陷:试图实现跨类型的比较,规则非常复杂

最佳实践:进行显式类型转换后,再使用 ===进行比较

StringToNumber

大多数情况下,Number 是比 parseIntparseFloat 更好的选择

1
2
3
4
// 十进制、二进制、八进制、十六进制
console.log(30, 0b111, 0o13, 0xFF); // 30 7 11 255
// 科学计数法(十进制)
console.log(1e3, -1e-2); // 1000 -0.01

parseInt 默认只支持 16 进制,且会忽略非数字字符,也不支持科学计数法

1
2
3
4
5
6
7
8
console.log(parseInt('0xFF')) // 255
console.log(parseInt('0xFFxxxx')) // 255
console.log(parseInt('0b13')) // 0
console.log(parseInt('1e3')) // 1

// 显式指定进制
console.log(parseInt('111', 2)) // 7
console.log(parseInt('13', 8)) // 11

parseFloat 直接把字符串作为十进制来解析,不会引入其它进制

1
2
3
4
5
6
7
console.log(parseFloat('123.45')) // 123.45
console.log(parseFloat('123.45xxx')) // 123.45
console.log(parseFloat('1e3')) // 1000
console.log(parseFloat('-1e-2')) // -0.01
console.log(parseFloat('0xFF')) // 0
console.log(parseFloat('0b111')) // 0
console.log(parseFloat('0o13')) // 0

NumberToString

当 Number 的绝对值较大或者较小的时候,将使用科学计数法表示(保证产生的字符串不会过长

1
2
console.log(String(1e256)) // 1e+256
console.log(String(1e-256)) // 1e-256

装箱转换

  1. 每一种基本类型NumberStringBooleanSymbol在对象中都有对应的类
  2. 装箱转换:将基本类型转换为对应的对象
  3. 装箱机制会频繁产生临时对象,在高性能场景,应该尽量避免对基本类型做装箱转换

Symbol

1
2
3
4
5
6
7
8
let s = (function () {
return this;
}).call(Symbol('A'));

console.log(typeof s); // symbol
console.log(s instanceof Object) // false
console.log(s instanceof Symbol) // false
console.log(s.constructor === Symbol) // true

通过内置的Object函数,可以显式调用装箱能力

1
2
3
4
5
let o = Object(Symbol('A'))

console.log(typeof o); // object
console.log(o instanceof Symbol); // true
console.log(o.constructor === Symbol); // true

每一类装箱对象都有私有的Class属性,可以通过 Object.prototype.toString 来获取
在 JavaScript 中,无法更改私有的 Class 属性,比 instanceof 本身更准确
call 本身会产生装箱操作,需要配合typeof来区分基本类型还是对象类型

1
2
3
let o = Object(Symbol('A'))

console.log(Object.prototype.toString.call(o)) // [object Symbol]

拆箱转换

ToPrimitive:对象类型 -> 基础类型

对象StringNumber 的转换:先拆箱再转换(对象 -> 基本类型 -> String/Number)

拆箱转换会尝试调用valueOftoString来获得拆箱后的基本类型
如果 valueOftoString 都不存在,或者没有返回基本类型,会产生类型错误 TypeError

valueOf -> toString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let o = {
valueOf: () => {
console.log("try valueOf");
return {}; // 返回对象类型
},
toString: () => {
console.log("try toString");
return {}; // 返回对象类型
}
}

// try valueOf
// try toString
// TypeError: can't convert o to number
o * 2

toString -> valueOf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let o = {
valueOf: () => {
console.log("try valueOf");
return {}; // 返回对象类型
},
toString: () => {
console.log("try toString");
return {}; // 返回对象类型
}
}

// try toString
// try valueOf
// TypeError: can't convert o to string
console.log(String(o));

在 ES6 之后,允许对象通过显式指定 @@toPrimitive Symbol覆盖原有的拆箱行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let o = {
valueOf: () => {
console.log("try valueOf");
return {};
},
toString: () => {
console.log("try toString");
return {};
}
}

o[Symbol.toPrimitive] = () => {
console.log("try toPrimitive");
return "hello js";
}

// try toPrimitive
// hello js
console.log(o + "")

typeof

同样为语言的设计缺陷:typeof 的运算结果,与运行时数据类型有较多不一致的地方

示例表达式 typeof 结果 运行时类型
null object Null
(function(){}) function Object
void 0 undefined Undefined
Symbol("A") symbol Symbol
{} object Object
3 number Number
“ok” string String
true boolean Boolean