温故而知新系列(一)你真的搞懂JavaScript数据类型了吗?快来缕缕吧!

·  阅读 1031
温故而知新系列(一)你真的搞懂JavaScript数据类型了吗?快来缕缕吧!

这是《温故而知新系列总结》,开篇先来讲讲数据结构这个最基础最基础的东西,可能很多同学都没有认认真真研究过,写的不到位的地方还请大大们多多指教!

JavaScript是一门神奇的语言,就连数据类型这个入门科目都够你捣鼓半天

image-20211125165034518

一、数据类型分类

image-20211125170017104

JavaScript将数据类型分为7种基本数据类型(Undefined,Null,Boolean,Number,String,Symbol,BigInt)1种复杂数据类型(Object)

  • 基本数据类型存放的是原始值primitive value
  • 复杂数据类型存放的是对象an object,即一个引用值reference value,其中有一个原型对象prototype object,可能为空(如顶级对象Objectprototype object即为空{}

image-20211125171519844


1. typeof操作符

typeof并不是一个函数,而是一个操作符,后面接一个操作数(一个表示对象或原始值的表达式),返回操作数的类型

//两种写法,返回值以字符串表示
typeof operand
typeof(operand)
复制代码

比较特殊:注意这里返回的类型并不是和数据类型一一对应的,下表进行一个总结

类型值举例typeof 操作结果
Undefinedundefined(仅此一值)"undefined"
Nullnull(仅此一值)"object"
Booleantrue,false(仅此两值)"boolean"
Number-0,+0,1,2.1,Infinity,NaN..."number"
BigInt10n,20n..."bignit"
String"","jack","it is a joke"..."string"
SymbolSymbol("aaa")..."symbol"
宿主对象Person创建的对象person1"Person"取决于具体实现
Function对象const foo = function(){}"function"
其他任何对象Array|Date|RegExp|Set|WeakSet|Map|WeakMap|Object|原始值包装对象..."object"

2. Undefined

只有一个值,就是undedined

2.1 声明未初始化

let a
console.log(a) //undefined
console.log(typeof a) //undefined
复制代码

2.2 未声明直接使用会报错

console.log(b) //ReferenceError: b is not defined
console.log(typeof b) //undefined
复制代码

建议: 即使未初始化的变量会被自动赋予undefined值,但我们仍然建议在声明变量的同时进行初始化。这样,当 typeof 返回"undefined"时,你就会知道那是因为给定的变量尚未声明,而不是声明了但未初始化

2.3 undefined是一个假值

console.log(Boolean(undefined)) //false
复制代码

3. Null

只有一个值,就是null

3.1 表示空对象指针

console.log(typeof obj) //object
复制代码

3.2 null是一个假值

console.log(Boolean(null)) //false
复制代码

3.3 undefinednull的派生用法

console.log(undefined == null) //true
复制代码

4. Boolean

只有两个值true\false

4.1 TrueFalse并不是布尔值,而是标识符

4.2 其他类型转布尔

Boolean() 是转型函数,可以用于转换任意数据类型

//1.String
console.log(Boolean('')) //false
console.log(Boolean('li')) //true
//2.Number
console.log(Boolean(0)) //false
console.log(Boolean(NaN)) //false
console.log(Boolean(1)) //true
console.log(Boolean(Infinity)) //true
console.log(Boolean(-Infinity)) //true
//3.Object
console.log(Boolean({name: 'li'})) //true
//4.Undefined
console.log(Boolean(undefined)) //false
//5.Null
console.log(Boolean(null)) //false
复制代码

5. Number

日常使用数值没那么复杂,一般不会涉及到很多边界情况

5.1 如果数值本身是整数,小数点后面的0将被忽略

console.log(3.0) //3
复制代码

5.2 浮点数计算有精度丢失

这是IEEE754数值的通病,永远不要测定某个特定的浮点值

console.log(0.1 + 0.2)  //0.30000000000000004
复制代码

5.3 值的范围

ECMAScript 可以表示的最小数值保存在Number.MIN_VALUE中,这个值在多数浏览器中是 5e-324;可以表示的最大数值保存在 Number.MAX_VALUE 中,这个值在多数浏览器中是 1.797 693 134 862 315 7e+308,如果某个计算的数值超出了JavaScript所能表示的范围,那么这个数值会被转换为正无穷+Infinity和负无穷-Infinity,这里我做了一个有意思的测试。

let max = Number.MAX_VALUE //1.7976931348623157e+308
let a = max + 1
console.log(isFinite(a)) //true
console.log(a) //1.7976931348623157e+308
复制代码

分析:按理来说,Number.MAX_VALUE+1后已经超出了js数值表示范围,为什么结果不是Infinity而依然是Number.MAX_VALUE,我的个人理解是,1这个值在Number.MAX_VALUE面前微乎其微被直接忽略了,那到底加一个多大的数才不会被忽略变为Infinity呢,测试吧!!

let max = Number.MAX_VALUE //1.7976931348623157e308
let a291 = max + 1.7976931348623157e291
let b292 = max + 1.7976931348623157e292
console.log(isFinite(a291), isFinite(b292))  //true false
console.log(a291, b292) //1.7976931348623157e+308 Infinity
复制代码

可以看出:a291Number.MAX_VALUE相差了17个数量级,b292Number.MAX_VALUE相差了16个数量级,因此数量级差大于等于17个数量级的数在Number.MAX_VALUE面前将被忽略,数量级差小于等于16个数量级的数在Number.MAX_VALUE面前将被正常考虑,当然咱们老百姓一般不可能用到这么大的数值的,以上测试纯属娱乐,仅个人观点,如有错误还请大佬们指正-Infinity同理,只是多加了一个负号,表示负无穷。

针对Number.MIN_VALUE来说,它是可表示的最小数,经过测试,它减去一个任意比它小的数之后得到的值依然是Number.MIN_VALUE,使用isFinite()始终返回true

let min = Number.MIN_VALUE
let min1 = min - 5e-500
let min2 = min - 5e-325
console.log(isFinite(min1), isFinite(min2)) //true true
console.log(min1, min2) //5e-324 5e-324
复制代码

这回清爽了,终于理解了什么叫可以表示的最大值和最小值,即使有些值肉眼上看是大于最大值,小于最小值,但其实js计算后依然是这个值,如果值再大一步,那就变成了Infinity

5.4 其他类型转数值

5.4.1 Number()

Number是转型函数,可以用于转换任意数据类型

//null
console.log(Number(null)) //0
//undefined
console.log(Number(undefined)) //NaN

//Boolean
console.log(Number(true)) //1
console.log(Number(false)) //0
复制代码

字符串的转换规则要稍微特殊一点

有几条规则总结了一下:空字符串返回0,只要含有非进制字符返回NaN,整数和浮点数前面忽略0,显示进制数不忽略前面的0,其他情况一律返回NaN

console.log(Number('')) //0
console.log(Number('a11')) //NaN
console.log(Number('011a11')) //NaN
console.log(Number('01.1a11')) //NaN
console.log(Number('011')) //11
console.log(Number('011.1')) //11.1
console.log(Number('0b11')) //3 二进制
console.log(Number('0o11')) //9 八进制
console.log(Number('0x11')) //17 16进制
复制代码

对象的转换,将调用valueOf方法,再调用上述规则,如果转换结果为NaN,则调用toString方法再按字符串规则转换。这是红宝书上的叙述,目前我并未遇到NaN再转toString的情况,也没有想到相关的例子,如果有大佬知道,还请赐教

简单分析两个示例

//a是一个对象Boolean{false},调用valueOf方法返回false
let a = new Boolean('')  
//等价console.log(Number(a.valueOf())) <=>console.log(Number(false))
console.log(Number()) //0

let b = new Date()
console.log(Number(b))//得到的是当前时间的毫秒数
复制代码
5.4.2 parseInt

可接收两个参数,第一个参数为待转换的字符串,第二个参数为转换的进制,当有第二个参数时,可省略第一个参数中的进制表示0b,0o,0x

parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被 忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回 NaN。这意味着空字符串也会返回NaN(这一点跟 Number()不一样,它返回 0)。如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。parseInt()函数也能识别不同的整数格式(十进制、八 进制、十六进制)

console.log(parseInt('')) //NaN
console.log(parseInt('a1a11')) //NaN
console.log(parseInt('1a11')) //1
console.log(parseInt(' -11.1')) //-11
console.log(parseInt('0x13')) //19
console.log(parseInt('13', 16)) //16进制 19
复制代码
5.4.3 parseFloat

类似parseInt,它只认第一个小数点,对于字符串开头的0如果数值是0.xxx则不忽略,如果是非十进制数则直接返回0,因此它只能解析十进制值,不能指定底数(进制数),如果小数点后只有一个0则也会返回整数

console.log(parseFloat(' a0.11a')) //NaN
console.log(parseFloat(' 01.1.1aa)) //1.1
console.log(parseFloat(' -0.1a')) //-0.1
console.log(parseFloat(' 0b11')) //0
console.log(parseFloat(' 02.0a')) //2
复制代码

5.5 NaN

表示不是一个数值,not a number,本来要返回数值的操作失败了

console.log(0 / 0) //NaN
console.log(1 / 0) //Infinity
console.log(1 / -0) //-Infinity
复制代码
  • 任何涉及NaN的操作都返回NaN

  • NaN不等于包括NaN在内的任何值

    console.log(NaN == NaN) //false
    复制代码
  • isNaN(param)用于检验这个参数是否不是数值,把一个值传给isNaN后,该函数会尝试将它转换为数值,能转为数值则返回false,不能转换为数值则返回true

    //能转换为数值的有 字符串数值如'10.1'或布尔值
    //任何不能转换为数值的值都会导致该函数返回true  
    console.log(isNaN(true)) //1---false
    console.log(isNaN(false)) //0---false
    console.log(isNaN('10.1')) //10.1---false
    console.log(isNaN('1')) //1---false
    console.log(isNaN(NaN)) //true
    console.log(isNaN('blue')) //true
    console.log(isNaN(undefined)) //true
    console.log(isNaN(null)) //0---false
    复制代码

6. BigInt

Number中有最大最小安全整数,超出他们的范围的数值运算被认为是不安全的,为解决这个问题,就产生了BigInt,它一一种内置对象,typeof操作符返回"bigint",并不是引用类型,而是存的原始值,使用在整数字面量后面加n的方式来表示,或者调用函数BigInt(),不可与new操作符配合使用,因为他构造出的不是对象实例,并不是引用类型

//BigIntConstructor. (value: bigint | boolean | number | string): bigint
let a = 1n
let b = BigInt(true)
console.log(typeof a) //bigint
console.log(a, b) //1n 1n
复制代码

6.1 操作符运算

以下操作符可以和 BigInt 一起使用: +-*/**% 。除 >>> (无符号右移)之外的也可以支持。因为 BigInt 都是有符号的, >>> (无符号右移)不能用于 BigInt

console.log(1n + 1n) //2n
console.log(1n - 2n) //-1n
console.log(1n * 2n) //2n
console.log(5n / 2n) //2n 向下取整,带小数的部分会被舍弃
console.log(1n % 2n) //1n
console.log(3n ** 2n) //9n
console.log('程序员最喜欢的数字是: ', 1n << 10n) //程序员最喜欢的数字是:  1024n
console.log(10n >> 2n) //2n  分析:10n / (2n**2n) <=> 10n / 4n <=> 2n
复制代码

6.2 比较

6.2.1 与Number相比较宽松相等,不严格相等
console.log(1n == 1) //true
console.log(1n === 1) //false
复制代码
6.2.2 可以和Number进行大小的比较
console.log(1n < 1) //false
console.log(2n >= 2) //true
复制代码

6.3 toString和valueOf

覆盖Object原型上的对应方法,toString()将返回以基数表示的字符串,valueOf()返回指定的bigint原始值

console.log(1n.toString()) //1
console.log(1n.valueOf()) //1n
复制代码

7. String

这个应该是使用最频繁的数据类型了,表示零个或多个16Unicode字符序列,三种表示方法:双引号"a",单引号'a',反引号(``)

7.1 转义字符

\开头表示类似回车、换行等操作,与其他编程语言类似

\xnn表示以十六进制编码 nn 表示的字符(其中 n 是十六进制数字 0~F),例如\x41 等于"A"

\unnnn表示以十六进制编码 nn 表示的Unicode字符(其中 n 是十六进制数字 0~F),例如\u03a3 等于"Σ"emojiUnicode 编码为E63EE757

7.2 其他类型转换为字符串

7.2.1 String()转型函数

null\undefined分别返回null\undefined,其他类型参考下面的toString方法

console.log(String(null)) //null
console.log(String(undefined)) //undefined
复制代码
7.2.1 toString()方法

null\undefined没有该方法,数值、布尔值、对象和字符串都有该方法,数值、布尔值、和字符串在使用该方法是其实是自动转为了包装对象来使用的,后面会细说,对象直接调用其toString方法,有的重写了有的没有重写,没有重写的toString返回[object type]

let a = 1
console.log(a.toString()) //1
console.log(true.toString()) //true
console.log(false.toString()) //false
//对象
let obj1 = {
  '': 'a' //键可以为空哦,不过一般不这么用
}
console.log(obj1.toString()) //[object Object]
let obj2 = {
  '': 'obj2',
  toString() {
    return this['']
  }
}
console.log(obj2.toString()) //[object Object]
复制代码

当数值使用该方法时可以添加底数,即添加一个进制数参数,表示即将转换为什么进制

let a = 10
console.log(a.toString()) //10
console.log(a.toString(2)) //1010
console.log(a.toString(8)) //12
console.log(a.toString(16)) //a
复制代码

7.3 模板字符串

ES6新增语法,使用**``**来表示字符串,内部将保留换行符,可以跨行定义字符串

let s = `
 hello`
console.log(s.length) //6
console.log(s[0] === '\n') //true
console.log(s[1] === ' ') //true
复制代码
7.3.1 插值语法

字符串插值通过在${}中使用JavaScript表达式实现,可以使用任何JavaScript表达式,会将最后结果强制转换为字符串

let a = 1
let s = `the number is ${a}`
console.log(s) //the number is 1
复制代码
7.3.2 标签函数

标签函数tag function的理解:

接收到的第一个参数:原始字符串数组,指的是因插值语法分隔开的字符串数组,包含前后的空字符串,后序的参数是每个插值表达式的求值结果。

function foo(x, y, z) {
  console.log(x) //[ 'hello', 'world', '' ]
  console.log(y) //second param
  console.log(z) //third param
  return 'foo function'
}
const res = foo`hello${'second param'}world${'third param'}`
console.log(res) //foo function
复制代码

这玩意可能在react中的利用率会比较高吧,暂时尚未涉足,后面考虑学一波

7.3.3 原始字符串

可以通过模板字符串获取原始的字符串内容,包括换行符或Unicode字符,使用默认的标签函数String.raw,标签函数的第一个参数有一个raw属性可以获取每个字符串的原始内容

function foo(str) {
  console.log('实际显示的字符串为:')
  for (const s of str) {
    console.log(s)
  }

  console.log('原始值为:')
  for (const s of str.raw) {
    console.log(s)
  }
}
foo`\u00a9${'none of my business!'}\n`

//实际显示的字符串为:
// [ '©', '\n' ]
// ©
// (这里是一个换行符)
//
// 原始值为:
// \u00a9
// \n

复制代码

8. Symbol

基本数据类型,Symbol([description])函数会返回Symbol类型的唯一值,即使可选描述符一样,生成的符号也不相等,可以作为对象属性的标识符,这也是该数据类型诞生的使命。

let s1 = Symbol()
let s2 = Symbol('foo')
let s3 = Symbol('foo')
console.log(s1, s2, s3) //Symbol() Symbol(foo) Symbol(foo)
console.log(s2 === s3) //false
console.log(s2.description) //foo
复制代码

8.1 全局共享的Symbol

使用Symbol.for()Symbol.keyFor()方法来创建和获取symbol,这种方法创建的符号可以跨文件使用

let s1 = Symbol.for('foo')
let des1 = Symbol.keyFor(s1)
console.log(des1) //foo
let s2 = Symbol.for('foo')
console.log(s1 === s2) //true
复制代码

8.2 属性描述符的3中使用方式

const s1 = Symbol('s1_Symbol')
const s2 = Symbol('s2_Symbol')
const s3 = Symbol('s3_Symbol')

/**
 * 1. 定义字面量直接使用,使用计算属性
 */
const obj = {
  name: 'dd',
  age: 19,
  [s1]: 'abc',
}

/**
 * 2.属性名赋值,使用类似索引的赋值方式
 */
obj[s2] = 'bcd'

/**
 * 3.使用Object.defineProperty
 */
Object.defineProperty(obj, s3, {
  configurable: true,
  enumerable: true,
  writable: true,
  value: 'cde',
})

console.log(obj)
/**
{
  name: 'dd',
  age: 19,
  [Symbol(s1_Symbol)]: 'abc',
  [Symbol(s2_Symbol)]: 'bcd',
  [Symbol(s3_Symbol)]: 'cde'
}
*/
复制代码

8.3 遍历

对象中的普通属性都可以通过infor...in得到,但是Symbol比较特殊,没法通过他们得到,必须使用Object.getOwnPropertySymbols()方法来获取,这是一个专门的方法。

console.log(Object.getOwnPropertySymbols(obj)) //此处的obj为8.2中的obj
//[ Symbol(s1_Symbol), Symbol(s2_Symbol), Symbol(s3_Symbol) ]
复制代码

9. Object

对象通过new操作符后面跟上一个对象类型名称来常见,类似java,但是跟java又不一样,因为JavaScript中其实并没有真正意义上的类的概念,Object上有很多属性和方法,创建出来的对象上依然会有,除非被重写,后期复习到原型原型链的时候再详细说吧,常见的属性和方法如下:

  • constructor 用于创建当前对象函数

  • hasOwnProperty(propertyName) 用于判断当前对象实例(不是原型)上是否存在给定的属

  • isPrototypeOf(object) 用于判断当前对象是否为另一个对象的原型

  • propertyIsEnumerable(propertyName) 用于判断给定的属性是否可枚举,如属性的enumerablefalse时,无法使用in\for...in进行遍历

  • toLocaleString() 返回对象的字符串表示,该字符串反映对象所在的本地化执行环境

  • toString()返回对象的字符串表示

  • valueOf()返回对象对应的字符串、数值或布尔值表示,通常与 toString()的返回值相同

关于对象,需要讲的东西太多,后期会专门写一篇来对其进行深入剖析

10. 包装类型(历史遗留问题)

什么是包装类型呢?学过java的的童鞋应该听说过一个自动装配的概念,有点类似,js中提供了Boolean,Number,String这三种基本数据类型的包装引用类型,可以通过new操作符进行使用,至于为什么要这么做,我的理解是:对象可以充分利用其属性及方法进行相关操作,而基本数据类型存的原始值没办法享受这份红利,所以为了让我们能非常方便的操作他们,就给他们做了包装,当然我们一般不会显示的使用他们,当要用到某个原始值的方法或属性时,后台会自动帮我们创建,使用后会自动销毁,无需我们进行其他操作。

let str1 = 'hello world.'
let str2 = str1.toUpperCase() //HELLO WORLD.
//等价于
let str1 = new String("hello world.") //创建一个String类型的实例
let str2 = str1.toUpperCase() //调用实例上的特定方法
str1 = null //销毁实例
复制代码

2.1 自动生命流程

自动创建的原始值包装对象只存在于访问它的那行代码执行期间,这意味着不能在运行时给原始值添加属性和方法

let s1 = 'hello world.'
s1.info = 's1'
console.log(s1.info) //undefined
复制代码

分析

  • 第2行代码创建了一个String实例,并在它身上加上了info属性,该行代码执行完毕该实例就会被销毁
  • 第3行代码创建了一个新的实例,并在它身上加上了info属性,但是并未进行赋值,该行代码执行完毕该实例就会被销毁

2.2 Boolean、Number、String

咱一般不显示的new它们,知道有这么个东西就行了,这是JavaScript历史遗留问题,当然各包装对象有一些方法还是要了解,具体可以参考MDN文档进行学习

链接JavaScript | MDN (mozilla.org)

数据类型就总结到这里吧,还有一些内置对象类型,诸如Date、RegRex、Set、Map等等基本都是一些API的使用,后期有时间的话再总结一波。

创作不易,求个关注或点赞😄😄🤗

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改