本帖最后由 steven026 于 2023-2-23 16:37 编辑
前言
本文为新手向教程,只阐述如何在浏览器控制台(Devtools Console)中使用简便的方法调试油猴、脚本猫中以// @grant
声明的API函数,不会深入探讨API函数如何使用,具体使用方法建议见各API函数指南(如果有😉)。
众所周知,油猴以及脚本猫的API函数文档写的非常简单,很多函数需要传入的参数写的都模棱两可让人摸不着头脑,而且油猴没有直接能够调试的方法,静态代码每次都要刷新页面重新加载脚本,这就导致了油猴API函数调试非常麻烦。因此我们希望寻找一个简便的方法进行调试,合理且连贯的调试方法能在学习脚本开发时节约大量试错时间。
另外本篇指南结论非常简单,但希望能阅读完全文理解其中原理以加深对油猴脚本的理解。
理论知识
推荐阅读以下文章/文档以更好理解本篇指南具体原理
1.[油猴脚本开发指南]油猴的本质
https://bbs.tampermonkey.net.cn/thread-2494-1-1.html
2.[油猴脚本开发指南]grant介绍,none与unsafeWindow
https://bbs.tampermonkey.net.cn/thread-160-1-1.html
3.Scope(作用域)
https://developer.mozilla.org/zh-CN/docs/Glossary/Scope
首先油猴是一个浏览器插件,依靠插件API先将油猴框架及用户脚本(userscript)代码插入到脚本匹配的网页中,然后再执行脚本代码。
从【油猴的本质】指南中可知,之所以油猴脚本能获取window全局上下文,是因为油猴脚本实际作用域是window全局作用域的子域。
而常用的调试工具浏览器控制台(Devtools Console)访问的是top作用域,即window全局作用域,全局作用域无法直接访问子域。因此即使我们在油猴脚本中声明了@grant GM_xmlhttpRequest
后正常运行脚本,我们直接在控制台中输入GM_xmlhttpRequest
也无法对其进行调试,会提示GM_xmlhttpRequest is not defined
,是因为全局作用域无法访问子作用的局部函数。
虽然我们无法直接在全局作用域中访问子域,但是【JavaScript万物皆是对象】我们实际上需要访问的函数方法也是对象,只要知道其地址就可以访问到。因此我们可以将子域中的函数从子域中暴露到全局作用域中,使全局作用域也能直接访问到暴露的函数方法。
这样,我们就可以对其进行调试。
代码
初级代码
// ==UserScript==
// @name 全局函数
// @match *://*/*
// @grant unsafeWindow
// @grant GM_info
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @grant GM_download
// @grant GM_openInTab
// @grant GM_listValues
// @grant GM_cookie
// ==/UserScript==
unsafeWindow.GM_info=GM_info;
unsafeWindow.GM_xmlhttpRequest=GM_xmlhttpRequest;
unsafeWindow.GM_setValue=GM_setValue;
unsafeWindow.GM_getValue=GM_getValue;
unsafeWindow.GM_openInTab=GM_openInTab;
unsafeWindow.GM_download=GM_download;
unsafeWindow.GM_openInTab=GM_openInTab;
unsafeWindow.GM_listValues=GM_listValues;
unsafeWindow.GM_cookie_=GM_cookie;
我们先以这10个API函数为例
如果我们的油猴脚本需要用这10个API函数,就要现在脚本头部对其进行一一声明
可以声明后不使用,而不可以不声明但使用(暂时不清楚声明过多会不会降低油猴运行效率)
声明完函数后,我们才能够在脚本中正常使用(部分API函数不需要声明也可使用,这里不介绍)
然后通过将该函数方法挂载到window对象unsafeWindow
上以实现暴露到全局的目的。
初级函数中重复unsafeWindow.XX=XX;
即可达到目的
进阶代码
我们拿GM_info
试验一下,可以看到原本无法访问的函数对象现在可以正常在控制台中访问并进行调试了。
观察GM_info
返回的对象,可以看到我们之前声明的10个API函数也出现在了GM_info中,聪明的你看到这里是不是想到了什么?
进阶代码1 eval
// ==UserScript==
// @name 全局函数
// @match *://*/*
// @grant unsafeWindow
// @grant GM_info
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @grant GM_download
// @grant GM_openInTab
// @grant GM_listValues
// @grant GM_cookie
// ==/UserScript==
/* eslint no-eval:0 */
for(const grant of GM_info.script.grant){
unsafeWindow[grant]=eval(grant)
}
我们可以直接利用for循环+eval对其批量赋值进行暴露
由于GM_info.script.grant中储存的只是API函数名称的字符串,并没有储存API函数本身,因此我们需要借助eval(或setTimeout)将字符串解析成函数,从而进行暴露。
这样的好处是我们不需要再以静态的方法每声明一个API函数就静态暴露一次,而是可以通过for循环这种动态的方法批量暴露,即以后我们删改// @grant
声明后,不需要再额外修改代码主体,将其全部交给for循环,重复利用代码,提高代码利用率,做到write less do more
的效果。
坏处也很明显,会触发eslint规则不要使用evaleslint: no-eval - eval can be harmful
摆烂的话可以直接无视,或者加一行注释/* eslint no-eval:0 */
来禁用这个eslint规则。
如果不想摆烂又符合eslint规范,可以继续研究。
进阶代码2 argument
// ==UserScript==
// @name 全局函数
// @match *://*/*
// @grant unsafeWindow
// @grant GM_info
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @grant GM_download
// @grant GM_openInTab
// @grant GM_listValues
// @grant GM_cookie
// ==/UserScript==
console.log(arguments)
for(let i=0;i<GM_info.script.grant.length;i++){
unsafeWindow[GM_info.script.grant[i]]=arguments[i+3]
}
先在脚本中加一行console.log(arguments)
,然后点击console
的源头,可以看到油猴脚本的本质,argument
中也一起传递了API函数的参数,这部分并不像GM_info
中是字符串,而是真实的函数对象,直接将这部分对象暴露给全局即可,由于有index偏移,所以需要使用索引for循环
注:油猴版本不同,传参的内容和顺序也可能不同,建议自己调试,顺便可以加深理解
至此我们实现了3种调试在控制台中调试油猴API函数方法
完结撒花
(课后作业:通过//@require
产生的变量是否也能用这类方式在控制台中调试? )
实际应用
(出处:https://bbs.tampermonkey.net.cn/thread-3917-1-1.html)