数据类型

12/26/2022

在JavaScript中,我们可以分成两种类型:

  • 基本类型
  • 引用类型

两种类型的区别是:存储位置不同

# 基本类型

基本类型主要为以下几种:

  • Number
  • String
  • Boolean
  • Undefined
  • Null
  • Symbol(es6新增)
  • BigInt(es10新增)

# Number

数值最常见的整数类型格式则为十进制,还可以设置八进制(零开头)、十六进制(0x开头)

let intNum = 55 // 10进制的55
let num1 = 070 // 8进制的56
let hexNum1 = 0xA //16进制的10
1
2
3

浮点类型则在数值汇总必须包含小数点,还可通过科学计数法表示

let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; // 有效,但不推荐
let floatNum = 3.125e7; // 等于 31250000
1
2
3
4

在数值类型中,存在一个特殊数值NaN,意为“不是数值”,用于表示本来要返回数值的操作失败了(而不是抛出错误)

console.log(0/0); // NaN
console.log(-0/+0); // NaN
1
2

# String

字符串可以使用双引号(")、单引号(')或反引号(`)标示

let firstName = "John";
let lastName = 'Jacob';
let lastName = `Jingleheimerschmidt`
1
2
3

字符串是不可变的,意思是一旦创建,它们的值就不能变了

let lang = "Java";
lang = lang + "Script";  // 先销毁再创建
1
2

# Boolean

Boolean(布尔值)类型有两个字面值: true 和false
通过Boolean可以将其他类型的数据转化成布尔值
规则如下:

数据类型 转换为 true 的值 转换为 false 的值
String 非空字符串 ""
Number 非零数值(包括无穷值) 0 、 NaN
Object 任意对象 null
Undefined N/A (不存在) undefined

# Undefined

Undefined 类型只有一个值,就是特殊值 undefined。当使用 var或 let声明了变量但没有初始化时,就相当于给变量赋予了 undefined值

let message;
console.log(message == undefined); // true
1
2

包含undefined 值的变量跟未定义变量是有区别的

let message; // 这个变量被声明了,只是值为 undefined

console.log(message); // "undefined"
console.log(age); // 没有声明过这个变量,报错
1
2
3
4

# Null

Null类型同样只有一个值,即特殊值 null
逻辑上讲, null 值表示一个空对象指针,这也是给typeof传一个 null 会返回 "object" 的原因

let car = null;
console.log(typeof car); // "object"
1
2

undefined 值是由 null值派生而来

console.log(null == undefined); // true
1

只要变量要保存对象,而当时又没有那个对象可保存,就可用 null来填充该变量

# Symbol(es6新增)

Symbol (符号)是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险

let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();
console.log(genericSymbol == otherGenericSymbol); // false

let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');
console.log(fooSymbol == otherFooSymbol); // false
1
2
3
4
5
6
7

# BigInt(es10新增)

BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数。而在其他编程语言中,可以存在不同的数字类型,例如:整数、浮点数、双精度数或大斐波数。
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回Infinity。

// 超过 53 个二进制位的数值,无法保持精度
Math.pow(2, 53) === Math.pow(2, 53) + 1 // true
 
// 超过 2 的 1024 次方的数值,无法表示
Math.pow(2, 1024) // Infinity
1
2
3
4
5

ES2020 引入了一种新的数据类型 BigInt,来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
为了与 Number 类型进行区分,BigInt 类型的数据必须添加后缀n。

12 	// 普通Number
12n // BigInt
 
// BigInt 的运算
1n + 2n // 3n

// 与Number 类型进行运算
1 + 1n // Uncaught TypeError
1
2
3
4
5
6
7
8

BigInt 与普通整数是两种值,它们之间并不相等。

12n === 12 // false
1

由于 BigInt 与 Number 完全属于两种类型,并且不会进行隐式转换,所以没有办法进行混合运算。想要运算的话,必须将两种数据类型转换为同一张后,方可进行计算:

BigInt(number) // 将一个 Number 转换为 BigInt
Number(bigint) // 将一个 BigInt 转换为 Number
1
2
  • typeof 运算符对于 BigInt 类型的数据返回 bigint
  • 由于 BigInt 并不是一个构造函数,所以,不能使用 new BigInt() 的方式来构建实例
  • 你创建一个 BigInt 的时候,参数必须为整数,否则或报错

# 引用类型

复杂类型统称为Object,我们这里主要讲述下面三种:

  • Object
  • Array
  • Function

# Object

创建object常用方式为对象字面量表示法,属性名可以是字符串或数值

let person = {
    name: "Nicholas",
    "age": 29,
    5: true
};
1
2
3
4
5

# Array

JavaScript数组是一组有序的数据,但跟其他语言不同的是,数组中每个槽位可以存储任意类型的数据。并且,数组也是动态大小的,会随着数据添加而自动增长

let colors = ["red", 2, {age: 20 }]
colors.push(2)
1
2

# Function

函数实际上是对象,每个函数都是 Function类型的实例,而 Function也有属性和方法,跟其他引用类型一样
函数存在三种常见的表达方式:

  • 函数声明
// 函数声明
function sum (num1, num2) {
  return num1 + num2;
}
1
2
3
4
  • 函数表达式
let sum = function(num1, num2) {
    return num1 + num2;
};
1
2
3
  • 箭头函数

函数声明和函数表达式两种方式

let sum = (num1, num2) => {
    return num1 + num2;
};
1
2
3

# 其他引用类型

除了上述说的三种之外,还包括Date、RegExp、Map、Set等......

# 存储区别

基本数据类型和引用数据类型存储在内存中的位置不同:

  • 基本数据类型存储在栈中
  • 引用类型的对象存储于堆中

当我们把变量赋值给一个变量时,解析器首先要确认的就是这个值是基本类型值还是引用类型值
下面来举个例子

# 基本类型

let a = 10;
let b = a; // 赋值操作
b = 20;
console.log(a); // 10值
1
2
3
4

a的值为一个基本类型,是存储在栈中,将a的值赋给b,虽然两个变量的值相等,但是两个变量保存了两个不同的内存地址

stack

# 引用类型

var obj1 = {}
var obj2 = obj1;
obj2.name = "Xxx";
console.log(obj1.name); // xxx
1
2
3
4

引用类型数据存放在堆中,每个堆内存对象都有对应的引用地址指向它,引用地址存放在栈中。
obj1是一个引用类型,在赋值操作过程中,实际是将堆内存对象在栈内存的引用地址复制了一份给了obj2,实际上他们共同指向了同一个堆内存对象,所以更改obj2会对obj1产生影响

heap

# 小结

  • 声明变量时不同的内存地址分配:
    • 简单类型的值存放在栈中,在栈中存放的是对应的值
    • 引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址
  • 不同的类型数据导致赋值变量时的不同:
    • 简单类型赋值,是生成相同的值,两个对象对应不同的地址
    • 复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象

# 类型判断

# typeof

typeof 操作符返回一个字符串,表示未经计算的操作数的类型

typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'
1
2
3
4
5
6
7
8
9
10

对值类型number、string、boolean 、null 、 undefined、 以及引用类型的function的反应是精准的;但是,对于对象{ } 、数组[ ] 、null 都会返回object

# instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

object instanceof constructor
1

object为实例对象,constructor为构造函数
构造函数通过new可以实例对象,instanceof能判断这个对象是否是之前那个构造函数生成的对象

// 定义构建函数
let Car = function() {}
let benz = new Car()
benz instanceof Car // true
let car = new String('xxx')
car instanceof String // true
let str = 'xxx'
str instanceof String // false
1
2
3
4
5
6
7
8

关于instanceof的实现原理,可以参考下面

function myInstanceof(left, right) {
  // 这里先用typeof来判断基础数据类型,如果是,直接返回false
  if(typeof left !== 'object' || left === null) return false;
  // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
  let proto = Object.getPrototypeOf(left);
  while(true) {                  
    if(proto === null) return false;
    if(proto === right.prototype) return true;//找到相同原型对象,返回true
    proto = Object.getPrototypeof(proto);
  }
}
1
2
3
4
5
6
7
8
9
10
11

从原型的角度,来判断某引用属于哪个构造函数,从而判定它的数据类型。也就是顺着原型链去找,直到找到相同的原型对象,返回true,否则为false

# 区别

typeof与instanceof都是判断数据类型的方法,区别如下:

  • typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值
  • instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
  • typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的也无法判断

可以看到,上述两种方法都有弊端,并不能满足所有场景的需求
如果需要通用检测数据类型,可以采用Object.prototype.toString,调用该方法,统一返回格式“[object Xxx]”的字符串

Object.prototype.toString({})       // "[object Object]"
Object.prototype.toString.call({})  // 同上结果,加上call也ok
Object.prototype.toString.call(1)    // "[object Number]"
Object.prototype.toString.call('1')  // "[object String]"
Object.prototype.toString.call(true)  // "[object Boolean]"
Object.prototype.toString.call(function(){})  // "[object Function]"
Object.prototype.toString.call(null)   //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g)    //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([])       //"[object Array]"
Object.prototype.toString.call(document)  //"[object HTMLDocument]"
Object.prototype.toString.call(window)   //"[object Window]"
1
2
3
4
5
6
7
8
9
10
11
12
13

了解了toString的基本用法,下面就实现一个全局通用的数据类型判断方法

function getType(obj){
  let type  = typeof obj;
  if (type !== "object") {    // 先进行typeof判断,如果是基础数据类型,直接返回
    return type;
  }
  // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
  return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); 
}
1
2
3
4
5
6
7
8
getType([])     // "Array" typeof []是object,因此toString返回
getType('123')  // "string" typeof 直接返回
getType(window) // "Window" toString返回
getType(null)   // "Null"首字母大写,typeof null是object,需toString来判断
getType(undefined)   // "undefined" typeof 直接返回
getType()            // "undefined" typeof 直接返回
getType(function(){}) // "function" typeof能判断,因此首字母小写
getType(/123/g)      //"RegExp" toString返回
1
2
3
4
5
6
7
8
Last Updated: 10/25/2024, 6:55:06 AM