在构造函数 returns 不同的实例对象之前获得对传递给构造函数的原始 `this` 的访问权

Gain access to the original `this` passed to a constructor before the constructor returns a different instance object

我必须使用按以下方式工作的某个第三方库:

const Thing = function () {
  if (!new.target) return new Thing() // Note: This previously wasn't there

  const privateData = this

  privateData.somethingPrivate = 123

  return {
    someFunction () {
      return privateData.somethingPrivate
    }
  }
}

module.exports = Thing

如您所见,它想用 new 调用,但它随后使用自动创建的 this 实例对象作为私有数据的容器,而不是 returns具有 public 函数的新对象(甚至没有正确的原型)。

所以,我们可以这样做...

const Thing = require('thing')

const thing = new Thing()

console.log(thing.someFunction()) // 123

...但是我们不能这样做:

thing.somethingPrivate = 456 // Does nothing

由于我现在无法在这里解释的原因,我必须能够从外部访问 somethingPrivate 等私人数据。此外,目前还不能选择分叉库。我知道这听起来像是 XY 问题,但请相信我,事实并非如此。我知道我正在尝试做一些我“不应该”做的事情(而且我知道我必须小心库更新)但我需要一个创造性的解决方案。 (不,这不是出于邪恶目的。)

现在,访问 somethingPrivate 所需的是获取调用构造函数 Thing 所用的原始 this。这个对象是JS自己通过new操作符创建的。 (通常情况下,这个对象也是从 new 运算符返回的,除非构造函数 returns 另一个对象,这就是这里发生的情况。)

最初,if (!new.target) return new Thing() 行在库中不存在。我有一个看起来像这样的解决方案(做一些类似于 new 运算符将做的事情,但保留对原始 this 的引用):

const Thing = require('thing')

const thingPrivateData = Object.create(Thing) // Manually create instance, keep reference
const thing = Thing.call(privateData) // Call constructor on the instance
thing._private = thingPrivateData // Assign original "private" instance to a public property

之后,我可以像这样访问 somethingPrivate

thing._private.privateData = 456 // Works!

但是,在最近的更新中,库添加了 if (!new.target) return new Thing() 行,这使我的解决方案变得无用,因为我手动调用 Thing 不会设置 new.target 值和然后库会用 new 再次调用自己,所以我手动传入的 this 不会被使用,而是会创建一个新的实例对象。

我研究了 Reflect.construct 并希望我能做这样的事情:

const Thing = require('thing')

const thing = Reflect.construct(function () {
  const obj = Thing.call(this) // This doesn't work because yet again new.target is unset
  obj._private = this
  return obj
}, [], Thing)

但是当然它不起作用,因为即使在 my(匿名)函数中设置了 new.target,当我调用 Thing,因为再次调用没有 new/Reflect.construct.

我是运气不好还是 是否有某种创造性的方法来访问作为 this 传递给构造函数的原始实例,and/or 到 set new.target 在调用中 also 传递自定义 this (而不是原型是自动创建的)?

我有针对我的特定问题的解决方案,但感觉比我想要的还要脏。我仍然对其他答案感兴趣!

我目前的解决方案如下:

let privateData
const Hook = function () {} // Without a function we get "#<Object> is not a constructor"
Hook.prototype = new Proxy(Thing.prototype, {
  set: function (target, prop, value) {
    privateData = target
    return Reflect.set(target, prop, value)
  }
})

const thing = Reflect.construct(Thing, [], Hook)
thing._private = privateData

之后,thing._private.somethingPrivate 就像我以前的解决方案一样工作。

这是有效的,因为显然我在原型上设置的代理陷阱以某种方式转移到对象实例(虽然我不是 100% 确定为什么它以这种方式工作),并且当构造函数写入私有数据时字段,它会触发我的陷阱,我可以保存对内部对象的引用。

请注意,这仅适用于此库中的构造函数已经写入 this 对象。如果它不会(并且只有方法会在稍后调用时这样做)那么我将需要一个更复杂的解决方案,其中代理实际上会同时捕获 getset 并重定向所有 属性 reads/writes 改为另一个对象。这样我们就无法访问实际的 this 但我们将拥有几乎相同的东西 - 我们将控制库从对象读取哪些数据,我们将看到它写入什么。