JavaScript之数据类型(二)

24 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

如何使(a == 1 && a == 2 && a == 3)(宽松相等)的值为true?

要回答这个问题就需要对 数据类型转换 的知识有足够的了解。

数据类型转换

JavaScript是一种动态类型语言,可以显式调用内置函数进行强制类型转换。 也正因为变量不受类型限制,可以随时赋予任意类型的值,所以使用不同类型进行一些运输或者判断时,代码内部会进行隐式类型转换

显式转换(强制转换)

显式转换主要是通过Number()String()以及Boolean()三个函数,显示的将各种类型的值分别转换为数字、字符串以及布尔值。

  • Number()

使用Number()函数可以将任意类型的值转换为Number类型的值

原始值引用值的转换规则不同:

  1. 原始值
// 转换数值:转换后还是原值
Number(234); // 234

// 转换字符串:如果可以解析,则转为对应的 Number 类型的值
Number('234'); // 234

// 转换字符串:不可以解析,返回NaN
Number('324abc'); // NaN

// 对于空字符串:返回 0
Number(''); // 0

// 布尔值:true => 1  false => 0
Number(true); // 1
Number(false); // 0

// undefined:转换为 NaN
// null:转换为 0
Number(undefined); // NaN
Number(null); // 0

Number函数对字符串的处理要比parseInt函数严格

// Number函数转换字符串时,只有整个字符串可以完全转换为数值,才会输出正确的数字,否则返回NaN
Number('234ab'); // NaN
Number('ab234'); // NaN

// parseInt函数转换字符串时,会尽可能的将字符串转换为对应的数值、
parseInt('234ab'); // 234
parseInt('ab234'); // NaN
  1. 引用值(对象)

简单的规则:Number函数接收的参数是对象时,除了只包含单个原始值的数组外,其他情况直接返回NaN

Number({a: 1}; // NaN
Number([1, 2, 3]); //  NaN
Number([5]); // 5

原因:Number函数内部的转换规则如下:

1. 先调用自身的 valueOf 方法,如果返回原始值,则直接对该值使用 Number 函数进行转换,然后返回转换后的值。
2. 若不满足1,则继续调用对象自身的 toString 函数,如果 toString 函数返回原始值,则直接对该值使用 Number 函数进行转换,然后返回转换后的值。
3. 若1、2都不满足,则报错
let obj = {
    x: 1
}
Number(obj); // NaN

// ===> 相当于

let _tempVal = obj.valueOf(); // {x: 1}

if(typeof _tempVal === 'obejct') {
    // 调用valueOf返回的是对象
    let _tempStr = obj.toString(); // '[object Object]'
    Number(_tempStr); // NaN
} else {
    // 返回的原始值,直接调用Number函数进行转换
    Number(_tempVal); 
}

通过上述例子可以发现:对引用值的处理是依次调用对象身上的valueOftoString方法,满足条件则调用Number函数,并返回其结果。

也就是说:可以通过重写对象的valueOftoString方法,输出任何我们想要的值。

let obj = {
    // 将 valueOf 和 toString 方法都重写,并返回空对象 {}
    valueOf: function() {
        return {};
    },
    toString: function() {
        return {};
    }
}
// 第3条转换规则:valueOf 和 toString 都没有返回原始值,报错
Number(obj); // Cannot convert object to primitive value
  • String()

分为原始值引用值

  1. 原始值

     数字:转为对应的字符串
     字符串:转换后和原值相同
     布尔值:true ==> 'true'false ==> 'false'
     undefined:字符串'undefined'
     null: 字符串 'null'
    
  2. 引用值

      参数是对象:返回一个类型字符串
      参数是数组:返回该数组的字符串形式
    
String({a: 1}); // '[object Object]'
String([1, 2, 3]); // '1,2,3'

String函数内部的转换规则与Number函数大致相同,只是valueOftoString函数的执行顺序不同。

1. 先调用自身的 toString 方法,如果返回原始值,则直接对该值使用 String 函数进行转换,然后返回转换后的值。
2. 若不满足1,则继续调用对象自身的 valueOf 函数,如果 valueOf 函数返回原始值,则直接对该值使用 String 函数进行转换,然后返回转换后的值。
3. 若1、2都不满足,则报错
String({a: 1}); // '[object Object]'

// ===> 等同于

// 先调用对象的 toString 函数,返回字符串 '[object Object]',满足条件1
String({a: 1}.toString());

String([1, 2, 3]); // '1,2,3'

// ===> 等同于

// 先调用对象的 toString 函数,返回字符串 '1,2,3',满足条件1
String([1, 2, 3].toString());
  • Boolean()

布尔值的转换规则相对简单,除了以下五个值转换为false,其他都为true:

undefined
null
0(包括+0和-0)
NaN
''

隐式转换(自动转换)

隐式转换是基于显示转换的,以下情况会自动转换数据类型:

  • 不同数据类型进行运算
123 + 'abc'; // '123abc'
  • 对非布尔值类型求布尔值
if('abc') {
    console.log('hello');
} // hello
  • 对非数值类型使用一元运算符(+-
+{a: 1} // NaN
+[1, 2, 3] // NaN

转换规则如下:

  1. 转为布尔值

内部调用Boolean函数完成转换

== 运算符会隐式的将表达式结果转换为布尔值

if('false') {
    console.log(true)
} // true

// ===> 相当于

if(Boolean('false')) {
    console.log(true)
}

if(0) {
    console.log(false);
} // false

// ===> 相当于

if(Boolean(0)) {
    console.log(false);
}

  1. 转为字符串

先将引用类型转为原始类型的值,再将原始类型转为字符串。

主要发生在使用字符串进行加法运算

'5' + 1; // '51'
'5' + null; // '5null'
'5' + {}; // '5[object Object]'
'5' + []; // '5'
'5' + [1, 2]; // '51,2'
  1. 转为数字

内部自动调用Number函数完成转换。

除了加法运算符可能会把运算值转为字符串类型,其他运算符都会直接把运算值自动转为数字类型


'5' - '2'; // 3
'5' * 2; // 10
true - '1'; // 0
'5' * []; // 0
'abc' - 1; // NaN
undefined + 1; // NaN
null + 1; // 1
+false; // 0
-'abc'; // NaN

注:undefined转为数值为NaNnull转为数值为0

总结

了解了以上知识就可以回调本文一开始提到的问题:如何使(a == 1 && a == 2 && a == 3)的值为true?

// a == 1 && a == 2 && a == 3; ==> 返回 true ==> a = ?

let a = {
    value: 1,
    valueOf: function() {
        return this.value ++;
    }
}

if(a == 1 && a == 2 && a == 3) {
    console.log('Hello a!!!')
} // Hello a!!!

代码解释:

1. 将 a 定义为引用值
2. 第一个判断 a == 1,要将引用值 a 转换为 等式右边的原始类型 Number,根据Number转换规则,先调用 a 的valueOf方法,返回原始值 1(此时value变为2),并使用 Number 函数进行转换,最后进行判断。
3. 后续判断 a == 2a == 3转换规则同步骤2
4. 最终整个条件语句返回 true

根据转换规则,也可以重新 toString 方法使等式成立

value定义为 String 类型 '1',等式也成立