数据类型
目前 JavaScript 中一共有 7 种数据类型。
type | 描述 |
---|---|
String | 字符串。 |
Number | 数值。 |
Boolean | 布尔值。 |
Null | 空值。 |
Undefined | 未定义。 |
Object | 对象。 |
Symbol | 独一无二的值。(ES6 新增) |
这 7 中数据类型又分为 简单数据类型
和 复杂数据类型
。
-
简单数据类型:String、Number、Boolean、Null、Undefined、Symbol。
-
复杂数据类型(引用数据类型):Object。
String类型
在 JavaScript 中字符串需要使用引号引起来。
typeof '' // string
复制代码
使用双引号或单引号都可以,但是不要混着使用。
引号不能嵌套,双引号不能放双引号,单引号不能放单引号.
在字符串中我们可以使用 \
符作为转义字符,当表示一些特殊字符时可以使用 \
符进行转义。
工作中常用的转义字符:
转义字符 | 描述 |
---|---|
\" |
表示双引号 " 。 |
\' |
表示单引号 ' 。 |
\n |
表示换行。 |
\t |
表示制表符。(tab 空格) |
\\ |
表示 \ 。 |
Number类型
在 JavaScript 中所有的数值都是 Number 类型。
typeof 1 // number
复制代码
Number 身上有可以表示数值的最大值 / 最小值。
-
Number.MAX_VALUE:1.7976931348623157e+308。
-
Number.MIN_VALUE(大于零的最小值):5e-324。
如果使用 Number 表示的数字超过了最大值,则会返回一个 Infinity
。
-
Infinity:表示正无穷。
-
-Infinity:表示负无穷。
NaN
是一个特殊的数字,表示 Not A Number
。
使用 typeof 检查 NaN Infinity 都会返回 number。
typeof NaN // number
typeof Infinity // number
复制代码
在 JavaScript 中整数的运算基本可以保证精确,如果进行小数运算,可能得到一个不精确的结果。
0.1 + 0.2 // 0.30000000000000004
复制代码
Boolean类型
布尔值只有 true
和 false
两个值,主要用来做逻辑判断。
-
true:表示真。
-
false:表示假。
typeof true // boolean
typeof false // boolean
复制代码
Null类型
Null 类型的值只有一个,就是 null
。
通常我们初始化变量,这个变量之后会被赋值为对象时,使用 null 进行变量的初始化赋值。
const obj = null
复制代码
使用 typeof 检查一个 null 值时,会返回 object。
typeof null // object
复制代码
Undefined类型
Undefined 类型的值只有一个,就是 undefined
。
当声明一个变量,但是并不给变量赋值时,它的值就是 undefined。
var str
console.log(str) // undefined
复制代码
使用 typeof 检查一个 undefined值时,会返回 undefined。
typeof undefined // undefined
复制代码
Object类型
对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性。
对象分类
内建对象
由 ES 标准中定义的对象,在任何的 ES 的实现中都可以使用。
比如:Math
、String
、Number
、Boolean
、Function
、Object
…
typeof Math // object
typeof String // object
typeof Number // object
typeof Boolean // object
typeof Function // object
typeof Object // object
复制代码
宿主对象
由 JavaScript 的运行环境提供的对象,目前来讲主要指由浏览器提供的对象。
比如:BOM
、DOM
typeof window // object
typeof document // object
复制代码
自定义对象
由开发人员自己创建的对象。
const obj = new Object()
typeof obj // object
复制代码
创建对象
通过 new
关键字,或者字面量的方式来创建对象。
const obj1 = new Object
const obj2 = {}
typeof obj1 // object
typeof obj2 // object
复制代码
new
关键字作用于构造函数,构造函数是专门用来创建对象的函数。
function Person(name) {
this.name = name
}
const person = new Person('构造函数')
console.log(person) // Person { name: '构造函数' }
复制代码
对象是由一组一组 { key: value }
形式的键值对组成,多个键值对用英文逗号隔开,最后一组键值对后面不用写逗号。(浏览器会忽略最后一个逗号)
-
key:属性名,一个对象中的 key 不能重复。
-
value:属性值,可以是任意的数据类型,也可以是一个对象。
使用对象字面量的方式,可以在创建对象时,直接指定对象中的属性。
const obj = {
name: '字面量'
}
复制代码
对象字面量的属性名可以加引号也可以不加,通常我们都不加,但是要使用一些特殊的属性,则必须加引号。
const obj = {
@@: '特殊属性' // 报错
}
const obj = {
'@@': '特殊属性' // 正常
}
复制代码
向对象添加属性
通常我们使用 object.key = value
的方式向一个对象添加属性。
const obj = {}
obj.name = '添加属性'
console.log(obj) // { name: '添加属性' }
复制代码
对象的属性名不强制要求遵守标识符的规范,但是我们使用时,还是尽量按照标识符的规范去做。
如果要使用特殊的属性名,不能使用 object.key = value
的方式来向一个对象添加属性。
必须使用 object[key] = value
这种方式。
const obj = {}
obj.@@ = '添加属性' // 报错
obj['@@'] = '添加属性'
console.log(obj) // { @@: '添加属性' }
复制代码
使用这种方式去操作属性,更加的灵活,在 [key]
中可以直接传递一个变量,这样变量值是多少就会操作哪个属性。
const obj = {}
const key = 'name'
obj[key] = '添加属性'
console.log(obj) // { name: '添加属性' }
复制代码
读取对象属性
读取对象属性,跟向对象添加属性的两种方式一样。
const obj = {
name: '读取属性'
}
obj.name // 读取属性
obj['name'] // 读取属性
复制代码
如果读取对象中的某个属性不存在,不会报错而是会返回 undefined。
const obj = {}
obj.name // undefined
复制代码
修改对象属性
修改对象属性,跟向对象添加属性的两种方式一样。
const obj = {
name: '修改属性'
}
obj.name = '修改属性-1'
console.log(obj) // { name: '修改属性-1' }
obj['name'] = '修改属性-2'
console.log(obj) // { name: '修改属性-2' }
复制代码
删除对象属性
使用关键字 delete
来删除对象属性。
语法:delete object.key
或 delete object[key]
。
const obj = {
name: '删除属性',
age: 100
}
delete obj.name
console.log(obj) // { age: 100 }
delete obj['age']
console.log(obj) // {}
复制代码
对象是否包含某个属性
使用 in
关键字来检查对象是否包含某个属性。
语法:key in object
const obj = {
name: '包含属性'
}
'name' in obj // true
'age' in obj // false
复制代码
对象特点
之所以说对象是一种复制数据类型,跟它的存储方式有很大的关联。
JavaScript 中的变量都是保存到栈内存中的。
基本数据类型的值直接在栈内存中存储,值与值之间是独立存在,修改一个变量不会影响其他的变量。
var str1 = '100'
var str2 = str1
str2 = '200'
console.log(str1) // 100
console.log(str2) // 200
复制代码
对象是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间。
而变量保存的是对象的内存地址(对象的引用),如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响。
const obj1 = {
name: '100'
}
const obj2 = obj1
obj2.name = '200'
console.log(obj1) // { name: '200' }
console.log(obj2) // { name: '200' }
复制代码
当比较两个基本数据类型的值时,就是比较值。
var str1 = '100'
var str2 = '100'
str1 === str2 // true
复制代码
而比较两个引用数据类型时,它是比较的对象的内存地址,如果两个对象是一模一样的,但是地址不同,也会返回 false。
const obj1 = {}
const obj2 = {}
obj1 === obj2 // false
复制代码
枚举对象属性
使用 for in
语句可以枚举对象中的属性。
const obj = {
a: 1,
b: 2,
c: 3
}
for (const key in obj) {
console.log(key, obj[key]) // 依次输出(a 1),(b 2),(c 3)
}
复制代码
Symbol类型
ES6 引入了一种新的数据类型 Symbol,表示独一无二的值。
使用方式
直接调用 Symbol 函数返回一个 Symbol 类型的值。
const symbol = Symbol() // Symbol()
typeof symbol // symbol
复制代码
或者传递一个参数,用于对 Symbol 的描述。
通过 Symbol 类型值身上的 description
属性,可以访问当前对 Symbol 的描述。
const symbol = Symbol('描述') // Symbol(描述)
symbol.description // 描述
复制代码
传递的参数一般是字符串类型,如果不是,内部会将参数转换字符串类型,再返回 Symbol。
Symbol(100) // Symbol(100)
Symbol(true) // Symbol(true)
Symbol(null) // Symbol(null)
Symbol(undefined) // Symbol(undefined)
Symbol({}) // Symbol([object Object])
Symbol([]) // Symbol()
Symbol(function(){}) // Symbol(function() {})
复制代码
虽然 Symbol 看起来像一个构造函数,但是不支持 new Symbol()
的方式进行创建。
new Symbol() // 报错
复制代码
特点
Symbol 属性对应的值是唯一的,解决命名冲突的问题。
const symbol1 = Symbol() // Symbol()
const symbol2 = Symbol() // Symbol()
symbol1 === symbol2 // false
复制代码
使用 Symbol 可以用来定义对象的唯一属性名。
const obj = {
[Symbol()]: 1,
[Symbol()]: 2,
[Symbol()]: 3
}
// { Symbol(): 1, Symbol(): 2, Symbol(): 3 }
复制代码
Symbol 值不能与其他数据进行计算,包括同字符串拼串。
const str = Symbol()
str + '' // 报错
复制代码
for in
,for of
遍历时不会遍历 Symbol 属性。
也不会被 Object.keys()
和 Object.getOwnPropertyNames()
检测到。
const obj = {
name: 'name',
[Symbol()]: 'Symbol'
}
// { name: 'name', Symbol(): 'Symbol' }
for (const k in obj) {
console.log(k) // 只会输出 name 属性
}
Object.keys(obj) // ['name']
Object.getOwnPropertyNames(obj) // ['name']
复制代码
如果想获取对象的 Symbol 属性,可以使用 Object.getOwnPropertySymbols()
或 Reflect.ownKeys()
注意:Object.getOwnPropertySymbols() 只获取 Symbol 类型的属性。
const obj = {
name: 'name',
[Symbol()]: 1,
[Symbol()]: 2,
[Symbol()]: 3,
}
Object.getOwnPropertySymbols(obj) // [Symbol(), Symbol(), Symbol()]
Reflect.ownKeys(obj) // ['name', Symbol(), Symbol(), Symbol()]
const symbolArr = Object.getOwnPropertySymbols(obj)
obj[symbolArr[0]] // 1
obj[symbolArr[1]] // 2
obj[symbolArr[2]] // 3
复制代码
全局共享Symbol
每次调用 Symbol() 方法都会返回一个独一无二的值,哪怕添加的描述一样。
const symbol1 = Symbol('one') // Symbol(one)
const symbol2 = Symbol('one') // Symbol(one)
symbol1 === symbol2 // false
复制代码
可以通过 Symbol.for()
的方式,创建一个全局可供搜索的 Symbol 类型值。
传递一个参数,作为 Symbol 的描述。
-
如果这个描述在全局不存在,就创建返回一个新的 Symbol 类型值。
-
如果这个描述全局已经存在,就直接返回已经存在的那个 Symbol 类型值。(类似单例模式)
const symbol1 = Symbol.for('one') // Symbol(one)
const symbol2 = Symbol.for('one') // Symbol(one)
symbol1 === symbol2 // true
复制代码
Symbol() 和 Symbol.for() 区别在于,前者是独一无二的,后者是全局可供搜索的。
const symbol1 = Symbol('one') // 此时 one 描述的 Symbol 不登记到全局
symbol1.description // one
const symbol2 = Symbol.for('one') // 查找全局是否有 one 描述的 Symbol 类型值,有就直接返回,没有就创建登记全局并返回
symbol2.description // one
symbol1 === symbol2 // false
const symbol3 = Symbol.for('one') // 此时全局已经有 one 描述的 Symbol 类型值了,会直接返回之前创建的值
symbol3.description // one
symbol2 === symbol3 // true
复制代码
使用 Symbol.keyFor()
可以返回一个全局已登记的 Symbol 类型值的描述。
可以用来检测该描述的 Symbol 类型值是否已被全局登记可供搜索。
const symbol1 = Symbol('one')
Symbol.keyFor(symbol1) // undefined
const symbol2 = Symbol.for('one')
Symbol.keyFor(symbol2) // one
复制代码
内置Symbol类型值
除了定义自己使用的 Symbol 类型值外,ES6 还暴露了 12 个内置的 Symbol 类型值,它们代表了内部语言行为。
Symbol 类型值 | 描述 |
---|---|
Symbol.iterator | 返回一个对象默认迭代器的方法。 被 for of 使用。 |
Symbol.asyncIterator | 返回对象默认的异步迭代器的方法。 被 for await of 使用。 |
Symbol.match | 用于对字符串进行匹配的方法,也用于确定一个对象是否可以作为正则表达式使用。 被 String.prototype.match() 使用。 |
Symbol.replace | 替换匹配字符串的子串的方法。 被 String.prototype.replace() 使用。 |
Symbol.search | 返回一个字符串中与正则表达式相匹配的索引的方法。 被 String.prototype.search() 使用。 |
Symbol.split | 在匹配正则表达式的索引处拆分一个字符串的方法。 被 String.prototype.split() 使用。 |
Symbol.hasInstance | 确定一个构造器对象识别的对象是否为它的实例的方法。 被 instanceof 使用。 |
Symbol.isConcatSpreadable | 一个布尔值,表明一个对象是否应该 flattened 为它的数组元素。 被 Array.prototype.concat() 使用。 |
Symbol.unscopables | 拥有和继承属性名的一个对象的值被排除在与环境绑定的相关对象外。 |
Symbol.species | 用于创建派生对象的构造器函数。 |
Symbol.toPrimitive | 将对象转化为基本数据类型的方法。 |
Symbol.toStringTag | 用于对象的默认描述的字符串值。 被 Object.prototype.toString() 使用。 |
在实际工作中,基本用不上这些,需要了解更多的同学可查看:阮老师写的内置 Symbol 值
笔者只用过 Symbol.iterator
,让 Object 对象可以被 for of
遍历。
Symbol.iterator
iterator
是一种接口机制,为各种不同的数据结构提供统一的访问机制。
工作原理:
-
创建一个指针对象(遍历器对象),指向数据结构的起始位置。
-
第一次调用
next
方法,指针自动指向数据结构的第一个成员。 -
接下来不断调用
next
方法,指针会一直往后移动,直到指向最后一个成员。 -
每次调用
next
方法,返回的是一个包含 value 和 done 的对象。
value:当前成员的值。
done:布尔值。(表示当前数据是否遍历结束)
遍历结束时,value 的值为 undefined,done 值为true。
默认情况下,for of
只能遍历具备 iterator 接口的数据。
比如:Array、arguments、set、map、String…
Object 对象是不支持的。
const arr = [1, 2, 3]
for (const v of arr) {
console.log(v) // 依次输出1 2 3
}
const obj = {
a: 1,
b: 2,
c: 3
}
// 报错 obj is not iterable
for (const v of obj) {
console.log(v)
}
复制代码
扩展 Object 原型对象方法,让 Object 对象支持 for of
遍历。
Object.prototype[Symbol.iterator] = function() {
// 获取对象的属性值放到一个数组中
const values = Object.values(this)
// 获取数组长度
const len = values.length
// 初始化索引
let i = 0
// 返回一个包含 next 方法的对象,遍历时内部会自行调用
return {
// next 方法将返回一个对象,包含当前遍历的属性值 value,是否遍历完成 done
next: () => ({ value: values[i++], done: i > len })
}
}
复制代码
现在我们再来遍历下 Object 对象。
const obj = {
a: 1,
b: 2,
c: 3
}
for (const v of obj) {
console.log(v) // 依次输出1 2 3
}
复制代码
OK,搞定。