<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[未央の星空]]></title><description><![CDATA[静水流深]]></description><link>https://weiyang.space</link><image><url>https://api.weiyang.space/api/v2/objects/avatar/ytfo7pctw47xxznfv3.png</url><title>未央の星空</title><link>https://weiyang.space</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Tue, 02 Jun 2026 11:30:10 GMT</lastBuildDate><atom:link href="https://weiyang.space/feed" rel="self" type="application/rss+xml"/><pubDate>Tue, 02 Jun 2026 11:30:10 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[Awesome AI Tools]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://weiyang.space/posts/vibe/awesome-ai-tools">https://weiyang.space/posts/vibe/awesome-ai-tools</a></blockquote><div><blockquote><p>精选的 AI 工具参考资源列表，不定时更新</p></blockquote><blockquote>
<p>文中提到的所有工具，评价带有强烈的个人偏好</p></blockquote><blockquote>
<p>本人定位：程序员 + 设计爱好者</p></blockquote>
<h2 id="ide">IDE和代码编辑器</h2><ul><li>🔥 <a href="https://www.cursor.com/">Cursor</a> -- AI代码编辑器，&quot;使用AI编程的最佳方式&quot;。（不管模型怎么变，我这里全有 一站式解决 ai 编程）</li><li>🔥  <a href="https://antigravity.google/">Antigravity</a> --  对比cursor近乎免费的订阅（99$/年，还可以家庭共享），提供了充裕的Gemini模型额度与周限制的Claude模型额度。对比Cursor，Agent 不会给你一堆日志，而是提交可视化成果，比如 UI 调整的对比截图、功能测试的录屏。支持用户直接验收和反馈，极大地缩短了反馈循环。</li><li>🔥 <a href="https://www.trae.ai/">Trae海外版</a> --  Cursor的下位代替。</li><li><a href="https://qoder.com/">Qoder</a> -- 阿里推出的智能编程平台，Quest模式体验不错。repo wiki功能方便理解整个代码库。</li><li><a href="https://copilot.tencent.com/ide">CodeBuddy</a> -- 腾讯出品，AI 一站式工作台：从此用自然语言实现产品、设计、研发全流程。</li><li><a href="https://codeium.com/windsurf">Windsurf Editor by Codeium</a> -- 代理IDE，最开始和 Cursor 并驾齐驱，在失去 Ant 支持/Cursor 获得融资之后，逐步失去竞争力。现在唯一的用处是使用DeepWiki读代码。</li><li><a href="https://zed.dev/">Zed</a> -- 专为与人类和AI高性能协作而设计的代码编辑器。以轻量、快速闻名，在 AI 时代迅速集成 AI 功能，包含ACP 集成，官方版本只支持 Mac。</li><li><a href="https://kiro.dev">Amazon Kiro</a> -- 从原型到生产的AI集成开发环境，Quest模式体验不错，需要 waitlist。</li></ul><h2 id="">插件和扩展</h2><ul><li>🔥 <a href="https://www.augmentcode.com/">Augment Code</a> -- 为专业软件工程师和大型代码库构建的AI编程助手。好用但贵。</li><li>🔥 <a href="https://github.com/features/copilot">GitHub Copilot</a> -- VS Code 官方集成。它提供代码补全、与AI模型聊天和代理编程的代理模式。刚推出时轰动一时，后面逐步被Cursor、CC 等新工具赶超，不过目前该有的功能都集成了，可用。</li><li><a href="https://lingma.aliyun.com/">通义灵码</a> -- 阿里云出品。国内工具使用体验最好的一个，主要是免费。</li><li><a href="https://cline.bot/">Cline</a> -- 可以使用你的CLI和编辑器的AI助手，适用于VS Code。团队非常有趣，Cursor 开始也借鉴了里面的功能特性。</li><li><a href="https://github.com/RooVetGit/Roo-Code">Roo Code</a> -- Cline的分支，具有额外功能/增强。</li></ul><h2 id="">本地应用</h2><ul><li>🔥 <a href="https://www.cherry-ai.com/">Cherry Studio</a> -- AI对话客户端，开源，新功能跟进很快，对话可以保存到 Obsidian、Notion，免费。</li><li>🔥 <a href="https://chatwise.app/">ChatWise</a> -- Tauri构建的AI对话客户端，快而且更好看，$29买断。</li><li><a href="https://ima.qq.com/">IMA</a> -- 腾讯出品，用于私人知识库构建，有知识库广场但需要自己筛选，可能是目前最好的国内知识库软件。注意保护好自己的注意力。</li></ul><h2 id="">命令行工具🔥🔥🔥</h2><ul><li>🔥🔥 <a href="https://github.com/anthropics/claude-code">anthropics/claude-code</a> -- 理解你的代码库、自动化任务、解释代码和管理git的编程代理，全部通过自然语言。CC强在工具，官网服务国内几乎无法使用，稳定使用需要拼车或者中转站。CC 平价方案：允许通过配置 API和 URL 来使用其他模型（GLM4.7/Kimi 等)</li><li>🔥🔥 <a href="https://github.com/openai/codex">OpenAI Codex CLI</a> -- OpenAI的轻量级编程代理，在终端中运行。 Codex强在模型，GPT-5-Codex high相当稳定，比 Sonnet 4.5 相比更克制，指哪打哪，很少自由发挥。我习惯每次的代码改动交由codex review一遍之后再提交。</li><li>🔥 <a href="https://github.com/google-gemini/gemini-cli">Gemini CLI</a> -- 谷歌开源的AI代理，将 Gemini 的强大功能直接带入您的终端。Gemini 3 Pro 具有优秀的前端审美，但是逻辑与工具调用能力较弱。Code Agent 能力较前两个较弱，可以用作日常翻译、搜索、子 Agent。</li></ul><h2 id="">基于浏览器的工具</h2><ul><li>🔥 <a href="https://v0.dev/chat">v0 by Vercel</a> -- 构建NextJS前端的助手,使用 react 和 tailwind 快速构建原型。</li><li>🔥 <a href="https://www.perplexity.ai">perplexity</a> -- 强大的 AI 搜索工具。</li><li><a href="https://bolt.new/">Bolt.new</a> -- 提示、运行、编辑和部署全栈Web和移动应用。</li><li><a href="https://lovable.dev/">Lovable</a> -- &quot;几秒钟从想法到应用。Lovable是你的超人全栈工程师&quot;。</li><li>🔥 <a href="https://www.lovart.ai/">Lovart</a> -- Lovart自动完成设计流程,从概念到图像、视频、3D等，使用体验最好的设计 Agent。</li><li><a href="https://manus.im">Manus</a> -- 一个通用 Agent。包括幻灯片、图像、视频生成、问题研究等。</li><li><a href="https://www.capcut.com">CapCut</a> -- 人人都能运用，采用 AI 技术的视频编辑器。</li><li><a href="https://www.figma.com">Figma</a> -- AI 时代下最方便的设计工具，得益于Figma生态积极拥抱 AI，有官方 MCP 服务与Agent。</li></ul><h2 id="ai">AI编程任务管理</h2><ul><li><a href="https://github.com/eyaltoledano/claude-task-master">Claude Task Master</a> -- 可以集成到Cursor、Lovable、Windsurf、Roo等工具中的AI驱动任务管理系统，提供了 MCP 版本。</li><li>🔥 <a href="https://github.com/Fission-AI/OpenSpec">OpenSpec</a> -- 一个轻量级的、基于规范的编码代理和 CLI 框架。通过将项目规范、架构决策、功能需求以结构化文档记录，OpenSpec 保证了上下文一致性、变更可追溯，也让 AI Agent 在任何时刻都能准确执行。</li></ul><h2 id="ai">AI编程文档</h2><ul><li>🔥 <a href="https://deepwiki.com/">DeepWiki</a> -- Devin“一个涵盖所有 GitHub 代码库的免费百科全书”。将任何公共 GitHub 代码库的 URL 替换为“ https:// deepwiki. com/org/repo ” 即可获得类似维基百科的精准库描述。阅读开源仓库代码时必用。</li><li><a href="https://zread.ai/">Zread</a> -- 智谱出品，类似DeepWiki，AI整理了Github仓库的内容，可以快速上手、了解架构。相比DeepWiki，优点是原生就有中英双语，对国人更友好。</li></ul><h2 id="mcp">MCP</h2><ul><li>🔥 <a href="https://context7.com">Context7</a> -- 让 AI 实时获取网络上的最新文档，适合更新老项目或迁移框架。</li><li>🔥 <a href="https://docs.exa.ai/reference/exa-mcp">Exa</a> -- 同 context7 专为编程相关场景使用的工具，支持文档的语义搜索。</li><li>🔥 <a href="https://github.com/ChromeDevTools/chrome-devtools-mcp">Chrome DevTools MCP</a> -- 允许代理控制和检查实时 Chrome 浏览器，简单来说，Chrome DevTools MCP 让你的 AI 智能体更擅长网页开发中的调试与优化。</li></ul><h2 id="">新闻和社交媒体</h2><blockquote><p>关注 AI 行业动态，精选信息源。</p></blockquote>
<ul><li>🔥 <a href="https://x.com/dotey">宝玉老师 X</a> -- AI 布道者</li><li>🔥 <a href="https://www.waytoagi.com/zh">WaytoAGI</a> -- 国内最好的 AI 知识库，每日更新</li><li><a href="https://app.folo.is/share/lists/68649150114432000">泛科技AI周刊合集</a> -- 基于 follow 的订阅合集</li></ul></div><p style="text-align:right"><a href="https://weiyang.space/posts/vibe/awesome-ai-tools#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://weiyang.space/posts/vibe/awesome-ai-tools</link><guid isPermaLink="true">https://weiyang.space/posts/vibe/awesome-ai-tools</guid><dc:creator><![CDATA[未央]]></dc:creator><pubDate>Sat, 04 Oct 2025 06:36:31 GMT</pubDate></item><item><title><![CDATA[Vercel界面设计指南]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://weiyang.space/posts/design/rauno-web-interface-design-guide-vercel-best-practices">https://weiyang.space/posts/design/rauno-web-interface-design-guide-vercel-best-practices</a></blockquote><div><blockquote><p>Rauno 两年前在 GitHub 上分享的 Web 界面指南，现在结合了 Vercel 的工程师与设计师的经验，发布了全新的版本。它提供了一系列关于如何设计高质量、易用且性能良好的 Web 界面的建议和最佳实践。</p><p>最佳实践，你需要通过在项目中维护 AGENTS.md 文件来确保生成的界面符合这些指南。 <a href="https://vercel.com/design/guidelines">原文</a></p></blockquote>
<h2 id="">交互</h2><ol start="1"><li><p><strong>键盘可访问所有功能。</strong> 所有操作都应可用键盘完成，并遵循 WAI-ARIA Authoring Patterns。</p></li><li><p><strong>清晰的焦点指示。</strong> 每个可聚焦元素都要有可见的焦点样式。优先使用 <code>:focus-visible</code> 而非 <code>:focus</code>，避免干扰鼠标用户；对控件分组使用 <code>:focus-within</code>。</p></li><li><p><strong>管理焦点。</strong> 需要时使用焦点陷阱，并按 WAI-ARIA 规范移动与归还焦点。</p></li><li><p><strong>视觉目标与点击目标一致。</strong> 如视觉元素小于 24px，应将可点击区域扩展到 ≥ 24px；移动端最小 44px。</p></li><li><p><strong>移动端输入框尺寸。</strong> <code>&lt;input&gt;</code> 在移动端字体 ≥ 16px，避免 iOS Safari 聚焦时的自动缩放/平移；或设置 <code>&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1, maximum-scale=1&quot; /&gt;</code>。</p></li><li><p><strong>尊重缩放功能。</strong> 不要禁用浏览器缩放。</p></li><li><p><strong>水合安全的输入框。</strong> 水合（hydration）后输入框不应丢失焦点或当前值。</p></li><li><p><strong>不要阻止粘贴。</strong> 不要在 <code>&lt;input&gt;</code> 或 <code>&lt;textarea&gt;</code> 禁用粘贴。</p></li><li><p><strong>按钮的加载态。</strong> 显示加载指示器，同时保留原按钮文案。</p></li><li><p><strong>加载态的最短显示时长。</strong> 使用旋转器/骨架屏时，加入短暂显示延迟（约 150–300ms）和最短可见时间（约 300–500ms），避免快速返回时闪烁。React 的 <code>&lt;Suspense&gt;</code> 已内置类似机制。</p></li><li><p><strong>URL 即状态。</strong> 将状态持久化到 URL，确保分享、刷新、前进/后退都能正确工作（如使用 nuqs 管理查询参数）。</p></li><li><p><strong>乐观更新。</strong> 成功概率高时先行更新 UI，并与服务端结果对账；失败时给出错误提示并回滚或提供撤销。</p></li><li><p><strong>省略号表示后续输入。</strong> 需要进一步输入的菜单项（如“重命名…”）用省略号结尾。</p></li><li><p><strong>确认破坏性操作。</strong> 需要确认，或提供带缓冲时间的撤销。</p></li><li><p><strong>避免控件双击触发缩放。</strong> 设置 <code>touch-action: manipulation</code>。</p></li><li><p><strong>点击高亮按设计控制。</strong> 设置 <code>-webkit-tap-highlight-color</code>。</p></li><li><p><strong>交互要有容错性。</strong> 宽松的点击目标、清晰的可用性暗示、可预测的行为，降低误操作（如利用“预测锥”概念）。</p></li><li><p><strong>工具提示时机。</strong> 组内第一个工具提示延迟显示；同组后续提示不延迟。</p></li><li><p><strong>过度滚动处理。</strong> 在模态/抽屉等场景有意设置 <code>overscroll-behavior: contain</code>。</p></li><li><p><strong>滚动位置持久化。</strong> 前进/后退应恢复上次滚动位置。</p></li><li><p><strong>合适的自动聚焦。</strong> 桌面端如只有一个主要输入项可自动聚焦；移动端尽量少自动聚焦，以免键盘弹出导致布局抖动。</p></li><li><p><strong>无“死区”。</strong> 看起来可交互的区域就应可交互，不要让用户猜哪里能点。</p></li><li><p><strong>一切都能深链接。</strong> 过滤器、标签页、分页、展开面板等（凡是用到 useState 的地方）都应可通过 URL 定位。</p></li><li><p><strong>干净的拖拽体验。</strong> 拖拽时禁用文本选择，并对不应交互的元素应用 <code>inert</code>，避免选择/悬停与拖拽竞争。</p></li><li><p><strong>链接就用链接。</strong> 导航使用 <code>&lt;a&gt;</code> 或 <code>&lt;Link&gt;</code>，保证浏览器默认行为（Cmd/Ctrl + 点击、中键、右键新标签）可用；不要用 <code>&lt;button&gt;</code> 或 <code>&lt;div&gt;</code> 代替导航。</p></li><li><p><strong>宣告异步更新。</strong> 提示与内联校验使用礼貌级别的 <code>aria-live</code>。</p></li><li><p><strong>本地化键盘快捷键。</strong> 适配非 QWERTY 布局；显示平台特定符号。</p></li></ol><h2 id="">动画</h2><ol start="1"><li><p><strong>尊重 <code>prefers-reduced-motion</code>。</strong> 提供减少动效的替代方案。</p></li><li><p><strong>实现优先级。</strong> 能用 CSS 就不用主线程 JS。</p></li><li><p><strong>优先级：</strong> CSS &gt; Web Animations API &gt; JS 动画库（如 motion）。</p></li><li><p><strong>合成器友好。</strong> 优先动画 <code>transform</code>、<code>opacity</code> 等 GPU 加速属性，避免触发布局/重绘（如 <code>width</code>、<code>height</code>、<code>top</code>、<code>left</code>）。</p></li><li><p><strong>确有其必要。</strong> 只在有明确因果或能带来有意的愉悦时使用动画。</p></li><li><p><strong>合适的缓动。</strong> 根据变化的尺寸、距离、触发方式选择缓动曲线。</p></li><li><p><strong>可中断。</strong> 用户输入应能取消动画。</p></li><li><p><strong>输入驱动。</strong> 避免自动播放；以用户操作为触发。</p></li><li><p><strong>正确的变换原点。</strong> 让运动从“物理上合理”的位置出发。</p></li><li><p><strong>不要用 <code>transition: all</code>。</strong> 明确列出要过渡的属性（通常是 <code>opacity</code>、<code>transform</code>）。<code>all</code> 容易让影响布局的属性参与动画而卡顿。</p></li><li><p><strong>跨浏览器的 SVG 变换。</strong> 将 CSS 变换/动画作用在 <code>&lt;g&gt;</code> 包裹上，并设置 <code>transform-box: fill-box; transform-origin: center;</code>。Safari 曾在 SVG 的 transform-origin 上有兼容问题，分组可避免原点计算错误。</p></li></ol><h2 id="">布局</h2><ol start="1"><li><p><strong>光学对齐。</strong> 当观感优先于几何准确时，可做 ±1px 的微调。</p></li><li><p><strong>有意对齐。</strong> 每个元素都要有对齐依据：网格、基线、边缘或光学中心，不要“随便放”。</p></li><li><p><strong>文本与图标的均衡。</strong> 并排时通过粗细、大小、间距或颜色让它们不打架。比如细线图标放在中等字重文本旁可能需要加粗描边。</p></li><li><p><strong>响应式覆盖。</strong> 在手机、笔记本与超宽屏上验证；模拟超宽屏可把页面缩放到 50% 观察。</p></li><li><p><strong>尊重安全区域。</strong> 使用安全区域变量，照顾刘海与系统内边距。</p></li><li><p><strong>没有多余滚动条。</strong> 只渲染必要的滚动；修复溢出避免冗余滚动条。可在 macOS 开启“始终显示滚动条”来模拟 Windows 体验。</p></li><li><p><strong>让浏览器做布局。</strong> 优先用 flex/grid/内在尺寸，避免 JS 测量；减少抖动，让 CSS 处理流动、换行与对齐。</p></li></ol><h2 id="">内容</h2><ol start="1"><li><p><strong>内联帮助优先。</strong> 优先用内联说明；工具提示作为最后手段。</p></li><li><p><strong>稳定的骨架屏。</strong> 骨架应尽量镜像最终内容，避免布局位移。</p></li><li><p><strong>准确的页面标题。</strong> <code>&lt;title&gt;</code> 要反映当前上下文。</p></li><li><p><strong>没有死胡同。</strong> 每个界面都提供下一步或返回路径。</p></li><li><p><strong>设计所有状态。</strong> 空、稀疏、密集与错误态都要覆盖。</p></li><li><p><strong>排版引号。</strong> 优先用弯引号（“ ”），而非直引号（&quot; &quot;).</p></li><li><p><strong>避免孤行寡行。</strong> 控制断行与对齐，让段落边缘自然整洁。</p></li><li><p><strong>比较用等宽数字。</strong> 使用 <code>font-variant-numeric: tabular-nums</code> 或等宽字体（如 Geist Mono）。</p></li><li><p><strong>冗余信号。</strong> 不要只靠颜色表达状态，需同时给出文本标签。</p></li><li><p><strong>图标要有标签。</strong> 为视障用户提供等价文字。</p></li><li><p><strong>没有“仅展示”模式。</strong> 视觉上可省略标签，但必须保留可访问的名称/标签供辅助技术使用。</p></li><li><p><strong>使用省略号字符。</strong> 用 <code>…</code> 而不是三个点 <code>...</code>。</p></li><li><p><strong>锚定标题。</strong> 章节链接时为标题设置合适的 <code>scroll-margin-top</code>。</p></li><li><p><strong>适应用户生成内容。</strong> 布局能兼容很短、一般、很长的内容。</p></li><li><p><strong>区域感知格式。</strong> 按用户区域格式化日期、时间、数字、分隔符与货币。</p></li><li><p><strong>语言优先于位置。</strong> 通过 Accept-Language 与 <code>navigator.languages</code> 检测语言；不要用 IP/GPS 判断语言。</p></li><li><p><strong>可访问的内容。</strong> 准确命名（<code>aria-label</code>）、隐藏装饰元素（<code>aria-hidden</code>），并在可访问性树中核验。</p></li><li><p><strong>仅图标按钮需要命名。</strong> 提供清晰的 <code>aria-label</code>。</p></li><li><p><strong>语义优先于 ARIA。</strong> 优先用原生元素（<code>button</code>、<code>a</code>、<code>label</code>、<code>table</code>）而非堆砌 <code>aria-*</code>。</p></li><li><p><strong>标题与跳过链接。</strong> 合理分层的 <code>&lt;h1–h6&gt;</code> 与“跳转到内容”的跳过链接。</p></li><li><p><strong>从徽标直达品牌资产。</strong> 支持在导航徽标上右键快速访问品牌资源。</p></li><li><p><strong>用不换行空格粘连术语。</strong> 用 <code>&amp;nbsp;</code> 把单位、快捷键、名称连在一起：10 MB → 10 MB，⌘ + K → ⌘ + K，Vercel SDK → Vercel SDK。无空格粘连可用 <code>&amp;#x2060;</code>。</p></li></ol><h2 id="">表单</h2><ol start="1"><li><p><strong>回车即可提交。</strong> 文本输入聚焦时，回车提交表单。</p></li><li><p><strong>文本区域的回车规则。</strong> 在 <code>&lt;textarea&gt;</code> 中：⌘/⌃ + 回车提交，回车换行。</p></li><li><p><strong>处处有标签。</strong> 每个控件都有 <code>&lt;label&gt;</code>，或能与标签关联以供辅助技术使用。</p></li><li><p><strong>标签可激活。</strong> 点击 <code>&lt;label&gt;</code> 应聚焦到关联控件。</p></li><li><p><strong>提交约束。</strong> 提交前按钮保持可用；发起请求后禁用按钮、显示加载指示，并包含幂等键。</p></li><li><p><strong>不要阻止输入。</strong> 即便字段只接受数字，也允许自由输入并给出校验反馈；屏蔽按键会让用户摸不着头脑。</p></li><li><p><strong>不要预先禁用提交。</strong> 允许提交不完整表单，以便显示校验反馈。</p></li><li><p><strong>控件无“死区”。</strong> 复选框/单选与其标签应共享一个宽松的点击目标。</p></li><li><p><strong>错误就近显示。</strong> 把错误显示在字段旁；提交时把焦点移到第一个错误上。</p></li><li><p><strong>自动完成与 name。</strong> 设置正确的 <code>autocomplete</code> 与有意义的 <code>name</code>，启用自动填充。</p></li><li><p><strong>有选择地关闭拼写检查。</strong> 对邮箱、代码、用户名等禁用拼写检查。</p></li><li><p><strong>正确的类型与输入模式。</strong> 选择合适的 <code>type</code> 与 <code>inputmode</code>，获得更好的键盘与校验体验。</p></li><li><p><strong>占位符表达“空”。</strong> 用省略号结尾。</p></li><li><p><strong>占位符示例值。</strong> 占位符应是示例或模式，如 +1 (123) 456-7890、sk-012345679…。</p></li><li><p><strong>未保存更改。</strong> 可能丢失数据时，在导航前警告。</p></li><li><p><strong>密码管理器与 2FA。</strong> 确保兼容，并允许粘贴一次性验证码。</p></li><li><p><strong>避免误触发密码管理器。</strong> 非认证字段（如“搜索”）避免使用保留名称（如 password），用 <code>autocomplete=&quot;off&quot;</code> 或 OTP 专用的 <code>autocomplete=&quot;one-time-code&quot;</code>。</p></li><li><p><strong>文本替换/扩展处理。</strong> 某些输入法会加尾随空格；提交前应修剪，避免莫名其妙的错误。</p></li><li><p><strong>Windows 的 <code>&lt;select&gt;</code> 背景。</strong> 原生 <code>&lt;select&gt;</code> 要明确设置 <code>background-color</code> 与 <code>color</code>，避免 Windows 深色模式下对比不够。</p></li></ol><h2 id="">性能</h2><ol start="1"><li><p><strong>设备/浏览器矩阵。</strong> 记得测试 iOS 低电量模式与 macOS Safari。</p></li><li><p><strong>可靠的测量。</strong> 禁用会加开销或改变运行时行为的浏览器扩展。</p></li><li><p><strong>跟踪重渲染。</strong> 尽量减少并加速重渲染；用 React DevTools 或 React Scan。</p></li><li><p><strong>分析时节流。</strong> 进行 CPU 与网络节流测试。</p></li><li><p><strong>减少布局工作。</strong> 读/写合批，避免不必要的回流/重绘。</p></li><li><p><strong>网络时延预算。</strong> <code>POST/PATCH/DELETE</code> 争取 500ms 内完成。</p></li><li><p><strong>按键开销。</strong> 优先使用非受控输入；让受控输入的更新闭环足够低开销。</p></li><li><p><strong>大列表。</strong> 虚拟化（如 virtua）或用 <code>content-visibility: auto</code>。</p></li><li><p><strong>审慎预加载。</strong> 只预加载首屏图；其余延迟加载。</p></li><li><p><strong>图像不引发 CLS。</strong> 明确指定图像尺寸并预留空间。</p></li><li><p><strong>预连接外部源。</strong> 对资产/CDN 域使用 <code>&lt;link rel=&quot;preconnect&quot;&gt;</code>（需要时加 <code>crossorigin</code>），减少 DNS/TLS 往返。</p></li><li><p><strong>预加载字体。</strong> 关键文本预加载字体，避免闪烁与位移。</p></li><li><p><strong>字体子集化。</strong> 通过 <code>unicode-range</code> 仅投递所需码点/脚本（可变轴也仅保留必要），缩小体积。</p></li><li><p><strong>避免主线程重活。</strong> 超长任务移到 Web Worker，避免阻塞交互。</p></li></ol><h2 id="">设计</h2><ol start="1"><li><p><strong>分层阴影。</strong> 至少两层：环境光 + 直射光。</p></li><li><p><strong>边框清晰。</strong> 边框与阴影结合；半透明边框能增强边缘清晰度。</p></li><li><p><strong>圆角套圆角。</strong> 子元素圆角 ≤ 父元素，且保持同心，曲线对齐。</p></li><li><p><strong>色调一致。</strong> 在非中性背景上，让边框/阴影/文字向同一色调轻微偏移。</p></li><li><p><strong>可达的图表。</strong> 使用色盲友好的配色。</p></li><li><p><strong>最低对比。</strong> 比起 WCAG 2，更推荐 APCA 的感知对比评估。</p></li><li><p><strong>交互增强对比。</strong> <code>:hover</code>、<code>:active</code>、<code>:focus</code> 的对比应高于静止态。</p></li><li><p><strong>浏览器 UI 与背景一致。</strong> 用 <code>&lt;meta name=&quot;theme-color&quot; content=&quot;#000000&quot;&gt;</code> 让浏览器主题色贴合页面背景。</p></li><li><p><strong>设置合适的配色方案。</strong> 深色主题下在 <code>&lt;html&gt;</code> 标注 <code>color-scheme: dark</code>，让滚动条等系统 UI 对比合适。</p></li><li><p><strong>文本抗锯齿与变换。</strong> 文本缩放会影响平滑；优先给外层容器做动画而非文本节点。如有残影可用 <code>translateZ(0)</code> 或 <code>will-change: transform</code> 升层。</p></li><li><p><strong>避免渐变色带。</strong> 某些颜色/显示器会出现色带；可用纹理或噪声遮罩改善。</p></li></ol><h2 id="vercel">针对Vercel</h2><p>这些偏好反映了 Vercel 的品牌与产品取向，并非通用指南。</p><br/><br/>
<h3 id="">文案写作</h3><ol start="1"><li><strong>用主动语态。</strong></li></ol><p>不要说“CLI 将被安装”，而说“安装 CLI”。</p><br/>
<ol start="2"><li><p><strong>标题与按钮用标题大小写（芝加哥风格）。</strong> 营销页使用句子式大小写。</p></li><li><p><strong>清晰、简洁。</strong> 用最少的词表达清楚。</p></li><li><p><strong>尽量用 &amp; 而不是 and。</strong></p></li><li><p><strong>面向行动的语言。</strong></p></li></ol><p>不要说“你需要 CLI…”，而说“安装 CLI…”。</p><br/>
<ol start="6"><li><p><strong>名词一致。</strong> 尽量减少独特术语的引入。</p></li><li><p><strong>使用第二人称。</strong> 避免第一人称。</p></li><li><p><strong>占位符一致。</strong></p></li></ol><p>字符串用：YOUR<em>API</em>TOKEN_HERE；数字用：0123456789。</p><br/>
<ol start="9"><li><strong>计数用阿拉伯数字。</strong></li></ol><p>不要写“八个部署”，而写“8 个部署”。</p><br/>
<ol start="10"><li><p><strong>货币格式一致。</strong> 在同一上下文中要么都 0 位小数，要么都 2 位，不要混用。</p></li><li><p><strong>数字与单位间留空格。</strong></p></li></ol><p>不要写 10MB，应写 10 MB（推荐不换行空格：10 MB）。</p><ol start="12"><li><strong>默认使用积极语气。</strong> 即便是错误，也以鼓励、可解决的口吻描述。</li></ol><p>不要说“你的部署失败了”，而说“出了点问题——请重试或联系支持”。</p><br/>
<ol start="13"><li><strong>错误要指向出路。</strong> 不只说错了什么，还要告诉如何修复。</li></ol><p>不要说“无效的 API 密钥”，而说“你的 API 密钥不正确或已过期。请在账户设置中生成新密钥。”文案与按钮/链接要帮助用户采取明确行动。</p><br/>
<ol start="14"><li><strong>避免歧义。</strong> 标签清晰具体。</li></ol><p>不要用“继续”，而用“保存 API 密钥”。</p>
<h2 id="">与代理集成</h2><p>AGENTS.md 文件为代理提供指导。将此 AGENTS.md 与你的代理一起使用，以确保你的界面遵循这些指南。我们建议审核所有生成的界面。</p><p><a href="下载链接">下载 AGENTS.md</a></p></div><p style="text-align:right"><a href="https://weiyang.space/posts/design/rauno-web-interface-design-guide-vercel-best-practices#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://weiyang.space/posts/design/rauno-web-interface-design-guide-vercel-best-practices</link><guid isPermaLink="true">https://weiyang.space/posts/design/rauno-web-interface-design-guide-vercel-best-practices</guid><dc:creator><![CDATA[未央]]></dc:creator><pubDate>Sun, 28 Sep 2025 08:59:39 GMT</pubDate></item><item><title><![CDATA[Vibe Coding]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://weiyang.space/posts/vibe/vibe-coding-ai-programming-future">https://weiyang.space/posts/vibe/vibe-coding-ai-programming-future</a></blockquote><div><blockquote><p>「当你抱怨 ChatGPT 鬼话连篇满嘴跑火车的时候，这可能有点像你看到一只猴子在沙滩上用石头写下1+1=3。它确实算错了，但这不是重点。它有一天会算对的。」</p></blockquote><blockquote>
<p>两年后你再访这片沙滩，那只猴子还在，但已经非复吴下阿蒙。此刻它正在充满困惑地摆弄一台袖珍电子计算器。电子计算器太小巧，显然是另一条文明路线下千锤百炼的产品，它的手指太粗太笨拙，还驾驭不了这么精致的工具。于是你充满信心——但也不无恐惧地——等待着它找到开关看懂按钮的那一刻的到来。</p></blockquote>
<p>如果你是程序员但还没听说过 vibe coding，那你已经落伍了。</p><p>这个词是上个月 Andrej Karpathy 在一篇半自嘲的推文里创造的，现在已经成了标准用语。没有人能精确定义它，但所有熟悉 AI 辅助编程体验的人都多少知道它大概在说什么。一些人对此嗤之以鼻，一些人认为这就是未来，还有更多人勉强让自己适应它。</p><p>Vibe coding 创造了一种模糊的实践。用 Andrej Karpathy 自己的话说：「对 AI 的建议我总是接受，不再审阅差异。当我收到错误消息时，我只是复制粘贴它们而不加评论，通常这样就可以解决问题。代码超出了我通常的理解范围。有时它无法修复错误，所以我只是绕过它或要求随机更改，直到它自行消失。」一方面它犹如神助，让你有一种第一次挥舞魔杖的幻觉。另一方面它写了新的忘了旧的，不断重构又原地打转，好像永远在解决问题但永远创造出更多新的问题，并且面对 bug 采取一种振振有词地姿态对你 gaslighting。你面对着层出不穷的工具甚至不知道自己该认真考虑哪个，心知肚明可能下个月就又有了新的「最佳实践」，养成任何肌肉记忆都是一种浪费，而所谓新的最佳实践只不过是用更快的速度产出更隐蔽的 bug 而已。</p><p>从技术上来说你可能觉得困难主要在于今天的大语言模型的上下文窗口还不够大，分层长期记忆机制还不够健全，或者别的什么理论上会在未来半年到一年里得到解决的瓶颈。但实际上，vibe coding 打破的是你作为一个程序员的自我认知：你一开始以为自己只是在为了效率做妥协，渐渐地你发现自已陷入在一重又一重建立在浮沙之上的迷宫里精疲力尽，最后你已经忘了效率是什么。</p><p>从某种意义上说，今天的 vibe coding 有点像一两年前的 AI 绘画，第一眼很对，放大后细节都是错的，到处是六根手指的手。问题在于，绘画远比编程更宽容——毕竟真的存在印象派这种绘画风格——编程难道不理应是非黑即白的吗？</p><p>但并不是，正是在这一点上现实开始扭曲起来。你很快就注意到 vibe coding 的「正确性」就像薛定谔的猫一样无法精确观察，你可能每天抱怨 LLM 的注意力窗口太小，而事实是你自己的注意力窗口更小，面对它不费吹灰之力生成的海量代码的冲刷很快就头晕目眩，放弃了审查和控制的执念。你试图借助类似于 .cursorrules 这样的规范来指导 AI，但这就像是野马辔头上的一根想象中的缰绳，你既不确定这些规则是否完备，也无从知道它们是否会被真的遵守。你以为这些原则相当于法律，其实它们只是孔子家语，而社会的运转既依赖于它们的尊崇神圣，也依赖于它们的晦涩模糊。你渐渐不再 care 你的代码是否正确，反正随时在改。Dario Amodei 说未来 3 到 6 个月内，90% 的代码将由 AI 编写，12 个月内几乎所有代码都可能由 AI 生成。在这个即将到来的世界里，六根手指的手应接不暇地出现，然后消失，你开始接受暂时 work 就是一切，变动不居才是事物的恒常。</p><p>某种意义上说这是这个时代的本质。当国际新闻和洋葱新闻开始无法分辨，当你发现所有号称追求真实的人最终追求的只不过是逃避认知失调，你所创造（或者你自以为你所创造）的一切也不会摆脱同样的命运。八年前我写过这样一段话：</p><p>「躲在一个气泡里的个体可以假定岁月静好，一切宛如昨日幻乐，但这往往是悲剧的起源。他看到的只是一个复杂屈折的世界在更低维度上的投影，一个对狂飙突进的历史湍流的简笔画般的描摹，一个更容易被媒体所采纳和记忆的粗糙叙事，一座层移倒悬重重折射下的海市蜃楼。而真实——如果真实仍然有意义的话——则掉落在幽暗深邃的维度的缝隙里。在那里，一幅粗粝斑驳扭曲异质的图景，会让一个在不经意的一瞥之间扭过头去的观察者惊骇和战栗不已。」</p><p>那时我以为世界刚刚开始崩塌。后来我理解了崩塌的不是世界，而是我自己的天真想象。 Vibe 不是真实的某种投射，而是它的实质。一开始你以为世界是一张完美的幕布，然后你在幕布上发现了一两条恼人的裂痕，接着你发现裂痕越来越多，无处不在，直到最后你意识到不断蔓延和生长的裂痕才是你真正生活的地方。It’s not just vibe coding, it’s vibe living.</p><hr/><p><strong> 本文转载自网络 </strong></p></div><p style="text-align:right"><a href="https://weiyang.space/posts/vibe/vibe-coding-ai-programming-future#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://weiyang.space/posts/vibe/vibe-coding-ai-programming-future</link><guid isPermaLink="true">https://weiyang.space/posts/vibe/vibe-coding-ai-programming-future</guid><dc:creator><![CDATA[未央]]></dc:creator><pubDate>Sat, 29 Mar 2025 08:01:30 GMT</pubDate></item><item><title><![CDATA[深入 JS：面向对象]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://weiyang.space/posts/archieve/deep-js-objects">https://weiyang.space/posts/archieve/deep-js-objects</a></blockquote><div><blockquote><p>ECMA-262 将对象定义为一组属性的无序集合。严格来说，这意味着对象就是一组没有特定顺序的值。对象的每个属性或方法都由一个名称来标识，这个名称映射到一个值。正因为如此（以及其他还未讨论的原因），可以把 ECMAScript 的对象想象成一张散列表，其中的内容就是一组名/值对，值可以是数据或者函数</p></blockquote>
<p>对象是JavaScript中一个非常重要的概念，这是因为对象可以将多个相关联的数据封装到一起，更好的描述一个事物</p><h4 id="">如何创建一个对象？</h4><p>早期使用创建对象的方式最多的是<strong>使用Object类</strong>，并且使用<strong>new关键字</strong>来创建一个对象：</p><p>这是因为早期很多JavaScript开发者是从Java过来的，它们也更习惯于Java中通过new的方式创建一个对象， 后来很多开发者为了方便起见，都是直接通过<strong>字面量的形式来创建对象</strong>：这种形式看起来更加的简洁，并且对象和属性之间的内聚性也更强，所以这种方式后来就流行了起来</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">//创建一个对象，对某个人进行抽象?(描述)
//方式1：通过new Object()创建
var obj1 = new Object()//以前的人喜欢这样创建对象
obj1.name = &quot;AA&quot;
obj1.age = 20
//上面的方式是对obj1当作一个构造函数，然后通过new关键字来执行函数，这个时候也会创建出来对象
//方式2：字面量形式
var obj = {
    name:&quot;BB&quot;,
    age:20,
    eating:function(){
        console.log(this.name+&quot;在吃辣条~&quot;)
    }
}//这种创建对象方式叫做对象的字面量，现在更多是这种
</code></pre><h3 id="">属性操作</h3><blockquote><p>ECMA-262 使用一些内部特性来描述属性的特征。这些特性是由为 JavaScript 实现引擎的规范定义的。因此，开发者不能在JavaScript 中直接访问这些特性。为了将某个特性标识为内部特性，规范会用两个中括号把特性的名称括起来，比如[[Enumerable]]</p></blockquote>
<p>如果我们想要对一个属性进行比较精准的操作控制，通过属性描述符可以精准的添加或修改对象的属性。</p><p>属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改</p>
<pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">//此方法 应当直接在 Object 构造器对象上调用此方法，而不是在任意一个 Object 类型的实例上调用。
Object.defineProperty(obj, prop, descriptor) 
// obj 要定义属性的对象,prop要定义或修改的属性的名称或 Symbol, descriptor要定义或修改的属性描述符
</code></pre><p>属性描述符有两种主要形式： *数据描述符 和 存取描述符  *一个描述符只能是两者其一</p><ol start="1"><li>数据属性
<ul><li>[[Configurable]]：表示属性是否可以通过delete删除属性，是否可以修改它的特性，或者是否可以将它修改为存取属性描述符  当我们直接在一个对象上定义某个属性时，这个属性的[[Configurable]]为true，当我们通过属性描述符定义一个属性时，这个属性的[[Configurable]]默认为false</li><li>[[Enumerable]]：表示属性是否可以通过for-in或者Object.key()返回该属性;当我们直接在一个对象上定义某个属性时，这个属性的为true，当我们通过属性描述符定义一个属性时，默认为false</li><li>[[Writable]]：表示是否可以修改属性的值；当我们直接在一个对象上定义某个属性时，这个属性的[[Writable]]为true 当我们通过属性描述符定义一个属性的时候，这个属性的[[Writable]]为false</li><li>[[value]]：属性的value值，读取属性时会返回该值，修改属性时，会对其进行修改；默认情况下这个值是undefined</li></ul></li><li>访问器属性
<ul><li>[[Configurable]]：表示属性是否可以通过delete删除属性，是否可以修改它的特性，或者是否可以将它修改为存取属性 描述符, 和数据属性描述符是一致的</li><li>[[Enumerable]]：表示属性是否可以通过for-in或者Object.keys()返回该属性，和数据属性描述符是一致的</li><li>[[get]]：获取属性时会执行的函数，默认为undefined</li><li>[[set]]：设置属性时会执行的函数，默认为undefined  <strong>(vue2响应式原理)</strong></li></ul></li></ol><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">//使用场景：1.隐藏某一个私有属性，不希望直接被外界使用和赋值 2.如果我们希望截获某一个属性，它访问和设置值的过程时，我们也会使用存储属性描述符
var obj = {
    name:&quot;A&quot;,
    age:18,
    _address:&quot;济南市&quot;//_开头表示私有的，不希望被人看到(程序猿的习惯)。我们就通过get来使用address代替掉_address。别人就通过address调用我们，而不是使用_address调用
}

//当我们使用get、set，不使用value和writable的时候，叫做存取属性描述符
Object.defineProperty(obj,&quot;address&quot;,{
    //很多配置
    enumerable:true,
    configurable:true,
    get:function(){
        foo()//我们希望获取值的时候提醒我们一下的时候就会这么干
        return this._address//将_address这个属性隐藏起来，给他套上了address这个马甲
    },
    set:function(value){
        bar()//这样就截获了它获取值的过程，这是Vue2响应式的原理
        //当我们如下对obj.address进行赋值的时候，值就通过形参传递了进来，我们在这里进行赋值的操作
        this._address = value
    }
})
console.log(obj)//{ name: &#x27;xiaoyu&#x27;, age: 18, address: [Getter/Setter] }
console.log(obj.address);//济南市，get拿到了值
obj.address = &quot;上海市&quot;//我们使用的是address，而不是_address了哦，注意这里的变化
console.log(obj.address);//上海市

function foo(){
    console.log(&quot;获取了一次address的值&quot;)
}
function bar(){
    console.log(&quot;设置了一次address的值&quot;)
}

// 在开发中，想快速对某一个属性定义对应的get和set，也可以这样写
var obj = {
    _age:20,
    set age(value){
        this._age = value
    },
    get age(){
        return this._age
    }
}
</code></pre><h3 id="-objectxx">常用对象方法 Object.xx()</h3><p>[文档](<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object">Object - JavaScript | MDN (mozilla.org)</a>)</p><ul><li>获取对象的属性描述符：<code>getOwnPropertyDescriptorgetOwnPropertyDescriptors</code></li><li>禁止对象扩展新属性：<code>preventExtensions</code>给一个对象添加新的属性会失败(在严格模式下会报错)</li><li>密封对象，不允许配置和删除属性：<code>seal</code>实际是调用<code>preventExtensions</code>并且将现有属性的configurable：<code>false</code></li><li>冻结对象，不允许修改现有属性：<code>freeze</code>实际上是调用<code>seal</code>并且将现有属性的<code>writable：false</code></li><li>合并对象， <code>Object.assign(target, ...sources)</code>实际上对每个源对象执行的是浅复制。如果多个源对象都有相同的属性，则使用最后一个复制的值</li><li>对象属性存在检测，<code>Object.hasOwn()</code>  如果指定的属性是该对象的直接属性返回 true，即使属性值是 null 或 undefined</li><li>属性遍历 <code>Object.keys()</code>  返回一个表示给定对象的所有可枚举属性的字符串数组</li><li>值遍历 <code>Object.values()</code>  返回一个给定对象自身的所有可枚举属性值的数组</li></ul><h3 id="">创建多个对象的方案</h3><h6 id="">工厂函数</h6><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">// 工厂函数通常使用对象字面量语法创建对象，然后返回这个新对象
function createPerson(name, age) {
  return {
    name: name,
    age: age,
    sayHello: function() {
      console.log(`Hello, my name is ${this.name} and I&#x27;m ${this.age} years old.`);
    }
  };
}

const person2 = createPerson(&#x27;Bob&#x27;, 30);
person2.sayHello(); // Hello, my name is Bob and I&#x27;m 30 years old.
</code></pre><h6 id="">构造函数</h6><blockquote><p>如果一个函数被使用new操作符调用了，那么会发生什么</p></blockquote>
<ol start="1"><li>在内存中创建一个新的对象（空对象）</li><li>这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性</li><li>构造函数内部的this，会指向创建出来的新对象</li><li>执行函数的内部代码（函数体代码）</li><li>如果构造函数没有返回非空对象，则返回创建出来的新对象</li></ol><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayName = function() { 
     console.log(this.name); 
 }; 
}
const person1 = new Person(&quot;Alice&quot;, 25);
</code></pre><p>如果你将person1和person2打印出来，会发现person1的开头多了一个Person类型(实际是constructor的属性)，这是因为 person1 继承自构造函数的原型对象，并且可以访问构造函数中定义的属性和方法</p><blockquote><p>一般来说，构造函数实在跟普通函数没有区别，所以约定构造函数通常以大写字母开头</p></blockquote>
<h6 id="">构造函数的缺点</h6><p>其定义的方法会在每个实例上都创建一遍，person1 和 person2 都有名为 sayName()的方法，但这两个方法不是同一个 Function 实例，要解决这个问题，可以把函数定义转移到构造函数外部。</p><p>这样虽然解决了相同逻辑的函数重复定义的问题，但
全局作用域也因此被搞乱了，因为那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法，
那么就要在全局作用域中定义多个函数。这会导致自定义类型引用的代码不能很好地聚集一起。</p>
<h3 id="">原型</h3><blockquote><p>当我们对一个东西定义在[[]]里面的时候，证明这个是ECMA标准把它叫做这个名字，prototype翻译过来就是原型的意思</p></blockquote>
<h5 id="">对象原型</h5><p>JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]]，这个特殊的对象可以指向另外一个对象。</p><blockquote><p>那么这个对象有什么用呢？</p></blockquote>
<p>当我们通过引用对象的属性key来获取一个value时，它会触发 [[get]]的操作；
这个操作会首先检查该属性是否有对应的属性，如果有的话就使用它；
如果对象中没有该属性，那么会访问对象[[prototype]]内置属性指向的对象上的属性</p><p>那么如果通过字面量直接创建一个对象，这个对象也会有这样的属性吗？如果有，应该如何获取这个属性呢？</p>
<ul><li>通过对象的 __proto__ 属性可以获取到（但是这个是早期浏览器自己添加的，存在一定的兼容性问题）；</li><li>通过 <code>Object.getPrototypeOf</code> 方法可以获取到；ES5之后提供的方法，不是浏览器提供的</li></ul><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">//我们每个对象中都有一个[[prototype]]，这个属性可以称之为对象的原型(隐式原型)，这里很重要
//称呼为隐式原型的原因：1.平时看不到2.以后也不会去改他3.也不会去使用他。我们只会利用他的底层原理来使用他
var obj = { name: &#x27;未央&#x27;}
console.log(obj) //node打印看不到隐藏起来的[[prototype]]的，这个需要到浏览器的控制台去看一下(也就是功能是浏览器提供的)
</code></pre><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">// 当我们从一个对象中获取某一个属性时，它会触发[[get]]操作
// 1.在当前对象中去查找对应的属性，如果找到就直接使用，如果没有找到，会沿着原型去查找，也就是
// var obj = {name:&quot;未央&quot;,__proto__} 先从前面开始找，没找到再从__proto__(原型)找

var obj = {name:&quot;未央&quot;}
obj.__proto__.age = 18
console.log(obj.age)//18
//我们在上面是找不到obj的age属性的，age除了obj.age = 18这种方式之外，直接赋值在原型中，也能够找到
//为什么要这么麻烦，要放到原型中，不直接放到对象里面呢？这是为了方便我们后续实现继承
</code></pre><h5 id="">函数原型</h5><p>所有的函数都有一个prototype的属性</p><p>上文所讲的，当new一个函数时的第二步：</p><blockquote><p><em>这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性</em></p></blockquote>
<p>也就意味着我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向Person.prototype：</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">var p1 = new Person()
var p2 = new Person()
//函数的执行体创建p1的对象，还有p2的对象，这个是我们一开始就通过var p1 = new Person()还有var p2 = new Person()达成的，总结就是p1跟p2指向了构造函数，构造函数的隐式原型指向了函数的显示原型。这也就造就了下面两个true相等的原因
console.log(p1.__proto__ === Person.prototype);//true
console.log(p2.__proto__ === Person.prototype);//true
</code></pre><blockquote><p>修改p1的隐式原型，一样会作用到p2身上，为什么？</p></blockquote>
<p>new操作符导致的：这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性</p><p>那么也就意味着我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向Person.prototype</p>
<p>无论何时，只要创建一个函数，就会按照特定的规则为这个函数创建一个 prototype 属性（指向原型对象）。默认情况下，所有原型对象自动获得一个名为 constructor 的属性，指回与之关联的构造函数。</p><p>对前面的例子而言，Person.prototype.constructor 指向 Person。</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">//正确写法
function Person(name,age,sex,address){
    this.name = name,
    this.age = age,
    this.sex = sex,
    this.address = address
    // this.eating = function(){
    //     console.log(this.name+&quot;今天吃烤地瓜了&quot;);
    // }
}
//由于函数如果放在Person里面，那每次都会在构造函数中创建出一个新的，但是里面的内容其实都是一样的，所以最好的方式就是放在原型中，需要的时候顺着原型链找过去
Person.prototype.eating = function(){
    console.log(this.name+&quot;今天吃烤地瓜了&quot;);
}
var p1 = new Person(&quot;AA&quot;,18,&quot;男&quot;,&quot;山东&quot;)
var p2 = new Person(&quot;BB&quot;,24,&quot;男&quot;,&quot;乔家大院&quot;)
console.log(p1.name);//AA    不会发生覆盖的问题了
</code></pre><h3 id="">继承</h3><p>面向对象有三大特性：封装、继承、多态</p><ul><li>封装：我们前面将属性和方法封装到一个类中，可以称之为封装的过程</li><li>继承：继承是面向对象中非常重要的，不仅仅可以减少重复代码的数量，也是多态前提（纯面向对象中）</li><li>多态：不同的对象在执行时表现出不同的形态</li></ul><h5 id="">原型链继承</h5><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">//实现继承的效果，关键在第四五步骤，顺序不能调整
//定义父类构造函数
function Person(){
    this.name = &quot;AA&quot;
    this.friends = []
}
//往父类原型上添加内容
Person.prototype.eating = function(){
    console.log(this.name +&quot;eating~&quot;);
}

//定义子类构造函数
function Student(){
    this.sno = 111
}
//4.创建父类对象，并且作为子类的原型对象(关键)
var p = new Person()
Student.prototype = p//这一步赋值的操作之后，Student原来的原型对象就不再被指向，会在下一轮中被垃圾回收掉。我个人认为这个p更像是链接子类跟父类的中转站，但是它是会替代掉子类原来的原型的

//5.在子类原型上添加内容 这一步不能够跟第四步换是很好理解的，因为第四步要替换掉我们的原型，如果第五步先的话，会绑定到要被替换掉的原型身上，然后跟着一起被替换掉。所以不能够这么做
Student.prototype.studying = function(){
    console.log(this.name + &quot;studying&quot;);
}

var stu= new Student()
console.log(stu.name);
//console.log(stu.eating());这里不需要使用console.log()，因为stu.eating()自身已经会调用一次了
stu.eating()
</code></pre><p>原型链继承的弊端</p><ul><li>我们通过直接打印对象是看不到这个属性的；</li><li>这个属性会被多个对象共享，如果这个对象是一个引用类型，那么就会造成问题；</li><li>不能给Person传递参数，因为这个对象是一次性创建的（没办法定制化）</li></ul><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">function Person(){
    this.name = &quot;小余&quot;
    this.friends = []
}
Person.prototype.eating = function(){
    console.log(this.name +&quot;eating~&quot;);
}
function Student(){
    this.sno = 111
}
var p = new Person()
Student.prototype = p

Student.prototype.studying = function(){
    console.log(this.name + &quot;studying&quot;);
}

var stu= new Student()
console.log(stu.name);
stu.eating()

//原型链弊端演示1
console.log(stu);//Student { sno: 111 }  内容不止一个sno
//原型链弊端演示2
//stu1跟stu2之间应该是相互独立的，因为stu1多了一个名叫小满的朋友，不代表stu2也能够获得同样的朋友
//2.创建出来两个stu对象
var stu1 = new Student()
var stu2 = new Student()
//那问题就来了，我们接下来要对stu1进行操作，但是同时影响到了stu2。因为我们friends是一个引用对象:数组，会造成问题。通常stu1.friends这种操作应该将内容放到自己的对象里面，也就是之前说的那个var moni = {}，是影响不到原型上的(直接修改对象上的属性，是给本对象添加新属性的)，但是当我们使用引用对象的时候，我们知道引用对象其实是获取引用，修改引用里面的值
//直接修改的例子：直接修改对象上的属性，是给本对象添加新属性的
stu.name = &quot;超级满&quot;
console.log(stu2.name)//小余，对stu1的修改影响不到stu2的
//引用的例子
stu1.friends.push(&quot;BB&quot;)
console.log(stu1.friends);//[ &#x27;BB&#x27; ]
console.log(stu2.friends);//[ &#x27;BB&#x27; ]
//对friends写法的区别
stu1.friends = [&quot;AA&quot;]//这种写法是直接往friends的数组里面添加内容，数据是在stu1自己的对象里面的
stu1.friends.push(&quot;BB&quot;)//stu1.friends是[[get]]操作，会顺着原型链一层层往上找，找到原型上面的friends，然后往里面塞入了一个&quot;BB&quot;
console.log(stu2.friends) // []
</code></pre><h5 id="">组合借用继承</h5><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">//解决原型链继承弊端第三点，不能给函数传递参数的问题
// 父类: 公共属性和方法
function Person(name, age, friends) {//公共的数据传递到父类中，然后子类会内部调用父类
  // this = stu
  this.name = name
  this.age = age
  this.friends = friends
}

Person.prototype.eating = function() {
  console.log(this.name + &quot; eating~&quot;)
}

// 子类: 特有属性和方法
function Student(name, age, friends, sno) {
  Person.call(this, name, age, friends)//这里的this是new Student时创建出来的对象，通过call调用这三个参数，就是一个普通的函数调用，就会去父类中调用函数了(子类型构造函数的内部调用父类型构造函数)
  // this.name = name 不能够这么传递，这样就把处理逻辑放到子类里面了，公共的应该抽到父类中去
  // this.age = age
  // this.friends = friends
  this.sno = 111
}

var p = new Person()
Student.prototype = p

Student.prototype.studying = function() {
  console.log(this.name + &quot; studying~&quot;)
}
</code></pre><p>组合继承是JavaScript最常用的继承模式之一：</p>
<p>如果你理解到这里, 点到为止, 那么组合来实现继承只能说问题不大；
但是它依然不是很完美，但是基本已经没有问题了；(不成问题的问题, 基本一词基本可用, 但基本不用)</p><p>但是它依然不是很完美，但是基本已经没有问题了；(不成问题的问题, 基本一词基本可用, 但基本不用)</p><blockquote><p>组合继承存在什么问题呢?</p></blockquote>
<p>组合继承最大的问题就是无论在什么情况下，都会调用两次父类构造函数。一次在创建子类原型的时候，另一次在子类构造函数内部(也就是每次创建子类实例的时候)。</p><p>所有的子类实例事实上会拥有两份父类的属性，一份在当前的实例自己里面(也就是person本身的)，另一份在子类对应的原型对象中(也就是 person.__proto__里面)。当然，这两份属性我们无需担心访问出现问题，因为默认一定是访问实例本身这一部分的</p>
<h5 id="">寄生组合式继承</h5><p>Object.create() 方法用于创建一个新对象，使用现有的对象来作为新创建对象的原型（prototype）。</p>
<pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">function Person(name,age,friends){
    this.name = name
    this.age = age
    this.friends = friends
}

Person.prototype.learn = function(){
    console.log(&quot;重新学习JavaScript&quot;);
}
Person.prototype.runAway = function(){
    console.log(&quot;跑路了&quot;);
}

function Student(name,age,friends,sno,score){
    Person.call(this,name,age,friends)//借用父级的这些属性
    this.sno = sno
    this.score = score
}

Student.prototype.eating = function(){
    console.log(&quot;eating &quot;);
}

// 获取到一份父类型的原型对象中的属性和方法
Student.prototype = Object.create(Person.prototype)

var stu = new Student(&quot;AA&quot;,20,[&quot;BB&quot;],111,100)
console.log(stu);
</code></pre></div><p style="text-align:right"><a href="https://weiyang.space/posts/archieve/deep-js-objects#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://weiyang.space/posts/archieve/deep-js-objects</link><guid isPermaLink="true">https://weiyang.space/posts/archieve/deep-js-objects</guid><dc:creator><![CDATA[未央]]></dc:creator><pubDate>Fri, 15 Apr 2022 06:02:20 GMT</pubDate></item><item><title><![CDATA[JS 事件调度]]></title><description><![CDATA[<link rel="preload" as="image" href="https://api.weiyang.space/api/v2/objects/icon/3kaz6b1vhqscchsv84.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://weiyang.space/posts/archieve/js-event-scheduling">https://weiyang.space/posts/archieve/js-event-scheduling</a></blockquote><div><h2 id="">前言</h2><p>我们都知道，javascript从诞生之日起就是一门单线程的非阻塞的脚本语言。</p><p>单线程是必要的，也是javascript这门语言的基石，原因之一在其最初也是最主要的执行环境——浏览器中，我们需要进行各种各样的dom操作（如果允许多线程，一个线程对某一个dom进行添加属性操作，另一个线程对该线程进行删除操作，显然是不可行的）。</p><p>单线程在保证了执行顺序的同时也限制了javascript的效率，<code>Web Worker</code> 是 HTML5 标准的一部分，这一规范定义了一套 API，允许我们在 js 主线程之外开辟新的 Worker 线程，并将一段 js 脚本运行其中，它赋予了开发者利用 js 操作多线程的能力。多用来进行复杂运算，操作 DOM 的行为是不可行的。</p><h2 id="event-loop">Event Loop</h2><p><img src="https://api.weiyang.space/api/v2/objects/icon/3kaz6b1vhqscchsv84.png" alt="js事件循环" height="401" width="800"/></p><h3 id="">执行栈</h3><blockquote><p>所有的JavaScript代码在运行时都是在上下文中进行的。那么，上下文是什么呢？简单来说，它就是当前JavaScript代码被解析和执行时所在环境的抽象概念。</p></blockquote>
<ul><li>全局上下文：最外层的上下文，也是最基础的上下文。根据ECMAScript实现的宿主环境，表示全局上下文的对象可能不一样。在浏览器中，它就是我们常说的window对象。一个程序中只能存在一个全局上下文。</li><li>函数上下文：函数被调用时会被创建，一个程序中可以存在多个函数上下文。</li><li>eval函数上下文：略</li></ul><p>执行栈（也叫调用栈或上下文栈），用于存储在代码执行期间创建的所有上下文.它是LIFO（Last in, First out，后进先出）结构。</p><p>当函数运行时，创建一个栈帧运来记录函数运行时的相关信息，执行时创建，return后销毁。ECMAScript程序的执行流就是通过这个执行栈进行控制的。</p><p>js的另一大特点是非阻塞，那么当一个异步代码（如发送ajax请求数据）执行后会如何呢？</p><p>实现这一点的关键在于下面要说的这项机制——任务队列（Task Queue）。</p><h3 id="">任务队列</h3><p>js引擎遇到一个异步事件后并不会一直等待其返回结果，而是会将这个事件挂起，继续执行执行栈中的其他任务。当一个异步事件返回结果后，js会将这个事件加入与当前执行栈不同的另一个队列，我们称之为<strong>任务队列</strong>。</p><p>被放入事件队列不会立刻执行其回调，而是等待当前<strong>执行栈中的所有任务都执行完毕</strong>， 主线程处于闲置状态时，主线程会去查找事件队列是否有任务。如果有，那么主线程会从中取出排在第一位的事件，并把<strong>这个事件对应的回调</strong>放入执行栈中，然后执行其中的同步代码...，如此反复，这样就形成了一个无限的循环。</p><p>Js 中，异步会有两类任务队列：宏任务队列（macro tasks）和微任务队列（microtasks）。</p><p>宏任务队列可以有多个，微任务队列只有一个。</p><ul><li>宏任务：script（全局任务）, setTimeout, setInterval, setImmediate, I/O, UI rendering.</li><li>微任务：process.nextTick （node.js中进程相关的对象）, Promise, Object.observer, MutationObserver。</li></ul><blockquote><p>Promise 比较奇葩，promise.then/cath /finally的回调属于微，回调之前的代码体中的属于宏</p></blockquote>
<p>在挂起任务时，JS 引擎会将所有任务按照类别分到这两个队列中，首先在 macrotask 的队列（这个队列就是 task queue）中取出第一个任务，执行完毕后取出 microtask 队列中的所有任务顺序执行；之后再取 macrotask 任务，周而复始，直至两个队列的任务都取完。</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">console.log(&#x27;script start&#x27;);

setTimeout(function () {
  console.log(&#x27;setTimeout&#x27;);
}, 0);

Promise.resolve()
  .then(function () {
    console.log(&#x27;promise1&#x27;);
  })
  .then(function () {
    console.log(&#x27;promise2&#x27;);
  });

console.log(&#x27;script end&#x27;);
// script start
// script end
// promise1
// promise2
// setTimeout
</code></pre><blockquote><p>promise1是微任务，setTimeout是宏任务，为什么看起来是微任务先执行？</p></blockquote>
<p>网上也确有微任务优先于宏任务的说法，在我看来是不准确的  why？</p><p><strong>script其实也存在于task中</strong>，当JS stack执行<code>script</code>时，我们的task是存在<code>run script</code>的全局任务的，也就是说setTimeout callback会被推入task，等待下一次取宏任务时再执行。当script执行完后，会取本次微任务队列中的promise执行。</p><p><a href="https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/?utm_source=html5weekly#what-are-some-browsers-doing-differently">Tasks, microtasks, queues and schedules - JakeArchibald.com</a></p><p>---</p><h2 id="">浏览器架构</h2><p>现代浏览器都是多进程架构设计。Chrome：1个浏览器主进程、1个GPU进程、多个渲染进程和多个插件进程。</p><table><thead><tr><th>进程</th><th>控制</th></tr></thead><tbody><tr><td>浏览器</td><td>控制应用中的 “Chrome” 部分，包括地址栏，书签，回退与前进按钮。以及处理 web 浏览器不可见的特权部分，如网络请求与文件访问。</td></tr><tr><td>渲染</td><td>控制标签页内网站展示。</td></tr><tr><td>插件</td><td>控制站点使用的任意插件，如 Flash。</td></tr><tr><td>GPU</td><td>处理独立于其它进程的 GPU 任务。GPU 被分成不同进程，因为 GPU 处理来自多个不同应用的请求并绘制在相同表面。</td></tr></tbody></table><p>通过上面的了解，我们知道在Chrome浏览器中，每次新开一个标签页，都会创建一个新的渲染进程，而渲染进程在标签页中又是扮演着重要的角色，负责标签页内发生的所有事情。其核心工作就是将HTML、CSS和JavaScript转换为用户与之交互的网页。</p><p>渲染进程包括多个线程工作：</p><ul><li>主线程：运行JavaScript、DOM、CSS、样式布局计算</li><li>工作线程：运行Web Worker，Service Worker</li><li>合成线程：将图层分成图块，并发送绘制命令发送给浏览器进程（生成页面，显示在显示器上）</li><li>光栅线程：将图块转换成位图并发送到 GPU</li></ul><p>而在主线程解析HTML时，遇到<script></script>标记时，就会暂停HTML的解析，开始加载、解析并执行JavaScript代码。这样就造成了HTML解析的阻塞，而JavaScript代码的执行又是为什么会阻塞HTML解析呢？这是因为JavaScript代码里可以通过类似<code>document.write()</code>的方法改写文档，这样就会导致HTML文档整体结构的变化。</p><h2 id="">小结</h2><p>event loop 运行机制:</p><ol start="1"><li>在执行栈中执行一个宏任务。</li><li>执行过程中遇到微任务，将微任务添加到微任务队列中。</li><li>当前宏任务执行完毕，立即执行微任务队列中的任务。</li><li>当前微任务队列中的任务执行完毕，检查渲染，GUI线程接管渲染。</li><li>渲染完毕后，js线程接管，开启下一次事件循环，执行下一次宏任务（事件队列中取）。</li></ol></div><p style="text-align:right"><a href="https://weiyang.space/posts/archieve/js-event-scheduling#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://weiyang.space/posts/archieve/js-event-scheduling</link><guid isPermaLink="true">https://weiyang.space/posts/archieve/js-event-scheduling</guid><dc:creator><![CDATA[未央]]></dc:creator><pubDate>Thu, 10 Mar 2022 02:37:53 GMT</pubDate></item></channel></rss>