前端基础学习之关于 JavaScript 的数据类型
- 7中数据类型
- Undefined
- Null
- Boolean
- Number
- 两个重要值
- 进制转换
- 精度问题
- 原因
- 解决方法
- String
- Symbol
- 定义
- 补充知识:类型转换
- 类型转换定义
- 常见的触发类型转换的操作
- Object
- 总结
7中数据类型
- 空(Null)
- 未定义(Undefined)
- 数字(Number)
- 字符串(String)
- 布尔值(Boolean)
- 符号(Symbol)
- 对象(Object)
Undefined
只有一个值“undefined”。得到undefined的几种方式:
- 引用已声明但未初始化的变量;
- 引用未定义的对象属性;
- 执行无返回值函数;
- 执行 void 表达式;
- 全局常量 window.undefined 或 undefined。
“void 0” 表达式常用来代表不执行任何操作,实例如下:
x>0 && x<5 ? fn() : void 0;
判断变量值是否为undefined常用的方式:
if(typeof x === 'undefined') {
...
}
注意,直接使用if(!x)和if(x===undefined)是不可取的,因为只要变量 x 的值为 undefined、空字符串、数值 0、null 时都会判断为真。直接使用三等号===
,当x为undefined,那么是会抛出错误 “ReferenceError: x is not defined” 导致程序执行终止。
Null
表示空值,只有唯一值null。
Boolean
Boolean 数据类型只有两个值:true 和 false,分别代表真和假。
需要注意,我们在if语句中进行条件判断时,if语句实惠进行类型转换的,将判断条件的变量转换成 Boolean 数据类型,而 0、空字符串、null、undefined 在转换时都会返回 false。如下代码,传入0时,返回的是undefined,而不是我们期望的"星期日"。
function getWeek(week) {
const dict = ['日', '一', '二', '三', '四', '五', '六'];
if(week) return `星期${dict[week]}`;
}
Number
两个重要值
Number 是数值类型,有 2 个特殊数值得注意一下,即 NaN 和 Infinity。
- NaN(Not a Number)通常在计算失败的时候会得到该值。要判断一个变量是否为 NaN,则可以通过 Number.isNaN 函数进行判断。
- Infinity 是无穷大,加上负号 “-” 会变成无穷小,在某些场景下比较有用,比如通过数值来表示权重或者优先级,Infinity 可以表示最高优先级或最大权重。
进制转换
将其他进制的整数转换成十进制显示的时候可以使用 parseInt 函数,此函数有来两个参数,第一个为数字或字符串,第二个为进制数,默认为10,当进制数转换失败时会返回 NaN。
['0', '1', '2'].map(parseInt) // [0, NaN, NaN]
将十进制转换成其他进制时,可以通过 toString 函数来实现。
(10).toString(2) // "1010"
精度问题
在JavaScript中,我们进行浮点数运算时,往往会遇到精度问题。如下:
0.1 + 0.2 // 0.30000000000000004//并不是期望的0.3
Math.pow(Math.pow(5, 1/2), 2) // 5.000000000000001
原因
JavaScript进行计算时,JavaScript 引擎会先将十进制数转换为二进制,然后进行加法运算,再将所得结果转换为十进制。
解决方法
两种方式消除无限小数位。
- 先转换成整数进行计算,然后再转换回小数,这种方式适合在小数位不是很多的时候。
- 舍弃末尾的小数位。比如对上面的加法就可以先调用 toPrecision 截取 12 位,然后调用 parseFloat 函数转换回浮点数。
parseFloat((0.1 + 0.2).toPrecision(12)) // 0.3
String
最常用的类型,相关的方法比较多,不一一介绍,可查看官方文档。
千位分隔符的两种实现方式:
//从右往左遍历数值每一位,每隔 3 位添加分隔符。将字符串数据转化成引用类型数据,即用数组来实现。
function sep(n) {
let [i, c] = n.toString().split(/(\.\d+)/)
return i.split('').reverse().map((c, idx) => (idx+1) % 3 === 0 ? ',' + c: c).reverse().join('').replace(/^,/, '') + c
}
//通过引用类型,即用正则表达式对字符进行替换来实现
function sep2(n){
let str = n.toString()
str.indexOf('.') < 0 ? str+= '.' : void 0
return str.replace(/(\d)(?=(\d{3})+\.)/g, '$1,').replace(/\.$/, '')
}
Symbol
定义
Symbol 是 ES6 中引入的新数据类型,它表示一个唯一的常量,通过 Symbol 函数来创建对应的数据类型,创建时可以添加变量描述,该变量描述在传入时会被强行转换成字符串进行存储。
一句话概括 Symbol 生成一个全局唯一的值。
常用于两个场景 避免常量值重复
和避免对象属性覆盖
补充知识:类型转换
类型转换定义
在处理不同数据类型运算或逻辑操作时会强制转换成同一数据类型。
通常强制转换的目标数据类型为 String、Number、Boolean 这三种。如下为转换关系:
常见的触发类型转换的操作
- 运算相关的操作符包括 +、-、+=、++、* 、/、%、<<、& 等。
- 数据比较相关的操作符包括 >、<、== 、<=、>=、三等号
===
。 - 逻辑判断相关的操作符包括 &&、!、||、三目运算符。
Object
引用类型,即键值对的集合。
浅拷贝和深拷贝:
- 由于引用类型在赋值时只传递指针,这种拷贝方式称为浅拷贝。
- 而创建一个新的与之相同的引用类型数据的过程称之为深拷贝。
通过typeof查看数据类型的描述:
[undefined, null, true, '', 0, Symbol(), {}].map(it => typeof it)// ["undefined", "object", "boolean", "string", "number", "symbol", "object"]
发现 null 有些特殊,返回结果和 Object 类型一样都为"object",所以需要再次进行判断。按照上面分析的结论,我们可以写出下面的函数:
function clone(data) {
let result = {}
const keys = [...Object.getOwnPropertyNames(data), ...Object.getOwnPropertySymbols(data)]
if(!keys.length) return data
keys.forEach(key => {
let item = data[key]
if (typeof item === 'object' && item) {//避免null
result[key] = clone(item)
} else {
result[key] = item
}
})
return result
}
在遍历 Object 类型数据时,我们需要把 Symbol 数据类型也考虑进来,所以不能通过 Object.keys 获取键名或 for…in 方式遍历,而是通过 getOwnPropertyNames 和 getOwnPropertySymbols 函数将键名组合成数组,然后进行遍历。对于键数组长度为 0 的非 Object 类型的数据可直接返回,然后再遍历递归,最终实现拷贝。
由于嵌套调用,因此当对象数据嵌套时,会出现死循环。怎么避免这种情况呢?一种简单的方式就是把已添加的对象记录下来,这样下次碰到相同的对象引用时,直接指向记录中的对象即可。要实现这个记录功能,我们可以借助 ES6 推出的 WeakMap 对象,该对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
function clone(obj) {
let map = new WeakMap()//记录已经拷贝过的对象
function deep(data) {
let result = {}
const keys = [...Object.getOwnPropertyNames(data), ...Object.getOwnPropertySymbols(data)]
if(!keys.length) return data
const exist = map.get(data)
if (exist) return exist //重新创建一个对象并加入 WeakMap 中
map.set(data, result)//重新创建一个对象并加入 WeakMap 中
keys.forEach(key => {
let item = data[key]
if (typeof item === 'object' && item) {
result[key] = deep(item)
} else {
result[key] = item
}
})
return result
}
return deep(obj)
}
总结
- 7种类型:空(Null)、未定义(Undefined)、数字(Number)、字符串(String)、布尔值(Boolean)、符号(Symbol)、对象(Object)。
- Number精度问题。
- 进制转换parseInt和toString函数。
- Symbol 生成一个全局唯一的值。
- Object深拷贝,getOwnPropertyNames 和 getOwnPropertySymbols 函数。
- WeakMap解决深拷贝递归调用死循环问题。