<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>nmtz</title>
        <link>https://zzfzzf.com</link>
        <description>nmtz的个人博客</description>
        <lastBuildDate>Sun, 08 Mar 2026 16:17:53 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Feed for nmtz</generator>
        <image>
            <title>nmtz</title>
            <url>https://zzfzzf.com/favicon.ico</url>
            <link>https://zzfzzf.com</link>
        </image>
        <copyright>All rights reserved 2026, nmtz</copyright>
        <item>
            <title><![CDATA[博客设计方案与实施]]></title>
            <link>https://zzfzzf.com/post/6972548384855035914</link>
            <guid>6972548384855035914</guid>
            <pubDate>Tue, 25 Mar 2025 01:56:29 GMT</pubDate>
            <content:encoded><![CDATA[<h2>背景</h2>
<p>在日常学习过程中，经常需要把遇到的问题记录下来，记录自己的成长之路，所以一直想拥有一个完全属于自己的博客。其间也体验过<code>wordpress</code>,无奈可定制化性不高，索性自己实现一个。</p>
<h2>简介</h2>
<p>本博客系统前端使用<code>React</code>技术栈，后端使用<code>Go</code>技术栈。从运维到设计都是自己完成的。</p>
<pre><code class="language-mermaid">graph LR
  A[Webpack 构建] --&gt; B[生成带 ContentHash 的文件]
  B --&gt; C{静态资源类型}
  C --&gt;|JS/CSS/图片| D[OSS 存储]
  C --&gt;|HTML| E[自建服务器]
  D --&gt; F[CDN 加速]
  F --&gt; G[用户访问]
  E --&gt; G
</code></pre>
<h2>技术选型</h2>
<h3>博客主页</h3>
<p><a href="https://zeus.zzfzzf.com">https://zeus.zzfzzf.com</a></p>
<ol>
<li>考虑到<code>seo</code>和加载性能，使用服务端渲染框架<code>nextjs</code>开发</li>
<li>公共组件提取到<code>@oc/design</code>UI<a href="https://design.zzfzzf.com/">组件库</a>中</li>
<li>静态资源托管在阿里云cdn上</li>
<li>使用 <code>drone</code> + <code>K8S</code> 持续集成</li>
</ol>
<h3>管理后台(宙斯系统)</h3>
<p><a href="https://zeus.zzfzzf.com">https://zeus.zzfzzf.com</a></p>
<ol>
<li>使用从零搭建的webpack5+Typescript+React18方案</li>
<li>UI组件库使用<code>antd</code></li>
<li>静态资源托管在阿里云cdn上</li>
<li>使用 <code>drone</code> + <code>K8S</code> 持续集成</li>
<li>做了权限控制，可用测试账户<code>test/test</code>访问</li>
</ol>
<h3>服务端</h3>
<ol>
<li>使用<a href="https://docs.gofiber.io/"><code>fiber</code></a>框架开发</li>
<li>数据库使用<code>mysql</code>，缓存使用<code>redis</code>，全文搜索引擎和日志使用<code>elasticsearch</code></li>
<li>使用<code>rabbitmq</code>和<code>kafka</code>处理异步任务</li>
<li>文件上传至阿里云oss保存</li>
<li>消息通知使用飞书消息</li>
<li>服务监控使用 <a href="https://github.com/louislam/uptime-kuma">https://github.com/louislam/uptime-kuma</a></li>
</ol>
<h3>错误监控以及用户行为日志</h3>
<ol>
<li>使用gif上报日志到es中</li>
<li>采集方法参考此篇文章<a href="https://zzfzzf.com/post/1450707176421322754">前端应用性能及错误监控设计方案</a></li>
</ol>
<h3>运维</h3>
<ol>
<li><a href="https://drone.io/">drone</a>+ K8S 持续集成</li>
<li>Grafana + Prometheus 对整个系统进行监控报警</li>
</ol>
<h3>数据库</h3>
<ol>
<li>Mysql数据库生产环境使用阿里云rds服务</li>
<li>redis、es等服务无备份只负责缓存和临时处理数据</li>
</ol>
<h2>特色</h2>
<ol>
<li>完全开源，可折腾性高。</li>
<li>博客支持暗黑模式，支持移动端</li>
<li>从组件库到后台管理系统都是从零搭的，实现了esbuild插件 babel插件处理组件库markdown转html文档，自动生成类型表格</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[2026 终端生产力革命]]></title>
            <link>https://zzfzzf.com/post/2022681505372835840</link>
            <guid>2022681505372835840</guid>
            <pubDate>Sat, 14 Feb 2026 15:00:48 GMT</pubDate>
            <content:encoded><![CDATA[<h2>一、 核心架构：Zellij + Claude Code</h2>
<p>我彻底放弃了沉重的 IDE，转而使用 <strong>Zellij</strong> 作为工作空间编排器。通过声明式的 KDL 布局，我实现了一键进入“战斗状态”。</p>
<h3>item2</h3>
<ul>
<li>Ctrl + A：跳到行首（最左边）。</li>
<li>Ctrl + E：跳到行尾（最右边）。</li>
<li>Ctrl + K：删除从光标位置到行尾的所有内容。</li>
<li>Ctrl + U：删除整行（或从光标位置到行首）。</li>
<li>Ctrl + L：清屏（等同于执行 clear）。</li>
</ul>
<h2>编辑器</h2>
<p>hx
zed</p>
<h3>1. 深度定制的 <code>dev.kdl</code> 布局</h3>
<p>我为我的 Rust 项目设计了一个三合一的“指挥中心”：</p>
<ul>
<li><strong>左侧 (60% 黄金位)</strong>：常驻 <strong>Claude Code</strong> (AI Agent)，负责全局代码理解、重构与生成的“主脑”。</li>
<li><strong>右上 (监控位)</strong>：实时 Git 状态窗格，使用 <code>watch</code> 命令捕捉每一行代码的变动。</li>
<li><strong>右下 (执行位)</strong>：<strong>Droid/Bacon</strong> 自动化任务处理，实时反馈 Rust 编译错误与测试结果。</li>
</ul>
<h3>2. 自动化启动</h3>
<p>配合 Zsh 别名，输入 <code>zd</code> 即可瞬间唤醒这套复杂的生产力矩阵：</p>
<pre><code class="language-zsh">alias zd=&#39;zellij --layout dev&#39;
</code></pre>
<p>// ~/.config/zellij/layouts/dev.kdl</p>
<pre><code>layout {
    pane size=1 borderless=true {
        plugin location=&quot;zellij:tab-bar&quot;
    }

    pane split_direction=&quot;vertical&quot; {
        // 左侧：AI 代理
        pane name=&quot;Claude&quot; size=&quot;60%&quot; {
            command &quot;claude&quot;
            focus true
        }

        // 右侧：监控与交互
        pane split_direction=&quot;horizontal&quot; {
            // 右上：代码变动
            pane name=&quot;Claude&quot; {
                command &quot;claude&quot;
        }
            // 右下：空命令行（进去后可以手动输入任何命令，如 bacon）
            pane name=&quot;Terminal&quot;
        }
    }

    pane size=1 borderless=true {
        plugin location=&quot;zellij:status-bar&quot;
    }
}
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[离线 AI 应用的实践]]></title>
            <link>https://zzfzzf.com/post/2020127821963202560</link>
            <guid>2020127821963202560</guid>
            <pubDate>Mon, 09 Feb 2026 04:30:05 GMT</pubDate>
            <content:encoded><![CDATA[<p>最近在写本地 AI 应用，主要想解决云端 Token 消耗大和隐私泄露的问题。我的配置是 <strong>M1 Mac (16GB)</strong>，目前对模型生成质量（如绝对准确性）要求不高。但有token消耗和隐私要求</p>
<h2>工具选择</h2>
<h3>基础设施</h3>
<p>通过 <code>huggingface-cli</code> 建立本地模型仓库。可以实现断点续传和精确的版本控制。</p>
<p>使用 Hugging Face CLI 可以更方便地从 <a href="https://huggingface.co/">huggingface.co</a>（模型界的 GitHub）下载和管理模型。</p>
<pre><code class="language-bash"># 安装工具
brew install huggingface-cli

# 推荐在 .zshrc 配置镜像加速（解决国内连接问题）
export HF_ENDPOINT=&quot;https://hf-mirror.com&quot;

# 仅下载 Q4_K_M 规格的 GGUF 文件（性价比最高的量化格式）
hf download Qwen/Qwen2.5-7B-Instruct-GGUF \
  --include &quot;*q4_k_m.gguf&quot; \
  --local-dir ./models/qwen2.5-7b
</code></pre>
<h3>llama.cpp</h3>
<p>开发者首选</p>
<pre><code class="language-bash"># 安装与运行一键式体验
brew install llama.cpp

# 启动网页版+OpenAI 兼容的 API 服务
llama-server -m qwen2.5-1.5b-instruct-q4_k_m.gguf --port 8080

# 极速推理：-ngl 99 将所有模型层挂载至 GPU 加速
llama-cli -m ./qwen2.5-1.5b-instruct-q4_k_m.gguf -ngl 99 -p &quot;你是谁？&quot;
</code></pre>
<pre><code class="language-bash">➜  ~ curl http://localhost:8080/v1/chat/completions \
  -H &quot;Content-Type: application/json&quot; \
  -d &#39;{
    &quot;model&quot;: &quot;qwen2.5-1.5b-instruct-q4_k_m.gguf&quot;,
    &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;你好&quot;}]
  }&#39;
{&quot;choices&quot;:[{&quot;finish_reason&quot;:&quot;stop&quot;,&quot;index&quot;:0,&quot;message&quot;:{&quot;role&quot;:&quot;assistant&quot;,&quot;content&quot;:&quot;你好！有什么问题我可以帮助你吗？&quot;}}],&quot;created&quot;:1770606395,&quot;model&quot;:&quot;qwen2.5-1.5b-instruct-q4_k_m.gguf&quot;,&quot;system_fingerprint&quot;:&quot;b7960-db6adb3c8&quot;,&quot;object&quot;:&quot;chat.completion&quot;,&quot;usage&quot;:{&quot;completion_tokens&quot;:10,&quot;prompt_tokens&quot;:30,&quot;total_tokens&quot;:40},&quot;id&quot;:&quot;chatcmpl-FTJnPHHWp5Jj0tklEUkj4tDDLVYXcFWp&quot;,&quot;timings&quot;:{&quot;cache_n&quot;:29,&quot;prompt_n&quot;:1,&quot;prompt_ms&quot;:68.001,&quot;prompt_per_token_ms&quot;:68.001,&quot;prompt_per_second&quot;:14.705666093145688,&quot;predicted_n&quot;:10,&quot;predicted_ms&quot;:121.87,&quot;predicted_per_token_ms&quot;:12.187000000000001,&quot;predicted_per_second&quot;:82.0546483958316}}%
</code></pre>
<h3>LM Studio</h3>
<p>零门槛体验，如果你偏好图形界面，<strong>LM Studio</strong> 是目前本地 LLM 客户端的“天花板”，支持一键搜索、下载并配置模型。</p>
<h2>模型选择</h2>
<p>在 M1 + 16GB 的硬件限制下，盲目追求大参数只会带来漫长的等待。</p>
<ul>
<li><strong>排名参考</strong>：除了 <a href="https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard">Open LLM Leaderboard</a>，更具实战意义的 <a href="https://arena.ai/leaderboard">LMSYS Chatbot Arena</a></li>
<li><strong>规格建议</strong>：锁定 <strong>7B 以下</strong> 模型。</li>
<li><strong>格式偏好</strong>：<ul>
<li><strong>MLX</strong>：Apple 官方出品，针对统一内存架构深度优化，速度极致。
    - <strong>GGUF</strong>：生态最全，兼容性最强。</li>
</ul>
</li>
<li><strong>量化 (Quantization)：</strong> 务必使用 <code>4-bit</code> 或 <code>Q4_K_M</code> 格式的模型。这能将模型大小压缩至 <strong>3-4GB</strong>，在精度损失极小的前提下，确保模型能在你的设备上流畅运行。
我下载了4个模型，具体还在体验中，不过吐字是非常快的。效果还在测评中。</li>
<li>qwen2.5-coder-7b-instruct</li>
<li>qwen/qwen3-4b-2507</li>
<li>qwen/qwen3-4b-thinking-2507</li>
<li>google/gemma-3-4b</li>
</ul>
<h2>开发的选择</h2>
<table>
<thead>
<tr>
<th><strong>维度</strong></th>
<th><strong>Python (MLX / PyTorch)</strong></th>
<th><strong>Rust (Candle / llama-cpp-rakane)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>优势</strong></td>
<td>算法库生态无敌，原型开发极快。</td>
<td>内存安全、无运行时、二进制包极小。</td>
</tr>
<tr>
<td><strong>适用场景</strong></td>
<td>内部脚本、数据科学、快速验证。</td>
<td>需要分发的桌面端、轻量级 CLI 工具。</td>
</tr>
<tr>
<td><strong>性能</strong></td>
<td>依靠底层 C++/Metal，表现良好。</td>
<td><strong>极致性能</strong>，极低的冷启动开销。</td>
</tr>
<tr>
<td>自部署模型一般是用来自己开发本机的项目，可以选择python或者rust，如果是应用推荐用python实现，如果需要分发软件包建议用rust</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3>Python</h3>
<ul>
<li>MLX 参考 <a href="https://community.niohome.com/article/cm4172bw900axj60afevbdvw6">https://community.niohome.com/article/cm4172bw900axj60afevbdvw6</a>
由于生态及其丰富，对做业务非常友好</li>
</ul>
<h3>Rust</h3>
<p>rust的优势是可以打包成可直接运行，利于分发。模型则用户自己去下载。可以做一些有意思的应用，比如tarui cli都很有意思. </p>
<ul>
<li>比如做本地离线个人知识库（<a href="https://anythingllm.com/%EF%BC%89%E8%99%BD%E7%84%B6%E5%B7%B2%E7%BB%8F%E6%9C%89%E4%BA%86%EF%BD%9E%EF%BD%9E">https://anythingllm.com/）虽然已经有了～～</a>
推荐几个库</li>
<li>llama-cpp-2</li>
<li>candle</li>
<li>rig-core</li>
</ul>
<h2>现实边界</h2>
<p>本地小模型在“实用上下文长度”上，与云端大模型存在本质差异。</p>
<table>
<thead>
<tr>
<th><strong>特性</strong></th>
<th><strong>云端大模型 (Claude / Gemini)</strong></th>
<th><strong>本地小模型 (M1 Mac)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>上下文上限</strong></td>
<td>200k - 2M (海量记忆)</td>
<td>实际 8k - 32k (短途记忆)</td>
</tr>
<tr>
<td><strong>性能波动</strong></td>
<td>相对恒定</td>
<td><strong>随 Token 长度指数级衰减</strong></td>
</tr>
<tr>
<td><strong>KV Cache</strong></td>
<td>云端分布式存储</td>
<td><strong>吞噬你的统一内存</strong></td>
</tr>
</tbody></table>
<p><strong>根本瓶颈</strong>：</p>
<ol>
<li><strong>内存墙</strong>：KV Cache(也就是我们平时用到的上下文缓存) 会随对话长度线性增长。在 16GB 内存的 Mac 上，这可能意味着一个 7B 模型处理超过 16K Token 就会触发内存交换，速度暴跌。</li>
<li><strong>算力墙</strong>：Transformer 注意力计算复杂度为 O(n²)。处理 10K Token 的计算量是 1K Token 的 <strong>100倍</strong>，直接导致“思考”时间变长，风扇狂转。</li>
</ol>
<h3>避坑指南</h3>
<p><strong>Q：为什么编译 llama.cpp 报错，或者找不到编译器？</strong>
<strong>A：</strong> macOS 升级（如从 14 升到 15）常会把 <code>CommandLineTools</code> 搞成“半残状态”。</p>
<pre><code class="language-bash"># 彻底删除旧工具
sudo rm -rf /Library/Developer/CommandLineTools

# 重新触发安装弹窗
xcode-select --install
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[2026 年的Node 版本管理器]]></title>
            <link>https://zzfzzf.com/post/2012875871773265920</link>
            <guid>2012875871773265920</guid>
            <pubDate>Sun, 25 Jan 2026 14:52:30 GMT</pubDate>
            <content:encoded><![CDATA[<h3>引言</h3>
<p>作为一名开发者，我们每天都在跟各种 runtime 打交道。今天在搞 Tauri 项目（Node + Rust），明天可能在调量化交易的策略（Python + Node）。但在管理 Node 版本时，你是否遇到过“明明删了包却还在报错”、“换个 Node 版本全局工具就失效”的灵异事件？</p>
<p>在 2026 年，Node 版本管理器的江湖已经发生了质变。</p>
<hr>
<h3>一、 老牌悍将的“中年危机”：Volta 与 nvm</h3>
<h4>1. Volta：曾经的“零成本”之王</h4>
<p>Volta 的设计初衷极具野心：通过 Shim（垫片）机制，让你在项目间切换时完全无感。</p>
<ul>
<li>痛点：它的沙盒机制是一把双刃剑。如果你习惯用 npm install -g，你会发现这些包被锁死在特定的 Node 镜像里。一旦 Volta 的索引出错，你就会遇到“删不掉、找不着、动不了”的尴尬。</li>
<li>现状：它的维护频率在降低，虽然依然稳定，但在处理日益复杂的全局工具链（如 claude-code）时，它的“过度封装”有时会让资深开发者感到束手无策。</li>
</ul>
<h4>2. nvm：历史的活化石</h4>
<p>nvm 是很多人的初恋。但到了 2026 年，它那慢吞吞的 shell 启动速度（每次打开终端都要载入一堆脚本）和 Windows 上的各种水土不服，让它已经退出了主流竞争。</p>
<hr>
<h3>二、 极简主义的选择：fnm</h3>
<p>如果你只想要一个 快、简单、符合直觉 的工具，fnm 是目前的标准答案。</p>
<ul>
<li>核心逻辑：它用 Rust 编写，速度极快。它不搞“沙盒”那一套，而是通过修改环境变量（PATH）来切换版本。</li>
<li>为什么选它：<ul>
<li>它支持 .node-version 这种通用标准。</li>
<li>全局包管理符合原生逻辑：装在哪个版本下就在哪里，清清楚楚。</li>
</ul>
</li>
<li>缺点：它只管 Node，不管别的。</li>
</ul>
<hr>
<h3>三、 2026 年的终极武器：mise (原 rtx)</h3>
<p>如果你像我一样，除了 Node 还要管理 Python 和 Rust，那么 mise 是你唯一的选择。</p>
<h4>为什么我要从 Volta 切换到 mise？</h4>
<ol>
<li>多语种制霸：一个 mise.toml 搞定项目里所有的版本依赖（Node, Python, Go, Terraform...）。</li>
<li>兼容性：它能直接识别 .nvmrc 和 .node-version。</li>
<li>零延迟：同样是 Rust 编写，它通过 Bash/Zsh 的 hook 自动切换环境，几乎没有性能损耗。</li>
<li>透明化：它不会像 Volta 那样建立一堆“假”的可执行文件。你想删包、想查路径，直接 which node 就能看到真实的物理地址。</li>
</ol>
<hr>
<h3>四、 总结：我该选哪个？</h3>
<p>在 2026 年，我的建议非常明确：</p>
<ul>
<li>追求极致稳定且只写 Node：选 fnm。</li>
<li>全栈/AI 开发，涉及多语言环境：无脑选 mise。</li>
<li>团队强制统一且不想写 .node-version 文件：留守 Volta。</li>
</ul>
<p>我的避坑建议：无论用哪个，尽量减少对全局包（-g）的依赖。现在是 npx 和 pnpm 的时代，让依赖留在项目内，才是环境整洁的终极方案。</p>
<hr>
<h3>结语</h3>
<p>这是目前最现代化的“全家桶”方案：</p>
<p>用 brew 安装管理工具： brew install mise uv。</p>
<p>用 mise 安装 Node/Go： mise use --global node@20。</p>
<p>用 uv 管理 Python： 项目内 uv venv。</p>
<p>注意： 在 mise 中可以不装 Python，完全交给 uv。
工具是用来服务开发的，不应该成为开发的负担。如果你还在为 volta uninstall 报错而烦恼，也许是时候给你的工具链做一次“断舍离”了。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[我的 AI 极简工具流]]></title>
            <link>https://zzfzzf.com/post/2010249791455367168</link>
            <guid>2010249791455367168</guid>
            <pubDate>Mon, 12 Jan 2026 02:19:10 GMT</pubDate>
            <content:encoded><![CDATA[<p>在 AI 领域，<strong>唯快不破</strong> 是常态。几乎每周都有新架构，每隔几天就有新方案。面对层出不穷的选择，我逐渐达成了一个共识：<strong>工具不在多，顺手才是王道。</strong></p>
<p>与其在反复搭建中消耗时间，不如构建一套底层稳健、场景明确的工作流。</p>
<hr>
<h2>模型</h2>
<p>模型的选择核心在于<em>性价比</em>与<em>场景匹配</em></p>
<h3>DeepSeek</h3>
<p>官网 <a href="https://www.deepseek.com/">https://www.deepseek.com/</a>
性价比之王
目前的绝对主力。无论是 WebStorm 的 Git 提交插件，还是博客后台的自动总结功能，DeepSeek 都能以极低的成本提供优秀的输出。</p>
<h3>OpenRouter</h3>
<p><a href="https://openrouter.ai/">https://openrouter.ai/</a>
灵活中转
模型聚合的中转站。主要用于快速调用海外的前沿模型，解决了访问障碍和单一模型受限的问题，极大地提升了调用的灵活性。</p>
<h3>GLM</h3>
<p>官网：<a href="https://bigmodel.cn/usercenter/glm-coding/usage">https://bigmodel.cn/usercenter/glm-coding/usage</a>
目前正在试用其 Coding 能力，<a href="https://bigmodel.cn/glm-coding">购买地址在此</a>。作为国内大模型的头部梯队，值得关注其在代码生成上的表现。</p>
<h2>Google AI Studio</h2>
<p><a href="https://aistudio.google.com/">https://aistudio.google.com/</a>
免费playground</p>
<h2>CLI</h2>
<h3>Claude Code</h3>
<p>虽然官方模型极其强大，但直接使用官方 API 成本较高。</p>
<ul>
<li><strong>建议</strong>：配合 OpenRouter 等第三方中转服务使用。</li>
<li><strong>支付</strong>：如果想体验原汁原味的官方服务，可以使用稳定币支付。</li>
</ul>
<h3>Opencode</h3>
<p>Claude Code 的社区开源平替版本，适合喜欢折腾开源方案的开发者。</p>
<h2>编辑器</h2>
<h3>Antigravity</h3>
<p>目前的生产力核心。建议通过 <a href="https://policies.google.com/country-association-form">账号关联表单</a> 查看所属地。 现在的 <a href="https://one.google.com/ai-nye">AI-NYE 优惠</a> 仅需 $99/年，性价比极高。</p>
<h3>Cursor</h3>
<p>虽然它的上下文补全非常出色，但经过一个月的使用，发现其交互逻辑与我的习惯并不完全契合，目前已果断弃用。<strong>适合别人的，未必适合自己。</strong></p>
<h2>日常生活</h2>
<p>手机端和网页端的交互，处理中文生态与生活琐事：</p>
<h3>豆包</h3>
<p>极致的语音交互，适合路上记录灵感和处理日常杂事。</p>
<h3>元宝</h3>
<p>依托腾讯生态，查询公众号深文和追踪国内热点的“总结神器”。</p>
<h3>Gemini</h3>
<ul>
<li><a href="https://aistudio.google.com/prompts/new_chat">https://aistudio.google.com/prompts/new_chat</a></li>
<li><a href="https://gemini.google.com/">https://gemini.google.com/</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[2025 总结]]></title>
            <link>https://zzfzzf.com/post/2008821771221864448</link>
            <guid>2008821771221864448</guid>
            <pubDate>Wed, 07 Jan 2026 08:45:58 GMT</pubDate>
            <content:encoded><![CDATA[<p>2025 对我来说是个不太开心的一年。</p>
<p>2025 应该也不会开心。</p>
<p>就这样。</p>
<p>很难受。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[AI狼人杀]]></title>
            <link>https://zzfzzf.com/post/2004576097986940928</link>
            <guid>2004576097986940928</guid>
            <pubDate>Sat, 27 Dec 2025 12:08:03 GMT</pubDate>
            <content:encoded><![CDATA[<h2>前言</h2>
<p>上手ai，需要做一个ai智能体，从狼人杀开始。
特点：</p>
<ul>
<li>角色扮演与指令遵循 (Role-playing): 狼人需要伪装，平民需要推理。如何让 LLM 坚持自己的“身份”而不产生幻觉？</li>
<li>私有状态 vs. 公有状态 (Information Asymmetry): 公有: 白天的聊天记录。私有: 狼人知道谁是队友，预言家知道验人结果。</li>
<li>挑战: 确保 Agent 不会“作弊”或泄露它不该知道的信息。</li>
<li>长短期记忆 (Memory): Agent 需要记得第一天谁投了谁，并以此作为第三天投票的依据。</li>
<li>结构化输出 (Structured Output): Agent 不能只聊天，还需要执行确定的动作（如：vote(&quot;Player_3&quot;) 或 kill(&quot;Player_1&quot;)）。</li>
</ul>
<h1>图设计</h1>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[DSPy]]></title>
            <link>https://zzfzzf.com/post/1988064213347602432</link>
            <guid>1988064213347602432</guid>
            <pubDate>Tue, 11 Nov 2025 02:00:26 GMT</pubDate>
            <content:encoded><![CDATA[<h1>什么是 DSPy</h1>
<h2>什么是ReAct</h2>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[美股交易工具]]></title>
            <link>https://zzfzzf.com/post/1984479926950039552</link>
            <guid>1984479926950039552</guid>
            <pubDate>Sat, 01 Nov 2025 05:23:31 GMT</pubDate>
            <content:encoded><![CDATA[<h1>美股交易分析工具完整指南</h1>
<h2>执行摘要</h2>
<p>在当今瞬息万变的美国股市中，正确的分析工具是投资者做出明-智决策、管理风险和发现机遇的关键。本指南综合了对六大类核心交易分析工具的深度研究，旨在为不同水平、不同风格的交易者提供一份清晰、实用且数据驱动的选型蓝图。我们深入剖析了从技术图表、基本面分析到异常期权流、市场情报、社交情绪及综合平台的各类工具，覆盖了从免费到机构级的不同解决方案。</p>
<p><strong>核心发现：</strong></p>
<ol>
<li><p><strong>技术图表分析：</strong> <strong>TradingView</strong> 以其强大的图表功能、庞大的社区指标库和广泛的全球市场覆盖，成为各类交易者的首选。对于专注于美股筛选和可视化的用户，<strong>FinViz Elite</strong> 提供了极高的效率。而 <strong>StockCharts</strong> 则以其专业的图表类型和系统化的技术分析教育资源，服务于深度技术分析师。</p>
</li>
<li><p><strong>基本面分析：</strong> 对于深度基本面研究，<strong>Seeking Alpha</strong> 凭借其财报电话会议逐字稿、量化评级和专家分析内容脱颖而出。<strong>MarketBeat</strong> 则在实时新闻、分析师评级和事件驱动警报方面表现出色，适合需要快速响应市场动态的投资者。</p>
</li>
<li><p><strong>社交与情绪分析：</strong> 学术与市场证据均表明，社交媒体情绪，特别是<strong>情绪量变化 (Sentiment Volume Change, SVC)</strong> 指标，能有效预测短期股价变动。<strong>Stocktwits</strong> 作为领先的金融社交平台，提供了宝贵的实时市场情绪数据。同时，YoloStocks、SwaggyStocks 等工具提供了对 Reddit 等社区热度的有效追踪。</p>
</li>
<li><p><strong>异常交易与期权流：</strong> <strong>Unusual Whales</strong> 以其全面的期权流数据、异常交易警报和强大的分析工具套件，成为该领域的领导者。<strong>FlowAlgo</strong> 和 <strong>BlackBoxStocks</strong> 等平台也提供了强大的实时订单流追踪和智能警报功能，帮助交易者追踪“聪明钱”的动向。</p>
</li>
<li><p><strong>市场情报与综合平台：</strong> 对于需要“速度与执行”的日内交易者，<strong>Benzinga Pro</strong> 提供了无与伦比的实时新闻和交易信号。而对于机构投资者，<strong>Bloomberg Terminal</strong> 和 <strong>LSEG Workspace (Refinitiv)</strong> 依然是数据、研究、分析和执行的黄金标准，提供了端到端的解决方案和强大的合规支持。</p>
</li>
</ol>
<p><strong>核心推荐：</strong></p>
<p>本指南的核心价值在于提供基于不同预算和交易风格的“工具组合”建议。我们发现，单一工具往往无法满足所有需求，最佳实践是通过组合来实现优势互补。</p>
<ul>
<li><p><strong>低预算/新手入门 (年预算 &lt; $500):</strong></p>
<ul>
<li><strong>TradingView (免费版)</strong> + <strong>Yahoo Finance (免费版)</strong> + <strong>FinViz (免费版)</strong> 作为基础图表、新闻和筛选工具。</li>
<li>考虑短期订阅 <strong>MarketBeat All Access ($39.99/月)</strong> 以验证事件驱动警报的价值。</li>
</ul>
</li>
<li><p><strong>活跃交易者 (年预算 $500 - $2,500):</strong></p>
<ul>
<li><strong>TradingView (付费版)</strong> + <strong>FinViz Elite ($299.50/年)</strong> 构成强大的图表与筛选组合。</li>
<li><strong>Seeking Alpha Premium (约 $299/年)</strong> 用于深度基本面研究。</li>
<li><strong>Unusual Whales (入门套餐 $36/月)</strong> 用于期权流监控。</li>
</ul>
</li>
<li><p><strong>专业/机构级 (年预算 &gt; $20,000):</strong></p>
<ul>
<li>核心选择 <strong>Bloomberg Terminal</strong> 或 <strong>LSEG Workspace</strong> 作为研究和执行的主平台。</li>
<li>叠加 <strong>Benzinga Pro (Essential)</strong> 以增强实时新闻和信号的触达速度。</li>
<li>集成 <strong>AlphaSense</strong> 等AI驱动的市场情报平台，以获取更深层次的洞察。</li>
</ul>
</li>
</ul>
<p>本指南将引导您深入了解每个工具的优劣势、定价细节和适用场景，帮助您构建最适合自身需求的个性化交易分析工具箱，从而在复杂的市场环境中获得持续的竞争优势。</p>
<h2>1. 引言</h2>
<p>美国股市以其巨大的市场规模、高度的流动性和丰富多样的金融产品，吸引了全球数以百万计的投资者。然而，市场的复杂性和信息的爆炸性增长也为交易者带来了前所未有的挑战。要在竞争激烈的市场中取得成功，交易者不仅需要坚实的投资知识和策略，更需要依赖强大、高效、可靠的分析工具来辅助决策。</p>
<p>本指南的目标，正是为美股交易者提供一份全面、客观且实用的分析工具选型手册。我们认识到，无论是初入市场的新手，还是经验丰富的专业交易员，亦或是管理庞大资产的机构投资者，都面临着在海量工具中做出最佳选择的难题。工具的选择不仅关系到交易效率和成本控制，更直接影响到投资策略的执行效果和最终的投资回报。</p>
<p>为此，我们系统性地研究了覆盖美股交易全流程的六大类分析工具，包括：</p>
<ul>
<li><strong>技术图表分析工具：</strong> 交易决策的“眼睛”，提供价格、成交量和技术指标的可视化分析。</li>
<li><strong>基本面分析工具：</strong> 价值投资的基石，提供公司财务、行业趋势和宏观经济的深度数据。</li>
<li><strong>社交交易和情绪跟踪工具：</strong> 把握市场脉搏的“耳朵”，捕捉由散户和社区驱动的市场热点。</li>
<li><strong>异常交易和期权分析工具：</strong> 追踪“聪明钱”流向的利器，揭示隐藏在大量订单背后的市场意图。</li>
<li><strong>市场情报和情绪分析工具：</strong> 提供宏观视角和深度洞察，连接经济、政治与市场动态。</li>
<li><strong>综合分析平台：</strong> 服务于专业和机构投资者的一站式解决方案，整合数据、新闻、分析和执行。</li>
</ul>
<p>通过对每个类别下主流工具的功能、数据质量、定价、优缺点和适用人群进行深度剖析，本指南旨在回答以下核心问题：</p>
<ul>
<li><strong>定位：</strong> 这个工具是为谁设计的？它在交易工作流中扮演什么角色？</li>
<li><strong>价值：</strong> 它解决了什么核心痛点？相比竞争对手，其独特优势是什么？</li>
<li><strong>成本：</strong> 获取这些功能需要付出多少成本？是否有更具性价比的替代方案？</li>
<li><strong>组合：</strong> 如何将不同工具组合起来，形成一个1+1&gt;2的强大工具箱？</li>
</ul>
<p>本指南严格基于公开可验证的信息，包括官方网站、定价页面、权威第三方评测和学术研究报告。所有分析和建议均以数据和事实为基础，力求客观中立。我们希望这份指南能成为您在美股交易道路上的得力助手，帮助您在信息海洋中精准导航，做出更明智、更高效的投资决策。</p>
<h2>2. 技术图表分析工具对比 (TradingView, FinViz, StockCharts)</h2>
<p>技术图表是交易者进行市场分析的“眼睛”。优秀的图表工具不仅提供实时、准确的价格数据，还集成了丰富的技术指标、绘图工具和分析功能，帮助交易者识别趋势、确定进出场点。本章节将深度对比三款业界领先的技术图表分析工具：TradingView、FinViz 和 StockCharts。</p>
<h3>2.1 TradingView: 全能型行业标杆</h3>
<p>TradingView 以其“超级图表 (Supercharts)”、强大的社区生态和广泛的市场覆盖，无可争议地成为了全球交易者的首选平台。它不仅仅是一个图表工具，更是一个集分析、社交和交易于一体的综合生态系统。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><strong>强大的图表系统：</strong> 提供超过17种图表类型（包括专业的砖形图、卡吉图等）、400+ 内置技术指标和110+ 绘图工具。用户体验流畅，定制化程度极高。</li>
<li><strong>庞大的社区与指标库：</strong> 拥有超过10万个由社区用户创建的公开指标和脚本。这意味着几乎任何可以想象到的分析方法，都能在社区中找到现成的解决方案。</li>
<li><strong>Pine Script™ 语言：</strong> 内置的程序化语言 Pine Script™ 允许用户创建、回测和自动化自己的交易策略，极大地降低了量化交易的门槛。</li>
<li><strong>广泛的市场覆盖：</strong> 连接全球数百个数据源，覆盖股票、ETF、外汇、期货、加密货币等几乎所有主流市场的实时和历史数据。</li>
<li><strong>社交与交易集成：</strong> 用户可以分享交易观点、观看实时直播、并与全球数百万交易者互动。同时，平台支持与多家券商直接连接，实现从分析到交易的无缝衔接。</li>
</ul>
<p><strong>定价与订阅：</strong></p>
<p>TradingView 提供从免费到多种付费等级的订阅计划。核心差异在于图表布局、指标数量、历史数据深度、警报数量和数据刷新速度。付费计划（如 Essential, Plus, Premium）能解锁无广告、更多指标、秒级数据和永久警报等专业功能。对于活跃交易者而言，付费订阅是释放平台全部潜力的必要投资。</p>
<table>
<thead>
<tr>
<th align="left">维度</th>
<th align="left">Basic (免费)</th>
<th align="left">Essential</th>
<th align="left">Plus</th>
<th align="left">Premium</th>
</tr>
</thead>
<tbody><tr>
<td align="left">每标签页图表数</td>
<td align="left">1</td>
<td align="left">2</td>
<td align="left">4</td>
<td align="left">8</td>
</tr>
<tr>
<td align="left">每图表指标数</td>
<td align="left">2</td>
<td align="left">5</td>
<td align="left">10</td>
<td align="left">25</td>
</tr>
<tr>
<td align="left">活跃价格/技术警报</td>
<td align="left">3 / 0</td>
<td align="left">20 / 20</td>
<td align="left">100 / 100</td>
<td align="left">400 / 400</td>
</tr>
<tr>
<td align="left">历史K线上限</td>
<td align="left">5K</td>
<td align="left">10K</td>
<td align="left">10K</td>
<td align="left">20K</td>
</tr>
<tr>
<td align="left">无广告</td>
<td align="left">否</td>
<td align="left">是</td>
<td align="left">是</td>
<td align="left">是</td>
</tr>
</tbody></table>
<p><em>注：此为简化对比，完整功能与价格以官网为准。</em></p>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 功能最全面、生态系统最强大、可定制性与扩展性无与伦比。</li>
<li><strong>劣势：</strong> 免费版功能限制较多且有广告；高级功能对新手有一定学习曲线。</li>
<li><strong>适用人群：</strong> 几乎所有类型的交易者。无论是需要多资产全球视野的宏观交易者，还是热衷于策略开发的量化分析师，都能在 TradingView 找到满足需求的工具。</li>
</ul>
<h3>2.2 FinViz: 最高效的股票筛选器</h3>
<p>FinViz 的核心价值在于其“强大、直观、高效”的股票筛选器和市场可视化功能。它将市场快照、热图、新闻、内部交易和筛选器整合在同一个界面，是快速发现交易机会的利器。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><strong>顶级股票筛选器：</strong> 提供海量的技术、基本面和描述性筛选条件，用户可以快速地从数千只股票中过滤出符合特定策略的标的。</li>
<li><strong>市场可视化：</strong> 其标志性的市场热图 (Market Maps) 能让用户在几秒钟内掌握整个市场的涨跌分布和板块轮动情况。</li>
<li><strong>简洁高效：</strong> 所有信息高度整合，界面简洁，无需复杂操作即可获取关键市场信息。</li>
</ul>
<p><strong>免费版 vs. Elite 版：</strong></p>
<p>FinViz 的价值分水岭在于其免费版和 Elite 付费版。免费版数据有15-20分钟延迟，功能受限，更适合盘后分析和概览。<strong>FinViz Elite</strong> ($39.50/月或 $299.50/年) 则解锁了平台的全部潜力，是活跃美股交易者的强大武器。</p>
<table>
<thead>
<tr>
<th align="left">维度</th>
<th align="left">免费版</th>
<th align="left">Elite 版</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>报价时效</strong></td>
<td align="left">延迟 15-20分钟</td>
<td align="left"><strong>实时</strong> (含盘前/盘后)</td>
</tr>
<tr>
<td align="left"><strong>图表能力</strong></td>
<td align="left">基本图表，操作受限</td>
<td align="left">高级图表，支持注释和技术指标</td>
</tr>
<tr>
<td align="left"><strong>筛选与警报</strong></td>
<td align="left">基础筛选，无警报</td>
<td align="left">高级筛选，实时警报（邮件/推送）</td>
</tr>
<tr>
<td align="left"><strong>数据导出/API</strong></td>
<td align="left">不支持</td>
<td align="left"><strong>支持</strong></td>
</tr>
<tr>
<td align="left"><strong>回测功能</strong></td>
<td align="left">无</td>
<td align="left"><strong>有</strong></td>
</tr>
</tbody></table>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 筛选功能无与伦比，可视化界面直观高效，Elite 版性价比高。</li>
<li><strong>劣势：</strong> 主要专注于美股市场；图表功能相比 TradingView 等专业工具较为基础。</li>
<li><strong>适用人群：</strong> 免费版适合进行市场概览和盘后研究的用户。<strong>Elite 版</strong>则强烈推荐给以<strong>美股</strong>为主要市场、依赖<strong>高效筛选</strong>发现交易机会的<strong>日内和波段交易者</strong>。</li>
</ul>
<h3>2.3 StockCharts: 专业图表与系统化教育</h3>
<p>StockCharts 是一个历史悠久且备受尊敬的技术分析平台，其核心优势在于提供专业的、高度定制化的图表工具和系统化的技术分析教育资源。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><strong>专业图表类型：</strong> 除了标准图表，还提供点数图 (P&amp;F)、相对旋转图 (RRG)、季节性图表等专业分析师青睐的高级图表，有助于从不同维度理解市场。</li>
<li><strong>强大的扫描与警报：</strong> 提供高级扫描工作台，用户可以基于复杂的技术条件创建扫描规则，并设置详细的警报，形成“扫描-分析-监控”的完整工作流。</li>
<li><strong>系统化的教育资源：</strong> 其 “ChartSchool” 是一个内容丰富的技术分析知识库，为从新手到专家的所有用户提供了宝贵的学习材料。</li>
</ul>
<p><strong>定价与订阅：</strong></p>
<p>StockCharts 提供 Basic, Extra, Pro 三个主要付费等级，核心差异在于历史数据深度（Pro可追溯至1900年）、扫描和警报的数量、以及高级图表功能（如 RRG 图）的可用性。其实时数据通常需要额外订阅。</p>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 提供独特的专业图表工具，扫描和警报系统强大，教育资源非常完善。</li>
<li><strong>劣势：</strong> 界面相对传统，实时数据需要额外付费，综合性不及 TradingView。</li>
<li><strong>适用人群：</strong> 专注于<strong>深度技术分析</strong>的交易者和图表分析师，以及希望系统化学习技术分析的投资者。对于需要使用 P&amp;F 或 RRG 等特定分析方法的用户，StockCharts 是不二之选。</li>
</ul>
<h3>2.4 技术图表工具横向对比与选型建议</h3>
<table>
<thead>
<tr>
<th align="left">维度</th>
<th align="left">TradingView</th>
<th align="left">FinViz Elite</th>
<th align="left">StockCharts</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>核心优势</strong></td>
<td align="left"><strong>全能生态系统</strong></td>
<td align="left"><strong>顶级股票筛选器</strong></td>
<td align="left"><strong>专业图表与教育</strong></td>
</tr>
<tr>
<td align="left"><strong>图表功能</strong></td>
<td align="left">★★★★★</td>
<td align="left">★★★☆☆</td>
<td align="left">★★★★☆</td>
</tr>
<tr>
<td align="left"><strong>筛选能力</strong></td>
<td align="left">★★★★☆</td>
<td align="left">★★★★★</td>
<td align="left">★★★★☆</td>
</tr>
<tr>
<td align="left"><strong>社区与扩展</strong></td>
<td align="left">★★★★★</td>
<td align="left">★★☆☆☆</td>
<td align="left">★★★☆☆</td>
</tr>
<tr>
<td align="left"><strong>易用性</strong></td>
<td align="left">★★★★☆</td>
<td align="left">★★★★★</td>
<td align="left">★★★☆☆</td>
</tr>
<tr>
<td align="left"><strong>市场覆盖</strong></td>
<td align="left"><strong>全球多资产</strong></td>
<td align="left"><strong>美股为主</strong></td>
<td align="left">美、加、英、印等</td>
</tr>
<tr>
<td align="left"><strong>年费参考</strong></td>
<td align="left">$155 - $719+</td>
<td align="left">$299.50</td>
<td align="left">$240 - $600+ (不含实时数据)</td>
</tr>
<tr>
<td align="left"><strong>最适合谁</strong></td>
<td align="left"><strong>几乎所有人</strong>，特别是需要脚本和多资产的交易者</td>
<td align="left"><strong>美股活跃交易者</strong>，依赖快速筛选</td>
<td align="left"><strong>纯粹的技术分析师</strong>，重视专业图表和学习资源</td>
</tr>
</tbody></table>
<p><strong>选型建议：</strong></p>
<ul>
<li><strong>如果你需要一个功能最全面、能够分析全球市场的平台，并且不介意投入时间学习和付费，选择 TradingView。</strong></li>
<li><strong>如果你的交易重心是美股，并且核心需求是通过高效筛选快速发现机会，FinViz Elite 是最具性价比的选择。</strong></li>
<li><strong>如果你是一位严肃的技术分析师，希望使用专业的图表工具并系统性地提升分析能力，StockCharts 是你的最佳伙伴。</strong></li>
</ul>
<h2>3. 基本面分析工具对比 (Seeking Alpha, MarketBeat, TipRanks)</h2>
<p>基本面分析是价值投资的基石，它通过深入研究公司财务状况、行业趋势和宏观经济环境，评估股票的内在价值。本章节将深度对比三款业界领先的基本面分析工具：Seeking Alpha、MarketBeat 和 TipRanks，帮助投资者选择最适合的深度研究平台。</p>
<h3>3.1 Seeking Alpha: 专业投资研究的金标准</h3>
<p>Seeking Alpha 作为最知名的投资研究平台之一，以其高质量的投资分析内容、财报电话会议逐字稿和量化评级系统而备受机构和个人投资者的推崇。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><p><strong>财报电话会议逐字稿：</strong> 这是 Seeking Alpha 最具价值的功能之一。平台提供数千家上市公司财报电话会议的完整逐字稿，包括管理层陈述和分析师问答环节，让投资者能够深入了解公司的经营状况和管理层的前瞻性指引。</p>
</li>
<li><p><strong>专业分析师报告：</strong> 汇聚了数万名经过认证的分析师，提供涵盖各个行业和公司的深度分析报告，包括投资论点、风险评估和目标价预测。</p>
</li>
<li><p><strong>Quant Rating 量化评级：</strong> 基于大数据和机器学习算法，从Growth、Profitability、Momentum、Revisions、Value 五个维度对股票进行客观评级，提供A+到F的评级结果。</p>
</li>
<li><p><strong>Factor Grade 因子评级：</strong> 深入分析影响股价的关键因子，包括财务实力、估值、增长前景等，为投资者提供多维度的投资决策支持。</p>
</li>
</ul>
<p><strong>订阅计划与定价：</strong></p>
<p>Seeking Alpha 提供多层次的订阅服务，满足不同投资者的需求：</p>
<table>
<thead>
<tr>
<th align="left">计划</th>
<th align="left">价格</th>
<th align="left">核心功能</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>免费版</strong></td>
<td align="left">$0</td>
<td align="left">基础新闻、有限分析文章、基本Quant评级</td>
</tr>
<tr>
<td align="left"><strong>Premium</strong></td>
<td align="left">~$299/年</td>
<td align="left">完整分析师报告、完整财报逐字稿、高级筛选器、Portfolio工具</td>
</tr>
<tr>
<td align="left"><strong>Alpha Picks</strong></td>
<td align="left">~$2,000/年</td>
<td align="left">专业投资组合推荐、深度研究报告、实时交易警报</td>
</tr>
</tbody></table>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 内容质量极高，财报逐字稿价值巨大，量化评级系统完善，适合深度基本面研究。</li>
<li><strong>劣势：</strong> 免费版功能有限，付费版价格较高；主要专注于美股市场。</li>
<li><strong>适用人群：</strong> 注重<strong>深度基本面研究</strong>的价值投资者、机构分析师和需要<strong>详细财报分析</strong>的专业投资者。</li>
</ul>
<h3>3.2 MarketBeat: 实时市场动态的综合平台</h3>
<p>MarketBeat 定位为一个&quot;一站式&quot;的投资信息聚合平台，特别擅长提供实时新闻、分析师评级变化、内部交易信息和市场事件警报。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><p><strong>实时新闻聚合：</strong> 从数百个新闻源汇聚最新的股市新闻，并提供智能分类和个性化推送，确保投资者不错过任何重要市场动态。</p>
</li>
<li><p><strong>分析师评级追踪：</strong> 实时跟踪所有主要投资银行和研究机构的评级变化，包括Buy、Hold、Sell建议和目标价调整，帮助投资者把握市场情绪变化。</p>
</li>
<li><p><strong>内部交易监控：</strong> 追踪公司高管、董事和大股东的买卖交易，为投资者提供&quot;内部人士&quot;观点的重要参考。</p>
</li>
<li><p><strong>智能警报系统：</strong> 提供基于价格、成交量、新闻、评级变化等多维度的智能警报，帮助投资者及时响应市场机会。</p>
</li>
<li><p><strong>财务数据与分析：</strong> 提供完整的财务报表、关键财务比率和盈利预测，支持基本面分析需求。</p>
</li>
</ul>
<p><strong>订阅计划：</strong></p>
<table>
<thead>
<tr>
<th align="left">计划</th>
<th align="left">价格</th>
<th align="left">核心功能</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>免费版</strong></td>
<td align="left">$0</td>
<td align="left">基础新闻、有限警报、基本评级信息</td>
</tr>
<tr>
<td align="left"><strong>All Access</strong></td>
<td align="left">$39.99/月</td>
<td align="left">无限警报、实时评级变化、内部交易数据、高级筛选器</td>
</tr>
</tbody></table>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 信息聚合全面，警报系统强大，价格相对亲民，适合快速获取市场动态。</li>
<li><strong>劣势：</strong> 分析深度不及Seeking Alpha，主要是信息聚合而非原创分析。</li>
<li><strong>适用人群：</strong> 需要<strong>实时市场资讯</strong>和<strong>事件驱动交易机会</strong>的活跃投资者，以及希望低成本获取全面市场信息的散户投资者。</li>
</ul>
<h3>3.3 TipRanks: 数据驱动的分析师追踪平台</h3>
<p>TipRanks 通过独特的&quot;分析师表现追踪&quot;方法论，为投资者提供基于历史成功率的分析师评级和股票推荐，是一个高度数据驱动的投资决策支持平台。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><p><strong>分析师排名系统：</strong> 基于历史预测准确性对数万名分析师进行排名，让投资者能够识别最可靠的分析师观点。</p>
</li>
<li><p><strong>Smart Score 评级：</strong> 综合分析师观点、内部交易、新闻情绪、技术指标等多个维度，生成1-10的综合评分，为投资者提供一目了然的投资参考。</p>
</li>
<li><p><strong>机构活动追踪：</strong> 监控对冲基金、养老基金等机构投资者的持仓变化，揭示&quot;聪明钱&quot;的投资动向。</p>
</li>
<li><p><strong>个人投资组合分析：</strong> 提供Portfolio分析工具，帮助投资者评估现有持仓的风险收益特征和优化建议。</p>
</li>
</ul>
<p><strong>订阅计划：</strong></p>
<table>
<thead>
<tr>
<th align="left">计划</th>
<th align="left">价格</th>
<th align="left">核心功能</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>免费版</strong></td>
<td align="left">$0</td>
<td align="left">基础Smart Score、有限分析师评级</td>
</tr>
<tr>
<td align="left"><strong>Premium</strong></td>
<td align="left">~$34.95/月</td>
<td align="left">完整Smart Score、分析师排名、机构活动、Portfolio工具</td>
</tr>
<tr>
<td align="left"><strong>Ultimate</strong></td>
<td align="left">~$79.95/月</td>
<td align="left">实时警报、高级筛选、API访问、优先客服</td>
</tr>
</tbody></table>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 数据驱动方法独特，分析师追踪功能强大，Smart Score系统实用性高。</li>
<li><strong>劣势：</strong> 内容原创性不足，主要依赖数据聚合；覆盖范围主要集中在美股。</li>
<li><strong>适用人群：</strong> 注重<strong>量化方法</strong>和<strong>数据驱动决策</strong>的投资者，以及希望追踪<strong>分析师表现</strong>和<strong>机构动向</strong>的专业投资者。</li>
</ul>
<h3>3.4 基本面分析工具横向对比与选型建议</h3>
<table>
<thead>
<tr>
<th align="left">维度</th>
<th align="left">Seeking Alpha</th>
<th align="left">MarketBeat</th>
<th align="left">TipRanks</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>核心优势</strong></td>
<td align="left"><strong>深度分析内容</strong></td>
<td align="left"><strong>实时信息聚合</strong></td>
<td align="left"><strong>数据驱动评级</strong></td>
</tr>
<tr>
<td align="left"><strong>内容质量</strong></td>
<td align="left">★★★★★</td>
<td align="left">★★★☆☆</td>
<td align="left">★★★★☆</td>
</tr>
<tr>
<td align="left"><strong>数据实时性</strong></td>
<td align="left">★★★☆☆</td>
<td align="left">★★★★★</td>
<td align="left">★★★★☆</td>
</tr>
<tr>
<td align="left"><strong>分析深度</strong></td>
<td align="left">★★★★★</td>
<td align="left">★★☆☆☆</td>
<td align="left">★★★☆☆</td>
</tr>
<tr>
<td align="left"><strong>性价比</strong></td>
<td align="left">★★★☆☆</td>
<td align="left">★★★★★</td>
<td align="left">★★★★☆</td>
</tr>
<tr>
<td align="left"><strong>独特功能</strong></td>
<td align="left">财报逐字稿、专业分析师</td>
<td align="left">实时警报、内部交易</td>
<td align="left">分析师排名、Smart Score</td>
</tr>
<tr>
<td align="left"><strong>年费参考</strong></td>
<td align="left">~$299 (Premium)</td>
<td align="left">~$479.88 (All Access)</td>
<td align="left">~$419.40 (Premium)</td>
</tr>
<tr>
<td align="left"><strong>最适合谁</strong></td>
<td align="left"><strong>深度研究型</strong>价值投资者</td>
<td align="left"><strong>事件驱动型</strong>活跃交易者</td>
<td align="left"><strong>量化导向型</strong>投资者</td>
</tr>
</tbody></table>
<p><strong>组合使用建议：</strong></p>
<p>对于不同类型的投资者，我们推荐以下工具组合策略：</p>
<ul>
<li><p><strong>价值投资者 (长期持有)：</strong> <strong>Seeking Alpha Premium</strong> 作为主要研究工具，辅以 <strong>TipRanks 免费版</strong> 的Smart Score作为二次验证。</p>
</li>
<li><p><strong>波段交易者 (中短期)：</strong> <strong>MarketBeat All Access</strong> 获取实时市场动态和事件驱动机会，配合 <strong>TipRanks Premium</strong> 的机构活动追踪。</p>
</li>
<li><p><strong>综合型投资者：</strong> 可以考虑 <strong>Seeking Alpha</strong> + <strong>MarketBeat</strong> 的组合，前者负责深度研究，后者负责实时监控。</p>
</li>
<li><p><strong>预算有限的投资者：</strong> <strong>MarketBeat All Access</strong> 提供了最佳的性价比，能够满足大部分基本面分析需求。</p>
</li>
</ul>
<p><strong>关键选型原则：</strong></p>
<ol>
<li><strong>投资风格匹配：</strong> 长期价值投资优选Seeking Alpha，短期事件驱动优选MarketBeat。</li>
<li><strong>信息深度需求：</strong> 需要深度分析选Seeking Alpha，需要广度覆盖选MarketBeat。</li>
<li><strong>预算考量：</strong> MarketBeat提供最好的入门级性价比，TipRanks居中，Seeking Alpha最昂贵但质量最高。</li>
<li><strong>技术偏好：</strong> 偏爱量化方法的投资者应优先考虑TipRanks的数据驱动approach。</li>
</ol>
<h2>4. 社交交易和情绪跟踪工具分析</h2>
<p>社交媒体已经成为现代金融市场的重要组成部分，散户投资者的集体情绪和行为对股价产生着越来越显著的影响。学术研究表明，社交媒体情绪，特别是<strong>情绪量变化 (Sentiment Volume Change, SVC)</strong> 指标，能够有效预测短期股价变动。本章节将深入分析当前最重要的社交交易和情绪跟踪工具。</p>
<h3>4.1 学术基础：社交情绪的预测价值</h3>
<p>在深入分析具体工具之前，我们需要理解社交情绪分析的科学基础。多项学术研究证实了社交媒体情绪与股价变动之间的显著关联：</p>
<p><strong>关键研究发现：</strong></p>
<ul>
<li><p><strong>情绪量变化 (SVC) 的预测力：</strong> 研究发现，当某股票在社交媒体上的讨论量突然激增，且情绪倾向发生显著变化时，往往预示着股价的短期异动。SVC指标通过量化这种变化，为投资者提供了早期预警信号。</p>
</li>
<li><p><strong>Reddit效应：</strong> 特别是自2021年GameStop事件以来，Reddit社区（如r/wallstreetbets）对股价的影响力得到了广泛关注。研究表明，Reddit讨论热度的激增往往领先股价变动1-3个交易日。</p>
</li>
<li><p><strong>情绪持续性：</strong> 社交媒体情绪往往具有一定的持续性和传染性，正面情绪会吸引更多投资者关注，从而形成自我强化的循环。</p>
</li>
</ul>
<p>这些发现为社交情绪工具的实用价值提供了坚实的理论支撑。</p>
<h3>4.2 Stocktwits: 金融社交媒体的领导者</h3>
<p>Stocktwits 是专门为投资者和交易者设计的社交媒体平台，被誉为&quot;金融界的Twitter&quot;，为用户提供实时的市场情绪数据和社区观点。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><p><strong>实时情绪追踪：</strong> 每个股票都有专门的&quot;情绪仪表盘&quot;，显示看涨/看跌的投资者比例，以及情绪变化趋势，帮助用户快速把握市场共识。</p>
</li>
<li><p><strong>影响力用户识别：</strong> 平台会识别和标记有影响力的投资者和分析师，其观点往往具有更强的市场影响力。</p>
</li>
<li><p><strong>热门话题追踪：</strong> 实时显示最热门讨论的股票和话题，帮助用户发现市场关注焦点。</p>
</li>
<li><p><strong>专业集成：</strong> 许多专业交易平台（如TD Ameritrade、E*TRADE等）都集成了Stocktwits的数据，体现了其专业认可度。</p>
</li>
</ul>
<p><strong>商业模式与定价：</strong></p>
<p>Stocktwits 采用免费增值模式，基础功能免费，高级功能通过 Stocktwits Premium 提供。Premium功能包括高级筛选器、详细分析工具和无广告体验。</p>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 专业性强，数据质量高，与主流交易平台集成度好。</li>
<li><strong>劣势：</strong> 用户基数相对较小，主要限于专业投资者群体。</li>
<li><strong>适用人群：</strong> 专业交易者和活跃投资者，特别是重视<strong>社区共识</strong>和<strong>专业观点</strong>的用户。</li>
</ul>
<h3>4.3 Reddit追踪工具：YoloStocks &amp; SwaggyStocks</h3>
<p>Reddit，特别是r/wallstreetbets等投资相关subreddit，已经成为散户投资者的重要聚集地。专门的Reddit追踪工具能够系统性地监控和分析这些社区的讨论热度和情绪变化。</p>
<h4>4.3.1 YoloStocks: 专业的Reddit数据聚合</h4>
<p>YoloStocks 专注于从Reddit投资社区中提取和分析数据，为用户提供量化的社区情绪指标。</p>
<p><strong>核心功能：</strong></p>
<ul>
<li><p><strong>热度排行榜：</strong> 实时显示Reddit上讨论最热门的股票，按照提及次数、upvote数量等维度排序。</p>
</li>
<li><p><strong>情绪分析：</strong> 使用自然语言处理技术分析评论的情绪倾向，生成看涨/看跌比例。</p>
</li>
<li><p><strong>历史趋势：</strong> 提供股票在Reddit上的历史讨论趋势，帮助用户识别情绪变化模式。</p>
</li>
</ul>
<h4>4.3.2 SwaggyStocks: 可视化的Reddit监控</h4>
<p>SwaggyStocks 以其直观的可视化界面和全面的数据覆盖而著称。</p>
<p><strong>核心功能：</strong></p>
<ul>
<li><p><strong>实时热图：</strong> 将Reddit讨论热度以热图形式展示，用户可以一目了然地发现热门股票。</p>
</li>
<li><p><strong>情绪仪表盘：</strong> 提供详细的情绪分析仪表盘，包括情绪评分、讨论量变化等关键指标。</p>
</li>
<li><p><strong>多维度筛选：</strong> 支持按时间段、subreddit、市值等多个维度筛选和分析数据。</p>
</li>
</ul>
<p><strong>两者对比：</strong></p>
<table>
<thead>
<tr>
<th align="left">维度</th>
<th align="left">YoloStocks</th>
<th align="left">SwaggyStocks</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>界面设计</strong></td>
<td align="left">简洁专业</td>
<td align="left">可视化丰富</td>
</tr>
<tr>
<td align="left"><strong>数据深度</strong></td>
<td align="left">★★★★☆</td>
<td align="left">★★★★★</td>
</tr>
<tr>
<td align="left"><strong>更新频率</strong></td>
<td align="left">实时</td>
<td align="left">实时</td>
</tr>
<tr>
<td align="left"><strong>免费功能</strong></td>
<td align="left">基础数据免费</td>
<td align="left">大部分功能免费</td>
</tr>
<tr>
<td align="left"><strong>适用场景</strong></td>
<td align="left">快速筛选热门股票</td>
<td align="left">深度分析社区情绪</td>
</tr>
</tbody></table>
<h3>4.4 Discord投资社区监控</h3>
<p>Discord已经成为许多投资社区的聚集地，特别是一些专注于特定策略或股票的小众社区。虽然没有专门的Discord监控工具，但许多投资者会加入相关的Discord服务器以获取实时的市场讨论和观点。</p>
<p><strong>主要Discord投资社区类型：</strong></p>
<ul>
<li><strong>交易策略社区：</strong> 专注于特定交易策略（如日内交易、期权交易）的讨论。</li>
<li><strong>股票研究群组：</strong> 深度研究特定公司或行业的专业社区。</li>
<li><strong>警报分享群：</strong> 会员分享交易信号和市场机会的实时群组。</li>
</ul>
<p><strong>参与Discord社区的建议：</strong></p>
<ul>
<li>选择有良好管理和活跃讨论的社区</li>
<li>关注社区的历史表现和成员质量</li>
<li>将Discord信息作为补充参考，而非主要决策依据</li>
</ul>
<h3>4.5 Quiver Quantitative: 另类数据的集大成者</h3>
<p>Quiver Quantitative 是一个创新的平台，专门收集和分析各种&quot;另类数据&quot;来源，包括国会议员交易、游说活动、政府合同等，为投资者提供独特的市场洞察。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><p><strong>国会议员交易追踪：</strong> 监控美国国会议员的股票交易活动，这些信息往往反映了政策内部人士的预期。</p>
</li>
<li><p><strong>政府合同数据：</strong> 追踪联邦政府合同的授予情况，帮助投资者发现潜在的政府相关投资机会。</p>
</li>
<li><p><strong>游说活动监控：</strong> 分析企业的游说支出和活动，识别可能影响股价的政策变化。</p>
</li>
<li><p><strong>社交媒体整合：</strong> 整合多个社交媒体平台的数据，提供综合的情绪分析。</p>
</li>
</ul>
<p><strong>定价模式：</strong></p>
<p>Quiver Quantitative 采用订阅制，提供不同层级的数据访问权限。基础功能相对免费，高级数据和API访问需要付费订阅。</p>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 数据来源独特，提供传统分析工具无法获得的洞察。</li>
<li><strong>劣势：</strong> 数据解释需要一定的专业知识，对初学者可能较为复杂。</li>
<li><strong>适用人群：</strong> 专业投资者、量化基金和需要<strong>另类数据</strong>洞察的高级交易者。</li>
</ul>
<h3>4.6 社交情绪工具综合评价与使用策略</h3>
<p><strong>工具功能矩阵：</strong></p>
<table>
<thead>
<tr>
<th align="left">工具</th>
<th align="left">实时性</th>
<th align="left">数据质量</th>
<th align="left">易用性</th>
<th align="left">独特价值</th>
<th align="left">最适合场景</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Stocktwits</strong></td>
<td align="left">★★★★★</td>
<td align="left">★★★★☆</td>
<td align="left">★★★★★</td>
<td align="left">专业社区情绪</td>
<td align="left">日内交易，情绪确认</td>
</tr>
<tr>
<td align="left"><strong>Reddit工具</strong></td>
<td align="left">★★★★★</td>
<td align="left">★★★☆☆</td>
<td align="left">★★★★☆</td>
<td align="left">散户热点发现</td>
<td align="left">趋势跟踪，热点挖掘</td>
</tr>
<tr>
<td align="left"><strong>Discord社区</strong></td>
<td align="left">★★★★★</td>
<td align="left">★★★☆☆</td>
<td align="left">★★★☆☆</td>
<td align="left">小众深度讨论</td>
<td align="left">策略学习，专业交流</td>
</tr>
<tr>
<td align="left"><strong>Quiver Quant</strong></td>
<td align="left">★★★☆☆</td>
<td align="left">★★★★★</td>
<td align="left">★★★☆☆</td>
<td align="left">政治经济洞察</td>
<td align="left">长期投资，政策影响分析</td>
</tr>
</tbody></table>
<p><strong>最佳实践与使用策略：</strong></p>
<ol>
<li><p><strong>多维度验证：</strong> 不要依赖单一社交情绪来源，建议结合多个平台的数据进行交叉验证。</p>
</li>
<li><p><strong>情绪变化比绝对水平更重要：</strong> 关注情绪的突然变化，而不是静态的情绪水平。SVC指标在这方面特别有用。</p>
</li>
<li><p><strong>结合基本面分析：</strong> 社交情绪应该作为基本面和技术分析的补充，而不是替代。</p>
</li>
<li><p><strong>时间敏感性：</strong> 社交情绪的影响通常是短期的，适合短线交易策略。</p>
</li>
<li><p><strong>风险控制：</strong> 社交媒体驱动的价格变动往往伴随高波动性，务必做好风险管理。</p>
</li>
</ol>
<p><strong>推荐使用组合：</strong></p>
<ul>
<li><strong>日内交易者：</strong> Stocktwits + SwaggyStocks，快速获取实时情绪变化</li>
<li><strong>波段交易者：</strong> Reddit工具 + Quiver Quantitative，发现中期趋势和催化剂</li>
<li><strong>长期投资者：</strong> Quiver Quantitative + 选择性参与Discord专业社区，获取深度洞察</li>
</ul>
<p>社交情绪分析正在成为现代投资决策流程中不可忽视的一环。掌握这些工具，能够帮助投资者更好地理解市场心理，抓住由情绪驱动的交易机会。## 5. 综合分析平台对比 (Bloomberg Terminal, LSEG Workspace, Benzinga Pro)</p>
<p>综合分析平台是为专业投资者和机构提供的&quot;一站式&quot;解决方案，它们整合了数据、新闻、分析、研究和交易执行功能。这些平台通常价格昂贵，但提供了无与伦比的信息深度、实时性和专业工具。本章节将深入分析三个最重要的综合分析平台。</p>
<h3>5.1 Bloomberg Terminal: 金融信息的&quot;王者&quot;</h3>
<p>Bloomberg Terminal 无疑是全球最知名、最权威的金融信息平台。自1981年推出以来，它已经成为华尔街和全球金融机构的标准配置，被誉为金融专业人士的&quot;必备工具&quot;。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><p><strong>无与伦比的数据覆盖：</strong> 提供全球几乎所有金融市场的实时数据，包括股票、债券、外汇、商品、衍生品等，覆盖深度和广度都是行业标杆。</p>
</li>
<li><p><strong>专业级新闻服务：</strong> Bloomberg News 提供24/7的全球财经新闻，速度和质量都是业界顶级。许多重要新闻往往在Bloomberg上首发。</p>
</li>
<li><p><strong>强大的分析工具：</strong> 内置数百个专业分析功能，从基本的图表分析到复杂的衍生品定价模型，满足各类专业分析需求。</p>
</li>
<li><p><strong>交易执行集成：</strong> 不仅提供分析功能，还支持直接交易执行，实现从研究到交易的无缝衔接。</p>
</li>
<li><p><strong>行业标准地位：</strong> 在专业投资圈，掌握Bloomberg Terminal的使用几乎是必备技能，许多金融机构将其作为员工培训的重要内容。</p>
</li>
</ul>
<p><strong>定价与成本：</strong></p>
<p>Bloomberg Terminal 的标准价格约为 <strong>$24,000/年/座位</strong>，对于需要实时数据的专业用户，这是一笔不小的投资。但考虑到其提供的信息价值和专业功能，对于管理大量资产的机构而言，这一成本往往是合理的。</p>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 数据最全面、信息最权威、功能最专业、行业认可度最高。</li>
<li><strong>劣势：</strong> 价格极其昂贵、学习曲线陡峭、对个人投资者而言性价比低。</li>
<li><strong>适用人群：</strong> <strong>机构投资者</strong>、<strong>专业基金经理</strong>、<strong>投资银行分析师</strong>和<strong>大型财富管理机构</strong>。</li>
</ul>
<h3>5.2 LSEG Workspace (原Refinitiv): Bloomberg的有力竞争者</h3>
<p>LSEG Workspace（原Refinitiv Workspace，更早前为Thomson Reuters Eikon）是Bloomberg Terminal的主要竞争对手，提供类似的综合金融信息服务，在某些方面甚至超越了Bloomberg。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><p><strong>卓越的新闻聚合：</strong> 整合了路透社等权威新闻源，在新闻聚合和分析方面具有传统优势。</p>
</li>
<li><p><strong>强大的固定收益工具：</strong> 在债券和固定收益市场分析方面，LSEG有着特别强的优势，其数据覆盖和分析工具在该领域甚至优于Bloomberg。</p>
</li>
<li><p><strong>API和数据服务：</strong> 为机构客户提供灵活的API接入和数据服务，便于集成到自有系统中。</p>
</li>
<li><p><strong>相对成本优势：</strong> 虽然同样昂贵，但通常比Bloomberg Terminal的价格稍低，为预算有限的机构提供了替代选择。</p>
</li>
<li><p><strong>区域化优势：</strong> 在某些地区市场（如欧洲、亚洲部分市场）的数据覆盖和本地化服务方面具有优势。</p>
</li>
</ul>
<p><strong>定价：</strong></p>
<p>LSEG Workspace 的价格通常在 <strong>$18,000-22,000/年/座位</strong> 左右，比Bloomberg稍低但仍属于高端专业工具范畴。</p>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 新闻服务优秀、固定收益工具强大、API服务灵活、价格相对有竞争力。</li>
<li><strong>劣势：</strong> 整体影响力不及Bloomberg、某些功能的用户体验略逊。</li>
<li><strong>适用人群：</strong> <strong>注重固定收益的机构投资者</strong>、<strong>需要灵活数据接入的量化基金</strong>、<strong>预算相对有限但仍需专业工具的金融机构</strong>。</li>
</ul>
<h3>5.3 Benzinga Pro: 面向活跃交易者的专业平台</h3>
<p>Benzinga Pro 定位为面向活跃交易者和中小型投资机构的专业信息平台，提供&quot;速度与执行&quot;导向的实时信息服务。</p>
<p><strong>核心功能与优势：</strong></p>
<ul>
<li><p><strong>超快新闻推送：</strong> Benzinga 以其新闻推送的速度著称，往往能在重要事件发生后的几秒钟内推送相关新闻，这对日内交易者具有巨大价值。</p>
</li>
<li><p><strong>实时交易信号：</strong> 提供基于新闻事件、技术分析和市场异动的实时交易信号，帮助交易者快速响应市场机会。</p>
</li>
<li><p><strong>期权流监控：</strong> 集成期权交易数据和异常交易监控，帮助交易者追踪&quot;聪明钱&quot;的动向。</p>
</li>
<li><p><strong>音频Squawk服务：</strong> 提供实时的语音新闻播报服务，让交易者在专注于屏幕交易时也能及时获取重要信息。</p>
</li>
<li><p><strong>社区与教育：</strong> 活跃的交易者社区和丰富的教育资源，适合交易者学习和交流。</p>
</li>
</ul>
<p><strong>订阅计划与定价：</strong></p>
<table>
<thead>
<tr>
<th align="left">计划</th>
<th align="left">价格</th>
<th align="left">核心功能</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Essential</strong></td>
<td align="left">$197/月</td>
<td align="left">实时新闻、基础信号、社区访问</td>
</tr>
<tr>
<td align="left"><strong>Professional</strong></td>
<td align="left">$297/月</td>
<td align="left">高级信号、期权流、音频Squawk</td>
</tr>
<tr>
<td align="left"><strong>Premium</strong></td>
<td align="left">$397/月</td>
<td align="left">全功能访问、优先支持、定制化警报</td>
</tr>
</tbody></table>
<p><strong>结论：</strong></p>
<ul>
<li><strong>优势：</strong> 新闻速度极快、交易信号实用、面向交易者设计、价格相对合理。</li>
<li><strong>劣势：</strong> 数据深度不及Bloomberg/LSEG、主要适合短线交易。</li>
<li><strong>适用人群：</strong> <strong>日内交易者</strong>、<strong>波段交易员</strong>、<strong>小型对冲基金</strong>和<strong>需要快速执行的活跃投资者</strong>。</li>
</ul>
<h3>5.4 其他值得关注的综合平台</h3>
<p>除了上述三大平台，还有一些其他重要的综合分析工具值得关注：</p>
<h4>5.4.1 CNBC Pro</h4>
<p><strong>定位：</strong> 面向专业投资者的新闻和数据服务。</p>
<p><strong>特色：</strong> 快速新闻推送、市场分析、投资组合工具。</p>
<p><strong>价格：</strong> 约 $34.99/月，性价比较高。</p>
<p><strong>适用人群：</strong> 需要专业新闻服务但预算有限的个人投资者。</p>
<h4>5.4.2 MarketWatch Premium</h4>
<p><strong>定位：</strong> 主流财经媒体的付费升级版。</p>
<p><strong>特色：</strong> 深度市场分析、投资组合追踪、警报服务。</p>
<p><strong>价格：</strong> 约 $34/月。</p>
<p><strong>适用人群：</strong> 长期投资者和需要综合市场信息的业余投资者。</p>
<h4>5.4.3 ZeroHedge Pro (Tyler Durden)</h4>
<p><strong>定位：</strong> 另类观点和深度市场分析。</p>
<p><strong>特色：</strong> 独特的市场观点、宏观经济分析、风险警示。</p>
<p><strong>价格：</strong> 年费约 $40/月。</p>
<p><strong>适用人群：</strong> 寻求另类观点和风险意识强的投资者。</p>
<h3>5.5 综合平台选型决策框架</h3>
<p><strong>按资产规模和专业程度选择：</strong></p>
<table>
<thead>
<tr>
<th align="left">用户类型</th>
<th align="left">推荐平台</th>
<th align="left">主要考虑因素</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>大型机构 (&gt;$1B AUM)</strong></td>
<td align="left">Bloomberg Terminal</td>
<td align="left">数据权威性、功能全面性、行业标准地位</td>
</tr>
<tr>
<td align="left"><strong>中型机构 ($100M-1B)</strong></td>
<td align="left">LSEG Workspace</td>
<td align="left">成本效益、功能专业性</td>
</tr>
<tr>
<td align="left"><strong>小型基金/家族办公室</strong></td>
<td align="left">Benzinga Pro + 其他工具组合</td>
<td align="left">速度、信号质量、成本控制</td>
</tr>
<tr>
<td align="left"><strong>活跃个人交易者</strong></td>
<td align="left">Benzinga Essential + CNBC Pro</td>
<td align="left">实时性、实用性、可负担性</td>
</tr>
<tr>
<td align="left"><strong>长期投资者</strong></td>
<td align="left">MarketWatch + ZeroHedge</td>
<td align="left">深度分析、多元观点、性价比</td>
</tr>
</tbody></table>
<p><strong>关键选型标准：</strong></p>
<ol>
<li><p><strong>信息时效性需求：</strong> 日内交易需要秒级信息，长期投资可接受分钟级延迟。</p>
</li>
<li><p><strong>分析深度要求：</strong> 专业机构需要复杂建模工具，个人投资者更重视易用性。</p>
</li>
<li><p><strong>数据覆盖范围：</strong> 全球多资产投资需要Bloomberg级别的覆盖，专注美股可选择专业化工具。</p>
</li>
<li><p><strong>成本承受能力：</strong> 机构可承受高昂的专业工具成本，个人投资者需要性价比。</p>
</li>
<li><p><strong>集成需求：</strong> 大型机构需要API和系统集成，个人用户更重视界面友好。</p>
</li>
</ol>
<p><strong>最佳实践建议：</strong></p>
<ul>
<li><p><strong>逐步升级策略：</strong> 从低成本工具开始，随着投资规模和专业需求增长逐步升级。</p>
</li>
<li><p><strong>功能互补组合：</strong> 没有单一工具能满足所有需求，合理的工具组合往往比单一昂贵平台更有效。</p>
</li>
<li><p><strong>试用评估：</strong> 大多数专业平台提供试用期，充分利用试用期评估实际使用效果。</p>
</li>
<li><p><strong>成本效益分析：</strong> 定期评估工具成本相对于投资回报的贡献，确保工具投资的合理性。</p>
</li>
</ul>
<p>综合分析平台是专业投资活动的重要基础设施。选择合适的平台，不仅能显著提升投资决策的质量和效率，还能在竞争激烈的市场中获得重要的信息优势。## 6. 异常交易和期权分析工具</p>
<p>异常交易和期权分析是现代量化交易的重要组成部分。这类工具通过监控大额交易、异常成交量和期权流数据，帮助交易者识别&quot;聪明钱&quot;的动向，发现市场中的潜在催化剂。本章节将分析该领域的主要工具。</p>
<h3>6.1 工具类别概述</h3>
<p>异常交易和期权分析工具主要分为以下几类：</p>
<p><strong>异常交易监控工具：</strong></p>
<ul>
<li>大额单笔交易追踪</li>
<li>异常成交量警报</li>
<li>暗池交易监控</li>
<li>机构订单流分析</li>
</ul>
<p><strong>期权流分析工具：</strong></p>
<ul>
<li>大额期权交易监控</li>
<li>期权流分析和异常活动</li>
<li>Delta对冲交易识别</li>
<li>期权情绪指标</li>
</ul>
<p><strong>综合分析平台：</strong></p>
<ul>
<li>多维度数据整合</li>
<li>实时警报系统</li>
<li>历史模式分析</li>
<li>风险管理工具</li>
</ul>
<h3>6.2 核心工具分析</h3>
<h4>6.2.1 Unusual Whales</h4>
<p><strong>平台定位：</strong> 专业的期权流分析平台，专注于异常期权交易的识别和分析。</p>
<p><strong>核心功能：</strong></p>
<ul>
<li><strong>期权流监控：</strong> 实时追踪大额期权交易，识别异常活动</li>
<li><strong>暗池数据：</strong> 提供暗池交易数据和分析</li>
<li><strong>国会交易追踪：</strong> 监控政府官员的交易活动</li>
<li><strong>社交情绪整合：</strong> 结合社交媒体数据进行综合分析</li>
</ul>
<p><strong>定价模式：</strong></p>
<ul>
<li>基础版：约 $36/月</li>
<li>专业版：约 $75/月</li>
<li>企业版：定制定价</li>
</ul>
<h4>6.2.2 FlowAlgo</h4>
<p><strong>平台定位：</strong> 实时订单流分析平台，专注于识别机构级别的交易活动。</p>
<p><strong>核心功能：</strong></p>
<ul>
<li><strong>智能订单识别：</strong> 使用算法识别大型机构的分割订单</li>
<li><strong>实时流监控：</strong> 提供实时的订单流数据和分析</li>
<li><strong>异常活动警报：</strong> 基于历史模式识别异常交易活动</li>
<li><strong>多资产覆盖：</strong> 覆盖股票、ETF和期权市场</li>
</ul>
<h4>6.2.3 BlackBoxStocks</h4>
<p><strong>平台定位：</strong> 面向日内交易者的实时警报和分析平台。</p>
<p><strong>核心功能：</strong></p>
<ul>
<li><strong>实时警报系统：</strong> 基于技术指标和异常活动的智能警报</li>
<li><strong>期权流分析：</strong> 专业的期权交易分析工具</li>
<li><strong>社区功能：</strong> 交易者社区和策略分享</li>
<li><strong>教育资源：</strong> 丰富的交易教育内容</li>
</ul>
<h3>6.3 工具选择指南</h3>
<p><strong>按交易风格选择：</strong></p>
<table>
<thead>
<tr>
<th align="left">交易风格</th>
<th align="left">推荐工具</th>
<th align="left">关键考虑因素</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>日内交易</strong></td>
<td align="left">BlackBoxStocks, FlowAlgo</td>
<td align="left">实时性、警报速度</td>
</tr>
<tr>
<td align="left"><strong>波段交易</strong></td>
<td align="left">Unusual Whales</td>
<td align="left">期权流分析、中期趋势</td>
</tr>
<tr>
<td align="left"><strong>长期投资</strong></td>
<td align="left">Unusual Whales (基础版)</td>
<td align="left">机构动向、政策影响</td>
</tr>
</tbody></table>
<p><strong>按预算水平选择：</strong></p>
<p><strong>入门级 (50美元每月)：</strong> Unusual Whales 基础版
<strong>专业级 (50-100美元每月)：</strong> FlowAlgo 或 BlackBoxStocks
<strong>机构级 (100美元每月)：</strong> 多工具组合或企业定制方案</p>
<h3>6.4 使用最佳实践</h3>
<p><strong>数据解读原则：</strong></p>
<ol>
<li><p><strong>关注异常而非绝对值：</strong> 重要的是交易量或期权活动的相对变化，而不是绝对数值。</p>
</li>
<li><p><strong>结合多个数据源：</strong> 单一数据源可能产生误导，建议交叉验证。</p>
</li>
<li><p><strong>理解时间敏感性：</strong> 异常交易信号的有效期通常很短，需要快速响应。</p>
</li>
<li><p><strong>风险管理优先：</strong> 异常交易数据应作为风险管理的工具，而不是盲目跟随的信号。</p>
</li>
</ol>
<p><strong>警报设置建议：</strong></p>
<ul>
<li><strong>成交量异常：</strong> 设置成交量超过日均3-5倍的警报</li>
<li><strong>期权活动：</strong> 关注期权成交量异常和大额单笔交易</li>
<li><strong>价格突破：</strong> 结合技术分析设置价格突破警报</li>
<li><strong>时间过滤：</strong> 避免在低流动性时段的误报</li>
</ul>
<p>这类工具对于理解市场微观结构和&quot;聪明钱&quot;动向具有重要价值，但需要与其他分析方法结合使用，并保持严格的风险控制。## 7. 不同预算水平的最佳工具组合推荐</p>
<p>基于对各类工具的深度分析，本章节将为不同预算水平和交易风格的投资者提供具体的工具组合建议。我们的核心理念是：<strong>没有单一工具能够满足所有需求，最佳实践是通过智能组合来实现优势互补</strong>。</p>
<h3>7.1 入门级组合 (年预算 &lt; $500)</h3>
<p><strong>目标用户：</strong> 刚进入美股市场的新手投资者、学生、预算有限的散户。</p>
<p><strong>核心原则：</strong> 免费优先，重点学习，逐步验证价值。</p>
<h4>推荐组合一：基础全覆盖套装</h4>
<p><strong>工具配置：</strong></p>
<ul>
<li><strong>TradingView 免费版</strong> (图表分析)</li>
<li><strong>Yahoo Finance</strong> (基础数据和新闻)</li>
<li><strong>FinViz 免费版</strong> (股票筛选和市场概览)</li>
<li><strong>Stocktwits</strong> (社交情绪)</li>
<li><strong>Seeking Alpha 免费版</strong> (基础研究)</li>
</ul>
<p><strong>总成本：</strong> $0/年</p>
<p><strong>优势：</strong></p>
<ul>
<li>覆盖分析链条的各个环节</li>
<li>完全免费，无财务压力</li>
<li>可以充分学习和熟悉各类工具</li>
</ul>
<p><strong>局限性：</strong></p>
<ul>
<li>数据延迟，不适合日内交易</li>
<li>功能受限，深度分析能力不足</li>
<li>广告干扰，用户体验一般</li>
</ul>
<h4>推荐组合二：精选验证套装</h4>
<p><strong>工具配置：</strong></p>
<ul>
<li><strong>TradingView 免费版</strong> </li>
<li><strong>MarketBeat All Access</strong> ($39.99/月，可按月试用)</li>
<li><strong>FinViz 免费版</strong></li>
<li><strong>Reddit 追踪工具</strong> (SwaggyStocks 免费版)</li>
</ul>
<p><strong>总成本：</strong> 约 $480/年 (可根据需要按月调整)</p>
<p><strong>优势：</strong></p>
<ul>
<li>引入一个付费工具验证价值</li>
<li>MarketBeat 提供实时警报和分析师追踪</li>
<li>灵活的订阅方式，可随时调整</li>
</ul>
<p><strong>使用建议：</strong></p>
<ul>
<li>前3个月重点验证 MarketBeat 的价值</li>
<li>如果效果好，可考虑升级到下一级别组合</li>
<li>如果价值有限，可退回到免费组合</li>
</ul>
<h3>7.2 活跃交易者组合 (年预算 $500 - $2,500)</h3>
<p><strong>目标用户：</strong> 有一定经验的活跃交易者、兼职投资者、小型投资顾问。</p>
<p><strong>核心原则：</strong> 性价比优先，功能互补，提升效率。</p>
<h4>推荐组合一：技术+基本面 均衡型</h4>
<p><strong>工具配置：</strong></p>
<ul>
<li><strong>TradingView Plus</strong> ($179/年) - 核心图表分析</li>
<li><strong>FinViz Elite</strong> ($299.50/年) - 实时筛选和市场监控</li>
<li><strong>Seeking Alpha Premium</strong> ($299/年) - 深度基本面研究</li>
<li><strong>MarketBeat All Access</strong> ($479.88/年) - 实时新闻和警报</li>
</ul>
<p><strong>总成本：</strong> $1,257.38/年</p>
<p><strong>优势：</strong></p>
<ul>
<li>覆盖技术分析、筛选、基本面研究、实时资讯四大核心需求</li>
<li>每个工具都是其类别中的性价比之选</li>
<li>功能互补性强，避免重复付费</li>
</ul>
<p><strong>适用场景：</strong></p>
<ul>
<li>中长期投资为主，偶尔进行短线交易</li>
<li>需要平衡技术分析和基本面研究</li>
<li>重视投资决策的全面性和深度</li>
</ul>
<h4>推荐组合二：短线交易 速度型</h4>
<p><strong>工具配置：</strong></p>
<ul>
<li><strong>TradingView Premium</strong> ($359/年) - 高级图表和警报</li>
<li><strong>FinViz Elite</strong> ($299.50/年) - 实时筛选</li>
<li><strong>Benzinga Pro Essential</strong> ($2,364/年) - 超快新闻和信号</li>
<li><strong>Unusual Whales 基础版</strong> ($432/年) - 期权流监控</li>
</ul>
<p><strong>总成本：</strong> $3,454.50/年 (略超预算，但价值明显)</p>
<p><strong>优势：</strong></p>
<ul>
<li>重点关注实时性和交易信号</li>
<li>新闻速度和期权流数据具有明显优势</li>
<li>适合日内和短线交易策略</li>
</ul>
<p><strong>适用场景：</strong></p>
<ul>
<li>以日内和短线交易为主</li>
<li>对信息时效性要求极高</li>
<li>有经验的活跃交易者</li>
</ul>
<h4>推荐组合三：价值投资 深度型</h4>
<p><strong>工具配置：</strong></p>
<ul>
<li><strong>TradingView Plus</strong> ($179/年)</li>
<li><strong>Seeking Alpha Premium</strong> ($299/年)</li>
<li><strong>TipRanks Premium</strong> ($419.40/年)</li>
<li><strong>Quiver Quantitative</strong> (~$300/年)</li>
</ul>
<p><strong>总成本：</strong> $1,197.40/年</p>
<p><strong>优势：</strong></p>
<ul>
<li>专注于深度研究和长期价值发现</li>
<li>提供量化评级和另类数据洞察</li>
<li>成本控制较好，信息密度高</li>
</ul>
<p><strong>适用场景：</strong></p>
<ul>
<li>价值投资和长期持有策略</li>
<li>重视基本面分析和量化指标</li>
<li>关注政策和宏观因素影响</li>
</ul>
<h3>7.3 专业级组合 (年预算 $2,500 - $10,000)</h3>
<p><strong>目标用户：</strong> 专业交易员、小型基金经理、高净值个人投资者。</p>
<p><strong>核心原则：</strong> 专业工具，全面覆盖，效率最大化。</p>
<h4>推荐组合一：全能专业型</h4>
<p><strong>工具配置：</strong></p>
<ul>
<li><strong>TradingView Premium</strong> ($359/年)</li>
<li><strong>FinViz Elite</strong> ($299.50/年)</li>
<li><strong>Seeking Alpha Alpha Picks</strong> (~$2,000/年)</li>
<li><strong>Benzinga Pro Professional</strong> ($3,564/年)</li>
<li><strong>Unusual Whales 专业版</strong> ($900/年)</li>
<li><strong>TipRanks Ultimate</strong> ($959.40/年)</li>
</ul>
<p><strong>总成本：</strong> $8,081.90/年</p>
<p><strong>优势：</strong></p>
<ul>
<li>每个工具类别都选择了最高级版本</li>
<li>覆盖从技术分析到另类数据的全谱系</li>
<li>信息获取速度和深度都达到专业水平</li>
</ul>
<h4>推荐组合二：机构模拟型</h4>
<p><strong>工具配置：</strong></p>
<ul>
<li><strong>TradingView Premium</strong> ($359/年)</li>
<li><strong>Benzinga Pro Premium</strong> ($4,764/年)</li>
<li><strong>Seeking Alpha Alpha Picks</strong> (~$2,000/年)</li>
<li><strong>专业级数据终端</strong> (如FactSet入门版，~$12,000/年)</li>
</ul>
<p><strong>总成本：</strong> $19,123/年 (需要更高预算)</p>
<p><strong>优势：</strong></p>
<ul>
<li>接近小型机构的信息配置水平</li>
<li>专业数据终端提供机构级数据质量</li>
<li>能够支持更复杂的投资策略</li>
</ul>
<h3>7.4 机构级组合 (年预算 &gt; $20,000)</h3>
<p><strong>目标用户：</strong> 对冲基金、资产管理公司、投资银行、大型家族办公室。</p>
<p><strong>核心原则：</strong> 顶级工具，数据权威，合规支持。</p>
<h4>核心配置选项</h4>
<p><strong>选项一：Bloomberg 主导型</strong></p>
<ul>
<li><strong>Bloomberg Terminal</strong> ($24,000/年/座位)</li>
<li><strong>专业新闻服务</strong> (如Dow Jones Newswires)</li>
<li><strong>专项数据服务</strong> (根据策略需求定制)</li>
</ul>
<p><strong>选项二：LSEG 主导型</strong></p>
<ul>
<li><strong>LSEG Workspace</strong> (~$20,000/年/座位)</li>
<li><strong>Benzinga Pro</strong> (增强实时信号)</li>
<li><strong>专业另类数据服务</strong></li>
</ul>
<p><strong>选项三：多工具整合型</strong></p>
<ul>
<li><strong>综合数据平台</strong> (FactSet, S&amp;P Capital IQ)</li>
<li><strong>交易执行系统</strong></li>
<li><strong>风险管理工具</strong></li>
<li><strong>研究管理系统</strong></li>
</ul>
<h3>7.5 特殊策略导向的工具组合</h3>
<h4>7.5.1 期权交易专门组合</h4>
<p><strong>核心工具：</strong></p>
<ul>
<li><strong>TradingView Premium</strong> (图表分析)</li>
<li><strong>Unusual Whales 专业版</strong> (期权流分析)</li>
<li><strong>FlowAlgo</strong> (实时订单流)</li>
<li><strong>BlackBoxStocks</strong> (期权信号)</li>
</ul>
<p><strong>年成本：</strong> $4,000-6,000</p>
<h4>7.5.2 宏观策略组合</h4>
<p><strong>核心工具：</strong></p>
<ul>
<li><strong>Bloomberg Terminal</strong> 或 <strong>LSEG Workspace</strong></li>
<li><strong>Quiver Quantitative</strong> (政策数据)</li>
<li><strong>专业经济数据服务</strong></li>
<li><strong>货币和商品数据终端</strong></li>
</ul>
<p><strong>年成本：</strong> $25,000-50,000</p>
<h4>7.5.3 ESG投资组合</h4>
<p><strong>核心工具：</strong></p>
<ul>
<li><strong>MSCI ESG数据服务</strong></li>
<li><strong>Sustainalytics</strong></li>
<li><strong>标准ESG研究平台</strong></li>
<li><strong>基础技术分析工具</strong></li>
</ul>
<p><strong>年成本：</strong> $5,000-15,000</p>
<h3>7.6 组合优化的核心原则</h3>
<p><strong>功能互补原则：</strong></p>
<ul>
<li>避免功能重叠的工具同时购买</li>
<li>确保覆盖分析链条的关键环节</li>
<li>考虑工具间的数据共享和集成可能性</li>
</ul>
<p><strong>成本效益分析：</strong></p>
<ul>
<li>定期评估每个工具的使用频率和价值贡献</li>
<li>考虑工具的学习成本和时间投入</li>
<li>与投资回报进行比较，确保工具投资的合理性</li>
</ul>
<p><strong>渐进升级策略：</strong></p>
<ul>
<li>从低成本工具开始，验证使用习惯和价值</li>
<li>根据投资规模和专业需求逐步升级</li>
<li>保持灵活性，随时调整工具配置</li>
</ul>
<p><strong>风险分散考虑：</strong></p>
<ul>
<li>避免过度依赖单一信息源</li>
<li>保持多个数据渠道的交叉验证能力</li>
<li>考虑工具提供商的稳定性和可靠性</li>
</ul>
<p>这些组合建议基于对各工具深度分析的结果，旨在为不同层次的投资者提供最具性价比的解决方案。投资者应根据自身的具体需求、预算限制和投资风格，选择最适合的工具组合，并保持定期评估和优化的习惯。## 8. 工具选择决策框架</p>
<p>选择合适的交易分析工具是一个复杂的决策过程，涉及多个维度的考量。本章节提供一个系统化的决策框架，帮助投资者做出最适合自身需求的工具选择。</p>
<h3>8.1 需求评估矩阵</h3>
<p>在选择工具之前，投资者首先需要进行全面的自我需求评估。以下矩阵可以帮助系统化地分析个人需求：</p>
<h4>8.1.1 投资风格评估</h4>
<table>
<thead>
<tr>
<th align="left">维度</th>
<th align="left">日内交易</th>
<th align="left">波段交易</th>
<th align="left">长期投资</th>
<th align="left">权重评分</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>数据实时性</strong></td>
<td align="left">极重要 (5)</td>
<td align="left">重要 (4)</td>
<td align="left">一般 (2)</td>
<td align="left">___</td>
</tr>
<tr>
<td align="left"><strong>技术分析深度</strong></td>
<td align="left">极重要 (5)</td>
<td align="left">极重要 (5)</td>
<td align="left">重要 (3)</td>
<td align="left">___</td>
</tr>
<tr>
<td align="left"><strong>基本面分析</strong></td>
<td align="left">一般 (2)</td>
<td align="left">重要 (4)</td>
<td align="left">极重要 (5)</td>
<td align="left">___</td>
</tr>
<tr>
<td align="left"><strong>新闻速度</strong></td>
<td align="left">极重要 (5)</td>
<td align="left">重要 (4)</td>
<td align="left">一般 (2)</td>
<td align="left">___</td>
</tr>
<tr>
<td align="left"><strong>社交情绪</strong></td>
<td align="left">重要 (4)</td>
<td align="left">重要 (4)</td>
<td align="left">一般 (2)</td>
<td align="left">___</td>
</tr>
<tr>
<td align="left"><strong>成本敏感性</strong></td>
<td align="left">低 (2)</td>
<td align="left">中 (3)</td>
<td align="left">高 (4)</td>
<td align="left">___</td>
</tr>
</tbody></table>
<p><strong>使用方法：</strong> 根据自己的主要投资风格，为每个维度打分，然后计算加权总分，以确定优先级排序。</p>
<h4>8.1.2 资源约束评估</h4>
<p><strong>预算约束分析：</strong></p>
<ul>
<li>年度工具预算上限：$_______</li>
<li>月度可承受支出：$_______</li>
<li>学习新工具的时间投入：___ 小时/周</li>
<li>技术复杂度承受能力：1-5分 ___</li>
</ul>
<p><strong>基础设施评估：</strong></p>
<ul>
<li>网络带宽和稳定性要求</li>
<li>设备性能和多屏幕支持</li>
<li>数据存储和备份需求</li>
<li>API集成和自动化需求</li>
</ul>
<h3>8.2 工具评估准则</h3>
<h4>8.2.1 核心功能评估 (40% 权重)</h4>
<p><strong>数据质量 (15%)：</strong></p>
<ul>
<li>数据来源的权威性和可靠性</li>
<li>数据更新频率和实时性</li>
<li>历史数据的深度和准确性</li>
<li>数据覆盖的广度（市场、资产类别）</li>
</ul>
<p><strong>分析功能 (15%)：</strong></p>
<ul>
<li>技术指标的丰富程度和准确性</li>
<li>筛选功能的灵活性和效率</li>
<li>分析工具的专业性和深度</li>
<li>自定义和扩展能力</li>
</ul>
<p><strong>易用性 (10%)：</strong></p>
<ul>
<li>界面设计的直观性和美观性</li>
<li>学习曲线的陡峭程度</li>
<li>操作效率和响应速度</li>
<li>移动端支持和同步能力</li>
</ul>
<h4>8.2.2 成本效益分析 (25% 权重)</h4>
<p><strong>直接成本分析：</strong></p>
<ul>
<li>订阅费用与功能匹配度</li>
<li>隐藏费用（数据费、超量费等）</li>
<li>取消和退款政策的灵活性</li>
<li>多用户和团队折扣</li>
</ul>
<p><strong>机会成本考虑：</strong></p>
<ul>
<li>学习和适应新工具的时间成本</li>
<li>从现有工具迁移的转换成本</li>
<li>错失交易机会的潜在损失</li>
<li>工具组合的协同效应</li>
</ul>
<h4>8.2.3 技术架构 (20% 权重)</h4>
<p><strong>稳定性和可靠性：</strong></p>
<ul>
<li>系统正常运行时间记录</li>
<li>数据准确性和一致性</li>
<li>客户服务响应质量</li>
<li>备份和灾难恢复能力</li>
</ul>
<p><strong>集成和扩展性：</strong></p>
<ul>
<li>API可用性和文档质量</li>
<li>与其他工具的集成能力</li>
<li>数据导出和导入功能</li>
<li>自动化和脚本支持</li>
</ul>
<h4>8.2.4 供应商因素 (15% 权重)</h4>
<p><strong>公司实力和信誉：</strong></p>
<ul>
<li>公司历史和财务稳定性</li>
<li>市场地位和用户基础</li>
<li>创新能力和产品路线图</li>
<li>监管合规和数据安全</li>
</ul>
<h3>8.3 决策流程模型</h3>
<h4>8.3.1 五步决策流程</h4>
<p><strong>第一步：需求明确化</strong></p>
<ol>
<li>明确投资目标和策略</li>
<li>确定主要使用场景</li>
<li>评估资源约束条件</li>
<li>列出必需功能清单</li>
</ol>
<p><strong>第二步：工具初筛</strong></p>
<ol>
<li>基于预算进行初步筛选</li>
<li>根据核心功能需求过滤</li>
<li>考虑技术兼容性要求</li>
<li>形成候选工具短名单 (3-5个)</li>
</ol>
<p><strong>第三步：深度评估</strong></p>
<ol>
<li>申请试用账户进行实测</li>
<li>对照评估准则逐一打分</li>
<li>计算综合评分和排名</li>
<li>识别关键优劣势</li>
</ol>
<p><strong>第四步：决策验证</strong></p>
<ol>
<li>与其他用户交流使用体验</li>
<li>查阅第三方评测和比较</li>
<li>考虑长期发展和升级路径</li>
<li>评估退出和替换的难易程度</li>
</ol>
<p><strong>第五步：实施和优化</strong></p>
<ol>
<li>制定实施和学习计划</li>
<li>设置关键绩效指标 (KPI)</li>
<li>定期评估使用效果</li>
<li>根据需要调整和优化</li>
</ol>
<h4>8.3.2 决策矩阵示例</h4>
<p>以一个活跃交易者选择技术分析工具为例：</p>
<table>
<thead>
<tr>
<th align="left">工具</th>
<th align="left">数据质量 (30%)</th>
<th align="left">分析功能 (25%)</th>
<th align="left">易用性 (20%)</th>
<th align="left">成本效益 (15%)</th>
<th align="left">技术架构 (10%)</th>
<th align="left">总分</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>TradingView Premium</strong></td>
<td align="left">9 × 0.3 = 2.7</td>
<td align="left">10 × 0.25 = 2.5</td>
<td align="left">9 × 0.2 = 1.8</td>
<td align="left">8 × 0.15 = 1.2</td>
<td align="left">9 × 0.1 = 0.9</td>
<td align="left"><strong>9.1</strong></td>
</tr>
<tr>
<td align="left"><strong>StockCharts Pro</strong></td>
<td align="left">8 × 0.3 = 2.4</td>
<td align="left">9 × 0.25 = 2.25</td>
<td align="left">7 × 0.2 = 1.4</td>
<td align="left">7 × 0.15 = 1.05</td>
<td align="left">8 × 0.1 = 0.8</td>
<td align="left"><strong>7.9</strong></td>
</tr>
<tr>
<td align="left"><strong>FinViz Elite</strong></td>
<td align="left">7 × 0.3 = 2.1</td>
<td align="left">6 × 0.25 = 1.5</td>
<td align="left">10 × 0.2 = 2.0</td>
<td align="left">9 × 0.15 = 1.35</td>
<td align="left">7 × 0.1 = 0.7</td>
<td align="left"><strong>7.65</strong></td>
</tr>
</tbody></table>
<p><em>注：评分为1-10分，权重根据个人需求调整</em></p>
<h3>8.4 常见决策陷阱与避免策略</h3>
<h4>8.4.1 常见陷阱</h4>
<p><strong>功能过度陷阱：</strong></p>
<ul>
<li>追求功能全面而忽视实际使用需求</li>
<li>为很少使用的高级功能支付高额费用</li>
<li>复杂工具导致学习成本过高</li>
</ul>
<p><strong>避免策略：</strong></p>
<ul>
<li>严格按照实际需求评估功能价值</li>
<li>优先选择核心功能强大的工具</li>
<li>考虑从简单工具开始逐步升级</li>
</ul>
<p><strong>成本忽视陷阱：</strong></p>
<ul>
<li>只关注订阅费用，忽视隐藏成本</li>
<li>未考虑学习和适应的时间成本</li>
<li>忽视工具组合的总成本控制</li>
</ul>
<p><strong>避免策略：</strong></p>
<ul>
<li>进行全生命周期成本分析</li>
<li>制定明确的工具预算上限</li>
<li>定期评估工具的实际价值贡献</li>
</ul>
<p><strong>短视决策陷阱：</strong></p>
<ul>
<li>只考虑当前需求，忽视未来发展</li>
<li>频繁更换工具，导致效率损失</li>
<li>缺乏长期工具策略规划</li>
</ul>
<p><strong>避免策略：</strong></p>
<ul>
<li>考虑3-5年的投资发展规划</li>
<li>选择有升级路径的工具</li>
<li>重视工具的可扩展性和适应性</li>
</ul>
<h4>8.4.2 决策优化建议</h4>
<p><strong>试用最大化原则：</strong></p>
<ul>
<li>充分利用免费试用期</li>
<li>在实际交易环境中测试工具</li>
<li>记录试用期间的具体体验和发现</li>
</ul>
<p><strong>社区验证原则：</strong></p>
<ul>
<li>参考真实用户的评价和建议</li>
<li>加入相关的用户社区和论坛</li>
<li>关注工具的用户满意度调查结果</li>
</ul>
<p><strong>渐进优化原则：</strong></p>
<ul>
<li>从基础工具开始，逐步升级</li>
<li>保持工具配置的灵活性</li>
<li>定期重新评估和调整工具组合</li>
</ul>
<h3>8.5 决策框架应用实例</h3>
<h4>实例：中等预算的波段交易者工具选择</h4>
<p><strong>背景：</strong></p>
<ul>
<li>投资经验：2年</li>
<li>主要策略：技术分析为主的波段交易</li>
<li>年预算：$1,500</li>
<li>主要市场：美股</li>
</ul>
<p><strong>第一步：需求评估</strong></p>
<ul>
<li>核心需求：高质量图表、股票筛选、实时数据</li>
<li>次要需求：基本面数据、新闻警报</li>
<li>预算约束：月度预算 $125</li>
</ul>
<p><strong>第二步：工具初筛</strong>
候选方案：</p>
<ol>
<li>TradingView Plus + FinViz Elite</li>
<li>StockCharts Pro + MarketBeat</li>
<li>TradingView Premium + 免费工具组合</li>
</ol>
<p><strong>第三步：决策矩阵分析</strong>
经过评分，TradingView Plus + FinViz Elite 组合获得最高分。</p>
<p><strong>第四步：验证和决策</strong></p>
<ul>
<li>试用验证：两周试用期确认了组合的实用性</li>
<li>成本分析：年费用 $479，在预算范围内</li>
<li>长期考虑：提供清晰的升级路径</li>
</ul>
<p><strong>第五步：实施效果</strong></p>
<ul>
<li>第一季度：显著提升了筛选和分析效率</li>
<li>半年评估：工具投资带来了明显的交易改善</li>
<li>年度总结：计划升级到 TradingView Premium</li>
</ul>
<p>这个决策框架提供了系统化的工具选择方法，帮助投资者在复杂的工具生态中做出最优决策。关键是要结合个人实际需求，进行客观理性的分析，避免被营销宣传或他人偏好所误导。## 9. 未来发展趋势与展望</p>
<p>美股交易分析工具行业正在经历快速的技术变革和创新。了解这些发展趋势对于投资者制定长期工具策略、提前布局新技术具有重要意义。本章节将分析当前最重要的技术趋势和未来发展方向。</p>
<h3>9.1 人工智能与机器学习的深度整合</h3>
<h4>9.1.1 智能信号生成</h4>
<p><strong>当前发展：</strong></p>
<ul>
<li>基于机器学习的异常检测算法</li>
<li>自然语言处理驱动的新闻情绪分析</li>
<li>深度学习模型的价格预测系统</li>
</ul>
<p><strong>未来趋势：</strong></p>
<ul>
<li><strong>个性化AI助手：</strong> 基于用户交易行为和偏好的智能推荐系统</li>
<li><strong>多模态分析：</strong> 整合文本、图像、音频数据的综合分析模型</li>
<li><strong>实时学习算法：</strong> 能够根据市场变化自动调整参数的自适应系统</li>
</ul>
<p><strong>投资者影响：</strong></p>
<ul>
<li>信号质量的显著提升</li>
<li>个性化体验的大幅改善</li>
<li>对人工分析依赖的减少</li>
</ul>
<h4>9.1.2 自动化交易决策支持</h4>
<p><strong>技术发展方向：</strong></p>
<ul>
<li>风险管理的智能化自动化</li>
<li>投资组合优化的实时算法</li>
<li>基于AI的交易执行优化</li>
</ul>
<p><strong>预期影响：</strong></p>
<ul>
<li>降低情绪化交易的风险</li>
<li>提高交易执行的精确性</li>
<li>使复杂策略的实施变得简单化</li>
</ul>
<h3>9.2 另类数据的主流化应用</h3>
<h4>9.2.1 数据源的多样化</h4>
<p><strong>新兴数据类型：</strong></p>
<ul>
<li><strong>卫星图像数据：</strong> 用于分析零售客流、工厂活动、商品库存</li>
<li><strong>信用卡消费数据：</strong> 实时反映消费趋势和企业业绩</li>
<li><strong>社交媒体语义分析：</strong> 更深层的情绪和意图挖掘</li>
<li><strong>供应链数据：</strong> 物流、运输、原材料价格的实时追踪</li>
</ul>
<p><strong>技术进步：</strong></p>
<ul>
<li>数据采集技术的成本下降</li>
<li>数据处理能力的大幅提升</li>
<li>数据隐私保护技术的发展</li>
</ul>
<h4>9.2.2 另类数据的民主化</h4>
<p><strong>当前问题：</strong></p>
<ul>
<li>另类数据主要服务于大型机构</li>
<li>成本高昂，个人投资者难以接触</li>
<li>数据解读需要专业知识</li>
</ul>
<p><strong>发展趋势：</strong></p>
<ul>
<li><strong>数据即服务 (DaaS) 模式：</strong> 降低另类数据的使用门槛</li>
<li><strong>预处理数据产品：</strong> 提供经过分析和解读的数据洞察</li>
<li><strong>众包数据验证：</strong> 通过社区协作提高数据质量</li>
</ul>
<h3>9.3 实时性和低延迟技术</h3>
<h4>9.3.1 极低延迟数据传输</h4>
<p><strong>技术发展：</strong></p>
<ul>
<li><strong>5G和光纤技术：</strong> 显著降低数据传输延迟</li>
<li><strong>边缘计算：</strong> 将计算能力部署到更接近数据源的位置</li>
<li><strong>专用网络：</strong> 金融机构建设专用的低延迟数据网络</li>
</ul>
<p><strong>应用前景：</strong></p>
<ul>
<li>毫秒级的新闻和数据推送</li>
<li>实时的订单流分析</li>
<li>超高频交易技术的普及化</li>
</ul>
<h4>9.3.2 云原生架构</h4>
<p><strong>技术优势：</strong></p>
<ul>
<li>弹性计算资源分配</li>
<li>全球化数据同步</li>
<li>成本效益的显著提升</li>
</ul>
<p><strong>对工具的影响：</strong></p>
<ul>
<li>工具访问的设备无关性</li>
<li>数据处理能力的大幅提升</li>
<li>协作功能的增强</li>
</ul>
<h3>9.4 区块链和去中心化技术</h3>
<h4>9.4.1 数据透明度和可验证性</h4>
<p><strong>应用领域：</strong></p>
<ul>
<li><strong>交易数据的不可篡改记录：</strong> 提供更可靠的历史数据</li>
<li><strong>分析师评级的透明追踪：</strong> 建立基于历史表现的信誉系统</li>
<li><strong>众包研究的激励机制：</strong> 通过代币经济激励高质量分析</li>
</ul>
<h4>9.4.2 去中心化分析平台</h4>
<p><strong>发展方向：</strong></p>
<ul>
<li>社区驱动的分析工具开发</li>
<li>去中心化的数据聚合和验证</li>
<li>基于智能合约的自动化投资策略</li>
</ul>
<h3>9.5 增强现实(AR)和虚拟现实(VR)</h3>
<h4>9.5.1 沉浸式数据可视化</h4>
<p><strong>技术应用：</strong></p>
<ul>
<li><strong>3D市场地图：</strong> 将复杂的市场关系以立体形式展现</li>
<li><strong>虚拟交易环境：</strong> 创建沉浸式的交易和分析空间</li>
<li><strong>多维数据交互：</strong> 通过手势和语音进行直观的数据操作</li>
</ul>
<h4>9.5.2 远程协作和教育</h4>
<p><strong>应用场景：</strong></p>
<ul>
<li>虚拟交易大厅和团队协作</li>
<li>沉浸式的投资教育和培训</li>
<li>实时的专家指导和咨询</li>
</ul>
<h3>9.6 监管科技(RegTech)的发展</h3>
<h4>9.6.1 合规自动化</h4>
<p><strong>发展方向：</strong></p>
<ul>
<li>自动化的合规监控和报告</li>
<li>智能风险管理系统</li>
<li>实时的监管要求更新</li>
</ul>
<p><strong>对工具的影响：</strong></p>
<ul>
<li>内置合规功能成为标配</li>
<li>风险管理工具的智能化</li>
<li>跨境投资的合规简化</li>
</ul>
<h3>9.7 工具生态的未来格局</h3>
<h4>9.7.1 平台化趋势</h4>
<p><strong>发展特征：</strong></p>
<ul>
<li><strong>一站式解决方案：</strong> 综合平台将整合更多功能</li>
<li><strong>开放API生态：</strong> 鼓励第三方开发者参与创新</li>
<li><strong>模块化架构：</strong> 用户可以根据需求定制功能组合</li>
</ul>
<h4>9.7.2 个性化和定制化</h4>
<p><strong>技术实现：</strong></p>
<ul>
<li>AI驱动的个性化界面</li>
<li>基于行为分析的功能推荐</li>
<li>自适应的用户体验优化</li>
</ul>
<h4>9.7.3 成本结构的变化</h4>
<p><strong>趋势预测：</strong></p>
<ul>
<li><strong>免费增值模式的普及：</strong> 基础功能免费，高级功能付费</li>
<li><strong>按使用付费模式：</strong> 根据实际使用量和价值收费</li>
<li><strong>订阅模式的细分化：</strong> 更灵活的订阅选项和定价策略</li>
</ul>
<h3>9.8 投资者应对策略</h3>
<h4>9.8.1 技术适应策略</h4>
<p><strong>核心原则：</strong></p>
<ul>
<li><strong>保持学习心态：</strong> 持续关注新技术和新工具的发展</li>
<li><strong>渐进式采用：</strong> 避免盲目追求最新技术，注重实用性</li>
<li><strong>技能投资：</strong> 提升数据分析和技术理解能力</li>
</ul>
<h4>9.8.2 工具选择策略</h4>
<p><strong>长期考虑：</strong></p>
<ul>
<li>选择具有强大技术路线图的平台</li>
<li>重视工具的可扩展性和适应性</li>
<li>建立灵活的工具组合，便于调整和升级</li>
</ul>
<h4>9.8.3 风险管理</h4>
<p><strong>关键风险：</strong></p>
<ul>
<li><strong>技术依赖风险：</strong> 避免过度依赖单一技术或平台</li>
<li><strong>数据质量风险：</strong> 建立多源数据验证机制</li>
<li><strong>隐私和安全风险：</strong> 重视数据保护和账户安全</li>
</ul>
<h3>9.9 行业发展预测</h3>
<h4>9.9.1 短期发展 (1-2年)</h4>
<ul>
<li>AI功能在主流工具中的广泛应用</li>
<li>另类数据产品的成本下降和普及</li>
<li>移动端分析能力的显著提升</li>
<li>实时协作功能的标准化</li>
</ul>
<h4>9.9.2 中期发展 (3-5年)</h4>
<ul>
<li>完全个性化的AI投资助手</li>
<li>AR/VR技术在专业工具中的应用</li>
<li>区块链技术的实质性整合</li>
<li>全球监管框架的逐步统一</li>
</ul>
<h4>9.9.3 长期愿景 (5-10年)</h4>
<ul>
<li>智能化程度接近人类分析师的AI系统</li>
<li>完全去中心化的投资分析生态</li>
<li>量子计算在金融分析中的应用</li>
<li>全自动化的投资决策系统</li>
</ul>
<p>美股交易分析工具行业正站在技术革命的前沿。这些发展趋势将重新定义投资者获取信息、分析市场和执行交易的方式。投资者需要保持开放的心态，积极拥抱新技术，同时保持理性判断，确保技术创新真正服务于投资目标的实现。</p>
<h2>10. 结论</h2>
<p>本指南通过对美股交易分析工具生态的全面调研和深度分析，为不同层次的投资者提供了系统性的工具选择指导。在信息化高度发达的现代金融市场中，正确的工具选择已成为投资成功的关键要素之一。</p>
<h3>10.1 核心发现总结</h3>
<p><strong>工具生态的多样化成熟：</strong>
美股交易分析工具已形成了从免费到机构级、从基础到专业的完整生态系统。无论是预算有限的新手投资者，还是管理巨额资产的机构，都能在这个生态中找到适合的解决方案。</p>
<p><strong>专业化分工的深度发展：</strong>
各类工具在专业化道路上不断深耕，形成了明确的功能定位和竞争优势。技术图表分析、基本面研究、社交情绪跟踪、异常交易监控等各个细分领域都有了专业的解决方案。</p>
<p><strong>技术创新的持续推动：</strong>
人工智能、大数据、云计算等新技术正在深刻改变工具的功能和体验。从简单的数据展示到智能化的分析建议，工具的价值正在不断提升。</p>
<p><strong>成本效益的显著改善：</strong>
随着技术进步和竞争加剧，高质量分析工具的成本壁垒正在降低。许多曾经只有机构才能负担的功能，现在个人投资者也可以以合理的价格获得。</p>
<h3>10.2 最佳实践建议</h3>
<p><strong>基于需求的理性选择：</strong>
投资者应该根据自身的投资风格、预算约束和专业水平来选择工具，避免盲目追求功能全面或最新技术。最适合的工具往往是最符合实际需求的工具。</p>
<p><strong>组合优于单一：</strong>
没有任何单一工具能够满足所有分析需求。通过合理的工具组合，能够实现功能互补、成本优化和风险分散。本指南提供的组合建议可以作为选择的起点。</p>
<p><strong>渐进式升级策略：</strong>
建议投资者从基础工具开始，随着经验积累和资产规模增长逐步升级。这种策略既能控制成本，又能确保工具投资与投资能力相匹配。</p>
<p><strong>持续学习和优化：</strong>
工具技术在快速发展，市场环境在不断变化，投资者需要保持学习心态，定期评估和调整自己的工具配置，确保始终保持竞争优势。</p>
<h3>10.3 价值创造的关键</h3>
<p><strong>信息优势的建立：</strong>
在信息驱动的现代市场中，获得高质量、及时、独特的信息是创造投资回报的重要源泉。合适的分析工具能够帮助投资者建立这种信息优势。</p>
<p><strong>决策效率的提升：</strong>
专业的分析工具能够显著提高投资决策的效率和质量，减少情绪化决策，提高投资纪律性。这种效率提升往往能够转化为实际的投资回报。</p>
<p><strong>风险管理的强化：</strong>
现代分析工具不仅提供投资机会的发现，更重要的是提供风险识别和管理功能。在波动激烈的市场中，风险管理往往比收益追求更为重要。</p>
<h3>10.4 未来展望</h3>
<p><strong>技术驱动的变革：</strong>
人工智能、区块链、AR/VR等新技术将继续推动分析工具的演进。投资者需要保持对新技术的关注，但也要理性评估其实际价值。</p>
<p><strong>个性化服务的普及：</strong>
随着AI技术的发展，个性化的投资分析服务将成为主流。每个投资者都将拥有定制化的分析助手和决策支持系统。</p>
<p><strong>民主化趋势的加速：</strong>
高端分析工具的平民化趋势将继续，更多原本只有机构才能使用的工具和数据将变得触手可及。这将进一步平衡个人投资者与机构投资者之间的信息差距。</p>
<h3>10.5 最终建议</h3>
<p>对于美股投资者而言，工具只是实现投资目标的手段，而不是目标本身。最重要的仍然是：</p>
<ol>
<li><strong>建立正确的投资理念和策略</strong></li>
<li><strong>保持严格的投资纪律</strong></li>
<li><strong>持续学习和适应市场变化</strong></li>
<li><strong>合理控制风险和情绪</strong></li>
</ol>
<p>在这个基础上，合适的分析工具能够成为投资成功的重要助力。本指南希望能够帮助投资者在复杂的工具生态中找到最适合的解决方案，在美股投资的道路上取得更好的成果。</p>
<p>投资分析工具的选择是一个持续的过程，而不是一次性的决定。随着个人投资经验的积累、资产规模的变化和市场环境的演进，工具配置也需要相应调整。保持开放的心态、理性的判断和持续的学习，是在这个快速变化的领域中保持竞争优势的关键。</p>
<p>最后，提醒所有投资者：无论多么先进的工具，都无法替代基本的投资常识和风险意识。工具可以提供信息和分析，但最终的投资决策和责任仍然在投资者自己手中。明智地使用这些工具，让它们成为您投资成功的助手，而不是依赖的拐杖。## 参考文献</p>
<p>本报告基于对105个权威来源的深度研究和分析，涵盖了主要工具官方网站、第三方评测机构、学术研究论文和专业投资社区的观点。以下为主要参考源：</p>
<h3>工具官方资源</h3>
<p><strong>技术图表分析工具:</strong></p>
<ul>
<li>[1] <a href="https://www.tradingview.com/pricing/">TradingView定价计划</a> - TradingView - <strong>高可靠性</strong> - 官方定价信息，包含功能对比矩阵</li>
<li>[2] <a href="https://finviz.com/elite">FinViz Elite订阅服务</a> - FinViz - <strong>高可靠性</strong> - 官方产品页面，详述实时数据和筛选功能</li>
<li>[3] <a href="https://stockcharts.com/pricing/">StockCharts定价结构</a> - StockCharts - <strong>高可靠性</strong> - 官方定价和功能级别说明</li>
</ul>
<p><strong>基本面分析工具:</strong></p>
<ul>
<li>[4] <a href="https://seekingalpha.com/subscriptions/premium">Seeking Alpha Premium订阅</a> - Seeking Alpha - <strong>高可靠性</strong> - 官方订阅服务介绍</li>
<li>[5] <a href="https://www.marketbeat.com/subscribe/all-access/">MarketBeat All Access订阅</a> - MarketBeat - <strong>高可靠性</strong> - 官方产品功能和定价</li>
<li>[6] <a href="https://www.tipranks.com/">TipRanks智能评分系统</a> - TipRanks - <strong>高可靠性</strong> - 官方平台功能介绍</li>
</ul>
<p><strong>社交交易和情绪工具:</strong></p>
<ul>
<li>[7] <a href="https://stocktwits.com/">Stocktwits社交交易平台</a> - Stocktwits - <strong>高可靠性</strong> - 官方平台，1000万+用户的股票社交网络</li>
<li>[8] <a href="https://www.quiverquant.com/">Quiver Quantitative另类数据</a> - Quiver Quantitative - <strong>高可靠性</strong> - 官方平台，追踪政府合同和国会交易</li>
</ul>
<p><strong>异常交易和期权工具:</strong></p>
<ul>
<li>[9] <a href="https://unusualwhales.com/">Unusual Whales期权流分析</a> - Unusual Whales - <strong>高可靠性</strong> - 官方产品页面，期权流分析工具</li>
<li>[10] <a href="https://flowalgo.com/">FlowAlgo实时订单流</a> - FlowAlgo - <strong>高可靠性</strong> - 官方智能订单流追踪平台</li>
<li>[11] <a href="https://blackboxstocks.com/">BlackBoxStocks期权扫描</a> - BlackBoxStocks - <strong>高可靠性</strong> - 官方期权交易警报平台</li>
</ul>
<p><strong>综合分析平台:</strong></p>
<ul>
<li>[12] <a href="https://www.bloomberg.com/professional/products/bloomberg-terminal/">Bloomberg Terminal专业服务</a> - Bloomberg - <strong>最高可靠性</strong> - 行业标准金融数据终端</li>
<li>[13] <a href="https://www.lseg.com/en/data-analytics/products/workspace">LSEG Workspace</a> - LSEG - <strong>最高可靠性</strong> - 全球领先金融数据平台</li>
<li>[14] <a href="https://www.benzinga.com/pro/">Benzinga Pro实时新闻</a> - Benzinga - <strong>高可靠性</strong> - 官方实时交易新闻服务</li>
</ul>
<h3>第三方评测和分析</h3>
<p><strong>专业评测机构:</strong></p>
<ul>
<li>[15] <a href="https://www.stockbrokers.com/review/tools/tradingview">2025年最佳交易工具评测</a> - StockBrokers.com - <strong>高可靠性</strong> - 权威券商评测网站</li>
<li>[16] <a href="https://www.forbes.com/advisor/investing/best-investment-apps/">投资应用深度分析</a> - Forbes Advisor - <strong>高可靠性</strong> - 知名财经媒体专业评测</li>
<li>[17] <a href="https://www.nerdwallet.com/best/investing/online-brokers-platforms-for-day-trading">交易平台综合对比</a> - NerdWallet - <strong>高可靠性</strong> - 个人理财权威评测平台</li>
</ul>
<p><strong>行业研究报告:</strong></p>
<ul>
<li>[18] <a href="https://www.wallstreetprep.com/knowledge/bloomberg-vs-capital-iq-vs-factset-vs-thomson-reuters-eikon/">Bloomberg vs竞争对手分析</a> - Wall Street Prep - <strong>高可靠性</strong> - 华尔街专业培训机构分析</li>
<li>[19] <a href="https://optionsscanners.com/best/unusual-options-activity-scanner">期权流分析工具对比</a> - Options Scanners - <strong>中等可靠性</strong> - 专业期权工具评测网站</li>
</ul>
<h3>学术研究和技术分析</h3>
<p><strong>学术论文:</strong></p>
<ul>
<li>[20] <a href="https://www.arxiv.org/pdf/2508.02089">社交媒体情绪预测算法交易策略</a> - arXiv - <strong>高可靠性</strong> - 学术预印本，同行评议研究</li>
<li>[21] <a href="https://www.sciencedirect.com/science/article/pii/S1544612324002575">大语言模型情绪交易研究</a> - ScienceDirect - <strong>最高可靠性</strong> - 同行评议学术期刊</li>
</ul>
<p><strong>监管机构报告:</strong></p>
<ul>
<li>[22] <a href="https://www.finra.org/rules-guidance/key-topics/fintech/report/artificial-intelligence-in-the-securities-industry/ai-apps-in-the-industry">AI在证券行业应用</a> - FINRA - <strong>最高可靠性</strong> - 美国金融监管机构官方报告</li>
</ul>
<h3>用户社区和实践经验</h3>
<p><strong>专业社区讨论:</strong></p>
<ul>
<li>[23] <a href="https://www.reddit.com/r/investing/comments/1gwbtj3/seeking_alpha_worth_it_any_other_research_tool/">投资工具价值讨论</a> - Reddit r/investing - <strong>中等可靠性</strong> - 投资者社区真实使用反馈</li>
<li>[24] <a href="https://www.reddit.com/r/options/comments/1eshkqs/best_orderflow_tool_for_options_flow_and_intra/">期权工具用户评价</a> - Reddit r/options - <strong>中等可靠性</strong> - 期权交易者社区经验分享</li>
</ul>
<h3>市场数据和定价参考</h3>
<p><strong>数据服务商:</strong></p>
<ul>
<li>[25] <a href="https://www.alphavantage.co/">Alpha Vantage免费API</a> - Alpha Vantage - <strong>高可靠性</strong> - 知名金融数据API提供商</li>
<li>[26] <a href="https://twelvedata.com/pricing">Twelve Data定价方案</a> - Twelve Data - <strong>高可靠性</strong> - 专业金融数据服务商</li>
</ul>
<p><strong>专业培训和教育:</strong></p>
<ul>
<li>[27] <a href="https://www.luxalgo.com/blog/latency-standards-in-trading-systems/">交易系统延迟标准</a> - LuxAlgo - <strong>中等可靠性</strong> - 技术分析教育平台</li>
<li>[28] <a href="https://www.confluence.com/impact-of-high-quality-market-data-on-modern-investment-strategy-part-1/">高质量市场数据影响分析</a> - Confluence - <strong>中等可靠性</strong> - 专业投资策略分析</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[Web3 新手入门指南]]></title>
            <link>https://zzfzzf.com/post/1977194868719489024</link>
            <guid>1977194868719489024</guid>
            <pubDate>Mon, 13 Oct 2025 08:31:13 GMT</pubDate>
            <content:encoded><![CDATA[<p>Web3作为互联网下一代演进方向，核心是让用户真正拥有数据主权与数字资产。刚接触的新手不用被复杂概念吓退——掌握几类核心工具，就能顺利踏入这个去中心化世界。我重新梳理了入门逻辑，从基础认知到工具使用，再到实操步骤，帮你系统化踏入Web3世界。</p>
<h2>一、认知</h2>
<p>用工具前先建立基础认知，避免盲目操作。<br>Web3（Web 3.0）是基于区块链技术的去中心化互联网形态，与当前Web2（中心化平台主导，如社交软件、电商）的核心区别有三点：</p>
<ul>
<li><strong>资产归用户</strong>：数字资产（如加密货币、NFT）由个人掌控，而非平台持有。</li>
<li><strong>去中心化架构</strong>：无单一机构垄断数据，交易与交互通过智能合约自动执行。</li>
<li><strong>核心场景</strong>：覆盖去中心化金融（DeFi，如借贷、交易）、非同质化代币（NFT，如数字艺术）、区块链游戏（GameFi）等。</li>
</ul>
<blockquote>
<p>风险提示：Web3涉及资金操作，存在市场波动、诈骗等风险，建议从“小额尝试+学习”开始，不盲目跟风投资。</p>
</blockquote>
<h2>二、工具</h2>
<p>Web3操作围绕“工具链”展开，按使用顺序优先掌握以下4类核心工具，覆盖“身份验证、买币、看盘、安全”全需求。</p>
<h3>2.1 钱包</h3>
<p>钱包是进入Web3的<strong>唯一入口</strong>——它不存储资产（资产在区块链上），而是管理“私钥/助记词”（相当于资产的最高权限密码），用于登录DApp、接收/发送资产。</p>
<h4>热门钱包推荐（新手优先热钱包）</h4>
<table>
<thead>
<tr>
<th align="left">钱包名称</th>
<th align="left">类型</th>
<th align="left">核心优势</th>
<th align="left">适用场景</th>
<th align="left">支持平台</th>
<th align="left">官网链接</th>
</tr>
</thead>
<tbody><tr>
<td align="left">MetaMask（小狐狸）</td>
<td align="left">热钱包（在线）</td>
<td align="left">生态最广，支持以太坊及EVM兼容链，无缝对接DApp</td>
<td align="left">新手入门、常用DApp交互、DeFi用户</td>
<td align="left">Chrome插件/iOS/Android</td>
<td align="left"><a href="https://metamask.io/">https://metamask.io/</a></td>
</tr>
<tr>
<td align="left">Trust Wallet</td>
<td align="left">热钱包（在线）</td>
<td align="left">多链支持全，内置DApp浏览器，币安官方推荐</td>
<td align="left">移动端优先、多链操作</td>
<td align="left">iOS/Android</td>
<td align="left"><a href="https://trustwallet.com/">https://trustwallet.com/</a></td>
</tr>
<tr>
<td align="left">Phantom</td>
<td align="left">热钱包（在线）</td>
<td align="left">Solana生态首选，界面直观，集成NFT与质押功能</td>
<td align="left">Solana链上应用、NFT玩家、频繁交易</td>
<td align="left">浏览器扩展/iOS/Android</td>
<td align="left"><a href="https://phantom.app/">https://phantom.app/</a></td>
</tr>
<tr>
<td align="left">Ledger Nano S/X</td>
<td align="left">冷钱包（离线）</td>
<td align="left">硬件存储私钥，防黑客/钓鱼，安全性最高</td>
<td align="left">大额资产存储、高安全需求、长期囤币</td>
<td align="left">USB连接电脑/手机</td>
<td align="left"><a href="https://www.ledger.com/">https://www.ledger.com/</a></td>
</tr>
</tbody></table>
<h4>新手设置关键步骤</h4>
<ol>
<li>从<strong>官网下载</strong>App/插件，避开应用商店的仿冒软件。</li>
<li>创建“新钱包”，生成12-24个单词的“助记词”——<strong>手写备份在安全处（不存手机/云端），绝不告诉任何人</strong>。</li>
<li>设置钱包密码（用于解锁App，非私钥，忘记可通过助记词找回）。</li>
<li>新手先添加“以太坊链”（生态最成熟，DApp最多），后续再学多链切换。</li>
</ol>
<h3>2.2 交易所</h3>
<p>交易所是用“法定货币（人民币/美元）购买加密货币（如BTC/ETH）”的主要渠道，分两类：中心化交易所（CEX，新手优先）和去中心化交易所（DEX，需先有加密货币）。</p>
<h4>（1）中心化交易所（CEX）：新手首选</h4>
<p>操作类似股票App，支持法币充值，界面友好，需完成KYC（身份验证）。</p>
<table>
<thead>
<tr>
<th>交易所名称</th>
<th>核心优势</th>
<th>适用人群</th>
<th>注意事项</th>
<th>官网链接</th>
</tr>
</thead>
<tbody><tr>
<td>Coinbase</td>
<td>合规性强，支持多国法币，新手引导完善，有“学习赚币”活动</td>
<td>海外用户、纯新手</td>
<td>手续费较高，部分功能受限</td>
<td><a href="https://www.coinbase.com/">https://www.coinbase.com/</a></td>
</tr>
<tr>
<td>Binance（币安）</td>
<td>全球交易量第一，交易对全（现货/期货/理财），手续费低</td>
<td>有基础后探索高级功能</td>
<td>部分地区需用合规版本</td>
<td><a href="https://www.binance.com/">https://www.binance.com/</a></td>
</tr>
<tr>
<td>欧易OKX</td>
<td>中文支持好，衍生品功能强，适配中文用户习惯</td>
<td>国内用户、需中文服务</td>
<td>需完成KYC，小额尝试即可</td>
<td><a href="https://www.okx.com/">https://www.okx.com/</a></td>
</tr>
</tbody></table>
<h4>（2）去中心化交易所（DEX）：进阶选择</h4>
<p>无需KYC，直接用钱包登录，适合“用加密货币换其他代币”，典型代表：</p>
<ul>
<li><strong>Uniswap</strong>：以太坊生态最大DEX，支持主流代币兑换，需支付Gas费（交易手续费，随网络拥堵波动）。</li>
<li><strong>PancakeSwap</strong>：基于币安智能链（BSC），Gas费低，适合小额交易。</li>
</ul>
<h3>2.3 看盘</h3>
<p>持有资产后，需通过工具判断市场情绪、资金流向，避免盲目操作。新手优先掌握<strong>CoinGlass</strong>（合约交易必备）。</p>
<h4>CoinGlass：合约交易者的“风险雷达”</h4>
<ul>
<li>核心功能：<ol>
<li>全市场爆仓数据：查看多空双方清算情况，判断短期波动风险。</li>
<li>资金费率：反映永续合约市场多空情绪（正费率=多头多，负费率=空头多）。</li>
<li>持仓量变化：跟踪大资金动向，辅助判断趋势。</li>
</ol>
</li>
<li>适用场景：做合约交易（如BTC/ETH期货）时，规避极端行情风险。</li>
<li>官网链接：<a href="https://www.coinglass.com/zh">https://www.coinglass.com/zh</a></li>
</ul>
<h3>2.4 辅助工具</h3>
<p>除核心工具外，这些辅助工具能提升操作安全性和效率：</p>
<table>
<thead>
<tr>
<th>工具类型</th>
<th>推荐工具</th>
<th>核心用途</th>
<th>官网链接</th>
</tr>
</thead>
<tbody><tr>
<td>区块链浏览器</td>
<td>Etherscan/PolygonScan</td>
<td>查交易记录、合约地址、代币流通量（验真伪）</td>
<td><a href="https://etherscan.io/">https://etherscan.io/</a></td>
</tr>
<tr>
<td>价格跟踪</td>
<td>CoinMarketCap/CoinGecko</td>
<td>看加密货币实时价格、市值、项目资讯</td>
<td><a href="https://coinmarketcap.com/">https://coinmarketcap.com/</a></td>
</tr>
<tr>
<td>安全加固</td>
<td>Trezor（硬件钱包）</td>
<td>大额资产离线存储，防电脑/手机中毒</td>
<td><a href="https://trezor.io/">https://trezor.io/</a></td>
</tr>
<tr>
<td>社区学习</td>
<td>Discord/Telegram</td>
<td>加入项目官方社区获取资讯（警惕私聊诈骗）</td>
<td>需下载对应App搜索项目群组</td>
</tr>
</tbody></table>
<h2>三、实操</h2>
<p>理论看完后，用“小额尝试”落地实践，按以下步骤操作，零经验也能上手：</p>
<ol>
<li><strong>创钱包</strong>：下载MetaMask（浏览器插件/手机App），备份助记词（关键步骤）。</li>
<li><strong>注交易所</strong>：选欧易OKX或Coinbase，完成KYC（身份证/护照验证）。</li>
<li><strong>买币</strong>：用“法币充值”（如支付宝/银行卡），买少量ETH（以太坊，Web3生态通用代币）。</li>
<li><strong>提币到钱包</strong>：从交易所“提现”ETH到MetaMask钱包地址（复制地址时反复核对，错了资产无法找回）。</li>
<li><strong>探DApp</strong>：用MetaMask登录Uniswap（兑换小额代币）或OpenSea（浏览NFT），体验“钱包登录”的去中心化操作。</li>
</ol>
<h2>四、避坑</h2>
<ol>
<li><strong>不泄助记词/私钥</strong>：任何人（包括平台客服）都不会要你的助记词，索要必是诈骗。</li>
<li><strong>官网下工具</strong>：仿冒钱包、交易所App多，官网链接认准“https”和官方标识（如MetaMask的狐狸Logo）。</li>
<li><strong>小额试</strong>：首次买币、转账、交易，都用小额资金（如100-200元）熟悉流程，再逐步操作。</li>
</ol>
<h2>Web3新手工具速查表</h2>
<p>本表浓缩核心工具的关键信息，方便你快速查阅用途、避坑要点及官网，实操时可直接对照使用。</p>
<table>
<thead>
<tr>
<th>工具类别</th>
<th>工具名称</th>
<th>类型/属性</th>
<th>核心用途</th>
<th>关键注意事项</th>
<th>官网链接</th>
</tr>
</thead>
<tbody><tr>
<td>钱包</td>
<td>MetaMask（小狐狸）</td>
<td>热钱包（在线）</td>
<td>登录DApp、接收/发送资产，Web3基础入口</td>
<td>助记词手写备份，不存手机/云端；从官网下载防仿冒</td>
<td><a href="https://metamask.io/">https://metamask.io/</a></td>
</tr>
<tr>
<td>钱包</td>
<td>Ledger Nano S/X</td>
<td>冷钱包（离线）</td>
<td>大额资产存储，防黑客/钓鱼</td>
<td>设备激活后妥善保管，不连接陌生设备</td>
<td><a href="https://www.ledger.com/">https://www.ledger.com/</a></td>
</tr>
<tr>
<td>交易所</td>
<td>欧易OKX</td>
<td>中心化（CEX）</td>
<td>用人民币买ETH/BTC，中文支持友好</td>
<td>完成KYC；首次买币用100-200元小额尝试</td>
<td><a href="https://www.okx.com/">https://www.okx.com/</a></td>
</tr>
<tr>
<td>交易所</td>
<td>Uniswap</td>
<td>去中心化（DEX）</td>
<td>用ETH兑换其他代币，无需KYC</td>
<td>需支付以太坊Gas费，确认费率后再交易</td>
<td><a href="https://app.uniswap.org/">https://app.uniswap.org/</a></td>
</tr>
<tr>
<td>看盘</td>
<td>CoinGlass</td>
<td>数据分析工具</td>
<td>查爆仓数据、资金费率，规避合约交易风险</td>
<td>重点关注“爆仓金额”，判断短期市场波动</td>
<td><a href="https://www.coinglass.com/zh">https://www.coinglass.com/zh</a></td>
</tr>
<tr>
<td>辅助工具</td>
<td>Etherscan</td>
<td>区块链浏览器</td>
<td>查ETH交易记录、合约地址真伪、代币流通量</td>
<td>验证DApp合约地址时，需确认与官网一致</td>
<td><a href="https://etherscan.io/">https://etherscan.io/</a></td>
</tr>
<tr>
<td>辅助工具</td>
<td>Trezor</td>
<td>硬件安全工具</td>
<td>大额资产离线存储，防电脑/手机中毒</td>
<td>初始化时做好助记词备份，不泄露设备信息</td>
<td><a href="https://trezor.io/">https://trezor.io/</a></td>
</tr>
<tr>
<td>辅助工具</td>
<td>CoinMarketCap</td>
<td>价格跟踪工具</td>
<td>查加密货币实时价格、市值、项目基础信息</td>
<td>仅作信息参考，不直接依据价格波动盲目交易</td>
<td><a href="https://coinmarketcap.com/">https://coinmarketcap.com/</a></td>
</tr>
</tbody></table>
<h2>结语</h2>
<p>Web3的核心是“掌控自己的资产”，入门关键不是懂所有技术，而是先熟练用工具。从装MetaMask、买少量ETH开始，每一步实操都会让你对Web3的理解更深刻。后续可逐步探索NFT、DeFi等场景，但始终记住：安全第一，理性投资。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[我的AI工具包]]></title>
            <link>https://zzfzzf.com/post/1967251359770087424</link>
            <guid>1967251359770087424</guid>
            <pubDate>Thu, 23 Oct 2025 02:12:43 GMT</pubDate>
            <content:encoded><![CDATA[<p>今天想和大家分享我最常用的四个 AI 服务：OpenRouter、Claude、腾讯元宝 和 DeepSeek。这些都是我亲测好用的工具，希望能给你的工作和生活带来一些便利～</p>
<h2>OpenRouter</h2>
<p>OpenRouter 是一个 AI 模型聚合平台，最大的优势是能通过一个统一的 API 接口调用多种主流 AI 模型。我会用它来完成大部分任务。</p>
<h2>Claude</h2>
<p>我已基本用 Claude Code 替代了其他编程助手（比如 Cursor），它帮我节省了大量时间。</p>
<h2>腾讯元宝</h2>
<p>生活上的问题以及各种网页对话场景就选它。</p>
<h2>DeepSeek</h2>
<p>当对生成的内容要求不高、追求性价比就选它，比如我的博客文章智能摘要。</p>
<h2>Codex</h2>
<p>Codex 是由 OpenAI 推出的编程代理工具，专门针对软件工程任务进行了优化。它能够读取代码库、运行测试、生成变更提交（pull request）并支持在沙盒环境中执行修改。我目前在较大型开发项目或重构任务时会考虑用它，尤其当需要更自动化、更深入的代码生成／审核流程时。</p>
<h2>选择建议</h2>
<ul>
<li>探索和对比模型时，我用 OpenRouter。  </li>
<li>专注编程任务，尤其是代码生成和重构，Claude Code 是我的主力。  </li>
<li>处理中文内容和个人项目时，DeepSeek 因其性价比成为常用选择。  </li>
<li>生活上的问题以及手机对话，我就找腾讯元宝。  </li>
<li>如果是更复杂的软件工程／开发流程，或者需要代码库级别自动化，我就把 Codex 纳入考虑。</li>
</ul>
<blockquote>
<p>（注：这只是我的个人使用体验，不同人因需求和使用习惯可能感受不同。所有产品信息请以官方最新说明为准。）</p>
</blockquote>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[我的数字支付]]></title>
            <link>https://zzfzzf.com/post/1967227403742744576</link>
            <guid>1967227403742744576</guid>
            <pubDate>Sun, 14 Sep 2025 16:05:45 GMT</pubDate>
            <content:encoded><![CDATA[<h2>大陆</h2>
<ul>
<li>中国银行 汇款HK无损</li>
<li>招行 APP体验最好</li>
<li>支付宝 主要买基金</li>
<li>微信 用的最多</li>
<li>其他乱七八糟的</li>
</ul>
<h2>海外</h2>
<ul>
<li>BOCHK 内地汇出渠道（不和别人转账，钱都转到za再和各大劵商交互）</li>
<li>WISE cladue等订阅 不支持大陆和港卡，这个差不多等于美国银行卡</li>
<li>ZA</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[claude code最佳实践]]></title>
            <link>https://zzfzzf.com/post/1965254188711546880</link>
            <guid>1965254188711546880</guid>
            <pubDate>Sun, 14 Sep 2025 14:01:29 GMT</pubDate>
            <content:encoded><![CDATA[<p>安装与配置
官方文档
安装使用很简单。介绍一些细节</p>
<ul>
<li>官方最佳实践 <a href="https://www.anthropic.com/engineering/claude-code-best-practices">https://www.anthropic.com/engineering/claude-code-best-practices</a>
一些工具</li>
<li><a href="https://github.com/oraios/serena">https://github.com/oraios/serena</a> 减少token使用（还不太会用）</li>
<li><a href="https://opcode.sh/">https://opcode.sh/</a> claude配置可视化</li>
<li><a href="https://docs.anthropic.com/zh-CN/docs/claude-code/ide-integrations">https://docs.anthropic.com/zh-CN/docs/claude-code/ide-integrations</a> claude code和ide集成
基本配置</li>
</ul>
<ol>
<li>settings.json文件</li>
<li>.claude目录
CLAUDE.md⁠
优先级从内到外寻找</li>
<li>项目目录：CLAUDE.md</li>
<li>根目录：~/.claude/CLAUDE.md
/init命令可以生成 CLAUDE.md 但效果不太好，建议根据生成的再不断迭代修改</li>
</ol>
<ul>
<li>遵循 KISS 原则，非必要不要过度设计</li>
<li>实现简单可维护，不需要考虑太多防御性的边界条件</li>
<li>我需要逐步进行，通过多轮对话来完成需求，进行渐进式开发</li>
<li>在开始设计方案或实现代码之前，我需要进行充分的调研。如果有任何不明确的要求，在继续之前向对方确认</li>
<li>当我收到一个需求时，首先需要思考相关的方案，并请求对方进行审核。通过审核后，需要将相应的任务拆解到 TODO 中</li>
<li>我从最本质的角度，用第一性原理来分析问题
[图片]
自定义命令
在项目 commands目录下新增markdown文件可新增指令。也可在根目录下设置全局的命令
⁠使用时需要 /+命令 比如 /
[图片]</li>
</ul>
<p>自定义智能体
同命令，<a href="https://docs.anthropic.com/zh-CN/docs/claude-code/sub-agents">https://docs.anthropic.com/zh-CN/docs/claude-code/sub-agents</a>
优势：不污染上下文
可以使用多个子代理链路
example：
首先使用代码分析器子代理查找性能问题，然后使用优化器子代理修复它们</p>
<p>代码中调用 Claude Code
<a href="https://docs.anthropic.com/zh-CN/docs/claude-code/sdk/sdk-typescript">https://docs.anthropic.com/zh-CN/docs/claude-code/sdk/sdk-typescript</a></p>
<h2>安装</h2>
<p><a href="https://opcode.sh/">https://opcode.sh/</a></p>
<blockquote>
<p>官方文档
<a href="https://docs.anthropic.com/zh-CN/docs/claude-code/mcp#%E9%80%89%E9%A1%B9-1%EF%BC%9A%E6%B7%BB%E5%8A%A0%E6%9C%AC%E5%9C%B0-stdio-%E6%9C%8D%E5%8A%A1%E5%99%A8">https://docs.anthropic.com/zh-CN/docs/claude-code/mcp#%E9%80%89%E9%A1%B9-1%EF%BC%9A%E6%B7%BB%E5%8A%A0%E6%9C%AC%E5%9C%B0-stdio-%E6%9C%8D%E5%8A%A1%E5%99%A8</a></p>
</blockquote>
<ul>
<li><a href="https://ctok.ai/claude-code-setup-ctok">https://ctok.ai/claude-code-setup-ctok</a></li>
<li></li>
</ul>
<h2>mcp</h2>
<ul>
<li><a href="https://github.com/oraios/serena">https://github.com/oraios/serena</a> 减少token使用</li>
</ul>
<h2>规则文件</h2>
<ul>
<li>CLAUDE.md⁠  根目录
⁠CLAUDE.md⁠ 是一个特殊文件，Claude 在开始对话时会自动将其拉入上下文。这使其成为记录以下内容的理想场所：</li>
</ul>
<p>• 常用 bash 命令</p>
<p>• 核心文件和工具函数</p>
<p>• 代码风格指南</p>
<p>• 测试说明</p>
<p>• 仓库规范（如分支命名、合并 vs. rebase 等）</p>
<p>• 开发环境设置（如 pyenv 使用、可用编译器等）</p>
<p>• 项目中特有的异常行为或警告</p>
<p>• 你希望 Claude 记住的其他信息</p>
<h2>CLAUDE.md⁠ 文件没有固定格式。我们建议保持简洁且易读</h2>
<h2>拼车</h2>
<ul>
<li><a href="https://github.com/Wei-Shaw/claude-relay-service">https://github.com/Wei-Shaw/claude-relay-service</a> 搭建</li>
<li><a href="https://www.aicodemirror.com/dashboard">https://www.aicodemirror.com/dashboard</a> 中转</li>
<li><a href="https://www.claudecode-cn.com/">https://www.claudecode-cn.com/</a> 中转</li>
</ul>
<h2>subagents</h2>
<p>echo &quot;root:YourNewPassword&quot; | sudo chpasswd</p>
<h2>使用LLM构建MCP服务器</h2>
<p>我们可以用像Claude这样的大语言模型（LLM）来加速MCP开发！</p>
<p>如何使用LLM来构建自定义的模型上下文协议（MCP）服务器和客户端？以Claude为例，其他大型模型（GPT、Gemini、Grok、Qwen、DeepSeek）都适用。</p>
<p>准备文档资料
在开始之前，请收集必要的文档资料，以帮助 Claude 理解 MCP：</p>
<p>访问<a href="https://modelcontextprotocol.io/llms-full.txt%E5%B9%B6%E5%A4%8D%E5%88%B6%E5%AE%8C%E6%95%B4%E7%9A%84%E6%96%87%E6%A1%A3%E6%96%87%E6%9C%AC%E3%80%82">https://modelcontextprotocol.io/llms-full.txt并复制完整的文档文本。</a>
前往MCP TypeScript SDK或Python SDK的代码仓库。
复制README文件和其他相关文档。
将这些文档粘贴到你与 Claude 的对话中。
描述您的服务器需求
提供文档后，明确向 Claude 描述您想要构建什么样的服务器。请具体说明：</p>
<p>您的服务器将开放哪些资源
将提供哪些工具
它应该提供哪些提示（Prompts）
它需要与哪些外部系统交互
例如：</p>
<p>构建一个 MCP 服务器，要求：</p>
<ul>
<li>连接到我公司的 PostgreSQL 数据库</li>
<li>将表结构作为资源开放出来</li>
<li>提供运行只读 SQL 查询的工具</li>
<li>包含用于常见数据分析任务的提示（Prompts）</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[sing-box+adh+openwrt]]></title>
            <link>https://zzfzzf.com/post/1923355959283945472</link>
            <guid>1923355959283945472</guid>
            <pubDate>Sun, 18 May 2025 16:11:12 GMT</pubDate>
            <content:encoded><![CDATA[<h2>sing-box</h2>
<ul>
<li><p><a href="https://sing-box.sagernet.org/installation/package-manager/#service-management">https://sing-box.sagernet.org/installation/package-manager/#service-management</a></p>
<ul>
<li><p>curl -4 <a href="https://ipinfo.io">https://ipinfo.io</a></p>
</li>
<li><p>systemctl status sing-box</p>
</li>
<li><p>curl <a href="https://baidu.com">https://baidu.com</a></p>
</li>
<li><p>curl <a href="https://google.com">https://google.com</a></p>
</li>
<li><p>curl -v <a href="https://baidu.com">https://baidu.com</a></p>
</li>
<li><p>nslookup google.com 192.168.100.212</p>
</li>
<li><p>dig <a href="http://www.youtube.com">www.youtube.com</a> @1.1.1.1 </p>
</li>
<li><p>nslookup <a href="http://www.youtube.com">www.youtube.com</a> 8.8.8.8</p>
</li>
<li><p>curl -v <a href="http://31.13.73.9">http://31.13.73.9</a> --connect-timeout 5</p>
</li>
</ul>
</li>
<li><p><a href="https://www.right.com.cn/forum/thread-355909-1-1.html">https://www.right.com.cn/forum/thread-355909-1-1.html</a></p>
</li>
<li><p><a href="https://sing-box.sagernet.org/manual/proxy-protocol/trojan/#server-example">https://sing-box.sagernet.org/manual/proxy-protocol/trojan/#server-example</a></p>
</li>
<li><p><a href="https://surgio.js.org/guide/client/sing-box.html#%E7%BC%96%E5%86%99-artifact">https://surgio.js.org/guide/client/sing-box.html#%E7%BC%96%E5%86%99-artifact</a></p>
</li>
<li><p><a href="https://icloudnative.io/posts/sing-box-tutorial/#dns-%E9%85%8D%E7%BD%AE">https://icloudnative.io/posts/sing-box-tutorial/#dns-%E9%85%8D%E7%BD%AE</a></p>
</li>
<li><p><a href="https://xtls.github.io/document/level-2/transparent_proxy/transparent_proxy.html#%E5%89%8D%E6%9C%9F%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C">https://xtls.github.io/document/level-2/transparent_proxy/transparent_proxy.html#%E5%89%8D%E6%9C%9F%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C</a></p>
</li>
<li><p><a href="https://github.com/mario-huang/sing-box-bypass-router-transparent-proxy-configuration">https://github.com/mario-huang/sing-box-bypass-router-transparent-proxy-configuration</a></p>
</li>
</ul>
<h3>DNS污染</h3>
<p>​Google 官方 IP 范围​
Google 的典型 IP 地址段为 142.250.x.x、172.217.x.x 等，而查询结果中的 8.7.198.46 和 46.82.174.69 均不在此范围内，属于 ​已知的污染 IP​ 。
​DNS 污染与劫持的区别​
​DNS 污染​：攻击者伪造 DNS 响应，返回虚假 IP（如 8.7.198.46），常见于中间网络节点（如运营商或网关）的主动干扰。
​DNS 劫持​：本地 DNS 服务器被恶意篡改，将合法域名解析到攻击者控制的 IP（如 46.82.174.69）。
​验证方法​
​对比权威 DNS​：
bash
复制</p>
<pre><code class="language-bash">dig google.com @208.67.222.222 +short  # OpenDNS
dig google.com @9.9.9.9 +short        # Quad9


nslookup google.com 8.8.8.8
dig google.com @1.1.1.1 +short
</code></pre>
<p>若结果仍异常，可能是全局污染；若正常，则本地网络存在劫持。
​使用加密 DNS​（DoH/DoT）：
bash
复制</p>
<pre><code class="language-bash">dig google.com @https://dns.google/dns-query  # DoH 查询
</code></pre>
<p>若加密查询返回正常 IP，则确认传统 DNS 被劫持 。</p>
<p>向一个 ​不存在​ 的 DNS 服务器（如 144.223.234.234）发起查询</p>
<pre><code class="language-bash">nslookup google.com 144.223.234.234
</code></pre>
<p><a href="https://ipinfo.io/31.13.106.4">https://ipinfo.io/31.13.106.4</a></p>
<p><a href="https://pymumu.github.io/smartdns/install/openwrt/">https://pymumu.github.io/smartdns/install/openwrt/</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[Vue&React]]></title>
            <link>https://zzfzzf.com/post/1915284167709429760</link>
            <guid>1915284167709429760</guid>
            <pubDate>Thu, 24 Apr 2025 06:58:11 GMT</pubDate>
            <content:encoded><![CDATA[<h3>Vue3高频面试题</h3>
<h4>Composition API 与 Options API 的区别</h4>
<ul>
<li><strong>Composition API</strong>：以逻辑功能为单位组织代码（如 <code>setup()</code>），解决复杂组件代码分散的问题，支持更灵活的逻辑复用（如自定义 Hook），对 TypeScript 支持更友好。  </li>
<li><strong>Options API</strong>：通过 <code>data</code>、<code>methods</code> 等选项分类组织代码，适合简单组件，但逻辑复杂时代码可读性下降。</li>
</ul>
<h4>Vue3性能优化手段</h4>
<ul>
<li><strong>响应式系统升级</strong>：使用 <code>Proxy</code> 替代 <code>Object.defineProperty</code>，支持动态属性监听和数组索引修改。  </li>
<li><strong>编译优化</strong>：静态节点提升（复用未变化 DOM）、PatchFlag 标记动态节点、事件缓存减少重复绑定。  </li>
<li><strong>Tree-shaking</strong>：按需打包 API（如未使用 <code>v-model</code> 则不包含相关代码）。</li>
</ul>
<h4>3. <strong>Proxy 的优势</strong></h4>
<ul>
<li>支持对象/数组的全属性监听，无需递归遍历；  </li>
<li>可拦截 <code>delete</code>、<code>has</code> 等操作；  </li>
<li>避免 Vue2 中无法检测新增属性或数组索引的问题。</li>
</ul>
<h4>Teleport 和 Suspense 的作用</h4>
<ul>
<li><strong>Teleport</strong>：将组件内容渲染到指定 DOM 节点（如全局弹窗），解决层级样式问题。  </li>
<li><strong>Suspense</strong>：处理异步组件加载状态，显示加载占位符（如骨架屏）。</li>
</ul>
<h4>Vue3 的 Tree-shaking 特性</h4>
<p>通过 ES Module 静态分析，移除未使用的代码。例如，未使用 <code>transition</code> 组件时，相关代码不会打包。</p>
<hr>
<h3>React 高频面试题</h3>
<h4>Fiber 架构的核心改进</h4>
<ul>
<li><strong>目标</strong>：解决同步更新导致的页面卡顿问题。  </li>
<li><strong>实现</strong>：将渲染过程拆分为可中断的异步任务，通过链表结构（Fiber 节点）实现优先级调度和增量更新。</li>
</ul>
<h4>Hooks 的优势与使用限制</h4>
<ul>
<li><strong>优势</strong>：逻辑复用（如 <code>useEffect</code>）、函数组件支持状态管理、减少类组件的复杂度。  </li>
<li><strong>限制</strong>：只能在函数组件或自定义 Hook 顶层调用，不可在循环/条件中使用。</li>
</ul>
<h4>受控组件与非受控组件</h4>
<ul>
<li><strong>受控组件</strong>：表单值由 React 状态管理（如 <code>value</code> + <code>onChange</code>），数据流可控。  </li>
<li><strong>非受控组件</strong>：表单值由 DOM 自身管理（通过 <code>ref</code> 获取），适合简单场景。</li>
</ul>
<h4>React Diff 算法原理</h4>
<ul>
<li><strong>策略</strong>：同级节点比较（避免跨层级移动）、通过 <code>key</code> 标识元素唯一性、类型不同则直接替换子树。  </li>
<li><strong>优化点</strong>：对列表元素使用唯一 <code>key</code> 提高复用效率，避免不必要的 DOM 操作。</li>
</ul>
<h4>性能优化手段</h4>
<ul>
<li><strong>避免不必要的渲染</strong>：<code>React.memo</code>、<code>useMemo</code>/<code>useCallback</code> 缓存计算结果。  </li>
<li><strong>代码分割</strong>：<code>React.lazy</code> + <code>Suspense</code> 实现按需加载。  </li>
<li><strong>虚拟列表</strong>：<code>react-window</code> 处理长列表渲染。</li>
</ul>
<hr>
<h3><strong>三、对比类问题</strong></h3>
<h4>1. <strong>Vue 与 React 设计理念差异</strong></h4>
<ul>
<li><strong>Vue</strong>：渐进式框架，强调模板语法和响应式系统，内置路由/状态管理方案（Vue Router/Pinia）。  </li>
<li><strong>React</strong>：专注于视图层，依赖社区生态（如 React Router/Redux），推崇函数式编程和不可变数据。</li>
</ul>
<h4>Composition API 与 React Hooks 的异同</h4>
<ul>
<li><strong>相同点</strong>：均支持逻辑复用，减少代码耦合。  </li>
<li><strong>不同点</strong>：Vue 的 Composition API 基于响应式系统，Hooks 依赖闭包和状态队列，需注意调用顺序。</li>
</ul>
<h4>1. Vue3 响应式系统的实现原理</h4>
<ul>
<li><strong>核心机制</strong>：通过 <code>Proxy</code> 代理对象，结合 <code>Reflect</code> 反射操作实现数据劫持。<code>Proxy</code> 可拦截对象的所有操作（如 <code>get</code>、<code>set</code>、<code>deleteProperty</code>），自动追踪依赖并触发更新。  </li>
<li><strong>对比 Vue2</strong>：  <ul>
<li><strong>动态属性支持</strong>：<code>Proxy</code> 无需预先定义属性（Vue2 需 <code>Vue.set</code>）。  </li>
<li><strong>性能优化</strong>：避免递归遍历对象属性，直接代理整个对象。</li>
</ul>
</li>
<li><strong>示例代码</strong>：  <pre><code class="language-javascript">const data = { count: 0 };
const proxy = new Proxy(data, {
  get(target, key) {
    track(target, key); // 依赖收集
    return Reflect.get(target, key);
  },
  set(target, key, value) {
    Reflect.set(target, key, value);
    trigger(target, key); // 触发更新
    return true;
  }
});
</code></pre>
</li>
</ul>
<h4>2. <strong>Composition API 的底层逻辑</strong></h4>
<ul>
<li><strong>解决的问题</strong>：Options API 中逻辑分散（如 <code>data</code>、<code>methods</code>），复杂组件难以维护。  </li>
<li><strong>核心设计</strong>：  <ul>
<li><strong>逻辑复用</strong>：通过自定义 Hook（如 <code>useFetch</code>）封装可复用逻辑。  </li>
<li><strong>响应式工具链</strong>：<code>ref</code>（基本类型包装）、<code>reactive</code>（对象代理）、<code>toRefs</code>（解构响应式对象）。</li>
</ul>
</li>
<li><strong>与 React Hooks 的区别</strong>：Vue 的 Composition API 基于响应式系统，无需手动管理闭包顺序。</li>
</ul>
<h4>3. <strong>Vue3 编译优化策略</strong></h4>
<ul>
<li><strong>静态提升（Hoist Static）</strong>：将静态节点提升至渲染函数外，避免重复创建。  </li>
<li><strong>PatchFlag 标记</strong>：在虚拟 DOM 中标记动态节点（如 <code>TEXT</code>、<code>CLASS</code>），仅对比变化的属性。  </li>
<li><strong>Tree-shaking</strong>：按需打包 API，如未使用 <code>v-model</code> 则剔除相关代码。</li>
</ul>
<h4>4. <strong>Teleport 和 Suspense 的底层实现</strong></h4>
<ul>
<li><strong>Teleport</strong>：通过 <code>portal</code> 技术将组件渲染到指定 DOM 节点（如 <code>body</code>），解决模态框层级问题。  </li>
<li><strong>Suspense</strong>：内部使用 <code>Promise</code> 管理异步组件，通过插槽展示加载状态（如骨架屏）。</li>
</ul>
<p>createGlobalState</p>
<p><code>createGlobalState</code> 是 VueUse 库中用于创建全局响应式状态的核心工具，其实现巧妙结合了 <strong>闭包</strong>、<strong>Vue3 响应式系统</strong> 和 <strong><code>effectScope</code></strong> 机制。以下是其核心实现逻辑与原理的深度解析：</p>
<hr>
<h3><strong>一、核心实现逻辑</strong></h3>
<h4>1. <strong>闭包与单例模式</strong></h4>
<ul>
<li><strong>状态隔离</strong>：通过闭包变量（如 <code>state</code>）保存全局状态实例，确保全局唯一性。首次调用时通过 <code>stateFactory</code> 初始化状态，后续调用直接返回已存在的实例。  <pre><code class="language-javascript">let initialized = false;
let state: any;
const scope = effectScope(true);

return () =&gt; {
  if (!initialized) {
    state = scope.run(() =&gt; stateFactory())!;
    initialized = true;
  }
  return state;
};
</code></pre>
</li>
</ul>
<h4>2. <strong><code>effectScope</code> 的作用</strong></h4>
<ul>
<li><strong>副作用管理</strong>：使用 <code>effectScope(true)</code> 创建一个独立的副作用作用域（<code>detached: true</code>），将 <code>stateFactory</code> 中生成的响应式状态（如 <code>ref</code>、<code>computed</code>、<code>watch</code>）自动收集到该作用域中。</li>
<li><strong>生命周期控制</strong>：当组件卸载或手动调用 <code>scope.stop()</code> 时，该作用域内所有副作用（如 <code>watch</code> 监听）会被自动清理，避免内存泄漏。</li>
</ul>
<h4>3. <strong>SSR（服务端渲染）支持</strong></h4>
<ul>
<li><strong>环境判断</strong>：通过 <code>__SSR__</code> 常量区分客户端与服务端环境。在服务端，状态会被挂载到请求级别的上下文对象（<code>ssrContext</code>），确保每个请求的隔离性。<pre><code class="language-javascript">if (__SSR__ &amp;&amp; getCurrentInstance()) {
  const ssrContext = useSSRContext();
  // 将状态绑定到 SSR 上下文，避免跨请求污染
}
</code></pre>
</li>
</ul>
<hr>
<h3><strong>二、与普通闭包的区别</strong></h3>
<h4>1. <strong>副作用自动清理</strong></h4>
<ul>
<li><strong>普通闭包</strong>：仅通过闭包保存状态，但无法自动清理响应式依赖（如 <code>watch</code>），可能导致内存泄漏。  </li>
<li><strong><code>effectScope</code> 优势</strong>：通过作用域统一管理副作用生命周期，无需手动调用 <code>stop()</code>，适合需要自动清理的场景（如组件卸载）。</li>
</ul>
<h4>2. <strong>响应式依赖的精准控制</strong></h4>
<ul>
<li><strong>示例场景</strong>：若状态中包含定时器或事件监听，使用 <code>onScopeDispose()</code> 注册清理回调，确保作用域停止时自动释放资源：<pre><code class="language-javascript">const useGlobalTimer = createGlobalState(() =&gt; {
  const interval = ref&lt;NodeJS.Timeout&gt;();
  const start = () =&gt; { /* 启动定时器 */ };
  const stop = () =&gt; { clearInterval(interval.value) };
  onScopeDispose(stop); // 作用域销毁时自动清理
  return { start, stop };
});
</code></pre>
</li>
</ul>
<hr>
<h3><strong>三、设计哲学与适用场景</strong></h3>
<h4>1. <strong>轻量级状态共享</strong></h4>
<ul>
<li><strong>对比 Pinia</strong>：<code>createGlobalState</code> 无复杂中间件或 DevTools 集成，适合简单全局状态（如用户 token、主题配置）。  </li>
<li><strong>开发效率</strong>：通过工厂函数快速定义状态逻辑，减少模板代码。</li>
</ul>
<h4>2. <strong>与组件生命周期的解耦</strong></h4>
<ul>
<li><strong>跨组件复用</strong>：状态独立于组件实例存在，可在任意组件中调用 <code>useGlobalState()</code> 访问，无需通过 Props 或 Provide/Inject 传递。</li>
</ul>
<hr>
<h3><strong>四、注意事项</strong></h3>
<ol>
<li><p><strong>SSR 兼容性</strong>  </p>
<ul>
<li>需确保服务端状态键唯一，避免不同请求间的状态污染。</li>
</ul>
</li>
<li><p><strong>类型安全</strong>  </p>
<ul>
<li>推荐使用 TypeScript 标注工厂函数返回值，避免类型推断错误。</li>
</ul>
</li>
<li><p><strong>性能优化</strong>  </p>
<ul>
<li>复杂状态需手动管理资源释放（如定时器、WebSocket 连接），避免因作用域未及时清理导致内存泄漏。</li>
</ul>
</li>
</ol>
<hr>
<h3><strong>五、总结</strong></h3>
<h2><code>createGlobalState</code> 的实现核心在于 <strong>闭包的单例模式</strong> + <strong><code>effectScope</code> 的副作用管理</strong>，既保留了 Vue3 响应式系统的自动化优势，又通过作用域机制规避了内存泄漏风险。其轻量级特性使其成为小型项目或简单全局状态管理的理想选择，但在复杂场景（如持久化、状态分片）中仍需结合 Pinia 等专业库。</h2>
<h3><strong>二、React 深入面试题</strong></h3>
<h4>1. <strong>Fiber 架构的核心原理</strong></h4>
<ul>
<li><strong>解决的问题</strong>：同步更新导致的页面卡顿（如长任务阻塞渲染）。  </li>
<li><strong>实现机制</strong>：  <ul>
<li><strong>链表结构</strong>：将任务拆分为多个 Fiber 节点，支持中断与恢复。  </li>
<li><strong>优先级调度</strong>：高优先级任务（如动画）优先执行，低优先级任务（如数据请求）延后。</li>
</ul>
</li>
<li><strong>三个阶段</strong>：Reconciliation（生成 Fiber 树）、Commit（更新 DOM）、Cleanup（执行副作用）。</li>
</ul>
<h4>2. <strong>Hooks 的底层实现与限制</strong></h4>
<ul>
<li><strong>链表存储状态</strong>：函数组件通过链表顺序记录 <code>useState</code>、<code>useEffect</code> 的状态，确保调用顺序一致。  </li>
<li><strong>规则限制</strong>：  <ul>
<li><strong>不可条件调用</strong>：Hooks 依赖调用顺序，条件语句会破坏链表结构。  </li>
<li><strong>闭包陷阱</strong>：异步操作需通过 <code>useRef</code> 捕获最新值。</li>
</ul>
</li>
</ul>
<h4>3. <strong>React 并发模式（Concurrent Mode）</strong></h4>
<ul>
<li><strong>核心能力</strong>：  <ul>
<li><strong>时间切片（Time Slicing）</strong>：将渲染任务分割为小块，避免阻塞主线程。  </li>
<li><strong>过渡更新（Transition）</strong>：区分紧急更新（如输入）与非紧急更新（如搜索结果渲染）。</li>
</ul>
</li>
<li><strong>应用场景</strong>：长列表渲染、异步数据加载时保持交互流畅。</li>
</ul>
<h4>4. <strong>虚拟 DOM 与 Diff 算法优化</strong></h4>
<ul>
<li><strong>Diff 策略</strong>：  <ul>
<li><strong>同层比较</strong>：仅对比同级节点，跨层级移动直接重建子树。  </li>
<li><strong>Key 的作用</strong>：唯一标识元素，减少不必要的 DOM 操作（如列表重排序）。</li>
</ul>
</li>
<li><strong>性能瓶颈</strong>：虚拟 DOM 的生成和对比本身消耗资源，需结合 <code>React.memo</code> 或 <code>shouldComponentUpdate</code> 优化。</li>
</ul>
<hr>
<h3><strong>三、框架对比类问题</strong></h3>
<h4>1. <strong>Vue 与 React 设计哲学差异</strong></h4>
<ul>
<li><strong>响应式机制</strong>：  <ul>
<li>Vue：自动依赖追踪（<code>Proxy</code>），开发者无感知更新。  </li>
<li>React：手动触发更新（<code>setState</code>），强调不可变数据。</li>
</ul>
</li>
<li><strong>模板 vs JSX</strong>：  <ul>
<li>Vue：约束性模板语法（如 <code>v-if</code>），内置优化。  </li>
<li>React：JSX 灵活，依赖开发者控制渲染逻辑。</li>
</ul>
</li>
</ul>
<h4>2. <strong>性能优化方向对比</strong></h4>
<ul>
<li><strong>Vue3</strong>：编译时优化（静态提升、PatchFlag）。  </li>
<li><strong>React</strong>：运行时优化（Fiber 调度、并发模式）。</li>
</ul>
<h4>3. <strong>状态管理方案差异</strong></h4>
<ul>
<li><strong>Vue</strong>：官方推荐 Pinia（Composition API 风格），轻量且类型友好。  </li>
<li><strong>React</strong>：社区生态主导（如 Redux、Recoil），需结合中间件（如 Redux-Thunk）处理异步。</li>
</ul>
<hr>
<h3><strong>四、高频代码题示例</strong></h3>
<h4>1. <strong>实现双向绑定（Vue3 vs React）</strong></h4>
<ul>
<li><strong>Vue3</strong>：  <pre><code class="language-html">&lt;input v-model=&quot;text&quot; /&gt;
&lt;script setup&gt;
const text = ref(&#39;&#39;);
&lt;/script&gt;
</code></pre>
</li>
<li><strong>React</strong>：  <pre><code class="language-jsx">const [text, setText] = useState(&#39;&#39;);
&lt;input value={text} onChange={(e) =&gt; setText(e.target.value)} /&gt;
</code></pre>
</li>
</ul>
<h4>2. <strong>React Hooks 代替 Redux 的思路</strong></h4>
<ul>
<li><strong>方案</strong>：使用 <code>useContext</code> + <code>useReducer</code> 实现全局状态管理：  <pre><code class="language-javascript">const StateContext = createContext();
const App = () =&gt; {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    &lt;StateContext.Provider value={{ state, dispatch }}&gt;
      &lt;ChildComponent /&gt;
    &lt;/StateContext.Provider&gt;
  );
};
</code></pre>
</li>
</ul>
<hr>
<h3><strong>总结建议</strong></h3>
<ul>
<li><strong>原理深挖</strong>：重点掌握 Proxy、Fiber、Hooks 链表等底层机制。  </li>
<li><strong>项目结合</strong>：举例说明优化手段（如 Vue3 的静态提升、React 的并发渲染）的实际应用场景。  </li>
<li><strong>框架对比</strong>：从响应式、模板、生态等维度结构化回答差异。</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[浏览器垃圾回收机制]]></title>
            <link>https://zzfzzf.com/post/1915283099252428800</link>
            <guid>1915283099252428800</guid>
            <pubDate>Wed, 11 Jun 2025 02:18:59 GMT</pubDate>
            <content:encoded><![CDATA[<h4>1. 垃圾回收的基本原理</h4>
<p>浏览器通过<strong>自动内存管理</strong>机制回收不再使用的对象，防止内存泄漏。JavaScript引擎（如V8）负责跟踪内存分配，识别不再可达的对象并释放其内存。
浏览器的垃圾回收（Garbage Collection, GC）机制是自动管理内存的核心，旨在识别并释放不再使用的对象内存，避免内存泄漏和性能问题。</p>
<hr>
<h4>2. 核心算法</h4>
<h5>a. 标记-清除（Mark-and-Sweep）</h5>
<ul>
<li><strong>流程</strong>：<ol>
<li><strong>标记阶段</strong>：从根对象（如<code>window</code>、全局变量）出发，遍历所有可达对象并标记为“活动”。</li>
<li><strong>清除阶段</strong>：遍历堆内存，回收未被标记的对象。</li>
</ol>
</li>
<li>优势：解决循环引用问题（如两个对象互相引用但整体不可达）。</li>
<li>缺陷：全堆遍历可能导致性能卡顿（“Stop-The-World”暂停）。</li>
</ul>
<h5>b. 分代回收（Generational Collection）</h5>
<ul>
<li><strong>内存分代</strong>：<ul>
<li><strong>新生代（Young Generation）</strong>：存放短生命周期对象（如局部变量），使用<strong>Scavenge算法</strong>​（复制存活对象至空闲区）。<ul>
<li><strong>Scavenge</strong>：将内存分为<code>From</code>和<code>To</code>空间，存活对象从<code>From</code>复制到<code>To</code>，清空<code>From</code>。</li>
<li><strong>晋升（Promotion）</strong>：多次存活的对象移至老生代。</li>
</ul>
</li>
<li><strong>老生代（Old Generation）</strong>：存放长生命周期对象（如全局变量），使用<strong>标记-清除</strong>或<strong>标记-整理</strong>（减少碎片）。</li>
</ul>
</li>
</ul>
<h5>c. 增量标记（Incremental Marking）与惰性清理（Lazy Sweeping）</h5>
<ul>
<li><strong>增量标记</strong>：将标记过程拆分为多个小步骤，与JS执行交替进行，减少页面卡顿。</li>
<li><strong>惰性清理</strong>：延迟清理操作，按需逐步回收内存。</li>
</ul>
<hr>
<h3>性能优化</h3>
<ol>
<li>增量回收（Incremental GC）
将标记过程拆分为小任务，与主线程交替执行，减少单次停顿时间。
挑战：需处理标记期间对象引用变化的写屏障（Write Barrier）问题。</li>
<li>并行回收（Parallel GC）
利用多线程并行标记和清除（如 V8 的并发标记），加速 GC 过程。</li>
<li>空闲时回收（Idle-Time GC）
在 CPU 空闲时执行 GC，减少对交互的影响。</li>
</ol>
<h4>3. 内存泄漏的常见原因与解决方案</h4>
<table>
<thead>
<tr>
<th>场景</th>
<th>问题描述</th>
<th>解决方案</th>
</tr>
</thead>
<tbody><tr>
<td>意外全局变量</td>
<td>未声明的变量挂载到<code>window</code></td>
<td>使用严格模式（<code>&#39;use strict&#39;</code>）</td>
</tr>
<tr>
<td>未清理的定时器/事件</td>
<td>回调函数持有DOM引用</td>
<td>及时调用<code>clearInterval</code>/<code>removeEventListener</code></td>
</tr>
<tr>
<td>闭包引用</td>
<td>函数内部引用外部变量未释放</td>
<td>手动解除引用（如置为<code>null</code>）</td>
</tr>
<tr>
<td>游离的DOM引用</td>
<td>已移除的DOM节点被JS引用</td>
<td>清除DOM节点的所有引用</td>
</tr>
</tbody></table>
<hr>
<h4>4. 优化策略与开发实践</h4>
<ul>
<li><strong>避免内存泄漏</strong>：<ul>
<li>使用<code>WeakMap</code>/<code>WeakSet</code>管理缓存（弱引用不阻止垃圾回收）。</li>
<li>及时清理无用的事件监听器和定时器。</li>
</ul>
</li>
<li><strong>性能监控</strong>：<ul>
<li><strong>Chrome DevTools</strong>：<ul>
<li><strong>Memory面板</strong>：通过<code>Heap Snapshot</code>分析内存分布。</li>
<li><strong>Performance面板</strong>：记录内存变化趋势，检测泄漏。</li>
</ul>
</li>
<li>代码中监控<code>performance.memory</code>（浏览器支持有限）。</li>
</ul>
</li>
</ul>
<hr>
<h4>5. 面试常见问题</h4>
<ul>
<li><strong>Q1：<code>WeakMap</code>和<code>Map</code>的区别？</strong><ul>
<li><code>WeakMap</code>键为弱引用，不阻止键对象被回收；<code>Map</code>键为强引用。</li>
</ul>
</li>
<li><strong>Q2：闭包会导致内存泄漏吗？</strong><ul>
<li>不一定。只有闭包引用外部变量且未释放时可能泄漏，需主动解除引用。</li>
</ul>
</li>
</ul>
<hr>
<h4>6. 示例代码</h4>
<pre><code class="language-javascript">// 内存泄漏示例（未清理定时器）
function leak() {
  const data = new Array(1000000).fill(&#39;*&#39;);
  setInterval(() =&gt; {
    console.log(data.length); // data一直被闭包引用
  }, 1000);
}

// 修复：清理定时器
function fixLeak() {
  const data = new Array(1000000).fill(&#39;*&#39;);
  const timer = setInterval(() =&gt; {
    console.log(data.length);
  }, 1000);
  // 在不需要时清理
  setTimeout(() =&gt; clearInterval(timer), 5000);
}
</code></pre>
<hr>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[LeetCode 热题 100 通用解题策略]]></title>
            <link>https://zzfzzf.com/post/1914641134945046528</link>
            <guid>1914641134945046528</guid>
            <pubDate>Wed, 04 Jun 2025 01:56:53 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>回溯算法的核心在于通过深度优先搜索（DFS）与状态回退遍历问题的所有可能解</p>
</blockquote>
<h3>通用模版</h3>
<pre><code class="language-js">function backtrack(choices, path, used) {
    // 终止条件：找到一个完整解
    if (path.length === choices.length) {
        result.push([...path]); 
        return;
    }
    
    // 遍历所有选择
    for (let i = 0; i &lt; choices.length; i++) {
        if (used[i]) continue; // 剪枝：跳过已用元素
        used[i] = true;       // 做选择
        path.push(choices[i]);
        backtrack(choices, path, used); // 递归下一层
        path.pop();            // 撤销选择
        used[i] = false;
    }
}
</code></pre>
<h3>交换法</h3>
<h3>剪枝优化策略</h3>
<h2>一、数组与双指针</h2>
<h3>1. 两数之和（Two Sum）</h3>
<p><strong>问题</strong>：在数组中找到两个数，使它们的和等于目标值。<br><strong>解法</strong>：哈希表记录已遍历元素。  </p>
<pre><code class="language-javascript">var twoSum = function(nums, target) {
    const map = new Map();
    for (let i = 0; i &lt; nums.length; i++) {
        const complement = target - nums[i];
        if (map.has(complement)) {
            return [map.get(complement), i];
        }
        map.set(nums[i], i);
    }
};
</code></pre>
<h3>2. 三数之和（3Sum）</h3>
<p><strong>问题</strong>：找出数组中所有不重复的三元组，使其和为0。<br><strong>解法</strong>：排序 + 双指针。  </p>
<pre><code class="language-javascript">var threeSum = function(nums) {
    nums.sort((a, b) =&gt; a - b);
    const result = [];
    for (let i = 0; i &lt; nums.length - 2; i++) {
        if (i &gt; 0 &amp;&amp; nums[i] === nums[i - 1]) continue; // 去重
        let left = i + 1, right = nums.length - 1;
        while (left &lt; right) {
            const sum = nums[i] + nums[left] + nums[right];
            if (sum === 0) {
                result.push([nums[i], nums[left], nums[right]]);
                while (left &lt; right &amp;&amp; nums[left] === nums[left + 1]) left++; // 去重
                while (left &lt; right &amp;&amp; nums[right] === nums[right - 1]) right--;
                left++;
                right--;
            } else if (sum &lt; 0) {
                left++;
            } else {
                right--;
            }
        }
    }
    return result;
};
</code></pre>
<hr>
<h2>二、动态规划</h2>
<h3>1. 爬楼梯（Climbing Stairs）</h3>
<p><strong>问题</strong>：每次爬1或2阶台阶，求爬到第 <code>n</code> 阶的方法数。<br><strong>状态转移</strong>：<code>dp[i] = dp[i-1] + dp[i-2]</code>。<br><strong>空间优化</strong>：用两个变量代替数组。  </p>
<pre><code class="language-javascript">var climbStairs = function(n) {
    if (n &lt;= 2) return n;
    let a = 1, b = 2;
    for (let i = 3; i &lt;= n; i++) {
        [a, b] = [b, a + b];
    }
    return b;
};
</code></pre>
<h3>2. 打家劫舍（House Robber）</h3>
<p><strong>问题</strong>：不能偷相邻房屋，求最大金额。<br><strong>状态转移</strong>：<code>dp[i] = max(dp[i-1], dp[i-2] + nums[i])</code>。  </p>
<pre><code class="language-javascript">var rob = function(nums) {
    let prev = 0, curr = 0;
    for (const num of nums) {
        [prev, curr] = [curr, Math.max(curr, prev + num)];
    }
    return curr;
};
</code></pre>
<hr>
<h2>三、链表</h2>
<h3>1. 反转链表（Reverse Linked List）</h3>
<p><strong>问题</strong>：反转单链表。<br><strong>解法</strong>：迭代法修改指针方向。  </p>
<pre><code class="language-javascript">var reverseList = function(head) {
    let prev = null, curr = head;
    while (curr) {
        const next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
};
</code></pre>
<h3>2. 合并两个有序链表（Merge Two Sorted Lists）</h3>
<p><strong>解法</strong>：虚拟头节点简化边界处理。  </p>
<pre><code class="language-javascript">var mergeTwoLists = function(l1, l2) {
    const dummy = new ListNode(-1);
    let curr = dummy;
    while (l1 &amp;&amp; l2) {
        if (l1.val &lt; l2.val) {
            curr.next = l1;
            l1 = l1.next;
        } else {
            curr.next = l2;
            l2 = l2.next;
        }
        curr = curr.next;
    }
    curr.next = l1 || l2;
    return dummy.next;
};
</code></pre>
<hr>
<h2>四、二叉树</h2>
<h3>1. 二叉树的最大深度（Maximum Depth of Binary Tree）</h3>
<p><strong>解法</strong>：递归分治。  </p>
<pre><code class="language-javascript">var maxDepth = function(root) {
    if (!root) return 0;
    return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
};
</code></pre>
<h3>2. 二叉树的层序遍历（Binary Tree Level Order Traversal）</h3>
<p><strong>解法</strong>：队列实现 BFS。  </p>
<pre><code class="language-javascript">var levelOrder = function(root) {
    if (!root) return [];
    const result = [], queue = [root];
    while (queue.length) {
        const level = [], size = queue.length;
        for (let i = 0; i &lt; size; i++) {
            const node = queue.shift();
            level.push(node.val);
            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }
        result.push(level);
    }
    return result;
};
</code></pre>
<hr>
<h2>五、回溯与DFS</h2>
<h3>1. 全排列（Permutations）</h3>
<p><strong>解法</strong>：回溯 + 路径记录。  </p>
<pre><code class="language-javascript">var permute = function(nums) {
    const result = [];
    const backtrack = (path, used) =&gt; {
        if (path.length === nums.length) {
            result.push([...path]);
            return;
        }
        for (let i = 0; i &lt; nums.length; i++) {
            if (used[i]) continue;
            used[i] = true;
            path.push(nums[i]);
            backtrack(path, used);
            path.pop();
            used[i] = false;
        }
    };
    backtrack([], []);
    return result;
};
</code></pre>
<h3>2. 组合总和（Combination Sum）</h3>
<p><strong>解法</strong>：排序剪枝 + 回溯。  </p>
<pre><code class="language-javascript">var combinationSum = function(candidates, target) {
    candidates.sort((a, b) =&gt; a - b);
    const result = [];
    const backtrack = (start, path, sum) =&gt; {
        if (sum === target) {
            result.push([...path]);
            return;
        }
        for (let i = start; i &lt; candidates.length; i++) {
            if (sum + candidates[i] &gt; target) break; // 剪枝
            path.push(candidates[i]);
            backtrack(i, path, sum + candidates[i]);
            path.pop();
        }
    };
    backtrack(0, [], 0);
    return result;
};
</code></pre>
<hr>
<h2>六、通用解题策略</h2>
<h3>1. 动态规划问题四步法</h3>
<ul>
<li><strong>定义状态</strong>：明确 <code>dp[i]</code> 的含义。</li>
<li><strong>状态转移方程</strong>：推导递推关系。</li>
<li><strong>初始条件</strong>：设置初始值。</li>
<li><strong>空间优化</strong>：用变量替代数组（如滚动数组）。</li>
</ul>
<h3>2. 回溯问题模板</h3>
<ul>
<li><strong>路径</strong>：记录已选择的值。</li>
<li><strong>选择列表</strong>：当前可选的元素。</li>
<li><strong>终止条件</strong>：满足条件时保存结果。</li>
<li><strong>剪枝</strong>：跳过无效分支（如重复元素去重）。</li>
</ul>
<h3>3. 链表问题技巧</h3>
<ul>
<li><strong>虚拟头节点</strong>：简化头节点处理逻辑。</li>
<li><strong>快慢指针</strong>：解决环检测、中间节点等问题。</li>
</ul>
<h3>0-1背包问题</h3>
<p><strong>特点</strong>：每个物品仅能选一次，需倒序遍历背包容量。  </p>
<table>
<thead>
<tr>
<th>题号</th>
<th>题目名称</th>
<th>核心考点</th>
</tr>
</thead>
<tbody><tr>
<td><strong><a href="https://leetcode.com/problems/partition-equal-subset-sum/">416</a></strong></td>
<td>分割等和子集</td>
<td>能否装满背包（布尔型DP）</td>
</tr>
<tr>
<td><strong><a href="https://leetcode.com/problems/last-stone-weight-ii/">1049</a></strong></td>
<td>最后一块石头的重量 II</td>
<td>背包最大价值（最接近 sum/2）</td>
</tr>
<tr>
<td><strong><a href="https://leetcode.com/problems/target-sum/">494</a></strong></td>
<td>目标和</td>
<td>装满背包的方案数（组合数）</td>
</tr>
<tr>
<td><strong><a href="https://leetcode.com/problems/ones-and-zeroes/">474</a></strong></td>
<td>一和零</td>
<td>二维费用背包（双重容量限制）</td>
</tr>
</tbody></table>
<hr>
<h3>♾️ <strong>二、完全背包问题</strong></h3>
<p><strong>特点</strong>：物品可无限选，需正序遍历背包容量。  </p>
<table>
<thead>
<tr>
<th>题号</th>
<th>题目名称</th>
<th>核心考点</th>
</tr>
</thead>
<tbody><tr>
<td><strong><a href="https://leetcode.com/problems/coin-change/">322</a></strong></td>
<td>零钱兑换</td>
<td>最小物品数（最值问题）</td>
</tr>
<tr>
<td><strong><a href="https://leetcode.com/problems/coin-change-ii/">518</a></strong></td>
<td>零钱兑换 II</td>
<td>组合数（顺序无关）</td>
</tr>
<tr>
<td><strong><a href="https://leetcode.com/problems/combination-sum-iv/">377</a></strong></td>
<td>组合总和 Ⅳ</td>
<td>排列数（顺序相关）</td>
</tr>
<tr>
<td><strong><a href="https://leetcode.com/problems/perfect-squares/">279</a></strong></td>
<td>完全平方数</td>
<td>最小物品数（建模为平方数）</td>
</tr>
<tr>
<td><strong><a href="https://leetcode.com/problems/word-break/">139</a></strong></td>
<td>单词拆分</td>
<td>布尔型DP（字符串分割）</td>
</tr>
</tbody></table>
<hr>
<h3>💡 <strong>三、关键技巧总结</strong></h3>
<ol>
<li><strong>组合 vs 排列</strong>：  <ul>
<li><strong>组合数</strong>（顺序无关）：先遍历物品，再遍历背包（如 <a href="https://leetcode.com/problems/coin-change-ii/">518</a>）。  </li>
<li><strong>排列数</strong>（顺序相关）：先遍历背包，再遍历物品（如 <a href="https://leetcode.com/problems/combination-sum-iv/">377</a>）。</li>
</ul>
</li>
<li><strong>最值问题</strong>：  <ul>
<li>初始化 <code>dp</code> 为极大值（最小个数）或极小值（最大价值），边界 <code>dp[0]=0</code>。</li>
</ul>
</li>
<li><strong>空间优化</strong>：  <ul>
<li>一维 <code>dp</code> 数组可覆盖多数场景，注意遍历方向（0-1背包倒序、完全背包正序）。</li>
</ul>
</li>
</ol>
<blockquote>
<p>建议按分类顺序刷题，重点理解状态定义（<code>dp[i]</code> 含义）和转移方程。</p>
</blockquote>
<h3>例子</h3>
<table>
<thead>
<tr>
<th>问题类型</th>
<th>典型例题</th>
<th>常用算法</th>
<th>关键技巧</th>
</tr>
</thead>
<tbody><tr>
<td>最优化问题</td>
<td>打家劫舍、爬楼梯</td>
<td>动态规划</td>
<td>状态压缩、贪心选择</td>
</tr>
<tr>
<td>组合/排列</td>
<td>电话号码字母组合</td>
<td>回溯/DFS</td>
<td>剪枝、路径记录</td>
</tr>
<tr>
<td>子数组/子序列</td>
<td><a href="https://leetcode.cn/problems/maximum-subarray">最大子数组和</a></td>
<td>动态规划/滑动窗口</td>
<td>前缀和、状态定义</td>
</tr>
<tr>
<td>图遍历</td>
<td>岛屿数量</td>
<td>BFS/DFS</td>
<td>访问标记、方向数组</td>
</tr>
<tr>
<td>链表操作</td>
<td>反转链表</td>
<td>迭代/递归</td>
<td>双指针、虚拟头节点</td>
</tr>
</tbody></table>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[沪港澳]]></title>
            <link>https://zzfzzf.com/post/1898918004419203072</link>
            <guid>1898918004419203072</guid>
            <pubDate>Tue, 06 May 2025 09:19:34 GMT</pubDate>
            <content:encoded><![CDATA[<h2>证件与网络</h2>
<ul>
<li>港澳通行证（有效期≥6个月，含有效签注）</li>
<li>内地身份证原件</li>
<li>准备3000港币现金</li>
<li>网络：支付宝购买流量包：在支付宝搜索 “境外上网”，可购买对应服务商的香港流量包。例如，移动的 3 天流量包价格约 24 元，无需单独开通漫游，到达香港后将手机设置为漫游状态即可使用。</li>
</ul>
<h2>开户准备</h2>
<ul>
<li>下载官方 App：建议提前下载好 HSBC HK 和 BOC HK 官方 App，因为在香港使用手机卡购买流量的速度可能较慢，提前下载可节省时间。</li>
<li>证件携带：携带好港澳通行证和身份证原件。</li>
<li>获取出入境记录：过关后半小时左右，微信搜索 “移民局 12367 小程序”，点击 “出入境记录查询”，下载记录文件并保存，开户时会用到。</li>
<li>转换插头（英标插座）</li>
<li>身份证✅</li>
<li>港澳通行证✅</li>
<li>通关小票</li>
<li>信用卡账单✅</li>
<li>在职证明</li>
<li>个税证明✅</li>
<li>社保证明✅</li>
<li>银行工资流水✅</li>
<li>个人所得税证明✅</li>
<li>收入证明✅</li>
<li>银行存款证明✅</li>
<li>股票交易证明</li>
<li>基金账单</li>
<li>公司股票激励账单</li>
</ul>
<h3>攻略</h3>
<ul>
<li><p>1️⃣打开之前现在的HSBC HK 官方App；</p>
</li>
<li><p>2️⃣选择我没有任何账户-&gt;身处香港，没有香港身份证-&gt;汇丰one-&gt;开户原因按需选择-&gt;填写邮箱-&gt;短信验证；</p>
</li>
<li><p>3️⃣反正操作按照他的页面提示，根据自己情况填写就好，很简单，过程中需要使用拍大陆身份证件和港澳通行证、人脸活体检测等，其中港澳通行证需要用手机NFC读取，提交之前下载的出入境文件，汇丰地址填写需要用英文或者拼音，我用的拼音；</p>
</li>
<li><p>4️⃣填写完上面的信息后，基本秒开通成功，马上就给号了。</p>
</li>
<li><p>5️⃣上面账户开通成功后，如果在App上立即注册手机银行，一定要记住自己的用户名，并且这个用户名好像有UU反馈不能修改，大家在取名字的时候还是想好了再取；</p>
</li>
<li><p>中银香港的流程和汇丰的差不大多，除了地址可以用中文，不需要NFC读取港澳通行证，其他流程也没什么差异。</p>
</li>
</ul>
<p><img src="https://w.zzfzzf.com/img/1742263447439.png" alt="CleanShot 2025-03-18 at 10.03.58@2x.png"></p>
<h2>汇丰银行（HSBC）开户流程</h2>
<h3>线上开户</h3>
<ol>
<li>身处香港时，打开 HSBC HK 官方 App。</li>
<li>选择 “我没有任何账户” -&gt; “身处香港，没有香港身份证” -&gt; “汇丰 one” -&gt; 按需选择开户原因 -&gt; 填写邮箱 -&gt; 进行短信验证。</li>
<li>按照页面提示，根据自身情况填写信息，过程中需拍摄大陆身份证件和港澳通行证、进行人脸活体检测。港澳通行证需用手机 NFC 读取，提交之前下载的出入境文件，汇丰地址填写需用英文或拼音（建议用拼音）。</li>
<li>信息填写完成后，基本可秒开通成功并获取账号。</li>
<li>账户开通成功后，若在 App 上立即注册手机银行，务必记住用户名，因为有用户反馈用户名无法修改。<blockquote>
<p>注意：一定要在香港境内进行线上开户操作，且手机需具备 NFC 功能，用于读取港澳通行证信息。</p>
</blockquote>
</li>
</ol>
<h3>线下</h3>
<p><img src="https://w.zzfzzf.com/img/1742262525261.png" alt="CleanShot 2025-03-18 at 09.48.20@2x.png"></p>
<ol>
<li>预约方式：可通过 “汇丰香港”微信 线下预约 One Account，也可直接到现场与门口接待人员说明开卡需求，接待人员会给予取号（部分门店在二楼）。票上有二维码，扫描后可通过微信查看排队顺序。</li>
<li>排队建议：若前方排队人数不超过 10 人，建议留在附近等候（避免空号）；若排队人数在 20 人以上，可在附近适当溜达，随时刷新关注排队动态。</li>
<li>开卡流程：轮到办理时，提供港澳通行证、通关小票，并提前下载好汇丰香港 APP。工作人员会询问开卡用途，可直接说明用于买港股或储蓄。操作过程中，熟练的业务员可能会直接帮忙填写部分信息。地址部分建议要求自己填写，以避免邮寄地址出错。</li>
<li>开卡结果：若顺利，会填写一份问卷，之后工作人员会现场下卡，并给予密码函。随后在楼下 ATM 修改密码并存钱即可。若不顺利，业务员会告知回去等邮件（部分重名人员可能会被系统卡住审核）。若业务员确认已开好卡，只需回家耐心等待。</li>
</ol>
<h2>中国银行香港分行（BOC HK）开户流程</h2>
<h3>线上开户</h3>
<p>使用中银香港在线开户</p>
<h3>线下开户</h3>
<p>提前7天仔<a href="https://transaction.bochk.com/whk/form/openAccount/input.action?lang=zh_HK">wx公众号</a>预约到了下午3.30的号。</p>
<p>在汇丰办好后，不想等到下午3.30，就walking葵涌广场分行。
不看pdf打印版本。现场看电子pdf</p>
<ul>
<li>银行卡流水</li>
<li>投资流水（2个月）</li>
<li>地址证明（招行信用卡）</li>
</ul>
<p>在经理看完资料后，引导app填写申请信息。在APP提交资料后，取号。
进入柜台开卡。发送pdf文件到银行邮箱。在确认好所有信息后。对讲机叫来更高级别经理刷卡确认。现场取实体卡发卡。之后到取款机查询余额激活，然后到存款机存入至少1000港币。</p>
<p><img src="https://w.zzfzzf.com/img/1742261670584.png" alt="CleanShot 2025-03-18 at 09.34.09@2x.png"></p>
<h2>开户方式对比</h2>
<table>
<thead>
<tr>
<th></th>
<th><strong>汇丰银行</strong></th>
<th><strong>中银香港</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>线上开户</strong></td>
<td>✅ 支持（需本人在香港）<br/>✅ 三字姓名秒批<br/>✅ 两字姓名需审核（审核期间可离港）</td>
<td>❌ 流程较严格<br/>⚠️ 可能要求股票账户/工资流水/工作证明</td>
</tr>
<tr>
<td><strong>线下开户</strong></td>
<td>✅ 仅需三件套：<br/>身份证+港澳通行证+入境小票</td>
<td>✅ 需携带纸质版补充材料</td>
</tr>
</tbody></table>
<ul>
<li><a href="https://www.v2ex.com/t/1030463">2024 年 4 月香港银行卡开户总结</a></li>
<li><a href="https://linktr.ee/webank88">https://linktr.ee/webank88</a></li>
<li><a href="https://dr.leviding.com">https://dr.leviding.com</a></li>
<li>汇丰香港开户教程： <a href="https://dr.leviding.com/docs/finance/open-a-bank-account-hsbc-hk">https://dr.leviding.com/docs/finance/open-a-bank-account-hsbc-hk</a></li>
<li>中银香港开户教程： <a href="https://dr.leviding.com/docs/finance/open-a-bank-account-boc-hk">https://dr.leviding.com/docs/finance/open-a-bank-account-boc-hk</a>
<img src="https://w.zzfzzf.com/img/1741677313808.png" alt="image.png"></li>
</ul>
<p><img src="https://w.zzfzzf.com/img/1746455352709.jpg" alt="5c0a73116b9470228d23bd1df3f0b715.jpg"></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[结合 AI 开发 VSCode 插件]]></title>
            <link>https://zzfzzf.com/post/1888059466285322240</link>
            <guid>1888059466285322240</guid>
            <pubDate>Sat, 08 Feb 2025 02:57:09 GMT</pubDate>
            <content:encoded><![CDATA[<p>最近 AI 比较火，若想结合 AI 来提效，最容易入手的就是结合 AI 开发 VSCode 插件。目标开发一个 Git 消息生成插件，借助 AI 的强大能力，自动生成规范且准确的 Git 提交消息，提升开发效率。</p>
<h2>成果</h2>
<p><a href="https://marketplace.visualstudio.com/items?itemName=ai-git.ai-git">https://marketplace.visualstudio.com/items?itemName=ai-git.ai-git</a></p>
<h2>准备</h2>
<h3>环境准备</h3>
<p>在开始开发之前，你需要确保已经安装了以下工具：</p>
<ul>
<li><strong>Node.js 和 npm</strong>：VSCode 插件基于 Node.js 开发，因此需要安装 Node.js 和其包管理工具 npm。你可以从 <a href="https://nodejs.org/">Node.js 官方网站</a> 下载并安装适合你操作系统的版本。安装完成后，在终端中运行以下命令来验证安装是否成功：</li>
</ul>
<pre><code class="language-bash">node -v
npm -v
</code></pre>
<ul>
<li><strong>Yeoman 和 VSCode 扩展生成器</strong>：Yeoman 是一个脚手架工具，而 VSCode 扩展生成器可以帮助我们快速创建 VSCode 插件的项目结构。使用以下命令进行全局安装：</li>
</ul>
<pre><code class="language-bash">npm install -g yo generator-code
</code></pre>
<h3>AI 服务选择</h3>
<p>为了实现自动生成 Git 消息的功能，我们需要借助 AI 服务。目前有许多可用的 AI 平台，选择硅基流动的API服务</p>
<ol>
<li><strong>注册 硅基流动 账号</strong>：访问 <a href="https://cloud.siliconflow.cn/">硅基流动 官方网站</a> 进行注册。</li>
<li><strong>获取 API Key</strong>：注册并登录后，在 OpenAI 平台的控制台中生成 API Key，这将用于后续调用 AI 服务。注意要妥善保管你的 API Key，避免泄露。</li>
</ol>
<h3>项目初始化</h3>
<p>使用 VSCode 扩展生成器创建一个新的插件项目。在终端中执行以下命令：</p>
<pre><code class="language-bash">yo code
</code></pre>
<p>按照提示进行选择：</p>
<ul>
<li><strong>What type of extension do you want to create?</strong>：选择 <code>New Extension (TypeScript)</code>，因为 TypeScript 提供了类型检查，能让代码更加健壮。</li>
<li><strong>What&#39;s the name of your extension?</strong>：输入插件的名称，例如 <code>git-message-generator</code>。</li>
<li><strong>What&#39;s the identifier of your extension?</strong>：一般保持默认即可。</li>
<li><strong>What&#39;s the description of your extension?</strong>：输入插件的描述信息，如 “Automatically generate Git commit messages using AI”。</li>
<li><strong>Initialize a git repository?</strong>：根据个人需求选择是否初始化 Git 仓库。</li>
<li><strong>Which package manager to use?</strong>：选择 <code>npm</code>。</li>
</ul>
<p>完成上述步骤后，Yeoman 会自动生成项目的基本结构和文件。</p>
<h3>项目结构介绍</h3>
<p>生成的项目主要包含以下重要文件和目录：</p>
<ul>
<li><code>package.json</code>：定义插件的元数据、激活事件、命令等信息。</li>
<li><code>src/extension.ts</code>：插件的入口文件，包含插件的主要逻辑。</li>
<li><code>tsconfig.json</code>：TypeScript 的配置文件，用于指定编译选项。</li>
<li><code>.vscode</code> 目录：包含调试和任务配置文件。</li>
</ul>
<h2>需要用到的网站</h2>
<ul>
<li>参考 <a href="https://juejin.cn/post/7076649162653040647">https://juejin.cn/post/7076649162653040647</a></li>
<li><a href="https://marketplace.visualstudio.com/">https://marketplace.visualstudio.com/</a></li>
<li><a href="https://dev.azure.com/">https://dev.azure.com/</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[杭州到长乐，奔赴一场游神之约]]></title>
            <link>https://zzfzzf.com/post/1886778281366130688</link>
            <guid>1886778281366130688</guid>
            <pubDate>Tue, 04 Feb 2025 14:06:11 GMT</pubDate>
            <content:encoded><![CDATA[<p>最近刷抖音，总是被福建长乐游神的视频霸屏。那热闹非凡的场面，人们抬着神像浩浩荡荡前行，鞭炮齐鸣、锣鼓喧天，独特又神秘的氛围一下子就把我吸引住了。当时我正在杭州游玩，身为一个说走就走的旅行爱好者，当下就决定直接自驾前往福州长乐，亲身去感受这场民俗文化盛宴 。</p>
<h3>自驾行程</h3>
<p>中午 12 点半，我满怀期待地从杭州出发，本以为一脚油门就能直达长乐，可导航显示足足要 7 个小时，这让我瞬间清醒。长途驾驶可不是小事，得悠着点。在南阳服务区，我简单吃了个泡面，价格非常平价，吃饱喝足后继续出发。越往南走，窗外的景色越发让我这个很少见到山的人兴奋不已。连绵不绝的山峦不断映入眼帘，那层层叠叠的绿色，与我平日熟悉的平原景象截然不同。进入温州段后，我发现车子的电耗飙升到了 35+，好奇之下看了一下手表的高度计，竟已接近海拔 800 米。一路飞驰，傍晚 8 点半，顺利抵达宁德。入住酒店稍作休息后，我前往宁德万达广场，品尝到了当地颇有名气的唐沫茶兮。清甜的茶香在舌尖散开，疲惫之感也随之消散。宁德的夜景很美，和杭州的灯火辉煌不同，这里多了几分海滨城市的静谧。夜晚漫步在宁德的街头，品尝着当地特色的小吃，感受着这座城市的独特魅力，疲惫感也渐渐消散。</p>
<p>第二天，我精神饱满地再次踏上旅程。宁德到长乐的路程比我想象中要短，仅花了一个半小时就顺利抵达。一路南下，窗外的景色从江南的平原渐渐过渡到福建的丘陵地带，山峦起伏，绿意盎然。随着离长乐越来越近，我的心情也愈发激动，对即将到来的游神活动充满了期待。</p>
<p><img src="https://w.zzfzzf.com/img/1738680189491.PNG" alt="IMG_2707.PNG"></p>
<p><img src="https://w.zzfzzf.com/img/1738680063953.jpg" alt="fcbf0b3a0b23cf4aa3d9d45b8e248502.jpg"></p>
<p><img src="https://w.zzfzzf.com/img/1738681209752.webp" alt="4749efea28656ce0b111b360ec6afef9.webp"></p>
<h3>到达长乐</h3>
<p>抵达长乐后，时间尚早，我径直前往商场，品尝了一顿当地特色美食当作午餐。饭后，我又马不停蹄地前往金峰镇台瑶村。刚到台瑶村，我就被眼前的景象震惊了，这里早已是人山人海，热闹非凡，大家都在翘首以盼游神活动的开始。放眼望去，停车场里满满当当停的都是浙江、江苏、上海牌照的车，看来这场盛大的游神活动吸引了不少周边地区的游客。我按照事先预定好的酒店办理入住，稍作休息后，便迫不及待地融入这人潮之中，感受着这座村庄特有的热闹氛围。因为人太多，很难找到合适的位置拍摄，不少人都带着无人机拍摄。</p>
<p><img src="https://w.zzfzzf.com/img/1738720855153.webp" alt="IMG_2353.webp"></p>
<p><img src="https://w.zzfzzf.com/img/1738720886149.webp" alt="IMG_2357.webp"></p>
<h3>热闹的台瑶村</h3>
<p><img src="https://w.zzfzzf.com/img/1738680498804.webp" alt="DSC08863.webp"></p>
<p><img src="https://w.zzfzzf.com/img/1738680639652.webp" alt="DSC08798.webp"></p>
<p>下午五点，随着一声响亮的鞭炮声，期待已久的游神正式开始。现场瞬间彩旗飘扬，锣鼓喧天，热闹的氛围被推向了高潮。游神队伍由众多村民组成，他们身着传统服饰，抬着神明的塑像，浩浩荡荡地走在街道上。每尊神像都装饰得金碧辉煌，在阳光的照耀下熠熠生辉。我在如潮水般的人群中艰难穿梭，一边感受着现场热烈的氛围，一边用相机记录下这精彩的瞬间。</p>
<p><img src="https://w.zzfzzf.com/img/1738681088163.webp" alt="DSC08948.webp"></p>
<h3>沉浸式感受过年氛围</h3>
<p><img src="https://w.zzfzzf.com/img/1738680980004.webp" alt="DSC08775.webp">
游神活动持续了很久，让我深深沉浸在这浓厚的过年氛围中。鞭炮声从游神开始就未曾停歇，噼里啪啦的声响不断刺激着我的感官，空气中弥漫着火药的味道，那是独属于新年的气息。抬头望去，烟花在夜空中此起彼伏地绽放，将整个天空照得如同白昼，五彩斑斓的光芒倒映在人们兴奋的脸庞上。</p>
<p>更让我触动的是，那些小朋友们虽然年纪小，却也有模有样地学着游神队伍的步伐。他们稚嫩的脸上满是认真，一招一式都透着对传统文化的热爱。看着他们，我真切地感受到了长乐游神这项民俗文化传承的活力与希望。在这里，过年不只是一个节日，更是一次文化的传承与延续，每一个环节、每一个举动都饱含着当地人对传统的尊重和对未来的期许 。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[2025年1月机械键盘选购]]></title>
            <link>https://zzfzzf.com/post/1879057429254115328</link>
            <guid>1879057429254115328</guid>
            <pubDate>Tue, 14 Jan 2025 06:46:16 GMT</pubDate>
            <content:encoded><![CDATA[<h2>1</h2>
<ul>
<li>23 年买的 VGN N75pro, 199 三模, 现在应该换代了</li>
<li>RK65</li>
<li>VGN98Pro</li>
<li>VGN v98 pro ，支持三模连接，手感还可以，不到 300 元</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[自部署一个基于大模型的知识库]]></title>
            <link>https://zzfzzf.com/post/1864955598601195520</link>
            <guid>1864955598601195520</guid>
            <pubDate>Fri, 06 Dec 2024 08:50:38 GMT</pubDate>
            <content:encoded><![CDATA[<p>分享如何使用 <strong>Dify</strong> 链接 <strong>Notion</strong> 并以 <strong>通义千问</strong> 作为模型供应商，来构建一个自部署的大模型知识库。通过这种方式，您可以自定义您的知识库，实现高效的自动化查询与知识管理。</p>
<h4>使用地址</h4>
<ul>
<li><strong>Dify</strong> 主页： <a href="https://dify.zzfzzf.com/">https://dify.zzfzzf.com/</a></li>
<li><strong>Dify</strong> 官网：<a href="https://dify.ai/zh">https://dify.ai/zh</a></li>
</ul>
<h4>Dify 部署指南</h4>
<p>Dify 是一个开源的知识库平台，能够连接到多个大模型供应商，提供基于自然语言的查询服务。它支持与 <strong>Notion</strong> 的集成，可以帮助您将 <strong>Notion</strong> 中的内容转换为知识库并与大模型进行交互。</p>
<p><strong>Dify</strong> 采用 <strong>Docker Compose</strong> 进行部署，以下是参考的部署教程：</p>
<ul>
<li><a href="https://docs.dify.ai/zh-hans/getting-started/install-self-hosted/docker-compose">Dify Docker Compose 部署教程</a></li>
</ul>
<p>部署步骤简述：</p>
<ol>
<li>克隆 Dify 仓库并进入目录。</li>
<li>配置 <code>.env</code> 文件以设置 Notion API 密钥和大模型服务信息。</li>
<li>使用 <code>docker-compose</code> 启动服务。</li>
<li>通过浏览器访问 Dify 界面进行管理和查询。</li>
</ol>
<h4>通义千问 - 大模型供应商</h4>
<p><strong>通义千问</strong> 是一种高效的自然语言处理模型，能够理解和处理复杂的知识查询任务。您可以将其作为后端模型集成到 Dify 中，利用其强大的语义理解和生成能力来优化知识库查询结果。</p>
<h4>其他模型供应商</h4>
<ul>
<li><p><strong>Ollama</strong>：<a href="https://ollama.com/">官网链接</a><br>Ollama 提供多种大模型接口，您可以根据需要选择合适的模型进行集成。</p>
</li>
<li><p><strong>文心一言</strong><br>文心一言是百度推出的中文大语言模型，适合中文语境的处理和查询任务。</p>
</li>
</ul>
<h4>优化文章内容以提高模型理解能力</h4>
<p>为了让大模型更好地理解和处理文章内容，以下是几条优化建议：</p>
<ol>
<li><p><strong>清晰的结构和标题</strong><br>确保文章具有清晰的结构，包括标题、段落和列表等。每个段落的开头应有简洁的引导句，标题应能准确概括段落的核心内容。</p>
</li>
<li><p><strong>简洁明了的语言</strong><br>文章内容应避免过于复杂的句子和术语。用简单明了的语言表述观点，有助于提高模型的理解效率。</p>
</li>
<li><p><strong>关键词优化</strong><br>在文章中合理地使用与主题相关的关键词，以便大模型能够更好地提取文章的核心信息。</p>
</li>
<li><p><strong>提供足够的上下文</strong><br>在向模型提问时，确保提供足够的上下文信息。包括问题背景、相关数据或事件，这有助于模型更准确地理解问题并生成有效的答案。</p>
</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[2025年1月mac外接显示器选购]]></title>
            <link>https://zzfzzf.com/post/1857621309706473472</link>
            <guid>1857621309706473472</guid>
            <pubDate>Sat, 16 Nov 2024 03:06:47 GMT</pubDate>
            <content:encoded><![CDATA[<h2>背景</h2>
<ol>
<li><strong>分辨率</strong>：4K/2K（优先考虑4K以提升编程体验）</li>
<li><strong>尺寸</strong>：24寸/27寸（27寸更适合长时间工作）</li>
<li><strong>用途</strong>：主要编程使用</li>
<li><strong>需求</strong>：无需高刷新率（60Hz 即可满足）</li>
<li><strong>加分项</strong>：支持反向供电、屏幕可旋转</li>
</ol>
<h2>推荐显示器</h2>
<h3>Dell U2723QE ¥2879～¥4399</h3>
<p><a href="https://item.jd.com/10020755592046.html">https://item.jd.com/10020755592046.html</a></p>
<ul>
<li><strong>分辨率</strong>：4K UHD（3840x2160）</li>
<li><strong>尺寸</strong>：27寸</li>
<li><strong>特色</strong>：<ul>
<li>支持 USB-C 供电（最高 90W）</li>
<li>支持屏幕旋转</li>
<li>出色的色彩表现（99% sRGB，支持 HDR）</li>
</ul>
</li>
<li><strong>参考价格</strong>：¥3481.51</li>
</ul>
<hr>
<h3>LG 27GP95U ¥2749-¥4199</h3>
<ul>
<li><strong>分辨率</strong>：4K UHD（3840x2160）</li>
<li><strong>尺寸</strong>：27寸</li>
<li><strong>特色</strong>：<ul>
<li>出厂校准，适合色彩要求高的场景</li>
<li>提供丰富的接口（支持 HDMI 2.1 和 USB-C）</li>
</ul>
</li>
<li><strong>参考价格</strong>：¥3173.06</li>
<li><strong>链接</strong>：<a href="https://www.lg.com">LG 27GP95U 产品页面</a></li>
</ul>
<hr>
<h3>Redmi A27U ¥1389-¥1499</h3>
<ul>
<li><strong>分辨率</strong>：4K UHD（3840x2160）</li>
<li><strong>尺寸</strong>：27寸</li>
<li><strong>特色</strong>：<ul>
<li>高性价比之选，适合预算有限的用户</li>
<li>99% sRGB 色域覆盖</li>
</ul>
</li>
<li><strong>参考价格</strong>：约 ¥1499</li>
<li><strong>链接</strong>：<a href="https://item.jd.com/100117296171.html">红米 A27U 京东详情页</a></li>
</ul>
<h3>LG 27UP850 ¥1619-¥2499</h3>
<p><a href="https://item.jd.com/100022124835.html">https://item.jd.com/100022124835.html</a></p>
<ul>
<li>4K 60HZ</li>
<li>Type-C90W反向充电</li>
<li>95%DCI-P3</li>
<li>内置音箱</li>
<li>可旋转</li>
</ul>
<h3>LG 27UQ750 ¥2199-¥3699</h3>
<p><a href="https://item.jd.com/100066231571.html">https://item.jd.com/100066231571.html</a></p>
<ul>
<li>旋转升降底座</li>
<li>Type-C90W反向充电</li>
<li>内置音箱</li>
<li>4K 144HZ</li>
</ul>
<p><img src="https://w.zzfzzf.com/img/1731730509413.png" alt="CleanShot 2024-11-16 at 12.15.03@2x.png"></p>
<h2>其他备选</h2>
<ul>
<li><strong>LG 27UQ750</strong>：4K，支持 HDR，约 ¥3000。</li>
<li><strong>华硕 ProArt PA248QV</strong>：24寸，16:10 比例，预算有限时可选。</li>
<li><strong>Dell U2720Q</strong>：经典款 4K 屏，功能全面，适合日常使用。</li>
</ul>
<hr>
<h2>总结建议</h2>
<ul>
<li><strong>预算较高</strong>：优先选择 Dell U2723QE 或 LG 27GP95U，提供更好的接口与供电支持。</li>
<li><strong>性价比</strong>：红米 A27U 在 4K 显示器中是高性价比之选。</li>
<li><strong>特殊需求</strong>：需要旋转和供电功能的用户优先考虑 Dell U2723QE。</li>
</ul>
<h2>最终选择</h2>
<p>Dell U2723QE
<a href="https://item.jd.com/100019203679.html">https://item.jd.com/100019203679.html</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[如何在网页中优雅地使用网络字体]]></title>
            <link>https://zzfzzf.com/post/1853476542278012928</link>
            <guid>1853476542278012928</guid>
            <pubDate>Mon, 04 Nov 2024 16:36:58 GMT</pubDate>
            <content:encoded><![CDATA[<p>本文分享一下在个人博客项目中使用网络字体的经验。字体对于一个网站的视觉效果和用户体验都有很大的影响，选择合适的字体能让整个页面看起来更有质感，也更有品牌特色。</p>
<p>为了提升阅读体验，我的博客使用的是<a href="https://github.com/lxgw/LxgwWenKai">霞鹜文楷</a>字体，喜欢这个字体的朋友可以尝试加入自己的项目中~</p>
<p>但是呢，霞鹜文楷在 GitHub 上只提供了 <code>.ttf</code> 格式的文件，而在 web 上最佳的选择是 <code>.woff2</code> 格式，因为它更轻量、更适合网络加载。下面我分享如何在网页中使用这个字体，也会讲讲性能优化的小技巧，让你的网站既好看又流畅。</p>
<hr>
<h2>什么是网络字体？</h2>
<p>网络字体（Web Fonts）就是可以通过网络加载的字体文件，允许我们在网页中使用用户设备上没有安装的字体。不仅能统一品牌视觉，还可以提升整体观感。  </p>
<h3>常见的网络字体来源</h3>
<p>这里介绍几个好用的字体来源，大家可以根据项目需求选择：</p>
<ul>
<li><strong>Google Fonts</strong>：免费、简单易用，并且兼容性好。</li>
<li><strong>Adobe Fonts</strong>：提供了商业级别的字体库。</li>
<li><strong>Font Squirrel</strong>：这里提供了许多免费和商业字体的 <code>.woff2</code> 文件，很适合直接下载应用。</li>
</ul>
<h2>如何引入网络字体</h2>
<p>接下来，我们看看如何加载这些网络字体。  </p>
<h3>使用 CSS <code>@font-face</code> 自定义加载字体</h3>
<p>霞鹜文楷的文件需要手动加入项目中，所以我们可以用 <code>@font-face</code> 来定义加载。  </p>
<pre><code class="language-css">@font-face {
  font-family: &#39;LxgwWenKai&#39;;
  src: url(&#39;path-to-your-font/LxgwWenKai.woff2&#39;) format(&#39;woff2&#39;), /* 推荐格式 */
       url(&#39;path-to-your-font/LxgwWenKai.ttf&#39;) format(&#39;truetype&#39;); /* 备用格式 */
  font-weight: normal;
  font-style: normal;
}
</code></pre>
<h3>从第三方字体库（如 Google Fonts）加载</h3>
<p>如果使用 Google Fonts，就非常简单了，直接用一行 <code>link</code> 标签即可：</p>
<pre><code class="language-html">&lt;link href=&quot;https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&amp;display=swap&quot; rel=&quot;stylesheet&quot;&gt;
</code></pre>
<p>这样引入的好处是简单、稳定，但一些自定义字体则需要手动下载和转换为 <code>.woff2</code> 格式。</p>
<h2>优化字体加载性能</h2>
<p>字体加载优化真的很重要，以下几个方法可以让页面加载更快，用户体验更好。</p>
<ul>
<li><strong>压缩字体文件</strong>：像 <code>.woff2</code> 本身就非常轻量；如果下载的是 <code>.ttf</code> 格式，可以用 <a href="https://www.fontsquirrel.com/tools/webfont-generator">font-squirrel</a> 之类的工具进行格式转换和压缩。</li>
<li><strong>懒加载字体</strong>：对于次要字体，可以考虑懒加载，这样首屏的渲染速度会快一些。</li>
<li><strong>字体子集</strong>：如果字体包比较大，可以创建一个只包含需要字符的子集文件，这样字体文件会小很多。</li>
</ul>
<h2>避免性能问题的小技巧</h2>
<p>使用字体时也要小心避免加载过慢或者页面闪烁的问题：</p>
<ul>
<li><strong>减少字体样式</strong>：尽量精简字体的粗细和样式，这样能减少加载时间。</li>
<li><strong>提前预加载字体</strong>：对主要字体用 <code>rel=&quot;preload&quot;</code> 标签，可以在页面加载前就开始加载字体。<pre><code class="language-html">&lt;link rel=&quot;preload&quot; href=&quot;path-to-your-font/LxgwWenKai.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin&gt;
</code></pre>
</li>
<li><strong>解决字体闪烁问题</strong>：可以使用 <code>font-display: swap;</code> 来避免页面加载字体前内容闪烁。</li>
</ul>
<h2>字体切割</h2>
<p>英文字符的字体文件通常较小，而中文字体文件则较大。如果只需部分中文字符，可以采用字体切割工具，比如 <a href="https://github.com/KonghaYao/cn-font-split">cn-font-split</a>，生成包含所需字符的字体文件，减小体积并提升加载效率。</p>
<h2>字体推荐</h2>
<ul>
<li><a href="https://github.com/tonsky/FiraCode">https://github.com/tonsky/FiraCode</a> 免费 编程字体</li>
<li><a href="https://www.jetbrains.com/lp/mono/">https://www.jetbrains.com/lp/mono/</a> 免费 编程字体</li>
<li><a href="https://github.com/lxgw/LxgwWenKai">https://github.com/lxgw/LxgwWenKai</a> 免费 中文字体</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[ 家用 HomeLab 的搭建]]></title>
            <link>https://zzfzzf.com/post/1846475377208201216</link>
            <guid>1846475377208201216</guid>
            <pubDate>Wed, 16 Oct 2024 08:56:50 GMT</pubDate>
            <content:encoded><![CDATA[<h2>背景</h2>
<ul>
<li><p>路由器限制：家中现有路由器无法很好地满足科学上网和广告拦截的需求。</p>
</li>
<li><p>云服务器劣势：云服务器价格高且配置低，无法满足需求。</p>
</li>
<li><p>媒体需求：期望家庭影院可以替代各种视频会员</p>
</li>
</ul>
<h2>设备选择</h2>
<ul>
<li><p>功耗考虑：家用服务器需要7x24小时不间断运行，因此低功耗设备是首选。</p>
</li>
<li><p>性能要求：如果打算搭建Homelab或小型服务器，需要选择具备一定性能的设备。尽管可以自行组装主板、CPU、内存、硬盘等，但难以做到小巧且节能。</p>
</li>
</ul>
<blockquote>
<p>或者用闲置废旧笔记本（曾经用大学的1T硬盘的笔记本搭的家庭影院 plex+infuse)</p>
</blockquote>
<h2>最终选择</h2>
<ul>
<li><p>淘宝购入零刻的小主机</p>
</li>
<li><p>京东购入内存条和硬盘</p>
</li>
</ul>
<p><img src="https://w.zzfzzf.com/img/1729069207754.png" alt="image.png"></p>
<p><img src="https://w.zzfzzf.com/img/1729069216482.png" alt="image.png"></p>
<p><img src="https://w.zzfzzf.com/img/1729069225047.png" alt="image.png"></p>
<h2>网络拓扑</h2>
<p>由于无线和弱电箱之间只有一根网线，目前无法实现有线Mesh网络。</p>
<p><img src="https://w.zzfzzf.com/img/1729069484034.png" alt="image.png">
服务器宿主系统</p>
<p>这里一般有2个选择</p>
<p><a href="https://pve.proxmox.com/wiki/Main_Page">https://pve.proxmox.com/wiki/Main_Page</a></p>
<p><a href="https://www.vmware.com/products/cloud-infrastructure/esxi-and-esx">https://www.vmware.com/products/cloud-infrastructure/esxi-and-esx</a></p>
<p>最终选择了Proxmox VE（PVE）作为虚拟化平台，因为它基于Debian系统，而ESXi是闭源的。</p>
<p>虚拟机系统过去使用CentOS，但由于停止维护，现已全部迁移到Debian</p>
<p><img src="https://w.zzfzzf.com/img/1729069472153.png" alt="image.png"></p>
<h2>内外网域名</h2>
<p>公网域名：分为备案和不备案域名。备案域名用于上海的服务器及阿里云服务，不备案域名则用于香港的服务器。</p>
<p>内网域名：仅供内网访问，DNS解析由内网DNS服务器控制</p>
<p><img src="https://w.zzfzzf.com/img/1729069461386.png" alt="image.png">
软路由编译</p>
<p>软路由即在电脑上运行一个操作系统，通常具备双网口。使用了开源项目 coolsnowwolf/lede 来编译固件，并将其刷入硬路由或虚拟机中。</p>
<p>也可以做单臂路由（单网口，把设备的dns指到路由器上就行）</p>
<p><img src="https://w.zzfzzf.com/img/1729069444743.png" alt="image.png"></p>
<p>VPN组网</p>
<p><a href="https://www.wireguard.com/">https://www.wireguard.com/</a></p>
<p><a href="https://tailscale.com/">https://tailscale.com/</a></p>
<p>因为电信给的 公网IP 大概一个月会改一次，所以需要ddns服务把 一个域名始终映射到公网ip上。</p>
<h2>科学上网</h2>
<h2>软件</h2>
<p>Openclash插件</p>
<p>Surge(Apple TV、mac、手机通用)</p>
<p>规则生成器</p>
<p><a href="https://github.com/surgioproject/surgio">https://github.com/surgioproject/surgio</a></p>
<h2>节点</h2>
<p>私聊～</p>
<h2>DNS&amp;去广告</h2>
<p>通过 AdGuardHome 实现去广告，并兼顾私有DNS解析与广告拦截功能。</p>
<p>自定义上游dns，负载均衡,使用doh</p>
<p>javascript 取消自动换行 复制<a href="https://dns10.quad9.net/dns-queryhttps://dns.alidns.com/dns-queryhttps://doh.pub/dns-querytls://dns.alidns.comtls://dns.pubtls://dns.google">https://dns10.quad9.net/dns-queryhttps://dns.alidns.com/dns-queryhttps://doh.pub/dns-querytls://dns.alidns.comtls://dns.pubtls://dns.google</a></p>
<p>广告黑名单</p>
<p><a href="https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txthttps://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txthttps://anti-ad.net/easylist.txthttps://adaway.org/hosts.txt">https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txthttps://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txthttps://anti-ad.net/easylist.txthttps://adaway.org/hosts.txt</a></p>
<h2>CI&amp;CD</h2>
<p>参考 FX 系统使用Drone CI连接GitHub，在代码推送后自动打包，并推送到内网的Docker镜像和私有NPM，随后通过K8S拉取代码并部署。</p>
<p><img src="https://w.zzfzzf.com/img/1729069381511.png" alt="image.png"></p>
<h2>K8S集群</h2>
<p>部署的好玩的服务</p>
<pre><code class="language-bash">kubectl get svcNAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                          AGEalert-manager           ClusterIP   10.43.190.52    &lt;none&gt;        9093/TCP                         17dblackbox-exporter-svc   ClusterIP   10.43.97.94     &lt;none&gt;        9115/TCP                         17dblog                    ClusterIP   10.43.94.69     &lt;none&gt;        3000/TCP                         32dblog-server             ClusterIP   10.43.78.129    &lt;none&gt;        8000/TCP                         32dddns                    ClusterIP   10.43.38.229    &lt;none&gt;        9876/TCP                         32ddrone-server-service    ClusterIP   10.43.117.206   &lt;none&gt;        80/TCP                           32ddrone-webhook-svc       ClusterIP   10.43.99.143    &lt;none&gt;        3000/TCP                         31des                      ClusterIP   10.43.44.167    &lt;none&gt;        9200/TCP                         32dgrafana-svc             ClusterIP   10.43.109.216   &lt;none&gt;        3000/TCP                         17dkafka                   ClusterIP   10.43.246.5     &lt;none&gt;        9092/TCP                         32dkubernetes              ClusterIP   10.43.0.1       &lt;none&gt;        443/TCP                          32dnexus                   ClusterIP   10.43.187.33    &lt;none&gt;        8081/TCP,8082/TCP                32dopenspeedtest           ClusterIP   10.43.149.230   &lt;none&gt;        3000/TCP,3001/TCP                31dpostgres-svc            ClusterIP   10.43.10.59     &lt;none&gt;        5432/TCP                         32dprometheus-svc          ClusterIP   10.43.98.41     &lt;none&gt;        9090/TCP                         17dproxy-robot             ClusterIP   10.43.44.178    &lt;none&gt;        80/TCP                           32drabbitmq                NodePort    10.43.80.197    &lt;none&gt;        5672:30017/TCP,15672:30018/TCP   32dredis                   ClusterIP   10.43.206.190   &lt;none&gt;        6379/TCP                         16dumami-app               ClusterIP   10.43.134.81    &lt;none&gt;        3000/TCP                         31d
</code></pre>
<h2>服务监测&amp;个人通知</h2>
<blockquote>
<p>家里网络挂了怎么办、服务挂了怎么办</p>
</blockquote>
<ul>
<li><p>Bark IOS APP 用来接受通知消息</p>
</li>
<li><p>prometheus</p>
</li>
<li><p>grafana</p>
</li>
</ul>
<h2>其他</h2>
<ul>
<li><p>数据备份与恢复策略: 数据库每天早上8点备份到阿里云oss，其他配置文件存储在git上，可通过k8s配置文件搭出完全一模一样的</p>
</li>
<li><p>自部署的相册应用：<a href="https://github.com/immich-app/immich">https://github.com/immich-app/immich</a></p>
</li>
<li><p>自部署的Vercel替代品：<a href="https://github.com/coollabsio/coolify">https://github.com/coollabsio/coolify</a></p>
</li>
</ul>
<blockquote>
<p>对家庭网络服务、NAS感兴趣的可以一起交流～</p>
</blockquote>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[大阪之旅]]></title>
            <link>https://zzfzzf.com/post/1824846354401202176</link>
            <guid>1824846354401202176</guid>
            <pubDate>Sat, 17 Aug 2024 16:30:49 GMT</pubDate>
            <content:encoded><![CDATA[<h2>前期准备</h2>
<ul>
<li>签证</li>
<li>日元</li>
</ul>
<h2>照片</h2>
<p><img src="https://w.zzfzzf.com/img/1729516437074.jpg" alt="DSC05276.jpg">
<img src="https://w.zzfzzf.com/img/1729519383204.webp" alt="DSC05280.webp"></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[ssl证书运维]]></title>
            <link>https://zzfzzf.com/post/1823563412693585920</link>
            <guid>1823563412693585920</guid>
            <pubDate>Wed, 14 Aug 2024 03:32:52 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>记录一下个人博客的运维工作
<a href="https://ohttps.com">https://ohttps.com</a></p>
</blockquote>
<h2>Gitea</h2>
<ul>
<li>私钥 cert.key </li>
<li>证书 fullchain.cer
下载放入<code>/var/lib/gitea/custom</code>中</li>
</ul>
<pre><code class="language-bash">ssh root@192.168.100.154
systemctl restart gitea
</code></pre>
<h2>K8S</h2>
<p>在linux上执行，mac上执行不了</p>
<pre><code class="language-bash">// tls.crt
cat fullchain.cer | base64 -w0
// tls.key
cat cert.key | base64 -w0
// 执行更新
kubectl apply -f https://git.ooxo.cc/k8s/yaml/raw/branch/main/https.yaml
kubectl get secret ooxo.cc-tls-secret
kubectl get secret ooxo.cc-tls-secret -o jsonpath=&#39;{.data.tls\.crt}&#39; | base64 --decode
kubectl get secret ooxo.cc-tls-secret -o jsonpath=&#39;{.data.tls\.key}&#39; | base64 --decode
</code></pre>
<h2>DNS&amp;AdGuard</h2>
<p>加密设置里调整</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[台州2天自驾游]]></title>
            <link>https://zzfzzf.com/post/1822434142386655232</link>
            <guid>1822434142386655232</guid>
            <pubDate>Sun, 11 Aug 2024 00:45:33 GMT</pubDate>
            <content:encoded><![CDATA[<h2>计划</h2>
<p>本来想去舟山的，一是去过，而是人很多，遂选择台州，虽然有点远</p>
<ul>
<li>🚗：蔚来ES6</li>
<li>🏠：温岭亚朵<blockquote>
<p>共400公里，6个小时</p>
</blockquote>
</li>
</ul>
<h2>Day1</h2>
<p>东西放到酒店，开1小时车去洞下沙滩
晚餐去焗海鲜。310元打完折250元 挺划算的
<img src="https://w.zzfzzf.com/img/1723336905091.webp" alt="DSC04820.webp"></p>
<p><img src="https://w.zzfzzf.com/img/1723337341234.webp" alt="DSC04888.webp"></p>
<h2>Day2</h2>
<blockquote>
<p>醒后直奔滨海绿道，有点失望的，观光车30元 一个渔港 一个金沙滩方向</p>
</blockquote>
<h3>嘉兴市-海宁市</h3>
<p>去海宁吃了个晚餐，性价比超级高，马路也很宽敞好开</p>
<h2>游后感</h2>
<ol>
<li>很累，很远，海鲜也很贵</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[linux把可执行文件注册为系统服务]]></title>
            <link>https://zzfzzf.com/post/1791300272845230080</link>
            <guid>1791300272845230080</guid>
            <pubDate>Fri, 17 May 2024 02:50:40 GMT</pubDate>
            <content:encoded><![CDATA[<h2>背景</h2>
<p>需要在公网服务器下运行bark文件，之前是使用nohup运行，重启后或者服务不太好管理</p>
<h2>步骤</h2>
<p>在 Debian 系统中，可以通过将可执行文件注册为服务，使其在系统启动时自动运行。以下是详细步骤：</p>
<h3>1. 创建服务单元文件</h3>
<p>在 <code>/etc/systemd/system/</code> 目录下创建一个新的服务单元文件。假设你的可执行文件是 <code>/usr/local/bin/my_executable</code>，你可以创建一个名为 <code>my_service.service</code> 的文件：</p>
<pre><code class="language-bash">sudo nano /etc/systemd/system/my_service.service
</code></pre>
<h3>2. 编辑服务单元文件</h3>
<p>在打开的编辑器中，添加以下内容：</p>
<pre><code class="language-ini">[Unit]
Description=My Custom Service
After=network.target

[Service]
ExecStart=/usr/local/bin/my_executable
Restart=always
User=your_username
Group=your_groupname

[Install]
WantedBy=multi-user.target
</code></pre>
<p>解释：</p>
<ul>
<li><code>[Unit]</code> 部分描述了服务，并指定了服务在网络启动之后运行。</li>
<li><code>[Service]</code> 部分定义了服务的主要参数：<ul>
<li><code>ExecStart</code> 指定了要运行的可执行文件的路径。</li>
<li><code>Restart</code> 设置为 <code>always</code>，以便在服务崩溃后自动重启。</li>
<li><code>User</code> 和 <code>Group</code> 设置为运行服务的用户和用户组。</li>
</ul>
</li>
<li><code>[Install]</code> 部分定义了服务安装的目标，这里设置为 <code>multi-user.target</code>，意味着服务在多用户模式下启动。</li>
</ul>
<h3>3. 重新加载 systemd 配置</h3>
<p>在编辑完服务单元文件后，重新加载 systemd 配置以使其生效：</p>
<pre><code class="language-bash">sudo systemctl daemon-reload
</code></pre>
<h3>4. 启动并启用服务</h3>
<p>启动服务：</p>
<pre><code class="language-bash">sudo systemctl start my_service
</code></pre>
<p>启用服务，使其在系统启动时自动运行：</p>
<pre><code class="language-bash">sudo systemctl enable my_service
</code></pre>
<h3>5. 检查服务状态</h3>
<p>你可以使用以下命令检查服务的状态，确保其正常运行：</p>
<pre><code class="language-bash">sudo systemctl status my_service
</code></pre>
<p>这将显示服务的当前状态，包括是否正在运行、最后一次启动的时间以及任何错误信息。</p>
<h3>6. 管理服务</h3>
<p>常用的管理命令包括：</p>
<ul>
<li><p>停止服务：</p>
<pre><code class="language-bash">sudo systemctl stop my_service
</code></pre>
</li>
<li><p>重启服务：</p>
<pre><code class="language-bash">sudo systemctl restart my_service
</code></pre>
</li>
<li><p>禁用服务（使其在系统启动时不自动运行）：</p>
<pre><code class="language-bash">sudo systemctl disable my_service
</code></pre>
</li>
</ul>
<p>通过以上步骤，你可以将你的可执行文件成功注册为一个系统服务，并且可以方便地管理和监控它。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[A7C2使用体验]]></title>
            <link>https://zzfzzf.com/post/1781976019314413568</link>
            <guid>1781976019314413568</guid>
            <pubDate>Sun, 21 Apr 2024 09:19:25 GMT</pubDate>
            <content:encoded><![CDATA[<h1>持续更新</h1>
<ul>
<li>相机：索尼A7C2</li>
<li>镜头：腾龙2875</li>
</ul>
<h1>202404样片分享</h1>
<blockquote>
<p>均经过压缩，原片使用 heif 格式，mac电脑转 jpeg 格式后使用 squoosh 转为 webp 格式</p>
</blockquote>
<p><img src="https://w.zzfzzf.com/img/1723336905091.webp" alt="DSC04820.webp"></p>
<p><img src="https://w.zzfzzf.com/img/1713691806307.webp" alt="DSC03428.webp"></p>
<p><img src="https://w.zzfzzf.com/img/1713691899954.webp" alt="DSC03425.webp"></p>
<p><img src="https://w.zzfzzf.com/img/1713692948268.webp" alt="DSC03407.webp"></p>
<p><img src="https://w.zzfzzf.com/img/1713693078033.webp" alt="DSC03283.webp"></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[vue优雅管理数据流动]]></title>
            <link>https://zzfzzf.com/post/1773020599870820352</link>
            <guid>1773020599870820352</guid>
            <pubDate>Wed, 27 Mar 2024 16:13:46 GMT</pubDate>
            <content:encoded><![CDATA[<p>从react转向写vue，有很多地方很别扭。很容易就写出不好的代码。本文介绍如何优雅的在vue中写出易于维护的代码。</p>
<h2>变量定义</h2>
<ol>
<li>是否纠结到底用<code>Ref</code>还是<code>Reactive</code>，teamplte不需要.value script需要.vue 不统一的写法</li>
<li>少用watch 不要盯着数据变化，而是要盯着数据为什么变化，数据不会无缘无故变。一定是有什么地方触动了，在触动的地方去修改</li>
<li>不用emit</li>
</ol>
<h2>hooks</h2>
<p>hooks不是单纯意义上的封装函数，逻辑复用，也是抽离副作用的</p>
<p>vue中有很多使用很方便的语法糖，但是后续维护会让人很难理解</p>
<ol>
<li>指令，指令是一个很方便的东西，但是不推荐使用</li>
<li>非props特性 $parent $chindren provide inject</li>
<li>所有的数据放状态管理里</li>
<li><a href="https://zh.javascript.info/ninja-code">https://zh.javascript.info/ninja-code</a></li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[debian12基础环境配置与运维]]></title>
            <link>https://zzfzzf.com/post/1768992518336155648</link>
            <guid>1768992518336155648</guid>
            <pubDate>Sat, 16 Mar 2024 13:27:37 GMT</pubDate>
            <content:encoded><![CDATA[<h2>查看ip地址</h2>
<pre><code class="language-shell">ip addr
</code></pre>
<pre><code class="language-shell">ssh chen@192.168.100.204
</code></pre>
<pre><code class="language-shell">su root
echo &quot;pub key&quot; &gt;&gt; ~/.ssh/authorized_keys
</code></pre>
<h2>update依赖</h2>
<pre><code class="language-shell">//更新软件包列表
apt update
//安装实际的软件包更新
apt upgrade
</code></pre>
<pre><code class="language-shell">apt install curl -y
</code></pre>
<h3>安装k8s master</h3>
<pre><code class="language-bash">curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE=&quot;644&quot; K3S_NODE_NAME=&quot;k8s-master-01&quot; sh -s -
</code></pre>
<h3>安装k8s worker</h3>
<pre><code class="language-bash">curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE=&quot;644&quot; K3S_NODE_NAME=&quot;k8s-worker-01&quot; K3S_URL=https://192.168.100.124:6443 K3S_TOKEN=mynodetoken sh -  
</code></pre>
<h2>安装helm</h2>
<p><a href="https://helm.sh/docs/intro/install/">https://helm.sh/docs/intro/install/</a></p>
<pre><code class="language-shell">curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
</code></pre>
<h2>安装dashbord</h2>
<p>先让traefik可以代理https流量</p>
<blockquote>
<p>/var/lib/rancher/k3s/server/manifests/traefik-config.yaml</p>
</blockquote>
<pre><code class="language-yaml">apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    globalArguments:
        - &quot;--serversTransport.insecureSkipVerify=true&quot;
    ports:
        psql:
            port: 5432
            expose: true
            exposedPort: 5432
            protocol: TCP
</code></pre>
<p><a href="https://github.com/kubernetes/dashboard">https://github.com/kubernetes/dashboard</a></p>
<pre><code class="language-bash">export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
</code></pre>
<blockquote>
<p>Add kubernetes-dashboard repository</p>
</blockquote>
<pre><code class="language-shell">helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
</code></pre>
<blockquote>
<p>Deploy a Helm Release named &quot;kubernetes-dashboard&quot; using the kubernetes-dashboard chart</p>
</blockquote>
<pre><code class="language-shell">helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard
</code></pre>
<ul>
<li>创建账户<a href="https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md">参考官网</a>，博主可直接内网使用<a href="https://git.ooxo.cc/k8s/yaml/raw/branch/main/kubernetes-dashboard.yaml">Kubernetes 资源清单</a></li>
</ul>
<pre><code class="language-bash">kubectl apply -f https://git.ooxo.cc/sonic/k8s/raw/branch/main/kubernetes-dashboard.yaml
</code></pre>
<p>访问 <a href="https://k8s.ooxo.cc/">https://k8s.ooxo.cc/</a></p>
<blockquote>
<p>获取token</p>
</blockquote>
<pre><code class="language-shell">kubectl get secret admin-user -n kubernetes-dashboard -o jsonpath={&quot;.data.token&quot;} | base64 -d
</code></pre>
<h2>安装nfs</h2>
<p><a href="https://zzfzzf.com/post/7028901660936245248">https://zzfzzf.com/post/7028901660936245248</a></p>
<h2>清除缓存</h2>
<pre><code>echo 1 &gt; /proc/sys/vm/drop_caches
</code></pre>
<h2>链接</h2>
<ul>
<li><a href="https://todoit.tech/k8s/">https://todoit.tech/k8s/</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[pve脏数据导致的time_out]]></title>
            <link>https://zzfzzf.com/post/1767922506028552192</link>
            <guid>1767922506028552192</guid>
            <pubDate>Wed, 13 Mar 2024 14:35:46 GMT</pubDate>
            <content:encoded><![CDATA[<pre><code class="language-shell">sysctl -p
</code></pre>
<h3>查看脏数据配置</h3>
<pre><code class="language-shell">sysctl -a | grep dirty
</code></pre>
<h3>查看多少条脏数据</h3>
<pre><code class="language-shell">cat /proc/vmstat | egrep &quot;dirty|writeback&quot;
</code></pre>
<h3>配置</h3>
<pre><code>#vi /etc/sysctl.conf
写入
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[测试]]></title>
            <link>https://zzfzzf.com/post/1759914225720889344</link>
            <guid>1759914225720889344</guid>
            <pubDate>Tue, 20 Feb 2024 12:13:43 GMT</pubDate>
            <content:encoded><![CDATA[<pre><code>  &lt;CodeSandpack files={{
  &#39;/App.js&#39;:`export default function App() {
</code></pre>
<p>  return <h1>Hello zzf !</h1>
}`
      }} template=&quot;react&quot; /&gt;2</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[React几个动画库的对比]]></title>
            <link>https://zzfzzf.com/post/1755602973972500480</link>
            <guid>1755602973972500480</guid>
            <pubDate>Thu, 08 Feb 2024 14:42:21 GMT</pubDate>
            <content:encoded><![CDATA[<p>这几个动画库都是在 React 应用中用于实现动画效果的流行选择。让我们来对比一下它们：</p>
<h3>@formkit/auto-animate</h3>
<p><a href="https://github.com/formkit/auto-animate">https://github.com/formkit/auto-animate</a></p>
<ul>
<li><strong>特点</strong>：<ul>
<li>基于 CSS transitions 或 animations 实现动画效果。</li>
<li>提供简单易用的 API，使得在 React 应用中集成动画变得简单。</li>
</ul>
</li>
<li><strong>优点</strong>：<ul>
<li>对于简单的动画效果，易于上手。</li>
<li>轻量级，不需要额外的依赖。</li>
</ul>
</li>
<li><strong>缺点</strong>：<ul>
<li>功能相对较为简单，可能无法满足复杂动画的需求。</li>
</ul>
</li>
</ul>
<h3>@react-spring/web</h3>
<p><a href="https://github.com/pmndrs/react-spring">https://github.com/pmndrs/react-spring</a></p>
<ul>
<li><strong>特点</strong>：<ul>
<li>基于 Spring Physics 模型实现动画，提供了更加自然的动画效果。</li>
<li>支持复杂的动画场景，如物理效果、链式动画等。</li>
</ul>
</li>
<li><strong>优点</strong>：<ul>
<li>动画效果更加流畅和自然。</li>
<li>提供了丰富的 API 和配置选项，适用于各种动画需求。</li>
</ul>
</li>
<li><strong>缺点</strong>：<ul>
<li>学习曲线较陡峭，相比其他库可能需要更多的时间来掌握。</li>
</ul>
</li>
</ul>
<h3>framer-motion</h3>
<p><a href="https://github.com/framer/motion">https://github.com/framer/motion</a></p>
<ul>
<li><strong>特点</strong>：<ul>
<li>提供了声明式的 API，使得创建动画变得简单直观。</li>
<li>支持复杂的动画效果，如手势驱动的交互、响应式设计等。</li>
</ul>
</li>
<li><strong>优点</strong>：<ul>
<li>API 设计直观，易于上手。</li>
<li>功能丰富，能够满足大部分动画需求。</li>
</ul>
</li>
<li><strong>缺点</strong>：<ul>
<li>有一定的学习曲线，但相对于 <code>@react-spring/web</code> 来说较为友好。</li>
</ul>
</li>
</ul>
<h3>react-transition-group</h3>
<p><a href="https://github.com/reactjs/react-transition-group">https://github.com/reactjs/react-transition-group</a>
2年未维护</p>
<ul>
<li><strong>特点</strong>：<ul>
<li>主要用于处理元素进入和离开 DOM 结构时的动画效果。</li>
<li>提供了一系列的组件，如 <code>Transition</code>, <code>CSSTransition</code>, <code>TransitionGroup</code> 等。</li>
</ul>
</li>
<li><strong>优点</strong>：<ul>
<li>针对元素的进入和离开提供了简单易用的解决方案。</li>
<li>可以与其他动画库结合使用，以实现更复杂的动画效果。</li>
</ul>
</li>
<li><strong>缺点</strong>：<ul>
<li>功能相对单一，主要用于处理进入和离开的动画，对于其他类型的动画需求可能不够灵活。</li>
</ul>
</li>
</ul>
<h2>总结</h2>
<p>如果项目需要复杂的动画效果，<code>@react-spring/web</code> 和 <code>framer-motion</code> 是不错的选择。如果只是需要简单的进入/离开动画效果，<code>react-transition-group</code> 可能是更合适的选择。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[在Vue3中使用JSX]]></title>
            <link>https://zzfzzf.com/post/1755497723848560640</link>
            <guid>1755497723848560640</guid>
            <pubDate>Thu, 08 Feb 2024 07:44:07 GMT</pubDate>
            <content:encoded><![CDATA[<h3>什么是JSX</h3>
<p>JSX，即JavaScript XML，是一种允许开发者在JavaScript代码中编写与HTML类似的结构的语法扩展，并能够通过诸如Babel这样的编译器转换为<code>createElement</code>（React中）或<code>createVNode</code>（Vue中）的函数调用，从而创建虚拟DOM。JSX结合了JavaScript的逻辑处理能力与标记语言的声明式编写模式，使得你可以在单个文件中处理UI和业务逻辑，实现组件的组织和复用。</p>
<h4>React和Vue中的对比</h4>
<p>JSX虽然在React和Vue中的使用方式非常相似，但还是存在一些差别：</p>
<ul>
<li>在React中，JSX转换后使用<code>React.createElement</code>或 <code>react/jsx-runtime</code> ，而Vue 3使用<code>createVNode</code>。</li>
<li>Vue中的JSX需要特定的Babel插件支持，例如<code>@vue/babel-plugin-jsx</code>。</li>
<li>在React中，CSS类使用<code>className</code>属性绑定，而<code>for</code>属性在JSX中需要替换为<code>htmlFor</code>。而在Vue中，则保持<code>class</code>和<code>for</code>的标准HTML属性使用。</li>
</ul>
<h3>入门基础语法</h3>
<h4>1. 创建基础元素</h4>
<pre><code class="language-jsx">const element = &lt;h1&gt;Hello, world!&lt;/h1&gt;;
</code></pre>
<h4>2. 嵌入表达式</h4>
<pre><code class="language-jsx">const name = &#39;c.chen&#39;;
const element = &lt;h1&gt;Hello, {name}&lt;/h1&gt;;
const avatar = &lt;img src={user.avatarUrl}&gt;&lt;/img&gt;;
</code></pre>
<h4>3. 条件渲染</h4>
<pre><code class="language-jsx">const avatar = user.avatarUrl ? &lt;img src={user.avatarUrl} /&gt; : null;
const avatar = user.avatarUrl &amp;&amp; &lt;img src={user.avatarUrl} /&gt;;
</code></pre>
<h4>4. 使用样式</h4>
<h5>行内样式</h5>
<pre><code class="language-jsx">const img1 = &lt;img style={{width:&quot;200px&quot;}}&gt;&lt;/img&gt;;
const customStyle = {
    width: &quot;200px&quot;
}
const img2 = &lt;img style={customStyle}&gt;&lt;/img&gt;;
</code></pre>
<h5>类绑定和外部样式文件</h5>
<pre><code class="language-jsx">// css module
import styles from &#39;./app.module.css&#39;;
const imgElement = &lt;img class={styles.myImage} /&gt;;
const imgClass = &lt;img class=&quot;my-image&quot;&gt;&lt;/img&gt;;
</code></pre>
<h3>在Vue3中如何上手</h3>
<h4>1. Babel配置 @vue/babel-plugin-jsx</h4>
<h5>webpack</h5>
<pre><code class="language-javascript">//babel.config.js
module.exports = {
  presets: [&#39;@vue/cli-plugin-babel/preset&#39;],
};
</code></pre>
<pre><code class="language-javascript">if (vueVersion === 2) {
  presets.push([require(&#39;@vue/babel-preset-jsx&#39;), jsxOptions])
} else if (vueVersion === 3) {
  plugins.push([require(&#39;@vue/babel-plugin-jsx&#39;), jsxOptions])
}
</code></pre>
<h5>Vite</h5>
<pre><code class="language-javascript">//vite.config.ts
import vueJsx from &#39;@vitejs/plugin-vue-jsx&#39;;

const config = defineConfig({
    plugins: [
      vue(),
      vueJsx(),
    ]
}
</code></pre>
<h4>2. tsconfig.json</h4>
<pre><code class="language-json">//tscongfig.json
{
    &quot;jsx&quot;: &quot;preserve&quot;,
    &quot;jsxImportSource&quot;: &quot;vue&quot;
}
</code></pre>
<h3>基础用法</h3>
<ol>
<li>文件名以<code>.tsx</code>结尾</li>
<li>自定义组件都是大驼峰，和HTML区分开</li>
<li><a href="https://cn.vuejs.org/guide/extras/render-function.html#basic-usage">Vue渲染函数基础用法</a></li>
</ol>
<h3>无状态组件（函数式组件）</h3>
<pre><code class="language-jsx">// UI = fn(state)
function MyComponent(props, { slots, emit, attrs }) {
    return &lt;NButton&gt;{props.message}&lt;/NButton&gt;
}
const App = () =&gt; &lt;div&gt;111&lt;/div&gt;;
</code></pre>
<h3>有状态组件</h3>
<pre><code class="language-jsx">&lt;template&gt;
  &lt;div&gt;
    &lt;slot&gt;&lt;/slot&gt;
    &lt;p&gt;Count: {{ count }}&lt;/p&gt;
    &lt;button ref=&quot;buttonRef&quot; @click=&quot;increment&quot;&gt;Increment&lt;/button&gt;    
    &lt;NInput
      v-model=&quot;baseInfo.description&quot;
    /&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script lang=&quot;ts&quot; setup&gt;
import { ref, defineProps, defineEmits, PropType } from &#39;vue&#39;;
import { NInput } from &#39;@nio-fe/lego&#39;; 

const props = defineProps({
  modelValue: String as PropType&lt;&#39;large&#39; | &#39;small&#39;&gt;
});

const emits = defineEmits([&#39;update:modelValue&#39;]);

const count = ref(0);
const baseInfo = ref({
  description: &#39;&#39;
});
const buttonRef = ref&lt;HTMLButtonElement&gt;();

// Define methods
const increment = () =&gt; {
  count.value++;
};

const updateDescription = (value: string) =&gt; {
  baseInfo.value.description = value;
  emits(&#39;update:modelValue&#39;, value);
};

const externalMethod = () =&gt; console.log(&#39;External method called&#39;);

defineExpose({
  externalMethod
});
&lt;/script&gt;
</code></pre>
<h3>引用组件</h3>
<p>在 JSX 中，推荐以直接引入的形式使用组件。</p>
<pre><code class="language-jsx">import { defineComponent } from &#39;vue&#39;;
import Counter from &#39;./Counter&#39;;

export default defineComponent({
  name: &#39;App&#39;,
  setup() {
    return () =&gt; (
      &lt;div&gt;
        &lt;h1&gt;Hello Vue 3 with TSX!&lt;/h1&gt;
        &lt;Counter /&gt;
      &lt;/div&gt;
    );
  },
});
</code></pre>
<h3>高阶用法</h3>
<p><a href="https://github.com/vuejs/babel-plugin-jsx/blob/main/packages/babel-plugin-jsx/README-zh_CN.md">JSX高阶用法</a></p>
<h3>Fragment</h3>
<p>类似于<code>template</code>，在JSX中不能使用<code>template</code>，会直接不渲染。</p>
<pre><code class="language-jsx">const App = () =&gt; (
  name === &#39;cc&#39; &amp;&amp; &lt;&gt;
    &lt;span&gt;I&#39;m&lt;/span&gt;
    &lt;span&gt;Fragment&lt;/span&gt;
  &lt;/&gt;
);
</code></pre>
<h3>v-model修饰符</h3>
<pre><code class="language-jsx">&lt;NInput
  v-model={[inputValue.value, [&#39;trim&#39;]]}
/&gt;
&lt;NInput
  modelValue={inputValue.value.trim()}
  onUpdate:modelValue={newVal=&gt;inputValue.value = newVal.trim()}
/&gt;
</code></pre>
<h3>动态组件</h3>
<pre><code class="language-jsx">const ComponentA = defineComponent({
    setup(){
        return ()=&gt; &lt;div&gt;ComponentA&lt;/div&gt;
    }
})
const ComponentB = defineComponent({
    setup(){
        return ()=&gt; &lt;div&gt;ComponentB&lt;/div&gt;
    }
})
const ColumnDiff = defineComponent({
    setup() {
        const components = {
            &#39;component-a&#39;: &lt;ComponentA /&gt;,
            &#39;component-b&#39;: &lt;ComponentB /&gt;,
        };
        const componentName = &#39;component-a&#39;;
        const ComponentToRender = components[componentName];
       

 return () =&gt; (
            &lt;&gt;
                &lt;div class={&#39;container mx-auto&#39;}&gt;
                    --1--
                    {ComponentToRender}
                    --2--
                    {ComponentToRender ? h(ComponentToRender) : null}
                    --3--
                    &lt;ComponentToRender /&gt;
                &lt;/div&gt;
            &lt;/&gt;
        )
    }
})
</code></pre>
<h3>插槽</h3>
<pre><code class="language-jsx">const A = (props, { slots }) =&gt; (
  &lt;&gt;
    &lt;h1&gt;{slots.default ? slots.default() : &#39;foo&#39;}&lt;/h1&gt;
    &lt;h2&gt;{slots.bar?.()}&lt;/h2&gt;
  &lt;/&gt;
);

const App = {
  setup() {
    const slots = {
      bar: () =&gt; &lt;span&gt;B&lt;/span&gt;,
    };
    return () =&gt; (
      &lt;A v-slots={slots}&gt;
        &lt;div&gt;A&lt;/div&gt;
      &lt;/A&gt;
    );
  },
};
</code></pre>
<h3>指令</h3>
<pre><code class="language-jsx">const app = createApp();

app.directive(&#39;highlight&#39;, {
  mounted(el, binding) {
    el.style.backgroundColor = binding.value;
  },
});
app.directive(&#39;tooltip&#39;, NVTooltip)
app.directive(&#39;popconfirm&#39;, NVPopconfirm)
app.directive(&#39;loading&#39;, NVLoading)
app.component(&#39;my-component&#39;, {
  setup() {
    return () =&gt; (
      &lt;div v-highlight=&quot;&#39;yellow&#39;&quot;&gt;
        This will be highlighted in yellow.
      &lt;/div&gt;
    );
  },
});

app.mount(&#39;#app&#39;);
</code></pre>
<h3>透传props</h3>
<pre><code class="language-jsx">import { defineComponent, h } from &#39;vue&#39;;

// 引入子组件
import ChildComponent from &#39;./ChildComponent.vue&#39;;

export default defineComponent({
  name: &#39;ParentComponent&#39;,
  props:{
    a:String,
  },
  setup(props, { attrs, slots, emit }) {
    // 所有的父 props 都会以 props 参数的形式接收
    // attrs 包含了非 props 的属性，例如 class, style, id 等
    return () =&gt; (
      &lt;ChildComponent {...props} {...attrs}&gt;
        {slots.default ? slots.default() : &#39;&#39;}
      &lt;/ChildComponent&gt;
    );
  },
});
</code></pre>
<h3>搭配Lego使用</h3>
<p>使用按需引入，<a href="https://design-system.nioint.com/horizon-web/dev-component/vue/base/183">Lego官方文档</a></p>
<h3>样式处理</h3>
<h4>Css In Js</h4>
<ul>
<li>Emotion <a href="https://emotion.sh/docs/introduction">官方文档</a></li>
</ul>
<pre><code class="language-jsx">&lt;div
  class={css({
    color: &#39;var(--n-text-secondary)&#39;
  })}
&gt;
  css in js style
&lt;/div&gt;
</code></pre>
<h4>Css Module</h4>
<ol>
<li>根目录创建类型定义文件</li>
</ol>
<pre><code class="language-typescript">//typings.d.ts
declare module &#39;*.scss&#39; {
    const content: { [className: string]: string };
    export default content;
}
</code></pre>
<ol start="2">
<li>在tsconfig.json文件中，确保你的&quot;include&quot;部分包含了SCSS文件的路径</li>
</ol>
<pre><code class="language-json">{
  &quot;include&quot;: [&quot;src/**/*.ts&quot;, &quot;src/**/*.d.ts&quot;, &quot;src/**/*.tsx&quot;, &quot;src/**/*.scss&quot;],
}
</code></pre>
<pre><code class="language-jsx">import styles from &#39;./demo.module.scss&#39;
.header{
  height: 80px;
  &amp;-left{
    color: var(--n-text-primary);
  }
}
&lt;div class={styles.header}&gt;
    &lt;div class={styles.headerLeft}&gt;
        name
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h4>Tailwind Css</h4>
<ol>
<li>webpack...没成功</li>
<li>vite项目可以使用</li>
</ol>
<h3>F&amp;Q</h3>
<h4>怎么页面中出现莫名奇妙的 0</h4>
<pre><code class="language-jsx">const list = []
const app = list.length &amp;&amp; list.map(item =&gt; )
</code></pre>
<h3>附录</h3>
<ul>
<li><a href="https://github.com/vuejs/babel-plugin-jsx/blob/main/packages/babel-plugin-jsx/README-zh_CN.md">babel-plugin-jsx</a></li>
<li><a href="https://cn.vuejs.org/guide/extras/render-function.html">渲染函数 &amp; JSX</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[2023年度总结-充满希望的一年]]></title>
            <link>https://zzfzzf.com/post/1742000365265489920</link>
            <guid>1742000365265489920</guid>
            <pubDate>Tue, 02 Jan 2024 01:50:26 GMT</pubDate>
            <content:encoded><![CDATA[<p>不知不觉又是一年，把2023做个总结，顺便规划一下2024</p>
<blockquote>
<p>这是2022的年度总结<a href="https://zzfzzf.com/post/7021740551498240000">戳我👇</a></p>
</blockquote>
<h2>软件</h2>
<blockquote>
<p>在延续现有软件的基础上，额外订阅了专为mac端设计的Setup App。已经用了快一年了</p>
</blockquote>
<h2>硬件</h2>
<ul>
<li>购买了Nintendo Switch玩了差不多两个月，现在已经吃灰了😭</li>
<li>我的第一块Apple Watch，每天的陪伴除了监测睡眠，还让我方便地掌握信息的动态。</li>
<li>索尼全画幅微单的加入，开启了我的摄影旅程，或许未来它将成为我的副业？拭目以待吧</li>
<li>发挥了DIY精神，组装了一台mini主机，仅仅20W功耗。现在不仅是家中的路由器，同时兼职服务器，效能十足。</li>
</ul>
<Space>
 ![description](https://w.zzfzzf.com/img/1704184714928.webp)
 ![description](https://w.zzfzzf.com/img/1704185362664.webp)
</Space>

<h2>职业发展</h2>
<p>工作上的2023年比较平稳，已经快在现在公司呆满2年了，现在是呆的最久的一家公司，嘻嘻</p>
<ul>
<li>接触了<code>css in js</code>,并应用在业务中</li>
<li>承担了组件库10个组件的维护工作</li>
</ul>
<h2>技术探索</h2>
<ul>
<li>对Go语言产生了浓厚兴趣，并将我的博客后端从<strong>midway</strong>重构为使用<strong>Go</strong>语言的<strong>fiber</strong>框架，大幅度提高了性能</li>
<li>完成了基于k8s搭建的cicd流程，部署于HomeLab上</li>
<li>尝试了一下LG的27存4K显示器，退货了</li>
<li>尝试了一下大疆无人机，租了3天玩了一下</li>
</ul>
<h2>浏览器从 A 到 Z</h2>
<h3>A</h3>
<p><a href="https://ant.design">https://ant.design</a>
虽然现在写vue，但是很多组件设计还是要参考ant</p>
<h3>B</h3>
<p><a href="https://www.bilibili.com/">https://www.bilibili.com/</a>
快乐源泉</p>
<h3>C</h3>
<p><a href="https://chat.openai.com">https://chat.openai.com</a>
人工智能！</p>
<h3>D</h3>
<p><a href="https://www.disneyplus.com">https://www.disneyplus.com</a>
挺多好看的电影</p>
<h3>E</h3>
<p><a href="https://esbuild.github.io/">https://esbuild.github.io/</a>
打包工具</p>
<h3>F</h3>
<p><a href="https://www.framer.com/motion/">https://www.framer.com/motion/</a>
动画库！！</p>
<h2>展望2024</h2>
<p>2024年计划好好提升一下自己的专业水平和内在认知</p>
<ul>
<li>深入学习<code>Chromium</code>及<code>V8引擎</code>的渲染原理。<strong>每个季度</strong> 能输出一篇文章</li>
<li>微信读书多看书，至少 <strong>每个月</strong> 读完一本书（包括不限于心理学、人物传、历史）</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[分享我的2023个人订阅账单总结]]></title>
            <link>https://zzfzzf.com/post/1714639631858798592</link>
            <guid>1714639631858798592</guid>
            <pubDate>Wed, 18 Oct 2023 13:48:39 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>看看今年订阅方面花了多少钱</p>
</blockquote>
<p>主要分为美元，土耳其，和人名币</p>
<h2>娱乐</h2>
<h2>工作</h2>
<ul>
<li>Github Copilot 100$</li>
</ul>
<h2>工具</h2>
<ul>
<li>simplelogin 15$一年 后续用ios土耳其区续费 57一年</li>
<li>云服务器</li>
<li>代理节点</li>
</ul>
<h2>购物</h2>
<ul>
<li>京东会员</li>
<li>淘宝88会员</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[相机入门选购指南]]></title>
            <link>https://zzfzzf.com/post/1714310230784872448</link>
            <guid>1714310230784872448</guid>
            <pubDate>Tue, 17 Oct 2023 15:59:44 GMT</pubDate>
            <content:encoded><![CDATA[<p>摄影是一门艺术，而选择合适的相机是开始这门艺术的关键步骤。以下是我的关于相机的一些研究和心得。 </p>
<h3>1. 相机的分类</h3>
<h4>a. 单反相机（DSLR）</h4>
<ul>
<li><p><strong>定义</strong>：通过反光镜和棱镜，用户可以直接通过取景器看到景物。</p>
</li>
<li><p><strong>特点</strong>：体积较大，对焦速度快，电池寿命长。</p>
</li>
</ul>
<h4>b. 无反相机（Mirrorless）</h4>
<ul>
<li><p><strong>定义</strong>：无反光镜，通过电子取景器或屏幕预览。</p>
</li>
<li><p><strong>特点</strong>：体积较小，重量轻，高速连拍能力强。</p>
</li>
</ul>
<h3>2. 传感器大小</h3>
<ul>
<li><p><strong>全画幅（Full Frame）</strong>：传感器大小为35mm，可以获得较宽的景角和优良的画质，通常适合专业摄影。</p>
</li>
<li><p><strong>半画幅（APS-C）</strong>：传感器大小小于35mm，体积和重量更轻，价格更亲民，适合初学者和中级摄影爱好者。</p>
</li>
</ul>
<h3>3. 推荐微单选择</h3>
<p>推荐索尼的<code>A7M4</code>。考虑到售后服务，建议购买jd自营的产品。</p>
<table>
<thead>
<tr>
<th>品牌</th>
<th>型号</th>
<th>价格</th>
<th>优点</th>
<th>缺点</th>
</tr>
</thead>
<tbody><tr>
<td>佳能</td>
<td>R50</td>
<td>-</td>
<td>入门级，易于使用，佳能色彩</td>
<td>无4K视频，较慢的连拍速度</td>
</tr>
<tr>
<td>尼康</td>
<td>Z30</td>
<td>-</td>
<td>入门级，紧凑轻便</td>
<td>无4K视频，基础的自动对焦系统</td>
</tr>
<tr>
<td>索尼</td>
<td>zve10</td>
<td>-</td>
<td>入门级，专为vlog设计，出色的自动对焦</td>
<td>APS-C画幅，无5轴传感器稳定化</td>
</tr>
<tr>
<td>佳能</td>
<td>R10</td>
<td>-</td>
<td>进阶模型，4K视频，高速连拍</td>
<td>单存储卡槽，较小的电池寿命,无防抖</td>
</tr>
<tr>
<td>索尼</td>
<td>A6700 2023年7月</td>
<td>¥9999</td>
<td>APS-C画幅，4K视频，5轴稳定化，快速自动对焦</td>
<td>较高的价格，无全画幅传感器</td>
</tr>
<tr>
<td>佳能</td>
<td>R7 22年5月</td>
<td>¥11999（18-150套）</td>
<td>超级变焦镜头套装，适合旅行拍摄</td>
<td>APS-C画幅，无5轴传感器稳定化</td>
</tr>
<tr>
<td>索尼</td>
<td>A7C2</td>
<td>¥15999</td>
<td>全画幅传感器，4K视频，Type-C接口，紧凑设计</td>
<td>单存储卡槽，较低的连拍速度</td>
</tr>
<tr>
<td>佳能</td>
<td>R8</td>
<td>¥11399</td>
<td>全画幅传感器，高分辨率，4K视频</td>
<td>无防抖，续航短</td>
</tr>
<tr>
<td>尼康</td>
<td>Z5</td>
<td>¥9000-¥14000</td>
<td>全画幅传感器，4K视频，双存储卡槽，5轴稳定化</td>
<td>较小的电池寿命，基础的自动对焦系统</td>
</tr>
</tbody></table>
<h3>4. 镜头推荐</h3>
<p>大三元镜头：三个焦段覆盖广泛、最大光圈通常很大（即f/2.8）的专业级镜头。这三个焦段通常包括：</p>
<ul>
<li><p>广角至标准焦距镜头（如24-70mm f/2.8）</p>
</li>
<li><p>标准至中远摄焦距镜头（如70-200mm f/2.8）</p>
</li>
<li><p>超广角镜头（如16-35mm f/2.8）
<strong>小三元镜头</strong>：这些是比大三元更小巧、重量轻、价格更亲民的镜头，但同样提供优良的成像质量和较大的光圈（通常在f/4左右）。这个类别的镜头可能包括：</p>
</li>
<li><p>广角至标准焦距镜头（如24-105mm f/4）</p>
</li>
<li><p>标准至中远摄焦距镜头（如70-200mm f/4）</p>
</li>
<li><p>超广角镜头（如12-24mm f/4或16-35mm f/4）</p>
</li>
</ul>
<h4>a. 佳能</h4>
<ul>
<li><strong>RF-S 18-150mm F3.5-6.3</strong>：超级变焦，旅行或不想频繁换镜头时的好选择。 3499¥</li>
<li><strong>RF 50mm</strong>：标准定焦镜头，适合人像和低光拍摄。 1399¥</li>
</ul>
<h4>b. 索尼</h4>
<blockquote>
<p>半画幅</p>
</blockquote>
<ul>
<li>腾龙 17-70  F2.8 <strong>4499¥</strong> （必备）</li>
<li>适马 56 F1.4    <strong>2699¥</strong>（人像定焦）</li>
</ul>
<blockquote>
<p>全画幅</p>
</blockquote>
<ul>
<li>腾龙 28-200 F/2.8-5.6 <strong>5490¥</strong> （最强小钢炮和最强天涯镜）光圈不恒定</li>
<li>适马 85 F1.4 <strong>6399¥</strong> （人像推荐）</li>
<li>索尼 85 F1.8 <strong>4049¥</strong> （人像）</li>
<li>适马 16-28 F2.8 <strong>4999¥</strong> （超广角补充）</li>
</ul>
<ol>
<li>我也是 a7c2，比较穷但是想顾全，二手 28 200+唯卓仕月底的 20 2.8 +美科 85 1.4 三颗镜头毕业[doge]</li>
<li>a7c2+适马2870加几颗小定焦，两万左右</li>
<li>镜头方面已经买了一根720gm2还没拆封[笑哭]就等着选一个机身了，再配个2070</li>
<li>永诺YN85mm F1.8S</li>
</ol>
<h4>c. 尼康</h4>
<ul>
<li><p><strong>Z DX 16-50mm f/3.5-6.3 VR</strong>：轻便的标准变焦镜头，适合日常和旅行摄影。</p>
</li>
<li><p><strong>Z 24-70mm f/4 S</strong>：全画幅标准变焦，提供高质量的成像性能。</p>
</li>
</ul>
<h2>总结</h2>
<ol>
<li>佳能R7</li>
</ol>
<ul>
<li>零售价 9499¥ </li>
<li>JD 9599¥</li>
<li>RF50套 10898¥  +1299¥</li>
<li>RF-S  18-150套 11999¥  +2400¥<blockquote>
<p>合计 2个镜头+机身 一个通用 一个拍人  13K</p>
</blockquote>
</li>
</ul>
<ol start="2">
<li>索尼 A6700</li>
</ol>
<ul>
<li>JD 9999¥</li>
<li>腾龙 17-70  F2.8 4499¥ （必备）</li>
<li>适马 56 F1.4 2799¥ （人像） </li>
<li>适马 18-50 F2.8 3699¥ （再看看）</li>
<li>腾龙 11-20  F2.8 3980¥（再看看）</li>
<li>索尼 70-350G ¥6899（再看看）<blockquote>
<p>合计 15K左右</p>
</blockquote>
</li>
</ul>
<ol start="4">
<li>索尼 A7C2</li>
</ol>
<ul>
<li>JD 14499¥</li>
<li>腾龙 28-200 F/2.8-5.6 5490¥   （最强小钢炮和最强天涯镜）光圈不恒定</li>
<li>人像？</li>
<li>腾龙 28-75 F/2.8 6180¥</li>
<li>索尼 20-70/f4 G ¥7999 （超广角）</li>
</ul>
<table>
<thead>
<tr>
<th>焦段</th>
<th>品牌</th>
<th>价格</th>
</tr>
</thead>
<tbody><tr>
<td>24-105mm F/4</td>
<td>索尼</td>
<td>6999</td>
</tr>
<tr>
<td>28-75mm F/2.8</td>
<td>腾龙</td>
<td>5980</td>
</tr>
<tr>
<td>24-70mm F/2.8</td>
<td>适马</td>
<td>7099</td>
</tr>
<tr>
<td>24-70mm F/2.8</td>
<td>索尼</td>
<td>14119</td>
</tr>
</tbody></table>
<blockquote>
<p>合计 20K</p>
</blockquote>
<h3>存储卡</h3>
<p>原厂V60</p>
<h3>电池</h3>
<p>原厂电池或者绿联</p>
<h2>结论</h2>
<blockquote>
<p>所有商品京东自营店购入</p>
</blockquote>
<ul>
<li>相机 <a href="https://item.jd.com/100064843474.html">A7C2</a></li>
<li>镜头 <a href="https://item.jd.com/100028093906.html">腾龙28-75</a> 后续考虑 <code>适马70-200</code>或者<code>腾龙70-180</code></li>
<li>电池 <a href="https://item.jd.com/100021040088.html">绿联两电一充套装</a></li>
<li><a href="https://item.jd.com/100010205662.html">存储卡</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[vue3中使用react风格语法]]></title>
            <link>https://zzfzzf.com/post/1711744566345863168</link>
            <guid>1711744566345863168</guid>
            <pubDate>Tue, 10 Oct 2023 14:04:41 GMT</pubDate>
            <content:encoded><![CDATA[<pre><code class="language-tsx">import { ref, UnwrapRef, reactive, watch, WatchOptions } from &#39;vue&#39;;

const isObject = (o): o is object =&gt; {
  return typeof o === &#39;object&#39;;
}

const isFunction = (f): f is Function =&gt; {
  return typeof f === &#39;function&#39;;
}

export const useState = &lt;T&gt;(defaultValue: T) =&gt; {
  if (isObject(defaultValue)) {
    return useStateObj(defaultValue);
  }
  const state = ref(defaultValue);
  const set = (value: T): void =&gt; {
    state.value = value as UnwrapRef&lt;T&gt;;
  };
  return [state, set];
};

const useStateObj = &lt;O extends object&gt;(defaultObj: O) =&gt; {
  const obj = reactive(defaultObj);
  const set = (valueObj: O): void =&gt; {
    Object.entries(valueObj).forEach(([key, val]) =&gt; {
      obj[key] = val;
    });
  };
  return [obj, set];
}

export const useEffect = (effectHandler, dependencies) =&gt; {
  return watch(dependencies, (changedDependencies, prevDependencies, onCleanUp) =&gt; {
    const effectCleaner = effectHandler(changedDependencies, prevDependencies);
    if (isFunction(effectCleaner)) {
      onCleanUp(effectCleaner);
    }
  }, { immediate: true, deep: true } as WatchOptions);
}
</code></pre>
<pre><code class="language-ts">import { defineComponent } from &#39;vue&#39;;
import useSWR from &#39;./path-to-useSWR&#39;;

function fetcher(url: string) {
    return fetch(url).then(res =&gt; res.json());
}

export default defineComponent({
    setup() {
        const { data, isLoading, error, mutate } = useSWR(&#39;https://api.example.com/data&#39;, fetcher);

        return {
            data,
            isLoading,
            error,
            refreshData: mutate,
        };
    },
});
</code></pre>
<pre><code class="language-ts">import { ref, watch, reactive } from &#39;vue&#39;;

interface SWRResponse&lt;T&gt; {
    data: T | null;
    error: any;
    isLoading: boolean;
    mutate: () =&gt; Promise&lt;void&gt;;
}

function useSWR&lt;T = any&gt;(url: string, fetcher: (url: string) =&gt; Promise&lt;T&gt;): SWRResponse&lt;T&gt; {
    const state = reactive({
        data: null as T | null,
        error: null as any,
        isLoading: true,
    });

    const fetchData = async () =&gt; {
        state.isLoading = true;

        try {
            const data = await fetcher(url);
            state.data = data;
        } catch (err) {
            state.error = err;
        } finally {
            state.isLoading = false;
        }
    };

    watch(url, fetchData, { immediate: true });

    return {
        get data() {
            return state.data;
        },
        get error() {
            return state.error;
        },
        get isLoading() {
            return state.isLoading;
        },
        mutate: fetchData,
    };
}

export default useSWR;
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[记一次X-Forwarded-For问题排查]]></title>
            <link>https://zzfzzf.com/post/1705964726925660160</link>
            <guid>1705964726925660160</guid>
            <pubDate>Sun, 24 Sep 2023 15:17:40 GMT</pubDate>
            <content:encoded><![CDATA[<h2>简介</h2>
<p>在复杂的生产环境中，请求头部的<code>X-Forwarded-For</code>可能会带来意想不到的问题。本文将详细讨论与<code>X-Forwarded-For</code>相关的问题，特别是在使用Caddy、Traefik和Cloudflare时遇到的问题。</p>
<hr>
<h2>问题背景</h2>
<p>在本次问题中，有几个重要的组件涉及：</p>
<ol>
<li><strong>Caddy</strong>：一种流行的web服务器，用于处理请求和执行反向代理。</li>
<li><strong>Traefik</strong>：一种现代HTTP反向代理和负载均衡器。</li>
<li><strong>Cloudflare</strong>：提供内容分发网络服务、DDoS保护和互联网安全性。</li>
</ol>
<hr>
<h2>问题描述</h2>
<p>当使用Caddy作为反向代理，并通过Cloudflare进行请求时，出现了不一致的<code>X-Forwarded-For</code>头部信息。这导致了后端服务在处理请求时的困惑，因为它不能确定真实的客户端IP地址。</p>
<hr>
<h2>原因分析</h2>
<ol>
<li><strong>Caddy配置问题</strong>：在Caddy的配置中，将反向代理的目标从<code>https://</code>更改为<code>http://</code>。这可能导致了<code>X-Forwarded-For</code>头部信息的更改。</li>
</ol>
<pre><code>api.ccw.es {
    reverse_proxy https://server.ooxo.cc {
        header_up Host {http.reverse_proxy.upstream.hostport}
    }
}
</code></pre>
<ol start="2">
<li><p><strong>Cloudflare的影响</strong>：Cloudflare为所有经过其网络的请求添加了<code>X-Forwarded-For</code>头部。这意味着，即使请求的原始来源没有这个头部，Cloudflare也会添加它。</p>
</li>
<li><p><strong>浏览器与API响应的不同</strong>：使用浏览器与使用API时的响应不同，可能是因为其中一个请求通过了Cloudflare，而另一个没有。</p>
</li>
</ol>
<hr>
<p><strong>解决方案</strong>：</p>
<ol>
<li><p><strong>调整Caddy的反向代理配置</strong>：确保Caddy的反向代理配置正确，并考虑是否需要https。</p>
</li>
<li><p><strong>理解Cloudflare的行为</strong>：了解Cloudflare如何处理头部，特别是<code>X-Forwarded-For</code>，并考虑是否需要在Caddy或后端服务中进行特殊处理。</p>
</li>
<li><p><strong>验证所有组件的行为</strong>：测试各种请求来源，确保每个组件都正确处理<code>X-Forwarded-For</code>。</p>
</li>
</ol>
<hr>
<h2>结论</h2>
<p>在复杂的网络环境中，理解每个组件的行为是至关重要的。特别是当涉及到请求头部，如<code>X-Forwarded-For</code>时，一点小的配置更改都可能导致不可预见的结果。最重要的是，始终验证并测试您的配置，确保一切按预期运行。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[zsh切换到fish]]></title>
            <link>https://zzfzzf.com/post/1703452436959596544</link>
            <guid>1703452436959596544</guid>
            <pubDate>Sun, 17 Sep 2023 16:54:44 GMT</pubDate>
            <content:encoded><![CDATA[<pre><code class="language-bash">brew install fish
</code></pre>
<p>将 fish shell 的路径添加到 /etc/shells 文件</p>
<pre><code class="language-bash">sudo echo $(which fish) &gt;&gt; /etc/shells
</code></pre>
<pre><code class="language-bash">chsh -s $(which fish)
#设置为默认shell为fish
chsh -s /usr/bin/fish
#设置为默认shell为zsh
chsh -s /bin/zsh
#设置为默认shell为bash
chsh -s /bin/bash
</code></pre>
<p>建议不要使用默认 <code>fish</code> 当shell 有一些一键安装的脚本不支持。需要的时候使用 <code>fish</code> 命令调出或者设置成编辑器的默认shell比较好。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[重复渲染]]></title>
            <link>https://zzfzzf.com/post/7105926098126508032</link>
            <guid>7105926098126508032</guid>
            <pubDate>Fri, 08 Sep 2023 14:53:23 GMT</pubDate>
            <content:encoded><![CDATA[<pre><code class="language-tsx">&#39;use client&#39;;
import { io, Socket } from &#39;socket.io-client&#39;;
import { useEffect, useRef, useState } from &#39;react&#39;;
import Monitor from &#39;utils/monitor&#39;;

const OnlineCount = () =&gt; {
  const [count, setCount] = useState(0);
  const socket = useRef&lt;WebSocket&gt;();
  async function initSocket() {
    const monitor = new Monitor();
    const userId = await monitor.getVisitor();
    socket.current = new WebSocket(`ws://localhost:3000/v1/ws?userId=$userId`)
    socket.current.onmessage = (event)=&gt;{
      const message = event.data;
      setCount(message)
    }
  }

  useEffect(() =&gt; {
    initSocket().then();
    return () =&gt; {
      socket.current?.close();
    };
  }, []);
  return (
    &lt;div&gt;
      在线人数 &lt;span className=&#39;font-mono&#39;&gt;{count}&lt;/span&gt;
    &lt;/div&gt;
  );
};
export default OnlineCount;
</code></pre>
<pre><code class="language-tsx">&quot;use client&quot;;
import React, { useEffect, useRef, useState } from &#39;react&#39;;
import Monitor from &#39;utils/monitor&#39;;

const OnlineCount = () =&gt; {
  const [count, setCount] = useState(0);
  const socket = useRef&lt;WebSocket | null&gt;(null); // 使用 null 初始化 ref
  const userIdRef = useRef&lt;string | null&gt;(null); // 存储 userId，避免重新渲染时重新获取

  useEffect(() =&gt; {
    async function initSocket() {
      if (!userIdRef.current) {
        const monitor = new Monitor();
        userIdRef.current = await monitor.getVisitor();
      }

      if (!socket.current) {
        socket.current = new WebSocket(`ws://localhost:3000/v1/ws?userId=$userIdRef.current`);
        socket.current.onmessage = (event) =&gt; {
          const message = event.data;
          setCount(message);
        };
      }
    }

    initSocket();

    return () =&gt; {
      if (socket.current) {
        socket.current.close();
        socket.current = null; // 清除连接引用，以便下次重新渲染时重新创建
      }
    };
  }, []); // 空数组表示只在挂载和卸载时执行

  return (
    &lt;div&gt;
      在线人数 &lt;span className=&#39;font-mono&#39;&gt;{count}&lt;/span&gt;
    &lt;/div&gt;
  );
};

export default OnlineCount;
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[GO 实践]]></title>
            <link>https://zzfzzf.com/post/7105733330657742848</link>
            <guid>7105733330657742848</guid>
            <pubDate>Fri, 08 Sep 2023 02:07:24 GMT</pubDate>
            <content:encoded><![CDATA[<h2>前言</h2>
<blockquote>
<p>折腾不息，生命不止</p>
</blockquote>
<p>这两天和<code>GPT</code>结对编程完成了后端从<code>NodeJs</code>到<code>Go</code>的迁移，数据库从<code>MySQL</code>到<code>PostgreSQL</code>的迁移。</p>
<h2>入门</h2>
<ul>
<li>定时任务 <a href="https://github.com/go-co-op/gocron">https://github.com/go-co-op/gocron</a></li>
<li>WEB <a href="https://github.com/gofiber/fiber">https://github.com/gofiber/fiber</a></li>
<li>postgresql <a href="https://github.com/lib/pq">https://github.com/lib/pq</a></li>
</ul>
<h2>git</h2>
<p><a href="https://github.com/zzfn/blog-server-go">https://github.com/zzfn/blog-server-go</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[kubernetes-dashboard安装]]></title>
            <link>https://zzfzzf.com/post/7103612485843947520</link>
            <guid>7103612485843947520</guid>
            <pubDate>Sat, 02 Sep 2023 05:42:15 GMT</pubDate>
            <content:encoded><![CDATA[<h3>安装</h3>
<ol>
<li>应用 Dashboard YAML 文件</li>
</ol>
<pre><code class="language-bash">kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
</code></pre>
<blockquote>
<p>注意：版本号（例如 v2.7.0）可能会有所不同，请检查 <a href="https://github.com/kubernetes/dashboard">Kubernetes Dashboard GitHub</a> 以获取最新版本。</p>
</blockquote>
<ol start="2">
<li>获取访问令牌</li>
</ol>
<pre><code class="language-yaml">#创建服务帐户
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
---
#创建 ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
</code></pre>
<ol start="3">
<li>获取令牌</li>
</ol>
<pre><code class="language-bash">kubectl -n kubernetes-dashboard create token admin-user
</code></pre>
<pre><code class="language-bash">kubectl get secret admin-user -n kubernetes-dashboard -o jsonpath={&quot;.data.token&quot;} | base64 -d
</code></pre>
<h3>清除权限</h3>
<pre><code class="language-bash">kubectl create serviceaccount dashboard-admin -n kubernetes-dashboard
kubectl create clusterrolebinding dashboard-admin --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-admin
---
kubectl -n kubernetes-dashboard delete serviceaccount admin-user
kubectl -n kubernetes-dashboard delete clusterrolebinding admin-user
</code></pre>
<h3>F&amp;Q</h3>
<p>https会报错 <code>bad certificate</code>,需要把yaml下载下来 把端口改成http的</p>
<h3>附录</h3>
<ul>
<li><a href="https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/">官方教程</a></li>
<li><a href="https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md">创建用户</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[2023-React生态相关技术选型]]></title>
            <link>https://zzfzzf.com/post/7101582360524754944</link>
            <guid>7101582360524754944</guid>
            <pubDate>Sun, 27 Aug 2023 15:12:55 GMT</pubDate>
            <content:encoded><![CDATA[<h3>状态管理</h3>
<ul>
<li>Jotai<blockquote>
<p>Jotai 旨在为 React 应用提供一种原子性的状态管理方式，使得开发者可以更容易地管理和更新应用的状态。</p>
</blockquote>
</li>
</ul>
<h5>特点</h5>
<ul>
<li><strong>简单性</strong>: Jotai 的 API 是直观的，不需要多余的配置或“胶水代码”。</li>
<li><strong>灵活性</strong>: 由于其原子性质，你可以轻松地将状态划分为多个独立的单元。</li>
<li><strong>与 React 良好的集成</strong>: 使用 hooks，Jotai 与现代 React 代码完美融合。</li>
</ul>
<h4>代码示例</h4>
<ul>
<li>定义一个原子状态</li>
</ul>
<pre><code class="language-ts">import { atom } from &#39;jotai&#39;;

const countAtom = atom(0); // 初始化为 0 的计数器原子
</code></pre>
<ul>
<li>使用该原子状态:</li>
</ul>
<pre><code class="language-tsx">import { useAtom } from &#39;jotai&#39;;

function Counter() {
  const [count, setCount] = useAtom(countAtom);

  return (
    &lt;div&gt;
      &lt;p&gt;Count: {count}&lt;/p&gt;
      &lt;button onClick={() =&gt; setCount(prev =&gt; prev + 1)}&gt;Increment&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h3>动画库</h3>
<blockquote>
<p>当涉及到在 React 中创建动画时，以下是一些受欢迎的库：</p>
</blockquote>
<ul>
<li><strong>react-transition-group</strong>: 提供了基本的状态过渡动画能力，适合那些需要简单过渡效果的场景。</li>
<li><strong>framer-motion</strong>: 是一个更高级的动画库，允许创建复杂的交互式动画。其 API 也相对简单，初学者也能快速上手。</li>
<li><strong>react-spring</strong>: 基于物理原理的动画库，特别适合创建自然和流畅的动画效果。</li>
</ul>
<h3>附录</h3>
<ul>
<li><a href="https://juejin.cn/post/7085542534943883301">2022 年的 React 生态</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[AMD 7840HS 使用体验]]></title>
            <link>https://zzfzzf.com/post/7089165226674884608</link>
            <guid>7089165226674884608</guid>
            <pubDate>Mon, 24 Jul 2023 08:51:40 GMT</pubDate>
            <content:encoded><![CDATA[<h2>背景</h2>
<p>一直想组装一台all in one的mini主机，之前家里的网络是多个已有设备组成的，但是分布在各个地方，也不太好管理</p>
<ol>
<li>一台废旧笔记本、主要用来跑PT 下电影</li>
<li>一台台式机、安装PVE 组k8s网络</li>
<li>一台j1900，安装openwrt
本次购入了AMD 7840HS+三星980pro 1T存储+ 64G三星DDR5 5600内存,
目前起了8台虚拟机</li>
</ol>
<p><img src="https://cdn.zzfzzf.com/article/7089165226674884608/1690188758479.png" alt="CleanShot 2023-07-24 at 16.50.31@2x.png"></p>
<h3>实现的功能</h3>
<blockquote>
<p>目前家里所有的设备功能都集成到这一个mini主机上，放在弱电箱，测试下来功率20W不到。还是挺省电的。
因为这是双网口，所以虚拟了一个openwrt做软路由把原来的j1900给替换掉了</p>
</blockquote>
<h3>PVE的安装</h3>
<p>需要1块U盘即可，无脑下一步</p>
<h3>gitea</h3>
<blockquote>
<p>私有git服务，为什么不用gitlab？用不到那么多功能，只是为了管理k8s配置文件的。平时的代码还是存在github上</p>
</blockquote>
<h3>node1</h3>
<blockquote>
<p>k8s master节点</p>
</blockquote>
<h3>node2</h3>
<blockquote>
<p>k8s slave节点,为什么就一台物理机器还虚拟2个虚拟机组k8s网？完全没意义。本着学习心态组的网络，没追求高可用的实用</p>
</blockquote>
<h3>drone</h3>
<blockquote>
<p>用来cicd的一台机器</p>
</blockquote>
<h3>openwrt</h3>
<blockquote>
<p>pve安装openwrt过程</p>
</blockquote>
<ol>
<li>在pve上传openwrt固件</li>
</ol>
<p><img src="https://w.zzfzzf.com/img/1707460820872.png" alt="CleanShot 2024-02-09 at 14.40.06@2x.png">
2. 进入pveshell系统，执行</p>
<pre><code class="language-shell">qm importdisk 105 /var/lib/vz/template/iso/openwrt-x86-64-generic-ext4-combined-efi.img local-lvm
</code></pre>
<p>未完待续。。。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[WireGuard]]></title>
            <link>https://zzfzzf.com/post/7045734832172830720</link>
            <guid>7045734832172830720</guid>
            <pubDate>Sun, 26 Mar 2023 12:34:47 GMT</pubDate>
            <content:encoded><![CDATA[<h2>序言</h2>
<p>最近将所有服务器更换为Debian，并在其上安装WireGuard<br>首先访问WireGuard官方网站获取安装信息：<a href="https://www.wireguard.com/install/#installation">https://www.wireguard.com/install/#installation</a></p>
<h2>安装WireGuard</h2>
<ol>
<li>首先，确保系统是最新的</li>
</ol>
<pre><code class="language-bash">apt update
</code></pre>
<ol start="2">
<li>接着，安装WireGuard</li>
</ol>
<pre><code class="language-bash">apt install wireguard
</code></pre>
<h2>配置WireGuard服务器</h2>
<ol>
<li>创建WireGuard配置目录并更改权限：</li>
</ol>
<pre><code class="language-bash">cd /etc/wireguard/ 
umask 077
</code></pre>
<ol start="2">
<li>生成密钥对</li>
</ol>
<pre><code class="language-bash">wg genkey &gt; server.key 
wg pubkey &lt; server.key &gt; server.key.pub
</code></pre>
<ol start="3">
<li>创建服务器配置文件：</li>
</ol>
<pre><code class="language-bash">vim /etc/wireguard/wg0.conf
</code></pre>
<ol start="4">
<li>编辑配置文件,将生成的PrivateKey替换到<code>wg0.conf</code>文件中的<code>PrivateKey</code>字段。</li>
</ol>
<pre><code class="language-ini">[Interface]
Address = 192.168.0.1/16
PostUp = iptables -A FORWARD -i %i -j ACCEPT;iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT;iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
ListenPort = 51820
PrivateKey = {Server_PrivateKey}

[Peer]
PublicKey = {Client1_PublicKey}
AllowedIPs = 192.168.0.2/32
PersistentKeepalive = 25

[Peer]
PublicKey = {Client2_PublicKey}
AllowedIPs = 192.168.0.3/32
PersistentKeepalive = 25
</code></pre>
<ol start="5">
<li>启动并检查WireGuard服务器：</li>
</ol>
<pre><code class="language-bash">sudo wg-quick up wg0 
sudo wg-quick down wg0 
sudo wg show
</code></pre>
<h2>配置WireGuard客户端</h2>
<ol>
<li>创建客户端配置文件：</li>
</ol>
<pre><code class="language-bash">vim /etc/wireguard/wg0.conf
</code></pre>
<ol start="2">
<li>生成密钥对</li>
</ol>
<pre><code class="language-bash">wg genkey &gt; client.key 
wg pubkey &lt; client.key &gt; client.key.pub
</code></pre>
<ol start="3">
<li>编辑配置文件</li>
</ol>
<pre><code class="language-ini">[Interface]
PrivateKey = {Client_PrivateKey}
Address = 192.168.0.2/16

[Peer]
PublicKey = {Server_PublicKey}
Endpoint = {Server_IP}:51820
AllowedIPs = 192.168.0.0/16
PersistentKeepalive = 25
</code></pre>
<h2>进阶</h2>
<blockquote>
<p>利用wireguard实现远程访问家里网络内任意设备</p>
</blockquote>
<p>我是在<code>openwrt</code>上安装的<code>wireguard</code>，你也可以在家里其他路由器上安装</p>
<ol>
<li>按上述组好网络,openwrt打开防火墙，允许 WireGuard 流量通过</li>
<li>查看是否开启IP转发功能</li>
</ol>
<pre><code class="language-bash">sysctl net.ipv4.ip_forward
</code></pre>
<p>如果该命令返回 net.ipv4.ip_forward = 1，那么 IP 转发功能已经启用了。如果没有，需要修改<code>/etc/sysctl.conf</code>,在该文件中添加</p>
<pre><code class="language-bash">net.ipv4.ip_forward=1
</code></pre>
<p>然后，运行<code>sysctl -p</code>命令应用更改。</p>
<p><img src="https://cdn.zzfzzf.com/article/7045734832172830720/1680194710337.png" alt="image.png"></p>
<ul>
<li>/32 表示单个IP地址，例如：192.168.1.1/32 表示只包括IP地址192.168.1.1。</li>
<li>/24 表示一个子网，有256个IP地址，例如：192.168.1.0/24 包括从192.168.1.0到192.168.1.255的所有IP地址。</li>
<li>/16 表示一个更大的子网，有65536个IP地址，例如：192.168.0.0/16 包括从192.168.0.0到192.168.255.255的所有IP地址。</li>
<li>/8 表示一个更大的子网，有16777216个IP地址，例如：10.0.0.0/8 包括从10.0.0.0到10.255.255.255的所有IP地址。</li>
</ul>
<h2>K8S组网</h2>
<p>待完善</p>
<h2>参考文章</h2>
<ol>
<li><a href="https://www.kuangstudy.com/bbs/1599352503701753858">填坑指南</a></li>
<li><a href="https://tun6.com/tool/wireguard/#%E6%9C%8D%E5%8A%A1%E7%AB%AF">WireGuard 安装与使用</a></li>
<li><a href="https://www.myfreax.com/how-to-set-up-wireguard-vpn-on-debian-11/#linuxmacos%E5%AE%A2%E6%88%B7%E7%AB%AF">如何在Debian 11安装WireGuard VPN</a></li>
<li><a href="https://dev.admirable.pro/using-wireguard/">使用 WireGuard 搭建 VPN 访问家庭内网</a></li>
<li><a href="https://www.ioiox.com/archives/163.html">CentOS 7 安装 WireGuard 详细教程</a></li>
<li><a href="https://blog.csdn.net/qq_42766492/article/details/122159479">https://blog.csdn.net/qq_42766492/article/details/122159479</a></li>
<li><a href="https://juejin.cn/post/7080010903256563748#heading-2">https://juejin.cn/post/7080010903256563748#heading-2</a></li>
<li><a href="https://blog.csdn.net/x_mm_c/article/details/117999495">https://blog.csdn.net/x_mm_c/article/details/117999495</a></li>
<li><a href="https://dev.admirable.pro/using-wireguard/">https://dev.admirable.pro/using-wireguard/</a></li>
<li><a href="https://outti.me/use_google_one_vpn">https://outti.me/use_google_one_vpn</a></li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[博客体验优化]]></title>
            <link>https://zzfzzf.com/post/7044848590027624448</link>
            <guid>7044848590027624448</guid>
            <pubDate>Fri, 24 Mar 2023 01:53:10 GMT</pubDate>
            <content:encoded><![CDATA[<h2>响应式</h2>
<p>整体响应式分三部分</p>
<ol>
<li>Compact</li>
<li>Medium</li>
<li>Expanded</li>
</ol>
<p><a href="https://github.com/shikijs/shiki">https://github.com/shikijs/shiki</a>
<a href="https://github.com/highlightjs/highlight.js">https://github.com/highlightjs/highlight.js</a>
<a href="https://github.com/PrismJS/prism">https://github.com/PrismJS/prism</a></p>
<p><a href="https://github.com/markedjs/marked">https://github.com/markedjs/marked</a>
<a href="https://github.com/markdown-it/markdown-it">https://github.com/markdown-it/markdown-it</a>
<a href="https://github.com/remarkjs/remark">https://github.com/remarkjs/remark</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[前端编码规范检查]]></title>
            <link>https://zzfzzf.com/post/7038894436650192896</link>
            <guid>7038894436650192896</guid>
            <pubDate>Tue, 07 Mar 2023 15:33:30 GMT</pubDate>
            <content:encoded><![CDATA[<p>统一团队的代码规范，以react项目为例子，举一反三</p>
<blockquote>
<p>”eslint“:  javascript代码检测工具<br>&quot;stylelint&quot;:  css检测工具<br>&quot;stylelint-config-standard&quot;:  stylelint的推荐配置<br>&quot;stylelint-order&quot;:  css属性排序插件，合理的排序加快页面渲染<br>&quot;stylelint-scss&quot;:  增加支持scss语法<br>&quot;husky+lint-staged&quot;：commit时候代码检查</p>
</blockquote>
<pre><code class="language-json">{
&quot;devDependencies&quot;: {
    &quot;@babel/core&quot;: &quot;^7.14.8&quot;,
    &quot;@babel/plugin-transform-runtime&quot;: &quot;^7.14.5&quot;,
    &quot;@babel/preset-env&quot;: &quot;^7.14.9&quot;,
    &quot;@babel/preset-react&quot;: &quot;^7.14.5&quot;,
    &quot;@babel/preset-typescript&quot;: &quot;^7.14.5&quot;,
    &quot;@babel/runtime-corejs3&quot;: &quot;^7.14.9&quot;,
    &quot;@pmmmwh/react-refresh-webpack-plugin&quot;: &quot;^0.5.1&quot;,
    &quot;@types/react-dom&quot;: &quot;^17.0.9&quot;,
    &quot;@typescript-eslint/eslint-plugin&quot;: &quot;^4.29.1&quot;,
    &quot;@typescript-eslint/parser&quot;: &quot;^4.29.1&quot;,
    &quot;babel-loader&quot;: &quot;^8.2.2&quot;,
    &quot;cross-env&quot;: &quot;^7.0.3&quot;,
    &quot;css-loader&quot;: &quot;^6.2.0&quot;,
    &quot;css-minimizer-webpack-plugin&quot;: &quot;^3.1.1&quot;,
    &quot;dotenv&quot;: &quot;^10.0.0&quot;,
    &quot;dotenv-webpack&quot;: &quot;^7.0.3&quot;,
    &quot;eslint&quot;: &quot;^7.32.0&quot;,
    &quot;eslint-config-google&quot;: &quot;^0.14.0&quot;,
    &quot;eslint-config-prettier&quot;: &quot;^8.3.0&quot;,
    &quot;eslint-plugin-prettier&quot;: &quot;^3.4.0&quot;,
    &quot;eslint-plugin-react&quot;: &quot;^7.24.0&quot;,
    &quot;eslint-plugin-react-hooks&quot;: &quot;^4.2.0&quot;,
    &quot;html-webpack-plugin&quot;: &quot;^5.3.2&quot;,
    &quot;husky&quot;: &quot;^7.0.2&quot;,
    &quot;lint-staged&quot;: &quot;^11.1.2&quot;,
    &quot;mini-css-extract-plugin&quot;: &quot;^2.4.2&quot;,
    &quot;postcss-loader&quot;: &quot;^6.2.0&quot;,
    &quot;postcss-preset-env&quot;: &quot;^6.7.0&quot;,
    &quot;prettier&quot;: &quot;^2.3.2&quot;,
    &quot;react-refresh&quot;: &quot;^0.10.0&quot;,
    &quot;sass&quot;: &quot;^1.40.1&quot;,
    &quot;sass-loader&quot;: &quot;^12.1.0&quot;,
    &quot;style-loader&quot;: &quot;^3.2.1&quot;,
    &quot;stylelint&quot;: &quot;^13.13.1&quot;,
    &quot;tsconfig-paths-webpack-plugin&quot;: &quot;^3.5.1&quot;,
    &quot;typescript&quot;: &quot;^4.3.5&quot;,
    &quot;webpack&quot;: &quot;^5.48.0&quot;,
    &quot;webpack-cli&quot;: &quot;^4.7.2&quot;,
    &quot;webpack-dev-server&quot;: &quot;^3.11.2&quot;,
    &quot;webpack-merge&quot;: &quot;^5.8.0&quot;
  }
 }
</code></pre>
<h2>tsconfig</h2>
<blockquote>
<p><code>tsconfig.json</code> 文件包含了 TypeScript 编译器的配置信息</p>
</blockquote>
<h3>Vue</h3>
<p>在一个 Vue TypeScript 项目中，下面是一个常用的配置：</p>
<h3>React</h3>
<p>在一个 React TypeScript 项目中，下面是一个常用的配置：</p>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es6&quot;,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;lib&quot;: [&quot;dom&quot;, &quot;dom.iterable&quot;, &quot;esnext&quot;],
    &quot;jsx&quot;: &quot;react&quot;,
    &quot;strict&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true
  },
  &quot;include&quot;: [&quot;src&quot;]
}
</code></pre>
<h2>eslint+prettier</h2>
<h3>Vue</h3>
<pre><code class="language-bash"># 按照依赖
npm install --save-dev eslint prettier eslint-plugin-vue eslint-config-prettier eslint-plugin-prettier husky lint-staged
</code></pre>
<pre><code class="language-js">// .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: [
    &quot;plugin:vue/essential&quot;,
    &quot;eslint:recommended&quot;,
    &quot;prettier&quot;,
    &quot;prettier/vue&quot;,
  ],
  plugins: [&quot;vue&quot;, &quot;prettier&quot;],
  parserOptions: {
    parser: &quot;babel-eslint&quot;,
  },
  rules: {
    &quot;prettier/prettier&quot;: [
      &quot;error&quot;,
      {
        printWidth: 100,
        semi: true,
        singleQuote: true,
        trailingComma: &quot;es5&quot;,
        arrowParens: &quot;always&quot;,
      },
    ],
  },
};
</code></pre>
<pre><code class="language-js">// .prettierrc.js
module.exports = {
  printWidth: 100,
  semi: true,
  singleQuote: true,
  trailingComma: &quot;es5&quot;,
  arrowParens: &quot;always&quot;,
};
</code></pre>
<pre><code class="language-json5">//package.json
{
  &quot;husky&quot;: {
    &quot;hooks&quot;: {
      &quot;pre-commit&quot;: &quot;lint-staged&quot;
    }
  },
  &quot;lint-staged&quot;: {
    &quot;*.js&quot;: [
      &quot;eslint --fix&quot;,
      &quot;git add&quot;
    ],
    &quot;*.vue&quot;: [
      &quot;eslint --fix&quot;,
      &quot;git add&quot;
    ]
  }
}
</code></pre>
<h3>React</h3>
<p>我们使用<code>ESLint</code>来<code>Linting Code</code></p>
<ol>
<li>安装所需要的依赖项</li>
</ol>
<pre><code class="language-bash">yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --dev
</code></pre>
<ol start="2">
<li>在项目根目录下添加<code>.eslintrc.json</code></li>
</ol>
<pre><code class="language-json">{
    &quot;env&quot;: {
        &quot;browser&quot;: true,
        &quot;es2020&quot;: true,
        &quot;node&quot;: true
    },
    &quot;extends&quot;: [
        &quot;plugin:promise/recommended&quot;,
        &quot;plugin:react/recommended&quot;,
        &quot;plugin:react-hooks/recommended&quot;,
        &quot;plugin:@typescript-eslint/recommended&quot;,
        &quot;prettier/@typescript-eslint&quot;,
        &quot;plugin:prettier/recommended&quot;
    ],
    &quot;parser&quot;: &quot;@typescript-eslint/parser&quot;,
    &quot;parserOptions&quot;: {
        &quot;ecmaFeatures&quot;: {
            &quot;jsx&quot;: true
        },
        &quot;ecmaVersion&quot;: 2020,
        &quot;sourceType&quot;: &quot;module&quot;
    },
    &quot;plugins&quot;: [
        &quot;promise&quot;,
        &quot;react&quot;,
        &quot;@typescript-eslint&quot;,
        &quot;react-hooks&quot;,
        &quot;prettier&quot;
    ],
    &quot;rules&quot;: {
        &quot;prettier/prettier&quot;: 2,
        &quot;react-hooks/rules-of-hooks&quot;: 2,
        &quot;react-hooks/exhaustive-deps&quot;: 1,
        &quot;react/display-name&quot;: 0,
        &quot;react/prop-types&quot;:0,
        &quot;@typescript-eslint/no-var-requires&quot;: 0
    },
    &quot;settings&quot;: {
        &quot;react&quot;: {
            &quot;version&quot;: &quot;detect&quot;
        }
    }
}
</code></pre>
<blockquote>
<p>.eslintrc.json</p>
</blockquote>
<pre><code class="language-json">{
    &quot;env&quot;: {
        &quot;browser&quot;: true,
        &quot;es2020&quot;: true,
        &quot;node&quot;: true
    },
    &quot;extends&quot;: [
        &quot;plugin:promise/recommended&quot;,
        &quot;plugin:react/recommended&quot;,
        &quot;plugin:react-hooks/recommended&quot;,
        &quot;plugin:@typescript-eslint/recommended&quot;,
        &quot;prettier/@typescript-eslint&quot;,
        &quot;plugin:prettier/recommended&quot;
    ],
    &quot;parser&quot;: &quot;@typescript-eslint/parser&quot;,
    &quot;parserOptions&quot;: {
        &quot;ecmaFeatures&quot;: {
            &quot;jsx&quot;: true
        },
        &quot;ecmaVersion&quot;: 2020,
        &quot;sourceType&quot;: &quot;module&quot;
    },
    &quot;plugins&quot;: [
        &quot;promise&quot;,
        &quot;react&quot;,
        &quot;@typescript-eslint&quot;,
        &quot;react-hooks&quot;,
        &quot;prettier&quot;
    ],
    &quot;rules&quot;: {
      //0 关闭 1 警告 2 错误
        &quot;prettier/prettier&quot;: 2,
        &quot;react-hooks/rules-of-hooks&quot;: 2,
        &quot;react-hooks/exhaustive-deps&quot;: 1,
        &quot;react/display-name&quot;: 0,
        &quot;react/prop-types&quot;:0,
        &quot;@typescript-eslint/no-var-requires&quot;: 0
    },
    &quot;settings&quot;: {
        &quot;react&quot;: {
            &quot;version&quot;: &quot;detect&quot;
        }
    }
}
</code></pre>
<pre><code class="language-bash">yarn add prettier eslint-config-prettier eslint-plugin-prettier --dev
</code></pre>
<p>在项目根目录下添加<code>.prettierrc</code></p>
<pre><code class="language-json">{
  &quot;trailingComma&quot;: &quot;all&quot;,
  &quot;tabWidth&quot;: 2,
  &quot;semi&quot;: true,
  &quot;singleQuote&quot;: true,
  &quot;endOfLine&quot;: &quot;lf&quot;,
  &quot;printWidth&quot;: 100,
  &quot;bracketSpacing&quot;: true,
  &quot;arrowParens&quot;: &quot;always&quot;,
  &quot;useTabs&quot;: false,
  &quot;quoteProps&quot;: &quot;as-needed&quot;,
  &quot;jsxSingleQuote&quot;: true,
  &quot;jsxBracketSameLine&quot;: false,
  &quot;requirePragma&quot;: false,
  &quot;insertPragma&quot;: false,
  &quot;proseWrap&quot;: &quot;preserve&quot;,
  &quot;htmlWhitespaceSensitivity&quot;: &quot;css&quot;,
  &quot;vueIndentScriptAndStyle&quot;: false,
  &quot;embeddedLanguageFormatting&quot;: &quot;auto&quot;
}
</code></pre>
<h2>husky+lint-staged</h2>
<blockquote>
<p>Husky 和 lint-staged 是两个常用的 Git 钩子工具，可以帮助我们在 Git 提交前运行一些脚本，如代码风格检查、代码格式化等。</p>
</blockquote>
<ol>
<li>安装 husky 和 lint-staged：</li>
</ol>
<pre><code class="language-bash">npm install husky lint-staged --save-dev
</code></pre>
<ol start="2">
<li>在 package.json 文件中添加 husky 和 lint-staged 的配置：</li>
</ol>
<pre><code class="language-json">{
  &quot;husky&quot;: {
    &quot;hooks&quot;: {
      &quot;pre-commit&quot;: &quot;lint-staged&quot;
    }
  },
  &quot;lint-staged&quot;: {
    &quot;*.js&quot;: [&quot;eslint --fix&quot;, &quot;git add&quot;]
  }
}
</code></pre>
<pre><code class="language-js">//.lintstagedrc.mjs
export default {
  &quot;*.js&quot;: [
    &quot;eslint --fix&quot;,
    &quot;git add&quot;
  ],
  &quot;*.css&quot;: [
    &quot;stylelint --fix&quot;,
    &quot;git add&quot;
  ]
}
</code></pre>
<p>上述配置的意思是在 Git 提交前运行 lint-staged，lint-staged 会检查所有的 .js 文件，对其中的代码运行 eslint --fix 命令进行代码风格检查和格式化，然后再将修改后的代码添加到 Git 中。
3. 在项目中添加 .eslintrc.js 文件来配置 ESLint，这里以 eslint-config-react-app 为例：</p>
<pre><code class="language-js">module.exports = {
  extends: &#39;react-app&#39;,
  rules: {
    // 在这里添加其他的规则
  }
};
</code></pre>
<ol start="4">
<li>在 package.json 文件中添加 scripts 来运行代码检查和格式化：</li>
</ol>
<pre><code class="language-json">{
  &quot;scripts&quot;: {
    &quot;lint&quot;: &quot;eslint src&quot;,
    &quot;prettier&quot;: &quot;prettier src/**/*.js --write&quot;
  }
}
</code></pre>
<pre><code class="language-json">{
  &quot;husky&quot;: {
      &quot;hooks&quot;: {
          &quot;pre-commit&quot;: &quot;lint-staged&quot;
      }
  },
  &quot;lint-staged&quot;: {
    &quot;src/**/*.{ts,tsx,js,json,less,md}&quot;: [
      &quot;prettier --write&quot;
    ],
    &quot;src/**/*.{ts,tsx,js}&quot;: [
      &quot;eslint --config .eslintrc.json&quot;
    ]
  }
}
</code></pre>
<h2>Babel</h2>
<pre><code class="language-bash">yarn add @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/plugin-transform-runtime -D
</code></pre>
<blockquote>
<p>babel.config.json</p>
</blockquote>
<pre><code class="language-json">{
  &quot;presets&quot;: [
    &quot;@babel/preset-env&quot;,
    &quot;@babel/preset-react&quot;,
    &quot;@babel/preset-typescript&quot;
  ],
  &quot;plugins&quot;: [
    &quot;@babel/plugin-transform-runtime&quot;
  ]
}
</code></pre>
<blockquote>
<p>tsconfig.json</p>
</blockquote>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES2020&quot;,
    &quot;module&quot;: &quot;commonjs&quot;,
    &quot;allowJs&quot;: true,
    &quot;jsx&quot;: &quot;react&quot;,
    &quot;declaration&quot;: false,
    &quot;downlevelIteration&quot;: true,
    &quot;strict&quot;: true,
    &quot;noImplicitAny&quot;: true,
    &quot;strictNullChecks&quot;: true,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;baseUrl&quot;: &quot;./src&quot;,
    &quot;paths&quot;: {
      &quot;api/*&quot;: [&quot;services/*&quot;]
    },
    &quot;types&quot;: [&quot;node&quot;, &quot;webpack-env&quot;],
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true
  },
  &quot;exclude&quot;: [&quot;config&quot;, &quot;commitlint.config.js&quot;]
}
</code></pre>
<blockquote>
<p>.prettierrc</p>
</blockquote>
<pre><code class="language-json">{
  &quot;trailingComma&quot;: &quot;all&quot;,
  &quot;tabWidth&quot;: 2,
  &quot;semi&quot;: true,
  &quot;singleQuote&quot;: true,
  &quot;endOfLine&quot;: &quot;lf&quot;,
  &quot;printWidth&quot;: 100,
  &quot;bracketSpacing&quot;: true,
  &quot;arrowParens&quot;: &quot;always&quot;,
  &quot;useTabs&quot;: false,
  &quot;quoteProps&quot;: &quot;as-needed&quot;,
  &quot;jsxSingleQuote&quot;: true,
  &quot;jsxBracketSameLine&quot;: false,
  &quot;requirePragma&quot;: false,
  &quot;insertPragma&quot;: false,
  &quot;proseWrap&quot;: &quot;preserve&quot;,
  &quot;htmlWhitespaceSensitivity&quot;: &quot;css&quot;,
  &quot;vueIndentScriptAndStyle&quot;: false,
  &quot;embeddedLanguageFormatting&quot;: &quot;auto&quot;
}
</code></pre>
<h2>换行符</h2>
<blockquote>
<p>一般来说，换行符有两种类型：Windows风格的CRLF（回车换行）和Unix风格的LF（仅换行）。
windows和mac、linux默认的换行符不同，为了方便git版本管理，我们需要统一换行符</p>
</blockquote>
<h3>配置详解</h3>
<blockquote>
<p> eol (end of line)</p>
</blockquote>
<pre><code class="language-plain">#.gitignore
text eol=lf
*.jpg   binary
*.png   binary
*.gif   binary
*.svg   binary
</code></pre>
<h3>已有项目更新换行符</h3>
<ol>
<li>Visual Studio Code：在打开的项目中，可以使用“查找和替换”功能来替换换行符。具体步骤如下：</li>
</ol>
<ul>
<li>打开要修改的文件夹或项目。</li>
<li>使用快捷键 Ctrl + Shift + F（Windows / Linux）或 Command + Shift + F（macOS）打开“查找和替换”面板。</li>
<li>在“查找”文本框中输入要替换的换行符类型，例如“\r\n”（Windows风格）或“\n”（Unix风格）。</li>
<li>在“替换为”文本框中输入要替换成的换行符类型，例如“\n”（Unix风格）或“\r\n”（Windows风格）。</li>
<li>点击“全部替换”按钮进行批量替换。</li>
</ul>
<ol start="2">
<li>WebStorm</li>
</ol>
<ul>
<li>打开 WebStorm 并打开要修改的项目。</li>
<li>在 WebStorm 的菜单栏中选择 Edit（编辑）-&gt; Find -&gt; Replace in Path（替换路径中的内容）。</li>
<li>在弹出的对话框中，输入要查找的文本，在“搜索路径”中选择要替换的文件或文件夹，并在“替换为”字段中输入要替换成的换行符类型。</li>
<li>在“options”选项卡中，可以选择要搜索的文件类型，以及是否区分大小写等。</li>
<li>点击“Find”按钮，WebStorm 将在项目中查找符合要求的文本，并在底部面板中显示搜索结果。</li>
<li>点击“Replace”按钮，WebStorm 将批量替换符合要求的文本。</li>
</ul>
<p>参考链接</p>
<ul>
<li><a href="https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project">Using ESLint and Prettier in a TypeScript Project</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[RIC和RAF]]></title>
            <link>https://zzfzzf.com/post/7038009029850959872</link>
            <guid>7038009029850959872</guid>
            <pubDate>Sun, 05 Mar 2023 04:55:12 GMT</pubDate>
            <content:encoded><![CDATA[<h2>RIC</h2>
<blockquote>
<p><code>requestIdleCallback()</code> (RIC) 是一种浏览器 API，它可以在浏览器空闲时执行回调函数。由于它不会阻塞主线程，因此可以用于执行一些后台任务或预加载资源，以避免阻塞用户界面</p>
</blockquote>
<h3>基本语法</h3>
<pre><code class="language-js">window.requestIdleCallback(callback[, options]);
</code></pre>
<p>其中，callback 是要运行的函数，options 是一个可选参数对象，可以设置回调函数的超时时间和调度优先级。例如：</p>
<pre><code class="language-js">window.requestIdleCallback(function() {
  console.log(&#39;这是一个任务&#39;);
}, { timeout: 2000 });
</code></pre>
<p>requestIdleCallback 的返回值是一个 ID，可以用于取消任务：</p>
<pre><code class="language-js">const requestId = window.requestIdleCallback(function() {
  console.log(&#39;这是一个任务&#39;);
});

window.cancelIdleCallback(requestId);
</code></pre>
<blockquote>
<p><code>requestIdleCallback()</code> 是一个浏览器 API，可以在主线程空闲时运行回调函数。这使得我们可以在不影响页面流畅性的情况下执行一些较重的操作。在优化 document.addEventListener 监听 scroll 事件时，可以使用 requestIdleCallback() 来推迟处理滚动事件，以减少滚动事件的处理次数。</p>
</blockquote>
<pre><code class="language-tsx">import { useEffect, useRef } from &#39;react&#39;;

function handleScroll() {
  // 处理滚动事件
}

function useRequestIdleScroll() {
  const tickingRef = useRef(false);

  function requestTick() {
    if (!tickingRef.current) {
      requestIdleCallback(() =&gt; {
        handleScroll();
        tickingRef.current = false;
      });
      tickingRef.current = true;
    }
  }

  useEffect(() =&gt; {
    document.addEventListener(&#39;scroll&#39;, requestTick);
    return () =&gt; {
      document.removeEventListener(&#39;scroll&#39;, requestTick);
    };
  }, []);
}

function App() {
  useRequestIdleScroll();
  // 渲染组件
}
</code></pre>
<pre><code class="language-ts">//useRequestIdleScroll.ts
import {useEffect, useRef} from &quot;react&quot;;

function useRequestIdleScroll(handler: () =&gt; void) {
    const tickingRef = useRef(false);
    function requestTick() {
        if (!tickingRef.current) {
            requestIdleCallback(() =&gt; {
                handler();
                tickingRef.current = false;
            });
            tickingRef.current = true;
        }
    }

    useEffect(() =&gt; {
        document.addEventListener(&#39;scroll&#39;, requestTick);
        return () =&gt; {
            document.removeEventListener(&#39;scroll&#39;, requestTick);
        };
    }, []);
}
export default useRequestIdleScroll
</code></pre>
<h2>RAF</h2>
<blockquote>
<p><code>requestAnimationFrame</code> (RAF) 是一种浏览器 API，它可以在浏览器下一次重绘之前执行回调函数。由于它与浏览器的渲染循环同步，因此在使用动画或其他需要频繁更新的元素时非常有用。</p>
</blockquote>
<pre><code class="language-js">window.requestAnimationFrame(callback);
</code></pre>
<p>其中，callback 是要运行的函数。该函数应该负责更新动画或其他页面元素，然后在下一次重新渲染页面之前再次调用 requestAnimationFrame。</p>
<pre><code class="language-js">function animate() {
  // 更新动画
  window.requestAnimationFrame(animate);
}

animate();

</code></pre>
<p>requestAnimationFrame 的返回值是一个 ID，可以用于取消动画：</p>
<pre><code class="language-js">const animationId = window.requestAnimationFrame(function() {
  // 更新动画
});

window.cancelAnimationFrame(animationId);
</code></pre>
<h3>常见场景</h3>
<ol>
<li><p>动画：<code>requestAnimationFrame</code> 是最常见的用例之一。通过在每个动画帧上更新元素的位置或样式，可以创建流畅的动画效果。使用 <code>requestAnimationFrame</code> 可以避免使用 <code>setInterval</code> 或 <code>setTimeout</code> 导致的不同步和性能问题。</p>
</li>
<li><p>游戏：<code>requestAnimationFrame</code> 也非常适合用于游戏开发。在每个游戏循环中使用 <code>requestAnimationFrame</code> 更新游戏状态和元素位置可以确保游戏的流畅度和响应性。</p>
</li>
<li><p>滚动：通过 <code>requestAnimationFrame</code> 更新页面的滚动位置，可以创建平滑的滚动效果，而不会出现卡顿或闪烁的情况。这在单页应用程序或长页面中非常有用。使用 <code>requestAnimationFrame()</code> 仍然可能会导致滚动事件处理函数的频率过高。因此，在实际应用中进行性能测试和调优非常重要，以确保代码可以在各种情况下正常运行</p>
</li>
<li><p>图表：使用 <code>requestAnimationFrame</code> 可以在每个动画帧上更新图表的数据和样式，从而创建动态的、实时的图表效果。</p>
</li>
<li><p>视频播放器：使用 <code>requestAnimationFrame</code> 可以在每个动画帧上更新视频的时间戳和播放状态，从而创建流畅的视频播放效果。</p>
</li>
</ol>
<p>需要注意的是，requestAnimationFrame 的回调函数应该尽可能快地执行完毕，以确保动画的流畅度和响应性。如果需要执行更长时间的任务，可以将它们拆分为多个小任务，并使用 requestIdleCallback 在空闲时运行它们。</p>
<h2>区别</h2>
<p><code>requestIdleCallback</code> 和 <code>requestAnimationFrame</code> 都是用于浏览器性能优化的 API，但它们的作用不同。<code>requestIdleCallback</code> 用于在浏览器空闲时运行任务，例如后台工作或预加载资源。<code>requestAnimationFrame</code> 则用于在每次重新渲染页面时更新动画或其他需要频繁更新的元素。</p>
<p>此外，<code>requestIdleCallback</code> 和 <code>requestAnimationFrame</code> 在性能和使用方面也有一些区别：</p>
<ul>
<li><p>性能：<code>requestIdleCallback</code> 可以将任务拆分为多个小任务，因此它更适合执行耗时较长的任务。而 <code>requestAnimationFrame</code> 的回调函数应该尽可能快地执行完毕，以确保动画的流畅度。</p>
</li>
<li><p>兼容性：<code>requestIdleCallback</code> 是一个比较新的 API，尚未被所有浏览器支持。而 <code>requestAnimationFrame</code> 已经被广泛支持，可以在大多数现代浏览器中使用。</p>
</li>
<li><p>使用：<code>requestIdleCallback</code> 可以用于执行后台任务或预加载资源，以避免阻塞用户界面。而 <code>requestAnimationFrame</code> 适用于在页面中创建流畅的动画或其他需要频繁更新的元素。</p>
</li>
</ul>
<p>使用建议
在实际项目中，我们可以根据具体场景来选择使用 <code>requestIdleCallback</code> 和 <code>requestAnimationFrame</code>。一般来说，我们应该优先选择 requestAnimationFrame 来更新动画或其他频繁更新的元素，以确保流畅度和性能。而对于一些后台任务或预加载资源，我们可以使用 requestIdleCallback 来避免阻塞用户界面。</p>
<p>另外，我们还可以结合使用这两个 API，例如在页面中创建复杂的动画时，我们可以使用 <code>requestAnimationFrame</code> 更新动画，同时在动画之外使用 <code>requestIdleCallback</code> 来执行一些后台任务或预加载资源，以提高用户体验。</p>
<h2>总结</h2>
<p><code>requestIdleCallback</code> 和 <code>requestAnimationFrame</code> 都是用于浏览器性能优化的 API。<code>requestIdleCallback</code> 可以在浏览器空闲时运行任务，例如后台工作或预加载资源，而 <code>requestAnimationFrame</code> 则用于在每次重新渲染页面时更新动画或其他需要频繁更新的元素。在使用时，我们应该根据具体场景选择合适的 API，同时结合使用它们以提高用户体验。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[k8s持久化存储之NFS]]></title>
            <link>https://zzfzzf.com/post/7028901660936245248</link>
            <guid>7028901660936245248</guid>
            <pubDate>Wed, 08 Feb 2023 01:45:57 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>首先在集群之外安装服务端</p>
</blockquote>
<h2>nfs-server</h2>
<pre><code class="language-bash">apt update
apt upgrade
apt install nfs-kernel-server

mkdir -p /srv/nfs/share #创建共享目录
chmod 777 /srv/nfs/share #分配权限
echo &quot;/srv/nfs/share 192.168.100.0/24(rw,sync,no_root_squash,no_subtree_check)&quot; &gt;&gt; /etc/exports
exportfs -rv //使配置生效

#验证挂载
showmount -e localhost
#Export list for localhost:
#/srv/nfs/share 192.168.100.0/22
</code></pre>
<h2>nfs-client</h2>
<blockquote>
<p>所有节点安装nfs客户端!!⚠️</p>
</blockquote>
<pre><code class="language-bash">apt update
apt upgrade
apt-get install -y nfs-common
showmount -e 192.168.100.193
#Export list for 192.168.100.0:
#/srv/nfs/share 192.168.100.0/22
</code></pre>
<h2>k8s 安装</h2>
<h3>驱动安装</h3>
<p>参考<a href="https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/docs/install-csi-driver-master.md">https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/docs/install-csi-driver-master.md</a></p>
<pre><code class="language-bash">curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/deploy/install-driver.sh | bash -s master --
</code></pre>
<h3>StorageClass</h3>
<p>参考<a href="https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/deploy/storageclass.yaml">https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/deploy/storageclass.yaml</a>
博主直接使用</p>
<pre><code class="language-bash">kubtctl apply -f https://git.ooxo.cc/k8s/yaml/raw/branch/main/storageclass-nfs.yaml
</code></pre>
<h2>参考资料</h2>
<ul>
<li><a href="https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/deploy/example/storageclass-nfs.yaml">https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/deploy/example/storageclass-nfs.yaml</a></li>
<li><a href="https://www.cnblogs.com/layzer/articles/nfs-csi-use.html">https://www.cnblogs.com/layzer/articles/nfs-csi-use.html</a></li>
<li><a href="https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/docs/install-csi-driver-master.md">https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/docs/install-csi-driver-master.md</a></li>
<li><a href="https://juejin.cn/post/7109646185924657188">kubernetes配置安装nfs</a></li>
<li><a href="https://blog.csdn.net/u010241463/article/details/107006690">NFS 服务器安装及客户端挂载</a></li>
<li><a href="https://juejin.cn/post/7186925237592653884">一文读懂 Kubernetes 存储设计</a></li>
<li><a href="http://www.lishuai.fun/2021/08/12/k8s-nfs-pv/">
史上最全之K8s使用nfs作为存储卷的五种方式</a></li>
<li><a href="https://www.jianshu.com/p/74bc360006d9">NFS服务原理及配置</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[2022我干了些什么]]></title>
            <link>https://zzfzzf.com/post/7021740551498240000</link>
            <guid>7021740551498240000</guid>
            <pubDate>Thu, 19 Jan 2023 07:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<h2>软件</h2>
<ul>
<li>公司有JR的正版授权，主力都用JR全家桶。普通文本编辑用的VS CODE。</li>
<li>1password,强烈安利。所有的密码都是它生成和记住的。我只需记住一个主密码就行了</li>
<li>simplelogin 匿名邮箱，每个网站注册留下的邮箱都是我的自定义域名的随机前缀邮箱，不想用了就删除就行了</li>
<li>从钉钉转到了飞书，只能说 相见恨晚</li>
</ul>
<h2>硬件</h2>
<ul>
<li>APPLE TV 日本亚马逊购入，使用体验很好</li>
<li>MAC M1PRO 公司发的，风扇从来没有转过。家里的intel的mac开机没一会就呼呼了</li>
<li>软路由+AD HOME 实现了广告入户拦截</li>
</ul>
<h2>服务</h2>
<p>2022年购买了 <strong>netflix</strong> <strong>disneyplus</strong> <strong>youtube</strong> <strong>爱奇艺</strong> <strong>优酷</strong> <strong>腾讯</strong> <strong>京东</strong> <strong>淘宝会员</strong>。后续视频会员可能不会再续费了，全是广告，不如看云盘</p>
<h2>工作</h2>
<p>年初因为原先的公司搬家到很远的地方，换了一份离家近的工作。确实舒服了很多</p>
<h2>其他</h2>
<ul>
<li>博客后端<strong>springboot</strong>重构成了<strong>midway</strong>框架</li>
<li><strong>jenkins</strong>换成了<strong>drone</strong></li>
<li>入坑了PT</li>
</ul>
<h2>2023</h2>
<ul>
<li>apple watch 计划中</li>
<li>switch 优先级不高</li>
<li>工作上再主动一点，多涉及其他领域的一些技能和业务。</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[高性价比的家用计算机]]></title>
            <link>https://zzfzzf.com/post/7005096001761579008</link>
            <guid>7005096001761579008</guid>
            <pubDate>Sun, 04 Dec 2022 09:10:35 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>本文具有时效性，2022年12月</p>
</blockquote>
<h2>需求背景</h2>
<p>家有旧电脑一台，需要再搭建一台家用服务器。决定把旧电脑改造成家用服务器，新购入硬件当家用计算机</p>
<blockquote>
<p>现有配置</p>
</blockquote>
<table>
<thead>
<tr>
<th>硬件</th>
<th>型号</th>
<th>购入渠道</th>
</tr>
</thead>
<tbody><tr>
<td>CPU</td>
<td>9400F</td>
<td>京东</td>
</tr>
<tr>
<td>主板</td>
<td>B360M-MORTAR</td>
<td>京东</td>
</tr>
<tr>
<td>显卡</td>
<td>蓝宝石RX580 8G</td>
<td>京东</td>
</tr>
<tr>
<td>内存</td>
<td>芝奇D4-2666 * 2</td>
<td>京东</td>
</tr>
<tr>
<td>硬盘</td>
<td>三星970EVO-PLUS 250G</td>
<td>京东</td>
</tr>
</tbody></table>
<blockquote>
<p>计划升级方案</p>
</blockquote>
<p>给家里主机淘宝购入亮机卡<strong>GT610</strong>,<strong>RX580</strong>拆下用来组装新主机，观察功耗，50W以内为合格。</p>
<p> 考虑到后续售后问题，全部京东购入。
 目测板U套比淘宝贵500至1000</p>
<p> 新主机配置如下</p>
<table>
<thead>
<tr>
<th>硬件</th>
<th>型号</th>
<th>购入渠道</th>
<th>价格</th>
<th>备注</th>
</tr>
</thead>
<tbody><tr>
<td>CPU</td>
<td>12400F</td>
<td>京东</td>
<td></td>
<td>性价比高</td>
</tr>
<tr>
<td>主板</td>
<td>B660M-MORTAR</td>
<td>京东</td>
<td></td>
<td>华硕重炮手和微星迫击炮中选择，虽然主板有点性能融于，但是支持PCIE4</td>
</tr>
<tr>
<td>内存</td>
<td>看看芝奇 还没研究</td>
<td></td>
<td></td>
<td>16G * 2，后续16G * 4</td>
</tr>
<tr>
<td>硬盘</td>
<td>980PRO-1T</td>
<td>京东</td>
<td>799</td>
<td>暂时先1T ，2个M2，6个sata</td>
</tr>
<tr>
<td>电源</td>
<td></td>
<td></td>
<td></td>
<td>目前500W电源，考虑后续显卡升级，新购600W电源</td>
</tr>
<tr>
<td>机箱</td>
<td></td>
<td></td>
<td></td>
<td>海景房?尽量不要光污染，小一点</td>
</tr>
</tbody></table>
<h2>硬盘对比</h2>
<h3>固态硬盘</h3>
<table>
<thead>
<tr>
<th>品牌</th>
<th>系列</th>
<th>渠道</th>
<th>1T价格</th>
<th>2T价格</th>
<th>备注</th>
</tr>
</thead>
<tbody><tr>
<td>致态</td>
<td>TiPro7000</td>
<td>京东</td>
<td>¥739</td>
<td>¥1599</td>
<td>性价比最高</td>
</tr>
<tr>
<td>西部数据</td>
<td>SN770</td>
<td>京东</td>
<td>¥699</td>
<td>¥1399</td>
<td>无缓存</td>
</tr>
<tr>
<td>西部数据</td>
<td>SN850</td>
<td>京东</td>
<td>¥919</td>
<td>¥1999</td>
<td>小贵</td>
</tr>
<tr>
<td>三星</td>
<td>980PRO</td>
<td>京东</td>
<td>¥799</td>
<td>¥1999</td>
<td>新手无脑选，贴吧传闻0e错误</td>
</tr>
</tbody></table>
<ul>
<li>综上购入三星<strong>980PRO-1T</strong>,五年质保。</li>
<li>后续计划购入<strong>TiPro7000-2T</strong>做第二块硬盘</li>
</ul>
<h3>内存</h3>
<table>
<thead>
<tr>
<th>品牌</th>
<th>型号</th>
<th>价格</th>
</tr>
</thead>
<tbody><tr>
<td>宏基</td>
<td>掠夺者Vesta 3600MHz</td>
<td>16G*2 1099</td>
</tr>
<tr>
<td>宏碁</td>
<td>掠夺者Predator Talos 3200MHz</td>
<td>16G*2 1299</td>
</tr>
<tr>
<td>芝奇皇家戟套条</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2>附录</h2>
<h3>板U套</h3>
<blockquote>
<p>全部来源JD</p>
</blockquote>
<ul>
<li><p>12400+华硕B660M-K D4 ¥2304。推荐+1 现货</p>
</li>
<li><p>12400+华硕B660M-E D4 ¥2354。</p>
</li>
<li><p>12400+华硕B660M-PLUS D4 ¥2534  性能冗余 给 i7-12700F用。现货</p>
</li>
<li><p>12400F+华硕B660M-PLUS D4 ¥2324  性能冗余 给 i7-12700F用。现货</p>
</li>
<li><p>12400+微星B660M-MORTAR D4 ¥2654 缺货</p>
</li>
<li><p>12400F+微星B660M-MORTAR D4 ¥2444 缺货</p>
</li>
<li><p>5600G+华硕B550M-PLUS ¥1628 缺货</p>
</li>
<li><p>5600G+微星B550M-MORTAR D4 ¥1849 现货</p>
</li>
</ul>
<h3>单买</h3>
<ul>
<li>京东 华硕B660M-PLUS ¥1099</li>
<li>京东 微星B660M-MORTAR D4 ¥1199</li>
<li>淘宝 12400 ¥1109</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[更好的保存互联网账号密码]]></title>
            <link>https://zzfzzf.com/post/7002903135874322432</link>
            <guid>7002903135874322432</guid>
            <pubDate>Mon, 28 Nov 2022 07:56:55 GMT</pubDate>
            <content:encoded><![CDATA[<h2>前言</h2>
<blockquote>
<p>随着我们接触的应用越来越多，需要记得账号密码也越来越多，邮箱的垃圾信息也越来越多</p>
</blockquote>
<h2>邮箱</h2>
<p>注册应用最好不要使用自己主要邮箱注册，最好使用<a href="https://sspai.com/post/70198">匿名邮箱</a>,博主使用的是<a href="https://app.simplelogin.io/">simplelogin</a>,30$/year，使用学生邮箱 15$/year。
可以使用自定义域名。注册网站时使用一个随机邮箱，通常是 <code>webName-uuid@domain.com</code></p>
<h3>TL;DR</h3>
<ul>
<li>Apple全家桶可以选择icloud+</li>
<li>Fastmail+1Password功能强，性价比不是很高</li>
<li>SimpleLogin性价比最高</li>
<li>FirefoxRelay免费，功能简单</li>
</ul>
<h3>主流邮箱</h3>
<ul>
<li>gmail</li>
<li>outlook</li>
<li><a href="https://proton.me/">protonMail</a></li>
<li><a href="https://bitwarden.com/">bitwarden</a></li>
</ul>
<h3>匿名邮箱</h3>
<h4>FirefoxRelay</h4>
<ul>
<li><a href="https://relay.firefox.com/">FirefoxRelay⁩</a></li>
<li>使用简单，支持chrome插件</li>
<li>免费版5个别名</li>
<li>付费版$0.99/m</li>
</ul>
<h4>SimpleLogin</h4>
<ul>
<li>开源</li>
<li><a href="https://simplelogin.io/">SimpleLogin</a></li>
</ul>
<h4>iCloud</h4>
<ul>
<li>6R/m</li>
<li>使用繁琐</li>
</ul>
<h4>Fastmail</h4>
<ul>
<li><a href="https://www.fastmail.com/">fastmail</a></li>
<li>配合1password</li>
</ul>
<h4>DuckDuckGo</h4>
<ul>
<li>必须安装浏览器插件</li>
</ul>
<h4>其他邮箱</h4>
<ul>
<li>zoho</li>
<li>yandex</li>
</ul>
<h2>密码</h2>
<p>密码选择<a href="https://sspai.com/post/73018">1password</a>,全平台同步</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[Multipass使用教程]]></title>
            <link>https://zzfzzf.com/post/6994144629549568000</link>
            <guid>6994144629549568000</guid>
            <pubDate>Fri, 04 Nov 2022 03:53:44 GMT</pubDate>
            <content:encoded><![CDATA[<h2>简介</h2>
<p>Multipass 是一个轻量虚拟机管理器，是由 Ubuntu 运营公司 Canonical 所推出的开源项目。运行环境支持 Linux、Windows、macOS。在不同的操作系统上，使用的是不同的虚拟化技术。在 Linux 上使用的是 KVM、Window 上使用 Hyper-V、macOS 中使用 HyperKit 以最小开销运行 VM，支持在笔记本模拟小型云。</p>
<blockquote>
<p>官网 <a href="https://multipass.run/">https://multipass.run/</a></p>
</blockquote>
<h2>安装</h2>
<blockquote>
<p>mac
<a href="https://formulae.brew.sh/cask/multipass">https://formulae.brew.sh/cask/multipass</a></p>
</blockquote>
<pre><code>brew install --cask multipass
brew uninstall multipass
brew uninstall --zap multipass # to destroy all data, too
</code></pre>
<blockquote>
<p>其他</p>
</blockquote>
<h2>常用命令</h2>
<pre><code>multipass --version
multipass find
multipass launch -n test01 -c 2 -m 4G -d 10G
multipass shell test01
</code></pre>
<blockquote>
<p>自定义配置创建可以参考如下方式:
-n, –name: 名称
-c, –cpus: cpu 核心数, 默认: 1
-m, –mem: 内存大小, 默认: 1G
-d, –disk: 硬盘大小, 默认: 5G</p>
</blockquote>
<pre><code># 启动实例
$ multipass start test01
# 停止实例
$ multipass stop test01
# 删除实例（删除后，还会存在）
$ multipass delete test01
$ multipass shell master
# 释放实例（彻底删除）
$ multipass purge test01
$ multipass purge
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[家庭网络环境的体验升级]]></title>
            <link>https://zzfzzf.com/post/6993104149973962752</link>
            <guid>6993104149973962752</guid>
            <pubDate>Tue, 01 Nov 2022 06:59:14 GMT</pubDate>
            <content:encoded><![CDATA[<h2>路由器选择</h2>
<p>路由器主要分为软路由和硬路由。如果你还不知道什么是软路由，<a href="https://baike.baidu.com/item/%E8%BD%AF%E8%B7%AF%E7%94%B1/4824918">点击这里</a>。个人比较推荐软路由做网关，买个硬路由mesh组网的形式</p>
<blockquote>
<p>软路由推荐</p>
</blockquote>
<p>海鲜市场买个工控机，自己编译openwrt刷进去即可</p>
<blockquote>
<p>硬路由推荐</p>
</blockquote>
<p>AX86U做主节点，AX56U做子节点</p>
<h2>CPU选择</h2>
<h3>AMD</h3>
<p>锐龙（Ryzen）系列分3、5、7、9四个定位档次</p>
<ul>
<li>u表示笔记本低压、定位轻薄本</li>
<li>H表示笔记本标压、定位性能本</li>
<li>X=自动超频版，</li>
<li>XT:代表官方优选型号 拥有更强的超频能力，</li>
<li>W=高性能/渲染强，</li>
<li>G=带核显，</li>
<li>E=降低功耗，</li>
<li>K=不锁倍/基本所有的AMD的CPU都支持超频，</li>
<li>HS：低功耗标压，</li>
<li>HX：支持超频的标压，</li>
<li>E=笔记本超低压，</li>
<li>没有后缀:不完整地支持AMD官方超频XFR技术</li>
</ul>
<table>
<thead>
<tr>
<th>平台</th>
<th>系列</th>
<th>CPU</th>
<th>发行年份</th>
<th>核心</th>
<th>线程</th>
<th>性能得分</th>
<th>功耗</th>
<th>备注</th>
</tr>
</thead>
<tbody><tr>
<td></td>
<td></td>
<td>J1900</td>
<td>2014Q1</td>
<td>4</td>
<td>4</td>
<td>1139</td>
<td>10W</td>
<td>纯软路由够了</td>
</tr>
<tr>
<td></td>
<td></td>
<td>J4125</td>
<td>2020Q1</td>
<td>4</td>
<td>4</td>
<td>2987</td>
<td>10W</td>
<td>性价比最高</td>
</tr>
<tr>
<td></td>
<td></td>
<td>N5105</td>
<td>2021Q2</td>
<td>4</td>
<td>4</td>
<td>4056</td>
<td>10W</td>
<td>发热</td>
</tr>
<tr>
<td></td>
<td></td>
<td>N6005</td>
<td>2021Q4</td>
<td>4</td>
<td>4</td>
<td>4633</td>
<td>10W</td>
<td>发热</td>
</tr>
<tr>
<td></td>
<td></td>
<td>i5-8600T</td>
<td>2018Q2</td>
<td>6</td>
<td>6</td>
<td>8369</td>
<td>65W</td>
<td></td>
</tr>
<tr>
<td>Intel</td>
<td>i3</td>
<td>10105</td>
<td>2021Q2</td>
<td>4</td>
<td>8</td>
<td>8783</td>
<td>65W</td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td>3400G</td>
<td>2019Q3</td>
<td>4</td>
<td>8</td>
<td>9340</td>
<td>65W</td>
<td></td>
</tr>
<tr>
<td>Intel</td>
<td>i5</td>
<td>9400F</td>
<td>2019Q1</td>
<td>6</td>
<td>6</td>
<td>9539</td>
<td>65W</td>
<td></td>
</tr>
<tr>
<td>Intel</td>
<td>i5</td>
<td>12100</td>
<td>2022Q1</td>
<td>4</td>
<td>8</td>
<td>14336</td>
<td>60W</td>
<td></td>
</tr>
<tr>
<td>AMD</td>
<td>Ryzen 7</td>
<td>4800U</td>
<td>2020Q2</td>
<td>8</td>
<td>16</td>
<td>17016</td>
<td>15W</td>
<td>贵</td>
</tr>
<tr>
<td>AMD</td>
<td>Ryzen 5</td>
<td>5600H</td>
<td>2021Q2</td>
<td>6</td>
<td>12</td>
<td>17118</td>
<td>45W</td>
<td></td>
</tr>
<tr>
<td>AMD</td>
<td>Ryzen 7</td>
<td>5800U</td>
<td>2021Q1</td>
<td>8</td>
<td>16</td>
<td>18796</td>
<td>15W</td>
<td>贵</td>
</tr>
<tr>
<td>AMD</td>
<td>Ryzen 5</td>
<td>5500</td>
<td>2022Q2</td>
<td>6</td>
<td>12</td>
<td>19481</td>
<td>65W</td>
<td></td>
</tr>
<tr>
<td>Intel</td>
<td>i5</td>
<td>12400</td>
<td>2022Q1</td>
<td>6</td>
<td>12</td>
<td>19501</td>
<td>65W</td>
<td></td>
</tr>
<tr>
<td>AMD</td>
<td>Ryzen 5</td>
<td>5600G</td>
<td>2021Q2</td>
<td>6</td>
<td>12</td>
<td>19835</td>
<td>65W</td>
<td>不支持pcie4.0</td>
</tr>
<tr>
<td>AMD</td>
<td>Ryzen 5</td>
<td>5600</td>
<td>2022Q2</td>
<td>6</td>
<td>12</td>
<td>21501</td>
<td>65W</td>
<td>无独显，可考虑</td>
</tr>
<tr>
<td>AMD</td>
<td>Ryzen 9</td>
<td>5900HX</td>
<td>2021Q1</td>
<td>8</td>
<td>16</td>
<td>22989</td>
<td>45W</td>
<td></td>
</tr>
<tr>
<td>AMD</td>
<td>Ryzen 7</td>
<td>5700G</td>
<td>2021Q2</td>
<td>8</td>
<td>16</td>
<td>24564</td>
<td>65W</td>
<td>不支持pcie4.0</td>
</tr>
</tbody></table>
<blockquote>
<p>总结2022.12</p>
</blockquote>
<ul>
<li>物理机<strong>Openwrt</strong> <strong>J4125</strong></li>
<li>需要核显 5700G 5600G 12400</li>
<li>不需要核显 12400F 5600</li>
</ul>
<blockquote>
<p>最终方案</p>
</blockquote>
<ul>
<li>12400</li>
<li>b660m</li>
<li>16G*2</li>
<li>1T*2</li>
</ul>
<p><img src="https://cdn.zzfzzf.com/article/6993104149973962752/1669788075296.png" alt="image.png"></p>
<h2>如何测速</h2>
<ul>
<li><a href="https://www.speedtest.net/">https://www.speedtest.net/</a></li>
<li><a href="https://fast.com/zh/cn/">https://fast.com/zh/cn/</a></li>
</ul>
<h3>信道</h3>
<p>国内5G最初只开放了149-165信道，后来工信部开放了36-48信道，
对于日版switch玩家，同时又是澳大利亚区玩家最好改成美区</p>
<blockquote>
<p>建议固定信道，不要自适应</p>
</blockquote>
<ul>
<li>2.4G 1、6和11信道</li>
<li>5G 36、48、149和161的頻道</li>
</ul>
<p>国内能用的160MHz信道只有一组，8个，36, 40, 44, 48, 52, 56, 60, 64。设置36和44效果没有区别，都是用这8个20MHz信道通信。</p>
<ul>
<li><a href="https://zhuanlan.zhihu.com/p/403722934">https://zhuanlan.zhihu.com/p/403722934</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/445806964">https://zhuanlan.zhihu.com/p/445806964</a></li>
<li><a href="https://github.com/AdguardTeam/AdGuardHome">https://github.com/AdguardTeam/AdGuardHome</a></li>
</ul>
<h2>AdGuardHome</h2>
<p>Github <a href="https://github.com/AdguardTeam/AdGuardHome">https://github.com/AdguardTeam/AdGuardHome</a></p>
<h3>宿主机直接安装</h3>
<pre><code class="language-bash">curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v
curl -sSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh
</code></pre>
<pre><code class="language-bash">sudo ./AdGuardHome
</code></pre>
<ul>
<li>AdGuardHome -s install -安装AdGuard Home服务。</li>
<li>AdGuardHome -s uninstall -卸载AdGuard Home服务。</li>
<li>AdGuardHome -s start -启动服务。</li>
<li>AdGuardHome -s stop -停止服务。</li>
<li>AdGuardHome -s restart -重新启动服务。</li>
<li>AdGuardHome -s status -显示当前服务状态。</li>
</ul>
<h3>docker安装</h3>
<pre><code class="language-bash">docker run --name adguardhome\
    --restart unless-stopped\
    -v /my/own/workdir:/opt/adguardhome/work\
    -v /my/own/confdir:/opt/adguardhome/conf\
    -p 53:53/tcp -p 53:53/udp\
    -p 67:67/udp -p 68:68/udp\
    -p 80:80/tcp -p 443:443/tcp -p 443:443/udp -p 3000:3000/tcp\
    -p 853:853/tcp\
    -p 784:784/udp -p 853:853/udp -p 8853:8853/udp\
    -p 5443:5443/tcp -p 5443:5443/udp\
    -d adguard/adguardhome
</code></pre>
<p>通常会有端口冲突，改一下冲突的端口</p>
<h3>上游dns设置（腾讯&amp;阿里）</h3>
<ul>
<li><p>DNS over HTTPS:</p>
<ul>
<li><a href="https://dns.alidns.com/dns-query">https://dns.alidns.com/dns-query</a></li>
<li><a href="https://doh.pub/dns-query">https://doh.pub/dns-query</a></li>
</ul>
</li>
<li><p>DNS over TLS：</p>
<ul>
<li>tls://dns.alidns.com</li>
<li>tls://dns.pub</li>
</ul>
</li>
<li><p><a href="https://nextdns.io/zh">https://nextdns.io/zh</a></p>
</li>
<li><p><a href="https://adguard-dns.io/kb/">https://adguard-dns.io/kb/</a></p>
</li>
<li><p><a href="https://zhuanlan.zhihu.com/p/514766693">https://zhuanlan.zhihu.com/p/514766693</a></p>
</li>
<li><p><a href="https://sspai.com/post/63088">https://sspai.com/post/63088</a></p>
</li>
<li><p><a href="https://isedu.top/index.php/archives/23/">https://isedu.top/index.php/archives/23/</a></p>
</li>
</ul>
<h3>规则设置</h3>
<ul>
<li><a href="https://github.com/privacy-protection-tools">https://github.com/privacy-protection-tools</a></li>
<li><a href="https://adaway.org/hosts.txt">https://adaway.org/hosts.txt</a></li>
<li><a href="https://anti-ad.net/easylist.txt">https://anti-ad.net/easylist.txt</a></li>
</ul>
<h2>Clash和AdGuardHome共用</h2>
<p>关闭openclash的dns劫持，dnsmasq转发dns到ad上，openclash使用自己的dns，流量先经过ad过滤，再到达openclash</p>
<h2>DDNS</h2>
<p>DDNS将用户的动态IP地址映射到一个固定的域名解析服务上
我们使用阿里云来实现ddns，已有开源方案
<a href="https://github.com/jeessy2/ddns-go">https://github.com/jeessy2/ddns-go</a></p>
<pre><code class="language-bash">wget https://github.com/jeessy2/ddns-go/releases/download/v4.2.0/ddns-go_4.2.0_Linux_x86_64.tar.gz
nohup ./ddns-go &gt;/dev/null 2&gt;&amp;1 &amp;
</code></pre>
<h2>NAS</h2>
<ul>
<li><p>写盘工具 <a href="https://sourceforge.net/projects/win32diskimager/">https://sourceforge.net/projects/win32diskimager/</a></p>
</li>
<li><p>PVE虚拟机 <a href="https://www.proxmox.com/en/downloads">https://www.proxmox.com/en/downloads</a></p>
</li>
<li><p>开源NAS OS <a href="https://www.truenas.com/">https://www.truenas.com/</a>
写盘工具踩坑后推荐</p>
</li>
<li><p>rufus dd格式</p>
</li>
<li><p>软碟通 raw格式</p>
</li>
<li><p>balenaetcher</p>
</li>
</ul>
<pre><code>口令
ESXi 8 4V492-44210-48830-931GK-2PRJ4
ESXi 8 4F40H-4ML1K-M89U0-0C2N4-1AKL4
ESXi 8 HG00K-03H8K-48929-8K1NP-3LUJ4
</code></pre>
<h3>PVE</h3>
<p>openwrt 刷进PVE</p>
<pre><code>qm importdisk 105 /var/lib/vz/template/iso/openwrt-x86-64-generic-ext4-combined-efi.img local-lvm
</code></pre>
<h2>Emby</h2>
<h2>Plex</h2>
<h2>Jellyfin</h2>
<h2>PT</h2>
<ul>
<li>qbittorrent 默认账号密码 admin/adminadmin</li>
</ul>
<pre><code class="language-yml">version: &quot;3&quot;
services:
  qbittorrent:
    image: linuxserver/qbittorrent
    container_name: qbittorrent
    environment:
      - PUID=0
      - PGID=0
      - WEBUI_PORT=8180
    volumes:
      - /root/qbittorrent/appdata/config:/config
      - /root/qbittorrent/downloads:/downloads
    restart: unless-stopped
    devices:
      - /dev/dri:/dev/dr
    ports:
      - 8180:8180
      - 16881:16881
      - 16881:16881/udp
  transmission:
    image: linuxserver/transmission 
    container_name: transmission 
    environment:
      - PUID=0
      - PGID=0
      - USER=admin
      - PASS=dub_ajd4MKB_ujvtmn
    volumes:
      - /root/transmission/data:/config
      - /root/transmission/downloads:/downloads
      - /root/transmission/watch/folder:/watch
    ports:
      - 9091:9091
      - 51413:51413
      - 51413:51413/udp
    restart: unless-stopped
</code></pre>
<h2>GITEA</h2>
<pre><code class="language-bash">wget -O gitea https://dl.gitea.io/gitea/1.17.3/gitea-1.17.3-linux-amd64
cd ../
chown -R git:git ./gitea
su git
chmod +x gitea
./gitea web
</code></pre>
<p><a href="https://docs.gitea.io/zh-cn/">https://docs.gitea.io/zh-cn/</a>
<a href="https://git.oocc.es/">https://git.oocc.es/</a></p>
<pre><code class="language-bash">setcap cap_net_bind_service=+eip /path/to/application
setcap -r /path/to/application
</code></pre>
<h2>监控</h2>
<p><a href="https://zzfzzf.com/post/6972548384863424532">https://zzfzzf.com/post/6972548384863424532</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[前端测试方案]]></title>
            <link>https://zzfzzf.com/post/6990187806299656192</link>
            <guid>6990187806299656192</guid>
            <pubDate>Mon, 24 Oct 2022 05:50:44 GMT</pubDate>
            <content:encoded><![CDATA[<h2>测试的好处</h2>
<p>自动化测试能够预防无意引入的 bug，并鼓励开发者将应用分解为可测试、可维护的函数、模块、类和组件。这能够帮助你和你的团队更快速、自信地构建复杂的 Vue 应用。</p>
<h2>何时测试</h2>
<p>越早越好！我们建议你尽快开始编写测试。拖得越久，应用就会有越多的依赖和复杂性，想要开始添加测试也就越困难。</p>
<h2>测试的类型</h2>
<p>当设计你的 Vue 应用的测试策略时，你应该利用以下几种测试类型：</p>
<ul>
<li><strong>单元测试</strong>：检查给定函数、类或组合式函数的输入是否产生预期的输出或副作用。</li>
<li><strong>组件测试</strong>：检查你的组件是否正常挂载和渲染、是否可以与之互动，以及表现是否符合预期。这些测试比单元测试导入了更多的代码，更复杂，需要更多时间来执行。</li>
<li><strong>端到端测试</strong>：检查跨越多个页面的功能，并对生产构建的 Vue 应用进行实际的网络请求。这些测试通常涉及到建立一个数据库或其他后端。</li>
</ul>
<p>每种测试类型在你的应用的测试策略中都发挥着作用，保护你免受不同类型的问题的影响。</p>
<h2>编码注意点</h2>
<p>组件分<strong>容器组件</strong>和<strong>逻辑组件</strong>，逻辑组件只消费props数据，容器组件处理副作用，用来接口交互等等</p>
<h3>单元测试</h3>
<p>单元测试将捕获函数的业务逻辑和逻辑正确性的问题</p>
<h3>组件测试</h3>
<ul>
<li>对于 <strong>视图</strong> 的测试：根据输入 prop 和插槽断言渲染输出是否正确。</li>
<li>对于 <strong>交互</strong> 的测试：断言渲染的更新是否正确或触发的事件是否正确地响应了用户输入事件。</li>
</ul>
<h3>端到端测试</h3>
<p>端到端测试的重点是多页面的应用表现，针对你的应用在生产环境下进行网络请求。他们通常需要建立一个数据库或其他形式的后端，甚至可能针对一个预备上线的环境运行</p>
<h3>vue3+vite应用接入方案</h3>
<p>vitest+cypress</p>
<h3>具体实施</h3>
<pre><code class="language-js">consol
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[k8s入门]]></title>
            <link>https://zzfzzf.com/post/6977799933197946880</link>
            <guid>6977799933197946880</guid>
            <pubDate>Tue, 20 Sep 2022 01:25:45 GMT</pubDate>
            <content:encoded><![CDATA[<h2>k3s</h2>
<ul>
<li><a href="https://docs.k3s.io/zh/">K3s</a>是Rancher实验室的一个轻量级Kubernetes发行版</li>
<li><a href="https://rancher.com/docs/k3s/latest/en/">文档</a></li>
<li><a href="https://fleet.rancher.io/">fleet</a></li>
<li><a href="https://github.com/zzfn/k3s">github-demo</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/479989816">入门教程</a></li>
</ul>
<h2>准备</h2>
<ul>
<li>centos7.9-1     192.168.100.105</li>
<li>centos7.9-2    192.168.100.124
安装好docker，开启端口</li>
</ul>
<pre><code class="language-bash">yum update 
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun systemctl start docker # 启动docker 
systemctl enable docker # 开机自动启动
docker systemctl restart docker # 重启docker
</code></pre>
<pre><code class="language-bash">firewall-cmd --zone=public --add-port=0-65535/udp --permanent
firewall-cmd --zone=public --add-port=0-65535/tcp --permanent
firewall-cmd --reload
firewall-cmd --query-port=6443/tcp
</code></pre>
<h2>安装k3s</h2>
<blockquote>
<p>master</p>
</blockquote>
<pre><code class="language-bash">curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE=&quot;644&quot; K3S_NODE_NAME=&quot;node1&quot; sh -s -

k3s kubectl get node

cat /var/lib/rancher/k3s/server/node-token

systemctl restart k3s
systemctl restart k3s-agent
</code></pre>
<pre><code class="language-bash">#云服务器
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn K3S_KUBECONFIG_MODE=&quot;644&quot; K3S_NODE_NAME=&quot;node1&quot; sh -s - --node-external-ip public_ip
</code></pre>
<blockquote>
<p>cluster</p>
</blockquote>
<p>检查是否ping通master
curl -vk <a href="https://192.168.100.105:6443/cacerts">https://192.168.100.105:6443/cacerts</a></p>
<pre><code class="language-bash">curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE=&quot;644&quot; K3S_NODE_NAME=&quot;k8s-worker-01&quot; K3S_URL=https://192.168.100.124:6443 K3S_TOKEN=mynodetoken sh -
</code></pre>
<pre><code class="language-bash">#云服务器
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn K3S_KUBECONFIG_MODE=&quot;644&quot; K3S_NODE_NAME=&quot;node2&quot; K3S_URL=https://内网ip:6443 K3S_TOKEN=token sh - 
</code></pre>
<p>k3s kubectl get node</p>
<p>kubectl get nodes
kubectl get nodes -o wide</p>
<p>kubectl cluster-info</p>
<h2>部署实例</h2>
<pre><code class="language-bash"># 部署
kubectl apply -f deployment.yml

kubectl apply -f service.yml
# 查看 pod
kubectl get po -o wide

# 查看 serivce
kubectl get svc


kubectl apply -f ingress.yml
</code></pre>
<h2>卸载</h2>
<p>/usr/local/bin/k3s-uninstall.sh</p>
<h2>常用命令</h2>
<pre><code class="language-bash">#查看node的使用情况
kubectl top nodes
#查看 pod 的使用情况
kubectl top pod
kubectl top pod -n kube-system

kubectl get pods

kubectl get pods -o wide

kubectl get svc

kubectl get ingress

# 查看目前所有的pod
kubectl get po 
# 查看目前所有的replica set
kubectl get rs 
# 查看目前所有的deployment
kubectl get deployment
# 查看my-nginx pod的详细状态
kubectl describe po my-nginx 
# 查看my-nginx replica set的详细状态
kubectl describe rs my-nginx 
# 查看my-nginx deployment的详细状态
kubectl describe deployment my-nginx 
</code></pre>
<h2>helm</h2>
<p><a href="https://helm.sh/zh/docs/intro/quickstart/">https://helm.sh/zh/docs/intro/quickstart/</a></p>
<pre><code class="language-bash">#安装
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
#修改配置路径
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
</code></pre>
<h2>F&amp;Q</h2>
<ol>
<li>traefik不会转发<code>X-Forwarded-For</code>等头
添加<code>/var/lib/rancher/k3s/server/manifests/traefik-config.yaml</code>文件</li>
</ol>
<pre><code class="language-yaml">#https://medium.com/@_jonas/traefik-kubernetes-ingress-and-x-forwarded-headers-82194d319b0e
# /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    additionalArguments:
      - &quot;--serversTransport.insecureSkipVerify=true&quot;
    dashboard:
      enabled: true
    ports:
      traefik:
        expose: true
      psql:
        port: 5432
        expose: true
        exposedPort: 5432
        protocol: TCP
      kafka:
        port: 9092
        expose: true
        exposedPort: 9092
        protocol: TCP
      redis:
        port: 6379
        expose: true
        exposedPort: 6379
        protocol: TCP
      elasticsearch:
        port: 9200
        expose: true
        exposedPort: 9200
        protocol: TCP
</code></pre>
<h2>重启pod</h2>
<pre><code>#滚动重启方法(推荐)
kubectl rollout restart deployment/bytebase
kubectl rollout restart deployment nginx-deploy
#环境变量方法
kubectl set env deployment nginx-deploy DEPLOY_DATE=&quot;$(date)&quot;
#副本扩/缩容
kubectl scale --replicas=0 deployment nginx-deploy
kubectl scale --replicas=N deployment nginx-deploy
kubectl scale --replicas=0 -f https://git/blog.yaml

kubectl describe pods my-pod
kubectl get services                          # 列出当前命名空间下的所有 services
kubectl get pods --all-namespaces             # 列出所有命名空间下的全部的 Pods
kubectl get pods -o wide                      # 列出当前命名空间下的全部 Pods，并显示更详细的信息
kubectl get deployment my-dep                 # 列出某个特定的 Deployment
kubectl get pods                              # 列出当前命名空间下的全部 Pods
kubectl get pod my-pod -o yaml                # 获取一个 pod 的 YAML
kubectl delete pods &lt;pod&gt; --grace-period=0 --force
</code></pre>
<h2>下线节点</h2>
<pre><code class="language-bash">#配置节点不可调度
kubectl cordon &lt;NODE_NAME&gt;
#删除pod
kubectl delete pod -n &lt;NAMESPACE&gt; &lt;POD_NAME&gt;
#删除node
kubectl delete node &lt;NODE_NAME&gt;
</code></pre>
<h2>https配置</h2>
<pre><code class="language-bash">//tls.crt
cat fullchain.cer | base64 -w0
//tls.key
cat cert.key | base64 -w0
kubectl get secret ooxo.cc-tls-secret
kubectl get secret ooxo.cc-tls-secret -o jsonpath=&#39;{.data.tls\.crt}&#39; | base64 --decode
kubectl get secret ooxo.cc-tls-secret -o jsonpath=&#39;{.data.tls\.key}&#39; | base64 --decode
</code></pre>
<h2>附录</h2>
<ul>
<li><p><a href="https://icloudnative.io/posts/deploy-k3s-cross-public-cloud/#6-%E5%86%85%E7%BD%91%E4%B8%8D%E4%BA%92%E9%80%9A%E7%9A%84%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95">https://icloudnative.io/posts/deploy-k3s-cross-public-cloud</a></p>
</li>
<li><p><a href="https://zhuanlan.zhihu.com/p/479989816">https://zhuanlan.zhihu.com/p/479989816</a></p>
</li>
<li><p><a href="https://juejin.cn/post/6952331691524358174#heading-59">https://juejin.cn/post/6952331691524358174#heading-59</a></p>
</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[购买一台性价比高的服务器]]></title>
            <link>https://zzfzzf.com/post/6975699410365845504</link>
            <guid>6975699410365845504</guid>
            <pubDate>Wed, 14 Sep 2022 06:19:01 GMT</pubDate>
            <content:encoded><![CDATA[<h2>活动优惠</h2>
<blockquote>
<p>首先比较划算的是以下三个活动</p>
</blockquote>
<ul>
<li>新人优惠</li>
<li>双11</li>
<li>618</li>
</ul>
<h2>长期选择</h2>
<h3>阿里云</h3>
<ul>
<li>轻量香港 24/月 2V1G（缺货）</li>
<li>轻量香港 34/月 2V2G（有货）</li>
<li><a href="https://common-buy.aliyun.com/?commodityCode=swas&regionId=cn-hongkong">官网</a></li>
</ul>
<h3>腾讯云</h3>
<ul>
<li>轻量香港 32/月 2V2G（缺货）</li>
<li><a href="https://buy.cloud.tencent.com/lighthouse?region=5&zone=ap-hongkong-1">官网</a></li>
</ul>
<h3>AWS</h3>
<ul>
<li>AWS Lightsail</li>
<li>AWS EC2</li>
<li>需要绑定信用卡</li>
</ul>
<h3>digitalocean</h3>
<ul>
<li>需要绑定信用卡或者paypal</li>
<li>1V1G $6/月</li>
</ul>
<h3>linode</h3>
<ul>
<li>1V1G $5/月</li>
</ul>
<h3>lightnode</h3>
<ul>
<li>1V2G $7.71/月</li>
</ul>
<h3>vultr</h3>
<ul>
<li><a href="https://vultr.com/">https://vultr.com/</a></li>
<li>1V1G. 6$/月</li>
<li>支持支付宝</li>
<li>没有香港，只有东京和新加坡和汉城</li>
<li>性价比高</li>
</ul>
<h3>buyvm</h3>
<p><a href="https://my.frantech.ca/">https://my.frantech.ca/</a></p>
<h3>OVH VPS</h3>
<p><a href="https://www.ovhcloud.com/en/vps/">https://www.ovhcloud.com/en/vps/</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[雪花算法node-ts版本]]></title>
            <link>https://zzfzzf.com/post/6972736465797255168</link>
            <guid>6972736465797255168</guid>
            <pubDate>Tue, 06 Sep 2022 02:05:20 GMT</pubDate>
            <content:encoded><![CDATA[<h2>简介</h2>
<p>Snowflake，雪花算法是由Twitter开源的分布式ID生成算法，以划分命名空间的方式将 64-bit位分割成多个部分，每个部分代表不同的含义。</p>
<h2>二进制</h2>
<pre><code>位运算异或( ^ ) ，左移( &lt;&lt; ) ，与( &amp; )，或( | )
</code></pre>
<p><img src="https://oss-zzf.zzfzzf.com/midway/Snowflake-identifier.png" alt=""></p>
<h2>具体实现</h2>
<pre><code class="language-ts">class SnowflakeIdGenerate {
  private START_STMP = 0;
  private SEQUENCE_BIT = 12;
  private MACHINE_BIT = 5;
  private DATACENTER_BIT = 5;
  private MAX_DATACENTER_NUM = -1 ^ (-1 &lt;&lt; this.DATACENTER_BIT);
  private MAX_MACHINE_NUM = -1 ^ (-1 &lt;&lt; this.MACHINE_BIT);
  private MAX_SEQUENCE = -1 ^ (-1 &lt;&lt; this.SEQUENCE_BIT);

  private MACHINE_LEFT = this.SEQUENCE_BIT;
  private DATACENTER_LEFT = this.SEQUENCE_BIT + this.MACHINE_BIT;
  private TIMESTMP_LEFT = this.DATACENTER_LEFT + this.DATACENTER_BIT;

  private datacenterId; //数据中心
  private machineId; //机器标识
  private sequence = 0; //序列号
  private lastStmp = -1; //上一次时间戳

  constructor(machineId = 1, datacenterId = 1) {
    if (this.machineId &gt; this.MAX_MACHINE_NUM || this.machineId &lt; 0) {
      throw new Error(
        &#39;config.worker_id must max than 0 and small than maxWrokerId-[&#39; +
          this.MAX_MACHINE_NUM +
          &#39;]&#39;
      );
    }
    if (this.datacenterId &gt; this.MAX_DATACENTER_NUM || this.datacenterId &lt; 0) {
      throw new Error(
        &#39;config.data_center_id must max than 0 and small than maxDataCenterId-[&#39; +
          this.MAX_DATACENTER_NUM +
          &#39;]&#39;
      );
    }
    this.machineId = machineId;
    this.datacenterId = datacenterId;
  }

  private getNewstmp = (): number =&gt; {
    return Date.now();
  };

  private getNextMill = (): number =&gt; {
    let timestamp = this.getNewstmp();
    while (timestamp &lt;= this.lastStmp) {
      timestamp = this.getNewstmp();
    }
    return timestamp;
  };

  nextId = (): string =&gt; {
    let timestamp: number = this.getNewstmp();
    if (timestamp &lt; this.lastStmp) {
      throw new Error(
        &#39;Clock moved backwards. Refusing to generate id for &#39; +
          (this.lastStmp - timestamp)
      );
    }
    if (this.lastStmp === timestamp) {
      this.sequence = (this.sequence + 1) &amp; this.MAX_SEQUENCE;
      if (this.sequence === 0) {
        timestamp = this.getNextMill();
      }
    } else {
      this.sequence = 0;
    }
    this.lastStmp = timestamp;
    const timestampPos =
      BigInt(timestamp - this.START_STMP) *
      BigInt(2) ** BigInt(this.TIMESTMP_LEFT);
    const dataCenterPos = BigInt(this.datacenterId &lt;&lt; this.DATACENTER_LEFT);
    const workerPos = BigInt(this.machineId &lt;&lt; this.MACHINE_LEFT);
    return (
      timestampPos |
      dataCenterPos |
      workerPos |
      BigInt(this.sequence)
    ).toString();
  };
}
</code></pre>
<h2>chatgpt的实现</h2>
<pre><code class="language-typescript">class Snowflake {  
  private readonly epoch: number = 1620950400000; // 开始时间戳，2021-05-14 00:00:00 UTC+8  
  private readonly workerIdBits: number = 5; // 机器 ID 所占的位数  
  private readonly datacenterIdBits: number = 5; // 数据中心 ID 所占的位数  
  private readonly maxWorkerId: number = ~(-1 &lt;&lt; this.workerIdBits); // 最大机器 ID，2^5-1  
  private readonly maxDatacenterId: number = ~(-1 &lt;&lt; this.datacenterIdBits); // 最大数据中心 ID，2^5-1  
  private readonly sequenceBits: number = 12; // 序列号所占的位数  
  private readonly workerIdShift: number = this.sequenceBits; // 机器 ID 左移位数，即序列号占用的位数  
  private readonly datacenterIdShift: number = this.sequenceBits + this.workerIdBits; // 数据中心 ID 左移位数  
  private readonly timestampLeftShift: number =  
    this.sequenceBits + this.workerIdBits + this.datacenterIdBits; // 时间戳左移位数  
  private sequenceMask: number = ~(-1 &lt;&lt; this.sequenceBits); // 序列号的掩码，2^12-1  
  private sequence: number = 0; // 当前序列号  
  private lastTimestamp: number = -1; // 上次生成雪花 ID 的时间戳  
  private readonly workerId: number; // 当前机器 ID  private readonly datacenterId: number; // 当前数据中心 ID  
  /**   * 构造函数  
   * @param workerId 机器 ID  
   * @param datacenterId 数据中心 ID  
   */  constructor(workerId: number, datacenterId: number) {  
    if (workerId &gt; this.maxWorkerId || workerId &lt; 0) {  
      throw new Error(`workerId must be between 0 and ${this.maxWorkerId}`);  
    }  
    if (datacenterId &gt; this.maxDatacenterId || datacenterId &lt; 0) {  
      throw new Error(`datacenterId must be between 0 and ${this.maxDatacenterId}`);  
    }  
    this.workerId = workerId;  
    this.datacenterId = datacenterId;  
  }  
  
  /**  
   * 生成下一个雪花 ID  
   * @returns 64 位的雪花 ID  
   */  nextId(): string {  
    let timestamp = this.timeGen();  
    if (timestamp &lt; this.lastTimestamp) {  
      throw new Error(  
        `Clock moved backwards. Refusing to generate id for ${  
          this.lastTimestamp - timestamp  
        } milliseconds`,  
      );  
    }  
    if (this.lastTimestamp === timestamp) {  
      this.sequence = (this.sequence + 1) &amp; this.sequenceMask;  
      if (this.sequence === 0) {  
        timestamp = this.tilNextMillis(this.lastTimestamp);  
      }  
    } else {  
      this.sequence = 0;  
    }  
    this.lastTimestamp = timestamp;  
    const id =  
      ((timestamp - this.epoch) &lt;&lt; this.timestampLeftShift) |  
      (this.datacenterId &lt;&lt; this.datacenterIdShift) |  
      (this.workerId &lt;&lt; this.workerIdShift) |  
      this.sequence;  
    return id.toString();  
  }  
  
  /**  
   * 获取当前时间  
   *  
   */  
  private timeGen(): number {  
    return Date.now();  
  }  
  
  /**  
  
   等待下一毫秒，直到返回新的时间戳  
   @param lastTimestamp 上次生成雪花 ID 的时间戳  
   @returns 新的时间戳  
   */  
  private tilNextMillis(lastTimestamp: number): number {  
    let timestamp = this.timeGen();  
    while (timestamp &lt;= lastTimestamp) {  
      timestamp = this.timeGen();  
    }  
    return timestamp;  
  }  
}
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[服务监控体系的搭建]]></title>
            <link>https://zzfzzf.com/post/6972548384863424532</link>
            <guid>6972548384863424532</guid>
            <pubDate>Mon, 05 Sep 2022 13:13:30 GMT</pubDate>
            <content:encoded><![CDATA[<h2>prometheus</h2>
<ul>
<li>官网 <a href="https://prometheus.io/">https://prometheus.io/</a></li>
</ul>
<h3>安装</h3>
<pre><code class="language-bash">kubectl apply -f https://git.ooxo.cc/k8s/yaml/raw/branch/main/prom/prom.yaml
</code></pre>
<p>使用docker-compose部署</p>
<ul>
<li>/root/docker-compose.yml</li>
<li>/root/prometheus.yml</li>
<li>/root/targets.json</li>
</ul>
<pre><code class="language-yml">version: &#39;3&#39;
services:
  prometheus:
    image: prom/prometheus
    restart: always
    volumes:
      - &#39;/root/prometheus/rules:/etc/prometheus/rules&#39;
      - &#39;/root/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml&#39;
      - &#39;/root/prometheus/targets.json:/etc/prometheus/targets.json&#39;
    command:
      - &#39;--config.file=/etc/prometheus/prometheus.yml&#39;
      - &#39;--web.enable-lifecycle&#39;
    ports:
      - &#39;9090:9090&#39;
  blackbox:
    image: prom/blackbox-exporter
    restart: always
    ports:
      - &#39;9115:9115&#39;
  alertmanager:
    image: prom/alertmanager
    restart: always
    ports:
      - &#39;9093:9093&#39;
    volumes:
       - /root/prometheus/alertmanager.yml:/etc/alertmanager/alertmanager.yml
  grafana:
    image: grafana/grafana
    ports:
      - &quot;3000:3000&quot;
    restart: always
</code></pre>
<h2>grafana</h2>
<h3>安装</h3>
<p>推荐使用 Kubernetes 安装 Grafana，可以参考 <a href="https://grafana.com/docs/grafana/latest/setup-grafana/installation/kubernetes/">Grafana 官方文档</a>。如果在内网环境中，可以直接执行以下命令：</p>
<pre><code class="language-bash">kubectl apply -f https://git.ooxo.cc/k8s/yaml/src/branch/main/prom/grafana.yaml
</code></pre>
<p>也可以使用 Docker Compose 进行安装，详细教程可以参考 <a href="https://midwayjs.org/docs/extensions/prometheus#%E6%95%B0%E6%8D%AE%E5%B1%95%E7%A4%BA">MidwayJS 文档</a>。</p>
<h3>配置数据源</h3>
<p>在 <a href="https://grafana.ooxo.cc/connections/datasources">https://grafana.ooxo.cc/connections/datasources</a> 中添加<code>prometheus</code>数据源，<br>地址为<a href="https://prom.ooxo.cc">https://prom.ooxo.cc</a></p>
<ul>
<li>可用docker-compose.yml运行</li>
<li>默认账号密码 admin/admin</li>
<li>插件市场 <a href="https://grafana.com/grafana/dashboards/">https://grafana.com/grafana/dashboards/</a></li>
</ul>
<h3>仪表盘</h3>
<p>在 <a href="https://grafana.ooxo.cc/dashboard/import">Grafana 仪表盘导入页面</a> 导入以下常用的仪表盘 ID：</p>
<ul>
<li>16098：Linux 主机监控</li>
<li>9965：黑盒监控</li>
<li>14403：Midway 服务监控</li>
</ul>
<p> linux监控</p>
<ul>
<li><a href="https://hub.docker.com/r/prom/node-exporter/tags">docker</a></li>
<li><a href="https://github.com/prometheus/node_exporter/releases">可执行文件</a></li>
<li>nohup ./node_exporter &gt;/dev/null 2&gt;&amp;1 &amp;</li>
<li>Grafana 面板ID 16098</li>
</ul>
<p> 黑盒监控</p>
<pre><code>nohup ./blackbox_exporter --config.file=&quot;./blackbox.yml&quot; --web.listen-address=&quot;:9098&quot; --log.level=debug &gt; /dev/null 2&gt;&amp;1 &amp;
</code></pre>
<ul>
<li>Grafana 面板ID 9965</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[springboot迁移midway]]></title>
            <link>https://zzfzzf.com/post/6972548384863424529</link>
            <guid>6972548384863424529</guid>
            <pubDate>Mon, 22 Aug 2022 05:00:19 GMT</pubDate>
            <content:encoded><![CDATA[<h2>springboot</h2>
<blockquote>
<p>Spring Boot是由Pivotal团队提供的全新框架，其设计目的是用来简化新Spring应用的初始搭建以及开发过程。 该框架使用了特定的方式来进行配置，从而使开发人员不再需要定义样板化的配置。</p>
</blockquote>
<h2>midway</h2>
<blockquote>
<p>Midway 是阿里巴巴 - 淘宝前端架构团队，基于渐进式理念研发的 Node.js 框架，通过自研的依赖注入容器，搭配各种上层模块，组合出适用于不同场景的解决方案。</p>
</blockquote>
<h2>未完待续</h2>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[从jenkins转到drone]]></title>
            <link>https://zzfzzf.com/post/6972548384863424528</link>
            <guid>6972548384863424528</guid>
            <pubDate>Fri, 12 Aug 2022 07:43:56 GMT</pubDate>
            <content:encoded><![CDATA[<p>个人博客和项目使用了两年多<a href="https://www.jenkins.io/">jenkins</a>去完成 ci/cd，确实解放了双手。但是最近 jenkns 的安全漏洞越来越频繁，前段时间<code>publish over ssh</code>因为安全漏洞被禁用了很久。也尝试用 go 去实现一款 ci/cd 工具，最近偶然发现<a href="https://www.drone.io/">drone</a>这么一款好用的工具，果断全部迁移过去</p>
<blockquote>
<p>也许你需要的 jenkins <a href="https://zzfzzf.com/post/6972548384842452992">部署教程</a></p>
</blockquote>
<p>也许有人会问,为啥不用 Github action、CircleCI、TravisCI、TeamCity</p>
<ol>
<li>有运行时长限制（虽然个人项目应该用不完，但是强迫症不能忍受）</li>
<li>也有用github action，比如npm 自动发包就用的github action。deploy到国内服务器网速问题</li>
</ol>
<h2>几个优点</h2>
<ul>
<li>开源，强大的社区支持</li>
<li>基础<code>docker</code>，一切都在<code>docker</code>上运行</li>
<li>支持跟市场上绝大部分git仓库集成</li>
<li>基于 <code>yaml</code> 配置</li>
<li>编写插件即编写一个docker镜像，可使用你喜欢的语言（还是<code>go</code>好用</li>
</ul>
<h2>drone start</h2>
<ul>
<li><a href="https://docs.drone.io/">官方文档</a></li>
</ul>
<blockquote>
<p>我们使用docker-compose 安装 drone server和和runner</p>
</blockquote>
<ol>
<li>首先创建网络<code>docker network create drone</code></li>
<li>创建<code>docker-compose.yml</code>
Drone 的部署分为 Server(Drone-Server) 和 Agent(Drone-agent):</li>
</ol>
<ul>
<li>Server端：负责后台管理界面以及调度</li>
<li>Agent端：负责具体的任务执行</li>
</ul>
<p>为了便于管理容器，采用<code>docker-compose.yml</code>去部署</p>
<pre><code class="language-yaml">version: &#39;3.9&#39;
networks:
  drone:
    name: drone
    driver: bridge
services:
  db:
    image: postgres:latest
    container_name: drone_db
    restart: always
    networks:
      - drone
    ports:
      - &#39;5432:5432&#39;
    environment:
      - POSTGRES_USER=drone
      - POSTGRES_PASSWORD=drone
      - POSTGRES_DB=drone
    volumes:
      - /volumes/drone/db:/var/lib/postgresql/data
  server:
    image: drone/drone:2
    container_name: drone_server
    restart: always
    networks:
      - drone
    ports:
      - &#39;80:80&#39;
      - &#39;443:443&#39;
    environment:
      - DRONE_TLS_AUTOCERT=true
      - DRONE_SERVER_PROTO=https
      - DRONE_SERVER_HOST=https://drone.your-domain.com
      - DRONE_RPC_SECRET=DRONE_RPC_SECRET
      - DRONE_WEBHOOK_ENDPOINT=http://webhook:3000
      - DRONE_WEBHOOK_SECRET=add8b88f1a7ff15cbcb5bdb6bd081b70
      - DRONE_DATABASE_DRIVER=postgres
      - DRONE_WEBHOOK_EVENTS=build:created,build:updated
      - DRONE_DATABASE_DATASOURCE=postgres://drone:drone@db/drone?sslmode=disable
      - DRONE_USER_CREATE=username:zzfn,admin:true
      - DRONE_USER_CREATE=username:prometheus,admin:true,machine:true,token:c576f2bd2d218d1ffd583c2db868d8c7
      - DRONE_GITHUB_CLIENT_ID=DRONE_GITHUB_CLIENT_ID
      - DRONE_USER_FILTER=zzfn,prometheus
      - DRONE_GITHUB_CLIENT_SECRET=DRONE_GITHUB_CLIENT_SECRET
    volumes:
      - /var/lib/drone:/data
    depends_on:
      - db
  runner:
    image: drone/drone-runner-docker:1
    container_name: drone_runner
    restart: always
    networks:
      - drone
    ports:
      - &#39;3000:3000&#39;
    environment:
      - DRONE_RUNNER_NAME=docker-runner
      - DRONE_RUNNER_CAPACITY=2
      - DRONE_RPC_PROTO=http
      - DRONE_RPC_HOST=server
      - DRONE_RPC_SECRET=DRONE_RPC_SECRET
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      - server
  webhook:
    image: registry.cn-shanghai.aliyuncs.com/zzf2001/drone-webhook:latest
    container_name: drone_webhook
    restart: always
    networks:
      - drone
    ports:
      - &#39;6000:3000&#39;
    environment:
      - DRONE_SECRET=DRONE_SECRET
      - PLUGIN_WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx
      - PLUGIN_SECRET=PLUGIN_SECRET
      - PLUGIN_BASE=https://drone.your-domain.com/
</code></pre>
<ol>
<li>替换<code>DRONE_RPC_SECRET</code> <code>DRONE_GITHUB_CLIENT_ID</code> <code>DRONE_GITHUB_CLIENT_SECRET</code></li>
<li>运行容器 <code>docker compose up -d</code></li>
</ol>
<h2>F&amp;Q</h2>
<ol>
<li>在迁移数据库后自增id会冲突</li>
</ol>
<pre><code class="language-sql">SELECT setval(&#39;orgsecrets_secret_id_seq&#39;, 100);
//找出最大值设置
SELECT setval(&#39;orgsecrets_secret_id_seq&#39;, COALESCE((SELECT MAX(secret_id)+1 FROM orgsecrets), 1), false);
</code></pre>
<h2>插件系统</h2>
<ul>
<li><a href="https://plugins.drone.io/">官方插件市场</a><blockquote>
<p>插件其实就是一个个的docker容器，使用插件也很简单</p>
</blockquote>
</li>
</ul>
<pre><code class="language-yaml">kind: pipeline
type: docker
name: default

steps:
  - name: webhook
    image: zzfn/webhook
    settings:
      url: http://hook.zzfn.com
      method: post
      body: |
        hello world
</code></pre>
<p>如果官方插件市场没有我们需要的插件我们可以自己去实现一个插件，我已经实现了4个插件，大家可以使用</p>
<ul>
<li><a href="https://github.com/drone-plugin">https://github.com/drone-plugin</a></li>
<li><a href="https://docs.drone.io/pipeline/environment/reference/">环境变量</a></li>
<li>自定义插件最好使用go实现</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[serverless云函数初体验]]></title>
            <link>https://zzfzzf.com/post/6972548384863424527</link>
            <guid>6972548384863424527</guid>
            <pubDate>Tue, 09 Aug 2022 06:38:35 GMT</pubDate>
            <content:encoded><![CDATA[<h2>背景</h2>
<blockquote>
<p>之前个人项目都是部署在云服务器上，导致买了4台服务器才满足了场景
准备逐步迁移到<code>serverless</code>上</p>
</blockquote>
<h2>serverless服务商</h2>
<ul>
<li><a href="%E9%98%BF%E9%87%8C%E4%BA%91%E5%87%BD%E6%95%B0%E8%AE%A1%E7%AE%97">阿里云</a></li>
<li><a href="https://aws.amazon.com/cn/lambda/">aws</a></li>
<li><a href="https://cloud.tencent.com/product/scf">腾讯云函数</a></li>
<li><a href="https://azure.microsoft.com/en-us/services/functions/">azure</a></li>
</ul>
<h2>准备知识</h2>
<p>我们使用阿里云函数来部署</p>
<h3>文档</h3>
<ul>
<li><a href="https://fcnext.console.aliyun.com">阿里云serverless</a></li>
<li><a href="https://serverless-devs.com">部署工具</a></li>
</ul>
<h3>概要</h3>
<ol>
<li>迁移主要对已有的项目进行迁移，涵盖node项目、前端项目、java项目，计划用<code>githubaction</code>和<code>jenkins</code>都可以<code>cicd</code>到<code>serverless</code></li>
<li>本着最小改动原则，我们对项目添加<code>s.yaml</code>配置文件+<code>cicd工具</code>进发布</li>
</ol>
<h2>node项目</h2>
<pre><code class="language-yaml">edition: 1.0.0
access: &quot;default&quot;

services:
  framework: # 业务名称/模块名称
    component: fc # 组件名称
    actions:
      pre-deploy:
        - run: pnpm i --frozen-lockfile
          path: ./
        - run: pnpm run build
          path: ./
    props: # 组件的属性值
      region: cn-shanghai
      service:
        name: node-service
        description: &quot;Serverless Devs Website Service&quot;
      function:
        name: blog
        description: &quot;Serverless Devs Website Docusaurus Function&quot;
        runtime: custom
        codeUri: ./
        timeout: 30
        memorySize: 512
        caPort: 9800
        customRuntimeConfig:
          command:
            - npm
          args:
            - &#39;run&#39;
            - &#39;start&#39;
      triggers:
        - name: httpTrigger
          type: http
          config:
            authType: anonymous
            methods:
              - GET
              - POST
              - PUT
              - DELETE
      customDomains:
        - domainName: auto
          protocol: HTTP
          routeConfigs:
            - path: /*
</code></pre>
<h2>纯前端静态资源项目</h2>
<pre><code class="language-yaml">edition: 1.0.0
access: &quot;default&quot;

services:
  framework: # 业务名称/模块名称
    component: fc # 组件名称
    actions:
      pre-deploy:
        - run: pnpm i --frozen-lockfile
          path: ./
        - run: pnpm run doc:build
          path: ./
        - plugin: website-fc
    props: # 组件的属性值
      region: cn-shanghai
      service:
        name: web-service
        description: &quot;Serverless Devs Website Service&quot;
      function:
        name: design
        description: &quot;Serverless Devs Website Docusaurus Function&quot;
        codeUri: ./dist
        timeout: 30
        memorySize: 512
      triggers:
        - name: httpTrigger
          type: http
          config:
            authType: anonymous
            methods:
              - GET
              - POST
              - PUT
              - DELETE
      customDomains:
        - domainName: auto
          protocol: HTTP
          routeConfigs:
            - path: /*
</code></pre>
<h2>java项目</h2>
<pre><code class="language-yaml">#s.yaml
edition: 1.0.0
access: &quot;default&quot;

services:
  framework: # 业务名称/模块名称
    component: fc # 组件名称
    actions:
      pre-deploy:
        - run: mvn clean package -DskipTests
          path: ./
    props: # 组件的属性值
      region: cn-shanghai
      service:
        name: java-service
        description: &quot;Serverless Devs Website Service&quot;
      function:
        name: server
        description: &quot;Serverless Devs Website Docusaurus Function&quot;
        runtime: custom
        codeUri: ./
        timeout: 30
        memorySize: 512
        caPort: 8080
        customRuntimeConfig:
          command:
            - java
          args:
            - &#39;-jar&#39;
            - &#39;target/blog-server-1.0.0.jar&#39;
      triggers:
        - name: httpTrigger
          type: http
          config:
            authType: anonymous
            methods:
              - GET
              - POST
              - PUT
              - DELETE
      customDomains:
        - domainName: auto
          protocol: HTTP
          routeConfigs:
            - path: /*
</code></pre>
<h2>附件</h2>
<ul>
<li><a href="https://gitee.com/devsapp/start-web-framework/tree/master/web-framework">https://gitee.com/devsapp/start-web-framework/tree/master/web-framework</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[个人组件库的实现方案]]></title>
            <link>https://zzfzzf.com/post/6972548384863424526</link>
            <guid>6972548384863424526</guid>
            <pubDate>Sat, 06 Aug 2022 03:23:00 GMT</pubDate>
            <content:encoded><![CDATA[<h2>前景</h2>
<p>个人组件库主要包含3个部分，UI组件库、ICON组件库（svg）、工具类及其他公共功能、组件库文档网站</p>
<h2>图标库</h2>
<h2>UI组件库</h2>
<h2>文档网站</h2>
<p><a href="https://zzfzzf.com/post/6972548384863424525">@oc/doc-loader</a></p>
<pre><code class="language-bash">npm i @oc/doc-loader --registry=https://npm.zzfzzf.com
</code></pre>
<h2>打包发布</h2>
<ul>
<li>standard-version</li>
<li>semantic-release</li>
<li>release-it</li>
</ul>
<h2>站在巨人的肩膀上</h2>
<ul>
<li><a href="https://github.com/sheinsight/shineout">https://github.com/sheinsight/shineout</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[markdown生成组件库文档方案]]></title>
            <link>https://zzfzzf.com/post/6972548384863424525</link>
            <guid>6972548384863424525</guid>
            <pubDate>Sun, 31 Jul 2022 09:42:26 GMT</pubDate>
            <content:encoded><![CDATA[<h2>成品</h2>
<p><a href="https://design.zzfzzf.com/">https://design.zzfzzf.com/</a></p>
<h2>背景</h2>
<p>开发一个组件库，我们往往本地npm link来调试，但是我们没有一个集中预览和查询使用文档的地方。在参考流行UI组件库后，最终借鉴<a href="https://arco.design/">arco</a>的组件库文档生成方式。</p>
<h2>所需要的知识基础</h2>
<ul>
<li>markdown</li>
<li>webpack-loader</li>
<li>babel</li>
<li>AST</li>
</ul>
<blockquote>
<p>以button按钮为列,文档分为描述、demo展示、demo源码</p>
</blockquote>
<p>大致流程为markdown-&gt;html-&gt;JSX-&gt;AST-&gt;转化-&gt;生成最近展示的CODE</p>
<h2></h2>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[vue3踩坑]]></title>
            <link>https://zzfzzf.com/post/6972548384863424524</link>
            <guid>6972548384863424524</guid>
            <pubDate>Fri, 29 Jul 2022 03:24:05 GMT</pubDate>
            <content:encoded><![CDATA[<h2>序</h2>
<blockquote>
<p>使用jsx语法</p>
</blockquote>
<h2>setup和render</h2>
<ol>
<li>render的渲染写法；</li>
<li>setup直接return的渲染写法；</li>
</ol>
<pre><code class="language-js">import childProps from &#39;./ChildProps&#39;;
export default defineComponent({
    name: &#39;parent&#39;,
    components: { childProps },
    setup() {
        return () =&gt; (
          &lt;child-props
            num={5}
            msg={&#39;这里是msg&#39;}
            isIfBool={true}
            isShowBool={true}
            // list={[&#39;one&#39;, &#39;two&#39;, &#39;three&#39;]}
            list={[{ name: &#39;one&#39; }, { name: &#39;two&#39; }, { name: &#39;three&#39; }]}
            obj={{ name: &#39;张三&#39;, age: 16, gender: &#39;男生&#39; }}
          /&gt;
        )
    }
}); 
</code></pre>
<pre><code class="language-js">import { defineComponent, reactive } from &#39;vue&#39;;

export default defineComponent({
  name: &#39;ChildProps&#39;,
  props: {
    // 接收 数值
    num: {
      type: Number,
      default: 0,
    },
    // 接收 字符串
    msg: {
      type: String,
      default: &#39;&#39;,
    },
    // 接收v-if的判断
    isIfBool: {
      type: Boolean,
      default: false,
    },
    // 接收v-show的判断
    isShowBool: {
      type: Boolean,
      default: false,
    },
    // 接收数组
    list: {
      type: Array,
      default: () =&gt; [],
    },
    // 接收对象
    obj: {
      type: Object,
      default: () =&gt; {
        return {};
      },
    },
  },
  setup(props) {
    interface Obj {
      name?: string;
      age?: number;
      gender?: string;
    }
    const obj = reactive({ ...props });
    console.log(obj);
    // { name: &#39;张三&#39;, age: 16, gender: &#39;男生&#39; }
    const newObj: Obj = obj.obj;

    // 将对象转换成数组
    const arr: Array&lt;any&gt; = reactive([]);
    // for (const i in newObj) {
    //   arr.push({ [i]: newObj[i] });
    // }
    // console.log(arr);
    /**
     * [
     *  {name: &#39;张三&#39;},
     *  {age: 16},
     *  {gender: &#39;男生&#39;},
     * ]
     */
    for (const key in newObj) {
      const o = {};
      o[&#39;name&#39;] = key;
      o[&#39;value&#39;] = newObj[key];
      arr.push(o);
    }
    // console.log(arr);
    /**
     * [
     *   {name: &#39;name&#39;, value: &#39;张三&#39;},
     *   {name: &#39;age&#39;, value: 16},
     *   {name: &#39;gender&#39;, value: &#39;男生&#39;},
     * ]
     */

    return () =&gt; (
      &lt;&gt;
        &lt;div&gt;-----------ChildProps 组件---------&lt;/div&gt;
        {/* Number 的写法 */}
        &lt;div&gt;{obj.num}&lt;/div&gt;
        {/* String 的写法 */}
        &lt;div&gt;{obj.msg}&lt;/div&gt;
        {/* v-if 的判断的写法 */}
        {props.isIfBool ? &lt;div&gt;这里是v-if的成功判断&lt;/div&gt; : null}
        {/* v-show的判断写法 */}
        &lt;div v-show={props.isShowBool}&gt;这里是v-show的成功判断&lt;/div&gt;
        {/* list 一维数组列表写法 */}
        {/* {this.list.map((item, index) =&gt; {
          return &lt;div key={index}&gt;{item}&lt;/div&gt;;
        })} */}
        {props.list.map((item: any, index: number) =&gt; {
          return &lt;div key={index}&gt;{item.name}&lt;/div&gt;;
        })}
        {/* 对象转换成数组 对象数组列表写法 */}
        {arr.map((item: any, index: number) =&gt; {
          return (
            &lt;div key={index}&gt;
              {item.name}:{item.value}
            &lt;/div&gt;
          );
        })}
        &lt;div&gt;-----------ChildProps 组件---------&lt;/div&gt;
      &lt;/&gt;
    );
  },
});
</code></pre>
<pre><code class="language-js">import { defineComponent, reactive } from &#39;vue&#39;;

export default defineComponent({
  name: &#39;ChildProps&#39;,
  props: {
    // 接收 数值
    num: {
      type: Number,
      default: 0,
    },
    // 接收 字符串
    msg: {
      type: String,
      default: &#39;&#39;,
    },
    // 接收v-if的判断
    isIfBool: {
      type: Boolean,
      default: false,
    },
    // 接收v-show的判断
    isShowBool: {
      type: Boolean,
      default: false,
    },
    // 接收数组
    list: {
      type: Array,
      default: () =&gt; [],
    },
    // 接收对象
    obj: {
      type: Object,
      default: () =&gt; {
        return {};
      },
    },
  },
  setup(props) {
    interface Obj {
      name?: string;
      age?: number;
      gender?: string;
    }
    const obj = reactive({ ...props });
    console.log(obj);
    // { name: &#39;张三&#39;, age: 16, gender: &#39;男生&#39; }
    const newObj: Obj = obj.obj;

    // 将对象转换成数组
    const arr: Array&lt;any&gt; = reactive([]);
    // for (const i in newObj) {
    //   arr.push({ [i]: newObj[i] });
    // }
    // console.log(arr);
    /**
     * [
     *  {name: &#39;张三&#39;},
     *  {age: 16},
     *  {gender: &#39;男生&#39;},
     * ]
     */
    for (const key in newObj) {
      const o = {};
      o[&#39;name&#39;] = key;
      o[&#39;value&#39;] = newObj[key];
      arr.push(o);
    }
    // console.log(arr);
    /**
     * [
     *   {name: &#39;name&#39;, value: &#39;张三&#39;},
     *   {name: &#39;age&#39;, value: 16},
     *   {name: &#39;gender&#39;, value: &#39;男生&#39;},
     * ]
     */

    return {
      arr,
    };
  },
  render() {
    return (
      &lt;&gt;
        &lt;div&gt;-----------ChildProps 组件---------&lt;/div&gt;
        {/* Number 的写法 */}
        &lt;div&gt;{this.num}&lt;/div&gt;
        {/* string 的写法 */}
        &lt;div&gt;{this.msg}&lt;/div&gt;
        {/* v-if 的判断的写法 */}
        {this.isIfBool ? &lt;div&gt;这里是v-if的成功判断&lt;/div&gt; : null}
        {/* v-show的判断写法 */}
        &lt;div v-show={this.isShowBool}&gt;这里是v-show的成功判断&lt;/div&gt;
        {/* list 一维数组列表写法 */}
        {/* {this.list.map((item, index) =&gt; {
          return &lt;div key={index}&gt;{item}&lt;/div&gt;;
        })} */}
        {this.list.map((item: any, index: number) =&gt; {
          return &lt;div key={index}&gt;{item.name}&lt;/div&gt;;
        })}
        {/* 对象转换成数组 对象数组列表写法 */}
        {this.arr.map((item: any, index: number) =&gt; {
          return (
            &lt;div key={index}&gt;
              {item.name}:{item.value}
            &lt;/div&gt;
          );
        })}
        &lt;div&gt;-----------ChildProps 组件---------&lt;/div&gt;
      &lt;/&gt;
    );
  },
});
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[react组件单元测试入门]]></title>
            <link>https://zzfzzf.com/post/6972548384863424522</link>
            <guid>6972548384863424522</guid>
            <pubDate>Fri, 15 Jul 2022 05:01:03 GMT</pubDate>
            <content:encoded><![CDATA[<h2>jest</h2>
<h2>Mocha</h2>
<h2>Enzyme</h2>
<ul>
<li><a href="https://juejin.cn/post/6863064626347819022">https://juejin.cn/post/6863064626347819022</a></li>
<li><a href="https://testing-library.com/">https://testing-library.com/</a></li>
<li><a href="https://juejin.cn/post/7116923978072981541">https://juejin.cn/post/7116923978072981541</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[npm install后发生了什么]]></title>
            <link>https://zzfzzf.com/post/6972548384863424520</link>
            <guid>6972548384863424520</guid>
            <pubDate>Thu, 30 Jun 2022 07:16:44 GMT</pubDate>
            <content:encoded><![CDATA[<h2>依赖管理</h2>
<p>依赖嵌套模式和依赖扁平模式是两种常见的依赖管理模式</p>
<h3>依赖嵌套模式</h3>
<p>依赖嵌套模式（Nested Dependencies）是最常见的依赖管理模式。在这种模式中，每个依赖项都有自己的node_modules目录，其中包含了该依赖项及其所有子依赖项的代码和资源。这种嵌套的目录结构可以形成一个依赖树，其中每个节点都代表一个依赖项，每个子节点都代表它的子依赖项。
在使用Node.js和npm进行软件开发时，依赖管理是一个重要的问题。依赖嵌套模式和依赖扁平模式是两种常见的依赖管理模式，本文将介绍这两种模式的概念和优缺点。</p>
<p>依赖嵌套模式
依赖嵌套模式（Nested Dependencies）是最常见的依赖管理模式。在这种模式中，每个依赖项都有自己的node_modules目录，其中包含了该依赖项及其所有子依赖项的代码和资源。这种嵌套的目录结构可以形成一个依赖树，其中每个节点都代表一个依赖项，每个子节点都代表它的子依赖项。</p>
<p>依赖嵌套模式的优点是：</p>
<ol>
<li><p>每个依赖项都有自己的独立空间，避免了不同版本的依赖项之间的冲突。</p>
</li>
<li><p>依赖树的结构可以清晰地反映出应用程序的依赖关系，便于开发人员理解和管理。</p>
</li>
<li><p>可以在不同的应用程序之间共享依赖项，避免了重复下载和安装。
但是，依赖嵌套模式也有一些缺点：</p>
</li>
<li><p>node_modules目录结构非常深，可能会导致文件路径过长的问题。</p>
</li>
<li><p>如果应用程序依赖的依赖项过多，依赖树的大小可能会变得非常大，导致安装和构建时间变慢。</p>
</li>
<li><p>每个依赖项都有自己的node_modules目录，占用了磁盘空间。</p>
</li>
</ol>
<h3>依赖扁平模式</h3>
<p>依赖扁平模式（Flat Dependencies）是一种新的依赖管理模式，它试图解决依赖嵌套模式的一些缺点。在这种模式中，所有依赖项都安装在项目的根目录下的node_modules目录中，而不是在各自的目录中。这样可以避免依赖树的嵌套结构，简化了目录结构，提高了构建和安装速度。</p>
<p>依赖扁平模式的优点是：</p>
<p>目录结构简单，避免了长路径和大量的子目录。
构建和安装速度更快，因为所有依赖项都在同一级目录中，避免了多层的查找和复制。
磁盘空间占用更少，因为依赖项共享同一个node_modules目录。</p>
<p>但是，依赖扁平模式也有一些缺点：</p>
<ol>
<li>如果有不同版本的依赖项，它们可能会互相冲突，导致应用程序无法正常工作。</li>
<li>依赖关系不再反映在目录结构中，这可能会使依赖管理更加困难和混乱。</li>
<li>无法在不同的应用程序之间共享依赖项，每个应用程序都需要自己安装和管理依赖项。</li>
<li></li>
</ol>
<h2>依赖管理工具</h2>
<h3>pnpm</h3>
<p>pnpm是一个另外的Node.js软件包管理器，旨在解决npm在安装过程中的一些常见问题，例如重复下载、存储占用等等。pnpm支持两种依赖模式：依赖嵌套模式和依赖扁平模式。默认情况下，pnpm使用依赖扁平模式，但是您可以通过将--no-flat标志传递给pnpm install命令来切换到依赖嵌套模式。</p>
<p>与npm不同，pnpm将所有模块安装到.pnpm目录下，并将依赖项符号链接到node_modules目录中。这意味着，即使在依赖扁平模式下，也可以避免冲突问题，因为每个依赖项都有自己的版本。同时，pnpm还支持并行安装和使用本地缓存，以提高安装速度。</p>
<h3>yarn</h3>
<p>yarn是由Facebook开发的另一个Node.js软件包管理器。与npm和pnpm不同，yarn只支持一种依赖扁平模式。类似于pnpm，yarn将所有模块安装到.yarn目录下，并将依赖项符号链接到node_modules目录中。这意味着，即使在依赖扁平模式下，每个依赖项也有自己的版本。</p>
<p>与npm不同，yarn使用了一个称为yarn.lock的锁定文件，以确保在重新安装应用程序时安装相同的版本。此外，yarn还支持并行安装、离线安装和使用本地缓存，以提高安装速度。</p>
<h2>install</h2>
<ol>
<li>检查package.json文件</li>
</ol>
<p>首先，npm会检查您的项目中是否存在package.json文件。package.json文件是一个描述了您的应用程序的元数据文件，其中包含了项目的名称、版本号、作者、许可证和依赖项等信息。如果不存在package.json文件，npm将无法安装依赖项，因为它不知道您的项目需要哪些依赖项。</p>
<ol start="2">
<li>下载依赖项</li>
</ol>
<p>如果存在package.json文件，npm将从npm注册表中下载依赖项。npm会检查package.json文件中的依赖项列表，并下载每个依赖项及其所有的子依赖项。下载的依赖项将被存储在您的项目的node_modules目录中。每个依赖项都有自己的文件结构，其中包含了所需的文件、文档、示例代码等。</p>
<ol start="3">
<li>安装依赖项</li>
</ol>
<p>一旦npm下载了依赖项，它将开始安装它们。这包括将依赖项的文件和目录复制到您的项目的node_modules目录中。此外，如果依赖项包含可执行文件，npm还将安装它们。</p>
<ol start="4">
<li>保存依赖项</li>
</ol>
<p>最后，npm将更新package.json文件，将所有安装的依赖项添加到dependencies或devDependencies字段中，具体取决于安装命令是否使用了--save或--save-dev选项。这是为了确保在将应用程序推送到其他开发人员或生产环境时，依赖项列表是可复制的，并且可以轻松安装相同的依赖项。</p>
<p>总的来说，npm install命令背后的工作流程是下载、安装和保存依赖项。这个命令是Node.js应用程序的核心组成部分，使得开发人员可以轻松地使用第三方代码，并管理这些代码的版本和依赖关系。</p>
<ul>
<li><a href="https://juejin.cn/post/7207702606646329399">https://juejin.cn/post/7207702606646329399</a></li>
<li><a href="https://juejin.cn/post/7114896608029835272">https://juejin.cn/post/7114896608029835272</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[常用排序算法]]></title>
            <link>https://zzfzzf.com/post/6972548384863424518</link>
            <guid>6972548384863424518</guid>
            <pubDate>Mon, 21 Mar 2022 04:12:10 GMT</pubDate>
            <content:encoded><![CDATA[<h2>冒泡排序</h2>
<pre><code class="language-js">function bubbleSort(dataSource) {
  for (let i = 0; i &lt; dataSource.length; i++) {
    for (let j = 0; j &lt; dataSource.length - i - 1; j++) {
      if (dataSource[j] &gt; dataSource[j + 1]) {
        [dataSource[j], dataSource[j + 1]] = [dataSource[j + 1], dataSource[j]];
      }
    }
  }
  return dataSource;
}
</code></pre>
<h2>选择排序</h2>
<blockquote>
<p>每轮循环把最小的数放在第一位，第二轮再找剩的最小的数</p>
</blockquote>
<pre><code class="language-js">function selectSort(dataSource) {
  const data = dataSource.slice();
  const length = data.length;
  for (let i = 0; i &lt; length; i++) {
    let min = i;
    for (let j = i + 1; j &lt; length; j++) {
      if (data[j] &lt; data[min]) {
        min = j;
      }
    }
    if (min !== i) {
      [data[i], data[min]] = [data[min], data[i]];
    }
  }
  return data;
}
</code></pre>
<h2>快速排序</h2>
<pre><code class="language-js">function quickSort(dataSource) {
  if (dataSource.length &lt;= 1) {
    return dataSource;
  }

  const pivot = dataSource[0];
  const left = [];
  const right = [];

  for (let i = 1; i &lt; dataSource.length; i++) {
    if (dataSource[i] &lt; pivot) {
      left.push(dataSource[i]);
    } else {
      right.push(dataSource[i]);
    }
  }

  return [...quickSort(left), pivot, ...quickSort(right)];
}
</code></pre>
<h2>插入排序</h2>
<pre><code class="language-js">function insertSort(dataSource) {
  const data = dataSource.slice();
  const length = data.length;
  for (let i = 1; i &lt; length; i++) {
    const current = data[i];
    let j = i - 1;
    while (j &gt;= 0 &amp;&amp; data[j] &gt; current) {
      data[j + 1] = data[j];
      j--;
    }
    data[j + 1] = current;
  }
  return data;
}
</code></pre>
<h2>希尔排序</h2>
<pre><code class="language-js">function shellSort(dataSource) {
  const data = dataSource.slice();
  const n = data.length;
  let gap = n / 2;
  while (gap &gt; 0) {
    for (let i = gap; i &lt; n; i += 1) {
      const temp = data[i];
      let j = i;
      while (j &gt;= gap &amp;&amp; data[j - gap] &gt; temp) {
        data[j] = data[j - gap];
        j -= gap;
      }
      data[j] = temp;
    }
    gap = Math.floor(gap / 2);
  }
  return data;
}
</code></pre>
<h2>归并排序</h2>
<h2>堆排序</h2>
<h2>总结</h2>
<table>
<thead>
<tr>
<th>排序类型</th>
<th>平均情况</th>
<th>最好情况</th>
<th>最坏情况</th>
<th>辅助空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody><tr>
<td>插入排序</td>
<td>O(n²)</td>
<td>O(n)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>稳定</td>
</tr>
<tr>
<td>希尔排序</td>
<td>O(n^1.3)</td>
<td>O(nlogn)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>不稳定</td>
</tr>
<tr>
<td>选择排序</td>
<td>O(n²)</td>
<td>O(n²)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>不稳定</td>
</tr>
<tr>
<td>堆排序</td>
<td>O(nlog₂n)</td>
<td>O(nlog₂n)</td>
<td>O(nlog₂n)</td>
<td>O(1)</td>
<td>不稳定</td>
</tr>
<tr>
<td>冒泡排序</td>
<td>O(n²)</td>
<td>O(n)</td>
<td>O(n²)</td>
<td>O(1)</td>
<td>稳定</td>
</tr>
<tr>
<td>快速排序</td>
<td>O(nlog₂n)</td>
<td>O(nlog₂n)</td>
<td>O(n²)</td>
<td>O(nlog₂n)</td>
<td>不稳定</td>
</tr>
<tr>
<td>归并排序</td>
<td>O(nlog₂n)</td>
<td>O(nlog₂n)</td>
<td>O(nlog₂n)</td>
<td>O(n)</td>
<td>稳定</td>
</tr>
<tr>
<td>基数排序</td>
<td>O(d(n+k))</td>
<td>O(d(n+k))</td>
<td>O(d(n+kd))</td>
<td>O(n+kd)</td>
<td>稳定</td>
</tr>
</tbody></table>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[浏览器工作原理]]></title>
            <link>https://zzfzzf.com/post/6972548384863424517</link>
            <guid>6972548384863424517</guid>
            <pubDate>Wed, 16 Mar 2022 02:28:58 GMT</pubDate>
            <content:encoded><![CDATA[<h2>大概流程</h2>
<ol>
<li>根据HTML构建HTML树 (DOM)</li>
<li>根据CSS构建 CSS树 (CSSOM)</li>
<li>将两棵树合并成一颗渲染树 (render tree)</li>
<li>Layout布局(文档流、盒模型、计算大小和位置)</li>
<li>Paint绘制(把边框颜色、文字颜色、阴影等画出来)</li>
<li>Compose 合成 （根据层叠关系展示画面）</li>
</ol>
<h2>浏览器进程</h2>
<ul>
<li><p>浏览器主进程，提供界面交互，管理子进程，文件存储等功能。</p>
</li>
<li><p>网络进程，提供下载网络资源的能力</p>
</li>
<li><p>插件进程，运行插件的进程</p>
</li>
<li><p>GPU进程，原是用来渲染CSS 3D的，后用来渲染UI界面</p>
</li>
<li><p>渲染进程，把HTML,CSS,JavaScript转为用户可与之交互的页面，JavaScript引擎便运行在渲染进程内。</p>
</li>
</ul>
<table>
<thead>
<tr>
<th>进程</th>
<th>功能</th>
</tr>
</thead>
<tbody><tr>
<td>浏览器主进程</td>
<td>主要负责界面显示、用户交互、子进程管理，同时提供存储等功能。</td>
</tr>
<tr>
<td>渲染进程</td>
<td>核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页，排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中。默认情况下，Chrome 浏览器会为每个tab标签创建一个渲染进程，但如果从一个页面打开了另一个新页面，而新页面和当前页面属于同一站点的话，那么新页面会复用父页面的渲染进程。</td>
</tr>
<tr>
<td>GPU进程</td>
<td>GPU 的使用初衷是为了实现 3D CSS 的效果，只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制，这使得 GPU 成为浏览器普遍的需求。最后，Chrome 在其多进程架构上也引入了 GPU 进程。</td>
</tr>
<tr>
<td>插件进程</td>
<td>主要是负责插件的运行，因插件易崩溃，所以需要通过插件进程来隔离，以保证插件进程崩溃不会对浏览器和页面造成影响。</td>
</tr>
<tr>
<td>网络进程</td>
<td>主要负责页面的网络资源加载。</td>
</tr>
</tbody></table>
<h2>详细流程</h2>
<h3>构建dom树</h3>
<h3>计算样式</h3>
<h3>布局</h3>
<h3>分层</h3>
<h3>图层绘制</h3>
<h3>栅格化(光栅化)</h3>
<h3>合成和显示</h3>
<h2>总结</h2>
<p>1、渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。</p>
<p>2、渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets，计算出 DOM 节点的样式。</p>
<p>3、创建布局树，并计算元素的布局信息。</p>
<p>4、对布局树进行分层，并生成分层树。</p>
<p>5、为每个图层生成绘制列表，并将其提交到合成线程。</p>
<p>6、合成线程将图层分成图块，并在光栅化线程池中将图块转换成位图。</p>
<p>7、合成线程发送绘制图块命令 DrawQuad 给浏览器进程。</p>
<p>8、浏览器进程根据 DrawQuad 消息生成页面，并显示到显示器上。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[前端监控方案]]></title>
            <link>https://zzfzzf.com/post/6972548384863424516</link>
            <guid>6972548384863424516</guid>
            <pubDate>Sat, 26 Apr 2025 01:17:45 GMT</pubDate>
            <content:encoded><![CDATA[<h2>总览</h2>
<blockquote>
<p>前端监控主要分为以下几个方面</p>
</blockquote>
<ol>
<li><p>性能监控
  采集以用户为中心的性能指标</p>
</li>
<li><p>错误监控
  捕获代码错误和资源错误</p>
</li>
<li><p>行为监控
  采集用户的点击事件、停留时间、ip, 清洗数据，描绘用户画像、pv、uv</p>
</li>
<li><p>数据上报
  合适的方式上报数据给服务端</p>
</li>
</ol>
<h2>性能监控</h2>
<blockquote>
<p>主要分为页面性能和资源性能</p>
</blockquote>
<ol>
<li>使用旧的<code>APIwindow.performance</code></li>
<li><code>requestAnimationFrame</code>测量浏览器帧率</li>
<li><code>PerformanceObserver</code>订阅与性能相关的事件，回调在空闲期间触发</li>
<li>使用<code>elementtiming</code>具体监听某个元素的加载时间</li>
</ol>
<h3>一、核心监控指标体系</h3>
<h4>1. 页面加载性能指标</h4>
<ul>
<li><strong>FCP（首次内容渲染）</strong>：衡量用户感知的首屏加载速度，Google建议控制在1.5秒内</li>
<li><strong>LCP（最大内容渲染）</strong>：标记主要内容加载完成时间，直接影响用户留存率（亚马逊数据显示每减少100msLCP可提升1%销售额）</li>
<li><strong>TTI（可交互时间）</strong>：反映页面功能可用性，理想值应小于3.8秒</li>
<li><strong>CLS（布局偏移）</strong>：评估视觉稳定性，优秀值需低于0.1</li>
</ul>
<h4>2. 网络性能指标</h4>
<ul>
<li><strong>TTFB（首字节时间）</strong>：服务器响应速度基准，超过600ms需优化后端服务</li>
<li><strong>资源加载耗时</strong>：监控CSS/JS文件加载时间，建议单资源小于150KB</li>
<li><strong>HTTP请求数</strong>：现代网页建议控制在50次以内</li>
</ul>
<h4>3. 运行时性能指标</h4>
<ul>
<li><strong>FID（首次输入延迟）</strong>：用户交互响应延迟，应小于100ms</li>
<li><strong>内存泄漏检测</strong>：通过Chrome DevTools Memory面板跟踪JS堆内存变化</li>
<li><strong>FPS（帧率）</strong>：动画场景需保持60fps，常规操作不低于30fps</li>
</ul>
<h3>二、监控工具与方法论</h3>
<h4>1. 自动化工具链</h4>
<ul>
<li><strong>Lighthouse</strong>：生成包含性能/SEO/可访问性的综合报告，支持CI/CD集成</li>
<li><strong>Web Vitals</strong>：Google官方库，可精准测量三大核心指标（LCP/FID/CLS）</li>
<li><strong>RUM（真实用户监控）</strong>：腾讯云方案支持日均4000亿数据量处理，覆盖Web/小程序等多端</li>
</ul>
<h4>2. 代码级监控</h4>
<pre><code class="language-js">// 使用Performance API获取FCP
const observer = new PerformanceObserver((list) =&gt; {
  const entries = list.getEntries();
  console.log(&#39;FCP:&#39;, entries[0].startTime);
});
observer.observe({type: &#39;paint&#39;, buffered: true});
</code></pre>
<h4>3. 全链路追踪</h4>
<ul>
<li><strong>资源加载瀑布图</strong>：通过Chrome Network面板分析请求时序</li>
<li><strong>Long Task监控</strong>：识别阻塞主线程超过50ms的任务</li>
<li><strong>Service Worker缓存命中率</strong>：优化离线体验的关键指标</li>
</ul>
<h3>三、实施流程与优化策略</h3>
<h4>1. **数据采集阶段</h4>
<ul>
<li><strong>埋点策略</strong>：混合使用代码埋点（关键路径）与无埋点（全量行为采集）</li>
<li><strong>采样控制</strong>：根据流量规模动态调整采样率（1%~100%）</li>
<li><strong>数据传输</strong>：优先使用<code>sendBeacon</code> API保证离线数据可靠上报</li>
</ul>
<h4>2. 性能优化手段</h4>
<ul>
<li><strong>资源压缩</strong>：WebP图片格式+Broti压缩可减少30%资源体积</li>
<li><strong>懒加载优化</strong>：首屏图片延迟加载使LCP提升40%</li>
<li><strong>缓存策略</strong>：设置<code>Cache-Control: max-age=31536000</code>实现静态资源长效缓存</li>
</ul>
<h4>3. 异常处理机制</h4>
<ul>
<li><strong>智能告警</strong>：腾讯云支持配置多维度阈值告警（如LCP&gt;4s触发P1级告警）</li>
<li><strong>根因分析</strong>：通过错误堆栈+用户操作路径回放快速定位问题</li>
<li><strong>热修复</strong>：结合React Error Boundary实现局部UI降级</li>
</ul>
<h2>错误监控</h2>
<ol>
<li><em>资源加载错误</em>
通过事件冒泡捕获 <code>&lt;img&gt;/&lt;script&gt;/&lt;link&gt;</code> 等资源加载失败</li>
</ol>
<pre><code class="language-js">document.addEventListener(&#39;error&#39;, (e) =&gt; {
    if (e.target.tagName.toLowerCase() === &#39;img&#39;) {
        reportError({
          type: &#39;resource&#39;,
          tag: e.target.tagName,
          url: e.target.src,
          status: e.target.naturalWidth === 0 ? &#39;404&#39; : &#39;unknown&#39;
        });
    }
}, true);
</code></pre>
<ol start="4">
<li><em>JS运行时错误</em></li>
</ol>
<p>使用 <code>window.onerror</code> + <code>window.addEventListener(&#39;error&#39;)</code> 组合方案</p>
<pre><code class="language-js">window.addEventListener(&#39;error&#39;, (event) =&gt; {
  const { message, filename, lineno, colno, error } = event;
  reportError({
    type: &#39;js&#39;,
    message,
    file: filename,
    line: lineno,
    column: colno,
    stack: error?.stack
  });
}, true);  // 注意使用捕获阶段监听资源错误
</code></pre>
<ol start="5">
<li><em>Promise异常</em>
通过 <code>unhandledrejection</code> 事件捕获未处理的Promise拒绝</li>
</ol>
<pre><code class="language-js">window.addEventListener(&#39;unhandledrejection&#39;, (event) =&gt; {
  reportError({
    type: &#39;promise&#39;,
    reason: event.reason?.message,
    stack: event.reason?.stack
  });
  event.preventDefault();  // 阻止控制台默认报错
});
</code></pre>
<ol start="8">
<li>react ErrorBoundary</li>
</ol>
<pre><code class="language-js">Vue.config.errorHandler = function (err) { setTimeout(() =&gt; { throw err }) }
  window.onerror = function (message, url, line, column, error) { console.log(message, url, line, column, error); }
</code></pre>
<h2>行为监控</h2>
<ol>
<li>基础交互行为</li>
</ol>
<ul>
<li>点击/滚动/悬停：通过事件监听捕获DOM元素操作（如click/scroll事件），记录用户操作路径</li>
<li>表单行为：监控输入框聚焦、内容修改、提交成功率（通过input/submit事件跟踪）</li>
<li>页面停留时间：通过visibilitychange事件计算页面可见时长，分析用户兴趣点</li>
</ul>
<ol start="2">
<li>深度行为分析</li>
</ol>
<ul>
<li>热区点击分布：通过坐标计算生成热力图，识别页面核心交互区域</li>
<li>滚动深度监控：记录页面垂直滚动百分比，评估内容吸引力（如70%用户未浏览第二屏需优化布局）</li>
<li>鼠标移动轨迹：通过mousemove事件采样轨迹数据，分析用户浏览习惯</li>
</ul>
<ol start="3">
<li>业务核心行为</li>
</ol>
<ul>
<li>转化漏斗：跟踪关键路径（如注册→支付→复购），计算各环节流失率</li>
<li>功能使用频次：记录特定模块点击量（如搜索框使用率），评估功能价值</li>
<li>用户分群行为：区分新老用户、设备类型等维度的行为差异（如iOS用户更倾向视频播放）</li>
</ul>
<ol>
<li><p>路由变化</p>
</li>
<li><p>设备信息：</p>
</li>
</ol>
<ul>
<li>操作系统、分辨率、浏览器</li>
<li>IP、指纹标识用户</li>
<li>pv、uv</li>
<li>document.referrer获取来源地址</li>
</ul>
<ol start="3">
<li>Referrer</li>
</ol>
<h2>错误上报</h2>
<p>字段设计</p>
<pre><code class="language-json">{
  &quot;timestamp&quot;: &quot;2025-04-25T14:30:00Z&quot;,
  &quot;url&quot;: window.location.href,
  &quot;userAgent&quot;: navigator.userAgent,
  &quot;errorType&quot;: &quot;js/promise/resource&quot;,
  &quot;message&quot;: &quot;Cannot read property &#39;length&#39; of undefined&quot;,
  &quot;stack&quot;: &quot;at buttonClick (main.js:32:15)...&quot;
}
</code></pre>
<pre><code class="language-js">const errorID = window.btoa(`${message}-${stack.split(&#39;\n&#39;)[0]}`);
</code></pre>
<blockquote>
<p>性能数据可以在页面加载完成之后采集并上报，尽量不要对页面性能造成影响。或者requestIdleCallback</p>
<p>错误上报一般使用一般采用批量和立即上报2种方式</p>
</blockquote>
<ol>
<li><p>Xhr</p>
</li>
<li><p>Image</p>
</li>
<li><p>Sendbeacon</p>
</li>
</ol>
<pre><code class="language-js">window.onload = () =&gt; {
// 在浏览器空闲时间获取性能及资源信息
// https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback
if (window.requestIdleCallback) {
    window.requestIdleCallback(() =&gt; {
        monitor.performance = getPerformance()
        monitor.resources = getResources()
    })
} else {
    setTimeout(() =&gt; {
        monitor.performance = getPerformance()
        monitor.resources = getResources()
    }, 0)
}
}
</code></pre>
<pre><code class="language-js">window.unload = function() { // 在页面卸载的时候上报错误数据
navigator.sendBeacon(&#39;/xxxx&#39;, monitor.errors)
}
document.addEventListener(&#39;visibilitychange&#39;, function logData() {
if (document.visibilityState === &#39;hidden&#39;) {
navigator.sendBeacon(&#39;/log&#39;, analyticsData);
}
});
//onunload不要用，移动端有问题
//可以用pagehide来代替
</code></pre>
<h2>常见问题</h2>
<ol>
<li>为什么用1*1的gif图片</li>
</ol>
<ul>
<li><p>没有跨域问题</p>
</li>
<li><p>发 POST 请求之后不需要获取和处理数据、服务器也不需要发送数据</p>
</li>
<li><p>不会携带当前域名 cookie</p>
</li>
<li><p>不会阻塞页面加载，影响用户的体验，只需 new Image 对象</p>
</li>
<li><p>相比于 BMP/PNG 体积最小，可以节约 41% / 35% 的网络资源小</p>
</li>
<li><p>详见<a href="https://juejin.cn/post/7065123244881215518">为什么大厂前端监控都在用GIF做埋点？ - 掘金</a></p>
</li>
</ul>
<h2>附录</h2>
<ul>
<li><p><a href="https://github.com/mitojs/mitojs">https://github.com/mitojs/mitojs</a></p>
</li>
<li><p><a href="https://github.com/zzfn/monitor">https://github.com/zzfn/monitor</a></p>
</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[虚拟dom和diff算法]]></title>
            <link>https://zzfzzf.com/post/6972548384863424515</link>
            <guid>6972548384863424515</guid>
            <pubDate>Tue, 22 Feb 2022 13:20:45 GMT</pubDate>
            <content:encoded><![CDATA[<h2>虚拟dom</h2>
<p>虚拟DOM（Virtual DOM）是一种将页面状态抽象为JavaScript对象（虚拟节点）的技术。在前端框架中，虚拟DOM常常被用来描述页面状态、维护组件状态，进而实现高效的页面更新。</p>
<p>虚拟DOM的基本原理是在内存中构建一棵虚拟的DOM树，用JavaScript对象模拟出真实的DOM结构，对这个虚拟的DOM进行操作，最后再将变化的部分更新到真实的DOM上。这个过程相当于把真实DOM转换成虚拟DOM，再将虚拟DOM转换成真实DOM。</p>
<h3>优点</h3>
<ol>
<li><p>减少DOM操作，提高性能。由于DOM操作是非常耗费性能的，因此虚拟DOM可以在内存中进行操作，最后一次性将变化更新到真实DOM上，从而减少了DOM操作的次数，提高了页面渲染性能。</p>
</li>
<li><p>方便跨平台开发。由于虚拟DOM是一个JavaScript对象，因此可以方便地在不同平台上共享和传递，例如Node.js、Web Worker等。</p>
</li>
<li><p>易于维护。通过使用虚拟DOM，我们可以将页面状态、UI逻辑和视图的渲染分离，从而更容易进行代码维护和重构。</p>
</li>
</ol>
<h2>diff</h2>
<blockquote>
<p>diff 分为单节点diff和多节点diff</p>
</blockquote>
<h3>diff优化</h3>
<ol>
<li>只对同级元素进行Diff。如果一个DOM节点在前后两次更新中跨越了层级，那么React不会尝试复用他。</li>
<li>两个不同类型的元素会产生出不同的树。如果元素由div变为p，React会销毁div及其子孙节点，并新建p及其子孙节点。</li>
<li>开发者可以通过 key prop来暗示哪些子元素在不同的渲染下能保持稳定。</li>
</ol>
<h3>单节点diff</h3>
<p>单节点Diff也被称为原地更新算法，它是在进行Virtual DOM比较时，只考虑同一层级下的节点，不会跨层级比较。具体实现是，当进行Virtual DOM比较时，如果节点类型相同且key相同，则更新其属性；否则替换整个节点。这样做的好处是可以提高比较速度，但缺点是无法处理节点的移动和复杂的变更操作。</p>
<h3>多节点diff</h3>
<p>多节点Diff是通过两轮遍历实现的。第一轮遍历是针对新旧节点列表进行的，用于标记需要进行删除、添加、移动和更新的节点，以及处理一些特殊情况（例如key属性变更）。第二轮遍历是针对被标记的节点进行的，用于实际的DOM操作。</p>
<p>具体来说，React的多节点Diff算法包括以下几个步骤：</p>
<ol>
<li><p>遍历旧节点列表，标记需要删除和移动的节点，并建立旧节点的索引；</p>
</li>
<li><p>遍历新节点列表，标记需要添加和移动的节点，并尝试复用旧节点；</p>
</li>
<li><p>处理特殊情况，如key属性变更和节点类型不同等；</p>
</li>
<li><p>遍历被标记的节点，执行删除、添加、移动和更新操作；</p>
</li>
</ol>
<p>5.对于无法复用的旧节点和新节点，创建新的DOM节点并添加到父节点上。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[编译个性化的路由器固件]]></title>
            <link>https://zzfzzf.com/post/6972548384863424514</link>
            <guid>6972548384863424514</guid>
            <pubDate>Fri, 13 Feb 2026 03:34:47 GMT</pubDate>
            <content:encoded><![CDATA[<h2>资源</h2>
<ol>
<li><a href="https://github.com/coolsnowwolf/lede">lean</a></li>
<li><a href="https://github.com/Lienol/openwrt">Lienol</a></li>
<li><a href="https://github.com/immortalwrt/immortalwrt">https://github.com/immortalwrt/immortalwrt</a></li>
<li>img写盘工具(DiskImg)</li>
<li><a href="https://github.com/kenzok8/openwrt-packages">工具合集</a></li>
<li><a href="https://github.com/fw876/helloworld">helloworld</a></li>
<li><a href="https://www.right.com.cn/forum/thread-344825-1-1.html">插件列表</a></li>
<li><a href="https://github.com/vernesong/OpenClash">openclash</a></li>
<li><a href="https://downloads.openwrt.org/">https://downloads.openwrt.org/</a></li>
<li><a href="https://github.com/openwrt/openwrt">https://github.com/openwrt/openwrt</a></li>
</ol>
<pre><code>OpenWrt Configuration【OpenWrt配置】Target System (x86)  ---&gt;   目标系统（x86）
Subtarget (x86_64)  ---&gt;   子目标（x86_64）
Target Profile (Generic)  ---&gt;目标配置文件（通用）
Target Images  ---&gt; 保存目标镜像的格式
Global build settings  ---&gt;      全局构建设置
Advanced configuration options (for developers)  ---- 高级配置选项（适用于开发人员）
Build the OpenWrt Image Builder 构建OpenWrt图像生成器
Build the OpenWrt SDK构建OpenWrt SDK
Package the OpenWrt-based Toolchain打包基于OpenWrt的工具链
Image configuration  ---&gt;图像配置
Base system  ---&gt;     基本系统
Administration  ---&gt;     管理
Boot Loaders  ---&gt;引导加载程序
Development  ---&gt;   开发
Extra packages  ---&gt;  额外包
Firmware  ---&gt;固件
Fonts  ---&gt;字体
Kernel modules  ---&gt;  内核模块
Languages  ---&gt;语言
Libraries  ---&gt;  图书馆
LuCI  ---&gt;      LuCI
Mail  ---&gt;邮件
Multimedia  ---&gt;多媒体
Network  ---&gt;网络
Sound  ---&gt; 声音
Utilities  ---&gt;实用程序
Xorg  ---&gt;Xorg
</code></pre>
<blockquote>
<p>wsl环境</p>
</blockquote>
<h2>环境准备</h2>
<h3>安装ubuntu wsl</h3>
<ol>
<li>win+x，选择Windows PowerShell（管理员）</li>
<li>输入</li>
</ol>
<pre><code>Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
</code></pre>
<ol start="3">
<li>回车，输入Y，重启！</li>
<li>重新打开已经安装的子系统，等几分钟，输入账户和密码。</li>
</ol>
<h3>开启ssh</h3>
<pre><code>sudo vim /etc/ssh/sshd_config
</code></pre>
<pre><code>Port 22 #默认即可，如果有端口占用可以自己修改
PasswordAuthentication yes # 允许用户名密码方式登录
</code></pre>
<pre><code>sudo /etc/init.d/ssh start
</code></pre>
<h2>首次编译</h2>
<ol>
<li>安装依赖</li>
</ol>
<pre><code>sudo apt update -y
sudo apt full-upgrade -y
sudo apt install -y ack antlr3 asciidoc autoconf automake autopoint binutils bison build-essential \
bzip2 ccache cmake cpio curl device-tree-compiler fastjar flex gawk gettext gcc-multilib g++-multilib \
git gperf haveged help2man intltool libc6-dev-i386 libelf-dev libglib2.0-dev libgmp3-dev libltdl-dev \
libmpc-dev libmpfr-dev libncurses5-dev libncursesw5-dev libreadline-dev libssl-dev libtool lrzsz \
mkisofs msmtp nano ninja-build p7zip p7zip-full patch pkgconf python2.7 python3 python3-pip qemu-utils \
rsync scons squashfs-tools subversion swig texinfo uglifyjs upx-ucl unzip vim wget xmlto xxd zlib1g-dev
</code></pre>
<ol start="2">
<li>下载源代码</li>
</ol>
<pre><code>git clone https://github.com/coolsnowwolf/lede
</code></pre>
<ol start="3">
<li>添加openclash</li>
</ol>
<pre><code>vim ./feeds.conf.default
添加一行
src-git openclash https://github.com/vernesong/OpenClash.git
</code></pre>
<ol start="4">
<li>修改默认ip地址</li>
</ol>
<pre><code>找到192.168.1.1 替换默认ip
vi package/base-files/files/bin/config_generate
</code></pre>
<ol start="5">
<li>更新 feeds 并选择配置</li>
</ol>
<pre><code>cd lede
./scripts/feeds update -a
./scripts/feeds install -a
make menuconfig
</code></pre>
<ul>
<li>Target System (x86) ---&gt; 目标系统（x86）</li>
<li>Subtarget (x86_64) ---&gt; 子目标（x86_64）</li>
<li>Target Profile (Generic) ---&gt;目标配置文件（通用）</li>
<li>Target Images ---&gt; 保存目标镜像的格式Global </li>
<li>build settings ---&gt; 全局构建设置</li>
<li>Advanced configuration options (for developers) ---- 高级配置选项（适用于开发人员）</li>
<li>Build the OpenWrt Image Builder 构建OpenWrt图像生成器Build the OpenWrt SDK构建OpenWrt SDKPackage the OpenWrt-based Toolchain打包基于OpenWrt的工具链Image configuration ---&gt;图像配置Base system ---&gt; 基本系统Administration ---&gt; 管理Boot Loaders ---&gt;引导加载程序Development ---&gt; 开发Extra packages ---&gt; 额外包Firmware ---&gt;固件</li>
<li>Fonts ---&gt;字体</li>
<li>Kernel modules ---&gt; 内核模块</li>
<li>Languages ---&gt;语言</li>
<li>Libraries ---&gt; 图书馆LuCI ---&gt; </li>
<li>LuCIMail ---&gt;邮件</li>
<li>Multimedia ---&gt;多媒体</li>
<li>Network ---&gt;网络</li>
<li>Sound ---&gt; 声音Utilities ---&gt;实用程序Xorg ---&gt;Xorg</li>
</ul>
<p>博主为j1900编译固件 x86架构</p>
<ul>
<li>Target Images 保留squashfs</li>
</ul>
<ol start="6">
<li>下载 dl 库，编译固件</li>
</ol>
<pre><code>make download -j8 V=s
make V=s -j1
</code></pre>
<h2>二次编译</h2>
<h3>配置不变，只是pull代码</h3>
<pre><code>cd lede
git pull
./scripts/feeds update -a &amp;&amp; ./scripts/feeds install -a
make defconfig
make -j8 download
make -j$(($(nproc) + 1)) V=s
</code></pre>
<h3>需要修改menuconfig</h3>
<pre><code>rm -rf ./tmp &amp;&amp; rm -rf .config //可选
make menuconfig
make -j$(($(nproc) + 1)) V=s
</code></pre>
<h3>添加其他源</h3>
<blockquote>
<p>如果是添加了其他源，如果你是第二次编译，最好使用make clean命令和 ./scripts/feeds clean命令清除一些障碍，如果是全新安装就无须clean。</p>
</blockquote>
<pre><code>make clean
./scripts/feeds clean
rm -rf ./tmp &amp;&amp; rm -rf .config //可选
make menuconfig
make -j$(($(nproc) + 1)) V=s
</code></pre>
<h2>产物</h2>
<blockquote>
<p>一般来说，用openwrt-x86-64-generic-squashfs-combined.img文件就可以了
编译完成后输出路径：bin/targets</p>
</blockquote>
<h2>Tips</h2>
<h3>固件空间</h3>
<p>插件比较多的时候，下x86/64下调整一下设置，留下空间</p>
<pre><code>Target Images ---&gt; (16) Kernel partition size (in MB) #默认是 (16) 建议修改 (256)
Target Images ---&gt; (160) Root filesystem partition size (in MB) #默认是 (160) 建议修改 (512)
</code></pre>
<h3>使用WSL/WSL2进行编译</h3>
<p>由于 WSL 的 PATH 中包含带有空格的 Windows 路径，有可能会导致编译失败，请在 make 前面加上：</p>
<pre><code>PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
</code></pre>
<h3>其他</h3>
<ul>
<li>不要用 root 用户进行编译！！！</li>
<li>默认登陆IP 192.168.1.1 密码 password</li>
</ul>
<h2>在线编译</h2>
<p><a href="https://openwrt.ai/">https://openwrt.ai/</a>
<img src="https://w.zzfzzf.com/img/CleanShot%202026-02-13%20at%2011.19.59@2x.png" alt="图片描述">
分离硬盘
<img src="https://w.zzfzzf.com/img/CleanShot%202026-02-13%20at%2011.26.54.png" alt="图片描述"></p>
<pre><code class="language-bash"># 语法：qm importdisk &lt;VM_ID&gt; &lt;镜像路径&gt; &lt;存储池&gt;
# 示例：将镜像导入到 ID 为 111 的虚拟机，存入 local-lvm
qm importdisk 111 /var/lib/vz/template/iso/kwrt-02.13.2026-x86-64-generic-squashfs-combined-efi.img local-lvm
</code></pre>
<p><img src="https://w.zzfzzf.com/img/CleanShot%202026-02-13%20at%2011.30.38.png" alt="图片描述"></p>
<p>执行完毕后，回到 PVE 控制台，点击 虚拟机 -&gt; 硬件。</p>
<p>你会看到一个“未使用的磁盘”，双击它并点击 添加。</p>
<p>最重要的一步：进入 选项 (Options) -&gt; 引导顺序 (Boot Order)，将新添加的磁盘勾选并拖动到第一位</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[常用的几个设计模式]]></title>
            <link>https://zzfzzf.com/post/6972548384863424513</link>
            <guid>6972548384863424513</guid>
            <pubDate>Tue, 25 Jan 2022 04:15:16 GMT</pubDate>
            <content:encoded><![CDATA[<h2>原型模式</h2>
<p>为JavaScript设计面向对象系统之初，Brendan Eich 就没有打算在JavaScript中加入类的概念。</p>
<p>在以类为中心的面向对象编程语言中，类和对象的关系可以想象成铸模和铸件的关系，对象总是从类中创建而来。而在原型编程的思想中，类并不是必需的，对象未必需要从类中创建而来， <strong>一个对象是通过克隆另外一个对象所得到的</strong>。</p>
<p>原型模式是用于创建对象的一种模式，如果我们想要创建一个对象， 一种方法是先指定它的类型，然后通过类来创建这个对象。原型模式选择了另外一种方式，我们 不再关心对象的具体类型，而是找到一个对象，然后通过克隆来创建一个一模一样的对象。</p>
<p>原型模式的实现关键，是语言本身是否提供了clone 方法。ECMAScript 5 提供了<code>Object.create</code> 方法，可以用来克隆对象。</p>
<p>但实际上，不需要调用<code>Object.create</code>，我们在JavaScript 遇到的每个对象，都是从<code>Object.prototype</code> 对象<strong>克隆</strong>而来的，<code>Object.prototype</code> 就是它们的原型。比如下面的obj1 对象和obj2 对象：</p>
<pre><code class="language-JavaScript">var obj1 = new Object();

var obj2 = {};

复制代码
</code></pre>
<p>在JavaScript 语言里，我们并不需要关心克隆的细节，因为这是引擎内部负责实现的。我们所需要做的只是显式地调用<em>var obj1 = new Object()</em> 或者 <em>var obj2 = {}</em>。此时，引擎内部会从 <code>Object.prototype</code>上面克隆一个对象出来，我们最终得到的就是这个对象。</p>
<p>再来看看如何用new 运算符从构造器中得到一个对象，下面的代码我们再熟悉不过了：</p>
<pre><code class="language-JavaScript">function Person( name ){

    this.name = name;

};

Person.prototype.getName = function(){

    return this.name;

};

var a = new Person( &#39;sven&#39; )

console.log( a.name ); // 输出：sven

console.log( a.getName() ); // 输出：sven

console.log( Object.getPrototypeOf( a ) === Person.prototype );

复制代码
</code></pre>
<p>我们调用了<em>new Person()</em> ，但在这里Person并不是类，而是函数构造器，JavaScript的函数既可以作为普通函数被调用，也可以作为构造器被调用。当使用new 运算符来调用函数时，此时的函数就是一个构造器。</p>
<p>在Chrome 和Firefox 等向外暴露了对象__proto__属性的浏览器下，我们可以通过下面这段代码来理解new运算的过程：</p>
<pre><code class="language-JavaScript">function Person( name ){

    this.name = name;

};

Person.prototype.getName = function(){

    return this.name;

};

var objectFactory = function(){

    var obj = new Object(), // 从Object.prototype 上克隆一个空的对象

    Constructor = [].shift.call( arguments ); // 取得外部传入的构造器，此例是Person

    obj.__proto__ = Constructor.prototype; // 指向正确的原型

    var ret = Constructor.apply( obj, arguments ); // 借用外部传入的构造器给obj 设置属性

    return typeof ret === &#39;object&#39; ? ret : obj; // 确保构造器总是会返回一个对象

};



var a = objectFactory( Person, &#39;sven&#39; );

console.log( a.name ); // 输出：sven

console.log( a.getName() ); // 输出：sven

console.log( Object.getPrototypeOf( a ) === Person.prototype ); // 输出：true

复制代码
</code></pre>
<p>对于原型，我们很清楚的一点是当一个对象无法响应某个请求的时候，它会顺着原型链把请求传递下去，直到遇到一个可以处理该请求的对象为止。</p>
<h2>单例模式</h2>
<blockquote>
<p>保证一个类仅有一个实例，并提供一个访问它的全局访问点</p>
</blockquote>
<pre><code class="language-JavaScript">class Demo {



}



function getInstance() {

    let instance = null;

    return function () {

        if (!instance) {

            instance = new Demo();

        }

        return instance;

    }

}

const instance1 = getInstance();

const instance2 = getInstance();
</code></pre>
<h2>策略模式</h2>
<blockquote>
<p>用于替换很多<code>if</code> <code>else</code>的场景</p>
</blockquote>
<p>改造前</p>
<pre><code class="language-JavaScript">const calculateBonus = function (performanceLevel, salary) {

    if (performanceLevel === &#39;S&#39;) {

        return salary * 4;

    }

    if (performanceLevel === &#39;A&#39;) {

        return salary * 3;

    }

    if (performanceLevel === &#39;B&#39;) {

        return salary * 2;

    }

};

console.log(calculateBonus(&#39;B&#39;, 20000))

console.log(calculateBonus(&#39;S&#39;, 6000))
</code></pre>
<p>改造后</p>
<pre><code class="language-JavaScript">const strategies = {

    &quot;S&quot;: function (salary) {

        return salary * 4;

    },

    &quot;A&quot;: function (salary) {

        return salary * 3;

    },

    &quot;B&quot;: function (salary) {

        return salary * 2;

    }

};

const calculateBonus = function (level, salary) {

    return strategies[level](salary);

};

console.log(calculateBonus(&#39;S&#39;, 20000))

console.log(calculateBonus(&#39;A&#39;, 10000))
</code></pre>
<h2>状态模式</h2>
<p><code>状态模式</code>的关键是区分事物内部的状态，事物内部状态的改变往往会带来事物的行为改变。</p>
<p>我们来想象这样一个场景：有一个电灯，电灯上面只有一个开关。当电灯开着的时候，此时按下开关，电灯会切换到关闭状态；再按一次开关，电灯又将被打开。同一个开关按钮，在不同的状态下，表现出来的行为是不一样的。</p>
<p>首先给出不用状态模式的电灯程序实现：</p>
<pre><code class="language-JavaScript">    var Light = function () {

      this.state = &#39;off&#39;; // 给电灯设置初始状态off

      this.button = null; // 电灯开关按钮

    };



    Light.prototype.init = function () {

      var button = document.createElement(&#39;button&#39;),

        self = this;

      button.innerHTML = &#39;开关&#39;;

      this.button = document.body.appendChild(button);

      this.button.onclick = function () {

        self.buttonWasPressed();

      }

    };



    Light.prototype.buttonWasPressed = function () {

      if (this.state === &#39;off&#39;) {

        console.log(&#39;开灯&#39;);

        this.state = &#39;on&#39;;

      } else if (this.state === &#39;on&#39;) {

        console.log(&#39;关灯&#39;);

        this.state = &#39;off&#39;;

      }

    };

    var light = new Light();

    light.init();

复制代码
</code></pre>
<p>令人遗憾的是，这个世界上的电灯并非只有一种。许多酒店里有另外一种电灯，这种电灯也只有一个开关，但它的表现是：第一次按下打开弱光，第二次按下打开强光，第三次才是关闭电灯。而这些必须改造上面的代码来完成这种新型电灯的制造。</p>
<p>但将状态改变的逻辑全部写在<em>buttonWasPressed</em>中存在以下问题：</p>
<ol>
<li><strong>违反开放-封闭原则</strong>的，每次新增或者修改<em>light</em>的状态，都需要改动<em>buttonWasPressed</em>方法中的代码。</li>
<li>所有跟状态有关的行为，都被封装在<em>buttonWasPressed</em>方法里，我们将无法预计这个<strong>方法</strong>将<strong>膨胀</strong>到什么地步。</li>
<li><strong>状态的切换非常不明显</strong>，仅仅表现为对state变量赋值，我们也没有办法一目了然地明白电灯一共有多少种状态。</li>
<li>状态之间的切换关系，不过是往<em>buttonWasPressed</em>方法里堆砌<em>if</em>、<em>else</em>语句，这使<em>buttonWasPressed</em>更加<strong>难以阅读和维护</strong>。</li>
</ol>
<p>现在我们学习使用状态模式改进电灯的程序。状态模式的关键是把事物的每种<strong>状态都封装成单独的类</strong>，跟此种<strong>状态有关的行为都被封装在这个类的内部</strong>，所以<em>button</em>被按下的的时候，只需要在上下文中，把这个请求委托给当前的状态对象即可，该状态对象会负责渲染它自身的行为。</p>
<pre><code class="language-JavaScript">    // OffLightState：

    var OffLightState = function (light) {

      this.light = light;

    };

    OffLightState.prototype.buttonWasPressed = function () {

      console.log(&#39;弱光&#39;); // offLightState 对应的行为

      this.light.setState(this.light.weakLightState); // 切换状态到weakLightState

    };

    // WeakLightState：

    var WeakLightState = function (light) {

      this.light = light;

    };

    WeakLightState.prototype.buttonWasPressed = function () {

      console.log(&#39;强光&#39;); // weakLightState 对应的行为

      this.light.setState(this.light.strongLightState); // 切换状态到strongLightState

    };

    // StrongLightState：

    var StrongLightState = function (light) {

      this.light = light;

    };

    StrongLightState.prototype.buttonWasPressed = function () {

      console.log(&#39;关灯&#39;); // strongLightState 对应的行为

      this.light.setState(this.light.offLightState); // 切换状态到offLightState

    };



    var Light = function () {

      this.offLightState = new OffLightState(this);

      this.weakLightState = new WeakLightState(this);

      this.strongLightState = new StrongLightState(this);

      this.button = null;

    };

    Light.prototype.init = function () {

      var button = document.createElement(&#39;button&#39;),

        self = this;

      this.button = document.body.appendChild(button);

      this.button.innerHTML = &#39;开关&#39;;

      this.currState = this.offLightState; // 设置当前状态

      this.button.onclick = function () {

        self.currState.buttonWasPressed();

      }

    };

    Light.prototype.setState = function (newState) {

      this.currState = newState;

    };

    var light = new Light();

    light.init();

复制代码
</code></pre>
<p>执行结果跟之前的代码一致，但是使用状态模式的好处很明显，它可以使每一种状态和它对应的行为之间的关系局部化，这些行为被分散和封装在各自对应的状态类之中，便于阅读和管理代码。</p>
<p>另外，状态之间的切换都被分布在状态类内部，这使得我们无需编写过多的<em>if</em>、<em>else</em>条件分支语言来控制状态之间的转换。</p>
<h2>适配器模式</h2>
<p><code>适配器模式</code>的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后，原本由于接口不兼容而不能工作的两个软件实体可以一起工作。</p>
<p>假设我们要用<em>googleMap</em>和<em>baiduMap</em>分别以各自的方式在页面中展现地图。</p>
<pre><code class="language-JavaScript">    var googleMap = {

      show: function () {

        console.log(&#39;开始渲染谷歌地图&#39;);

      }

    };

    var baiduMap = {

      display: function () {

        console.log(&#39;开始渲染百度地图&#39;);

      }

    };

    var renderMap = function (map) {

      if (map.show instanceof Function) {

        map.show();

      }

    };

复制代码
</code></pre>
<p>由于两个地图的展示方法不同，所以不能直接调用<em>renderMap</em>函数，因此我们要为百度地图提供一个适配器。这样我们就可以同时渲染2个地图了。</p>
<pre><code class="language-JavaScript">    var baiduMapAdapter = {

      show: function () {

        return baiduMap.display();

      }

    };

    renderMap(googleMap); // 输出：开始渲染谷歌地图

    renderMap(baiduMapAdapter); // 输出：开始渲染百度地图
</code></pre>
<h2>发布订阅模式</h2>
<blockquote>
<p>状态变化需要通知其他事件</p>
</blockquote>
<pre><code class="language-JavaScript">    var salesOffices = {}; // 定义售楼处

    salesOffices.clientList = []; // 缓存列表，存放订阅者的回调函数

    salesOffices.listen = function (fn) { // 增加订阅者

      this.clientList.push(fn); // 订阅的消息添加进缓存列表

    };

    salesOffices.trigger = function () { // 发布消息

      for (var i = 0, fn; fn = this.clientList[i++];) {

        fn.apply(this, arguments); // (2) // arguments 是发布消息时带上的参数

      }

    };



    salesOffices.listen(function (price, squareMeter) { // 小明订阅消息

      console.log(&#39;价格= &#39; + price);

      console.log(&#39;squareMeter= &#39; + squareMeter);

    });

    salesOffices.listen(function (price, squareMeter) { // 小红订阅消息

      console.log(&#39;价格= &#39; + price);

      console.log(&#39;squareMeter= &#39; + squareMeter);

    });

    salesOffices.trigger(2000000, 88); // 输出：200 万，88 平方米

    salesOffices.trigger(3000000, 110); // 输出：300 万，110 平方米
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[css常见布局方案总结]]></title>
            <link>https://zzfzzf.com/post/6972548384863424512</link>
            <guid>6972548384863424512</guid>
            <pubDate>Thu, 20 Jan 2022 15:49:29 GMT</pubDate>
            <content:encoded><![CDATA[<h2>基线</h2>
<p>vertical-align</p>
<h2>两栏布局</h2>
<ol>
<li>flex</li>
<li>float+BFC</li>
<li>float+margin-left</li>
</ol>
<h2>三栏布局</h2>
<blockquote>
<p>初始代码</p>
</blockquote>
<pre><code class="language-html">&lt;div class=&quot;column-container&quot;&gt;
    &lt;div class=&quot;column left&quot;&gt;left&lt;/div&gt;
    &lt;div class=&quot;column center&quot;&gt;center&lt;/div&gt;
    &lt;div class=&quot;column right&quot;&gt;right&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<pre><code class="language-css">    .left{
        background-color: #61dafb;
    }
    .center{
        background-color: #282c34;
    }
    .right{
        background-color: #ff99cc;
    }
</code></pre>
<h4>grid</h4>
<pre><code class="language-css">.column-container {
  display: grid;
  grid-template-columns: 200px auto 200px;
  column-gap: 10px;
}
</code></pre>
<h4>Flex 布局</h4>
<pre><code class="language-css">.column-container {
  display: flex;
  column-gap: 10px;
}
.left,
.right {
  width: 200px;
}
.center {
  flex: 1;
}
</code></pre>
<h4>圣杯/双飞翼布局</h4>
<p><a href="https://zzfzzf.com/post/1414224256736174082">https://zzfzzf.com/post/1414224256736174082</a></p>
<h4>绝对定位布局</h4>
<h4>Table 布局</h4>
<h4>BFC 三栏布局</h4>
<h4>流体布局</h4>
<h2>居中</h2>
<ol>
<li><p>Flex布局：Flex布局是CSS3的一种新布局方式，可以实现元素的水平和垂直居中，且适用于不同的元素和屏幕尺寸。可以通过设置容器的display属性为flex，然后设置justify-content和align-items属性为center，即可实现水平和垂直居中。</p>
</li>
<li><p>绝对定位布局：可以将元素设置为绝对定位，并设置top、bottom、left、right属性为0，再设置margin属性为auto，即可实现水平和垂直居中。需要注意的是，这种布局方式需要父元素具有相对定位。</p>
</li>
<li><p>表格布局：可以将元素设置为display: table和display: table-cell，然后设置vertical-align属性为middle，即可实现水平和垂直居中。需要注意的是，这种布局方式需要考虑表格元素的特殊性。</p>
</li>
<li><p>Grid布局：可以将元素设置为display: grid，然后设置justify-content和align-items属性为center，即可实现水平和垂直居中。需要注意的是，这种布局方式需要考虑浏览器的兼容性。</p>
</li>
<li><p>transform属性布局：可以使用transform属性，将元素向左和向上移动一半的宽度和高度，然后设置left和top属性为50%，即可实现水平和垂直居中。需要注意的是，这种布局方式需要考虑元素的尺寸。</p>
<blockquote>
<p>初始代码如下</p>
</blockquote>
</li>
</ol>
<pre><code class="language-CSS">.container {

  position: relative;

}



.content {

  position: absolute;

  top: 0;

  left: 0;

  right: 0;

  bottom: 0;

  margin: auto;

}
&lt;div class=&quot;container&quot;&gt;

    &lt;div class=&quot;content&quot;&gt;&lt;/div&gt;

&lt;/div&gt;
</code></pre>
<h4><code>margin:auto</code></h4>
<pre><code class="language-CSS">.container {

  position: relative;

}



.content {

  position: absolute;

  top: 0;

  left: 0;

  right: 0;

  bottom: 0;

  margin: auto;

}
</code></pre>
<h4><code>translateY</code></h4>
<pre><code class="language-CSS">.container {

  position: relative; /* 设置父元素position为relative */

}

.content {

  position: absolute;

  top: 50%;

  transform: translate(-50%, -50%);

  left: 50% 

}
</code></pre>
<h4><code>display: flex</code>+<code>margin: auto</code></h4>
<pre><code class="language-CSS">.container {

  display: flex; /* 设置父元素设置为flex容器 */

}

.content {

  margin: auto;

}
</code></pre>
<h4><code>justify-content</code>+<code>align-items</code></h4>
<pre><code class="language-CSS">.container {

  display: flex;

  justify-content: center;

  align-items: center;

}
</code></pre>
<h4><code>align-self</code></h4>
<pre><code class="language-CSS">.container {

  display: flex;

}



.content {

  align-self: center;

  margin: 0 auto;

}
</code></pre>
<h4><code>margin: auto</code></h4>
<pre><code class="language-CSS">.container {

  display: grid;

}

.content {

  margin: auto;

}
</code></pre>
<h4><code>align-items</code>+<code>justify-content</code></h4>
<pre><code class="language-CSS">.container {

  display: grid;

  align-items: center;

  justify-content: center;

}
</code></pre>
<h4><code>align-self</code></h4>
<pre><code class="language-CSS">.container {

  display: grid;

}

.content {

  justify-self: center; /*  设置子元素垂直居中 */

  align-self: center; /* 设置子元素水平居中*/

}
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[taro跨端编译原理]]></title>
            <link>https://zzfzzf.com/post/6972548384859230249</link>
            <guid>6972548384859230249</guid>
            <pubDate>Mon, 03 Jan 2022 04:09:40 GMT</pubDate>
            <content:encoded><![CDATA[<h2>taro3之前</h2>
<p>编译时是使用 babel-parser 将 Taro 代码解析成抽象语法树，然后通过 babel-types 对抽象语法树进行一系列修改、转换操作，最后再通过 babel-generate 生成对应的目标代码
几个缺点</p>
<ul>
<li>JSX ⽀持程度不完美。Taro 对 JSX 的⽀持是通过编译时的适配去实现的，但 JSX ⼜⾮常之灵活，因此还不能做到 100% ⽀持所有的 JSX 语法。JSX 是一个 JavaScript 的语法扩展，它的写法千变万化，十分灵活。之前Taro团队是采用穷举的方式对 JSX 可能的写法进行了一一适配，这一部分工作量很大。</li>
<li>不⽀持 source-map。Taro 对源代码进⾏了⼀系列的转换操作之后，就不⽀持 source-map 了，⽤户 调试、使⽤这个项⽬就会不⽅便。</li>
<li>维护和迭代⼗分困难。Taro 编译时代码⾮常的复杂且离散，维护迭代都⾮常的困难。</li>
</ul>
<h2>taro3</h2>
<p>react-dom是react在浏览器的渲染器，taro通过taro-react重写react-dom来连接react-reconciler
react-reconciler-&gt;taro-react-&gt;taro-runtime</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[AST入门]]></title>
            <link>https://zzfzzf.com/post/6972548384859230248</link>
            <guid>6972548384859230248</guid>
            <pubDate>Sat, 18 Dec 2021 16:44:21 GMT</pubDate>
            <content:encoded><![CDATA[<h2>什么是AST</h2>
<blockquote>
<p>抽象语法树：AST（Abstract Syntax Tree)，是源代码的抽象语法结构的树状表现形式，这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。之所以说语法是「抽象」的，是因为这里的语法并不会表示出真实语法中出现的每个细节。</p>
</blockquote>
<blockquote>
<p>token-&gt;AST-&gt;字节码</p>
</blockquote>
<p>以下是一个简单的JavaScript代码片段的AST示例：</p>
<pre><code class="language-js">function add(a, b) {
  return a + b;
}

console.log(add(1, 2));
</code></pre>
<p>对应的AST如下所示：</p>
<pre><code>Program
├── FunctionDeclaration (name: add)
│   ├── Identifier (name: a)
│   ├── Identifier (name: b)
│   └── BinaryExpression
│       ├── Identifier (name: a)
│       └── Identifier (name: b)
└── ExpressionStatement
    └── CallExpression
        ├── Identifier (name: console.log)
        └── Arguments
            ├── CallExpression
            │   ├── Identifier (name: add)
            │   ├── Literal (value: 1)
            │   └── Literal (value: 2)
            └── Empty
</code></pre>
<p>可以看到，AST中的每个节点都表示代码中的一个语法结构，例如Program表示整个程序，FunctionDeclaration表示函数声明，BinaryExpression表示二元表达式等等。节点之间的关系则表示了代码中的控制流、数据流等等。</p>
<h2>生成AST</h2>
<h3>词法分析</h3>
<h3>语法分析</h3>
<h2>AST的应用</h2>
<ul>
<li><p>编辑器的错误提示、代码格式化、代码高亮、代码压缩、代码自动补全；</p>
</li>
<li><p><code>elint</code>、<code>pretiier</code> 对代码错误或风格的检查；</p>
</li>
<li><p><code>webpack</code> 通过 <code>babel</code> 转译 <code>javascript</code> 语法；</p>
</li>
<li><p><a href="https://zzfzzf.com/post/1422769918766682113">babel应用</a></p>
</li>
</ul>
<h2>总结</h2>
<p>AST是一种用于代码分析和转换的数据结构，它可以将源代码转换成一棵树状结构，使得代码的分析和修改更加方便。AST的生成通常分为词法分析和语法分析两个步骤，其中语法分析器通过递归下降算法来进行AST的生成。AST的应用非常广泛，包括代码检查、代码重构、代码转换等等。通过对AST的遍历和分析，我们可以实现很多有用的功能。</p>
<h2>相关链接</h2>
<ul>
<li><a href="https://gogocode.io/zh/">GoGoCode</a></li>
<li><a href="https://astexplorer.net/">AST explorer</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[八股文之HTTP]]></title>
            <link>https://zzfzzf.com/post/6972548384859230246</link>
            <guid>6972548384859230246</guid>
            <pubDate>Fri, 03 Dec 2021 01:53:42 GMT</pubDate>
            <content:encoded><![CDATA[<h2>OSI 7层模型和TCP/IP 4层模型</h2>
<table>
<thead>
<tr>
<th>OSI 7层模型</th>
<th>TCP/IP 4层模型</th>
<th>对应网络协议</th>
</tr>
</thead>
<tbody><tr>
<td>应用层</td>
<td>应用层</td>
<td>http, ftp,smtp</td>
</tr>
<tr>
<td>表示层</td>
<td>应用层</td>
<td></td>
</tr>
<tr>
<td>会话层</td>
<td>应用层</td>
<td>dns</td>
</tr>
<tr>
<td>传输层</td>
<td>传输层</td>
<td>tcp,udp,端口</td>
</tr>
<tr>
<td>网络层</td>
<td>网络层</td>
<td>ip</td>
</tr>
<tr>
<td>数据链路层</td>
<td>数据链路层</td>
<td></td>
</tr>
<tr>
<td>物理层</td>
<td>数据链路层</td>
<td></td>
</tr>
</tbody></table>
<h2>http状态码</h2>
<ul>
<li>1XX- 信息型，服务器收到请求，需要请求者继续操作。</li>
<li>2XX- 成功型，请求成功收到，理解并处理。</li>
<li>3XX - 重定向，需要进一步的操作以完成请求。</li>
<li>4XX - 客户端错误，请求包含语法错误或无法完成请求。</li>
<li>5XX - 服务器错误，服务器在处理请求的过程中发生了错误。</li>
</ul>
<blockquote>
<p>常用状态码</p>
</blockquote>
<ol>
<li>200 成功</li>
<li>301 永久重定向</li>
<li>302 临时重定向</li>
<li>304 协商缓存</li>
<li>400 错误请求</li>
<li>401 缺少身份验证</li>
<li>403 禁止访问</li>
<li>404 资源丢失</li>
<li>500 服务器故障</li>
</ol>
<h2>https</h2>
<blockquote>
<p>解决了以下问题</p>
</blockquote>
<ul>
<li><p>数据加密 传输内容进行混淆解决明文传输</p>
</li>
<li><p>身份验证 通信双方验证对方的身份真实性</p>
</li>
<li><p>数据完整性保护 检测传输的内容是否被篡改或伪造</p>
</li>
</ul>
<p>https在原来的TCP传输层上多了安全传输层协议(TLS)或者安全套接层(SSL)，因此https也称HTTP over TLS 或 HTTP over SSL</p>
<table>
<thead>
<tr>
<th>http</th>
<th>https</th>
</tr>
</thead>
<tbody><tr>
<td>TCP</td>
<td>SSL/TLS</td>
</tr>
<tr>
<td>IP</td>
<td>TCP</td>
</tr>
<tr>
<td>mac</td>
<td>IP</td>
</tr>
<tr>
<td></td>
<td>mac</td>
</tr>
</tbody></table>
<blockquote>
<p>SSL（Secure Sockets Layer） 发展到v3阶段时，互联网工程组把它改名为 TLS（传输层安全，Transport Layer Security），正式标准化，SSL 是 TLS 前身。</p>
</blockquote>
<ul>
<li>tls1.2</li>
<li>tls1.3</li>
</ul>
<h3>加密方式</h3>
<p>HTTPS采用对称加密和非对称密钥加密混合的加密方式，使用非对称加密加密对称加密的密钥，随后使用对称加密来传输数据</p>
<h3>认证方式</h3>
<p>数字证书</p>
<p>HTTP中没有加密机制，可以通过SSL（Secure Socket Layer 安全套接层）或TLS（Transport Layer Security 安全层传输协议）的组合使用，加密HTTP的通信内容。</p>
<p>http+ssl/tls</p>
<p>tls1.2</p>
<p>tls1.3</p>
<p>Tcp</p>
<p>Udp</p>
<h3>如何优化https</h3>
<h3>对称加密</h3>
<ul>
<li>加密原文和解密成原文都使用一个秘钥</li>
<li>常用的只有 AES 和 ChaCha20</li>
<li>缺点是密钥如何安全传输</li>
</ul>
<h3>非对称加密</h3>
<ul>
<li>有2个密钥，一个公钥，一个私钥</li>
<li>公钥公开，私钥保存在服务器，无需传输</li>
<li>常用加密算法有<strong>RSA</strong></li>
</ul>
<h3>详细流程</h3>
<ul>
<li>客户端使用 https url 访问服务器，则要求 web 服务器<code>建立 ssl 链接</code>。</li>
<li>web 服务器接收到客户端的请求之后，会<code>将网站的证书（证书中包含了公钥），传输给客户端</code>。</li>
<li>客户端生成一个随机数（会话密钥），用上一步证书中的公钥进行加密，并将加密后的信息发送给服务端</li>
<li>服务端获取后，通过私钥进行解密，获取到随机数（会话密钥）</li>
<li>客户端和服务端通过随机数（会话密钥）进行对称加解密</li>
</ul>
<h2>缓存</h2>
<p><a href="https://zzfzzf.com/post/1414224248678916097">强缓存和协商缓存</a></p>
<h2>http请求</h2>
<ol>
<li>请求头</li>
<li>请求行</li>
<li>空行</li>
<li>请求体</li>
</ol>
<table>
<thead>
<tr>
<th>请求头</th>
<th>作用</th>
</tr>
</thead>
<tbody><tr>
<td>Accept</td>
<td>接收类型，表示浏览器支持的MIME类型（对标服务端返回的Content-Type）</td>
</tr>
<tr>
<td>Content-Type</td>
<td>客户端发送出去实体内容的类型</td>
</tr>
<tr>
<td>Cache-Control</td>
<td>指定请求和响应遵循的缓存机制，如no-cache</td>
</tr>
<tr>
<td>If-Modified-Since</td>
<td>对应服务端的Last-Modified,用来匹配看文件是否变动，只能精确到1s之内</td>
</tr>
<tr>
<td>Expires</td>
<td>缓存控制，在这个时间内不会请求，直接使用缓存，服务端时间</td>
</tr>
<tr>
<td>Max-age</td>
<td>代表资源在本地缓存多少秒，有效时间内不会请求，而是使用缓存</td>
</tr>
<tr>
<td>If-None-Match</td>
<td>对应服务端的ETag,用来匹配文件内容是否改变（非常精确）</td>
</tr>
<tr>
<td>Cookie</td>
<td>有cookie并且同域访问时会自动带上</td>
</tr>
<tr>
<td>Referer</td>
<td>该页面的来源URL（适用于所有类型的请求，会精确到详细页面地址，csrf拦截常用到这个字段）</td>
</tr>
<tr>
<td>Origin</td>
<td>最初的请求是从哪里发起的（只会精确到端口)）,Origin比Referer更尊重隐私</td>
</tr>
<tr>
<td>User-Agent</td>
<td>用户客户端的一些必要信息，如UA头部等</td>
</tr>
</tbody></table>
<h2>http响应</h2>
<ol>
<li>状态行</li>
<li>响应头</li>
<li>空行</li>
<li>响应体</li>
</ol>
<table>
<thead>
<tr>
<th>响应头</th>
<th>作用</th>
</tr>
</thead>
<tbody><tr>
<td>Cache-Control</td>
<td>指定请求和响应遵循的缓存机制，如no-cache</td>
</tr>
<tr>
<td>Content-Type</td>
<td>服务端返回的实体内容的类型</td>
</tr>
<tr>
<td>Last-Modified</td>
<td>请求资源的最后修改时间</td>
</tr>
<tr>
<td>ETag</td>
<td>资源的特定版本的标识符，类似于指纹</td>
</tr>
<tr>
<td>Expires</td>
<td>应该在什么时候认为文档已经过期，从而不再缓存它</td>
</tr>
<tr>
<td>Server</td>
<td>服务器的一些相关信息</td>
</tr>
<tr>
<td>Set-Cookie</td>
<td>设置和页面关联的cookie,服务器通过这个头部把cookie传给客户端</td>
</tr>
<tr>
<td>Access-Control-Allow-Origin</td>
<td>服务器端允许的请求Origin头部(譬如为*)</td>
</tr>
</tbody></table>
<h2>http1.1</h2>
<ul>
<li>持久化连接</li>
<li>浏览器为每个域名最多同时维护6个TCP持久连接; </li>
<li>使用CDN的实现域名分片机制。<blockquote>
<p>Keep-Alive</p>
</blockquote>
</li>
</ul>
<p>http1.0默认关闭keep-alive，需要请求头和响应头添加Connection: Keep-Alive</p>
<p>http1.1默认开启keep-alive,如果需要关闭，请求头需添加Connection:close</p>
<blockquote>
<p>缺点</p>
</blockquote>
<ol>
<li>安全性不足</li>
<li>性能不高</li>
</ol>
<h2>Http2.0</h2>
<ol>
<li>二进制分帧传输</li>
<li>头部压缩</li>
<li>多路复用，一个域名共用一个链接</li>
<li>性能瓶颈在tcp连接上</li>
<li>开启http2.0需要启用https协议</li>
</ol>
<h2>Http3.0</h2>
<blockquote>
<p>http2使用多路复用实现了多个请求在一个tcp中传输，任意一个出现丢包，即会阻塞所有的请求，丢包率增加，http2效率反而更差</p>
</blockquote>
<p>基于udp实现的http3.0协议，</p>
<h2>tcp和udp区别</h2>
<ol>
<li>TCP 是面向连接的，UDP 是无连接的即发送数据前不需要先建立链接。</li>
<li>对系统资源的要求（TCP较多，UDP少）</li>
<li>UDP程序结构较简单，TCP 的首部较大为 20 字节，而 UDP 只有 8 字节。</li>
<li>TCP 是面向字节流，UDP 面向报文，并且网络出现拥塞不会使得发送速率降低(因此会出现丢包，对实时的应用比如 IP 电话和视频会议等)。</li>
<li>TCP保证数据正确性，UDP可能丢包，TCP 提供可靠的服务。也就是说，通过 TCP 连接传送的数据，无差错， 不丢失，不重复，且按序到达;UDP 尽最大努力交付，即不保证可靠交付。并且因为 tcp 可靠，面向连接，不会丢失数据因此适合大数据量的交换。</li>
<li>TCP保证数据顺序，UDP不保证</li>
<li>TCP 只能是 1 对 1 的，UDP 提供单播，多播，广播，支持 1 对 1,1 对多。</li>
</ol>
<table>
<thead>
<tr>
<th></th>
<th>UDP</th>
<th>TCP</th>
</tr>
</thead>
<tbody><tr>
<td>是否连接</td>
<td>无连接</td>
<td>面向连接</td>
</tr>
<tr>
<td>是否可靠</td>
<td>不可靠传输，不使用流量控制和拥塞控制</td>
<td>可靠传输，使用流量控制和拥塞控制</td>
</tr>
<tr>
<td>连接对象个数</td>
<td>支持一对一，一对多，多对一和多对多交互通信</td>
<td>只能是一对一通信</td>
</tr>
<tr>
<td>传输方式</td>
<td>面向报文</td>
<td>面向字节流</td>
</tr>
<tr>
<td>首部开销</td>
<td>首部开销小，仅8字节</td>
<td>首部最小20字节，最大60字节</td>
</tr>
<tr>
<td>适用场景</td>
<td>适用于实时应用（IP电话、视频会议、直播等）</td>
<td>适用于要求可靠传输的应用，例如文件传输</td>
</tr>
</tbody></table>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[Typescript在React中的最佳实践]]></title>
            <link>https://zzfzzf.com/post/6972548384859230245</link>
            <guid>6972548384859230245</guid>
            <pubDate>Tue, 30 Nov 2021 07:34:34 GMT</pubDate>
            <content:encoded><![CDATA[<h2>背景</h2>
<p>UI=fn(state)</p>
<p>我们的组件函数接收一个状态stage作为参数，函数的调用结果就是当前我们视图的UI，所以react最重要的就是fn和state这两部分、所以ts在react中最佳实践差不多等于在函数最佳实践，区别就是多了个jsx语法，TS开发确实比js开发多花时间去编写类型，但是后续维护，重构和代码提示方面确实收益大于话费的时间的。</p>
<h2>目录</h2>
<ol>
<li>typescript环境配置</li>
<li>ts与react的基础知识</li>
<li>函数式组件的定义</li>
<li>Hooks的定义</li>
<li>HTML元素的定义</li>
</ol>
<h2>环境配置</h2>
<p>目前的javascript项目基本都是webpack构建的，对于一个<strong>javascript</strong>项目我们迁移到 <strong>typescript</strong> 我们只需要以下几个重要步骤。</p>
<ol>
<li>安装依赖以及类型定义文件</li>
</ol>
<pre><code class="language-bash">npm install typescript -D
</code></pre>
<p>在开发中我们难免用到第三方的npm模块，这些模块不一定是ts开发的，也不一定提供类型定义文件</p>
<p>我们可以安装对应的类型定义文件</p>
<p>@types/包<a href="https://www.typescriptlang.org/dt/search?search=">https://www.typescriptlang.org/dt/search?search=</a></p>
<pre><code class="language-shell">npm install @types/react -D

npm install @types/react-dom -D
</code></pre>
<ol>
<li>配置typescript</li>
</ol>
<p>tsc --init  生成tsconfig.json</p>
<p><a href="https://www.typescriptlang.org/tsconfig">https://www.typescriptlang.org/tsconfig</a></p>
<p>仅仅给idea提示用的</p>
<p>声明文件</p>
<p>装新包用ts写的一般都会提供声明文件，否则我们需要自己再</p>
<p>Package.json typings</p>
<p>有的项目没有自带声明文件，比如</p>
<p>react本身</p>
<p><a href="https://www.typescriptlang.org/dt/search?search=">https://www.typescriptlang.org/dt/search?search=</a></p>
<p>用@types/</p>
<p>@types/react</p>
<p>@types/react-dom</p>
<pre><code class="language-json">{

  // ...

  &quot;compilerOptions&quot;: {

    &quot;incremental&quot;: true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件，第二次编译会在第一次的基础上进行增量编译，可以提高编译的速度

    &quot;tsBuildInfoFile&quot;: &quot;./buildFile&quot;, // 增量编译文件的存储位置

    &quot;diagnostics&quot;: true, // 打印诊断信息 

    &quot;target&quot;: &quot;ES5&quot;, // 目标语言的版本

    &quot;module&quot;: &quot;CommonJS&quot;, // 生成代码的模板标准

    &quot;outFile&quot;: &quot;./app.js&quot;, // 将多个相互依赖的文件生成一个文件，可以用在AMD模块中，即开启时应设置&quot;module&quot;: &quot;AMD&quot;,

    &quot;lib&quot;: [&quot;DOM&quot;, &quot;ES2015&quot;, &quot;ScriptHost&quot;, &quot;ES2019.Array&quot;], // TS需要引用的库，即声明文件，es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性，通常都需要配置，如es8的数组新特性需要引入&quot;ES2019.Array&quot;,

    &quot;allowJS&quot;: true, // 允许编译器编译JS，JSX文件

    &quot;checkJs&quot;: true, // 允许在JS文件中报错，通常与allowJS一起使用

    &quot;outDir&quot;: &quot;./dist&quot;, // 指定输出目录

    &quot;rootDir&quot;: &quot;./&quot;, // 指定输出文件目录(用于输出)，用于控制输出目录结构

    &quot;declaration&quot;: true, // 生成声明文件，开启后会自动生成声明文件

    &quot;declarationDir&quot;: &quot;./file&quot;, // 指定生成声明文件存放目录

    &quot;emitDeclarationOnly&quot;: true, // 只生成声明文件，而不会生成js文件

    &quot;sourceMap&quot;: true, // 生成目标文件的sourceMap文件

    &quot;inlineSourceMap&quot;: true, // 生成目标文件的inline SourceMap，inline SourceMap会包含在生成的js文件中

    &quot;declarationMap&quot;: true, // 为声明文件生成sourceMap

    &quot;typeRoots&quot;: [], // 声明文件目录，默认时node_modules/@types

    &quot;types&quot;: [], // 加载的声明文件包

    &quot;removeComments&quot;:true, // 删除注释 

    &quot;noEmit&quot;: true, // 不输出文件,即编译后不会生成任何js文件

    &quot;noEmitOnError&quot;: true, // 发送错误时不输出任何文件

    &quot;noEmitHelpers&quot;: true, // 不生成helper函数，减小体积，需要额外安装，常配合importHelpers一起使用

    &quot;importHelpers&quot;: true, // 通过tslib引入helper函数，文件必须是模块

    &quot;downlevelIteration&quot;: true, // 降级遍历器实现，如果目标源是es3/5，那么遍历器会有降级的实现

    &quot;strict&quot;: true, // 开启所有严格的类型检查

    &quot;alwaysStrict&quot;: true, // 在代码中注入&#39;use strict&#39;

    &quot;noImplicitAny&quot;: true, // 不允许隐式的any类型

    &quot;strictNullChecks&quot;: true, // 不允许把null、undefined赋值给其他类型的变量

    &quot;strictFunctionTypes&quot;: true, // 不允许函数参数双向协变

    &quot;strictPropertyInitialization&quot;: true, // 类的实例属性必须初始化

    &quot;strictBindCallApply&quot;: true, // 严格的bind/call/apply检查

    &quot;noImplicitThis&quot;: true, // 不允许this有隐式的any类型

    &quot;noUnusedLocals&quot;: true, // 检查只声明、未使用的局部变量(只提示不报错)

    &quot;noUnusedParameters&quot;: true, // 检查未使用的函数参数(只提示不报错)

    &quot;noFallthroughCasesInSwitch&quot;: true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)

    &quot;noImplicitReturns&quot;: true, //每个分支都会有返回值

    &quot;esModuleInterop&quot;: true, // 允许export=导出，由import from 导入

    &quot;allowUmdGlobalAccess&quot;: true, // 允许在模块中全局变量的方式访问umd模块

    &quot;moduleResolution&quot;: &quot;node&quot;, // 模块解析策略，ts默认用node的解析策略，即相对的方式导入

    &quot;baseUrl&quot;: &quot;./&quot;, // 解析非相对模块的基地址，默认是当前目录

    &quot;paths&quot;: { // 路径映射，相对于baseUrl

      // 如使用jq时不想使用默认版本，而需要手动指定版本，可进行如下配置

      &quot;jquery&quot;: [&quot;node_modules/jquery/dist/jquery.min.js&quot;]

    },

    &quot;rootDirs&quot;: [&quot;src&quot;,&quot;out&quot;], // 将多个目录放在一个虚拟目录下，用于运行时，即编译后引入文件的位置可能发生变化，这也设置可以虚拟src和out在同一个目录下，不用再去改变路径也不会报错

    &quot;listEmittedFiles&quot;: true, // 打印输出文件

    &quot;listFiles&quot;: true// 打印编译的文件(包括引用的声明文件)

  }

}
import * as React from &#39;react&#39;

import * as ReactDOM from &#39;react-dom&#39;

//allowSyntheticDefaultImports
//tsconfig.json

{

  &quot;include&quot;: [

    &quot;src/**/*&quot;

  ],

  &quot;compilerOptions&quot;: {

    &quot;target&quot;: &quot;es5&quot;,

    &quot;module&quot;: &quot;es6&quot;,

    &quot;noEmit&quot;: true,//不生成类型

    &quot;noImplicitAny&quot;: true, //不能有隐式any

    &quot;allowTernary&quot;: true,

    &quot;allowShortCircuit&quot;: true,

    &quot;allowSyntheticDefaultImports&quot;:true,//允许默认导入

    &quot;esModuleInterop&quot;: true,//esmodule支持

    &quot;strict&quot;: true,

    &quot;lib&quot;: [

      &quot;dom&quot;,

      &quot;es2015&quot;//dom、html类型定义 es定义

    ],

    &quot;jsx&quot;: &quot;react-jsx&quot;//最重要

  }

}
</code></pre>
<p>此时我们已经可以用tsc来编译我们的ts代码了，tsconfig知识给vscode</p>
<ol>
<li>修改构建配置</li>
</ol>
<blockquote>
<p>这一步我们主要修改webpack和babel的相关配置</p>
</blockquote>
<pre><code class="language-js">//webpack.config.js

const path = require(&#39;path&#39;);



module.exports = {

    resolve: {

        extensions: [&#39;.tsx&#39;, &#39;.ts&#39;, &#39;.js&#39;]

    },

    module: {

        rules: [{

            test: /\.(ts|js)x?$/,

            exclude: /node_modules/,

            loader: &#39;babel-loader&#39;,

        }],

    }

};
</code></pre>
<p>对于typescript的编译我们主流方案有</p>
<table>
<thead>
<tr>
<th>编译方案</th>
<th>缺点</th>
<th>优点</th>
</tr>
</thead>
<tbody><tr>
<td>awesome-typescript-loader</td>
<td>最近一次维护3年前</td>
<td></td>
</tr>
<tr>
<td>ts-loader</td>
<td>es6语法无法转化、每次修改文件会重新去编译ts文件</td>
<td>类型校验加编译</td>
</tr>
<tr>
<td>babel/babel-loader</td>
<td>没有类型检查功能,因此语法正确但无法通过 TypeScript 类型检查的代码可能会成功转换 部分语法无法编译常量枚举（7.15.0已支持） tsc --noEmit --watch 现有最佳方案 @babel/preset-typescript  使用 babel，不仅能处理 typescript，之前 babel 就已经存在的 polyfill 功能也能一并享受。并且由于 babel 只是移除类型注解节点，所以速度相当快。</td>
<td>不经过tsc直接转化成js，自带缓存，速度快</td>
</tr>
<tr>
<td>swc-loader</td>
<td>rust写的不会校验类型</td>
<td>速度快</td>
</tr>
<tr>
<td>esbuild/esbuild-loader</td>
<td>go写的 不会校验类型</td>
<td>速度快</td>
</tr>
</tbody></table>
<p>没有类型检查功能,因此语法正确但无法通过 TypeScript 类型检查的代码可能会成功转换 </p>
<p>tsc --noEmit --watch</p>
<p><img src="https://cdn.zzfzzf.com/1640939190561rdtjT8.(null)" alt="img"></p>
<blockquote>
<p>目前最佳方案是使用babel-loader转化语法，使用tsc去检查类型</p>
</blockquote>
<ol>
<li>其他准备<ol>
<li>使用到jsx语法的文件后缀为.tsx,普通的js语法后缀为.ts</li>
<li>全局变量或者扩展window对象属性在<strong>typings/global.d.ts</strong>文件里</li>
</ol>
</li>
</ol>
<pre><code class="language-ts">//png资源

declare module &quot;*.png&quot;;

//扩展window对象

declare global {

  interface Window {

    MyVendorThing: MyVendorType;

  }

}
</code></pre>
<h2>基础知识</h2>
<ol>
<li>interface和type该用哪个</li>
</ol>
<blockquote>
<p><strong>inerface</strong>和<strong>type</strong>都可以定义类型，所以我们该用哪个，</p>
</blockquote>
<blockquote>
<p>表现上来说2者可以实现的功能都可以互相实现，唯一的声明合并方面，type不行即<strong>type</strong> 类型不能二次编辑，而 <strong>interface</strong> 可以随时扩展</p>
</blockquote>
<ul>
<li><p>在定义公共 API 时(比如编辑一个库）使用 <strong>interface</strong>，这样可以方便使用者继承接口</p>
</li>
<li><p>在定义组件属性（Props）和状态（State）时，建议使用 <strong>type</strong>，因为 <strong>type</strong>的约束性更强</p>
</li>
</ul>
<pre><code class="language-ts">interface Person {

  age: number;

}



interface Person {

  name: string;

}





type Person {

  age: number;

}



type Person {

  name: string;

}

//TS2300: Duplicate identifier &#39;Person&#39;.
</code></pre>
<ol start="2">
<li><code>{}</code>和<code>Record&lt;string,any&gt;</code></li>
</ol>
<p>表示没有成员的对象</p>
<p>我们表示一个对象可以<code>Record&lt;string,any&gt;</code></p>
<ol start="3">
<li>unknown和any</li>
</ol>
<p>anyscript</p>
<p>使用any代表我们放弃了所有的类型检查，推荐使用<strong>Unknown</strong></p>
<p>不缩小类型，我们无法对unknown 类型执行任何操作</p>
<p><strong>unknown 和 any 的主要区别是 unknown 类型会更加严格：在对 unknown 类型的值执行大多数操作之前，我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前，我们不必进行任何检查。</strong></p>
<p>使用as</p>
<p>使用typeof</p>
<p>断言错了时语法能通过检测，但是运行的时候就会报错了！</p>
<ol start="4">
<li>JSX.Element、ReactNode、ReactElement</li>
</ol>
<p>JSX.Element 是React.createElement 或是转译 JSX 获得的对象的类型等价于ReactElement</p>
<p>JSX.Element React.createElement创建的jsx对象</p>
<p>React.ReactNode 所有可能返回值的集合,</p>
<p>他的声明如下，是一个联合类型</p>
<pre><code class="language-ts">type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
</code></pre>
<p>我们可以把jsx赋值给他，在react世界里相当于any</p>
<ol start="5">
<li>获取未导出的类型</li>
</ol>
<p>这种方式的一个好处是获取到的是真实的类型声明， 导出的类型声明有时候是不完整的</p>
<pre><code class="language-ts">import { Button } from &#39;antd&#39;;



type ButtonProps = React.ComponentProps&lt;typeof Button&gt;;
</code></pre>
<ol start="6">
<li>3个小知识<ol>
<li><code>??</code>代替 <code>||</code></li>
<li>魔法值</li>
</ol>
</li>
</ol>
<pre><code class="language-ts">const enum Gender {

  female = &#39;1&#39;,

  male = &#39;2&#39;,

}



function getSex(sex: string) {

  if (sex === Gender.female) {

    console.log(sex);

  }

}
</code></pre>
<h2>函数式组件的相关声明</h2>
<p>现在重尚函数式编程，react也逐渐在向函数式编程思想迈进，当然这个函数式不是函数组件的意思，只是函数组件的编程思想运用到了函数式</p>
<p>一个标准的函数式组件是这样定义的</p>
<pre><code class="language-ts">type PersonProps = {

  name?: string;

  age: number;

  onClick: () =&gt; void;

};



function Person(props: PersonProps): JSX.Element {

  const { name = &#39;&#39;, age } = props;

  return (

    &lt;div onClick={() =&gt; console.log(&#39;hello&#39;)}&gt;

      {name}-{age}

    &lt;/div&gt;

  );

}
</code></pre>
<p>props接口推荐以 <strong>ComponentName</strong>+<strong>props</strong>命名 <strong>ComponentNameProps</strong></p>
<p>返回类型其实可以不写，ts可以自动推断我们的返回类型</p>
<pre><code class="language-tsx">function App() {

  const isShow = false;

  return isShow ? &lt;input type=&#39;text&#39; /&gt; : null;

}
</code></pre>
<p><img src="https://cdn.zzfzzf.com/1640939190561S8EWjT.(null)" alt="img"></p>
<p>很多人喜欢用React.FC定义类型,这是不推荐的做法,</p>
<p><a href="https://github.com/facebook/create-react-app/pull/8177">https://github.com/facebook/create-react-app/pull/8177</a></p>
<ul>
<li><p>React.FC 显式地定义了返回类型，其他方式是隐式推导的；如果返回类型是数组类型检查就会不通过提供子项的隐式定义，即使您的组件不需要有子项。</p>
</li>
<li><p>React.FC 对静态属性：displayName、propTypes、defaultProps 提供了类型检查和自动补全；</p>
</li>
<li><p>React.FC 为 children 提供了隐式的类型（ReactNode）</p>
</li>
</ul>
<p>比如以下就会报错</p>
<pre><code class="language-ts">const App: React.FC = props =&gt; props.children



const App: React.FC = () =&gt; [1, 2, 3]



const App: React.FC = () =&gt; &#39;hello&#39;
</code></pre>
<p>children推荐用React.ReactNode定义</p>
<pre><code class="language-ts">interface ButtonProps {

  children: React.ReactNode;

}



function Button({ children }: ButtonProps) {

  return &lt;button&gt;{children}&lt;/button&gt;;

}
</code></pre>
<p>react也为我们封装了一个类型工具</p>
<pre><code class="language-js">type ButtonProps = {

  onClick: () =&gt; void;

};



function Button(props: PropsWithChildren&lt;ButtonProps&gt;) {

  const { children } = props;

  return &lt;button onClick={() =&gt; console.log(&#39;onClick&#39;)}&gt;{children}&lt;/button&gt;;

}
</code></pre>
<blockquote>
<p>归根到底</p>
</blockquote>
<pre><code class="language-ts">type AppProps = { message: string }; /* 也可用 interface */

const App = ({ message }: AppProps) =&gt; &lt;div&gt;{message}&lt;/div&gt;; // 无大括号的箭头函数，利用 TS 推断。
</code></pre>
<h2></h2>
<h2>Hooks的相关声明</h2>
<h3>useState</h3>
<p>如果初始值已经可以说明类型，那么不用手动声明类型，TS 会自动推断类型</p>
<p>如果没有初始值需要给null初始值，需要给定类型</p>
<p>但是在使用时大家通常会加user？.id</p>
<p>通常没有初始值，需要初始化空对象，我们可以类型断言来骗ts编译器</p>
<pre><code class="language-ts">//简单类型的初始值

const [val, setVal] = useState(false);

//复杂类型的初始值

const [user, setUser] = useState({ name: &#39;张三&#39;, age: 18, password: &#39;123456&#39; });

const handleInitial =(val:typeof user) =&gt; {

    setUser(val);

};

//一般情况下我们没有初始值，需要调用接口去请求数据

const [user, setUser] = useState&lt;UserType | null&gt;(null);//user?.id

const [user, setUser] = useState&lt;UserType&gt;({} as UserType);//better
</code></pre>
<h3>useRef</h3>
<p>通常两种用法</p>
<ol>
<li>只读的HTML引用</li>
</ol>
<pre><code class="language-js">function App() {

  const divRef = useRef&lt;HTMLDivElement&gt;(null);//必须给null初始值，类型不用给null包括在里面



  useLayoutEffect(() =&gt; {

    if (!divRef.current) throw Error(&#39;divRef is not assigned&#39;);

    divRef.current.innerHTML = &#39;hello world&#39;;

  });



  return &lt;div ref={divRef}&gt;hello&lt;/div&gt;;

}
</code></pre>
<p>如果我我们的ref不是有条件的渲染，divRef永远不会为空</p>
<pre><code class="language-js">function App() {

  const divRef = useRef&lt;HTMLDivElement&gt;(null!);



  useLayoutEffect(() =&gt; {

    divRef.current.innerHTML = &#39;hello world&#39;;

  });



  return &lt;div ref={divRef}&gt;hello&lt;/div&gt;;

}
</code></pre>
<p>一般的都是HTML<strong>Button</strong>Element类似的，如果实在不清楚我们可以写**HTMLElement，**编译器会提示我们正确的类型。</p>
<p><img src="https://cdn.zzfzzf.com/1640939192739Ev3mYA.(null)" alt="img"></p>
<ol>
<li>可变的存储变量</li>
</ol>
<p>和useState差不多用法</p>
<pre><code class="language-ts">function App() {

  const timer = useRef(0);



  useLayoutEffect(() =&gt; {

    timer.current = setTimeout(() =&gt; {

      console.log(&#39;timer&#39;);

    }, 1000);

    return () =&gt; clearTimeout(timer.current);

  });



  return &lt;div&gt;hello&lt;/div&gt;;

}
</code></pre>
<h3>useContext</h3>
<blockquote>
<p>推断类型</p>
</blockquote>
<pre><code class="language-ts">const AppContext = createContext({

  authenticated: true,

  lang: &#39;en&#39;,

  theme: &#39;dark&#39;,

});

const MyComponent = () =&gt; {

  const appContext = useContext(AppContext); //inferred as an object

  return &lt;h1&gt;The current app language is {appContext.lang}&lt;/h1&gt;;

};
</code></pre>
<blockquote>
<p>给定类型</p>
</blockquote>
<pre><code class="language-ts">type Theme = &#39;light&#39; | &#39;dark&#39;;

const ThemeContext = createContext&lt;Theme&gt;(&#39;dark&#39;);

const App = () =&gt; {

  const theme = useContext(ThemeContext);

  return &lt;div&gt;The theme is {theme}&lt;/div&gt;;

};
</code></pre>
<p><img src="https://cdn.zzfzzf.com/1640939193227Kp3Dym.(null)" alt="img"></p>
<h3>useEffect、useLayoutEffect、useMemo、useCallback</h3>
<p>useEffect、useLayoutEffect一个参数，返回值必须函数或者<strong>undefined(void)</strong></p>
<pre><code class="language-tsx">const multiply = React.useCallback((value) =&gt; value * 2, []);//给类型
</code></pre>
<h3>自定义hook</h3>
<p>需要注意，自定义 Hook 的返回值如果是<strong>数组类型</strong>，TS 会自动推导为 所有<strong>Union</strong> 类型，而我们实际需要的是数组里里每一项的具体类型，需要手动添加 <strong>const</strong> <strong>断言</strong> 进行处理：告诉ts这是个常量不会修改顺序删除</p>
<pre><code class="language-js">function useToggle() {

  const [state, setState] = useState(false);

  const toggle = () =&gt; setState(!state);

  return [state, toggle];

}
</code></pre>
<p><img src="https://cdn.zzfzzf.com/1640939193787k5GSID.(null)" alt="img"></p>
<pre><code class="language-js">function useToggle() {

  const [state, setState] = useState(false);

  const toggle = () =&gt; setState(!state);

  return [state, toggle] as const;

}
</code></pre>
<p><img src="https://cdn.zzfzzf.com/1640939194398S0QNly.(null)" alt="img"></p>
<h2>HTML</h2>
<h3>HTML属性</h3>
<p>button有很多属性 type 我们要一个个加吗</p>
<p>可以用 </p>
<ol>
<li><em><strong>ButtonHTMLAttributes</strong></em></li>
<li><strong>ComponentPropsWithoutRef</strong></li>
</ol>
<pre><code class="language-tsx">type inputProps = ButtonHTMLAttributes&lt;HTMLButtonElement&gt;;



type inputProps = ComponentPropsWithoutRef&lt;&#39;button&#39;&gt;

function Button(props: inputProps) {

  return &lt;button {...props}&gt;{props.children}&lt;/button&gt;;

}
</code></pre>
<p>如果要扩展属性也很方便使用 <em><strong>interface</strong></em> <em>或者</em>  <em><strong>type</strong></em></p>
<pre><code class="language-ts">interface ButtonProps extends React.ComponentPropsWithoutRef&lt;&#39;button&#39;&gt; {

  customProp: string;

}



type ButtonProps = React.ComponentPropsWithoutRef&lt;&#39;button&#39;&gt; &amp; {

  customProp: string;

};



function Button(props: ButtonProps) {

  console.log(props.customProp);

  return &lt;button {...props}&gt;{props.children}&lt;/button&gt;;

}
</code></pre>
<p>这部分主要是处理点击事件以及input类似的声明，众所周知，使用箭头函数内联在html中每次渲染都会生成新的函数，所以对性能是有影响的，但是内联的函数能非常正确的推断我们的类型</p>
<pre><code class="language-js">function App() {

  return (

    &lt;&gt;

      &lt;button onClick={(e) =&gt; console.log(e)}&gt;hello&lt;/button&gt;

      &lt;input onChange={(e) =&gt; console.log(e)} /&gt;

    &lt;/&gt;

  );

}
</code></pre>
<p><img src="https://cdn.zzfzzf.com/164093919494827dnfw.(null)" alt="img"></p>
<blockquote>
<p>事件类型</p>
</blockquote>
<p>1. </p>
<pre><code class="language-ts">React.XXXEvent&lt;HTMLXXXElement&gt;

ChangeEvent, FormEvent, FocusEvent, KeyboardEvent, MouseEvent, DragEvent, PointerEvent, WheelEvent, TouchEvent
</code></pre>
<ul>
<li><p><strong>剪切板事件对象</strong>：<code>ClipboardEvent&lt;T = Element&gt;</code></p>
</li>
<li><p><strong>拖拽事件对象</strong>：<code>DragEvent&lt;T = Element&gt;</code></p>
</li>
<li><p><strong>焦点事件对象</strong>：<code>FocusEvent&lt;T = Element&gt;</code></p>
</li>
<li><p><strong>表单事件对象</strong>：<code>FormEvent&lt;T = Element&gt;</code></p>
</li>
<li><p><strong>Change事件对象</strong>：<code>ChangeEvent&lt;T = Element&gt;</code></p>
</li>
<li><p><strong>键盘事件对象</strong>：<code>KeyboardEvent&lt;T = Element&gt;</code></p>
</li>
<li><p><strong>鼠标事件对象</strong>：<code>MouseEvent&lt;T = Element, E = NativeMouseEvent&gt;</code></p>
</li>
<li><p><strong>触摸事件对象</strong>：<code>TouchEvent&lt;T = Element&gt;</code></p>
</li>
<li><p><strong>滚轮事件对象</strong>：<code>WheelEvent&lt;T = Element&gt;</code></p>
</li>
<li><p><strong>动画事件对象</strong>：<code>AnimationEvent&lt;T = Element&gt;</code></p>
</li>
<li><p><strong>过渡事件对象</strong>：<code>TransitionEvent&lt;T = Element&gt;</code></p>
</li>
</ul>
<pre><code class="language-js">function App() {

  const handleChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;): void =&gt; {

    console.log(e.target.value);

  };

  return &lt;input onChange={handleChange} /&gt;;

}
</code></pre>
<blockquote>
<p>事件处理函数</p>
</blockquote>
<pre><code class="language-ts">React.ReactEventHandler&lt;HTMLXXXElement&gt;
function App() {

  const handleChange: React.ChangeEventHandler&lt;HTMLInputElement&gt; = (e) =&gt; {

    console.log(e.target.value);

  };

  return &lt;input onChange={handleChange} /&gt;;

}
</code></pre>
<h2>axios封装</h2>
<p>Axios react-query
<code>Promise&lt;T&gt;</code> 是一个泛型类型</p>
<pre><code class="language-ts">import type { AxiosRequestConfig } from &#39;axios&#39;;

import axios from &#39;axios&#39;;



interface Res&lt;T = unknown&gt; {

  code: number;

  data: T;

  message: string;

}

async function http&lt;T&gt;(config: AxiosRequestConfig): Promise&lt;Res&lt;T&gt;&gt; {

  const instance = axios.create({

    baseURL: process.env.BASE_URL,

    timeout: 10000,

    headers: { &#39;Content-Type&#39;: &#39;application/json;charset=UTF-8&#39; },

    validateStatus:  ()=&gt; true,

  });

  instance.interceptors.request.use(

    (config) =&gt; {

      return config;

    },

    (error) =&gt; {

      return Promise.reject(error);

    },

  );

  instance.interceptors.response.use(

    (response) =&gt; {

      return response;

    },

    (error) =&gt; {

      return Promise.reject(error);

    },

  );

  const { data } = await instance.request&lt;Res&lt;T&gt;&gt;(config);

  return data;

}



export default http;
export const getArticle = (params: ParamsType) =&gt; {

  return http&lt;Article&gt;({

    method: &#39;get&#39;,

    url: &#39;/article/page&#39;,

    params,

  });

};
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[centos7部署gitlab]]></title>
            <link>https://zzfzzf.com/post/6972548384859230242</link>
            <guid>6972548384859230242</guid>
            <pubDate>Thu, 18 Nov 2021 02:10:47 GMT</pubDate>
            <content:encoded><![CDATA[<p>先上<a href="https://docs.gitlab.com/ee/install/docker.html#install-gitlab-using-docker-engine">官方文档</a></p>
<h2>快速安装</h2>
<pre><code>docker run -d --name gitlab -p 8080:80 --restart always gitlab/gitlab-ce
</code></pre>
<h2>初始密码</h2>
<pre><code>docker exec -it gitlab grep &#39;Password:&#39; /etc/gitlab/initial_root_password
</code></pre>
<h2>修改密码</h2>
<p><a href="https://docs.gitlab.com/ee/security/reset_user_password.html">https://docs.gitlab.com/ee/security/reset_user_password.html</a></p>
<ol>
<li>docker ps
2.进入gitlab容器中
docker exec -it gitlab bash</li>
</ol>
<pre><code>gitlab-rails console -e production
user = User.where(id: 1).first
user = User.find_by(email: &#39;admin@example.com&#39;)
user.password = &#39;secret_pass&#39;
user.password_confirmation = &#39;secret_pass&#39;
user.save!
</code></pre>
<h2>后续集成devops</h2>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[centos7 网络时间同步]]></title>
            <link>https://zzfzzf.com/post/6972548384859230241</link>
            <guid>6972548384859230241</guid>
            <pubDate>Thu, 11 Nov 2021 01:25:02 GMT</pubDate>
            <content:encoded><![CDATA[<h2>背景</h2>
<p>因购买的服务器使用jenkins性能问题，所以jenkins在本机虚拟机docker部署，使用frp映射到公网。但是本机经常休眠，休眠时centos时间就不会前进，所以需要自动同步时间。</p>
<h2>Chrony</h2>
<p>经过调研使用<code>Chrony</code>技术同步时间。一下是安装步骤及常用命令</p>
<ol>
<li>yum -y install chrony</li>
<li>systemctl enable chronyd</li>
<li>systemctl start chronyd</li>
<li>systemctl restart chronyd</li>
<li>timedatectl status //查看时间同步状态</li>
<li>timedatectl set-ntp true //开启网络时间同步</li>
</ol>
<pre><code>#查看 ntp_servers
chronyc sources -v

#查看 ntp_servers 状态
chronyc sourcestats -v

#查看 ntp_servers 是否在线
chronyc activity -v

#查看 ntp 详细信息
chronyc tracking -v
#查看日期时间、时区及 NTP 状态
timedatectl

#查看时区列表
timedatectl list-timezones
timedatectl list-timezones |  grep  -E &quot;Asia/S.*&quot;

#修改时区
timedatectl set-timezone Asia/Shanghai

#开启 NTP
timedatectl set-ntp true/flase
# 强制同步
chronyc -a makestep
# 校准
chronyc tracking
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[解决Springboot打包jar过大问题]]></title>
            <link>https://zzfzzf.com/post/6972548384859230240</link>
            <guid>6972548384859230240</guid>
            <pubDate>Sun, 07 Nov 2021 09:37:06 GMT</pubDate>
            <content:encoded><![CDATA[<h2>背景</h2>
<p>最近Jenkins打包springboot项目的jar包都80M以上，在部署时候，就算压缩以后也很大，经过资料查找，最终采用瘦包部署</p>
<h2>项目改造</h2>
<ol>
<li>修改pom.xml文件
在<code>build</code>-<code>plugins</code>-<code>plugin</code>下的<code>spring-boot-maven-plugin</code>添加一个依赖</li>
</ol>
<pre><code class="language-xml">&lt;!--pom.xml--&gt;
&lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
                &lt;dependencies&gt;
                    &lt;dependency&gt;
                        &lt;groupId&gt;org.springframework.boot.experimental&lt;/groupId&gt;
                        &lt;artifactId&gt;spring-boot-thin-layout&lt;/artifactId&gt;
                        &lt;version&gt;1.0.28.RELEASE&lt;/version&gt;
                    &lt;/dependency&gt;
                &lt;/dependencies&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;
</code></pre>
<ol start="2">
<li>执行打包命令<code>mvn clean package</code>发现jar包只有一百多KB
打包后的jar包只包含了我们自己编写的代码，依赖都不在jar包里，那么我们如何去运行它，让他去找到依赖呢。</li>
</ol>
<h2>运行jar包</h2>
<blockquote>
<p>我们有2种方式在服务器上运行jar包</p>
</blockquote>
<h3>直接运行</h3>
<ol>
<li>会把依赖下载到<code>~/.m2/repository</code>目录<br><code>java -jar my-app.jar</code></li>
<li>我们可以指定依赖下载到当前目录<br> <code>java -Dthin.root=. -jar my-app.jar</code></li>
<li>我们可以在程序启动之前手动预热<br><code>java -Dthin.dryrun=true -Dthin.root=. -jar my-app.jar</code></li>
</ol>
<h3>手动上传依赖</h3>
<blockquote>
<p>我们使用插件在本地把依赖构建好</p>
</blockquote>
<pre><code class="language-xml">&lt;!--pom.xml--&gt;
    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
                &lt;dependencies&gt;
                    &lt;dependency&gt;
                        &lt;groupId&gt;org.springframework.boot.experimental&lt;/groupId&gt;
                        &lt;artifactId&gt;spring-boot-thin-layout&lt;/artifactId&gt;
                        &lt;version&gt;1.0.28.RELEASE&lt;/version&gt;
                    &lt;/dependency&gt;
                &lt;/dependencies&gt;
            &lt;/plugin&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot.experimental&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-thin-maven-plugin&lt;/artifactId&gt;
                &lt;version&gt;1.0.28.RELEASE&lt;/version&gt;
                &lt;executions&gt;
                    &lt;execution&gt;
                        &lt;id&gt;resolve&lt;/id&gt;
                        &lt;goals&gt;
                            &lt;goal&gt;resolve&lt;/goal&gt;
                        &lt;/goals&gt;
                        &lt;inherited&gt;false&lt;/inherited&gt;
                    &lt;/execution&gt;
                &lt;/executions&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;
</code></pre>
<ol>
<li><p>把<code>target/thin/root/repository</code>目录上传到服务器</p>
</li>
<li><p>运行命令<code>java -Dthin.root=. -jar my-app.jar</code>即可</p>
</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[暗黑模式与色彩搭配]]></title>
            <link>https://zzfzzf.com/post/6972548384859230238</link>
            <guid>6972548384859230238</guid>
            <pubDate>Mon, 25 Oct 2021 06:55:05 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>如何使网站适配深色模式的文章已经很多了，本文不再讲解,本文讲一下深色与浅色模式的颜色搭配</p>
</blockquote>
<h2>颜色搭配网站</h2>
<ol>
<li><a href="https://colorhunt.co/">https://colorhunt.co/</a>
适用这个网站去寻找你中意的颜色</li>
</ol>
<h2>颜色表示</h2>
<ol>
<li>rgba</li>
<li>hsla</li>
<li>16进制</li>
</ol>
<h2>主题色</h2>
<blockquote>
<p>主题色大量适用于我们网站的各个地方
我们把颜色分为三个等级，依此darken或者lighten</p>
</blockquote>
<ol>
<li>primary</li>
<li>secondary</li>
<li>tertiary</li>
</ol>
<h2>检查色彩搭配的可访问性</h2>
<ol>
<li><a href="https://material.io/resources/color/#!/?view.left=0&view.right=0">https://material.io/resources/color/#!/?view.left=0&amp;view.right=0</a></li>
<li><a href="https://material.io/design/color/the-color-system.html#tools-for-picking-colors">https://material.io/design/color/the-color-system.html#tools-for-picking-colors</a></li>
<li><a href="https://mui.com/zh/customization/color/">https://mui.com/zh/customization/color/</a></li>
<li><a href="https://colorable.jxnblk.com/">对比度测试</a></li>
<li><a href="https://material.io/inline-tools/color/">https://material.io/inline-tools/color/</a></li>
<li><a href="https://anthonyhobday.com/sideprojects/saferules/">https://anthonyhobday.com/sideprojects/saferules/</a></li>
</ol>
<h2>目标</h2>
<blockquote>
<p>支持<code>深色</code>,<code>浅色</code>,<code>自动</code>三种模式</p>
</blockquote>
<ol>
<li><p>css媒体查询</p>
</li>
<li><p>js监听深色浅色</p>
</li>
<li><p>在html上添加自定义属性<code>data-color-mode</code></p>
</li>
<li><p><code>prefers-color-scheme</code>css属性用于检测深色or浅色模式</p>
</li>
<li><p>matchMedia</p>
</li>
</ol>
<pre><code>const mediaQuery = window.matchMedia(&#39;(prefers-color-scheme: dark)&#39;)

function darkModeHandler() {
    if (mediaQuery.matches) {
        console.log(&#39;现在是深色模式&#39;)
    } else {
        console.log(&#39;现在是浅色模式&#39;)
    }
}

// 监听模式变化
mediaQuery.addListener(darkModeHandler)
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[CSS预处理]]></title>
            <link>https://zzfzzf.com/post/6972548384859230236</link>
            <guid>6972548384859230236</guid>
            <pubDate>Mon, 18 Oct 2021 03:04:48 GMT</pubDate>
            <content:encoded><![CDATA[<h2>PostCSS</h2>
<p>将css解析成AST，由插件提供能力，</p>
<p>常用插件</p>
<ol>
<li><a href="https://github.com/postcss/autoprefixer">Autoprefixer</a> 为 CSS 中的属性添加浏览器特定的前缀。</li>
<li><a href="https://github.com/csstools/postcss-preset-env">postcss-preset-env</a> 根据 <code>browserslist</code> 指定的目标浏览器将一些 CSS 的新特性转换为目标浏览器所支持的语法。</li>
<li><a href="https://github.com/cssnano/cssnano">cssnano</a> 提供 CSS 压缩功能。</li>
<li><a href="https://github.com/postcss/postcss-nested">postcss-nested</a> 提供 CSS 嵌套功能。</li>
<li><a href="https://github.com/evrone/postcss-px-to-viewport">postcss-px-to-viewport</a> 提供 px 转 vw 功能。</li>
<li><a href="https://github.com/postcss/postcss-custom-properties">postcss-custom-properties</a> 支持 CSS 的自定义属性。</li>
</ol>
<h3>优点</h3>
<ul>
<li>插件系统完善，扩展性强。</li>
<li>配合插件功能齐全。</li>
<li>生态优秀。</li>
</ul>
<h3>缺点</h3>
<ul>
<li>配置相对复杂。</li>
</ul>
<h2>Sass</h2>
<ol>
<li>变量：变量中可以存储颜色、字体或任何 CSS 值。</li>
<li>嵌套：可嵌套 CSS 选择器，提供清晰的层次结构。</li>
<li>混合：可以定义&amp;重用代码块。</li>
<li>扩展/集成：可以在一个选择器内继承另一个选择器。</li>
<li>操作符：可以在 CSS 中使用操作符进行计算。</li>
<li>条件/循环语句：可以循环/条件生成 CSS。</li>
<li>自定义函数：可以自定义复杂操作的函数。</li>
</ol>
<h3>缺点</h3>
<p>node-sass安装困难，可用dart-sass代替</p>
<h2>Less</h2>
<ul>
<li>变量：变量中可以存储颜色、字体或任何 CSS 值。</li>
<li>嵌套：可嵌套 CSS 选择器，提供清晰的层次结构。</li>
<li>混合：可以定义&amp;重用的代码块。</li>
<li>扩展/集成：可以在一个选择器内继承另一个选择器。</li>
<li>运算：可以在 CSS 中进行计算。</li>
<li>条件/循环语句：可以循环/条件生成 CSS。</li>
</ul>
<h3>优点</h3>
<ul>
<li>使用广泛。</li>
<li>可以在浏览器中运行，容易实现主题定制功能。</li>
</ul>
<h3>缺点</h3>
<ul>
<li>不支持自定义函数（可通过 mixins 实现简单逻辑）。</li>
<li>编程能力相对较弱。</li>
</ul>
<h2>Css Modules</h2>
<h2>Css In Js</h2>
<ul>
<li><a href="https://styled-components.com">styled-components</a></li>
</ul>
<pre><code class="language-jsx">const Button = styled.a`
  /* This renders the buttons above... Edit me! */
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
  background: transparent;
  color: white;
  border: 2px solid white;

  /* The GitHub button is a primary button
   * edit this to target it specifically! */
  ${props =&gt; props.primary &amp;&amp; css`
    background: white;
    color: black;
  `}
`
</code></pre>
<h2>Tailwind</h2>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[js数据类型判断]]></title>
            <link>https://zzfzzf.com/post/6972548384859230235</link>
            <guid>6972548384859230235</guid>
            <pubDate>Sat, 18 Sep 2021 02:26:22 GMT</pubDate>
            <content:encoded><![CDATA[<h2>JS中的数据类型</h2>
<ol>
<li>number</li>
<li>string</li>
<li>boolean</li>
<li>null</li>
<li>undefined</li>
<li>symbol</li>
<li>bigint</li>
</ol>
<h2>检测办法</h2>
<ol>
<li>typeof 运算符</li>
<li>instanceof [原型链]</li>
<li>constructor [构造函数]</li>
<li>Object.prototype.toString.call([value]) 检测数据类型</li>
<li>Array.isArray([value]) 检测一个值是否为数组</li>
</ol>
<h3>typeof</h3>
<ol>
<li>可以识别基本数据类型 <strong>null</strong>为<strong>object</strong></li>
<li>引用类型为object和function</li>
</ol>
<blockquote>
<p>利用二进制检测，二进制前3位存储</p>
</blockquote>
<ul>
<li>000: 对象</li>
<li>010: 浮点数</li>
<li>100：字符串</li>
<li>110：布尔</li>
<li>1：整数</li>
<li>null的二进制全为000所以是object</li>
</ul>
<h3>instanceof</h3>
<p>基于原型链检查</p>
<p><code>[] instanceof Array</code></p>
<pre><code class="language-javascript">function isInstanceOf(instance, constructor) {
    const list = [&#39;string&#39;, &#39;number&#39;, &#39;boolean&#39;, &#39;undefined&#39;, &#39;symbol&#39;]
    const type = typeof instance
    const isBasicVal = instance === null || list.indexOf(type) &gt; -1
    const prototype = constructor.prototype
    let proto = isBasicVal ? null : instance.__proto__
    while (true) {
        if (proto === null) return false;
        if (proto === prototype) return true;
        proto = Object.getPrototypeof(proto);
    }
}
</code></pre>
<h3>Contructor</h3>
<pre><code class="language-javascript">[].constructor===Array
</code></pre>
<blockquote>
<p>缺点：在自身实例上判断、容易被修改</p>
</blockquote>
<h3>Object.prototype.toString.call()</h3>
<pre><code>Object.prototype.toString({})       // &quot;[object Object]&quot;
Object.prototype.toString.call([])  //&quot;[object Array]&quot;
Object.prototype.toString.call(&#39;1&#39;) // &quot;[object String]&quot;
</code></pre>
<h3>Array.isArray</h3>
<blockquote>
<p>判断是否是数组</p>
</blockquote>
<pre><code>Array.isArray([]) // true
Array.isArray(1) // false
</code></pre>
<h3>Object.prototype.isPrototypeOf</h3>
<pre><code class="language-javascript">Array.prototype.isPrototypeOf([])
</code></pre>
<h3>Object.getPrototypeOf</h3>
<pre><code class="language-javascript">Object.getPrototypeOf([]) === Array.prototype;
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[react渲染流程与生命周期]]></title>
            <link>https://zzfzzf.com/post/6972548384859230234</link>
            <guid>6972548384859230234</guid>
            <pubDate>Tue, 14 Sep 2021 08:21:40 GMT</pubDate>
            <content:encoded><![CDATA[<h2>react生命周期</h2>
<p><img src="https://oss-zzf.zzfzzf.com/cdn/1645422967906gRpCBX.png" alt="1645422967906gRpCBX"></p>
<h3>渲染流程</h3>
<p>Reconcile计算状态变化包含diff.      render阶段 可中断</p>
<p>Render渲染状态变化 reactDom reactNative。 commit阶段</p>
<p>this.setState=&gt;reconcile去计算状态变化=&gt;reactDom渲染在视图中</p>
<p>新版生命周期</p>
<table>
<thead>
<tr>
<th></th>
<th>Mount</th>
<th>Update</th>
<th>Unmount</th>
<th>Error</th>
</tr>
</thead>
<tbody><tr>
<td>Render阶段</td>
<td>constructor</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Render阶段</td>
<td>getDerivedStateFromProps</td>
<td>getDerivedStateFromProps</td>
<td></td>
<td>getDerivedStateFromError</td>
</tr>
<tr>
<td>Render阶段</td>
<td></td>
<td>shouldComponentUpdate</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Render阶段</td>
<td>render</td>
<td>render</td>
<td></td>
<td></td>
</tr>
<tr>
<td>pre-commit阶段</td>
<td></td>
<td>getSnapshotBeforeUpdate</td>
<td></td>
<td></td>
</tr>
<tr>
<td>commit阶段</td>
<td>componentDidMount</td>
<td>componentDidUpdate</td>
<td>componentWillUnmount</td>
<td>componentDidCatch</td>
</tr>
<tr>
<td>commit阶段</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3>组件树</h3>
<blockquote>
<p>挂载</p>
</blockquote>
<ol>
<li>ReactDom.render</li>
<li>进入render阶段，深度优先遍历创建Fiber树</li>
<li>进入commit阶段，从子节点开始执行生命周期函数</li>
</ol>
<blockquote>
<p>更新</p>
</blockquote>
<blockquote>
<p>每次setState都会完整创建Fiber树</p>
</blockquote>
<p>双缓存树</p>
<h2>hooks模拟class生命周期</h2>
<table>
<thead>
<tr>
<th>class 组件</th>
<th>Hooks 组件</th>
</tr>
</thead>
<tbody><tr>
<td>constructor</td>
<td>useState</td>
</tr>
<tr>
<td>getDerivedStateFromProps</td>
<td>useEffect 手动对比 props， 配合 useState 里面 update 函数</td>
</tr>
<tr>
<td>shouldComponentUpdate</td>
<td>React.memo</td>
</tr>
<tr>
<td>render</td>
<td>函数本身</td>
</tr>
<tr>
<td>componentDidMount</td>
<td>useEffect 第二个参数为<code>[]</code></td>
</tr>
<tr>
<td>componentDidUpdate</td>
<td>useEffect 配合useRef</td>
</tr>
<tr>
<td>componentWillUnmount</td>
<td>useEffect 里面返回的函数</td>
</tr>
<tr>
<td>componentDidCatch</td>
<td>无</td>
</tr>
<tr>
<td>getDerivedStateFromError</td>
<td>无</td>
</tr>
</tbody></table>
<pre><code>import React, { useState, useEffect, useRef, memo } from &#39;react&#39;;

// 使用 React.memo 实现类似 shouldComponentUpdate 的优化， React.memo 只对 props 进行浅比较
const UseEffectExample = memo((props) =&gt; {
    console.log(&quot;===== UseStateExample render=======&quot;);
    // 声明一个叫 “count” 的 state 变量。
    const [count, setCount] = useState(0);
    const [count2, setCount2] = useState(0);
    const [fatherCount, setFatherCount] = useState(props.fatherCount)

    console.log(props);

    // 模拟 getDerivedStateFromProps
    useEffect(() =&gt; {
        // props.fatherCount 有更新，才执行对应的修改，没有更新执行另外的逻辑
        if(props.fatherCount == fatherCount ){
            console.log(&quot;======= 模拟 getDerivedStateFromProps=======&quot;);
            console.log(props.fatherCount, fatherCount);
        }else{
            setFatherCount(props.fatherCount);
            console.log(props.fatherCount, fatherCount);
        }
    })

    // 模拟DidMount
    useEffect(() =&gt; {
        console.log(&quot;=======只渲染一次(相当于DidMount)=======&quot;);
        console.log(count);
    }, [])

    // 模拟DidUpdate
    const mounted = useRef();
    useEffect(() =&gt; {
        console.log(mounted);
        if (!mounted.current) {
            mounted.current = true;
          } else {
            console.log(&quot;======count 改变时才执行(相当于DidUpdate)=========&quot;);
            console.log(count);
          }
    }, [count])

    // 模拟 Didmount和DidUpdate 、 unmount
    useEffect(() =&gt; {
        // 在 componentDidMount，以及 count 更改时 componentDidUpdate 执行的内容
        console.log(&quot;======初始化、或者 count 改变时才执行(相当于Didmount和DidUpdate)=========&quot;);
        console.log(count);
        return () =&gt; {
            
            console.log(&quot;====unmount=======&quot;);
            console.log(count);
        }
    }, [count])

    return (
        &lt;div&gt;
            &lt;p&gt;You clicked {count} times&lt;/p&gt;
            &lt;button onClick={() =&gt; setCount(count + 1)}&gt;
                Click me
            &lt;/button&gt;

            &lt;button onClick={() =&gt; setCount2(count2 + 1)}&gt;
                Click me2
            &lt;/button&gt;
        &lt;/div&gt;
    );
});
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[跨域问题]]></title>
            <link>https://zzfzzf.com/post/6972548384859230233</link>
            <guid>6972548384859230233</guid>
            <pubDate>Sat, 04 Sep 2021 13:50:18 GMT</pubDate>
            <content:encoded><![CDATA[<h2>为什么会跨域</h2>
<p>跨域是受浏览器限制而产生的，浏览器基于同源策略组织跨域资源</p>
<blockquote>
<p>同源策略，即协议+域名+端口号不同即为跨域
可window.origin查看源
同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据。</p>
</blockquote>
<h2>解决方案</h2>
<p>主流的解决方案以下3种</p>
<h3>jsonp</h3>
<p>不常用</p>
<h3>Cors</h3>
<p>全称为<strong>Cross-Origin Resource Sharing</strong> 跨域资源共享,实现CORS通信的关键是服务器</p>
<pre><code> &#39;Access-Control-Allow-Origin&#39; //源

 &#39;Access-Control-Allow-Headers&#39; //请求头

 &#39;Access-Control-Allow-Methods&#39; //请求方法

 &#39;Access-Control-Allow-Credentials&#39; //cookie
</code></pre>
<p>响应头包含以上3个则可设置cors</p>
<ul>
<li><p>简单请求会带上<strong>Origin</strong> 字段</p>
<blockquote>
<p>简单请求需同时满意以下两个条件</p>
</blockquote>
</li>
</ul>
<ol>
<li><p>请求方法为<code>HEAD</code> <code>GET</code> <code>POST</code></p>
</li>
<li><p>请求头为<code>Accept</code>、<code>Accept-Language</code> 、<code>Content-Language</code> 、<code>Last-Event-ID</code> 、<code>Content-Type</code> ：只限于三个值 <code>application/x-www-form-urlencoded</code>、<code>multipart/form-data</code>、<code>text/plain</code></p>
</li>
</ol>
<ul>
<li>非简单请求</li>
</ul>
<p>非简单请求会先发送<strong>OPTIONS</strong> 来询问,如果浏览器否定了“预检”请求，会返回一个正常的HTTP回应，但是没有任何CORS相关的头信息字段，这时浏览器就会认定服务器不同意预检请求，触发错误；</p>
<p>通过预检请求后请求头会带上<strong>Origin 字段</strong></p>
<p>我们一般用json交互都是非简单请求<code>application/json</code></p>
<p>Cookie 需要设置<strong>proxy_cookie_domain</strong> node设置<strong>cookieDomainRewrite</strong></p>
<h3>反向代理</h3>
<p>线上可nginx或者koa反向代理</p>
<p>利用了服务器对服务器没有同源限制</p>
<pre><code>server {

  listen  80;

  server_name  client.com;

  location /api {

    proxy_pass server.com;

  }

}
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[正则实践]]></title>
            <link>https://zzfzzf.com/post/6972548384859230232</link>
            <guid>6972548384859230232</guid>
            <pubDate>Fri, 03 Sep 2021 06:46:28 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>本文使用JavaScript语言编写正则</p>
</blockquote>
<h2>创建正则表达式</h2>
<ol>
<li>构造函数</li>
</ol>
<pre><code>const reg = new RegExp(/\d/, &#39;g&#39;)
&#39;123&#39;.match(reg)
</code></pre>
<ol>
<li>字面量</li>
</ol>
<pre><code>const reg = /\d/g
</code></pre>
<blockquote>
<p>匹配模式</p>
</blockquote>
<ol>
<li>i 忽略大小写</li>
<li>g 全局匹配</li>
<li>m 多行匹配</li>
</ol>
<h2>零宽断言</h2>
<blockquote>
<p>当我们想要匹配特定内容时，要求内容前后必须是特定内容，但是又不想捕获这些内容</p>
</blockquote>
<h3>a(?=b)</h3>
<p>目的要匹配a，但要求a后面一定要包含字符b才能匹配（如果a后面有b，则匹配a）</p>
<h3>a(?!b)</h3>
<p>目的要匹配a，但要求a后面一定不能包含字符b才能匹配</p>
<h3>(?&lt;=b)a</h3>
<p>目的要匹配a，但要求a前面一定要包含字符b才能匹配</p>
<h3>(?&lt;!b)a</h3>
<p>目的要匹配a，但要求a前面一定不能包含字符b才能匹配</p>
<h2>方法</h2>
<ol>
<li>regexp.exec (str)</li>
<li>regexp.test (str)</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[函数式编程最佳实践🔋]]></title>
            <link>https://zzfzzf.com/post/6972548384859230231</link>
            <guid>6972548384859230231</guid>
            <pubDate>Wed, 01 Sep 2021 09:25:24 GMT</pubDate>
            <content:encoded><![CDATA[<h2>特点</h2>
<ol>
<li>函数是一等公民，可以被赋值、作为参数传递、作为返回值等等。</li>
</ol>
<blockquote>
<p>函数可以被赋值给一个变量;
函数可以被当作参数传递给其他函数;
函数可以作为另一个函数的返回值</p>
</blockquote>
<ol start="2">
<li>声明式编程，强调描述要达到的目标，而不是具体实现的步骤。</li>
<li>惰性执行，只有在需要时才会执行。</li>
<li>无状态，数据不可变，避免出现意外的副作用。</li>
<li>纯函数，即相同的输入永远得到相同的输出，没有可观察的副作用。</li>
</ol>
<h2>什么是纯函数</h2>
<blockquote>
<p>副作用即和函数外部环境发生的交互就是副作用
纯函数是这样一种函数，即相同的输入，永远会得到相同的输出，而且没有任何可观察的副作用。
纯函数也可以理解为数学上的函数</p>
</blockquote>
<ul>
<li>更改文件系统</li>
<li>往数据库插入记录</li>
<li>发送一个 http 请求</li>
<li>可变数据</li>
<li>打印/log</li>
<li>获取用户输入</li>
<li>DOM 查询</li>
<li>访问系统状态</li>
</ul>
<h3>优点</h3>
<ol>
<li>可缓存，避免重复计算相同的结果。</li>
<li>可移植，可以在不同的平台上使用。</li>
<li>可测试，因为纯函数可以更容易地进行单元测试。</li>
<li>函数合成，可以把多个函数组合成一个更大的函数。</li>
<li>函数柯里化，可以把多个参数的函数转换成接受一个参数的函数，有助于函数复用和组合。</li>
</ol>
<h2>函数合成</h2>
<pre><code class="language-js">function compose(...args) {
    return function (value) {
        return args.reduceRight((previousValue, currentValue) =&gt; {
            return currentValue(previousValue)
        },value)
    }
}
</code></pre>
<h2>函数柯里化</h2>
<blockquote>
<p>只传递给函数一部分参数来调用它，让它返回一个函数去处理剩下的参数
函数柯里化利用闭包对函数参数进行缓存，可以把函数分为更细粒度</p>
</blockquote>
<pre><code class="language-js">const curry = (fn, ...args) =&gt;
    args.length &gt;= fn.length
        ? fn(...args)
        : (..._args) =&gt; curry(fn, ...args, ..._args);
</code></pre>
<h2>常用库</h2>
<ol>
<li><a href="https://ramda.cn/docs/#">ramda</a> 提供了一系列的函数式编程工具函数。</li>
<li><a href="https://rxjs.dev/guide/overview">rxjs</a> 提供了响应式编程工具，可以方便地处理异步事件流。</li>
<li><a href="https://underscorejs.org/">underscorejs</a> 提供了一系列的实用工具函数。</li>
<li><a href="https://github.com/immerjs/immer">immerjs</a> 可以方便地实现不可变数据。</li>
<li><a href="https://github.com/immutable-js/immutable-js/">immutable</a> 提供了一系列的不可变数据结构。</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[Babel7最佳实践]]></title>
            <link>https://zzfzzf.com/post/6972548384859230227</link>
            <guid>6972548384859230227</guid>
            <pubDate>Wed, 04 Aug 2021 04:02:37 GMT</pubDate>
            <content:encoded><![CDATA[<h2>准备知识</h2>
<blockquote>
<p>需要用到的网站</p>
</blockquote>
<ol>
<li><a href="https://astexplorer.net/">AST分析</a></li>
<li><a href="https://esprima.org/demo/parse.html#">AST</a></li>
<li><a href="https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/README.md">babel手册</a></li>
<li><a href="https://babeljs.io/">babel官网</a></li>
<li><a href="https://github.com/browserslist/browserslist">浏览器版本配置</a></li>
<li><a href="http://kangax.github.io/compat-table/es6/">compat-table</a></li>
<li><a href="https://caniuse.com/">caniuse</a></li>
</ol>
<blockquote>
<p>前置知识</p>
</blockquote>
<ol>
<li>babel编译流程</li>
</ol>
<p>围绕<strong>AST 解析(parse)</strong>-&gt;<strong>转化(transform)</strong>-&gt;<strong>生成(generate)</strong></p>
<ol>
<li><strong>parse</strong>阶段 @babel/parse</li>
<li><strong>transform</strong>阶段 @babel/traverse遍历AST、@babel/types可以判断AST的各个类型 @babel/template 简化AST创建逻辑</li>
<li><strong>generate</strong>阶段 @babel/generate生成source map</li>
</ol>
<blockquote>
<p>执行顺序</p>
</blockquote>
<p>plugins-&gt;presets</p>
<p>插件优先于预设</p>
<p>插件从前往后执行</p>
<p>预设从后往前执行</p>
<h2>babel基础</h2>
<h3>@babel/core</h3>
<p>babel转化核心包，内置转化方法</p>
<h3>@babel/cli</h3>
<p>使用命令行编译文件，开发插件需要用到</p>
<h3>@babel/preset-env</h3>
<p>根据配置的浏览器适配代码，默认只会转化语法，不会转化新api，新api可以通过2种方法适配，</p>
<ol>
<li>配置 <strong>corejs</strong> <strong>useBuiltIns</strong> 会污染全局</li>
<li>配置 <strong>@babel/plugin-transform-runtime</strong> 提供别名，不会污染全局</li>
</ol>
<blockquote>
<p>安装</p>
</blockquote>
<ol>
<li><code>npm i @babel/runtime-corejs3</code></li>
<li><code>npm i @babel/runtime</code></li>
</ol>
<pre><code class="language-json">{
  &quot;plugins&quot;: [
    &quot;@babel/plugin-transform-runtime&quot;
  ],
  &quot;presets&quot;: [
    [
      &quot;@babel/preset-env&quot;,
      {
        &quot;modules&quot;: false,
        &quot;targets&quot;: &quot;&gt; 0.25%, not dead&quot;,
        &quot;corejs&quot;: &quot;3&quot;,
        &quot;useBuiltIns&quot;: &quot;usage&quot;
      }
    ]
  ]
}
</code></pre>
<h3>@babel/plugin-transform-runtime</h3>
<p>从 <strong>@babel/runtime</strong> 按需加载辅助函数公共包，我们可以直接安装这个包，会自动依赖**@babel/runtime** 不需要手动安装@babel/runtime,生产环境需要 <strong>@babel/runtime</strong></p>
<ol>
<li>配置了corejs为3<code>npm i @babel/runtime-corejs3</code> （推荐）</li>
<li>没有配置corejs<code>npm i @babel/runtime</code></li>
</ol>
<pre><code class="language-json">{
  &quot;plugins&quot;: [
    [
      &quot;@babel/plugin-transform-runtime&quot;,
      {
        &quot;corejs&quot;: 3
      }
    ],
    [
      &quot;@zzf-babel/plugins/lib/babel-plugin-import&quot;,
      {
        &quot;lib&quot;: &quot;foo&quot;
      }
    ]
  ],
  &quot;presets&quot;: [
    [
      &quot;@babel/preset-env&quot;,
      {
        &quot;useBuiltIns&quot;: &quot;false&quot;,
        &quot;modules&quot;: false,
        &quot;targets&quot;: {
          &quot;browsers&quot;: [
            &quot;&gt; 1%&quot;,
            &quot;last 2 versions&quot;,
            &quot;not dead&quot;
          ]
        }
      }
    ]
  ]
}
</code></pre>
<h2>babel插件</h2>
<p>//todo</p>
<h3>自定义插件</h3>
<p>//todo</p>
<h2>babel预设</h2>
<p>一堆插件的集合，避免一个个配置插件</p>
<h3>自定义预设</h3>
<pre><code class="language-js">module.exports = function (options) {

    console.log(options)

    return {

        presets: [],

        plugins: [

            require(&quot;@zzf-babel/plugins/lib/babel-plugin-remove-console&quot;)

        ]

    };

}
</code></pre>
<h2>学习仓库</h2>
<p><a href="https://github.com/zzfn/babel">https://github.com/zzfn/babel</a></p>
<h2>待整理</h2>
<ul>
<li><a href="https://live.juejin.cn/4354/4815025">https://live.juejin.cn/4354/4815025</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[mongoDb、rabbitmq安装]]></title>
            <link>https://zzfzzf.com/post/6972548384859230224</link>
            <guid>6972548384859230224</guid>
            <pubDate>Fri, 09 Jul 2021 03:06:57 GMT</pubDate>
            <content:encoded><![CDATA[<h3>mongoDb</h3>
<ol>
<li>下载并启动</li>
</ol>
<pre><code>docker run --name mongo  -p 27017:27017 -d mongo:latest  --auth
</code></pre>
<ol start="2">
<li>进入容器</li>
</ol>
<pre><code>docker exec -it container_id  mongo admin
</code></pre>
<ol start="3">
<li>创建admin</li>
</ol>
<pre><code>db.createUser({ user: &#39;admin&#39;, pwd: &#39;admin&#39;, roles: [ { role: &quot;userAdminAnyDatabase&quot;, db: &quot;admin&quot; } ] }); 
#验证一下对不对
db.auth(&quot;admin&quot;,&quot;admin&quot;);
#退出
exit
</code></pre>
<ol start="4">
<li>创建业务库</li>
</ol>
<pre><code>use zzf;
db.createUser({ user: &quot;zzf&quot;, pwd: &quot;zzf&quot;, roles: [ { role: &quot;dbAdmin&quot;, db:&quot;zzf&quot; } ] });
#or
db.createUser({ user: &quot;zzf&quot;, pwd: &quot;zzf&quot;, roles: [ { role: &quot;readWrite&quot;, db:&quot;zzf&quot; } ] });
db.auth(&quot;zzf&quot;,&quot;zzf&quot;);
db.items.insert({&quot;name&quot;:2})
</code></pre>
<h3>rabbitmq</h3>
<pre><code>docker run --name rabbitmq -d -p 15672:15672 -p 5672:5672 rabbitmq:management
</code></pre>
<p>进入 ip:15672
账号密码都是<strong>guest</strong></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[setState与批处理]]></title>
            <link>https://zzfzzf.com/post/6972548384859230223</link>
            <guid>6972548384859230223</guid>
            <pubDate>Wed, 07 Jul 2021 01:42:04 GMT</pubDate>
            <content:encoded><![CDATA[<p>本文基于版本<strong>17.0.2</strong>和<strong>18</strong></p>
<h2>同步还是异步</h2>
<p><code>setState</code>本身逻辑并不是由异步逻辑实现</p>
<p>在setState之后console.log打印的值并不是最新的值、由于批处理，状态变化是异步的</p>
<p>不同模式下表现是不一样的</p>
<ol>
<li>react17同步模式<code>ReactDOM.render</code>中如果在异步里是同步的，否则是异步的</li>
<li>react18异步模式<code>ReactDOM.createRoot</code>中是都是异步更新的</li>
</ol>
<h2>批处理</h2>
<blockquote>
<p> 是什么</p>
</blockquote>
<p>在批处理下，你可能不会得到想到的结果</p>
<pre><code class="language-jsx">    this.setState({ num: this.state.num + 1 });
    this.setState({ num: this.state.num + 2 });
    this.setState({ num: this.state.num + 3 });
    #等于
    Object.assign(
      previousState,
      {num: state.num + 1},
      {num: state.num + 2},
      {num: state.num + 3}
    )
    #结果+3
</code></pre>
<blockquote>
<p>为什么</p>
</blockquote>
<p>如果每一次setState都去重新渲染更新视图，那么对性能有非常大的影响</p>
<blockquote>
<p>具体表现</p>
</blockquote>
<p>在react17，异步(setTimeout)或者绕过react组件(addEventListener)的不会进行批处理</p>
<pre><code class="language-jsx">import &quot;./styles.css&quot;;
import React, { Component } from &quot;react&quot;;
export default class App extends Component {
  state = {
    num: 1
  };
  handleClick = () =&gt; {
    // setTimeout(() =&gt; {
    //   this.setState({ num: this.state.num + 1 });
    //   this.setState({ num: this.state.num + 1 });
    //   console.log(this.state.num);
    // });
  };
  componentDidMount() {
    document.querySelector(&quot;button&quot;).addEventListener(&quot;click&quot;, () =&gt; {
      this.setState({ num: this.state.num + 1 });
      this.setState({ num: this.state.num + 1 });
      console.log(this.state.num);
    });
  }
  render() {
    return (
      &lt;div className=&quot;App&quot;&gt;
        &lt;h2&gt;{this.state.num}&lt;/h2&gt;
        &lt;button onClick={this.handleClick}&gt;111&lt;/button&gt;
      &lt;/div&gt;
    );
  }
}
</code></pre>
<h2>怎么获取最新的state值</h2>
<h3>react17</h3>
<p>在class component下</p>
<ol>
<li><p>setState用函数写法</p>
</li>
<li><p>setState回调函数获取最新</p>
</li>
<li><p>setTimeout</p>
</li>
<li><p>addEventListener(不推荐)</p>
</li>
</ol>
<p>在function下</p>
<ol>
<li>useEffect</li>
</ol>
<h3>react18</h3>
<p>在class component下</p>
<ol>
<li><p>setState用函数写法</p>
</li>
<li><p>setState回调函数获取最新</p>
</li>
</ol>
<p>在function下</p>
<ol>
<li>useEffect</li>
</ol>
<h2>总结</h2>
<ol>
<li>在react18以前，setState只有在原生和setTimeout是可以立即获取值的</li>
<li>setState执行过程和代码是同步的，但是由于事件合成、批处理无法立即拿到更新后的值，形成了异步、</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[如何学习前端]]></title>
            <link>https://zzfzzf.com/post/6972548384859230222</link>
            <guid>6972548384859230222</guid>
            <pubDate>Tue, 06 Jul 2021 16:22:45 GMT</pubDate>
            <content:encoded><![CDATA[<h2>前言</h2>
<p>万事开头难，仅以此文给刚入门的小伙伴</p>
<h2>算法</h2>
<blockquote>
<p>建议按照以下链接顺序学习，先了解入门知识，再分类刷算法</p>
</blockquote>
<table>
<thead>
<tr>
<th>标题</th>
<th>语言</th>
<th>类型</th>
<th>介绍</th>
<th>推荐指数</th>
</tr>
</thead>
<tbody><tr>
<td><a href="https://greyireland.gitbook.io/algorithm-pattern/">算法入门</a></td>
<td>Go</td>
<td></td>
<td>算法基础知识</td>
<td></td>
</tr>
<tr>
<td><a href="https://github.com/youngyangyang04/leetcode-master">代码随想录</a></td>
<td>C++</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="https://books.halfrost.com/leetcode/">霜神的算法</a></td>
<td>Go</td>
<td>分类总结</td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="https://labuladong.github.io/algo/">labuladong的算法小抄</a></td>
<td>Java</td>
<td>分类总结</td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="https://wizardforcel.gitbooks.io/the-art-of-programming-by-july/content/index.html">编程之法：面试和算法心得</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="https://github.com/SharingSource/LogicStack-LeetCode">宫水三叶的刷题笔记</a></td>
<td></td>
<td>分类刷题</td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="https://github.com/doocs/leetcode?utm_source=gold_browser_extension">算法题解</a></td>
<td></td>
<td>多语言题解</td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="https://github.com/azl397985856/leetcode?utm_source=gold_browser_extension">算法通关之路</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="https://leetcode-cn.com/circle/article/48kq9d/">各个题型推荐刷的题</a></td>
<td></td>
<td>分类</td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="https://github.com/trekhleb/javascript-algorithms/blob/master/README.zh-CN.md">JavaScript算法与数据结构</a></td>
<td>JavaScript</td>
<td>分类</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2>基础知识</h2>
<ul>
<li><a href="https://time.geekbang.org/column/article/151370">浏览器工作原理与实践</a></li>
</ul>
<h2>JavaScript基础</h2>
<ul>
<li><a href="https://github.com/stephentian/33-js-concepts">JavaScript开发者应懂的33个概念</a></li>
<li><a href="https://es6.ruanyifeng.com/">ES6入门教程</a></li>
<li><a href="https://github.com/mqyqingfeng/Blog">JavaScript深入系列</a></li>
<li><a href="https://juejin.cn/post/6946022649768181774">36手写题</a></li>
</ul>
<h2>React</h2>
<ul>
<li><a href="https://react.iamkasong.com/">React技术揭秘</a></li>
<li><a href="https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/">useEffect</a></li>
<li><a href="https://juejin.cn/post/6947856296771223589">diff</a></li>
</ul>
<h2>webpack</h2>
<ul>
<li><a href="https://juejin.cn/post/6844903877771264013">webpack的面试题总结</a></li>
</ul>
<h2>网络</h2>
<ul>
<li><a href="https://juejin.cn/post/6844904100035821575">灵魂HTTP系列</a></li>
<li><a href="https://juejin.cn/post/6844904070889603085">灵魂TCP系列</a></li>
</ul>
<h2>面经</h2>
<ul>
<li><a href="https://juejin.cn/post/6844904106537009159">面经分享</a></li>
<li><a href="https://juejin.cn/post/6945625394154307592">面经分享</a></li>
<li><a href="https://mp.weixin.qq.com/s/9mH4AyA59E8O0iwOsGmqkA">面经分享</a></li>
<li><a href="https://juejin.cn/post/6989422484722286600">面经分享</a></li>
<li><a href="https://mp.weixin.qq.com/s/D6zTSh_szw4uhk3eqkiBPA">面经分享</a></li>
</ul>
<h2>电子书</h2>
<ul>
<li><a href="https://awesome-programming-books.github.io/">经典技术书籍</a></li>
<li><a href="https://github.com/linghuam/boutique-books">补充技术书籍</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[前端的hash和history路由]]></title>
            <link>https://zzfzzf.com/post/6972548384859230220</link>
            <guid>6972548384859230220</guid>
            <pubDate>Mon, 05 Jul 2021 06:27:01 GMT</pubDate>
            <content:encoded><![CDATA[<p>SPA路由主要应用了hash和history来避免页面刷新,history是利用浏览历史记录栈的API实现，hash是监听location对象hash值变化事件来实现</p>
<h2>hash</h2>
<ol>
<li>hash即锚点</li>
<li><code>window.location.hash</code>可设置或获得hash值</li>
<li><code>onhashchange</code>可以监听hash的变化</li>
<li>优点是不需要服务端配合，缺点不太美观</li>
</ol>
<blockquote>
<p>几种触发onhashchange的情况</p>
</blockquote>
<ol>
<li>浏览器地址栏散列值的变化（包括浏览器的前进、后退）会触发window.location.hash值的变化，从而触发onhashchange事件</li>
<li>当浏览器地址栏中URL包含哈希如 <code>https://zzfzzf.com/#home</code>，这时按下输入，浏览器发送<code>https://zzfzzf.com</code>请求至服务器，请求完毕之后设置散列值为#home，进而触发onhashchange事件；</li>
<li>当只改变浏览器地址栏URL的哈希部分，这时按下回车，浏览器不会发送任何请求至服务器，这时发生的只是设置散列值新修改的哈希值，并触发onhashchange事件；</li>
<li>html中<code>&lt;a&gt;</code>标签的属性 href 可以设置为页面的元素ID如 #top，当点击该链接时页面跳转至该id元素所在区域，同时浏览器自动设置 window.location.hash 属性，地址栏中的哈希值也会发生改变，并触发onhashchange事件</li>
</ol>
<h2>history</h2>
<ol>
<li>window.history 属性指向 History 对象</li>
<li>history.lenght 当前窗口访问过的网址数量</li>
<li>优点是美观，缺点是需要服务端配合</li>
<li>操作路由</li>
</ol>
<pre><code class="language-js">history.back();
history.forward();
history.go(1);
history.go(-1);
history.go(0); #刷新
</code></pre>
<pre><code class="language-js">const state = { &#39;page_id&#39;: 1, &#39;user_id&#39;: 5 }
const title = &#39;&#39;
const url = &#39;hello-world.html&#39;

history.pushState(state, title, url) #不可跨域，添加 History 对象的一条记录
history.replaceState(state, title, url)#不可跨域，修改 History 对象的当前记录
</code></pre>
<p>调用history.pushState()或history.replaceState()不会触发popstate事件
只有在做出浏览器动作时，才会触发该事件，如用户点击浏览器的回退按钮（或者在Javascript代码中调用history.back()或者history.forward()方法）</p>
<pre><code class="language-js">window.addEventListener(&#39;popstate&#39;, (event) =&gt; {
  console.log(&quot;location: &quot; + document.location + &quot;, state: &quot; + JSON.stringify(event.state));
});
history.pushState({page: 1}, &quot;title 1&quot;, &quot;?page=1&quot;);
history.pushState({page: 2}, &quot;title 2&quot;, &quot;?page=2&quot;);
history.replaceState({page: 3}, &quot;title 3&quot;, &quot;?page=3&quot;);
history.back(); // Logs &quot;location: http://example.com/example.html?page=1, state: {&quot;page&quot;:1}&quot;
history.back(); // Logs &quot;location: http://example.com/example.html, state: null
history.go(2);  // Logs &quot;location: http://example.com/example.html?page=3, state: {&quot;page&quot;:3}
</code></pre>
<pre><code class="language-js">window.onpopstate = function(event) {
  console.log(&quot;location: &quot; + document.location + &quot;, state: &quot; + JSON.stringify(event.state));
};
history.pushState({page: 1}, &quot;title 1&quot;, &quot;?page=1&quot;);
history.pushState({page: 2}, &quot;title 2&quot;, &quot;?page=2&quot;);
history.replaceState({page: 3}, &quot;title 3&quot;, &quot;?page=3&quot;);
history.back(); // Logs &quot;location: http://example.com/example.html?page=1, state: {&quot;page&quot;:1}&quot;
history.back(); // Logs &quot;location: http://example.com/example.html, state: null
history.go(2);  // Logs &quot;location: http://example.com/example.html?page=3, state: {&quot;page&quot;:3}
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[rss入门]]></title>
            <link>https://zzfzzf.com/post/6972548384859230218</link>
            <guid>6972548384859230218</guid>
            <pubDate>Fri, 02 Jul 2021 20:10:40 GMT</pubDate>
            <content:encoded><![CDATA[<h2>什么是RSS</h2>
<blockquote>
<p>请查看<a href="https://zh.wikipedia.org/wiki/RSS">rss</a></p>
</blockquote>
<h2>订阅源管理</h2>
<ul>
<li><a href="https://feed43.com/">feed43</a>可以将任意网页制作成 RSS 订阅源</li>
<li><a href="https://www.inoreader.com/">inoreader</a>管理rss源</li>
<li><a href="https://feedly.com">feedly</a>管理rss源</li>
<li><a href="https://docs.rsshub.app/">rsshub</a>anything</li>
</ul>
<h2>阅读器</h2>
<ul>
<li><a href="https://reederapp.com/">reeder5</a></li>
</ul>
<h2>RssHub部署</h2>
<pre><code>docker pull diygod/rsshub
docker run -d --name rsshub -p 1200:1200 diygod/rsshub
</code></pre>
<h2>推荐关注的订阅源</h2>
<p>分为2部分，一部分为网站自带的rss服务，一部分为我们自建的rss服务</p>
<ul>
<li><a href="https://juejin.cn/rss">掘金</a></li>
<li><a href="https://rss-zzfn.vercel.app/juejin/category/frontend">掘金-分类-前端</a></li>
<li><a href="https://rss-zzfn.vercel.app/juejin/tag/%E5%89%8D%E7%AB%AF">掘金-标签-前端</a></li>
<li><a href="https://weekly.75.team/rss">奇舞周刊</a></li>
<li><a href="https://fed.taobao.org/atom.xml">淘系前端</a></li>
<li><a href="https://www.zhangxinxu.com/wordpress/feed/">张鑫旭博客</a></li>
<li><a href="http://www.ruanyifeng.com/blog/atom.xml">阮一峰博客</a></li>
<li><a href="https://rss-zzfn.vercel.app/infzm/2">南方周末</a></li>
<li><a href="https://rss-zzfn.vercel.app/juejin/posts/4089838988440024">微医前端团队</a></li>
<li><a href="https://rss-zzfn.vercel.app/juejin/posts/1926000100002232">涂鸦大前端</a></li>
<li><a href="https://rss-zzfn.vercel.app/juejin/posts/4098589725834317">字节前端</a></li>
<li><a href="https://weekly.zoo.team/rss/zooTeam/blog">政采云前端</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[linux服务器监控]]></title>
            <link>https://zzfzzf.com/post/6972548384859230217</link>
            <guid>6972548384859230217</guid>
            <pubDate>Thu, 01 Jul 2021 04:31:54 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>在线演示 <a href="https://tz.cloudcpp.com/">https://tz.cloudcpp.com/</a></p>
</blockquote>
<h2>服务端安装</h2>
<ol>
<li>获取配置文件</li>
</ol>
<pre><code class="language-bash">wget https://raw.githubusercontent.com/cppla/ServerStatus/master/autodeploy/config.json
</code></pre>
<ol start="2">
<li>修改相关配置</li>
<li>启动docker</li>
</ol>
<pre><code class="language-bash">docker run -d --restart=always --name=serverstatus -v ~/config.json:/ServerStatus/server/config.json -p 80:80 -p 35601:35601 cppla/serverstatus:latest
</code></pre>
<h2>客户端安装</h2>
<pre><code class="language-bash">wget --no-check-certificate -qO client-linux.py &#39;https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py&#39; &amp;&amp; nohup python client-linux.py SERVER=45.79.67.132 USER=s04  &gt;/dev/null 2&gt;&amp;1 &amp;
</code></pre>
<h3>附录</h3>
<ul>
<li><a href="https://github.com/cppla/ServerStatus">https://github.com/cppla/ServerStatus</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[react 定时器]]></title>
            <link>https://zzfzzf.com/post/6972548384859230216</link>
            <guid>6972548384859230216</guid>
            <pubDate>Wed, 30 Jun 2021 04:54:26 GMT</pubDate>
            <content:encoded><![CDATA[<ol>
<li>方案1</li>
</ol>
<pre><code>import { useEffect, useState } from &quot;react&quot;;

export default function App() {
  const [count, setCount] = useState(10);
  useEffect(() =&gt; {
    setTimeout(() =&gt; {
      setCount(count - 1);
    }, 1000);
  }, [count]);
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;h1&gt;{count}&lt;/h1&gt;
      &lt;button onClick={() =&gt; setCount((c) =&gt; c - 1)}&gt;
        Edit to see some magic happen!
      &lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<ol start="2">
<li>方案2</li>
</ol>
<pre><code>
</code></pre>
<ol start="3">
<li>方案3</li>
</ol>
<pre><code>useInterval
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[canal同步es]]></title>
            <link>https://zzfzzf.com/post/6972548384859230215</link>
            <guid>6972548384859230215</guid>
            <pubDate>Sun, 27 Jun 2021 17:24:39 GMT</pubDate>
            <content:encoded><![CDATA[<h2>基础</h2>
<p>安装好<a href="https://zzfzzf.com/post/e32795747ae3beb941e10eaa996d2ce9">es</a>
本文使用版本<code>canal</code>1.1.15
<a href="https://github.com/alibaba/canal">https://github.com/alibaba/canal</a></p>
<h2>server端安装</h2>
<h3>docker安装</h3>
<pre><code>sh run.sh -e canal.auto.scan=false \
          -e canal.destinations=blog \
          -e canal.instance.master.address=106.14.239.36:3306  \
          -e canal.instance.dbUsername=zzf-dev  \
          -e canal.instance.dbPassword=YaThM7reZJjXt4pj  \
          -e canal.instance.connectionCharset=UTF-8 \
          -e canal.instance.tsdb.enable=true \
          -e canal.instance.gtidon=false
</code></pre>
<h3>安装包安装</h3>
<ol>
<li>下载</li>
</ol>
<pre><code class="language-bash">wget https://github.com/alibaba/canal/releases/download/canal-1.1.5/canal.adapter-1.1.5.tar.gz
</code></pre>
<ol start="2">
<li>修改配置</li>
</ol>
<pre><code>#conf/example/instance.properties
## mysql serverId
canal.instance.mysql.slaveId = 1234
#position info，需要改成自己的数据库信息
canal.instance.master.address = 127.0.0.1:3306 #修改数据库地址
canal.instance.master.journal.name = 
canal.instance.master.position = 
canal.instance.master.timestamp = 
#canal.instance.standby.address = 
#canal.instance.standby.journal.name =
#canal.instance.standby.position = 
#canal.instance.standby.timestamp = 
#username/password，需要改成自己的数据库信息
canal.instance.dbUsername = canal  #修改数据库账号
canal.instance.dbPassword = canal  #修改数据库密码
canal.instance.defaultDatabaseName =
canal.instance.connectionCharset = UTF-8
#table regex
canal.instance.filter.regex = .\*\\\\..\*
</code></pre>
<ol start="3">
<li>启动</li>
</ol>
<pre><code>sh bin/startup.sh 
cat logs/canal/canal.log
cat logs/example/example.log
sh bin/stop.sh
</code></pre>
<h2>es适配器</h2>
<ol>
<li>下载<a href="https://github.com/alibaba/canal/releases/download/canal-1.1.5-alpha-2/canal.adapter-1.1.5-SNAPSHOT.tar.gz">适配器</a>，不要下载正式版，有bug</li>
<li>修改启动配置</li>
</ol>
<pre><code>#application.yml
canal.conf:
  canalServerHost: 127.0.0.1:11111
  batchSize: 500
  syncBatchSize: 1000
  retries: 0
  timeout:
  mode: tcp 
  srcDataSources:
    defaultDS:
      url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true  #修改数据库链接
      username: root #修改数据库账号
      password: 121212 #修改数据库密码
  canalAdapters:
  - instance: example 
    groups:
    - groupId: g1
      outerAdapters:
      - 
        key: exampleKey
        name: es7                           #修改为es7
        hosts: 127.0.0.1:9300               #es 集群地址, 逗号分隔
        properties:
          mode: transport # or rest         #可指定transport模式或者rest模式
          cluster.name: elasticsearch       #es cluster name
</code></pre>
<ol start="3">
<li>修改映射配置</li>
</ol>
<pre><code>dataSourceKey: defaultDS        # 源数据源的key, 对应上面配置的srcDataSources中的值
outerAdapterKey: exampleKey     # 对应application.yml中es配置的key 
destination: example            # cannal的instance或者MQ的topic
groupId:                        # 对应MQ模式下的groupId, 只会同步对应groupId的数据
esMapping:
  _index: mytest_user           # es 的索引名称
  _type: _doc                   # es 的type名称, es7下无需配置此项
  _id: _id                      # es 的_id, 如果不配置该项必须配置下面的pk项_id则会由es自动分配
#  pk: id                       # 如果不需要_id, 则需要指定一个属性为主键属性
  # sql映射
  sql: &quot;select a.id as _id, a.name as _name, a.role_id as _role_id, b.role_name as _role_name,
        a.c_time as _c_time, c.labels as _labels from user a
        left join role b on b.id=a.role_id
        left join (select user_id, group_concat(label order by id desc separator &#39;;&#39;) as labels from label
        group by user_id) c on c.user_id=a.id&quot;
#  objFields:
#    _labels: array:;           # 数组或者对象属性, array:; 代表以;字段里面是以;分隔的
#    _obj: object               # json对象
  etlCondition: &quot;where a.c_time&gt;=&#39;{0}&#39;&quot;     # etl 的条件参数
  commitBatch: 3000                         # 提交批大小
</code></pre>
<ol start="4">
<li>启动</li>
</ol>
<pre><code>sh bin/startup.sh 
sh bin/stop.sh
</code></pre>
<h2>es同步</h2>
<ol>
<li>初始化索引</li>
</ol>
<pre><code>put
http://127.0.0.1:9200/blog
{
    &quot;settings&quot;: {
        &quot;analysis.analyzer.default.type&quot;: &quot;ik_max_word&quot;,
        &quot;number_of_shards&quot;: 5,
        &quot;number_of_replicas&quot;: 0
    },
    &quot;mappings&quot;: {
        &quot;properties&quot;: {
            &quot;id&quot;: {
                &quot;type&quot;: &quot;long&quot;
            },
            &quot;title&quot;: {
                &quot;type&quot;: &quot;text&quot;,
                &quot;analyzer&quot;: &quot;ik_max_word&quot;,
                &quot;search_analyzer&quot;: &quot;ik_smart&quot;
            },
            &quot;tag&quot;: {
                &quot;type&quot;: &quot;keyword&quot;
            },
            &quot;tag_desc&quot;: {
                &quot;type&quot;: &quot;text&quot;,
                &quot;analyzer&quot;: &quot;ik_max_word&quot;,
                &quot;search_analyzer&quot;: &quot;ik_smart&quot;
            },
            &quot;is_delete&quot;: {
                &quot;type&quot;: &quot;short&quot;
            },
            &quot;is_release&quot;: {
                &quot;type&quot;: &quot;short&quot;
            },
            &quot;content&quot;: {
                &quot;type&quot;: &quot;text&quot;,
                &quot;analyzer&quot;: &quot;ik_max_word&quot;,
                &quot;search_analyzer&quot;: &quot;ik_smart&quot;
            }
        }
    }
}
</code></pre>
<ol start="2">
<li>全量同步</li>
</ol>
<pre><code>post
curl -X POST http://127.0.0.1:8081/etl/es7/mytest_user.yml
</code></pre>
<h2>F&amp;Q</h2>
<ul>
<li>如果系统是1个cpu，需要将canal.instance.parser.parallel设置为false</li>
<li>启动失败一般来说只需调整startup里的内存配置即可</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[网站性能优化]]></title>
            <link>https://zzfzzf.com/post/6972548384859230214</link>
            <guid>6972548384859230214</guid>
            <pubDate>Wed, 23 Jun 2021 15:58:06 GMT</pubDate>
            <content:encoded><![CDATA[<p><img src="https://z3.ax1x.com/2021/06/24/RQ5Q8s.gif" alt="video"></p>
<h2>概述</h2>
<blockquote>
<p>前端性能优化主要从加载性能和渲染性能两个方面去优化。</p>
</blockquote>
<h2>性能指标</h2>
<blockquote>
<p>几个基本概念</p>
</blockquote>
<ol>
<li><p>FCP</p>
<blockquote>
<p>First Contentful Paint
首次内容绘制，即白屏时间，页面有了第一个内容开始绘制</p>
</blockquote>
</li>
<li><p>LCP</p>
<blockquote>
<p>Largest Contentful Paint
最大内容绘制，即当页面的所有内容都已显示时。</p>
</blockquote>
</li>
<li><p>CLS</p>
<blockquote>
<p>Cumulative Layout Shift
加载时页面元素移动了多少。</p>
</blockquote>
</li>
<li><p>FID</p>
<blockquote>
<p>First Input Delay
页面对用户的第一次交互做出反应的时间。</p>
</blockquote>
</li>
</ol>
<h2>性能测试</h2>
<ul>
<li><a href="https://www.webpagetest.org/">https://www.webpagetest.org/</a></li>
<li><a href="https://web.dev/measure/">https://web.dev/measure/</a></li>
<li><a href="https://developers.google.com/speed/pagespeed/insights/">PageSpeedInsights</a></li>
<li><a href="https://search.google.com/">Search Console</a></li>
<li><a href="https://developers.google.com/web/tools/chrome-user-experience-report">Chrome User Experience Report</a></li>
</ul>
<h2>优化加载性能</h2>
<blockquote>
<p>缓存 -&gt; 发送请求 -&gt; 等待响应 -&gt; 解析 -&gt; 处理各类静态资源 -&gt; 运行时 -&gt; 预加载（等待后续的请求）</p>
</blockquote>
<blockquote>
<p>主要思路是压缩资源，提高传输速度</p>
</blockquote>
<ul>
<li><p>静态资源压缩，精灵图</p>
</li>
<li><p>图片、路由懒加载 预加载</p>
</li>
<li><p>强制缓存和协商缓存</p>
</li>
<li><p>压缩图片、css、js</p>
</li>
<li><p>cdn、oss</p>
</li>
<li><p>做缓存</p>
</li>
<li><p>Gzip压缩</p>
</li>
</ul>
<h2>优化渲染性能</h2>
<ul>
<li><p><strong>CSS</strong>：基于CSS规则</p>
</li>
<li><p><strong>DOM</strong>：基于DOM操作</p>
</li>
<li><p><strong>阻塞</strong>：基于脚本加载</p>
</li>
<li><p><strong>异步更新</strong>：基于异步更新</p>
</li>
<li><p>减少回流和重绘制</p>
</li>
<li><p>css和js脚本阻塞渲染，可以懒加载或者SSR加快首屏时间，css，js放置正确位置</p>
</li>
</ul>
<h2>性能监控</h2>
<blockquote>
<p>性能优化不是一蹴而就，需要性能监控体系去持续优化</p>
</blockquote>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[虚拟列表的实现]]></title>
            <link>https://zzfzzf.com/post/6972548384859230212</link>
            <guid>6972548384859230212</guid>
            <pubDate>Tue, 22 Jun 2021 02:01:16 GMT</pubDate>
            <content:encoded><![CDATA[<p>虚拟列表即可视区域渲染</p>
<p><img src="https://cdn.zzfzzf.com/1624328023287LWw6HT.png" alt="例子"></p>
<p>虚拟列表分为动态高度和固定高度</p>
<p>不考虑任何可变因素，实现一个最简单的例子，假定列表的li标签高度为30px,可视区域大小为300px,所以，我们在上下滚动时，可视元素为10个左右，只需要考虑如何渲染这10个元素</p>
<p>例子采用react实现，原生和vue同理</p>
<p>思路</p>
<ul>
<li>虚拟列表的表现形式和普通列表一样，但是只渲染10个元素，我们就需要一个空的元素去撑起滚动条，给这个元素虚拟列表本应该有的高度，就可以模拟滚动条</li>
<li>虚拟列表可视区域渲染的个数时固定的，我们只需要知道当前滚动的scrollTop再除每一项的高度，就可以知道已经滚过去了多少项，拿到当前可视区域的起始序号，再总<code>list.slice(start,end)</code>即可</li>
</ul>
<pre><code class="language-jsx">import { useEffect, useRef, useState, Profiler } from &quot;react&quot;;
import &quot;./styles.css&quot;;
const total = 10000;
const arr = Array.from({ length: total }).map((_, idx) =&gt; idx);
export default function App() {
  const appRef = useRef();
  const [start, setStart] = useState(0);
  const [scrollTop, setScrollTop] = useState(0);
  const [offset, setOffset] = useState(0);
  const [list, setList] = useState([]);
  const onScroll = (e) =&gt; {
    requestAnimationFrame(() =&gt; {
      setScrollTop(e.target.scrollTop);
      let start = Math.floor(e.target.scrollTop / 30);
      setStart(start);
      let end = start + 10;
      setList(arr.slice(start, end));
    });
  };
  useEffect(() =&gt; {
    const container = appRef.current;
    container.addEventListener(&quot;scroll&quot;, onScroll);
  }, []);
  useEffect(() =&gt; {
    setOffset(start * 30);
  }, [start]);
  function callBack(...arg) {
    console.log(arg[2]);
  }
  return (
    &lt;div&gt;
      {scrollTop}
      &lt;Profiler id=&quot;vl&quot; onRender={callBack}&gt;
        &lt;div ref={appRef} className=&quot;App&quot;&gt;
          &lt;div className=&quot;mask&quot; style={{ height: &quot;300000px&quot; }}&gt;&lt;/div&gt;
          &lt;ul style={{ transform: `translate3d(0, ${offset}px, 0)` }}&gt;
            {list.map((item) =&gt; (
              &lt;li key={item}&gt;{item}&lt;/li&gt;
            ))}
          &lt;/ul&gt;
        &lt;/div&gt;
      &lt;/Profiler&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<pre><code class="language-css">.App {
  font-family: sans-serif;
  text-align: center;
  background-color: aqua;
  height: 300px;
  overflow-y: auto;
  position: relative;
}
.mask {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}
ul {
  transform: translate3d(0, 0, 0);
}
li {
  height: 30px;
}
li:nth-of-type(odd) {
  background-color: #f00;
}
li:nth-of-type(even) {
  background-color: #0f0;
}
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[循环和闭包]]></title>
            <link>https://zzfzzf.com/post/6972548384859230210</link>
            <guid>6972548384859230210</guid>
            <pubDate>Sat, 12 Jun 2021 03:55:02 GMT</pubDate>
            <content:encoded><![CDATA[<pre><code class="language-js">for (var i = 1; i &lt;= 5; i++) {
    setTimeout(()=&gt;{
        console.log(i);//Mutable variable is accessible from closure 
    },i*1000);
}
</code></pre>
<blockquote>
<p><strong>预期结果</strong>输出1到5，每隔一秒，一秒一个<br><strong>实际结果</strong>输出6个6，每隔一秒，一秒一个</p>
</blockquote>
<h2>为什么</h2>
<p>延迟函数的回掉会在循环结束时才执行。即使setTimeout(()=&gt;{},0),回调依然是在循环结束后才会被执行。因此会每次输出一个6。</p>
<h2>怎么改</h2>
<h3>ES5</h3>
<pre><code class="language-js">for (var i = 1; i &lt;= 5; i++) {
    (function () {
        var j=i
        setTimeout(()=&gt; {
            console.log(j);
        }, j * 1000)
    })()
}
</code></pre>
<pre><code class="language-js">for (var i = 1; i &lt;= 5; i++) {
    (function (j) {
        setTimeout(()=&gt; {
            console.log(j);
        }, j * 1000)
    })(i)
}
</code></pre>
<pre><code class="language-js">for (var i = 1; i&lt;=5; i++) { 
   setTimeout( 
       function timer(j) { 
           console.log(j) 
       }, 
       i * 1000, 
       i 
   ) 
} 
</code></pre>
<h3>ES6</h3>
<pre><code class="language-js">for (var i = 1; i &lt;= 5; i++) {
    let j=i
    setTimeout(() =&gt; {
        console.log(j);
    }, j * 1000);
}
</code></pre>
<pre><code class="language-js">for (let i = 1; i &lt;= 5; i++) {
    setTimeout(() =&gt; {
        console.log(i);
    }, i * 1000);
}
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[技术周报·2021-06-11]]></title>
            <link>https://zzfzzf.com/post/6972548384859230209</link>
            <guid>6972548384859230209</guid>
            <pubDate>Fri, 11 Jun 2021 08:20:58 GMT</pubDate>
            <content:encoded><![CDATA[<ul>
<li><a href="https://greyireland.gitbook.io/algorithm-pattern/">算法快速入门</a></li>
<li><a href="https://juejin.cn/post/6971254859643224095">位运算</a></li>
<li><a href="https://github.com/firstcontributions/first-contributions">提供第一个PR</a></li>
<li><a href="https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/">函数式编程</a></li>
<li><a href="https://github.com/Tencent/secguide">代码安全指南</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[从零理解redux]]></title>
            <link>https://zzfzzf.com/post/6972548384859230208</link>
            <guid>6972548384859230208</guid>
            <pubDate>Mon, 07 Jun 2021 15:58:34 GMT</pubDate>
            <content:encoded><![CDATA[<p>理解本文，你需要以下基础知识</p>
<ol>
<li>纯函数</li>
<li>compose函数</li>
</ol>
<h2>三大原则</h2>
<ol>
<li>单一数据源</li>
<li>State 是只读的</li>
<li>使用纯函数来执行修改</li>
</ol>
<h2>Redux架构</h2>
<ul>
<li><strong>state</strong>: 全局的状态对象，唯一且不可变。</li>
<li><strong>store</strong>: 调用createStore 函数生成的对象，里面封入了定义在createStore内部用于操作全局状态的方法，用户通过这些方法使用Redux。</li>
<li><strong>action</strong>: 描述状态如何修改的对象，固定拥有一个type属性，通过store的dispatch方法提交。</li>
<li><strong>reducer</strong>: 实际执行状态修改的纯函数，由用户定义并传入，接收来自dispatch的action作为参数，计算返回全新的状态，完成state的更新，然后执行订阅的监听函数。</li>
<li><strong>storeEnhancer</strong>: createStore的高阶函数封装，用于加强store的能力，redux提供的applyMiddleware是官方实现的一个storeEnhancer。</li>
<li><strong>middleware</strong>: dispatch的高阶函数封装，由applyMiddleware把原dispatch替换为包含middleware链式调用的实现。</li>
</ul>
<h2>简单实现</h2>
<p>理解redux的方法当然是取实现一个<code>tiny-redux</code>啦，进入<a href="https://github.com/reduxjs/redux">官方仓库</a>，我们可以看到一个最简单的使用示例</p>
<pre><code class="language-js">import { createStore } from &#39;redux&#39;

function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case &#39;counter/incremented&#39;:
      return { value: state.value + 1 }
    case &#39;counter/decremented&#39;:
      return { value: state.value - 1 }
    default:
      return state
  }
}

let store = createStore(counterReducer)

store.subscribe(() =&gt; console.log(store.getState()))

store.dispatch({ type: &#39;counter/incremented&#39; })
// {value: 1}
store.dispatch({ type: &#39;counter/incremented&#39; })
// {value: 2}
store.dispatch({ type: &#39;counter/decremented&#39; })
// {value: 1}
</code></pre>
<h2>链接</h2>
<ul>
<li><a href="https://github.com/zzfn/tiny-redux">tiny-redux</a></li>
<li><a href="https://github.com/reduxjs/redux">redux</a></li>
<li><a href="https://github.com/reduxjs/react-redux">react-redux</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[深入理解JavaScript-对象]]></title>
            <link>https://zzfzzf.com/post/6972548384855035924</link>
            <guid>6972548384855035924</guid>
            <pubDate>Thu, 03 Jun 2021 16:23:58 GMT</pubDate>
            <content:encoded><![CDATA[<h2>创建对象</h2>
<h2>存在性</h2>
<p>判断对象中是否存在某个属性一般来说适用3种方法</p>
<ol>
<li><code>in</code> 操作符，缺点是会判断继承过来的属性</li>
<li>hasOwnProterty</li>
<li>对于Object.create(null)创建的对象需要使用<code>Object.prototype.hasOwnProterty.call(myobje,&#39;a&#39;)</code>来判断</li>
<li>Reflect.has({},&quot;toString&quot;)</li>
<li>Object.hasOwn()</li>
</ol>
<blockquote>
<p>tips
in 操作符检查的是key键 of是值</p>
</blockquote>
<h2>可枚举</h2>
<h2>遍历k</h2>
<ol>
<li>for...in</li>
</ol>
<p><strong><code>for...in</code>语句</strong>以任意顺序遍历一个对象的除<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol">Symbol</a>以外的<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Enumerability_and_ownership_of_properties">可枚举</a>属性。</p>
<p>遍历对象及其原型链上可枚举的属性</p>
<ol start="2">
<li><p>Object.key()</p>
</li>
<li><p>返回对象自身可枚举属性组成的数组</p>
</li>
<li><p>不会遍历对象原型链上的属性以及 Symbol 属性</p>
</li>
<li><p>对数组的遍历顺序和 for in 一致</p>
</li>
<li><p>Object.getOwnPropertyNames</p>
</li>
</ol>
<table>
<thead>
<tr>
<th>----------------------------</th>
<th>含原型属性</th>
<th>含不可枚举</th>
<th>含 Symbol 属性</th>
<th>返回值</th>
</tr>
</thead>
<tbody><tr>
<td>for...in</td>
<td>是</td>
<td>否</td>
<td>否</td>
<td>key</td>
</tr>
<tr>
<td>Object.keys</td>
<td>否</td>
<td>否</td>
<td>否</td>
<td>[key...]</td>
</tr>
<tr>
<td>Object.getOwnPropertyNames</td>
<td>否</td>
<td>是</td>
<td>否</td>
<td>[key...]</td>
</tr>
<tr>
<td>Object.getOwnPropertySymbols</td>
<td>否</td>
<td>是(只有 symbol)</td>
<td>是</td>
<td>[key...]</td>
</tr>
<tr>
<td>Reflect.ownKeys</td>
<td>否</td>
<td>是</td>
<td>是</td>
<td>[key...]</td>
</tr>
<tr>
<td>Object.values</td>
<td>否</td>
<td>否</td>
<td>否</td>
<td>[value...]</td>
</tr>
<tr>
<td>Object.entries</td>
<td>否</td>
<td>否</td>
<td>否</td>
<td>[[key,value]...]</td>
</tr>
</tbody></table>
<h2>map和对象</h2>
<p>Map:</p>
<ul>
<li>Map有一个大小的属性，Object没有一个内置的方法去查询他的大小；</li>
<li>Map是直接可迭代的，而Object不是；</li>
<li>Map在使用方面更具有灵活性，可以使用任何数据类型作为键；</li>
<li>Map保留了数据插入的顺序。</li>
</ul>
<p>Object：</p>
<ul>
<li>可以完美地使用JSON.parse()和JSON.stringify()去和JSON格式数据去做一个转换；</li>
<li>可以使用 Object.key 直接访问属性的值，不需向Map一样使用get()。</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[TypeScript进阶]]></title>
            <link>https://zzfzzf.com/post/6972548384855035923</link>
            <guid>6972548384855035923</guid>
            <pubDate>Tue, 01 Jun 2021 15:22:28 GMT</pubDate>
            <content:encoded><![CDATA[<h2>官网</h2>
<ul>
<li><a href="https://www.typescriptlang.org/">https://www.typescriptlang.org/</a></li>
<li><a href="https://devblogs.microsoft.com/typescript/">https://devblogs.microsoft.com/typescript/</a></li>
</ul>
<h2>工作原理</h2>
<ul>
<li>TypeScript 源码经过扫描器扫描之后变成一系列 Token；</li>
<li>解析器解析 token，得到一棵 AST 语法树；</li>
<li>绑定器遍历 AST 语法树，生成一系列 Symbol，并将这些 Symbol 连接到对应的节点上；</li>
<li>检查器再次扫描 AST，检查类型，并将错误收集起来；</li>
<li>发射器根据 AST 生成 JavaScript 代码。</li>
</ul>
<h2>操作符</h2>
<h2>函数</h2>
<pre><code class="language-typescript">//定义
//1
const add = (num1: number, num2: number): number =&gt; num1 + num2
//2
const add: (num1: number, num2: number) =&gt; number = (num1, num2) =&gt; num1 + num2
//3
type Add = (num1: number, num2: number) =&gt; number
const add: Add = (num1, num2) =&gt; num1 + num2
//4
let add: (num1: number, num2: number) =&gt; number
add = (num1: number, num2: number): number =&gt; num1 + num2
//可选参数必须放最后一个
const add = (num1: number, num2?: number): number =&gt; num1 + num2 ?? 0
//默认值
const add = (num1: number, num2: number = 0): number =&gt; num1 + num2
//有默认值也可以不给类型，会自动推断
const add = (num1: number, num2 = 0): number =&gt; num1 + num2
//重载，需要使用function
function fun(arg: string): string[]
function fun(arg: number): number 
</code></pre>
<h2>泛型</h2>
<h2>泛型工具</h2>
<ol>
<li>Required<T> 必选</li>
<li>Partial<T> 可选</li>
<li>Omit&lt;T, K&gt; 忽略属性</li>
<li>Pick&lt;T, K&gt; 提取属性</li>
<li>Record&lt;K, T&gt; 键值对</li>
<li>Exclude&lt;T, U&gt; 去除交集</li>
<li>ReturnType<T>获取函数的返回类型</li>
</ol>
<h2>常见问题</h2>
<ol>
<li>interface和type</li>
<li>类型定义放哪里合适</li>
</ol>
<h2>配置文件</h2>
<pre><code class="language-json">//tsconfig.json
{
  &quot;compilerOptions&quot;: {

    /* 基本选项 */
    &quot;target&quot;: &quot;es5&quot;,                       // 指定 ECMAScript 目标版本: &#39;ES3&#39; (default), &#39;ES5&#39;, &#39;ES6&#39;/&#39;ES2015&#39;, &#39;ES2016&#39;, &#39;ES2017&#39;, or &#39;ESNEXT&#39;
    &quot;module&quot;: &quot;commonjs&quot;,                  // 指定使用模块: &#39;commonjs&#39;, &#39;amd&#39;, &#39;system&#39;, &#39;umd&#39; or &#39;es2015&#39;
    &quot;lib&quot;: [],                             // 指定要包含在编译中的库文件
    &quot;allowJs&quot;: true,                       // 允许编译 javascript 文件
    &quot;checkJs&quot;: true,                       // 报告 javascript 文件中的错误
    &quot;jsx&quot;: &quot;preserve&quot;,                     // 指定 jsx 代码的生成: &#39;preserve&#39;, &#39;react-native&#39;, or &#39;react&#39;
    &quot;declaration&quot;: true,                   // 生成相应的 &#39;.d.ts&#39; 文件
    &quot;sourceMap&quot;: true,                     // 生成相应的 &#39;.map&#39; 文件
    &quot;outFile&quot;: &quot;./&quot;,                       // 将输出文件合并为一个文件
    &quot;outDir&quot;: &quot;./&quot;,                        // 指定输出目录
    &quot;rootDir&quot;: &quot;./&quot;,                       // 用来控制输出目录结构 --outDir.
    &quot;removeComments&quot;: true,                // 删除编译后的所有的注释
    &quot;noEmit&quot;: true,                        // 不生成输出文件
    &quot;importHelpers&quot;: true,                 // 从 tslib 导入辅助工具函数
    &quot;isolatedModules&quot;: true,               // 将每个文件作为单独的模块 （与 &#39;ts.transpileModule&#39; 类似）.

    /* 严格的类型检查选项 */
    &quot;strict&quot;: true,                        // 启用所有严格类型检查选项
    &quot;noImplicitAny&quot;: true,                 // 在表达式和声明上有隐含的 any类型时报错
    &quot;strictNullChecks&quot;: true,              // 启用严格的 null 检查
    &quot;noImplicitThis&quot;: true,                // 当 this 表达式值为 any 类型的时候，生成一个错误
    &quot;alwaysStrict&quot;: true,                  // 以严格模式检查每个模块，并在每个文件里加入 &#39;use strict&#39;

    /* 额外的检查 */
    &quot;noUnusedLocals&quot;: true,                // 有未使用的变量时，抛出错误
    &quot;noUnusedParameters&quot;: true,            // 有未使用的参数时，抛出错误
    &quot;noImplicitReturns&quot;: true,             // 并不是所有函数里的代码都有返回值时，抛出错误
    &quot;noFallthroughCasesInSwitch&quot;: true,    // 报告 switch 语句的 fallthrough 错误。（即，不允许 switch 的 case 语句贯穿）

    /* 模块解析选项 */
    &quot;moduleResolution&quot;: &quot;node&quot;,            // 选择模块解析策略： &#39;node&#39; (Node.js) or &#39;classic&#39; (TypeScript pre-1.6)
    &quot;baseUrl&quot;: &quot;./&quot;,                       // 用于解析非相对模块名称的基目录
    &quot;paths&quot;: {},                           // 模块名到基于 baseUrl 的路径映射的列表
    &quot;rootDirs&quot;: [],                        // 根文件夹列表，其组合内容表示项目运行时的结构内容
    &quot;typeRoots&quot;: [],                       // 包含类型声明的文件列表
    &quot;types&quot;: [],                           // 需要包含的类型声明文件名列表
    &quot;allowSyntheticDefaultImports&quot;: true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    &quot;sourceRoot&quot;: &quot;./&quot;,                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    &quot;mapRoot&quot;: &quot;./&quot;,                       // 指定调试器应该找到映射文件而不是生成文件的位置
    &quot;inlineSourceMap&quot;: true,               // 生成单个 soucemaps 文件，而不是将 sourcemaps 生成不同的文件
    &quot;inlineSources&quot;: true,                 // 将代码与 sourcemaps 生成到一个文件中，要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    &quot;experimentalDecorators&quot;: true,        // 启用装饰器
    &quot;emitDecoratorMetadata&quot;: true          // 为装饰器提供元数据的支持
  }
}
</code></pre>
<h2>附录</h2>
<p><a href="https://jkchao.github.io/typescript-book-chinese/">参考1</a></p>
<p><a href="https://juejin.cn/post/6926794697553739784">参考2</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[技术周报·2021-05-28]]></title>
            <link>https://zzfzzf.com/post/6972548384855035922</link>
            <guid>6972548384855035922</guid>
            <pubDate>Fri, 28 May 2021 15:35:25 GMT</pubDate>
            <content:encoded><![CDATA[<ul>
<li><a href="https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/README.md">babel手册</a></li>
<li><a href="https://juejin.cn/post/6966119324478079007">从 0 构建自己的脚手架知识体系</a></li>
<li><a href="https://juejin.cn/post/6965761736083243044">前端性能优化——图片篇</a></li>
<li><a href="https://juejin.cn/post/6964357725652254734">前端水印实现方案</a></li>
<li><a href="https://juejin.cn/post/6844904035271573511">手把手带你入门 AST 抽象语法树</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[EventEmitter]]></title>
            <link>https://zzfzzf.com/post/6972548384855035921</link>
            <guid>6972548384855035921</guid>
            <pubDate>Fri, 28 May 2021 06:14:20 GMT</pubDate>
            <content:encoded><![CDATA[<p>需要实现以下api</p>
<ul>
<li>on(event, listener)：为指定事件注册一个监听器，接受一个字符串 event 和一个回调函数。</li>
<li>emit(event, [arg1], [arg2])： 按监听器的顺序执行执行每个监听器</li>
<li>once(event, listener): 和on类似，但只触发一次，随后便解除事件监听</li>
<li>off(event, listener)： 移除指定事件的某个监听回调</li>
<li>offAll([event])：移除指定事件的所有监听回调</li>
<li>setMaxListeners(n)：用于提高监听器的默认限制的数量。（默认10监听回调个产生警告）</li>
<li>listeners(event)： 返回指定事件的监听器数组。</li>
</ul>
<pre><code class="language-ts">class EventEmitter {
    private readonly listeners: Record&lt;string, any&gt;
    private maxListener: number

    constructor(maxListener = 10) {
        this.listeners = {}
        this.maxListener = maxListener
    }

    /**
     * 订阅
     * @param event
     * @param cb
     */
    on(event: string, cb: any) {
        if (this.listeners[event]?.length &gt;= this.maxListener) {
            throw (&#39;超出最大监听限制&#39;)
        }
        if (Array.isArray(this.listeners[event])) {
            this.listeners[event].push(cb)
        } else {
            this.listeners[event] = [].concat(cb)
        }

    }

    /**
     * 订阅一次
     * @param event
     * @param cb
     */
    once(event: string, cb: any) {

    }

    /**
     * 取消订阅
     * @param event
     * @param cb
     */
    off(event: string, cb: any) {
        this.listeners[event]=this.listeners[event].filter((n: any) =&gt; n !== cb)
    }

    /**
     * 发布
     * @param event
     * @param arg
     */
    emit(event: string, ...arg: any[]) {
        this.listeners[event].forEach((cb: any) =&gt; {
            cb.apply(null, arg)
        })
    }
}

export {EventEmitter}
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[技术周报·2021-05-21]]></title>
            <link>https://zzfzzf.com/post/6972548384855035919</link>
            <guid>6972548384855035919</guid>
            <pubDate>Fri, 21 May 2021 14:30:02 GMT</pubDate>
            <content:encoded><![CDATA[<ul>
<li><a href="https://juejin.cn/post/6964357725652254734">前端水印实现方案</a></li>
<li><a href="https://juejin.cn/post/6963904955262435336">CSS 实现多行文本“展开收起”</a></li>
<li><a href="https://juejin.cn/post/6964555379313213470">原理解析：如何实现自己的脚手架工具？</a></li>
<li><a href="https://juejin.cn/post/6964364929377779719">Postcss了解一下</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[webpack5热更新]]></title>
            <link>https://zzfzzf.com/post/6972548384855035918</link>
            <guid>6972548384855035918</guid>
            <pubDate>Fri, 21 May 2021 08:08:02 GMT</pubDate>
            <content:encoded><![CDATA[<h2>webpack热更新</h2>
<h2>概念</h2>
<ul>
<li>热更新类似于局部刷新，因此本质就是想办法拿到那些发生改变了的代码 ，然后重新打包到界面上</li>
<li>webpack 内部实现了 HotModuleReplacement 插件，它可以生成二个文件，一个是 json ， 一个是 js</li>
<li>第一次打包时不存在热更新，正常走打包逻辑，后续以 watch 的模式监听文件系统当中的文件变化</li>
<li>一旦某个文件发生了变更之后就会重新执行打包动作产出新的内容，然后再将内容变更通知到浏览器</li>
<li>浏览器获取到变更信息之后就会想办法来获取到实际发生了变更的文件内容，然后执行回调展示在界面上</li>
<li>在 webpack 的运行时中 <code>__webpack__modules__</code> 用以维护所有的模块。而热模块替换的原理，即通过 <code>chunk</code> 的方式加载最新的 <code>modules</code>，找到 <code>__webpack__modules__</code> 中对应的模块逐一替换，并删除其上下缓存。</li>
</ul>
<h2>使用</h2>
<pre><code class="language-js">// webpack.config.js
const HtmlWebpackPlugin = require(&#39;html-webpack-plugin&#39;);
const path = require(&quot;path&quot;);

module.exports = {
    target: &#39;web&#39;,
    mode: &quot;development&quot;,
    entry: &#39;./index.js&#39;,
    output: {
        filename: &#39;[name].bundle.js&#39;,
        path: path.resolve(__dirname, &#39;dist&#39;)
    },
    plugins: [
        new HtmlWebpackPlugin({title: &#39;Hot Module Replacement&#39;})
    ],
    devtool: &#39;source-map&#39;,
    devServer: {
        // inline:false,
        // hot:false
        // hotOnly: true
    }
}
</code></pre>
<ol>
<li><p>启动webpack <code>webpack serve</code> <img src="https://oss-zzf.zzfzzf.com/cdn/1643039300212yG4Ll8.png" alt="1622105686449sJDT5x"> </p>
</li>
<li><p>打开浏览器，修改js，浏览器自动刷新</p>
</li>
<li><p>修改配置 <code>inline置为true</code></p>
</li>
<li><p>修改代码，浏览器无刷新</p>
</li>
</ol>
<h2>原理</h2>
<ol start="0">
<li>监听文件的变化，node的fs监听文件，触发重新编译，因为dev是存在内存中的，使用的是<code>memory-fs</code>，新文件产出之后如何通知到浏览器（基于 websocket 通信）</li>
<li>devServer通过websocket通知文件更新，将新模块的hash用json格式发送给浏览器</li>
<li>浏览器收到服务端发来的消息，收到type为hash的消息，存下hash，收到type为ok的消息，执行reload操作</li>
<li>如果配置了热更新，通过hash以jsonp方式执行热更新，没有热更新就刷新</li>
<li>先找出过期的模块及其依赖；从缓存中删除过期的模块和依赖；将新的模块添加到modules中</li>
</ol>
<h2>F&amp;Q</h2>
<ol>
<li><code>hot</code>和<code>hotOnly</code>的区别</li>
</ol>
<blockquote>
<p>hot与hotOnly都会开启HMR热更新，但是对于不支持HMR的资源<strong>hot</strong>会fallback刷新加载，<strong>hotOnly</strong>不会刷新加载，会报如上错误</p>
</blockquote>
<ol start="2">
<li>在react项目中，css可以热更新，js不可以热更新</li>
</ol>
<blockquote>
<p>要自己配置</p>
</blockquote>
<p>如果你遇到以下的问题</p>
<p><img src="https://oss-zzf.zzfzzf.com/cdn/16430393909399uTdY6.png" alt="image20210521161243171"></p>
<blockquote>
<p>必须在入口文件加这个代码，否则热更新无效</p>
</blockquote>
<pre><code class="language-js">if (module.hot) {
  module.hot.accept();
}
</code></pre>
<ol start="3">
<li>为什么css可以直接热更新，js不能直接热更新<br>  <code>style-loader</code>实现了热更新插件</li>
</ol>
<h2>链接</h2>
<ul>
<li><a href="https://github.com/zzfn/webpack-HMR">demo</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[图片格式]]></title>
            <link>https://zzfzzf.com/post/6972548384855035917</link>
            <guid>6972548384855035917</guid>
            <pubDate>Thu, 20 May 2021 08:46:34 GMT</pubDate>
            <content:encoded><![CDATA[<h1>按类型分</h1>
<h2>位图</h2>
<blockquote>
<p>放大会失真</p>
</blockquote>
<h2>矢量图</h2>
<blockquote>
<p>可无损放大</p>
</blockquote>
<ul>
<li>svg</li>
</ul>
<h1>按压缩分</h1>
<h2>无压缩</h2>
<ul>
<li>BMP</li>
</ul>
<h2>有损压缩</h2>
<p> jpg/JPEG 不适用logo、icon、包含文本或平面颜色的图像，可用于网站的背景图片、幻灯片或横幅广告等大型图像。</p>
<h2>无损</h2>
<ul>
<li>gif</li>
<li>png</li>
<li>svg</li>
</ul>
<h2>比较</h2>
<ol>
<li>通常地，PNG ≈ JPG &gt; GIF</li>
<li>透明性：PNG &gt; GIF &gt; JPG</li>
<li>色彩丰富程度：JPG &gt; PNG &gt;GIF</li>
<li>兼容程度：GIF ≈ JPG &gt; PNG</li>
</ol>
<h2>webp</h2>
<blockquote>
<p>支持有损无损，最牛逼
WebP是一种由Google开发的图像格式，旨在提供高质量的图像和更小的文件大小。WebP图像通常用于网站上的大型背景图片和Banner广告。</p>
</blockquote>
<p><a href="https://caniuse.com/?search=webp">支持度</a>
<a href="https://mp.weixin.qq.com/s/KQgp_4l0h3F9X5qDUHkkrQ">参考文档</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[技术周报·2021-05-14]]></title>
            <link>https://zzfzzf.com/post/6972548384855035916</link>
            <guid>6972548384855035916</guid>
            <pubDate>Fri, 14 May 2021 14:38:18 GMT</pubDate>
            <content:encoded><![CDATA[<ul>
<li><a href="https://juejin.cn/post/6961698605740752933">React 17 hooks 原理</a></li>
<li><a href="https://juejin.cn/post/6961319565926072357#heading-11">最详细实现一个深拷贝函数</a></li>
<li><a href="https://juejin.cn/post/6961299855981428750">写了个 babel-plugin，我收获了不止一点</a></li>
<li><a href="https://juejin.cn/post/6959802564770529287">原来redux的原理如此简单</a></li>
<li><a href="https://juejin.cn/post/6958013132568526855">webpack构建流程</a></li>
<li><a href="https://juejin.cn/post/6952404632610013215">聊聊用JS实现网页瀑布流布局</a></li>
<li><a href="https://juejin.cn/post/6844903549420175367">从0实现一个tiny react</a></li>
<li><a href="https://juejin.cn/post/6844903520865386510">import、require、export、module.exports 混合使用详解</a></li>
<li><a href="https://juejin.cn/post/6844903683411410951">前端常用插件、工具类库汇总，不要重复造轮子啦！！！</a></li>
<li><a href="https://juejin.cn/post/6844904068431740936#heading-10">从0到1教你搭建前端团队的组件系统（高级进阶必备）</a></li>
<li><a href="https://juejin.cn/post/6962122030002995207?utm_source=gold_browser_extension">如何用Webpack打包组件库</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[js中的时间处理]]></title>
            <link>https://zzfzzf.com/post/6972548384855035915</link>
            <guid>6972548384855035915</guid>
            <pubDate>Fri, 14 May 2021 06:19:50 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>本文总结js中日期时间的常用操作，以供查阅</p>
</blockquote>
<h2>Date</h2>
<h3>获取时间戳</h3>
<p>毫秒时间戳</p>
<ol>
<li><code>Date.parse(new Date())</code> 精确到秒 毫秒位为0</li>
<li><code>new Date().getTime()</code></li>
<li><code>new Date().valueOf()</code></li>
<li><code>+new Date()</code></li>
</ol>
<h3>Date.now()</h3>
<p>返回自 1970-1-1 00:00:00  UTC（世界标准时间）至今所经过的毫秒数。</p>
<h3>Date.parse()</h3>
<p>解析一个表示日期的字符串，并返回从 1970-1-1 00:00:00 所经过的毫秒数。</p>
<h3>new Date().getTime()</h3>
<p>返回从1970-1-1 00:00:00 UTC（协调世界时）到该日期经过的毫秒数，对于1970-1-1 00:00:00 UTC之前的时间返回负值。</p>
<h2>时间库</h2>
<ul>
<li><a href="https://github.com/moment/moment">momentjs</a></li>
<li><a href="https://github.com/iamkun/dayjs">dayjs</a>(推荐)</li>
</ul>
<h2>Temporal</h2>
<blockquote>
<p>目前处于第<a href="https://github.com/tc39/proposals#stage-3">3</a>阶段的新提案</p>
</blockquote>
<ul>
<li><a href="https://github.com/tc39/proposal-temporal">仓库</a></li>
<li>使用<a href="https://tc39.es/proposal-temporal/">文档</a></li>
<li>polyfill<a href="https://tc39.es/proposal-temporal/docs/">文档</a>，F12后可直接在控制台使用新特性</li>
</ul>
<blockquote>
<p>因为目前不能直接使用，需借助<a href="https://github.com/tc39/proposal-temporal/tree/main/polyfill">polyfill</a></p>
</blockquote>
<ol>
<li>使用前提</li>
</ol>
<pre><code class="language-js">npm install --save proposal-temporal
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[js模块化]]></title>
            <link>https://zzfzzf.com/post/6972548384855035912</link>
            <guid>6972548384855035912</guid>
            <pubDate>Tue, 04 May 2021 15:56:31 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>模块化主要为了解决js的变量作用域问题</p>
</blockquote>
<h2>模块化的好处</h2>
<ol>
<li>避免命名冲突(减少命名空间污染)</li>
<li>更好的分离, 按需加载</li>
<li>更高复用性</li>
<li>高可维护性</li>
</ol>
<h2>常见的方案</h2>
<ul>
<li><p>commonjs</p>
</li>
<li><p>amd（Require.js）</p>
</li>
<li><p>cmd（sea.js）</p>
</li>
<li><p>es6</p>
</li>
</ul>
<h2>几种构建格式</h2>
<h3>CJS</h3>
<ol>
<li>node模块化的主要解决方案</li>
<li><code>require</code>同步加载模块</li>
<li>require和module.exports</li>
<li>node运行</li>
<li>require才加载</li>
<li>输出值是值的拷贝</li>
</ol>
<h3>AMD</h3>
<ol>
<li>全称Asynchronous Module Definitio</li>
<li>依赖<code>requireJS</code>的异步加载解决方案</li>
<li>主要在浏览器端使用</li>
</ol>
<h3>CMD</h3>
<ol>
<li>全称Commin Module Definition</li>
<li>依赖<code>SeaJS</code>的异步加载解决方案</li>
</ol>
<h3>UMD</h3>
<ul>
<li>amd，cjs 和 iife 合体</li>
<li>兼容浏览器和node</li>
<li>Node.js 同步加载，浏览器端异步加载。</li>
</ul>
<h3>EJS、ESM</h3>
<ol>
<li>输出值是值的引用</li>
<li>即是ES6 模块，类库打包以供摇树</li>
<li>编译时加载</li>
</ol>
<h3>iife</h3>
<p>rollup立即执行函数格式，给浏览器使用</p>
<h3>system</h3>
<p>SystemJS 加载器格式</p>
<h2>F&amp;Q</h2>
<h3>AMD和CMD的差异</h3>
<blockquote>
<p>对模块加载的处理不同</p>
</blockquote>
<h3>ES6 模块与 CommonJS 模块的差异</h3>
<ol>
<li>CommonJS 模块输出的是一个值的拷贝，ES6 模块输出的是值的引用。</li>
<li>CommonJS 模块是运行时加载，ES6 模块是编译时就能确定模块的<code>依赖关系</code>以及<code>输入</code>和<code>输出</code>的变量</li>
<li>CommonJS 模块的require()是同步加载模块，ES6 模块的import命令是异步加载，有一个独立的模块依赖的解析阶段。</li>
</ol>
<h2>其他</h2>
<ol>
<li>import加载commonjs模块只能整体加载不能按需摇树</li>
</ol>
<h3>摇树</h3>
<p>摇树是一种依赖 ESM 模块静态分析实现的功能，它可以在编译时安全的移除代码中未使用的部分。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[搭建npm和docker私有仓库]]></title>
            <link>https://zzfzzf.com/post/6972548384855035910</link>
            <guid>6972548384855035910</guid>
            <pubDate>Mon, 26 Apr 2021 07:33:07 GMT</pubDate>
            <content:encoded><![CDATA[<h2>Nexus</h2>
<h3>环境准备</h3>
<ul>
<li>centos7.6</li>
<li><a href="https://zzfzzf.com/post/1414224254387363841">docker</a></li>
</ul>
<h3>nextus3部署</h3>
<ol>
<li>拉取镜像</li>
</ol>
<pre><code class="language-bash">docker pull sonatype/nexus3
</code></pre>
<ol start="3">
<li>启动容器</li>
</ol>
<pre><code class="language-bash">docker run -d -p 8081:8081 sonatype/nexus3
</code></pre>
<blockquote>
<p>nexus的jvm默认内存比较大，可能起不来，我们可以调小一点</p>
</blockquote>
<pre><code class="language-bash">docker run -d -p 8081:8081 --name nexus -e INSTALL4J_ADD_VM_PARAMS=&quot;-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/some-other-dir&quot; --restart=always sonatype/nexus3
</code></pre>
<blockquote>
<p>看看有没有启动成功</p>
</blockquote>
<pre><code class="language-bash">curl http://localhost:8081/
</code></pre>
<pre><code>version: &#39;3&#39;
services:
  nexus3:
    image: sonatype/nexus3
    environment:
      - TZ=Asia/Shanghai
      - &quot;INSTALL4J_ADD_VM_PARAMS=-Xms128m -Xmx512m -XX:MaxDirectMemorySize=512m -Djava.util.prefs.userRoot=/some-other-dir&quot;
    ports:
      - &#39;8081:8081&#39;
      - &#39;8443:8443&#39;
      - &#39;9102:9102&#39;
      - &#39;9101:9101&#39;
      - &#39;9103:9103&#39;
      - &#39;9200:9200&#39;
      - &#39;9201:9201&#39;
    restart: always
    volumes:
      - &#39;./keystore.jks:/opt/sonatype/nexus/etc/ssl/keystore.jks:ro&#39;
      - &#39;./keystore.cer:/opt/sonatype/nexus/etc/ssl/keystore.cer:ro&#39;
      - &#39;./nexus-default.properties:/opt/sonatype/nexus/etc/nexus-default.properties:ro&#39;
      - &#39;./jetty-https.xml:/opt/sonatype/nexus/etc/jetty/jetty-https.xml:ro&#39;
</code></pre>
<ol start="5">
<li>查看密码<blockquote>
<p>首先我们进入容器</p>
</blockquote>
</li>
</ol>
<pre><code class="language-bash">docker exec -it container_id bash
</code></pre>
<blockquote>
<p>查看默认密码</p>
</blockquote>
<pre><code class="language-bash">cat /nexus-data/admin.password
</code></pre>
<ul>
<li>简单步骤 <a href="https://cloud.tencent.com/developer/article/1352350">https://cloud.tencent.com/developer/article/1352350</a></li>
</ul>
<h3>SSL</h3>
<p>docker不使用ssl或者使用自签的ssl每个客户端都需要配置一遍,不然会报错<code>certificate signed by unknown authority</code>,所以选取免费的商业证书，</p>
<ul>
<li>证书格式转化 <a href="https://myssl.com/cert_convert.html">https://myssl.com/cert_convert.html</a></li>
<li>使用nexus提供的https可参考 <a href="https://juejin.cn/post/7033440785294950430">https://juejin.cn/post/7033440785294950430</a> 但是还是会报错，待研究</li>
<li>使用nginx反向代理成功 <a href="https://www.jianshu.com/p/5f9bd492f186">https://www.jianshu.com/p/5f9bd492f186</a></li>
</ul>
<h3>项目配置</h3>
<h4>配置文件准备</h4>
<ol>
<li>项目根目录添加<code>.npmrc</code>文件<blockquote>
<p><code>_auth</code>是 username:password 的<a href="https://tool.oschina.net/encrypt?type=3">base64</a>值</p>
</blockquote>
</li>
</ol>
<pre><code>registry=http://127.0.0.1:8081/repository/npm-group
_auth=ZGVwbG95ZXI6ZGVwbG95ZXI=
</code></pre>
<ol start="2">
<li>清理缓存</li>
</ol>
<pre><code>npm cache clean -f
</code></pre>
<h3>发布私有包</h3>
<blockquote>
<p>⚠️注意私有包地址是hosted不是group或者proxy</p>
</blockquote>
<ol>
<li><blockquote>
<p>直接使用命令关联仓库发布</p>
</blockquote>
</li>
</ol>
<pre><code class="language-bash">npm publish --registry=http://localhost:8081/repository/npm-hosted/
</code></pre>
<ol start="2">
<li><blockquote>
<p>在package.json添加配置发布</p>
</blockquote>
</li>
</ol>
<pre><code class="language-json">{
&quot;publishConfig&quot; : {
    &quot;registry&quot; : &quot;http://localhost:8081/repository/npm-hosted/&quot;
    }
}
</code></pre>
<pre><code class="language-bash">npm publish
</code></pre>
<h3>安装私有包</h3>
<blockquote>
<p>查看安装信息</p>
</blockquote>
<pre><code class="language-bash">npm --loglevel info install react  
</code></pre>
<h2>cnpmcore</h2>
<p><a href="https://github.com/cnpm/cnpmcore">https://github.com/cnpm/cnpmcore</a></p>
<h2>verdaccio</h2>
<pre><code>npm install -g verdaccio
</code></pre>
<pre><code>
//npm.zzfzzf.com/:_authToken=NpmToken
@zzf:registry=http://npm.zzfzzf.com/
always-auth=true

registry=https://npm.zzfzzf.com/
_auth=base64
</code></pre>
<h2>总结</h2>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[Object.is浅比较]]></title>
            <link>https://zzfzzf.com/post/6972548384855035909</link>
            <guid>6972548384855035909</guid>
            <pubDate>Thu, 22 Apr 2021 01:30:59 GMT</pubDate>
            <content:encoded><![CDATA[<h2>判断2个值是否是同一个值</h2>
<h2><code>==</code></h2>
<p>存在隐式类型转换，<code>undefined</code>,<code>null</code>,<code>&#39;&#39;</code>,<code>0</code>转化为false</p>
<h2><code>===</code></h2>
<p>存在2个缺点
<code>+0===-0</code>//true
<code>NAN===NAN</code>//false</p>
<h2>ES5实现</h2>
<pre><code class="language-js">function ObjectIs(x, y) {
    if (x === y) {
        //如果 x !== 0，则返回true
        // -0 +0
        return x !== 0 || 1 / x === 1 / y;
    } else {
        // NaN
        return x !== x &amp;&amp; y !== y;
    }
}
</code></pre>
<h2>React里的浅比较</h2>
<h3>比较对象相等</h3>
<p><a href="https://github.com/lessfish/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L1094-L1190">https://github.com/lessfish/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L1094-L1190</a></p>
<h3>比较数组相等</h3>
<pre><code>function shallowEqual(objA: mixed, objB: mixed): boolean {
  //判断objA和objB是否相等，相等直接返回true
  if (is(objA, objB)) {
    return true;
  }
  /*
      如果不相等，那么就要看下两参数是否是对象（Object.is只能对基本类型数据做比较精准的比较）。
      浅比较需要进入到对象的第一层，不能仅引用不一样就直接返回false。
      此外，如果两者有一个参数为null，那也不用继续下去了，两者已经不相等了，并且有一个为null，则
      表示肯定有变化发生了，shallowEqual可以直接返回false
  */
  if (
    typeof objA !== &#39;object&#39; ||
    objA === null ||
    typeof objB !== &#39;object&#39; ||
    objB === null
  ) {
    return false;
  }
  //如果是对象，同时两者又没有null，则开始进入到对象的第一层来比较
  //获取两个对象的key值组成数组
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
  //如果属性长度都不一样，则表示两者不相等，返回false
  if (keysA.length !== keysB.length) {
    return false;
  }

  //接下来一个属性一个属性的比较
  //如果objA的属性objB没有，或者两者同一个属性的值不相等，则表示objA和objB不相等，返回false
  for (let i = 0; i &lt; keysA.length; i++) {
    if (
      !hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}
</code></pre>
<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is">参考链接</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[移动端滚动穿透问题]]></title>
            <link>https://zzfzzf.com/post/6972548384855035908</link>
            <guid>6972548384855035908</guid>
            <pubDate>Fri, 16 Apr 2021 01:53:46 GMT</pubDate>
            <content:encoded><![CDATA[<pre><code class="language-js">&lt;template&gt;
 &lt;div @click=&quot;handleHambergerClick&quot;&gt;&lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
 name: &#39;BaseHeaderMobile&#39;,
 data() {
  return {
   isHeaderVisible: false,
  };
 },
 methods: {
  handleHambergerClick() {
   // hack: 滑动穿透问题
   if (!this.isHeaderVisible) {
    this.lockBody();
   } else {
    this.resetBody();
   }

   this.isHeaderVisible = !this.isHeaderVisible;
  },
  lockBody() {
   const { body } = document;
   const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
   body.style.position = &#39;fixed&#39;;
   body.style.width = &#39;100%&#39;;
   body.style.top = `-${scrollTop}px`;
  },
  resetBody() {
   const { body } = document;
   const { top } = body.style;
   body.style.position = &#39;&#39;;
   body.style.width = &#39;&#39;;
   body.style.top = &#39;&#39;;
   document.body.scrollTop = -parseInt(top, 10);
   document.documentElement.scrollTop = -parseInt(top, 10);
  },
 },
};
&lt;/script&gt;
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[前端安全]]></title>
            <link>https://zzfzzf.com/post/6972548384855035907</link>
            <guid>6972548384855035907</guid>
            <pubDate>Wed, 24 Mar 2021 07:21:40 GMT</pubDate>
            <content:encoded><![CDATA[<h2>同源策略</h2>
<blockquote>
<p>协议、域名、端口需一致</p>
</blockquote>
<h2>XSS</h2>
<h3>定义</h3>
<p>中文<strong>跨站脚本攻击</strong>，本质是代码注入，即没有过滤恶意代码</p>
<h3>分类</h3>
<ul>
<li>反射型(不持久)</li>
</ul>
<p>url链接被服务器反射到浏览器渲染</p>
<ul>
<li>存储型（持久型）</li>
</ul>
<p>在留言中比较典型，会存储在服务器中</p>
<ul>
<li>基于dom型</li>
<li>基于浏览器型</li>
</ul>
<h3>防范</h3>
<ul>
<li>对用户的输入与url进行过滤和转译、html标签</li>
<li>cookie设置<strong>http-only</strong>以避免cookie劫持的危险</li>
<li>node可使用js-xss</li>
</ul>
<h2>CSRF</h2>
<h3>定义</h3>
<p>跨站请求伪造</p>
<p><img src="https://oss-zzf.zzfzzf.com/cdn/1644222647631joww3v.jpg" alt="1644222647631joww3v"></p>
<h3>防范</h3>
<ul>
<li>服务器服务端去做验证处理</li>
<li>验证Referer</li>
<li>重要操作验证码、加强与用户交互</li>
<li>自定义属性验证，比如token验证</li>
<li>阻止不明外域的访问<ul>
<li>同源检测</li>
</ul>
</li>
<li>Samesite Cookie</li>
<li>提交时要求附加本域才能获取的信息<ul>
<li>CSRF Token</li>
</ul>
</li>
<li>双重Cookie验证</li>
<li>避免get方法修改操作</li>
</ul>
<h2>SQL注入</h2>
<p>利用现有应用程序，可以将恶意的SQL命令注入到后台数据库引擎中并执行。也可以通过在Web表单中输入恶意SQL语句得到一个存在安全漏洞的网站上的数据库，而不是按照设计者意图去执行SQL语句</p>
<h3>防范</h3>
<ul>
<li>杜绝用户提交的参数入库并且执行</li>
<li>在代码层，不准出现sql语句</li>
<li>在web输入参数处，对所有的参数做sql转义</li>
<li>上线测试，需要使用sql自动注入工具进行所有的页面sql注入测试</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[浏览器缓存知识]]></title>
            <link>https://zzfzzf.com/post/6972548384855035906</link>
            <guid>6972548384855035906</guid>
            <pubDate>Wed, 17 Mar 2021 05:12:49 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>浏览器缓存分强缓存和协商缓存</p>
</blockquote>
<h2>强缓存</h2>
<blockquote>
<p>不会向服务器发送请求，从缓存中读取资源，返回的状态码是200
如果响应头有expires、pragma或者cache-control字段，代表这是强缓存</p>
</blockquote>
<ol>
<li><code>Expires</code> HTTP/1.0 时间不准</li>
<li><code>Cache-Control</code> HTTP/1.1</li>
<li><code>Cache-Control</code>优先级高，覆盖<code>Expires</code></li>
<li><code>Cache-Control</code>设置<code>no-cache</code>开启协商缓存</li>
<li>缓存过期之后，不管资源有没有变化，都会重新发起请求，重新获取资源</li>
</ol>
<h2>协商缓存</h2>
<blockquote>
<p>协商缓存就是强缓存失效后，浏览器携带缓存标识向服务器发送请求，由服务器根据缓存标识来决定是否使用缓存的过程。</p>
</blockquote>
<ul>
<li>协商缓存生效，返回304</li>
<li>协商缓存失效，返回200和请求结果</li>
</ul>
<ol>
<li>Last-Modifed/If-Modified-Since</li>
</ol>
<ul>
<li>Last-Modified是服务器响应请求时，返回该资源文件在服务器最后被修改的时间。</li>
<li>If-Modified-Since则是客户端再次发起该请求时，携带上次请求返回的Last-Modified值，通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求，发现请求头含有If-Modified-Since字段，则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比，若服务器的资源最后被修改时间大于If-Modified-Since的字段值，则重新返回资源，状态码为200；否则则返回304，代表资源无更新，可继续使用缓存文件。</li>
</ul>
<ol start="2">
<li>Etag/If-None-Match</li>
</ol>
<ul>
<li><p>Etag是服务器响应请求时，返回当前资源文件的一个唯一标识(由服务器生成)。</p>
</li>
<li><p>If-None-Match是客户端再次发起该请求时，携带上次请求返回的唯一标识Etag值，通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后，发现该请求头中含有If-None-Match，则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比，一致则返回304，代表资源无更新，继续使用缓存文件；不一致则重新返回资源文件，状态码为200。</p>
</li>
<li><p>Last-Modifed缺点：</p>
<ul>
<li>文件不改变内容，仅改变修改时间</li>
<li>1s修改多次，Last-Modifed只能精确到秒</li>
</ul>
</li>
<li><p>服务器优先验证Etag，若一致，继续比对Last-Modifed，最后判断是否返回304</p>
</li>
<li><p>Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since，同时存在则只有Etag / If-None-Match生效。</p>
</li>
<li><p>可以这样设置来使用协商缓存</p>
</li>
</ul>
<pre><code class="language-javascript">ctx.set(&#39;Cache-Control&#39;, &#39;public, max-age=0&#39;);
ctx.set(&#39;Last-Modified&#39;, xxx);
ctx.set(&#39;ETag&#39;, xxx);
</code></pre>
<p><img src="https://oss-zzf.zzfzzf.com/cdn/1645608159621rMCf36.png" alt="1645608159621rMCf36">
<img src="https://oss-zzf.zzfzzf.com/cdn/1643808374278XG5csa.jpg" alt="1643808374278XG5csa"></p>
<p><code> If-None-Match</code> <code>If-Modified-Since</code></p>
<h2>区别</h2>
<ol>
<li>优先查找强缓存，没有命中再查找协商缓存</li>
<li>强缓存不会发请求到服务器，协商缓存会发请求，所以服务器文件更新了强缓存无法感知，协商缓存可以感知</li>
</ol>
<h2>刷新浏览器</h2>
<ul>
<li>直接输入url地址，检查强制缓存</li>
<li>f5刷新 检查协商缓存，不检查强制缓存</li>
<li>ctrl+f5 不检查强制缓存和协商缓存，全部重新请求</li>
</ul>
<h2>分类型使用不同缓存</h2>
<ol>
<li>html使用协商缓存</li>
<li>css, image, js使用协商缓存，文件名使用hash</li>
</ol>
<h2>缓存优先级</h2>
<ol>
<li>Service Worker</li>
<li>Memory Cache(内存)</li>
<li>Disk Cache(硬盘)</li>
<li>Push Cache</li>
</ol>
<h2>请求流程</h2>
<ol>
<li>浏览器发送请求前，根据请求头的expires和cache-control判断是否命中（包括是否过期）强缓存策略，如果命中，直接从缓存获取资源，并不会发送请求。如果没有命中，则进入下一步。</li>
<li>没有命中强缓存规则，浏览器会发送请求，根据请求头的last-modified和etag判断是否命中协商缓存，如果命中，直接从缓存获取资源。如果没有命中，则进入下一步。</li>
<li>如果前两步都没有命中，则直接从服务端获取资源。</li>
</ol>
<h2>实战</h2>
<p>使用koa模拟
<a href="https://github.com/zzfn/koa-server/blob/main/routes/cache.js">https://github.com/zzfn/koa-server/blob/main/routes/cache.js</a></p>
<pre><code class="language-javascript">const Router = require(&#39;koa-router&#39;)

const router = new Router({prefix: &#39;/cache&#39;})

module.exports = router


router.get(&#39;/Expired&#39;, async ctx =&gt; {
    // ctx.response.lastModified = new Date();
    const Expired=new Date(new Date().getTime()+10000).toUTCString()
    ctx.set(&#39;Expires&#39;,Expired)
    ctx.body={
        now:new Date().toUTCString(),
        Expired
    }
})
router.get(&#39;/cache-control&#39;, async ctx =&gt; {
    // ctx.response.lastModified = new Date();
    const Expired=new Date(new Date().getTime()+10000).toUTCString()
    ctx.set(&#39;Cache-Control&#39;,&#39;max-age=10&#39;)
    ctx.body={
        now:new Date().toUTCString(),
        Expired
    }
})
router.get(&#39;/etag&#39;, async ctx =&gt; {
    console.log(1)
    // ctx.response.lastModified = new Date();
    const Expired=new Date(new Date().getTime()+10000).toUTCString()
    ctx.set(&#39;Etag&#39;,&#39;1123&#39;)
    ctx.status = 304;
    ctx.body={
        a:1,
        now:new Date().toUTCString(),
        Expired
    }
})
router.get(&#39;/Last-Modified&#39;, async ctx =&gt; {
    // ctx.response.lastModified = new Date();
    const Expired=new Date(new Date().getTime()+10000).toUTCString()
    ctx.set(&#39;Cache-Control&#39;,&#39;max-age=10&#39;)
    ctx.body={
        now:new Date().toUTCString(),
        Expired
    }
})
module.exports = router
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[link和import的区别]]></title>
            <link>https://zzfzzf.com/post/6972548384850841640</link>
            <guid>6972548384850841640</guid>
            <pubDate>Fri, 05 Mar 2021 07:24:52 GMT</pubDate>
            <content:encoded><![CDATA[<h2>加载顺序</h2>
<p>页面被加载时，link会同时被加载，而@import引用的css会等到页面加载结束后加载。</p>
<h2>标签不同</h2>
<p>link属于html标签，而@import是css提供的</p>
<h2>兼容性</h2>
<p>@import只有在IE5以上的才能识别，而link标签无此问题。</p>
<h2>优先级</h2>
<p>link方式样式的权重高于@import的。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[js-函数]]></title>
            <link>https://zzfzzf.com/post/6972548384850841639</link>
            <guid>6972548384850841639</guid>
            <pubDate>Fri, 05 Mar 2021 00:45:50 GMT</pubDate>
            <content:encoded><![CDATA[<h2>箭头函数和普通函数</h2>
<ul>
<li>箭头函数不会创建自己的this<blockquote>
<p>会捕获其所在的上下文的this值，作为自己的this值</p>
</blockquote>
</li>
<li>箭头函数继承而来的this指向永远不变<blockquote>
<p>call | apply | bind 无法改变箭头函数中this的指向</p>
</blockquote>
</li>
<li>箭头函数不能作为构造函数使用</li>
<li>箭头函数没有自己的arguments</li>
<li>箭头函数没有原型prototype</li>
<li>不能用做<code>Generator</code>函数</li>
</ul>
<h2>函数声明和函数表达式</h2>
<pre><code class="language-js">//函数声明
function test() { 

}
//函数表达式
const test = function () { 

}
</code></pre>
<ol>
<li>函数声明提升</li>
</ol>
<h2>静态、原型、实例属性</h2>
<h2>Function</h2>
<pre><code class="language-js">function Foo() {
    this.age = 18//实例属性
}

Foo.sex = &#39;women&#39;//静态属性
Foo.sayAge = function () {//静态属性
    return 19
}
Foo.prototype.sayName = function () {//原型属性
    return &#39;zzf&#39;
}
</code></pre>
<h2>Class</h2>
<pre><code class="language-js">class Bar {
    static sex = &#39;women&#39;//静态属性
    constructor() {
        this.age = 18//实例属性
    }
    sayName() {
        return &#39;zzf&#39;//原型属性
    }
    sayHello=()=&gt;{
        return &#39;hello&#39;//实例属性
    }
}
Bar.sayAge = function () {//静态属性
    return 19
}
</code></pre>
<p>babel转译后为</p>
<pre><code class="language-js">&quot;use strict&quot;;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(&quot;Cannot call a class as a function&quot;); } }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var Bar = function Bar() {
  _classCallCheck(this, Bar);
  _defineProperty(this, &quot;sayName&quot;, function () {
    return &#39;zzf&#39;;
  });
  this.age = 18;
};
_defineProperty(Bar, &quot;sex&quot;, &#39;women&#39;);
Bar.sayAge = function () {
  return 19;
};
</code></pre>
<h2>new过程</h2>
<ol>
<li>创建一个空的简单Javascript对象 （即{}）</li>
<li>继承父类原型上的方法</li>
<li>添加父类的属性到新的对象上并初始化. 保存方法的执行结果</li>
<li>如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象</li>
</ol>
<pre><code class="language-js">function myNew(fn,...args) {
    let obj = {}
    obj.__proto__ = fn.prototype
    let result = fn.apply(obj,args)
    return result instanceof Object ? result : obj
}
</code></pre>
<h2>总结</h2>
<p>箭头函数的 this 永远指向其上下文的 this ，任何方法都改变不了其指向，如 call() , bind() , apply() ，可以说正是因为没有自己的this，才使其具备了以上介绍的大部分特点；
普通函数的this指向调用它的那个对象</p>
<h2>F&amp;Q</h2>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[JavaScript常见手写题]]></title>
            <link>https://zzfzzf.com/post/6972548384850841638</link>
            <guid>6972548384850841638</guid>
            <pubDate>Tue, 10 Jun 2025 06:45:52 GMT</pubDate>
            <content:encoded><![CDATA[<h2>数组原型方法</h2>
<h3>map</h3>
<pre><code class="language-javascript">//reduce实现
Array.prototype.myMap = function (fn) {
    return this.reduce((previousValue, currentValue, idx) =&gt; {
        previousValue.push(fn(currentValue, idx))
        return previousValue
    }, [])
}
</code></pre>
<h3>flat</h3>
<blockquote>
<p>比较详细<a href="https://juejin.cn/post/6844904025993773063">https://juejin.cn/post/6844904025993773063</a></p>
</blockquote>
<pre><code class="language-javascript">//代码https://github.com/zzfn/best-practice/blob/main/javascript/other/flat.js
//递归
Array.prototype.myFlat = function (level = 1) {
    const array = this
    if (level &gt; 0) {
        let result = [];
        for (const item of array) {
            if (Array.isArray(item)) {
                result = [...result, ...item.myFlat(level - 1)]
            } else {
                result.push(item)
            }
        }
        return result;
    } else {
        return array;
    }
}
//reduce
Array.prototype.myFlat = function (level = 1) {
    return level &gt; 0 ? this.reduce((acc, item) =&gt; {
        if (Array.isArray(item)) {
            return [...acc, ...item.myFlat(level - 1)]
        } else {
            return [...acc, item]
        }
    }, []) : this
}
console.log([1, 2, [3, [4, [5]]]].myFlat(2))
</code></pre>
<h3>reverse</h3>
<pre><code class="language-javascript">const arr = [1, 2, 3, 4, 5];
Array.prototype.myReverse = function () {
    const o=[]
    const arr=this
    while (arr.length){
        o.unshift(arr.shift())
    }
    return o
}
console.log(arr.myReverse())
</code></pre>
<h3>reduce</h3>
<pre><code class="language-js">Array.prototype.myReduce = function (fn, initialValue) {
    let acc = initialValue;
    for (let i = 0; i &lt; this.length; i++) {
        if (acc === undefined) {
            acc = this[i];
        } else {
            acc = fn(acc, this[i], i, this);
        }
    }
    return acc;
}
</code></pre>
<pre><code class="language-javascript">Array.prototype.myReduce = function (callback, initialValue) {
    // 判断调用该API的元素是否为null
    if (this == null) {
        throw new TypeError(&#39;this is null or not defined&#39;)
    }
    // 判断是否为function
    if (typeof callback !== &quot;function&quot;) {
        throw new TypeError(callback + &#39; is not a function&#39;)
    }
    const arr = this
    const len = arr.length
    // 第二个参数
    let accumulator = initialValue
    let index = 0
    // 如果第二个参数是undefined 则数组的第一个有效值
    // 作为累加器的初始值
    if (accumulator === undefined) {
        // 找到数组中的第一个有效值 不一定就是arr[0]
        while (index &lt; len &amp;&amp; !(index in arr)) {
            index++
        }
        if (index &gt;= len) {
            throw new TypeError(&#39;Reduce of empty array with no initial value&#39;)
        }
        // 输出第一个有效数组元素，作为累加器的第一个元素
        accumulator = arr[index++]
    }
    while (index &lt; len) {
        if (index in arr) {
            // arr[index] 为 accumulator 的下一个元素
            accumulator = callback.call(undefined, accumulator, arr[index], index, arr)
        }
        // 持续后移
        index++
    }
    // 返回结果
    return accumulator
}
</code></pre>
<h3>some</h3>
<pre><code class="language-javascript">Array.prototype.myReduce=function(fn,initalValue){
    let initialArr = this;
    let arr = initialArr.concat();

    if (initalValue) arr.unshift(initalValue);
    let index, newValue;

    while (arr.length &gt; 1) {
      index = initialArr.length - arr.length + 1;
      newValue = fn.call(null, arr[0], arr[1], index, initialArr);

      arr.splice(0, 2, newValue);
    }
    return newValue;
}
</code></pre>
<h3>every</h3>
<h3>filter</h3>
<h3>foreach</h3>
<pre><code class="language-javascript">Array.prototype.myForEach = function (callback, thisArg) {
    // 判断调用该API的元素是否为null
    if (this == null) {
        throw new TypeError(&#39;this is null or not defined&#39;)
    }
    // 判断是否为function
    if (typeof callback !== &quot;function&quot;) {
        throw new TypeError(callback + &#39; is not a function&#39;)
    }
    // 通过this得到调用者arr
    const arr = this
    // 确定循环变量
    let index = 0
    // 循环遍历给每个数组元素调用callback
    while (index &lt; arr.length) {
        // 判断是否存在这个项
        if (index in arr) {
            // 通过call将this指向thisArg，并且传入3个参数
            callback.call(thisArg, arr[index], index, arr)
        }
        index++
    }
}
</code></pre>
<h2>对象原型方法</h2>
<h3>Object.assign</h3>
<pre><code class="language-javascript">Object.myAssign = function (target, ...source) {
    if ([null, undefined].includes(target)) {
        throw new TypeError(&#39;Cannot convert undefined or null to object&#39;)
    }
    let result = Object(target)
    source.forEach(function (obj) {
        if (obj) {
            for (let key in obj) {
                if (Object.prototype.hasOwnProperty.call(obj,key)) {
                    result[key] = obj[key]
                }
            }
        }
    })
    return result
}
</code></pre>
<h2>函数原型方法</h2>
<h3>call</h3>
<pre><code class="language-javascript">Function.prototype.myCall = function (context, ...arg) {
    const ctx = context || window
    ctx.fn = this
    const result = ctx.fn(...arg)
    delete ctx.fn
    return result;
}

//es6
Function.prototype.myCall = function (thisArg, ...argArray) {
  thisArg.fn=this
  const result=thisArg.fn(...argArray)
  Reflect.deleteProperty(thisArg,&#39;fn&#39;)
  return result
}
</code></pre>
<h3>apply</h3>
<pre><code class="language-javascript">Function.prototype.myApply = function (context, arg) {
    const ctx = context || window
    ctx.fn = this
    const result = Array.isArray(arg) ? ctx.fn(...arg) : ctx.fn()
    delete ctx.fn
    return result;
}
//
Function.prototype.myApply = function (thisArg, ...argArray) {
  thisArg.fn=this
  //判断argArray类数组或者数组
  thisArg.fn(argArray)
  Reflect.deleteProperty(thisArg,&#39;fn&#39;)
}
</code></pre>
<h3>bind</h3>
<pre><code class="language-javascript">//使用apply
Function.prototype.myBind = function (ctx) {
  const fn = this;
  return function (...arg) {
    return fn.apply(ctx,...arg);
  };
};
//构造函数
Function.prototype.myBind = function (context, ...args) {
    const ctx = context
    const _this = this
    const F = function () {
    }
    const ret = function (..._args) {
        if (this instanceof F) {
            return new _this(...args, ..._args)
        }
        return _this.apply(context, args.concat(_args))
    }
    F.prototype = this.prototype
    ret.prototype = new F()
    return ret;
}
//不使用apply
Function.prototype.myBind = function (context, ...arg) {
    const ctx = context || window
    const fn = Symbol()
    ctx[fn] = this
    return function (..._arg) {
        const result = ctx[fn](...arg, ..._arg)
        delete ctx[fn]
        return result
    }
}
Function.prototype.myBind = function (context, ...args) {
    const ctx = context
    const fn = Symbol()
    ctx[fn] = this
    const F = function () {}
    const ret = function (..._args) {
        if (this instanceof F) {
            const result = new ctx[fn](...args, ..._args)
            delete ctx[fn]
            return result
        }
        const result = ctx[fn](...args, ..._args)
        delete ctx[fn]
        return result
    }
    F.prototype = this.prototype
    ret.prototype = new F()
    return ret;
}
</code></pre>
<h3>trim</h3>
<pre><code class="language-javascript">String.prototype.myTrim=function () {
    return this.replace(/^\s\s*/,&#39;&#39;).replace(/\s\s*$/,&#39;&#39;)
}
</code></pre>
<h2>其他常见手写题</h2>
<h3>new</h3>
<ol>
<li><p>创建空对象</p>
</li>
<li><p>绑定原型链</p>
</li>
<li><p>执行构造函数并绑定this</p>
</li>
<li><p>处理返回值：若构造函数返回对象则直接返回，否则返回新对象</p>
</li>
</ol>
<pre><code class="language-javascript">function myNew(constructor,...args) {
    let obj = {}
    obj.__proto__ = constructor.prototype // Object.setPrototypeOf(obj, constructor.prototype);
    let result = constructor.apply(obj,args)
    return result instanceof Object ? result : obj
}
function myNew(fn,...args) {
    let obj = Object.create(fn.prototype)
    let result = fn.apply(obj,args)
    return result instanceof Object ? result : obj
}
</code></pre>
<h3>instanceof</h3>
<pre><code class="language-javascript">function myInstanceof(child, parent) {
    let proto = Object.getPrototypeOf(child);
    let prototype = parent.prototype
    while (proto) {
        if (proto === prototype) {
            return true;
        }
        proto = Object.getPrototypeOf(proto);
    }
    return false;
}
</code></pre>
<h3>柯理化</h3>
<pre><code class="language-javascript">const curry = (fn, ...args) =&gt;
    args.length &gt;= fn.length
        ? fn(...args)
        : (..._args) =&gt; curry(fn, ...args, ..._args);

const curry = (fn, ...args) =&gt; args.length &lt; fn.length ? curry.bind(null, fn, ...args) : fn(...args);
</code></pre>
<h3>反柯理化</h3>
<pre><code class="language-javascript">Function.prototype.uncurrying = function() {
  var self = this;
  return function() {
    return Function.prototype.call.apply(self,arguments)
  };
}
</code></pre>
<h3>防抖</h3>
<pre><code class="language-javascript">function debounce(fn,delay=1000) {
    let timer=null
    return function (...arg) {//不能用箭头函数，不然上下文不对
        if(timer){
            clearTimeout(timer)
        }
        timer=setTimeout(()=&gt;{
            clearTimeout(timer)
            fn.apply(this,arg)
            timer = null
        },delay)
    }
}
</code></pre>
<h3>节流</h3>
<blockquote>
<p>定时器版本</p>
</blockquote>
<pre><code class="language-javascript">  function throttle(fn, delay) {
    let timer = null;
    return function(...args) {
      if (timer) return;
      timer = setTimeout(() =&gt; {
        clearTimeout(timer);
        timer = null;
        fn.apply(this, args);
      }, delay);
    };
  }
</code></pre>
<blockquote>
<p>时间戳版本</p>
</blockquote>
<pre><code class="language-javascript">  const throttle = (func, delay) =&gt; {
    let startTime = Date.now();
    return function(...args) {
      const currentTime = Date.now();
      if (delay &lt;= (currentTime - startTime)) {
        func.apply(this, args);
        startTime = Date.now();
      }
    };
  };
</code></pre>
<h3>数组转树</h3>
<pre><code class="language-javascript">function list2tree(list) {
    const option = {id: &#39;id&#39;, pid: &#39;pid&#39;, children: &#39;children&#39;};
    const a=list.reduce((prev, curr) =&gt; {
        const obj = list.find((item) =&gt; item[option.id] === curr[option.pid]);
        if (obj) {
            !Object.prototype.hasOwnProperty.call(obj, option.children) &amp;&amp; (obj[option.children] = []);
            obj[option.children].push(curr);
        } else {
            prev.push(curr);
        }
        return prev;
    }, []);
    console.log(a)
}
</code></pre>
<h3>once</h3>
<pre><code class="language-javascript">function once(fn) {
    let ret
    return function (...args) {
        if (!fn) return ret
        ret = fn(...args)
        fn = undefined
        return ret
    }
}
</code></pre>
<h3>sleep</h3>
<h3>Promise.all</h3>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[BFC]]></title>
            <link>https://zzfzzf.com/post/6972548384850841637</link>
            <guid>6972548384850841637</guid>
            <pubDate>Mon, 01 Mar 2021 12:40:18 GMT</pubDate>
            <content:encoded><![CDATA[<ol>
<li>例子1</li>
</ol>
<pre><code class="language-html">      &lt;div className=&quot;father&quot;&gt;
        &lt;div className=&quot;son&quot;&gt;&lt;/div&gt;
      &lt;/div&gt;
</code></pre>
<pre><code class="language-css">.father {
  display: flow-root;
  height: 500px;
  background-color: #f00;
  margin-top: 100px;
}
.son {
  height: 100px;
  background-color: #0f0;
  margin-top: 50px;
}
</code></pre>
<ol start="2">
<li><p>例子2</p>
<pre><code class="language-html">&lt;div class=&quot;blue&quot;&gt;&lt;/div&gt;
&lt;div class=&quot;red-outer&quot;&gt;
  &lt;div class=&quot;red-inner&quot;&gt;red inner&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<pre><code class="language-css">.blue, .red-inner {
  height: 50px;
  margin: 10px 0;
}

.blue {
  background: blue;
}

.red-outer {
  overflow: hidden;
  background: red;
}
</code></pre>
</li>
</ol>
<blockquote>
<p>BFC直译为块级格式化上下文,它是一个独立的渲染区域</p>
</blockquote>
<p>下面方式会创建BFC</p>
<ol>
<li>根元素（<code>&lt;html&gt;</code>）</li>
<li>浮动元素（元素的 float 不是 none）</li>
<li>绝对定位元素（元素的 position 为 absolute 或 fixed）</li>
<li>行内块元素（元素的 display 为 inline-block）</li>
<li>表格单元格（元素的 display 为 table-cell，HTML表格单元格默认为该值）</li>
<li>表格标题（元素的 display 为 table-caption，HTML表格标题默认为该值）</li>
<li>匿名表格单元格元素（元素的 display 为 table、table-row、 table-row-group、table-header-group、table-footer-group（分别是HTML table、row、tbody、thead、tfoot 的默认属性）或 inline-table）</li>
<li>overflow 计算值(Computed)不为 visible 的块元素</li>
<li>display 值为 flow-root 的元素</li>
<li>contain 值为 layout、content 或 paint 的元素</li>
<li>弹性元素（display 为 flex 或 inline-flex 元素的直接子元素）</li>
<li>网格元素（display 为 grid 或 inline-grid 元素的直接子元素）</li>
<li>多列容器（元素的 column-count 或 column-width 不为 auto，包括 column-count 为 1）</li>
<li>column-span 为 all 的元素始终会创建一个新的BFC，即使该元素没有包裹在一个多列容器中（标准变更，Chrome bug）。</li>
</ol>
<p>无副作用的属性</p>
<p><code>display:flow-root</code></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[前端页面优化]]></title>
            <link>https://zzfzzf.com/post/6972548384850841636</link>
            <guid>6972548384850841636</guid>
            <pubDate>Tue, 23 Feb 2021 08:22:06 GMT</pubDate>
            <content:encoded><![CDATA[<h3>公共优化</h3>
<ol>
<li>请求优化</li>
<li>ajax局部加载数据</li>
<li>预加载</li>
<li>资源合并</li>
<li>css3替换js动画</li>
</ol>
<h3>vue编码优化</h3>
<ol>
<li>不要把所有数据放data里</li>
<li>v-for循环大数据用事件代理</li>
<li>尽可能拆组件</li>
<li>多用v-if</li>
<li>使用key</li>
<li>Object.feeze</li>
<li>懒加载</li>
<li>按需导入</li>
</ol>
<h3>react编码优化</h3>
<ol>
<li>shouldComponentUpdate</li>
<li>PureComponent</li>
<li>React.memo </li>
<li>Fragments</li>
<li>避免内联函数</li>
<li>useMemo</li>
<li>useCallback</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[python爬虫微博用户照片]]></title>
            <link>https://zzfzzf.com/post/6972548384850841634</link>
            <guid>6972548384850841634</guid>
            <pubDate>Wed, 13 Jan 2021 09:29:42 GMT</pubDate>
            <content:encoded><![CDATA[<pre><code class="language-python"># -*- coding: utf-8 -*-
import random
import urllib.request
import json
import re
# 定义要爬取的微博大V的微博ID
import requests
import time

# id=(input(&quot;请输入要抓的微博uid:&quot;))
id = &#39;1353112775&#39;
na = &#39;a&#39;
# 设置代理IP

iplist = [&#39;113.195.147.93:9999&#39;, &#39;123.169.127.142:9999&#39;, &#39;123.169.117.120:9999&#39;]

proxy_addr = &quot;113.117.66.85:9999&quot;


# 定义页面打开函数
def use_proxy(url, proxy_addr):
    req = urllib.request.Request(url)
    req.add_header(&quot;User-Agent&quot;,
                   &quot;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0&quot;)
    req.add_header(&quot;cookie&quot;,
                   &quot;ALF=1563501151; SCF=Apx1PRwrfB4If6VYDuCoe_oorlZ5gXCWgirtv_8vjRInbSiwiRjXJzR1HlwIwMRHub8Qq-MsnmzwbCu94WX7vlA.; SUB=_2A25wDe0fDeRhGeVH71AT9yrPzTmIHXVT8fNXrDV6PUJbktANLWrRkW1NT0BnhRvEpps1o8r4U5Lq6WlEI11t3hu5; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9WhgALk5OZhvyM8uOfTuxHgy5JpX5K-hUgL.Foe4ShzES0B0So-2dJLoIpjLxKnLB.BLB-qLxK-LBK-LBoqLxKML1h.L1-zt; SUHB=0idrAQnnVzMAp9; MLOGIN=1; _T_WM=62061002588; WEIBOCN_FROM=1110006030; XSRF-TOKEN=afb963; M_WEIBOCN_PARAMS=luicode%3D10000011%26lfid%3D1005051353112775%26oid%3D4244627785851269%26fid%3D1078031353112775%26uicode%3D10000011&quot;)
    proxy = urllib.request.ProxyHandler({&#39;http&#39;: random.choice(iplist)})
    opener = urllib.request.build_opener(proxy, urllib.request.HTTPHandler)
    urllib.request.install_opener(opener)
    data = urllib.request.urlopen(req).read().decode(&#39;utf-8&#39;, &#39;ignore&#39;)
    return data


# 获取微博主页的containerid，爬取微博内容时需要此id
def get_containerid(url):
    data = use_proxy(url, random.choice(iplist))
    content = json.loads(data).get(&#39;data&#39;)
    for data in content.get(&#39;tabsInfo&#39;).get(&#39;tabs&#39;):
        if (data.get(&#39;tab_type&#39;) == &#39;weibo&#39;):
            containerid = data.get(&#39;containerid&#39;)
    return containerid


# 获取微博大V账号的用户基本信息，如：微博昵称、微博地址、微博头像、关注人数、粉丝数、性别、等级等
def get_userInfo(id):
    url = &#39;https://m.weibo.cn/api/container/getIndex?type=uid&amp;value=&#39; + id
    data = use_proxy(url, random.choice(iplist))
    content = json.loads(data).get(&#39;data&#39;)
    profile_image_url = content.get(&#39;userInfo&#39;).get(&#39;profile_image_url&#39;)
    description = content.get(&#39;userInfo&#39;).get(&#39;description&#39;)
    profile_url = content.get(&#39;userInfo&#39;).get(&#39;profile_url&#39;)
    verified = content.get(&#39;userInfo&#39;).get(&#39;verified&#39;)
    guanzhu = content.get(&#39;userInfo&#39;).get(&#39;follow_count&#39;)
    name = content.get(&#39;userInfo&#39;).get(&#39;screen_name&#39;)
    na = name
    fensi = content.get(&#39;userInfo&#39;).get(&#39;followers_count&#39;)
    gender = content.get(&#39;userInfo&#39;).get(&#39;gender&#39;)
    urank = content.get(&#39;userInfo&#39;).get(&#39;urank&#39;)
    print(&quot;微博昵称：&quot; + name + &quot;\n&quot; + &quot;微博主页地址：&quot; + profile_url + &quot;\n&quot; + &quot;微博头像地址：&quot; + profile_image_url + &quot;\n&quot; + &quot;是否认证：&quot; + str(
        verified) + &quot;\n&quot; + &quot;微博说明：&quot; + description + &quot;\n&quot; + &quot;关注人数：&quot; + str(guanzhu) + &quot;\n&quot; + &quot;粉丝数：&quot; + str(
        fensi) + &quot;\n&quot; + &quot;性别：&quot; + gender + &quot;\n&quot; + &quot;微博等级：&quot; + str(urank) + &quot;\n&quot;)


# 获取微博内容信息,并保存到文本中，内容包括：每条微博的内容、微博详情页面地址、点赞数、评论数、转发数等
def get_weibo(id, file):
    i = 1
    Directory = &quot;/Users/zzf/Desktop/dev/python/zzf/&quot;
    while True:
        url = &#39;https://m.weibo.cn/api/container/getIndex?type=uid&amp;value=&#39; + id
        weibo_url = &#39;https://m.weibo.cn/api/container/getIndex?type=uid&amp;value=&#39; + id + &#39;&amp;containerid=&#39; + get_containerid(
            url) + &#39;&amp;page=&#39; + str(i)
        try:
            data = use_proxy(weibo_url, random.choice(iplist))
            content = json.loads(data).get(&#39;data&#39;)
            cards = content.get(&#39;cards&#39;)
            if (len(cards) &gt; 0):
                for j in range(len(cards)):
                    print(&quot;-----正在爬取第&quot; + str(i) + &quot;页，第&quot; + str(j) + &quot;条微博------&quot;)
                    card_type = cards[j].get(&#39;card_type&#39;)
                    if (card_type == 9):
                        mblog = cards[j].get(&#39;mblog&#39;)
                        # print(mblog)
                        # print(str(mblog).find(&quot;转发微博&quot;))
                        if str(mblog).find(&#39;retweeted_status&#39;) == -1:
                            if str(mblog).find(&#39;original_pic&#39;) != -1:
                                img_url = re.findall(r&quot;&#39;url&#39;: &#39;(.+?)&#39;&quot;, str(mblog))  ##pics(.+?)
                                n = 1
                                timename = str(time.time())
                                timename = timename.replace(&#39;.&#39;, &#39;&#39;)
                                timename = timename[7:]  # 利用时间作为独特的名称
                                for url in img_url:
                                    print(&#39;第&#39; + str(n) + &#39; 张&#39;, end=&#39;&#39;)
                                    with open(Directory + timename + url[-5:], &#39;wb&#39;) as f:
                                        f.write(requests.get(url).content)
                                    print(&#39;...OK!&#39;)
                                    n = n + 1
                        attitudes_count = mblog.get(&#39;attitudes_count&#39;)
                        comments_count = mblog.get(&#39;comments_count&#39;)
                        created_at = mblog.get(&#39;created_at&#39;)
                        reposts_count = mblog.get(&#39;reposts_count&#39;)
                        scheme = cards[j].get(&#39;scheme&#39;)
                        text = mblog.get(&#39;text&#39;)
                        with open(file, &#39;a&#39;, encoding=&#39;utf-8&#39;) as fh:
                            fh.write(&quot;----第&quot; + str(i) + &quot;页，第&quot; + str(j) + &quot;条微博----&quot; + &quot;\n&quot;)
                            fh.write(&quot;微博地址：&quot; + str(scheme) + &quot;\n&quot; + &quot;发布时间：&quot; + str(
                                created_at) + &quot;\n&quot; + &quot;微博内容：&quot; + text + &quot;\n&quot; + &quot;点赞数：&quot; + str(
                                attitudes_count) + &quot;\n&quot; + &quot;评论数：&quot; + str(comments_count) + &quot;\n&quot; + &quot;转发数：&quot; + str(
                                reposts_count) + &quot;\n&quot;)
                i += 1
            else:
                break
        except Exception as e:
            print(e)
            pass


if __name__ == &quot;__main__&quot;:
    file = &#39;/Users/zzf/Desktop/dev/python\\&#39; + id + &quot;.txt&quot;
    get_userInfo(id)
    get_weibo(id, file)
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[escape、encodeURI、encodeURIComponent]]></title>
            <link>https://zzfzzf.com/post/6972548384850841633</link>
            <guid>6972548384850841633</guid>
            <pubDate>Mon, 11 Jan 2021 02:46:46 GMT</pubDate>
            <content:encoded><![CDATA[<p>都是处理编码的</p>
<h3>escape</h3>
<p>对字符串进行编码</p>
<h3>encodeURI</h3>
<p>encodeURI方法不会对下列字符编码 ASCII字母 数字 ~!@#$&amp;*()=:/,;?+&#39;</p>
<p>encodeURIComponent方法不会对下列字符编码 ASCII字母 数字 ~!*()&#39;</p>
<h3>encodeURIComponent</h3>
<p>如果是对字符串进行编码，不涉及url 使用escape<br>如果对url参数进行编码 encodeURIComponent
如果是对整个url编码 使用encodeURI</p>
<h3>decodeURI() 和 decodeURIComponent()</h3>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[样式测试]]></title>
            <link>https://zzfzzf.com/post/6972548384850841632</link>
            <guid>6972548384850841632</guid>
            <pubDate>Mon, 04 Jan 2021 10:55:15 GMT</pubDate>
            <content:encoded><![CDATA[<h1>欢迎使用 Markdown在线编辑器 MdEditor</h1>
<p><strong>Markdown是一种轻量级的「标记语言」</strong></p>
<p><img src="https://www.mdeditor.com/images/logos/markdown.png" alt="markdown" title="markdown"></p>
<p>Markdown是一种可以使用普通文本编辑器编写的标记语言，通过简单的标记语法，它可以使普通文本内容具有一定的格式。它允许人们使用易读易写的纯文本格式编写文档，然后转换成格式丰富的HTML页面，Markdown文件的后缀名便是“.md”</p>
<h2>MdEditor是一个在线编辑Markdown文档的编辑器</h2>
<p><em>MdEditor扩展了Markdown的功能（如表格、脚注、内嵌HTML等等），以使让Markdown转换成更多的格式，和更丰富的展示效果，这些功能原初的Markdown尚不具备。</em></p>
<blockquote>
<p>Markdown增强版中比较有名的有Markdown Extra、MultiMarkdown、 Maruku等。这些衍生版本要么基于工具，如<del>Pandoc</del>，Pandao；要么基于网站，如GitHub和Wikipedia，在语法上基本兼容，但在一些语法和渲染效果上有改动。</p>
</blockquote>
<p>MdEditor源于Pandao的JavaScript开源项目，开源地址<a href="https://github.com/pandao/editor.md" title="Editor.md">Editor.md</a>，并在MIT开源协议的许可范围内进行了优化，以适应广大用户群体的需求。向优秀的markdown开源编辑器原作者Pandao致敬。</p>
<p><img src="https://pandao.github.io/editor.md/images/logos/editormd-logo-180x180.png" alt="Pandao editor.md" title="Pandao editor.md"></p>
<h2>MdEditor的功能列表演示</h2>
<h1>标题H1</h1>
<h2>标题H2</h2>
<h3>标题H3</h3>
<h4>标题H4</h4>
<h5>标题H5</h5>
<h6>标题H5</h6>
<h3>字符效果和横线等</h3>
<hr>
<p><del>删除线</del> <s>删除线（开启识别HTML标签时）</s></p>
<p><em>斜体字</em>      <em>斜体字</em></p>
<p><strong>粗体</strong>  <strong>粗体</strong></p>
<p><em><strong>粗斜体</strong></em> <em><strong>粗斜体</strong></em></p>
<p>上标：X<sub>2</sub>，下标：O<sup>2</sup></p>
<p><strong>缩写(同HTML的abbr标签)</strong></p>
<blockquote>
<p>即更长的单词或短语的缩写形式，前提是开启识别HTML标签时，已默认开启</p>
</blockquote>
<p>The <abbr title="Hyper Text Markup Language">HTML</abbr> specification is maintained by the <abbr title="World Wide Web Consortium">W3C</abbr>.</p>
<h3>引用 Blockquotes</h3>
<blockquote>
<p>引用文本 Blockquotes</p>
</blockquote>
<p>引用的行内混合 Blockquotes</p>
<blockquote>
<p>引用：如果想要插入空白换行<code>即&lt;br /&gt;标签</code>，在插入处先键入两个以上的空格然后回车即可，<a href="https://www.mdeditor.com/">普通链接</a>。</p>
</blockquote>
<h3>锚点与链接 Links</h3>
<p><a href="https://www.mdeditor.com/">普通链接</a>
<a href="https://www.mdeditor.com/" title="普通链接带标题">普通链接带标题</a>
直接链接：<a href="https://www.mdeditor.com">https://www.mdeditor.com</a>
[锚点链接][anchor-id]
[anchor-id]: <a href="https://www.mdeditor.com/">https://www.mdeditor.com/</a>
<a href="mailto:test.test@gmail.com">mailto:test.test@gmail.com</a>
GFM a-tail link @pandao
邮箱地址自动链接 <a href="mailto:test.test@gmail.com">test.test@gmail.com</a>  <a href="mailto:www@vip.qq.com">www@vip.qq.com</a></p>
<blockquote>
<p>@pandao</p>
</blockquote>
<h3>多语言代码高亮 Codes</h3>
<h4>行内代码 Inline code</h4>
<p>执行命令：<code>npm install marked</code></p>
<h4>缩进风格</h4>
<p>即缩进四个空格，也做为实现类似 <code>&lt;pre&gt;</code> 预格式化文本 ( Preformatted Text ) 的功能。</p>
<pre><code>&lt;?php
    echo &quot;Hello world!&quot;;
?&gt;
</code></pre>
<p>预格式化文本：</p>
<pre><code>| First Header  | Second Header |
| ------------- | ------------- |
| Content Cell  | Content Cell  |
| Content Cell  | Content Cell  |
</code></pre>
<h4>JS代码</h4>
<pre><code class="language-javascript">function test() {
    console.log(&quot;Hello world!&quot;);
}
</code></pre>
<h4>HTML 代码 HTML codes</h4>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;mate charest=&quot;utf-8&quot; /&gt;
        &lt;meta name=&quot;keywords&quot; content=&quot;Editor.md, Markdown, Editor&quot; /&gt;
        &lt;title&gt;Hello world!&lt;/title&gt;
        &lt;style type=&quot;text/css&quot;&gt;
            body{font-size:14px;color:#444;font-family: &quot;Microsoft Yahei&quot;, Tahoma, &quot;Hiragino Sans GB&quot;, Arial;background:#fff;}
            ul{list-style: none;}
            img{border:none;vertical-align: middle;}
        &lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;h1 class=&quot;text-xxl&quot;&gt;Hello world!&lt;/h1&gt;
        &lt;p class=&quot;text-green&quot;&gt;Plain text&lt;/p&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h3>图片 Images</h3>
<p>图片加链接 (Image + Link)：</p>
<p><a href="https://www.mdeditor.com/images/logos/markdown.png" title="markdown"><img src="https://www.mdeditor.com/images/logos/markdown.png" alt=""></a></p>
<blockquote>
<p>Follow your heart.</p>
</blockquote>
<hr>
<h3>表格</h3>
<table>
<thead>
<tr>
<th>First Header</th>
<th>Second Header</th>
</tr>
</thead>
<tbody><tr>
<td>Content Cell</td>
<td>Content Cell</td>
</tr>
<tr>
<td>Content Cell</td>
<td>Content Cell</td>
</tr>
</tbody></table>
<h3>列表 Lists</h3>
<h4>无序列表（减号）Unordered Lists (-)</h4>
<ul>
<li>列表一</li>
<li>列表二</li>
<li>列表三</li>
</ul>
<h4>无序列表（星号）Unordered Lists (*)</h4>
<ul>
<li>列表一</li>
<li>列表二</li>
<li>列表三</li>
</ul>
<h4>无序列表（加号和嵌套）Unordered Lists (+)</h4>
<ul>
<li>列表一</li>
<li>列表二<ul>
<li>列表二-1</li>
<li>列表二-2</li>
<li>列表二-3</li>
</ul>
</li>
<li>列表三<ul>
<li>列表一</li>
<li>列表二</li>
<li>列表三</li>
</ul>
</li>
</ul>
<h4>有序列表 Ordered Lists (-)</h4>
<ol>
<li>第一行</li>
<li>第二行</li>
<li>第三行</li>
</ol>
<h4>GFM task list</h4>
<ul>
<li><input checked="" disabled="" type="checkbox"> GFM task list 1</li>
<li><input checked="" disabled="" type="checkbox"> GFM task list 2</li>
<li><input disabled="" type="checkbox"> GFM task list 3<ul>
<li><input disabled="" type="checkbox"> GFM task list 3-1</li>
<li><input disabled="" type="checkbox"> GFM task list 3-2</li>
<li><input disabled="" type="checkbox"> GFM task list 3-3</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> GFM task list 4<ul>
<li><input disabled="" type="checkbox"> GFM task list 4-1</li>
<li><input disabled="" type="checkbox"> GFM task list 4-2</li>
</ul>
</li>
</ul>
<hr>
<h3>绘制表格 Tables</h3>
<table>
<thead>
<tr>
<th>项目</th>
<th align="right">价格</th>
<th align="center">数量</th>
</tr>
</thead>
<tbody><tr>
<td>计算机</td>
<td align="right">$1600</td>
<td align="center">5</td>
</tr>
<tr>
<td>手机</td>
<td align="right">$12</td>
<td align="center">12</td>
</tr>
<tr>
<td>管线</td>
<td align="right">$1</td>
<td align="center">234</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>First Header</th>
<th>Second Header</th>
</tr>
</thead>
<tbody><tr>
<td>Content Cell</td>
<td>Content Cell</td>
</tr>
<tr>
<td>Content Cell</td>
<td>Content Cell</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>First Header</th>
<th>Second Header</th>
</tr>
</thead>
<tbody><tr>
<td>Content Cell</td>
<td>Content Cell</td>
</tr>
<tr>
<td>Content Cell</td>
<td>Content Cell</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>Function name</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><code>help()</code></td>
<td>Display the help window.</td>
</tr>
<tr>
<td><code>destroy()</code></td>
<td><strong>Destroy your computer!</strong></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th align="left">Left-Aligned</th>
<th align="center">Center Aligned</th>
<th align="right">Right Aligned</th>
</tr>
</thead>
<tbody><tr>
<td align="left">col 3 is</td>
<td align="center">some wordy text</td>
<td align="right">$1600</td>
</tr>
<tr>
<td align="left">col 2 is</td>
<td align="center">centered</td>
<td align="right">$12</td>
</tr>
<tr>
<td align="left">zebra stripes</td>
<td align="center">are neat</td>
<td align="right">$1</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>Item</th>
<th align="right">Value</th>
</tr>
</thead>
<tbody><tr>
<td>Computer</td>
<td align="right">$1600</td>
</tr>
<tr>
<td>Phone</td>
<td align="right">$12</td>
</tr>
<tr>
<td>Pipe</td>
<td align="right">$1</td>
</tr>
</tbody></table>
<hr>
<h4>特殊符号 HTML Entities Codes</h4>
<p>&copy; &amp;  &uml; &trade; &iexcl; &pound;
&amp; &lt; &gt; &yen; &euro; &reg; &plusmn; &para; &sect; &brvbar; &macr; &laquo; &middot;</p>
<p>X&sup2; Y&sup3; &frac34; &frac14;  &times;  &divide;   &raquo;</p>
<p>18&ordm;C  &quot;  &apos;</p>
<p>[========]</p>
<h3>Emoji表情 :smiley:</h3>
<blockquote>
<p>Blockquotes :star:</p>
</blockquote>
<h4>GFM task lists &amp; Emoji &amp; fontAwesome icon emoji &amp; editormd logo emoji :editormd-logo-5x:</h4>
<ul>
<li><input checked="" disabled="" type="checkbox"> :smiley: @mentions, :smiley: #refs, <a href="">links</a>, <strong>formatting</strong>, and <del>tags</del> supported :editormd-logo:;</li>
<li><input checked="" disabled="" type="checkbox"> list syntax required (any unordered or ordered list supported) :editormd-logo-3x:;</li>
<li><input checked="" disabled="" type="checkbox"> [ ] :smiley: this is a complete item :smiley:;</li>
<li><input disabled="" type="checkbox"> []this is an incomplete item <a href="#">test link</a> :fa-star: @pandao;</li>
<li><input disabled="" type="checkbox"> [ ]this is an incomplete item :fa-star: :fa-gear:;<ul>
<li><input disabled="" type="checkbox"> :smiley: this is an incomplete item <a href="#">test link</a> :fa-star: :fa-gear:;</li>
<li><input disabled="" type="checkbox"> :smiley: this is  :fa-star: :fa-gear: an incomplete item <a href="#">test link</a>;</li>
</ul>
</li>
</ul>
<h4>反斜杠 Escape</h4>
<p>*literal asterisks*</p>
<p>[========]</p>
<h3>科学公式 TeX(KaTeX)</h3>
<p>$$E=mc^2$$</p>
<p>行内的公式$$E=mc^2$$行内的公式，行内的$$E=mc^2$$公式。</p>
<p>$$x &gt; y$$</p>
<p>$$(\sqrt{3x-1}+(1+x)^2)$$</p>
<p>$$\sin(\alpha)^{\theta}=\sum_{i=0}^{n}(x^i + \cos(f))$$</p>
<p>多行公式：</p>
<pre><code>\displaystyle
\left( \sum\_{k=1}^n a\_k b\_k \right)^2
\leq
\left( \sum\_{k=1}^n a\_k^2 \right)
\left( \sum\_{k=1}^n b\_k^2 \right)
</code></pre>
<pre><code>\displaystyle
    \frac{1}{
        \Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{
        \frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {
        1+\frac{e^{-6\pi}}
        {1+\frac{e^{-8\pi}}
         {1+\cdots} }
        }
    }
</code></pre>
<pre><code>f(x) = \int_{-\infty}^\infty
    \hat f(\xi)\,e^{2 \pi i \xi x}
    \,d\xi
</code></pre>
<h3>分页符 Page break</h3>
<blockquote>
<p>Print Test: Ctrl + P</p>
</blockquote>
<p>[========]</p>
<h3>绘制流程图 Flowchart</h3>
<pre><code>st=&gt;start: 用户登陆
op=&gt;operation: 登陆操作
cond=&gt;condition: 登陆成功 Yes or No?
e=&gt;end: 进入后台

st-&gt;op-&gt;cond
cond(yes)-&gt;e
cond(no)-&gt;op
</code></pre>
<p>[========]</p>
<h3>绘制序列图 Sequence Diagram</h3>
<pre><code>Andrew-&gt;China: Says Hello
Note right of China: China thinks\nabout it
China--&gt;Andrew: How are you?
Andrew-&gt;&gt;China: I am good thanks!
</code></pre>
<h3>End</h3>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[javascript深拷贝和浅拷贝]]></title>
            <link>https://zzfzzf.com/post/6972548384850841629</link>
            <guid>6972548384850841629</guid>
            <pubDate>Mon, 04 Jan 2021 02:13:53 GMT</pubDate>
            <content:encoded><![CDATA[<h2>浅拷贝</h2>
<h3>lodash.clone</h3>
<pre><code class="language-js">var _ = require(&#39;lodash&#39;);
var obj1 = {    a: 1,    b: { f: { g: 1 } },    c: [1, 2, 3]};
var obj2 = _.clone(obj1);console.log(obj1.b.f === obj2.b.f);// true
</code></pre>
<h3><code>...</code>运算符</h3>
<h3>Object.assign</h3>
<h3><code>Array.prototype.concat()</code></h3>
<h3><code>Array.prototype.slice()</code></h3>
<h2>深拷贝</h2>
<h3>JSON.parse(JSON.stringify())</h3>
<blockquote>
<p>缺点：只适用JSON安全的对象</p>
</blockquote>
<ul>
<li>循环引用无法拷贝,BigInt报错</li>
<li>丢失对象的constructor</li>
<li>Infinity和-Infinity为null</li>
<li>只能序列化对象的可枚举属性</li>
<li>正则，函数，undefined</li>
<li>Symbol、undefined,Function会丢失，</li>
<li>正则、Error,Map、Set变空对象</li>
<li>NAN会变null</li>
<li>日期 Date 对象变字符串</li>
</ul>
<pre><code class="language-js">const test = {  name: &quot;test&quot;};
const data = {  a: &quot;123&quot;, 
              b: 123, 
              c: true, 
              d: [43, 2], 
              e: undefined,
              f: null,
              g: function() {    console.log(&quot;g&quot;);  },
              h: new Set([3, 2, null]),
              i: Symbol(&quot;fsd&quot;),
              j: test,
              k: new Map([    [&quot;name&quot;, &quot;张三&quot;],    [&quot;title&quot;, &quot;Author&quot;]  ]),
              l: NaN,
              m: new RegExp(&quot;.*?&quot;),
              n: new Date()
            };

console.log(JSON.parse(JSON.stringify(data)))
</code></pre>
<p><img src="https://oss-zzf.zzfzzf.com/cdn/1643184199916iJaatQ.png" alt="1615270182505BbMqiS"></p>
<h3>lodash.cloneDeep</h3>
<pre><code class="language-js">   const _ = require(&#39;lodash&#39;);
   const obj1 = {    a: 1,    b: { f: { g: 1 } },    c: [1, 2, 3]};
   const obj2 = _.cloneDeep(obj1);console.log(obj1.b.f === obj2.b.f);
</code></pre>
<h3>MessageChannel</h3>
<ol start="4">
<li>如果需要拷贝的对象没有函数，可以使用MessageChannel实现</li>
</ol>
<pre><code class="language-js">function deepClone(val) {
  return new Promise(resolve =&gt; {
    const { port1, port2 } = new MessageChannel()
    port2.onmessage = e =&gt; resolve(e.data)
    port1.postMessage(val)
  })
}
</code></pre>
<h2>递归实现</h2>
<ol>
<li>首先实现一个最简单的深拷贝</li>
</ol>
<pre><code class="language-js">function deepClone(target) {
 let cloneTarget = {};
 const keys=Object.keys(target)
 for (const key of keys) {
     cloneTarget[key] = target[key];
 }
 return cloneTarget;
};
</code></pre>
<ol start="2">
<li>如果是原始类型直接返回，引用类型，递归拷贝</li>
</ol>
<pre><code class="language-javascript">function deepClone(target) {
 if(typeof target===&#39;object&#39;){
     let cloneTarget = Array.isArray(target) ? [] : {};
     const keys = Object.keys(target)
     for (const key of keys) {
         cloneTarget[key] = deepClone(target[key]);
     }
     return cloneTarget;
 }else {
     return target
 }
};
</code></pre>
<ol start="3">
<li>解决循环引用</li>
</ol>
<pre><code class="language-javascript">function deepClone(target,map = new Map()) {
 if(typeof target===&#39;object&#39;){
     let cloneTarget = Array.isArray(target) ? [] : {};
     if (map.has(target)) {
         return target;
     }
     map.set(target, cloneTarget);
     const keys = Object.keys(target)
     for (const key of keys) {
         cloneTarget[key] = deepClone(target[key],map);
     }
     return cloneTarget;
 }else {
     return target
 }
};
</code></pre>
<ol start="4">
<li>进一步完善</li>
</ol>
<blockquote>
<p>单独处理各个数据类型 funtion map set 等等</p>
</blockquote>
<pre><code class="language-js">function deepClone(target, map = new WeakMap()) {
 if (typeof target === &#39;object&#39;) {
     if (target === null) {//null
         return null
     }
     if(target instanceof Date){
         return target
     }
     let cloneTarget = Array.isArray(target) ? [] : {};
     if (map.has(target)) {
         return target;
     }
     map.set(target, cloneTarget);
     const keys = Object.keys(target)
     for (const key of keys) {
         cloneTarget[key] = deepClone(target[key], map);
     }
     return cloneTarget;
 } else {
     return target
 }
}
</code></pre>
<h3>Proxy</h3>
<p>//todo</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[vue实现原理]]></title>
            <link>https://zzfzzf.com/post/6972548384850841628</link>
            <guid>6972548384850841628</guid>
            <pubDate>Mon, 04 Jan 2021 02:00:16 GMT</pubDate>
            <content:encoded><![CDATA[<h2>大概流程</h2>
<blockquote>
<p>使用正则解析模版，生成AST(抽象语法树)，优化之后生成render函数，然后生成虚拟dom，然后通过h函数生成真实dom节点，通过patch函数渲染到页面<br>响应式依赖Object.defineProperty和发布订阅模式实现，在模版解析的时候，把模板中变量换成数据，绑定更新函数，添加订阅者，通过get方法依赖收集，在属性变化之后，通过setter方法通知订阅了该属性的每一个观察者更新视图，生成新的虚拟dom，再调用patch函数用diff算法比较虚拟dom区别并更新差异到视图</p>
</blockquote>
<p>mvvm实现依靠Object.defineProperty和发布订阅模式实现</p>
<h2>watch和computed</h2>
<h3>Vue</h3>
<h3>Compiler</h3>
<h3>Dep</h3>
<h3>Observer</h3>
<p>劫持数据</p>
<h3>Watcher</h3>
<p>更新视图</p>
<ol>
<li>Get() 计算 执行函数</li>
<li>update() 触发run()</li>
<li>run() node同步，浏览器异步，最终调用get()</li>
<li>cleanDep()清除队列</li>
</ol>
<h3>依赖收集和派发更新</h3>
<h3>mvvm/响应式原理</h3>
<ol>
<li>数据劫持+发布订阅 </li>
<li>Object.definePropertty()来劫持各个属性的getter setter，在数据变动时候通知观察者，观察者作出对应的回调去更新视图</li>
<li>数据变动的时候发布消息给订阅者，触发监听回调 </li>
<li>Observer,Compile,Watcher</li>
<li>Observer监听model</li>
<li>Compile解析编译模版</li>
<li>Watcher在Observer和Compile通信，达到数据变化更新视图</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[vue-$nextTick个人理解]]></title>
            <link>https://zzfzzf.com/post/6972548384850841627</link>
            <guid>6972548384850841627</guid>
            <pubDate>Mon, 04 Jan 2021 01:26:40 GMT</pubDate>
            <content:encoded><![CDATA[<h2>vue学习笔记</h2>
<h3>$nextTick</h3>
<pre><code class="language-js">&lt;template&gt;
  &lt;div class=&quot;home&quot;&gt;
    &lt;span ref=&quot;title&quot;&gt;{{name}}&lt;/span&gt;
    &lt;img alt=&quot;Vue logo&quot; src=&quot;../assets/logo.png&quot; /&gt;
    &lt;HelloWorld msg=&quot;Welcome to Your Vue.js App&quot; /&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
// @ is an alias to /src
import HelloWorld from &quot;@/components/HelloWorld.vue&quot;;

export default {
  name: &quot;Home&quot;,
  data(){
    return{
      name:&#39;小红&#39;
    }
  },
  components: {
    HelloWorld
  },
  async mounted() {
    this.name=&#39;小米&#39;
    console.log(3,this.$refs.title);
    await this.$nextTick()
    console.log(4,this.$refs.title);
  },
  async created() {
    this.name=&#39;小猫&#39;
    console.log(1,this.$refs.title);
    await this.$nextTick()
    console.log(2,this.$refs.title);
  }
};
&lt;/script&gt;
</code></pre>
<p><img src="https://cdn.zzfzzf.com//16097246844103PeiuI.png" alt="image-20210104094444125"></p>
<pre><code class="language-js">&lt;template&gt;
  &lt;div class=&quot;home&quot;&gt;
    &lt;span ref=&quot;title&quot;&gt;{{name}}&lt;/span&gt;
    &lt;img alt=&quot;Vue logo&quot; src=&quot;../assets/logo.png&quot; /&gt;
    &lt;HelloWorld msg=&quot;Welcome to Your Vue.js App&quot; /&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
// @ is an alias to /src
import HelloWorld from &quot;@/components/HelloWorld.vue&quot;;

export default {
  name: &quot;Home&quot;,
  data(){
    return{
      name:&#39;小红&#39;
    }
  },
  components: {
    HelloWorld
  },
  async mounted() {
    // this.name=&#39;小米&#39;
    // console.log(3,this.$refs.title);
    // await this.$nextTick()
    // console.log(4,this.$refs.title);
    console.log(1);
    await console.log(2)
    console.log(3)

  },
  async created() {
    console.log(4);
    await console.log(5)
    console.log(6)
    // this.name=&#39;小猫&#39;
    // console.log(1,this.$refs.title);
    // await this.$nextTick()
    // console.log(2,this.$refs.title);
  }
};
&lt;/script&gt;
</code></pre>
<p><img src="https://cdn.zzfzzf.com//1609724791966A3Pmpg.png" alt="image-20210104094631787"></p>
<h2>实现原理</h2>
<p>分为宏任务和微任务</p>
<ul>
<li>微任务检测<code>Promise</code>不支持降级为宏任务</li>
<li>宏任务检测<code>setImmediate</code>不支持就检测<code>MessageChannel</code>，最后降级为<code>setTimeout</code></li>
</ul>
<blockquote>
<p>将callback压入callbacks数组，在下一个tick时遍历callbacks数组
这里使用 callbacks 而不是直接在 nextTick 中执行回调函数的原因是保证在同一个 tick 内多次执行 nextTick，不会开启多个异步任务，而把这些异步任务都压成一个同步任务，在下一个 tick 执行完毕。</p>
</blockquote>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[事件循环]]></title>
            <link>https://zzfzzf.com/post/6972548384850841626</link>
            <guid>6972548384850841626</guid>
            <pubDate>Fri, 01 Jan 2021 12:09:46 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>⚠️本篇只讨论浏览器部分</p>
</blockquote>
<h2>概念</h2>
<h3>进程(process)和线程(thread)</h3>
<p>进程（process）：计算机已经运行的程序，是操作系统管理程序的一种方式。
线程（thread）：操作系统能够运行运算调度的最小单位，通常情况下它被包含在进程中。</p>
<blockquote>
<p>浏览器是多进程的，通常每个tab页都是一个进程，一个页面崩溃不会影响其他页面</p>
</blockquote>
<ol>
<li>进程是操作系统分配资源的最小单位，线程是程序执行的最小单位。</li>
<li>一个进程由一个或多个线程组成，线程是一个进程中代码的不同执行路线；</li>
<li>进程之间相互独立，但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号)。</li>
<li>调度和切换：线程上下文切换比进程上下文切换要快得多。</li>
</ol>
<p>以谷歌浏览器为例，由以下线程组成</p>
<ul>
<li><p>GUI 渲染线程</p>
</li>
<li><p>JavaScript引擎线程</p>
</li>
<li><p>定时触发器线程</p>
</li>
<li><p>事件触发线程</p>
</li>
<li><p>异步http请求线程</p>
</li>
</ul>
<blockquote>
<p>GUI渲染线程与JavaScript引擎互斥、所以JS会阻塞页面的加载</p>
</blockquote>
<h3>调用栈</h3>
<p>管理执行上下文的栈为调用栈、<strong>先进后出</strong>，当入栈的执行上下文超过了一定数目，JS引擎就会报栈溢出错误</p>
<h3>微任务和宏任务</h3>
<p>JavaScript执行分为同步任务和异步任务，同步任务压入执行栈中执行，异步任务放入任务队列中，任务队列又分为微任务队列和宏任务队列</p>
<h4>宏任务有哪些</h4>
<ul>
<li>script标签中运行代码</li>
<li>事件的回调函数</li>
<li><code>setTimeout</code>,<code>setInterval</code>,<code>setImmediate</code>,<code> I/O</code>, <code>UI rendering</code>,<code>requestAnimationFrame</code></li>
</ul>
<h4>微任务有哪些</h4>
<ul>
<li><code>Promise</code></li>
<li><code>MutationObserver</code></li>
<li><code>process.nextTick</code>, <code>Promises</code>, <code>Object.observe</code>, <code>MutationObserver</code>,<code>fetch </code></li>
</ul>
<h3>任务队列</h3>
<p>先进先出的数据结构</p>
<h3>执行机制</h3>
<blockquote>
<p>简单概括</p>
</blockquote>
<ol>
<li>执行栈中的同步任务，如果存在微任务，则该任务进入微任务队列中；如果存在宏任务，则该任务进入宏任务队列中</li>
<li>在执行栈清空后，检查微任务队列中的微任务，微任务队列里的微任务依次出列执行（先进先出），直至队列清空；</li>
<li>检查宏任务队列中的宏任务，宏任务队列取出一个任务执行，执行步骤1</li>
</ol>
<p>⚠️在所有任务开始的时候，由于宏任务中包括了script，所以浏览器会先执行一个宏任务，在这个过程中你看到的延迟任务(例如<code>setTimeout</code>)将被放到下一轮宏任务中来执行。</p>
<blockquote>
<p>浏览器渲染事件循环</p>
</blockquote>
<ol>
<li>一开始整个脚本作为一个宏任务执行</li>
<li>执行过程中同步代码直接执行，宏任务进入宏任务队列，微任务进入微任务队列</li>
<li>当前宏任务执行完出队，检查微任务列表，有则依次执行，直到全部执行完</li>
<li>执行浏览器UI线程的渲染工作</li>
<li>检查是否有Web Worker任务，有则执行</li>
<li>执行完本轮的宏任务，回到2，依此循环，直到宏任务和微任务队列都为空</li>
</ol>
<h2>什么是Event Loop</h2>
<ul>
<li><p>JavaScript是<strong>单线程</strong>的语言</p>
</li>
<li><p>事件循环是JavaScript的执行机制 </p>
</li>
<li><p>JavaScript事件分同步和异步事件</p>
</li>
</ul>
<p> JavaScript引擎在等待任务，执行任务中不断循环，宏任务和微任务执行完成后都会判断是否还有微任务，有的话执行微任务，没有就执行宏任务，如此循环</p>
<p>用代码表示即</p>
<pre><code>for (const macroTask of macroTaskQueue) {  

  handleMacroTask();    

  for (const microTask of microTaskQueue) {    

      handleMicroTask(microTask);  

  }

}
</code></pre>
<h3>题目1</h3>
<pre><code class="language-js">console.log(0)

setTimeout(_ =&gt; console.log(4),0)

new Promise(resolve =&gt; {

  resolve()

  console.log(1)

}).then(_ =&gt; {

  console.log(3)

})

console.log(2)
</code></pre>
<h3>题目2</h3>
<pre><code class="language-js">async function fn1() {
    console.log(1)
}

async function fn2() {
    console.log(2)
}

async function result() {
    await fn1()
    fn2()
    setTimeout(() =&gt; {
       console.log(3);	
    });
    Promise.resolve().then(res =&gt; console.log(4))
    console.log(5);
}

result()
console.log(6)
Promise.resolve().then(res =&gt; console.log(7))
setTimeout(() =&gt; {
    console.log(8);	
});
</code></pre>
<h2>node</h2>
<p><code>Node</code>的事件循环是基于<code>libuv</code>实现的</p>
<blockquote>
<p>待更新</p>
</blockquote>
<h2>总结</h2>
<ol>
<li>js工作机制是单线程的，但是任务有同步任务和异步任务之分</li>
<li>执行任务的地方叫执行栈，而异步任务会进入任务队列等待被执行</li>
<li>执行栈中的任务执行完毕之后，执行栈会去查看任务队列中还有没有未执行的任务。有，则拿出来执行；没有，则开始下一次的工作</li>
<li>重复第三点的工作，不断地处理后续的任务，这样的工作机制就是<strong>事件循环</strong></li>
</ol>
<h2>F&amp;Q</h2>
<ol>
<li>为啥是单进程</li>
</ol>
<p>如果 JavaScript 引擎线程不是单线程的，那么可以同时执行多段 JavaScript，如果这多段 JavaScript 都修改 DOM，那么就会出现 DOM 冲突。所以<strong>避免 DOM 渲染的冲突</strong></p>
<ol>
<li>为什么有异步</li>
</ol>
<p>引入单线程就意味着，所有任务需要排队，前一个任务结束，才会执行后一个任务。这同时又导致了一个问题：如果前一个任务耗时很长，后一个任务就不得不一直等着。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[TypeScript、ESLint、Prettier、Babel代码规范集成]]></title>
            <link>https://zzfzzf.com/post/6972548384850841625</link>
            <guid>6972548384850841625</guid>
            <pubDate>Tue, 29 Dec 2020 06:05:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>以React17为例子</p>
<h3>Babel</h3>
<pre><code class="language-bash">yarn add @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/plugin-transform-runtime -D
</code></pre>
<blockquote>
<p>babel.config.json</p>
</blockquote>
<pre><code class="language-json">{
  &quot;presets&quot;: [
    &quot;@babel/preset-env&quot;,
    &quot;@babel/preset-react&quot;,
    &quot;@babel/preset-typescript&quot;
  ],
  &quot;plugins&quot;: [
    &quot;@babel/plugin-transform-runtime&quot;
  ]
}
</code></pre>
<h2>Eslint</h2>
<blockquote>
<p>.eslintrc.json</p>
</blockquote>
<pre><code class="language-json">{
    &quot;env&quot;: {
        &quot;browser&quot;: true,
        &quot;es2020&quot;: true,
        &quot;node&quot;: true
    },
    &quot;extends&quot;: [
        &quot;plugin:promise/recommended&quot;,
        &quot;plugin:react/recommended&quot;,
        &quot;plugin:react-hooks/recommended&quot;,
        &quot;plugin:@typescript-eslint/recommended&quot;,
        &quot;prettier/@typescript-eslint&quot;,
        &quot;plugin:prettier/recommended&quot;
    ],
    &quot;parser&quot;: &quot;@typescript-eslint/parser&quot;,
    &quot;parserOptions&quot;: {
        &quot;ecmaFeatures&quot;: {
            &quot;jsx&quot;: true
        },
        &quot;ecmaVersion&quot;: 2020,
        &quot;sourceType&quot;: &quot;module&quot;
    },
    &quot;plugins&quot;: [
        &quot;promise&quot;,
        &quot;react&quot;,
        &quot;@typescript-eslint&quot;,
        &quot;react-hooks&quot;,
        &quot;prettier&quot;
    ],
    &quot;rules&quot;: {
      //0 关闭 1 警告 2 错误
        &quot;prettier/prettier&quot;: 2,
        &quot;react-hooks/rules-of-hooks&quot;: 2,
        &quot;react-hooks/exhaustive-deps&quot;: 1,
        &quot;react/display-name&quot;: 0,
        &quot;react/prop-types&quot;:0,
        &quot;@typescript-eslint/no-var-requires&quot;: 0
    },
    &quot;settings&quot;: {
        &quot;react&quot;: {
            &quot;version&quot;: &quot;detect&quot;
        }
    }
}
</code></pre>
<blockquote>
<p>tsconfig.json</p>
</blockquote>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES2020&quot;,
    &quot;module&quot;: &quot;commonjs&quot;,
    &quot;allowJs&quot;: true,
    &quot;jsx&quot;: &quot;react&quot;,
    &quot;declaration&quot;: false,
    &quot;downlevelIteration&quot;: true,
    &quot;strict&quot;: true,
    &quot;noImplicitAny&quot;: true,
    &quot;strictNullChecks&quot;: true,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;baseUrl&quot;: &quot;./src&quot;,
    &quot;paths&quot;: {
      &quot;api/*&quot;: [&quot;services/*&quot;]
    },
    &quot;types&quot;: [&quot;node&quot;, &quot;webpack-env&quot;],
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true
  },
  &quot;exclude&quot;: [&quot;config&quot;, &quot;commitlint.config.js&quot;]
}
</code></pre>
<blockquote>
<p>.prettierrc</p>
</blockquote>
<pre><code class="language-json">{
  &quot;trailingComma&quot;: &quot;all&quot;,
  &quot;tabWidth&quot;: 2,
  &quot;semi&quot;: true,
  &quot;singleQuote&quot;: true,
  &quot;endOfLine&quot;: &quot;lf&quot;,
  &quot;printWidth&quot;: 100,
  &quot;bracketSpacing&quot;: true,
  &quot;arrowParens&quot;: &quot;always&quot;,
  &quot;useTabs&quot;: false,
  &quot;quoteProps&quot;: &quot;as-needed&quot;,
  &quot;jsxSingleQuote&quot;: true,
  &quot;jsxBracketSameLine&quot;: false,
  &quot;requirePragma&quot;: false,
  &quot;insertPragma&quot;: false,
  &quot;proseWrap&quot;: &quot;preserve&quot;,
  &quot;htmlWhitespaceSensitivity&quot;: &quot;css&quot;,
  &quot;vueIndentScriptAndStyle&quot;: false,
  &quot;embeddedLanguageFormatting&quot;: &quot;auto&quot;
}
</code></pre>
<h2>lint-staged和husky</h2>
<p>在package.json添加</p>
<pre><code class="language-json">{
  &quot;husky&quot;: {
      &quot;hooks&quot;: {
          &quot;pre-commit&quot;: &quot;lint-staged&quot;
      }
  },
  &quot;lint-staged&quot;: {
    &quot;src/**/*.{ts,tsx,js,json,less,md}&quot;: [
      &quot;prettier --write&quot;
    ],
    &quot;src/**/*.{ts,tsx,js}&quot;: [
      &quot;eslint --config .eslintrc.json&quot;
    ]
  }
}
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[nth-child和nth-of-type]]></title>
            <link>https://zzfzzf.com/post/6972548384850841623</link>
            <guid>6972548384850841623</guid>
            <pubDate>Fri, 25 Dec 2020 08:22:19 GMT</pubDate>
            <content:encoded><![CDATA[<h2>nth-of-type</h2>
<p><img src="https://cdn.zzfzzf.com//16088847459176wAQty.png" alt="image-20201225162545220"></p>
<h2>nth-child</h2>
<p><img src="https://cdn.zzfzzf.com//1608884773374PGctnf.png" alt="image-20201225162613256"></p>
<h2>nth-child</h2>
<p><img src="https://cdn.zzfzzf.com//1608884819425Ph7snr.png" alt="image-20201225162659309"></p>
<h2>nth-of-type</h2>
<p><img src="https://cdn.zzfzzf.com//1608884834835RuEwwW.png" alt="image-20201225162714731"></p>
<h2>使用odd测试</h2>
<ul>
<li><img src="https://cdn.zzfzzf.com//1608885093442T1tQ8K.png" alt="image-20201225163133338"></li>
<li><img src="https://cdn.zzfzzf.com//1608885051926P9sBeq.png" alt="image-20201225163051823"></li>
<li><img src="https://cdn.zzfzzf.com//1608885032019NG68o5.png" alt="image-20201225163031909"></li>
</ul>
<blockquote>
<p>nth-of-type 在同类标签中找，nth-child在所有标签中找正好位置是那个的那个元素 否则就无效</p>
</blockquote>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[js总结之数组]]></title>
            <link>https://zzfzzf.com/post/6972548384850841622</link>
            <guid>6972548384850841622</guid>
            <pubDate>Tue, 22 Dec 2020 15:03:53 GMT</pubDate>
            <content:encoded><![CDATA[<h2>创建数组</h2>
<p>使用[]或者Array()构造函数来创建数组</p>
<pre><code class="language-js">let arr1 = [1, 2, 3];
let arr2 = Array(4, 5, 6);
</code></pre>
<h3>传统方法</h3>
<pre><code class="language-js">const arr=[]
</code></pre>
<h3>构造函数</h3>
<pre><code class="language-js">const arr=Array(3)
[...Array(100).fill(0)]
</code></pre>
<h2>类数组</h2>
<p>类数组是指具有length属性和一组按照索引值存储数据的对象，但是它不具备数组的所有特性。在JavaScript中，有很多内置对象都可以被看作是类数组，例如DOM中的NodeList对象、函数中的arguments对象等。这些对象都具有类数组的特点，但是不能直接使用数组的方法进行操作。</p>
<h3>类数组转化</h3>
<pre><code class="language-js">Array.prototype.slice.call()
[...]
Array.apply(null, { length: 100 }).fill(0);
Array.from({length: 100}, _ =&gt; 0)
Array.from({length: 100}).map(()=&gt; 0)
</code></pre>
<h2>创建0-100的数组</h2>
<pre><code class="language-js">传统方法 for 循环
Object.keys(Array.from({length: 100}));
[...new Array(100).keys()]
Array.from({length:100},(v,i) =&gt; i)
Array.from(new Array(100).keys())
[...Array(100).keys()]
[...Array.from({length:100}).keys()]
</code></pre>
<h2>实现flat</h2>
<ol>
<li>遍历</li>
<li>判断是否数组</li>
</ol>
<h2>数组空位</h2>
<blockquote>
<p>存在空位的数组为稀疏数组，没有空位的为密数组</p>
</blockquote>
<pre><code class="language-js">[,,,]//[empty × 3]
new Array(3)//[empty × 3]
</code></pre>
<blockquote>
<p>es5数组的方法处理一般会跳过数组的空位</p>
</blockquote>
<pre><code class="language-js">// forEach 忽略空位
[1, , 2].forEach(v =&gt; console.log(v)); // 1 2

// for in 忽略空位
for(let key in [1, , 2]){ console.log(key); } // 0 2

// filter 忽略空位
console.log([1, , 2].filter(v =&gt; true)); // [1, 2]

// every 忽略空位
console.log([1, , 1].every(v =&gt; v === 1)); // true

// some 忽略空位
console.log([1, , 1].some(v =&gt; v !== 1)); // false

// map 遍历时忽略空位 新数组保留空位
console.log([1, , 1].map(v =&gt; 11)); // (3) [11, empty, 11]

// join 将空位与undefined以及null视为空字符串
console.log([1, , 1, null, undefined].join(&quot;|&quot;)); // 1||1||

// toString 将空位与undefined以及null视为空字符串
console.log([1, , 1, null, undefined].toString()); // 1,,1,,`
</code></pre>
<blockquote>
<p>es6数组的方法处理一般把空位转化为undefined</p>
</blockquote>
<pre><code class="language-js">// Array.form 将空位转为undefined
console.log(Array.from([1, , 2])); // (3) [1, undefined, 2]

// ... 将空位转为undefined
console.log([...[1, , 2]]); // (3) [1, undefined, 2]

// copyWithin 将空位一并拷贝
console.log([1, , 2].copyWithin()); // (3) [1, empty, 2]

// for of 遍历空位并将值作为undefined
for(let key of [1, , 2]){ console.log(key); } // 1 undefined 2

// includes 将空位处理成undefined
console.log([, , ,].includes(undefined)); // true

// entries 将空位处理成undefined
console.log([...[1, , 2].entries()]); // [[0, 1], [1, undefined], [2, 2]]

// keys 会取出空位的索引
console.log([...[1, , 2].keys()]); // [0, 1, 2]

// values 将空位处理成undefined
console.log([...[1, , 2].values()]); // [1, undefined, 2]

// find 将空位处理成undefined
console.log([, , 1].find(v =&gt; true)); // undefined

// find 将空位处理成undefined
console.log([, , 1].findIndex(v =&gt; true)); // 0
</code></pre>
<h2>基础数据类型的数组</h2>
<pre><code class="language-js">const array = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
console.log([...new Set(array)])//[1]
console.log(Array.from(new Set(array)))//[1]
</code></pre>
<h2>引用对象类型的数组</h2>
<h3>filter</h3>
<blockquote>
<p>循环遍历每个值，判断索引，当前索引和<code>findIndex</code>索引一致则不重复，不一致则重复</p>
</blockquote>
<pre><code class="language-js">const arr = [{name: &#39;zxc&#39;}, {name: &#39;zxc&#39;}, {name: &#39;zxc&#39;}, {name: &#39;zxc&#39;}]
const b=arr.filter((item,idx)=&gt;{
    return arr.findIndex(_=&gt;_.name===item.name)===idx
})
console.log(b)//[{name: &quot;zxc&quot;}]
</code></pre>
<blockquote>
<p>中间值hash，循环遍历，如果存在返回flase，不存在加入hash返回true</p>
</blockquote>
<h3>reduce</h3>
<pre><code class="language-js">const arr = [{name: &#39;zxc&#39;}, {name: &#39;zxc&#39;}, {name: &#39;zxc&#39;}, {name: &#39;zxc&#39;}]
const a=arr.reduce((prev, curr) =&gt; {
    if (prev.some(_=&gt;_.name===curr.name)) {
        return prev
    } else {
       return [...prev,curr]
    }
}, [])
console.log(a)//[{name: &quot;zxc&quot;}]
</code></pre>
<h3>findIndex</h3>
<pre><code class="language-javascript">const arr = [{name: &#39;zxc&#39;}, {name: &#39;zx1c&#39;}, {name: &#39;zxc&#39;}, {name: &#39;zx1c&#39;},]
const newArray = []
arr.forEach((item, idx) =&gt; {
    if (arr.findIndex(i =&gt; i.name === item.name) === idx) {
        newArray.push(item)
    }
})
console.log(newArray)
</code></pre>
<h3>Map</h3>
<pre><code class="language-javascript">const arr = [{name: &#39;zxc&#39;}, {name: &#39;zx1c&#39;}, {name: &#39;zxc&#39;}, {name: &#39;zx1c&#39;},];
const newArray = []
const set = new Set()
arr.forEach((item) =&gt; {
    if (!set.has(item.name)) {
        newArray.push(item)
        set.add(item.name)
    }
})
console.log(newArray)
</code></pre>
<p><a href="https://juejin.cn/post/7199836397951041591">参考文章</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[git常用知识总结]]></title>
            <link>https://zzfzzf.com/post/6972548384850841621</link>
            <guid>6972548384850841621</guid>
            <pubDate>Tue, 22 Dec 2020 07:35:18 GMT</pubDate>
            <content:encoded><![CDATA[<h3>设置本机用户名密码</h3>
<pre><code class="language-bash">git config --global user.name &quot;用户名&quot;
git config --global user.email &quot;邮箱地址&quot;
</code></pre>
<h3>日常开发常用命令</h3>
<ol>
<li>克隆仓库</li>
</ol>
<pre><code class="language-bash">git clone 仓库地址
</code></pre>
<ol start="2">
<li><p>初始化</p>
<pre><code class="language-bash">git init
</code></pre>
</li>
<li><p>查看分支</p>
</li>
</ol>
<pre><code class="language-bash">git branch -a
</code></pre>
<ol start="3">
<li>创建分支</li>
</ol>
<pre><code class="language-bash">git branch 分支名称
</code></pre>
<ol start="4">
<li>切换分支</li>
</ol>
<pre><code class="language-bash">git checkout 需要切换到的分支名称
</code></pre>
<ol start="5">
<li>创建并切换分支</li>
</ol>
<pre><code class="language-bash">git checkout -b 分支名称
</code></pre>
<ol start="6">
<li>回滚</li>
</ol>
<pre><code class="language-bash">git reset --hard id
</code></pre>
<ol start="7">
<li>其他</li>
</ol>
<pre><code class="language-bash">git log
git staus
</code></pre>
<h3>git工作流</h3>
<h4>主要分为以下分支</h4>
<ul>
<li>master</li>
</ul>
<p>master分支的commit都打tag</p>
<ul>
<li>develop</li>
<li>feature</li>
<li>release</li>
<li>hotfix</li>
</ul>
<h2>git---merge和rebase区别</h2>
<pre><code>merge 是一个合并操作，会将两个分支的修改合并在一起

merge 的提交历史忠实地记录了实际发生过什么，关注点在真实的提交历史上面

rebase 并没有进行合并操作，只是提取了当前分支的修改，将其复制在了目标分支的最新提交后面
</code></pre>
<p><a href="https://www.cnblogs.com/cnblogsfans/p/5075073.html">https://www.cnblogs.com/cnblogsfans/p/5075073.html</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[输入url后发生了什么]]></title>
            <link>https://zzfzzf.com/post/6972548384850841620</link>
            <guid>6972548384850841620</guid>
            <pubDate>Tue, 22 Dec 2020 06:51:44 GMT</pubDate>
            <content:encoded><![CDATA[<h2>URL解析</h2>
<p>url由协议、域名、端口、路径、查询参数、锚点组成
解析出域名ip地址、下一步进行DNS查询
<img src="https://oss-zzf.zzfzzf.com/cdn/1643352490531vIf9Xa.jpeg" alt="1643352490531vIf9Xa"></p>
<h2>缓存查询</h2>
<p>网络进程检查强缓存，若有责直接返回给浏览器进程，没有则进入网络请求过程</p>
<h2>DNS查询</h2>
<blockquote>
<p>如果是域名就解析成ip</p>
</blockquote>
<ol>
<li>检查浏览器缓存，客户端和浏览器，本地DNS之间的查询方式是递归查询；</li>
<li>检查本机缓存</li>
<li>检查host，</li>
<li>检查路由器缓存、</li>
<li>DNS查询，检查互联网提供商缓存，根域名、顶级域名、次域名查询、本地DNS服务器与根域及其子域之间的查询方式是迭代查询；</li>
</ol>
<blockquote>
<p>DNS解析流程</p>
</blockquote>
<ul>
<li>首先会访问本地域名服务器，这个本地域名服务器一般都是有你的网络服务商（电信、移动等）分配的，它通常就在网络服务商的某个机房中.</li>
<li>本地域名服务器在没有这个时候就需要访问根服务器了，根服务器里面存储的都是顶级域名服务器地址，根服务器会根据你的顶级域名找到顶级域名的服务器然后返回给你顶级域名服务器的地址</li>
<li>等到顶级域名服务器之后，访问顶级域名服务器，这里面存储的都是二级域名服务器的地址。这个时候根据你的二级域名来找到对应二级域名服务器如果你的域名没有三级域名那么就会直接返回你IP,如果还有三级域名则以此类推继续找三级域名的IP地址最终就找到了IP。</li>
<li>找到对应的IP之后返回给本地DNS,此时本地DNS再将IP地址返回给客户端，最后建立链接至此DNS解析流程结束</li>
</ul>
<h2>TCP连接</h2>
<blockquote>
<p>三次握手,确定通行双方的发送与接收功能是否正常，同步通信序列号，交换tcp通信窗口大小与最大报文段长度（mss）
查询到IP地址和端口后进行三次握手</p>
</blockquote>
<p>服务端首先要在某个端口进入<code>Listen</code>状态</p>
<p>客户端发送<strong>SYN同步报文</strong>，把<code>SYN标志位</code>置1，<strong>序列号(sequence number) 生成一个跟时间有关的随机数</strong>，目的是为了告诉服务端客户端的<strong>初始序列号</strong>，序列号的作用是解决<strong>包乱序、重复，以及对端回复确认收到的数据</strong>，同时客户端进入<code>SYN_SENT</code>状态</p>
<p>服务端收到客户端的<code>SYN</code>包后，回复<code>SYN+ACK</code>,<strong>序列号</strong>也是生成一个服务端的序列号，<strong>确认号</strong>为客户端传来的<strong>序列号+1</strong>，告诉客户端，这个序列号之前的数据都已经接收到，其实在这里就是回复收到了客户端的请求报文。服务端进入<code>SYN_RCVD</code>状态。</p>
<p>客户端收到服务端的<code>SYN+ACK</code>包后，回复<code>ACK</code>，也代表客户端收到了服务端的同步报文，同时，客户端进入<code>ESTABLISHED</code>状态。服务端收到<code>ACK</code>包后，进入<code>ESTABLISHED</code>状态。</p>
<ol>
<li><p>浏览器对服务器 我喜欢你</p>
</li>
<li><p>服务器对浏览器 我也喜欢你</p>
</li>
<li><p>浏览器对服务器 知道啦（不然浏览器不知道服务器也喜欢浏览器）</p>
<blockquote>
<p> F&amp;Q </p>
</blockquote>
<p>http1.0 1.1 2.0区别</p>
</li>
</ol>
<h2>http请求</h2>
<p>握手完成之后，就要<strong>构建请求行</strong>，例如包含<code>GET /index.html http/1.1</code>.</p>
<ul>
<li>首先发送请求行</li>
<li>发送请求头</li>
<li>如果是<code>POST</code>请求等，再发送请求体</li>
</ul>
<h2>https请求</h2>
<p>如果请求的URL地址是<code>HTTPS</code>协议，那么还要进行<code>TLS</code>握手。</p>
<ul>
<li>客户端发送<code>Client Hello</code>包，里面主要包含<code>密码套件、客户端随机数Client_Random、TLS版本</code>，密码套件包括HTTPS中使用的算法<code>对称加密、非对称加密、摘要算法</code>,例如<code>Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)</code>，分别是<code>非对称加密算法，非对称加密算法（可能是用于身份验证，私钥加密）、对称加密算法、摘要算法</code></li>
<li>服务端发送<code>Server Hello</code>包，包含选择的<code>密码套件、服务器随机数Server_Random，版本号</code></li>
<li>服务端发送证书</li>
<li>如果是<code>ECDHE</code>算法，服务端发送<code>Server Key Exchange</code>，主要是为了发送<code>Server_Param</code>，用于生成<strong>第三个数</strong>，也就是配合<code>Client_Param</code>生成<strong>合成会话密钥的最后一个数.</strong></li>
<li>服务端发送<code>Server Hello Done</code>，打招呼结束了</li>
<li>客户端收到服务端证书，进行校验<ul>
<li>证书有<strong>服务器公钥，服务器其它信息，还有一个数字签名</strong>，客户端用系统内置的<strong>CA公钥</strong>来解密数字签名，获得一个<strong>消息摘要</strong>。</li>
<li>根据<strong>服务器公钥，以及其它内容</strong>，通过相同的<code>HASH算法</code>，生成一个<strong>消息摘要</strong>，<strong>生成的消息摘要跟解密获得的消息摘要比较是否相同</strong>。相同则该公钥是服务器的公钥，也就是完成<strong>服务器的身份验证</strong></li>
</ul>
</li>
<li>校验证书通过后，用获得的<strong>服务器公钥</strong>加密发送<code>Client Key Exchange</code>，给服务器发送<code>Client_Param</code>。同时，自己根据<code>Client_Param、Server_Param</code>，生成一个<code>pre-master</code>，再根据<code>Client_Random、Server_Random、pre-master</code>生成最终的<strong>会话密钥---对称加密的钥匙</strong></li>
<li>客户端发送<code>Change Cipher Spec</code>，代表下面的通信就将使用加密通信，也为了校验会话密钥两者是否相同。</li>
<li>服务端也用同样的规则，生成一个会话密钥，也发送<code>Change Cipher Spec</code>，TLS握手结束。</li>
</ul>
<h2>接收响应</h2>
<p>等待服务器返回响应。</p>
<p>响应也是<code>状态行、响应头、响应体</code> 根据状态码，如果是301，那么就要根据<code>Location</code>进行重定向了，
如果是200，代表浏览器可以继续处理请求。
根据<code>Content-Type</code>，如果是文件，就交给浏览器的下载管理器去下载。
如果是HTML，就提供给<strong>渲染进程</strong>，进行页面的渲染了。</p>
<blockquote>
<p>常见状态码</p>
</blockquote>
<ul>
<li><p>1xx：指示信息–表示请求已接收，继续处理。</p>
</li>
<li><p>2xx：成功–表示请求已被成功接收、理解、接受。</p>
</li>
<li><p>3xx：重定向–要完成请求必须进行更进一步的操作。</p>
</li>
<li><p>4xx：客户端错误–请求有语法错误或请求无法实现。</p>
</li>
<li><p>5xx：服务器端错误–服务器未能实现合法的请求。</p>
</li>
</ul>
<h2>TCP四次挥手</h2>
<blockquote>
<p>客户端来决定何时关闭连接,确保数据能够完整传输
假如客户端没有数据发送，要关闭TCP连接了。
1、 客户端发送<code>FIN</code>包，把FIN标志位置1，客户端进入<code>FIN-WAIT1</code>状态。
2、 服务端回复<code>ACK</code>包，服务端进入<code>CLOST-WAIT</code>状态。
3、 客户端接收到<code>ACK</code>包后，进入<code>FIN-WAIT2</code>状态。</p>
</blockquote>
<p>等待服务器的主动关闭
4、 服务器发送<code>FIN</code>报文。服务器进入<code>LAST-ACK</code>状态
5、 客户端回复<code>ACK</code>报文，客户端进入<code>TIME_WAIT</code>状态，等待<strong>2MSL</strong>后，进入<code>CLOSE</code>状态。
6、 服务器收到回复后，进入<code>CLOSE</code>状态。</p>
<ol>
<li>浏览器对服务器 我说完了，再见</li>
<li>服务器对浏览器 好的，我还有几句话说一下</li>
<li>服务器对浏览器 说完了</li>
<li>浏览器对服务器 拜拜</li>
</ol>
<h2>渲染页面</h2>
<blockquote>
<p>chrome未每个页面分配一个渲染进程，同一站点复用一个进程</p>
</blockquote>
<ul>
<li><p>解析HTML生成DOM树。</p>
</li>
<li><p>解析CSS生成CSSOM规则树。</p>
</li>
<li><p>将DOM树与CSSOM规则树合并在一起生成渲染树。</p>
</li>
<li><p>递归遍历渲染树开始布局，计算每个节点的位置大小信息。</p>
</li>
<li><p>将渲染树每个节点绘制到屏幕。</p>
</li>
</ul>
<blockquote>
<p>合成层的硬件加速</p>
</blockquote>
<ul>
<li><p>3D 或透视变换(perspective transform) CSS 属性</p>
</li>
<li><p>使用加速视频解码的 <em>video</em> 元素 拥有 3D</p>
</li>
<li><p><em>WebGL</em> 上下文或加速的 2D 上下文的 <em>canvas</em> 元素</p>
</li>
<li><p>混合插件(如 Flash)</p>
</li>
<li><p>对自己的 opacity 做 CSS动画或使用一个动画变换的元素</p>
</li>
<li><p>拥有加速 CSS 过滤器的元素</p>
</li>
<li><p>元素有一个包含复合层的后代节点(换句话说，就是一个元素拥有一个子元素，该子元素在自己的层里)</p>
</li>
<li><p>元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)</p>
</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[vue2生命周期与属性执行顺序]]></title>
            <link>https://zzfzzf.com/post/6972548384850841619</link>
            <guid>6972548384850841619</guid>
            <pubDate>Tue, 22 Dec 2020 04:07:16 GMT</pubDate>
            <content:encoded><![CDATA[<ul>
<li>new Vue()</li>
<li>Before created</li>
<li>Inject</li>
<li>props</li>
<li>methods</li>
<li>data</li>
<li>computed</li>
<li>Watch</li>
<li>Provide</li>
<li>Created</li>
</ul>
<table>
<thead>
<tr>
<th>生命周期</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td>beforeCreate</td>
<td>组件实例被创建之初，组件的属性生效之前</td>
</tr>
<tr>
<td>created</td>
<td>组件实例已经完全创建，属性也绑定，但真实 dom 还没有生成，$el 还不可用</td>
</tr>
<tr>
<td>beforeMount</td>
<td>在挂载开始之前被调用：相关的 render 函数首次被调用</td>
</tr>
<tr>
<td>mounted</td>
<td>el 被新创建的 vm.$el 替换，并挂载到实例上去之后调用该钩子</td>
</tr>
<tr>
<td>beforeUpdate</td>
<td>组件数据更新之前调用，发生在虚拟 DOM 打补丁之前</td>
</tr>
<tr>
<td>update</td>
<td>组件数据更新之后</td>
</tr>
<tr>
<td>activited</td>
<td>keep-alive 专属，组件被激活时调用</td>
</tr>
<tr>
<td>deactivated</td>
<td>keep-alive 专属，组件被销毁时调用</td>
</tr>
<tr>
<td>beforeDestory</td>
<td>组件销毁前调用</td>
</tr>
<tr>
<td>destoryed</td>
<td>组件销毁后调用</td>
</tr>
</tbody></table>
<p><img src="https://cdn.zzfzzf.com//1613697897431cF1pJF.jpeg" alt="1613697897431cF1pJF">
<img src="https://cdn.zzfzzf.com//16136997872597dH8hr.png" alt="16136997872597dH8hr">
<img src="https://cdn.zzfzzf.com//16106245413564IfCH9.jpg" alt="16106245413564IfCH9">
for-if-once</p>
<h2>父子组件</h2>
<h3>加载渲染</h3>
<ol>
<li>父beforeCreate-&gt;</li>
<li>父created-&gt;</li>
<li>父beforeMount-&gt;</li>
<li>子beforeCreate-&gt;</li>
<li>子created-&gt;</li>
<li>子beforeMount-&gt;</li>
<li>子mounted-&gt;</li>
<li>父mounted</li>
</ol>
<h3>子组件更新过程</h3>
<p>父 beforeUpdate -&gt; 子 beforeUpdate -&gt; 子 updated -&gt; 父 updated</p>
<h3>父组件更新过程</h3>
<p>父 beforeUpdate -&gt; 父 updated</p>
<h3>销毁过程</h3>
<p>父 beforeDestroy -&gt; 子 beforeDestroy -&gt; 子 destroyed -&gt; 父 destroyed</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[收藏夹-待分类]]></title>
            <link>https://zzfzzf.com/post/6972548384850841618</link>
            <guid>6972548384850841618</guid>
            <pubDate>Mon, 21 Dec 2020 13:05:14 GMT</pubDate>
            <content:encoded><![CDATA[<h2>未分类</h2>
<ul>
<li><a href="https://github.com/timqian/chinese-independent-blogs">中文独立博客列表</a></li>
<li><a href="https://github.com/brickspert/blog/issues/1">从零搭建React全家桶框架教程</a></li>
<li><a href="https://juejin.cn/post/6844903859488292871">最全的Eslint配置模板，从此统一团队的编程习惯</a></li>
<li><a href="https://juejin.cn/post/6867715946941775885">落魄前端，整理给自己的前端知识体系复习大纲</a></li>
<li><a href="https://juejin.cn/post/6855579207448133646">如何设计低代码平台快速构建页面</a></li>
<li><a href="https://juejin.cn/post/6869249404495200263">用 TypeScript 写 React &amp; Redux - 完全指南</a></li>
<li><a href="https://juejin.cn/post/6844904129769242632">从零搭建中后台框架的核心流程</a></li>
<li><a href="https://juejin.cn/post/6871020964998545422">前端 10 问之 TypeScript (第一篇）</a></li>
<li><a href="https://juejin.cn/post/6844903621805473800">使用ESLint+Prettier来统一前端代码风格</a></li>
<li><a href="https://juejin.cn/post/6844904045862191118">2020 Create React App 开始一个UI组件库</a></li>
<li><a href="https://github.com/ctq123/antd-custom">antd + react + redux + webpack4 + react-router4基础框架</a></li>
<li><a href="https://juejin.cn/post/6844904055039344654">TS 常见问题整理（60多个，持续更新ing）</a></li>
<li><a href="https://juejin.cn/post/6844904137662955527">从零开始的 webpack4 + React 构建</a></li>
<li><a href="https://juejin.cn/post/6844903605086978062">TypeScript+Webpack+React组件库开发采坑实记</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/107832106">从零开始配置 react + typescript（三）：webpack</a></li>
<li><a href="https://www.html.cn/doc/webpack2/guides/webpack-and-typescript/">Webpack &amp; Typescript</a></li>
<li><a href="https://github.com/kon9chunkit/GitHub-Chinese-Top-Charts">Github中文排行榜</a></li>
<li><a href="https://github.com/iptv-org/iptv">IPTV</a></li>
<li><a href="https://github.com/kujian/frontendDaily">前端开发博客</a></li>
</ul>
<h2>bilibili</h2>
<ul>
<li><a href="https://www.bilibili.com/video/BV1dt411G7Je">https://www.bilibili.com/video/BV1dt411G7Je</a></li>
<li><a href="https://www.bilibili.com/video/BV1e54y1x7zB">https://www.bilibili.com/video/BV1e54y1x7zB</a></li>
<li><a href="https://www.bilibili.com/video/BV1pJ411H7jS">https://www.bilibili.com/video/BV1pJ411H7jS</a></li>
<li><a href="https://www.bilibili.com/video/av59639585">https://www.bilibili.com/video/av59639585</a></li>
</ul>
<h2>掘金好文</h2>
<ul>
<li><a href="https://juejin.cn/post/6909788084666105864">https://juejin.cn/post/6909788084666105864</a></li>
<li><a href="https://juejin.cn/post/6844904085213151239">https://juejin.cn/post/6844904085213151239</a></li>
<li><a href="https://juejin.cn/post/6844904104150433799">https://juejin.cn/post/6844904104150433799</a></li>
<li><a href="https://juejin.cn/post/6906401963655561223">https://juejin.cn/post/6906401963655561223</a></li>
<li><a href="https://juejin.cn/post/6905635299897032718">https://juejin.cn/post/6905635299897032718</a></li>
<li><a href="https://juejin.cn/post/6904994458303987720">https://juejin.cn/post/6904994458303987720</a></li>
<li><a href="https://juejin.cn/post/6844904085213151239">https://juejin.cn/post/6844904085213151239</a></li>
<li><a href="https://juejin.cn/post/6897791146194386952">https://juejin.cn/post/6897791146194386952</a></li>
<li><a href="https://juejin.im/post/6860129883398668296">https://juejin.im/post/6860129883398668296</a></li>
<li><a href="https://juejin.cn/post/6847902225025466376">https://juejin.cn/post/6847902225025466376</a></li>
<li><a href="https://juejin.cn/post/6844904111259942925">https://juejin.cn/post/6844904111259942925</a></li>
<li><a href="https://juejin.cn/post/6892918622230937613">https://juejin.cn/post/6892918622230937613</a></li>
<li><a href="https://juejin.im/post/6892003555818143752">https://juejin.im/post/6892003555818143752</a></li>
<li><a href="https://juejin.cn/post/6844904137709060104">https://juejin.cn/post/6844904137709060104</a></li>
<li><a href="https://juejin.cn/post/6844904048043229192">https://juejin.cn/post/6844904048043229192</a></li>
<li><a href="https://juejin.cn/post/6898482129592647693">https://juejin.cn/post/6898482129592647693</a></li>
<li><a href="https://juejin.cn/post/6897096999724646408">https://juejin.cn/post/6897096999724646408</a></li>
<li><a href="https://juejin.cn/post/6862341107859062791">https://juejin.cn/post/6862341107859062791</a></li>
<li><a href="https://juejin.cn/post/6850418121669345288">https://juejin.cn/post/6850418121669345288</a></li>
<li><a href="https://juejin.cn/post/6844903537487396871">https://juejin.cn/post/6844903537487396871</a></li>
<li><a href="https://juejin.cn/post/6844903749912100871">https://juejin.cn/post/6844903749912100871</a></li>
<li><a href="https://juejin.cn/post/6875943665597546510">https://juejin.cn/post/6875943665597546510</a></li>
<li><a href="https://juejin.cn/post/6844903976693940231">https://juejin.cn/post/6844903976693940231</a></li>
<li><a href="https://juejin.cn/post/6844903897610321934">https://juejin.cn/post/6844903897610321934</a></li>
<li><a href="https://juejin.im/post/6892994632968306702">https://juejin.im/post/6892994632968306702</a></li>
<li><a href="https://juejin.cn/post/6893456594042880008">https://juejin.cn/post/6893456594042880008</a></li>
<li><a href="https://juejin.cn/post/6899689989207818254">https://juejin.cn/post/6899689989207818254</a></li>
<li><a href="https://juejin.cn/post/6844903858070618126">https://juejin.cn/post/6844903858070618126</a></li>
<li><a href="https://juejin.cn/post/6896657931270373389">https://juejin.cn/post/6896657931270373389</a></li>
<li><a href="https://juejin.cn/post/6876981358346895368">https://juejin.cn/post/6876981358346895368</a></li>
<li><a href="https://juejin.cn/post/6901900140655673357">https://juejin.cn/post/6901900140655673357</a></li>
<li><a href="https://juejin.cn/post/6901552013717438472">https://juejin.cn/post/6901552013717438472</a></li>
<li><a href="https://juejin.cn/post/6901124887847403527">https://juejin.cn/post/6901124887847403527</a></li>
<li><a href="https://juejin.cn/post/6844903602117410829">https://juejin.cn/post/6844903602117410829</a></li>
<li><a href="https://juejin.cn/post/6876981358346895368">https://juejin.cn/post/6876981358346895368</a></li>
<li><a href="https://juejin.cn/post/6907189103151087623">https://juejin.cn/post/6907189103151087623</a></li>
<li><a href="https://juejin.cn/post/6844904025565954055">https://juejin.cn/post/6844904025565954055</a></li>
<li><a href="https://juejin.cn/post/6844904200032239629">https://juejin.cn/post/6844904200032239629</a></li>
<li><a href="https://juejin.cn/post/6844904160568016910">https://juejin.cn/post/6844904160568016910</a></li>
<li><a href="https://juejin.cn/post/6897030228867022856">https://juejin.cn/post/6897030228867022856</a></li>
<li><a href="https://juejin.cn/post/6844904068431740936">https://juejin.cn/post/6844904068431740936</a></li>
<li><a href="https://juejin.cn/post/6913835186933366798">https://juejin.cn/post/6913835186933366798</a></li>
<li><a href="https://juejin.cn/post/6844904084617560071">https://juejin.cn/post/6844904084617560071</a></li>
<li><a href="https://juejin.cn/post/6844903793042128903">https://juejin.cn/post/6844903793042128903</a></li>
<li><a href="https://juejin.cn/post/6933041656765612039">https://juejin.cn/post/6933041656765612039</a></li>
<li><a href="https://juejin.cn/post/6933111691215372302">https://juejin.cn/post/6933111691215372302</a></li>
<li><a href="https://juejin.cn/post/6907109642917117965">https://juejin.cn/post/6907109642917117965</a></li>
<li><a href="https://juejin.cn/post/6911251933386768391">https://juejin.cn/post/6911251933386768391</a></li>
<li><a href="https://juejin.cn/post/6947860760840110088">https://juejin.cn/post/6947860760840110088</a></li>
<li><a href="https://juejin.cn/post/6919661941552218125">https://juejin.cn/post/6919661941552218125</a></li>
<li><a href="https://juejin.cn/post/6918634850396307469">https://juejin.cn/post/6918634850396307469</a></li>
<li><a href="https://juejin.cn/post/6917548766287364110">https://juejin.cn/post/6917548766287364110</a></li>
<li><a href="https://juejin.cn/post/6917160609863860231">https://juejin.cn/post/6917160609863860231</a></li>
<li><a href="https://juejin.cn/post/6913691919386312712">https://juejin.cn/post/6913691919386312712</a></li>
<li><a href="https://juejin.cn/post/6847902219904221191">https://juejin.cn/post/6847902219904221191</a></li>
<li><a href="https://juejin.cn/post/6888262631360135175">https://juejin.cn/post/6888262631360135175</a></li>
<li><a href="https://juejin.cn/post/6898292945867571207">https://juejin.cn/post/6898292945867571207</a></li>
<li><a href="https://juejin.cn/post/6909986037871673358">https://juejin.cn/post/6909986037871673358</a></li>
<li><a href="https://juejin.cn/post/6844903988073070606">https://juejin.cn/post/6844903988073070606</a></li>
<li><a href="https://juejin.cn/post/6913734532206166023">https://juejin.cn/post/6913734532206166023</a></li>
<li><a href="https://juejin.cn/post/6914831351271292936">https://juejin.cn/post/6914831351271292936</a></li>
<li><a href="https://juejin.cn/post/6911897255087702030">https://juejin.cn/post/6911897255087702030</a></li>
<li><a href="https://juejin.cn/post/6913793936381181965">https://juejin.cn/post/6913793936381181965</a></li>
<li><a href="https://juejin.cn/post/6955002046504239134">https://juejin.cn/post/6955002046504239134</a></li>
<li><a href="https://juejin.cn/post/6954971119614967816">https://juejin.cn/post/6954971119614967816</a></li>
<li><a href="https://juejin.cn/post/6955227050877648903">https://juejin.cn/post/6955227050877648903</a></li>
<li><a href="https://juejin.cn/post/6952696734078369828">https://juejin.cn/post/6952696734078369828</a></li>
<li><a href="https://juejin.cn/post/6952066955868110879">https://juejin.cn/post/6952066955868110879</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[linux 常用脚本]]></title>
            <link>https://zzfzzf.com/post/6972548384850841617</link>
            <guid>6972548384850841617</guid>
            <pubDate>Thu, 17 Dec 2020 01:54:39 GMT</pubDate>
            <content:encoded><![CDATA[<h2>重启jar包</h2>
<pre><code class="language-bash">#!/bin/sh
jarname=&#39;demo&#39;
pid=`ps aux | grep $jarname | grep -v grep | awk &#39;{print $2}&#39;`
echo $pid
kill -9 $pid
nohup java -jar $jarname.jar --spring.profiles.active=prod  &amp;
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[css绘制三角形]]></title>
            <link>https://zzfzzf.com/post/6972548384850841616</link>
            <guid>6972548384850841616</guid>
            <pubDate>Tue, 15 Dec 2020 01:49:41 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>当宽高都为0时，设置4个border的颜色，发现</p>
</blockquote>
<p><img src="https://cdn.zzfzzf.com//1607998278206GRv7iH.png" alt="image-20201215101117321"></p>
<blockquote>
<p>当我们设置一边的边框高度为0时</p>
</blockquote>
<p><img src="https://cdn.zzfzzf.com//1607999104260uc5QJw.png" alt="image-20201215102504042"></p>
<blockquote>
<p>设置左上和右上的颜色为透明</p>
</blockquote>
<p><img src="https://cdn.zzfzzf.com//1607999250542m0liBY.png" alt="image-20201215102730387"></p>
<blockquote>
<p>设置border-width 设置三角形各个边大小</p>
</blockquote>
<p><img src="https://cdn.zzfzzf.com//1607999571583pEbie7.png" alt="image-20201215103251356"></p>
<blockquote>
<p>利用这个特性，我们可以设置各种三角形，空心的三角形，我们一般用一个透明的三角形叠加上去</p>
</blockquote>
<p><a href="http://apps.eky.hk/css-triangle-generator/zh-hant">http://apps.eky.hk/css-triangle-generator/zh-hant</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[Elasticsearch的安装]]></title>
            <link>https://zzfzzf.com/post/6972548384850841614</link>
            <guid>6972548384850841614</guid>
            <pubDate>Tue, 08 Dec 2020 14:45:50 GMT</pubDate>
            <content:encoded><![CDATA[<h2>docker安装(推荐)</h2>
<pre><code class="language-bash">docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 --restart=always -e &quot;discovery.type=single-node&quot; -e ES_JAVA_OPTS=&quot;-Xms128m -Xmx512m&quot; docker.elastic.co/elasticsearch/elasticsearch:7.10.1
</code></pre>
<blockquote>
<p>重置密码</p>
</blockquote>
<pre><code class="language-bash">bin/elasticsearch-reset-password -u elastic
</code></pre>
<pre><code class="language-bash">bin/elasticsearch-setup-passwords interactive
</code></pre>
<h2>手动安装</h2>
<h3>环境准备</h3>
<ol>
<li>安装java环境</li>
</ol>
<pre><code class="language-bash">yum install -y java-1.8.0-openjdk
</code></pre>
<ol start="2">
<li>配置环境变量</li>
</ol>
<pre><code>update-alternatives --config java
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.aarch64
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
javac
</code></pre>
<h3>下载地址</h3>
<ul>
<li><a href="https://www.elastic.co/cn/downloads/elasticsearch">elasticsearch</a></li>
<li><a href="https://github.com/mobz/elasticsearch-head">浏览器插件</a></li>
</ul>
<h3>elasticsearch安装</h3>
<blockquote>
<p>不能安装在root目录，会权限不足启动失败</p>
</blockquote>
<ol>
<li><code>yum -y install wget</code></li>
<li><code>wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.10.0-linux-x86_64.tar.gz</code></li>
<li><code>tar -zxvf elasticsearch-7.10.0-linux-x86_64.tar.gz -C /usr/local/</code></li>
</ol>
<pre><code>adduser es
passwd es
chown -R es elasticsearch-7.10.0
su es

vi  config/elasticsearch.yml
network.host:  0.0.0.0
discovery.type: single-node
jvm.options改内存
./bin/elasticsearch -d
curl http://localhost:9200/
</code></pre>
<h2>中文分词器</h2>
<p><a href="https://github.com/medcl/elasticsearch-analysis-ik">https://github.com/medcl/elasticsearch-analysis-ik</a></p>
<blockquote>
<p>用root账户安装</p>
</blockquote>
<pre><code class="language-bash">./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.10.0/elasticsearch-analysis-ik-7.10.0.zip
</code></pre>
<p>smartcn分词器</p>
<pre><code class="language-bash">bin/elasticsearch-plugin install analysis-smartcn
</code></pre>
<h2>canal安装</h2>
<pre><code class="language-bash">docker run -p 11111:11111 --name canal -m 512M --memory-swap -1 canal/canal-server
docker update --restart=always canal
</code></pre>
<h2>F&amp;Q</h2>
<ol>
<li>内存不足<br>/usr/local/logstash-7.10.0/config/jvm.options 添加以下代码</li>
</ol>
<pre><code>-Xms512m
-Xmx512m
</code></pre>
<ol start="2">
<li>启动权限问题<br>不能使用root用户，新建一个用户</li>
</ol>
<h2>NODE API</h2>
<p><a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/get_examples.html">https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/get_examples.html</a>
<a href="https://blog.csdn.net/UbuntuTouch/article/details/122874932">https://blog.csdn.net/UbuntuTouch/article/details/122874932</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[vue3升级差异体验]]></title>
            <link>https://zzfzzf.com/post/6972548384850841609</link>
            <guid>6972548384850841609</guid>
            <pubDate>Wed, 18 Nov 2020 11:10:12 GMT</pubDate>
            <content:encoded><![CDATA[<h2>值得关注的新特性</h2>
<ul>
<li>组合式 API</li>
<li>Teleport</li>
<li>片段</li>
<li>触发组件选项</li>
<li>来自 @vue/runtime-core 的 createRenderer API 创建自定义渲染器</li>
<li>单文件组件组合式 API 语法糖 <code>&lt;script setup&gt;</code> 实验性</li>
<li>单文件组件状态驱动的 CSS 变量 <code>&lt;style vars&gt;</code> 实验性</li>
<li>单文件组件 <code>&lt;style scoped&gt;</code> 现在可以包含全局规则或只针对插槽内容的规则</li>
</ul>
<p>vue2 选项式api</p>
<blockquote>
<p>vue3基本模板</p>
</blockquote>
<pre><code class="language-html">&lt;template&gt;
  &lt;button @click=&quot;increment&quot;&gt;
    Count is: {{ state.count }}, double is: {{ state.double }}
  &lt;/button&gt;
&lt;/template&gt;

&lt;script&gt;
import { reactive, computed, defineComponent, onMounted } from &#39;vue&#39;;

export default defineComponent({
  props:{
    msg:{type:String}
  },
  setup() {
    const
    const state = reactive({
      count: 0,
      double: computed(() =&gt; state.count * 2),
    });

    function increment() {
      state.count++;
    }

    onMounted(() =&gt; {
      console.log(&#39;component is mounted!&#39;);
    });
    return {
      state,
      increment,
    };
  },
});
&lt;/script&gt;
</code></pre>
<h3>组合式api</h3>
<blockquote>
<p><code>setup</code>入口函数
在<code>beforeCreate</code>之后<code>created</code>之前执行</p>
</blockquote>
<p><code>接收props和context={slots,emit,refs}</code></p>
<pre><code class="language-js">export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup(props,context) {
    console.log(props) // { user: &#39;&#39; }

    return {} // 这里返回的任何内容都可以用于组件的其余部分
  }
  // 组件的“其余部分”
}
</code></pre>
<blockquote>
<p>setup的props不能直接解构，否则会失去响应式,</p>
<p>如果要解构，可以使用<code>toRefs</code></p>
</blockquote>
<pre><code class="language-js">import { toRefs } from &#39;vue&#39;

setup(props) {
    const { title } = toRefs(props)
    console.log(title.value)
  console.log(props.value)
}
</code></pre>
<pre><code class="language-js">export default {
  setup(props) {
    /* ✗ BAD */
    const { count } = props

    watch(() =&gt; {
      /* ✓ GOOD */
      const { count } = props
      console.log(count)
    })

    return () =&gt; {
      /* ✓ GOOD */
      const { count } = props
      return h(&#39;div&#39;, count)
    }
  }
}
</code></pre>
<pre><code class="language-js">export default {
  /* ✗ BAD */
  setup({ count }) {
    watch(() =&gt; {
      console.log(count) // not going to detect changes
    })

    return () =&gt; {
      return h(&#39;div&#39;, count) // not going to update
    }
  }
}
</code></pre>
<h3><code>reactive</code>和<code>ref</code></h3>
<pre><code class="language-js">import { reactive } from &#39;vue&#39;

// reactive state
const state = reactive({
  count: 0
})
//类似于vue2的`Vue.observable()`
</code></pre>
<pre><code class="language-js">import { ref } from &#39;vue&#39;

const counter = ref(0)

console.log(counter) // { value: 0 }
console.log(counter.value) // 0

counter.value++
console.log(counter.value) // 1
</code></pre>
<h3>vue实现reactHooks</h3>
<pre><code class="language-js">//获取鼠标位置hooks
import { ref, onMounted, onUnmounted } from &#39;vue&#39;

export function useMousePosition() {
  const x = ref(0)
  const y = ref(0)

  function update(e) {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() =&gt; {
    window.addEventListener(&#39;mousemove&#39;, update)
  })

  onUnmounted(() =&gt; {
    window.removeEventListener(&#39;mousemove&#39;, update)
  })

  return { x, y }
}
</code></pre>
<pre><code class="language-js">//组件使用
import { useMousePosition } from &#39;./mouse&#39;

export default {
  setup() {
    const { x, y } = useMousePosition()
    // other logic...
    return { x, y }
  }
}
</code></pre>
<h3>参考</h3>
<ul>
<li><a href="https://vue-composition-api-rfc.netlify.app/">vueRfc</a></li>
<li><a href="https://cn.vuejs.org/">vue2</a></li>
<li><a href="https://juejin.cn/post/6897030228867022856?utm_source=gold_browser_extension#heading-8">常用api</a></li>
<li><a href="https://vue3js.cn/docs/zh/">vue3</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[Jrebel激活]]></title>
            <link>https://zzfzzf.com/post/6972548384850841608</link>
            <guid>6972548384850841608</guid>
            <pubDate>Tue, 17 Nov 2020 06:49:49 GMT</pubDate>
            <content:encoded><![CDATA[<p>激活地址<code>https://jrebel.qekang.com/{GUID}</code><br>GUID <a href="https://www.guidgen.com/">https://www.guidgen.com/</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[mac与windows软件推荐]]></title>
            <link>https://zzfzzf.com/post/6972548384850841607</link>
            <guid>6972548384850841607</guid>
            <pubDate>Mon, 16 Nov 2020 02:09:15 GMT</pubDate>
            <content:encoded><![CDATA[<h2>mac</h2>
<p><a href="https://github.com/jaywcjlove/awesome-mac/blob/master/README-zh.md">awesome-mac</a></p>
<blockquote>
<p>包含免费与付费，每种筛选性价比最高的一款，拒绝选择困难症</p>
</blockquote>
<table>
<thead>
<tr>
<th>软件</th>
<th>描述</th>
<th>官网</th>
<th>收费</th>
</tr>
</thead>
<tbody><tr>
<td>keka</td>
<td>解压缩</td>
<td><a href="https://www.keka.io">https://www.keka.io</a></td>
<td>免费</td>
</tr>
<tr>
<td>downie4</td>
<td>视频下载</td>
<td><a href="https://software.charliemonroe.net/downie/">https://software.charliemonroe.net/downie/</a></td>
<td>付费</td>
</tr>
<tr>
<td>motrix</td>
<td>通用下载</td>
<td><a href="https://motrix.app">https://motrix.app</a></td>
<td>免费</td>
</tr>
<tr>
<td>IINA</td>
<td>视频播放</td>
<td><a href="https://iina.io/">https://iina.io/</a></td>
<td>免费</td>
</tr>
<tr>
<td>BetterTouchTool</td>
<td>触摸板增强</td>
<td><a href="https://folivora.ai/">https://folivora.ai/</a></td>
<td>付费</td>
</tr>
<tr>
<td>SwitchHosts</td>
<td>便捷切换 host</td>
<td><a href="https://github.com/oldj/SwitchHosts">https://github.com/oldj/SwitchHosts</a></td>
<td>免费</td>
</tr>
<tr>
<td>nuoshell</td>
<td>mac 终端</td>
<td><a href="https://www.nuoshell.com/">https://www.nuoshell.com/</a></td>
<td>免费</td>
</tr>
<tr>
<td>electerm</td>
<td>mac 终端</td>
<td><a href="https://electerm.html5beta.com/">https://electerm.html5beta.com/</a></td>
<td>免费</td>
</tr>
<tr>
<td>tabby</td>
<td>mac 终端</td>
<td><a href="https://github.com/Eugeny/tabby">https://github.com/Eugeny/tabby</a></td>
<td>免费</td>
</tr>
<tr>
<td>markdown</td>
<td>编辑器</td>
<td><a href="https://github.com/marktext/marktext">https://github.com/marktext/marktext</a></td>
<td>免费</td>
</tr>
<tr>
<td>fig</td>
<td>命令行提示工具</td>
<td><a href="https://fig.io/">https://fig.io/</a></td>
<td>免费</td>
</tr>
<tr>
<td>snipaste</td>
<td>截图软件</td>
<td><a href="https://zh.snipaste.com/">https://zh.snipaste.com/</a></td>
<td>免费</td>
</tr>
<tr>
<td>shottr</td>
<td>截图软件</td>
<td><a href="https://shottr.cc/">https://shottr.cc/</a></td>
<td>免费</td>
</tr>
</tbody></table>
<h3>ssh</h3>
<ul>
<li>item2&amp;&amp;transmit5</li>
<li><a href="https://tabby.sh/">https://tabby.sh/</a></li>
<li><a href="https://github.com/electerm/electerm">https://github.com/electerm/electerm</a></li>
<li><a href="https://termius.com/">https://termius.com/</a></li>
</ul>
<h2>windows</h2>
<table>
<thead>
<tr>
<th>软件</th>
<th>描述</th>
<th>官网</th>
<th>收费</th>
</tr>
</thead>
<tbody><tr>
<td>PotPlayer</td>
<td>视频播放</td>
<td><a href="https://potplayer.daum.net/">https://potplayer.daum.net/</a></td>
<td>免费</td>
</tr>
</tbody></table>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[回流与重绘]]></title>
            <link>https://zzfzzf.com/post/6972548384850841606</link>
            <guid>6972548384850841606</guid>
            <pubDate>Mon, 16 Nov 2020 00:48:59 GMT</pubDate>
            <content:encoded><![CDATA[<h2>回流</h2>
<p>元素几何属性或布局发生变化即回流
会导致回流的操作：</p>
<ul>
<li>页面首次渲染</li>
<li>浏览器窗口大小发生改变</li>
<li>元素尺寸或位置发生改变</li>
<li>元素内容变化（文字数量或图片大小等等）</li>
<li>元素字体大小变化</li>
<li>添加或者删除可见的DOM元素</li>
<li>激活CSS伪类（例如：:hover）</li>
<li>查询某些属性或调用某些方法</li>
</ul>
<p>一些常用且会导致回流的属性和方法：</p>
<p>clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
scrollIntoView()、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()</p>
<h2>重绘</h2>
<p>元素样式发生变化，但不影响元素的几何布局（如大小，位置，宽高）<br>回流一定会触发重绘，而重绘不一定会回流
color、background-color、visibility</p>
<ol>
<li>使用react与vue 框架内部已经优化了回流与重绘的内容</li>
</ol>
<h2>减少回流与重绘</h2>
<h3>css硬件加速（GPU）</h3>
<p>transform/opacity/filters</p>
<h3>使用visibility，opacity 代替 display: none</h3>
<p>前2个不会改变布局，即只会重绘，display会回流</p>
<h3>css</h3>
<ol>
<li>避免使用table布局;</li>
<li>尽可能在DOM树的最末端改变class;</li>
<li>避免设置多层内联样式;</li>
<li>将动画效果应用到position属性为absolute或fixed的元素上;</li>
<li>避免使用CSS表达式（例如：calc()）</li>
</ol>
<h3>js</h3>
<ol>
<li>避免频繁操作样式，最好一次性重写style属性，或者将样式列表定义为class并一次性更改class属性。</li>
<li>避免频繁操作DOM，创建一个documentFragment，在它上面应用所有DOM操作，最后再把它添加到文档中。</li>
<li>也可以先为元素设置display: none，操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。</li>
<li>避免频繁读取会引发回流/重绘的属性，如果确实需要多次使用，就用一个变量缓存起来。</li>
<li>对具有复杂动画的元素使用绝对定位，使它脱离文档流，否则会引起父元素及后续元素频繁回流。</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[前端小知识]]></title>
            <link>https://zzfzzf.com/post/6972548384850841605</link>
            <guid>6972548384850841605</guid>
            <pubDate>Thu, 12 Nov 2020 03:10:40 GMT</pubDate>
            <content:encoded><![CDATA[<h3>一次性函数</h3>
<pre><code class="language-js">function fnc() {
    console.log(&quot;x&quot;);
    fnc = function() {
        console.log(&quot;y&quot;);
    }
}
fnc()//x
fnc()//y
fnc()//y
</code></pre>
<h3>空值过滤</h3>
<pre><code class="language-js">   console.log([undefined, null, &quot;&quot;,0,false,NaN,1,2].filter(Boolean))
   //[1,2]
</code></pre>
<h3>判断数据类型</h3>
<blockquote>
<p>可判断类型：undefined、null、string、number、boolean、array、object、symbol、date、regexp、function、asyncfunction、arguments、set、map、weakset、weakmap</p>
</blockquote>
<pre><code class="language-js">function DataType(tgt, type) {
    const dataType = Object.prototype.toString.call(tgt).replace(/\[object (\w+)\]/, &quot;$1&quot;).toLowerCase();
    return type ? dataType === type : dataType;
}
</code></pre>
<ul>
<li><a href="https://juejin.cn/post/6899344555653464077">https://juejin.cn/post/6899344555653464077</a></li>
<li><a href="https://juejin.cn/post/6898962197335490573?utm_source=gold_browser_extension#heading-1">https://juejin.cn/post/6898962197335490573?utm_source=gold_browser_extension#heading-1</a></li>
</ul>
<h3>css单行溢出省略号</h3>
<pre><code class="language-css">overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
</code></pre>
<h3>多行溢出省略号</h3>
<pre><code class="language-css">display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
</code></pre>
<h3>隐藏元素</h3>
<pre><code class="language-css">//元素不会占用空间，在页面中不显示，子元素也不会显示。
display: none;
//元素隐藏，但元素仍旧存在，占用空间，页面中无法触发该元素的事件。
visibility: hidden;
//元素透明度将为0，但元素仍然存在，绑定的事件仍旧有效仍可触发执行。
opacity: 0;
</code></pre>
<h2>??默认值</h2>
<pre><code>false或0 不执行
false||&#39;a&#39;
0||&#39;a&#39;
false&amp;&amp;&#39;a&#39;
0&amp;&amp;&#39;a&#39;
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[文本换行和溢出方案]]></title>
            <link>https://zzfzzf.com/post/6972548384850841604</link>
            <guid>6972548384850841604</guid>
            <pubDate>Tue, 10 Nov 2020 08:21:09 GMT</pubDate>
            <content:encoded><![CDATA[<h2>换行</h2>
<blockquote>
<p>white-space</p>
</blockquote>
<table>
<thead>
<tr>
<th>是否能发挥作用</th>
<th>换行符</th>
<th>空格</th>
<th>自动换行</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody><tr>
<td>normal</td>
<td>×</td>
<td>×（合并）</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>nowrap</td>
<td>×</td>
<td>×（合并）</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>pre</td>
<td>√</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>pre-wrap</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>pre-line</td>
<td>√</td>
<td>×（合并）</td>
<td>√</td>
<td>√</td>
</tr>
</tbody></table>
<ul>
<li>white-space，<strong>控制空白字符的显示</strong>，同时还能控制是否自动换行。它有五个值：<code>normal | nowrap | pre | pre-wrap | pre-line</code></li>
<li>word-break，<strong>控制单词如何被拆分换行</strong>。它有三个值：<code>normal | break-all | keep-all</code><ul>
<li>break-all 一律换行 keep-all 一律不换行</li>
</ul>
</li>
<li>word-wrap（overflow-wrap）<strong>控制长度超过一行的单词是否被拆分换行</strong>，是<code>word-break</code>的补充，它有两个值：<code>normal | break-word</code></li>
</ul>
<h2>溢出</h2>
<h3>一行溢出省略号</h3>
<pre><code class="language-css">.ellipsis{
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
</code></pre>
<h3>多行溢出省略号</h3>
<pre><code class="language-css">.ellipsis{
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  text-overflow: ellipsis;
}
</code></pre>
<p><a href="https://juejin.im/post/6892974106908557320">参考文章</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[centos基础环境配置]]></title>
            <link>https://zzfzzf.com/post/6972548384850841603</link>
            <guid>6972548384850841603</guid>
            <pubDate>Tue, 10 Nov 2020 04:55:54 GMT</pubDate>
            <content:encoded><![CDATA[<h2>首先更新yum</h2>
<pre><code>yum update
</code></pre>
<h2>网络配置</h2>
<pre><code class="language-bash">#打开配置文件
vi /etc/sysconfig/network-scripts/ifcfg-ens33
#配置
TYPE=&quot;Ethernet&quot;   # 网络类型为以太网
BOOTPROTO=&quot;static&quot;  # 手动分配ip
NAME=&quot;ens33&quot;  # 网卡设备名，设备名一定要跟文件名一致
DEVICE=&quot;ens33&quot;  # 网卡设备名，设备名一定要跟文件名一致
ONBOOT=&quot;yes&quot;  # 该网卡是否随网络服务启动
IPADDR=&quot;192.168.220.101&quot;  # 该网卡ip地址就是你要配置的固定IP，如果你要用xshell等工具连接，220这个网段最好和你自己的电脑网段一致，否则有可能用xshell连接失败
GATEWAY=&quot;192.168.220.2&quot;   # 网关
NETMASK=&quot;255.255.255.0&quot;   # 子网掩码
DNS1=&quot;8.8.8.8&quot;    # DNS，8.8.8.8为Google提供的免费DNS服务器的IP地址
#重启
service network restart
</code></pre>
<h2>git</h2>
<pre><code> yum install https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm
 yum install git
</code></pre>
<h2>时间配置</h2>
<pre><code>#查看时区
timedatectl status|grep &#39;Time zone&#39;
#设置硬件时钟调整为与本地时钟一致
timedatectl set-local-rtc 1
#设置时区为上海
timedatectl set-timezone Asia/Shanghai
#安装ntpdate
yum -y install ntpdate
#同步时间
ntpdate -u  pool.ntp.org
#同步完成后,date命令查看时间是否正确
date
</code></pre>
<h2>java环境配置</h2>
<ol>
<li><p><code>yum install -y java-1.8.0-openjdk</code></p>
</li>
<li><p><code>java -version</code></p>
</li>
</ol>
<h2>NODE环境配置</h2>
<ol>
<li>直接安装<blockquote>
<p>参考<a href="https://github.com/nodesource/distributions">https://github.com/nodesource/distributions</a></p>
</blockquote>
</li>
</ol>
<blockquote>
<p>添加存储库</p>
</blockquote>
<pre><code class="language-bash">curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
</code></pre>
<blockquote>
<p>安装</p>
</blockquote>
<pre><code class="language-bash">sudo yum install -y nodejs
</code></pre>
<ol start="2">
<li><a href="https://github.com/nvm-sh/nvm">nvm</a>安装</li>
</ol>
<pre><code class="language-bash">curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
</code></pre>
<h2>maven</h2>
<pre><code>yum install maven
</code></pre>
<h2>宝塔脚本一键安装</h2>
<pre><code class="language-bash">yum install -y wget &amp;&amp; wget -O install.sh http://download.bt.cn/install/install_6.0.sh &amp;&amp; sh install.sh
</code></pre>
<p> <a href="https://juejin.cn/post/6844903953084186632#heading-13">https://juejin.cn/post/6844903953084186632#heading-13</a></p>
<h2>后台运行命令</h2>
<pre><code class="language-bash">nohup ./deploy.sh &gt; nohup.log 2&gt;&amp;1 &amp;
</code></pre>
<h2>RabbitMQ</h2>
<ol>
<li>安装 <a href="https://hub.docker.com/_/rabbitmq">docker镜像网址</a></li>
</ol>
<pre><code>docker run -d --hostname my-rabbit --name some-rabbit -p 8080:15672 -p 5672:5672 rabbitmq:3-management
</code></pre>
<ol start="2">
<li>默认账号密码都是<code>guest</code></li>
</ol>
<h2>oracle安装</h2>
<ol>
<li>搜索oracle镜像  <blockquote>
<p><code>$ docker search oracle</code></p>
</blockquote>
</li>
</ol>
<pre><code>[root@instance-1 ~]# docker search oracle
NAME                                  DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
oraclelinux                           Official Docker builds of Oracle Linux.         641                 [OK]                
jaspeen/oracle-11g                    Docker image for Oracle 11g database            153                                     [OK]
oracleinanutshell/oracle-xe-11g                                                       88                                      
oracle/openjdk                        Docker images containing OpenJDK Oracle Linux   60                                      [OK]
oracle/graalvm-ce                     GraalVM Community Edition Official Image        59                                      [OK]
absolutapps/oracle-12c-ee             Oracle 12c EE image with web management cons…   38                                      
araczkowski/oracle-apex-ords          Oracle Express Edition 11g Release 2 on Ubun…   27                                      [OK]
oracle/nosql                          Oracle NoSQL on a Docker Image with Oracle L…   23                                      [OK]
bofm/oracle12c                        Docker image for Oracle Database                23                                      [OK]
datagrip/oracle                       Oracle 11.2 &amp; 12.1.0.2-se2 &amp; 11.2.0.2-xe        18                                      [OK]
</code></pre>
<ol start="2">
<li>下载镜像<br><code>$ docker pull oracleinanutshell/oracle-xe-11g</code></li>
<li>使用镜像 Run with 1521 port opened<br><code>$ docker run -d -p 49161:1521 oracleinanutshell/oracle-xe-11g</code></li>
<li>连接信息</li>
</ol>
<pre><code>hostname: localhost
port: 49161
sid: xe
username: system
password: oracle
</code></pre>
<ol start="5">
<li>镜像说明
<a href="https://hub.docker.com/r/oracleinanutshell/oracle-xe-11g">oracleinanutshell/oracle-xe-11g</a></li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[原型链与继承]]></title>
            <link>https://zzfzzf.com/post/6972548384850841602</link>
            <guid>6972548384850841602</guid>
            <pubDate>Wed, 28 Oct 2020 02:56:08 GMT</pubDate>
            <content:encoded><![CDATA[<h2>原型</h2>
<h3>定义</h3>
<p>每个对象创建时从另一个对象上继承属性，另一个对象就是原型（原型对象）</p>
<h3>获取原型的几种方法</h3>
<pre><code class="language-js">function Test() {}
const test = new Test()
//1
console.log(Object.getPrototypeOf(test));
//2
console.log(test.__proto__);
//3
console.log(Test.prototype);
//4
console.log(test.constructor.__proto__);
//5
console.log(Reflect.getPrototypeOf(test))
//6
console.log(Test.prototype === test.__proto__)//true
</code></pre>
<h3>特点</h3>
<ol>
<li><code>__proto__</code>和<code>constructor</code>属性是对象所独有的</li>
<li>prototype属性是函数所独有的</li>
<li>函数也是对象，所有函数也有<code>__proto__</code>和<code>constructor</code></li>
<li>proto属性的作用就是当访问一个对象的属性时，如果该对象内部不存在这个属性，那么就会去它的proto属性所指向的那个对象（父对象）里找，一直找，直到proto属性的终点null，然后返回undefined，通过proto属性将对象连接起来的这条链路即我们所谓的原型链。</li>
<li>prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法，即f1.proto === Foo.prototype。</li>
<li>constructor属性的含义就是指向该对象的构造函数，所有函数（此时看成对象了）最终的构造函数都指向Function()。</li>
</ol>
<h2>原型链</h2>
<h3>定义</h3>
<p>实例对象和原型对象通过<code>__proto__</code>层层关联,直到null为止，形成原型链</p>
<h3>检测</h3>
<p><code>instanceof</code>检测是否在原型链上</p>
<pre><code class="language-javascript">function Test() {
}

const test = new Test()
console.log(test instanceof Test)//true
console.log(test instanceof Object)//true
console.log(Test instanceof Function)//true
console.log(Test instanceof Object)//true
</code></pre>
<h3>实现<code>instanceof</code></h3>
<pre><code class="language-javascript">function MyInstanceof(objInstance, Func) {
    let objProto = objInstance.__proto__
    let FuncP = Func.prototype
    while (true) {
        if (!objProto) return false
        if (objProto === FuncP) return true
        objProto = objProto.__proto__
    }
}
</code></pre>
<h2>继承</h2>
<h3>class继承</h3>
<pre><code class="language-js">// ES6的继承
class Parent {
  constructor() {
    this.name = &quot;大人&quot;
    this.hairColor = &quot;黑色&quot;
  }
}

class Child extends Parent {
  constructor() {
    super()  //调用父级的方法和属性
    this.name = &quot;小孩&quot;
  }
}

let c = new Child()
console.log(c.name, c.hairColor) //小孩 黑色

let p = new Parent()
console.log(p.name, p.hairColor) //大人 黑色
</code></pre>
<h3>原型链继承</h3>
<pre><code class="language-js">// 定义父类
function Parent() {
    this.name = &#39;Jack&#39;;
}

// 父类原型添加方法
Parent.prototype.getName = function () {
    return this.name;
};

// 子类
function Child() {
}

// 子类的原型设置为父类Parent的实例
Child.prototype = new Parent();
Child.prototype.constructor = Child

// 实例化子类
const child = new Child();

console.log(child.getName()); // Jack
</code></pre>
<blockquote>
<p>缺点</p>
</blockquote>
<ol>
<li>原型上的引用类型会被共享</li>
<li>无法传参数</li>
</ol>
<h3>借用构造函数继承</h3>
<pre><code class="language-js">function SuperType() {
    this.colors = [&#39;red&#39;, &#39;blue&#39;, &#39;green&#39;];
}

function SubType() {
    // 继承 SuperType
    SuperType.call(this);
}

const instance1 = new SubType();
instance1.colors.push(&#39;black&#39;);
console.log(instance1.colors); // [ &#39;red&#39;, &#39;blue&#39;, &#39;green&#39;, &#39;black&#39; ]
const instance2 = new SubType();
console.log(instance2.colors); // [ &#39;red&#39;, &#39;blue&#39;, &#39;green&#39; ]
</code></pre>
<blockquote>
<p>缺点</p>
</blockquote>
<ol>
<li>实例无法访问父类原型上的方法</li>
<li>每个字类都会对父类实例化</li>
</ol>
<h3>组合继承</h3>
<pre><code class="language-js">function SuperType(name) {
    this.name = name;
    this.colors = [&#39;red&#39;, &#39;blue&#39;, &#39;green&#39;];
}

SuperType.prototype.sayName = function () {
    console.log(this.name);
};

function SubType(name, age) {
    // 继承属性
    SuperType.call(this, name);
    this.age = age;
}

// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
    console.log(this.age);
};

const usr1 = new SubType(&#39;Nicholas&#39;, 29);
usr1.colors.push(&#39;black&#39;);
console.log(usr1.colors); // [ &#39;red&#39;, &#39;blue&#39;, &#39;green&#39;, &#39;black&#39; ]
usr1.sayName(); // Nicholas
usr1.sayAge(); // 29

const usr2 = new SubType(&#39;Greg&#39;, 27);
console.log(usr2.colors); // [ &#39;red&#39;, &#39;blue&#39;, &#39;green&#39; ]
usr2.sayName(); // Greg
usr2.sayAge(); // 27
</code></pre>
<blockquote>
<p>缺点</p>
<p>多次实例化</p>
</blockquote>
<h3>原型式继承</h3>
<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create">Object.create</a></p>
<pre><code class="language-js">const person = {
    name: &#39;Nicholas&#39;,
    friends: [&#39;Shelby&#39;, &#39;Court&#39;, &#39;Van&#39;],
};

const anotherPerson = Object.create(person);
anotherPerson.name = &#39;Greg&#39;;
anotherPerson.friends.push(&#39;Rob&#39;);

const anotherPerson2 = Object.create(person);
anotherPerson2.name = &#39;Linda&#39;;
anotherPerson2.friends.push(&#39;Barbie&#39;);
console.log(person.friends); // [ &#39;Shelby&#39;, &#39;Court&#39;, &#39;Van&#39;, &#39;Rob&#39;, &#39;Barbie&#39; ]
</code></pre>
<blockquote>
<p>缺点</p>
</blockquote>
<ol>
<li>引用类型会被共享</li>
</ol>
<h3>寄生式继承</h3>
<pre><code class="language-js">function createAnother(original) {
    const clone = Object.create(original); // 通过调用函数创建一个新对象
    clone.sayHi = function () {
        // 以某种方式增强这个对象
        console.log(&#39;hi&#39;);
    };
    return clone; // 返回这个对象
}

const person = {
    name: &#39;Nicholas&#39;,
    friends: [&#39;Shelby&#39;, &#39;Court&#39;, &#39;Van&#39;],
};

const anotherPerson = createAnother(person);
anotherPerson.sayHi(); // hi
console.log(anotherPerson.name); // Nicholas
console.log(anotherPerson.friends); // [ &#39;Shelby&#39;, &#39;Court&#39;, &#39;Van&#39; ]
</code></pre>
<h3>寄生组合式继承</h3>
<blockquote>
<p>最优的继承方式</p>
</blockquote>
<pre><code class="language-js">function inheritPrototype(subType, superType) {
  subType.prototype = Object.create(superType.prototype);
  subType.prototype.constructor = subType;
}
</code></pre>
<h2>面试知识点</h2>
<ol>
<li>什么是 JavaScript 原型？<ul>
<li>在js中，每个函数都有一个<code>prototype</code>属性，它是一个对象。为该函数的原型对象。</li>
</ul>
</li>
<li>什么是js原型链<ul>
<li>每个对象有<code>__proto__</code>属性，指向该对象的<strong>原型对象</strong>，当访问一个对象的属性或方法时，若当前对象未找到，沿着<code>__proto__</code>指向的原型对象继续找，知道找到原型链顶端 <strong>null</strong>。</li>
</ul>
</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[造轮子-组件库-获取元素框高判断可视区域]]></title>
            <link>https://zzfzzf.com/post/6972548384850841601</link>
            <guid>6972548384850841601</guid>
            <pubDate>Mon, 26 Oct 2020 07:05:36 GMT</pubDate>
            <content:encoded><![CDATA[<h2>获取元素框高</h2>
<blockquote>
<ol>
<li>offsetTop &lt; clientHeight + scrollTop</li>
<li>element.getBoundingClientRect().top &lt; clientHeight</li>
<li>IntersectionObserver</li>
</ol>
</blockquote>
<ol>
<li><code>window.getComputedStyle()</code> <code>element.currentStyle</code></li>
<li><code>offsetHeight、offsetWidth、offsetTop、offsetLeft、officeParent</code></li>
<li><code>clientWidth、clientHeight</code></li>
<li><code>scrollHeight、 scrollWidth、 scrollTop、 scrollLeft</code></li>
<li><code>getBoundingClientRect()</code></li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[点击目标元素外区域触发事件]]></title>
            <link>https://zzfzzf.com/post/6972548384846647310</link>
            <guid>6972548384846647310</guid>
            <pubDate>Thu, 22 Oct 2020 07:50:02 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>常见的弹窗组件中，点击弹窗区域之外关闭弹窗的功能，可通过阻止事件冒泡来方便地实现，而不用这种方式的话，会引入复杂的判断当前点击坐标是否在弹窗之外的复杂逻辑</p>
</blockquote>
<blockquote>
<p>可以用事件冒泡来实现</p>
</blockquote>
<pre><code class="language-js">document.addEventListener(&quot;click&quot;, () =&gt; {
  // close dialog
});
dialogElement.addEventListener(&quot;click&quot;, event =&gt; {
event.stopPropagation();
});
</code></pre>
<blockquote>
<p><a href="https://www.cnblogs.com/Wayou/p/react_event_issue.html">参考文档</a></p>
</blockquote>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[从闭包看作用域]]></title>
            <link>https://zzfzzf.com/post/6972548384846647309</link>
            <guid>6972548384846647309</guid>
            <pubDate>Tue, 13 Oct 2020 14:06:56 GMT</pubDate>
            <content:encoded><![CDATA[<h2>闭包是什么</h2>
<blockquote>
<p>闭包是指那些能够访问自由变量的函数。<br>指的是一个函数可以访问另外一个函数的作用域中的变量。<br>常见的做法是在函数中定义另外一个函数，内部函数可以引用外部函数的变量。外部函数中的变量不会被回收</p>
</blockquote>
<h2>防抖</h2>
<blockquote>
<p>在事件被触发n秒后再执行回调，如果在这n秒内又被触发，则重新计时。
 形象理解，防抖即法师释放技能读条，读条时候取消了重新释放技能再次读条</p>
</blockquote>
<pre><code class="language-js">export function debounce(fn, delay) {
  let timer = null;
  return function() {
    let context = this;
    let arg = arguments;
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, arg);
    }, delay);
  };
}
</code></pre>
<pre><code class="language-js">function log(str) {
    console.log(str)
}
function debounce1(fn, interval = 300) {
    let timeout = null;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() =&gt; {
            fn.apply(this, arguments);
        }, interval);
    };
}
function debounce2(fn, delay = 200) {
    let timeout;
    return function() {
        timeout &amp;&amp; clearTimeout(timeout);
        timeout = setTimeout(fn.bind(this), delay, ...arguments);
    }
}
const fn1=debounce1(log,300)
const fn2=debounce2(log,300)
function test(){
    fn1(&#39;sss&#39;)
    fn2(&#39;sss&#39;)
}
</code></pre>
<h2>节流</h2>
<blockquote>
<p>规定在一个单位时间内，只能触发一次函数。如果这个单位时间内触发多次函数，只有一次生效。
形象理解，节流即在游戏中的攻速设定，鼠标按的再快，也只能在一定时间内攻击一下</p>
</blockquote>
<pre><code class="language-js">function throttle(fn, delay) {
    let previous = 0;
    // 使用闭包返回一个函数并且用到闭包函数外面的变量previous
    return function() {
        let args = arguments;
        let now = new Date();
        if(now - previous &gt; delay) {
            fn.apply(this, args);
            previous = now;
        }
    }
}
</code></pre>
<h2>应用场景</h2>
<p>debounce</p>
<p>search搜索联想，用户在不断输入值时，用防抖来节约请求资源。
window触发resize的时候，不断的调整浏览器窗口大小会不断的触发这个事件，用防抖来让其只触发一次</p>
<p>throttle</p>
<p>鼠标不断点击触发，mousedown(单位时间内只触发一次)
监听滚动事件，比如是否滑到底部自动加载更多，用throttle来判断</p>
<h2>作用域</h2>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[圣杯/双飞翼布局与负边距]]></title>
            <link>https://zzfzzf.com/post/6972548384846647307</link>
            <guid>6972548384846647307</guid>
            <pubDate>Fri, 02 Oct 2020 12:45:40 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>左右定宽，中间自适应，利用负边距来改变文档流</p>
</blockquote>
<p><img src="https://oss-zzf.zzfzzf.com/cdn/1643097602846KsAUVA.png" alt="左右定宽，中间自适应"></p>
<h2>圣杯布局</h2>
<pre><code class="language-html">&lt;div class=&quot;content&quot;&gt;
  &lt;div class=&quot;middle&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;left&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;right&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<pre><code class="language-css">.content {
  overflow: hidden;
  padding: 0 100px;
}

.middle {
  position: relative;
  width: 100%;
  float: left;
  height: 80px;
  background: #f00;
}

.left {
  position: relative;
  width: 100px;
  float: left;
  left: -100px;
  height: 80px;
  margin-left: -100%;
  background: #0f0;
}

.right {
  position: relative;
  width: 100px;
  float: left;
  right: -100px;
  height: 80px;
  margin-left: -100px;
  background: #00f;
}
</code></pre>
<h2>双飞翼布局</h2>
<pre><code class="language-html">    &lt;div class=&quot;content&quot;&gt;
        &lt;div class=&quot;middle&quot;&gt;
            &lt;div class=&quot;inner-middle&quot;&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;left&quot;&gt;&lt;/div&gt;
        &lt;div class=&quot;right&quot;&gt;&lt;/div&gt;
    &lt;/div&gt;
</code></pre>
<pre><code class="language-css">        .content {
            overflow: hidden;
        }

        .middle {			
            width: 100%;
            float: left;
        }
               .inner-middle{
            width:100%;
            height: 80px;
            
            background: green;			
        }
        .left {
            width: 100px;
            float: left;
            height: 80px;
            margin-left: -100%;
            background: yellow;
        }

        .right {			
            width: 100px;
            float: left;
            height: 80px;
            margin-left: -100px;
            background: pink
        }
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[前端CICD工程化]]></title>
            <link>https://zzfzzf.com/post/6972548384846647303</link>
            <guid>6972548384846647303</guid>
            <pubDate>Wed, 23 Sep 2020 10:42:07 GMT</pubDate>
            <content:encoded><![CDATA[<ul>
<li><a href="https://fossa.com/">https://fossa.com/</a> 项目许可证验证</li>
<li><a href="https://circleci.com/">https://circleci.com/</a> 代码持续发布</li>
<li><a href="https://www.travis-ci.com/">https://www.travis-ci.com/</a> 代码持续发布</li>
<li><a href="https://vercel.com/dashboard">https://vercel.com/dashboard</a> 代码部署</li>
<li><a href="https://app.netlify.com/">https://app.netlify.com/</a> 代码部署</li>
<li><a href="https://surge.sh/">https://surge.sh/</a> 代码部署</li>
<li><a href="https://codecov.io/">https://codecov.io/</a> 测试覆盖率测试</li>
<li><a href="https://coding.net/">https://coding.net/</a> 国内代码部署平台</li>
<li><a href="https://shields.io/">shields.io/</a> 徽章</li>
<li><a href="https://badge.fury.io/">badge.fury.io</a> 徽章</li>
<li><a href="https://badgen.net/">badgen</a>徽章</li>
<li><a href="https://nodei.co/">nodei</a>徽章</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[SSH免密登录]]></title>
            <link>https://zzfzzf.com/post/6972548384846647302</link>
            <guid>6972548384846647302</guid>
            <pubDate>Wed, 23 Sep 2020 06:11:13 GMT</pubDate>
            <content:encoded><![CDATA[<h2>ssh</h2>
<p>全称<code>secure shell protocol</code></p>
<pre><code class="language-bash">ssh root@127.0.0.1
</code></pre>
<h2>免密登陆</h2>
<blockquote>
<p>公钥放在远程服务器的 authorized_keys</p>
</blockquote>
<pre><code class="language-bash">#生成密钥
ssh-keygen -t rsa #一路回车确认
#拷贝公钥放服务器，执行命令
cat ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys
echo &quot;xxxxxxx&quot; &gt;&gt; ~/.ssh/authorized_keys
#设置权限
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
#查看权限
ls -l 文件名称
ls -ld 文件夹名称
</code></pre>
<blockquote>
<p>公钥放服务器，私钥放本机，即可本机免密ssh服务器</p>
</blockquote>
<h2>禁用密码登陆</h2>
<pre><code class="language-bash">#编辑服务器端的 /etc/ssh/sshd_config
#禁用密码登录

Host *
  PasswordAuthentication no
</code></pre>
<h2>ssh git配置</h2>
<pre><code>ssh -T -p 443 git@ssh.github.com
</code></pre>
<p>~/.ssh/config</p>
<pre><code>Host github.com
Hostname ssh.github.com
Port 443
User git
</code></pre>
<pre><code class="language-bash">ssh -T git@github.com
ssh -vT git@github.com
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[统一团队的代码规范]]></title>
            <link>https://zzfzzf.com/post/6972548384846647301</link>
            <guid>6972548384846647301</guid>
            <pubDate>Fri, 18 Sep 2020 07:30:32 GMT</pubDate>
            <content:encoded><![CDATA[<h2>背景</h2>
<p>统一团队的代码规范，以react项目为例子，举一反三</p>
<blockquote>
<p>”eslint“:  javascript代码检测工具<br>&quot;stylelint&quot;:  css检测工具<br>&quot;stylelint-config-standard&quot;:  stylelint的推荐配置<br>&quot;stylelint-order&quot;:  css属性排序插件，合理的排序加快页面渲染<br>&quot;stylelint-scss&quot;:  增加支持scss语法<br>&quot;husky+lint-staged&quot;：commit时候代码检查</p>
</blockquote>
<pre><code class="language-json">{
&quot;devDependencies&quot;: {
    &quot;@babel/core&quot;: &quot;^7.14.8&quot;,
    &quot;@babel/plugin-transform-runtime&quot;: &quot;^7.14.5&quot;,
    &quot;@babel/preset-env&quot;: &quot;^7.14.9&quot;,
    &quot;@babel/preset-react&quot;: &quot;^7.14.5&quot;,
    &quot;@babel/preset-typescript&quot;: &quot;^7.14.5&quot;,
    &quot;@babel/runtime-corejs3&quot;: &quot;^7.14.9&quot;,
    &quot;@pmmmwh/react-refresh-webpack-plugin&quot;: &quot;^0.5.1&quot;,
    &quot;@types/react-dom&quot;: &quot;^17.0.9&quot;,
    &quot;@typescript-eslint/eslint-plugin&quot;: &quot;^4.29.1&quot;,
    &quot;@typescript-eslint/parser&quot;: &quot;^4.29.1&quot;,
    &quot;babel-loader&quot;: &quot;^8.2.2&quot;,
    &quot;cross-env&quot;: &quot;^7.0.3&quot;,
    &quot;css-loader&quot;: &quot;^6.2.0&quot;,
    &quot;css-minimizer-webpack-plugin&quot;: &quot;^3.1.1&quot;,
    &quot;dotenv&quot;: &quot;^10.0.0&quot;,
    &quot;dotenv-webpack&quot;: &quot;^7.0.3&quot;,
    &quot;eslint&quot;: &quot;^7.32.0&quot;,
    &quot;eslint-config-google&quot;: &quot;^0.14.0&quot;,
    &quot;eslint-config-prettier&quot;: &quot;^8.3.0&quot;,
    &quot;eslint-plugin-prettier&quot;: &quot;^3.4.0&quot;,
    &quot;eslint-plugin-react&quot;: &quot;^7.24.0&quot;,
    &quot;eslint-plugin-react-hooks&quot;: &quot;^4.2.0&quot;,
    &quot;html-webpack-plugin&quot;: &quot;^5.3.2&quot;,
    &quot;husky&quot;: &quot;^7.0.2&quot;,
    &quot;lint-staged&quot;: &quot;^11.1.2&quot;,
    &quot;mini-css-extract-plugin&quot;: &quot;^2.4.2&quot;,
    &quot;postcss-loader&quot;: &quot;^6.2.0&quot;,
    &quot;postcss-preset-env&quot;: &quot;^6.7.0&quot;,
    &quot;prettier&quot;: &quot;^2.3.2&quot;,
    &quot;react-refresh&quot;: &quot;^0.10.0&quot;,
    &quot;sass&quot;: &quot;^1.40.1&quot;,
    &quot;sass-loader&quot;: &quot;^12.1.0&quot;,
    &quot;style-loader&quot;: &quot;^3.2.1&quot;,
    &quot;stylelint&quot;: &quot;^13.13.1&quot;,
    &quot;tsconfig-paths-webpack-plugin&quot;: &quot;^3.5.1&quot;,
    &quot;typescript&quot;: &quot;^4.3.5&quot;,
    &quot;webpack&quot;: &quot;^5.48.0&quot;,
    &quot;webpack-cli&quot;: &quot;^4.7.2&quot;,
    &quot;webpack-dev-server&quot;: &quot;^3.11.2&quot;,
    &quot;webpack-merge&quot;: &quot;^5.8.0&quot;
  }
 }
</code></pre>
<h2>添加ESLint</h2>
<p>我们使用<code>ESLint</code>来<code>Linting Code</code></p>
<ol>
<li>安装所需要的依赖项</li>
</ol>
<pre><code class="language-bash">yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --dev
</code></pre>
<ol start="2">
<li>在项目根目录下添加<code>.eslintrc.json</code></li>
</ol>
<pre><code class="language-json">{
    &quot;env&quot;: {
        &quot;browser&quot;: true,
        &quot;es2020&quot;: true,
        &quot;node&quot;: true
    },
    &quot;extends&quot;: [
        &quot;plugin:promise/recommended&quot;,
        &quot;plugin:react/recommended&quot;,
        &quot;plugin:react-hooks/recommended&quot;,
        &quot;plugin:@typescript-eslint/recommended&quot;,
        &quot;prettier/@typescript-eslint&quot;,
        &quot;plugin:prettier/recommended&quot;
    ],
    &quot;parser&quot;: &quot;@typescript-eslint/parser&quot;,
    &quot;parserOptions&quot;: {
        &quot;ecmaFeatures&quot;: {
            &quot;jsx&quot;: true
        },
        &quot;ecmaVersion&quot;: 2020,
        &quot;sourceType&quot;: &quot;module&quot;
    },
    &quot;plugins&quot;: [
        &quot;promise&quot;,
        &quot;react&quot;,
        &quot;@typescript-eslint&quot;,
        &quot;react-hooks&quot;,
        &quot;prettier&quot;
    ],
    &quot;rules&quot;: {
        &quot;prettier/prettier&quot;: 2,
        &quot;react-hooks/rules-of-hooks&quot;: 2,
        &quot;react-hooks/exhaustive-deps&quot;: 1,
        &quot;react/display-name&quot;: 0,
        &quot;react/prop-types&quot;:0,
        &quot;@typescript-eslint/no-var-requires&quot;: 0
    },
    &quot;settings&quot;: {
        &quot;react&quot;: {
            &quot;version&quot;: &quot;detect&quot;
        }
    }
}
</code></pre>
<h2>添加Prettier</h2>
<pre><code class="language-bash">yarn add prettier eslint-config-prettier eslint-plugin-prettier --dev
</code></pre>
<p>在项目根目录下添加<code>.prettierrc</code></p>
<pre><code class="language-json">{
  &quot;trailingComma&quot;: &quot;all&quot;,
  &quot;tabWidth&quot;: 2,
  &quot;semi&quot;: true,
  &quot;singleQuote&quot;: true,
  &quot;endOfLine&quot;: &quot;lf&quot;,
  &quot;printWidth&quot;: 100,
  &quot;bracketSpacing&quot;: true,
  &quot;arrowParens&quot;: &quot;always&quot;,
  &quot;useTabs&quot;: false,
  &quot;quoteProps&quot;: &quot;as-needed&quot;,
  &quot;jsxSingleQuote&quot;: true,
  &quot;jsxBracketSameLine&quot;: false,
  &quot;requirePragma&quot;: false,
  &quot;insertPragma&quot;: false,
  &quot;proseWrap&quot;: &quot;preserve&quot;,
  &quot;htmlWhitespaceSensitivity&quot;: &quot;css&quot;,
  &quot;vueIndentScriptAndStyle&quot;: false,
  &quot;embeddedLanguageFormatting&quot;: &quot;auto&quot;
}
</code></pre>
<h2>换行符</h2>
<blockquote>
<p>windows和mac、linux默认的换行符不同，为了方便git版本管理，我们需要统一换行符，在项目根目录添加<strong>gitignore</strong>文件</p>
</blockquote>
<h3>配置详解</h3>
<blockquote>
<p> eol (end of line)</p>
</blockquote>
<pre><code>#.gitignore
text eol=lf
*.jpg   binary
*.png   binary
*.gif   binary
*.svg   binary
</code></pre>
<h3>已有项目更新换行符</h3>
<pre><code>git rm --cached -r .
git reset --hard
</code></pre>
<p>参考链接</p>
<ul>
<li><a href="https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project">Using ESLint and Prettier in a TypeScript Project</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[webpack4入门到进阶]]></title>
            <link>https://zzfzzf.com/post/6972548384846647300</link>
            <guid>6972548384846647300</guid>
            <pubDate>Sun, 13 Sep 2020 07:12:17 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>在查看众多开源项目后，发现大多数具有html的页面都使用<a href="https://webpack.js.org/">webpack</a>打包，而纯js或者工具包类的都使用<a href="https://rollupjs.org/">rollupjs</a>打包</p>
</blockquote>
<h2>初始化项目</h2>
<p>安装webpack
<code>yarn add webpack webpack-cli --dev</code></p>
<p>常见loader</p>
<p>配置文件
<code>webpack.config.js</code></p>
<pre><code class="language-js">module.exports = {
  //开发or生产
  mode: &#39;.....&#39;,

  //定义入口
  entry: &#39;.....&#39;,

  //输出配置
  output: {},

  //各种loader
  module: {},

  //如何生成 Source Map
  devtool: &#39;......&#39;,

  //别名设置
  resolve: {},

  //插件（数组）
  plugins: [],

  //配置优化
  optimization: {},

  //排除打包时的依赖项
  externals: {},

  //开发服务器
  devServer: {},
  //代码切割
  splitChunks: {},

  //运行时的配置
  runtimeChunk: {},
};
</code></pre>
<h3>entry</h3>
<blockquote>
<p>打包文件的起点入口<br>入口可分为多入口和单入口</p>
</blockquote>
<pre><code class="language-js">//单入口
module.exports = {
  entry: `./index.js`,
}
//多入口
module.exports = {
  entry: { 
    &quot;index&quot;: `./index.js`,
  },
}
</code></pre>
<h3>output</h3>
<blockquote>
<p>用来指定打包后的文件名字和路径以及打包格式</p>
</blockquote>
<h3>loader</h3>
<p>loader就是翻译官，把文件翻译给webpack认识</p>
<h3>插件</h3>
<p>增强webpack功能</p>
<h3>开发服务器配置</h3>
<h3>代码切割配置</h3>
<h2>webpack优化</h2>
<h3>构建速度优化</h3>
<blockquote>
<p>主要思路<br>减少要处理的文件<br>缩小要查找的范围</p>
</blockquote>
<ul>
<li>hard-loader 缓存</li>
<li>多线程编译</li>
<li>webpack4</li>
<li>使用别名</li>
<li>配置后缀</li>
</ul>
<h3>打包体积优化</h3>
<ul>
<li>cdn+externals</li>
<li>按需加载</li>
<li>分离大包</li>
<li>动态加载</li>
</ul>
<p>附录</p>
<ul>
<li><a href="https://juejin.cn/post/6844904036936712200">https://juejin.cn/post/6844904036936712200</a></li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[够用的Docker知识]]></title>
            <link>https://zzfzzf.com/post/6972548384846647298</link>
            <guid>6972548384846647298</guid>
            <pubDate>Fri, 13 Feb 2026 03:13:46 GMT</pubDate>
            <content:encoded><![CDATA[<h2>核心概念</h2>
<ol>
<li>仓库</li>
<li>镜像</li>
<li>容器</li>
</ol>
<p>仓库中存储镜像，镜像生成容器</p>
<h2>安装</h2>
<h3>一键安装(推荐)</h3>
<pre><code class="language-bash">apt update -y
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
systemctl start docker # 启动docker
systemctl enable docker # 开机自动启动docker 
systemctl restart docker # 重启docker
</code></pre>
<h3>aws机器安装</h3>
<p><a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/create-container-image.html">https://docs.aws.amazon.com/AmazonECS/latest/developerguide/create-container-image.html</a></p>
<pre><code class="language-bash">sudo yum update -y
sudo amazon-linux-extras install docker
sudo service docker start
sudo systemctl enable docker
sudo usermod -a -G docker ec2-user
docker info
</code></pre>
<h3>手动安装(自动安装失败时用此方案)</h3>
<pre><code class="language-bash">#step 1: 安装必要的一些系统工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
#Step 2: 添加软件源信息&amp;使用阿里云镜像
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
#Step 3: 更新缓存并安装 Docker-CE
sudo yum makecache fast
sudo yum -y install docker-ce
#Step 4: 开启Docker服务
sudo systemctl start docker
sudo systemctl enable docker
</code></pre>
<h2>配置镜像加速</h2>
<blockquote>
<p><a href="https://cr.console.aliyun.com/cn-shanghai/instances/mirrors">https://cr.console.aliyun.com/cn-shanghai/instances/mirrors</a></p>
</blockquote>
<pre><code class="language-bash">sudo mkdir -p /etc/docker

sudo tee /etc/docker/daemon.json &lt;&lt;-&#39;EOF&#39;
{
  &quot;registry-mirrors&quot;: [&quot;https://*****.mirror.aliyuncs.com&quot;]
}
EOF

sudo systemctl daemon-reload

sudo systemctl restart docker
</code></pre>
<h2>搭建私有仓库</h2>
<pre><code class="language-bash">docker run -d -p 5000:5000 --restart=always --name registry registry
</code></pre>
<pre><code class="language-bash">#用 curl 查看仓库中的镜像
curl 127.0.0.1:5000/v2/_catalog
</code></pre>
<h2>常用命令</h2>
<h3>进程</h3>
<ol>
<li>systemctl stop docker</li>
<li>systemctl restart docker</li>
<li>systemctl status docker</li>
<li>systemctl enable docker</li>
<li>systemctl start docker</li>
</ol>
<h3>镜像</h3>
<ol>
<li>所有镜像 <code>docker images</code></li>
<li>删除镜像 <code>docker rmi image_id</code></li>
</ol>
<h3>容器</h3>
<ol>
<li>暂停容器 <code>docker stop container_id</code></li>
<li>删除容器 <code>docker rm container_id</code></li>
<li>启动已被停止的容器 <code>docker start container_id</code></li>
<li>停止运行中的容器 <code>docker stop container_id</code></li>
<li>重启容器 <code>docker restart container_id</code></li>
<li>所有正在运行的容器 <code>docker ps</code></li>
<li>所有容器 <code>docker ps -a</code></li>
<li>启动容器 <code>docker start container_id</code></li>
</ol>
<h3>其他</h3>
<ol>
<li>查看端口 <code>docker port container_id</code></li>
<li>删除无意义的镜像 <code>docker image prune</code></li>
</ol>
<h1>DOCKERFILE</h1>
<blockquote>
<p>Dockerfile 是一个文本文件，其内包含了一条条的 指令(Instruction)，每一条指令构建一层，因此每一条指令的内容，就是描述该层应当如何构建。</p>
</blockquote>
<table>
<thead>
<tr>
<th>关键字</th>
<th>作用</th>
<th>备注</th>
<th>关键字</th>
</tr>
</thead>
<tbody><tr>
<td>FROM</td>
<td>指定父镜像</td>
<td>指定dockerfile基于那个image构建</td>
<td>FROM</td>
</tr>
<tr>
<td>MAINTAINER</td>
<td>作者信息</td>
<td>用来标明这个dockerfile谁写的</td>
<td>MAINTAINER</td>
</tr>
<tr>
<td>LABEL</td>
<td>标签</td>
<td>用来标明dockerfile的标签 可以使用Label代替Maintainer 最终都是在docker image基本信息中可以查看</td>
<td>LABEL</td>
</tr>
<tr>
<td>RUN</td>
<td>执行命令</td>
<td>执行一段命令 默认是/bin/sh 格式: RUN command 或者 RUN [&quot;command&quot; , &quot;param1&quot;,&quot;param2&quot;]</td>
<td>RUN</td>
</tr>
<tr>
<td>CMD</td>
<td>容器启动命令</td>
<td>提供启动容器时候的默认命令 和ENTRYPOINT配合使用.格式 CMD command param1 param2 或者 CMD [&quot;command&quot; , &quot;param1&quot;,&quot;param2&quot;]</td>
<td>CMD</td>
</tr>
<tr>
<td>ENTRYPOINT</td>
<td>入口</td>
<td>一般在制作一些执行就关闭的容器中会使用</td>
<td>ENTRYPOINT</td>
</tr>
<tr>
<td>COPY</td>
<td>复制文件</td>
<td>build的时候复制文件到image中</td>
<td>COPY</td>
</tr>
<tr>
<td>ADD</td>
<td>添加文件</td>
<td>build的时候添加文件到image中 不仅仅局限于当前build上下文 可以来源于远程服务</td>
<td>ADD</td>
</tr>
<tr>
<td>ENV</td>
<td>环境变量</td>
<td>指定build时候的环境变量 可以在启动的容器的时候 通过-e覆盖 格式ENV name=value</td>
<td>ENV</td>
</tr>
<tr>
<td>ARG</td>
<td>构建参数</td>
<td>构建参数 只在构建的时候使用的参数 如果有ENV 那么ENV的相同名字的值始终覆盖arg的参数</td>
<td>ARG</td>
</tr>
<tr>
<td>VOLUME</td>
<td>定义外部可以挂载的数据卷</td>
<td>指定build的image那些目录可以启动的时候挂载到文件系统中 启动容器的时候使用 -v 绑定 格式 VOLUME [&quot;目录&quot;]</td>
<td>VOLUME</td>
</tr>
<tr>
<td>EXPOSE</td>
<td>暴露端口</td>
<td>定义容器运行的时候监听的端口 启动容器的使用-p来绑定暴露端口 格式: EXPOSE 8080 或者 EXPOSE 8080/udp</td>
<td>EXPOSE</td>
</tr>
<tr>
<td>WORKDIR</td>
<td>工作目录</td>
<td>指定容器内部的工作目录 如果没有创建则自动创建 如果指定/ 使用的是绝对地址 如果不是/开头那么是在上一条workdir的路径的相对路径</td>
<td>WORKDIR</td>
</tr>
<tr>
<td>USER</td>
<td>指定执行用户</td>
<td>指定build或者启动的时候 用户 在RUN CMD ENTRYPONT执行的时候的用户</td>
<td>USER</td>
</tr>
<tr>
<td>HEALTHCHECK</td>
<td>健康检查</td>
<td>指定监测当前容器的健康监测的命令 基本上没用 因为很多时候 应用本身有健康监测机制</td>
<td>HEALTHCHECK</td>
</tr>
<tr>
<td>ONBUILD</td>
<td>触发器</td>
<td>当存在ONBUILD关键字的镜像作为基础镜像的时候 当执行FROM完成之后 会执行 ONBUILD的命令 但是不影响当前镜像 用处也不怎么大</td>
<td>ONBUILD</td>
</tr>
<tr>
<td>STOPSIGNAL</td>
<td>发送信号量到宿主机</td>
<td>该STOPSIGNAL指令设置将发送到容器的系统调用信号以退出。</td>
<td>STOPSIGNAL</td>
</tr>
<tr>
<td>SHELL</td>
<td>指定执行脚本的shell</td>
<td>指定RUN CMD ENTRYPOINT 执行命令的时候 使用的shell</td>
<td></td>
</tr>
</tbody></table>
<pre><code class="language-dockerfile">FROM ubuntu:bionic-20190612

LABEL maintainer xxxx@xxxx.com

ENV MYSQL_USER=mysql \
    MYSQL_VERSION=5.7 \
    MYSQL_DATA_DIR=/var/lib/mysql \
    MYSQL_RUN_DIR=/run/mysqld \
    MYSQL_LOG_DIR=/var/log/mysql

RUN apt-get update \
 &amp;&amp; DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-server=${MYSQL_VERSION}* \
 &amp;&amp; rm -rf ${MYSQL_DATA_DIR} \
 &amp;&amp; rm -rf /var/lib/apt/lists/*

EXPOSE 3306/tcp

CMD [&quot;/usr/bin/mysqld_safe&quot;]
</code></pre>
<h2>docker build</h2>
<h2>docker-compose</h2>
<p><a href="https://docs.docker.com/compose/">官方文档</a></p>
<h3>安装</h3>
<pre><code class="language-bash"># 下载
curl -SL https://github.com/docker/compose/releases/download/v2.10.2/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
# 赋权
sudo chmod +x /usr/local/bin/docker-compose
# 验证
docker-compose version
</code></pre>
<blockquote>
<p>独立版 Compose 使用破折号compose语法,其他使用空格语法</p>
</blockquote>
<h2>常用命令</h2>
<pre><code class="language-bash">#查看帮助
docker-compose -h

#-f  指定使用的 Compose 模板文件，默认为 docker-compose.yml，可以多次指定。
docker-compose -f docker-compose.yml up -d 

#启动所有容器，-d 将会在后台启动并运行所有的容器
docker-compose up -d

#停用移除所有容器以及网络相关
docker-compose down

#查看服务容器的输出
docker-compose logs

#列出项目中目前的所有容器
docker-compose ps

#构建（重新构建）项目中的服务容器。服务容器一旦构建后，将会带上一个标记名，例如对于 web 项目中的一个 db 容器，可能是 web_db。可以随时在项目目录下运行 docker-compose build 来重新构建服务
docker-compose build

#拉取服务依赖的镜像
docker-compose pull

#重启项目中的服务
docker-compose restart

#删除所有（停止状态的）服务容器。推荐先执行 docker-compose stop 命令来停止容器。
docker-compose rm 

#在指定服务上执行一个命令。
docker-compose run ubuntu ping docker.com

#设置指定服务运行的容器个数。通过 service=num 的参数来设置数量
docker-compose scale web=3 db=2

#启动已经存在的服务容器。
docker-compose start

#停止已经处于运行状态的容器，但不删除它。通过 docker-compose start 可以再次启动这些容器。
docker-compose stop
</code></pre>
<pre><code class="language-dockerfile">version: &quot;3&quot;

services:
  doc:
    restart: always
    image: registry.cn-shanghai.aliyuncs.com/zzf2001/bot-doc:latest
    ports:
      - &quot;9060:80&quot;
  vue:
    restart: always
    image: registry.cn-shanghai.aliyuncs.com/zzf2001/bot-vue2:latest
    ports:
      - &quot;9061:80&quot;
  react:
    restart: always
    image: registry.cn-shanghai.aliyuncs.com/zzf2001/bot-react:latest
    ports:
      - &quot;9062:80&quot;
</code></pre>
<h2>参考文章</h2>
<ol>
<li><a href="https://yeasy.gitbook.io/docker_practice/">https://yeasy.gitbook.io/docker_practice/</a></li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[自己部署内网穿透]]></title>
            <link>https://zzfzzf.com/post/6972548384846647297</link>
            <guid>6972548384846647297</guid>
            <pubDate>Sat, 18 Jul 2020 16:48:45 GMT</pubDate>
            <content:encoded><![CDATA[<h2>frp</h2>
<ul>
<li><a href="https://github.com/fatedier/frp">官网</a></li>
<li><a href="https://gofrp.org/docs/overview/">文档</a></li>
</ul>
<h3>下载</h3>
<ul>
<li><a href="https://github.com/fatedier/frp/releases">下载页面</a></li>
</ul>
<pre><code class="language-bash">wget https://github.com/fatedier/frp/releases/download/v0.44.0/frp_0.44.0_linux_arm64.tar.gz
</code></pre>
<h3>解压</h3>
<pre><code class="language-bash">tar -zxvf frp_0.44.0_linux_arm64.tar.gz
</code></pre>
<h3>配置</h3>
<ul>
<li>将 <strong>frps</strong> 及 <strong>frps.ini</strong> 放到具有公网 IP 的机器上。</li>
<li>将 <strong>frpc</strong> 及 <strong>frpc.ini</strong> 放到处于内网环境的机器上。</li>
</ul>
<blockquote>
<p>配置frps.ini</p>
</blockquote>
<pre><code class="language-ini"># frps.ini
[common]
bind_port = 7000           #与客户端绑定的进行通信的端口
vhost_http_port = 9000     #域名后的端口号
</code></pre>
<blockquote>
<p>启动服务端</p>
</blockquote>
<pre><code class="language-bash">./frps -c ./frps.ini
</code></pre>
<blockquote>
<p>配置客户端</p>
</blockquote>
<pre><code class="language-ini"># frpc.ini
[common]
server_addr = 120.56.37.48   #公网服务器ip
server_port = 7000           #与服务端bind_port一致
#公网访问内部web服务器以http方式
[web]
type = http         #访问协议
local_port = 8081   #本地端口
custom_domains = frp.annyyy.com   #域名
</code></pre>
<blockquote>
<p>centos要给755可执行权限,启动脚本</p>
</blockquote>
<pre><code class="language-bash">   ./frpc -c  ./frpc.ini
</code></pre>
<h2>资源</h2>
<p><a href="https://github.com/anderspitman/awesome-tunneling">awesome-tunneling</a></p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[node包管理]]></title>
            <link>https://zzfzzf.com/post/6972548384846647296</link>
            <guid>6972548384846647296</guid>
            <pubDate>Sat, 11 Jul 2020 13:25:42 GMT</pubDate>
            <content:encoded><![CDATA[<h2>包管理工具</h2>
<h3>npm</h3>
<blockquote>
<p><a href="https://github.com/npm/cli">https://github.com/npm/cli</a></p>
</blockquote>
<pre><code class="language-bash">npm install -g npm
npm list -g --depth=0
</code></pre>
<h3>yarn</h3>
<blockquote>
<p><a href="https://github.com/yarnpkg/yarn">https://github.com/yarnpkg/yarn</a></p>
</blockquote>
<pre><code class="language-bash">npm install -g yarn
yarn global upgrade yarn
yarn add react
yarn add react -D
yarn global add react
</code></pre>
<h3>pnpm</h3>
<blockquote>
<p><a href="https://github.com/pnpm/pnpm">https://github.com/pnpm/pnpm</a></p>
</blockquote>
<pre><code class="language-bash">npm install -g pnpm
</code></pre>
<h2>npm源关联</h2>
<h3>npm</h3>
<ul>
<li><code>npm get registry</code>
查看源</li>
<li><code>npm --registry registry.npm.taobao.org install any-touch</code>
临时修改</li>
<li><code>npm config set registry registry.npm.taobao.org</code>
持久使用</li>
<li><code>npm config set registry registry.npmjs.org</code>
还原</li>
<li><code>npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass </code>
设置node-sass镜像</li>
</ul>
<h3>yarn</h3>
<ul>
<li><code>yarn config get registry</code>
查看源</li>
<li><code>yarn add any-touch@latest --registry=registry.npmjs.org/</code>
临时修改</li>
<li><code>yarn config set registry registry.npm.taobao.org/</code>
持久修改</li>
<li><code>yarn config set sass_binary_site https://npm.taobao.org/mirrors/node-sass </code>
设置node-sass镜像</li>
</ul>
<h3>pnpm</h3>
<ul>
<li><code>pnpm get registry</code>
查看源</li>
<li><code>pnpm --registry registry.npm.taobao.org install any-touch</code>
临时修改</li>
<li><code>pnpm config set registry registry.npm.taobao.org</code>
持久使用</li>
<li><code>pnpm config set registry registry.npmjs.org</code>
还原</li>
<li></li>
</ul>
<h3>工具切换</h3>
<ul>
<li>切换npm源 <a href="https://github.com/Pana/nrm">nrm官网</a></li>
<li>切换yarn源 <a href="https://github.com/i5ting/yrm">yrm官网</a></li>
<li>切换cgr源 <a href="https://www.npmjs.com/package/cgr">cgr官网</a></li>
</ul>
<h2>常用命令</h2>
<table>
<thead>
<tr>
<th>npm</th>
<th>yarn</th>
<th>功能</th>
</tr>
</thead>
<tbody><tr>
<td>npm install</td>
<td>yarn</td>
<td>安装全部依赖</td>
</tr>
<tr>
<td>npm install react --save</td>
<td>yarn add react</td>
<td></td>
</tr>
<tr>
<td>npm uninstall react --save</td>
<td>yarn remove react</td>
<td>移除依赖</td>
</tr>
<tr>
<td>npm install react --save-dev</td>
<td>yarn add react --dev</td>
<td></td>
</tr>
<tr>
<td>npm update --save</td>
<td>yarn upgrade</td>
<td></td>
</tr>
<tr>
<td>npm install react --global</td>
<td>yarn global add react</td>
<td></td>
</tr>
<tr>
<td>npm list -g --depth 0</td>
<td>yarn global list --depth=0</td>
<td>查看全局安装的包</td>
</tr>
</tbody></table>
<h2>npm全局包路径修改</h2>
<pre><code class="language-bash">npm config get prefix
npm config set prefix &quot;/usr/local/Cellar/node@12/12.21.0_1&quot;
</code></pre>
<h2>设置代理</h2>
<pre><code class="language-bash">npm config set proxy http://127.0.0.1:6152
npm config set https-proxy http://127.0.0.1:6152
yarn config set proxy  http://127.0.0.1:6152
yarn confit set https-proxy http://127.0.0.1:6152 
npm config delete proxy
npm config delete https-proxy
yarn config delete proxy
yarn config delete https-proxy
</code></pre>
<h2>更新项目依赖</h2>
<pre><code class="language-bash">#yarn更新依赖
yarn upgrade-interactive --lates
#更新所有依赖
npm update
#执行以下命令不应有输出
npm outdated
</code></pre>
<h2>更新npm</h2>
<h2>Monorepo支持</h2>
<h2>package.json</h2>
<ul>
<li>dependencies</li>
<li>devDependencies</li>
<li>peerDependencies<blockquote>
<p>这三个字段在我们的业务项目中是没有什么区别的，主要区别是在开发npm包时候和node应用</p>
</blockquote>
</li>
</ul>
<h3>dependencies</h3>
<blockquote>
<p>项目依赖,比如<code>react</code></p>
</blockquote>
<h3>devDependencies</h3>
<blockquote>
<p>开发依赖，比如<code>eslint</code></p>
</blockquote>
<h3>peerDependencies</h3>
<blockquote>
<p>peerDependencies 主要用于依赖包中，表示安装该包时还需要安装哪些包</p>
</blockquote>
<h3>版本号</h3>
<ul>
<li>^主版本号不会变，另外两个版本号会更新到最新</li>
<li>~主版本和次要版本不会变，最后一个版本号会更新到最新</li>
<li>啥也没有：固定版本，不会更新</li>
</ul>
<h2>npm install发生了什么</h2>
<p><img src="https://oss-zzf.zzfzzf.com/cdn/d5031e325dd1411599291834a745b03c_tplv-k3u1fbpfcp-zoom-in-crop-mark_3024_0_0_0.webp" alt=""></p>
<blockquote>
<p>主要步骤如下</p>
</blockquote>
<ul>
<li>检查配置。包括项目级、用户级、全局级、内置的 .npmrc 文件。</li>
<li>确定依赖版本，构建依赖树。确定项目依赖版本有两个来源，一是 package.json 文件，一是 lockfile 文件,两个确认版本、构建依赖树的来源，互不可少、相辅相成。如果 package-lock.json 文件存在且符合 package.json 声明的的情况下，直接读取；否则重新确认依赖的版本。</li>
<li>下载包资源。下载前先确认本地是否存在匹配的缓存版本，如果有就直接使用缓存文件，如果没有就下载并添加到缓存，然后将包按依赖树解压到 node_modules 目录。</li>
<li>生成 lockfile 文件。</li>
</ul>
<ol>
<li>构建依赖树的过程中，版本确认需要结合 package.json 和 package-lock.json 两个文件。先确认 package-lock.json 安装版本，符合规则就以此为准，否则由 package.json 声明的版本范围重新确认。特别地，若是在开发中手动更改包信息，会导致 lockfile 版本信息异常，也可能由 package.json 确认。确认好的依赖树会存到 package-lock.json 文件中，这里跟 yarn.lock 存在差异。</li>
<li>同一个依赖，更高版本的包会安装到顶层目录，即 node_modules 目录；否则会分散在某些依赖的 node_modules 目录，如：node_modules/expect-jsx/node_modules/react 目录。</li>
<li>如果依赖升级，造成版本不兼容，需要多版本共存，那么仍然是将高版本安装到顶层，低版本分散到各级目录。</li>
<li>lockfile 的存在，保证了项目依赖结构的确定性，保障了项目在多环境运行的稳定性。</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[centos高频命令]]></title>
            <link>https://zzfzzf.com/post/6972548384842452996</link>
            <guid>6972548384842452996</guid>
            <pubDate>Sat, 11 Jul 2020 13:24:51 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>版本-<strong>centos7.9</strong></p>
</blockquote>
<h2>切换目录</h2>
<pre><code>cd ~ 
cd /
cd ..
cd -
</code></pre>
<h2>查看目录或文件</h2>
<pre><code>ls
cat demo
cat demo --number
cat -b demo
cat demo demo-A
cat demo demo-A &gt; demo-B
cat demo demo-A&gt;&gt; demo-B
</code></pre>
<h2>拷贝</h2>
<pre><code>cp demo-A /usr/local/demo
cp demo demo-bak
cp -R  demo /usr/local/demo
cp - R demo /demo-bak
</code></pre>
<h2>移动</h2>
<pre><code>mv demo demo-A
mv demo-bake ~/demo_bak
</code></pre>
<h2>解压缩</h2>
<pre><code class="language-bash">tar -zcvf filename.tar.gz /folder # gzip 压缩
tar -zxvf filename.tar.gz         # 当前目录下解压文件
</code></pre>
<h2>系统</h2>
<pre><code class="language-bash">df -h # 查看系统硬盘
</code></pre>
<h2>防火墙</h2>
<pre><code class="language-bash">#查看防火墙状态
systemctl status firewalld
#关闭防火墙
systemctl stop firewalld
#重启防火墙
firewall-cmd --reload
#查看一个端口是否开启
firewall-cmd --query-port=80/tcp
#开启一个端口（--permanent 永久生效，没有此参数重启后失效，--zone 作用域 ，--add-port=80/tcp 添加端口，格式为：端口/通讯协议）
firewall-cmd --zone=public --add-port=80/tcp --permanent
#开启端口区间
firewall-cmd --zone=public --add-port=0-65535/udp --permanent
firewall-cmd --zone=public --add-port=0-65535/tcp --permanent
</code></pre>
<h2>进程</h2>
<pre><code class="language-bash">#查看进程
yum install lsof -y
ps -ef | grep ssh
lsof -i tcp:80
netstat -lnp|grep 8000
</code></pre>
<h2>可执行文件</h2>
<pre><code class="language-bash">nohup ./program &gt;/dev/null 2&gt;&amp;1 &amp;
</code></pre>
<blockquote>
<p>/dev/null</p>
</blockquote>
<p>/dev/null属于字符特殊文件，它属于空设备，是一个特殊的设备文件，它会丢弃一切写入其中的数据，写入它的内容都会永远丢失，而且没有任何可以读取的内容。它就像一个黑洞，我们一般会把/dev/null当成一个垃圾站，不要的东西丢进去。比如来清除文件中的内容</p>
<blockquote>
<p>Linux的重定向</p>
</blockquote>
<p>0：表示标准输入；</p>
<p>1：标准输出,在一般使用时，默认的是标准输出；</p>
<p>2：表示错误信息输出。</p>
<p>./program &gt;/dev/null 2&gt;log表示将program的错误信息输出到log文件，其他信息丢进/dev/null。</p>
<p>./program &gt;/dev/null 2&gt;&amp;1表示将program的错误信息重定向到标准输出，其他信息丢进/dev/null。</p>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[Mac & Windows Setup(2026)]]></title>
            <link>https://zzfzzf.com/post/6972548384842452994</link>
            <guid>6972548384842452994</guid>
            <pubDate>Fri, 02 Jan 2026 03:01:46 GMT</pubDate>
            <content:encoded><![CDATA[<h2>🍎 macOS 配置篇</h2>
<blockquote>
<p>当拥有一个全新的mac电脑或者重装系统后，可根据本文配置mac。<br>建议安装顺序按照文章顺序<br>一个全新的mac本，配置node、java开发环境<br>本文软件从官网下载，开发环境包使用<a href="https://brew.sh/">brew</a>管理</p>
</blockquote>
<ol>
<li>基础工具安装</li>
</ol>
<pre><code class="language-bash">xcode-select --install
</code></pre>
<h3>Chrome</h3>
<p><a href="https://www.google.com/chrome/">https://www.google.com/chrome/</a></p>
<h3>item2</h3>
<p><a href="https://iterm2.com/">https://iterm2.com/</a></p>
<h3>Homebrew</h3>
<blockquote>
<p><a href="https://brew.sh/">官网</a></p>
</blockquote>
<ol>
<li>安装</li>
</ol>
<pre><code class="language-bash">/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;
</code></pre>
<ol start="2">
<li>常用命令</li>
</ol>
<pre><code class="language-bash">brew -v #版本
brew update #更新
brew search node #搜索
brew install node@12 #安装
</code></pre>
<h3>Homebrew可视化</h3>
<p><a href="https://www.cakebrew.com/">cakebrew</a></p>
<h3>zsh</h3>
<ol>
<li>安装<a href="https://ohmyz.sh/">Oh My Zsh</a></li>
</ol>
<pre><code class="language-bash">sh -c &quot;$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;
</code></pre>
<ol start="2">
<li>安装插件<br>step1 下载</li>
</ol>
<pre><code class="language-bash">brew install zsh-syntax-highlighting//高亮
brew install zsh-autosuggestions//自动补全
</code></pre>
<p>step2
在<code>~/.zshrc</code>中添加如下配置</p>
<pre><code class="language-bash">source $(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh
</code></pre>
<p>step3 使配置生效
<code>source ~/.zshrc</code></p>
<h3>alfred</h3>
<blockquote>
<p><a href="https://www.alfredapp.com">官网</a></p>
</blockquote>
<h3>JDK安装</h3>
<pre><code class="language-bash">#关联旧版本
brew tap homebrew/cask-versions
brew install --cask temurin@8
brew install --cask temurin@17
</code></pre>
<p>环境变量</p>
<pre><code class="language-bash">export JAVA_HOME=&quot;$(/usr/libexec/java_home -v &quot;$JAVA_VERSION&quot;)&quot;
</code></pre>
<h3>MAVEN</h3>
<ol>
<li>安装</li>
</ol>
<pre><code class="language-bash"> brew search maven
 brew install maven@3.5 --ignore-dependencies #防止下载关联的jdk依赖
 mvn -v
</code></pre>
<ol start="2">
<li>配置一下环境变量</li>
</ol>
<pre><code>#.zshrc添加一行
export JAVA_HOME=&quot;$(/usr/libexec/java_home -v &quot;$JAVA_VERSION&quot;)&quot;
export PATH=&quot;/usr/local/opt/maven@3.5/bin:$PATH&quot;
</code></pre>
<h3>NODE安装</h3>
<ol>
<li>直接安装</li>
</ol>
<pre><code class="language-bash">brew search node
brew install node@24
</code></pre>
<blockquote>
<p>安装完配置一下环境变量</p>
</blockquote>
<pre><code class="language-bash">#.zshrc添加一行
export PATH=&quot;/usr/local/opt/node@24/bin:$PATH&quot;
</code></pre>
<blockquote>
<p>不建议使用nvm,可以使用 <a href="https://volta.sh/">https://volta.sh/</a>
2. 通过nvm安装
nvm 使用方式见<a href="https://github.com/nvm-sh/nvm">文档</a></p>
</blockquote>
<pre><code class="language-bash">brew install nvm
</code></pre>
<h2>🪟 Windows 配置篇（更新于2022年）</h2>
<h3>git</h3>
<p><a href="https://git-scm.com/">https://git-scm.com/</a></p>
<h3>node</h3>
<p><a href="https://nodejs.org/">https://nodejs.org/</a></p>
<h3>jdk 配置</h3>
<p><a href="https://adoptium.net/">下载</a></p>
<blockquote>
<p>环境变量配置</p>
<ol>
<li><ul>
<li>变量名：<code>JAVA_HOME</code></li>
</ul>
</li>
</ol>
<ul>
<li>变量值：<code>C:\Program Files\Java\jdk1.8.0_211</code></li>
</ul>
</blockquote>
<hr>
<ol start="2">
<li><ul>
<li>变量名：Path</li>
<li>变量值：<code>%JAVA_HOME%\bin</code> <code>%JAVA_HOME%\jre\bin</code></li>
</ul>
</li>
</ol>
<hr>
<ol start="3">
<li><ul>
<li>检测是否成功 <code>java -version</code> <code>java</code> <code>javac</code></li>
</ul>
</li>
</ol>
<h3>maven 配置</h3>
<ol>
<li></li>
</ol>
<ul>
<li>变量名：<code>MAVEN_HOME</code></li>
<li>变量值：<code>C:\Program Files\Java\apache-maven-3.6.3</code></li>
</ul>
<hr>
<ol start="2">
<li><ul>
<li>变量名：Path</li>
<li>变量值：<code>%MAVEN_HOME%\bin</code></li>
</ul>
</li>
</ol>
<hr>
<ol start="3">
<li><ul>
<li>检测是否成功 <code>mvn -v</code></li>
</ul>
</li>
<li><ul>
<li>maven源改为阿里云镜像</li>
</ul>
</li>
</ol>
<blockquote>
<p>修改maven根目录下的conf文件夹中的setting.xml文件</p>
</blockquote>
<pre><code>&lt;mirror&gt;
        &lt;id&gt;aliyunmaven&lt;/id&gt;
        &lt;mirrorOf&gt;central&lt;/mirrorOf&gt;
        &lt;name&gt;阿里云公共仓库&lt;/name&gt;
        &lt;url&gt;https://maven.aliyun.com/repository/public&lt;/url&gt;
&lt;/mirror&gt;
</code></pre>
<h3>oracle驱动安装</h3>
<p><a href="https://annyyy.oss-cn-shanghai.aliyuncs.com/ojdbc6-11.2.0.3.jar">ojdbc6-11.2.0.3.jar</a></p>
<pre><code>mvn install:install-file -Dfile=ojdbc6-11.2.0.3.jar -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0.3 -Dpackaging=jar
</code></pre>
<h2>idea&amp;&amp;Webstorm插件</h2>
<ol>
<li>Git commit template</li>
<li>Gittoolbox</li>
<li>Rainbow brackets</li>
<li>translation</li>
<li>tabnine</li>
<li>Alibaba Java Coding Guidelines</li>
<li>Lombok</li>
<li>MybatisX</li>
<li>SonarLint</li>
<li>JRebel</li>
<li>Easy Code</li>
<li>Grep Console</li>
<li>Mybatis Log Plugin</li>
</ol>
<h2>vscode插件</h2>
<p><a href="https://vsce.github.io/">https://vsce.github.io/</a></p>
<h2>常用软件</h2>
<ol>
<li><a href="https://www.proxifier.com/">proxifier</a> //激活码 5EZ8G-C3WL5-B56YG-SCXM9-6QZAP</li>
</ol>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[常用网址导航]]></title>
            <link>https://zzfzzf.com/post/6972548384842452993</link>
            <guid>6972548384842452993</guid>
            <pubDate>Sat, 11 Jul 2020 13:14:57 GMT</pubDate>
            <content:encoded><![CDATA[<h1>网址导航</h1>
<h2>技术</h2>
<ul>
<li><a href="https://mp.weixin.qq.com/s/CzfqTIEUsyuKVYe5TN-D-g">构建工具对比</a></li>
<li><a href="https://github.com/facebook/lexical">富文本</a></li>
<li><a href="https://github.com/JakHuang/form-generator">vue表单可视化生成</a></li>
<li><a href="http://www.macrozheng.com/#/README">mall文档</a></li>
<li><a href="https://cp.anyknew.com/">拷贝兔</a></li>
<li><a href="https://www.wenshushu.cn/">文叔叔拷贝</a></li>
<li><a href="https://fe-cool.github.io/news/index.html">前端情报站台</a></li>
<li><a href="https://unbug.github.io/codelf/">命名</a></li>
</ul>
<h2>影视</h2>
<ul>
<li><a href="https://www.netflix.com/browse">netflix</a></li>
<li><a href="https://www.youtube.com/">youtube</a></li>
</ul>
<h2>工具</h2>
<ul>
<li><a href="https://ip.skk.moe/">ip地址检测</a></li>
<li><a href="https://mp3cut.net/cn/">ios手机铃声</a></li>
<li><a href="https://myssl.com/">ssl检测</a></li>
<li><a href="https://letsencrypt.org/zh-cn/">ssl证书</a></li>
<li><a href="https://www.digitalocean.com/community/tools/nginx">nginx配置</a></li>
<li><a href="https://yeasy.gitbook.io/docker_practice/">docker</a></li>
<li><a href="https://uniq.site/zh">名称生成器/取名</a></li>
<li><a href="https://carbon.now.sh/">代码图片生成</a></li>
</ul>
<h2>图片处理</h2>
<table>
<thead>
<tr>
<th>工具</th>
<th>地址</th>
<th>备注</th>
</tr>
</thead>
<tbody><tr>
<td>图片放大</td>
<td><a href="http://waifu2x.udp.jp/index.zh-CN.html">http://waifu2x.udp.jp/index.zh-CN.html</a></td>
<td>免费</td>
</tr>
<tr>
<td>图片放大</td>
<td><a href="https://bigjpg.com">https://bigjpg.com</a></td>
<td></td>
</tr>
<tr>
<td>去水印</td>
<td><a href="https://www.magiceraser.io">https://www.magiceraser.io</a></td>
<td></td>
</tr>
<tr>
<td>去水印</td>
<td><a href="https://img.logosc.cn">https://img.logosc.cn</a></td>
<td>不太推荐</td>
</tr>
<tr>
<td>去水印</td>
<td><a href="https://www.watermarkremover.io">https://www.watermarkremover.io</a></td>
<td></td>
</tr>
<tr>
<td>消除背景</td>
<td><a href="https://www.remove.bg">https://www.remove.bg</a></td>
<td></td>
</tr>
<tr>
<td>消除背景</td>
<td><a href="https://baseline.is/tools/background-remover">https://baseline.is/tools/background-remover</a></td>
<td></td>
</tr>
<tr>
<td>图片压缩</td>
<td><a href="https://squoosh.app">https://squoosh.app</a></td>
<td></td>
</tr>
<tr>
<td>图片压缩</td>
<td><a href="https://zh.recompressor.com">https://zh.recompressor.com</a></td>
<td></td>
</tr>
<tr>
<td>图片压缩</td>
<td><a href="https://zh.pixfix.com">https://zh.pixfix.com</a></td>
<td></td>
</tr>
<tr>
<td>图片压缩</td>
<td><a href="https://www.picdiet.com/">https://www.picdiet.com/</a></td>
<td></td>
</tr>
<tr>
<td>图片压缩</td>
<td><a href="https://tinypng.com/">https://tinypng.com/</a></td>
<td></td>
</tr>
<tr>
<td>文件格式转化</td>
<td><a href="https://convertio.co">https://convertio.co</a></td>
<td></td>
</tr>
<tr>
<td>css渐变可视化</td>
<td><a href="https://cssgradient.io">https://cssgradient.io</a></td>
<td></td>
</tr>
<tr>
<td>动画曲线可视化</td>
<td><a href="https://cubic-bezier.com">https://cubic-bezier.com</a></td>
<td></td>
</tr>
<tr>
<td>异形边框</td>
<td><a href="https://9elements.github.io/">https://9elements.github.io/</a></td>
<td></td>
</tr>
<tr>
<td>可视化阴影调整</td>
<td><a href="https://neumorphism.io/">https://neumorphism.io/</a></td>
<td></td>
</tr>
</tbody></table>
<h2>编程</h2>
<ul>
<li><a href="https://es6.ruanyifeng.com/">es6</a></li>
<li><a href="https://github.com/qishibo/AnotherRedisDesktopManager/">redis工具</a></li>
<li><a href="https://regex101.com/">正则检查</a></li>
<li><a href="https://codepen.io/">在线编程</a></li>
<li><a href="https://codesandbox.io/">在线框架编程</a></li>
<li><a href="https://npm.runkit.com/">在线npm验证</a></li>
<li><a href="https://www.uupoop.com/">在线PS</a></li>
<li><a href="https://developer.mozilla.org/zh-CN/">mdn-api文档</a></li>
<li><a href="https://tableconvert.com/">markdown表格生成</a></li>
</ul>
<h2>java</h2>
<ul>
<li><a href="https://mp.baomidou.com/guide/">mybatisplus</a></li>
</ul>
<h2>logo生成</h2>
<ul>
<li><a href="https://hatchful.shopify.com/">https://hatchful.shopify.com/</a></li>
<li><a href="https://www.namecheap.com/logo-maker/app/">https://www.namecheap.com/logo-maker/app/</a></li>
</ul>
<h2>颜色搭配</h2>
<ul>
<li><a href="https://color.adobe.com/zh/create/color-wheel">配色1</a></li>
<li><a href="https://nipponcolors.com/">配色2</a></li>
<li><a href="https://www.htmlcsscolor.com/">配色3</a></li>
<li><a href="https://colorhunt.co/">配色4</a></li>
<li><a href="https://www.bootcss.com/p/websafecolors/">安全色</a></li>
<li><a href="https://flatuicolors.com/">色彩搭配</a></li>
<li><a href="https://coolors.co/">https://coolors.co/</a></li>
<li><a href="https://nipponcolors.com/">日本色</a></li>
<li><a href="http://zhongguose.com/">中国色</a></li>
</ul>
<h2>网站设计</h2>
<ul>
<li><a href="http://reeoo.com/">http://reeoo.com/</a></li>
<li><a href="http://iconpark.bytedance.com/">字节跳动图标库</a></li>
</ul>
<h2>动画</h2>
<ul>
<li><a href="https://animate.style/">https://animate.style/</a></li>
</ul>
<h2>正则</h2>
<ul>
<li><a href="https://regexper.com/">https://regexper.com/</a></li>
<li><a href="https://www.debuggex.com/">https://www.debuggex.com/</a></li>
</ul>
<h2>壁纸</h2>
<ul>
<li><a href="https://konachan.net/">https://konachan.net/</a></li>
<li><a href="https://wallhaven.cc/">https://wallhaven.cc/</a></li>
<li><a href="https://wall.alphacoders.com/">https://wall.alphacoders.com/</a></li>
<li><a href="https://www.goodfon.com/">https://www.goodfon.com/</a></li>
<li><a href="https://www.wallpapermaiden.com/">https://www.wallpapermaiden.com/</a></li>
</ul>
<h2>持续集成</h2>
<ul>
<li><a href="https://app.netlify.com/">netlify</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/59686072">https://zhuanlan.zhihu.com/p/59686072</a></li>
</ul>
<h2>前端UI库</h2>
<ul>
<li><a href="https://github.com/cuke-ui/cuke-ui">黄瓜UI</a></li>
<li><a href="https://github.com/winyh/zswui">zswui</a></li>
<li>[LANIF-UI](</li>
</ul>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
        <item>
            <title><![CDATA[Jenkins 安装与配置]]></title>
            <link>https://zzfzzf.com/post/6972548384842452992</link>
            <guid>6972548384842452992</guid>
            <pubDate>Sat, 02 May 2020 02:46:09 GMT</pubDate>
            <content:encoded><![CDATA[<h1>准备工作</h1>
<ol>
<li>centos7.9</li>
<li><a href="https://zzfzzf.com/post/1414224254387363841">docker环境</a></li>
</ol>
<h1>Jenkins 安装与配置</h1>
<blockquote>
<p><a href="https://hub.docker.com/r/jenkins/jenkins">jenkins-docker镜像官网</a></p>
</blockquote>
<ol>
<li><p>使用docker安装jenkins</p>
<pre><code class="language-bash">docker run -d -u root -v jenkins_home:/var/jenkins_home -v $(which docker):/usr/bin/docker  -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 --restart=always jenkins/jenkins:lts-jdk11
</code></pre>
</li>
<li><p>获取密码</p>
</li>
</ol>
<pre><code class="language-bash">docker ps
docker exec -u 0 -it container_id /bin/bash
cat /var/jenkins_home/secrets/initialAdminPassword
</code></pre>
<p><img src="https://oss-zzf.zzfzzf.com/cdn/1643290620250xbnxNv.png" alt="1643290620250xbnxNv">
4. 插件安装</p>
<blockquote>
<ol>
<li><code>Generic Webhook Trigger</code></li>
<li><code>Publish over SSH</code></li>
<li><code>nodejs</code></li>
<li><code>ssh</code></li>
<li><code>Maven Integration</code></li>
</ol>
</blockquote>
<pre><code>#Generic Webhook Trigger配置
^(refs/heads/master)_(jello-web)$
$ref_$name
</code></pre>
<blockquote>
<p>jar包进程脚本</p>
</blockquote>
<pre><code class="language-bash">#!/bin/bash
SERVER_NAME=jello
JAR_NAME=jello-0.0.1-SNAPSHOT
echo &quot;查询进程id--&gt;$SERVER_NAME&quot;
PID=`ps -ef | grep &quot;$SERVER_NAME&quot; | grep -v grep | awk &#39;{print $2}&#39;`
echo &quot;得到进程ID：$PID&quot;
echo &quot;结束进程&quot;
for id in $PID
do
    kill -9 $id  
    echo &quot;killed $id&quot;  
done
echo &quot;结束进程完成&quot;

cd /www/wwwroot/api.jello.annyyy.com/
chmod 755 $JAR_NAME.jar
nohup java -jar  $JAR_NAME.jar  &amp;
echo &#39;执行完了构建&#39;
</code></pre>
<pre><code class="language-bash">npm ci
npm run  build
tar -czvf dist.tar.gz \.next env public package.json \.npmrc
</code></pre>
<h2>F&amp;Q</h2>
<ol>
<li>构建springboot不能自动结束</li>
</ol>
<pre><code class="language-bash">nohup java -jar test.jar &gt;start.log 2&gt;&amp;1 &amp;
</code></pre>
<ol start="2">
<li>时间比主机时间慢8小时<blockquote>
<p>一次性设置，重启无效</p>
</blockquote>
</li>
</ol>
<pre><code>System.setProperty(&#39;org.apache.commons.jelly.tags.fmt.timeZone&#39;,&#39;Asia/Shanghai&#39;)
</code></pre>
<blockquote>
<p>永久设置</p>
</blockquote>
<pre><code class="language-bash">docker exec -it -u root container_id bash
cat /etc/timezone
echo  &#39;Asia/Shanghai&#39; &gt; /etc/timezone
</code></pre>
]]></content:encoded>
            <author>feedback@ooxo.cc (zzfn)</author>
        </item>
    </channel>
</rss>