阅读视图

发现新文章,点击刷新页面。

魔改笔记四:友链页重构及友链朋友圈适配

前天晚上,店长更新了方舟系列的友链卡片,作为店长铁粉,当然要第一时间买店长的火柴啦,于是我将店长所有的友链卡片稍微整合了一下,结合安知鱼的教程,最终实现了“五世同堂”,五种友链可以混合搭配,于是在此分享给大家!

谈谈闭包

✇BeaCox
作者 BeaCox

在学习JS的过程中,我遇到了闭包这个概念,当时并没有在意。直到最近我开始自学python,在廖雪峰老师的python教程中又一次看到了这个名词,我才意识到闭包其实是一个重要的概念,或者说特性,许多高级语言支持闭包(比如近些年比较火的Go语言)。于是我查看了相关文档、教程,打算谈谈我对闭包的一些认识。

闭包的定义

闭包有许多不同的定义,个人认为最简洁而达意的是MDN对于闭包的定义:

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。

词法环境

维基百科这样描述闭包中的词法环境:

环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。

简单来说,词法环境包含两部分:

  • 环境记录:存储符号-值对
  • 对外部环境的引用:对父级词法环境的引用。

也就是说,一个函数的词法环境包含了在函数中的符号定义和函数外部的词法环境。考虑如下python代码:

1
2
3
4
5
6
7
def init():
name = "BeaCox"
def displayName():
greeting = "Hello"
print(greeting+', '+name)
displayName()
init()

displayName函数的词法环境包含了环境记录(greeting的符号-值对)以及对外部环境的引用(namedisplayName的符号-值对),这也就是在displayName函数中可以访问name变量的原因。执行displayName函数,其实就是创建了一个闭包。

使用闭包

看完上面的例子,好像有点迷糊了:这不就是“内层作用域可以访问外层作用域的变量”吗?C++不支持闭包,不也能完成上面的工作吗?这是因为上面的例子并没有展示出闭包函数与词法环境捆绑的特性。将上面的代码稍加改动:

1
2
3
4
5
6
7
8
def init():
name = "BeaCox"
def displayName():
greeting = "Hello"
print(greeting+', '+name)
return displayName
outsideDisplay=init()
outsideDisplay()

这段代码与上面不同的地方在于,displayName函数并不在init函数中执行,而是作为返回值,在init函数外部,有一个outsideDisplay接收了这个返回值。
如果我们从C++的思想来考虑这段代码,会发现:在init函数执行完后,局部变量name已经被回收,这时候outsideDisplayname变量是没有被定义的,这段代码应该不能正常运行。
然而,我们运行这段python程序后会发现,终端正常输出Hello, BeaCox,这就是闭包的魔力!

这段程序之所以正常运行的原因,就是python中返回函数会形成闭包。闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。在本例子中,outsideDisplay 是执行 init 时创建的 displayName 函数实例的引用。displayName 函数和其捆绑的词法环境(变量 name 存在于其中)的引用形成了一个闭包,因此init函数执行完毕后,该词法环境没有消失,变量name也没有被回收。因此,当 outsideDisplay 被调用时,变量 name 仍然可用,程序能够正确运行。

闭包的用途

模拟公有成员函数对私有变量的操作

读完上述代码不难发现,outsideDisplay函数在init函数外部调用,但却访问到了init函数内部的变量。这与C++中,调用类的公有成员函数来操作类的私有变量非常相似。与C++不同,python不存在严格意义上的私有变量,python通过以双下划线为开头来命名变量的方式,实现的是一种伪私有变量,它不应该被从外部访问,而不是不能被从外部访问。python、JavaScript等不支持严格私有变量的语言可以通过创建闭包来模拟公有成员函数对私有变量的操作

创建一个生命周期极长的局部变量

观察上述例子,outsideDisplay函数可以继续重复运行,直到整个程序终止。也就是说name变量直到程序运行结束之前,都一直存在于内存中。听起来貌似很像全局变量,但这个变量却是一个局部变量。仅这个程序而言,这个变量只能被outsideDisplay函数和init函数访问。

闭包可能导致的问题

内存泄漏

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

上文提到,闭包可以创建一个生命周期极长(直到程序运行结束前始终留存在内存中)的变量,如果这样的变量过多,就会导致程序运行速度减慢甚至系统崩溃。

在循环中创建闭包导致意料之外的错误

廖雪峰老师的python教程中给出了一个这样的例子:

1
2
3
4
5
6
7
8
9
def count():
fs = []
for i in range(1, 4)://i从13
def f():
return i*i
fs.append(f)
return fs

f1, f2, f3 = count()

这段程序的期望目标是f1, f2, f3分别返回1,4,9。实际返回9, 9, 9。这是因为每个f()函数捆绑外部词法环境中的i是对i的引用,在return fs之前,i已经变成3了,因此每个f()函数返回的都是3*3

因此,要尽量避免在循环中创建闭包。如若必需,务必要谨慎!

基于GitHub Actions的看雪论坛自动签到,可选推送与否

✇BeaCox
作者 BeaCox

看雪论坛称得上是国内较好的安全论坛了。不过要1k雪币(论坛虚拟币,新用户几乎都可以获得220及以上)才可以升级为正式会员。临时会员有诸多限制,包括不能查看『WEB安全』版块等。对于我这种想白嫖的安全小白来说,唯一的方法就是每天签到随机获得1-10枚雪币。但是我经常会忘记签到,这等到猴年马月?
正好我最近正在学习JS,于是写了一个自动签到的脚本。当然,除了升级正式会员,雪币还有许多用处,所以对已经是正式会员的用户来说也还算有些用罢。

先上传送门:

实现方法

这个脚本的实现非常简单。

  1. 通过抓包可以发现,看雪论坛的签到是通过向https://bbs.pediy.com/user-signin.htm页面发送含Cookie的POST请求来实现的(也是绝大多数签到业务的设计逻辑),因此利用Axios库的API来向该页面发送请求,模拟用户签到。
  2. 签到完成后,将响应的数据赋值给一个对象,通过response.data.coderesponse.data.message来判断网络正常情况下,签到任务的三种可能情况。
    • code == 0 && message = <签到获得雪币数> : 表示签到成功。推送消息显示`签到成功,获得${msg}雪币`。
    • code == -1 && message == '您今日已签到成功' : 表示已经签到过,此处为重复签到。推送消息显示’您今日已签到成功’。
    • code == -1 && message == '请先登录': 表示Cookie验证失败。打印错误并不推送消息
  3. 推送消息的功能利用pushplus提供的接口实现,因为比Server酱免费版限制少一些,当然后续可能会添加server酱等其他选项。同样是利用了Axios的库来向接口发送请求。可以参考pushplus文档中心
  4. 利用GitHub Actions,在GitHub提供的主机上用node运行js,通过crontab完成定时任务。
  5. GitHub Actions在仓库60天以上没有任何活动时会被suspended(推迟),因此利用Keepalive Workflow来使工作流按期运行。

后续

希望各位能帮我点一个star✨(理直气壮)
由于这是我第一个js脚本,程序健壮性想必不甚好,欢迎大家提出issue和pr!

❌