<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Fisher's E-Garden]]></title><description><![CDATA[Hi 😊]]></description><link>https://www.amfishers.com</link><image><url>https://www.amfishers.com/i3he56xsmzbaina4e4.ico</url><title>Fisher&apos;s E-Garden</title><link>https://www.amfishers.com</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Sat, 04 Jul 2026 07:58:28 GMT</lastBuildDate><atom:link href="https://www.amfishers.com/feed" rel="self" type="application/rss+xml"/><pubDate>Sat, 04 Jul 2026 07:58:28 GMT</pubDate><language><![CDATA[en-US]]></language><item><title><![CDATA[前端多语言检查脚本的核心原理]]></title><description><![CDATA[<div><blockquote>This render is generated by the Shiro API and may have formatting issues. For the best experience, visit:<a href="https://www.amfishers.com/posts/technology/i18n-check-script-core">https://www.amfishers.com/posts/technology/i18n-check-script-core</a></blockquote><div><p>在多语言前端项目里，文案通常不会直接写死在页面中，而是通过类似下面的方式引用：</p><pre class="language-js lang-js"><code class="language-js lang-js">$t(&#x27;Common.Button.Add&#x27;)</code></pre><p>这样做的好处是：同一套代码可以根据当前语言环境显示不同文案。</p><p>但项目变大之后，多语言也会带来一些维护问题：</p><ul><li>代码里用了一个不存在的多语言 key；</li><li>同一段中文文案被重复添加了多个 key；</li><li>开发时临时写了 <code>$t(&#x27;添加&#x27;)</code>，但项目里其实已经有对应 key；</li><li>靠人工检查很容易遗漏。</li></ul><p>多语言检查脚本的作用，就是把这些容易出错的地方自动找出来。</p><p>它的本质并不复杂：<strong>读取语言包，扫描源码，提取 <code>$t()</code>，然后判断里面的内容是否存在于语言包中。</strong></p><h2 id="">一、整体流程</h2><p>可以先用一张简单流程图理解：</p><pre class="language-text lang-text"><code class="language-text lang-text">读取语言包文件
      ↓
合并成一个语言字典
      ↓
扫描源码文件
      ↓
提取 $t(&#x27;xxx&#x27;)
      ↓
判断 xxx 是否存在
      ↓
输出缺失项，或自动替换为已有 key</code></pre><p>这个脚本通常运行在 Node.js 环境里。</p><p>它不会启动前端页面，也不会真的渲染组件，而是直接读取源码文本，所以更像一个轻量级的“静态检查工具”。</p><h2 id="">二、准备语言字典</h2><p>假设我们有一个中文语言包：</p><pre class="language-js lang-js"><code class="language-js lang-js">export default {
  Common: {
    Button: {
      Add: &#x27;添加&#x27;,
      Delete: &#x27;删除&#x27;
    }
  },
  Product: {
    Name: &#x27;商品名称&#x27;
  }
}</code></pre><p>脚本首先要把语言包加载进来，得到一个普通 JavaScript 对象：</p><pre class="language-js lang-js"><code class="language-js lang-js">const messages = {
  Common: {
    Button: {
      Add: &#x27;添加&#x27;,
      Delete: &#x27;删除&#x27;
    }
  },
  Product: {
    Name: &#x27;商品名称&#x27;
  }
}</code></pre><p>如果项目里有多个语言包文件，脚本会把它们逐个读取，再合并成一个大的语言字典。</p><p>有了这个字典之后，就可以检查某个 key 是否存在。</p><p>比如：</p><pre class="language-js lang-js"><code class="language-js lang-js">Common.Button.Add</code></pre><p>对应的查找路径就是：</p><pre class="language-js lang-js"><code class="language-js lang-js">messages.Common.Button.Add</code></pre><p>如果能取到值，说明这个 key 存在。</p><p>如果取不到，说明代码里使用了一个不存在的多语言 key。</p><h2 id="-key">三、根据路径查找 key</h2><p>多语言 key 通常是点分格式：</p><pre class="language-js lang-js"><code class="language-js lang-js">Common.Button.Add</code></pre><p>但对象访问需要一层一层取：</p><pre class="language-js lang-js"><code class="language-js lang-js">messages.Common.Button.Add</code></pre><p>所以脚本里通常会有一个工具函数，把点分 key 拆开后逐层查找：</p><pre class="language-js lang-js"><code class="language-js lang-js">function getValueByPath(key, source) {
  const paths = key.split(&#x27;.&#x27;)
  let current = source

  for (const path of paths) {
    if (!current) return undefined
    current = current[path]
  }

  return current
}</code></pre><p>使用时：</p><pre class="language-js lang-js"><code class="language-js lang-js">getValueByPath(&#x27;Common.Button.Add&#x27;, messages)</code></pre><p>如果返回 <code>&#x27;添加&#x27;</code>，说明 key 存在。</p><p>如果返回 <code>undefined</code>，说明 key 不存在。</p><p>这就是多语言检查最核心的一步。</p><h2 id="">四、扫描源码文件</h2><p>语言字典准备好后，下一步就是扫描源码。</p><p>脚本通常会扫描这些文件：</p><pre class="language-text lang-text"><code class="language-text lang-text">.js
.ts
.vue
.jsx
.tsx</code></pre><p>实际项目里一般不会扫描所有文件，而是只扫描业务源码目录。</p><p>可以使用 <code>glob</code> 找出目标文件：</p><pre class="language-js lang-js"><code class="language-js lang-js">const glob = require(&#x27;glob&#x27;)

const files = glob.sync(&#x27;src/**/*.{js,ts,vue,jsx,tsx}&#x27;)</code></pre><p>然后逐个读取：</p><pre class="language-js lang-js"><code class="language-js lang-js">const fs = require(&#x27;fs&#x27;)

files.forEach(file =&gt; {
  const content = fs.readFileSync(file, &#x27;utf-8&#x27;)
})</code></pre><p>此时我们拿到的是文件内容字符串。</p><p>接下来要做的事情，就是从字符串里找出所有 <code>$t(&#x27;xxx&#x27;)</code>。</p><h2 id="-t-">五、提取 <code>$t()</code> 中的内容</h2><p>常见的多语言调用可能长这样：</p><pre class="language-js lang-js"><code class="language-js lang-js">$t(&#x27;Common.Button.Add&#x27;)
$t(&quot;Common.Button.Delete&quot;)
i18n.t(&#x27;Product.Name&#x27;)</code></pre><p>脚本可以用正则表达式识别这些调用：</p><pre class="language-js lang-js"><code class="language-js lang-js">const i18nRe = /((?:i18n\.t|\$t)\([&#x27;&quot;])([^&#x27;&quot;]+)([&#x27;&quot;]\))/g</code></pre><p>这个正则最重要的是中间这一段：</p><pre class="language-js lang-js"><code class="language-js lang-js">([^&#x27;&quot;]+)</code></pre><p>它表示：匹配引号里面的内容。</p><p>比如：</p><pre class="language-js lang-js"><code class="language-js lang-js">$t(&#x27;Common.Button.Add&#x27;)</code></pre><p>会被拆成：</p><pre class="language-text lang-text"><code class="language-text lang-text">$t(&#x27;                 -&gt; 调用开头
Common.Button.Add    -&gt; 真正的 key
&#x27;)                   -&gt; 调用结尾</code></pre><p>脚本真正关心的是中间的：</p><pre class="language-js lang-js"><code class="language-js lang-js">Common.Button.Add</code></pre><p>提取之后，就可以拿它去语言字典里查。</p><h2 id="-key">六、检查缺失的多语言 key</h2><p>把前面的步骤串起来，检查逻辑可以简化成这样：</p><pre class="language-js lang-js"><code class="language-js lang-js">const result = {}

files.forEach(file =&gt; {
  const content = fs.readFileSync(file, &#x27;utf-8&#x27;)
  const matches = content.match(i18nRe) || []

  const keys = matches.map(item =&gt; {
    return item.replace(i18nRe, &#x27;$2&#x27;)
  })

  const missingKeys = keys.filter(key =&gt; {
    return !getValueByPath(key, messages)
  })

  if (missingKeys.length) {
    result[file] = missingKeys
  }
})</code></pre><p>这段代码做了几件事：</p><ol start="1"><li>读取文件内容；</li><li>找出所有多语言调用；</li><li>提取 <code>$t()</code> 里的 key；</li><li>去语言字典里查；</li><li>把不存在的 key 记录下来。</li></ol><p>最后统一输出：</p><pre class="language-js lang-js"><code class="language-js lang-js">Object.keys(result).forEach(file =&gt; {
  console.log(file)
  console.log(result[file].join(&#x27;\n&#x27;))
})</code></pre><p>输出结果可能是：</p><pre class="language-text lang-text"><code class="language-text lang-text">src/pages/demo.vue
Common.Button.Unknown
Product.Detail.EmptyText</code></pre><p>这就说明这些 key 在代码中被使用了，但在语言包里没有定义。</p><h2 id="">七、自动替换已有文案</h2><p>除了检查缺失 key，还可以做一个更进一步的能力：<strong>把已有中文文案自动替换成标准 key。</strong></p><p>比如代码里写了：</p><pre class="language-js lang-js"><code class="language-js lang-js">$t(&#x27;添加&#x27;)</code></pre><p>但语言包里已经有：</p><pre class="language-js lang-js"><code class="language-js lang-js">{
  Common: {
    Button: {
      Add: &#x27;添加&#x27;
    }
  }
}</code></pre><p>这时候脚本可以自动把代码改成：</p><pre class="language-js lang-js"><code class="language-js lang-js">$t(&#x27;Common.Button.Add&#x27;)</code></pre><p>它的核心思路是：把语言字典反过来。</p><p>原来的语言字典是：</p><pre class="language-js lang-js"><code class="language-js lang-js">{
  Common: {
    Button: {
      Add: &#x27;添加&#x27;
    }
  }
}</code></pre><p>反转后变成：</p><pre class="language-js lang-js"><code class="language-js lang-js">{
  &#x27;添加&#x27;: &#x27;Common.Button.Add&#x27;
}</code></pre><p>这样当脚本看到：</p><pre class="language-js lang-js"><code class="language-js lang-js">$t(&#x27;添加&#x27;)</code></pre><p>就可以通过中文文案直接查到对应 key：</p><pre class="language-js lang-js"><code class="language-js lang-js">textToKeyMap[&#x27;添加&#x27;]</code></pre><p>得到：</p><pre class="language-js lang-js"><code class="language-js lang-js">Common.Button.Add</code></pre><p>然后把源码中的 <code>$t(&#x27;添加&#x27;)</code> 替换成 <code>$t(&#x27;Common.Button.Add&#x27;)</code>。</p><h2 id="">八、如何把嵌套语言包反转</h2><p>语言包通常是嵌套对象，所以需要递归处理。</p><p>可以写一个扁平化函数：</p><pre class="language-js lang-js"><code class="language-js lang-js">function flattenMessages(source, prefix = &#x27;&#x27;, map = {}) {
  Object.keys(source).forEach(key =&gt; {
    const value = source[key]
    const currentKey = prefix ? `${prefix}.${key}` : key

    if (typeof value === &#x27;object&#x27; &amp;&amp; value !== null) {
      flattenMessages(value, currentKey, map)
    } else {
      map[value] = currentKey
    }
  })

  return map
}</code></pre><p>输入：</p><pre class="language-js lang-js"><code class="language-js lang-js">{
  Common: {
    Button: {
      Add: &#x27;添加&#x27;
    }
  }
}</code></pre><p>输出：</p><pre class="language-js lang-js"><code class="language-js lang-js">{
  &#x27;添加&#x27;: &#x27;Common.Button.Add&#x27;
}</code></pre><p>这样就完成了从“key 找文案”到“文案找 key”的转换。</p><h2 id="">九、为什么这种方案有效？</h2><p>因为大多数多语言系统都有两个稳定特征：</p><p>第一，源码里会通过固定函数调用多语言，比如：</p><pre class="language-js lang-js"><code class="language-js lang-js">$t(&#x27;xxx&#x27;)</code></pre><p>第二，语言包通常是结构化对象，比如：</p><pre class="language-js lang-js"><code class="language-js lang-js">{
  Common: {
    Button: {
      Add: &#x27;添加&#x27;
    }
  }
}</code></pre><p>只要这两个条件成立，脚本就可以通过静态扫描完成检查。</p><p>它不需要理解整个前端框架，也不需要启动项目。</p><p>它只需要做三件事：</p><pre class="language-text lang-text"><code class="language-text lang-text">找文件
找 $t()
查字典</code></pre><p>这也是它实现简单、运行速度快的原因。</p><h2 id="">十、这种方案的边界</h2><p>正则扫描适合处理规范、静态的写法。</p><p>比如：</p><pre class="language-js lang-js"><code class="language-js lang-js">$t(&#x27;Common.Button.Add&#x27;)</code></pre><p>但如果代码里大量使用动态 key：</p><pre class="language-js lang-js"><code class="language-js lang-js">$t(prefix + &#x27;.Add&#x27;)
$t(`Common.${type}.Name`)</code></pre><p>脚本就很难准确判断。</p><p>因为它在静态文本里无法知道运行时的 <code>prefix</code> 或 <code>type</code> 到底是什么。</p><p>所以，如果希望多语言检查脚本稳定工作，最好遵守一个约定：</p><blockquote><p>多语言 key 尽量使用静态字符串，避免动态拼接。</p></blockquote>
<p>如果确实需要动态 key，也可以在团队规范中单独约定白名单或注释跳过机制。</p><h2 id="">十一、可以继续扩展什么？</h2><p>基础检查完成后，还可以继续扩展：</p><ul><li>检查重复文案；</li><li>检查未被使用的多语言 key；</li><li>检查不同语言之间 key 是否一致；</li><li>输出 JSON 或 HTML 报告；</li><li>接入 Git hooks，在提交前自动检查；</li><li>接入 CI，在合并前自动拦截问题。</li></ul><p>这些能力的底层原理仍然类似：扫描源码、读取语言包、建立映射关系、做差异比较。</p><h2 id="">十二、总结</h2><p>前端多语言检查脚本的核心原理可以概括为一句话：</p><blockquote><p>读取语言包形成字典，扫描源码提取 <code>$t()</code>，再判断 key 是否存在，或者根据已有文案反查并替换成标准 key。</p></blockquote>
<p>它不是复杂的编译器，也不需要启动应用。</p><p>它只是把人工检查中最重复、最容易遗漏的部分自动化了。</p><p>对于中大型前端项目来说，这类脚本虽然小，但很有价值。它能帮助团队提前发现问题，减少重复翻译，并让多语言使用方式保持一致。</p></div><p style="text-align:right"><a href="https://www.amfishers.com/posts/technology/i18n-check-script-core#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.amfishers.com/posts/technology/i18n-check-script-core</link><guid isPermaLink="true">https://www.amfishers.com/posts/technology/i18n-check-script-core</guid><dc:creator><![CDATA[Fisher]]></dc:creator><pubDate>Thu, 02 Jul 2026 12:42:17 GMT</pubDate></item><item><title><![CDATA[Don't Outsource the Learning]]></title><description><![CDATA[<div><blockquote>This render is generated by the Shiro API and may have formatting issues. For the best experience, visit:<a href="https://www.amfishers.com/posts/default/dont_outsource_the_learning">https://www.amfishers.com/posts/default/dont_outsource_the_learning</a></blockquote><p>Right now, it&#x27;s too easy to let AI write the code while you skip the learning. The bug gets fixed but your mental model doesn&#x27;t move. It might get worse over time. We are silently trading future capability for present-day speed, and the tools won&#x27;t force us to do otherwise. That part has to come from you.
There&#x27;s a default loop most of us have settled into. You paste in a spec or error message. The model hands you a fix and the symptom vanishes. You ship. Somewhere in that loop, the messy struggle between problem and solution stops happening at all.
I&#x27;ve written before about cognitive surrender, the moment an AI reviewer&#x27;s verdict quietly replaces your own. This is the solo version of that same loop. It&#x27;s just you and the model. The model is faster, so you stop trying to compete on comprehension. Across thousands of these small interactions, what you can actually build without an AI looking over your shoulder gets a little weaker every week. None of these moments feel like a problem on the day they happen.
I&#x27;m not anti-AI. I use these tools daily and have shipped more with them in the last year than in the years before it. But the default way we use them is optimized for one thing: closing tasks. 
That is a completely different goal from staying sharp enough to steer them over a career that spans a long time.
The studies are converging on the same point
Several pieces of research over the last year have landed in roughly the same place.
Anthropic ran a randomized trial in early 2026 where engineers learned a new Python library, half with AI assistance and half without. Both groups finished the tasks at the same speed. But the AI group bombed the follow-up comprehension quiz: 50% versus 67% for the manual group, with the gap widening on debugging. The interesting cut was inside the AI group itself. Engineers who used AI to ask conceptual questions scored above 65%. Engineers who copy-pasted the generated code scored under 40%. The tool didn&#x27;t determine the outcome. The posture did.
MIT&#x27;s Your Brain on ChatGPT study compared essay writing across LLM, search-engine, and brain-only groups. EEG measurements showed brain connectivity scaling down with every layer of external support. The LLM group showed the weakest coupling. After writing the essay, 83% of LLM users couldn&#x27;t quote a single line of what they had just produced. The researchers called this cognitive debt: saving mental effort today, paying for it in critical thinking tomorrow.
A CHI 2026 study added a related finding. When people had LLM access at the start of a task, the LLM framed the entire problem. Even when the human did the rest of the work themselves, that initial anchoring produced measurably worse decisions. The order of operations mattered more than the total amount of AI used.
Different methodologies reaching the same conclusion. Using AI without an active intent to learn quietly degrades the skill you&#x27;re being paid for.
The tools default to shipping, not teaching
If you fire up a coding agent and stick to the defaults, everything is tuned for one metric: getting the task done. 
The model writes the code. You accept it. The loop repeats. At no point does the tool pause and ask &quot;what do you think the problem is?&quot; or &quot;try writing the first five lines yourself.&quot;
That&#x27;s where UX gravity is right now. Product teams get rewarded for merged changes and shorter cycle times, not for making you a sharper engineer. We all want fewer keystrokes, so the tools have sanded the friction away. The trouble is that friction was where the learning lived.
A few companies have tried pushing back on these loops not encouraging us to really learn.
Anthropic shipped Learning Mode for Claude, which uses Socratic questioning and stops to ask you to write code before continuing. OpenAI and Google have shipped similar features. Almost nobody uses them for real production work though if we&#x27;re being real. We&#x27;ve quietly filed them under &quot;for students&quot; and that&#x27;s a mistake. The same feature that helps a sophomore learn React works for a senior engineer learning Rust. You just have to be willing to feel like a beginner again.
&quot;If the AI can do it, why do I need to understand it?&quot;
A fair question. For some work, the answer is: maybe...you don&#x27;t? If it&#x27;s boilerplate, glue code, or a throwaway CI script you&#x27;ll never look at again, delegate it. The opportunity cost of memorizing some syntax is too high.
For real software, pure delegation breaks down in a few specific places.
When something breaks. AI-generated code crashes the same way human code does. &quot;The agent wrote it&quot; doesn&#x27;t help you debug problems. Somebody on the team has to understand the architecture.
When it&#x27;s confidently wrong. LLMs can still hallucinate. The only defense against a plausible-looking incorrect answer is enough expertise to spot it. Bandaids like skills, CLIs etc only get you so far.
When the foundation changes. Code is temporary; systems are permanent. When frameworks update or a security review flags a structural issue, you can&#x27;t re-prompt your way out. You need engineers who understand the system well enough to migrate it.
When you leave the median. AI is brilliant at problems that have been solved a million times on GitHub. The further you stray from the median, the worse it gets. The hard, undocumented problems, the ones that justify a senior engineer&#x27;s salary, still require deep understanding.
When the market adjusts. Engineers who can only ship with AI, and not without it, are entering a labor pool that is already re-pricing what expertise is worth. If you use AI to skip learning, you&#x27;re trading future relevance for a slightly easier Tuesday.
The fix is in how you prompt
The good news is that the same tools that produce cognitive debt can produce sharper engineers. The difference is in what you ask of them.
Form a hypothesis before you ask. Before requesting a fix, write down two or three sentences on what you think the problem is. Use the model&#x27;s answer to test your theory, not to replace it.
Ask for the explanation before the code. In unfamiliar territory, your first prompt should be something like &quot;explain how this works, what the alternatives are, and what the tradeoffs are.&quot; Ask for the code only after you&#x27;ve grasped the concepts.
Turn on Learning Mode when you&#x27;re out of your depth. Yes, it feels slower. That&#x27;s the point.
Treat AI output like a PR from a junior engineer. Read it. Critique it. Push back on it. Would you merge it just because the tests passed? If not, don&#x27;t merge it here either.
Re-derive things by hand once in a while. Take a piece of code the model wrote for you and try to recreate it from scratch. It&#x27;s the calibration check that tells you how much you&#x27;ve quietly lost.
Ask the model to teach you what it just did. After it writes a clever function, ask what concepts it used and what you&#x27;d need to read to understand the design choice. One extra prompt changes what you take away from the session.
None of these are dramatic. They&#x27;re small posture shifts inside the same tools you&#x27;re already using.
Two metrics, not one
I&#x27;ve started ending coding sessions with a simple question: did I learn anything today, or did I just close issues?
Sometimes the honest answer is &quot;I just closed issues&quot; and that&#x27;s fine. If it becomes the answer for months in a row, cognitive debt is accumulating in the background.
Ship and learn are two separate metrics. Your manager and your customers will only ever ask about the first one. The second is on you.
I&#x27;d rather ship 80% of what I could have and learn 100% of what I needed to, than the reverse. Over years, those two strategies produce very different engineers.
You don&#x27;t have to choose between using AI and learning. You do have to choose a workflow that does both, because the defaults won&#x27;t choose it for you. The tools are ready whenever you are. 
The next boring task you were about to delegate is a good place to start.</p><p style="text-align:right"><a href="https://www.amfishers.com/posts/default/dont_outsource_the_learning#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.amfishers.com/posts/default/dont_outsource_the_learning</link><guid isPermaLink="true">https://www.amfishers.com/posts/default/dont_outsource_the_learning</guid><dc:creator><![CDATA[Fisher]]></dc:creator><pubDate>Fri, 22 May 2026 06:05:04 GMT</pubDate></item><item><title><![CDATA[AI Coding 的下一阶段：从生成能力到治理能力]]></title><description><![CDATA[<div><blockquote>This render is generated by the Shiro API and may have formatting issues. For the best experience, visit:<a href="https://www.amfishers.com/posts/default/ai_generation_to_governance">https://www.amfishers.com/posts/default/ai_generation_to_governance</a></blockquote><div><p>过去两年，关于 AI Coding 的讨论，几乎每隔几个月就会换一个重心。</p><p>最开始，大家讨论的是：AI 到底能不能写代码。后来，大家开始关心：它能不能真正参与开发。再后来，问题变成了：它能不能独立完成一个模块、一个项目，甚至一段完整的交付流程。</p><p>到了今天，这个问题其实已经有了阶段性答案。AI 不仅能写代码，而且已经能够在很多场景中写得很快、写得像样，甚至能把原型、骨架、测试和一些基础功能一并做出来。真正的分水岭，已经不再是“它会不会写”，而是“它写出来的东西，能不能被人和组织真正接住”。</p><p>这背后，意味着 AI Coding 正在进入一个新的阶段：竞争的重点，开始从生成能力，转向治理能力；从单点能力，转向系统能力；从“做出来”，转向“做得稳、接得住、放得进生产”。</p><p>这不仅是一个技术问题，也是一个组织问题，甚至还是一个个人问题。  <a href="https://www.amfishers.com/posts/default/ai_generation_to_governance">作者Fisher</a></p><h2 id="ai-coding-">一、AI Coding 的真正难点，不在生成端，而在接收端</h2><p>在试点阶段，AI Coding 往往给人一种很强的兴奋感。</p><p>它写代码很快，搭架子很快，补测试也很快。很多过去需要半天、一天才能看到雏形的事情，现在半小时就能起出一个像样的原型。于是团队会自然得出一个判断：AI Coding 已经可用了。</p><p>但真正的问题，通常不是在这里暴露的。</p><p>问题往往出现在推广阶段。有人把 AI 生成的代码直接提交，跳过评审；有人发现 AI 改了不该改的接口，但变更已经合进去了；还有人虽然也用了 AI，但最后还是不得不手动重写一遍，因为 AI 的输出和团队的工程习惯、边界约束、交付要求对不上。</p><p>这时候大家才会意识到，试点阶段验证的，往往只是“AI 能不能写代码”；而没有验证的是，“AI 写出来的代码，如何被工程体系接纳”。</p><p>这两者不是一回事。</p><p>AI Coding 的瓶颈，从来不只在模型这一端。真正决定它能不能进入生产的，是代码生成之后的一整套问题：它是不是在边界内生成的？有没有跑偏？改动面是不是可控？结果是否经过验证？最后能不能通过评审、检查和放行体系？</p><p>这些问题，模型自己回答不了，只能由工程系统来回答。</p><p>所以，AI Coding 的下一阶段，不是继续追问“怎样让 AI 写更多代码”，而是要回答另一个更关键的问题：<strong>怎样让 AI 在工程体系中稳定完成交付。</strong></p><h2 id="-rulespecloop--harness">二、从 Rule、Spec、Loop 到 Harness，本质上是在建设控制面</h2><p>如果把 AI Coding 的演进过程拆开来看，可以发现它并不是一个单点突破的问题，而是一条逐层收紧的建设路径。</p><p>先是 Rule，再是 Spec，然后是 Loop，最后才是 Harness。</p><p>这四层看似是四个模块，实际上是一条递进关系。每一层都不是孤立存在的，而是在为下一层提供约束条件。它们共同解决的，不是“AI 怎么更聪明”，而是“AI 怎么不乱来、怎么不跑偏、怎么持续收敛、最后怎么被组织信任”。 <a href="https://www.amfishers.com/posts/default/ai_generation_to_governance">作者Fisher</a></p><h3 id="rule">Rule：先把“不要乱来”写成机器边界</h3><p>很多团队一开始最关心的，是 AI 能做什么。但在真实项目里，最昂贵的错误，往往不是写得不漂亮，而是越过边界、误改契约、扩散变更面，或者在不该结束的时候宣布任务完成。</p><p>所以 Rule 必须排在第一层。</p><p>Rule 不是为了让 AI 更有背景知识，而是为了让它先学会不越界。它首先要写清楚的，不是“建议怎么做”，而是“绝对不要做什么”。先写 NEVER，再写 DO NOT；先约束高代价错误，再去讨论更优雅的实现。</p><p>更重要的是，规则文件不是装饰品。它如果只是上下文的一部分，那它就只是提醒；真正的护栏，必须由测试、lint、build、schema check、review policy 这些外部信号来接管。也就是说，Rule 的价值不在于“告诉 AI”，而在于“让系统有办法拦住 AI”。</p><p>Rule 解决的是一个最基础的问题：先别乱来。</p><h3 id="spec">Spec：再把“想做什么”压缩成一次可执行的变更</h3><p>只有 Rule 还不够。因为边界清楚，不等于目标清楚。</p><p>很多 AI Coding 失控，不是模型没理解需求，而是需求本身没有被整理成一个范围明确、结果可验证、边界可审查的交付对象。于是 AI 很自然地扩写任务：修一个提示，顺手重构流程；补一个状态，顺手改掉契约；写一个 happy path，顺手把整个模块“整理”一遍。</p><p>这时候需要的就是 Spec。</p><p>Spec 不是更长的 prompt，也不是把背景写得更厚，而是把一次模糊的意图，收敛成一次可执行、可审查、可验证的变更。它至少要回答几件事：这次要解决什么，这次不解决什么，允许改哪些 surface，哪些 contract 不能动，怎样才算完成。</p><p>它真正的价值，是把范围钉住，把完成条件前置，把“顺手改一下”的冲动提前消灭掉。</p><p>如果说 Rule 解决的是“别乱来”，那么 Spec 解决的就是“别跑偏”。</p><h3 id="loop">Loop：把一次性生成，改造成可收敛的执行闭环</h3><p>有了边界和目标，还不够。因为执行过程本身依然会出错。</p><p>真正决定 AI 能不能持续参与工程工作的，不是它某一次回答得多漂亮，而是有没有一个 Loop，把“读取上下文—做最小改动—运行外部验证—记录状态—进入下一轮”变成系统动作。</p><p>这一步非常关键，因为它改变了 AI Coding 的工作形态。它不再是一锤子买卖，而是一个持续逼近验收条件的过程。它要求每一轮改动足够小，小到能被验证器快速裁决；它要求状态外置到文件、日志、Git 历史、计划文档里，而不是寄希望于模型“记得住”；它也要求一旦检查失败，就必须停下来修，而不是带着失败继续往前滚。</p><p>没有 Loop，AI 就更像一次性生成器；有了 Loop，它才开始接近一个可持续协作的执行体。</p><p>Loop 优化的，不是生成质量，而是收敛效率。</p><h3 id="harness-ai-">Harness：最后把 AI 放回组织治理，而不是直接推向生产</h3><p>当 Rule、Spec、Loop 三层建立起来之后，AI 已经不只是“会写代码”，而是在一个受控环境里推进任务了。但要真正进入企业生产环境，还差最后一层：Harness。</p><p>Harness 不是某一个具体工具，而是一整套把 AI 产出纳入验证、提交、评审、放行与追责体系的工程外骨骼。它负责守住共享边界，承接前面三层的约束结果，并把这些结果交给组织级的质量系统去裁决。</p><p>从这个意义上说，Harness 的核心任务，从来不是让 AI 更自由，而是让组织更放心。它要回答的，不是“AI 能不能写出代码”，而是“这次变更为什么值得被信任”。</p><p>所以，Harness Engineering 的价值，不在于增强 AI 的能力，而在于让 AI 的能力在企业环境中变得可控、可验证、可回滚、可审计、可接纳。</p><p>这也解释了为什么 AI Coding 的竞争，最终不会只是模型之争，而会逐渐变成工程体系之争。真正的壁垒，不在于谁能更快生成一段代码，而在于谁能更早把一套完整的交付系统建出来。</p><h2 id="ai-">三、AI 改变的不只是工程系统，也在改变人本身</h2><p>如果说前面的讨论更多是组织视角，那么 AI 的另一面，其实是个体体验的剧烈变化。</p><p>很多人最初想象 AI 时，带着一种很朴素的期待：它帮我做事，我就轻松了。它替我干活，我就有时间喝茶、散步、发呆，或者去做那些真正重要的事情。</p><p>但现实并不完全是这样。</p><p>AI 让开始一个项目的门槛变得极低。过去半天才能搭出来的原型，现在半小时就有雏形；过去觉得“先别做，太费劲了”的想法，现在会因为“反正 AI 很快”而被迅速启动。于是，人很容易进入一种新的状态：不舍得放弃任何一个想法，不断开新项目，不断并行推进，在多个窗口和多个任务之间来回切换。</p><p>AI 并没有自动带来自由，反而把人的欲望和执行冲动一起放大了。</p><p>这也是为什么，很多人在拥有 AI 之后，并没有更轻松，反而更快、更忙、更停不下来。问题不在 AI 本身，而在于当手里有了“锤子”，人就会拼命去找“钉子”。效率的提升，并不会自动带来秩序；如果没有新的节奏感和取舍能力，它甚至会把一个人拖进更深的碎片化和焦虑里。</p><p>从这个角度看，AI 不只是要求组织建立 Harness，也在要求个人建立某种属于自己的“内在 Harness”——也就是边界、节奏、取舍和完成意识。</p><p>因为 AI 降低的是“开始”的门槛，但没有降低“做完、做好、做成”的难度。真正困难的部分，往往还在后面：细节逻辑的补全、体验的打磨、产品的传播、用户的接受、市场的验证。这些事情，不会因为 AI 会写代码，就自动迎刃而解。</p><p>所以，AI 时代真正稀缺的能力，开始发生转移。仅仅会写代码已经不够了，甚至仅仅会调用 AI 也不够了。更重要的是：你能不能判断什么值得做，能不能控制节奏，能不能把一个想法推进到真正成立，能不能在无限可能性里保住自己的主线。</p><h2 id="">四、当“有用”越来越容易被生产，人的价值会重新回到表达</h2><p>AI 的另一个重要影响，是它正在改变“表达”的结构。</p><p>过去，人们写博客、做播客、写公众号，很多时候强调的是“输出干货”“提供价值”“讲明白一个知识点”。但今天，AI 在生产“有用内容”这件事上，已经越来越专业、越来越全面、越来越结构化。它可以快速生成一篇逻辑完整、框架清晰、信息密度很高的文章。</p><p>这反而会逼着人重新思考：当“有用”越来越容易被机器生产，人还要表达什么？</p><p>答案也许恰恰是：表达自己。</p><p>不是为了博流量，不是为了证明自己很懂，不是为了拼谁的内容更完整，而是为了留下一个具体的人在具体时刻的感受、观察、挣扎和判断。那种带着个人气味的表达，那种“我是谁、我正在经历什么、我为什么会这样想”的表达，反而会在 AI 时代变得更珍贵。</p><p>同样的变化，也发生在内容媒介上。AIGC 不只是一个生产工具，它更像是一种新的表达媒介。它让普通人可以更低门槛地进入影像、漫剧、短片、叙事性创作，让很多过去只能停留在脑海里的东西，有机会被更完整地表达出来。</p><p>从这个意义上说，AI 不只是改变了软件的生产方式，也在改变创作的形式、表达的入口以及内容被看见的方式。旧的表达形式不会立刻消失，但新的表达形式，正在进入主流视野。对个人来说，这不只是一个工具升级的问题，而是一个“如何在新媒介中继续表达自己”的问题。</p><h2 id="ai-">五、AI 时代的真正分水岭，不是谁更会用工具，而是谁更早建立秩序</h2><p>把组织和个人这两条线放在一起看，会发现一个很有意思的结论。</p><p>AI 的核心问题，已经不再只是能力问题，而是秩序问题。</p><p>对组织来说，这个秩序体现在工程系统里。它表现为 Rule、Spec、Loop、Harness，表现为上下文工程、架构约束、检查点、人工介入、审计与回滚。它要解决的是：AI 如何在组织中被约束、被验证、被信任、被放行。</p><p>对个人来说，这个秩序体现在自我管理里。它表现为取舍、节奏、边界、表达和长期主线。它要解决的是：AI 如何不把人拖进无穷无尽的并行和焦虑，而是成为真正放大价值的工具。</p><p>所以，AI Coding 的下一阶段，拼的不会只是模型能力，也不会只是提示词技巧。真正拉开差距的，最终会是谁更早把经验转成系统，把约束变成默认，把流程变成闭环，把输出纳入治理，把高效率放回可控秩序里。</p><p>这就是为什么，未来的竞争重点，不会只是“谁的 Agent 更聪明”，而更可能是“谁的 Harness 更成熟”；不会只是“谁会让 AI 写代码”，而是“谁能让 AI 稳定完成交付”；不会只是“谁启动得更快”，而是“谁在快的同时没有失去方向”。</p><p> <a href="https://www.amfishers.com/posts/default/ai_generation_to_governance">作者Fisher</a></p><h2 id="">结语</h2><p>过去两年，AI 让很多曾经听起来像幻想的事情，迅速变成了现实。它让代码生成、产品原型、内容创作、跨领域尝试，都出现了前所未有的加速度。可也正因为如此，真正困难的问题才终于浮出水面。</p><p>我们真正要建设的，不是一个更会写代码的助手，而是一套能在边界内稳定工作的交付系统；我们真正要解决的，也不只是技术问题，而是人与组织如何在高智能、高速度的环境中重新建立秩序的问题。</p><p>从 Rule 到 Spec，从 Loop 到 Harness，这是一条工程系统逐步成熟的路径。
从兴奋到焦虑，从并行到取舍，从“输出有用”到“表达自我”，这是一条个人在 AI 时代重新认识自己的路径。</p><p>AI 不会替我们完成这一切。
但它正在逼着我们，更认真地回答这些问题。</p><p> <a href="https://www.amfishers.com/posts/default/ai_generation_to_governance">作者Fisher</a></p><p>而这，也许正是 AI Coding 下一阶段真正的开始。</p></div><p style="text-align:right"><a href="https://www.amfishers.com/posts/default/ai_generation_to_governance#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.amfishers.com/posts/default/ai_generation_to_governance</link><guid isPermaLink="true">https://www.amfishers.com/posts/default/ai_generation_to_governance</guid><dc:creator><![CDATA[Fisher]]></dc:creator><pubDate>Wed, 15 Apr 2026 07:54:13 GMT</pubDate></item><item><title><![CDATA[面向 JavaScript 开发人员的 Python]]></title><description><![CDATA[<div><blockquote>This render is generated by the Shiro API and may have formatting issues. For the best experience, visit:<a href="https://www.amfishers.com/posts/technology/py-js">https://www.amfishers.com/posts/technology/py-js</a></blockquote><div><h1 id="-javascript--python">面向 JavaScript 开发人员的 Python</h1><p>JavaScript 是我的面包和黄油，但我一点也不讨厌其他编程语言。我一直很喜欢 <strong>Python</strong>，多年来我一直使用它，从脚本到 Flask 和 Django 的 API 应用程序。</p><p>在这篇文章中，我将引导您对这两种语言进行 10,000 英尺的比较。</p><p>要继续学习，您至少需要对 JavaScript 及其怪癖有<strong>基本的了解</strong>。</p><p>您可以在浏览器控制台或 Node.js REPL 中尝试 JavaScript 代码示例。对于 Python，您可以使用 <a href="https://docs.python.org/3.6/tutorial/interpreter.html">Python REPL</a>。代码示例里出现 <code>&gt;&gt;&gt;</code> 即表示 REPL。</p><p>享受阅读的乐趣！</p><h2 id="python--javascript-">Python 和 JavaScript 中的算术运算符</h2><p>让我们从算术开始吧！</p><p>Python 和 JavaScript 或多或少共享相同的算术运算符。除了（无双关语）<strong>除法运算符</strong>之外，Python 还有一个<strong>整除运算符</strong>。</p><table><thead><tr><th>运算符</th><th>PYTHON</th><th>JAVASCRIPT</th></tr></thead><tbody><tr><td>加法</td><td>+</td><td>+</td></tr><tr><td>减法</td><td>-</td><td>-</td></tr><tr><td>乘法</td><td><em> | </em></td></tr><tr><td>指数</td><td><strong> | </strong></td></tr><tr><td>除法</td><td>/</td><td>/</td></tr><tr><td>整除</td><td>//</td><td>n/a</td></tr><tr><td>取模</td><td>%</td><td>%</td></tr></tbody></table><p><strong>底除法返回两个数字相除的整数商</strong>。考虑以下 Python 示例：</p><p><code>&gt;&gt;&gt; 89 / 4</code></p>
<p># 输出：22.25</p><p>要获得整数而不是浮点数，我们可以使用<strong>向下除法</strong>（<code>//</code> 在 JavaScript 中是注释）：</p><p><code>&gt;&gt;&gt; 89 // 4</code></p>
<p># 输出：22</p>
<p>在 JavaScript 中可用 <code>Math.floor</code> 得到同样结果：</p><p><code>Math.floor(89 / 4)</code></p>
<p>// 输出：22</p><p><strong>Python 的算术</strong>运算符<strong>不仅仅对数字进行运算</strong>。例如，您可以使用<strong>字符串乘法</strong>来重复模式：</p><p><code>&gt;&gt;&gt; &quot;a&quot; * 9</code></p>
<p># 输出：&#x27;aaaaaaaaa&#x27;</p><p>或者<strong>加法</strong>也可以连接简单的字符串：</p><p><code>&gt;&gt;&gt; &quot;a&quot; + &quot;aa&quot;</code></p>
<p># 输出：&#x27;aaa&#x27;</p><p>对于其他一切，Python 都会引发 TypeError。这意味着<strong>你不能将数字和字符串相加</strong>：</p><p><code>&gt;&gt;&gt; &quot;a&quot; + 9</code></p>
<p># 类型错误：只能将 str （不是“int”）连接到 str</p><p><strong>也不划分它们</strong>（除了没有意义之外）：</p><p><code>&gt;&gt;&gt; &quot;a&quot; / 9</code></p>
<p># 类型错误：/ 不支持的操作数类型：“str”和“int”</p><p>在这方面，由于<strong>臭名昭著的类型强制</strong>，<strong>JavaScript</strong> 在外部观察者眼中完全是一团糟。不仅<strong>JavaScript的算术运算符可以自由地将数字转换为字符串</strong>：</p><p><code>&quot;a&quot; + 9</code></p>
<p>// 输出：“a9”</p><p>它们<strong>在无效算术运算的情况下不会引发任何错误</strong>：</p><p><code>&quot;a&quot; / 9</code></p>
<p>// 输出：NaN</p><p>相反，我们得到 <code>NaN</code>——由无效算术运算产生的特殊 JavaScript 值，难以处理，直到 ECMAScript 2015 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN">Number.isNaN</a>。</p><h2 id="python--javascript-">Python 和 JavaScript 中的自增和自减运算符</h2><table><thead><tr><th>运算符</th><th>PYTHON</th><th>JAVASCRIPT</th></tr></thead><tbody><tr><td>自增</td><td>n/a</td><td>++</td></tr><tr><td>自减</td><td>n/a</td><td>--</td></tr></tbody></table><p>JavaScript 有一个递增和一个递减运算符。两者都可以使用<strong>前缀</strong>（在数字之前）：</p><p><code>var a = 34;</code></p><p>++a</p><p>或 <strong>后缀</strong>（在数字之后）：</p><p><code>var a = 34;</code></p><p>一个++</p><p>在第一种情况（<strong>前缀</strong>）中，<strong>运算符递增数字并返回新数字</strong>：</p><p><code>var a = 34;</code></p><p>++a</p><p>// 返回 35</p><p>在第二种情况（<strong>后缀</strong>）中，<strong>运算符递增数字，但返回原始数字</strong>：</p><p><code>var a = 34;</code></p><p>一个++</p><p>// 返回 34</p><p>// 增加到 35</p><p><strong>同样的规则也适用于减运算符</strong>。</p><p><strong>相反，Python 中不存在递减/递增运算符这样的东西</strong>。相反，您必须使用<strong>赋值运算符</strong>。这是 Python 中的增量赋值：</p><p><code>&gt;&gt;&gt; a = 34</code></p><blockquote><p>&gt;&gt; a += 1</p></blockquote>
<p># a 现在 35</p><p>这是一个减量赋值：</p><p><code>&gt;&gt;&gt; a = 35</code></p><blockquote><p>&gt;&gt; a -= 2</p></blockquote>
<p># a 现在是 33</p><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators">JavaScript 也有赋值运算符</a>。</p><h2 id="python--javascript-">Python 和 JavaScript 中的比较运算符</h2><p>JavaScript 和 Python 具有相同的比较运算符，除了 <strong>三等号</strong>，它象征着 <strong>JavaScript 奇怪的强制规则</strong>。</p><p>关于这个主题有大量文献，我不会过多打扰您。以下是并列的 Python 和 JavaScript 比较运算符：</p><table><thead><tr><th>运算符</th><th>PYTHON</th><th>JAVASCRIPT</th></tr></thead><tbody><tr><td>大于</td><td>&gt;</td><td>&gt;</td></tr><tr><td>小于</td><td>&lt;</td><td>&lt;</td></tr><tr><td>大于等于</td><td>&gt;=</td><td>&gt;=</td></tr><tr><td>小于或等于</td><td>&lt;=</td><td>&lt;=</td></tr><tr><td>相等</td><td><mark class="rounded-md"><span class="px-1"> | </span></mark></td></tr><tr><td>严格相等（三等号）</td><td>n/a</td><td>===</td></tr><tr><td>不等于</td><td>!=</td><td>!=</td></tr><tr><td>严格不相等</td><td>n/a</td><td>!==</td></tr></tbody></table><p>准确地说，Python 还有 <strong> is 运算符</strong>（此处未讨论），对我来说，它更多地属于身份运算符家族。</p><p>这里重要的是 <strong>Python 在比较值时是可预测的</strong>：</p><p><code>&gt;&gt;&gt; 9 == &quot;9&quot;</code></p>
<p># 输出：假</p><p>另一方面，每次涉及<em>抽象比较</em>运算符时，JavaScript 都会执行一次转换：</p><p><code>9 == &quot;9&quot;</code></p>
<p>// 输出：真</p><p>这里<strong>第一个运算符 9 在比较之前被转换为字符串</strong>。为了避免转换，您必须使用“<strong>严格比较运算符</strong>”，也称为<strong>三等于</strong>：</p><p><code>9 === &quot;9&quot;</code></p>
<p>// 输出：假</p><h2 id="python--javascript-">Python 和 JavaScript 中的逻辑运算符</h2><p>现在我们讨论了算术和比较运算符，让我们看看它们的同伴：<strong>逻辑运算符</strong>。哦，我的朋友，我还记得高中时的布尔代数。你？</p><p>以下是 Python 和 JavaScript 中最常见的逻辑运算符：</p><table><thead><tr><th>运算符</th><th>PYTHON</th><th>JAVASCRIPT</th></tr></thead><tbody><tr><td>逻辑与</td><td>and</td><td>&amp;&amp;</td></tr><tr><td>逻辑或</td><td>or</td><td></td><td></td><td></td></tr><tr><td>逻辑非</td><td>not</td><td>!</td></tr></tbody></table><p><strong>当您想要根据表达式的结果在代码中执行（或不执行）某些操作时，逻辑运算符非常有用</strong>。假设我们只想在 2019 年大于 1900 时打印“I am Cool”。以下是在 Python 中使用 <strong>逻辑运算符 and</strong> 执行此操作的方法：</p><p><code>&gt;&gt;&gt; 2019 &gt; 1900 and print(&quot;I am cool&quot;)</code></p>
<p># 输出：“我很酷”</p><p><strong>和</strong>表示：仅当<strong>左侧的内容为</strong>true<em></em>时才执行右侧的内容。 JavaScript 中的相同逻辑映射为：</p><p><code>2019 &gt; 1900 &amp;&amp; console.log(&quot;I am cool&quot;)</code></p><p><strong>或逻辑运算符的工作方式相反</strong>。 <strong>或</strong>表示：仅当<strong>左侧的内容为</strong>假<em></em>时才执行右侧的内容。下面是一个 Python 示例：</p><p><code>&gt;&gt;&gt; 2019 &gt; 1900 or print(&quot;I am cool&quot;)</code></p>
<p># 输出：真</p><p>结果是<code>True</code></p><p>因为2019年大于1900所以评估就到此为止。 JavaScript 中的逻辑如下：</p><p><code>2019 &gt; 1900 || console.log(&quot;I am cool&quot;)</code></p><p>最后但并非最不重要的一点是<strong>逻辑否定</strong>，当您想要<strong>翻转表达式的结果</strong>时很有用。例如，即使左侧给定的表达式为 <strong>false</strong>，我们也可以打印“I am Cool”。如何？使用 Python 中的 <strong>not</strong> 运算符：</p><p><code>&gt;&gt;&gt; not 2019 &gt; 151900 and print(&quot;I am cool&quot;)</code></p>
<p># 输出：“我很酷”</p><p>2019 显然小于 151900，但 <strong>逻辑非</strong> 翻转了结果，因此仍然评估正确的表达式。下面的例子应该更容易理解：</p><p><code>&gt;&gt;&gt; not False and print(&quot;I am cool&quot;)</code></p>
<p># 输出：“我很酷”</p><p><strong>JavaScript 中的逻辑否定使用感叹号</strong>：</p><p><code>!false &amp;&amp; console.log(&quot;I am cool&quot;)</code></p><h2 id="python--javascript-">Python 和 JavaScript 中的基本数据类型</h2><p>在计算机编程中，<strong>数据类型</strong>是您可以处理的<strong>“形状”</strong>。 <strong>大多数编程语言都有一组基本数据类型，如字符串、数字和布尔值</strong>。</p><p>Python 和 JavaScript 也不例外。以下概述了两种语言中可用的最重要的<strong>基本数据类型</strong>（n/a 代表不适用）。</p><table><thead><tr><th>PYTHON</th><th>JAVASCRIPT</th><th>Python 中是否可变</th><th>JavaScript 中是否可变</th></tr></thead><tbody><tr><td>float</td><td>Number</td><td>否</td><td>否</td></tr><tr><td>int</td><td>BigInt</td><td>否</td><td>否</td></tr><tr><td>int</td><td>n/a</td><td>否</td><td>n/a</td></tr><tr><td>string</td><td>String</td><td>否</td><td>否</td></tr><tr><td>boolean</td><td>Boolean</td><td>否</td><td>否</td></tr><tr><td>None</td><td>Null</td><td>否</td><td>否</td></tr><tr><td>n/a</td><td>Undefined</td><td>n/a</td><td>否</td></tr><tr><td>n/a</td><td>Symbol</td><td>n/a</td><td>否</td></tr></tbody></table><p>JavaScript 有八种类型，其中七种称为<strong>原语</strong>（对象本身就是一种类型）。从表中您可以注意到，Python 和 JavaScript 基本类型都是<strong>不可变的</strong>。例如，在 <strong>Python 中，我们将字符串称为 unicode 字符的不可变序列</strong>。</p><p>与大多数基本类型一样，Python 字符串也有常见操作的方法：</p><p><code>&gt;&gt;&gt; name = &quot;caty&quot;</code></p><blockquote>
<p>&gt;&gt; name.capitalize()</p></blockquote><blockquote>
<p>&gt;&gt; name.center(40)</p></blockquote><blockquote>
<p>&gt;&gt; name.count(&quot;t&quot;)</p></blockquote>
<h2 id="">还有更多！</h2><p>由于字符串是不可变的，因此字符串操作的结果始终是一个新字符串。</p><p>现在数字。在 JavaScript 中，整数和浮点数没有区别，它们只是一个 <code>Number</code> 数据类型。<strong>在 Python 中，有整数和浮点数</strong>。</p><p>JavaScript 最近获得了 <code>BigInt</code>，用于表示非常大的数字的原始类型。相反，Python 似乎使用单个 int 类型来处理小整数和大整数。</p><p>如果不提及典型的 <strong>boolean</strong> 那就太失职了，在 Python 中它可以假设 <code>False</code> 或 <code>True</code>。</p><p>关于<strong>特殊值</strong>的重要说明。Python 用 <code>None</code> 表示空值；JavaScript 中对应的是 <code>null</code>。</p><p>同样在 Python 中也没有 <code>undefined</code> 的概念；也没有与 JavaScript 的 <code>Symbol</code> 相当的类型（相当深奥的原语）。</p><h2 id="python--javascript-">Python 和 JavaScript 中的正则表达式</h2><p><strong>正则表达式</strong>是针对文本进行模式匹配的强大工具。为了在 Python 中使用正则表达式，我们使用 <code>re</code> 模块。</p><p>要创建新模式，我们可以使用 <code>re.compile</code> 编译正则表达式：</p><p><code>import re</code></p>
<p>regex = re.compile(r&quot;\d\d\d\d&quot;)</p>
<p><code>re.compile</code> 对于复杂的正则表达式特别有用，因为它可以加快速度。</p><p>有了正则表达式后，可用 <code>.search()</code> 在一段文本上匹配：</p><p><code>import re</code></p>
<p>regex = re.compile(r&quot;\d\d\d\d&quot;)</p>
<p>text = &quot;Your id is 4933&quot;</p>
<p>match = regex.search(text)</p><p>这里我们用 <code>\d\d\d\d</code> 搜索文本中的四个连续数字。若匹配成功，结果是一个 match 对象，带有 <code>.start()</code> 和 <code>.end()</code> 两个方法。</p><p>这些方法分别返回已找到模式的文本部分的开始和结束索引<em></em>：</p><p><code>import re</code></p>
<p>regex = re.compile(r&quot;\d\d\d\d&quot;)</p>
<p>text = &quot;Your id is 4933&quot;</p>
<p>match = regex.search(text)</p>
<p>start, end = match.start(), match.end()</p>
<p># 开始是 11，结束是 15</p><p>有了这些索引，我们现在可以提取匹配项：</p><p><code>import re</code></p>
<p>regex = re.compile(r&quot;\d\d\d\d&quot;)</p>
<p>text = &quot;Your id is 4933&quot;</p>
<p>match = regex.search(text)</p>
<p>start, end = match.start(), match.end()</p>
<p>found = text[start:end] # 4933</p>
<p>在 JavaScript 中可用字面量或 <code>RegExp</code> 构造函数达到同样效果：</p><p><code>const regex = new RegExp(/\d\d\d\d/);</code></p><p>我们还可以将标志传递给构造函数。这里我们构建一个全局正则表达式：</p><p><code>const regex = new RegExp(/\d\d\d\d/, &quot;g&quot;);</code></p><p>现在给定要匹配的文本，可用 <code>.exec()</code> 与 <code>lastIndex</code> 在字符串中多次匹配，或更好的是使用新的 <code>String.prototype.matchAll()</code>，返回 <a href="https://www.valentinog.com/blog/generator/#iterables-and-iterators">迭代器</a>：</p><p><code>const regex = new RegExp(/\d\d\d\d/, &quot;g&quot;);</code></p><p>const text = &quot;Your 7795 id is 4933&quot;;</p>
<p>const match = text.matchAll(regex);</p>
<p>const found = [...match].map((el) =&gt; el[0]);</p><p>// [&#x27;7795&#x27;，&#x27;4933&#x27;]</p><h2 id="python--javascript">Python 数据类型和 JavaScript：不变性和变量</h2><p>您可能从上表中注意到，<strong>大多数基本 Python 数据类型都是不可变的</strong>。 <strong>JavaScript在实践中似乎更轻松</strong>。在浏览器控制台或 Node.js REPL 中尝试以下示例。数字？ <strong>看起来</strong>可变：</p><p><code>var a = 34;</code></p><p>++a</p><p>// 返回 35</p><p>布尔值？ <strong>看起来</strong>也是可变的：</p><p><code>var x = false;</code></p><p>x++;</p><p>// 返回 0 并且 x 变为 1</p><p>无效的？再次，<strong>似乎</strong>是可变的：</p><p><code>var x = null;</code></p><p>x++;</p><p>// 返回 0 并且 x 变为 1</p><p><strong>别被愚弄了。这里改变的不是底层原语</strong>，而是分配给变量的<em></em>值。</p><p><strong>最后两个带有 boolean 和 null 的示例是类型强制的情况。引擎将这两个值转换为相应的数字表示</strong>，并且变量被分配新值。</p><p>参见 <a href="https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Another_Example_Step-by-step">this example on MDN for more</a>。</p><h2 id="python--javascript-">Python 和 JavaScript 中的复杂数据类型</h2><p><strong>复杂数据类型是一种更复杂的形状</strong>，与字符串和数字等简单基元相反。在Python中，我们可以命名<strong>列表、字典、集合和元组</strong>。</p><p>相反，在 JavaScript 中，区别稍微不太明显，因为 <code>Object</code></p><p>是最顶层的复杂类型和其他子类型，例如 <code>Array</code></p><p>和<code>Set</code></p><p>是其“祖先”的特殊版本（<code>Function</code></p><p>也是一个对象）。</p><p>以下是 Python 和 JavaScript 中复杂数据类型的细分：</p><table><thead><tr><th>PYTHON</th><th>JAVASCRIPT</th><th>Python 中是否可变</th><th>JavaScript 中是否可变</th></tr></thead><tbody><tr><td>list</td><td>Array</td><td>是</td><td>是</td></tr><tr><td>dictionary</td><td>Object</td><td>是</td><td>是</td></tr><tr><td>set</td><td>Set</td><td>是</td><td>是</td></tr><tr><td>tuple</td><td>n/a</td><td>否</td><td>n/a</td></tr></tbody></table><p>正如你所看到的 <strong>JavaScript 中的数组和对象总是可变的</strong>，所以 <code>Object</code></p><p>。除非使用外部库，否则无法真正保护对象（<code>Object.freeze</code></p><p>是浅的）。不过，未来有计划将 <a href="https://github.com/rricard/proposal-const-value-types">immutable types</a> 添加到 JavaScript 中。</p><p>另一方面，Python 是一种<strong>不可变的复杂类型，称为元组</strong>（而列表、字典和集合是可变的）。让我们更详细地了解 Python 复杂类型。</p><h2 id="python-">Python 数据类型：列表和元组</h2><p>Python 中的列表是元素的集合，就像 JavaScript 数组一样。这是一个 Python 列表：</p><p><code>&gt;&gt;&gt; my_list = [&quot;vale&quot;, 98, &quot;caty&quot;, None]</code></p><p><strong>列表操作总是会改变原始列表</strong>：</p><p><code>&gt;&gt;&gt; my_list = [&quot;a&quot;, None, 44, True, &quot;f&quot;]</code></p><blockquote>
<p>&gt;&gt; my_list.append(44)</p></blockquote><blockquote>
<p>&gt;&gt; my_list.remove(&#x27;f&#x27;)</p></blockquote><blockquote>
<p>&gt;&gt; print(my_list)</p></blockquote>
<p># 输出：[“a”，无，44，真，44]</p><p>相反，元组是不可变的，实际上它只有两个只读操作（索引和计数）：</p><p><code>&gt;&gt;&gt; my_tuple = (&quot;vale&quot;, &quot;Italy&quot;, 105)</code></p><blockquote><p>&gt;&gt; my_tuple.count(&quot;Italy&quot;) # 1</p></blockquote><blockquote>
<p>&gt;&gt; my_tuple.index(105) # 2</p></blockquote>
<p>请注意，<strong>元组包含在圆括号中</strong>。</p><p>我真的很喜欢 Python 如何<strong>使用加法运算符将两个列表连接在一起</strong>：</p><p><code>&gt;&gt;&gt; my_list = [&quot;a&quot;, None, 44, True, &quot;f&quot;]</code></p><blockquote><p>&gt;&gt; another_list = [&quot;b&quot;, None, 89, False, &quot;x&quot;]</p></blockquote><blockquote>
<p>&gt;&gt; my_list + another_list</p></blockquote>
<p># 输出：[“a”，无，44，True，44，“b”，无，89，False，“x”]</p><p>但是<strong>不敢用 JavaScript 尝试这个技巧</strong>，你会感到惊讶：</p><p><code>var my_list = [&quot;a&quot;, null, 44, true, &quot;f&quot;]</code></p><p>var another_list = [&quot;b&quot;, null, 89, false, &quot;x&quot;]</p>
<p>result = my_list + another_list</p><p>// 结果是：“a,,44,true,fb,,89,false,x”</p><p>与 <a href="https://www.ecma-international.org/ecma-262/10.0/index.html#sec-addition-operator-plus-runtime-semantics-evaluation">JavaScript spec goes</a> 一样，当操作数都是非字符串时（如本示例所示），加法运算符会将其操作数转换为字符串。注意，<strong>Python 列表和元组不能连接在一起</strong>：</p><p><code>&gt;&gt;&gt; my_tuple = (&quot;vale&quot;, &quot;Italy&quot;, 105)</code></p><blockquote><p>&gt;&gt; my_list = [&quot;a&quot;, None, 44, True, &quot;f&quot;]</p></blockquote><blockquote>
<p>&gt;&gt; my_tuple + my_list</p></blockquote>
<p># 输出类型错误：只能将元组（不是“列表”）连接到元组</p><h2 id="python--javascript-">Python 数据类型：字典和 JavaScript 对象</h2><p>JavaScript 的对象是键/值对的<strong>容器</strong>，也是其他专用对象<strong>的</strong>支柱：</p><p><code>var obj = {</code></p><p>姓名：“约翰”，</p><p>年龄：33</p><p>};</p><p>“JavaScript 中几乎所有东西都是对象”不仅仅是一种说法。这是事实。数组、函数、集​​合等等都只是 JavaScript 对象。</p><p><strong>Python 有类似的东西，称为字典</strong>（或简称 dict），但它只是一个容器，而不是基本构建块：</p><p><code>&gt;&gt;&gt; my_dict = {</code></p><p>“姓名”：“约翰”，</p><p>“城市”：“罗马”，</p><p>“年龄”：44</p><p>}</p><p>字典中的值可以通过它们的键访问或更改：</p><p><code>&gt;&gt;&gt; my_dict = {</code></p><p>“姓名”：“约翰”，</p><p>“城市”：“罗马”，</p><p>“年龄”：44</p><p>}</p><blockquote>
<p>&gt;&gt; my_dict[&quot;name&quot;]</p></blockquote><blockquote>
<p>&gt;&gt; my_dict[&quot;city&quot;] = &quot;Florence&quot;</p></blockquote>
<p>Python 引发 <code>KeyError</code></p><p>当您访问不存在的密钥时：</p><p><code>&gt;&gt;&gt; my_dict = {</code></p><p>“姓名”：“约翰”，</p><p>“城市”：“罗马”，</p><p>“年龄”：44</p><p>}</p><blockquote>
<p>&gt;&gt; my_dict[&quot;not_here&quot;]</p></blockquote>
<p># 输出：KeyError：&#x27;not_here&#x27;</p><p><code>.get()</code></p><p>方法是直接键访问的更安全的替代方法，因为它不会引发，并且让我们为不存在的键指定默认值：</p><p><code>&gt;&gt;&gt; my_dict = {</code></p><p>“姓名”：“约翰”，</p><p>“城市”：“罗马”，</p><p>“年龄”：44</p><p>}</p><blockquote>
<p>&gt;&gt; my_dict.get(&quot;not_here&quot;, &quot;not found&quot;)</p></blockquote>
<p># 输出：“未找到”</p><p>其他只读操作为<code>.items()</code></p><p>，或 <code>.keys()</code></p><p>:</p><p><code>&gt;&gt;&gt; my_dict = {</code></p><p>“姓名”：“约翰”，</p><p>“城市”：“罗马”，</p><p>“年龄”：44</p><p>}</p><blockquote>
<p>&gt;&gt; my_dict.keys() # [&#x27;name&#x27;, &#x27;city&#x27;, &#x27;age&#x27;]</p></blockquote>
<p>您还可以使用 <code>.clear()</code> 从字典中删除元素或完全清除它</p><p>:</p><p><code>&gt;&gt;&gt; my_dict = {</code></p><p>“姓名”：“约翰”，</p><p>“城市”：“罗马”，</p><p>“年龄”：44</p><p>}</p><blockquote>
<p>&gt;&gt; my_dict.pop(&quot;name&quot;) # returns &#x27;John&#x27; and removes it</p></blockquote><blockquote>
<p>&gt;&gt; my_dict.clear() # my_dict is empty now</p></blockquote>
<p>列表、元组、字典和集合是<strong>集合或序列</strong>，因为它们保存一定数量的元素，并公开<strong>与它们交互的通用操作</strong>。</p><h2 id="">没有开关，没有派对</h2><p>“Python 中没有开关？我应该怎么做”……几年前你就可以听到我的声音。你没看错<strong>没有</strong> <code>switch</code></p><p><strong>在Python中</strong>。 Java 程序员喜欢它，JavaScript 开发人员也喜欢它。</p><p><code>switch</code></p><p>语句是 JavaScript 中的常见习惯用法。考虑以下示例：</p><p><code>function getUrlConf(host) {</code></p><p>switch (host) {</p><p>案例“www.example-a.dev”：</p><p>return &quot;firstApp.urls&quot;;</p><p>案例“www-example-b.dev”：</p><p>return &quot;secondApp.urls&quot;;</p><p>案例“www.example-c.dev”：</p><p>return &quot;thirdApp.urls&quot;;</p><p>默认：</p><p>return &quot;Sorry, no match&quot;;</p><p>}</p><p>}</p>
<p>getUrlConf(&quot;www-example-b.dev&quot;);</p><p>函数必须检查主机字符串以根据主机值返回相应的配置。配<code>switch</code></p><p>我们比较值<code>switch (host)</code></p><p>在 <code>case</code> 中</p><p>条款<code>case &quot;www.example-a.dev&quot;:</code></p><p>。对于每个 <code>case</code></p><p>我们返回一个值。 <code>default</code></p><p>子句确保在没有匹配项的情况下返回默认值。</p><p>Python 中没有 <code>switch</code></p><p>：使用字典可以得到相同的结果：</p><p><code>def get_url_conf(host):</code></p><p>mapping = {</p><p>&quot;www.example-a.dev&quot;: &quot;firstApp.urls&quot;,</p><p>&quot;www-example-b.dev&quot;: &quot;secondApp.urls&quot;,</p><p>&quot;www.example-c.dev&quot;: &quot;thirdApp.urls&quot;</p><p>}</p>
<p>return mapping.get(host, &quot;Sorry, no match&quot;)</p><p>如果你问我的话，就干净多了。如果您更喜欢的话，还有 <a href="https://www.valentinog.com/blog/switch/">same pattern works for JavaScript</a>。</p><h2 id="javascript-python-">JavaScript 对象传播/合并，Python 字典传播/合并</h2><p>ECMAScript 2018 添加了“分解”JavaScript <strong>对象</strong>的功能，也称为 <strong>“传播”</strong>。此语法<strong>对于保持对象不变（或克隆它们）特别方便</strong>：</p><p><code>const initial = {</code></p><p>不要触摸：“我的布雷尔”</p><p>};</p>
<p>const next = { ...initial, dontTouch: &quot;just a copy&quot; };</p>
<p>// initial.dontTouch 是“我的 breil”</p><p>// next.dontTouch 是“只是一个副本”</p><p>或者<strong>将它们合并在一起</strong>：</p><p><code>const a = {</code></p><p>姓名：“朱莉安娜”，</p><p>年龄：33</p><p>};</p>
<p>const b = {</p><p>姓氏：“克莱恩”，</p><p>城市：“旧金山”</p><p>};</p>
<p>const movie = {</p><p>书名：《高堡里的男人》</p><p>};</p>
<p>const all = { ...a, ...b, ...movie };</p>
<p>console.log(all);</p>
<p>/*</p><p>输出：</p><p>{</p><p>名称：“朱莉安娜”，</p><p>年龄：33，</p><p>姓氏：“克莱恩”，</p><p>城市：“旧金山”，</p><p>书名：《高堡里的人》</p><p>}</p><p>*/</p><p>在对象传播之前，使用 <code>Object.assign</code> 实现了相同的结果</p><p>:</p><p><code>const initial = {</code></p><p>不要触摸：“我的布雷尔”</p><p>};</p>
<p>const next = Object.assign({}, initial, { dontTouch: &quot;just a copy&quot; });</p><p>有几种方法可以在 <strong>Python</strong> 中获得相同的结果：一种方法涉及遍历 <code>dict.update()</code></p><p>我不会在这里介绍。</p><p><strong>字典解包看起来更像是 JavaScript 的对象传播</strong>，我更喜欢它。这是第一个例子：</p><p><code>initial = {&quot;dont_touch&quot;: &quot;my breil&quot;}</code></p>
<p>next = {**initial, &quot;dont_touch&quot;: &quot;just a copy&quot;}</p><p>这里是<code>initial</code></p><p>被传播到 <code>next</code></p><p>，“dont_touch”的值会覆盖原始值，同时保留 <code>initial</code></p><p>。要<strong>合并两个或多个字典</strong>，我们可以这样做：</p><p><code>a = {&quot;name&quot;: &quot;Juliana&quot;, &quot;age&quot;: 33}</code></p>
<p>b = {&quot;surname&quot;: &quot;Crain&quot;, &quot;city&quot;: &quot;San Francisco&quot;}</p>
<p>movie = {&quot;title&quot;: &quot;The man in the high castle&quot;}</p>
<p>all = {<strong>a, </strong>b, **movie}</p><p>不幸的是，字典解包有一些限制，导致 Python 添加<strong>字典的联合运算符</strong>。所以从 <strong>Python 3.9 开始你可以做 union</strong>：</p><p><code>a = {&quot;name&quot;: &quot;Juliana&quot;, &quot;age&quot;: 33}</code></p>
<p>b = {&quot;surname&quot;: &quot;Crain&quot;, &quot;city&quot;: &quot;San Francisco&quot;}</p>
<p>movie = {&quot;title&quot;: &quot;The man in the high castle&quot;}</p>
<p>all = a | b | movie  # Dict union!</p>
<p>print(all)</p><p>”“”</p><p>输出</p>
<p>{&#x27;name&#x27;: &#x27;Juliana&#x27;, &#x27;age&#x27;: 33, &#x27;surname&#x27;: &#x27;Crain&#x27;, &#x27;city&#x27;: &#x27;San Francisco&#x27;, &#x27;title&#x27;: &#x27;The man in the high castle&#x27;}</p><p>”“”</p><p>整洁的！更多<a href="https://www.python.org/dev/peps/pep-0584/">info here</a>。</p><h2 id="python-set--javascript-set">Python 数据类型：Set 和 JavaScript Set</h2><p><strong>JavaScript 的 Set 是一种方便的数据结构</strong>，可用于存储<strong>唯一值</strong>。 Set 可以包含原始类型（甚至为 null 和未定义）或对对象的引用：</p><p><code>const mySet = new Set();</code></p>
<p>mySet.add( &quot;aString&quot; )</p><p>mySet.add( null )</p><p>mySet.add( NaN )</p><p>mySet.add(1)</p><p>mySet.add(1)</p>
<p>// Output: Set { &#x27;aString&#x27;, null, NaN, 1 }</p><p>除了添加元素、检查它们是否存在或循环遍历之外，您对 JavaScript Set 没有什么可以做的。 Set 有以下方法：</p><ul><li>.添加</li><li>.清除</li><li>.删除</li><li>.条目</li><li>.forEach</li><li>.有</li><li>.keys</li><li>.尺寸</li><li>.values</li></ul><p>Python 的 Set 的工作原理大致相同。您可以在其中添加<strong>独特</strong>元素或也检查它们：</p><p><code>&gt;&gt;&gt; my_set = set()</code></p><blockquote><p>&gt;&gt; my_set.add( &quot;aString&quot; )</p></blockquote><blockquote>
<p>&gt;&gt; my_set.add( None )</p></blockquote><blockquote>
<p>&gt;&gt; my_set.add( 1 )</p></blockquote><blockquote>
<p>&gt;&gt; my_set.add( 2 )</p></blockquote><blockquote>
<p>&gt;&gt; my_set.add( 1 )</p></blockquote><blockquote>
<p>&gt;&gt; my_set</p></blockquote>
<p># Output: {None, 2, &#x27;aString&#x27;, 1}</p><p>然而，与 JavaScript 的主要区别在于 <strong>Python 有更多有用的数学集合运算方法</strong>，如“差”、“交”、“issubset”、“issuperset”、“union”等。</p><p>在 Python 中声明 Set 也有文字形式：</p><p><code>&gt;&gt;&gt; my_set = { None, &quot;caty&quot;, &quot;venice&quot;, 84}</code></p><blockquote><p>&gt;&gt; another_set = { &quot;and&quot;, &quot;mouse&quot;, 44, &quot;a&quot; }</p></blockquote>
<p>正如您可能已经猜到的那样，<strong>两种语言中的设置对于删除重复项确实很有用</strong>。在Python中可以做一些不错的事情，比如<strong>union</strong>：</p><p><code>&gt;&gt;&gt; my_set | another_set</code></p>
<p># Output: {&#x27;and&#x27;, &#x27;mouse&#x27;, None, &#x27;venice&#x27;, &#x27;caty&#x27;, 44, 84, &#x27;a&#x27;}</p><p>或<strong>交叉点</strong>：</p><p><code>&gt;&gt;&gt; my_set = { None, &quot;caty&quot;, &quot;venice&quot;, 84, &quot;common_element&quot; }</code></p><blockquote><p>&gt;&gt; another_set = { &quot;and&quot;, &quot;mouse&quot;, 44, &quot;a&quot;, &quot;common_element&quot; }</p></blockquote><blockquote>
<p>&gt;&gt; my_set &amp; another_set</p></blockquote>
<p># Output: {&#x27;common_element&#x27;}</p><p>值得一提的是，集合在 JavaScript 和 Python 中都是可变的，但集合内的单个元素仍然是不可变的。</p><h2 id="python--javascript-">Python 和 JavaScript 中的成员资格运算符</h2><p>“这个元素是否存在于另一个元素内部”？ <strong>会员运营商</strong>知道答案。 Python 和 JavaScript 都有一个名为 <strong>in</strong> 的成员运算符：</p><table><thead><tr><th>PYTHON</th><th>JAVASCRIPT</th></tr></thead><tbody><tr><td><code>in</code></td><td><code>in</code></td></tr></tbody></table><p>在 Python 中，您可以对几乎任何数据类型使用 <strong>in</strong>。例如，您可以检查字符是否出现在字符串中：</p><p><code>&gt;&gt;&gt; &quot;o&quot; in &quot;Florence&quot;</code></p>
<p># 输出：真</p><p>它也适用于列表、元组和字典：</p><p><code>&gt;&gt;&gt; my_list = [&quot;vale&quot;, 98, &quot;caty&quot;, None]</code></p><blockquote><p>&gt;&gt; 98 in my_list # Output: True</p></blockquote><blockquote>
<p>&gt;&gt; my_tuple = (&quot;vale&quot;, &quot;Italy&quot;, 105)</p></blockquote><blockquote>
<p>&gt;&gt; &quot;italy&quot; in my_tuple # Output: False</p></blockquote>
<p>my_dict = {</p><p>“姓名”：“约翰”，</p><p>“城市”：“罗马”，</p><p>“年龄”：44</p><p>}</p><p>my_dict 中的“age” # 输出：True</p><p>相反，在 <strong>JavaScript 中</strong>，<strong>in 运算符<em></em></strong>无论是在字符串上还是在数组上都不能直观地工作<em></em>：</p><p><code>&quot;a&quot; in [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]</code></p><p>// 输出：假</p><p>“佛罗伦萨”中的“o”</p><p>// 输出：类型错误：无法使用“in”运算符在佛罗伦萨搜索“o”</p><p>它对字符串没有用，并且<strong>不搜索数组中的实际元素</strong>。它寻找的是<strong>数组索引</strong>：</p><p><code>1 in [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]</code></p><p>// 输出：真</p><p>考虑到 Array 是 Object 的子类型，并且将其想象为具有以下形状的对象更正确，这是有道理的：</p><p><code>var myArr = {</code></p><p>0：“一”，</p><p>1：“b”，</p><p>2：“c”</p><p>};</p><p>回顾一下，JavaScript 中的 <strong>in</strong> 仅适用于对象键，如果给定键存在于对象中<em></em>，则返回 true：</p><p><code>var my_obj = {&quot;name&quot;: &quot;John&quot;, &quot;city&quot;: &quot;Rome&quot;, &quot;age&quot;: 44}</code></p><p>my_obj 中的“名称”</p>
<p>// 输出：真</p><h2 id="">实例操作符：免责声明</h2><p>“谁创建了这个对象”？ <strong>实例操作员</strong>尝试回答这个问题。现在，在你对我大喊大叫之前，让我先澄清一下。 <strong>下表只是我对一个更复杂的故事的心理表征</strong>：</p><table><thead><tr><th>PYTHON</th><th>JAVASCRIPT</th></tr></thead><tbody><tr><td><code>isinstance()</code></td><td><code>instanceof</code></td></tr><tr><td><code>type()</code></td><td><code>typeof</code></td></tr></tbody></table><p>正如您将看到的，在这个问题上不可能将 Python 和 JavaScript 1 对1 映射，因为 Python 是真正面向对象的，而 <strong>JavaScript 是基于原型的</strong>。</p><p>如果您了解一点 JavaScript，您就会知道“类”是一种幻觉，每次我们谈论 JavaScript 中的类时，我们都在撒谎。</p><p>但让我们用一些例子来揭开我的表格的神秘面纱。</p><h2 id="python-isinstance--javascript-instanceof">Python 类、isinstance 和 JavaScript instanceof</h2><p>考虑一个 Python 类，并由它创建一个新对象：</p><p><code>class Person:</code></p><p>def <strong>init</strong>(self, name, age):</p><p>self.name = 姓名</p><p>自我年龄 = 年龄</p>
<p>def print_details(self):</p><p>details = f&quot;Name: {self.name} - Age: {self.age}&quot;</p><p>print(details)</p>
<p>tom = Person(&quot;Tom&quot;, 89)</p>
<p>tom.print_details()</p><p>汤姆是什么？在面向对象编程中，我们说它是一个<strong>类实例</strong>，即<strong>从类蓝图构造的新对象</strong>。 Python 中的 <strong>isinstance</strong> （它是一个函数，而不是一个运算符）如果一个对象似乎是由给定的类构建的，则返回 true。所以在我们的例子中：</p><p><code>&gt;&gt;&gt; isinstance(tom, Person)</code></p>
<p># 输出：真</p><p>那么用 JavaScript 来代替呢？这是同一个类：</p><p><code>class Person {</code></p>
<p>constructor(name, age) {</p><p>这个.name = 名称</p><p>this.age = 年龄</p><p>}</p>
<p>printDetails() {</p><p>const details = <code>Name: ${this.name} - Age: ${this.age}</code></p><p>console.log(details)</p><p>}</p>
<p>}</p>
<p>const tom = new Person(&quot;Tom&quot;, 44)</p><p>汤姆现在怎么样了？让我们来看看：</p><p><code>console.log(tom instanceof Person)</code></p><p>// 输出：真</p>
<p>并非严格的「实例」：<code>tom</code> 只是通过原型链连到 <code>Person.prototype</code> 的<strong>普通对象</strong>，同时也连到 <code>Object</code>：</p><p><code>console.log(tom instanceof Object)</code></p><p>// 输出：真</p><p>因此 <strong>JavaScript 的 <code>instanceof</code></strong> 在对象「看起来」由某类构造时返回 true，<strong>并不是因为严格意义上的实例</strong>，而只是因为 <code>tom</code> 通过原型链连到了 <code>Person.prototype</code>。</p><p>我尝试比较两种语言的实例运算符，这是我能想到的最好的结果。我认为 <strong>Python 的 isinstance() 和 JavaScript instanceof 相似只是因为它们的行为</strong>，但这仍然是一个危险的比较。</p><p>如果是这样，您可能会想，为什么要引入 isinstance() 呢？这导致使用下一节......</p><h2 id="python--type--javascript--typeof">Python 中的 type() 和 JavaScript 中的 typeof</h2><p>任何时候你想<strong>检查 JavaScript 中给定值的类型，typeof</strong> 都是你的朋友：</p><p><code>typeof &quot;alex&quot; // &quot;string&quot;</code></p><p>typeof 9 //“数字”</p><p>typeof [1,2] // “对象”</p><p><strong>当我们想要检查函数是否存在时，JavaScript 中的 typeof 很方便</strong>，例如：</p><p><code>if (typeof window.futureStuff === &quot;undefined&quot;) {</code></p><p>window.futureStuff = function () {</p><p>// 做事</p><p>}</p><p>}</p><p>来自 JavaScript 的您可能希望在 Python 中使用类似的运算符。 <strong>type()</strong> 函数可能是您的首选，因为它似乎与 JavaScript 的 typeof 相关。但是 <strong>type()</strong> 返回不同的东西：</p><p><code>&gt;&gt;&gt; type(tom)</code></p><p># 输出：<class></class></p><blockquote>
<p>&gt;&gt; type(&#x27;ahh&#x27;)</p></blockquote>
<p># 输出：&lt;类&#x27;str&#x27;&gt;</p><p>不太方便。 <strong>isinstance() 非常适合这里的工作</strong>，这就是我在上一节中引入它的原因。它的工作原理如下：</p><p><code>&gt;&gt;&gt; isinstance(9, int)</code></p><p># 输出：真</p><blockquote>
<p>&gt;&gt; isinstance(tom, Person)</p></blockquote>
<p># 输出：真</p><blockquote>
<p>&gt;&gt; isinstance(&quot;caty&quot;, str)</p></blockquote>
<p># 输出：真</p><p><strong>当您需要根据另一个已知类型检查值时 </strong>isinstance()<strong> 很有用</strong>。然而，<strong>可能存在您需要模仿 typeof</strong> 的情况。在这种情况下，您仍然可以通过根据内置类型检查对象来使用 Python 的类型函数：</p><p><code>&gt;&gt;&gt; type(9) is int</code></p><p># 输出：真</p><h2 id="python">Python中如何处理异常：什么是异常？</h2><p>如果程序能一直运行良好就好了。但现实世界有点疯狂。如果一个程序可能会失败，那么它肯定会失败。</p><p>考虑一个简单的 Python 函数。这里可能会出现什么问题？</p><p><code>def divide(a, b):</code></p><p>result = a / b</p><p>return result</p>
<p>divide(89, 6)</p><p>divide(89, &quot;6&quot;)</p><p>divide(89, 2)</p><p>将代码保存在文件中（我将其命名为 <code>exceptions.py</code></p><p>）并运行它。你会看到很多东西，更重要的是，一个<code>TypeError</code></p><p>:</p><p><code>TypeError: unsupported operand type(s) for /: &#x27;int&#x27; and &#x27;str&#x27;</code></p><p>它来自对divide的第二次调用，我们试图将一个数字和一个字符串相除，这在Python中是无效的（在JavaScript中也是如此）。程序停止，永远不会到达第三个函数调用。</p><p>Python 在这里所做的是<strong>引发异常</strong>，即一个异常事件会导致我们的代码崩溃。</p><p>所以<strong>异常是严重的错误，大多数时候可以停止程序</strong>。我们如何<strong>处理异常</strong>决定了失败的程序和可以自行恢复的程序之间的区别。</p><h2 id="-python--javascript-">处理 Python 和 JavaScript 中的同步异常</h2><p>如果我们处于<strong>同步世界</strong>，那么在处理异常方面，Python 和 JavaScript 非常相似。</p><p>在Python中有<code>try/except</code></p><p>。 <code>try</code></p><p>块应该处理代码的“快乐路径”，而 <code>except</code></p><p>将拦截实际的异常。</p><p>前面的示例可以重构为：</p><p><code>def divide(a, b):</code></p><p>try:</p><p>return a / b</p><p>except TypeError:</p><p>print(&quot;Oops!&quot;)</p>
<p>divide(89, 6)</p><p>divide(89, &quot;6&quot;)</p><p>divide(89, 2)</p><p>请注意，您应该指定除 <code>TypeError</code> 之外的错误类型</p><p>在我们的例子中。</p><p><code>try/except</code></p><p>或多或少意味着：尝试此代码，如果失败<strong>拦截错误</strong>并执行其他操作。</p><p>现在程序打印“Oops!”，但是<strong>它不再停止</strong>。所有函数调用（除了出错的函数调用）都会继续运行。</p><p>您发现了如何<strong>在 Python 中处理异常</strong>！ JavaScript 怎么样？</p><p>考虑以下示例 (JavaScript)：</p><p><code>function find(member, target) {</code></p><p>return member in target;</p><p>}</p>
<p>find(&quot;a&quot;, &quot;siena&quot;);</p><p>这是对<code>in</code>的无效使用</p><p>运算符（仅适用于对象）。</p><p>运行代码，您应该看到：</p><p><code>TypeError: Cannot use &#x27;in&#x27; operator to search for &#x27;a&#x27; in siena</code></p><p>好的 JavaScript，很公平。为了处理错误，JavaScript 中有 <code>try/catch</code></p><p>。 <code>try</code></p><p>尝试快乐的道路，而<code>catch</code></p><p>处理问题。</p><p>我们可以将代码重写为：</p><p><code>function find(member, target) {</code></p><p>try {</p><p>return member in target;</p><p>} catch (err) {</p><p>console.log(&quot;Oops!&quot;);</p><p>}</p><p>}</p>
<p>find(&quot;a&quot;, &quot;siena&quot;);</p><p>var result = find(&quot;city&quot;, { name: &quot;Jane&quot;, city: &quot;London&quot; });</p><p>console.log(result);</p><p>现在程序打印“哎呀！”、“true”，并且<strong>不再停止</strong>。</p><p>注意参数<code>err</code></p><p>？让我们在下一节中看看如何使用它。</p><p>嘘。查看 <a href="https://www.valentinog.com/blog/error/">&quot;A mostly complete guide to error handling in JavaScript&quot;</a> 以获取有关错误和异常的深入指南。</p><h2 id="-python--javascript-">如何处理异常：在 Python 和 JavaScript 中使用错误消息</h2><p>处理异常固然很棒，但打印“Oops”却并非如此。最好打印<strong>错误消息</strong>。</p><p>在JavaScript中，我们可以使用<code>catch</code>的参数</p><p>，大多数时候叫<code>error</code></p><p>或<code>err</code></p><p>作为一个惯例。它包含实际的<strong>异常对象</strong>。</p><p>反过来，几乎每个异常都有一个名为 <code>message</code> 的属性</p><p>，与实际的文本错误。前面的 JavaScript 示例变为：</p><p><code>function find(member, target) {</code></p><p>try {</p><p>return member in target;</p><p>} catch (err) {</p><p>console.log(err.message);</p><p>}</p><p>}</p>
<p>find(&quot;a&quot;, &quot;siena&quot;);</p><p>var result = find(&quot;city&quot;, { name: &quot;Jane&quot;, city: &quot;London&quot; });</p><p>console.log(result);</p><p>打印：</p><p><code>Cannot use &#x27;in&#x27; operator to search for &#x27;a&#x27; in siena</code></p><p>真的</p><p>比“哎呀”好多了。</p><p>在Python中，你可以用<code>as</code>做同样的事情</p><p>:</p><p><code>def divide(a, b):</code></p><p>try:</p><p>return a / b</p><p>except TypeError as err:</p><p>print(err)</p><p>这个例子不言而喻：拦截<code>TypeError</code></p><p>并创建一个名为 <code>err</code> 的绑定</p><p>for convenience. Then print the error.</p><h2 id="-python--javascript-">在 Python 和 JavaScript 中引发自己的异常</h2><p>与编程中的几乎任何事物一样，即使对于最简单的事物，术语也是模糊的。</p><p>以术语<strong>抛出</strong>为例。当我们开发人员需要终止程序并说“呃，这不好，让我们停止”时，在 JavaScript 中通常会说“抛出”。</p><p>错误可能是 JavaScript 引擎抛出的，也可能是开发人员在检查无效值时故意抛出的。</p><p>我们也可以<strong>说“引发”而不是抛出，并将错误称为“异常”</strong>。你为什么会提出或抛出错误？再次考虑 JavaScript 示例。我们知道<code>in</code></p><p>运算符仅适用于对象。</p><p>如果我们检查参数 <strong>target</strong>，当它是字符串时，我们可以 <strong>抛出</strong> 错误。方法如下：</p><p><code>function find(member, target) {</code></p><p>if (typeof target === &quot;string&quot;)</p><p>throw TypeError(&quot;Target must be an object, got string&quot;);</p>
<p>try {</p><p>return member in target;</p><p>} catch (err) {</p><p>console.log(err.message);</p><p>}</p><p>}</p><p>现在调用该函数：</p><p><code>find(&quot;a&quot;, &quot;siena&quot;);</code></p><p>添加 throw 的最终效果是该函数的使用者将得到错误：</p><p><code>Error: Target must be an object, got string</code></p><p>这是预期的，如果目标是字符串，我们希望停止执行，因此代码永远不会到达 <code>try</code></p><p>堵塞。</p><p>现在让我们回到Python。我们留下了这段代码：</p><p><code>def divide(a, b):</code></p><p>try:</p><p>return a / b</p><p>except TypeError as err:</p><p>print(err)</p><p>这里我们可以在相除之前检查两个参数是否都是数字，然后抛出错误。 <code>throw</code></p><p>Python 中的对应项是 <code>raise</code></p><p>:</p><p><code>def divide(a, b):</code></p><p>if isinstance(a, str) or isinstance(b, str):</p><p>raise TypeError(&quot;预期，得到对象&quot;)</p><p>try:</p><p>return a / b</p><p>except TypeError as err:</p><p>print(err)</p><p>当调用该函数时：</p><p><code>divide(1, &quot;2&quot;)</code></p><p>你应该看到：</p><p><code>TypeError: Expected in, got object</code></p><p>注意这次使用<code>TypeError</code></p><p>for creating a custom error message. If you want you can also extend <code>Exception</code></p><p>创建您自己的自定义异常：</p><p><code>class CustomError(Exception):</code></p><p>pass</p>
<p>def divide(a, b):</p><p>if isinstance(a, str) or isinstance(b, str):</p><p>raise CustomError(&quot;预期，得到对象&quot;)</p><p>try:</p><p>return a / b</p><p>except TypeError as err:</p><p>print(err)</p>
<p>divide(1, &quot;2&quot;)</p><p>作为 <code>raise</code> 的替代品</p><p>我们也可以使用<code>assert</code></p><p>，方便开发中的调试：</p><p><code>def divide(a, b):</code></p><p>断言 isinstance(a, str), &quot;预期 int，得到对象&quot;</p><p>断言 isinstance(a, str), &quot;预期 int，得到对象&quot;</p><p>try:</p><p>return a / b</p><p>except TypeError as err:</p><p>print(err)</p>
<p>divide(1, &quot;2&quot;)</p><h2 id="-restructuredtext--python-">使用 reStructuredText 编写 Python 代码文档</h2><p>在 <strong>JavaScript 中，我们使用 JSDoc</strong> 来添加类、函数参数、函数返回值等的文档。下面是 JSDoc 带注释的函数的示例：</p><p><code>/**</code></p><ul><li><p>将数字提高到指数</p></li><li><p>@param {number} value - 要提高的基数</p></li><li><p>@param {number} exponent - 指数</p></li><li><p>@return {number} - 指数幂</p></li></ul><p>*/</p><p>function poooow(value, exponent) {</p><p>return value <em></em> exponent;</p><p>}</p><p>请注意参数和返回上的“number”类型。通过添加这些类型提示，您可以帮助 IDE 在您使用该函数时为您提供帮助（无双关语）。同样的概念也适用于 Python。</p><p><strong>如何在Python中添加代码文档</strong>？实际上有很多方法，但<strong>reStructuredText是推荐的方法之一</strong>。 reStructuredText 是纯文本，可以嵌入到 Python 文档字符串中。</p><p><strong>Docstring 只是一个恰好存在于 Python 函数或模块内部的字符串，并充当代码的文档</strong>。为了给您提供一些背景信息，这里有一个 Python 函数内的 Docstring 示例：</p><p><code>def string_repeat(string, count):</code></p><p>”“”</p><p>重复字符串 N 次</p><p>”“”</p><p>return string * count</p><p>当您决定添加<strong>参数和返回值的文档</strong>时，reStructuredText 就会发挥作用。这是我们的示例，使用 reStructuredText 进行了丰富：</p><p><code>def string_repeat(string, count):</code></p><p>”“”</p><p>重复字符串 N 次</p><p>:param string: 要重复的字符串</p><p>:param count: 重复次数</p><p>:return: 字符串重复N次</p><p>”“”</p><p>return string * count</p><p>我知道，<strong>文档可能看起来多余</strong>，但如果您决定尝试静态类型，reStructuredText 可以<strong>大放异彩</strong>。首先，有一点类型理论。</p><h2 id="">一点类型理论：动态类型与静态类型</h2><p><strong>Python 和 JavaScript 是动态语言</strong>。也就是说，它们给予程序员几乎绝对的自由。例如，在 JavaScript 中，变量可能首先包含字符串，然后更改为包含布尔值。 JavaScript 引擎不会抱怨。 （但是，您可以使用 <strong>const</strong> 防止重新分配基本类型）。</p><p>Python 也做同样的事情。考虑以下示例：</p><p><code>&gt;&gt;&gt; name = &quot;Caty&quot;</code></p><blockquote><p>&gt;&gt; name = True</p></blockquote><blockquote>
<p>&gt;&gt; print(name)</p></blockquote>
<p>真的</p><p>变量 <strong>name</strong> 最初是字符串类型，但后来更改为布尔类型。大多数时候<strong>通过自律，你可以驯服动态打字并在晚上睡觉</strong>。在某些情况下，动态类型可能是不允许的，或者风险太大（想想金融系统）。</p><p>与动态类型相反，<strong>静态类型</strong>意味着为每个变量分配一个<strong>固定类型</strong>，因此变得<strong>更难以意外更改类型</strong>。</p><p>然而，<strong>静态类型是一种不同的野兽</strong>，学习它可能是一个<strong>痛苦的过程，特别是对于初学者来说</strong>。</p><p>幸运的是，在 JavaScript 和 Python 中，我们都可以使用<strong>文档来“逐步打字”</strong>。</p><p>让我们在下一节中了解如何<strong>在 Python 中使用 reStructuredText 添加类型</strong>。</p><h2 id="-restructuredtext--python-">使用 reStructuredText 在 Python 中逐步输入</h2><p>借助 reStructuredText，您可以熟悉 Python 中的静态类型，而无需在代码中添加实际类型。</p><p>除了“:param”和“:return”等常见标签之外，reStructuredText还有<strong>:type</strong>和<strong>:rtype</strong>，分别用于<strong>为参数和返回值添加类型</strong>。</p><p>如果您想跟随代码进行操作，那么现在是创建并激活 Python 虚拟环境的好时机（在下一节中，我们还需要安装 mypy）：</p><p><code>mkdir docs_and_typings &amp;&amp; cd $_</code></p><p>python3 -m venv venv</p><p>源 venv/bin/activate</p><p>为了说明该示例，请在项目文件夹中创建一个名为 <strong>my_str_utils.py</strong> 的新文件，其中包含以下函数：</p><p><code>def string_repeat(string, count):</code></p><p>”“”</p><p>重复字符串 N 次</p><p>:param string: 要重复的字符串</p><p>:param count: 重复次数</p><p>:return: 字符串重复N次</p><p>”“”</p><p>return string * count</p><p>现在让我们使用 :type 和 :rtype<strong> 在 reStructuredText 中添加 </strong>类型：</p><p><code>def string_repeat(string, count):</code></p><p>”“”</p><p>重复字符串 N 次</p><p>:param string: 要重复的字符串</p><p>：类型字符串：str</p><p>:param count: 重复次数</p><p>：类型计数：int</p><p>:return: 字符串重复N次</p><p>:r 类型：str</p><p>”“”</p><p>return string * count</p><p>这是<strong>渐进式打字</strong>：您现在拥有一个类型化函数，而没有静态类型系统的认知负担。 <strong>大多数 IDE 都能够读取 reStructuredText 类型</strong>，例如在 PyCharm 中 <strong>当参数的类型与实际参数的类型不匹配时，您会收到警告</strong>：</p><p>同样在 Pycharm 中，通过单击函数名称并按 Ctrl+Q（或 MacO 上的 F1），您可以获得实际的类型提示：</p><p>这些提示与我们将在下一节中看到的“<strong>真实类型提示</strong>”相同（大多数时候，即使没有 :rtype，PyCharm 也能够通过检查 return 语句来推断返回类型）。</p><p>在 JavaScript 中，我们可以使用 <a href="https://www.valentinog.com/blog/jsdoc/">JSDoc types</a> 获得相同的效果。</p><h2 id="python--javascript-">Python 和 JavaScript 中的静态类型</h2><p>我之前给出的渐进打字的定义有点模糊。确实，<strong>您可以通过文档字符串中的代码文档了解静态类型</strong>。</p><p><strong>但是渐进类型的更严格解释是：在代码中逐渐添加真实类型的过程</strong>。现在我所说的“类型”是指可以使用工具检查的类型注释。</p><p>JavaScript 有 <strong>TypeScript</strong>：JavaScript 之上的一层，您可以插入其中。 <a href="https://www.valentinog.com/blog/typescript/">TypeScript is an actual language on its own</a>，但可以逐渐适应任何 JavaScript 代码库。</p><p><strong>Python</strong> 也是如此：近年来它获得了 <a href="https://www.python.org/dev/peps/pep-0484/">an optional type system based on &quot;type hints&quot;</a>。在这里，我们将在 30,000 英尺的高度看到 Python 类型提示。 <strong>一旦掌握了基础知识</strong>希望<strong>您将能够将类型应用到您的Python代码中</strong>。让我们采用上一节中的 <strong>string_repeat</strong> ：</p><p><code>def string_repeat(string, count):</code></p><p>”“”</p><p>重复字符串 N 次</p><p>:param string: 要重复的字符串</p><p>：类型字符串：str</p><p>:param count: 重复次数</p><p>：类型计数：int</p><p>:return: 字符串重复N次</p><p>:r 类型：str</p><p>”“”</p><p>return string * count</p><p>这次我们将去掉 reStructuredText 类型：</p><p><code>def string_repeat(string, count):</code></p><p>”“”</p><p>重复字符串 N 次</p><p>:param string: 要重复的字符串</p><p>:param count: 重复次数</p><p>:return: 字符串重复N次</p><p>”“”</p><p>return string * count</p><p>此时 IDE 将失去所有提示。但是如果您还记得上一节中的“真实”类型提示就像“参数：类型”，那么将它们添加到函数签名中应该很容易：</p><p><code>def string_repeat(string: str, count: int):</code></p><p># 忽略</p><p>该样式与 TypeScript 使用的样式类似。您还可以使用 <strong>-&gt; type</strong> 键入返回值：</p><p><code>def string_repeat(string: str, count: int) -&gt; str:</code></p><p>”“”</p><p>重复字符串 N 次</p><p>:param string: 要重复的字符串</p><p>:param count: 重复次数</p><p>:return: 字符串重复N次</p><p>”“”</p><p>return string * count</p><p>对代码进行类型注释后，您可以使用名为 <strong>mypy</strong> 的工具检查其正确性，这是我们下一节的主题。</p><h2 id="-mypy-">使用 mypy 检查类型</h2><p><strong>mypy</strong> 是一个 <strong>Python 类型检查器</strong>。要在测试项目中安装 mypy，请确保激活虚拟环境，然后运行：</p><p><code>pip install mypy</code></p><p>打开 <strong>my_str_utils.py</strong> 并尝试将 <strong>string_repeat</strong> 与 float 而不是 int 一起使用：</p><p><code>def string_repeat(string: str, count: int) -&gt; str:</code></p><p>”“”</p><p>重复字符串 N 次</p><p>:param string: 要重复的字符串</p><p>:param count: 重复次数</p><p>:return: 字符串重复N次</p><p>”“”</p><p>return string * count</p>
<p>string_repeat(&#x27;hello&#x27;, 15.6)</p><p>IDE 会警告您，但您也可以<strong>对 Python 脚本使用 mypy</strong>：</p><p><code>mypy my_str_utils.py</code></p><p>mypy 也会警告你：</p><p><code>my_str_utils.py:11: error: Argument 2 to &quot;string_repeat&quot; has incompatible type &quot;float&quot;; expected &quot;int&quot;</code></p><p>在 1 个文件中发现 1 个错误（检查了 1 个源文件</p><p>与编译为“普通”JavaScript 的 TypeScript 不同，<strong>mypy 不会生成任何代码，它只是检查其类型</strong>。</p><h2 id="-javascript--python">面向 JavaScript 开发人员的 Python：更进一步</h2><p>您可能感兴趣的其他文章：</p><p>感谢您的阅读并继续关注此博客！</p></div><p style="text-align:right"><a href="https://www.amfishers.com/posts/technology/py-js#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.amfishers.com/posts/technology/py-js</link><guid isPermaLink="true">https://www.amfishers.com/posts/technology/py-js</guid><dc:creator><![CDATA[Fisher]]></dc:creator><pubDate>Thu, 02 Apr 2026 12:31:37 GMT</pubDate></item><item><title><![CDATA[Vue 2（含 <2.6）如何升级兼容 Composition API 原理]]></title><description><![CDATA[<div><blockquote>This render is generated by the Shiro API and may have formatting issues. For the best experience, visit:<a href="https://www.amfishers.com/posts/technology/vue2-update-composition-api">https://www.amfishers.com/posts/technology/vue2-update-composition-api</a></blockquote><div><blockquote><p>本文详细说明本项目如何在 Vue 2（包括低于 2.6 的版本）上通过插件注入与运行时适配，提供与 Vue 3 Composition API 接近的开发体验。内容覆盖安装入口、启用机制、运行时上下文桥接、响应式与计算/侦听适配、插槽与生命周期、兼容策略、执行时序、常见问题与限制，以及关键代码索引。
阅读本文时，请配合 composition-api 源码食用  <a href="https://github.com/vuejs/composition-api">https://github.com/vuejs/composition-api</a></p></blockquote>
<h2 id="">一、背景与总体思路</h2><ul><li>目标：在不依赖 Vue 3 内核（Proxy/Effect）的前提下，让 Vue 2 应用可以使用 <code>setup()</code>、<code>ref/reactive/computed/watch</code> 等 Composition API。</li><li>总体方法：通过 <code>Vue.use(Plugin)</code> 安装插件，在全局混入与选项合并层面“接入” Vue 2 的组件初始化流程，同时复用 Vue 2 的 <code>Observer/Dep/Watcher</code> 作为底层响应式机制。</li><li>关键桥接：构造 Vue3-like 的“当前实例上下文”（<code>getCurrentInstance()</code> 等），并将返回的 <code>setup</code> 对象或函数正确注入到 Vue 2 的渲染/实例属性中。</li></ul><h2 id="">二、安装入口与引入即用</h2><ul><li>主入口导出并安装插件：
<ul><li><code>src/index.ts:15</code> 导出 <code>Plugin</code>；<code>src/index.ts:25</code> 在浏览器存在 <code>window.Vue</code> 时自动执行 <code>window.Vue.use(Plugin)</code>。</li><li>CommonJS 入口：<code>index.js:3-6</code> 按 <code>NODE_ENV</code> 指向 dev/prod 构建文件。</li></ul></li><li>安装对象与执行：
<ul><li><code>src/install.ts:80-82</code> 定义 <code>Plugin.install(Vue)</code>；内部调用 <code>install(Vue)</code> 完成注册。</li></ul></li><li>为什么“引入文件即可生效”：
<ul><li>当以 CDN 或打包方式引入入口文件，若全局存在 <code>Vue</code>，会自动 <code>Vue.use(Plugin)</code>；否则在应用入口手动 <code>Vue.use(Plugin)</code> 即完成接入。</li></ul></li></ul><h2 id="setup---">三、启用机制：setup 合并策略 + 全局混入</h2><ul><li>选项合并策略：
<ul><li><code>src/install.ts:64-74</code> 为 <code>setup</code> 设置合并规则，支持父子组件的 <code>setup</code> 共存与返回对象的融合，约束执行顺序与一致性。</li></ul></li><li>全局混入注入：
<ul><li><code>src/mixin.ts:30-42</code> 通过 <code>Vue.mixin</code> 在 <code>beforeCreate</code> 阶段调用 <code>functionApiInit</code>，将 Composition API 接入每个组件实例。</li><li>保持渲染上下文：<code>src/mixin.ts:53-59</code> 包装 <code>render</code>，在渲染时激活当前组件实例上下文。</li><li>执行顺序控制：<code>src/mixin.ts:75-85</code> 重写 <code>data</code> 解析顺序，保证 <code>setup</code> 先于 <code>data</code> 执行，以避免低版本 Vue 的选项时序差异导致行为不一致。</li></ul></li><li><code>setup</code> 返回值处理：
<ul><li>返回函数：作为渲染函数使用（<code>src/mixin.ts:106-114</code>），直接参与 VNode 生成。</li><li>返回对象：对 <code>ref/reactive/function</code> 等进行自动解包与保护，并挂载到实例上（<code>src/mixin.ts:115-147</code>）。</li></ul></li><li>局部响应式绑定：
<ul><li>为包含响应式子项（如数组）的对象执行局部 <code>defineReactive</code>，减小副作用面（<code>src/mixin.ts:161-183</code>）。</li></ul></li></ul><h2 id="vue2--vue3-">四、运行时上下文桥接（Vue2 → Vue3 风格）</h2><ul><li>当前实例管理与暴露：
<ul><li><code>getCurrentInstance()</code>：<code>src/runtimeContext.ts:232-234</code>。</li><li>激活/恢复实例：<code>src/runtimeContext.ts:104-110,261-268</code>，确保生命周期与渲染期间上下文一致。</li></ul></li><li>Vue3-like 实例描述：
<ul><li>将 Vue2 vm 映射为 <code>ComponentInternalInstance</code> 近似结构（<code>src/runtimeContext.ts:241-320</code>）。</li></ul></li><li>安装状态与多实例防护：
<ul><li>已安装判定与重复安装警告：<code>src/runtimeContext.ts:45-48,72-83</code>；<code>src/install.ts:43-49</code>。</li><li>版本校验提示：<code>src/install.ts:52-62</code>。</li></ul></li></ul><h2 id="reactivereadonlyref">五、响应式适配：reactive/readonly/ref</h2><ul><li>复用 Vue2 响应式：
<ul><li><code>observe/defineReactive</code> 作为响应式基石（<code>src/reactivity/reactive.ts:231-249</code>）。</li><li>访问控制与自动解包：<code>defineAccessControl/proxy</code>（<code>src/reactivity/reactive.ts:52-114</code>），在读取时自动解包 <code>ref</code>，在写入只读对象时进行拦截。</li><li>原始对象关联与跳过标记：通过 <code>__ob__</code>、<code>SKIPFLAG/rawSet</code> 标识（<code>src/reactivity/reactive.ts:160-176,251-281</code>）。</li></ul></li></ul><h2 id="computedwatchwatcheffect">六、计算属性与侦听（computed/watch/watchEffect）</h2><ul><li>computed 适配：
<ul><li>有 vm：直接用 Vue2 <code>Watcher</code> 构建 lazy 计算，提供 <code>evaluate/depend</code>（<code>src/apis/computed.ts:26-57</code>）。</li><li>无 vm/SSR：创建“宿主组件”承载 <code>computed</code>（<code>src/apis/computed.ts:62-83</code>）。</li></ul></li><li>watch / watchEffect：
<ul><li>基于 <code>$watch(getter, callback, { immediate, deep, sync })</code>（<code>src/apis/watch.ts:379-387</code>）。</li><li>补丁清理：劫持 <code>teardown</code> 注入副作用清理（<code>src/apis/watch.ts:192-200,402-407</code>），避免内存泄漏与残留订阅。</li></ul></li></ul><h2 id="">七、插槽与生命周期</h2><ul><li>插槽代理：
<ul><li>将 Vue2 插槽转换为函数式访问，使其更接近 Vue3 的 <code>slots</code> 使用范式（<code>src/utils/instance.ts:161-180</code>、<code>src/utils/helper.ts:41-55</code>）。</li></ul></li><li>生命周期钩子：
<ul><li>统一包装调用并维护当前实例上下文（<code>wrapHookCall</code>），各 <code>onMounted/onUpdated/...</code> 以同样方式暴露（<code>src/apis/lifecycle.ts:33-58</code>）。</li></ul></li></ul><h2 id="-vue26-">八、对 Vue&lt;2.6 的兼容策略</h2><ul><li>不依赖 Vue3 内核：完全复用 Vue2 的 <code>Observer/Dep/Watcher</code>。</li><li>能力缺失的场景下提供回退：通过宿主组件或代理机制补足（例如无 vm 的 <code>computed</code>）。</li><li>插槽与 <code>scopedSlots</code> 的差异用代理函数化统一访问，屏蔽低版本差异。</li><li><code>setup</code> 专属合并与时序控制，避免低版本选项解析导致的执行顺序问题。</li><li>严格的安装/多实例防护，降低复杂构建/微前端场景下上下文错配的风险。</li></ul><h2 id="">九、典型执行时序（组件创建）</h2><ol start="1"><li>应用引入插件，自动或手动 <code>Vue.use(Plugin)</code>（<code>src/index.ts:25</code>）。</li><li>全局混入生效，组件在 <code>beforeCreate</code> 进入 <code>functionApiInit</code>（<code>src/mixin.ts:30-42</code>）。</li><li>包装渲染与数据解析，保证 <code>setup</code> 优先执行（<code>src/mixin.ts:53-59,75-85</code>）。</li><li>构造 <code>SetupContext</code> 并执行 <code>setup</code>：
<ul><li>返回函数 → 设为 <code>render</code>（<code>src/mixin.ts:106-114</code>）。</li><li>返回对象 → 自动解包并挂载为实例属性（<code>src/mixin.ts:115-147</code>）。</li></ul></li><li>如返回值包含响应式子项，进行局部 <code>defineReactive</code>（<code>src/mixin.ts:161-183</code>）。</li><li>生命周期钩子按需调用，期间激活当前实例上下文（<code>src/apis/lifecycle.ts:33-58</code>、<code>src/runtimeContext.ts:104-110</code>）。</li></ol><h2 id="">十、常见问题与限制</h2><ul><li>必须先安装：未 <code>Vue.use(Plugin)</code> 就使用 API 会触发断言/警告（<code>src/runtimeContext.ts:50-57</code>）。</li><li>重复安装与多 Vue 构造：会给出警告，避免上下文错配（<code>src/install.ts:43-49</code>、<code>src/runtimeContext.ts:72-83</code>）。</li><li>行为差异：由于底层是 Vue2 的响应式系统，个别边界行为与 Vue3 可能不完全一致（例如深层 Proxy 行为、依赖收集的粒度）。</li><li>SSR/无实例：<code>computed/watch</code> 会走宿主组件或降级路径，性能与时序需关注。</li></ul><h2 id="">十一、关键代码索引（含行号）</h2><ul><li>入口与安装：
<ul><li><code>src/index.ts:15,25</code>；<code>index.js:3-6</code></li><li>插件安装：<code>src/install.ts:64-78,80-82</code></li></ul></li><li>运行时上下文：
<ul><li>安装状态与校验：<code>src/runtimeContext.ts:45-48,72-83</code></li><li>当前实例与映射：<code>src/runtimeContext.ts:104-110,232-234,241-320,261-268</code></li></ul></li><li>混入与 setup 注入：
<ul><li><code>src/mixin.ts:30-42,53-59,65-73,75-85,87-159,161-183</code></li></ul></li><li>响应式适配：
<ul><li><code>src/reactivity/reactive.ts:52-114,160-176,231-249,251-281</code></li></ul></li><li>computed/watch：
<ul><li><code>src/apis/computed.ts:26-57,62-83</code></li><li><code>src/apis/watch.ts:192-200,379-387,402-407</code></li></ul></li><li>插槽代理：
<ul><li><code>src/utils/instance.ts:161-180</code>；<code>src/utils/helper.ts:41-55</code></li></ul></li><li>生命周期：
<ul><li><code>src/apis/lifecycle.ts:33-58</code></li></ul></li></ul><h2 id="">十二、快速使用指南</h2><ul><li>安装：在入口文件中执行 <code>import Plugin from &#x27;...&#x27; ; Vue.use(Plugin)</code>。</li><li>组件：
<ul><li>使用 <code>setup(props, ctx)</code> 返回对象或渲染函数；对象中使用 <code>ref/reactive/computed</code> 等 API。</li><li>在 <code>onMounted/onUpdated/...</code> 中编写副作用逻辑，<code>watch</code>/<code>watchEffect</code> 根据需要侦听与清理。</li></ul></li></ul><h2 id="">十三、总结</h2><ul><li>该插件通过安装与全局混入，将 Composition API 嵌入到 Vue 2 的组件初始化与响应式系统之中。</li><li>响应式与计算/侦听复用 Vue2 的 <code>Observer/Dep/Watcher</code>，并在缺失能力的场景下提供宿主/代理回退。</li><li>运行时上下文桥接保证了 <code>getCurrentInstance</code>、插槽、生命周期的可用与一致体验，从而实现“引入即用”的 Composition API 支持。</li></ul></div><p style="text-align:right"><a href="https://www.amfishers.com/posts/technology/vue2-update-composition-api#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.amfishers.com/posts/technology/vue2-update-composition-api</link><guid isPermaLink="true">https://www.amfishers.com/posts/technology/vue2-update-composition-api</guid><dc:creator><![CDATA[Fisher]]></dc:creator><pubDate>Mon, 01 Dec 2025 07:54:04 GMT</pubDate></item><item><title><![CDATA[命运根本没有给我出题]]></title><description><![CDATA[<div><blockquote>This render is generated by the Shiro API and may have formatting issues. For the best experience, visit:<a href="https://www.amfishers.com/notes/12">https://www.amfishers.com/notes/12</a></blockquote><p>昨天晒太阳的时候，我突然意识到，既然我认识到命运不该被拟人化，命运既不给予也不剥夺，不是恩人也不是仇人，那我也不该把它看成一个针对我的出题考官。命运只是发生，好事发生，坏事发生，相似的事情发生，陌生的事情发生。
没有出题人，没有考题，没有新答案，没有考官。本质上，是我看待命运的方式决定了我如何解释生活中发生的事情。想要弥补遗憾的执念困住了我自己。</p><p style="text-align:right"><a href="https://www.amfishers.com/notes/12#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.amfishers.com/notes/12</link><guid isPermaLink="true">https://www.amfishers.com/notes/12</guid><dc:creator><![CDATA[Fisher]]></dc:creator><pubDate>Tue, 25 Nov 2025 00:42:31 GMT</pubDate></item><item><title><![CDATA[努力也是一种能力]]></title><description><![CDATA[<div><blockquote>This render is generated by the Shiro API and may have formatting issues. For the best experience, visit:<a href="https://www.amfishers.com/notes/10">https://www.amfishers.com/notes/10</a></blockquote><div><p>可是当我拿起单词表来，却发现连十分钟都不能安心的去背单词，各种破碎繁杂的思绪纷至沓来，让我想放下书本，刷一下知乎，看一下朋友圈，逗逗狗狗。</p><p>突然惊觉，努力并不是一种选择，它是一种能力，它需要心神凝聚，需要很好的执行力，需要心静，需要专注，这些都是需要在漫长的生活中一点一滴培养和固化的。</p><p>一个人长久自律的努力即是一种修行</p><p>努力的方向或许每个人是不同的，或是钱财名利或是想赢或是梦想或是平淡生活，但努力这种能力是相通的，这种修行路漫漫其修远兮，需上下而求索。</p><p>努力本身并不能带来幸福，达成了什么，赢得了什么，这些努力的成果有时候甚至和你的初衷背道而驰。但是一个幸福的人无疑在生活中拥有很好的心性和智慧，而幸福的修行和努力的能力是殊途同归的，它们的内核是相通的。</p><p>我不愿眼里的神采追逐在手机屏幕上，我还要奔跑还要去看看这世界的大好河山。</p><p>我不愿手指的灵性委顿于零食键盘间，我还要写故事要画画要抚摸夜里穿越过亿万年的星光。</p><p>努力，修行，努力修行。</p><p>若有一天我匍匐在菩提树下，愿不是因为我疲懒无力。</p><p>而是我跨越千山万水终于归于原点的虔诚。</p><p>幸而此刻并不太晚，幸而永远不会太晚。</p><p>和自己争夺自己。</p></div><p style="text-align:right"><a href="https://www.amfishers.com/notes/10#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.amfishers.com/notes/10</link><guid isPermaLink="true">https://www.amfishers.com/notes/10</guid><dc:creator><![CDATA[Fisher]]></dc:creator><pubDate>Tue, 14 Oct 2025 01:31:02 GMT</pubDate></item><item><title><![CDATA[Vue3系列 - 组件通信方式]]></title><description><![CDATA[<div><blockquote>This render is generated by the Shiro API and may have formatting issues. For the best experience, visit:<a href="https://www.amfishers.com/posts/technology/vue-communication-methods">https://www.amfishers.com/posts/technology/vue-communication-methods</a></blockquote><div><p>Vue3提供了多种组件间通信的方式，满足不同场景的需求。</p><p>主要的组件通信方式：</p><ol start="1"><li><strong>Props向下传递</strong>：父组件通过props向子组件传递数据。</li></ol><pre class="language-typescript lang-typescript"><code class="language-typescript lang-typescript">// 子组件定义props
export function defineProps&lt;PropNames extends string = string&gt;(
  props: PropNames[]
): Readonly&lt;{ [key in PropNames]?: any }&gt;</code></pre><ol start="2"><li><strong>事件向上传递</strong>：子组件通过事件向父组件发送消息。</li></ol><pre class="language-typescript lang-typescript"><code class="language-typescript lang-typescript">// 子组件定义emits
export function defineEmits&lt;EE extends string = string&gt;(
  emitOptions: EE[]
): EmitFn&lt;EE[]&gt;

// 在setup上下文中使用emit
setup(props, { emit }) {
  emit(&#x27;update&#x27;, newValue)
}</code></pre><ol start="3"><li><p><strong>v-model双向绑定</strong>：在父子组件间建立双向数据绑定。</p></li><li><p><strong>依赖注入</strong>：使用<code>provide</code>和<code>inject</code>在祖先和后代组件间传递数据。</p></li></ol><pre class="language-typescript lang-typescript"><code class="language-typescript lang-typescript">export function provide&lt;T&gt;(key: InjectionKey&lt;T&gt; | string | number, value: T) {
  // ...
}

export function inject&lt;T&gt;(key: InjectionKey&lt;T&gt; | string): T | undefined</code></pre><ol start="5"><li><p><strong>事件总线</strong>：在Vue3中，可以使用外部的事件库或创建一个空的Vue实例作为事件中心。</p></li><li><p><strong>Vuex/Pinia状态管理</strong>：对于复杂应用，可以使用状态管理库集中管理共享状态。</p></li><li><p><strong>组合式API</strong>：通过<code>setup</code>函数和组合式API，可以更灵活地组织和共享组件逻辑。</p></li></ol><pre class="language-typescript lang-typescript"><code class="language-typescript lang-typescript">export type SetupFunction&lt;
  Props,
  RawBindings = {},
  Emits extends EmitsOptions = {}
&gt; = (
  this: void,
  props: Readonly&lt;Props&gt;,
  ctx: SetupContext&lt;Emits&gt;
) =&gt; RawBindings | (() =&gt; VNode | null) | void</code></pre><ol start="8"><li><strong>Refs引用</strong>：通过<code>ref</code>属性获取子组件实例，直接调用其方法或访问其数据。</li></ol><p>选择合适的通信方式取决于组件间的关系和通信的复杂度，合理使用这些方式可以构建出清晰、可维护的组件架构。</p></div><p style="text-align:right"><a href="https://www.amfishers.com/posts/technology/vue-communication-methods#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.amfishers.com/posts/technology/vue-communication-methods</link><guid isPermaLink="true">https://www.amfishers.com/posts/technology/vue-communication-methods</guid><dc:creator><![CDATA[Fisher]]></dc:creator><pubDate>Fri, 03 Oct 2025 15:47:13 GMT</pubDate></item><item><title><![CDATA[Vue3系列 - 渲染优化技术]]></title><description><![CDATA[<div><blockquote>This render is generated by the Shiro API and may have formatting issues. For the best experience, visit:<a href="https://www.amfishers.com/posts/technology/vue-render-optimization">https://www.amfishers.com/posts/technology/vue-render-optimization</a></blockquote><div><p>Vue3采用了多种渲染优化技术来提高性能，这些技术主要体现在虚拟DOM的实现和响应式系统中。</p><p>主要的渲染优化技术：</p><ol start="1"><li><p><strong>虚拟DOM</strong>：Vue使用虚拟DOM来最小化实际DOM操作，只更新必要的部分。</p></li><li><p><strong>diff算法优化</strong>：Vue3改进了diff算法，通过静态提升、事件缓存等技术减少了比对开销。</p></li><li><p><strong>响应式系统</strong>：通过精确的依赖追踪，Vue只重新渲染依赖已更改数据的组件。</p></li><li><p><strong>编译优化</strong>：Vue3的模板编译器能够识别静态内容，减少运行时的工作量。</p></li><li><p><strong>异步更新队列</strong>：Vue将DOM更新操作缓存在队列中，然后一次性执行，避免不必要的计算和渲染。</p></li></ol><pre class="language-typescript lang-typescript"><code class="language-typescript lang-typescript">export function effect(fn: () =&gt; any, scheduler?: (cb: any) =&gt; void) {
  const watcher = new Watcher(currentInstance, fn, noop, {
    sync: true
  })
  if (scheduler) {
    watcher.update = () =&gt; {
      scheduler(() =&gt; watcher.run())
    }
  }
}</code></pre><ol start="6"><li><strong>计算属性缓存</strong>：计算属性只有在其依赖项变化时才会重新计算。</li></ol><pre class="language-typescript lang-typescript"><code class="language-typescript lang-typescript">if (watcher.dirty) {
  watcher.evaluate()
}</code></pre><ol start="7"><li><strong>组件实例复用</strong>：通过<code>key</code>属性，Vue可以在列表渲染中复用组件实例，减少创建和销毁的开销。</li></ol><p>这些优化技术共同作用，使Vue能够在处理复杂UI时保持高性能。</p></div><p style="text-align:right"><a href="https://www.amfishers.com/posts/technology/vue-render-optimization#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.amfishers.com/posts/technology/vue-render-optimization</link><guid isPermaLink="true">https://www.amfishers.com/posts/technology/vue-render-optimization</guid><dc:creator><![CDATA[Fisher]]></dc:creator><pubDate>Fri, 03 Oct 2025 15:45:50 GMT</pubDate></item><item><title><![CDATA[Vue3系列 - 自定义指令的实现]]></title><description><![CDATA[<div><blockquote>This render is generated by the Shiro API and may have formatting issues. For the best experience, visit:<a href="https://www.amfishers.com/posts/technology/vue-directive">https://www.amfishers.com/posts/technology/vue-directive</a></blockquote><div><p>Vue3中的自定义指令允许开发者直接操作DOM元素，为框架增加了更多的灵活性。</p><p>自定义指令的核心实现：</p><ol start="1"><li><p><strong>指令钩子函数</strong>：指令定义了一系列的钩子函数，对应元素的不同生命周期。</p><ul><li><code>bind</code>：指令第一次绑定到元素时调用</li><li><code>inserted</code>：被绑定元素插入父节点时调用</li><li><code>update</code>：所在组件的VNode更新时调用</li><li><code>componentUpdated</code>：指令所在组件的VNode及其子VNode全部更新后调用</li><li><code>unbind</code>：指令与元素解绑时调用</li></ul></li><li><p><strong>指令注册</strong>：可以全局注册或组件内注册。</p></li></ol><pre class="language-typescript lang-typescript"><code class="language-typescript lang-typescript">Vue.directive(&#x27;my-directive&#x27;, {
  bind(el, binding, vnode) {
    // 元素首次绑定指令时的逻辑
  },
  update(el, binding, vnode, oldVnode) {
    // 元素更新时的逻辑
  }
})</code></pre><ol start="3"><li><p><strong>指令参数</strong>：每个钩子函数接收以下参数：</p><ul><li><code>el</code>：指令所绑定的元素</li><li><code>binding</code>：包含指令的各种属性的对象</li><li><code>vnode</code>：Vue编译生成的虚拟节点</li><li><code>oldVnode</code>：上一个虚拟节点（仅在update和componentUpdated钩子中可用）</li></ul></li><li><p><strong>动态参数</strong>：Vue3支持动态指令参数，可以根据组件实例数据动态改变。</p></li></ol><pre class="language-typescript lang-typescript"><code class="language-typescript lang-typescript">&lt;div v-my-directive:[dynamicArg]=&quot;value&quot;&gt;&lt;/div&gt;</code></pre><p>自定义指令为处理DOM操作提供了强大的工具，特别适合于需要直接操作DOM的场景，如管理焦点、滚动行为、或集成第三方库。</p></div><p style="text-align:right"><a href="https://www.amfishers.com/posts/technology/vue-directive#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.amfishers.com/posts/technology/vue-directive</link><guid isPermaLink="true">https://www.amfishers.com/posts/technology/vue-directive</guid><dc:creator><![CDATA[Fisher]]></dc:creator><pubDate>Thu, 02 Oct 2025 15:44:59 GMT</pubDate></item></channel></rss>