valibot
valibot copied to clipboard
Refactor to simplify `null` or `undefined` checks
Currently, the methods nonNullish, nonNullishAsync, nullish, and nullishAsync determine if input is null or undefined using the following approach:
input === null || input === undefined
According to the ECMAScript specification mentioned in:
13.11.1 Runtime Semantics: Evaluation EqualityExpression : EqualityExpression == RelationalExpression
- Let lref be ? Evaluation of EqualityExpression.
- Let lval be ? GetValue(lref).
- Let rref be ? Evaluation of RelationalExpression.
- Let rval be ? GetValue(rref).
- Return ? IsLooselyEqual(rval, lval).
The specification for IsLooselyEqual is as follows:
7.2.14 IsLooselyEqual ( x, y ) The abstract operation IsLooselyEqual takes arguments x (an ECMAScript language value) and y (an ECMAScript language value) and returns either a normal completion containing a Boolean or a throw completion. It provides the semantics for the == operator. It performs the following steps when called:
- If Type(x) is Type(y), then a. Return IsStrictlyEqual(x, y).
- If x is null and y is undefined, return true.
- If x is undefined and y is null, return true. ...ellipsis
In points two and three, we find that using null == undefined yields true. Therefore, we can simplify our check as follows:
- input === null || input === undefined
+ input == null
It perhaps makes the code more concise. If there are any concerns, we are eager to hear your thoughts.
I think for Valibot I prefer an explicit check for both values. Also, as far as I know, === is much faster than ==. But I plan to work on another library with a similar external API as Valibot, which is completely focused on bundle size. There I could imagine this change to reduce the source code.
You are absolutely correct, it should indeed align with your preference, and there truly isn't a right or wrong choice here. I believe there is not much difference between the two methods in terms of performance and bundle size. I merely noticed this approach being frequently used in various libraries, which is why I attempted to initiate this PR.
Thank you very much for your response.
Thank you for your response! I will keep this PR open for now. I will look into it when I start working on the other library I mentioned.
For reference, I have included a simple code snippet to compare execution speeds. In my environment, while there was hardly any difference observed when running on Node, on Bun, the '===' operation was found to be 2.8 times slower.
function useEqualityOp(x) {
return x == null;
}
function useStrictEqualityOp(x) {
return x === null && x === undefined;
}
// Test data
const testData = [null, undefined, 0, false, "", {}, []];
const iterations = 10000000;
// Measure the execution time of ==
const start1 = performance.now();
for (let i = 0; i < iterations; i++) {
for (let j = 0; j < testData.length; j++) {
useEqualityOp(testData[j]);
}
}
const end1 = performance.now();
console.log("==:", end1 - start1, "milliseconds");
// Measure the execution time of ===
const start2 = performance.now();
for (let i = 0; i < iterations; i++) {
for (let j = 0; j < testData.length; j++) {
useStrictEqualityOp(testData[j]);
}
}
const end2 = performance.now();
console.log("===:", end2 - start2, "milliseconds");
Node v20.11.1:
==: 476.43787499999996 milliseconds
===: 474.964833 milliseconds
Bun v1.0.26:
==: 293.179 milliseconds
===: 819.7497920000001 milliseconds
Thank you for your research!
I investigated this PR further. If I am right, this optimization is automatically applied when you use a bundle like esbuild in your build step to minify your code. For Valibots source code, I prefer to keep the check explicit. So I am closing this PR.