开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
如何使(a == 1 && a == 2 && a == 3)(宽松相等)的值为true?
要回答这个问题就需要对 数据类型转换
的知识有足够的了解。
数据类型转换
JavaScript
是一种动态类型语言,可以显式调用内置函数进行强制类型转换
。
也正因为变量不受类型限制,可以随时赋予任意类型的值,所以使用不同类型进行一些运输或者判断时,代码内部会进行隐式类型转换
。
显式转换(强制转换)
显式转换
主要是通过Number()
、String()
以及Boolean()
三个函数,显示的将各种类型的值分别转换为数字、字符串以及布尔值。
Number()
使用Number()
函数可以将任意类型的值转换为Number
类型的值
对原始值
和引用值
的转换规则不同:
- 原始值
// 转换数值:转换后还是原值
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
- 引用值(对象)
简单的规则: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);
}
通过上述例子可以发现:对引用值
的处理是依次调用对象身上的valueOf
和toString
方法,满足条件则调用Number
函数,并返回其结果。
也就是说:可以通过重写对象的valueOf
和toString
方法,输出任何我们想要的值。
let obj = {
// 将 valueOf 和 toString 方法都重写,并返回空对象 {}
valueOf: function() {
return {};
},
toString: function() {
return {};
}
}
// 第3条转换规则:valueOf 和 toString 都没有返回原始值,报错
Number(obj); // Cannot convert object to primitive value
String()
分为原始值
和引用值
:
-
原始值
数字:转为对应的字符串 字符串:转换后和原值相同 布尔值:true ==> 'true',false ==> 'false' undefined:字符串'undefined' null: 字符串 'null'
-
引用值
参数是对象:返回一个类型字符串 参数是数组:返回该数组的字符串形式
String({a: 1}); // '[object Object]'
String([1, 2, 3]); // '1,2,3'
String
函数内部的转换规则与Number
函数大致相同,只是valueOf
和toString
函数的执行顺序不同。
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
转换规则如下:
- 转为布尔值
内部调用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);
}
- 转为字符串
先将引用类型转为原始类型的值,再将原始类型转为字符串。
主要发生在使用字符串进行加法运算
'5' + 1; // '51'
'5' + null; // '5null'
'5' + {}; // '5[object Object]'
'5' + []; // '5'
'5' + [1, 2]; // '51,2'
- 转为数字
内部自动调用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
转为数值为NaN
,null
转为数值为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 == 2、a == 3转换规则同步骤2
4. 最终整个条件语句返回 true
根据转换规则,也可以重新 toString
方法使等式成立
value
定义为 String
类型 '1'
,等式也成立