Javascript如何实现日期比较运算符?

4

这个答案 说:

Date对象 可以实现你想要的效果——为每个日期构造一个日期对象,然后使用 ><<=>= 进行比较。

我在参考页面中没有看到这个功能的介绍。

  • 这是特定于浏览器的行为还是语言的标准特性?
  • 如果 Date 是 JavaScript 的 "对象",而 JavaScript 不支持运算符重载,那么 Date 对象上的 ><<=>= 运算符的这种行为是如何实现的?我的意思是,它是在 Date.prototype 上定义的,还是因为某些隐式转换被定义(例如从对象到数字或字符串),还是作为一种特殊情况添加到 JavaScript 语言解释器 / 运行时的 Date 对象中,或者其他什么方式?
2个回答

5
每当您使用一个原本需要数字的对象(如加法、减法、大小比较)时,valueOf方法就会被调用将该对象转换为基本类型:
{ valueOf: () => 2 }.valueOf() // 2
//or not explicitly called:
+ { valueOf: () => 2 } // 2
//using the compare operator:
{ valueOf: () => 2 } < { valueOf: ()=>3 } //true as 2 < 3

因此,当你比较日期时也会发生这种情况。由于Date.prototype.valueOf方法返回自1970年以来的毫秒数,因此您可以使用它来比较两个日期...

new Date().valueOf();
//or
+new Date(); 
//or
new Date() < new Date();

谢谢。我还在抽象关系比较算法的描述中找到了更多细节。 - ChrisW

2
这是标准特性。
如果Date是JavaScript的“对象”,而JavaScript不支持运算符重载,那么如何实现Date对象上的>、<、<=或>=运算符的行为?
由于Date实现了valueOfSymbol.toPrimitive的方式(toString与你提到的特定运算符无关),这三个方法在规范中的OrdinaryToPrimitive抽象操作中用于将对象隐式转换为原始值的过程中被使用,当运算符需要原始值(如字符串或数字)但接收到一个对象时,运算符会使用该操作。
特别地,Date 通过返回其基础时间值(自1970年1月1日格林威治标准时间午夜以来的毫秒数)来实现 valueOf。因此,date1 >= date2 在每个日期上调用 valueOf,提示首选数字,该数字从日期获取时间值(一个数字),然后比较它们。以下是更多信息。
这个问题的副本问道:
“我们可以对任何类执行这个操作吗?”
是的,在ES2015中,这都是标准的,Date 不再像以前那样特殊(它的特殊之处相对较小,下面有更多信息)。例如:

class MyThing {
    constructor(value) {
        this.value = value;
    }
    
    valueOf() {
        return this.value;
    }

    toString() {
        return String(this.value);
    }
}

const a = new MyThing(27);
const b = new MyThing(42);
console.log(`a < b? ${a < b}`);   // a < b? true
console.log(`b < a? ${b < a}`);   // b < a? false
console.log(`b - a = ${b - a}`);  // b - a = 15
console.log(`a + b = ${a + b}`);  // a + b = 69

上述内容遗漏了一些`Date`拥有的东西,稍后会进行介绍。
当操作符或类似对象需要将一个对象转换为原始值时,它使用对象的“to primitive”操作,可选择提供关于它希望得到什么类型(字符串,数字或没有偏好)的“提示”。关系运算符更喜欢数字,所有纯数学运算符如减号也是如此。加号运算符既可以是加法又可以是字符串连接,所以不提供提示,因此使用对象的默认值。几乎内置的对象在没有提示的情况下默认为数字;而`Date`和`Symbol`则默认为字符串。这曾经只是规范操作的逻辑(对于`Date`而言;那时还不存在`Symbol`),但现在通过`Symbol.toPrimitive`方法来处理,对象可以覆盖该方法以提供其自己的处理方式来转换为原始值。
如果我们想要MyThing默认为字符串而不是像Date一样默认为数字,我们需要添加Symbol.toPrimitive方法(我还添加了console.logvalueOftoString以展示哪个方法被用于操作):

class MyThing {
    constructor(value) {
        this.value = value;
    }
    
    valueOf() {
        console.log("valueOf");
        return this.value;
    }

    toString() {
        console.log("toString");
        return String(this.value);
    }

    [Symbol.toPrimitive](hint) {
        if (hint === "number") {
            return this.valueOf();
        }
        // "default" or "number"
        return this.toString();
    }
}

const a = new MyThing(27);
const b = new MyThing(42);
console.log(`a < b? ${a < b}`);   // a < b? true
console.log(`b < a? ${b < a}`);   // b < a? false
console.log(`b - a = ${b - a}`);  // b - a = 15
console.log(`a + b = ${a + b}`);  // a + a = "2742"

注意现在 a + b 是字符串连接,而在第一个代码片段中是加法。这是因为我们更改了默认行为,使其优先选择字符串而不是数字。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接