案例分析
今天在遇到一个开发中的问题,在进行一个简单的后台字段展示的时候,莫名其妙的出现问题。在定位问题的时候,发现一个判断value>0?A操作:B操作
。因为在后台定义字段时候,明确说明,value
的值是一个数字并且>0
(肯定有值),所以上述的判断没有任何的毛病。
但是上线之后,发现有些数据没有正常展示,在进行接口数据的查询的时候,发现value
有的值竟然是B101
这类的数据。
当时的第一反应,好像是没有问题的,因为在很久之前,我在自己搭建的github博客
中对JS数据类型转换进行了简单汇总。
但是没有出现,就很疑惑,为了很快的解决线上问题,就没有深究,直接将判断条件置换为value?A操作:B操作
。
但是在今天下班的时候,和同事讨论到这个问题。发现他也遇到类似JS数据转换问题。他的问题是[]==![]
首先我们来看第一个情况:'B101'>0?A操作:B操作
的结果为什么?
其实这里涉及到一个JS类型转换的问题。在JS进行不同类型的数据类型比较的时候,请记住一条铁的规则:向数值方向靠拢(这个值不一定必须是数值,也可以是Boolean,也就是说true
和false
,或者也可以将true
看成1,false
看出0)。
我们来按上面的'B101'>0
来解释一下:有两种数据类型String
类型的B101
、Number
类型的0
。在进行比较的时候,其实JS为我们做了一些数值处理,将B101
尝试着转换为Number
类型 ==>Number('B101')
。但是很可惜,没有转换成功。Number('B101')
的值为NAN
。NAN
和所有类型的值进行比较都是false
。所以第一个例子的返回值为B操作
。
让我们来看我同事提出的问题:[]==![]
我们还是遵循向数值方向靠拢的规定:有一个元操作!(非)
。也就是说将![]
先转换为基本类型。![]==!Boolean([])
他的值为true
。
这里有一点需要澄清的是,在[]
进行转变为基本数据类型的时候,是经历了一些处理的。先进行valueOf()
的取值。如果valueOf
返回的是一个基本数据类型,然后进行直接比较,如果还是一个复杂数据类型,调用toString()
.
让我们来分析一下[]
在进行比较的时候,进行了如何的处理
[].valueOf();//[]返回了本身,但是也还是一个复杂数据类型 [].toString();//"" 返回了一个空串 ""==!Boolean([]) //true 复制代码
然后我们来分析一个更加直接的列子:
1>{valueOf:function(){return 0},toString:function(){return 2}} 复制代码
如果在控制台进行输出的话,结果是true
。
然后我们继续另外一个例子
1>{valueOf:function(){return {}},toString:function(){return 2}} 复制代码
在控制台输出的结果为false
。
例如此类的例子很多,但是有一点就是复杂类型数据和基本数据类型进行比较的时候,都是先调用valueOf()
,如果返回的值为基本数据类型,进行比较,如果不是基本数据类型,继续调用该复杂类型的toString()
。
因为我原来在github博客
中有对应的文章解释,所有,今天就偷一个懒,直接拿来主义了。
变量
ECMAScript的变量是松散类型的,所谓松散类型就是可以保存任何类型的数据。换句话说,每个变量仅仅是一个用于保存值的占位符。
定义变量的时候用var(ES5) let/const(ES6)等操作符。
var paramA; let paramB; const paramC; 复制代码
上面的代码定义变量paramX,变量可以用来保存任何值(如上定义的变量,是未经初始化的变量,会保存一个特殊的值undefined
)
数据类型
ECMAScript中有5种简单数据类型(基本数据类型)
- · 数值(Number):整数和小数(比如1和3.14)
- · 字符串(String):字符组成的文本(比如”Hello World”)
- · 布尔值(Boolean):true(真)和false(假)两个特定值
- · Undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
- · Null:表示无值,即此处的值就是“无”的状态。
还有1中复杂数据类型
- · 对象(Object): 本质上是由一组无序的名值对组成。其实对象也可以细分
- 狭义的对象(object)
2. 数组(array)
3. 函数(function)
针对于ES5来说只有上诉的6中数据类型,而对于ES6来说又新增了Symbol
typeof操作符
鉴于ECMAScript的数据类型是松散的,所以需要一种手段来检测给定变量的数据类型typeof。
原始数据类型
数值、字符串、布尔值分别返回number
、string
、boolean
typeof 123 // "number" typeof '123' // "string" typeof false // "boolean" 复制代码
函数
函数返回function
function f() {} typeof f // "function" 复制代码
undefined
undefined返回undefined
。
typeof undefined// "undefined" 复制代码
其他
除此以外,其他情况都返回object
。
typeof window // "object" typeof {} // "object" typeof [] // "object" typeof null // "object" 复制代码
需要注意点
Note:对于ES5对于没有定义的变量typeof undefinedParam //undefined,但是对于用ES6中let/const定义的变量,在变量定义之前进行typeof操作的话,会报错。
typeof paramVar;//undefined var paramVar; typeof paramLet;//ReferenceError:paramLet is not defined let paramLet 复制代码
出现上述问题的根源在于两点
- 1. 用var定义的变量会发现变量提升
- 2. 用let/const定义的变量存在TDZ(暂时性死区)
Undefined类型
Undefined类型只有一个值,既特殊的undefined。在使用var/let/const等操作符声明变量但是没有赋初值时,这个变量的值就是undefined。
undefined的主要目的是用于比较,而在ECMA-262第三版之前是不存在的。在第三版引入这个值是为了正式区分空对象指针(null)与未经初始化的变量。
Null类型
Null类型是第二个只有一个值的数据类型,这个特殊的值是null。从逻辑角度来看,null值表示一个空对象指针,而这也是使用typeof检测null值的时会返回”object”的原因。
Boolean
Boolean类型只有两个字面量:true和false。
虽然Boolean类型的字面量只有两个,但是ECMAScript中所有类型的值都有与这两个Boolean值等价的值。要将一个值转换为其对应的Boolean值,可以调用转型函数Boolean()
;
数据类型 | 转换为true的值 | 转换为false的值 |
Boolean | true | false |
String | 任何非空字符串 | ""(空字符串) |
Number | 任何非0数字值(+∞) | 0和NaN |
Object | 任何对象 | null |
Undefined | 不适用 | undefined |
Number类型
ECMAScript中使用IEEE754标准中双精度浮点数来表示一个数字,不区分整数和浮点数。
IEEE754: 在 IEEE754 中,双精度浮点数采用 64 位存储,即 8 个字节表示一个浮点数 。其存储结构如
下图所示:
第1位:符号位,0表示正数,1表示负数 第2位到第12位:指数部分 第13位到第64位:小数部分(即有效数字)
指数位可以通过下面的方法转换为使用的指数值:
由于保存浮点数值需要的内存是整数值的两倍,因此ECMAScript会不失时机的将浮点数值转换为整数值。其实Number类型采用的是双精度浮点数,但是在保存和应用的时候,为了内存和速度考虑,会表现出整数和浮点数的区分。
浮点数值
浮点数值:数值中必须包含一个小数点,并且小数点后面必须至少有一位数值。
浮点数值的最高精度是17位小数,但在进行算术计算时其精度远远不如整数。这样就会导致一些违背常理的bug。
let a = 0.1,b = 0.2; a+b ==0.3;//false 复制代码
出现这种精度缺失的情况,在于IEEE754内存结构导致的。 出现上诉问题的深度解析
数值范围
从存储结构中可以看出, 指数部分的长度是11个二进制,即指数部分能表示的最大值是 2047(211-1),取中间值进行偏移,用来表示负指数,也就是说指数的范围是 [-1023,1024] 。因此,这种存储结构能够表示的数值范围为 21024 到 2-1023 ,超出这个范围的数无法表示 。21024 转换为科学计数法如下所示: 21024 = 1.7976931348623157 × 10308
因此,JavaScript 中能表示的最大值是 1.7976931348623157e+308,最小值为 5e-324 。
这两个边界值可以分别通过访问 Number 对象的 MAX_VALUE 属性和 MIN_VALUE 属性来获取:
Number.MAX_VALUE; //1.7976931348623157e+308 Number.MIN_VALUE; //5e-324 复制代码
如果数字超过最大值或最小值,JavaScript 将返回一个不正确的值,这称为正向溢出(overflow) 或 负向溢出(underflow) 。
Number.MAX_VALUE+1 == Number.MAX_VALUE; //true Number.MAX_VALUE+1e292; //Infinity Number.MIN_VALUE + 1; //1 Number.MIN_VALUE - 3e-324; //0 Number.MIN_VALUE - 2e-324; //5e-324 复制代码
数值精度
在 64 位的二进制中,符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。
IEEE754 规定,有效数字第一位默认总是1 。因此,在表示精度的尾数前面,还存在一个隐藏位 ,固定为 1 ,但它不保存在 64 位浮点数之中。也就是说,有效数字总是 1.xx…xx 的形式,其中 xx..xx 的部分保存在 64 位浮点数之中,最长为52位 。所以,JavaScript 提供的有效数字最长为 53 个二进制位,其内部实际的表现形式为:
(-1)^符号位 1.xx…xx 2^指数位 这意味着,JavaScript 能表示并进行精确算术运算的整数范围为:
//最大 Math.pow(2, 53)-1 ; // 9007199254740991 //最少 -Math.pow(2, 53)-1 ; // -9007199254740991 复制代码
可以通过 Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER 来分别获取这个最大值和最小值。
console.log(Number.MAX_SAFE_INTEGER) ; // 9007199254740991 console.log(Number.MIN_SAFE_INTEGER) ; // -9007199254740991 复制代码
NaN
NaN:即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况。
NaN本身有两个不同寻常的特点
- 1. 任何涉及NaN的操作(NaN/10)都会返回NaN
- 2. NaN与任何值都不相等,包括NaN本身
针对NaN的这个两个特点,ECMAScript定义了isNaN()函数。该函数接受一个参数,参数可以是任何类型。
数值转换
有三个函数可以把非数值转换为数值:
- · Number() :可以用于任何数据类型
- · parseInt() : 专门用于把字符串转成数值
- · parseFloat() : 专门用于把字符串转成数值
Number()针对不同类型的数据类型转换如下
数据类型 | 转换为true的值 |
Boolean | true=>1 false=>0 |
Number | 简单的传入传出 |
null | 0 |
undefined | NaN |
String | 1:如果只包含数字(包括前面带-/+的情况),=>十进制数值 2:包含有效的浮点格式(eg:1.1),=>对应的浮点数值 3:含有有效的十六进制格式(eg:0xf),=>相同大小的十进制数值 4:空串 =>0 5:除了前几个情况,=>NaN |
Object | 1:调用对象的valueOf()方法,按照前面的规格进行转换返回的值 2:如果结果1的值是NaN,则调用对象的toString()方法,再次根据上诉规则转换返回的值 |
一元加操作符的操作与Number()函数作用一样。 使用Number对String进行转换的时候,会发生违背常规的情况。
Number('')//0
所以在转换String类型为Number类型的时候,一般是用parseInt()。parseInt()函数在转换String时,更多的是看String是否符合数值模式。它会忽略字符串前面的空格,直到找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt()就会返回NaN。 这样就规避了利用Number()对''进行转换的情况
parseInt('')//NaN 复制代码
如果想将String转换为浮点数类型,可以使用parseFloat()。
String类型
String类型是用于表示由零或者多个16位Unicode字符组成的字符序列。
String的特点
ECMAScript中的String是不可变的,也就是说,String一旦创建,他们的值就不能改变。要改变某个变量保存的String,首先要销毁原来的String,然后再用另一个包含新值的String填充该变量。
var stringVariable = '北宸'; stringVariable = stringVariable + '南蓁'; 复制代码
实现这个操作的过程如下:
- 1. 创建一个能容纳8个字符的新String
- 2. 在这个String中填充 "北宸"和"南蓁"
- 3. 销毁原来的String "北宸"和"南蓁"
转换为String
要把一个值转换为String有两种方式
- 1. toString() 返回相应值的字符串表现
null和undefined值没有这个方法 - 2. String() 能够将任何类型的值转换为String.
String()函数遵循下列转换规则
1.如果值有toString()方法,则调用该方法(没有参数)并返回相应的结果
2.null =>"null"
3.undefined => "undefined"
Object类型
一组数据和功能的集合。
对象可以通过执行new 操作符后跟要创建的对象类型的名称来创建。而创建Object类型的实例并为其添加属性和(或)方法,就可以创建自定义对象。
在ECMAScript中,Object类型是所有它的实例的基础换句话说,Object类型所具有的任何属性和方法也同样存在于更具体的对象中
Object的每个实例都具有下列属性和方法。
属性或方法 | 描述 |
constructor(属性) | 保存着用于创建当前对象的函数 |
hasOwnProperty(propertyName) | 检查给定的属性在当前对象实例中是否存在。 propertyName必须是String类型。 |
isPrototypeof(object) | 检查传入的对象是否是当前对象的原型 |
propertyIsEnumerable(prototyName) | 给定的属性是否能够使用for-in来枚举 propertyName必须是String类型。 |
toLocaleString() | 返回对象的String表示,与执行环境对应 |
toStirng() | 返回对象的Sting表示 |
valueOf() | 返回对象的Sting、Number或者Boolean表示。一般和toString()方法返回值相同 |