就是加不加 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 = 脚本代码
来实现的。这种情况下,想对它们进行兼容十分困难。
可以的,谢谢
原来可以用 window.location.href = 脚本代码 这种方式自动调用小书签啊
学到了
好多知识冷的叫人发抖。真的可能掌握以后直到这技术淘汰都用不上。
但是玩油猴子这种,就免不了时常需要一些冷知识,也挺有趣的。
示例七:加入统计代码
建议先阅读:【老鼠讲故事系列 004】油猴子加入统计代码
第一步:判断用户设置
如果用户拒绝追踪,那就不要插入统计代码。
if (!navigator.doNotTrack) {
// 这里是后续的代码
}
如果 navigator.doNotTrack
结果为 1,则禁止跟踪。0 为允许,未设置为 null
。
第二步:插入一个框架
这是因为我们可以对框架进行更好的操作,比如修改网址和标题,而不会影响到用户对页面浏览的体验。而这些修改可以让统计携带回更多有效的数据。
这并不是打算做什么坏事,是因为我们要跟踪的是脚本的使用情况,而脚本相关的一些数据并不能够通过普通的统计来获得。
框架中的页面最好是一个十分分简单,并且访问速度非常快的页面,比较好的选择就是一些网站的 404 页面,我使用了,提供统计服务的网站的某个页面。这样如果这个页面都无法访问,那么统计代码大概率也无法生效。
const href = 'https://c.statcounter.com/' + window.location.hostname + '/';
const iframe = document.createElement('iframe');
iframe.src = href;
iframe.style = 'display: none !important; width: 0; height: 0;';
document.body.appendChild(iframe);
就是很简单的在页面中追加一个元素,在加入之前对元素的一些属性进行必要的设置。这里我将当前页面的域名部分附加在了这个页面网址之后,我们可以以类似的方式附加一些有效信息。当然要确保最后这个网址所获得的页面符合我们的预期。
记得设置这个框架隐藏起来,不要显示。
第三步:对这个页面进行修改
首先我们要把上一个页面放入脚本的匹配( @match )之中。然后我们的脚本要可以在框架下运行。
如果发现当前页面的网址和我们预设的这个页面相匹配。就去修改这个页面的标题,让它携带更多信息,比如我让他携带了脚本管理器的名称、版本、当前脚本的版本号等。
if (/^https:\/\/c\.statcounter\.com\//i.test(window.location.href)) {
// 在标题中放入脚本相关信息
document.title = GM_info.scriptHandler+
'-' + GM_info.version +
'-' + GM_info.script.version
}
第四步:加入统计代码
这里要注意,不能直接通过修改 html 代码来插入,因为可能导致脚本没有正确的运行,所以还是要像上面那样,通过追加元素的方式进行插入,但在这之前可以先简单清空一下 body
元素中的内容。下面代码中项目编号和加密的识别码,我都用变量替代了。
document.body.innerHTML = '';
const scriptA = document.createElement('script');
scriptA.type = 'text/javascript';
scriptA.innerHTML =
`var sc_project=` + sc_project + `;
var sc_invisible=1;
var sc_security="` + sc_security + `";
var sc_https=1;
var sc_remove_link=1;
`;
document.body.append(scriptA);
第五步:优化兼容性
一般统计代码在脚本无法正确运行的时候,会采取备用方式,就是插入一张图片。这里我们准备好这张图片,当上面的代码没有能够正确注入时,就插入这一张图片。同样的,代码中项目编号和加密的识别码,我都用变量替代了。
scriptB.type = 'text/javascript';
scriptB.src = 'https://www.statcounter.com/counter/counter.js';
document.body.append(scriptB);
scriptB.onerror = () => {
const img = document.createElement('img');
img.src =
'https://c.statcounter.com/' +
sc_project +
'/0/' +
sc_security +
'/1/#' +
Number(new Date());
img.style = 'display: none';
document.body.append(img);
第六步:降低干扰
这个干扰是双方向的,一方面在页面打开时就插入统计代码,虽然影响本身是很小的,但终归会让页面打开的速度稍微慢一丢丢。如果等页面彻底打开之后,再去加入统计代码,这样对用户的影响会更小。
同时如果用户还没有等这个页面彻底打开,就将页面关闭了,那么对这个页面进行统计也没有什么必要。所以我将插入框架的时间进行了一个延迟。
第七步:控制次数
但是我只是希望了解用户使用的脚本管理器和一些版本号的情况。就没有必要用户访问,每一个页面都进行统计。所以我设置了每天只统计一次。但这一点需要脚本管理器提供数据存储的接口才能够实现。
首先我获取当前的时间,然后除以一天的好秒数,将结果取整数作为一个标记。
然后查看脚本存取的数据里的标记和这个标记是否相同,如果相同那么意味着今天已经做过统计了,就无需再插入统计代码。如果不同,那么插入统计代码,同时更新储存的这个标记为当前标记。
以上便是在脚本中放入统计代码的思路分析,都不复杂,但是这些细节我也确实思考了好久,尽自己的可能去降低对用户的影响。
下面放出相对完整的代码:
// 如果用户拒绝追踪,则不添加统计代码
if (!navigator.doNotTrack) {
// 如果是预定页面,则进行改造
if (/^https:\/\/c\.statcounter\.com\//i.test(window.location.href)) {
// 在标题中放入脚本相关信息
document.title = GM_info
? GM_info.scriptHandler.replace(/monkey/i, '') +
'-' +
GM_info.version +
'-' +
GM_info.script.version
: navigator.userAgent + '-' + scriptVersion;
// 插入统计代码
document.body.innerHTML = '';
const scriptA = document.createElement('script');
scriptA.type = 'text/javascript';
scriptA.innerHTML =
`var sc_project=` + sc_project + `;
var sc_invisible=1;
var sc_security="` + sc_security + `";
var sc_https=1;
var sc_remove_link=1;
`;
document.body.append(scriptA);
const scriptB = document.createElement('script');
scriptB.type = 'text/javascript';
scriptB.src = 'https://www.statcounter.com/counter/counter.js';
document.body.append(scriptB);
scriptB.onerror = () => {
const img = document.createElement('img');
img.src =
'https://c.statcounter.com/' +
sc_project +
'/0/' +
sc_security +
'/1/#' +
Number(new Date());
img.style = 'display: none';
document.body.append(img);
};
} else {
// 非预定页面则插入统计代码
setTimeout(() => {
const todayMark = Math.floor(+new Date() / 864e5); // 获取今日时间标记
const recordMark = GM_getValue('timeMark', 0); // 获取记录中时间标记
if (todayMark !== recordMark) {
// 时间标记不同则插入统计代码
const href =
'https://c.statcounter.com/' + window.location.hostname + '/';
const iframe = document.createElement('iframe');
iframe.src = href;
iframe.style = 'display: none !important; width: 0; height: 0;';
document.body.appendChild(iframe);
GM_setValue('timeMark', todayMark); // 将新的时间标记存入记录
}
}, delay);
}
}