JS中的数据类型,看这一片就够了,深度总结 【之】基本数据类型 | 【javascript基础系列】

545 阅读12分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

前言

这个系列 主要介绍 javacript基础内容系列文章主要介绍 javascript 基础部分
这里不仅仅 知识 讨论 理论部分,也会结合自己的实际写代码遇到的问题
尽量能够做到 理论与实践相结合的目的
这样才会对某个知识点有更多的认识,也便于理解相应的知识点
也会提供一些 关于 某些知识点的最佳实践

JS中有哪些数据类型

JS 中的类型从大的方向上面可以分为两类

第一类是 基本数据类型,包括 String,Number,Boolen,Null,undefined,Symbol,BigInt

基本数据类型存储在 内存中的 栈区

第二类是 引用数据类型,也就是 Object,而 其余的 引用类型都是 继承于 Object(比如 function)

引用类型 存储在 内存中的 堆区

基本数据类型

基本类型一览

基本数据类型中 的 string 表示字符串;number表示 数字;Blooen表示 布尔值,这应该大家都能理解,无需过多解析

SymbolBigInt是ES6 新加的两个类型

string

表示 字符串 类型,无需过多解释

number

MDN链接

表示数字,数字也分了 Int,Float,都属于 Number类型

但是需要注意的点如下:

  • js中 number,是有范围的 ,范围在 (253 1)(253 1)-(2^{53}  - 1) 到 (2^{53}  - 1)

  • 一般比较大的值,直接书写不太方便,因为后面会挂很多的 0,那么就可用 科学(指数)法来表示

    let num = 333e5 // 代表 333 * (10 的 5次方) 
    
  • 如果数字超出了 范围,那么就用 number的两个特殊的值 来表示

    • Infinity 表示 正无穷大
    • -Infinity 表示 负无穷大
  • NaN 表示不是数字,但它的类型是 Number

    很多时候,我们在转换 字符串的时候,如果字符串不能被转换成 数字,那么它就成了 NaN,代表它不是数字

    let name =  Number('三年三月')
    console.log(name) //NaN
    console.log(typeof(name)) // number
    

image.png

  • 不得不提的数字精度问题

    如果你不知道 js 中的 数字计算 精度问题。那么相信下面的一道面试题,你迟早会遇到

    一道经典的面试题 0.1+0.2 !== 0.3 返回的是 true

    具体什么原因导致的,已经有很多的文章 详细说过这个了,这里不展开表述

    其实就是 js浮点数运算标准和规则。javascript的浮点数运算就是采用了IEEE 754的标准

    这里说下解决方案:

    一般情况下,复杂的计算逻辑,亦或是 比较重要的计算逻辑(比如金额计算),是不会放在前端做的,这就导致 精度问题 好像没有被大幅的讨论,得不到过多的重视

    其实很多很多前端同学做项目好几年可能都不会遇到一次这样的问题,所以此处 你只需要知道个大概 与 解决方案即可

    一般前端 都用第三方库来解决,我一般用 Decimal.js来处理计算问题 官网传送门

    # 安装
    npm install --save decimal.js  // 安装
    import Decimal from "decimal.js"  // 具体文件中引入
    # 加
    let a = 1
    let b = 6 
    // a 与 b 可以是 任何类型,Decimal 内部会自己处理兼容
    // 下面两种都可以 可以带 new 也不可以不带 new
    let res = new Decimal(a).add(new Decimal(b)) 
    let res = Decimal(a).add(Decimal(b)) 
    # 减
    let a = "4"
    let b = "8"
    // a 与 b 可以是 任何类型,Decimal 内部会自己处理兼容
    // 下面两种都可以 可以带 new 也不可以不带 new
    let res = new Decimal(a).sub(new Decimal(b)) 
    let res = Decimal(a).sub(Decimal(b)) 
    
    # 乘
    let a = 1
    let b = 6 
    // a 与 b 可以是 任何类型,Decimal 内部会自己处理兼容
    // 下面两种都可以 可以带 new 也不可以不带 new
    let res = new Decimal(a).mul(new Decimal(b)) 
    let res = Decimal(a).mul(Decimal(b)) 
    # 除 
    let a = 1
    let b = 6 
    // a 与 b 可以是 任何类型,Decimal 内部会自己处理兼容
    // 下面两种都可以 可以带 new 也不可以不带 new
    let res = new Decimal(a).div(new Decimal(b)) 
    let res = Decimal(a).div(Decimal(b)) 
    
    

Null

MDN-Null

Null 是基本类型,表示一个不存在或者无效 或者地址引用

相信很多人对这一点有疑惑,既然是 基本类型,那么为什么类型判断为 Object

对的,其实是个 bug,因为最开始 机器一般都是 32位,通过内存地址的前几位来判断 类型(也就是typeof的判断),而null 为 000000

虽然 ES6 有提案将这个改过来,但是鉴于 目前世界上 很多代码 都在 运行,历史包袱很重,所以只能将错就错了

所以你记住即可 null 是基本类型,但是 typeof(null) 却是 object

Undefined

参考

Undefined 是基本类型,它的值只有一个 就是 undefined

nullundefined 的区别

nullundefined 这两个值,真的可以算得上是一个 JS 的老大难的问题,虽然不完全搞懂,对开发代码 没有啥影响,但是我们既然是学习,那么就要打破砂锅问到底

null 的字面意思是:空值  。这个值的语义是,希望表示 一个对象被人为的重置为空对象,而非一个变量最原始的状态 。  在内存里的表示就是,栈中的变量没有指向堆中的内存对象

undefined 的字面意思就是:未定义的值 。这个值的语义是,希望表示一个变量最原始的状态,而非人为操作的结果

估计看了上面的解释,还是太抽象,下面举个例子

function test(x){
console.log(x)
}
test() // undefined
let bbb;
console.log(bbb)// undefined

let abc = {}
abc = null
console.log(abc) // null

看下上面的例子,你主要要理解 undefined 不是人为赋值,null 是人为赋值,代表空,即可

test函数中 x,在调用的时候没有赋值,也就是 没有值,那么 就是空值

let bbb ,变量申明了,但是没有赋值,也是空值,那么就是 undefined

null,是我们在写代码的时候,需要自己 给一个 变量 空值,即人为的赋空值,才会用 null,以此来 释放对应的内存空间

当然你也可以简单的理解为:系统自动赋值的空值 为 undefined,人为设置空值 为 null

以上便是纯理论理解部分,也就是规范部分,官方定义部分

但是,浏览器作为解析 js 的主体,会有多个厂家的浏览器,并且他们还存在高度的竞争关系,并没有形成一致的执行原则

你可以理解为,规范的制定单位与执行单位不是一个主体,并且还有好多个执行主体,每个执行主体对规范的执行还存在一定的差异

看看奇葩的地方:undefined,作为系统自己设的空值,那么理论上来说是不允许修改的,但是实际情况是,你设置空值的时候,既可以 设置成 undefined,也可以使 null,并没有限制,所以这是导致混乱的地方

更奇葩的是 undefined 居然还不是 保留字,居然还可以当做 变量名,真的是 吐血,所以导致 各种混乱 具体详情查看这里

image.png

总结:undefined 与 null 都代表空值,在实际用法上没有区别,null 代表人为主动赋空值,undefined代表系统预设的空值

以上个人认为都是不对的,或者是历史遗留问题,并不是故意设置,能掌握最好,不能掌握,只需理解上面的总结即可

Symbol

Symbol的定义

参考来源 Symbol 是 ES6 推出的新基本数据类型,表示独一无二的值

Symbol() 产生一个唯一的值,每次执行都是一个新的 也就是 Symbol() !== Symbol()

当然可以加字符串描述 Symbol('name'),但是描述 也仅仅是描述 ,即使同样的描述,每次出来的 Symbol的值 也是不一样的 Symbol('name') !== Symbol('name')(要是相等了,那么跟 字符就没有啥区别了)

关于属性名字的遍历

此处需要有一些注意点:

Symbol 值作为属性名,遍历对象的时候,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。、

这也就牵扯出另外一个问题:JSON.stringify() 为什么只能支持基本类型,但是却又不能支持 Symbol类型

  1. JSON 做为一个独特的数据类型,他有自己的规范,ES6刚加的类型,JS支持,但是JSON不一定要支持
  2. JSON 作为一个不同语言之间通信的 数据结构,把Symbol 传递过去,别的编程语言如何解析,又是另外一个大问题

那么如何获取 Symbol 的属性呢?

有两种方法:

  1. 通过 Object.getOwnPropertySymbols():但是它只能获取 Symbol类型的 属性,字符串类型的无法获取
  2. 通过 Reflect.ownKeys():这也是 ES6 新的API,它能获取 全部的属性,是最全面的

Symbol常见的使用场景

当做属性值

JS 中某一个对象,对象可以有很多的属性,但是,如果这个对象 被别人使用的过程中,又想扩展自己的属性,那么这个时候,如果还是用 string来定义 属性,很有可能 属性同名,而被覆盖

此时用Symbol完美解决这个问题

let mySymbol = mySymbol
let obj = {name:'三年三月'}
obj[mySymbol] = '张三'
console.log(obj.name) // 三年三月
console.log(obj[mySymbol]) // 张三
console.log(obj.mySymbol) // undefined 记得不能用点号运算符

Symbol 作为内置的值

Symbol 有很多内置的变量,如果某个对象以 该Symbol值作为 属性,实现方法,那么相当于 自己实现了某个 JS对象的内部方法

下面举例 Symbol.toStringTag 代表了对象的 Object.porotetype.toString的方法 只要你自己写一个 对象,内部实现 Symbol.toStringTag 的方法,那么你就改写了 toString方法

let obj1 = {}
let obj2 = {
    [Symbol.toStringTag]:'三年三月'
}
console.log(obj1.toString()) // [object Object]
console.log(obj2.toString()) // [object 三年三月]

当然还有更多其他的,详情请看这里 参考来源

Symbol的总结

虽然上面写了那么多 Symbol的用法,确实在理论上来说是可以那么用,但是,本人查询很多资料,确实没有找到 太多 关于Symbol的比较有说服力的使用场景。上面的那些用法 都显得比较 牵强

比如 作为属性,确实能保证唯一,let mySymbol = Symbol() 但是获取属性值得时候 必须使用 mySymbol 这个变量

否则 你可以用 Symbol为属性的值,但是没有 mySymbol 这个变量,那么你就获取到其对应的值

除非用 Object.getOwnPropertySymbols()Reflect.ownKeys() 全部遍历出来

那如果这个时候 又有多个 Symbol的属性值,真的够麻烦,你是不太好判断具体哪个,因为 Symbol('三年三月') !== Symbol('三年三月'),尴尬的不行

再比如 通过 Symbol.toStringTag 改变 toString 的方法,为什么要那么麻烦,我直接 重写覆盖 它的 toString 方法 不也可以吗

let obj1 = {}
let obj2 = {
    toString:function(){
    return `[object 三年三月]`
    }
}
console.log(obj1.toString()) // [object Object]
console.log(obj2.toString()) // [object 三年三月]

image.png

目前我的认知水平,Symbol并没有达到设计它的目的,它没有被大量使用;

同样的 ES6中的 模板字符串 就被大量的使用,说明有很多的用处,达到了效果

拓展阅读

BigInt

BigInt是什么

参考MDN

BigInt 是一种内置对象,它提供了一种方法来表示大于 2^53 - 1 的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数

关于BigInt,也就是大整数,它目的还是为了解决 javascript自身的 Number类型的自身缺陷而设计的,太大数字无法表达出来,所以 才有了 BigInt

关于BigInt的用法简单说下即可,有两种方式 ,可以用 Bigint(1),也可以用 1n(数字后面用n来表示)

在比较运算和加减乘除的时候,也可以与Number来进行混用,但是需要注意的是

BigInt运算完会舍弃小数位,导致精度丢失,同理,在跟 Number 进行类型转换的时候也一样会存在精度丢失的问题

image.png

其他更多的细节,可以在这里查看,此处不做过多陈述,下面的篇幅主要讨论下我对下 BigInt的看法

BigInt 我的看法

BigInt既然是为了解决 最大数的问题,看似好像就解决了这个问题。但是细看,我们发现不是这么一回事

然道我们只需要表达最大数吗?有数字存在就必定会存在 响应的计算,有计算就必然有精度问题,小数问题,更何况,BigIntNumber 相互转也会出现 精度丢失

说白了我既然要在JS 中使用 数字,那么我就想用一个 一把梭的 方案 来解决所有数字问题,现在用了 BigInt,相应的又带出来那么多新的问题,没有任何意义

再问一句,既然这样,你原生解决不了,那我就用三方的库,一把梭解决所有数字问题,多好

何必搞得那么麻烦

当然也许我的观点不太正确,很多知识点没有理解到位,欢迎大家讨论与指正

包装类型

此处多提一嘴,然包装类型其实 算是 引用类型,但是它与 基本类型中 的 string,number等关联性很大

具体的我们会在下一章讲解

总结

本文主要介绍了 javacript中的 基本数据类型,简单介绍了 每个数据类型的基本知识,重点还是 结合我的自己思考,提出了很多在实际使用中的困惑,并且 找到了一部分答案,这才是最重要的

这是一个系列文章,不断更新,希望对大家有帮助

有问题可以留言一起探讨