今天晚上不更新了,有点累,缓缓。明天肝元数据部分。因为全是自己整理书写,要几个文档反复对照,然后翻译,是个硬骨头,嗷~
油猴子的基本格式
校对完成,更新时间:2020-07-23 11:22:25
首先记住它会分为两部分:
- 上面以注释形式出现的元数据
- 下面要注入页面中的脚本代码
在各种编辑器中这两部分都会以不同的颜色进行显示,所以很容易区分,我们先来研究元数据。
元数据,这个名词又很新鲜,英文 meta。就是对脚本的一些基本信息的说明,或者你叫它头部信息什么的,只要能够顺畅的交流,叫什么名字其实并不太重要。而且好多翻译也确实做不到信达雅,反倒让人增加迷惑。所以不要太在意这些,小老鼠自己也完全搞不清楚这些名词,反正会写就完事儿了。
那么起到说明性的作用,就是说明书?!那就是不用读系列……
并不是哦!虽然也许不书写元数据部分在某些平台下它也是可以使用的。但元数据信息真的是非常重要。这就好像做好我们本职的工作这是份内的事情,而处理好人际关系,似乎并不在这个本职工作之内,但是大家都懂的,如果人际关系处理不好,那你的本职工作也很难顺利完成。所以在工作前先和各部门打好招呼,我要做什么事情了,大家要给予支持,然后相互协调,避免冲突。这些话虽然很套路,但是总还是要说一下的。
居然是套路,那应该就有模板。你在脚本管理器里去新建一个脚本,它就会自动填写上一些基本的元数据。比如我在暴力猴中新建一个脚本,得到的内容是下面这个样子的:
// ==UserScript==
// @name New script - meta.appinn.net
// @namespace Violentmonkey Scripts
// @match https://meta.appinn.net/t/topic/17705
// @grant none
// @version 1.0
// @author -
// @description 2020/7/20 上午7:22:46
// ==/UserScript==
虽然全是英文,但也不用紧张,咱们要看的主要是格式。反正程序员一般都有强迫症,所以代码里你会看到许多整整齐齐的格式,很舒适的。
这里的第 1 行和第 9 行就是个固定格式,表示元数据的开始和结束,并且说明这是用户脚本。对,这又是一个我必须要给你们讲一下,但是你们并不一定需要记住的问题。反正每一次新建脚本模板都给好了,要不是需要给你们讲,我早都忘了这两行的事情……
每一行用双斜线开头,是表示这是注释不参与运行,这就避免了某些不兼容的情况下,这些元数据对脚本产生影响。
然后中间几行,前面有一个以 @
开头的单词,然后一些空格,后面写了些内容。前面的单词是说这一行记录了什么信息,后面的内容就是对应的信息。
那么现在看第 2 行,@name
显然是说名字,空格后面的内容就是这个脚本的名字,我们可以根据自己的需求进行修改。
理解这一条后面的就都好理解了,因为书写的格式是一样的。现在的问题就是,我要知道都可以去设置哪些数据。对这个事情总是要有一个规范的,要不然像小老鼠这么碎嘴子的,能在上面写个长篇小说,固然会把该说的都说清楚了,但是脚本管理器表示宝宝并看不懂,不就鸡对鸭讲了么……(咦,好像哪里不太对的亚子
可用的元数据
在告诉大家有哪些元数据的字段(标签)可用之前要先做一些简要的说明:
- 我们并不需要把所有可用的字段全写上,只要写自己用得上的那部分就好;
- 有些字段是可以多次出现的,但有些就只能出现一次,这些我会进行标注,大家根据字段所表达的意思去理解为什么是这样;
- 不同的脚本管理器,对元数据的支持也不相同,但基础部分大家基本是一致的。而且你即便写上了这个脚本管理器无法理解的字段,也不会产生负面影响。
如果你以前看过一些相关的内容,那么很容易理解我将要做什么,就是把这些元数据的字段罗列出来,挨个给大家解释。差不多等于把文档复制过来并翻译一遍。
但如果我真的那么做,我估计连篇累牍的内容能立刻劝退 1/3 的小伙伴。讲还是要讲的,我们就稍微换一个角度,对这些元数据做一个粗略的归类。
必须有的元数据
这也不是绝对的必须,不写也不是说完全不行。但如果你要去写元数据,我觉得应该没有人会拒绝把这几个项目写上去。
@name
脚本总要有个名字吧。要不以后怎么称呼它?嘿,那个脚本……
但这里存在一个误解,就是把它看得过分重要。其实比较准确的理解是这里大概相当于昵称。就是用来让人便于识别和称呼的,它也会用来显示在脚本列表等地方,让我们能够快速的了解这是哪一个脚本。
@namespace
命名空间,这名词又让人迷惑。而且上面已经有一个名字了,那就更让人迷惑。那你把它理解为用户(脚本) ID 好了。
我们和上面对照一下,昵称是用来称呼的,ID 是用来识别它就是它的。这就好像在微信里,我们可以经常的去修改昵称,但是微信号轻易是不能修改的。
在文档中的描述是:用 @name
和 @namespace
的组合来识别脚本,但在不同的脚本管理器下可能有所区别。总之这两个内容设置好之后,尽量就不要修改了。尤其是 @namespace
,如果修改肯定要引发问题。
自然也很容易理解,这两者不要和其他的脚本冲突。两个学生具有相同的名字或者相同的学号,就会让人很难分辨,然后引发错误。
@version
版本号,基本格式就是: 1.0.0 这样。第 1 位表示大版本,第 2 位表示小功能的增加,第 3 位表示细节的修补。
但是对于版本号也没有那么严格的要求,按着这个格式去书写就可以了,至于数字代表什么,或者你随便去写,都没所谓。比如现在浏览器的大版本号(第一位)就是随着时间固定的增长。而我自己写东西一般第 1 位就永远是 0,因为我一直认为自己写的东西不够完善,不足以发出一个正式的版本。
但是新版本的版本号一定要比旧版本的大,因为这是判断升级的一个依据。如果脚本管理器发现,线上的版本比当前已安装的版本版本号更高,那么这个脚本就可以进行升级。
@author
作者,这后面写上你的大名,愿意加上邮箱,也没人管你。或者加上一堆形容词。反正就是写清楚脚本的作者是谁,你一定不会希望别人用了这个脚本,却不知道它是你写的,那就真的变得默默无闻了。
@description
对于这个脚本的描述,它会显示在应用市场的简介中,就是脚本名字下面最先显示出来的那部分。在脚本列表中,显示的脚本信息也对应着这里的内容。
这里进行一些补充说明。上面这 4 个都只能出现一次哦,很容易理解的吧,我们总不能给一个脚本弄出两个名字来,两个版本号就更加荒唐了。如果有多个作者,那就都写在那一行里就好。对,一行。就算是描述也要写成一行,不要用回车换行。(别抬杠,抬杠就兼容性,不一定所有脚本管理器都支持特殊的格式,所以用最基本的格式最不容易出错)
@name
和@description
因为是描述性的内容,所以支持多语言,就是你可以为每一种语言单独设置一份名字和描述,具体格式如下:// @name My first user script. // @name:zh-CN 我的第一个脚本 // @name:en-US Meow~ // @description dms is a sunshine boy. // @description:zh-CN 小老鼠帅帅哒~ // @description:en-US Delicacies
描述中建议写上脚本的创作日期和更新日期,这样方便自己日后回顾。如果你有强迫症,觉得在描述中混入这样的信息,让自己不舒服,也完全可以自己创造两个字段来标记,反正脚本管理器不理解就会把这些字段跳过去,但是自己看代码的时候是有意义的。
@match
这是描述这个脚本要在哪些网址下运行。如果和后面的网址匹配就运行。
但大多数时候我们的脚本并不只运行在某一个确定的网址下,而是希望在某个网站上都起作用,或者这个网站的某个分类下都起作用,这时候就要用到通配符。
网址会被分为三部分:协议、域名、路径。
https://www.appinn.com/auto-expand-full-text/
这样的一个网址,其中 https
就是协议,那我们知道还有 http
的协议。
www.appinn.com
这部分就是域名,表示的这是哪一个网站。
auto-expand-full-text/
这部分,就是在域名后面那条斜线之后的部分,用来表达这个网站中的哪个页面,这就是路径。
然后对于每一部分,我们都可以完整准确的去写出它的内容,或者使用星号代替一定的字符。
如果在协议部分使用星号,那么代表的是:http
或者 https
,对,只有这两种协议,并不包含其他。
如果在域名部分只有星号,那么它匹配任何网站。如果域名的子域是星号(比如上面域名的 www 部分),那就表示这个域名下的任何子域(www 可以被换成任何内容)。
如果是路径位置的星号,它可以匹配 0 到任意多个字符。
*://*/*
所以像上面这样写就表示匹配任何协议下的任何网站的任何路径,就是在所有能运行的页面全都运行这个脚本。
*://www.appinn.com/*
上面这样写表示在这个网站下的所有页面都运行。
*://www.appinn.com/posts/*
上面这样写表示在这个网站下的 posts
目录下的页面中全都运行。
这个规则并不难掌握,因为它没有搞得很复杂。几种简单的情况,稍微熟悉一下就可以很好的掌握了。但这时候我们就会遇到一个很普通又很棘手的问题,都有很多网站,我们在域名部分写出下面两种形式都可以正常的访问:
www.appinn.com
appinn.com
就是加不加 www 都可以。但这就很难使用通配符进行表达。
*://*.appinn.com/*
这样前面不是 www 的也会被匹配,并不符合我们的预期。而且注意在星号后面多了一个点,所以这种书写方式并没有办法匹配到完全不加 www 的情况。
有同学可能说,反正星号可以代替各种字符,那我就这样写:
*://*appinn.com/*
先不考虑这个写法是不是真的符合规则。就算可以正常使用,那么会不会匹配到下面的网址:
https://meow-appinn.com/
这显然就完全是另外一个网站了,所以这样写的疏漏太大,是完全不可取的。正确的方法是,索性写两条规则好了,就像下面这样:
@match *://www.appinn.com/*
@match *://appinn.com/*
所以,@match
可以出现多条。把你需要适配的情况写完全,一条规则一行,就像上面这样书写就好。
可选的地址信息
写不写都可以,写上会更完善一些。
@homepage, @homepageURL, @website, @source
这是个选择题,4 选 1,因为表达的意思是一样的。由此可见,对于脚本的元数据,大家创造了很多规范,也并不是那么十分统一。还有就是随着规范的不断更新,又要去兼容以前的规范,导致多种规范并行。
这个字段设置的是脚本的主页,就是官方网站的意思。但很多脚本也并没有主页,于是就放上作者自己的网站博客等。有人说这就是免费的广告位,谁会放弃呢?(我的脚本好像大部分都没有写,亏了亏了
这个信息会显示在脚本市场或者脚本列表中的主页信息上,一般只要点击就会直接跳转到对应的网站。
然后如果在 @namespace
中填写的直接是一个网址,也会被视为这个脚本的主页。
@icon, @iconURL, @defaulticon
三选一,设置脚本的图标,放上图标图片的网址。这时候你可能需要一个好用的图床,这确实挺麻烦,所以好多脚本就并没有设置。
当然如果设置了会显得很好看,也很专业的样子。
@icon64, @icon64URL
表示 64×64 像素尺寸的图标,和上面一样给出一个图片的地址。和上面的区别是这代表更高清晰度的图标。但一般来说我们也不怎么会用上,而且也不是所有脚本管理器都支持。如果设置图标,就设置上面的字段就够了,这个就作为了解一下,又是你可以不用,但我不能不说的内容。
@supportURL
支持页地址,如果用户有问题去什么地方找你进行反馈,那么把这个地址放在这里。脚本市场和脚本列表中对应的位置会进行显示。
@downloadURL
脚本的下载地址,就是你的脚本文件在网络中的访问地址。脚本管理器会通过访问这个网址来更新脚本。
但一般也没有人写,因为如果是通过网络地址安装的脚本,脚本管理器会自动为这个脚本添加它的下载地址,以备日后进行更新。我们上传到脚本市场的内容,即便没有填写这些信息,也会被添加/修改为市场的地址。这可以让用户更方便稳定的获得脚本更新。
@updateURL
脚本的更新地址。首先这个不是所有的脚本管理器都支持,很多脚本管理器直接通过上面的下载地址判断是否需要更新。
脚本管理器通过访问这个地址,获取当前最新版本的版本号,然后得出一个是否有更新的结果。所以只是借此进行判断,而不是通过这个网址进行更新。
大部分情况下无需填写此信息。只不过是既然这些网址都出现了,索性把它们放在一起讲一下。
为了避免疑惑,也方便大家出去装 x,所以我把这个没什么用的东西也解释一下。这个更新地址里放什么呢,也放你的脚本,就和上面的 @downloadURL
是一样的。然后脚本管理器访问 @updateURL
来获取脚本的版本号和更新日期,来判断是否需要更新。如果需要,那么通过 @downloadURL
的地址来下载更新。
好像这样复杂的设计,非常莫名其妙,因为这两个网址可以完全一样,并且会正常的工作,搞成两个,这不是多此一举么?那现在我们来设想一个特殊的情况:我的脚本非常复杂,所以体积很大,下载一次需要好几分钟那种。然后用户又有强迫症,希望保持脚本的及时更新,那每一次检查更新需要花费的时间就很长。然后我很懒惰,三年才更新一次脚本,三年里用户的脚本管理器每天都在检查更新,这是怎样的资源浪费啊!但把检查更新和实际进行更新这两个网址分开,就意味着我可以在 @updateURL
这里放一个只包含脚本元数据的版本,这样检查的速度就非常快了,反正也就是对照一下元数据,看看是否需要更新嘛。如果发现确实有更新的需要,再去下载地址,下载那个体积巨大的完整版本。
这么一解释就会发现这个设计是合理的,但为什么现在又不需要呢?下载地址完全可以让脚本管理器自动去解决,你从哪里安装的这个脚本,它就把这个地址自动的记录下来,然后随时从这个地址去检查更新,这样无论是作者还是用户都会更加方便。至于检查更新的时候,脚本管理器可以只下载前面的一段数据进行比较,反正元数据都写在最前面,我读取到版本号就好了,后面的内容不下载。就是通过脚本管理器的增强和优化,让这些以前有意义的设计变得没有什么必要了。
出于强迫症或者一致性的考虑,主页字段我会选择使用
@homepageURL
这个名称,图标字段用@iconURL
。当然如果新建脚本的时候给出的模板已经写了,那我也懒得去修改,直接填上内容就好。
需要时才会出现的内容
以下内容仅在你需要进行这项设置的时候才书写它。否则就没有必要让它们露面了。
@exclude-match
这个和我们前面讲到的 @match
书写和使用都是一样的,但表达的意思是不要在这些网站上去运行脚本。可以认为 @match
是一个白名单,而 @exclude-match
则是一个黑名单。
如果我们只打算匹配几个确定的网站,那么使用白名单(@match
)肯定最方便;而如果要匹配几乎所有的网站,只有极个别的网站要排除掉,这时候就要配合使用。先用白名单(@match
)匹配所有网站(*://*/*
),再用黑名单(@exclude-match
)将特定的网站排除掉。
@match
是必须有的,@exclude-match
则根据情况去进行设置。这两项在(针对单个)脚本的设置中,也会有相应的选项,这是供用户进行设置的,用户的设置可以覆盖脚本自带的设置,并且不会受到脚本更新的影响。
@include / @exclude
和 @match
/ @exclude-match
差不多,建议使用 @match
/ @exclude-match
而不是 @include
/@exclude
因为匹配规则更安全,更严格。
你可以把这认为是旧的方式,可以用,但不推荐,然后一些脚本管理器可能不支持 @exclude-match
,那就只好用 @exclude
了。
@require
引入的库。
写脚本就是为了给自己带来便利,那如果每一个功能都自己写,确实挺不容易的。要是把别人写好的工具库引入进来,然后再做一些简单的调用,那就方便多了。
这个字段后面应该写一个 js 文件的地址,就是你要引入的那个库。然后脚本管理器就会自动的把它插入到页面之中。其实我们通过自己书写脚本来实现这个功能也不算很麻烦,但是能少写几行代码总是很开心的。
因为可能需要引入多个库,所以这个字段可以书写多个。
@resource
这个字段用来将一些在脚本中会使用的资源进行预加载。就是打开页面以后,它就会开始下载这些资源,这样等到我们需要使用的时候,就不需要再去等待那个下载的时间了。
具体的使用需要配合接口,这个我们在后面会讲到,这里只先演示它的书写:
// @resource logo https://img3.appinn.net/static/wp-content/uploads/appinn190.png
后面的内容首先是我们给这个资源取的名称,用来在脚本中调用。然后是这个资源的地址,可以是图片或者某些文件的网址。
当然我们需要引入的资源可能不止一个,所以这个字段也可以书写多个。
@grant
上面说到了接口,就是脚本管理器给我们脚本提供的一些便利,和更高的权限,让我们能够更好的完成工作。就好像可爱的女仆打算为小老鼠做饭,她就要跟管家申请使用厨房,管家允许她使用厨房,并且有权使用厨房中的各种食材,厨房里的厨师们也要听从她的指挥来进行辅助。那么烹饪这件事情就变得简单而畅快起来了。
当然如果你需要这些权限,这些辅助,要先和管家明确的提出申请,这样管家才知道如何安排才能够更好的帮助到你。@grant
这个字段就是用来申请接口的权限,说明我要使用这个接口,然后才可以在脚本中进行使用,它的书写格式是这样的:
// @grant GM_getValue
// @grant GM_setValue
在它后面写上要使用的接口名称就可以了,每一行只能申请一个接口,如果需要多个接口,那就像上面这样书写多行。
@run-at
这个字段是用来设定我们的脚本在什么时候注入到页面之中,有如下几个值可供选择:
-
document-end
这是 Violentmonkey 的默认值,就是当页面加载完成之后,再注入我们的脚本。一般来说这个时机是不错的,页面加载完成以后不会再发生大的变化了,然后我们对它进行修改,就不容易出现混乱,或者被后续的改变而覆盖。但如果某些页面使用了懒加载技术,那么可能虽然已经宣称页面加载结束,其实还在进行着各种加载工作,这时候就要具体问题具体处理了; -
document-start
这表示让我们的脚本尽可能早的被注入,但这并不能够保证我们的脚本就在其他所有脚本之前运行。 -
document-idle
这是 Tampermonkey 的默认值。这个时机可以简单粗暴的认为是在页面加载完成之前的那一瞬间。虽然在页面加载完成之后去注入是一个不错的时机,但是可能有许多脚本都在等待着这个时机运行,我们不想和其他脚本争抢,所以就走一个 VIP 通道,当页面加载完成,但还没有对外宣布的时候,就是还没有到document-end
这个时间点的时候,注入我们的脚本。然后再对外宣布页面加载完成,即到达document-end
这个时间点。 -
document-body
如果存在<body>
元素,则执行脚本。Tampermonkey 支持此选项,Violentmonkey 中则未提及。 -
context-menu
当在浏览器的右键菜单中点击该脚本的项目,才进行脚本的注入。这个方法最大限度的保证了脚本不被使用就不被加载。Tampermonkey 支持此选项(但未来可能改变,并且文档中没有说如何设置右键菜单),Violentmonkey 中则未提及。
@noframes
这个字段后面不需要写任何的内容。如果没有写这个字段,则脚本默认是在页面和页面里的所有的框架中运行(如果这个框架的网址也符合我们设定的条件的话)。
添加上这个标记,则只在页面中运行,而忽略掉所有的框架。
如果你没有听懂这是在说什么,就在发现某个页面中这个脚本被运行了多次,不符合你预期时加上这个。
一些不是所有脚本管理器都支持的标记
其实上面已经讲到一些,比如 @icon64
、@updateURL
,以及上面运行时机中的一些选项。后面这些则是对于新手几乎是完全没有用的,即便是老手也很少需要用到它们,或者在用到的时候需要十分慎重,因为要认真的考虑兼容性和可能引发的其他问题。
@connect
后面是书写一个域名,用来声明我们会在脚本中使用到这个域名中的资源。这可以用来解决跨域问题。
这个功能我没有使用过,而且我也希望所有作者都能够谨慎使用此功能。虽然在前端上禁止跨域是一件非常烦人的事情,但这个限制在安全层面考虑还是非常有必要的。现在工具可以给我们更高的权限来突破这个限制,但是“能力越大,责任越大”,我们也有必要十分谨慎的对待这个强大的权限。
此字段可书写多个,Violentmonkey 似乎并不支持。
@nocompat
标记不兼容的浏览器,因为每个浏览器在技术上总是有着细微的差别,所以导致有些脚本只能在特定的浏览器上去运行。我们通过这个标记来告诉脚本管理器,当前的脚本不兼容哪些浏览器。
此属性似乎 Tampermonkey 独有。
@inject-into
用来指定脚本的注入模式,有如下三种选项:
-
page
:这是默认值,脚本会被直接注入到页面之中,各方面都和页面原生的脚本是一样的; -
content
:在一个与页面隔离的环境下运行,可以访问和修改页面的 DOM,但是无法访问到网页的 JavaScript 对象。 不过可以通过unsafeWindow
对象来绕过限制进行访问,但这种做法(绕过)是非常不推荐的。 -
auto
:先尝试使用page
模式进行注入,如果出现问题,则改用content
模式来进行注入。
这个字段似乎是 Violentmonkey 独有。Tampermonkey 默认用 content
模式注入,在必要的情况下用 unsafeWindow
来访问 window
对象。
应用市场提供的标记
这里仅参照 Greasy Fork 的说明,不同市场会有不同的规定。
@license
脚本的许可协议,如果没有填写,则表示仅允许个人使用,且不得二次分发。
@contributionURL
用来收取捐助的链接。
@contributionAmount
建议的捐助金额
@compatible
脚本兼容的浏览器,并可附加更多说明。如:
@compatible firefox 火狐上必须关闭广告过滤器
目前能被网站识别的浏览器名称有:firefox
, chrome
, opera
, safari
。
@incompatible
不被兼容的浏览器,并可附加更多说明,格式参照上一条。
附录:上面提到的所有元数据的速读版本
-
@name 【通用】【多语言】脚本名称
-
@namespace 【通用】命名空间
-
@version 【通用】版本号
-
@author 【通用】作者
-
@description 【通用】【多语言】脚本描述
-
@match 【通用】【可多条】匹配规则
-
@homepage, @homepageURL, @website, @source 【可选】主页
-
@icon, @iconURL, @defaulticon 【可选】图标
-
@icon64, @icon64URL 【非通用】高清图标
-
@supportURL 【通用】【可选】支持页地址
-
@downloadURL 【通用】【不必填写】脚本下载地址,一般无需书写
-
@updateURL 【非通用】【不必填写】脚本的升级地址,无需书写
-
@exclude-match 【非通用】【可多条】排除规则
-
@include 【通用】【可选】【可多条】匹配规则
-
@exclude 【通用】【可选】【可多条】排除规则
-
@require 【通用】【可选】【可多条】引入的脚本
-
@resource 【通用】【可选】【可多条】预加载资源
-
@grant 【通用】【可选】【可多条】接口权限申请
-
@run-at 【通用】【可选】脚本注入时机
-
@noframes 【通用】【可选】不在框架内注入脚本
-
@connect 【非通用】【可选】【可多行】白名单外部域
-
@nocompat 【非通用】【可选】不兼容的浏览器
-
@inject-into 【非通用】【可选】脚本的注入模式
-
@license 【市场】脚本的许可协议
-
@contributionURL 【市场】脚本的捐助链接
-
@contributionAmount 【市场】建议的捐助金额
-
@compatible 【市场】兼容的浏览器
-
@incompatible 【市场】不兼容的浏览器
以上内容,小老鼠花费了一天多的时间辛苦整理。肯定还有很多疏漏,也有很多不准确的地方。但我也很难将它做的面面俱到,毕竟在不同的浏览器下,不同的脚本管理器下,面临的情况都不相同。而且还有油猴标准不断更新的因素在影响。所以这些内容只是作为给大家的参考,但实际书写的时候还是要以实际运行的效果为标准,根据自己面对的情况灵活的选择。
【返回目录】 | 【下一章 如何调试脚本】
如何调试脚本
校对完成,更新时间:2020-07-23 11:36:10
如果打算自己去书写脚本,就难免要对脚本进行调试。代码写出来,试一试它能不能按照我的预期去运行,如果不符合预期,那么看一看究竟是哪里出了差错,然后修正这个问题,让脚本回归预期。
最基本,最重要
最简单直接的方法就是把写出来的脚本安装进去,让它实际工作一下,看看效果如何。前面我们也已经讲到过了,无非就是直接在脚本编辑器里进行编辑;或者在外面编辑好,然后复制粘贴进去;再或者将本地文件安装到脚本管理器里,让它可以更加便捷地进行更新(同步更改)。
开发者工具
然后要用到的是浏览器的开发者工具,可以从浏览器的菜单打开,或者在页面上右键检查元素,再或者按快捷键 F12。
其中会有一个控制台的标签(Console),这里主要用来输出页面的各种运行信息,如果我们的脚本出错,那么它的报错信息应该也会出现在这里。
其中有一个禁止符,可以清空控制台当前的所有输出。如果在清空之后再触发脚本,那么可以更加容易阅读,避免其他干扰信息。
如果使用的是 Violentmonkey,那么脚本默认的插入模式是 page
,就是和网页自身的脚本无异。所以这时候在控制台的过滤器里选择 top,就是查看最顶层的信息,就对了。如果是 Tampermonkey,或者以 content
方式插入,则在过滤器里选择对应的脚本管理器。
脚本出现问题产生的信息,一般在右侧的文件名处会是 脚本名称.user.js
,也可能是 VM.js 或者什么类似的东西。因为环境的不同产生的效果可能也不一样,这种事情就灵活变通就好。反正大部分情况这些信息还是很容易分辨的。
这里我就不特别进行展开了,因为后面连贯的是一整套的前端知识和开发相关的技巧,一旦展开,那这篇内容就直接从中篇飙升为长篇,老鼠就真的要累趴趴了。
如果对这方面有兴趣建议去网络上搜索相关内容,或者深入阅读 MDN 的前端开发文档,或者谷歌的开发者工具文档( https://developers.google.com/web/tools/chrome-devtools )。又或者来阅读小老鼠写的前端教程( https://2019.dmnydn.com/ )。
断点
如果我们在代码中插入这样一行:
debugger
就为代码加入了一个断点,代码运行到这里会暂停并打开开发者工具显示对应的位置,这时候前面已经运行的代码上会标示出已有的量,方便查看程序是如何运作的。
片段
如果只是想测试代码中某一个细节,比如这一句代码是否可以准确的获取页面中某个元素的值。那么直接将这段代码复制到控制台中,回车运行,查看结果也是一个很不错的办法。
这里需要提示的就是在控制台中输入时,如果点击回车,就会将已输入的代码运行。想要对代码进行换行,则需要使用 Shift+Enter。
Code Runner
在 VS Code 编辑器里有一款叫做 Code Runner 的拓展,它可以在编辑器中直接运行 JavaScript,并查看效果。甚至需要测试的代码都无需保存,便可以直接运行。
当然这是在你的代码与页面内容无关的情况下,比如就做一个计算器,那么就和页面中的元素等无关,所以在编辑器中直接进行测试会方便一点。
或者只是用它来测试一些与页面无关的代码片段。
耐心
虽然这算不上工具,但真的是写脚本时非常重要的东西。
如果是书写一个页面,那么我们从零开始搭建,干扰的因素是很少的。但脚本却是在别人页面的基础上去进行修改,这就仿佛在尝试解开一个残局。其中会有很多制约,很多掣肘的地方,还有很多你可能没有想到的影响因素,就需要凭着耐心一点一点进行排查。
到这里我们算是对油猴脚本有了一定的了解。如果你本身有前端的基础,其实现在已经完全可以进行书写了。就算没有基础,凭着已经了解到的这些内容,再去看别人的代码,也能从中了解到很多信息。后面我们要进入到它的入门部分,了解它能够带来的便利,并尝试书写一些简单的小脚本。
就是加不加 www 都可以。但这就很难使用通泰服进行表达。
感谢指正,已经修改。
因为是使用语音输入法完成的,难免有识别错误的地方。回头我会再做一遍校对。
油猴子的接口
校对完成,更新时间:2020-07-23 13:15:11
这是脚本管理器提供给作者们的便捷功能,所以是否有这些接口以及接口的具体使用方法,还有接口会产生怎样的效果,都取决于脚本管理器,在书写前先去查阅脚本管理器所提供的文档是很有必要的。
其中一部分接口,我们也可以通过自己书写代码来实现,但有现成的方法可以少写几行代码,而且兼容性可能更好一些,这还是很让人乐于接受的。
然后脚本管理器作为浏览器的扩展,它是有比较高的权限的,通过这些接口我们的脚本也可以调用这些权限,来实现更高级的功能。
再强调一次,这里所列出的接口仅仅作为参考,在使用时因为具体环境的差异而有所不同。
接口详解
unsafeWindow
这大概不能算是一个接口,但是 Tampermonkey 的文档中也把它列出来了。当脚本以 content
模式注入时,我们可以用这个对象来访问一些原本受限的内容。就如它的名字一样,使用这个方法意味着绕过了一些安全限制,也就有可能带来一些安全上的问题。
资源验证
这大概不能算是一个接口,但是 Tampermonkey 的文档中也把它列出来了,Violentmonkey 似乎并不支持此方法。
当我们引入一个资源,如何确定这个资源就是我们所预期的那个,而没有出差错,在请求的过程中没有被人替换掉。那么我们可以对引入的资源加入一个校验。
具体使用是在我们引入资源时,网址后面以井号分割的形式加入对应的校验特征码:
// @resource logo https://img3.appinn.net/static/wp-content/uploads/appinn190.png#md5=ad34bb...
Tampermonkey 本身支持 md5 方法,其它(SHA-1,SHA-256,SHA-384和SHA-512)资源特征方法,都取决于 window.crypto
的支持。
如果资源校验不成功,则不会传递给用户脚本。文档中说所有校验用的特征码都需要以十六进制或 Base64 格式编码,具体我未做测试。
一般的讲,这并不是很有必要,虽然确实在安全方面做得很到位。但是一般网站中的资源也都没有经过校验,目前对于资源的网址启用 https 已经可以杜绝大部分问题。
这个方法的主要问题是有的脚本管理器并不支持。不过因为附加的内容以锚点形式出现,所以并不会带来什么副作用,而且附带上文件的校验特征,也可以确保当此文件发生更新之后使用的是更新后的文件,而不是缓存。
以下以
GM_
开头的这些方法才是相对正式的接口,如果需要使用,它们均需要在元数据用以@grant
字段进行声明。
GM_info
这个方法可以用来获取一些有用的信息,但根据脚本管理器的不同,返回的内容也不太一样。不过一般都会包含脚本的元数据,脚本管理器的相关信息,系统环境的相关信息。
const info = GM_info()
console.log(info)
如果需要使用,可以像上面那样先输出出来,看一看它能够获取到的信息。下面列出一些其中比较通用的属性(Tampermonkey 和 Violentmonkey 中都有的):
- scriptMetaStr: 脚本的元数据,整个元数据部分,以字符串形式返回。
- scriptWillUpdate: 脚本是否会自动更新
- scriptHandler: 脚本管理器的名称
- version: 脚本管理器的版本
-
script: 脚本的元数据,以对象形式返回。其中包含的元数据可能因脚本管理器的不同而不同。
@match
变成了matchs
,以数组形式包含全部的规则,其它可以出现多次的条目类似。@run-at
变成了runAt
,即避免产生歧义的驼峰写法。
GM_addStyle
向页面中加入样式,这个方法非常简单并且好用,只需要给它一串 CSS 的文本,它就会在文档的 <head>
标签中加入一个 <style>
标签,并把这些 CSS 的内容放进去。然后会返回这个 <style>
元素的对象。
一般来说, <style>
标签会被添加到 <head>
标签的最后面,以确保覆盖页面中原有的样式。但这个事情并没有绝对的保证,只是它在努力去做而已,所以想要确保自己的样式覆盖页面中原有的样式,还是要在 CSS 中添加 !important
。
听不太懂也没有关系,下面看示例代码:
GM_addStyle(`
// 这里写 CSS 代码,可以多行
`)
复制上面代码,然后在中间添入你的 CSS 就行了。注意这里使用的是反引号,这被称为模板字符串,在其中可以进行换行,这样在里面书写 CSS 就可以带上清晰的格式,就像平时书写 CSS 文件一样。如果把其它地方复制过来的 CSS 粘贴进去,也无需因为是在 JS 中而调整 CSS 字符串的格式。
如果你有对这个 <style>
元素的对象进行进一步操作的需求,可以获取它的返回值。
GM_log
这个方法是 Tampermonkey 提供的,用来在控制台输出日志,就和 console.log
一样,但是我更愿意使用 console
下属的方法,因为足够强大,如果觉得名字有点长,完全可以自己命名一个变量进行指代。就没必要由脚本管理器再提供一个简写方式。不过也可能是这个方法有什么我没有发现的特别之处。
GM_log('要输出的内容')
GM_openInTab
很容易理解,就是在新标签页中打开一个网址:
GM_openInTab('https://www.appinn.com/')
可以加入第 2 个参数,表示是否在后台打开这个标签页,所以第 2 个参数是一个布尔值。
GM_openInTab('https://www.appinn.com/', 'true')
像上面这样书写这个标签,则会在后台打开.
第 2 个参数也可以是一个对象,用来传入一些配置信息,此选项对象在不同脚本管理器下的支持是不相同的,只说两个相对通用的属性:
-
active: 这个标签打开后是否处于激活状态,默认为
true
,这个设置和是否在后台打开的选项是正好相反的。如果在前台打开那么自然打开之后就是激活状态,如果在后台打开则是没有激活的状态。 -
insert: 新标签页是否紧挨着当前标签,默认为
true
,如果设置为false
一般浏览器会将这个标签放在最后一个。
然后选项中有可能还可以设置新打开的标签是否被固定,或者是否在隐私模式下打开。在使用这些选项之前,请先确定脚本管理器对此支持。不支持的结果就是达不到你预期的效果,但是链接还是可以打开的。
GM_download
这个方法用来生成一个下载,如果你对前端有所了解,就会知道这个其实和打开链接是类似的操作,同时也和获取网络资源的方法相类似。(这些类似是说的在原理层面)
最简单的使用方法就是直接给它要下载文件的网址:
GM_download('https://img3.appinn.net/static/wp-content/uploads/appinn190.png')
也可以再给它第 2 个参数来说明这个下载的文件默认保存为什么名字,推荐使用这个方法,因为有些脚本管理器可能不支持省略文件名称:
GM_download('https://img3.appinn.net/static/wp-content/uploads/appinn190.png', 'logo.png')
对于新人了解上面的书写方法已经足够了。如果需要进行更细致的控制,可以只给一个对象参数,对象中的属性如下:
- url: 要下载的文件地址,这个是必须的。
- name: 文件的默认保存名称,这个也是必须的。(不是所有脚本管理器都支持省略名称
- onload: 当下载完成后要执行的函数
- headers: 下载请求的头信息
- save As:一个布尔型,是否显示另存为的对话框(Tampermonkey 支持)
- onerror: 当下载出错时要执行的函数
- onprogress: 在下载进度发生变化时执行的函数
- ontimeout: 由于超时导致下载失败时需要执行的函数
GM_notification
让浏览器发出一个通知来提醒用户,现在浏览器基本都支持直接发出一个系统级的通知,就是在右下角弹出那种。
最基本的书写方式:
GM_notification('一些要显示在通知中的文本')
但是这样太简陋了,所以我们还可以给它第 2 个参数作为通知的标题:
GM_notification('一些要显示在通知中的文本', '还有标题就显得很专业')
如果有需要,还可以加入第 3 个参数,放入一个图片地址,它会显示在通知之中。第 4 个参数可以是一个函数,当这个通知被用户点击会执行此函数。这些都是可供选择的,我就不一一演示了。
如果需要更复杂的控制,同样可以传入一个对象作为参数,对象的通用属性如下:
- text: 要在通知中显示的文字内容(必须)
- title: 通知的标题
- image: 要显示在通知中的图片
- ondone:通知被关闭时要执行的函数(无论是何种原因使通知被关闭,都会触发此函数
- onclick: 通知被点击时要执行的函数
还有一些其他属性可供选择,但是不同的脚本管理器有不同的设置,就不在这里详细举例了。
Tampermonk 中提供了通知超时的设置,即超过一定时间通知自动关闭。Violentmonkey 则是提供了 remove()
方法来移除通知(但我并未尝试出具体的使用方法)。所以让脚本发出一个通知是容易的,但是想让通知自动关闭却没有一个通行的并且可靠的方法。
GM_setClipboard
将内容写入系统剪切板,就是通过脚本复制。
GM_setClipboard('这些文本会被放入到剪切板之中')
它还可以有第 2 个参数来说明内容的类型,默认值是:text/plain
,就是纯文本。
如果需要设置为其它类型,可以自行查阅 MIME types 的相关信息。但除文本以外的内容复制效果不一定能够得到确切的保证。
GM_setValue
保存一个数据,这会保存在这个脚本自有的储存空间,以供此脚本随时访问,它不受标签页关闭、刷新以及浏览器关闭等操作的影响。借助这样的全局数据,我们可以实现页面间的通讯。此数据可在脚本设置中进行观察。不同脚本间的数据并不互通。我们可以将比如用户对脚本的设置等需要延续性的数据存储到这里。
GM_setValue(‘myAge’, 16)
第 1 个参数是这个数据的名字,第 2 个参数是它的值,这两个参数都是必须的。
GM_getValue
凭数据的名称,获取已保存的数据值。
let data = GM_getValue('myAge')
但有一种很容易出现的尴尬情况,就是当我们试图获取这个数据的时候,其实它还没有被储存过,也就是还不存在。因为这种现象太普遍了,所以准备了一个快捷的解决方法,就是给它第 2 个参数作为默认值,像下面这样:
let data = GM_getValue('myAge', 18)
如果我们获取到已经储存的数据,那就使用获取到的值作为结果,如果发现还没有储存这个数据,就使用后面的默认值,这里是 18。
GM_deleteValue
删除一个已储存的数据,只要有这个数据的名字就行了。
GM_deleteValue('myAge')
GM_listValues
当我们存储的数据比较多的时候,就可能记不起来都存储了哪些数据。又或者我只是想判断一下现在有哪些数据已经被存储。就可以使用这个方法,它会返回一个数组,数组中是所有已存储数据的名字。
const dataNameArray = GM_listValues()
GM_addValueChangeListener
添加一个监听器,某个储存的数据发生变化时,就执行对应的操作。第 1 个参数是这个数据的名字,第 2 个参数是要执行的操作(一般是一个函数)。
调用对应的操作时,会向它传入 4 个参数:
- 这个数据的名称
- 这个数据以前的值
- 这个数据改变以后的值
- 是否是由其他标签页造成的修改
const listenerId = GM_addValueChangeListener('myAge', (name, oldAge, newAge)=>{
console.log('我以前的年纪是 ', oldAge, ' ,现在的年纪是 ', newAge)
})
这个操作会返回一个编号,用来标识此监听器。
GM_removeValueChangeListener
凭借监听器的编号移除对数据变化的监听。
GM_removeValueChangeListener(listenerId)
GM_getResourceText
获取资源的文本内容,它只有一个参数,就是这个资源的名称。这个资源应该在元数据中以如下形式进行声明:
// @resource logo https://img3.appinn.net/static/wp-content/uploads/appinn190.png
然后我们就可以像下面这样获取:
const text = GM_getResourceText('logo')
console.log(text)
但需要注意,此方法会将资源当作文本内容进行读取,所以适合用来获取一些文本类的信息,比如 Json 格式的文件。上面举例中因为资源是一个图片,所以用此方法,输出的内容会是一片乱码。
GM_getResourceURL
这个方法用来获取资源的 Blob
格式数据地址。可以用来读取各种二进制文件,就比如图片。
const blobUrl = GM_getResourceURL('logo')
const img = new Image()
img.src = blobUrl
GM_registerMenuCommand
这个方法可以注册一个脚本菜单,它只是出现在脚本管理器的弹出列表中,方便用户操作。
GM_registerMenuCommand('这里是显示的名称', ()=>{
console.log('啊,你点击了菜单~')
})
第 1 个参数是菜单显示的名称,第 2 个参数是当菜单被点击时要执行的动作。
GM_unregisterMenuCommand
用来移除一个脚本菜单,这个操作在不同的脚本管理器下有所不同。
Violentmonkey 下:凭借菜单显示的文字即可删除对应菜单。
GM_unregisterMenuCommand('菜单显示的文字')
Tampermonkey 下: 注册菜单时会返回一个菜单 ID,然后凭借这个 ID 去删除对应的菜单。
const menuID = GM_registerMenuCommand('这里是显示的名称', ()=>{
console.log('啊,你点击了菜单~')
})
GM_unregisterMenuCommand(menuID)
GM_xmlhttpRequest
创建一个 xmlHttpRequest 请求。如果你不懂,那基本现在也用不上。
它的参数是一个对象,其中包含有大量可选属性。
-
method: 发起请求的方法,可选: GET, HEAD, POST,默认:
GET
- url:请求的目标网址
- headers:请求的头信息
-
data:
POST
方法下要发送的数据 - cookie: 需要额外附加的 cookie 信息,这是在页面原有 cookie 信息之外增加的补丁。Tampermonkey 支持此属性
- binary: 用二进制模式发送数据
- nocache:不对资源进行缓存,Tampermonkey 支持此属性
- revalidate:重新验证缓存的资源,如果通过验证则使用缓存,Tampermonkey 支持此属性
- timeout: 请求的超时时限,单位是毫秒(ms)
- context: 请求的上下文
-
responseType:请求期望的资源类型,默认为:
text
,可选值有:text
、json
、blob
、arraybuffer
、document
(此值 Violentmonkey 支持) - overrideMimeType: 请求的 MIME type 类型
-
anonymous:匿名模式,设为
true
则不发送任何 cookie 信息 - fetch:使用 fetch 而不是 xhr 请求,Tampermonkey 支持此属性,且为测试功能
- username: 用于验证的用户名
- password:用于验证的密码
下面内容同样放在这个参数对象之中,它们是一些事件的处理方法。
- onabort: 如果请求被终止,则执行此操作
- onerror: 如果请求出现错误,则执行此操作
- onloadstart: 当请求开始加载,则执行此操作
- onprogress: 当请求的进度发生变化,则执行此操作
- onreadystatechange: 如果请求的状态发生改变,则执行此操作
- ontimeout: 当请求超时,则执行此操作
- onload: 当请求完成,则执行此操作
执行上述事件所对应的处理方法时,会传入如下参数:
- finalUrl:从请求中返回的重定向地址
- readyState:请求完成的状态,这是一个数字
- status:请求的状态,这是一个数字
- statusText:请求状态的文本
- responseHeaders: 响应的头部信息
- response:响应的数据对象
- responseXML:将响应数据当作 XML 文档读取,Tampermonkey 支持此属性
- responseText:将响应数据当作纯文本文档读取,Tampermonkey 支持此属性
发出请求会返回一个对象,这个对象有如下属性:
- abort:这是一个用来终止该请求的函数
以上便是油猴子中比较通用的接口。
你可以尝试将上面举例的代码放入自己的脚本之中,运行一下,看看能不能得到预期的效果。
【返回目录】 | 【下一章 需要的基础知识】
需要的基础知识
校对完成,更新时间:2020-07-23 13:40:55
如果你已经具备前端基础,那就别听老鼠在这里磨叽了,动手写吧,你可以的.
如果你对基础有所了解,但要想知道的更多,就去认真的研读文档吧。在技术精进的道路上,除了学和做真的没有任何捷径。如果谁说有,那么把他打死,因为他和小老鼠讲的不一样,哼唧~(霸道!
如果你毫无基础,那么往下读,我会告诉你一些最基本的东西,让你可以做一些非常简单的操作,从中获取成就感,也更有利于你对这些技术有一个比较直观的印象。
但是就不要期望我完全展开,因为那样真的就成了大长篇的教程了。如果对前端有兴趣,推荐阅读 MDN 的开发文档;或者阅读我书写的《代码能有多难?》( https://2019.dmnydn.com/ ),相对通俗浅显的将基础知识串讲了一遍,可能更有利于入门吧~如果你想学,又觉得难以坚持,那就来找小老鼠,我们一起探讨,或者让老鼠拿着小皮鞭每天督促你。
是的,以上是广告。这是多么良心的广告啊,难道你不想多读几遍么?!
以下所讲的内容,是写油猴脚本所必须的最基础的东西。当然别抬杠,就算是最基础的东西,也有可能一直都用不上它。一日三餐还是很基础的事情呢,但中午才起床的同学可能几年都没吃过早餐了。
HTML
认识标签,了解标签的结构,还有网页的基本结构。
下面是标签的最基本结构:
<这是开始标签>这是标签里面的文字或者代码</这是结束标签>
像这种东西,只要你不一看代码就吱哇的喊着:难呀难呀~应该有两分钟时间,自己就参悟透了。
<开始标签 属性名=“属性值”>内容</结束标签>
上面这样就给这个标签增加了一个属性。然后你看着代码对号入座就行了。
不知道有哪些标签,不知道有哪些属性,那你就先以为你不知道的就用不上,掩耳盗铃就很快活。因为油猴子很多时候都是去修改别人的网页,所以并不一定要求你对这些东西有特别深的了解,我也不知道你这是啥,我也不知道有什么用,但我就是想把这里的 1 改成 2,这没问题。
CSS
如果你想改变网页的样式,那需要学习 CSS;如果你想要一些看起来酷炫的动画,那最好也学习一下 CSS。
如果你只是想修改网页中的某个基本样式,那也许不需要深入的学习,只要能够通过开发者工具去查看这个元素所具有的样式,然后尝试修改就可以了,反正这些样式的属性名写的还算是比较直白,只不过是用英文单词去表述它的功能而已。大部分英文单词也都是非常基础的说。
但是,CSS 的选择器是必须学会的,想操作一个元素,你总要告诉脚本我要操作的是哪一个元素。而描述这件事情,就需要选择器的帮助。
这里简单的讲一下,记住,我只是简单的讲一下(强调!),所以还有更多更复杂的选择器,如果展开讲,你可能会看睡着的,而且有一些真的非常不常用。(但是学会了冷门的技能,遇到特殊情况的时候,就可以显示出自己比别人牛逼的地方来了,诶嘿嘿,我看你骨骼清奇,要不要跟我学习啊~~
选择器
-
用标签选择: 就直接用标签名进行选择,
a
指的就是链接,p
就是段落 -
用 id 选择: 如果这个元素具有 id 属性,我们可以用井号加它的 id 去选择这个元素,比如:
#meow
-
用 class 选择: 如果这个元素具有 class 属性,我们可以用英文的句号加上它的 class 去选择这个元素,比如:
.meow
- 用通配符选择: 星号代表一切元素
-
后代选择:把上面这些组合使用,中间用空格分隔,就表示什么里面的什么,比如:
#cat .meow
就是说我们要找的是 id 为cat
这个元素中所有具有值为meow
的 class 属性的元素。 - 子元素选择:如果把上面的空格换成大于号(大于号左右可以加上空格,只是为了好看),就表示什么里面的直接子元素中的什么。直接子元素就是,被套在里面又紧挨着的。反正就是儿子辈儿,不是孙子辈儿,也不是更下面的后代
JavaScript
这里就很重点了,因为这是我们主要写的东西,哪怕只是写一句代码,我们要写的也是它,这是真正绕不过去的。
有人跟我说:你就直接告诉我怎么去做这件事情,我不想听你从头讲起,深入展开。
如果可以,我也真的想这样做,但实际上那样你并没有学会,什么都没学会。都不如不学,然后请我喝杯咖啡,帮你写这句简单的代码。我这样说肯定有人不太相信,毕竟也有很多教程,告诉你一句代码解决什么问题,又好学又厉害,隔壁的小孩都馋哭了。
那我就按照这个思路给你们掰扯一下:我现在要把网页中所有链接都变成链接到我的网站:
document.querySelectorAll('a').forEach(e=>{ e.href = 'https://dmnydn.com/' })
这代码不算长吧,如果我说这是一句代码,虽然不是很严谨,但也绝对不算过分。现在你可以把这句代码拿走了,而且不需要我讲解,你就知道修改后面的网址可以改变最终的结果。
好了,一句话,学会一个功能,多厉害!但如果真的是零基础,除了最后那个网址,你还能修改什么?而且在特定的情况下只是替换网址都可以导致出错,出错了也不知道发生什么,那就是小老鼠教给你的技巧有问题,嗯,都是小老鼠的错。还有,如果页面中的链接是用 JS 控制跳转的,那这个方法可能修改不了,于是,这个方法没起作用,全是小老鼠不好!!
那么上面一行代码中包含着多少相关的知识呢,我列个表看看吧:
代码 | 对应知识 | 讲解难度(十分粗略) |
---|---|---|
document | DOM 对象 系统对象 属性、方法、事件 …… |
仅基本理解这些概念 大概需要一万字 |
querySelectorAll | 方法 元素对象 |
表面看几百字能说明白 但要了解相关方法也需要几千字 这个方法是选择所有符合元素的 还有只选择一个元素的方法 和一些其他选择方法 |
‘a’ | 字符串 选择器 HTML 标签 HTML 基础 |
选择器本身就是一个比较复杂的事情 如果你想要准确的选择到自己想操作的元素 就必然需要对这方面有足够的了解 再加上 HTML 基础,得一万字以上才能讲清楚 |
forEach | 数组 遍历 循环 |
都是重要概念,都是基础 只是弄明白这是在干啥 也需要三千字以上的讲解 |
e => {} | 函数 箭头函数 函数的参数 形参和实参的分辨 元素对象 |
函数可是个大重点 用一万字讲解不算过分 |
e.href | 元素对象 对象 |
这些前面已经提到过了 |
= | 赋值 运算符 |
赋值操作对于没有基础的人 是第一个需要重点理解的概念 只要想再稍微多了解一点 就必然涉及到运算符的问题 聊个五千字完全挡不住啊 |
‘https://dmnydn.com/’ | 字符串 | 这个前面已经提到过了 |
所谓的讲解字数,你就当我是开玩笑就好,我只是为了让完全对此没有概念的朋友能拥有一个相对直观的感受。这个形容方式十分的不精确,因为讲解的字数并不能真正的表达这个问题的复杂程度,有些概念的一句话都需要我们认真的琢磨半天。
那就有人说了,我不想搞得这么深入,我就想对页面做一点非常小的修改,所以像上面那样,给我一句代码,告诉我这句代码是做什么的,就好啦。这样毫无痛苦的学习,难道它不香吗?!
你开心就好。那如果需要修改链接的其他属性呢?如果是修改其他元素的其他属性?不同的属性还有不同的特点呢,它们的注意事项也不相同……然后我们就为每个属性去记录一条代码,那笔记可就真成代码大全了。不过这也挺好,就这样穷举每一句可能的写法,这活儿能干到孙子辈儿。(如果技术停滞不前不再进行发展的话
如果一个人会煮凉面或者能用电饭锅做米饭,他说自己会做饭,也算能够接受的;如果一个人会炒鸡蛋或者哪怕是凉拌个黄瓜,他说自己会做菜,也算能够接受的。水平高低放在一边,但起码饿不着自己。然而有的人就希望别人把菜做好了,然后他端到餐桌上去跟大家说:别客气,尝尝我的手艺……这种我就不是很能理解。
下面我列举的这些知识,仅仅是让大家可以动手开始书写的基础,就是勉强可以让你下凉面、蒸米饭、炒鸡蛋、拌黄瓜的意思。
基础知识
常量、变量
我们在描述的时候不可能总是去说这个数据的值,为了方便就会使用变量或者常量去指代它们。连带出来的问题就是变量和常量的声明、赋值和作用域。
const a = 1
let b = 2
const
声明的是一个常量,let
声明的是一个变量。它们的作用域可以粗略的认为是它们所在的大括号范围。=
是赋值符,表示把右侧的值赋给左侧的量。
再次强调,这里只是非常粗略的告诉大家需要这些知识,并没有进行展开。如果需要了解,可以自己去查阅文档。查询所需要用到的关键词,我基本都已经讲出来了。
运算符
上面的赋值符也是一种运算。我们最熟悉的就是加减乘除。但除此之外还有非常多的运算符,了解它们会带来许多便利,让代码更加简洁。
数据类型
基本的是数字,然后我们会经常用到字符串,就是一串字符,它们会用引号进行包裹。
然后是布尔型,布尔型很简单,只有两个值:true
和 false
,分别表示真和假,或者说成立和不成立。当我们判断两个值,它们是否相等,或者大于、小于、大于等于、小于等于这类关系时,就可以获得一个布尔型的数据,就是讲这个关系是成立还是不成立。
数组,当你有一组同类的数据时,可能就需要用到数组。当数据的结构变得更加复杂,就需要用到对象。这两个概念对于新人理解起来有一点困难,初期可以考虑先把它们绕过去,熟悉了简单操作,再去慢慢理解。
判断
如果小括号里的条件成立就执行大括号里的内容,如果不成立就执行 else 后面的大括号。
if(这里是条件){
// 这里是条件成立时要执行的代码
}else{
// 这里是条件不成立时要执行的代码
}
还有一些其他的变种格式,上面这个仅仅是最基础的,如果是非常简单的判断操作,可能我们会使用三元运算符解决。
循环
随便换一种就可以,如果仅仅说循环,那么肯定推荐使用 while 循环,因为它书写起来和判断差不多:
while(条件){
// 如果条件达成就循环执行此大括号的内容,直到条件无法达成
}
但是,for 循环是无法躲避的,因为在很多情况下 for 循环都更加好用,但是它的格式挺多的,这里我只举例一个最基础的:
for(let i=0; i<10; i++){
console.log(i)
}
以上是起码要会的,已经算是极限压缩了。下面列举一些要用到的对象:
有用的对象
数学
Math 对象包含很多数学方法,稍微复杂一点的运算都可能用到它。
字符串、数组、对象
这三个数据类型本身会带有一定的对它们的操作方法,了解这些方法会带来很多便利。
window
网页中的一切都在这个对象之上,怎么也得认识一下它吧。
document
网页中的元素都在这个对象之上。
网页元素对象
网页元素都具有哪些属性,如何进行获取和设置,这应该算是修改网页的基础操作了。
时间日期对象
Date 对象,涉及时间就会用到它,弄个时钟日历啥的,都和它有关。
这些就根据你希望侧重的方向和需求去了解就好。如果对某方面感兴趣,一定记得自己去阅读文档哦。遇到问题的话,可以来找小老鼠。
下一章我会给大家演示一些基础的操作和套路,算是给大家几块基础形状的积木,供大家尝试和理解。
基本操作演示
校对完成,更新时间:2020-07-23 14:31:52
这些操作并不会很全面,主要作为演示,供大家了解。尽量去顾及趣味性,并有一定的实用性。
想到什么就写什么,并没有特定的条理,后面有时间我可能会再完善一下。
基本
最简单的输出方式
console.log('这里是要输出的内容')
const text = '也可以把这些内容放在一个变量或者常量里'
console.log(text)
然后我们就可以在浏览器的开发者工具中看到这些输出了,它们不会对程序的运行造成太大的影响(如果有大量输出,会让程序明显减慢),这在我们调试代码希望弄清楚究竟发生了什么的时候非常有用。
alert('这也是要输出的内容')
这行代码会弹出一个提示,所以这个输出很明确也很直接,不需要打开开发者工具就可以看到,所以可以用作和用户的交互。但它会将程序暂停,等到关闭这个提示框之后,程序才会继续。
代数运算
let a = 1
let b = 2
let c = a+b*5
console.log(c)
除了需要对变量进行声明,其他的用法和我们学习的代数几乎一致(初级用法),就可以用来很方便的进行数学运算。
连接字符串
const text = '小老鼠'+'他好帅呀~'
我们对两个字符串做加法,就可以将两个字符串连接在一起。
模板字符串
const css = `
body {
background: yellow;
}
`
这个我们在前面接口中,讲向页面里添加 CSS 的时候提到过。简单的讲就是用反引号(主键盘数字 1 左侧的按键)来替代引号,这样在字符串之中可以放引号和换行,就更方便在其中写入,比如 CSS 或者 html 的代码。
函数
()=>{}
对于初学者,又希望快速使用,那么可以先用箭头函数,因为写起来方便一点,不过在很老旧的浏览器里是不被支持的,如果你真遇到这种情况,请把浏览器换掉。
前面小括号里用来写参与这个函数执行的一些数据,后面大括号里是对这些数据进行的一些操作。函数其实将一些固定操作打包成一个整体,方便使用,
如果没有数据需要传入函数之中,那么就写一个空的小括号;如果只有一个数据需要传入函数之中,可以只写这个数据的名称而省略小括号;如果有多个数据要传入函数之中,那么小括号必须存在,并且中间用逗号分隔。
现在来定义一个做加法的函数:
const add = (a, b)=>{
const result = a+b
console.log(result)
}
我们定义了一个函数,然后用 add
来指代这个函数。现在仅仅是完成了定义,我们设计了这样一套操作,然后这套操作应该怎样去做。对这个事情都说清楚了,但并没有实际的进行操作。
下面来执行这个函数:
add(1, 2)
我要使用叫做 add
的这个函数,后面的小括号里放入这个函数需要的数据,然后程序就会完成我们刚才设定好的那一套操作,它会把两个数字相加,然后输出出来。
函数本身是一个比较复杂的概念,这里演示的十分简陋,只能是有一个大约的概念。有时候我们使用函数是为了将这些代码打包成一个整体,有时候是为了将这套操作方便的放入其他位置,而不至于因为代码太多,导致混乱。
网页
标题
document.title = '这个页面的标题已经被帅气的小老鼠占领了'
这是 document
对象下面的一个属性,表示的是我们页面的标题,就是显示在浏览器标签上的那行文字。我们可以像上面那样对它进行赋值,就修改了它的内容,也可以像下面这样将它的内容赋值给其他的变量,来获取它当前的值。
const title = document.title
console.log(title)
身体
document.body
这个一般不会单独使用,但是要注意页面中的所有元素基本都在这个对象之下,对应的是网页 <body>
这个元素对象。
同理可得,用 document.head
可以获取网页的头部信息。
网址
window.location
这个对象中包含当前网页网址的相关信息。大部分时间我们会这样用来获取完整的网址:
window.location.href
有了完整网址以后,只要稍加分析就可以获得网址相关的各种信息。不过其实上面的网址对象已经帮我们完成了初步的分析操作,大家可以将它输出出来,然后查看一下。比较常用的属性如下:
- href: 完整的网址
- hostname: 网站的域名
- pathname: 域名后面的路径部分
- search: 网址中问号及其后面的部分
- hash: 网址中井号及其后面的部分
选择一个元素
document.body.querySelector('a')
这样我们就获取了页面中第 1 个链接元素,小括号中的这个字符串用来书写对应元素的选择器。这个方法只获得第 1 个符合这个选择器的元素,所以它获得的就是一个元素对象。
选择多个元素
document.body.querySelectorAll('a')
这样获取的就是页面中所有的链接元素,和上面代码对比多了一个 All
,这就很合理。因为是多个元素,所以这个方法获得的是所有符合这个选择器元素所组成的数组。一般情况下这个方法后面都会连接 forEach
(后面会讲)。
其实对于元素的选择还有很多方法,但对于新手快速掌握上面这两种是最方便的,因为直接使用选择器学习成本相对要低一点,通用性又很高。
元素遍历
document.body.querySelectorAll('a').forEach(e=>{ })
选择多个元素之后在后面直接接 forEach
对这些元素进行遍历,就是把这些元素都过一遍,对每一个元素执行相同的操作。后面的小括号里就是要进行的操作。
小括号里的内容是一个箭头函数,这里的 e
是我假定的一个值,用它来代表当前正在被操作的那个元素,就是现在轮到哪个元素它就代表哪个元素。就好像小学生排队打预防针,当然要一个一个进行,不过对每一个同学做的操作都是一样的。而医生和学生说话的时候,都是叫同学,但我们明白,他嘴里的同学是用来代表当前正在打针的那个同学。这样类比着去理解就容易一些。
然后大括号里再书写对元素的具体操作。很容易看出,这里是一个函数的定义。也就是程序会对每一个单独的元素去执行一下这里的函数。
元素操作
对于元素的大部分属性都可以用 元素.属性名
的形式调用,用起来和上面设置网页标题的操作一样。
document.body.querySelectorAll('a').href = 'https://dmnydn.com/'
我选择了一个链接元素,然后它的 href
属性被我重新赋值。这样就修改了这个链接的链接地址。其他属性也都类似,当然你只能给属性去赋予它能够接受的值,否则一般情况是不起作用。至于都有哪些属性,大家可以通过开发者工具去观察网页中的元素,不同元素可以拥有的属性也不太一样。
注意有一些特殊属性是没有办法按照上面方法进行操作的。
元素的内容
document.body.querySelectorAll('a').innerText = '小老鼠他超帅的'
这样我们就修改了这个链接显示的文字。如果一个元素中可以包含文字内容,那么就可以用这个方法进行设置。或者用它来获取这个元素中的文字内容。
元素的代码
但是上面的方法有一些问题,获取的时候,我们彻底忽略了元素中的其他代码。设置的时候这个元素里面就只能拥有文字了。那如果想让在这个元素里放入其他代码,可以用下面的方法:
document.body.querySelectorAll('a').innerHTML = '小老鼠他超帅的(<strong>确信</strong>'
追加元素
有时候我们需要向页面中插入某个元素,当然可以像上面那样去修改元素的代码,把我们的元素直接书写进去。但这样导致整个元素的代码都被重写了,原有的内容会被覆盖。
我们先获取原有的代码,然后通过修改来加入我们自己的元素。这看起来是可行的,但实际会有副作用。留下一句解释,新人可以忽略掉,因为很可能看不懂。这样的操作会导致这些代码中的元素全被更新,而绑定在它们上面的事件可能就丢失了。
正确的方法是像下面这样操作,比如我要在 <body>
元素中追加一个链接:
const newLink = document.createElement('a')
newLink.href = 'https://dmnydn.com/'
newLink.innerText = '可可爱爱'
document.body.appendChild(newLink)
第 1 行先创建了一个元素,它的标签是 a
,下面两行用来设定这个元素的一些属性,最后将这个元素追加到 body
对象之中。
点击元素
document.body.querySelectorAll('a').click()
这样就对页面中的第 1 个链接元素进行了一个点击操作,这个方法可以用来自动化的完成一些事情。
数据
日期
时间日期是我最喜欢鼓捣的数据之一,因为用法简单,又十分实用,特别适合给新手用作练习。
那么先来一个时间:
const now = new Date()
我们说需要一个全新的时间对象,但是没有给出更多的要求(小括号里面是空的),于是它就返回了当前时间。所以这里我把这个时间对象放在了 now
之中。
它是一个对象,但是你可以把它直接输出出来,会显示成为一段文字,类似这样:Wed Jul 22 2020 16:14:59 GMT+0800 (中国标准时间)
。
我们也可以把它转换为数字,这时候代表的是时间戳,就表示这个时间相对于某个特定时间相差的毫秒数。也可以从中获取这个时间对象中的一些具体数据,比如下面这样:
const year = now.getFullYear()
这样就可以获取其中的年份信息,其他方法和详细解释请查阅文档。
类型转换
有时候我们需要对数据类型进行转换,比较常见的就是数字和字符串之间的转换。以下讲解不考虑无法转换而出错的情况。
const a = '123'
const b = +a
const c = Number(a)
这里的 a
是一个字符串,它的内容是一串数字。如果在它前面加一个正号,相当于进行了一次数学运算,所以程序会自动将它转换为数字之后再进行运算,这样得出的结果就是一个数字。而因为前面加正号,这个运算并不会对数字本身产生修改,所以就起到了类型转换的作用。
第二种写法(Number(a)
),则是语义上十分明确,虽然长了一点,但还是很推荐使用。
const a = 666
const b = ''+a
const c = String(a)
两种方法的思路和解释都和上面类似.
操作
延时
window.setTimeout(()=>{
// 一些操作
}, 2000)
小括号里是两个参数,第 1 个参数是一个函数,用来放我们希望进行的操作;第 2 个参数是一个时间,单位是毫秒。这样就会在经过这个时间之后再执行前面的函数。
window.setInterval(()=>{
// 一些操作
}, 2000)
这段代码和上边非常相似,但它表示的是每经过这个固定的时间间隔就执行一次这个函数。
精通?!
精通什么的,大概只会存在于毕业生的简历之中。
随着年龄的增长,越来越不敢去讲精通,甚至连自己是否会这项技能都有所怀疑。
而且用户脚本是会遇到许多限制的,这些限制是日常书写网页不太容易遇到的,所以仅仅是擅长写网页,并不一定擅长解决这些问题。
JavaScript 的整个知识体系也是巨大的,一般开发者很难做到全都了解。所以难免遇到许多知识上的盲区。而在知识的基础之上,还有解决问题的灵活性。如果是与人下棋,我们可以设法让局面走向自己熟悉的状况;但如果是解开残局,就是把自己扔到各种可能之中了。
如何进一步进行学习,我已经在前面讲了好多次。反正这种东西就是一个工具,学着玩儿呗。
如果你对前端有兴趣可以找小老鼠学习交流。
那么都已经读到这里了,想必你已经付费了吧?!如果没有,记得补个票哦~
在后面我会不定期的放上来一些简短的小脚本,供大家研究,也向大家证明,几行代码也能够去完成一些有用的事情。
发布脚本
去 https://greasyfork.org/zh-CN 注册个账号,然后填表发布就好,限制不高。
或者简单的把自己的脚本放在网上,供人下载,就更没限制了。(但需要有自己的空间。
或者,脚本存在 Github 里面,然后让 greasyfork 自动同步,我喜欢这种方法,它可以绕过某些限制。而且很方便啊~
原来喵喵喵喵是这个意思啊.
其实比较希望能看到一个完整的可以使用的脚本的编写流程,函数、语句、关键字很容易就能找到相关的介绍,迷糊的是到底该怎么开始.
原本打算先做完文字校对再放示例的。
但是几万字嗷,自己懒得逐字检查,却发现中文的拼写检查工具没几个好用的,正在乱翻……
你等一下,我这就先上一个例子。
期待中
示例一:获取 B 站视频封面
需求分析:首先在视频列表页复制封面的图片地址,然后进入这个视频的页面,在源码中去搜索这个地址的关键词(就是文件名)。这个操作本身是碰运气,然后发现他把封面图片的地址清晰的写在了网页的头信息中。
<meta data-vue-meta="true" itemprop="image" content="http://i1.hdslb.com/bfs/archive/8ec1b95254e0e67cacad1191e918e66e06fabcbf.jpg">
这个问题就变得非常简单了,只需要获取网页中特定元素的特定属性即可。
那么下一个问题就是获取到的这个地址,如何输出出来。我选择的方法是直接将当前页面的网址修改为这个图片地址,因为这样最简单,而且图片直接显示出来了,如果是在手机上长按图片就可以进行分享。
接下来的问题是如何触发。如果这个功能在网页打开之后就立刻执行,得到的结果是打开一个视频页面,结果显示出来的是它的封面图。(那我以后还怎么摸鱼呀!!!
所以我选择给这个脚本添加了一个菜单,当点击这个菜单的时候才执行这个动作。
好了,问题分析完了,下面来看代码比解释少系列:
// ==UserScript==
// @name 获取 B 站视频封面
// @namespace Get bilibili video cover
// @match *://www.bilibili.com/video/*
// @match *://m.bilibili.com/video/*
// @grant GM_registerMenuCommand
// @version 1.0
// @author -
// @description 2020/7/15 上午7:27:34
// ==/UserScript==
GM_registerMenuCommand('获取此视频封面', ()=>{
window.location.href = document.querySelector('meta[itemprop="image"]').getAttribute('content')
})
代码中相比我讲到的知识有两处超纲:
- 这个选择器是稍微复杂一点的属性选择器
- 这个元素的
content
属性似乎无法直接获得,于是用了getAttribute
方法
但在操作思路上没有超纲的地方
示例二:论坛高亮楼主头像
有时候楼层比较高,看着看着就忘记了谁是楼主。本论坛看过的帖子再点进去或直接从你上次阅读的地方开始,就更容易遇到,看回复时不知道楼主是谁的尴尬。这会很影响我对回复内容的理解。所以就做一下高亮,让我能够识别出来。
本来也想像 V2EX 家插件那样直接对楼层进行高亮,但试了试,在本论坛的显示效果不好。那就高亮头像吧。然后我也记不清是从哪里复制了一下样式,反正挺简单的两行 CSS。
那怎么确定谁是楼主呢?这个问题我也挺犯愁的,因为如果直接打开是最先回复的位置,那么有可能顶楼并没有被加载出来,这就不太容易判断。但我觉得程序不会把这么重要的信息直接忽略掉,于是在代码中观察了一下,发现凡是楼主所在的楼层都会有这样的类 topic-owner
。那问题就很简单了,就是给对应的元素添加上样式就好。
看下面代码,和我们讲添加样式的接口时所举的例子已经十分一致了。
// ==UserScript==
// @name 论坛细节优化
// @namespace Appinn Forum Details Optimization
// @match *://meta.appinn.net/*
// @grant GM_addStyle
// @version 1.0
// @author -
// @description 2020/7/13 下午2:05:15
// ==/UserScript==
GM_addStyle(`
.topic-owner .topic-avatar img {
box-shadow: 0 0 3px 1px #81c3e4;
border: 2px solid #85c2e0;
}
`)
示例三:链接地址洗白白(简化)
地址简化,主要就是这样几种情况:
- 地址中的 get 参数可以清理,比如淘宝的搜索结果,只保留查询的关键词即可
- 地址中的路径可以被出新组合,比如本论坛的帖子地址
对于第 2 种,可以利用正则替换来修改网址,简单理解就是查找替换。对于第 1 种,因为查询的参数前后顺序是不固定的,用正则难以处理这种顺序不固定的情况,所以先提取出查询参数,然后重组成自己需要的格式。
针对已知的网站设定规则,然后根据当前网站使用对应的规则进行处理。为了适配更多的网站,所以设置了一些常用的查询参数,对于为准确是配的网站,尝试只保留这些查询参数。
// ==UserScript==
// @name 链接地址洗白白(精简示例版)
// @namespace Daomouse Link Cleaner
// @version 0.0.1
// @author 稻米鼠
// @description 把链接地址缩减至最短可用状态
// @match *://*/*
// @grant GM_setClipboard
// @grant GM_registerMenuCommand
// @noframes
// ==/UserScript==
const cleanIt = ()=>{
const url=window.location.href
const hash = url.replace(/^[^#]*(#.*)?$/, '$1')
const base = url.replace(/(\?|#).*$/, '')
let pureUrl = url
const getQueryString = function(key) {
let ret = url.match(new RegExp('(?:\\?|&)(' + key + '=[^?#&]*)', 'i'))
return ret === null ? '' : ret[1]
}
const pureUrl = base + '?q=' + getQueryString('q')
GM_setClipboard(pureUrl)
}
GM_registerMenuCommand('链接净化', cleanIt)
大概就是这个样子,上面代码实现了一下对淘宝搜索结果链接的净化。其中正则表达式应用的很多,但是前面我们没有讲。
总之就是注册一个菜单,当我们点击这个菜单的时候,运行上面已经写好的函数。函数里面对当前网址一通操作,然后把修改完的地址放入剪切板。
只要你会使用正则,并且对网址的结构有基础的了解,做这样一个脚本并不困难。真正麻烦的是对规则的收集和维护。
不过这种方法并不能够解决所有网址净化的问题,不过那不在我们这次的讨论范围之内了。
示例四:自动展开全文(简化)
这个更简单,就做两件事情,让那个按钮不显示,然后去掉对内容部分的限制。
这两个问题都可以通过 CSS 解决,所以事实上就是对页面添加几句 CSS。麻烦的就是对每一个网站的适配确定这个网站中哪个元素是按钮,哪个元素是文章内容。
这个我就不放代码了,因为精简到只针对一个网站的话,和前面我们写的为页面加入 CSS 是一样的。
针对多个网站,就是遍历已有的规则,验证网址是否符合,如果符合就按照这个规则中记录的元素去设定相应的 CSS。
(代码不复杂,维护规则就……
示例五:网易云音乐歌词放大
没啥解释的,就是把当前(那一句)歌词的字号设置大一点,方便看歌词。
// ==UserScript==
// @name 网易云音乐歌词放大
// @namespace Violentmonkey Scripts
// @match https://music.163.com/*
// @grant GM_addStyle
// @version 1.0
// @author -
// @description 2020/5/24 下午6:18:58
// ==/UserScript==
GM_addStyle(`
.m-playbar .listlyric p.z-sel {
font-size: 22px;
}
`)
这个例子就是告诉大家用已有的知识去解决小问题是可行的。我也确实在用这个脚本。
如果愿意折腾,把歌词弄到网页标题里也可以,这样切到别的页面也能看歌词。不知道如果动用浏览器的 Picture in picture 功能的话,能否实现桌面浮动歌词。我自己需求不大,就懒得折腾。
示例六:假装水墨屏
代码看这里吧: https://greasyfork.org/zh-CN/scripts/407641/code
原理很简单,为页面插入一段 CSS,其中有四段内容:
- 页面滤镜,让网页变成黑白色调,在各种纪念日,网站变黑白用的就是这个方法。我这里增加了一层亮度调节,这样可以降低深色元素的浓度,毕竟水墨屏的纯黑也不是特别黑,而手机屏现在可以显示纯黑的,这差别很大。
- 定义一个类,具有浅灰色背景,来模拟水墨屏本身的灰色(随着发展,现在的水墨屏越来越白了,但依旧不算纯白,加上手机电脑的屏幕本身比较亮,要灰一点才像
- 定义一个类,具有深灰色文字,页面中深灰色的文字,全都换成这个值,让显示效果更好控制。
- 让所有元素的文字具有阴影,借此模拟水墨屏文字发虚(现在高 PPI 设备基本不虚了),和屏幕残影造成的效果。
然后做了两个判断:
- 每一个背景色为浅色的元素,设置为上面的浅灰色背景
- 每一个文字颜色为深色的元素,调整我设定的那个颜色
其实直接把前面样式全局应用就可以兼容大多数小说站了,加入判断是为了更加严谨。
高级技法:
瀑布流和延迟加载的页面效果会很差。因为我们上面讲的这些,只是在页面载入后执行一次,后面新增加的元素并不会受到影响,背景和文字的颜色就不会被优化到。
所以加入和页面变化监控,当有元素变化时,对元素进行上述两个判断,按需要修改。这就解决问题了。
但是下一个问题又来了:当新元素需要修改时,我们修改了。这个元素不也发生变化了么,那监控元素变化的部分就会再通知我们这个元素变化了……然后陷入死循环(可能)。所以,当我们对元素进行修改时,要先停止对元素变化的监控,修改后再重新开启监控。
这部分比较比较复杂,不推荐新手现在啃。就是先了解些思路。这里算是展示一个看起来很简单,但其实挺复杂的脚本。
为了尽可能提高自己脚本的兼容性,所以十分诚心诚意的去尝试手机上那些号称支持脚本的浏览器。
以前我对他们的认知只限于他们介绍中的说明,并未实际使用过。现在体验下来大跌眼镜,我是真的用来。
如果支持从市场中直接安装脚本的体验,还算好一丢丢,但是可能浏览器自建的市场中收入的脚本非常少,并且收入之后不再及时更新。如果自己新建复制粘贴进去,因为好多脚本的代码比较长,在手机上的操作体验是非常差的,甚至多次操作才能成功。
然后自己新建的话一般匹配网址部分需要自己单独额外输入,如果匹配多个网址,在手机上的输入体验,依然让人抓狂。
既然在输入格式上都不能脚好的去适配油猴规范,就很难指望他们能够提供对应的接口。前面我们说过这些接口是给开发者带来便利的,所以在有需要的情况下,开发者肯定会优先选择使用这些接口。这就导致这些浏览器对脚本的兼容性必然很差。
感觉他们对脚本的支持,都处于小书签的水平。都怀疑是用 window.location.href = 脚本代码
来实现的。这种情况下,想对它们进行兼容十分困难。
可以的,谢谢