你绝对用过但不见得关心过的JS隐式转换

前言

我们都知道, 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 ,会执行以下步骤:

  1. 如果 input 是原始值,直接返回这个值;
  2. 否则,如果 input 是对象,调用 input.valueOf() ,如果结果是原始值,返回结果;
  3. 否则,调用 input.toString() 。如果结果是原始值,返回结果;
  4. 否则,抛出错误。
    如果转换的类型是 String ,步骤2和步骤3会交换执行,即先执行 input.toString() 方法。

你也可以省略 preferedType ,此时,日期会被认为是 String,而其他的值会被当做 Number 。

toString 和 valueOf

我们发现,无论哪种数据类型进行隐式转换,本质上都是在调用自身的 toString 和 valueOf 方法,那么我们来验证一下:

toString :

数字的 toString 结果和上述规则相同


数字转换成字符串

布尔值以及对象等 toString 结果也与规则相同


其他类型转换成字符串

valueOf :

基础类型的 valueOf 结果都是本身


基础类型的 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

推荐阅读更多精彩内容