如何在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
相关产品推荐
相关产品推荐

