在 Minecraft 中漫步北大校园


物理学院

五四体育场
2019 年 8 月,Hexo 的 NexT 主题正式加入 PJAX 功能。最初的 PR 一共包含 35 个 commit,约 600 行代码改动。不过这个数字有些夸张,其中约有 200 行是在适配 PJAX 过程中,发现一些插件对于 PJAX 不友好,顺手修改了。此后根据收到的用户反馈,又用了不下十个 PR,修复了 PJAX 中全部已知问题。
由于在同一时期,NexT 已有弃用 jQuery 的计划,因此没有采用广泛使用的 jquery-pjax,而是用了一个较为小众的库:moOx/pjax,并且根据需求修改了一些功能。
适配 PJAX 的要点总结来说有以下几条。
对于一个网站中的全部网页,往往具有一些在每个页面中相同的部分,以及在每个页面中不同的部分。相同部分可能包括<head>中加载的<script>,以及导航栏、页脚等组件。不同部分则是页面的正文部分等。PJAX 的刷新区域应当只包含这些不同的部分。因此,将页面中的内容进行分类非常重要。对于使用各种模版引擎生成的网站,这一操作往往并不复杂,并且还有些额外的好处,例如可以对相同的内容进行缓存,加快网站的生成时间。
一般 PJAX 会根据设定的选择器来确定刷新页面中的哪些区域。这时需要确保选择器的唯一性。例如,如果文章内容由 Markdown 渲染生成,那么可能会在各个 Heading 处产生带有id的元素。需要避免它们与 PJAX 刷新区域的id冲突,那么可以考虑使用class进行选择。
另一个问题是 PJAX 刷新过程中,页面状态的一致性。NexT 主题在适配 PJAX 时就遇到了这个问题。在侧边栏中有两个区域:文章目录和站点概览。与之对应的是两个<button>,点击一个<button>便会显示其对应的区域,隐藏另一个;这一过程由className控制。但有一些页面,例如首页,是没有文章目录的。如果从一个文章页面通过 PJAX 刷新回到首页,就可能导致侧边栏中的两个区域都被隐藏,看上去没有内容,并且<button>的状态与侧栏中两个区域的状态不符合。解决方案有两种:
className移动到刷新区域外,例如设置为<body>的className。第二种方案可以减少 DOM reflow,保证 PJAX 刷新时渲染一步到位。
一些常见的 PJAX 插件都自带了重新加载脚本的功能,但为了精确地控制,这一部分可以考虑自行实现。
页面中的脚本大致可以分为三类:
对于第一类脚本,将其放在 PJAX 刷新区域之外即可,不需要进行任何其它的处理。这里额外提一下addEventListener的问题。一般的 PJAX 插件会给出 PJAX 刷新完成时的事件,需要正确的使用。而DOMContentLoaded和load事件都只会触发一次。
此外,应当避免重复注册相同的addEventListener。如果每个新页面都使用匿名的回调函数注册一个相同的事件,最后一个事件将触发所有的回调函数,场面一定非常壮观。要避免这个问题,可以:
function进行声明,然后将其作为addEventListener的参数,这可以保证其只触发一次;removeEventListener。对于第二类脚本,需要在 PJAX 刷新完成后,重新执行。最简单的方法是复制这个脚本的属性和内容,然后将其移除,再通过replaceChild方法将复制插入到原来的位置。
为了使 PJAX 区分第一类和第二类脚本,避免错误地加载,NexT 使用的方法是为全部第二类脚本加上data-pjax属性。这样,重新加载脚本的代码如下所示1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const elements = document.querySelectorAll('script[data-pjax]');
elements.forEach(function(element) {
const code = element.text || element.textContent || element.innerHTML || '';
const script = document.createElement('script');
if (element.id) {
script.id = element.id;
}
if (element.className) {
script.className = element.className;
}
if (element.type) {
script.type = element.type;
}
if (element.src) {
script.src = element.src;
// Force synchronous loading of peripheral JS.
script.async = false;
}
if (element.dataset.pjax !== undefined) {
script.dataset.pjax = '';
}
if (code !== '') {
script.appendChild(document.createTextNode(code));
}
element.parentNode.replaceChild(script, element);
});
复制type是为了避免执行非 JS 脚本,例如 MathJax 的配置。复制dataset.pjax则是为了确保在多次的 PJAX 刷新中,script[data-pjax]选择器始终有效。
比较麻烦的是第三类脚本。对于那些可以重复进行初始化的插件而言,其「本体」只需要加载一次,此后的 PJAX 刷新过程只需要再次进行初始化。典型的例子是 Valine 和 MathJax。对于 Valine 而言,每个新页面中,重新执行new Valine()即可。而对于 MathJax,初始化也是类似的。无论如何,最佳的实现应当保证:
1 | MathJax.startup.document.state(0); |
当然,你也可以通过其它方式实现同样的效果。如果要省事的话,可以将第三类脚本按照第二类的方式进行处理。唯一的不同是需要将其放置在 PJAX 刷新区域之内,否则可能造成额外的网络请求和资源消耗。
总而言之,PJAX 适配过程中最大的难点是重新加载脚本的问题。想要做到完美而没有任何疏忽和遗漏无疑是困难的。这依赖于大量的测试来检验系统的鲁棒性。
Proof by intimidation Trivial!
Proof by cumbersome notation The theorem follows immediately from the fact that
Proof by inaccessible literature The theorem is an easy corollary of a result proven in a hand-written note handed out during a lecture by the Yugoslavian Mathematical Society in 1973.
Proof by ghost reference The proof my be found on page 478 in a textbook which turns out to have 396 pages.
Circular argument Proposition 5.18 in [BL] is an easy corollary of Theorem 7.18 in [C], which is again based on Corollary 2.14 in [K]. This, on the other hand, is derived with reference to Proposition 5.18 in [BL].
Proof by authority My good colleague Andrew said he thought he might have come up with a proof of this a few years ago...
Internet reference For those interested, the result is shown on the web page of this book. Which unfortunately doesn't exist any more.
Proof by avoidance Chapter 3: The proof of this is delayed until Chapter 7 when we have developed the theory even further.
Chapter 7: To make things easy, we only prove it for the case
Appendix C: The formal proof is beyond the scope of this book, but of course, our intuition knows this to be true.
本文转载自:Mathematicx - Facebook
你相信吗,仅仅利用一张日落的照片,你就能得出地球的半径大小!Princeton 大学的Robert Vanderbei 在 2008 年的一篇论文《The Earth is Not Flat——Can a photo of the sunset over Lake Michigan reveal the shape of our planet ?》中对一张摄于密歇根湖的日落照片进行了分析,不但证实了地球是圆的,还依据照片上的内容对地球半径进行了估算。

事情的起因就是上面这张很平常的日落照片,以及这样一个大家平时并没有太在意的问题:太阳露出水面的部分应该是一个标准的弓形,但为什么在日出日落时,我们所看到的太阳是一个橄榄球一样的形状?大家或许会很快想到,发光体的下半部分其实是日光反射在水面上造成的。随之产生的是另一个问题:为什么它的下半部分要比上半部分小一些呢?
这是因为 —— 想到这个问题的答案并不容易 —— 地球是圆的。下图就是人站在地球上看日出的一个比例夸张版示意图,其中 O 为地球的中心,A 为人眼的位置,AB 为视平线,B 点为水天交界处。由于太阳距离我们相当遥远,因此我们把太阳光看作是一束理想的平行光线。我们把直接射入人眼的太阳光与 AB 的夹角记为


如果再已知人眼(或者说相机)离水面的垂直距离 h 为 1.8 米,那么根据这些数据我们就足以估算出地球的半径了。不妨把
在 Mac 上安装了 Bootcamp 后,如果空间不足了,该如何调整大小呢?在以前的文章MacBook 使用一块移动硬盘做 Win To Go 及 Time Machine 备份中,笔者谈到了一种从 APFS 的主分区中划出一部分空间给 Bootcamp 的做法。但由于没有进行试验,笔者没有展开来讲。今天专门写一篇文章,记录一下几种不同的方法,以及具体的操作步骤。
Paragon CampTune 是一个专门用来调整 Bootcamp 分区大小的商业软件。免费试用版本只能调整 2G 的空间,简直是杯水车薪。完整版则需要购买许可证。
其官方网站为:Paragon CampTune | Paragon Software
这是风险最小的方法:完全备份 Bootcamp 分区中的内容,然后将其移除,并在主硬盘上重新划分 Bootcamp 分区。这一方案的缺点是需要备份数据以及重装 Windows 系统,比较繁琐、耗时间。
这是本文重点介绍的方法。如果你对于 Mac 的引导方式不熟悉,担心分区出现问题,那么请不要继续往下阅读,只用考虑前面的两种思路。
由于 Bootcamp 是磁盘上靠后的分区,并不能用「常规操作」向前扩充,吞并 APFS 分区的空间。这里的常规操作指的是 Mac 和 Windows 上自带的磁盘工具,其拓展卷功能只允许向后拓展磁盘分区大小。如果要吞并靠前的分区,需要借助 DiskGenius 或 MiniTool Partition Wizard 这样的专业软件。笔者以前折腾另一台电脑上的硬盘分区时,就曾进行过类似的调整:通过 DiskGenius 使用计算器 + 手动改分区表数据,来划分各个分区的大小。这一方案同样存在分区表损坏或数据丢失的风险,开始前必须做好备份。
如果在 Mac 上进行过 Time Machine 备份,请确保最新的备份已经存储在外部的硬盘上,然后执行以下命令删除本地的备份。1
tmutil deletelocalsnapshots /
原因是 Time Machine 备份会存储在 APFS 分区的末端,如果不将它们删除,那么在进行硬盘分区时往往会提示「无法拆分此容器,因为生成的容器将太小。」
为了让「磁盘工具」确认最大能够划分的分区大小,需要在 Macintosh HD 上运行「急救」。在「磁盘工具」的工具栏中点击「急救」,然后选择「运行」即可。
