一个困扰我半年的bug,cannot read properties of null (reading 'addeventlistener'),真凶竟是篡改猴!

没错如果你的Vue出现了cannot read properties of null (reading
‘addeventlistener’),而且下面的索引为userscript.html的212行,内容为:

1
2
3
4
// 给按钮绑定点击事件
function setBtnClick(){
document.querySelector("#rwl-setbtn").addEventListener("click",setMenu);
}

一开始我以为是全局配置错了啥的,网上搜出来说还要挂载dom前停止addEventLisener事件啥的,甚至我把addeventlistener的所有代码都删了,试了半天都不行。

罪魁祸首:

修改方法

那么恭喜你,你写代码的时候不能用篡改猴了,或者不闲麻烦可以使用下面的修改方法:

在 Tampermonkey(篡改猴)测试版中遇到 Cannot read properties of null (reading 'addEventListener') 错误,通常是因为用户脚本尝试访问尚未加载或动态生成的 DOM 元素。以下是针对 Tampermonkey 环境的详细解决方案:


1. 确保脚本在 DOM 加载完成后执行

Tampermonkey 默认在 document-end 阶段执行脚本,但某些动态内容可能需要更晚的执行时机。使用 @run-at 元数据或 DOMContentLoaded 事件确保元素存在。

方法 1:调整脚本执行阶段

在脚本的元数据(==UserScript== 部分)中指定 @run-at document-idle,确保脚本在页面完全空闲时执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ==UserScript==
// @name Your Script Name
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Example
// @match https://example.com/*
// @run-at document-idle // 确保在 DOM 空闲后执行
// @grant none
// ==/UserScript==

(function() {
'use strict';
const btn = document.querySelector("#rwl-setbtn");
if (btn) {
btn.addEventListener("click", setMenu);
} else {
console.error("未找到按钮 #rwl-setbtn");
}
})();

方法 2:手动监听 DOMContentLoaded

即使使用 @run-at,某些动态页面仍需进一步等待:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ==UserScript==
// @match https://example.com/*
// @grant none
// ==/UserScript==

document.addEventListener("DOMContentLoaded", function() {
const btn = document.querySelector("#rwl-setbtn");
if (btn) {
btn.addEventListener("click", setMenu);
} else {
console.warn("初次未找到按钮,尝试动态监听...");
observeDynamicButton();
}
});

function observeDynamicButton() {
const observer = new MutationObserver(() => {
const btn = document.querySelector("#rwl-setbtn");
if (btn) {
btn.addEventListener("click", setMenu);
observer.disconnect(); // 绑定后停止监听
console.log("动态按钮已绑定事件");
}
});
observer.observe(document.body, { childList: true, subtree: true });
}

function setMenu() {
// 处理点击事件
}

2. 处理跨域或 iframe 中的元素

如果目标元素位于 iframe 内,需先访问 iframecontentDocument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ==UserScript==
// @match https://example.com/*
// @grant none
// ==/UserScript==

document.addEventListener("DOMContentLoaded", function() {
const iframe = document.querySelector("iframe"); // 调整选择器以匹配实际 iframe
if (iframe) {
iframe.addEventListener("load", function() {
const btn = iframe.contentDocument.querySelector("#rwl-setbtn");
if (btn) {
btn.addEventListener("click", setMenu);
}
});
}
});

3. 使用 Tampermonkey 的 unsafeWindow 访问页面上下文

Tampermonkey 脚本默认运行在隔离的沙盒环境,直接使用 document 可能无法访问页面 DOM。需通过 unsafeWindow 访问实际页面对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ==UserScript==
// @match https://example.com/*
// @grant unsafeWindow // 声明需要 unsafeWindow 权限
// ==/UserScript==

const uw = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;

document.addEventListener("DOMContentLoaded", function() {
const btn = uw.document.querySelector("#rwl-setbtn");
if (btn) {
btn.addEventListener("click", setMenu);
}
});

4. 调试与日志输出

添加日志以确认元素是否存在及脚本执行状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ==UserScript==
// @match https://example.com/*
// @grant none
// ==/UserScript==

console.log("Tampermonkey 脚本已加载");

document.addEventListener("DOMContentLoaded", function() {
console.log("DOMContentLoaded 触发");
const btn = document.querySelector("#rwl-setbtn");
console.log("查找到的按钮:", btn);
if (btn) {
btn.addEventListener("click", function() {
console.log("按钮点击事件触发");
setMenu();
});
}
});

5. 验证元素选择器

确保 #rwl-setbtn 选择器正确:

  1. 在目标页面上按 F12 打开开发者工具。
  2. 使用 Ctrl+F 在 Elements 面板中搜索 #rwl-setbtn
  3. 确认元素存在且 ID 无误。如果元素是动态生成的,检查其父级容器是否已渲染。

6. 处理 Shadow DOM

如果元素位于 Shadow DOM 内部,需穿透访问:

1
2
3
4
5
6
7
8
// 示例:访问 Shadow DOM 中的元素
const host = document.querySelector("#shadow-host");
if (host && host.shadowRoot) {
const btn = host.shadowRoot.querySelector("#rwl-setbtn");
if (btn) {
btn.addEventListener("click", setMenu);
}
}

总结

  1. 执行时机:通过 @run-at document-idleDOMContentLoaded 确保脚本在 DOM 就绪后运行。
  2. 动态元素:使用 MutationObserver 监听 DOM 变化。
  3. 跨域/iframe:通过 iframe.contentDocumentunsafeWindow 访问目标元素。
  4. 调试:添加 console.log 验证元素是否存在及脚本执行流程。
  5. 选择器验证:确保元素 ID 正确且位于当前作用域。

如果问题仍未解决,请提供目标页面的 URL 或 HTML 结构,以便进一步分析。