01.JavaScript基础系列:数据类型及类型转换
发布于 2022年 02月 11日 10:51
核心知识点:类型
数据类型分类
- 原始类型:number、string、boolen
- 两个特殊原始值:undefined、null
- 对象类型:array,object,function...
原始值是不可以修改的,非原始值是可以修改的。
隐式类型转换
- 原始类型的数据转换
- 对象类型与原始值的转换
- toString() 和 valueOf()
数据类型的定义
计算机程序的运行需要对值进行操作,能够表示并操作的值的类型称做数据类型(type)。程序需要将值保存起来以备将来使用时,便将其赋值给一个变量,变量是一个值的符号名称,可以通过名称来获得对值的引用。
数据类型的分类
原始类型
- 数字
- 字符串
- 布尔值
对象类型
对象是属性的集合,每个属性都由名/值对构成。
- 对象:无序集合
- 数组:有序集合
- 函数
两个特殊原始值
- null(空)
- undefined(未定义)
另外分类方式
- 可变类型:值是可以修改的,eg: 对象和数组属于可变类型
- 不可变类型:值是不可以修改的,eg: 数字、布尔值、字符串、null、undefined 属于不可变类型
IEEE754 标准
IEEE754标准中规定:对于 float 单精度浮点数在机器中,用 1 位表示数字的符号,用 8 位来表示指数,用23 位来表示尾数,即小数部分;对于 double 双精度浮点数在机器中表示用 1 位表示符号,用 11 位表示指数,52 位表示尾数,即小数部分。
其中指数域称为阶码,对阶码 E 的计算采用源码的计算方式,32位浮点数的 8bits 的阶码E的取值范围是 0 到 255;64位的浮点数的 11bits 的阶码E的取值范围是 0 到 1023;其中当E为全0或者全1时。
阶码的二进制位数决定浮点数的表示范围,尾数的二进制位数表示浮点数的精度。以32位浮点数为例,尾数域有23位。那么浮点数以二进制表示的话精度是23位,23位所能表示的最大数是,所以十进制的尾数部分最大数值是8388607
1.原始类型:数字
基本概念
JavaScript 中的数字格式,能够表示的整数范围是从 -2 ~ 2,包含边界值。然而需要注意的是 JavaScript 中实际的操作是基于32位整数。
JavaScript 中的算术运算存在溢出、下溢或被零整除时不会报错
- 溢出
当数字运算结果超过 JavaScript 所能表示的数字上限,结果为一个特殊的无穷大(Infinity)值。同样地,当负值超过了 JavaScript 所能表示的数字下限,结果为一个特殊的负无穷大(-Infinity)值
- 下溢
当运算结果无限接近于零并比 JavaScript 能表示的最小值还小的时候发生的一种情形。这种情况下,JavaScript 将会返回 0。
- NaN
零除以零是没有意义的,这种整除运算结果也是一个非数字(not-a-number)值,用 NaN 表示。
案例
0 / 1 //0
0 / -1 // -0
0 === -0 // true
1 / 0 // Infinity
-1 / 0 // -Infinity
Infinity === -Infinity // false
Infinity + Infinity // Infinity
-Infinity - Infinity // -Infinity
0 / 0 // NaN
Math.sqrt(-1) // NaN
-Infinity + Infinity // NaN
NaN === NaN // false
isFinite(NaN) // false
null + null // 0
undefined + undefined // NaN
二进制复杂度和四舍五入错误
JavaScript 采用了 IEEE754 浮点数表示法,这是一种二进制表示法,可以精确地表示分数,比如 1/2、1/8 和 1/1024。遗憾的是,我们常用的分数都是十进制 1/10,1/100等,二进制浮点数表示法并不能准确表示类似 0.1 这样简单的数字。
习题
0.1 + 0.2 !== 0.3
0.3 - 0.2 !== 0.2 - 0.1
2.原始类型:字符串
字符串是一组由16位值组成的不可变的有序序列,每个字符通常来自 Unicode 字符集。JavaScript 通过字符串类型来表示文本,字符串的长度是其所含 16 位值的个数。
在 JavaScript 中字符串是固定不变的,类似 replace()
和 toUpperCase()
的方法都返回新字符串,原字符串本身并没有发生变化。
3.原始类型:布尔值
只有两个值,保留字 true 和 false。JavaScript 程序中的比较语句的结果通常都是布尔值。
undefined、null、0、-0、NaN、'' 在 JavaScript 中会被转换成 false。
4.两个特殊原始值:null 和 undefined
null 表示一个特殊值,常用来描述 "空值",对 null 执行 typeof 其结果返回字符串 "object",也就是说,可以将 null 认为是一个特殊的对象值,含义是 "非对象"。
undefined 是预定义的全局变量,他的值是 "未定义",如果函数没有返回任何值,则返回 undefined;引用没有提供实参的函数形参的值也是 undefined。使用 typeof 运算符得到的值是 "undefined"。
5.原始类型的包装对象
数字、布尔值、字符串可以通过 Number()、Boolean()、String() 构造函数创建一个临时对象,方法的调用均是来自这个临时对象,如果试图给其属性赋值,会忽略其操作,修改操作只是发生在临时对象身上,而这个临时对象并未继续保留下来。
存取字符串、数组或布尔值的属性时创建的临时对象称做包装对象。还可以通过 Number()、Boolean()、String() 构造函数来显示创建包装对象。
null 和 undefined 没有包装对象。
s1 = new String('hello')
s1.len = 4
t1 = s1.len // 4
s1 = 'hello'
s1.len = 4
t1 = s1.len // undefined
n1 = 1;
n2 = new Number(n1)
n1 == n2 // true
n1 === n2 // false
typeof n1 // "number"
typeof n2 // "object"
6.不可变的原始值和可变的对象引用
JavaScript 中的原始值(undefined、null、布尔值、数组和字符串)是不可更改的,任何方法都无法更改一个原始值;原始值的比较是值的比较,只有在它们的值相等时,它们才相等。
对象是可变的,它们的值是可修改的;对象的比较均是引用的比较,当且仅当它们引用同一个基本对象时,它们才相等。
类型转换
转换和相等性
由于 JavaScript 可以做灵活的类型转换,因此其 ""
相等运算符也随相等的含义灵活多变。在进行 ""
等于运算符判断两个值是否相等时,会做类型转换。
显式类型转换
最简式的方法是使用 Boolean(),Number(),String() 或 Object() 函数,不通过 new 运算符调用这些函数时,会作为类型转换函数进行类型转换。
Number 类的 toString() 方法可以接收并注入和基数的可选参数。如果不知道此参数,转换规则是基于十进制。
Number 类的 toFixed() 根据小数点后的指定位将数字转换为字符串。
隐式类型转换
- 如果 "+" 运算符的一个操作数是字符串,它将会把另外一个操作数转换为字符串
- 一元 "+" 运算符将其操作数转换为数字
- 一元 "!" 运算符将其操作数转换为布尔值并取反
对象转换为原始值
对象转字符串和数字的规则只使用本地对象(native object),不适用宿主对象(eg: web 浏览器定义的对象)
toString()
作用是返回一个反映这个对象的字符串
({a: 1}).toString() // "[object Object]"
数组类的 toString() 方法将每个数组元素转为字符串,并在元素直接添加逗号后返回字符串
[].toString() // ""
[1, 2].toString() // "1,2"
函数类的 toString() 方法返回这个函数的实现定义的表示方式
(function f() {fn(1)}).toString() // "function f() {fn(1)}"
RegExp类的 toString()方法将 RegExp 对象转换为正则表达直接量的字符串
/\d+/.toString() // "/\d+/"
日期类的 toString() 方法返回一个可读的日期和时间字符串
(new Date()).toString() // "Sat Feb 01 2020 21:46:17 GMT+0800 (中国标准时间)"
valueOf()
如果存在任意原始值,它就默认将对象转换为表示它的原始值。对于对象来说,无法真正表示一个原始值,默认返回对象本身。
数组、函数、正则的 valueOf() 默认返回对象本身
({a: 1}).valueOf() // {a: 1}
[1, 2].valueOf() // [1, 2]
[].valueOf() // []
/\d+/.valueOf() // /\d+/
(function f() {fn(1)}).valueOf() // f() {fn(1)}
日期类的 valueOf() 返回时间戳
(new Date()).valueOf() // 1580565225890
- 对象到布尔类型转换非常简单,全是 true
- 对象到字符串
- 如果对象具有 toString() 方法,且该方法返回是一个原始值,JavaScript 会将方法返回的原始值转换为字符串
- 如果对象没有 toString() 方法,或这个方法返回值不是原始值,如果具有 valueOf() 且返回值是原始值,会将方法返回的原始值转换为字符串
- 否则会抛出一个类型错误异常
- 对象到数字
- 如果对象具有 valueOf() 方法,且该方法返回是一个原始值,JavaScript 会将方法返回的原始值转换为数字
- 如果对象没有 valueOf() 方法,或这个方法返回值不是原始值,如果具有 toString() 且返回值是原始值,会将方法返回的原始值转换为数字
- 否则会抛出一个类型错误异常
"+" 和 "==" 应用的对象(非日期类)到原始值的转换基本上都是对象到数字的转换(首先调用 valueOf());日期对象则使用对象到字符串的转换模式(首先调用 toString())。通过 valueOf 或 toString 返回的原始值被直接使用,而不会被强制转换为数字或字符串。
变量
变量作用域
变量的作用域(scope) 是程序源代码中定义这个变量的区域,在函数内声明的变量和函数参数定义的变量都是局部变量,作用域在函数体内有效。
函数作用域(function scope):变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
声明提前(hoisting): 函数内声明的所有变量在函数体内始终是可见的,即函数里声明的变量会被提前至函数体的顶部。
属性的变量
当声明一个 JavaScript 全局变量时,实际上是定义全局对象的一个属性。
- 当使用 var 声明一个变量时,创建的属性是不可配置的,即不可通过 delete 删除
- 在非严格模式下,给一个未声明的变量赋值,是可配置的,可以通过 delete 删除
作用域链
每一段 JavaScript 代码都有一个与之关联的作用域链(scope chain),这个作用域链是一个对象列表或链表,这组对象定义了这段代码作用域中的变量。
作用域链由一个全局对象组成,在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。
当创建一个函数时,它实际上保存了一个作用域链;当调用这个函数时,它创建了一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的链。