前言
我们都知道, Javascript 是一门弱类型语言,弱类型语言与强类型语言相比,最大的不同就是定义变量时不需要指定具体数据类型,以及不同类型的数据可以进行运算,这都归功于语言自己实现的隐式转换方法,而 Javascript 是如何做到的呢?
类型转换规则
上图就是 Javascript 的类型转换规则,但是多个不同类型的数据进行运算,隐式转换成统一类型的选择有很多,如何判断当前应该转换成哪种类型呢?
不同操作符的隐式转换优先级
加法运算 +
字符串在加法运算中有最高的优先级,与字符串相加必定是字符串连接运算,其他类型转换为字符串。
'a' + 1 // 'a1'
1 + 'a' // '1a'
true + 'a' // 'truea'
null + 'a' // 'nulla'
undefined + 'a' // 'undefineda'
如果没有字符串,则认为是数字的加法运算,其他类型转换为数字。
true + 1 // 2
null + 1 // 1
undefined + 1 // NaN
比较运算 > < ==
null 和 undefined 两者内部不全等,此外和谁都不等
null == undefined // true
null == null // true
undefined == undefined // true
null == 'null' // false
null == 0 // false
undefined == 'undefined' // false
undefined == NaN // false
当两者都是字符串时,直接用字符串比较(依次比较每个字符的ASCII编码的大小)
'11' > '2' // false
除此之外都是转换成数字进行比较
'11' > 2 // true
1 == true // true
'1' == true // true
'1' > null // true
其余运算符的转换
- 除加法外,减乘除等数字运算符全部转化为数字;
- 条件操作符 if ,while 等,全部转化为布尔;
- 成员访问时会转化成对应的类的实例;
- 全等 === 不会进行转换。
基础类型间的隐式转换
基础类型之间是可以隐式转化的,其实是调用三种方法: Number() , Boolean() , String() 。
Number
Number(true) // 1
Number('') // 0
Number('1') // 1
Number('-1') // -1
Number('a') // NaN
Number(null) // 0
Number(undefined) // NaN
Boolean
Boolean(1) // true
Boolean(-1) // true
Boolean(0) // false
Boolean(NaN) // false
Boolean(Infinity) // true
Boolean('') // false
Boolean('0') // true
Boolean(null) // false
Boolean(undefined) // false
String
String(1) // '1'
String(0) // '0'
String(NaN) // 'NaN'
String(Infinity) // 'Infinity'
String(true) // 'true'
String(null) // 'null'
String(undefined) // 'undefined'
Object 转换为基础类型
除上述基础类型之间的转换规则以外,我们会发现 Object 的转换规则各不相同,但是他们其实都在调用同一个抽象方法—— toPrimitive , Object 需要隐式转换时,都会先调用 toPrimitive 转换为基础类型后,再根据基础类型的规则继续转换。
toPrimitive(input, preferedType?)
参数解释:
- input 是输入的值,即要转换的对象,必选;
- preferedType 是期望转换的基本类型,他可以是字符串,也可以是数字。选填,默认为 Number ;
执行过程:
如果转换的类型是 Number ,会执行以下步骤:
- 如果 input 是原始值,直接返回这个值;
- 否则,如果 input 是对象,调用 input.valueOf() ,如果结果是原始值,返回结果;
- 否则,调用 input.toString() 。如果结果是原始值,返回结果;
- 否则,抛出错误。
如果转换的类型是 String ,步骤2和步骤3会交换执行,即先执行 input.toString() 方法。
你也可以省略 preferedType ,此时,日期会被认为是 String,而其他的值会被当做 Number 。
toString 和 valueOf
我们发现,无论哪种数据类型进行隐式转换,本质上都是在调用自身的 toString 和 valueOf 方法,那么我们来验证一下:
toString :
数字的 toString 结果和上述规则相同
布尔值以及对象等 toString 结果也与规则相同
valueOf :
基础类型的 valueOf 结果都是本身
函数和数组没有实现自己的 valueOf 方法,都通过原型链调用对象的 valueOf 方法,结果为本身。
隐式转换的妙用
判断类型
// 可通过Object.prototype.toString方法会返回类型字符串来区分各个类型
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call(''); // [object String]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(() => {}); // [object Function]
// ...
// 准确返回类型的方法
const getDataType = data => Object.prototype.toString
.call(data)
.replace(/^\[object\s(\w+)\]$/, '$1');
可参与计算的复杂类型
// 正常的数组
console.log([1, 2].toString()); // "1,2"
// 运算时自动取和的数组
const getNewArray = arr => {
arr.toString = function () {
return this.reduce((result, item) => result + item, 0);
};
return arr;
};
console.log(getNewArray([1, 2]) + 3); // 6
小结
隐式转换和运算符优先级一样,是很多人知道但没太在意过的知识,但是他们是检验你前端基础的有效利器。下面这道题,目前在我的面试中还没有候选人能完美地答出来,如果他学习过这些的话肯定会很轻松的过关吧。
var a;
// a = ???
if(a == 1 && a == 2 && a == 3) {
console.log('success');
}
// a 如何赋值可使 success 打印出来
// 答案
a = {
value: 0,
valueOf() {
return ++this.value;
}
};
// 写出打印结果,并解释原因
console.log([] == 0);
console.log(![] == 0);
console.log([] == []);
console.log(![] == []);
console.log({} == {});
console.log(!{} == {});
// 答案
// true true
// false true
// false false