如何将正确的 this 上下文提供或绑定到 JavaScript 中的事件处理程序?

How does one provide or bind a correct this context to an event handler in JavaScript?

据我所知 classes 的 private fields/functions 的建议在 ES2019 中带有 # 语法。

我有以下 class 例如:

class MyClass {
    #firstPrivateMethod(parameter) {
        // method functionality.
    }

    #secondPrivateMethod(parameter) {
        var someValueFromTheOtherMethod = this.#firstPrivateMethod(parameter);
    }

    constructor() {
        var firstPrivateMethod = this.#firstPrivateMethod;
        var secondPrivateMethod = this.#secondPrivateMethod;

        window.onresize = function() {
            this.#firstPrivateMethod = firstPrivateMethod; // <-- error: Cannot write private member #firstPrivateMethod to an object whose class did not declare it.
            secondPrivateMethod(parameter);
        }
    }
}

我想暂时将我的 class 的私有方法和字段传递给事件处理程序的 this 对象,然后我会删除这些事件处理程序或在它们完成某个 class基于任务。

我需要这个的原因是我的 class 将元素附加到 DOM 并且这些元素的样式是根据它的依赖关系计算的。我认为让它响应媒体查询真的很难或不可能。我有一些针对不同设备的断点。在每个断点之后,我会删除附加的元素,然后我会更改依赖项的值并使用新样式重新附加它们。

起初我有一个想法。我试图像这样修改我的代码:

class MyClass {
    #firstPrivateMethod(parameter) {
        // method funcionality.
    }

    #secondPrivateMethod(firstPrivateMethod, parameter) {
        var someValueFromTheOtherMethod = firstPrivateMethod;
    }

    constructor() {
        var firstPrivateMethod = this.#firstPrivateMethod;
        var secondPrivateMethod = this.#secondPrivateMethod;

        window.onresize = function() {
            secondPrivateMethod(firstPrivateMethod, parameter);
        }
    }
}

但这让我一事无成,因为我的代码太乱了,很难阅读。

你知道如何解决这个问题吗?

第一个提供的代码尝试预测 OP 可能真正的痛苦来源并提出一个解决方案,证明对于 OP 的给定示例,甚至可能不需要基于私有实例的方法fields/methods.

猜测是基于 OP 提供的错误消息...

error: Cannot write private member #firstPrivateMethod to an object whose class did not declare it.

...受影响的代码(部分)看起来像...

constructor() {
    var firstPrivateMethod = this.#firstPrivateMethod;
    // ...

    window.onresize = function() {
        this.#firstPrivateMethod = firstPrivateMethod; // <-- error: Cannot write private member #firstPrivateMethod to an object whose class did not declare it.
        // ...
    }
}

错误很可能是由于处理程序函数的 this 上下文不合适引起的。对于给定的示例,调用时调整大小处理程序的 this 上下文是 window 对象本身。但是私有字段值只能通过此类字段受保护绑定到的实例的直接或原型方法来访问。

因此,关键方法应该是为调整大小处理程序提供正确的 this 上下文,这可以通过 Function.prototype.bind.

轻松完成

当然,下一个例子完全没有抓住要点的可能性很高……不过,我们开始吧……

// suggest a module scope for the `MyType` class

function doSomethingElseWithType(type, ...args) {
  console.log('doSomethingElseWithType :: type, args : ', type, args);
  // ... even more computation.
}

function doSomethingWithType(type, ...args) {
  console.log('doSomethingWithType :: type, args : ', type, args);

  // ... more computation.

  doSomethingElseWithType(type, ...args);
}


function handleWindowResizeWithBoundType(evt) {
  const type = this;

  doSomethingWithType(type, evt.type, ...Object.keys(evt));
}


/*default export */class MyType {
  constructor(label) {
    this.label = label;

    window.onresize = handleWindowResizeWithBoundType.bind(this);
  }
}


const type = new MyType('test');

// NOTE: please squeeze the window vertically but be careful
//       about the frequency of this action.
.as-console-wrapper { min-height: 100%!important; top: 0; }

万一第一种方法没有完全忽略重点,可以想出另一种方法,这次必须使用私有实例字段,以便提供删除私有 held/kept 调整大小处理程序 ...

// suggest a module scope for the `MyType` class

function doSomethingElseWithType(type, ...args) {
  console.log('doSomethingElseWithType :: type, args : ', type, args);
  // ... even more computation.
}

function doSomethingWithType(type, ...args) {
  console.log('doSomethingWithType :: type, args : ', type, args);

  // ... more computation.

  doSomethingElseWithType(type, ...args);
}


function handleWindowResizeWithBoundType(evt) {
  const type = this;

  doSomethingWithType(type, evt.type, ...Object.keys(evt));
}


/*default export */class MyType {

  #resizeHandler;

  constructor(label) {
    this.label = label;

    this.#resizeHandler = handleWindowResizeWithBoundType.bind(this);

    window.addEventListener('resize', this.#resizeHandler);
  }
  unlisten() {
    window.removeEventListener('resize', this.#resizeHandler);
  }
}


const type = new MyType('another test');

setTimeout(type.unlisten.bind(type), 7000);

// NOTE: please squeeze the window vertically.
.as-console-wrapper { min-height: 100%!important; top: 0; }