三滑稽甲苯 发表于 2023-5-3 21:30

[JS] 监听 checkbox.checked 的变化

本帖最后由 三滑稽甲苯 于 2023-5-3 21:32 编辑

# 问题
看到这个标题,可能有的观众会问了:“这不是添加一个 `change` 的 EventListener 就好了吗?”

让我先叙述一下完整的问题:

> 有一个形如 `<input type="checkbox" id="checkbox">` 的 checkbox,我想要在 `checkbox.checked` 改变时被通知,包括被用户勾选以及被脚本改变时。

在这种情况下,`checkbox.addEventListener("change", () => {console.log(checkbox.checked);})` 设置的 EventListener 只会在点击 checkbox 时被调用,并不会在脚本更改它的值时被调用。
# 解决过程
我第一个想到的解法也是 EventListener,但是直接监听 `change` 事件没法监听脚本的改动。那么自然想到了 `Object.defineProperty`。
```javascript
var _val = checkbox.checked;
Object.defineProperty(checkbox, "checked", {
    get: () => {
      return _val;
    },
    set: (val) => {
      _val = val;
      console.log(_val);
    }
})
```
这么干应该可以解决这个问题。但是实际上脚本更改了值后 checkbox 的外观没有改变,用户点击了 checkbox 后脚本得到的值没有改变。于是上 stackoverflow 上查找解决方案。经提示后找到了[这个回答](https://stackoverflow.com/a/68426166/16468609):



于是我稍作改动,得到了可行的代码:
```javascript
const { get, set } = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'checked');
Object.defineProperty(checkbox, 'checked', {
    get() {
      return get.call(this);
    },
    set(newVal) {
      console.log(`Setting "checked" property to "${newVal}"...`);
      return set.call(this, newVal);
    }
});
```
只能说我的思路没错,还是欠了点火候。
# 通用适配
高端的解决方案往往采用最简单的调用方式。还记得开头的 EventListener 吗?如果我们在得知 checkbox.checked 被更改后发送事件 "change",那么添加的 EventListener 不就可以同时得知脚本更改事件了吗?于是有了以下代码:
```javascript
function patch(checkbox) {
    const { get, set } = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'checked');
    Object.defineProperty(checkbox, 'checked', {
      get() {
            // If you want to monitor the retrieval of the `checked` property, uncomment the line below and do whatever you like...
            // console.log('Getting "checked" property...');
            return get.call(this);
      },
      set(newVal) {
            // console.log(`Setting "checked" property to "${newVal}"...`); // Debug
            this.dispatchEvent(new Event("change"));
            return set.call(this, newVal);
      }
    });
}
```
这样子,当我们想要监听某个 checkbox.checked 的更改(包括用户勾选以及脚本更改)时,只需调用 `patch(checkbox)`,随后正常 `addEventListener` 即可。若想要区分是用户更改还是脚本更改,只需检查 Event 的 `isTrusted` 即可。
# MWE 最小工作示例
```html
<!DOCTYPE html>
<html>
    <head>
      <title>Checkbox patch test</title>
    </head>
    <body>
      <p><a href="https://github.com/PRO-2684/gadgets/tree/main/checkbox_patch">Github repo</a>, licensed under gpl 3.0.</p>
      <p>Checkbox -> <input type="checkbox"></p>
      <p>Click to set value <code>checked</code> via script -> <button>CLICK ME</button></p>
      <p>You will be able to see below when <code>change</code> event is detected.</p>
      <div id="result"></div>
      <script>
            function patch(checkbox) {
                const { get, set } = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'checked');
                Object.defineProperty(checkbox, 'checked', {
                  get() {
                        return get.call(this);
                  },
                  set(newVal) {
                        this.dispatchEvent(new Event("change"));
                        return set.call(this, newVal);
                  }
                });
            }
      </script>
      <script>
            let checkbox = document.querySelector('input');
            patch(checkbox);
            function setChecked() {
                checkbox.checked = !checkbox.checked;
            }
            checkbox.addEventListener("change", (e) => {
                console.log(e);
                let ele = document.createElement("p");
                ele.innerText = `Change event detected! isTrusted: ${e.isTrusted}; Timestamp: ${e.timeStamp};`;
                result.appendChild(ele);
            })
      </script>
    </body>
</html>
```

# 开源地址
https://github.com/PRO-2684/gadgets/blob/main/checkbox_patch

q438721 发表于 2023-5-9 17:52

defineProperty可以替换为proxy,真正意义的拦截器

kkoo 发表于 2023-5-3 23:29

厉害,向大佬学习

songxp03 发表于 2023-5-4 12:27

现在主要用elementplus了,基本操作一概不会

Manjibody 发表于 2023-5-4 15:17

学习了,感谢分享!

xiao_007 发表于 2023-5-6 09:26

简单的问题
页: [1]
查看完整版本: [JS] 监听 checkbox.checked 的变化