# 数据类型

# js中的8种数据类型

目前阶段JavaScript中的数据类型,一共8种,主要分类两大类型:基本类型引用类型.

# 两大类型的区别

基本类型又叫做简单类型或者值类型,引用类型又叫做复杂类型;

TIP

基本类型(按值访问):在存储时变量中存储的是值本身,因此也叫做值类型;

引用类型(按引用访问):在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型。

(stack) :用来保存简单的数据字段

(heap) :用来保存栈中简单数据字段对指针的引用

区别:

TIP

  • 栈(操作系统): 由操作系统自动分配释放存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈;

    简单数据类型直接存放到栈里面。

  • 堆(操作系统): 存储复杂类型,一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收;

    复杂数据类型引用放在栈里面,实际数据存放到堆里面。

为啥会导致上述区别。

TIP

  • 基本类型的数据简单,所占用空间比较小,内存由系统自动分配;

  • 引用类型数据比较复杂,复杂程度是动态的,计算机为了较少反复的创建和回收引用类型数据所带来的损耗, 就先为其开辟另外一部分空间——即堆内存,以便于这些占用空间较大的数据重复利用;

  • 堆内存中的数据不会随着方法的结束立即销毁,有可能该对象会被其它方法所引用, 直到系统的垃圾回收机制检索到该对象没有被任何方法所引用的时候才会对其进行回收。

举个小栗子:

// 基本数据类型
let  a = 1;
let  b = a;
b = 2;
console.log(a, b) // 1, 2
1
2
3
4
5
let  obj1 = {a: 1, b: 2};
let  obj2 = obj1;
obj2.a = 20;
console.log(obj1.a, obj2.a)
1
2
3
4

# 两大类型的具体细分

两大类型细分图

TIP

基本数据类型的数据既只保存原始值,是没有函数可以调用的。

为什么说原始类型没有函数可以调用,但'1'.toString()却又可以在浏览器中正确执行?

TIP

因为'1'.toString()中的字符串'1'在这个时候会被封装成其对应的字符串对象,以上代码相当于new String('1').toString(),因为new String('1')创建的是一个对象,而这个对象里是存在toString()方法的。

详见 toString 详解

TIP

typeof 判断的原理

在 javascript 的最初版本中,使用的 32 位系统,为了性能考虑使用低位存储了变量的类型信息:

  • 000:对象
  • 1:整数
  • 010:浮点数
  • 100:字符串
  • 110:布尔

有 2 个值比较特殊:

  • undefined:用 - (−2^30)表示。
  • null:对应机器码的 NULL 指针,一般是全零。

# 1、null

现在很多书籍把null解释成空对象,是一个对象类型。然而在早期JavaScript的版本中使用的是 32 位系统,考虑性能问题,使用低位存储变量的类型信息,000开头代表对象,而null就代表全零,所以将它错误的判断成Object,虽然后期内部判断代码已经改变,但null类型为object的判断却保留了下来,至于null具体是什么类型,属于仁者见仁智者见智,你说它是一个bug也好,说它是空对象,是对象类型也能理解的通。

来个官方文档贴图: 关于  的官方解释截图

# 2、Symbol

Symbol 详解

  • 1、Symbol 本质上是一种唯一标识符,可用作对象的唯一属性名,这样其他人就不会改写或覆盖你设置的属性值。声明方法;
let id = Symbol("id");
1
  • 2、Symbol 数据类型的特点是唯一性,即使是用同一个变量生成的值也不相等。
let id1 = Symbol('id');
let id2 = Symbol('id');
console.log(id1 == id2);  //false
1
2
3
  • 3、Symbol 数据类型的另一特点是隐藏性,for···inobject.keys() 不能访问:
let id = Symbol("id");
 let obj = {
  [id]:'symbol'
 };
 for(let option in obj){
     console.log(obj[option]); //空
 }
1
2
3
4
5
6
7

但是也有能够访问的方法:Object.getOwnPropertySymbols, 该方法会返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

let id = Symbol("id");
let obj = {
 [id]:'symbol'
};
let array = Object.getOwnPropertySymbols(obj);
console.log(array); //[Symbol(id)]
console.log(obj[array[0]]);  //'symbol'
1
2
3
4
5
6
7

虽然这样保证了 Symbol 的唯一性,但我们不排除希望能够多次使用同一个 symbol 值的情况。为此,官方提供了全局注册并登记的方法:Symbol.for()

  • 3、Symbol.for();
let name1 = Symbol.for('name'); //检测到未创建后新建
let name2 = Symbol.for('name'); //检测到已创建后返回
console.log(name1 === name2); // true
1
2
3

通过这种方法就可以通过参数值获取到全局的 symbol 对象了,反之,能不能通过 symbol 对象获取到参数值呢?是可以的 ,通过 Symbol.keyFor()

  • 4、Symbol.keyFor()
let name1 = Symbol.for('name');
let name2 = Symbol.for('name');
console.log(Symbol.keyFor(name1));  // 'name'
console.log(Symbol.keyFor(name2)); // 'name'
1
2
3
4

最后,在创建symbol类型数据 时的参数只是作为标识使用,所以 Symbol() 也是可以的。

# 3、BigInt

  • 它是什么

BigInt 数据类型提供了一种方法来表示大于 2^53-1 的整数。BigInt 可以表示任意大的整数

  • 解决了什么问题

Number 类型只能安全的支持 -9007199254740991(-(2^53-1))9007199254740991(2^53-1) 之间的整数,任何超过这个范围的数值都会失去精度;而BigInt可以解决这个问题

console.log(9007199254740999) //9007199254741000
console.log(9007199254740993===9007199254740992) //true
1
2

如上,当数值超过 Number 数据类型支持的安全范围值时,将会被四舍五入,从而导致精度缺失的问题

  • 如何使用BigInt

方式一:在整数的末尾追加n

console.log(9007199254740999n); //9007199254740999
1

方式二:调用 BigInt() 构造函数

let bigInt = BigInt("9007199254740999"); //传递给BigInt()的参数将自动转换为BigInt
console.log(bigInt); //9007199254740999n   
1
2
  • 注意事项

1)、BigInt 除了不能使用一元加号运算符外,其他的运算符都可以使用

console.log(+1n); // Uncaught TypeError: Cannot convert a BigInt value to a number
console.log(-1n); //ok
1
2

2)、BigIntNumber 之间不能进行混合操作

console.log(1n+5)
1

如果希望使用 BigIntNumber 执行算术计算,首先需要确定应该在哪个类型中执行该操作。为此,只需通过调用 Number()BigInt() 来转换操作数:

BigInt(10) + 10n;    // → 20n
// 或者(在同一环境中操作)
10 + Number(10n);    // → 20
1
2
3
  • 总结:

1)、BigInt 数据类型提供了一种方法来表示大于 2^53-1 或者小于-2^53-1 的整数,BigInt 可以表示任意大的整数;

2)、不能使用 NumberBigInt 操作数的混合执行算术运算,需要通过显式转换其中的一种类型,使得两者在同一环境中操作;

3)、此外,出于兼容性原因,不允许在 BigInt 上使用一元加号(+)运算符。

# 4 Object(万物皆对象)

Js中常用的引用类型有:ObjectArrayFunctionDateRegExp

  • Object类型

带有属性和方法的特殊数据类型;

创建 Object 实例的方式有两种。第一种是使用 new 操作符后跟 Object 构造函数,例如;

let person = new Object();
person.name = "Nicholas";
person.age = 29;
console.log(person instanceof Object); // true
1
2
3
4

另一种方式是使用对象字面量表示法。例如:

let person = {
  name : "Nicholas",
  age ; 29
}
console.log(person instanceof Object); // true
1
2
3
4
5

WARNING

注意:在通过对象字面量定义对象时,实际上不会调用Object构造函数。

  • Array类型

是使用单独的变量名来存储一系列的值; 创建数组的基本方式有两种。第一种是使用 Array 构造函数,例如:

let colors = new Array();
console.log(colors instanceof Array);  // true
1
2

第二种基本方式是使用数组字面量表示法。数组字面量由一对包含数组项的方括号表示,多个数组项之间以逗号隔开,例如:

let colors = ["red","blue","green"];
console.log(colors instanceof Array);  // true
1
2
  • Function类型

函数类型在JavaScript中也是对象

函数实际上是对象,函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。函数通常是使用函数声明语法定义的:(函数声明提升)

function sum (sum1,sum2) {
  return sum1 + sum2;
}
console.log(sum instanceof Function); // true
1
2
3
4

还有一种方式,使用函数表达式定义函数:

let sum = function(sum1,sum2) {
  return sum1 +sum2 ;
};
console.log(sum instanceof Function); // true
1
2
3
4

# 数据类型的4种判断方法

# typeof

TIP

typeof能准确判断除null以外的原始类型的值,对于对象类型,除了函数会判断成function,其他对象类型一律返回object

typeof 1                        // number
typeof '1'                      // string
typeof true                     // boolean
typeof null                     // object---有点儿特殊,见下
typeof undefined                // undefined
typeof Symbol()                 // symbol
typeof 9007199254740999n        // bigint
typeof BigInt(9007199254740999) // bigint

typeof []                       // object
typeof {}                       // object
typeof console.log              // function
1
2
3
4
5
6
7
8
9
10
11
12

关于基础类型 null,使用 typeof 返回的是 object 的说明见:上文 null

# instanceof

TIP

instanceof通过原型链可以判断出对象的类型,但并不是百分百准确

function Person(name) {
  this.name = name;
}
var p1 = new Person();
console.log(p1 instanceof Person) // true
var str = new String('abc');
console.log(str instanceof String)// true

// 基本类型
console.log('1' instanceof String) // false
console.log(1 instanceof Number)  // false
console.log(true instanceof Boolean)  // false

// console.log(undefined instanceof undefined)
      // Uncaught TypeError: Right-hand side of 'instanceof' is not an object
// console.log(null instanceof null)
      // Uncaught TypeError: Right-hand side of 'instanceof' is not an object

console.log(typeof Symbol('id') instanceof Symbol)  // false
console.log(typeof 9007199254740999n instanceof BigInt)  // false
console.log(typeof BigInt(9007199254740999) instanceof BigInt)  // false

// 引用类型
console.log([] instanceof Array) 
console.log(function () {} instanceof Function) 
console.log({} instanceof Object) 
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

小结

instanceof 可以用于引用类型的检测,但对于基本类型是不生效的;

不能用于检测 nullundefined, 会抛错。

# isPrototypeOf()

isPrototypeOf() 是 Object函数(类)的下的一个方法,用于判断当前对象是否为另外一个对象的原型,如果是就返回 true,否则就返回 false。

这个函数理解的关键是在原型链上,这个据说是JavaScript的三座大山之一。

let o = new Object();
console.log(Object.prototype.isPrototypeOf(o)); // true

function Human() {}
let human = new Human();

console.log(Human.prototype.isPrototypeOf(human)); // true
// Human.prototype.__proto__ === Object.prototype
// huamn.__proto__  === Human.prototype
// huamn.__proto__.__proto__  === Object.prototype
1
2
3
4
5
6
7
8
9
10

与 instanceof, 两个表达的意思是一致的,就是写法不同。

JavaScript中isPrototypeOf函数详解 (opens new window)

# Object.getPrototypeOf()

要返回其原型的对象。

let arr = [];
Object.getPrototypeOf(arr) === Array.prototype;
1
2

# constructor

// 基本类型
console.log('1'.constructor === String) // true
console.log((1).constructor === Number) // true
console.log(true.constructor === Boolean) // true
// console.log(undefined.constructor === Boolean)
      // Uncaught TypeError: Cannot read properties of undefined (reading                 'constructor')
// console.log(null.constructor === Boolean)
      // Uncaught TypeError: Cannot read properties of undefined (reading                 'constructor')
console.log(Symbol('id').constructor === Boolean) // false
console.log(9007199254740999n.constructor === Boolean) // false
console.log(BigInt(9007199254740999).constructor === Boolean) // false

// 引用类型
console.log([].constructor === Array) // true
console.log(function () {}.constructor === Function) // true
console.log({}.constructor === Object) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

撇去 nullundefinedSymbolBigInt,似乎说 constructor 能用于检测js的基本类型和引用类型

但当涉及到原型和继承的时候,便出现了问题,如下:

function fun() {};

fun.prototype = new Array();

let f = new fun();

console.log(f.constructor===fun); // false
console.log(f.constructor===Array); // true
1
2
3
4
5
6
7
8

在这里,我们先是定义了一个函数 fun ,并将该函数的原型指向了数组,同时,声明了一个f为 fun 的类型,然后利用 constructor 进行检测时,会发现并不符合预期

# 小结

撇去 nullundefinedSymbolBigInt,似乎说 constructor 能用于检测js的基本类型和引用类型,但当对象的原型更改之后,constructor便失效了。

# Object.prototype.toString.call()

let test = Object.prototype.toString

// 基本类型
console.log(Object.prototype.toString.call("Picker"));//[object String]
console.log(Object.prototype.toString.call(12));//[object Number]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]

console.log(Object.prototype.toString.call(Symbol('Picker')));//[object Symbol]
console.log(Object.prototype.toString.call(9007199254740999n));//[object BigInt]
console.log(Object.prototype.toString.call(BigInt(9007199254740999)));//[object BigInt]

console.log(Object.prototype.toString.call({name: "Picker"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call(new Date));//[object Date]
console.log(Object.prototype.toString.call(/\d/));//[object RegExp]

function Person(){};
console.log(Object.prototype.toString.call(new Person));//[object Object]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

得到结果之后, 你可以通过:xxx.slice(8, -1).toLowerCase(),就可拿来正常使用咯;

我们看下继承之后是否能检测出来:

function fun() {};

fun.prototype = new Array();

let f = new fun();

console.log(Object.prototype.toString.call(fun)) // [object Function]
console.log(Object.prototype.toString.call(f))   // [object Object]
1
2
3
4
5
6
7
8

可以看出,Object.prototype.toString.call()可用于检测js所有的数据类型,完美~

WARNING

无法区分自定义对象类型,自定义类型可以采用 instanceof 区分

关于 toString 可以参考: toString 详解

# Summary

数据类型 & 精准检测

建议使用说明