The logic doesn't work when used inside method of a class but works out side if I use it in functional style.
class Hook {
constructor(object) {
this.object = object;
}
toStringProperty() {
const handler = {
apply: function (target, thisArg, args){
if (thisArg === Function.prototype.toString) {
return 'function toString() { [native code] }'
}
if (thisArg === this.object) {
return "Hooked String"
}
return target.apply(thisArg, args)
}
}
Function.prototype.toString = new Proxy(Function.prototype.toString, handler)
}
}
let hook = new Hook(HTMLAudioElement);
hook.toStringProperty();
// Interesting enough this when called (I use Devtools) logs Proxy Object itself but only happen if I use a Class
console.log(Function.prototype.toString)
console.log(HTMLAudioElement.toString())
What should I do to make it works inside a class?
The "problem" is not with the proxy - all calls already go through it. The issue is the age old specifics of how the this
keyword works. In short, it's determined at call time, so this.object
will have a different meaning depending on when and how the function is called. In this case, the value of this
is "lost" not unlike how you lose it in a callback.
If you need to refer to something concretely, you have a few choices
this
using an arrow function () => {}
An arrow function uses the this
value of the enclosing context at creation time, so it doesn't vary at call time:
class Hook {
constructor(object) {
this.object = object;
}
toStringProperty() {
const handler = {
apply: (target, thisArg, args) => { //<--- arrow function
if (thisArg === Function.prototype.toString) {
return 'function toString() { [native code] }'
}
if (thisArg === this.object) {
return "Hooked String"
}
return target.apply(thisArg, args)
}
}
Function.prototype.toString = new Proxy(Function.prototype.toString, handler)
}
}
let hook = new Hook(HTMLAudioElement);
hook.toStringProperty();
// Interesting enough this when called (I use Devtools) logs Proxy Object itself but only happen if I use a Class
console.log(Function.prototype.toString)
console.log(HTMLAudioElement.toString())
this
using Function#bind
This is basically redundant with arrow functions, but still an option:
class Hook {
constructor(object) {
this.object = object;
}
toStringProperty() {
const handler = {
apply: function (target, thisArg, args){
if (thisArg === Function.prototype.toString) {
return 'function toString() { [native code] }'
}
if (thisArg === this.object) {
return "Hooked String"
}
return target.apply(thisArg, args)
}.bind(this) //<--- bind `this` from creation time
}
Function.prototype.toString = new Proxy(Function.prototype.toString, handler)
}
}
let hook = new Hook(HTMLAudioElement);
hook.toStringProperty();
// Interesting enough this when called (I use Devtools) logs Proxy Object itself but only happen if I use a Class
console.log(Function.prototype.toString)
console.log(HTMLAudioElement.toString())
This avoids the usage of this
by capturing the value of this.object
at creation time with const obj = this.object
and just using obj
later which will always have the same value:
class Hook {
constructor(object) {
this.object = object;
}
toStringProperty() {
const obj = this.object; //<--- capture
const handler = {
apply: function (target, thisArg, args){
if (thisArg === Function.prototype.toString) {
return 'function toString() { [native code] }'
}
if (thisArg === obj) { //<--- use
return "Hooked String"
}
return target.apply(thisArg, args)
}
}
Function.prototype.toString = new Proxy(Function.prototype.toString, handler)
}
}
let hook = new Hook(HTMLAudioElement);
hook.toStringProperty();
// Interesting enough this when called (I use Devtools) logs Proxy Object itself but only happen if I use a Class
console.log(Function.prototype.toString)
console.log(HTMLAudioElement.toString())