JS数据类型

Posted by Rimin on 2019-04-18

概述

js基本类型: Undefined、Null、Boolean、Number、String、Symbol

js引用类型: Object (Object类型,Array类型,Date类型,Function类型,RegExp类型)

存储

  • 基本数据类型占 8字节,存储在栈内存
    如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var num1 = 5;
var num2 = num1;

|______|___|
| | |
|______|___|
| num1 | 5 |
|______|___|


|______|___|
| num2 | 5 |
|______|___|
| num1 | 5 |
|______|___|
  • 引用类型变量只保存对对象,数组,和函数等引用类型的值得引用(即指针或者说内存地址),而引用类型的值保存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var obj1 = new Object
var obj2 = obj1
obj1.name = 'Jack'
console.log(obj2.name) // 'Jack'

栈 堆
_______________
|______|_______| | |--------—| |
| | | _____\| | Obj1 | |
|______|_______| / /| |name:jack| |
| obj1 | x0c01 |/ | --------- |
|______|_______| |_______________|


________________
|______|_______| | |--------—| |
| obj2 | x03c1 |--\ _____\| | Obj1 | |
|______|_______| / /| |name:jack| |
| obj1 | x0c01 |/ | --------- |
|______|_______| |_______________|
  • 二进制浮点数

JavaScript 中的数字类型是基于“二进制浮点数”实现的, 64 bit , 8 字节 使用的是“双精度”格式。
浮点数值最高精度是17位小数,但在进行计算时其精确度远远不如整数。会有舍入误差

1
2
3
4
5
6
7
8
9
10
11
var a = 0.1 + 0.2; // 结果是0.30000000000000004
var b = 0.3;
console.log(a === b); //false
console.log(Number.isInteger(a*10)); //false
console.log(Number.isInteger(b*10)); //true
console.log(a); //0.30000000000000004



var f = 1.1
f-- //0.10000000000000009 (由于浮点舍入错误所致)

数据类型的判别

  • NaN属于Number类型,并且 NaN不等于任何值,包括它自己
  • typeof: (判断不了引用类型)除了null都可以正确判断类型
1
2
3
4
5
typeof null // "object"
/***为什么会出现这种情况呢?因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。****/
typeof [1, 2, 3] // "object"
typeof b // "undefined" 虽然没有第一但是还是会显示为undefined类型
typeof NaN // "number"
  • instanceof (通过原型链来判断)
1
2
3
4
5
6
(function (){}) instanceof Function // true

[1, 2, 3, 4] instanceof Array // true
new Number(1) instanceof Number // true
new Boolean(true) instanceof Boolean // true
new String('123') instanceof String // true

当然,

1
[] instanceof Object; // true

因此,instanceof 只能用来判断对象类型,原始类型不可以,所有的对象类型instaceof Object 都是true

  • constructor(通过构造器判断,但是没有绝对可靠性)

  • Object.prototype.toString (这个方法只能判断 js 的内置数据类型)

1
2
3
4
5
6
7
Object.prototype.toString.call(123) // "[object Number]"
Object.prototype.toString.call('string') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(Symbol(12)) // "[object Symbol]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call([1,2,3]) // "[object Array]"
Object.prototype.toString.call(function(){}) // "[object Function]"
  • 通过 Object.getPrototypeOf(a) === Array.prototype 判断引用类型
  • Array.prototype.isPrototypeOf(a) 判断引用类型
1
2
3
4
var a  = [];
Array.prototype.isPrototypeOf(a);

Object.getPrototypeOf(a) === Array.prototype;

数组还有一个方法,是es5新增的方法:

1
Array.isArray()

类型转换

JavaScript是动态类型,变量是没有类型的,可以随时赋予任意值

js类型转换有显式和隐式转换,都会遵循一定的原理。

隐式转换

先来看下面的代码:😲

1
2
3
4
5
6
7
8
9
{}+[] //0
[] + {} // "[object Object]"
var a = {}+[]
console.log(a) // [object Object]
console.log(typeof a) // string

[] == false // true

{} == true // true
  1. 条件判断: 在条件判断时,除了 undefinednullfalseNaN''0-0,其他所有值都转为 true,包括所有对象。

    1
    2
    3
    if(undefined) {console.log("false, 不执行")}

    if([] || {}) {console.log("true, 执行")}
  2. 隐式转换原理判断
    若想从源头出发,理解一些怪异的类型转换,就需要看 ECMA-262详细的规范,从而探究其内部原理,我们从这段内部原理示意代码开始:

原理代码
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
43
44
45
46
47
// ECMA-262, section 9.1, page 30. Use null/undefined for no hint,
// (1) for number hint, and (2) for string hint.
function ToPrimitive(x, hint) {
// Fast case check.
if (IS_STRING(x)) return x;
// Normal behavior.
if (!IS_SPEC_OBJECT(x)) return x;
if (IS_SYMBOL_WRAPPER(x)) throw MakeTypeError(kSymbolToPrimitive);
if (hint == NO_HINT) hint = (IS_DATE(x)) ? STRING_HINT : NUMBER_HINT;
return (hint == NUMBER_HINT) ? DefaultNumber(x) : DefaultString(x);
}

// ECMA-262, section 8.6.2.6, page 28.
function DefaultNumber(x) {
if (!IS_SYMBOL_WRAPPER(x)) {
var valueOf = x.valueOf;
if (IS_SPEC_FUNCTION(valueOf)) {
var v = %_CallFunction(x, valueOf);
if (IsPrimitive(v)) return v;
}

var toString = x.toString;
if (IS_SPEC_FUNCTION(toString)) {
var s = %_CallFunction(x, toString);
if (IsPrimitive(s)) return s;
}
}
throw MakeTypeError(kCannotConvertToPrimitive);
}

// ECMA-262, section 8.6.2.6, page 28.
function DefaultString(x) {
if (!IS_SYMBOL_WRAPPER(x)) {
var toString = x.toString;
if (IS_SPEC_FUNCTION(toString)) {
var s = %_CallFunction(x, toString);
if (IsPrimitive(s)) return s;
}

var valueOf = x.valueOf;
if (IS_SPEC_FUNCTION(valueOf)) {
var v = %_CallFunction(x, valueOf);
if (IsPrimitive(v)) return v;
}
}
throw MakeTypeError(kCannotConvertToPrimitive);
}

上面代码的逻辑是这样的:

  1. 如果变量为字符串,直接返回.
  2. 如果!IS_SPEC_OBJECT(x),直接返回.
  3. 如果IS_SYMBOL_WRAPPER(x),则抛出异常
  4. 否则会根据传入的hint来调用DefaultNumber和DefaultString,比如如果为Date对象,会调用DefaultString
  5. DefaultNumber:首先x.valueOf,如果为primitive,则返回valueOf后的值,否则继续调用x.toString,如果为primitive,则返回toString后的值,否则抛出异常
  6. DefaultString:和DefaultNumber正好相反,先调用toString,如果不是primitive再调用valueOf.

比如:

1
2
3
1 + {} // "1[object Object]"

1 + [] // "1"

{}和1首先会调用ToPrimitive {}会走到DefaultNumber,首先会调用valueOf,返回的是Object {},不是primitive类型,从而继续走到toString,返回[object Object],是String类型 最后加操作,结果为[object Object]1 再比如有人问你[] + 1输出啥时,你可能知道应该怎么去计算了,先对[]调用ToPrimitive,返回空字符串,最后结果为"1"。

  1. 四则运算符

    • + 有字符串,另一方也转换为字符串。如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
    • + - * /: 只要其中一方是数字,那么另一方就会被转为数字
  2. 比较运算符

    x == y,其中x和y是值

    1. 若Type(x)与Type(y)相同,则
      • 若Type(x)为Udefined,返回true
      • 若Type(x)为Null, 返回true
      • 若Type(x)为Number,则
        • xNaN, 返回false
        • yNaN, 返回false
        • xy为相等数值,返回true
        • x+0y-0,或x-0y+0,返回true
    2. 若x为null且y为Undefined,返回true,反之同理,即null == undefined
    3. 若Type(x)为Number且Type(y)为String, 比较 x == ToNumber(y)的结果,反之同理
    4. 若 Type(x) 为Boolean,返回比较ToNumber(x) == y 的结果,反之同理
    5. 若Type(x) 为String或 Number且Type(y)为 Object,返回比较x == ToNumber(y)的结果,反之同理
    6. 返回 false

验证:

1
2
3
4
5
6
7
8
9
'' == false // true
'0' == false // ture
[] == false // true
'' == 0 // true
'0' == 0 // true
0 == [] // true
'' == [] // true
null == undefined // true
{} == true // true
显示转换

类型转换表

value string number boolean object
undefined “undefined” NaN false throws TypeError
null “null” 0 false throws TypeError
true “true” 1 true new Boolean(false)
false “false” 0 false new Boolean(false)
“” 0 false new String(’’)
0 (-0) “0” false new Number(0)
NaN ‘NaN’ false new Number(NaN)
Infinity “Infinity” true new Number(Infinity)
‘’ 0 true
[9] ‘9’ 9 true

例子:

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
Object(undefined) // {} 返回一个空对象
Object(null) // {} 返回一个空对象

var n = 123456.789
n.toFixed(0) // "123457"
n.toExponential(1) // "1.2e+5"
n.toPrecision(5) // "1.2346e+5"

n = 1000000000000000000000000000000
parseInt(n) // 1 因为这里会将n先用科学计数法转换成 "1e+30" 然后再parseInt截断成 1
n.toString() // "1e+30"

[1,2,3].toString() // "1,2,3"


var a = new Boolean(false)
var b = new Number(0)
var c = new String("")
a && b && c // 1
// a, b, c为假值对象

a === false // false 这里由于类型不同,当然不相等
a == false // true 这里不是严格相等,而是将他们进行转换后值相等


var c = {
valueOf(){
return 123
},
toString(){
return 456
}
}
Number(c) // 123 // 这个例子也说明了上面转换的原理。且可以改写

在 TypeScript中的应用

TypeScript 是 JavaScript 的一个超集(如果一个集合S2中的每一个元素都在集合S1中,且集合S1中可能包含S2中没有的元素,则集合S1就是S2的一个超集),主要提供了类型系统和对 ES6 的支持
可以在编译阶段就发现大部分错误,比在运行时候出错好

熟悉javascript的类型相关后,可以基于其学习 ts对其变量类型的定义规则
例子:

  • 举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let isDone: boolean = false;

let createdByNewBoolean: boolean = new Boolean(1); // 错误,属于对象

let notANumber: number = NaN;

let infinityNumber: number = Infinity;

let binaryLiteral: number = 0b1010; => var binaryLiteral = 10; // 二级制编译成十进制

let octalLiteral: number = 0o744; => var octalLiteral = 484; // 八进制编译成 十进制

let u: undefined = undefined;
let n: null = null;
1
2
3
4
5
6
7
8
9
10
11
12
// 内置对象
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;

// DOM 和 BOM Document、HTMLElement、Event、NodeList
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
  • 不同点:
    ts 新增了很多对类型的控制,它更像是一种代码类型规范约束手段或者说代码检查的工具。

比如 void接口(Interfaces) 类型断言 联合类型 字符串字面量类型,元组枚举(Enum)类型 类的public、private 和 protected的定义

PS: 初识typescript: 个人对 typescript的思考: 首先代码类型检查是一个很好的习惯, 平时在开发中应当多注意类型的判断,否则很有可能在一些公共组件中会有因为类型错误而无法运行出错的情况。但是因为只是静态类型的检测,因此还是避免不了一些类型错误,比如运行时。因此,个人感觉单靠typescript是无法解决所有类型变量出问题而出现Bug的情况。另外,代码的易读性会有所降低,重构成本和维护成本也会比较大。但是代码质量,特别是在团队合作时更能有保证。