分析lodash——compact,compact 的源码非常简单,并没有引用其他的函数。但是看源码之前,首先想一个问题,lodash 源码里大量出现了 a == null 或 a != null 的判断,所以值 a 什么情况下 满足 a == null 为真?什么情况下 a == undefined 为真?
宽松相等
谁与 null 宽松相等这个问题,我问了公司的资深前端们,给出了解答,答案就在 ECMAScript262 标准里:抽象相等比较算法,如下所示:
The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
- If Type(x) is the same as Type(y), then
- Return the result of performing Strict Equality Comparison x === y.
- If x is null and y is undefined, return true.
- If x is undefined and y is null, return true.
- If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y).
- If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y.
- If Type(x) is BigInt and Type(y) is String, then
- Let n be ! StringToBigInt(y).
- If n is NaN, return false.
- Return the result of the comparison x == n.
- If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x.
- If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y.
- If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y).
- If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
- If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result of the comparison ToPrimitive(x) == y.
- If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then
- If x or y are any of NaN, +∞, or -∞, return false.
- If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false.
- Return false.
翻译一下:
在 x == y 这种比较中,x 和 y 都是值,返回 true 或者 false。这种比较执行的步骤如下所示:
- 如果 x 的类型与 y 的类型相同:
- 返回严格相等比较 x === y 的结果。
- 如果 x 为 null 且 y 为 undefined,返回 true。
- 如果 x 为 undefined 且 y 为 null,返回 true。
- 如果 x 为 Number 类型且 y 为 String 类型,返回比较 x == ! ToNumber(y)的结果。
- 如果 x 为 String 类型且 y 为 Number 类型,返回比较 ! ToNumber(x) == y 的结果。
- 如果 x 为 BigInt 类型且 y 为 String 类型:
- let n = ! StringToBigInt(y)。
- 如果 n 为 NaN,返回 false。
- 返回比较 x == n 的结果。
- 如果 x 为 String 类型且 y 为 BigInt 类型,返回比较 y == x 的结果。
- 如果 x 为 Boolean 类型,返回比较! ToNumber(x) == y 的结果。
- 如果 y 为 Boolean 类型,返回比较 x == ! ToNumber(y)的结果。
- 如果 x 为 String, Number, BigInt 或 Symbol 类型之一,且 y 为 Object 类型,返回比较 x == ToPrimitive(y)的结果。
- 如果 x 为 Object 类型,且 y 为 String, Number, BigInt 或 Symbol 类型之一,返回比较 ToPrimitive(x) == y 的结果。
- 如果 x 为 BigInt 类型且 y 为 Number 类型,或 x 为 Number 类型且 y 为 BigInt 类型:
- 如果 x 或者 y 为 NaN, +∞, or -∞ 之一,返回 false。
- 如果 x 与 y 的数学计算结果相等,返回 true;否则返回 false。
- 返回 false。
比较条件特别细致和繁琐,概括下:
- 类型相同,返回严格相等比较 x === y 的结果。
- null == undefined,返回 true。
- 数字(Number 和 BigInt)类型和 String 类型比较,转换为数字再比较。
- Boolean 类型转换为 Number 类型再比较
- Object 类型与其他原始类型比较前,先转为原始类型再比较。
所以回到最初的问题:
- null 为 Null 类型的唯一值,undefined 为 Undefined 类型的唯一值,所以同类型比较时执行严格相等比较,null == null 和 undefined = undefined 为真。
- 第二步和第三步说明了 undefined == null 和 null == undefined 为真;
- 其余的牵扯到 null 和 undefined 的宽松相等比较都为假。
Truthy(真值)和 Falsy (假值)
在 compact 代码中,还用到了关于真假值(Truthy、Falsy)的判断,compact 会剔除数组中的所有假值,那么什么样的值算假值?换句话说,什么样的值类型转换为 Boolean 后为假值?还是见ECMA262:
直接翻译表 10: ToBoolean 转换
参数类型 | 结果 |
---|---|
Undefined | 返回 false。 |
Null | 返回 false。 |
Boolean | 返回参数。 |
Number | 如果参数为+0, -0 或 NaN,返回 false;其余情况返回 true。 |
String | 如果参数是空字符串(length 属性为 0),返回 false;其余情况返回 true。 |
Symbol | 返回 true。 |
BigInt | 如果参数为 0n,返回 false;其余情况返回 true。 |
Object | 返回 true。 |
compact
1 | /** |
原生实现
1 | // Lodash |
原生实现的代码中直接用了Array.prototype.filter()
,在后面却直接把 Boolean 当参数传进去,冷不丁看起来难理解,但只要明白下面两点就很容易想通:
首先,
Boolean
本身就是一个函数:1
2
3
4
5
6Boolean(0); // false
Boolean(1); // true
Boolean(false); // false
Boolean(2); // true
Boolean(''); // false
Boolean(3); // trueArray.prototype.filter(callback)
中,入参 callback 其实一个回调函数,只要函数能满足入参为element[, index[, array]]
就可以,所以如下两种写法是等价的:1
2
3
4
5
6
7
8// 简写,Boolean本身就是一个函数
// 所以只要参数能对应上,也就能直接作为回调函数callback用
b = a.filter(Boolean);
// 使用function语法构造了一个回调函数,再把item传给Boolean
b = a.filter(function (item) {
return Boolean(item);
});