极品小猫 发表于 2022-12-9 16:56:17

【油猴开发指南】脚本执行的时机?为什么只在控制台有效

本帖最后由 极品小猫 于 2022-12-14 14:51 编辑

> 本帖最后由 极品小猫 于 2022-12-12 08:49 编辑

# 本教程为小白向

本教程不涉猎高深技术,但可能会有部分专业的名词,请参看 API 名词清单中的说明。小白可放心阅读,如果你已经掌握了小白向的操作,后续还有进阶的操作,提高执行效率。

小白写脚本,常常会遇到一些脚本执行时机的问题!
下面为新手以定时器作为思路解决问题,如果你已经掌握了定时器的用法,希望进阶学习,可以看文末最后推荐监视器监听DOM变动的进阶方法文章(难度比较高,需要多练习)。

#API名词清单

* (https://developer.mozilla.org/zh-CN/docs/Web/API/Window/load_event)
* (https://developer.mozilla.org/zh-CN/docs/Web/API/setTimeout)
* (https://developer.mozilla.org/zh-CN/docs/Web/API/setInterval)
* (https://developer.mozilla.org/zh-CN/docs/Web/API/clearInterval)
* (https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe)
*

## 选择器为什么只在控制台里有效(如iframe问题)
[【油猴脚本开发指南】基本油猴编程的一些常见误区](https://bbs.tampermonkey.net.cn/thread-835-1-1.html)
小白入门的基本知识,在阅读后续的问题解决方法之前,**强烈推荐**先阅读上述文章,先对脚本执行时机有个及认识。

## window.onload 事件与 setTimeout 倒计时定时器方案

掌握控制台的使用之后,往往是控制台里执行某些代码是有效的,放到脚本里运行就没效了。此类问题多数因为元素未加载时脚本执行导致的。
报错如:

> Uncaught TypeError: Cannot set property 'value' of null

例:[https://bbs.tampermonkey.net.cn/thread-3839-1-1.html](https://bbs.tampermonkey.net.cn/thread-3839-1-1.html)
例2:https://bbs.tampermonkey.net.cn/thread-3844-1-1.html

通常情况下,你所创建的脚本默认是运行于网页DOM加载成功后,DOM可以理解为网页用于显示内容的HTML代码传输完毕时。

除了DOM加载成功后,还有 window.onload 事件,即网页中所有的内容加载完毕后。这些内容包括引入的JS、CSS、图片等所有资源文件,只有等待这些资源加载完毕后才会执行脚本,但是不包括通过 ajax 技术动态加载的资源。

```javascript
window.addEventListener("load", function(){
    alert("网页内容加载完毕")
}
);
//监听网页元素加载完毕时,执行提示窗口
setTimeout(function(){
    alert("5秒后提示!")
}, 5000);
//定时执行器,5秒后执行代码
```

在实际操作中,onload 事件的效果与 setTimeout 延时执行的效果并无太多区别,只是一个明确了时机,一个是固定的时机,这些方法在效率上往往不太理想。同时,有些你想操作的对象,需要一定的触发条件,例如不仅需要**等待内容加载完毕**,同时需要**选择后或者点击后**这两个条件,因此 onload 与 setTimeout 的无法解决这些需求。

在上述的例2中,访问 [https://static.qspfw.moe.gov.cn/user/#/user/login](https://static.qspfw.moe.gov.cn/user/#/user/login) 这一地址时,可以明显的察觉到网页是有一个加载的阶段,这意味着 onload 与 setTimeout 都无法准确的去预估网页什么时候会加载完成。

## 什么是 ajax 动态加载技术
前文提到了 ajax 动态加载技术,由于该内容的知识点过于庞大复杂,此处仅针对**脚本执行时为什么不存在元素**说明其工作机制。
即你脚本想要对某个input文本框进修内容的读写,但是这个文本框并不是在你打开网页时就会显示,而是在你网页加载完成后,再单独进行局部内容加载的技术。常见于某些下载链接,在你点击某个按钮之后,网页不刷新就显示出来了。
    由于是后加载技术,导致你的脚本执行过早,获取不到操作的对象。

## setInterval 定时循环执行计时器

例3:[https://bbs.tampermonkey.net.cn/home.php?mod=spacecp](https://bbs.tampermonkey.net.cn/home.php?mod=spacecp)
在例3中,存在一个更复杂的情况,省市区街道的下拉框,需要前一级内容选择后才会显示,通过下面的这两行代码可以触发一次选择并显示下一级内容。

```javascript
document.querySelector('#resideprovince').value="北京市";
document.querySelector('#resideprovince').onchange()
```

这个内容是通过Ajax动态加载的,当触发了change事件后,整个省市街道的根节点的内容都会重新加载一遍,因此你无法简单的通过对触发 change 事件后来联动修改后续的信息,因为你不知道什么时候 ajax 动态加载才会完成。

利用 setInterval 定时循环执行器是新手入门中最容易掌握的操作办法,只要掌握好定时循环的节奏即可。

```javascript
document.querySelector("#resideprovince").value = "北京市";
document.querySelector("#resideprovince").onchange();
let t = setInterval(function () {
//设定循环定时器,1000毫秒=1秒,1秒钟检查一次目标对象是否出现
let obj = document.querySelector("#residecity"); //声明要查询的对象
if (obj) {
    //判断对象是否存在,存在则开始设置值
    obj.value = "朝阳区"; //对这个对象设置有效的值
    obj.onchange(); //触发 change 事件
    clearInterval(t); //清除循环定时器
}
}, 1000);
```

通过这个循环定时器,可以更加高效的去检查对象目标出现时,执行某个代码,后续的街道等信息,可以以此类推进行套娃。
此方法也可以适用于例2中案例

# 封装循环定时检测函数

```javascript
function checkObj(selection, fn) {
let t = setInterval(function () {
    //设定循环定时器,1000毫秒=1秒,1秒钟检查一次目标对象是否出现
    let obj = document.querySelector(selection); //声明要查询的对象
    if (obj) {
      //判断对象是否存在,存在则开始设置值
      fn(obj);
      clearInterval(t); //清除循环定时器
    }
}, 1000);
}
//执行函数
checkObj("#residecity", function (obj) {
//传入选择器的参数,要目标对象出现时,要执行的操作函数,obj 为选择器的操作对象
obj.value = "朝阳区"; //对这个对象设置有效的值
obj.onchange(); //触发 change 事件
});
```

# 【进阶】addEventListener 监听器监听 DOMNodeInserted(DOM节点改变事件)
https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
该方法是一个建议弃用的方法,但是小白入门之前首先需要学习解决死循环问题,这在新手操作中极容易出现的问题。也极有可能是你百度的时候,会学习到的方法。
下述文章旨在掌握解决死循环问题,不是学习 DOMNodeInserted 的真正用途。
如果你想学习DOM 节点的深度操作(例如需要处理ajax加载后的内容),我更推荐学习使用 MutationObserver 观察器。

[【油猴脚本开发指南】addEventListener DOMNodeInserted监听元素变动](https://bbs.tampermonkey.net.cn/thread-3864-1-1.html)
[\[油猴脚本开发指南\]脚本往页面上添加新元素](https://bbs.tampermonkey.net.cn/thread-237-1-1.html)

# 【高级进阶 】MutationObserver 观察器
API:https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
[油猴脚本开发指南]MutationObserver简单详解
https://bbs.tampermonkey.net.cn/thread-1007-1-1.html
[油猴脚本开发指南]MutationObserver简易例子
https://bbs.tampermonkey.net.cn/thread-1008-1-1.html
[油猴脚本开发指南]MutationObserver实战
https://bbs.tampermonkey.net.cn/thread-1017-1-1.html

lc1550458025 发表于 2022-12-9 19:04:16

谢谢大佬的解答。

cxxjackie 发表于 2022-12-9 20:42:59

我做那个库很大一部分原因也是因为这些问题太日经了,很多小白往往不关心背后的原理,只想要一个解决方案,那我就直接把最好的方案拍他脸上{:4_108:}

lc1550458025 发表于 2022-12-9 22:42:34

大佬你好,我是你文章中的例一,我的代码刷新网页后控制台直接输入也无效,必须在当前网页ctrl+shift+c随意查看一个元素,然后再输入代码才能成功,这是什么原因呢?

wwwwwllllk 发表于 2022-12-9 22:50:49

期待gg的更新

李恒道 发表于 2022-12-10 14:07:59

lc1550458025 发表于 2022-12-9 22:42
大佬你好,我是你文章中的例一,我的代码刷新网页后控制台直接输入也无效,必须在当前网页ctrl+shift+c随意 ...

iframe问题

lc1550458025 发表于 2022-12-10 19:37:37

李恒道 发表于 2022-12-10 14:07
iframe问题

谢谢大佬,已经解决。

极品小猫 发表于 2022-12-12 08:15:08

cxxjackie 发表于 2022-12-9 20:42
我做那个库很大一部分原因也是因为这些问题太日经了,很多小白往往不关心背后的原理,只想要一个解决方案, ...

我觉得那个库的入手门槛好像还是有点高

cxxjackie 发表于 2022-12-12 21:10:18

极品小猫 发表于 2022-12-12 08:15
我觉得那个库的入手门槛好像还是有点高

应该是get方法的Promise比较费解,我是为了await才写成Promise,结果多数人只能看懂.then的用法,早知道跟each一样做成回调函数了。我发现很多人对Promise都望而却步,明明这是个很好的特性,论坛这方面的教程还是不太够啊(我给道哥说过让他补充一下,他好像忘了哈哈)。

极品小猫 发表于 2022-12-13 09:18:29

cxxjackie 发表于 2022-12-12 21:10
应该是get方法的Promise比较费解,我是为了await才写成Promise,结果多数人只能看懂.then的用法,早知道 ...

promise 就和 MutationObserver 一样,setInterval 循环去判断一个对象是否存在是一个很小白的办法

即便小白有一定基础了,让它使用 MutationObserver简直就要命了,这已经不是一个等级了,不是小白看3天教程就能入门的东西

小白使用这个库,不仅要能理解async/await的使用关系,还得懂得基础的promise,太难了

C大的那个库,不是入门级小白能掌握的东西{:4_93:}
页: [1] 2
查看完整版本: 【油猴开发指南】脚本执行的时机?为什么只在控制台有效