js强制类型转换规则
这篇随笔记录一下js中数据的各种类型转换的规则,虽然很基础,但是重新过一遍会发现有些规范还是挺意想不到的
首先介绍一下ToString, ToNumber, ToBoolean 的转换规则
1、ToString
规则1:null 转换为 “null” , undefined 转换为 “undefined” , true 转换为 “true” ;
规则2:普通对象,除非自定义,否则用 toString();
数组的toString()方法,先把所有单元字符串化,然后再用“,”连接;[1,2,3,4] // “1,2,3,4”;
2、ToNumber
规则1:布尔值 true 转换为 1, false 转换为 0; undefined 转换为 NaN; null 转换为 0;字符串处理失败时返回NaN;
规则2:以 0开头地十六进制数会被当成十进制;
规则3:对象会先被处理成相应地基本类型值,再按照值类型做相应处理;
对象做ToPrimitive操作规则:先valueOf(), 如果valueOf的结果不是基本类型,再进行 toString();如果均不返回基本类型,则报TypeError;
使用Object.create(null) 创建的对象,无valueOf 跟 toString 方法,故不能被强制类型转换
规则4:空数组和空字符串,都会被转换为0:
Number("") // 0; Number([]) // 0;
3、ToBoolean
规则1:undefined,null,false,+0,-0,NaN,“” 会被转换为false;但是对应的封装对象为true
规则2:js中 document.all 如果用在 if 语句中,会被转换为false;此条规则需要注意的是,在ie<= 10 的浏览器中,会返回true;
规则3:除规则1和规则2,其他都返回true
Boolean(undefined) // false; Boolean(null); // false; Boolean(false); // false; Boolean(+0); // false; Boolean(-0); // false; Boolean(""); // false; const a = new Boolean( false) ; // true const b = new Number(0); // true const c = new String("") ; // true if(document.all){
// ie10 及 以下,会被打印; console.log('document.all 在if语句中被强制类型转换,但转换的结果为false,这条语句不会被打印'); }
JSON.stringify 的规则与 强制类型转换关联,故也总结到这里:
JSON字符串化简单数值时,规则与toString大致相同;但是序列化的结果总是字符串:JSON.stringify("42") // ""42"";
undefined、function、symbol 和包含对象间循环引用的,都属于不安全的JSON值,这些值会被忽略,数组种则会返回 null,仪保证单元位置不变
例如:JSON.stringify([1,undefine,function(){},2]) // [1, null, null, 2];
JSON.stringify( {a:1, b: undefine, c: function(){} } ) // {a:1}; 此特性可用于发起请求时的无效参数过滤;
可自定义toJSON 函数,在JSON序列化时会被调用;该函数应该返回一个能够被字符串化的安全的JSON值,而不是直接返回一个字符串,因为处理逻辑是对toJSON返回的字符串做字符化;
JSON.stringify 支持传入可选参数replacer,用来指定对象序列化过程中属性的处理逻辑:
a、当replacer为数组时, JSON.stringify 只序列化数组中包含的属性,例如:
var a = { name: "Lily", age: 12 , hobby: ["basketball", "football"] } JSON.stringify(a, ["name", "age"]); // "{"name": "Lily" , "age": 12}"
// 这个特性可以用来提取对象中指定属性
b、当replacer为函数时, 函数会对将要被序列化的对象做一个遍历,入参为key 和 value,可在函数中写处理逻辑:
var a = { name: "Lily", age: 12 , hobby: ["basketball", "football"] } JSON.stringify(a, function(key, value){ // "{"name":"Lily","hobby":["basketball","football"]}"
if(key !== "age") return value;
});
这里有一个细节: 当replacer函数第一次被调用时,是对对象本身的调用,key 为 undefined;
属性的值为数组时,数组中的值也将被遍历,此时key为数组的索引,value为数组的单元值;
接下来,我们看下一些操作符在强制类型转换中的影响:
1、+ 操作符
+String : 会把字符串转换为数字;
+Date:会把时间对象转换为时间戳;同样使用 Date.now(); new Date().getTime()也可以得到时间戳;
String + other :首先 other 会被转换为字符串,再进行拼接操作;下面看一个例子:
{} + [] // 0 此处 {} 出现在语句的开始,会被解析为代码块,[]被转换为数字0 [] + {} // "[Object Object]" 此处遵循字符串相加规则,[] 为 “”, {}为 "[Object Object]"
Number + Boolean:Boolean 会先被转换为Number类型,再进行相加操作
- x / 等操作符,只针对Number类型有效,故这三个操作符会把操作数转换为数字类型;
2、位运算符 ~ 和 | 位运算符只适用于32位整数,故操作数会被ToInt32强制转换为32位格式;非数字类型的值会先通过ToNumber转换为 数字类型;
~ 运算(字位操作“非”)返回2的补码 ~x 大致等于 -(x+1);在~运算中,只有 x 为 -1 时,才会返回 0;这个特性可以用在将-1做为哨位值的运算中,避免暴露底层实现细节(抽象渗漏)
var a = "Hello world"; if(~a.indexOf("H")){ // 找到匹配 }
| 运算符(字位操作“或”)
0 | -0 // 0 0 | NaN // 0 0 | Infinity // 0 0 | -Infinity // 0
!运算符 :非运算符常出现在布尔值的隐式转换中;
3、&& 和 || 运算 : 该逻辑运算(更形象地可以说是操作数选择器运算)会返回其中一个操作数:
a && b : 首先对第一个操作数做条件判断,结果为 true, 则返回 第二个操作数 b;如果为 false, 则返回第一个操作数a;
a || b : 首先对第一个操作数做条件判断,结果为true,返回第一个操作数,条件判断结果为 false, 返回第二个操作数;
这里有一个细节,a || b 的条件判断语句中,如果 a为true,则不会再继续执行 b;在这个特性下,可以做个小小的优化:
a ? a : b 可被优化为: a || b;
另外一个优化点就是我们熟知的短路写法: b && b(); var a = b || 'default'; 等。
4、== 和 ===
宽松相等(==)和严格相等(===)的区别在于,宽松相等允许在相等比较中进行强制类型转换。他们都会对值类型进行检查,区别在于类型不同时他们的处理方式。
以下是 == 操作的一些规则:
规则1:NaN 不等于 NaN;+0 等于 -0;
规则2:对象的比较方式为:两个对象指向同一个值时,即为相等,不发生强制类型转换
强制转换规则:
a、字符串和数字之间:字符串先被转换成数字,再进行比较
b、其他类型和布尔值之间:布尔值要先被转换为数字,再进行比较
c、null 和 undefined之间的相等比较:null == undefined // 返回true
d、对象和非对象之间的比较:对象会被先转换为标量基本类型(字符串/数字/布尔值),然后再进行比较;
下面看一些容易被迷惑的例子:
var a = null; var b = Object(null); a == b // false; null 没有对应的对象类型,故 d 相当于Object()创建的一个普通对象 var c = undefined; var d = Object(d); c == d // false undefined 没有对应的对象类型,故 d 相当于Object()创建的一个普通对象
var e = NaN; var f = Object(NaN); e == f // false f被包装为Number类型,拆封后为NaN,NaN == NaN // false
"0" == false // true 根据规则 b , 布尔值跟其他值比较,要先把布尔值转换为数字,false -> 0; "0" == 0 为true
false == "" // true 根据规则b, false 被转换为 0; 根据规则a,字符串 "" 也被转换为 0
false == [] // true [] 先调用 valueOf,返回数组本身,继续调用 toString, 返回空字符串 ""; false == "" 为 true
false == {} // false {} 先调用 valueOf,返回 {} ; 继续调用 toString, 返回字符串 "[object Object]" , false == "[object Object]" 为 false
[] == ![] // true 首先计算 ![] 根据布尔值的转换规则,[] 为 true , 那么 ![] 则为false 即 [] == false 结果为 true
2 == [2] // true [2] 调用 valueOf 返回数组对象[2], 继续调用toString, 返回 "2" , 2 == "2" 结果为 true
"" == [null] // true 同样valueOf 返回数组对象 [null], toString 返回 "" ;
0 == "\n" //true 空字符串,或者空格,都会被ToNumber转换为0
"true" == true // false 这个简单, 布尔值会先被转换为Number, "true" == 1 结果为 false
5、关于 <= 和 >=
这次最让人意外的就是这俩操作符,我们来看个例子:
var a = {b: 42}; var b = {b: 43}; a < b // false; a > b // false; a == b // false; a <= b // true; a >= b // true;
所以 <= 和 >= 为什么都为true?
首先,a 和 b都会先被处理成字符串 "[object Object]", 故 > 和 < 比较都为 false;根据对象的 == 运算规则,地址一致为相等, 故 == 比较也为false;
根据 规范 a <= b 会被处理成 b < a, 然后将结果反转。因为 b < a 为 false, 故 a <= b 为 true;也就是说,js中的 <= 其实是 “不大于” 的意思。。。
另外,还有一些对数据类型做了转换的函数,也值得关注
1、parseInt() 显示解析数字字符串
和 Number的显示转换 不同,解析允许字符串中含有非数字字符,而Number显示转换则不允许:
Number("42px"); // NaN; 显示转换 parseInt("42px"); // 42 显示解析
Number(""); // 0
parseInt("") // NaN
parseInt针对的是字符串值,如果向parseInt传入了一些奇奇怪怪的参数,那转换结果也是会出乎意料的:
parseInt先将参数强制类型转换为字符串再进行解析
parseInt(0.000008) // 0 首先被转换为字符串“0.000008”;然后被解析出 0; parseInt(0.0000008) // 8 首先被转换为字符串“8e-7”, 然后被解析出 8 parseInt(false, 16) // 250 "false" 进行 16进制转换,前面的两个字母 “fa” 在16进制的字母内,被转换为250 parseInt(parseInt, 16) // 15 首先被解析为字符串“function (){}...” 首字母 “f” 以十六进制转换规则,被转换为十进制 15 parseInt("0x10") // 16 0x开头为16进制,所以只看后两位0*16^0+1*16^1=16,所以0x10=16 parseInt("103", 2) // 2 字符串103 以2进制规则转换,前两位 "10" 符合二进制数,被转换为 2, "3" 不在二进制表达范围内,故被舍弃,最终转换为十进制的结果为2;
最后,提一下es6中符号类型,它的强制转换都必须是显式的,但转换为布尔值除外;并且符号不能够被强制类型转换为数字:
var s1 = Symbol("cool"); String(s1) // "Symbol(cool)"; var s2 = Symbol("not cool"); s2 + "" ; // TypeError Boolean(s1) ; // true !!s1 // true
if(s1){
console.log('这条语句可以被执行');
}