akira-cn/FE_You_dont_know

续:在什么情况下a===a-1 ?

akira-cn opened this issue · 1 comments

还记得上一期我们讨论过在什么情况下a===a-1 ?

当时提了几种情况,我们先复习一下:

  1. ±Infinity,Infinity与任何有穷数相加减的值均为Infinity
  2. 较大的数,比如1e45,准确地说,是超过Number.MAX_SAFE_INTEGER的数,超过整数精度范围

其实除了利用Number的思路之外,还有其他思(歪)路(招),那么我们今天再讨论更多的情况。

如果在浏览器中,我们可以利用window对象:

let count = 0;
Object.defineProperty(window, 'a', {
  get() {
    return ++count;
  }
});

console.log(a === a - 1); // true

或者按照新的TC39的proposal-global

let count = 0;
Object.defineProperty(globalThis, 'a', {
  get() {
    return ++count;
  }
});

console.log(a === a - 1); // true

在这里我们利用global scope下直接访问一个变量,会访问global对象上的属性这一特性,以及ES5的accessor property,在global对象上定义a属性为getter,每次获取a的时候,让count+1,于是 a === a - 1 由于每次获取a,a的值都加1,就正好是true了。

不过上面的实现还有个问题,那就是我们在外面定义了一个count变量,这样导致代码实现有side effect,让我这种有强迫症代码洁癖的程序员,感觉不是很好……

所以我们改进一下:

Object.defineProperty(window, 'a', {
  get() {
    let count = 0;
    Object.defineProperty(this, 'a', {
      get() {
        return ++count;
      }
    });
    return count;
  },
  configurable: true,
});

console.log(a === a - 1); // true

我们把count变量搬到defineProperty里面,把side effect限制在window.a这个属性里,这样感觉就好多了。

这里的技巧是,在通过getter读取a属性的时候,再一次重新defineProperty这个window.a属性,注意因为要重新defineProperty,我们要将descriptor对象的configurable属性设置为true。

补充

@hax 贺老提到我们可以直接用块级作用域把防止count泄漏出来,这样也挺好的。

{
  let count = 0;
  Object.defineProperty(globalThis, 'a', {
    get() {
      return ++count;
    }
  });
}

那么还有什么办法让a === a - 1呢?

好吧,我们可以使用禁招,请出with同学~

with({
  count: 0,
  get a() {
    return ++this.count;
  }
}){
  console.log(a === a - 1); // true
}

如果说没有不让用with的话,那么这招也是一个办法😄,不过就是这个代码就限制只能在with的block里面了。

好了,以上是另外三种让a === a - 1成立的办法。

你还有什么其他好办法吗?

欢迎在issue中讨论~

hax commented

这个双重defineProp感觉没有必要啊,不想把count漏出来我们可以直接用个block包一下就好啦:

{
let count = 0;
Object.defineProperty(globalThis, 'a', {
  get() {
    return ++count;
  }
});
}

另外side effect或者说泄漏了一个状态在 a === a - 1 这种奇异行为面前简直是小巫见大巫,不,是咪咪巫见格格巫!所以我觉得就不用care了 😛