Search code examples
javascripthtmlcustom-element

Overriding function in prototype of custom element changes the this object


I made a custom element class and then tried to wrap one of the functions at the prototype level so every instance would use the wrapper. However, when I do this, the value of this changes inside the function. How can I change a prototype function and access the instance this inside of the new function?

In this example, every time I log this in MyClass2 I expect it to print <my-class-2></my-class-2>, just like when I log this in MyClass it prints <my-class></my-class>.

Here's a fiddle as well.

class MyClass extends HTMLElement {
    connectedCallback() {
        console.log('Inside MyClass:')
        console.log(this)
    }
}

customElements.define('my-class', MyClass)


class MyClass2 extends HTMLElement {
    connectedCallback() {
        console.log('Inside MyClass2:')
        console.log(this)
    }
}

let connectedCallback = MyClass.prototype.connectedCallback
MyClass2.prototype.connectedCallback = () => {
    console.log('Outside MyClass2')
    console.log(this)
    connectedCallback()
}

customElements.define('my-class-2', MyClass2)
<my-class></my-class>
<my-class-2></my-class-2>

Console output:

Inside MyClass:
<my-class>​</my-class>​
Outside MyClass2
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: global, …}
Inside MyClass:
undefined

Solution

  • First, when you redefine the connectedCallback() method, you must not use an arrow function, because arrow function calls won't set the value of this to the reference of the object.

    That's generally an advantage, but not here. Instead use the classic function () notation:

     MyClass2.prototype.connectedCallback = function() {
         console.log(this)
     }
    

    Second, when you write let connectedCallback = MyClass.prototype.connectedCallback actually you disconnect the referenced function from the object.

    As a consequence, this won't reference the object from where it is call but the current context which is window.

    Instead, you can use bind() to set this with the value of the object, before calling the method:

     MyClass2.prototype.connectedCallback = function() {
         connectedCallback.bind(this)()
     }    
    

    Alternately, you could define the function as a method of the current object.

     MyClass2.prototype.connectedCallback_old = MyClass.prototype.connectedCallback
     MyClass2.prototype.connectedCallback = function() {
         this.connectedCallback_old()
     }    
    

    class MyClass extends HTMLElement {
        connectedCallback() {
            console.log('Inside MyClass:')
            console.log(this)
        }
    }
    
    customElements.define('my-class', MyClass)
    
    
    class MyClass2 extends HTMLElement {
        connectedCallback() {
            console.log('Inside MyClass2:')
            console.log(this)
        }
    }
    
    let connectedCallback = MyClass.prototype.connectedCallback
    MyClass2.prototype.connectedCallback = function () {
        console.log('Outside MyClass2')
        console.log(this)
        connectedCallback.bind(this)()
    }
    
    customElements.define('my-class-2', MyClass2)
    <my-class></my-class>
    <my-class-2></my-class-2>