解锁JavaScript类型转换的魔法
隐式类型转换
原始值之间的转换/原始值与对象之间的转换
在JavaScript中可以根据任意类型的值进行类型的自动转换,下面的表格为各种类型之间的相互转换。
转为数字的情况比较微妙,以数字表示的字符串可以直接转换为数字,也允许在开始和结尾处带有空格。若开始和结尾处有任意非空格字符则会转换为NaN。
let str = ' 123'
str - 1 // 122
let str = 'a123'
str - 1 // NaN
上面主要是针对原始值类型的转换,比较重要的是原始值到对象的转换,原始值通过调用Boolean()、Number()、String()
方法转为它们各自的包装对象。
包装对象
不知道大家有没有想过字符串中为什么会有属性,比如:
let s = "string";
s.length; // 6
这说明在读取字符串、数字和布尔值的时候JavaScript会通过Boolean()、Number()、String()
构造函数(new String()...)创建一个临时对象,这个对象继承了构造函数中的属性和方法,所以才可以访问到,这个临时对象就是包装对象。 null
和undefined
没有包装对象。
let s = 'test';
s.len = 'len test';
s.len; // undefined
由上述代码可以看出包装对象在属性引用结束之后就会被销毁。第二行代码创建了一个临时的字符串对象,并且为len属性赋值,随即便销毁了该对象,因此再往下就访问不到该属性了。
也就是说在读取字符串、数字和布尔值的属性值或方法时表现得像对象一样。但如果试图给其属性赋值则会忽略这个操作。修改只是发生在临时对象身上,而这个临时对象并未继续保留下来。
==
运算符将原始值和其包装对象视为相等,===
运算符将原始值和其包装对象视为不相等,可以使用typeof
看出两者的不同。
let s = "test"
let S = new String('test')
s == S // true
s === S // false
typeof s // string
typeof S // object
对象转原始值
上面说完了原始值与原始值之间的转换以及原始值与对象之间的转换,下面说下对象如何转为原始值,这个过程稍微复杂了一点。当原始值与非原始值进行比较时,JavaScript 会尝试将两个操作数转换为相同的类型,js会先调用对象的valueOf
方法,如果不存在会调用toString
方法,valueOf
会默认返回对象本身,此时返回的值若还不是原始值则会继续调用toString方法。
toString
方法的作用是返回一个该类型的字符串格式。
valueOf
方法的作用是返回对象的原始值形式。对于原始值来说返回其本身,对于非原始值来说,由于无法直接将非原始值转为原始值,所以也会默认返回非原始值本身。
这两个方法是可以进行自定义的,后面应用中会说到。
举几个例子:
let arr = [1,2,3]
arr.toString() // "1,2,3"
let obj = {}
obj.toString() // '[object Object]'
上面的实例都是需要注意的地方,数组转为字符串会把每个元素转为字符串,对象会转为[object Object]。
let arr = [];
Number(arr) // 0
let arr1 = [1]
Number(arr1) // 1
解释一下这里为什么空数组转换后会输出0,首先会调用对象的valueOf
方法,该方法返回其原始值,但是返回的是其数组对象本身而不是一个原始值,所以会继续调用toString
方法转换成''
空字符串,空字符串转换成数字0。第二个例子也是同样的道理。
显示类型转换
显示类型转换就是使用Boolean()、Number()、String()、Object()
函数,有几个特别需要注意的:
Boolean({}) // true
Boolean([]) // true
Boolean(Infinity) // true
Boolean(-Infinity) //true
Object(3) // new Number(3)
Object(undefined) // {}
Object(null) // {}
需要注意的是空对象和空数组转为布尔值都是true,undefined和null类型使用Object转换都为空对象。
应用
下面来对上面的知识进行巩固,看下面这样一道面试题。
// 如何让这段代码成立并进行输出
if(a == 1 && a == 2 && a == 3){
conosle.log("You win !")
}
这道题有两种方法来实现,分别是使用toString/valueOf
和Object.defineProperty
方法。
方法一
let a = {
count: 0,
valueOf() {
return ++this.count;
}
}
当然这里也可以自定义toString
方法,因为js如果没有检测到valueOf
方法会自动调用对象中的toString
方法。通过将a
声明为一个对象,然后通过自增自身的count
属性的方式,与原始值进行比较,这样就会发生隐式类型转换,会默认调用对象中的valueOf/toString
方法,看返回的是否是原始值(不是的话则继续调用toString方法)这样一来就满足了第一个条件,依此类推,所有等式就都成立了。
方法二
第二种方法相对来说就比较容易想到了,就是通过对对象使用数据劫持的方式。
let count = 0;
Object.defineProperty(window, "a", {
get() {
return ++count;
}
})
通过在window
对象上定义一个被劫持的a
属性,然后在每次访问该属性时也会触发自增操作,所以也可以使得整个等式成立。
总结
这篇文章主要讲解了隐式类型转换和显示类型转换以及穿插了应用及包装对象的概念,希望对大家有所帮助!
转载自:https://juejin.cn/post/7371649020122890240