JavaScript数据类型及判断
- Published on
Introduction
JavaScript中一共有8种基本的数据类型,分别是:Number
, String
, Boolean
, Null
, Undefined
, Symbol
, BigInt
, Object
。 其中Number
, String
, Boolean
, Null
, Undefined
, Symbol
, BigInt
是基本数据类型,Object
是引用数据类型。
我们可以将任何类型的值存入变量,例如:
let name = 'joy'
name = 22
可以修改变量的值为不同类型,允许这种操作的编程语言被称为“动态类型”(dynamiclly typed)。与之相对的Java``C``C++
等语言是“静态类型”(statically typed)。
Number类型
JavaScript中的所有数字都是Number
类型,包括整数和浮点数。例如:
let num = 22
let num2 = 22.5
除了常规的数字外,JavaScript还有一些特殊的数字值,例如Infinity
, -Infinity
, NaN
。
- Infinity: 表示无穷大,例如
1/0
。 - -Infinity: 表示无穷小,例如
-1/0
。 - NaN: 表示它是一个invalid number, 虽然它invalid,但它扔是一个number。
- NaN是粘性的,任何对NaN的操作都会返回NaN。
- 如果我们在
JavaScript
中做了不合理的数学运算,例如0/0
,Infinity-Infinity
,Math.sqrt(-1)
等,都会返回NaN
。
[!Tip] >
NaN
是唯一一个不等于自己的值,即NaN !== NaN
。 所以要写一个函数判断一个东西是不是NaN
,可以这样写,很有意思:function isNaN(value) { return value !== value }
BigInt类型
虽然说number
也可以存储更大的整数,最多到7976931348623157 * 10308
,但超出安全整数范围 ±(253-1)
会出现精度问题,因为并非所有数字都适合固定的 64
位存储。因此,可能存储的是“近似值”。 如下例:
const a = 9007199254740991 //安全范围顶
console.log(9007199254740991 + 1); // 9007199254740992
console.log(9007199254740991 + 2); // 9007199254740992
两个打印结果都是 9007199254740992
,这是因为这两个数字已经超出了安全范围,所以会出现精度问题。
在大多数情况下,±(253-1) 范围就足够了,但有时候我们需要整个范围非常大的整数,例如用于密码学或微秒精度的时间戳。这时候就需要 BigInt
了。
BigInt
是ES6
中引入的新的数据类型,可以安全存储和操作大整数。BigInt
是一种内置对象,它提供了一种方法来表示大于 253-1
的整数。BigInt
可以表示任意大的整数,没有范围限制,甚至可以超过 253-1
的限制。 通过在数字后面加上 n
来创建 BigInt
类型的数字,例如:
const bigInt = 1234567890123456789012345678901234567890n;
String类型
String
类型是字符串类型,用于表示文本数据。字符串可以包含一个或多个字符,可以是字母、数字、符号等。字符串可以使用单引号、双引号或反引号来表示。例如:
let str = 'hello'
let str1 = 'world'
let str2 = `hello world ${str}`
反引号(`)可以用来创建多行字符串,也可以在字符串中嵌入变量。例如:
let str = `hello
world`
let str1 = `hello ${str}`
在${}
中可以放入任何表达式,表达式计算结果成为字符串的一部分。
Boolean类型
boolean类型有两个值:true
和false
。例如:
let isTrue = true
let isFalse = false
布尔值也可以作为比较的结果
let isGreater = 4 > 1
console.log(isGreater) // true
Null类型
null
是一个特殊的值,表示“空”或“未知”。例如:
let age = null
上面的代码表示age
是未知的。
Undefined类型
undefined
表示“未定义”。如果一个变量已经声明但没有赋值,那么它的值就是undefined
。例如:
let name
console.log(name) // undefined
我们也可以显式地将一个变量赋值为undefined
,例如:
let name = undefined
……但是不建议这样做。通常,使用 null 将一个“空”或者“未知”的值写入变量中,而 undefined 则保留作为未进行初始化的事物的默认初始值。
Symbol类型
Symbol
是ES6
中引入的新的数据类型,表示独一无二的值。Symbol
是一种基本数据类型,不是对象,不能使用new
关键字创建。
我们可以使用Symbol()
函数来创建Symbol
类型的值,例如:
let id = Symbol()
也可以给Symbol
添加一个描述,例如:
let id = Symbol('id')
Symbol
的一个主要特点是,它们是独一无二的,即使它们的描述相同,它们也是不同的。例如:
let id1 = Symbol('id')
let id2 = Symbol('id')
console.log(id1 == id2) // false
symbol还有以下特点:
symbol
的值不会被自动转为字符串 例如:
let id = Symbol('id')
console.log(id) // Symbol(id)
console.log(id.toString()) // Symbol(id)
symbol
的值不会被自动转为布尔值 例如:
let id = Symbol('id')
if (id) {
console.log(id)
}
上面的代码不会输出Symbol(id)
,因为Symbol
的值不会被自动转为布尔值。
for...in
循环不会遍历symbol
属性 例如:
let id = Symbol('id')
let user = {
name: 'joy',
age: 22,
[id]: 1,
}
for (let key in user) {
console.log(key) // name, age
}
- 在对象字面量中使用
Symbol
,需要使用方括号括起来
let id = Symbol('id')
let user = {
name: 'John',
[id]: 123, // 而不是 "id":123
}
- 全局
Symbol
注册表Symbol
是全局注册的,所以在不同的地方使用相同的描述创建的Symbol
是相同的。例如:
let id1 = Symbol.for('id') // 如果symbol不存在,则创建一个新的symbol
let id2 = Symbol.for('id') // 获取到描述为'id'的symbol
console.log(id1 === id2) // true
Object类型
Object
类型是唯一的引用数据类型(引用类型和基本类型的区别在于,基本类型存储的是值,而引用类型存储的是地址。基础类型的值存在栈中,引用类型的值存在堆中。),用于存储多个值。
对象是键值对的集合,其中的值可以是任何数据类型。对象的键是字符串/Symbol,值可以是任何数据类型。
创建对象
- 使用对象字面量
let user = {
name: 'joy',
age: 22,
}
- 使用构造函数
let user = new Object()
user.name = 'joy'
user.age = 22
访问对象属性
我们可以使用.
或[]
来访问对象的属性。例如:
let user = {
name: 'joy',
age: 22,
}
console.log(user.name) // joy
console.log(user['name']) // joy
let user1 = {}
user1['likes birds'] = true
alert(user1['likes birds']) // true
删除对象属性
我们可以使用delete
关键字来删除对象的属性。例如:
let user = {
name: 'joy',
age: 22,
}
delete user.name
console.log(user) // { age: 22 }
计算属性
我们可以使用[]
来计算属性名。例如:
let fruit = 'apple'
let bag = {
[fruit]: 5,
}
console.log(bag.apple) // 5
在typescript
中,这种写法叫做索引签名
,可以用来描述对象的索引类型。例如:
interface NumberDictionary {
[index: string]: number
length: number // 可以,length是number类型
name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
}
属性值简写
当对象的属性名和变量名相同时,可以使用属性值简写。例如:
let name = 'joy'
let age = 22
let user = {
name,
age,
}
console.log(user) // { name: 'joy', age: 22 }
属性名限制
变量名不能是编程语言的某个保留字,如 “for”、“let”、“return” 等…… 但是,对象的属性名没有这个限制。任何字符串都可以作为属性名。例如:
let obj = {
for: 1,
let: 2,
return: 3,
}
console.log(obj.for + obj.let + obj.return) // 6
属性存在性检查
我们可以使用in
操作符来检查对象是否包含某个属性。例如:
let user = {
name: 'joy',
age: 22,
}
console.log('name' in user) // true
console.log('job' in user) // false
for...in
循环
for...in
循环可以遍历对象的所有可枚举属性。例如:
let user = {
name: 'joy',
age: 22,
}
for (let key in user) {
console.log(key) // name, age
}
数据类型判断
typeof
操作符
typeof
是一个一元操作符,放在其单个操作数之前,操作数可以是任意类型。typeof
返回一个表示操作数类型的字符串。 typeof
能判断所有的值类型和函数,但是无法对null
, object
和数组进行区分。
例如:
// 能判断
console.log(typeof 1) // number
console.log(typeof 'hello') // string
console.log(typeof true) // boolean
console.log(typeof undefined) // undefined
console.log(typeof Symbol('id')) // symbol
console.log(typeof function () {}) // function
// 无法区分,都返回object
console.log(typeof null) // object
console.log(typeof {}) // object
console.log(typeof []) // object
instanceof
操作符
instanceof
能判断对象的具体类型,但是不能判断基本类型。其内部运行机制是判断在其原型链中能否找到该类型的原型。
console.log([] instanceof Array) // true
console.log([] instanceof Object) // true
console.log({} instanceof Object) // true
console.log(null instanceof Object) // false
function Person() {}
let p = new Person()
console.log(p instanceof Person) // true
Object.prototype.toString.call()
所有的对象都有internal properties
,例如[[Class]]
,[[Prototype]]
等。[[class]]
用来描述对象的类型信息,
当我们调用 Object.prototype.toString()
时,以下步骤会发生:
- If the this value is undefined, return "[object Undefined]".
- If the this value is null, return "[object Null]".
- Let O be ToObject(this value).
- Let isArray be IsArray(O).
- ReturnIfAbrupt(isArray).
- If isArray is true, let builtinTag be "Array".
- Else, if O is an exotic String object, let builtinTag be "String".
- Else, if O has an [[ParameterMap]] internal slot, let builtinTag be "Arguments".
- Else, if O has a [[Call]] internal method, let builtinTag be "Function".
- Else, if O has an [[ErrorData]] internal slot, let builtinTag be "Error".
- Else, if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".
- Else, if O has a [[NumberData]] internal slot, let builtinTag be "Number".
- Else, if O has a [[DateValue]] internal slot, let builtinTag be "Date".
- Else, if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".
- Else, let builtinTag be "Object".
- Let tag be Get (O, @@toStringTag).
- ReturnIfAbrupt(tag).
- If Type(tag) is not String, let tag be builtinTag.
- Return the String that is the result of concatenating "[object ", tag, and "]".
——— ECMAScript 2015 Language Specification
简单来说就是,Object.prototype.toString.call()
会返回一个[object class]
的字符串,其中class
是对象的类型。例如:
console.log(Object.prototype.toString.call(1)) // [object Number]
console.log(Object.prototype.toString.call('hello')) // [object String]
console.log(Object.prototype.toString.call(true)) // [object Boolean]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(Symbol('id'))) // [object Symbol]
console.log(Object.prototype.toString.call(function () {})) // [object Function]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call({})) // [object Object]
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call(new Date())) // [object Date]
console.log(Object.prototype.toString.call(/a/)) // [object RegExp]
// Arguments
function f() {
console.log(Object.prototype.toString.call(arguments)) // [object Arguments]
}
判断NaN
isNaN
函数isNaN
函数用来判断一个值是否是NaN
,它会先将参数转换为数字,然后再判断是否是NaN
。
console.log(isNaN(NaN)) // true
console.log(isNaN('hello')) // true
console.log(isNaN('22')) // false
console.log(isNaN(22)) // false
console.log(isNaN(undefined)) // true
console.log(isNaN(null)) // false
// 为什么isNaN(null)返回false? isNaN(undefined)返回true?
// 因为null会被转换为0,0不是NaN
// undefined会被转换为NaN
Number.isNaN
函数Number.isNaN
函数用来判断一个值是否是NaN
,它不会将参数转换为数字,只有在参数是NaN
时才返回true
。
console.log(Number.isNaN(NaN)) // true
console.log(Number.isNaN('hello')) // false
console.log(Number.isNaN('22')) // false
console.log(Number.isNaN(22)) // false
console.log(Number.isNaN(undefined)) // false
console.log(Number.isNaN(null)) // false
判断数组
Array.isArray
函数
console.log(Array.isArray([])) // true
console.log(Array.isArray({})) // false
instanceof
操作符
console.log([] instanceof Array) // true
console.log({} instanceof Array) // false
Object.prototype.toString.call()
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call({})) // [object Object]
Array.prototype.isPrototypeOf()
console.log(Array.prototype.isPrototypeOf([])) // false
console.log(Array.prototype.isPrototypeOf({})) // false
constructor
属性
console.log([].constructor === Array) // true
console.log({}.constructor === Array) // false
__proto__
属性
console.log([].__proto__ === Array.prototype) // true
console.log({}.__proto__ === Array.prototype) // false
JavaScript里“空”的概念
undeclared vs undefined
typeof notExist // undefined
当我们typeof
一个未定义的变量时,返回undefined
。这里的undefined
到底是什么呢? 为什么返回的不是null
?
[!Tip] >
typeof
是唯一一个能够访问一个未声明变量而不抛出错误的方法,其他情况下就会抛出ReferenceError
。const a = 1 console.log(a + c) // ReferenceError: c is not defined
- undefined通常表示变量已声明但未赋值,或者根本没有声明。
- null是一个表示无值或空值的对象。它通常用于表示意图上的“空”或“无,是一个故意赋值的结果。
在上面的例子中,notExist
变量是一个未声明的状态(undeclared),而不是声明了未赋值的状态(undefined)。
从感觉上说,返回undefined
是不合理的。但JavaScript就是这样设计的,如你不知道的JavaScript
的作者Kyle
所说:这是一个历史遗留问题,JavaScript试图假装声明的缺失并不是那么重要。
That's another historical wart, JavaScript trying to pretend as if the absence of declaration isn't that big deal, you can work around with it. In retrospect, they should just return a string of undeclared
uninitialized vs undefined
console.log(a) // undefined
console.log(b) // ReferenceError: b is not defined
let b
var a
var声明的变量会在代码执行前进行变量提升,初始化为undefined。而let声明的变量在声明前处于未初始化(uninitialized)状态,尝试访问这些变量会导致ReferenceError。