小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
JavaScript是弱类型编程语言,即在声明变量的时候,不需要指定其数据类型。那么JavaScript一共包含多少种数据类型呢?答案是 8种,分为基本数据类型和引用数据类型。
- null
- undefined
- Boolean
- String
- Number
- Symbol
- BigInt
- Object(引用数据类型)
基本数据类型和引用数据类型
基本数据类型保存在栈内存中,其占用空间小,大小固定,通过按值来访问,属于被频繁使用的数据。
引用数据类型保存在堆内存中,其占用空间通常较大,且大小不固定。在栈内存中存储的是指针,该指针指向堆中的地址,即在栈中存地址,堆中存内容。
八大数据类型简介
null
null类型只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针,因此typeof null === "object"。
undefined
undefined类型也同样只有一个值,即特殊值undefined。当时用var或者let声明了变量却没有初始化的时候,就相当于给变量赋予了undefined值,但需要注意的是,包含undefined值的变量跟未定义变量是有区别的。
let name;
// let age;
console.log(name); // 输出undefined
console.log(age); // 报错
Boolean
Boolean布尔类型是非常常用的数据类型之一,它有两个字面值:true和false。这两个布尔值不同于数值,因此true不等于1,false不等于0。
虽然布尔值只有两个,但所有其他ECMAScript类型的值都有相应布尔值的等价形式。
| Truthy表示为真 | Falsy表示为假 |
|---|---|
| true | false |
| 非0数 | 正负0 |
[](空数组) | ""(空字符串) |
{}(空对象) | undefined |
| Infinity | null |
| ...... | NaN |
String
String字符串类型表示零或多个16位Unicode字符序列。字符串可以使用双引号("")、单引号('')或反引号(``)表示。但是以某种引号作为字符串开头,必须仍然以该种引号作为字符串结尾。例如下面的写法会导致语法的错误
let name = '小陈同学吗"; // 语法错误,引号不匹配,连我这个注释都变红色了
ECMAScript中的字符串是不可变的,意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,则必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。
Number
Number数值类型蛮有意思,可用于表示整数和浮点数,不同的数值类型相应地也有不同的数值字面量格式。
let year = 1998; // 十进制整数
let birthday = 0x1AB // 十六进制的427,0x为16进制数的前缀
let age = 027; // 八进制的23,0为8进制数的前缀
要定义浮点数,数值中必须包含小数点,而且小数点后面必须至少有一个数字。因为存储浮点数使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法地把值转化为整数。类似地,如果数值本身就是整数,只是小数点后面跟着0(如1.0),那它也会被转换为整数。
let floatNum1 = 1.; // 小数点后面没有数字,被当做整数1来处理
let floatNum2 = 5.0; // 小数点后面是零,被当做整数10来处理
对于非常大或非常小的数值,浮点数也可以用科学记数法来表示。
let floatNum3 = 3.14125e7; // 等于31412500
let floatNum4 = 3e-17; // 等于0.00000000000000003
此外,有一个特殊的数值叫NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了,而不是抛出错误。
console.log(0/0); // NaN
console.log(-0/+0); // NaN
若分子是非0值,分母是有符号0或无符号0,则会返回Infinity或-Infinity。
console.log(5/0); // Infinity
console.log(5/-0); // -Infinity
NaN有几个独特的属性。首先,任何涉及NaN的操作始终返回NaN(如NaN/10)。其次,NaN不等于包括NaN在内的任何值,即NaN != NaN。那么我们该如何判断一个变量是否为NaN呢?ECMAScript提供了isNaN()函数,该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。该函数会尝试把参数转换为数值,任何不能转换为数值的值都会导致这个函数返回true。
console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // false,10是数值
console.log(isNaN("10")); // false,"10"可以转换为数值10
console.log(isNaN("小陈同学吗")); // true,不可转换为数值
console.log(isNaN(true)); // false,可以转换为数值1
但我认为,isNaN()函数仅仅做到的是判断一个变量能否转换为数值,若能则返回false,然则反之。它不能用来严格判断是否为NaN,因为小陈同学也可以返回true。
ES6提供了Number.isNaN()方法用来判断一个值是否严格等于NaN,它会首先判断传入的值是否为数字类型,如不是,直接返回false。当且仅当参数为NaN时才返回true!
Symbol
Symbol是ES6新增的数据类型,它用于表示独一无二的值,确保对象属性使用的唯一标识符,不会发生属性冲突的危险。
它需要使用Symbol()函数初始化,也可以传入一个字符串参数作为对Symbol的描述。
let sym1 = Symbol();
let sym2 = Symbom();
let sym3 = Symbol('小陈同学');
let sym4 = Symbol('小陈同学');
console.log(sym1 == sym2); // false
console.log(sym3 == sym4); // false
下面是使用Symbol作为属性的一个例子。
let mySymbol = Symbol();
// 第一种写法
let obj = {};
obj[mySymbol] = '小陈同学吗';
// 第二种写法
let obj = {
[mySymbol]: '小陈同学吗'
};
// 第三种写法
let obj = {};
Object.defineProperty(obj, mySymbol, { value: '小陈同学吗' });
// 以上写法都得到同样结果
obj[mySymbol] // "小陈同学吗"
BigInt
BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值(此前Number所能表示的最大值为2^53 - 1)。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用BigInt,整数溢出将不再是问题。
BigInt是在ES2020中引入的,我在JS高程这本书的目录中并没有看到关于BigInt的内容,了解基础用法如下。
// 方式一:加n
console.log(1n); // 1n
// 方式二:使用构造函数 BigInt
console.log(BigInt(1)); // 1n
1n === BigInt(1) // true
typeof 1n; // bigint
Object
Object是引用数据类型。它是一组数据和功能的集合,对象通过new操作符后跟对象类型的名称来创建。
// 写法1:
let obj = {name:'小陈同学吗',gender:'男'}
// 写法2:
let obj1 = new Object({name:'小陈同学吗',gender:'男'})
// 写法3:
let obj2 = {}
obj2.name = '小陈同学吗'
obj2.gender = '男'
判断数据类型的方法
typeof
typeof undefined // "undefined"
typeof true // "boolean"
typeof "小陈同学吗" // "string"
typeof 123 // "number"
typeof NaN // "number" (注意NaN也是Number类型的)
typeof 123n // "bigint"
typeof Symbol() // "symbol"
typeof console.log // "function"
typeof null // "object" (这很特殊)
typeof {} // "object"
typeof [1,2,3] // "object"
细心的朋友已经发现了,使用typeof检测的类型中,并没有null类型,这是因为,null的本身含义就是表示一个空对象指针,因此就会返回object类型。
那怎么还多了个function类型?
根据红宝书上写的内容,函数在ECMAScript中被认为是对象,并不代表一种数据类型。可是,函数也有自己特殊的属性。为此,就有必要通过typeof操作符来区分函数和其他对象。
使用typeof检测数据类型还是会出现一个问题,那就是无法区分数组和对象,因为数组的打印值也是"object",所以typeof并不是最优解,他是来判断基本类型的,下面再来介绍一下instanceof操作符。
instanceof
instanceof操作符的原理是:通过查找原型链来检测某个变量是否为某个类型数据的实例。
a instanceof A // 只要右边变量A的 `prototype` 在左边变量a的原型链上即可。
需要注意的是,instanceof只适用于对象类型,不适用原始类型的值。
那么我们来解决刚才的问题:如何检测数组Array类型?
[] instanceof Array // true
{} instanceof Array // false
(console.log) instanceof Function // true
可以发现它是可以检测出Array类型的数组,但是这里还是会存在一个问题,Array这个构造函数的实例的原型链上也存在Object,也就是说[] instanceof Object也是返回true。那么这里就存在一个顺序的问题,我们可以写一个函数,先检测是不是Array类型的,再检测Object,我们的函数如下所示。
function checkArray(obj) {
if(obj instanceof Array) return 'Array';
if(obj instanceof Object) return 'Object';
else return 'Others'
}
但其实挺麻烦的,那么到底有没有更具普适性的检测方法呢?有的。
Object.prototype.toString()
Object.prototype.toString()可以说是判断数据类型的究极解决方案了。
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(console.log) // '[object Function]'
Object.prototype.toString.call('小陈同学吗') // '[object String]'
Object.prototype.toString.call(123) // '[object Number]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(Symbol()) // '[object Symbol]'
Object.prototype.toString.call(null) // '[object Null]' !!!
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(Math) // '[object Math]'
Object.prototype.toString.call(new Set()) // '[object Set]'
Object.prototype.toString.call(new WeakSet()) // '[object WeakSet]'
Object.prototype.toString.call(new Map()) // '[object Map]'
Object.prototype.toString.call(new WeakMap()) // '[object WeakMap]'
该方法不仅仅检测出Array类型,就连null也攻破了,而且还可以检测出Date、Math、Set等。
但经查阅相关资料,需要说明的是
- 传入基本数据类型也能够判定出结果是因为对值进行了包装。
null和undefined能够输出结果是内部实现有做处理。- 自定义类型结果会打印
"[object Object]",所以需要用instanceof来判断。
再写个方法把多余部分去掉,只剩下数据类型就好了。
function test(obj){
let res = Object.prototype.toString.call(obj)
let str = res.split(" ")[1]
return str.substring(0,str.length-1)
}
// 测试
// test([]) 打印 Array
结语
JavaScript的数据类型,平时使用的时候感觉还是挺简单的(甚至都没管它),但深挖进去你会发现还是有很多细节上的东西,它会涉及到数据的存储方式(堆栈)、浮点数如何存储与表示、原型链等知识。我也只是通过写文章的形式来巩固我学过的东西,加深记忆。
🎉 好了,小陈在这预祝看到这篇文章或还没看到的朋友们,国庆快乐!
参考资料
- 《JavaScript高级程序设计(第4版)》
- 《JavaScript权威指南(第7版)》
- 网上其他优秀作者的文章