目前在学前端,第一次写掘金博客,想与大家一起学习,是参考了掘金上一些优秀文章整合出来的,有什么问题小伙伴们可以留言啊!!!一起进步!!!
在js中隐式转换类型总是返回基本类型值(如数字,布尔值等),不会返回对象或者函数。
一、js内部用于实现类型转换的4个函数
在ECMAScript定义了4个抽象的操作,在js内部分别为:
- ToPrimitive(input [,type]) 将值转换为基本类型值
- ToString(argument) 将值转换为字符串类型的操作
- ToNumber(argument) 将值转换为数字类型的操作
- ToBoolean(argument) 将值转换为布尔值类型的操作
(一) ToPrimitive
JS中每个值隐含的自带的方法,用来将值(无论是基本类型还是引用类型)转换为基本类型值。
如果值为基本类型,则直接返回值本身,如果值为对象,其看起来大概是这样:
Toprimitive(obj, type)
obj:需要转换的对象
type:期望的结果类型,type的值可以是number 或者 string, 默认情况下为 number,但 Date对象特殊,其默认为 string
-
当type 的值为number时规则如下:
(1). 调用 obj的valueOf 方法,若为原始值,则返回,否则下一步
(2). 调用 obj的 toString 方法,如果为原始值,则返回,否则下一步
(3). 如果 toString方法 返回的是对象,且该对象不是自己重写的,则调用原型链顶端的toString方法,即Object.prototype.toString.call(对象) 如果是自己重写的,则报错,抛出TypeError异常
const obj = {
toString() {
return 2
},
valueOf() {
return 1
}
}
Number(obj) //返回值为 1,想转化为数字型,先调用valueof
-
** 当type 的值为string时规则如下:**
(1). 调用 obj的 toString 方法,如果为原始值,则返回,否则下一步
(2). 调用 obj的valueOf 方法,若为原始值,则返回,否则下一步
(3). 抛出TypeError异常
const obj = {
toString() {
return 2
},
valueOf() {
return 1
}
}
String(obj) //'2' 想转化为字符型,先调用toString方法
注意为引用类型类型时,且没有重写 toString方法时,使用的是原型链顶端的toString方法,即Object.prototype.toString.call (对象)
(二) ToString
这里所说的ToString可不是对象的toString方法,而是指其他类型的值转换为字符串类型的操作。规则如下:
-
null:转为 "null"
-
undefined 转为 ”undefined“
-
布尔类型:
true和false分别被转为"true"和"false" -
数字类型:转为数字的字符串形式,如
10转为"10",1e21转为"1e+21" -
Symbol() : 抛出异常
-
数组:转为字符串是将所有元素按照","连接起来,内部调用数组的
Array.prototype.join()方法,如[1, 2, 3]转为"1,2,3",空数组[]转为空字符串,数组中的null或undefined,会被当做空字符串处理 -
普通对象:转为字符串相当于直接使用
Object.prototype.toString(),返回"[object Object]"
String(null) //'null'
String(undefined) //'undefined'
String(true) //'true'
String(10) //'10'
String(011) //'9'
String(1e21) //'1e+21'
String([1, 2, 3]) // '1,2,3'
String([]) // ''
String([null]) // ''
String([1, undefined, 3]) // '1,,3'
String({}) // '[object Object]'
String({a:1, b:'22'}) // '[object Object]'
// 拥有toString() 方法的值调用 String()函数时,实际上就是在调用 toString()方法,如String([])等价于[].toString()
// null 和undefined没有 toString()方法,所以对这两个调用 toString()时会报错
注意:上面所说的规则是在默认的情况下,如果修改默认的`toString()`方法,会导致不同的结果
(三) ToNumber
指其他类型转换为数字类型的操作。
- null:转为0
- undefined: 转为 NaN
- 布尔值:true 和 false分别被转为 1 和 0
- 字符串:如果是纯数字,转化为对应的数字,空字符串(包括只有纯空格的字符串)转为0,否则一律按转换失败处理,转为 NaN
- Symbol() :抛出异常
- 数组:数组首先会被转为原始类型,也就是
ToPrimitive,然后在根据转换后的原始类型按照上面的规则处理 - 对象:同数组的处理
Number(null) // 0
Number(undefined) // NaN
Number('10') // 10
Number('10a') // NaN
Number('') // 0
Number(' ')//0
Number(true) // 1
Number(false) // 0
Number([]) // 0 []调用toString()转为原始数据:'',然后将其转为数字类型 0
Number(['1']) // 1 ['1']调用toString()转为原始数据:'1',然后将其转为数字类型 1
Number(['1','2']) //NaN ['1','2']调用toString()转为原始数据:字符串'1,2',然后转为NaN
Number({}) // NaN {}调用toString()被转为原始类型:字符串'[Object Object]',然后将其转为数字NaN
//修改了默认的toString 和valueOf方法
const obj1 = {
valueOf () {
return 100
},
toString () {
return 101
}
}
Number(obj1) // 100
const obj2 = {
toString () {
return 102
}
}
Number(obj2) // 102
const obj3 = {
toString () {
return {}
}
}
Number(obj3) // TypeError
(四) ToBoolean
指其他类型转换为布尔类型的操作。
js中的假值只有false、null、undefined、''、0和NaN6个,其它值转为布尔型都为true。
注意 带有空格的空串也转为 true.
Boolean(null) // false
Boolean(undefined) // false
Boolean('') // flase
Boolean(NaN) // flase
Boolean(0) // flase
Boolean(' ') //true
Boolean([]) // true
Boolean({}) // true
Boolean(Infinity) // true
二、隐式类型转换规则
2.1+操作符
- 两边有至少一个为 String 类型时,两边的变量都会被隐式转换为字符串;
- 当一侧为Number类型,另一侧为引用类型时,将引用类型和Number类型转换成字符串后拼接;
- 其余情况均转换为Number
即如果 +操作符其中一个操作数为字符串(或者通过ToPrimitive操作之后最终得到字符串),则执行字符串的拼接,否则执行数字加法。
注意语句开始时为{ }时,会被解析为代码块,所以 {} + '1' 结果为 1,因为 {}被js解析器解析为代码块,实质上运算的是 +'1',所以结果为 1,如果用小括号括起来就可以了
console.log(undefined + '2') //'undefined2' 隐式转换,将undefined转换为字符串'undefined'
console.log(['1',2,'2'] + '1') //'1,2,21'
console.log([] + '1') //'1' []-->''
console.log(1 + '23') //'123'
console.log(1 + false) //1
console.log(true + false) //1
{} + [] //0, 当语句开始为{时,会被JS解释器认为是代码块,所以实质上运算的是+[],将空数组转为Number,得0
[] + {} //[object Object] 使用 ToPrimitive 方法,[]转为'',而{}转为[object Object],最终相加得[object Object]
let a = {name:'hhh'}
let b = {age:13}
console.log(a + b) //[object Object][object Object]
console.log(1 + 1 + '23') //'223' 从左向右 1+1结果为2,2+‘23’字符串拼接
console.log(1 + '23' + 4 + 5) //'12345'
两个操作数都是数值时,规则为:
- 如果一个操作数为NaN,则结果为 NaN
- 如果是 Infinity + Infinity,结果是 Infinity
- 如果是 -Infinity + (-Infinity ),结果是-Infinity
- 如果是Infinity + (-Infinity ),结果是NaN
- 如果是 +0 + (+0),结果是 +0
- 如果是(-0) + (-0),结果是 -0
- 如果是 (+0) + (-0),结果是 +0
2.2 -、*、/ 操作符
会先将非Number 类型转换为数字
console.log(25 - '23'); //2
console.log(1 * false); //0
console.log(1 / 'aa'); //NaN 'aa'转为数字 NaN,所以 1/NaN 返回NaN
console.log(2 * ['5']) //10
2.3 == 操作符
1. 布尔类型和其他类型比较
只要布尔类型参与比较,会首先将 布尔类型的值转换为数字类型
console.log(3 == true); //false 3-->3, true --> 1
console.log('0' == false); //true false-->0, '0'-->0
2. 数字类型和字符串类型比较
当数字类型与字符串相等比较时,字符串类型会先被转换为数字类型
根据字符串的 ToNumber规则,如果是纯数字形式的字符串,则转为对应的数字,空字符串转为0,否则一律按转换失败处理,转为NaN。注意Number('Infinity') 转换为 Infinity数字
console.log('0' == 0); //true
console.log('' == 0); //true
console.log('12a' == 12) //false
console.log(Infinity == 'Infinity') //true
console.log(Infinity == Infinity); //true
3. 对象与原始数据比较
当对象类型 和 原始类型作相等比较时,对象类型会依照 ToPrimitive 规则转换为 原始类型。
'[object Object]' == {} // true
'1,2,3' == [1, 2, 3] // true
[2] == 2 //true 先将 [2]使用toString转换为原始类型:'2',现在变为字符串和数字比较,所以将字符串'2'转为数字类型 2
[null] == 0 // true 先将[null]使用toString()转换为原始类型:空串'',同样为字符串和数字类型比较,将字符串''转为数字类型为0,所以返回 true
[undefined] == 0 // true 先将[undefined]使用toString()转换为原始类型:空串'',同样为字符串和数字类型比较,将字符串''转为数字类型为0,所以返回 true
[] == 0 // true []--> '' ,''--> 0
const a = {
valueOf () {
return 10
}
toString () {
return 20
}
}
a == 10 // true 首先调用a 的valueOf方法得到原始数据 10
> 对象的`ToPrimitive`操作会先调用`valueOf`方法,并且`a`的`valueOf`方法返回一个原始类型的值,所以`ToPrimitive`的操作结果就是`valueOf`方法的返回值`10`。
4. null、undefined和其他类型比较
null 和 undefined 相等的结果为 true, ECMAScript规范中 规定null 和 undefined之间互相 宽松相等(==),并且也与其自身相等,但和其他所有值都不相等
console.log(null == undefined); //true
console.log(null == null); //true
console.log(undefined == undefined); //true
console.log(null == ''); //false
console.log(null == {}); //false
5.如果一个操作值为NaN
如果一个值为NaN,则相等比较返回false(NaN本身也不等于NaN)
6.如果两个操作值都为对象
如果两个操作值都为对象(不会发生类型转换),则比较它们是不是指向同一个对象,即内存地址
2.4>、<、>=、<= 关系操作符
-
两边均为字符串时,则比较字符串的字符编码值
可使用 String.prototype.charCodeAt()方法得到字符串给定索引处的字符编码值
// 按照字符串的字符编码值
console.log('c' > 'b'); //true
console.log('de' > 'fg'); //false
console.log('1' > '2') //false
console.log('2' > '12') //true
-
只有一个操作数是数值,则将另一个操作值转换为数值,进行数值比较
注:NaN是非常特殊的值,它不和任何类型的值相等,包括它自己,同时它与任何类型的值比较大小时都返回false。即任何关系操作符在涉及比较NaN时都返回 false。
console.log('12' < 13); //true
console.log(false < -1); //false
console.log(NaN > 0) //false NaN与任何值比较都返回false
console.log(Infinity > 0) //true
-
如果有布尔值,将其转换为数值再执行比较
-
如果有对象,将其转化为原始值再根据前面的规则执行比较
// 对象
let a = {}
console.log(a > 2); //false, 转换过程,首先调用 valueOf()--> 对象 {}, 再调用toString() --> '[object Object]'
console.log(a.valueOf()) //{}
console.log(a.toString()); //'[object Object]'
console.log(Number(a.toString())); //NaN
2.5 递增递减++、--(前置与后置)、一元正负 操作符
一元正负操作符:使用ToNumber,即调用Number()函数进行转换为数值型
+[] //0
+true //1
+{} //NaN,{}首先转化为原始类型:字符串'[Obeject Obeject]',故为NaN
+undefined //NaN
+null //0
+'234' //234
+'234a' //NaN
+' 23' //23
+' ' //0
自增、自减操作符:只针对变量(常量不可用)
a++、++a或者a--、--a在运算中等同于: a = a+1;或者 a= a-1; 如果此处是常量如: 3 = 3+1,常量是不允许赋值的。所以自增、自减只针对变量
前置自增、自减:符号在前,先变值再赋值
后置自增、自减:符号在后,先赋值再变值
自增自减操作符就是变量使用ToNumber()规则转换为数值类型,然后进行自增自减操作,最后变量变为数值变量。
-
如果是包含有效数字字符的字符串,先将其转换为数字值(转换规则同Number()),再执行加减1的操作,字符串变量变为数值变量。
-
如果是不包含有效数字字符的字符串,将变量的值设置为NaN,字符串变量变成数值变量。
-
如果是布尔值,先将其转换为0或1再执行加减1的操作,布尔值变量变成数值变量。
-
如果是浮点数值,执行加减1的操作。
-
如果是对象,先使用ToPrimitive规则转换为原始类型,再加减1。对象变量变成数值变量。
// 包含有效数字字符的字符串
let n = '123'
console.log(n++) //123
console.log(n); //124 ,数值变量
// 不包含有效数字字符的字符串
let n1 = '12a'
console.log(n1++) //NaN
console.log(n1); //NaN ,数值变量
// 变量是布尔值
let n2 = true
console.log(++n2) //2
console.log(n2); //2 ,数值变量
// 变量是对象
let n3 = {}
console.log(++n3) //NaN
console.log(n3); //NaN ,数值变量
2.6 !、&&、|| 逻辑运算符
逻辑非 (!) 首先通过Boolean() 函数将操作值转换为布尔值,然后求反。
逻辑与(&&) 操作符,如果一个操作值不是布尔值,遵循以下规则进行转换:
- 如果第一个操作数经Boolean()转换后为false,返回第一个值,否则返回第二个值(不是Boolean()转换后的值)
- 多个操作符串联时,返回第一个虚值表达式,如果没有 找到任何虚值表达式,则返回最后一个真值表达式,采用短路来防止不必要的工作。
虚值只有6个(0,NaN,'',undefined,null,false)
console.log( 1 && 2) //2
console.log('0' && 200); //200
console.log(NaN && {}); //NaN
console.log(" " && true && 5); //5
逻辑或(||) 操作符,如果一个操作值不是布尔值,遵循以下规则进行转换:
- 如果第一个操作数经Boolean()转换后为true,返回第一个值,否则返回第二个值(不是Boolean()转换后的值)
- 多个操作符串联时,返回第一个真值表达式,采用短路来防止不必要的工作。可以用于初始化函数中的默认参数值
console.log( null || 1 || undefined) //1
function logName(name) {
let n = name || "mark" //用于初始化函数中的默认参数值name
console.log(n)
}
三、类型转换相关的题目
3.1 笔试题
'2' > '10' == true //true '2'>'10' 大于号两边均为字符串,所以比较对应的字符编码,故为 true,变为true == true 故返回 true
[] == ![] // true 首先对[]取反为false 变为 [] == false,有布尔值时,先将布尔值转为数值型,所以首先将false转为数字0,[]转为原始类型 '',所以返回true
[] == 0 // true [] --> '' --> 0 0==0
[2] == 2 // true [2]-->'2' 变为'2' == 2
['0'] == false // true false-->0, ['0'] --> '0'
'0' == false // true false-->0, '0'-->0
[] == false // true false-->0, [] --> '' -->0
[null] == 0 // true [null]-->''-->0
null == 0 // false null与除自身和undefined以外所有的数据都不相等
null == false // false
[null] == false // true
[undefined] == false // true
undefined == false // false
[] == [] //false 两个对象进行相等比较,比较的是内存地址
{} == {} //false
{} == !{} //false !{}转为false,再转为数值型为0,{}进行转换为原始类型:'[Object Obeject]' ,转为数值类型为NaN, 即判断 NaN == 0,返回false
3.2 变量a在什么情况下会执行输出语句打印1,即a==1 && a==2 && a==3
let a = ?
if (a == 1 && a == 2 && a == 3) {
console.log(1);
}
分析:这道题考查的知识点是:相等运算符(==)在作比较时会进行隐式转换,而如果操作数是引用类型,则会调用 toString() 或 valueOf() 方法对引用类型数据进行隐式转换。
只要改变原生的 valueOf 或者 toString方法就可以达到效果。
// 方法一:利用toString方法
let a = {
// 定义一个属性来做累加
i:1,
toString() {
return a.i++
}
}
if(a == 1 && a == 2 && a == 3) {
console.log(1);
}
// 方法二:利用valueOf方法
let a = {
// 定义一个属性来做累加
i: 1,
valueOf() {
return a.i++
}
}
if(a == 1 && a == 2 && a == 3) {
console.log(1);
}
// 方法三:利用 Symbol,改写ES6的symbol类型的toPrimitive
let a = {[Symbol.toPrimitive]: ((i) => ()=> ++i)(0)}
if(a == 1 && a == 2 && a == 3) {
console.log(1);
}
//方法四:利用Object.difineProperty()
let val = 0
Object.defineProperty(window, 'a', {
get(){
return ++val
}
})
//方法五:利用数组(非常厉害)
let a = [1,2,3]
a.join = a.shift
if(a == 1 && a == 2 && a == 3) {
console.log(1);
}
方法三:ES6 中提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
Symbol.toPrimitive 就是其中一个,它指向一个方法,当该对象被转为原始类型的值时,会调用这个方法,并返回该对象对应的原始类型值。这里就是改变这个属性,把它的值改为一个闭包返回的函数。
方法四:a.join = a.shift 的目的是将数组的 join 方法替换成 shift 方法。因为数组在参与相等比较时也会通过 toString() 将数组转为字符串,而该字符串实际上是数组中每个元素的 toString() 返回值经调用 join() 方法拼接(由逗号隔开)组成。现在我们将 join() 方法替换为了 shift() 方法,也就意味着数组在通过 toString() 隐式转换后,得到是 shift() 的返回值,每次返回数组中的第一个元素,而原数组删除第一个值,正好可以使判断成立。
Array.prototype.toString()会在内部访问join方法,不带参数 。覆盖一个数组实例的join也将覆盖它的toString行为。当在稀疏数组上使用时,
join()方法迭代空槽,就像它们的值为undefined一样。
join()方法是通用的。它只期望this值具有length属性和整数键属性。
3.3 a在什么情况下会执行输出语句打印1,即a===1 && a===2 && a===3
if(a === 1 && a === 2 && a === 3) {
console.log(1);
}
=== 没有类型转换,但还是可以的。在Vue源码实现双向数据绑定中,就利用了 defineProperty方法进行观察,观察到数据的变化并实时反映到视图层。每一次访问对象中的某一个属性时,就会调用这个方法定义的对象里面的get方法。每一次改变对象属性的值,就会访问 set方法。
自己定义get 方法
var b = 1
Object.defineProperty(window, 'a', {
get: function() {
return b++
}
})
var s = (a ===1 && a === 2 && a === 3)
console.log(s); //true
每一次访问 a 属性,a 的属性值就会 +1。proxy也是类似的方法,都可以实现
参考