You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Greasemonkey中向事件监听器传递变量?脚本调试求助

解决Greasemonkey脚本中异步回调仅修改第一个元素的问题

嘿,这个问题我太熟了——典型的闭包作用域陷阱!你猜的完全没错,循环里的node变量没有被异步回调正确捕获,导致所有回调最终都引用了同一个变量实例,才出现只有一行生效的情况。

问题原因

你用var声明的node是函数级作用域的变量,整个循环里只有这一个node变量。当XHR的load回调触发时,循环早就执行完了,此时node已经指向循环最后一次迭代的元素(因为你是倒序循环,所以就是第一个元素),所以所有回调都会修改这个元素的内容,看起来就像只有第一行生效了。

解决方案

有两种简单的方式可以修复这个问题:

方案1:用let代替var声明node

let是块级作用域的,每次循环迭代都会创建一个新的node变量,每个异步回调都会捕获到当前迭代的那个node实例:

// ==UserScript==
// @name Unnamed Script 785375
// @include https://msdn.microsoft.com/en-us/*
// @version 1
// @grant none
// ==/UserScript==
//get all links in Name column of Constructors, Properties and Methods tables
var nodes = document.evaluate("//table[@id='idConstructors' or @id='idProperties' or @id='idMethods']/tbody/tr/td[@data-th='Name']/a", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
//update text of each link to contain the full signature of the class member
for (let i = nodes.snapshotLength - 1; i >= 0; i--) {
    let node = nodes.snapshotItem(i);
    //the signature is found in the first pre tag on the webpage pointed by the current link
    var oReq = new XMLHttpRequest();
    oReq.addEventListener("load", function() {
        var html = document.createElement('html');
        html.innerHTML = this.responseText;
        var elements = html.getElementsByTagName('pre');
        var s = elements[0].textContent;
        s = s.replace(/\n/g, ""); //remove unnecessary white space from the signature
        s = s.replace(/\t/g, " ");
        s = s.replace(/\( /g, "(");
        console.log(s);
        node.textContent = s;
    });
    oReq.open("GET", node.getAttribute("href"));
    oReq.send();
}

方案2:用立即执行函数表达式(IIFE)创建独立作用域

如果你需要兼容更老的浏览器(不过现在Firefox对let支持很好),可以用IIFE把每次迭代的node封装到独立作用域里:

// ==UserScript==
// @name Unnamed Script 785375
// @include https://msdn.microsoft.com/en-us/*
// @version 1
// @grant none
// ==/UserScript==
//get all links in Name column of Constructors, Properties and Methods tables
var nodes = document.evaluate("//table[@id='idConstructors' or @id='idProperties' or @id='idMethods']/tbody/tr/td[@data-th='Name']/a", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
//update text of each link to contain the full signature of the class member
for (var i = nodes.snapshotLength - 1; i >= 0; i--) {
    (function(currentNode) {
        var oReq = new XMLHttpRequest();
        oReq.addEventListener("load", function() {
            var html = document.createElement('html');
            html.innerHTML = this.responseText;
            var elements = html.getElementsByTagName('pre');
            var s = elements[0].textContent;
            s = s.replace(/\n/g, ""); //remove unnecessary white space from the signature
            s = s.replace(/\t/g, " ");
            s = s.replace(/\( /g, "(");
            console.log(s);
            currentNode.textContent = s;
        });
        oReq.open("GET", currentNode.getAttribute("href"));
        oReq.send();
    })(nodes.snapshotItem(i));
}

额外小建议

现在可以用更现代的fetch API代替XHR,代码会更简洁易读,比如:

// 替换XHR部分
fetch(node.getAttribute("href"))
  .then(response => response.text())
  .then(text => {
    var html = document.createElement('html');
    html.innerHTML = text;
    var elements = html.getElementsByTagName('pre');
    var s = elements[0].textContent;
    s = s.replace(/\n/g, "").replace(/\t/g, " ").replace(/\( /g, "(");
    console.log(s);
    node.textContent = s;
  });

这样修改后,每个方法链接都会正确显示完整的签名啦!

内容的提问来源于stack exchange,提问作者iwis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:45:23