为什么postMessage()只给我上一个DOM的信息?



浏览器: 谷歌浏览器 (Win 10x64)

这是我第一次使用javascript的postMessageAPI,因此我不知道它的所有细微差别。

我正在尝试遍历一组 DOM 元素,然后在不同的选项卡中打开一堆链接。打开这些选项卡后,在各自的 DOM 中找到某些元素,并通过主窗口中的postMessage()显示它们。

问题是,即使我打开了几个选项卡,我也只能从最后一个选项卡中获取信息。

这是我的代码:

  1. 在主窗口上准备postMessage()侦听器:

    window.addEventListener('message', (event) => { console.log(event.data); }, false);

  2. 创建我的选项卡数组并获取我的 DOM 元素:

    alert_list = document.querySelectorAll("tr.expand.alerts-table-tablerow"); var newTabs = [];

  3. 循环访问 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_elementnewTabs[newTabs.length - 1]中获取它的值,现在是最后一个选项卡元素。

解决方案是删除导致问题的技巧代码。


闭包在 JavaScript 中是如何工作的,这本身就是一个主题。然而,为了解释这个答案,有一个介绍:
  • 调用函数时,记录(JavaScript 术语中的对象)用于保存函数中使用的变量和函数的值。实际上,变量标识符现在绑定到这样的"环境记录"。在 ES3 和更早版本中,不需要在块级别(大括号内的语句)或for( let identifier...循环的特殊情况下添加额外的记录,因为尚未引入letconstclass声明。ES3 中的单个环境记录称为"激活对象"。

  • 通常,当函数返回时,为单个调用创建的环境记录可能会被垃圾回收,因为它们无法再通过代码访问 - 这是JavaScript内存垃圾回收(MGC)的当前标准。

  • 但是,如果嵌套函数和变量值可以在函数退出后在代码中达到,则它们不符合从内存中删除的条件。这种情况通常被描述为函数和变量被保存在"闭包"中。

    1. 示例解决方案中的winVar值保存在不同的环境中,forEach对其函数参数进行的每次调用记录 - 连续调用中生成的不同值不会相互覆盖。
    2. onload 和setTimeout回调函数是不同闭包中的不同函数对象。setTimeout回调有权访问其父onload函数的环境记录,该函数有权访问其父选项卡打开函数的环境记录。
    3. 计时器回调中的tabWin引用最终解析为forEach函数参数的环境记录中保存的标识符的绑定,该参数在调用时打开选项卡窗口。

最新更新