浏览器: 谷歌浏览器 (Win 10x64)
这是我第一次使用javascript的postMessage
API,因此我不知道它的所有细微差别。
我正在尝试遍历一组 DOM 元素,然后在不同的选项卡中打开一堆链接。打开这些选项卡后,在各自的 DOM 中找到某些元素,并通过主窗口中的postMessage()
显示它们。
问题是,即使我打开了几个选项卡,我也只能从最后一个选项卡中获取信息。
这是我的代码:
-
在主窗口上准备
postMessage()
侦听器:window.addEventListener('message', (event) => { console.log(event.data); }, false);
-
创建我的选项卡数组并获取我的 DOM 元素:
alert_list = document.querySelectorAll("tr.expand.alerts-table-tablerow");
var newTabs = [];
-
循环访问 DOM 元素,打开选项卡,添加 JS 代码,该代码使用所需数据回调到主窗口。这是主要工作发生的地方:
alert_list.forEach((currentValue, currentIndex) => { alert_status = currentValue.childNodes[13].innerText; if(alert_status == "Enabled") { console.log(currentValue.childNodes[3].innerText + " " + currentValue.childNodes[7].innerText); newTabs.push(window.open(currentValue.childNodes[5].children[0].href, "_blank")); newTabs[newTabs.length - 1].onload = function() { setTimeout(((tab_element) => {return () => { window.parent.postMessage(tab_element.document.querySelectorAll("h1.search-name.section-title.search-title-searchname")[0].innerText); }})(newTabs[newTabs.length - 1]), 120*1000); }; }
} );
现在,我不断从多次打开的最后一个选项卡中获取信息。确切地说是打开的选项卡数。这让我相信问题出在 javascript 对setTimeout
回调进行后期绑定,因此我受此启发将其更改为以下内容:
((tab_element) => {return () => {
window.parent.postMessage(tab_element.document.querySelectorAll("h1.search-name.section-title.search-title-searchname")[0].innerText);
}})(newTabs[newTabs.length - 1])
这实际上执行了我想要的实际 DOM 元素的闭包。但它仍然不起作用。
我做错了什么?
如果需要任何其他信息,请告诉我。
谢谢。
setTimeout 不是同步调用的,而是在选项卡的"load"事件触发后调用的,此时forEach
循环已完成运行并newTabs.length - 1
引用最后一个选项卡。
一种解决方案可能是使用变量来替换重复使用的length-1
。未经测试,但大致如下:
alert_list.forEach((currentValue, currentIndex) => {
alert_status = currentValue.childNodes[13].innerText;
if(alert_status == "Enabled") {
// console.log(currentValue.childNodes[3].innerText + " " + currentValue.childNodes[7].innerText);
let tabWin = window.open(currentValue.childNodes[5].children[0].href, "_blank")
newTabs.push( tabWin);
tabWin.onload = function(event) {
setTimeout( () => {
window.parent.postMessage(
tabWin.document.querySelectorAll("h1.search-name.section-title.search-title-searchname")
[0].innerText
);
}, 120*1000);
};
}
});
下面是缩进的原始代码,以更清楚地显示延迟评估发生的位置(添加了用于alert_status
的声明:
alert_list.forEach((currentValue, currentIndex) => {
let alert_status = currentValue.childNodes[13].innerText;
if(alert_status == "Enabled") {
console.log(currentValue.childNodes[3].innerText + " " + currentValue.childNodes[7].innerText);
newTabs.push(window.open(currentValue.childNodes[5].children[0].href, "_blank"));
newTabs[newTabs.length - 1].onload = function() {
setTimeout(
(
(tab_element) => {
return () => {
window.parent.postMessage(tab_element.document.querySelectorAll("h1.search-name.section-title.search-title-searchname")[0].innerText);
}
}
)(newTabs[newTabs.length - 1])
, 120*1000);
};
}
});
匿名 onload 函数在添加时编译为函数对象,但在触发 load 事件之前不会执行。当它执行时,它会创建一个"闭包",用于tab_element
从newTabs[newTabs.length - 1]
中获取它的值,现在是最后一个选项卡元素。
解决方案是删除导致问题的技巧代码。
闭包在 JavaScript 中是如何工作的,这本身就是一个主题。然而,为了解释这个答案,有一个介绍:
调用函数时,记录(JavaScript 术语中的对象)用于保存函数中使用的变量和函数的值。实际上,变量标识符现在绑定到这样的"环境记录"。在 ES3 和更早版本中,不需要在块级别(大括号内的语句)或
for( let identifier...
循环的特殊情况下添加额外的记录,因为尚未引入let
、const
和class
声明。ES3 中的单个环境记录称为"激活对象"。通常,当函数返回时,为单个调用创建的环境记录可能会被垃圾回收,因为它们无法再通过代码访问 - 这是JavaScript内存垃圾回收(MGC)的当前标准。
但是,如果嵌套函数和变量值可以在函数退出后在代码中达到,则它们不符合从内存中删除的条件。这种情况通常被描述为函数和变量被保存在"闭包"中。
- 示例解决方案中的
winVar
值保存在不同的环境中,forEach
对其函数参数进行的每次调用记录 - 连续调用中生成的不同值不会相互覆盖。 - onload 和
setTimeout
回调函数是不同闭包中的不同函数对象。setTimeout
回调有权访问其父onload
函数的环境记录,该函数有权访问其父选项卡打开函数的环境记录。 - 计时器回调中的
tabWin
引用最终解析为forEach
函数参数的环境记录中保存的标识符的绑定,该参数在调用时打开选项卡窗口。
- 示例解决方案中的