如何减慢JavaScript代码的执行速度?

时间:2018-03-08 02:08:28

标签: javascript youtube

我正在编写一个脚本来删除我的所有YouTube评论。这段代码的每一行都可以删除一条注释,但是当我将它放入循环中时,我会得到Uncaught TypeError: Cannot read property 'click' of undefined,当我单独运行每一行时,它不会出现。我想如果我能找到一种在代码行之间睡觉的方法,我可以删除错误。

var myList = document.getElementsByClassName("dropdown-trigger style-scope ytd-menu-renderer");
for(i = 0; i < myList.length; i++) {
    myList[i].click();
    document.getElementsByClassName("style-scope ytd-menu-navigation-item-renderer")[4].click(); //error here
    document.getElementById("confirm-button").click();
}

我尝试使用setTimeout,如下所示:

var myList = document.getElementsByClassName("dropdown-trigger style-scope ytd-menu-renderer");
for(i=0; i<myList.length; i++) {
    myList[i].click();
    setTimeout(function(){document.getElementsByClassName("style-scope ytd-menu-navigation-item-renderer")[4].click();}, 1000);
    setTimeout(function(){document.getElementById("confirm-button").click();}, 5000);
}

它返回了279个,但没有错误,也没有删除任何评论。发生了什么事?

2 个答案:

答案 0 :(得分:1)

罪魁祸首是,自您执行.click()函数以来,页面没有时间更新DOM。你需要给它那个时间,可能是requestAnimationFrame或不太精确setTimeout。在下面的解决方案中(完全未经测试,在现场制作),进展是在暂停后执行每个操作(打开下拉列表,单击删除,确认)以给DOM更新时间。考虑:

function deleteAllComments ( ) {
    'use strict';
    var commentsToDelete, i;

    function openNextCommentOptionsDropdown ( ) {
        if ( ! (++i < commentsToDelete.length ) ) {
            console.log('No more comments to remove.');
            return;
        }

        commentsToDelete[i].click();
        setTimeout(activateCommentDeleteButton, 50);
    }

    function activateCommentDeleteButton ( ) {
        var el = document.getElementsByClassName("style-scope ytd-menu-navigation-item-renderer")[4];

        if ( ! el ) {
            console.warn('No "Delete" button found, at comment #', i, commentsToDelete[i]);
            console.log('Stopping delete operation');
            return;
        }

        el.click();
        setTimeout(activateCommentDeleteConfirmationButton, 50);
    }

    function activateCommentDeleteConfirmationButton ( ) {
        var el = document.getElementById("confirm-button");

        if ( ! el ) {
            console.warn('Unable to confirm comment delete action, at comment #', i, commentsToDelete[i]);
            console.log('Stopping delete operation');
            return;
        }

        el.click();
        setTimeout(openNextCommentOptionsDropdown, 50); // continue larger "loop"
    }

    commentsToDelete = document.getElementsByClassName("dropdown-trigger style-scope ytd-menu-renderer");

    openNextCommentOptionsDropdown(); // start the "loop"
}

这里有两个很大的细节:

  1. 为响应代码已启动的操作(setTimeout(..., 50)提供DOM更新时间,其中50毫秒应该足够时间;根据需要减少,或者只使用requestAnimationFrame

  2. 防御性编码:在对退回的项目盲目执行函数之前,首先检查项目是否实际存在(if ( ! el ) { ... }

答案 1 :(得分:1)

以下所有选项都有点“hackish” - 但是在完成单击事件处理后,没有一致且简单的方法来获取通知。 您可以将自己的单击事件侦听器添加到目标元素,但是,可以通过stopPropagation将其“静音”... 即使这样,也没有(简单的)方法知道DOM何时因为点击事件而“重新绘制” - 你可以看一下使用 MutationObserver,我想

我假设(从不这样做)document.getElementsByClassName("style-scope ytd-menu-navigation-item-renderer")[4]定位的元素会因点击myList[i].click();而动态添加 这就是你遇到问题的原因。在myList[i].click();之后,DOM将不会“更新”“一段时间” - 因此,document.getElementsByClassName("style-scope ytd-menu-navigation-item-renderer")可能很容易失败。

  

一个潜在的问题是,如果删除评论会更改document.getElementsByClassName("dropdown-trigger style-scope ytd-menu-renderer");定位的元素数量 - 因为HTMLCollection是“实时”,删除DOM元素会改变HTMLCollection - 只有最后两个代码片段将不受这种可能性的影响

所以,这里有四种不同的方法

选项1 - ES5,只是简单的'回调

var DELAY = 0; // try 0, then try increasing values
var myList = document.getElementsByClassName("dropdown-trigger style-scope ytd-menu-renderer");
function confirmClick(callback3) {
    document.getElementById("confirm-button").click();
    setTimeout(callback3, DELAY);
}
function itemClick(callback2, callback3)  {
    document.getElementsByClassName("style-scope ytd-menu-navigation-item-renderer")[4].click();
    setTimeout(callback2, DELAY, callback3);
}
function listClick(element, callback1, callback2, callback3) {
    element.click();
    setTimeout(callback1, DELAY, callback2, callback3);
}
function doOne(i) {
    listClick(myList[i], itemClick, confirmClick, function() {
        ++i;
        if (i < myList.length) {
            doOne(i);
        }
    });
}
doOne(0);
  

当你试图将多个异步(某种)进程“链接”在一起时,Promise(似乎)使代码变得不那么麻烦

选项2 - 承诺,但嵌套setTimeout的 - 使用Array#reduce将点击链接在一起,以便严格按照一个接一个地进行处理

这是丑陋的,真的很难看,让金字塔建筑留给死去的埃及人, 包括说明我想要实现的目标

var DELAY = 0; // try 0, then try increasing values
var myList = document.getElementsByClassName("dropdown-trigger style-scope ytd-menu-renderer");
var p = Promise.resolve();
for(i = 0; i < myList.length; i++) {
    myList[i].click();
    p = p.then(() => new Promise(resolve) => {
        setTimeout(() => {
            document.getElementsByClassName("style-scope ytd-menu-navigation-item-renderer")[4].click();
            setTimeout(() => {
                document.getElementById("confirm-button").click();
                setTimeout(resolve, DELAY);
            }, DELAY);
        }, DELAY);
    });
}

选项3 - 使用辅助函数的Promise - 基本上是上面的代码,但添加了一个帮助函数来防止金字塔

const DELAY = 0; // try 0, then try increasing values
const clickThenDelay = element => new Promise(resolve => {
    element.click();
    setTimeout(resolve, DELAY);
});
var myList = document.getElementsByClassName("dropdown-trigger style-scope ytd-menu-renderer");

Array.from(myList).reduce((p, item) => { 
    return p
    .then(() => clickThenDelay(item))
    .then(() => clickThenDelay(document.getElementsByClassName("style-scope ytd-menu-navigation-item-renderer")[4]))
    .then(() => clickThenDelay(document.getElementById("confirm-button")))
}, Promise.resolve());

选项4 - 类似于选项3,但是这一个在触发点击之前添加了一个点击事件处理程序,在点击被“处理”之后被删除。 据推测,最后添加的处理程序最后被调用。但是,不确定是否可以保证。 此外,如果先前的处理程序调用{​​{1}}

,这将不起作用
event.stop[Immediate]Propagation()