JS 类型隐式转换
1、js 数据类型
js 中有 7 种数据类型,可以分为两类:原始类型、对象类型:
基础类型(原始值):
Undefined、 Null、 String、 Number、 Boolean、 Symbol (es6 新出的,本文不讨论这种类型) 复杂类型(对象值):
object
2、三种隐式转换类型
js 中一个难点就是 js 隐形转换,因为 js 在一些操作符下其类型会做一些变化,所以 js 灵活,同时造成易出错,并且难以理解。
涉及隐式转换最多的两个运算符 + 和 ==。
+运算符即可数字相加,也可以字符串相加。所以转换时很麻烦,有些也比较难理解:
[]+{}
"[object Object]"
{}+[]
"a"+[]
"a"
"a"+{}
"a[object Object]"
1+[]
"1"
1+{}
"1[object Obje
0
1+'0'
"10"
null+1
1
null+"q"
"nullq"
undefined+1
NaN
undefined+"b"
"undefinedb"
NaN+1
NaN
NaN+"c"
"NaNc"
== 不同于===,故也存在隐式转换。
undefined == null
true
null == 1
false
null == 0
false
undefined == 1
false
undefined == 0
false[1] == 1
true
var a = { 1: 1 }
undefined
a == 1
false
- / 这些运算符只会针对 number 类型,故转换的结果只能是转换成 number 类型,比较简单。
既然要隐式转换,那到底怎么转换呢,应该有一套转换规则,才能追踪最终转换成什么了。
隐式转换中主要涉及到三种转换:
1、将值转为原始值,ToPrimitive()。
2、将值转为数字,ToNumber()。
3、将值转为字符串,ToString()。
2.1、通过 ToPrimitive 将值转换为原始值
js 引擎内部的抽象操作 ToPrimitive 有着这样的签名:
ToPrimitive(input, PreferredType?)
input 是要转换的值,PreferredType 是可选参数,可以是 Number 或 String 类型。 他只是一个转换标志,转化后的结果并不一定是这个参数所值的类型,但是转换结果一定是一个原始值(或者报错)。
2.1.1、如果 PreferredType 被标记为 Number,则会进行下面的操作流程来转换输入的值。
1、如果输入的值已经是一个原始值,则直接返回它 2、否则,如果输入的值是一个对象,则调用该对象的 valueOf()方法, 如果 valueOf()方法的返回值是一个原始值,则返回这个原始值。 3、否则,调用这个对象的 toString()方法,如果 toString()方法返回的是一个原始值,则返回这个原始值。 4、否则,抛出 TypeError 异常。
2.1.2、如果 PreferredType 被标记为 String,则会进行下面的操作流程来转换输入的值。
1、如果输入的值已经是一个原始值,则直接返回它 2、否则,调用这个对象的 toString()方法,如果 toString()方法返回的是一个原始值,则返回这个原始值。 3、否则,如果输入的值是一个对象,则调用该对象的 valueOf()方法, 如果 valueOf()方法的返回值是一个原始值,则返回这个原始值。 4、否则,抛出 TypeError 异常。
既然 PreferredType 是可选参数,那么如果没有这个参数时,怎么转换呢?
PreferredType 的值会按照这样的规则来自动设置:
1、该对象为 Date 类型,则 PreferredType 被设置为 String
2、否则,PreferredType 被设置为 Number
2.1.3、valueOf 方法和 toString 方法解析
上面主要提及到了 valueOf 方法和 toString 方法,那这两个方法在对象里是否一定存在呢?
答案是肯定的。在控制台输出 Object.prototype,你会发现其中就有 valueOf 和 toString 方法,而 Object.prototype 是所有对象原型链顶层原型,所有对象都会继承该原型的方法,故任何对象都会有 valueOf 和 toString 方法。
对象的 valueOf 函数,其转换结果是什么?
对于 js 的常见内置对象:Date, Array, Math, Number, Boolean, String, Array, RegExp, Function。
1、Number、Boolean、String 这三种构造函数生成的基础值的对象形式,通过 valueOf 转换后会变成相应的原始值。如:
var num = new Number('123');
num.valueOf(); // 123
var str = new String('12df');
str.valueOf(); // '12df'
var bool = new Boolean('fd');
bool.valueOf(); // true
2、Date 这种特殊的对象,其原型 Date.prototype 上内置的 valueOf 函数将日期转换为日期的毫秒的形式的数值。
var a = new Date()
a.valueOf() // 1532234132475
3、除此之外返回的都为 this,即对象本身:
var a = new Array()
a.valueOf() === a
// true
var b = new Object({})
b.valueOf() === b
// true
toString 函数,其转换结果是什么?
对于 js 的常见内置对象:Date, Array, Math, Number, Boolean, String, Array, RegExp, Function。
1、Number、Boolean、String、Array、Date、RegExp、Function 这几种构造函数生成的对象,通过 toString 转换后会变成相应的字符串的形式,因为这些构造函数上封装了自己的 toString 方法。如:
Number.prototype.hasOwnProperty('toString') // true
Boolean.prototype.hasOwnProperty('toString') // true
String.prototype.hasOwnProperty('toString') // true
Array.prototype.hasOwnProperty('toString') // true
Date.prototype.hasOwnProperty('toString') // true
RegExp.prototype.hasOwnProperty('toString') // true
Function.prototype.hasOwnProperty('toString') // true
var num = new Number('123sd')
num.toString() // 'NaN'
var str = new String('12df')
str.toString() // '12df'
var bool = new Boolean('fd')
bool.toString() // 'true'
var arr = new Array(1, 2)
arr.toString() // '1,2'
var d = new Date()
d.toString() // "Sun Jul 22 2018 12:38:42 GMT+0800 (中国标准时间)"
var func = function() {}
func.toString() // "function () {}"
var obj = new Object({})
obj.toString() // "[object Object]"
Math.toString() // "[object Math]"
除这些对象及其实例化对象之外,其他对象返回的都是该对象的类型,都是继承的 Object.prototype.toString 方法。
从上面 valueOf 和 toString 两个函数对对象的转换可以看出为什么对于 ToPrimitive(input, PreferredType?)
PreferredType 没有设定的时候,除了 Date 类型,PreferredType 被设置为 String,其它的会设置成 Number。
- 因为 valueOf 函数会将 Number、String、Boolean 基础类型的对象类型值转换成 基础类型,Date 类型转换为毫秒数,其它的返回对象本身.
- 而 toString 方法会将所有对象转换为字符串。
- 显然对于大部分对象转换,valueOf 转换更合理些,因为并没有规定转换类型,应该尽可能保持原有值,而不应该想 toString 方法一样,一股脑将其转换为字符串。
所以对于没有指定 PreferredType 类型时,先进行 valueOf 方法转换更好,故将 PreferredType 设置为 Number 类型。
而对于 Date 类型,其进行 valueOf 转换为毫秒数的 number 类型。在进行隐式转换时,没有指定将其转换为 number 类型时,将其转换为那么大的 number 类型的值显然没有多大意义。(不管是在+运算符还是==运算符)还不如转换为字符串格式的日期,所以默认 Date 类型会优先进行 toString 转换。故有以上的规则:
PreferredType 没有设置时,Date 类型的对象,PreferredType 默认设置为 String,其他类型对象 PreferredType 默认设置为 Number。
2.2、通过 To Number 将值转换为数字
28.png
注意:
'12a'==12
false
2.3、通过 ToString 将值转换为字符串
根据参数类型进行下面转换: undefined ’undefined’ null ’null’ 布尔值转换为’true’ 或 ‘false’ 数字转换字符串,比如:1.765 转为’1.765’ 对象(obj)先进行 ToPrimitive(obj, String)转换得到原始值,在进行 ToString 转换为字符串 讲了这么多,是不是还不是很清晰,先来看看一个例子:
({} + {})
//[object Object]
两个对象的值进行+运算符,肯定要先进行隐式转换为原始类型才能进行计算。 1、进行 ToPrimitive 转换,由于没有指定 PreferredType 类型,{}会使默认值为 Number,进行 ToPrimitive(input, Number)运算。 2、所以会执行 valueOf 方法,({}).valueOf(),返回的还是{}对象,不是原始值。 3、继续执行 toString 方法,({}).toString(),返回"[object Object]",是原始值。 故得到最终的结果,"[object Object]" + "[object Object]" = "[object Object][object object]"
再来一个指定类型的例子:
2 * {}
//NaN
1、首先*运算符只能对 number 类型进行运算,故第一步就是对{}进行 ToNumber 类型转换。 2、由于{}是对象类型,故先进行原始类型转换,ToPrimitive(input, Number)运算。 3、所以会执行 valueOf 方法,({}).valueOf(),返回的还是{}对象,不是原始值。 4、继续执行 toString 方法,({}).toString(),返回"[object Object]",是原始值。 5、转换为原始值后再进行 ToNumber 运算,"[object Object]"就转换为 NaN。 故最终的结果为 2 * NaN = NaN
3、== 运算符隐式转换
类型相同时,没有类型转换,主要注意 NaN 不与任何值相等,包括它自己,即 NaN !== NaN。
==在比较的时候可以转换数据类型,若等式两边数据类型不相同,将会往数值类型方向转换后再进行比较
1、x,y 为 null、undefined 两者中一个 // 返回 true
2、x、y 为 Number 和 String 类型时,则转换为 Number 类型比较。
3、有 Boolean 类型时,Boolean 转化为 Number 类型比较。
4、一个 Object 类型,一个 String 或 Number 类型,将 Object 类型进行原始转换后,按上面流程进行原始值比较。
27.jpg
"" == 0 //ture
" " == 0 //ture
"" == true //false
"" == false //ture
" " == true //false
" " == true //false
" " == false //ture
"hello" == true //false
"hello" == false //false
"0" == true //false
"0" == false //true
"00" == false //true
"0.00" == false //true
undefined == null //ture
true =={} //false
[] == true //false
var obj = {
a: 0,
valueOf: function(){return 1}
}
obj == "[object Object]" //false
obj == 1 //true
obj == true //true
注意
1.==等好两边都为对象时,比较的是地址
[]==[]
false
{}=={}
false
2.对象转化成数值为 NaN,数组则不一定。
let arr = [123]
undefined
let obj = { 123: 123 }
undefined
Number(arr)
123
Number(obj)
NaN
obj == 123
false
arr == 123
true
1.png
3.1、== 例子解析
所以类型不相同时,可以会进行上面几条的比较,比如:
var a = {
valueOf: function() {
return1
},
toString: function() {
return '123'
}
}
true == a // true;
boolean 类型进行 ToNumber 转换为 1 object 类型,对 y 进行原始转换,ToPrimitive(a, ?),没有指定转换类型,默认 number 类型。 而后,ToPrimitive(a, Number)首先调用 valueOf 方法,返回 1,得到原始类型 1。 最后 1 == 1, 返回 true。
我们再看一段很复杂的比较,如下:
[] == !{}
true
1、! 运算符优先级高于==,故先进行!运算。 2、!{}运算结果为 false,结果变成 [] == false 比较。 3、转换数字类型结果变成 [] == 0。 比较变成 ToPrimitive([]) == 0。 按照上面规则进行原始值转换,[]会先调用 valueOf 函数,返回 this。 不是原始值,继续调用 toString 方法, [].toString() = ''。 故结果为 '' == 0 比较。 5、 ToNumber('') = 0。 所以结果变为: 0 == 0,返回 true,比较结束。
最后看看这道题目:
const a = { i: 1, toString: function () { return a.i++; } } if (a == 1 && a == 2 && a == 3) { console.log('hello world!'); }
1、当执行 a == 1 && a == 2 && a == 3 时,会从左到右一步一步解析,首先 a == 1,会进行上面第 9 步转换。ToPrimitive(a, Number) == 1。
2、ToPrimitive(a, Number),按照上面原始类型转换规则,会先调用 valueOf 方法,a 的 valueOf 方法继承自 Object.prototype。返回 a 本身,而非原始类型,故会调用 toString 方法。
3、因为 toString 被重写,所以会调用重写的 toString 方法,故返回 1,注意这里是 i++,而不是++i,它会先返回 i,在将 i+1。故 ToPrimitive(a, Number) = 1。也就是 1 == 1,此时 i = 1 + 1 = 2。
4、执行完 a == 1 返回 true,会执行 a == 2,同理,会调用 ToPrimitive(a, Number),同上先调用 valueOf 方法,在调用 toString 方法,由于第一步,i = 2 此时,ToPrimitive(a, Number) = 2, 也就是 2 == 2, 此时 i = 2 + 1。
5、同上可以推导 a == 3 也返回 true。故最终结果 a == 1 && a == 2 && a == 3 返回 true
再比如:
{}+[]
0
[]+{}
"[object Object]"
0+{}
"0[object Object]"
{}+0
0
是因为{}可以是空块或空对象文字,具体取决于上下文。
- 可以是一元加运算符,也可以是连接运算符,具体取决于上下文。
第一个代码示例是一个空块,它可能也不存在,使表达式相同+[],意思是“空数组转换为数字”。
你不能把一个块作为一个函数参数,所以第二个代码示例{}是一个对象,代码的意思是“用一个数组来传递一个对象”(隐式地将对象和数组转换为字符串)。换句话说,{} + []表达式是一个 empty 代码块,后面跟 array 一个数字(Number[])的约束。
在第二个示例中,您只是向 concat 数组提供了一个对象文字(空对象)。这就是你收到的原因[object Object]。 再比如:
null==0
false
null==1
false
+null
0
Null 类型的比较不等于 1 或 0(实际上,null 仅与undefined
相当),但当强制转换 ToNumber(null)===0;
最终 null==undefined//true ,实在无法理解,暂时只能理解这就是个坑,强行死记硬背。
原文:https://www.jianshu.com/p/7cb41d109b7e