第十七章 · 最后一章

学习路线与练习

这一章没有新知识。前面十六章学的东西够用了,这一章只做三件事:给你一条四周练习路线、出 5 道练习题让你自测、最后做个结课小结。

学编程和学开车一样:看懂 ≠ 会写。只有自己动手敲过、错过、改对过,才算真的学会。

这一页怎么用

① 四周计划:每周做完就点左边的圆圈打勾,看着进度条涨满。

② 练习题:先自己想,能跑的题先点「运行」看结果,想好了再点「看答案」,对一下标准答案和解析,尤其看「常见错」是不是你刚犯的那个。

③ 代码块右上角有「复制」,可以把代码粘到浏览器控制台或编辑器里自己改着玩。

第一部分四周计划

不用贪快,一周攻一块。每周都给了「过关标准」,达不到就多练两天再往下走——地基不牢,后面全是糊涂账。打勾只在本次打开页面时有效,关掉就清零,真正的进度记在你练没练上。

四周计划进度0 / 4 周
还没开始。先从第 1 周练起,每天只要 20 分钟。
第 1 周 · 基础
对应第二至六章

每天打开浏览器控制台,写 20 分钟。内容就四样:变量(let / const 存东西)、字符串方法(includes 查包含、split 拆开这类)、数组四件套(filter 筛、map 变、find 找、forEach 挨个过)、函数(把一段活儿打包,喊一声就干)。

过关标准:不用查任何资料,张口就能写出 filter、map 和箭头函数(x => … 这种写法)。
第 2 周 · 数据与异步
对应第七、八、九章

还是在控制台练两件事:一是 JSON 互转——JSON.parse(文字变对象)、JSON.stringify(对象变文字),来回转到熟;二是 fetch 拿数据——找一个公开接口(比如天气接口),把返回的 JSON 取回来,打印出其中某一个字段(比如温度)。

过关标准:能独立写出「fetch 一个网址 → 转成 JSON → 打印某个字段」这一整串,中间不用停下来查。
第 3 周 · Workers
对应第十章

去 Cloudflare 后台建一个练习用的 Worker,照第十章的「路由」写法练:不同网址走不同代码。再照实战一完整做一遍 KV 短链接(把长网址存进 KV,访问短码就跳过去)。做完拿手机浏览器打开短链接,亲眼确认能跳转。

过关标准:短链接在手机上点开能正确跳到目标网址,且你能说清每一段代码是干什么的。
第 4 周 · 客户端脚本
对应第十一至十五章

先读:挑一个你现在正在用的现成模块,把它的每一行声明逐行看懂(什么时机跑、对哪类请求、要不要响应体)。再改:照实战二,自己动手改掉一个接口返回里的某个字段。最后去 App 的日志里确认:脚本真的跑了,而且 $done 正常结束、没有报错。

过关标准:改完后 App 里效果对得上(字段真的变了),日志里能找到脚本运行记录且 $done 正常。

第二部分练习题(共 5 道)

规矩都一样:先自己想 → 点「运行」看结果(能跑的题)→ 再点「看答案」。直接看答案 = 白做,这章就是查漏补缺的。

1

筛出香港节点,拼成一行

有节点名数组 ["香港01","美国02","香港03"]。请写一行代码:只留下名字里带「香港」的,再用逗号拼成一个字符串。

先想一想:筛东西用数组四件套里的哪一个?判断「带不带香港」用字符串的哪个方法?拼字符串又是哪个方法?
运行结果
标准答案
JavaScript
["香港01","美国02","香港03"].filter(x => x.includes("香港")).join(",")
// filter:挨个问 includes("香港"),答「是」的留下
// join(","):把留下的用逗号连成一个字符串

运行结果:"香港01,香港03"

解析:分两步。第一步 filter(筛选):它挨个拿数组里的名字去问箭头函数,includes("香港") 返回「真」的就留下、返回「假」的就扔掉,所以留下香港01 和香港03。第二步 join(",")(连接):把剩下的数组用逗号粘成一个字符串。

常见错:只筛出了第一个就停手——那是写成了 find(查找,只返回头一个符合的)。题目要的是「全部香港节点」,必须用 filter。记住:find 找一个,filter 筛一串。
2

数一数列表里有几条

写一个函数:给它一段文字(是某个接口返回的 JSON 字符串),返回里面 data.list 这个列表的长度;如果这段文字根本不是合格的 JSON(解析失败),返回 -1,不许让程序崩掉。

先想一想:文字变对象用哪个方法?它遇到坏文字会「抛错」,接住错误靠哪一对关键字?
运行结果(好数据 / 坏数据各测一次)
标准答案
JavaScript
function getListLength(text) {
  try {
    const obj = JSON.parse(text);  // 文字转对象,这一步可能抛错
    return obj.data.list.length;   // 一层层点进去,取列表长度
  } catch (e) {
    return -1;                     // 出错就走这里,返回 -1
  }
}

解析:关键在 JSON.parse(解析)这个动作靠不住:文字是好 JSON 它就转成对象,文字是坏的它直接抛错,没人接的话整个脚本当场停摆。所以把 parse 和取值都放进 try(试着做)里,一旦抛错,自动跳到 catch(接住)里返回 -1。这就是处理「外部给的数据」时的保命写法:数据不是你产的,永远假设它可能是坏的。

常见错:忘了 try/catch,直接写 return JSON.parse(text).data.list.length。数据好的时候没事,哪天接口抽风返回一句报错文字,脚本立刻崩掉——而且往往是在你没看着的时候崩。
3

给短链 Worker 加一个查次数的接口

回到第 3 周做的实战一(KV 短链接)。现在给这个 Worker 加一个新接口:访问 /api/stat?key=gh 时,返回短码 gh 这条短链被访问过多少次,用 JSON 形式返回。假设每次跳转时已经把次数存进了 KV,键名是 count: 加短码。

先想一想:判断「是不是这个网址」看 url 的哪个部分?网址问号后面的 key 怎么取出来?从 KV 读出来的数是什么类型,能直接当数字用吗?
模拟运行结果(Worker 代码要在 CF 里跑,这里用假 KV 演示同样的逻辑)
标准答案
JavaScript · Worker 里的一段路由
if (url.pathname === "/api/stat") {
  const key = url.searchParams.get("key");        // 取问号后面的 key=gh
  const n = Number(await env.LINKS.get("count:" + key)) || 0;
  return Response.json({ key, count: n });        // 以 JSON 形式返回
}

解析:三点。① 路由判断靠 pathname(路径部分):url.pathname 只含 /api/stat,不含问号后面的参数,所以拿它跟字符串比正合适;参数则用 url.searchParams.get("key") 单独取。② KV 读出来的是字符串:哪怕存的是次数,读出来也是文字,必须用 Number() 转成数字才能用。③ 兜底 0:这条短链一次都没被点过时,KV 里根本没有这个键,读出来是空的,转完是 NaN(不是数字),靠 || 0 兜成 0,返回给人看才正常。

常见错:一是拿整个网址去比,忘了问号后面的参数不属于 pathname;二是忘了 Number() 转换,后面做加法时会变成字符串拼接("5" + 1 得到 "51");三是没兜底,新短链查出来是个 NaN。
4

给请求统一加一个请求头

写一个 http-request 类型的脚本(请求发出去之前跑的那种):给某个域名的所有请求,统一加上一个请求头 X-From: module。用法忘了可以回翻第十二章讲的 $done({headers})。

先想一想:改请求头靠 $done 传什么?原来的请求头(登录信息那些)还在不在?怎么才能「新头加上、旧头不丢」?
模拟运行结果($request / $done 是 App 提供的,这里用假请求演示)
标准答案
JavaScript · Surge / Loon 脚本
$done({
  headers: {
    ...$request.headers,   // 先把原有请求头全部摊开带上
    "X-From": "module"     // 再加上我们要的新头
  }
});

解析:http-request 脚本里想改请求头,就用 $done({ headers }) 把改好的头部交回去,App 会拿这份头部去发请求。关键在 ...$request.headers 这一行:三个点叫展开,意思是「把原来所有的头一个不少地先放进来」,然后再补上 X-From。这样新头有了,原来的头(比如登录凭证、设备信息)也都还在。

常见错:只写新头:$done({ headers: { "X-From": "module" } })。这一交,请求就只剩这一个头了,原来的头全丢——轻则接口报错,重则登录状态没了、App 直接用不了。改头先展开原头,这一步永远不能省。
5

读懂一个正在用的模块(开放题)

找一个你现在正在用的模块,逐行注释它的 [Script] 声明,再注释它脚本的第一段代码。最后用大白话说清三件事:它在什么时机跑、对哪一类请求下手、做了什么。

先想一想:这道题没有唯一答案,考的是「读代码」的功夫——这也是第 4 周计划的核心。先别急着看示范,自己挑一个模块试着拆一遍,拆不动的地方再对照示范的拆法。
示范(用第十二章的去广告例子)

第一步:逐行拆 [Script] 声明

模块声明 · 逐行注释
去广告 = type=http-response, pattern=^https://api\.example\.com/feed, requires-body=true, script-path=ad.js
// type=http-response  → 响应回来之后才跑这个脚本
// pattern=网址规则     → 只对 feed 接口的网址触发,别的请求不碰
// requires-body=true  → 需要拿到响应体(返回的数据正文)
// script-path=ad.js   → 真正干活的脚本文件是 ad.js
type=http-response时机:服务器把数据返回来之后、App 显示之前,脚本插进来改数据。
pattern=…/feed对象:只有网址对上这个规则的请求才触发,也就是信息流(feed)接口,其他接口不受影响。
requires-body=true条件:要改返回内容,就必须声明需要响应体;不写这一项,脚本根本拿不到数据正文。

第二步:注释脚本第一段

JavaScript · 脚本第一段
const obj = JSON.parse($response.body);
// $response.body 是响应正文,此时还是一段文字
// JSON.parse 把文字转成对象,后面才能 . 点着取字段、删广告

第三步:一句话总结。这个模块在信息流接口的数据返回来之后、App 显示之前跑,先把返回的文字转成对象,再把里面的广告条目删掉、交回给 App。所以你刷信息流时看不到广告——不是广告没下发,是下发了又被脚本在半路删掉了。

自查清单:你的注释里如果说不清「type 决定时机、pattern 决定对谁、requires-body 决定拿不拿得到正文」这三件中的任何一件,就说明那一行还没真读懂,回第十二章再翻一遍。

第三部分结课小结

十七章到这里就全部结束了。回头看看,你其实已经走完了一整条线:

JS 基础

变量、字符串、数组、函数、对象、JSON、异步——能自己在控制台写小段代码解决问题。

Workers

能在 Cloudflare 上写按网址分发的服务,用 KV 存数据,做得出短链接这种完整的小工具。

客户端脚本

Surge / Loon / REX 的模块声明能逐行读懂,能改请求头、改响应字段,知道去日志里验货。

接下来怎么练

  1. 先把上面的四周计划完整走一遍。这是把「看懂」变「会写」的唯一办法,没有捷径。
  2. 拿自己的东西练手。你的 tg-bot 就是现成的 Worker 靶场,你天天在用的模块就是现成的阅读材料——改坏了能改回来,才叫真练。
  3. 卡住就回头翻对应章节的交互页。哪块忘了翻哪章,代码都点开能重跑,不用从头再学。
  4. 养成验货习惯。每次改完:控制台看输出、手机上实测、App 日志里确认——三样都对上,才算做完。