在构造函数 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
对象。如果它不会(并且只有方法会在稍后调用时这样做)那么我将需要一个更复杂的解决方案,其中代理实际上会同时捕获 get
和 set
并重定向所有 属性 reads/writes 改为另一个对象。这样我们就无法访问实际的 this
但我们将拥有几乎相同的东西 - 我们将控制库从对象读取哪些数据,我们将看到它写入什么。
我必须使用按以下方式工作的某个第三方库:
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
对象。如果它不会(并且只有方法会在稍后调用时这样做)那么我将需要一个更复杂的解决方案,其中代理实际上会同时捕获 get
和 set
并重定向所有 属性 reads/writes 改为另一个对象。这样我们就无法访问实际的 this
但我们将拥有几乎相同的东西 - 我们将控制库从对象读取哪些数据,我们将看到它写入什么。