1

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?

jeffbRTC
  • 1,941
  • 10
  • 29
  • What are you trying to accomplish? – Patrick Roberts Feb 01 '21 at 14:38
  • I think you need to create the proxy after the class is created. – luek baja Feb 01 '21 at 14:40
  • @PatrickRoberts Nothing. Just experimenting ES6 Proxy with a class. – jeffbRTC Feb 01 '21 at 14:41
  • @luekbaja If you see code, you can see that I create the proxy after class initialization, – jeffbRTC Feb 01 '21 at 14:42
  • I don't understand the question. *What* doesn't work? – VLAZ Feb 01 '21 at 14:58
  • @VLAZ Proxification of course. Please run the code, and you will see that it logs the original value of HTMLAudioElement.toString() instead the one I want to return ("Hooked String") – jeffbRTC Feb 01 '21 at 15:00
  • OK, then this is what confused me. There is nothing wrong with the proxy - all calls *do* go through it. However, `this.object` is not what you think it is. – VLAZ Feb 01 '21 at 15:03
  • this.object refer to the object (It's a name) variable of the class. It's created by the constructor. The value of this.object should be HTMLAudioElement – jeffbRTC Feb 01 '21 at 15:04
  • @VLAZ I'm 100% sure the word "object" is not reserved by JS. What's reserved is "Object" – jeffbRTC Feb 01 '21 at 15:05
  • [As you should know](https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) the value of `this` is not static and is determined at call time. – VLAZ Feb 01 '21 at 15:06
  • @VLAZ What should I do in this case to refer this variable then? – jeffbRTC Feb 01 '21 at 15:07
  • @VLAZ Ok, I found that I can refer it outside then refer it through different name. It works. Thanks! – jeffbRTC Feb 01 '21 at 15:17

1 Answers1

2

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

Lexically bind 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())

Manually bind 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())

Capture the value in a variable

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())
VLAZ
  • 26,331
  • 9
  • 49
  • 67