<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>张景林的博客</title><link>https://zhangjinglin.github.io/</link><description>关于技术，关于人生</description><generator>Hugo 0.168.0 &amp; FixIt v1.0.0-beta.4</generator><language>zh-CN</language><managingEditor>ldxyzjl@163.com (张景林)</managingEditor><webMaster>ldxyzjl@163.com (张景林)</webMaster><lastBuildDate>Fri, 23 Oct 2026 18:26:03 +0800</lastBuildDate><atom:link href="https://zhangjinglin.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>当老师不再敢说「学这个将来有前途」</title><link>https://zhangjinglin.github.io/2026/10/ai-education/</link><pubDate>Fri, 23 Oct 2026 17:50:00 +0800</pubDate><author>ldxyzjl@163.com (张景林)</author><guid>https://zhangjinglin.github.io/2026/10/ai-education/</guid><category domain="https://zhangjinglin.github.io/categories/%E9%9A%8F%E7%AC%94/">随笔</category><description>&lt;img src="https://zhangjinglin.github.io/images/ai-education.jpg" alt="featured image" referrerpolicy="no-referrer"&gt;&lt;p&gt;去年有个学生来找我，成绩很好，做事也认真。他问我：我该上大学学什么。&lt;/p&gt;&#10;&lt;p&gt;这个问题我回答过很多年，几乎不用想。先问问他喜欢什么、擅长什么，再看看眼下什么行业缺人，然后给个方向——计算机、数学、电子，都行——最后加一句：好好学，这条路是稳的。学生听了踏实，我也觉得自己尽到了责任。&lt;/p&gt;&#10;&lt;p&gt;现在我一句话都说不出口。不是不知道有哪些专业，专业我都熟；是「稳」这个字我不敢用了。我不知道四年、七年之后，他学的东西还值不值钱。&lt;/p&gt;&#10;&lt;p&gt;但真正让我放不下的，其实不是这一个学生。&lt;/p&gt;&#10;&lt;p&gt;我想到的是这样一笔账：假如一年培养一千万学生，其中五十万、最多一百万能成为真正的专家——这已经是相当乐观的估计了——那剩下九百万呢？他们不会成为专家，也没有那么多时间再去成为，可他们同样要用 AI 干活、做决定，并且承担决定的后果。&lt;/p&gt;&#10;&lt;p&gt;判断对错这件事，确实是专家的事。可专家培养不出那么多，也不该指望培养出那么多。所以真正压在教育身上的问题其实很朴素：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;那九百多万人，在学校里该学什么？&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;这才是我想说的。要说清它从哪儿来，得先讲这两年我看见的三件事。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="从一篇摘要说起"&gt;&lt;span&gt;从一篇摘要说起&lt;/span&gt;&#10; &lt;a href="#%e4%bb%8e%e4%b8%80%e7%af%87%e6%91%98%e8%a6%81%e8%af%b4%e8%b5%b7" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;事情要从 2022 年 11 月 30 日讲起。那天 ChatGPT 上线，用的是 GPT-3.5。&lt;/p&gt;&#10;&lt;p&gt;一开始我没太在意。聊天机器人早就有了，无非是笨一点、慢一点，属于能预料到的进步。&lt;/p&gt;&#10;&lt;p&gt;真正让我停下来的是件很小的事。我丢给它一篇文章，让它说说这篇文章讲了什么。它说对了，而且说得比不少学生清楚。&lt;/p&gt;&#10;&lt;p&gt;那一刻我承认了一件事：它有点「智能」了。能读进去、能拎出主旨，这在我原来的认识里，是只有人才干得了的活。它到底有没有真懂，我说不好，但它给出的结果已经很像了。&lt;/p&gt;&#10;&lt;p&gt;可我没有顺着往下想。我的判断是：读懂一篇文章是一回事，写程序是另一回事。写程序要的是另一种聪明——逻辑得立得住，条件得想全，跑不通就是跑不通。语言上那点灵光，代替不了这个。所以我当时并不慌，觉得它顶多是个会写文字的帮手。我教了多年计算机，这条界线在我心里画得挺清楚。&lt;/p&gt;&#10;&lt;p&gt;这道界线，不到一年就被抹掉了。2023 到 2024 年，AI 编程工具一下子铺开。像 v0 这类东西，你用普通话把想要什么讲一遍，前端界面就出来了。&lt;/p&gt;&#10;&lt;p&gt;让我不安的不是「程序员能写得更快了」。快是好事，效率问题人总会习惯。让我不安的是：有些东西过去得学好几年才做得出来，现在不用了。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="写代码这件事"&gt;&lt;span&gt;写代码这件事&lt;/span&gt;&#10; &lt;a href="#%e5%86%99%e4%bb%a3%e7%a0%81%e8%bf%99%e4%bb%b6%e4%ba%8b" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;所以我当时就有个感觉：一个年轻人要是把学编程当成自己最重要的本钱，这个本钱恐怕不像以前那么靠得住了。&lt;/p&gt;&#10;&lt;p&gt;这话说出去容易被人听成两个意思，得说清楚。我的意思是，写代码这件事的市场价钱可能会掉。我不是说编程知识从此没用——那是另一回事，而且不对。&lt;/p&gt;&#10;&lt;p&gt;编程的知识还是有用的，只是用处在挪位置。它让你看得懂一个系统是怎么转的，能把一个含糊的需求说成能干的活，能看出别人交来的东西对不对，能为自己签字的那一块负责。这些事不会因为工具变强而消失，反而更重要了——因为东西出得多了，判断的责任就重了。&lt;/p&gt;&#10;&lt;p&gt;真正变的是位置：会写代码，以前是进门的那道门槛，现在只是个起点。它是你理解系统的一种方式，仅此而已。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="数学也躲不过"&gt;&lt;span&gt;数学也躲不过&lt;/span&gt;&#10; &lt;a href="#%e6%95%b0%e5%ad%a6%e4%b9%9f%e8%ba%b2%e4%b8%8d%e8%bf%87" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;有人会想，那不去写代码就是了。可事情没这么便宜。&lt;/p&gt;&#10;&lt;p&gt;编程先被打穿，是因为它有个特别好用的信号：代码跑不跑得通，一试就知道。数学看着更玄，更吃天赋和年头，可它也一样有硬标准——一个证明对不对，是可以验的。凡是能一眼验出对错的地方，机器都会追上来。&lt;/p&gt;&#10;&lt;p&gt;于是问题就变了。不再是「AI 会不会做题」，而是「AI 能不能坐下来做研究」。&lt;/p&gt;&#10;&lt;p&gt;会做题不算什么。竞赛题是答案已知的封闭问题，做对一百道也不等于能提出新问题。真正要看的是：它做出来的证明，别人能不能独立审？是不是原创的？有没有人读懂、有没有人真的用上？&lt;/p&gt;&#10;&lt;p&gt;我给「打穿」这个词下过一个定义，到现在还觉得准：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;打穿一个领域，不是指人再也不用学这个领域了，而是指：过去得靠多年训练、只有少数专家才干得了的核心工作，开始能由 AI 大批量地做。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;今年十月那批七百多篇数学手稿，我单独写过一篇（&lt;a href="https://zhangjinglin.github.io/2026/10/ai-math-civilization/"&gt;《古法创新：当 AI 接管数学之后》&lt;/a&gt;），这里不重复。只说一点：那些东西该一篇一篇去核实原文、看质量和审查情况，不能光看数量下结论。但研究这件事的做法确实在变，这一点已经够让教书的人重新想一想了。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="答不上来的那句话"&gt;&lt;span&gt;答不上来的那句话&lt;/span&gt;&#10; &lt;a href="#%e7%ad%94%e4%b8%8d%e4%b8%8a%e6%9d%a5%e7%9a%84%e9%82%a3%e5%8f%a5%e8%af%9d" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;三件事讲完，还得回到那个学生。&lt;/p&gt;&#10;&lt;p&gt;「这条路是稳的」这句话我说不出口，不是我懂得不够多，是我没法再有把握地跟他说：你把人生里很大一块时间投到这个方向，将来就能拿回相应的东西。&lt;/p&gt;&#10;&lt;p&gt;这个「将来」太远了。今天看着稀罕的本事，等他毕业可能满街都是；今天得练好多年的活，可能被模型吃掉一半，甚至大半。技术变得比任何人做预测都准。&lt;/p&gt;&#10;&lt;p&gt;而且这不只是我知道得够不够的问题，是责任的问题。学生会照着老师、学校、社会的话去投几年时间、几年学费，还有几年本来能干别的事的机会。这笔账是他一个人付的，赌的是我猜得对不对。要是我给的建议建立在过时的前提上，代价却是他掏，那我这话再真心也不算负责。&lt;/p&gt;&#10;&lt;p&gt;所以负责任的说法，不该是假装自己能算准十年后的就业市场。得反过来：讲清楚我凭什么这么说，我假设了什么，风险在哪，什么情况下这个建议就不成立了。这么讲不像建议，可它是眼下唯一诚实的说法。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="剩下那九百万怎么办"&gt;&lt;span&gt;剩下那九百万怎么办&lt;/span&gt;&#10; &lt;a href="#%e5%89%a9%e4%b8%8b%e9%82%a3%e4%b9%9d%e7%99%be%e4%b8%87%e6%80%8e%e4%b9%88%e5%8a%9e" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;开头那笔账，来自一个很具体的念头：能不能把判断这件事也交给 AI。&lt;/p&gt;&#10;&lt;p&gt;让一个模型去做，再让另一个模型去看看它有没有错——A 判断 B，在很多场合确实管用。但它有个前提我没想清楚：A 和 B 得站在不同的位置上。如果两个模型用的是同一套技术、同一条路径、同一个领域的知识，它们就会犯同样的错，然后互相确认对方是对的。你得到的不是校验，只是两个一致的声音。&lt;/p&gt;&#10;&lt;p&gt;所以在培养人的过程里，能不能判断对错，最终还是得靠人，靠专家。这里没有捷径——一个能在自己领域里看出 AI 错在哪儿的人，要很多年才养得出来，而且只能一个一个地养。&lt;/p&gt;&#10;&lt;p&gt;这就是那笔账的来路：一千万学生，五十万到一百万能长成这样的人，剩下九百万都不算。他们不会成为专家，也没有那么多时间再成为专家，可他们要用 AI 干活、做决定、担后果。&lt;/p&gt;&#10;&lt;p&gt;用「自己多留个心眼」来回答，等于把系统的可靠性问题推给个人，等于不回答。出路我知道两条：一条是专家把验证的办法做成普通人用得上的工具，这条是远水，等它来要很多年；另一条是学校现在就能做的，教他们一种不是专家、但也不至于全盘照收的判断力。&lt;/p&gt;&#10;&lt;p&gt;九百多万人今天就在教室里。下面讲的，是第二条。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="想起废除科举"&gt;&lt;span&gt;想起废除科举&lt;/span&gt;&#10; &lt;a href="#%e6%83%b3%e8%b5%b7%e5%ba%9f%e9%99%a4%e7%a7%91%e4%b8%be" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;想这些事的时候，我总会想起清朝废除科举。&lt;/p&gt;&#10;&lt;p&gt;有人寒窗苦读二十年，把一辈子的打算、脸面、全家的生计都压在这套制度上。制度一废，那些年头不会自动变成新出路。书还在，人还在，可那条通向体面日子的路没了。&lt;/p&gt;&#10;&lt;p&gt;AI 时代当然不是废除科举。科举是一刀切，某年某月一纸诏书，规矩全换；AI 对职业的侵蚀是一点点来的，一个领域一个领域地发生，方向和快慢都还说不准，有时候只是把入行的门槛悄悄抬了一点。&lt;/p&gt;&#10;&lt;p&gt;可两件事背后有个一样的问题：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;一个人照着社会认可的规矩投了很多年，后来规矩变了，这笔损失该不该全让他自己扛？&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;这不只是「他有没有适应能力」的事。它牵到教育制度、学校、雇主和社会：能不能给他重新转行的机会，能不能让他有条件接着学，转型的风险能不能大家分一分。&lt;/p&gt;&#10;&lt;p&gt;教育者没法保证未来不变。但至少有一件事可以不做：把没把握的预测，说成有把握的承诺。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="那该教学生什么"&gt;&lt;span&gt;那该教学生什么&lt;/span&gt;&#10; &lt;a href="#%e9%82%a3%e8%af%a5%e6%95%99%e5%ad%a6%e7%94%9f%e4%bb%80%e4%b9%88" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;回到那九百万学生。这几年我想下来，方向其实是清楚的：别只教他适应某一个岗位，教他在环境变了以后，能把本事带走、重新长出来的能力。&lt;/p&gt;&#10;&lt;p&gt;这个词容易被理解歪，先说它不是什么。它不是「什么都懂一点」——那种人什么都聊得上两句，真上手什么都拿不出来；也不是说不要专业，因为专业没到一定深度，所谓「跨界」就只是拼凑。&lt;/p&gt;&#10;&lt;p&gt;它说的是这么回事：当原来那身本事不值钱了、干活的方式变了，或者不得不进一个完全陌生的行当，一个人能拿已有的底子，比较快地学、判断、上手，然后调整方向。拆开看，大概六样。&lt;/p&gt;&#10;&lt;p&gt;**敢进陌生领域，还能把架子搭起来。**碰上没学过的问题，知道怎么圈定它、怎么立起最基本的概念、上哪儿找材料、怎么看出自己缺的是哪一块，然后靠动手把水平一点点推到能用。这事听着虚，其实很实：它就是「三天能上手一样没见过的东西」和「三个月还在原地转」的差别。&lt;/p&gt;&#10;&lt;p&gt;**能把问题说清楚，把目标定清楚。**讲得明白自己要解决什么、有什么限制、做到什么算成。这跟研究提示词不是一回事。模型越强，很多话术它自己会处理，专门学一套说法的收益越来越小；但「说清楚自己要什么」这件事自动化不了——它不是口才问题，是想没想明白的问题。&lt;/p&gt;&#10;&lt;p&gt;**能看出结果靠不靠得住。**不用人人成专家，但能看出哪些结论得有证据、哪个风险不能当没看见、什么情况下必须找人验一遍或者去请教懂行的人。说白了，是知道自己什么时候不该信，包括不该信自己。&lt;/p&gt;&#10;&lt;p&gt;**能把知识用到真事上。**能把想法变成动作，再拿结果反推自己哪儿想错了。真事比标准答案更能照出理解的窟窿，做过一遍才知道，那些自以为懂的地方其实没懂。&lt;/p&gt;&#10;&lt;p&gt;**能把一个领域的东西搬到另一个领域。**看得见技术和具体的生意、和人的处境之间的关系。这一条最容易说成空话，可前面几条真做到了，它就不空：你得在至少一件事上扎得够深，深到知道这法子为什么管用、到哪儿就不管用了，才知道它能搬到哪儿去。&lt;/p&gt;&#10;&lt;p&gt;**能跟人合作，能担事。**能跟人一块干，能说清自己的依据和难处，能明白别人要什么，也能对自己负责的那块认账。这条听着最不像本事，可在一个产出多得看不完、判断越来越缺的环境里，它可能是最贵的。&lt;/p&gt;&#10;&lt;p&gt;这六样都不是专业的替代品。它们就一个用处：把你已经会的带到新地方去，需要的时候，重新学一遍学得更快。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="学校能做的"&gt;&lt;span&gt;学校能做的&lt;/span&gt;&#10; &lt;a href="#%e5%ad%a6%e6%a0%a1%e8%83%bd%e5%81%9a%e7%9a%84" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;如果上面这套说得通，大学大概得同时干两件事，而且这两件看着还有点别扭。&lt;/p&gt;&#10;&lt;p&gt;一件是把专业打扎实：让学生真懂一个领域的核心概念、方法和它的边界，而不是只练一套容易被机器接手的操作。另一件是教方法：怎么进一个陌生领域，怎么把问题界定清楚，怎么验一个结论，怎么用 AI，怎么处理真活，条件变了怎么拐弯。&lt;/p&gt;&#10;&lt;p&gt;这不是说所有专业都该改成泛泛的通识课。恰恰相反，没有深度的通识是最没用的一种教育，专业能力给不了，迁移的起点也给不了。关键只有一条：别把整个教育押在「某个岗位会一直在」这个假设上。&lt;/p&gt;&#10;&lt;p&gt;还有一件比改课程表难得多：老老实实承认不确定。跟学生讲清这个专业好在哪、险在哪、还有没有别的路，给他中途重新选一次的机会，尽量让转方向的代价小一点。这些事做起来不好看，因为它要求学校承认自己也不知道十年后什么样。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="最后"&gt;&lt;span&gt;最后&lt;/span&gt;&#10; &lt;a href="#%e6%9c%80%e5%90%8e" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;过去当老师的容易说一句话：学这个专业，将来有前途。&lt;/p&gt;&#10;&lt;p&gt;现在这句话越来越难说出口。技术一变，专业知识的用法、职业的样子、市场的价钱都会跟着变，而这些变化，教书的人未必算得准。&lt;/p&gt;&#10;&lt;p&gt;但更诚实的做法不是从此不开口，而是换一种说法：讲清现在知道什么、猜的是什么、还不知道什么；讲清这条路的理由和风险；帮他打个扎实的底子，但别把命押在一个岗位上；教他在规矩变了以后，还能重新学、重新判断、重新长本事；让制度给人留转的机会，而不是把适应的代价全推给个人。&lt;/p&gt;&#10;&lt;p&gt;所以最后的问题不是「还有哪个专业绝对安全」——大概没有。&lt;/p&gt;&#10;&lt;p&gt;而是：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;我们没法保证一个学生今天学的东西十年后还值钱，那教育该怎么陪他熬过这个变化，而不是让他一个人去扛整个时代转的账？&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;这个问题我没有答案。但我知道，把没把握的事说成有把握，是最坏的一种回答。&lt;/p&gt;&#10;</description></item><item><title>古法创新：当 AI 接管数学之后</title><link>https://zhangjinglin.github.io/2026/10/ai-math-civilization/</link><pubDate>Fri, 23 Oct 2026 16:50:00 +0800</pubDate><author>ldxyzjl@163.com (张景林)</author><guid>https://zhangjinglin.github.io/2026/10/ai-math-civilization/</guid><category domain="https://zhangjinglin.github.io/categories/%E9%9A%8F%E7%AC%94/">随笔</category><description>&lt;img src="https://zhangjinglin.github.io/images/ai-math-civilization.jpg" alt="featured image" referrerpolicy="no-referrer"&gt;&lt;p&gt;如果哪一天，AI 把一个定理完整地证完了，摆在你面前，你打开形式化验证器跑一遍，结果显示：正确。&lt;/p&gt;&#10;&lt;p&gt;然后你问它：这个定理意味着什么？它答了。你追问：为什么它重要？它也答了。每一句都通顺，每一句你都能读，但你就是没法确认——它说的是不是真的那个“重要”。&lt;/p&gt;&#10;&lt;p&gt;那一刻，知识仍然是真的，只是不再属于你。&lt;/p&gt;&#10;&lt;p&gt;这篇文章想理清的，就是走到那一步之前，中间会经过哪些站。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="一个更朴素的问题"&gt;&lt;span&gt;一个更朴素的问题&lt;/span&gt;&#10; &lt;a href="#%e4%b8%80%e4%b8%aa%e6%9b%b4%e6%9c%b4%e7%b4%a0%e7%9a%84%e9%97%ae%e9%a2%98" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;这些想法大部分来自一次和 AI 的长聊。起点其实不是“AI 会不会取代数学家”，而是一个更朴素的直觉：&lt;/p&gt;&#10;&lt;p&gt;如果 AI 已经能源源不断地产出数学结果，未来的孩子还会不会把“数学家”当成一件值得干一辈子的事？&lt;/p&gt;&#10;&lt;p&gt;关键不在于数学家会不会失业。数学是一门高度抽象、需要长期训练和真实热爱的学科。一次突破往往不是“会做题”就能得到，而是几十年持续投入的结果。一旦社会上慢慢形成“数学已经被 AI 解决了”的看法，愿意把一生投进去的年轻人就会减少。&lt;/p&gt;&#10;&lt;p&gt;这会构成一条反馈链：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;AI 数学能力增强 → 人类认为数学已属于 AI → 年轻人减少 → 人类数学研究力量下降 → 人类原创数学进一步减少 → 更加证明“数学已经是 AI 的领域”。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;真正要担心的，是&lt;strong&gt;人类会不会逐渐停止生产数学家&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="为什么数学和打字员不一样"&gt;&lt;span&gt;为什么数学和打字员不一样&lt;/span&gt;&#10; &lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e6%95%b0%e5%ad%a6%e5%92%8c%e6%89%93%e5%ad%97%e5%91%98%e4%b8%8d%e4%b8%80%e6%a0%b7" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;打字员消失了，胶片冲印师消失了，相关的能力也随之留在历史里。这没什么可怕——那些手艺本来就不是别的东西的地基。&lt;/p&gt;&#10;&lt;p&gt;数学不一样。&lt;/p&gt;&#10;&lt;p&gt;物理、化学、计算机、气候模型、生物、工程，全都长在数学思想之上。如果人类不再培养能深入数学的人，失去的不只是一个职业群体，而是未来科学创新所需要的思想来源。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;技术职业的消失，不一定意味着技术文明停止；但基础学科研究者的大规模消失，可能影响文明继续产生新知识的能力。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 class="heading-element" id="文学也会衰落但代价不同"&gt;&lt;span&gt;文学也会衰落，但代价不同&lt;/span&gt;&#10; &lt;a href="#%e6%96%87%e5%ad%a6%e4%b9%9f%e4%bc%9a%e8%a1%b0%e8%90%bd%e4%bd%86%e4%bb%a3%e4%bb%b7%e4%b8%8d%e5%90%8c" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;讨论到这里绕了一下：文学是不是早就经历过类似的事？&lt;/p&gt;&#10;&lt;p&gt;是的。现代人越来越少进行长时间阅读，社交媒体把注意力切成了碎片。未来大概仍然有作家，但成为大作家的人可能越来越少。&lt;/p&gt;&#10;&lt;p&gt;不过文学和数学有一个根本差异：文学的门槛低，会说话就能写，区别只在于写得好不好；数学的门槛高，做原创数学需要长期训练、深厚基础和高度专业化的能力。&lt;/p&gt;&#10;&lt;p&gt;所以，当社会对某件事失去功利需求时，文学的衰落是缓慢的、分散的；而数学一旦失去人，可能断得很干脆。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="古法创新"&gt;&lt;span&gt;古法创新&lt;/span&gt;&#10; &lt;a href="#%e5%8f%a4%e6%b3%95%e5%88%9b%e6%96%b0" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;“古法创新”这个词，是与张晋骞博士（&lt;a href="https://harushinken.github.io" target="_blank" rel="external nofollow noopener noreferrer"&gt;harushinken.github.io&lt;i class="fa-solid fa-external-link-alt fa-xs ms-1 text-secondary" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;）在一次聊天里提到的。这个说法在他之前是不是已经有人用过，我没有查证；但它比“原创”准确得多：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;古法创新：不借助外部智能，主要依靠自己的知识、推理和能力，去发现新的东西。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;这不是反对工具，也不是认为过去的方法一定更好。它指向的是一种具体的文明能力：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;人在不知道答案的情况下，自己摸索、犯错、形成猜想、继续推进，最后把答案挖出来。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;ChatGPT 出现之后，这个过程正在被改写。越来越多人会把问题直接交出去，先拿到答案，再回头理解——甚至不回头。&lt;/p&gt;&#10;&lt;p&gt;于是问题不是“手搓是不是更先进”，而是：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;人类是否还应该保留亲自发现未知的能力？&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="当-ai-的产出超过人类的消化速度"&gt;&lt;span&gt;当 AI 的产出超过人类的消化速度&lt;/span&gt;&#10; &lt;a href="#%e5%bd%93-ai-%e7%9a%84%e4%ba%a7%e5%87%ba%e8%b6%85%e8%bf%87%e4%ba%ba%e7%b1%bb%e7%9a%84%e6%b6%88%e5%8c%96%e9%80%9f%e5%ba%a6" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;2026 年 10 月 6 日，OpenAI 在 GitHub 上一次性放出了 722 篇数学论文，说其中包含数百个未解问题的解答，而生成它们的模型还没有对外发布。据报道，这批手稿号称解决了 300 多道此前无人攻克的难题，覆盖代数、逻辑、几何、概率。&lt;/p&gt;&#10;&lt;p&gt;这个数字很惊人，但更让我在意的是它揭示的可能性：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;AI 可以在很短的时间里，产出过去需要数学家长期研究才能得到的结果。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;一旦 AI 产出成果的速度超过人类阅读、理解和验证的速度，数学家的角色就会变：&lt;/p&gt;&#10;&lt;p&gt;过去的链条是——&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;人类提出问题 → 人类研究 → 人类发现 → 人类证明 → 人类发表&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;未来可能变成——&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;AI 提出/解决问题 → AI 生成证明与论文 → 人类验证 → 人类理解&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;人类从“发现者”，退到“鉴定者、验证者、解释者”。&lt;/p&gt;&#10;&lt;p&gt;这个位置看上去仍然体面。但它有一个前提：我们还能理解自己在验证什么。&lt;/p&gt;&#10;&lt;p&gt;有意思的是，几个月前“数学与人工智能顾问小组”在回应这批成果时，说的几乎是同一件事：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;数学研究的未来不能仅仅是解读 AI 实验室产出的结果。数学家必须能够自主提出问题、构建研究思路，并探索那些没有被选为 AI 能力展示案例的研究方向。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 class="heading-element" id="更远一步当人类理解不了"&gt;&lt;span&gt;更远一步：当人类理解不了&lt;/span&gt;&#10; &lt;a href="#%e6%9b%b4%e8%bf%9c%e4%b8%80%e6%ad%a5%e5%bd%93%e4%ba%ba%e7%b1%bb%e7%90%86%e8%a7%a3%e4%b8%8d%e4%ba%86" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;如果 AI 的能力继续推进，终有一天，人类数学家可能无法理解它的发现。那时“人类审阅 AI”这套机制本身就失效了。&lt;/p&gt;&#10;&lt;p&gt;人类或许依然能检查形式上的东西：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;这个证明对不对？用 Lean 这类形式化系统跑一遍就知道。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;但人类回答不了：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;这个定理到底意味着什么？&lt;/li&gt;&#10;&lt;li&gt;它为什么重要？&lt;/li&gt;&#10;&lt;li&gt;它和其他数学结构有什么关系？&lt;/li&gt;&#10;&lt;li&gt;它打开了一个怎样的新领域？&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;于是会出现一种很奇怪的状态：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;人类知道一个数学结果是真的，却不知道它为什么重要。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;它像一块外星文明留下的碑文。你能确认上面刻的每一条都成立，却说不清它想告诉你什么。&lt;/p&gt;&#10;&lt;p&gt;所以，比“AI 替代数学家”更根本的问题是：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;如果人类无法理解自己最先进的知识，人类还是不是这个知识的主人？&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 class="heading-element" id="后来发现这已经不是数学的问题"&gt;&lt;span&gt;后来发现，这已经不是数学的问题&lt;/span&gt;&#10; &lt;a href="#%e5%90%8e%e6%9d%a5%e5%8f%91%e7%8e%b0%e8%bf%99%e5%b7%b2%e7%bb%8f%e4%b8%8d%e6%98%af%e6%95%b0%e5%ad%a6%e7%9a%84%e9%97%ae%e9%a2%98" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;讨论最后从数学滑向了整个技术文明。&lt;/p&gt;&#10;&lt;p&gt;如果 AI 不只掌握数学，还掌握物理、化学、生物、工程，并且能自主创新，那么可能出现：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;AI 负责创新，人类负责接受创新。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;传统文明的接力是这样的：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;上一代 → 教给下一代 → 下一代在此基础上继续创新 → 再传给下一代&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;AI 主导的文明可能变成：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;人类文明 → 把知识交给 AI → AI 继续创新 → 人类学习 AI 的成果&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;下一代人不再是知识的生产者，而主要是学习者和使用者。&lt;/p&gt;&#10;&lt;p&gt;这里有一个我认为最重要的结论：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;文明进步 ≠ 人类生活水平提高。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;假设 AI 不断发现新的能源技术、医学技术、材料、物理理论和工程方案，人类的生活水平当然会持续上升。但如果这些进步全部来自 AI，而人类自己已经不在知识前沿上，那么：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;技术仍然在进步，但人类文明未必仍然在作为“创新主体”进步。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;想像这样一个文明：&lt;strong&gt;人生活在越来越先进的文明里，却逐渐失去了创造这个文明的能力。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="真正该担心的不是-ai-知道得多"&gt;&lt;span&gt;真正该担心的不是 AI 知道得多&lt;/span&gt;&#10; &lt;a href="#%e7%9c%9f%e6%ad%a3%e8%af%a5%e6%8b%85%e5%bf%83%e7%9a%84%e4%b8%8d%e6%98%af-ai-%e7%9f%a5%e9%81%93%e5%be%97%e5%a4%9a" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;“AI 吸收了全人类已有的知识”，这件事本身并不意味人类创新的结束。过去的知识只是过去。&lt;/p&gt;&#10;&lt;p&gt;真正危险的假设是另一句：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;AI 不仅吸收了过去，还能独立地从过去推导未来，而且创新能力长期超过人类。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;如果这一条成立，问题就从“AI 会不会帮助人类创新”，变成：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;未来还有没有属于人类自己的知识前沿？&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;如果答案是否定的，人类可能仍然生活在一个飞速发展的世界里，却已经不再是推动这个世界往前走的主体。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="最后的问题"&gt;&lt;span&gt;最后的问题&lt;/span&gt;&#10; &lt;a href="#%e6%9c%80%e5%90%8e%e7%9a%84%e9%97%ae%e9%a2%98" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;这次讨论最后收束成一句话：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;如果 AI 接管了基础科学和技术领域的创新，人类文明还算不算一个正在发展的文明？&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;它可以再拆成三个问题：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;人类是否必须亲自参与知识生产，才配称为一个有活力的文明？&lt;/li&gt;&#10;&lt;li&gt;如果 AI 的知识产出超过人类的理解能力，人类是否仍然是知识的主人？&lt;/li&gt;&#10;&lt;li&gt;如果人类逐渐停止“古法创新”，未来还会不会有真正属于人类自己的知识前沿？&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;这不是“AI 会不会抢走工作”的问题。它问的是：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;当人类第一次造出一个可能比自己更强的知识生产主体之后，“人类文明”这个概念本身会发生什么变化？&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;坦白说，我没有答案。&lt;/p&gt;&#10;&lt;p&gt;但我知道，这个问题最终不是靠辩论得出结论的，而是靠一件件具体的事：下一个年轻人面对一个没有答案的问题时，是选择打开对话框，还是选择拿出一张白纸。&lt;/p&gt;&#10;&lt;p&gt;古法创新不需要被立法保护。它需要被认真地选择。&lt;/p&gt;&#10;</description></item><item><title>十年之后，重新出发</title><link>https://zhangjinglin.github.io/2026/10/restart/</link><pubDate>Fri, 23 Oct 2026 14:50:00 +0800</pubDate><author>ldxyzjl@163.com (张景林)</author><guid>https://zhangjinglin.github.io/2026/10/restart/</guid><category domain="https://zhangjinglin.github.io/categories/%E9%9A%8F%E7%AC%94/">随笔</category><description>&lt;p&gt;&lt;img loading="lazy" src='https://zhangjinglin.github.io/images/cover.jpg' alt="不止于代码 —— 一个程序员的后半生"&gt;&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="十年"&gt;&lt;span&gt;十年&lt;/span&gt;&#10; &lt;a href="#%e5%8d%81%e5%b9%b4" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;这个站点的第一篇文章写于 2016 年 2 月，最后一篇停在 2017 年 5 月。&lt;/p&gt;&#10;&lt;p&gt;中间空了将近十年。&lt;/p&gt;&#10;&lt;p&gt;十年是很长的一段时间，长到足以让一件当时很顺手的事情，变成一件需要重新学起的事情。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="技术在变"&gt;&lt;span&gt;技术在变&lt;/span&gt;&#10; &lt;a href="#%e6%8a%80%e6%9c%af%e5%9c%a8%e5%8f%98" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;十年前我写的是 PHP、CodeIgniter、Laravel，是 Android 的 ListView，是 OpenCV 在 macOS 和 Windows 上那些绕来绕去的配置，是 Docker 刚出来时的兴奋，是 Vim 配置到半夜。&lt;/p&gt;&#10;&lt;p&gt;那时候我还会为 Yarn 和 Tesseract.js 出现在 GitHub 热榜上写一篇文章。&lt;/p&gt;&#10;&lt;p&gt;今天再看，这些东西有的已经死了，有的完全换了一副样子。前端的工具链换了一轮又一轮，我当年熟悉的那套东西，现在多半只是历史。连这个博客本身——当年用 Hexo 搭的，现在也得换成 Hugo 才能重新跑起来。&lt;/p&gt;&#10;&lt;p&gt;技术的世界里，十年前的经验有一半会过期。这是这一行的规矩，接受起来并不难。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="人生也在变"&gt;&lt;span&gt;人生也在变&lt;/span&gt;&#10; &lt;a href="#%e4%ba%ba%e7%94%9f%e4%b9%9f%e5%9c%a8%e5%8f%98" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;难的是另一件事。&lt;/p&gt;&#10;&lt;p&gt;这十年里，变的不只是技术。&lt;/p&gt;&#10;&lt;p&gt;身体会提醒你，它不像从前那样了。有些以前不在意的小毛病，开始变成需要认真对待的事情。&lt;/p&gt;&#10;&lt;p&gt;这十年里，我的父母先后离开了我。&lt;/p&gt;&#10;&lt;p&gt;有些电话不会再打来。有些事情，从那一刻起，再也没有人可以问了。&lt;/p&gt;&#10;&lt;p&gt;年轻时以为只要努力，事情就会有答案；到了某个年纪才发现，有些问题根本没有答案，只能带着它继续往前走。&lt;/p&gt;&#10;&lt;p&gt;技术的变化是外在的，你看得见，也能学。人生的变化是内在的，它不给你文档，不给你版本号，也不会告诉你哪一天会升级。它只是在某一天，忽然让你明白，前面已经没有人替你挡着了。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="为什么重新开始"&gt;&lt;span&gt;为什么重新开始&lt;/span&gt;&#10; &lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e9%87%8d%e6%96%b0%e5%bc%80%e5%a7%8b" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;所以为什么要在这个时候，重新把这个博客打开？&lt;/p&gt;&#10;&lt;p&gt;不是因为我又学会了一门新技术，也不是因为我想证明什么。&lt;/p&gt;&#10;&lt;p&gt;是因为我发现，&lt;strong&gt;人是需要记录的&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;当年写下的那些笔记，现在回头看，技术上的东西大半已经过时，但里面有一种东西没有过时——那时候的我，是怎么样去理解一件事的，是怎么样笨拙地、一步一步把它弄明白的。&lt;/p&gt;&#10;&lt;p&gt;那种东西，比结论更值钱。&lt;/p&gt;&#10;&lt;p&gt;这十年里我也有很多想法，很多体会，如果一直放在心里，它们会慢慢淡掉。写下来，它们才存在过。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="重新出发"&gt;&lt;span&gt;重新出发&lt;/span&gt;&#10; &lt;a href="#%e9%87%8d%e6%96%b0%e5%87%ba%e5%8f%91" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;所以，就这么重新开始了。&lt;/p&gt;&#10;&lt;p&gt;技术变了，就重新学。人生变了，就重新面对。&lt;/p&gt;&#10;&lt;p&gt;这个博客会写技术，也会写技术之外的东西——那些在代码里找不到答案的部分。&lt;/p&gt;&#10;&lt;p&gt;十年前我在这个博客的首页写：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;我来到这个世界，不是转一圈就走的，我至少要改变这个世界一下。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;十年过去，这句话我依然相信。只是现在我知道，所谓改变世界，未必是什么宏大的事。把自己弄明白一点，把懂的写下来，能让后来的人少走一点弯路，也算数。&lt;/p&gt;&#10;&lt;p&gt;那就从这里开始。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;em&gt;顺便说一下，这个站换了新的引擎：从 Hexo 换成了 Hugo，主题从 NexT 换成了 FixIt，文章源码也改成了 Markdown。旧文章都还在，地址没有变。&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>Express, Passport and JSON Web Token(jwt) 认证入门</title><link>https://zhangjinglin.github.io/2017/05/express-passport-jwt/</link><pubDate>Sat, 06 May 2017 06:04:43 +0000</pubDate><author>ldxyzjl@163.com (张景林)</author><guid>https://zhangjinglin.github.io/2017/05/express-passport-jwt/</guid><description>&lt;p&gt;在使用前端框架的时候，我们的后端平台可能是 API，我们在每次请求的时候发送 jwt 而不是依赖于 Session。我们完成两个路由功能&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;创建 &lt;code&gt;/login&lt;/code&gt; 路由请求令牌&lt;/li&gt;&#10;&lt;li&gt;创建 &lt;code&gt;/secret&lt;/code&gt; 路由，只有持有令牌的认证用户能够访问。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;首先， 创建项目目录。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;mkdir project-dir&#10;cd project-dir&#10;npm init -y&#10;yarn add express body-parser passport passport-jwt jsonwebtoken lodash&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们先来创建 &lt;code&gt;index.js&lt;/code&gt;，启动一个最简单的 express&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;// file: index.js&#10;const express = require(&amp;#39;express&amp;#39;)&#10;const app = express()&#10;&#10;app.get(&amp;#39;/&amp;#39;, (req, res) =&amp;gt; {&#10; res.json({message: &amp;#39;Express is up!&amp;#39;})&#10;})&#10;&#10;app.listen(3000, () =&amp;gt; {&#10; console.log(&amp;#39;Express running&amp;#39;)&#10;})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;启动我们的服务器&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;nodemon index.js&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;访问 &lt;code&gt;http://localhost:3000&lt;/code&gt; 查看结果。&#10;&lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/05/express-passport-jwt/express-passport-jwt-01.png' alt="/2017/05/express-passport-jwt/express-passport-jwt-01.png"&gt;&lt;/p&gt;&#10;&lt;p&gt;修改我们的 &lt;code&gt;index.js&lt;/code&gt;，导入相应的包&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const _ = require(&amp;#39;lodash&amp;#39;)&#10;const express = require(&amp;#39;express&amp;#39;)&#10;const bodyParser = require(&amp;#39;body-parser&amp;#39;)&#10;const jwt = require(&amp;#39;jsonwebtoken&amp;#39;)&#10;const passport = require(&amp;#39;passport&amp;#39;)&#10;const passportJWT = require(&amp;#39;passport-jwt&amp;#39;)&#10;&#10;const ExtractJWT = passportJWT.ExtractJWT&#10;const JwtStrategy = passportJWT.Strategy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;下面创建模拟用户数组，实际工作中这些数据应该来自数据库，但现在我们采用数组。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;let users = [&#10; {&#10; id: 1,&#10; name: &amp;#39;zhangjinglin&amp;#39;,&#10; password: &amp;#39;111111&amp;#39;&#10; },&#10; {&#10; id: 2,&#10; name: &amp;#39;test&amp;#39;,&#10; password: &amp;#39;222222&amp;#39;&#10; }&#10;]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;passport.js&lt;/code&gt; 中存在 &lt;strong&gt;策略&lt;/strong&gt; 的概念。他们是一系列的中间件，用于处理请求如何到达路由。如果定义的身份验证策略失败，这意味着回调将被调用，回调中的错误不为 &lt;code&gt;null&lt;/code&gt; 或第二个参数表示用户对象但是为 &lt;code&gt;false&lt;/code&gt;，那么该路由将不会被调用，而是将发送 &lt;code&gt;401&lt;/code&gt; 未授权的响应。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;let jwtOptions = {&#10; jwtFromRequest: ExtractJWT.fromAuthHeader(),&#10; secretOrKey: &amp;#39;zhangjinglin&amp;#39;&#10;}&#10;&#10;let strategy = new JwtStrategy(jwtOptions, (jwt_payload, next) =&amp;gt; {&#10; console.log(&amp;#39;payload received&amp;#39;, jwt_payload)&#10; let user = users[_.findIndex(users, {id: jwt_payload.id})]&#10; if (user) {&#10; next(null, user)&#10; } else {&#10; next(null, false)&#10; }&#10;})&#10;&#10;passport.use(strategy)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;使用 &lt;code&gt;passport body-parser&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;&#10;const app = express()&#10;&#10;app.use(passport.initialize())&#10;&#10;app.use(bodyParser.urlencoded({extended: true}))&#10;app.use(bodyParser.json())&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;下面我们完成登录&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;app.post(&amp;#39;/login&amp;#39;, (req, res) =&amp;gt; {&#10; if (req.body.name &amp;amp;&amp;amp; req.body.password) {&#10; let name = req.body.name&#10; let password = req.body.password&#10; //通常应该访问数据库&#10; let user = users[_.findIndex(users, { name })]&#10; if (!user) {&#10; res.status(401).json({ message: &amp;#39;no such user found&amp;#39; })&#10; }&#10; if (user.password === password) {&#10; let payload = { id: user.id }&#10; let token = jwt.sign(payload, jwtOptions.secretOrKey)&#10; res.json({message: &amp;#39;ok&amp;#39;, token})&#10; } else {&#10; res.status(401).json({message: &amp;#39;password not correct&amp;#39;})&#10; }&#10; } else {&#10; res.status(401).json({message: &amp;#39;name and password must be vaid&amp;#39;})&#10; }&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/05/express-passport-jwt/express-passport-jwt-02.png' alt="/2017/05/express-passport-jwt/express-passport-jwt-02.png"&gt;&#10;下面我们要准备保护我们的路由&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;app.get(&amp;#39;/secret&amp;#39;, passport.authenticate(&amp;#39;jwt&amp;#39;, { session: false }), (req, res) =&amp;gt; {&#10; res.json(&amp;#39;Success! You can not see this without a token&amp;#39;)&#10;})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;直接访问，不提供token&#10;&lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/05/express-passport-jwt/express-passport-jwt-03.png' alt="/2017/05/express-passport-jwt/express-passport-jwt-03.png"&gt;&lt;/p&gt;&#10;&lt;p&gt;提供token&#10;&lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/05/express-passport-jwt/express-passport-jwt-04.png' alt="/2017/05/express-passport-jwt/express-passport-jwt-04.png"&gt;&lt;/p&gt;&#10;&lt;p&gt;OK，大功告成！&lt;/p&gt;&#10;</description></item><item><title>使用基于 Knex 的 Bookshelf ORM和 Sqlite 创建 Node Express 项目</title><link>https://zhangjinglin.github.io/2017/04/bookshelf-knex/</link><pubDate>Fri, 28 Apr 2017 03:34:14 +0000</pubDate><author>ldxyzjl@163.com (张景林)</author><guid>https://zhangjinglin.github.io/2017/04/bookshelf-knex/</guid><description>&lt;p&gt;使用基于 Knex 的 Bookshelf ORM和 Sqlite 创建 Node Express 项目&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="设置项目"&gt;&lt;span&gt;设置项目&lt;/span&gt;&#10; &lt;a href="#%e8%ae%be%e7%bd%ae%e9%a1%b9%e7%9b%ae" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;mkdir project-dir&#10;cd project-dir&#10;mkdir bin server&#10;npm init -y&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="heading-element" id="express"&gt;&lt;span&gt;Express&lt;/span&gt;&#10; &lt;a href="#express" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;yarn add express body-parser morgan&#10;touch app.js&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;app.js 文件内容&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const express = require(&amp;#39;express&amp;#39;)&#10;const logger = require(&amp;#39;morgan&amp;#39;)&#10;const bodyParser = require(&amp;#39;body-parser&amp;#39;)&#10;&#10;const app = express()&#10;&#10;app.use(logger(&amp;#39;dev&amp;#39;))&#10;app.use(bodyParser.json())&#10;app.use(bodyParser.urlencoded({extended: false}))&#10;&#10;app.get(&amp;#39;*&amp;#39;, (req, res) =&amp;gt; {&#10; res.status(200).send({&#10; message: &amp;#39;Welcome to the beginning of nothingness.&amp;#39;&#10; })&#10;})&#10;&#10;module.exports = app&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在 &lt;code&gt;bin&lt;/code&gt; 目录中创建 &lt;code&gt;www&lt;/code&gt; 文件&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const http = require(&amp;#39;http&amp;#39;)&#10;const app = require(&amp;#39;../app&amp;#39;)&#10;&#10;const port = parseInt(process.env.PORT, 10) || 8000&#10;app.set(&amp;#39;port&amp;#39;, port)&#10;&#10;const server = http.createServer(app)&#10;server.listen(port, () =&amp;gt; {&#10; console.log(&amp;#39;server running on &amp;#39; &amp;#43; port)&#10;})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;为了避免每次修改代码都需要重新启动服务，我们使用 &lt;code&gt;nodemon&lt;/code&gt;。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;yarn add nodemon --dev&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改 &lt;code&gt;package.json&lt;/code&gt; 创建启动服务器的命令。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;...&#10;&amp;#34;scripts&amp;#34;: {&#10; &amp;#34;start:dev&amp;#34;: &amp;#34;nodemon ./bin/www&amp;#34;,&#10; &amp;#34;test&amp;#34;: &amp;#34;echo \&amp;#34;Error: no test specified\&amp;#34; &amp;amp;&amp;amp; exit 1&amp;#34;&#10;},&#10;...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;尝试启动服务器&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;yarn run start:dev&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;打开浏览器可以看见结果&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;{&#10; &amp;#34;message&amp;#34;: &amp;#34;Welcome to the beginning of nothingness.&amp;#34;&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="heading-element" id="knex-设置"&gt;&lt;span&gt;Knex 设置&lt;/span&gt;&#10; &lt;a href="#knex-%e8%ae%be%e7%bd%ae" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;创建 &lt;code&gt;db&lt;/code&gt; 目录，我们所有的数据库相关的东东放在这个位置。创建 &lt;code&gt;sqlite&lt;/code&gt; 数据库&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;touch db/todos-dev.db&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;安装 &lt;code&gt;Knex&lt;/code&gt; 工具&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;npm install -g knex&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;安装 &lt;code&gt;bookshelf knex sqlite&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;yarn add knex bookshelf sqlite3&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;初始化 &lt;code&gt;knex&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;knex init&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样就创建了 &lt;code&gt;knexfile.js&lt;/code&gt;，我们可以配置此文件来满足我们的需求。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;module.exports = {&#10; client: &amp;#39;sqlite3&amp;#39;,&#10; connection: {&#10; filename: &amp;#39;./db/todos-dev.db&amp;#39;&#10; },&#10; migrations: {&#10; directory: &amp;#39;./db/migrations&amp;#39;&#10; },&#10; seeds: {&#10; directory: &amp;#39;./db/seeds&amp;#39;&#10; }&#10;};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们来生成数据库迁移文件：&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;knex migrate:make create_todo&#10;knex migrate:make create_todo_item&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;/db/migrations/&amp;lt;date&amp;gt;-create_todo.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;exports.up = function(knex, Promise) {&#10; return knex.schema.createTable(&amp;#39;Todos&amp;#39;, (table) =&amp;gt; {&#10; table.increments()&#10; table.string(&amp;#39;title&amp;#39;)&#10; table.timestamps()&#10; })&#10;};&#10;&#10;exports.down = function(knex, Promise) {&#10; return knex.schema.dropTable(&amp;#39;todos&amp;#39;)&#10;};&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;/db/migrations/&amp;lt;date&amp;gt;_create_todo_item.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;&#10;exports.up = function(knex, Promise) {&#10; return knex.schema.createTable(&amp;#39;TodoItems&amp;#39;, (table) =&amp;gt; {&#10; table.increments()&#10; table.string(&amp;#39;content&amp;#39;)&#10; table.boolean(&amp;#39;complete&amp;#39;)&#10; table.integer(&amp;#39;todo_id&amp;#39;).references(&amp;#39;Todos.id&amp;#39;)&#10; table.timestamps()&#10; })&#10;};&#10;&#10;exports.down = function(knex, Promise) {&#10; return knex.schema.dropTable(&amp;#39;TodoItems&amp;#39;)&#10;};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在可以进行数据库迁移了&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;knex migrate:latest&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;下面我们来生成一些种子，以便测试。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;knex seed:make todo_seed&#10;knex seed:make todo_item_seed&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;/db/seeds/todo_seed.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;exports.seed = function(knex, Promise) {&#10; // Deletes ALL existing entries&#10; return knex(&amp;#39;Todos&amp;#39;).del()&#10; .then(function () {&#10; // Inserts seed entries&#10; return knex(&amp;#39;Todos&amp;#39;).insert([&#10; {id: 1, title: &amp;#39;The first todo&amp;#39;},&#10; {id: 2, title: &amp;#39;The second todo&amp;#39;},&#10; ]);&#10; });&#10;};&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;/db/seeds/todo_item_seed.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;exports.seed = function(knex, Promise) {&#10; // Deletes ALL existing entries&#10; return knex(&amp;#39;TodoItems&amp;#39;).del()&#10; .then(function () {&#10; // Inserts seed entries&#10; return knex(&amp;#39;TodoItems&amp;#39;).insert([&#10; {id: 1, content: &amp;#39;the content 1 in todo 1&amp;#39;, todo_id: 1},&#10; {id: 2, content: &amp;#39;the content 2 in todo 1&amp;#39;, todo_id: 1},&#10; {id: 3, content: &amp;#39;the content 1 in todo 2&amp;#39;, todo_id: 2}&#10; ]);&#10; });&#10;};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;运行种子。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;knex seed:run&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以打开 &lt;code&gt;sqlite&lt;/code&gt; 数据库控制台查看插入的结果。&lt;/p&gt;&#10;&lt;h3 class="heading-element" id="生成模型"&gt;&lt;span&gt;生成模型&lt;/span&gt;&#10; &lt;a href="#%e7%94%9f%e6%88%90%e6%a8%a1%e5%9e%8b" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h3&gt;&lt;p&gt;我们先创建 &lt;code&gt;/db/db.js&lt;/code&gt; 文件，来进行 &lt;code&gt;knex&lt;/code&gt; 和 &lt;code&gt;bookshelf&lt;/code&gt; 的初始化，因为应该在项目中使用单一实例。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const knex = require(&amp;#39;knex&amp;#39;)(require(&amp;#39;../knexfile&amp;#39;))&#10;&#10;const bookshelf = require(&amp;#39;bookshelf&amp;#39;)(knex)&#10;&#10;module.exports = {&#10; knex,&#10; bookshelf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们有两个模型 &lt;code&gt;Todo&lt;/code&gt; 和 &lt;code&gt;TodoItem&lt;/code&gt;，是一对多的关系。让我们生成模型和集合类。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;/db/models.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;var bookshelf = require(&amp;#39;./db&amp;#39;).bookshelf&#10;&#10;const Todo = bookshelf.Model.extend({&#10; tableName: &amp;#39;Todos&amp;#39;,&#10; hasTimestamps: true,&#10; items() {&#10; return this.hasMany(TodoItem, &amp;#39;todo_id&amp;#39;)&#10; }&#10;})&#10;&#10;const TodoItem = bookshelf.Model.extend({&#10; tableName: &amp;#39;TodoItems&amp;#39;,&#10; hasTimestamps: true,&#10; todo() {&#10; return this.belongsTo(Todo, &amp;#39;todo_id&amp;#39;)&#10; }&#10;})&#10;&#10;module.exports = {&#10; Todo,&#10; Todos,&#10; TodoItem,&#10; TodoItems,&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="heading-element" id="创建控制器和路由"&gt;&lt;span&gt;创建控制器和路由&lt;/span&gt;&#10; &lt;a href="#%e5%88%9b%e5%bb%ba%e6%8e%a7%e5%88%b6%e5%99%a8%e5%92%8c%e8%b7%af%e7%94%b1" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;创建文件 &lt;code&gt;controllers/todos.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const Todo = require(&amp;#39;../db/models&amp;#39;).Todo&#10;&#10;module.exports = {&#10; create(req, res) {&#10; return Todo.forge({title: req.body.title}).save()&#10; .then(todo =&amp;gt; res.status(201).send(todo))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; },&#10;&#10; list(req, res) {&#10; return Todo.forge().fetchAll()&#10; .then((todos) =&amp;gt; res.status(200).send(todos))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; },&#10;&#10; retrieve(req, res) {&#10; Todo.forge({ id: req.params.todoId }).fetch({ withRelated: [&amp;#39;items&amp;#39;] })&#10; .then(todo =&amp;gt; {&#10; if (!todo) {&#10; res.status(400).send({&#10; message: &amp;#39;Todo Not Found&amp;#39;&#10; })&#10; }&#10; res.status(200).send(todo)&#10; })&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; },&#10;&#10; update(req, res) {&#10; Todo.forge({ id: req.params.todoId }).fetch()&#10; .then(todo =&amp;gt; {&#10; if (!todo) {&#10; res.status(400).send({&#10; message: &amp;#39;Todo Not Found&amp;#39;&#10; })&#10; }&#10; todo.save({&#10; title: req.body.title || todo.title&#10; }).then(todo =&amp;gt; {&#10; res.status(200).send(todo)&#10; })&#10; })&#10; },&#10;&#10; destroy(req, res) {&#10; Todo.forge({ id: req.params.todoId }).fetch()&#10; .then(todo =&amp;gt; {&#10; if (!todo) {&#10; res.status(400).send({&#10; message: &amp;#39;Todo Not Found&amp;#39;&#10; })&#10; }&#10; todo.destroy()&#10; .then(() =&amp;gt; res.status(204).send({&#10; message: &amp;#39;Todo deleted successful&amp;#39;&#10; }))&#10; })&#10; }&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;创建文件 &lt;code&gt;controllers/todoitems.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const TodoItem = require(&amp;#39;../db/models&amp;#39;).TodoItem&#10;&#10;module.exports = {&#10; create(req, res) {&#10; // console.log(req.body.content, req.params.todoId);&#10; // return res.status(200).send({msg: &amp;#39;OK&amp;#39;})&#10; return TodoItem.forge({ content: req.body.content, todo_id: req.params.todoId }).save()&#10; .then(todoItem =&amp;gt; res.status(201).send(todoItem))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; },&#10;&#10; update(req, res) {&#10; TodoItem.forge({ id: req.params.todoItemId }).fetch()&#10; .then(todoItem =&amp;gt; {&#10; if (!todoItem) {&#10; return res.status(400).send({message: &amp;#39;TodoItem Not Found&amp;#39;})&#10; }&#10;&#10; todoItem.save({&#10; content: req.body.content || todoItem.content,&#10; complete: req.body.complete || todoItem.complete&#10; })&#10; .then(updateTodoItem =&amp;gt; res.status(200).send(updateTodoItem))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; })&#10; },&#10;&#10; destroy(req, res) {&#10; TodoItem.forge({ id: req.params.todoItemId }).fetch()&#10; .then(todoItem =&amp;gt; {&#10; if (!todoItem) {&#10; res.status(400).send({message: &amp;#39;TodoItem Not Found&amp;#39;})&#10; }&#10;&#10; todoItem.destroy()&#10; .then(res.status(204).send())&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; })&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; }&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;创建 &lt;code&gt;controllers/index.js&lt;/code&gt;，暴露我们所有的控制器&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const todos = require(&amp;#39;./todos&amp;#39;)&#10;const todoItems = require(&amp;#39;./todoitems&amp;#39;)&#10;&#10;module.exports = {&#10; todos,&#10; todoItems&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;创建 &lt;code&gt;routes/index.js&lt;/code&gt; 控制路由&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const todosController = require(&amp;#39;../controllers&amp;#39;).todos&#10;const todoItemsController = require(&amp;#39;../controllers&amp;#39;).todoItems&#10;&#10;module.exports = (app) =&amp;gt; {&#10; app.get(&amp;#39;/api&amp;#39;, (req, res) =&amp;gt; res.status(200).send({&#10; message: &amp;#39;Welcome to the Todo api&amp;#39;&#10; }))&#10; app.get(&amp;#39;/api/todos&amp;#39;, todosController.list)&#10; app.post(&amp;#39;/api/todos&amp;#39;, todosController.create)&#10; app.get(&amp;#39;/api/todos/:todoId&amp;#39;, todosController.retrieve)&#10; app.put(&amp;#39;/api/todos/:todoId&amp;#39;, todosController.update)&#10; app.delete(&amp;#39;/api/todos/:todoId&amp;#39;, todosController.destroy)&#10;&#10; app.post(&amp;#39;/api/todos/:todoId/items&amp;#39;, todoItemsController.create)&#10; app.put(&amp;#39;/api/todos/:todoId/items/:todoItemId&amp;#39;, todoItemsController.update)&#10; app.delete(&amp;#39;/api/todos/:todoId/items/:todoItemId&amp;#39;, todoItemsController.destroy)&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;修改 &lt;code&gt;app.js&lt;/code&gt; ，添加路由&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;...&#10;app.use(bodyParser.urlencoded({extended: false}))&#10;&#10;require(&amp;#39;./routes&amp;#39;)(app)&#10;&#10;app.get(&amp;#39;*&amp;#39;, (req, res) =&amp;gt; {&#10;...&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>创建基于 Sequelize ORM 和 Sqlite 的 Node Express 项目</title><link>https://zhangjinglin.github.io/2017/04/express-sequelize/</link><pubDate>Mon, 24 Apr 2017 08:12:01 +0000</pubDate><author>ldxyzjl@163.com (张景林)</author><guid>https://zhangjinglin.github.io/2017/04/express-sequelize/</guid><description>&lt;p&gt;如何使用 Sequelize ORM 来创建一个 Express 的项目&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="设置项目"&gt;&lt;span&gt;设置项目&lt;/span&gt;&#10; &lt;a href="#%e8%ae%be%e7%bd%ae%e9%a1%b9%e7%9b%ae" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;mkdir project-dir&#10;cd project-dir&#10;mkdir bin server&#10;npm init -y&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="heading-element" id="express"&gt;&lt;span&gt;Express&lt;/span&gt;&#10; &lt;a href="#express" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;yarn add express body-parser morgan&#10;touch app.js&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;app.js 文件内容&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const express = require(&amp;#39;express&amp;#39;)&#10;const logger = require(&amp;#39;morgan&amp;#39;)&#10;const bodyParser = require(&amp;#39;body-parser&amp;#39;)&#10;&#10;const app = express()&#10;&#10;app.use(logger(&amp;#39;dev&amp;#39;))&#10;app.use(bodyParser.json())&#10;app.use(bodyParser.urlencoded({extended: false}))&#10;&#10;app.get(&amp;#39;*&amp;#39;, (req, res) =&amp;gt; {&#10; res.status(200).send({&#10; message: &amp;#39;Welcome to the beginning of nothingness.&amp;#39;&#10; })&#10;})&#10;&#10;module.exports = app&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在 &lt;code&gt;bin&lt;/code&gt; 目录中创建 &lt;code&gt;www&lt;/code&gt; 文件&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const http = require(&amp;#39;http&amp;#39;)&#10;const app = require(&amp;#39;../app&amp;#39;)&#10;&#10;const port = parseInt(process.env.PORT, 10) || 8000&#10;app.set(&amp;#39;port&amp;#39;, port)&#10;&#10;const server = http.createServer(app)&#10;server.listen(port)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;为了避免每次修改代码都需要重新启动服务，我们使用 &lt;code&gt;nodemon&lt;/code&gt;。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;yarn add nodemon --dev&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改 &lt;code&gt;package.json&lt;/code&gt; 创建启动服务器的命令。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;...&#10;&amp;#34;scripts&amp;#34;: {&#10; &amp;#34;start:dev&amp;#34;: &amp;#34;nodemon ./bin/www&amp;#34;,&#10; &amp;#34;test&amp;#34;: &amp;#34;echo \&amp;#34;Error: no test specified\&amp;#34; &amp;amp;&amp;amp; exit 1&amp;#34;&#10;},&#10;...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;尝试启动服务器&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;yarn run start:dev&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;打开浏览器可以看见结果&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;{&#10; &amp;#34;message&amp;#34;: &amp;#34;Welcome to the beginning of nothingness.&amp;#34;&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="heading-element" id="sequelize-设置"&gt;&lt;span&gt;Sequelize 设置&lt;/span&gt;&#10; &lt;a href="#sequelize-%e8%ae%be%e7%bd%ae" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;安装 &lt;code&gt;Sequelize CLI&lt;/code&gt; 工具&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;npm install -g sequelize-cli&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建 &lt;code&gt;.sequelizerc&lt;/code&gt; 文件描述路径。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const path = require(&amp;#39;path&amp;#39;)&#10;&#10;module.exports = {&#10; &amp;#34;config&amp;#34;: path.resolve(&amp;#39;./server/config&amp;#39;, &amp;#39;config.json&amp;#39;),&#10; &amp;#34;models-path&amp;#34;: path.resolve(&amp;#39;./server/models&amp;#39;),&#10; &amp;#34;seeders-path&amp;#34;: path.resolve(&amp;#39;./server/seeders&amp;#39;),&#10; &amp;#34;migrations-path&amp;#34;: path.resolve(&amp;#39;./server/migrations&amp;#39;)&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;安装 &lt;code&gt;sequelize sqlite&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;yarn add sequelize sqlite3&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;初始化 &lt;code&gt;sequelize&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;sequelize init&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改 &lt;code&gt;server/models/index.js&lt;/code&gt; 从 &lt;code&gt;es5&lt;/code&gt; 修改为 &lt;code&gt;es2015&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;&amp;#39;use strict&amp;#39;;&#10;&#10;const fs = require(&amp;#39;fs&amp;#39;);&#10;const path = require(&amp;#39;path&amp;#39;);&#10;const Sequelize = require(&amp;#39;sequelize&amp;#39;);&#10;const basename = path.basename(module.filename);&#10;const env = process.env.NODE_ENV || &amp;#39;development&amp;#39;;&#10;const config = require(__dirname &amp;#43; &amp;#39;/../config/config.json&amp;#39;)[env];&#10;const db = {};&#10;&#10;let sequelize&#10;&#10;if (config.use_env_variable) {&#10; sequelize = new Sequelize(process.env[config.use_env_variable]);&#10;} else {&#10; sequelize = new Sequelize(config.database, config.username, config.password, config);&#10;}&#10;&#10;fs&#10; .readdirSync(__dirname)&#10; .filter(function(file) {&#10; return (file.indexOf(&amp;#39;.&amp;#39;) !== 0) &amp;amp;&amp;amp; (file !== basename) &amp;amp;&amp;amp; (file.slice(-3) === &amp;#39;.js&amp;#39;);&#10; })&#10; .forEach(function(file) {&#10; var model = sequelize[&amp;#39;import&amp;#39;](path.join(__dirname, file));&#10; db[model.name] = model;&#10; });&#10;&#10;Object.keys(db).forEach(function(modelName) {&#10; if (db[modelName].associate) {&#10; db[modelName].associate(db);&#10; }&#10;});&#10;&#10;db.sequelize = sequelize;&#10;db.Sequelize = Sequelize;&#10;&#10;module.exports = db;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建 &lt;code&gt;sqlite&lt;/code&gt; 数据库&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;touch todos-dev.db&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改 &lt;code&gt;server/config/config.json&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;{&#10; &amp;#34;development&amp;#34;: {&#10; &amp;#34;storage&amp;#34;: &amp;#34;todos-dev.db&amp;#34;,&#10; &amp;#34;dialect&amp;#34;: &amp;#34;sqlite&amp;#34;&#10; },&#10; &amp;#34;test&amp;#34;: {&#10; &amp;#34;storage&amp;#34;: &amp;#34;todos-test.db&amp;#34;,&#10; &amp;#34;dialect&amp;#34;: &amp;#34;sqlite&amp;#34;&#10; }&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;h3 class="heading-element" id="生成模型"&gt;&lt;span&gt;生成模型&lt;/span&gt;&#10; &lt;a href="#%e7%94%9f%e6%88%90%e6%a8%a1%e5%9e%8b" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h3&gt;&lt;p&gt;我们有两个模型 &lt;code&gt;Todo&lt;/code&gt; 和 &lt;code&gt;TodoItem&lt;/code&gt;，是一对多的关系。让我们生成 &lt;code&gt;Todo&lt;/code&gt; 模型。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;sequelize model:create --name Todo --attributes title:string&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;生成了 &lt;code&gt;server/models/todo.js&lt;/code&gt; 和 &lt;code&gt;server/migrations/&amp;lt;date&amp;gt;-create-todo.js&lt;/code&gt; 两个文件，一个是模型文件，一个是数据库迁移文件。&lt;/p&gt;&#10;&lt;p&gt;生成 &lt;code&gt;TodoItem&lt;/code&gt; 模型。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;sequelize model:create --name TodoItem --attributes content:string,complete:boolean&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改 &lt;code&gt;todo.js&lt;/code&gt;，添加关系并更改为 &lt;code&gt;es2015&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;module.exports = (sequelize, DataTypes) =&amp;gt; {&#10; const Todo = sequelize.define(&amp;#39;Todo&amp;#39;, {&#10; title: {&#10; type: DataTypes.STRING,&#10; allowNull: false,&#10; }&#10; }, {&#10; classMethods: {&#10; associate: (models) =&amp;gt; {&#10; // associations can be defined here&#10; Todo.hasMany(models.TodoItem, {&#10; foreignKey: &amp;#39;todoId&amp;#39;,&#10; as: &amp;#39;todoItems&amp;#39;,&#10; })&#10; }&#10; }&#10; });&#10; return Todo;&#10;};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改 &lt;code&gt;todoitem.js&lt;/code&gt;，添加关系并更改为 &lt;code&gt;es2015&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;module.exports = (sequelize, DataTypes) =&amp;gt; {&#10; const TodoItem = sequelize.define(&amp;#39;TodoItem&amp;#39;, {&#10; content: {&#10; type: DataTypes.STRING,&#10; allowNull: false,&#10; },&#10; complete: {&#10; type: DataTypes.BOOLEAN,&#10; defaultValue: false&#10; }&#10; }, {&#10; classMethods: {&#10; associate: (models) =&amp;gt; {&#10; // associations can be defined here&#10; TodoItem.belongsTo(models.Todo, {&#10; foreignKey: &amp;#39;todoId&amp;#39;,&#10; onDelete: &amp;#39;CASCADE&amp;#39;&#10; })&#10; }&#10; }&#10; });&#10; return TodoItem;&#10;};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;为了保持一致性，我们修改 &lt;code&gt;&amp;lt;date&amp;gt;-create-todo.js&lt;/code&gt; 迁移文件并更改为 &lt;code&gt;es2015&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;module.exports = {&#10; up: (queryInterface, Sequelize) =&amp;gt; {&#10; return queryInterface.createTable(&amp;#39;Todos&amp;#39;, {&#10; id: {&#10; allowNull: false,&#10; autoIncrement: true,&#10; primaryKey: true,&#10; type: Sequelize.INTEGER&#10; },&#10; title: {&#10; type: Sequelize.STRING,&#10; allowNull: false&#10; },&#10; createdAt: {&#10; allowNull: false,&#10; type: Sequelize.DATE&#10; },&#10; updatedAt: {&#10; allowNull: false,&#10; type: Sequelize.DATE&#10; }&#10; });&#10; },&#10; down: (queryInterface, Sequelize) =&amp;gt; {&#10; return queryInterface.dropTable(&amp;#39;Todos&amp;#39;);&#10; }&#10;};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;同样处理 &lt;code&gt;&amp;lt;date&amp;gt;-create-todo-item.js&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;&amp;#39;use strict&amp;#39;;&#10;module.exports = {&#10; up: (queryInterface, Sequelize) =&amp;gt; {&#10; return queryInterface.createTable(&amp;#39;TodoItems&amp;#39;, {&#10; id: {&#10; allowNull: false,&#10; autoIncrement: true,&#10; primaryKey: true,&#10; type: Sequelize.INTEGER&#10; },&#10; content: {&#10; type: Sequelize.STRING,&#10; allowNull: false,&#10; },&#10; complete: {&#10; type: Sequelize.BOOLEAN,&#10; defaultValue: false,&#10; },&#10; createdAt: {&#10; allowNull: false,&#10; type: Sequelize.DATE&#10; },&#10; updatedAt: {&#10; allowNull: false,&#10; type: Sequelize.DATE&#10; },&#10; todoId: {&#10; type: Sequelize.INTEGER,&#10; onDelete: &amp;#39;CASCADE&amp;#39;,&#10; refrences: {&#10; model: &amp;#39;Todos&amp;#39;,&#10; key: &amp;#39;id&amp;#39;,&#10; as: &amp;#39;todoId&amp;#39;&#10; }&#10; }&#10; });&#10; },&#10; down: (queryInterface, Sequelize) =&amp;gt; {&#10; return queryInterface.dropTable(&amp;#39;TodoItems&amp;#39;);&#10; }&#10;};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在我们可以进行数据库迁移了&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;sequelize db:migrate&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="heading-element" id="创建控制器和路由"&gt;&lt;span&gt;创建控制器和路由&lt;/span&gt;&#10; &lt;a href="#%e5%88%9b%e5%bb%ba%e6%8e%a7%e5%88%b6%e5%99%a8%e5%92%8c%e8%b7%af%e7%94%b1" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;创建文件 &lt;code&gt;server/controllers/todos.js&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const Todo = require(&amp;#39;../models&amp;#39;).Todo&#10;&#10;module.exports = {&#10; create(req, res) {&#10; return Todo.create({&#10; title: req.body.title,&#10; })&#10; .then(todo =&amp;gt; res.status(201).send(todo))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; }&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建 &lt;code&gt;server/controllers/index.js&lt;/code&gt;，暴露我们所有的控制器&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const Todo = require(&amp;#39;../models&amp;#39;).Todo&#10;&#10;module.exports = {&#10; create(req, res) {&#10; return Todo.create({&#10; title: req.body.title,&#10; })&#10; .then(todo =&amp;gt; res.status(201).send(todo))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; }&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建 &lt;code&gt;server/controllers/index.js&lt;/code&gt;，暴露我们所有的控制器&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const todos = require(&amp;#39;./todos&amp;#39;)&#10;&#10;module.exports = {&#10; todos,&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建 &lt;code&gt;server/routes/index.js&lt;/code&gt; 控制路由&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const todosController = require(&amp;#39;../controllers&amp;#39;).todos&#10;&#10;module.exports = (app) =&amp;gt; {&#10; app.get(&amp;#39;/api&amp;#39;, (req, res) =&amp;gt; res.status(200).send({&#10; message: &amp;#39;Welcome to the Todos API&amp;#39;&#10; }))&#10; app.post(&amp;#39;/api/todos&amp;#39;, todosController.create)&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改 &lt;code&gt;app.js&lt;/code&gt; ，添加路由&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;...&#10;app.use(bodyParser.urlencoded({extended: false}))&#10;&#10;require(&amp;#39;./server/routes/&amp;#39;)(app)&#10;&#10;app.get(&amp;#39;*&amp;#39;, (req, res) =&amp;gt; {&#10;...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;启动 &lt;code&gt;postman&lt;/code&gt; 来进行测试&#10;&lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/04/express-sequelize/node-sequelize-sqlite-53958.png' alt="/2017/04/express-sequelize/node-sequelize-sqlite-53958.png"&gt;&lt;/p&gt;&#10;&lt;h3 class="heading-element" id="查询全部待办事项"&gt;&lt;span&gt;查询全部待办事项&lt;/span&gt;&#10; &lt;a href="#%e6%9f%a5%e8%af%a2%e5%85%a8%e9%83%a8%e5%be%85%e5%8a%9e%e4%ba%8b%e9%a1%b9" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h3&gt;&lt;p&gt;修改 &lt;code&gt;server/controllers/todos.js&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const Todo = require(&amp;#39;../models&amp;#39;).Todo&#10;&#10;module.exports = {&#10; create(req, res) {&#10; ...&#10; },&#10;&#10; list(req, res) {&#10; return Todo&#10; .all()&#10; .then(todos =&amp;gt; res.status(200).send(todos))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; }&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改路由文件 &lt;code&gt;server/routes/index.js&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const todosController = require(&amp;#39;../controllers&amp;#39;).todos&#10;&#10;module.exports = (app) =&amp;gt; {&#10; ...&#10; app.post(&amp;#39;/api/todos&amp;#39;, todosController.create)&#10; app.get(&amp;#39;/api/todos&amp;#39;, todosController.list)&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;使用 &lt;code&gt;postman&lt;/code&gt; 测试新路由&#10;&lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/04/express-sequelize/node-sequelize-sqlite-bc8c9.png' alt="/2017/04/express-sequelize/node-sequelize-sqlite-bc8c9.png"&gt;&lt;/p&gt;&#10;&lt;h3 class="heading-element" id="创建-todo-items"&gt;&lt;span&gt;创建 Todo Items&lt;/span&gt;&#10; &lt;a href="#%e5%88%9b%e5%bb%ba-todo-items" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h3&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/controllers/todoitems.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const TodoItem = require(&amp;#39;../models&amp;#39;).TodoItem&#10;&#10;module.exports = {&#10; create(req, res) {&#10; return TodoItem&#10; .create({&#10; content: req.body.content,&#10; todoId: req.params.todoId,&#10; })&#10; .then(todoItem =&amp;gt; res.status(201).send(todoItem))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; },&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/controllers/index.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const todos = require(&amp;#39;./todos&amp;#39;)&#10;const todoItems = require(&amp;#39;./todoitems&amp;#39;)&#10;&#10;module.exports = {&#10; todos,&#10; todoItems,&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/routes/index.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const todosController = require(&amp;#39;../controllers&amp;#39;).todos&#10;const todoItemsController = require(&amp;#39;../controllers&amp;#39;).todoItems&#10;&#10;module.exports = (app) =&amp;gt; {&#10; ...&#10; app.get(&amp;#39;/api/todos&amp;#39;, todosController.list)&#10; app.post(&amp;#39;/api/todos/:todoId/items&amp;#39;, todoItemsController.create)&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;postman 测试 &lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/04/express-sequelize/node-sequelize-sqlite-6d615.png' alt="/2017/04/express-sequelize/node-sequelize-sqlite-6d615.png"&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;查询某个 todo 中的所有 todo-items &lt;code&gt;server/controllers/todos.js&lt;/code&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const Todo = require(&amp;#39;../models&amp;#39;).Todo&#10;const TodoItem = require(&amp;#39;../models&amp;#39;).TodoItem&#10;&#10;module.exports = {&#10; create(req, res) {&#10; ...&#10; },&#10;&#10; list(req, res) {&#10; return Todo&#10; .findAll({&#10; include: [{&#10; model: TodoItem,&#10; as: &amp;#39;todoItems&amp;#39;&#10; }]&#10; })&#10; .then(todos =&amp;gt; res.status(200).send(todos))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; },&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/04/express-sequelize/node-sequelize-sqlite-4d4d2.png' alt="/2017/04/express-sequelize/node-sequelize-sqlite-4d4d2.png"&gt;&lt;/p&gt;&#10;&lt;h3 class="heading-element" id="获取指定-todo"&gt;&lt;span&gt;获取指定 todo&lt;/span&gt;&#10; &lt;a href="#%e8%8e%b7%e5%8f%96%e6%8c%87%e5%ae%9a-todo" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h3&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/controllers/todos.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const Todo = require(&amp;#39;../models&amp;#39;).Todo&#10;const TodoItem = require(&amp;#39;../models&amp;#39;).TodoItem&#10;&#10;module.exports = {&#10; create(req, res) {&#10; ...&#10; },&#10;&#10; list(req, res) {&#10; ...&#10; },&#10;&#10; retrieve(req, res) {&#10; return Todo&#10; .findById(req.params.todoId, {&#10; include: [{&#10; model: TodoItem,&#10; as: &amp;#39;todoItems&amp;#39;&#10; }]&#10; })&#10; .then(todo =&amp;gt; {&#10; if (!todo) {&#10; return res.status(400).send({&#10; message: &amp;#39;Todo Not Found&amp;#39;&#10; })&#10; }&#10; return res.status(200).send(todo)&#10; })&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; },&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;修改路由 &lt;code&gt;server/routes/index.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const todosController = require(&amp;#39;../controllers&amp;#39;).todos&#10;const todoItemsController = require(&amp;#39;../controllers&amp;#39;).todoItems&#10;&#10;module.exports = (app) =&amp;gt; {&#10; ...&#10; app.get(&amp;#39;/api/todos/:todoId&amp;#39;, todosController.retrieve)&#10; ...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/04/express-sequelize/node-sequelize-sqlite-5084d.png' alt="/2017/04/express-sequelize/node-sequelize-sqlite-5084d.png"&gt;&lt;/p&gt;&#10;&lt;h3 class="heading-element" id="更新一个-todo"&gt;&lt;span&gt;更新一个 todo&lt;/span&gt;&#10; &lt;a href="#%e6%9b%b4%e6%96%b0%e4%b8%80%e4%b8%aa-todo" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h3&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/controllers/todos.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;update(req, res) {&#10; return Todo&#10; .findById(req.params.todoId, {&#10; include: [{&#10; model: TodoItem,&#10; as: &amp;#39;todoItems&amp;#39;&#10; }]&#10; })&#10; .then(todo =&amp;gt; {&#10; if (!todo) {&#10; return res.status(400).send({&#10; message: &amp;#39;Todo Not Found&amp;#39;&#10; })&#10; }&#10; return todo&#10; .update({&#10; title: req.body.title || todo.title,&#10; })&#10; .then(() =&amp;gt; res.status(200).send(todo))&#10; .catch((error) =&amp;gt; res.status(400).send(error))&#10; })&#10;},&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/routes/index.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;...&#10;app.put(&amp;#39;/api/todos/:todoId&amp;#39;, todosController.update)&#10;...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/04/express-sequelize/node-sequelize-sqlite-6835a.png' alt="/2017/04/express-sequelize/node-sequelize-sqlite-6835a.png"&gt;&lt;/p&gt;&#10;&lt;h3 class="heading-element" id="删除-todo"&gt;&lt;span&gt;删除 todo&lt;/span&gt;&#10; &lt;a href="#%e5%88%a0%e9%99%a4-todo" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h3&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/controllers/todos.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;destroy(req, res) {&#10; return Todo&#10; .findById(req.params.todoId)&#10; .then(todo =&amp;gt; {&#10; if (!todo) {&#10; return res.status(400).send({&#10; message: &amp;#39;Todo Not Found&amp;#39;&#10; })&#10; }&#10;&#10; return todo&#10; .destroy()&#10; .then(() =&amp;gt; res.status(204).send())&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; })&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/routes/index.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;...&#10;app.delete(&amp;#39;/api/todos/:todoId&amp;#39;, todosController.destroy)&#10;...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img loading="lazy" src='https://zhangjinglin.github.io/2017/04/express-sequelize/node-sequelize-sqlite-9d858.png' alt="/2017/04/express-sequelize/node-sequelize-sqlite-9d858.png"&gt;&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="更新和删除-todo-items"&gt;&lt;span&gt;更新和删除 todo items&lt;/span&gt;&#10; &lt;a href="#%e6%9b%b4%e6%96%b0%e5%92%8c%e5%88%a0%e9%99%a4-todo-items" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/controllers/todoitems.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const TodoItem = require(&amp;#39;../models&amp;#39;).TodoItem&#10;&#10;module.exports = {&#10; create(req, res) {&#10; return TodoItem&#10; .create({&#10; content: req.body.content,&#10; todoId: req.params.todoId,&#10; })&#10; .then(todoItem =&amp;gt; res.status(201).send(todoItem))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; },&#10;&#10; update(req, res) {&#10; return TodoItem&#10; .find({&#10; where: {&#10; id: req.params.todoItemId,&#10; },&#10; })&#10; .then(todoItem =&amp;gt; {&#10; if (!todoItem) {&#10; return res.status(400).send({message: &amp;#39;TodoItem Not Found&amp;#39;})&#10; }&#10;&#10; return todoItem&#10; .update({&#10; content: req.body.content || todoItem.content,&#10; complete: req.body.complete || todoItem.complete&#10; })&#10; .then(updatedTodoItem =&amp;gt; res.status(200).send(updatedTodoItem))&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; })&#10; .catch(error =&amp;gt; res.status(400).send(error));&#10; },&#10;&#10; destroy(req, res) {&#10; return TodoItem&#10; .find({&#10; where: {&#10; id: req.params.todoItemId,&#10; },&#10; })&#10; .then(todoItem =&amp;gt; {&#10; if (!todoItem) {&#10; return res.status(400).send({message: &amp;#39;TodoItem Not Found&amp;#39;})&#10; }&#10; return todoItem&#10; .destroy()&#10; .then(() =&amp;gt; res.status(204).send())&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; })&#10; .catch(error =&amp;gt; res.status(400).send(error))&#10; }&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/routes/index.js&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;const todosController = require(&amp;#39;../controllers&amp;#39;).todos&#10;const todoItemsController = require(&amp;#39;../controllers&amp;#39;).todoItems&#10;&#10;module.exports = (app) =&amp;gt; {&#10; app.get(&amp;#39;/api&amp;#39;, (req, res) =&amp;gt; res.status(200).send({&#10; message: &amp;#39;Welcome to the Todos API&amp;#39;&#10; }))&#10; app.post(&amp;#39;/api/todos&amp;#39;, todosController.create)&#10; app.get(&amp;#39;/api/todos&amp;#39;, todosController.list)&#10; app.get(&amp;#39;/api/todos/:todoId&amp;#39;, todosController.retrieve)&#10; app.put(&amp;#39;/api/todos/:todoId&amp;#39;, todosController.update)&#10; app.delete(&amp;#39;/api/todos/:todoId&amp;#39;, todosController.destroy)&#10;&#10; app.post(&amp;#39;/api/todos/:todoId/items&amp;#39;, todoItemsController.create)&#10; app.put(&amp;#39;/api/todos/:todoId/items/:todoItemId&amp;#39;, todoItemsController.update)&#10; app.delete(&amp;#39;/api/todos/:todoId/items/:todoItemId&amp;#39;, todoItemsController.destroy)&#10;&#10; app.all(&amp;#39;/api/todos/:todoId/items&amp;#39;, (req, res) =&amp;gt; {&#10; res.status(405).send({&#10; message: &amp;#39;Method Not Allowed&amp;#39;&#10; })&#10; })&#10;}&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Harp.js</title><link>https://zhangjinglin.github.io/2017/04/harpjs/</link><pubDate>Fri, 07 Apr 2017 08:29:05 +0000</pubDate><author>ldxyzjl@163.com (张景林)</author><guid>https://zhangjinglin.github.io/2017/04/harpjs/</guid><description>&lt;h1 class="heading-element" id="harpjs"&gt;&lt;span&gt;Harp.js?&lt;/span&gt;&#10; &lt;a href="#harpjs" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h1&gt;&lt;p&gt;&lt;a href="http://harpjs.com/" target="_blank" rel="external nofollow noopener noreferrer"&gt;Harp.js&lt;i class="fa-solid fa-external-link-alt fa-xs ms-1 text-secondary" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;The static web server with built-in preprocessing.&#10;Harp serves Jade, Markdown, EJS, CoffeeScript, Sass, LESS and Stylus as HTML, CSS &amp;amp; JavaScript—no configuration necessary.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 class="heading-element" id="how-to-use"&gt;&lt;span&gt;How to use&lt;/span&gt;&#10; &lt;a href="#how-to-use" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;ol&gt;&#10;&lt;li&gt;install&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;pre&gt;&lt;code&gt;npm i -g harp&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&#10;&lt;li&gt;create website&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;pre&gt;&lt;code&gt;harp init hello-harp&#10;cd hello-harp&#10;harp server&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&#10;&lt;li&gt;compile&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;pre&gt;&lt;code&gt;harp compile&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&#10;&lt;li&gt;metadata&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;_data.json&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;&amp;#34;team&amp;#34;: {&#10; &amp;#34;title&amp;#34;: &amp;#34;Team page&amp;#34;,&#10; &amp;#34;members&amp;#34;: [&amp;#34;Rob&amp;#34;, &amp;#34;Brock&amp;#34;, &amp;#34;Jorge&amp;#34;]&#10;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#10;&lt;li&gt;ejs or jade&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;pre&gt;&lt;code&gt;h1= title&#10;ul&#10; each member in members&#10; li= member&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>JavaScript 函数式编程（二）</title><link>https://zhangjinglin.github.io/2017/03/function-javascript-02/</link><pubDate>Mon, 27 Mar 2017 05:39:51 +0000</pubDate><author>ldxyzjl@163.com (张景林)</author><guid>https://zhangjinglin.github.io/2017/03/function-javascript-02/</guid><description>&lt;h1 class="heading-element" id="高阶函数higher-order-functions"&gt;&lt;span&gt;高阶函数（Higher-Order Functions）&lt;/span&gt;&#10; &lt;a href="#%e9%ab%98%e9%98%b6%e5%87%bd%e6%95%b0higher-order-functions" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h1&gt;&lt;pre&gt;&lt;code&gt;const forEach = (array, fn) =&amp;gt; {&#10; let i&#10; for (i = 0; i &amp;lt; array.length; i&amp;#43;&amp;#43;) {&#10; fn(array[i])&#10; }&#10;}&#10;&#10;let array = [1, 2, 3]&#10;forEach(array, (data) =&amp;gt; console.log(data))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在我们已经看到了如何创建和执行高阶函数。 一般来说，高阶函数通常用来抽象常见的问题。 换句话说，高阶函数只不过是定义抽象。&lt;/p&gt;&#10;&lt;p&gt;forEach本质上是遍历数组。 如何遍历JavaScript对象？遍历JavaScript对象具有以下步骤：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;迭代给定对象的所有键。&lt;/li&gt;&#10;&lt;li&gt;确定键属于自己的对象。&lt;/li&gt;&#10;&lt;li&gt;如果步骤2为真，则获取键的值。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;pre&gt;&lt;code&gt;const forEachObject = (obj, fn) =&amp;gt; {&#10; for (let prop in obj) {&#10; if (obj.hasOwnProperty(prop)) {&#10; fn(prop, obj[prop])&#10; }&#10; }&#10;}&#10;&#10;let object = {a: 1, b: 2}&#10;forEachObject(object, (k, v) =&amp;gt; console.log(k &amp;#43; &amp;#34;:&amp;#34; &amp;#43; v))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;让我们创建一个名为&lt;code&gt;unless&lt;/code&gt;的函数，这是一个简单的函数，它需要一个断言（应该是true或false）; 如果断言是假，则调用fn。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const forEach = (array, fn) =&amp;gt; {&#10; let i&#10; for (i = 0; i &amp;lt; array.length; i&amp;#43;&amp;#43;) {&#10; fn(array[i])&#10; }&#10;}&#10;&#10;const unless = (predicate, fn) =&amp;gt; {&#10; if (!predicate)&#10; fn()&#10;}&#10;&#10;forEach([1,2,3,4,5,6,7], (number) =&amp;gt; {&#10; unless((number % 2), () =&amp;gt; {&#10; console.log(number, &amp;#39; is even&amp;#39;)&#10; })&#10;})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在上述情况下，我们从数组列表中获取偶数。 如果我们要从0到100获得偶数的列表怎么办？ 我们不能在这里使用（当然我们可以，如果我们有数组有[0,1,2 …..，100]）。 我们来看看另一个叫做times的高阶函数。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const unless = (predicate, fn) =&amp;gt; {&#10; if (!predicate)&#10; fn()&#10;}&#10;&#10;const times = (times, fn) =&amp;gt; {&#10; for (let i = 0; i &amp;lt; times; i&amp;#43;&amp;#43;)&#10; fn(i)&#10;}&#10;&#10;times(20, (n) =&amp;gt; {&#10; unless(n % 2, () =&amp;gt; console.log(n, &amp;#39;is even&amp;#39;))&#10;})&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="heading-element" id="现实世界中的高阶函数"&gt;&lt;span&gt;现实世界中的高阶函数&lt;/span&gt;&#10; &lt;a href="#%e7%8e%b0%e5%ae%9e%e4%b8%96%e7%95%8c%e4%b8%ad%e7%9a%84%e9%ab%98%e9%98%b6%e5%87%bd%e6%95%b0" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;h3 class="heading-element" id="every-function"&gt;&lt;span&gt;every Function&lt;/span&gt;&#10; &lt;a href="#every-function" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h3&gt;&lt;p&gt;通常作为JavaScript开发人员，我们需要检查内容数组是否是数字，自定义对象或其他内容。 我们通常写典型的循环方法来解决这些问题。 但是让我们把它们抽成一个叫做&lt;code&gt;every&lt;/code&gt;函数。 每个函数都有两个参数：数组和函数。 它检查数组的所有元素是否被传递函数求值为true。ES6规格中， for..of循环可用于迭代数组元素&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const every = (arr, fn) =&amp;gt; {&#10; let result = true;&#10; for (const value of arr) {&#10; result = result &amp;amp;&amp;amp; fn(value)&#10; }&#10;&#10; return result&#10;}&#10;&#10;every([NaN, NaN, NaN], isNaN) // true&#10;every([NaN, NaN, 4], isNaN) // false&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;every 一个典型的高阶函数，易于实现，同时它也非常有用！&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="some-function"&gt;&lt;span&gt;some Function&lt;/span&gt;&#10; &lt;a href="#some-function" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;类似于&lt;code&gt;every&lt;/code&gt;函数，我们也有一个称为&lt;code&gt;some&lt;/code&gt;的函数。 &lt;code&gt;some&lt;/code&gt;函数与&lt;code&gt;every&lt;/code&gt;函数完全相反，如果数组中的任何一个元素对传递的函数返回true，那么&lt;code&gt;some&lt;/code&gt;函数将返回true。 &lt;code&gt;some&lt;/code&gt;函数也被称为&lt;code&gt;any&lt;/code&gt;函数。 为了实现&lt;code&gt;some&lt;/code&gt;函数，我们使用|| 而不是 &amp;amp;&amp;amp;&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const some = (arr, fn) =&amp;gt; {&#10; let result = false;&#10; for (const value of arr) {&#10; result = result || fn(value)&#10; }&#10;&#10; return(result)&#10;}&#10;&#10;console.log(some([NaN, NaN, 4], isNaN)) // true&#10;console.log(some([2, 3, 4], isNaN)) // false&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="heading-element" id="sort-function"&gt;&lt;span&gt;sort Function&lt;/span&gt;&#10; &lt;a href="#sort-function" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;sort&lt;/code&gt;是JavaScript的Array原型中提供的内置函数。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;var fruit = [&amp;#39;cherries&amp;#39;, &amp;#39;apples&amp;#39;, &amp;#39;bananas&amp;#39;];&#10;console.log(fruit.sort())&#10;// [&amp;#34;apples&amp;#34;, &amp;#34;bananas&amp;#34;, &amp;#34;cherries&amp;#34;]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;排序函数是一个高阶函数，它接受一个函数作为其参数，这将有助于排序函数来决定排序逻辑。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;arr.sort([compareFunction])&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里的compareFunction是可选的。 如果没有提供compareFunction，则通过将元素转换为字符串并以Unicode代码顺序对比字符串进行排序。 为了在执行排序时将元素与我们自己的逻辑进行比较，我们需要设计我们自己compareFunction。 我们可以感觉到排序函数的设计如此灵活，以便它可以对JavaScript世界中的任何数据进行排序，只要我们通过一个compareFunction。 排序功能是灵活的，由于高阶功能的本质！&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const people = [&#10; {firstname: &amp;#34;aaFirstName&amp;#34;, lastname: &amp;#34;cclastName&amp;#34;},&#10; {firstname: &amp;#34;ccFirstName&amp;#34;, lastname: &amp;#34;aalastName&amp;#34;},&#10; {firstname:&amp;#34;bbFirstName&amp;#34;, lastname:&amp;#34;bblastName&amp;#34;}&#10;]&#10;&#10;// compare firstname&#10;people.sort((a, b) =&amp;gt; {&#10; return (a.firstname &amp;lt; b.firstname) ? -1 : (a.firstname &amp;gt; b.firstname) ? 1 : 0&#10;})&#10;&#10;// compare lastname&#10;people.sort((a,b) =&amp;gt; {&#10; return (a.lastname &amp;lt; b.lastname) ? -1 : (a.lastname &amp;gt; b.lastname) ? 1 : 0&#10;})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们的compareFunction的算法，可以做得更好吗？ 我们可以将上述逻辑抽象成一个函数吗,而不是每次写比较函数？ 正如你在上面的例子中看到的，我们写了两个函数，用于比较firstName和lastName几乎相同的重复代码。 我们用高阶函数来解决这个问题。 现在我们要设计的函数将不会以函数为参数，而是返回一个函数。 （记住HOC也可以返回一个函数）。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;const people = [&#10; {firstname: &amp;#34;aaFirstName&amp;#34;, lastname: &amp;#34;cclastName&amp;#34;},&#10; {firstname: &amp;#34;ccFirstName&amp;#34;, lastname: &amp;#34;aalastName&amp;#34;},&#10; {firstname:&amp;#34;bbFirstName&amp;#34;, lastname:&amp;#34;bblastName&amp;#34;}&#10;]&#10;&#10;const sortBy = (property) =&amp;gt; {&#10; return (a, b) =&amp;gt; {&#10; return (a[property] &amp;lt; b[property]) ? -1 : (a[property] &amp;gt; b[property]) ? 1 : 0&#10; }&#10;}&#10;&#10;people.sort(sortBy(&amp;#39;lastname&amp;#39;))&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>JavaScript 函数编程（一）</title><link>https://zhangjinglin.github.io/2017/03/function-javascrit-01/</link><pubDate>Mon, 27 Mar 2017 05:15:08 +0000</pubDate><author>ldxyzjl@163.com (张景林)</author><guid>https://zhangjinglin.github.io/2017/03/function-javascrit-01/</guid><description>&lt;h1 class="heading-element" id="函数式编程的简单术语"&gt;&lt;span&gt;函数式编程的简单术语&lt;/span&gt;&#10; &lt;a href="#%e5%87%bd%e6%95%b0%e5%bc%8f%e7%bc%96%e7%a8%8b%e7%9a%84%e7%ae%80%e5%8d%95%e6%9c%af%e8%af%ad" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h1&gt;&lt;h2 class="heading-element" id="什么是函数式编程为什么很重要"&gt;&lt;span&gt;什么是函数式编程？为什么很重要？&lt;/span&gt;&#10; &lt;a href="#%e4%bb%80%e4%b9%88%e6%98%af%e5%87%bd%e6%95%b0%e5%bc%8f%e7%bc%96%e7%a8%8b%e4%b8%ba%e4%bb%80%e4%b9%88%e5%be%88%e9%87%8d%e8%a6%81" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;数学中的函数&lt;/p&gt;&#10;&lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;mo stretchy="false"&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo stretchy="false"&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;Y&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;f(x)= Y&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="katex-base"&gt;&lt;span class="katex-strut" style="height:1em;vertical-align:-0.25em;"&gt;&lt;/span&gt;&lt;span class="mord mathnormal" style="margin-right:0.1076em;"&gt;f&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2778em;"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2778em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="katex-base"&gt;&lt;span class="katex-strut" style="height:0.6833em;"&gt;&lt;/span&gt;&lt;span class="mord mathnormal" style="margin-right:0.2222em;"&gt;Y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;p&gt;函数F，它以X为参数，并返回输出Y。&#10;在定义中有重要的含义：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;函数必须有参数。&lt;/li&gt;&#10;&lt;li&gt;函数必须始终返回一个值。&lt;/li&gt;&#10;&lt;li&gt;函数只能对其接收参数（即X）起作用，而不是外部。&lt;/li&gt;&#10;&lt;li&gt;对于给定的X，将只有一个Y。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;函数式编程技术主要基于数学函数及其思想，我们将会看到数学函数的想法以及如何使用它们来理解函数式编程。&lt;/p&gt;&#10;&lt;p&gt;想象一下，我们必须编写一个执行税收计算的函数。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;var percentValue = 5;&#10;var calculateTax = (value) =&amp;gt; { return value/100 * (100 &amp;#43; percentValue) }&#10;console.log(calculateTax(100))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;以上的功能calculateTax完全符合我们要做的。 看起来整洁，不是吗？ 我们暂停一下，就我们的数学定义分析上述功能。 我们的数学函数术语的关键之一是函数逻辑不应该依赖于外部世界。 在我们上面定义的函数calculateTax中，我们使函数取决于全局变量percentValue。 因此，我们创建的上述函数在数学意义上不能被称为真正的函数。 所以我们来解决一下。&lt;/p&gt;&#10;&lt;p&gt;修复很简单：我们只需要将percentValue作为我们的函数参数：&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;var calculateTax = (value, percentValue) =&amp;gt; { return value/100 * (100 &amp;#43; percentValue) }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在我们的函数calculateTax可以被称为真正的函数。 我们刚刚在calculateTax函数中消除了全局变量访问。 删除函数内的全局变量访问使得测试变得容易。&#10;函数式编程是一个范例，我们将在其中创建功能，通过依赖于其输入来实现其逻辑。 这样可以确保在多次调用时函数将返回相同的结果。 该功能也不会改变外部任何数据，导致可高速缓存和可测试的代码库。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="引用透明referential-transparency"&gt;&lt;span&gt;引用透明（Referential Transparency）&lt;/span&gt;&#10; &lt;a href="#%e5%bc%95%e7%94%a8%e9%80%8f%e6%98%8ereferential-transparency" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;所有的函数都将为相同的输入返回相同的值，而把这个特征称为引用透明。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;var identity = (i) =&amp;gt; { return i }&#10;sum(4,5) &amp;#43; identity(1)&#10;//使用我们的引用透明定义，我们可以将上述语句转换为：&#10;sum(4,5) &amp;#43; 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在这个过程称为替代模型(Substitution model)，因为您可以直接用返回值替换函数的结果（主要是因为函数的逻辑不依赖于其他全局变量）。&lt;/p&gt;&#10;&lt;p&gt;这可以产生并行代码和缓存。&lt;/p&gt;&#10;&lt;p&gt;想象一下，使用这个模型，您可以轻松地使用多个线程运行上述函数，甚至不需要同步！为什么？同步的原因来自线程在并行运行时不应该对全局数据采取行动的事实。&lt;/p&gt;&#10;&lt;p&gt;而且由于函数将为给定的输入返回相同的值，所以实际上可以缓存它！例如，假设有一个叫做“factorial”的函数，它计算给定数字的阶乘。 我们都知道’5’的阶乘将是’120’。如果用户第二次调用“5”的“factorial”怎么办？如果“因子”函数遵循参照透明度，那么我们知道结果将像以前一样是“120”（并且只取决于输入参数）。考虑到这个特征，我们可以缓存我们的“factorial”函数的值。因此，如果第二次调用“factorial”，输入为“5”，我们可以返回缓存的值而不是再次计算。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="强制性声明性抽象性"&gt;&lt;span&gt;强制性，声明性，抽象性&lt;/span&gt;&#10; &lt;a href="#%e5%bc%ba%e5%88%b6%e6%80%a7%e5%a3%b0%e6%98%8e%e6%80%a7%e6%8a%bd%e8%b1%a1%e6%80%a7" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;假设你有一个列表或数组，并想要遍历数组并将其打印到控制台。 代码可能如下所示：&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;var array = [1,2,3]&#10;for(i=0;i&amp;lt;array.length;i&amp;#43;&amp;#43;)&#10; console.log(array[i])&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;工作正常但是，在上述解决问题的方法中，我们正在准确地告诉我们如何做到这一点。例如，我们已经编写了一个隐含的for循环，并使用索引计算数组长度并打印项目。这里的任务是什么？ “打印数组元素”对吧？但是看起来我们正在告诉编译器该怎么做。在这种情况下，我们告诉“获取数组长度，循环我们的数组，使用索引获取数组的每个元素”。我们称之为“命令式”解决方案。&lt;/p&gt;&#10;&lt;p&gt;命令编程就是告诉编译器“怎么做”事情。&lt;/p&gt;&#10;&lt;p&gt;我们现在将切换到硬币的另一面，声明式编程。在声明式编程中，我们将要告诉编译器需要做什么，而不是“如何”的部分。 “如何”部分被抽象为共同的功能（这些功能被称为高阶函数，我们将在后面介绍）。现在我们可以使用内建的forEach函数来迭代数组并进行打印。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;var array = [1,2,3]&#10;array.forEach((element) =&amp;gt; console.log(element))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上面的代码段打印完全相同的输出。 但是在这里我们已经删除了“如何”的部分，如“获取数组长度，循环我们的数组，使用索引获取数组的每个元素”等。我们使用了一个抽象的函数，它实现了“如何”部分。&lt;/p&gt;&#10;&lt;p&gt;函数式编程是以抽象的方式创建函数，可以由代码的其他部分重用。&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="纯函数pure-functions"&gt;&lt;span&gt;纯函数（Pure Functions）&lt;/span&gt;&#10; &lt;a href="#%e7%ba%af%e5%87%bd%e6%95%b0pure-functions" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;纯函数是为给定输入返回相同输出的函数。&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;var double = (value) =&amp;gt; value * 2;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上述函数“double”是一个纯函数，只是因为给定一个输入，它将始终返回相同的输出。 使用输入5调用总是将结果返回为10！ 纯函数服从引用透明。 因此，我们可以用10替换双（5），没有任何犹豫。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;纯函数产生可测试代码&lt;/li&gt;&#10;&lt;li&gt;纯函数产生合理的代码&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 class="heading-element" id="并行代码"&gt;&lt;span&gt;并行代码&lt;/span&gt;&#10; &lt;a href="#%e5%b9%b6%e8%a1%8c%e4%bb%a3%e7%a0%81" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;纯函数允许我们并行运行代码。 由于纯函数不会改变其任何环境，所以这意味着我们不需要担心所有的同步！&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="缓存"&gt;&lt;span&gt;缓存&lt;/span&gt;&#10; &lt;a href="#%e7%bc%93%e5%ad%98" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;由于纯函数总是为给定的输入返回相同的输出，我们可以缓存功能输出。 想象一下，我们有一个功能耗时的计算，我们将这个函数命名为longRunningFunction&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;var longRunningFunction = (ip) =&amp;gt; { //do long running tasks and return }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果longRunningFunction函数是一个纯函数，那么我们知道对于给定的输入，它将返回给我们相同的输出！想象一下，我们有一个对象，它保留longRunningFunction的所有函数调用结果&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;var longRunningFnBookKeeper = { 2 : 3, 4 : 5 }&#10;//check if the key present in longRunningFnBookKeeper&#10;//if get back the result else update the bookkeeping object&#10;longRunningFnBookKeeper.hasOwnProperty(ip) ?&#10; longRunningFnBookKeeper[ip] :&#10; longRunningFnBookKeeper[ip] = longRunningFunction(ip)&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="heading-element" id="管道和组合"&gt;&lt;span&gt;管道和组合&lt;/span&gt;&#10; &lt;a href="#%e7%ae%a1%e9%81%93%e5%92%8c%e7%bb%84%e5%90%88" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;使用纯函数，我们将在该函数中仅做一件事情。 我们已经看到，纯函数将如何通过查看其名称来作为对该功能的自我理解。 纯函数的设计应该只有一件事。 只做一件事情就是一个UNIX哲学; 我们将在实现我们的纯函数时遵循相同的规定。 在UNIX和LINUX平台中有许多命令，用于日常任务。 例如，我们使用cat打印文件的内容，grep来搜索文件，wc来计算行数等。这些命令一次解决一个问题。 但是我们可以组合或管理这些复杂的任务。&lt;/p&gt;&#10;&lt;p&gt;组合不仅仅是UNIX / LINUX命令行的唯一，而是函数式编程范例的核心。 我们称之为&lt;em&gt;函数组合&lt;/em&gt;。&lt;/p&gt;&#10;</description></item><item><title>(FMS03)Vim 的插件</title><link>https://zhangjinglin.github.io/2016/12/fms03-vim02-md/</link><pubDate>Sat, 03 Dec 2016 05:47:01 +0000</pubDate><author>ldxyzjl@163.com (张景林)</author><guid>https://zhangjinglin.github.io/2016/12/fms03-vim02-md/</guid><description>&lt;p&gt;Vim 除了强大的键盘模式以外, Vim 还有插件功能, 可以通过插件将 Vim 的功能增强, 经过多年的发展, Vim 的插件系统有着庞大的规模, 有很多号称神级的插件, 是很多编辑器的模仿对象.&lt;/p&gt;&#10;&lt;h2 class="heading-element" id="怎么管理插件"&gt;&lt;span&gt;怎么管理插件&lt;/span&gt;&#10; &lt;a href="#%e6%80%8e%e4%b9%88%e7%ae%a1%e7%90%86%e6%8f%92%e4%bb%b6" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;插件的安装一般通过插件管理器来进行安装. Vim 的插件管理器有很多种, 我采用的是&lt;a href="https://github.com/tpope/vim-pathogen" target="_blank" rel="external nofollow noopener noreferrer"&gt;pathogen&lt;i class="fa-solid fa-external-link-alt fa-xs ms-1 text-secondary" aria-hidden="true"&gt;&lt;/i&gt;&lt;/a&gt; 管理, 你也可以采用其他的管理方式, 比如常见的 vbundle, 请自行 google 之.&lt;/p&gt;&#10;&lt;p&gt;在pathogen 的主页上可以学习到安装方式.&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;mkdir -p ~/.vim/autoload ~/.vim/bundle &amp;amp;&amp;amp; \&#10;curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;进入到新建的 &lt;code&gt;bundle&lt;/code&gt; 目录, 下载相应的插件. 下面列出来我用的一些常用的插件&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;&amp;#34; 自动关闭括号, 也就是输入左括号(, 然后自动添加右括号), 支持{} [] ()&#10;https://github.com/vim-scripts/AutoClose.git&#10;&#10;&amp;#34; 迅速打开当前目录下的所有文件, 支持模糊查找, 按下 ctrl&amp;#43;p 组合件之后, 输入要查找的文件名的一部分, 快速定位, 回车打开, vim 神器之一&#10;https://github.com/kien/ctrlp.vim.git&#10;&#10;&amp;#34; html 神器之一, 其他编辑器也有相应的实现&#10;https://github.com/mattn/emmet-vim.git&#10;&#10;&amp;#34; 自动完成插件, 需要 lua 支持, 记得我们在安装 vim 的时候增加了 --with-lua 的选项了么? 在 Windows 平台的 vim 中不支持 lua, 需要自行编译 vim 增加 lua 支持, Windows 充满了大坑啊! MacOS 你不变的选择.&#10;https://github.com/Shougo/neocomplete.vim.git&#10;&#10;&amp;#34; 缩进美化, 对不同层级的缩进增加可视化竖条&#10;https://github.com/nathanaelkane/vim-indent-guides.git&#10;&#10;&amp;#34; vim 神器之一, 快速定位到你需要的位置&#10;https://github.com/easymotion/vim-easymotion.git&#10;&#10;&amp;#34; 美化 vim 的状态条&#10;https://github.com/Lokaltog/vim-powerline.git&#10;&#10;&amp;#34; solarized 配色, 最流行的一种配色&#10;https://github.com/altercation/vim-colors-solarized.git&#10;&#10;&amp;#34; vim 的侧边文件管理器, 不过我用了 ctrl&amp;#43;p 之后, 很少用到它&#10;https://github.com/scrooloose/nerdtree.git&lt;/code&gt;&lt;/pre&gt;&lt;h2 class="heading-element" id="怎么安装"&gt;&lt;span&gt;怎么安装&lt;/span&gt;&#10; &lt;a href="#%e6%80%8e%e4%b9%88%e5%ae%89%e8%a3%85" class="heading-mark"&gt;&lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&#10;&lt;/h2&gt;&lt;p&gt;我举一个例子, 比如 &lt;code&gt;vim-colors-solarized&lt;/code&gt; 来说明.&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;cd ~/.vim/bundle&#10;git clone --depth 1 https://github.com/altercation/vim-colors-solarized.git&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;ok, 大功告成. 不过一般的情况下我们还需要在 &lt;code&gt;.vimrc&lt;/code&gt; 中进行配置, 很多插件有很多的配置, 请自行在每个插件的网址中详细查看.&lt;/p&gt;&#10;&lt;p&gt;附上我的完整 &lt;code&gt;.vimrc&lt;/code&gt; 配置.&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;&#10;let mapleader=&amp;#34;;&amp;#34;&#10;&#10;nmap &amp;lt;Leader&amp;gt;h 0&#10;nmap &amp;lt;Leader&amp;gt;l $&#10;vnoremap &amp;lt;Leader&amp;gt;y &amp;#34;&amp;#43;y&#10;nmap &amp;lt;Leader&amp;gt;p &amp;#34;&amp;#43;p&#10;nmap &amp;lt;Leader&amp;gt;q :q&amp;lt;CR&amp;gt;&#10;&#10;&amp;#34; 在 vim 中, :vs 垂直分隔一个窗口, :sp(lit) 水平分割一个窗口, 然后使用下面四个映射命令, 在多个窗口中移动.&#10;nmap &amp;lt;Leader&amp;gt;wh &amp;lt;C-W&amp;gt;h&#10;nmap &amp;lt;Leader&amp;gt;wl &amp;lt;C-W&amp;gt;l&#10;nmap &amp;lt;Leader&amp;gt;wj &amp;lt;C-W&amp;gt;j&#10;nmap &amp;lt;Leader&amp;gt;wk &amp;lt;C-W&amp;gt;k&#10;&#10;function! CBProcess()&#10; if (getline(&amp;#34;.&amp;#34;)[col(&amp;#34;.&amp;#34;)-2] == &amp;#39;{&amp;#39; &amp;amp;&amp;amp; getline(&amp;#34;.&amp;#34;)[col(&amp;#34;.&amp;#34;)-1] == &amp;#39;}&amp;#39;) || (getline(&amp;#34;.&amp;#34;)[col(&amp;#34;.&amp;#34;)-2] == &amp;#39;[&amp;#39; &amp;amp;&amp;amp; getline(&amp;#34;.&amp;#34;)[col(&amp;#34;.&amp;#34;)-1] == &amp;#39;]&amp;#39;) || (getline(&amp;#34;.&amp;#34;)[col(&amp;#34;.&amp;#34;)-2] == &amp;#39;(&amp;#39; &amp;amp;&amp;amp; getline(&amp;#34;.&amp;#34;)[col(&amp;#34;.&amp;#34;)-1] == &amp;#39;)&amp;#39;)&#10; return &amp;#34;\&amp;lt;CR&amp;gt;\&amp;lt;C-o&amp;gt;O&amp;#34;&#10; else&#10; return &amp;#34;\&amp;lt;CR&amp;gt;&amp;#34;&#10; endif&#10;endfunction&#10;&#10;inoremap &amp;lt;expr&amp;gt;&amp;lt;CR&amp;gt; CBProcess()&#10;inoremap ;; &amp;lt;C-o&amp;gt;$;&amp;lt;CR&amp;gt;&#10;&#10;&amp;#34; 映射启动 NERDTreeToggle 插件的快捷键&#10;map &amp;lt;Leader&amp;gt;n :NERDTreeToggle&amp;lt;CR&amp;gt;&#10;&#10;&amp;#34; 为了让插件生效, 必须配置插件管理器&#10;execute pathogen#infect()&#10;&#10;filetype plugin indent on&#10;&#10;syntax on&#10;syntax enable&#10;&#10;&#10;set autowrite&#10;set autowriteall&#10;set ignorecase&#10;set hlsearch&#10;set incsearch&#10;set smartcase&#10;set gdefault&#10;set showmatch&#10;&#10;set autoindent&#10;set ts=4 sts=4 sw=4 expandtab&#10;&#10;set backspace=indent,eol,start&#10;set noswapfile&#10;set visualbell&#10;set cursorline&#10;set number&#10;set laststatus=2&#10;set ruler&#10;&#10;set encoding=utf-8&#10;set fileencoding=utf-8&#10;&#10;&amp;#34; 设置背景颜色, 采用亮色的 solarized, 也可以设置为 dark 采用暗色调&#10;set background=light&#10;colorscheme solarized&#10;set guifont=Menlo:h20&#10;&#10;&amp;#34; 设置 indent guides 插件的参数&#10;let g:indent_guides_enable_on_vim_startup=1&#10;let g:indent_guides_start_level=1&#10;let g:indent_guides_guide_size=1&#10;&#10;&amp;#34; 设置 neocomplete&#10;let g:neocomplete#enable_at_startup=1&#10;let g:neocomplete#enable_smart_case=1&#10;&#10;&amp;#34; 设置 emmet 的展开方式快捷键&#10;inoremap &amp;lt;expr&amp;gt;&amp;lt;TAB&amp;gt; pumvisible() ? &amp;#34;\&amp;lt;C-n&amp;gt;&amp;#34; : &amp;#34;\&amp;lt;TAB&amp;gt;&amp;#34;&#10;autocmd FileType css setlocal omnifunc=csscomplete#CompleteCSS&#10;autocmd FileType html,markdown setlocal omnifunc=htmlcomplete#CompleteTags&#10;autocmd FileType javascript setlocal omnifunc=javascriptcomplete#CompleteJS&#10;autocmd FileType python setlocal omnifunc=pythoncomplete#Complete&#10;autocmd FileType xml setlocal omnifunc=xmlcomplete#CompleteTags&#10;&#10;&amp;#34; 设置 easymotion 是否区分大小写&#10;let g:EasyMotion_smartcase=1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;有问题, 请联系我.&lt;/p&gt;&#10;</description></item></channel></rss>