JS - 这一次彻底搞懂 "x == y" 的判断逻辑
程序员文章站
2024-02-08 21:40:16
...
javascript 是弱类型语言,在进行 == 运算的时候 ,如果等号两边的数值类型不同的时候,会进行类型转换。
JavaScript语言的数据类型转换一直被很多人诟病,认为它晦涩难懂,使得很多人对他敬而远之。
但其实如果你知道了其中的原理,数据类型转换不仅不会困扰到你,反而会让你的代码可读性大大增强。也因此对于数据类型转换的掌握程度成为了判断一个前端开发工程师是否为一个老司机的主要标准。
1. 类型转换的逻辑算法
关于如何进行类型转换,ECMA 的标准里面有说明,如下所示:
归纳起来,是按照以下的顺序来执行判断逻辑的:
- 首先判断
x
和y
的数据类型,如果数据类型相同,则判断值是否相同,如果相同则为true
, 否则为false
。其中需要注意的是Number
类型,如果 x 和 y,二者中至少有一个为NaN
,则为false;
。如果类型不同,则会进行类型转换。 - 如果
x
和y
,二者中一个为null
,一个为undefined
,则为true
; - 如果
x
和y
中,一个是Number
类型,一个是String
类型,则将String
类型的数据转换为Number
类型; - 如果
x
和y
中,一个是Boolearn
类型,则将Boolearn
类型的数据转换为Number
类型,true -> 1
,false -> 0
; - 如果
x
和y
,二者中一个为Object
,另一个为Number
或String
,则为会调用ToPrimitive(Object)
,然后再将其返回值跟另一个值进行比较; - 如果不满足上述的判断,则返回
false
。除了null
和undefined
以外,其他类型的数据,与null
和undefined
进行==
比较时,直接返回false
。
2. ToPrimitive
上述中,当 x
和 y
中有一个是 Object
类型时,会先进行 ToPrimitive()
的运算,于是继续查找 ToPrimitive()
的标准。
关于 OrdinaryToPrimitive(input, hint)
,标准如下:
看了以上的标准,关键信息总结如下:
- 如果传入的
hint
是String
,先判断toString
能否调用,再判断toString
() 的结果,是基本类型才返回,再判断valueOf
能否调用,再判断valueOf()
的结果,是基本类型才返回,否则报错。 - 如果传入的
hint
是Number
(或者没有hint
,默认是Number
),先判断valueOf
,再判断toString
。 - 对于普通
Object
,默认用hint
为Number
的方式来转换,对于Date
类型的Object
,用hint
为String
的方式来转换。 - 同时,因为
Object
的valueOf
和toString
方法有可能会被重写,所以会调用重写后的valueOf
和toString
。
关于宽松相等 “==” 的判断,MDN 官网上有总结:
3. 常见 Object 类型的 valueOf() 和 toString() 返回值
// 普通 object
let obj1 = {};
let obj2 = {a: 1, b: 'obj', c: function(){}, d: undefined, e: null};
let obj3 = new Object('test');
let obj4 = new Object(1);
let obj5 = new Object({a: 1});
// 普通 object 的 valueOf
obj1.valueOf(); // {}
obj2.valueOf(); // {a: 1, b: 'obj', c: function(){}, d: undefined, e: null}
obj3.valueOf(); // test
obj4.valueOf(); // 1
obj5.valueOf(); // {a: 1}
// 普通 object 的 toString
obj1.toString(); // "[object Object]"
obj2.toString(); // "[object Object]"
obj3.toString();// "test"
obj4.toString();// "1"
obj5.toString();// "[object Object]"
// 数组
let arr1 = [];
let arr2 = [undefined];
let arr3 = [null];
let arr4 = [1, '1'];
let arr5 = [{a: 1}, {fn: function(){}}];
// 数组的 valueOf
arr1.valueOf(); // []
arr2.valueOf(); // [undefined]
arr3.valueOf(); // [null]
arr4.valueOf(); // [1, '1']
arr5.valueOf(); // [{a: 1}, {fn: function(){}}]
// 数组的 toString
arr1.toString(); // ""
arr2.toString(); // ""
arr3.toString(); // ""
arr4.toString(); // "1,1"
arr5.toString(); // "[object Object],[object Object]"
// 函数
let fn = function(){};
// 函数默认的 valueOf
fn.valueOf(); // function(){}
// 函数默认的 toString
fn.toString(); // "function(){}"
// 函数的 valueOf 和 toString 方法可以被改写
let fn1 = function() {};
fn1.valueOf = function(){
return '改写后的valueOf';
};
fn1.toString = function(){
return '改写后的toString';
};
fn1.valueOf(); // "改写后的valueOf"
fn1.toString(); // "改写后的toString"
4. 实例
[] == ![]; // true
// 首先 ![] 为 Boolean 类型,等号两边的数据类型不同,会将 ![] 进行类型转换,因为 ![] 为 false,所以转为数值类型 0, 此时变为了 [] == 0,调用 [] 的 ToPrimitive 方法,先执行 [].valueOf(),返回值为 [],不是基本数据类型,从而继续调用 [].toString(),返回值为 "",此时等式变为了 "" == 0,然后将 "" 空字符串转换为数值类型 0,从而等式变为了 0 == 0,为 true。
[] == []; // false
[] == {}; // false
'' == '0'; // false
'' == 0; // true
false == 'false'; // false
false == '0'; // true
false == undefined; // false
false == null; // false
undefined == null; // true
{} + 1; // 1
// 有人会认为答案应该是'[object Object]1',因为对Object进行ToPrimary(),先看valueOf(),发现是自身,不是基本类型,再看toString(),发现是'[object Object]',返回这个值,然后再相加;但是问题出在编译器会认为前面的{}是一个代码块,后面是一元操作符加号和1,所以结果为1
{} + '1'; // 1
// 没错,这里证明了这个加号是一元的,将'1'转化为了number
var a = {};
a + 1; // '[object Object]1'
// 老铁,这次就对了
[1,2] + 1; // '1,21'
// [1,2],先对Object进行ToPrimary(),先看valueOf(),发现是自身,不是基本类型,再看toString,Array的toString()相当于join(','),所以得到'1,2'再和1相加得到'1,21'
// 普通的Object
var a = {}; // 普通类型的ToPrimitive会遵从hint Number的转换规则
a.toString = () => 100;
a.valueOf = () => '10';
a + 2; // '102' -> 相当于 '10' + 2 为 '102'
a + '2'; // '102'
a > 3; // true -> 进行ToPrimitive() hint为Number操作之后 -> 比较 '10' > 3 -> 不都是String类型,对'10'进行ToNumber(), 10 > 3为true
a > '3'; // false -> 实际比较的是 '10' > '3' 第一个字符的ascii码小,直接为false
a == 100; // false -> 相当于 '10' == 100为false
// Date类型的Object
var b = new Date(); // Date类型的ToPrimitive会遵从hint String的转换规则
b.toString = () => 100; // 这里的搞怪为了测试
b.valueOf = () => '10';
b + 2; // 102 -> 加号是ToPrimitive(), 100 + 2 为102
b + '2'; // '1002' -> 相当于 100 + '2'为'1002'
b > 3; // true -> 进行ToPrimitive() hint为Number操作之后 -> 比较 '10' > 3 -> 不都是String类型,对'10'进行ToNumber(), 10 > 3为true
b > '3'; // false -> 实际比较的是 '10' > '3' 第一个字符的ascii码小,直接为false
b == 100; // true -> 进行ToPrimitive()操作
实现下面的题目:
// 实现一个add方法
function add() {
// ...
}
// 满足以下类型的调用和计算
add(1)(2)(3) + 1; // 7
var addTwo = add(2);
addTwo + 5; // 7
addTwo(3) + 5; // 10
add(4)(5) + add(2); // 11
答案:
function add (n) {
function fn (x) {
return add(n + x);
}
fn.valueOf = function(){
return n;
}
return fn;
}
参考资料:
上一篇: 机器学习之决策树算法
下一篇: 机器学习算法之决策树算法