【问题标题】:Why does postMessage() only give me info from the last DOM?为什么 postMessage() 只给我来自最后一个 DOM 的信息?
【发布时间】:2020-03-28 11:21:03
【问题描述】:

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

这是我第一次使用 javascript 的 postMessage API,因此我不了解它的所有细微差别。

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

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

这是我的代码:

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

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

  2. 创建我的 tabs 数组并获取我的 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 回调进行了后期绑定,因此我将其更改为以下受 this 启发的内容:

((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 元素的闭包。但是还是不行。

我做错了什么?

如果需要任何其他信息,请告诉我。

谢谢。

【问题讨论】:

  • @Lynx242 那么我如何从最后一个选项卡中获取信息?如果我无法获得信息,我应该也无法从最后一个选项卡中获得信息,对吧?
  • @Lynx242 在 chrome 上试用一下,您可以从已打开的选项卡中读取数据。
  • @Lynx242 这基本上就是我正在做的事情。

标签: javascript closures postmessage


【解决方案1】:

setTimeout 不是同步调用的,而是在选项卡的“加载”事件触发之后,此时 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... 循环的特殊情况,因为尚未引入 letconstclass 声明. ES3 中的单一环境记录称为“激活对象”。

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

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

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

【讨论】:

  • 不,我不明白。请注意,我立即为 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]),这个立即执行,不等待onload,这个函数返回的函数在timeout/onload之后执行。
  • 你同意我的分析吗?我是不是哪里出错了?
  • 不,我不同意 - 用于设置 .onload 的匿名函数中的任何内容都不会执行,直到 "load" 事件触发,只有这样才能评估最终的 newTabs[newTabs.length - 1]。我会扩展答案来解释。
  • 如果我错了,请纠正我,但您的解决方案有效的全部原因是因为let 允许您在其自己的词法范围内声明变量,而不是像var 那样提升它们。因此,当您在 setTimeout 中引用 tabWin 时,您实际上指的是在该迭代中创建的对象。
  • 不完全是。 tabWin 可以被 timeout 函数看到,因为它(timeout 函数)嵌套在声明 tabWin 的函数中(forEach 调用的函数)。虽然用法是一个闭包(外部函数已返回),但 tabWin 可以使用 var 声明而没有任何区别。更多讨论请参见stackoverflow.com/q/750486/5217142
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-13
  • 2021-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多