<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="https://v-blog.yyshino.top/rss.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <atom:link href="https://v-blog.yyshino.top/rss.xml" rel="self" type="application/rss+xml"/>
    <title>yyshino blogs</title>
    <link>https://v-blog.yyshino.top/</link>
    <description>欢迎</description>
    <language>zh-CN</language>
    <pubDate>Tue, 10 Feb 2026 01:28:51 GMT</pubDate>
    <lastBuildDate>Tue, 10 Feb 2026 01:28:51 GMT</lastBuildDate>
    <generator>vuepress-plugin-feed2</generator>
    <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
    <category>游戏引擎</category>
    <category>优化</category>
    <category>FrontEnd</category>
    <category>AfterEnd</category>
    <category>other</category>
    <category>设计模式</category>
    <category>面经</category>
    <category>算法</category>
    <category>Go</category>
    <category>图形学</category>
    <category>浏览器</category>
    <category>使用指南</category>
    <category>Computer</category>
    <category>CSS</category>
    <category>JS</category>
    <item>
      <title>Demo</title>
      <link>https://v-blog.yyshino.top/posts/Godot/Demo.html</link>
      <guid>https://v-blog.yyshino.top/posts/Godot/Demo.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Demo</source>
      <description>Demo</description>
      <category>游戏引擎</category>
      <pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 必要元素</h1>
<ul>
<li>现实：创造一个世界</li>
<li>成长：生物具有成长特性
<ul>
<li>达尔文进化论：</li>
<li>成长速度快，但是不能丢失细节：</li>
<li></li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>搜索接口调研</title>
      <link>https://v-blog.yyshino.top/posts/Spring/%E6%90%9C%E7%B4%A2%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%A0%94.html</link>
      <guid>https://v-blog.yyshino.top/posts/Spring/%E6%90%9C%E7%B4%A2%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%A0%94.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">搜索接口调研</source>
      <description>搜索接口调研</description>
      <category>优化</category>
      <pubDate>Sun, 02 Mar 2025 22:33:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 一、背景</h1>
<p>背景：老板让我优化一个用户搜索接口，要求是用户搜索请假交接人时优先展示同部门的用户其次是上级和下级部门的用户最后是其他部门的用户。</p>
<h1> 二、优化方案</h1>
<ol>
<li>数据库查询优化。目前的公司内部的平台都是比较通用的查询，一些没有用的字段，也查询了。这里我去找前端沟通确定他们那边需要用到哪些字段，开发一个专用接口去进行操作。</li>
<li>借鉴Elasticsearch的<code>IndexSorting</code>技术，优化搜索性能。</li>
<li>并行调用与异步处理
<ul>
<li>使用<code>CompletableFuture</code>实现并行调用，提高接口响应速度。
示例代码：<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
</ul>
</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>Godot 入门</title>
      <link>https://v-blog.yyshino.top/posts/Godot/Godot%20%E5%85%A5%E9%97%A8.html</link>
      <guid>https://v-blog.yyshino.top/posts/Godot/Godot%20%E5%85%A5%E9%97%A8.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Godot 入门</source>
      <description>Godot 入门</description>
      <category>游戏引擎</category>
      <pubDate>Sun, 02 Mar 2025 22:18:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 入门推荐</h1>
<ul>
<li><strong><a href="https://docs.godotengine.org/zh-cn/4.x/index.html" target="_blank" rel="noopener noreferrer">godot docs</a></strong> ：godot文档</li>
<li><strong><a href="https://github.com/GDQuest/learn-gdscript" target="_blank" rel="noopener noreferrer">learn-gdscript</a></strong>：快速入门 gdscript</li>
<li><strong><a href="https://www.bilibili.com/video/BV14Y411h7Po/?spm_id_from=333.1387.favlist.content.click&amp;vd_source=fb3505db9b87542728213f28843a6d74" target="_blank" rel="noopener noreferrer"># godot 4.x 教程 100集</a></strong> ：B站视频教程，本人之后打算学习</li>
<li><strong><a href="https://www.bilibili.com/video/BV1ytrpY7Emm/?spm_id_from=333.1387.homepage.video_card.click" target="_blank" rel="noopener noreferrer"># 2025年，独立游戏开发者必备的16款软件！</a></strong>：<a href="https://space.bilibili.com/335835274" target="_blank" rel="noopener noreferrer">秦无邪OvO</a>老师，本人大学期间学习过他的Unity教程，这个视频课主要介绍他使用的游戏开发工具</li>
</ul>
<h2> Godot 常用快捷键</h2>
<h1> 原则</h1>
<ul>
<li>节点通信时的经验法则
<ul>
<li>父子节点：<code>Call down ，signal up</code>  往下用调用，往上用信号
<ul>
<li>父节点应该调用子节点的函数，反之则不然</li>
<li>子节点应该用信号给父节点</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1> Godot Shader</h1>
<h2> 第一个GDShader-摇晃Shader</h2>
<ul>
<li>gdshader</li>
<li>在需要让树摇晃的时候，通过脚本动态设置uniform参数 shake_intensity <code>material.set_shader_parameter("shake_intensity", 1.0)</code></li>
<li>短暂等待后<code>await get_tree().create_timer(1.0).timeout</code></li>
<li>让树停止摇晃，<code>material.set_shader_parameter("shake_intensity", 0.0)</code></li>
</ul>
<div class="language-gdshader line-numbers-mode" data-ext="gdshader"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>如何搭建自己的个人博客</title>
      <link>https://v-blog.yyshino.top/posts/Plugin/%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2.html</link>
      <guid>https://v-blog.yyshino.top/posts/Plugin/%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">如何搭建自己的个人博客</source>
      <description>template</description>
      <category>FrontEnd</category>
      <pubDate>Fri, 28 Feb 2025 16:10:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 一、方案汇总</h1>
<table>
<thead>
<tr>
<th>方案</th>
<th>平台</th>
<th>成本</th>
<th>优缺点</th>
</tr>
</thead>
<tbody>
<tr>
<td>方案一：社区平台</td>
<td>- 飞书<br>    <br>- 语雀<br>    <br>- CSDN/博客园<br>    <br>- 其他技术社区</td>
<td>经济上：0<br><br>技术上：无</td>
<td>优点：<br><br>- 方便，0成本<br>    <br>- 背靠社区<br>    <br>- 有后台管理<br>    <br><br>缺点：<br><br>- 比较难自定义页面</td>
</tr>
<tr>
<td>方案二：Github Pages/Vercel + Hexo/Vuepress</td>
<td>- Github Pages<br>    <br>- Github + Vercel<br>    <br>- Gitee Pages（Pro关闭了）</td>
<td>经济上：0<br><br>技术上：需要了解前端知识</td>
<td>优点：<br><br>- 自定义能力强<br>    <br>- 主题多<br>    <br><br>缺点：<br><br>- 这类博客一般是纯静态网页，一般没有后台或数据统计<br>    <br>- 需要一定前端知识</td>
</tr>
<tr>
<td>方案三：云服务器 + Wordpress</td>
<td>- 云服务器 + Wordpress</td>
<td>经济上：需要一台云服务器（0~100/月，新用户/学生优惠）<br><br>技术上：无，了解前端/后端更好</td>
<td>优点：<br><br>- 主题多<br>    <br>- 有后台管理<br>    <br><br>缺点：<br><br>- 经济上有一定成本</td>
</tr>
<tr>
<td>方案四：微信公众号以及其他各类公众号</td>
<td>- 申请公众号</td>
<td>经济上：0<br><br>技术上：无</td>
<td>优点：<br><br>- 方便，0成本<br>    <br>- 背靠社区<br>    <br>- 有后台管理<br>    <br><br>缺点：<br><br>- 比较难自定义页面</td>
</tr>
</tbody>
</table>
<p>方案一：社区平台、方案四：微信公众号以及其他各类公众号这里不多介绍，上手较为简单，主要是输出好的文章和运营自己的账号；方案三只做简单介绍，因为本人也用的较少</p>
<p>这里主要介绍方案二：</p>
<h1> 二、方案二：Github Pages/Vercel + Hexo/Vuepress</h1>
<h2> 1. 准备环境</h2>
<ul>
<li>Nodejs（推荐一手NVM）</li>
<li>Git</li>
</ul>
<h2> 2. 找喜欢的主题</h2>
<table>
<thead>
<tr>
<th>Hexo主题推荐</th>
<th>Vuepress主题推荐</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank" rel="noopener noreferrer">GitHub - blinkfox/hexo-theme-matery: A beautiful hexo blog theme with material design and responsive</a></td>
<td><a href="https://github.com/vuepress-theme-hope/vuepress-theme-hope" target="_blank" rel="noopener noreferrer">vuepress-theme-hope A vuepress theme with tons of features✨</a></td>
</tr>
<tr>
<td><a href="https://github.com/gitalk/gitalk" target="_blank" rel="noopener noreferrer">GitHub - gitalk/gitalk: Gitalk is a modern comment component based on Github Issue and Preact.</a></td>
<td><a href="https://github.com/xugaoyi/vuepress-theme-vdoing" target="_blank" rel="noopener noreferrer">vuepress-theme-vdoing 🚀一款简洁高效的VuePress知识管理&amp;博客(blog)主题</a></td>
</tr>
<tr>
<td>更多请查看<a href="https://github.com/search?q=hexo%20theme&amp;type=repositories" target="_blank" rel="noopener noreferrer">hexo theme</a></td>
<td>更多请查看<a href="https://github.com/search?q=vuepress+theme&amp;type=repositories" target="_blank" rel="noopener noreferrer">vuepress theme</a></td>
</tr>
</tbody>
</table>
<h2> 3. 初始化项目并上传Github</h2>
<p>这里以<a href="https://github.com/vuepress-theme-hope/vuepress-theme-hope" target="_blank" rel="noopener noreferrer">vuepress-theme-hope</a> 主题为例，也推荐大家使用。官方文档<a href="https://theme-hope.vuejs.press/zh/get-started/" target="_blank" rel="noopener noreferrer">快速上手</a>和<a href="https://stackblitz.com/edit/vuepress-theme-hope-bbv7fc?file=blog%2Fzh%2Fdemo%2Fpage.md" target="_blank" rel="noopener noreferrer">线上Demo</a></p>
<p>一个基本的项目结构如下:</p>
<div class="language-Plaintext line-numbers-mode" data-ext="Plaintext"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 4. 部署 Deploy</h2>
<p>官方有介绍如何部署到Github Pages- <a href="https://theme-hope.vuejs.press/zh/get-started/deploy.html" target="_blank" rel="noopener noreferrer">Deploy Github Pages</a></p>
<p>我这里补充如何部署到Vercel</p>
<h3> 4.1. 如何部署到Vercel</h3>
<ul>
<li>登录Vercelhttps://vercel.com/，Github授权</li>
<li>Vercel添加项目</li>
</ul>
<p>![[Pasted image 20250228155556.png]]</p>
<ul>
<li>选择自己的博客存储库，点击import</li>
</ul>
<p>![[Pasted image 20250228155627.png]]</p>
<ul>
<li>点击部署Deploy等待即可</li>
</ul>
<p>![[Pasted image 20250228155636.png]]</p>
<ul>
<li>部署好后会有一个默认的域名，如果需要自定义域名可以去购买一个域名然后解析即可（.top的域名我之前购买是3年/27）</li>
</ul>
<p>![[Pasted image 20250228155657.png]]</p>
<h2> 5. 成果</h2>
<p>https://v-blog.yyshino.top/</p>
<p>![[Pasted image 20250228155715.png]]
![[Pasted image 20250228155722.png]]
![[Pasted image 20250228155729.png]]
![[Pasted image 20250228155737.png]]</p>
<h1> 三、方案三 云服务器 + Wordpress</h1>
<p>我之前是通过</p>
<p><a href="https://blog.naibabiji.com/an-zhuang-wordpress" target="_blank" rel="noopener noreferrer">WordPress教程，0基础学会WordPress建站（2024/7月更新） – 奶爸建站笔记</a></p>
<h1> 四、参考文章</h1>
<ul>
<li>
<p><a href="https://www.bilibili.com/read/cv12633102/" target="_blank" rel="noopener noreferrer">2021最全hexo搭建博客+matery美化+使用（保姆级教程）</a></p>
</li>
<li>
<p><a href="https://blog.csdn.net/cungudafa/article/details/106278206" target="_blank" rel="noopener noreferrer">hexo（matery）背景、滚动条优化+增加点击跳评论_首页+色-CSDN博客</a></p>
</li>
<li>
<p><a href="https://docs.github.com/zh/pages" target="_blank" rel="noopener noreferrer">GitHub Pages 文档 - GitHub 文档</a></p>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Spring 事务</title>
      <link>https://v-blog.yyshino.top/posts/Spring/Spring%20%E4%BA%8B%E5%8A%A1.html</link>
      <guid>https://v-blog.yyshino.top/posts/Spring/Spring%20%E4%BA%8B%E5%8A%A1.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Spring 事务</source>
      <description>Spring 事务的工作原理，以及失效场景</description>
      <category>AfterEnd</category>
      <pubDate>Fri, 28 Feb 2025 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 1. Spring 事务的工作原理</h1>
<p>Spring 使用两种代理机制来管理事务：</p>
<ul>
<li>JDK 动态代理：针对实现了接口的类，Spring 会创建一个实现了相同接口的代理类。事务逻辑通过代理类在方法调用时插入。</li>
<li>CGLIB 代理：针对没有实现接口的类，Spring 会使用 CGLIB 生成子类代理，拦截方法调用并插入事务逻辑。</li>
</ul>
<p>不论哪种代理方式，Spring 都是在代理类中对事务进行管理。如果调用来自外部的类，代理对象会拦截该调用并正确地管理事务逻辑。</p>
<h1> 2. Spring 事务失效场景</h1>
<h2> 2.1. 同类中的方法调用导致事务失效</h2>
<p>不生效原因：<strong>这是因为内部方法调用不会通过 Spring 生成的代理类进行调用，而是直接在当前对象中执行，因此 Spring 无法介入处理事务。</strong></p>
<ul>
<li>内部调用：当类内部的方法调用另一个带有&nbsp;<code>@Transactional</code>&nbsp;注解的方法时，这个调用不会通过 Spring 的代理对象进行，而是直接通过&nbsp;<code>this</code>&nbsp;引用，因此 Spring 无法拦截并应用事务。</li>
<li>代理对象失效：Spring AOP 代理机制只能拦截通过代理对象进行的方法调用，而不能拦截类内部的直接方法调用。</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 2.1.1. 解决同类中事务失效的问题</h3>
<h4> 方法 1：通过Spirng容器重新获取代理对象</h4>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 方法 2：将事务性方法抽离到另一个类</h4>
<p>....</p>
<h4> 方法 3：使用 事务模版 （推荐）</h4>
<ol>
<li>删除事务注解 @Transactional</li>
<li>使用事务模版将需要事务管理的部分包裹起来</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>Emoji 表情报自取</title>
      <link>https://v-blog.yyshino.top/posts/Plugin/Emoji%20%E8%A1%A8%E6%83%85%E8%87%AA%E5%8F%96.html</link>
      <guid>https://v-blog.yyshino.top/posts/Plugin/Emoji%20%E8%A1%A8%E6%83%85%E8%87%AA%E5%8F%96.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Emoji 表情报自取</source>
      <description>Emoji 表情报自取</description>
      <category>other</category>
      <pubDate>Fri, 02 Aug 2024 14:43:29 GMT</pubDate>
      <content:encoded><![CDATA[<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>如何阅读源码</title>
      <link>https://v-blog.yyshino.top/posts/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/00-%E5%A6%82%E4%BD%95%E9%98%85%E8%AF%BB%E6%BA%90%E7%A0%81.html</link>
      <guid>https://v-blog.yyshino.top/posts/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/00-%E5%A6%82%E4%BD%95%E9%98%85%E8%AF%BB%E6%BA%90%E7%A0%81.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">如何阅读源码</source>
      <description>作者：lgd8981289 搬运自 https://github.com/lgd8981289/vue-next-mini 简介 那么在上一小节中我们已经知道了如何对vue的源代码进行debugger ,但是如果想要学习或者了解 vue的代码执行，那么光靠 debugger是不够的,除此之外我们还需要掌握另外一个能力,那么如何阅读源代码 阅读源代码的误区 很多同学在去阅读源代码的时候,都会面临一个误区,那就是：我需要把源代码中每一行代码都读明 这是一个非常不对的行为，很容易让我们 事倍功半</description>
      <category>FrontEnd</category>
      <pubDate>Sun, 05 May 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>作者：lgd8981289</p>
<p>搬运自 https://github.com/lgd8981289/vue-next-mini</p>
</blockquote>
<h2> 简介</h2>
<p>那么在上一小节中我们已经知道了如何对vue的源代码进行debugger ,但是如果想要学习或者了解 vue的代码执行，那么光靠 debugger是不够的,除此之外我们还需要掌握另外一个能力,那么如何阅读源代码</p>
<h2> 阅读源代码的误区</h2>
<p>很多同学在去阅读源代码的时候,都会面临一个误区,那就是：我需要把源代码中每一行代码都读明</p>
<p>这是一个非常不对的行为，很容易让我们 事倍功半</p>
<p>所以在这里我们需要先给大家明确一点：<strong>阅读源码绝对不是要读明白其中每一行代码的意思，而是在众多的业务代码中寻找到主线，跟随这个主线来进行阅读</strong></p>
<h2> 阅读源码的正确姿势</h2>
<p>想要快速、轻松的阅读源码,正确的姿势非常重要,主要有两点:</p>
<ol>
<li>摒弃边缘情况</li>
<li>跟随一条主线</li>
</ol>
<h3> 摒弃边缘情况</h3>
<p>在大型项目的源码中，都会充斥着非常多的业务代码，这些业务代码是用来处理很多 <strong>边缘情况</strong> 的，如果我们过分深究这些业务代码则会让我们陷入到一个 <strong>代码泥潭</strong> 中，在繁琐的业务中找不到方向。</p>
<p>所以，我们在阅读源码之前，必须要明确好一点，那就是：<strong>仅阅读核心逻辑</strong></p>
<h4> 跟随一条主线</h4>
<p>对于像vue这种量级的项目来说,哪怕我们只去阅读它的核心代码,你也会发现也是非常困难的。</p>
<p>我们之前说过, vue的核心大致可以分为三块:</p>
<ol>
<li>响应性</li>
<li>运行时</li>
<li>编译器</li>
</ol>
<p>每一大块的内部又分为了很多的业务分支。所以哪怕仅阅读核心代码已然是一个浩大的工作量。</p>
<p>所以说我们还需要另外一个方式，那就是：跟随一条主线</p>
<h2> 总结</h2>
<p>总结这一小节我们讲解了如何阅读源代码，以上方式不知可以应用到 vue 中，也可以应用到其他的框架之中，所以我们把这一小节叫做 授人以渔。</p>
<p>当然，我们这里只是通过一个简单的方式来进行了举例，在大家实际阅读的过程之中，肯定还是会遇到很多的困难的，不过好在，在这个过程中，我们会一起来进行阅读~~</p>
]]></content:encoded>
    </item>
    <item>
      <title>Vue3源码解析</title>
      <link>https://v-blog.yyshino.top/posts/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/01-Vue%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html</link>
      <guid>https://v-blog.yyshino.top/posts/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/01-Vue%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Vue3源码解析</source>
      <description>Vue响应式 原理 Proxy与Object.defineProperty Proxy Proxy 将代理一个对象（被代理对象），得到一个新的对象（代理对象），同时拥有被代理对象中所有的属性。 当想要修改对象的指定属性时,我们应该使用代理对象进行修改 代理对象 的任何一个属性都可以触发 handler 的 getter 和 setter Object.defineProperty Object.defineProperty 为指定对象的指定属性设置属性描述符 当想要修改对象的指定属性时，可以使用原对象进行修改 通过属性描述符，只有 被监听 的指定属性，才可以触发 getter 和 setter</description>
      <category>FrontEnd</category>
      <pubDate>Sun, 05 May 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> Vue响应式</h2>
<h3> 原理</h3>
<h4> <code>Proxy</code>与<code>Object.defineProperty</code></h4>
<ol>
<li><code>Proxy</code>
<ol>
<li><code>Proxy</code> 将代理一个对象（被代理对象），得到一个新的对象（代理对象），同时拥有被代理对象中所有的属性。</li>
<li>当想要修改对象的指定属性时,我们应该使用代理对象进行修改</li>
<li>代理对象 的任何一个属性都可以触发 <code>handler</code> 的 <code>getter</code> 和 <code>setter</code></li>
</ol>
</li>
<li><code>Object.defineProperty</code>
<ol>
<li><code>Object.defineProperty</code> 为指定对象的指定属性设置属性描述符</li>
<li>当想要修改对象的指定属性时，可以使用原对象进行修改</li>
<li>通过属性描述符，只有 被监听 的指定属性，才可以触发 <code>getter</code> 和 <code>setter</code></li>
</ol>
</li>
</ol>
<p>所以当 vue3 通过 Proxy 实现响应性核心 API 之后，vue 将 不会 再存在新增属性时失去响应性的问题。</p>
<h4> <code>Reflect</code></h4>
<p>https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect</p>
<h4> <code>Reflect</code>+<code>Proxy</code></h4>
<p>当我们期望监听代理对象的 getter 和 setter 时,不应该使用 target[key] ,因为它在某些时刻（比如 fullName）下是不可靠的。而 应该使用 Reflect ，借助它的 get 和 set 方法，使用receiver （proxy 实例） 作为 this，已达到期望的结果（触发三次 getter）</p>
<p>最后如果我们想要“安全”的使用Proxy,还需要配合Reflect一起才可以,因为一旦我们在被代理对象的内部，通过 this 触发 getter 和 setter 时，也需要被监听到。</p>
<h3> Reactive的响应式 源码实现</h3>
<p>核心</p>
<ol>
<li>创建 proxy</li>
<li>收集 effect 的依赖</li>
<li>触发收集的依赖</li>
</ol>
<h4> reactive方法</h4>
<ol>
<li>
<p>触发 reactive 方法</p>
</li>
<li>
<p>创建reactive对象: return createReactiveObject</p>
</li>
<li>
<p>进入 new Proxy</p>
<ol>
<li>第一个参数 target：为传入的对象</li>
<li>第二个参数 handler: TargetType.COLLECTION = 2， targetType = 1， 所以 handler为 baseHandlers</li>
<li>那这个 baseHandlers 是什么呢？</li>
</ol>
</li>
<li>
<p>在 reactive 方法中可知， baseHandlers 是触发 createReactiveObject 传递的第三个参数: mutableHandlers</p>
</li>
<li>
<p>而 mutableHandlers 则是 packages/reactivity/src/baseHandlers.ts 中导出的对象</p>
</li>
<li>
<p>所以我们到 packages/reactivity/src/baseHandlers.ts 中，为它的 get (createGetter)和 set (createSetter) 分别打入一个断点</p>
</li>
<li>
<p>我们知道 get 和 set 会在 取值 和 赋值 时触发，所以此时这两个断点 不会执行</p>
</li>
<li>
<p>最后 reactive方法内执行了 proxyMap.set(target, proxy)方法</p>
</li>
<li>
<p>最后返回了代理对象。</p>
</li>
<li>
<p>那么至此 reactive 方法执行完成。</p>
</li>
</ol>
<p>由以上执行逻辑可知，对于 reactive 方法而言，其实做的事情非常简单：</p>
<ol>
<li>创建了 proxy</li>
<li>把 proxy 加到了 proxyMap 里面</li>
<li>最后返回了 proxy</li>
</ol>
<h4> effect</h4>
<ol>
<li>在 packages/reactivity/src/effect.ts 第 170 行可以找到 effect 方法，在这里给一个断点</li>
<li>执行 new ReactiveEffect(fn)，其中的 fn 就是我们传入的匿名函数：
<ol>
<li>这里涉及到了一个类 ReactiveEffect</li>
<li>查看该类可知，内部实现了两个方法：
<ol>
<li>run</li>
<li>stop</li>
</ol>
</li>
<li>我们分别为这两个方法 增加断点</li>
</ol>
</li>
<li>代码继续进行</li>
<li>可以发现执行了 run 方法，进入方法内部：
<ol>
<li>执行 activeEffect = this，赋值完成之后, activeEffect为传入的匿名函数fn</li>
<li>然后执行 return this.fn（） 触发 fn 函数</li>
<li>我们知道 fn 函数其实就是 传入的匿名函数，所以document.querySelector('#app').innerText = obj.name</li>
</ol>
</li>
<li>但是大家不要忘记，obj 是一个 proxy，obj.name会 触发 getter，所以接下来我们就会进入到 mutableHandlers 的 createGetter 中
<ol>
<li>在该代码中,触发了该方法 const res = Reflect.get(target, key, receiver)</li>
<li>此时的 res 即为 张三</li>
<li>注意：接下来触发了 track 函数，该函数是一个重点函数，track 在此为 跟踪 的意思，我们来看它内部都做了什么：
<ol>
<li>在 4-1 步中，为 activeEffect 进行了赋值，我们知道 activeEffect 代表的就是fn 函数</li>
<li>执行代码可知，rack 内部主要做了两件事情：
<ol>
<li>为 targetMap 进行赋值，targetMap 的组成比较复杂：
<ol>
<li>key; target</li>
<li>value: Map
<ol>
<li>key: key</li>
<li>value: Set</li>
</ol>
</li>
</ol>
</li>
<li>最后执行了 trackEffects(dep, eventInfo)1,其中eventInfo是一个对象,内部包含四个属性:其中 effect即为activeEffect 即 fn 函数</li>
<li>在 trackEffects 函数内部，核心也是做了两件事情：
<ol>
<li>为dep (targetMap[target][key]得到的Set实例) 添加了activeEffect函数</li>
<li>为activeEffect函数的静态属性deps，增加了一个值dep</li>
<li>即：建立起了dep和activeEffect的联系</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li>那么至此，整个 track 的核心逻辑执行完成</li>
<li>我们可以把整个 track 的核心逻辑说成：收集了 activeEffect（即： fn)</li>
</ol>
</li>
<li>最后在 createGetter 函数中返回了 res （即：张三)</li>
<li>至此，整个 effect 执行完成</li>
</ol>
<p>由以上逻辑可知，整个 effect 主要做了3 件事情：</p>
<ol>
<li>生成ReactiveEffect实例</li>
<li>触发fn方法，从而激活getter</li>
<li>简历了targetMap和activeEffect之间的联系
<ol>
<li>dep.add(activeEffect)</li>
<li>activeEffect.deps.push(dep)</li>
</ol>
</li>
</ol>
<p>那么至此:页面中即可展示 obj.name ,但是不要忘记,等待两秒之后,我们会修改 obj.name 的值,我们知道,这样会触发 setter ,那么我们接下来来看 setter中又做了什么呢?</p>
<ol>
<li>两秒之后触发 setter ,会进入到 packages/reactivity/src/baseHandlers.ts 中的的createSetter 方法中</li>
<li>创建变量: oldValue =张三</li>
<li>创建变量: value=李四、</li>
<li>执行 const result = Reflect,set(target, key, value, receiver），即： 修改了 obj 的值为“李四”I</li>
<li>触发: trigger(target, TriggerOpTypes.SET, key, value, oldvalue),此时各参数的值为：</li>
<li>trigger 在这里为 触发 的意思，那么我们来看 trigger 内部做了什么?
<ol>
<li>首先执行： const depsMap = targetMap.get(target），其中 targetMap 即我们在track 函数中，保存 activeEffect 的 targetMap</li>
<li>然后代码执行到: deps.push(depsMap.get(key))。 depsMap.get(key)获取到的即为之前保存的 activeEffect，即 fn 函数</li>
<li>然后触发 triggerEffects(deps[0]，eventInfo)，我们来看 triggerEffects 中做了什么：
<ol>
<li>声明常量： const effects = isAray(dep) ? dep : [...dep]，此时的 effects 保存的为 fn 的集合</li>
<li>遍历 effects，执行：triggerEffect(effect， debuggerEventExtraInfo) 方法，那么我们来看 triggerEffect 做了什么
<ol>
<li>执行 effect.run(）方法，已知： effect 是一个ReactiveEffect 类型的对象，则run 方法会触发 ReactiveEffect 的 run ,那么我们接下来来看 这一次 进入run 方法时，内部做了什么？
<ol>
<li>首先还是为activeEffect = this赋值,但是要注意:此时的this不再是一个 fn，而是一个复杂对象：</li>
<li>最后执行this.fn() 即：effect时传入的匿名函数</li>
<li>至此，fn执行，意味着：document.querySelector('#app').innerText = 李四，页面将发生变化</li>
</ol>
</li>
</ol>
</li>
<li>triggerEffects 完成</li>
</ol>
</li>
<li>triggerEffects 完成</li>
</ol>
</li>
<li>trigger 完成</li>
<li>setter回调完成</li>
</ol>
<p>由以上逻辑可知，整个setter主要做了2件事情：</p>
<ol>
<li>修改obj的值</li>
<li>触发targetMap下保存的fn函数</li>
</ol>
<figure><img src="https://shinoimg.yyshino.top/img/image-20240425162252067.png" alt="image-20240425162252067" tabindex="0" loading="lazy"><figcaption>image-20240425162252067</figcaption></figure>
<h3> Ref 复杂数据类型的响应性</h3>
<ol>
<li>对于ref函数,会返回RefImpl类型的实例</li>
<li>在该实例中，会根据传入的数据类型进行分开处理
<ol>
<li>复杂数据类型:转化为reactive返回的proxy实例</li>
<li>简单数据类型：不做处理</li>
</ol>
</li>
<li>无论我们执行obj.value.name还是obj.value.name = xxx本质上都是触发了get value</li>
<li>之所以会进行 响应性 是因为 obj.value 是一个 reactive 函数生成的 proxy</li>
</ol>
<h4> 总结</h4>
<p>那么到这里我们就已经完成了 ref 响应性函数的构建，那么大家还记不记得开篇时所问的三个问题：</p>
<ol>
<li>ref 函数是如何进行实现的呢？</li>
<li>ref 可以构建简单数据类型的响应性吗？</li>
<li>为什么 ref 类型的数据，必须要通过 .value 访问值呢？</li>
</ol>
<p>大家现在再次面对这三个问题，是否能够回答出来呢？</p>
<ol>
<li>问题一: ref 函数是如何进行实现的呢？
<ol>
<li>ref 函数本质上是生成了一个 RefImpl 类型的实例对象,通过 get 和 set 标记处理了value 函数</li>
</ol>
</li>
<li>问题二： ref 可以构建简单数据类型的响应性吗？
<ol>
<li>是的。 ref 可以构建简单数据类型的响应性</li>
</ol>
</li>
<li>问题三：为什么 ref 类型的数据，必须要通过 .value 访问值呢？
<ol>
<li>因为 ref 需要处理简单数据类型的响应性，但是对于简单数据类型而言，它无法通过proxy 建立代理。</li>
<li>所以vue通过get value() 和 set value() 定义了两个属性函数，通过主动触发这两个函数（属性调用）的形式来进行<strong>依赖收集</strong>和<strong>依赖触发</strong></li>
</ol>
</li>
</ol>
<h3> <code>computed</code> &amp;&amp; <code>watch</code></h3>
<h4> computed</h4>
<p>debugged调试，具体流程</p>
<ol>
<li>整个事件有obj.name开始</li>
<li>触发 proxy 实例的 setter</li>
<li>执行 trigger，第一次触发依赖</li>
<li>注意，此时 effect 包含调度器属性，所以会触发调度器</li>
<li>调度器指向<code>ComputedRefImpl</code>的构造函数中传入的匿名函数</li>
<li>在匿名函数中会：再次触发依赖</li>
<li>即:两次触发依赖</li>
<li>最后执行：computed中的回调函数</li>
</ol>
<h3> 总结</h3>
<p>接下来我们来总结一下计算属性实现的重点：</p>
<ol>
<li>计算属性的实例,本质上是一个ComputedRefImpl的实例</li>
<li>ComputedRefImpl 中通过 dirty 变量来控制 run 的执行和 triggerRefValue 的触发</li>
<li>想要访问计算属性的值,必须通过 .value ,因为它内部和 ref 一样是通过get value来进</li>
<li>每次 .value 时都会触发 trackRefValue 即：收集依赖</li>
<li>在依赖触发时，需要谨记，先触发 computed 的 effect ，再触发非 computed 的 effect行实现的</li>
</ol>
<h2> Vue运行时核心</h2>
<h3> 基础概念</h3>
<p>虚拟DOM</p>
<blockquote>
<p>虚拟 DOM (Virtual DOM，简称 VDOM) 是一种编程概念，意为将目标所需的 UI 通过数据结构“虚拟”地表示出来，保存在内存中，然后将真实的 DOM 与之保持同步。这个概念是由 <a href="https://reactjs.org/" target="_blank" rel="noopener noreferrer">React</a> 率先开拓，随后被许多不同的框架采用，当然也包括 Vue。</p>
</blockquote>
<blockquote>
<p><a href="https://cn.vuejs.org/guide/extras/rendering-mechanism.html#render-pipeline" target="_blank" rel="noopener noreferrer">渲染管线</a></p>
<p>从高层面的视角看，Vue 组件挂载时会发生如下几件事：</p>
<ol>
<li><strong>编译</strong>：Vue 模板被编译为<strong>渲染函数</strong>：即用来返回虚拟 DOM 树的函数。这一步骤可以通过构建步骤提前完成，也可以通过使用运行时编译器即时完成。</li>
<li><strong>挂载</strong>：运行时渲染器调用渲染函数，遍历返回的虚拟 DOM 树，并基于它创建实际的 DOM 节点。这一步会作为<a href="https://cn.vuejs.org/guide/extras/reactivity-in-depth.html" target="_blank" rel="noopener noreferrer">响应式副作用</a>执行，因此它会追踪其中所用到的所有响应式依赖。</li>
<li><strong>更新</strong>：当一个依赖发生变化后，副作用会重新运行，这时候会创建一个更新后的虚拟 DOM 树。运行时渲染器遍历这棵新树，将它与旧树进行比较，然后将必要的更新应用到真实 DOM 上去。</li>
</ol>
<p>...</p>
</blockquote>
<h3> h函数</h3>
<p>查看 <code>packages/runtime-core/src/renderer.ts</code> 中第 354 patch 方法的代码可知，Vue 总共处理了：</p>
<ol>
<li>Text: 文本节点</li>
<li>Comment：注释节点</li>
<li>Static: 静态 DOM 节点</li>
<li>Fragment:包含多个根节点的模板被表示为一个片段(fragment)括</li>
<li>ELEMENT: DOM 节点Imooc</li>
<li>COMPONENT：组件</li>
<li>TELEPORT:新的内置组件</li>
<li>SUSPENSE：新的 内置组件</li>
<li>...</li>
</ol>
<p>在vue中，组件本质上是 一个对象或一个函数（Function Component）</p>
<h4> <code>shapeFlag</code></h4>
<h2> 组件的设计原理与渲染</h2>
<h3> 简介</h3>
<p>组件本身是一个对象(仅考虑对象的情况,忽略函数式组件)。它必须包含一个render函数，该函数决定了它的渲染内容。</p>
<p>如果我们想要定义数据，那么需要通过 data 选项进行注册。 data 选项应该是一个 函数，并且renturn一个对象，对象中包含了所有的响应性数据。</p>
<p>除此之外，我们还可以定义例如 生命周期、计算属性、 watch 等对应内容。</p>
<h3> 无状态基础挂着逻辑组件</h3>
<blockquote>
<p>Vue 中通常把 状态 比作 数据 的意思。我们所谓的无状态，指的就是 无数据 的意思。</p>
</blockquote>
<h2> Diff算法核心实现</h2>
<h3> 前置知识</h3>
<p>那么到目前为止，我们已经完成了 4 种 diff 场景的对应处理，经过前面的学习我们可以知道，对于前四种 diff 场景而言， diff 的处理本质上是比较简单的:</p>
<ol>
<li>自前向后的 diff对比：主要处理从前到后的相同 VNode。例如： (a b) c 对应(a b) d e</li>
<li>自后向前的 diff对比:主要处理从后到前的相同VNode。例如: a (b c)对应d e (b c)</li>
<li>新节点多余旧节点的 diff 对比：主要处理新增节点。</li>
<li>旧节点多余新节点的 diff 对比：主要处理删除节点。</li>
</ol>
<p>但是仅靠前四种场景的话,那么是无法满足实际开发中的所有更新逻辑的。所以我们还需要最关键的一种场景需要处理，那就是 乱序场景。</p>
<h4> 最长递增子序列</h4>
<ol>
<li>什么是最长递增子序列</li>
<li>最长递增子序列在diff 中的作用是什么</li>
</ol>
<blockquote>
<p>维基百科 -最长递增子序列</p>
<p>在一个给定的数值序列中，找到一个子序列，使得这个子序列元素的数值依次递增，并且这个子序列的长度尽可能地大。</p>
</blockquote>
<h3> 乱序下的diff比对</h3>
<ol>
<li>diff指的就是:添加、删除、打补丁、移动这四个行为</li>
<li>
<h2> 最长递增子序列 是什么，如何计算的，以及在 diff 中的作用</h2>
</li>
<li>场景五的乱序，是最复杂的场景，将会涉及到 添加、删除、打补丁、移动 这些所有场景。</li>
</ol>
<h3> 总结</h3>
<ol>
<li>首先我们讲解了dom、节点、节点树和虚拟DOM,虚拟节点之间的概念。</li>
<li>然后我们说明了 render 函数和 h 函数各自的作用。我们知道 h 函数可以得到一个 vnode ，而 render 函数可以渲染一个 vnode</li>
<li>接下来我们讲解了挂载、更新、卸载,这三组概念。也知道了针对于不同的vnode节点,那么他们的挂载、更新、卸载方式也都是不同的。</li>
<li>下面我们讲解了组件,我们知道组件本质上是一个对象(或函数) ,组件的挂载本质上是render 函数的挂载。</li>
<li>组件内部维护了一个effect对象,以达到响应性渲染的效果。</li>
<li>而针对于 setup 函数而言，它在实现上反而更加简单，因为我们不需要改变 this 指向了。</li>
<li>结合所学，新旧节点的所有挂载和更新情况，可以被分为九种场景：
<ol>
<li>旧节点为纯文本时：
<ol>
<li>新节点为纯文本：执行文本替换操作</li>
<li>新节点为空:删除旧节点</li>
<li>新节点为数组：清空文本，添加多个新节点2.</li>
</ol>
</li>
<li>旧节点为空时：
<ol>
<li>新节点为纯文本：添加新节点</li>
<li>新节点为空：不做任何事情</li>
<li>新节点为数组时：添加多个新节点</li>
</ol>
</li>
<li>旧节点是数组时：
<ol>
<li>新节点为纯文本：删除所有旧节点，添加新节点</li>
<li>新节点为空：删除所有旧节点</li>
<li>新节点为数组时：进行 diff 操作</li>
</ol>
</li>
</ol>
</li>
<li>最后的 diff 分为 5 种场景，最后一种场景还是比较复杂的。</li>
</ol>
<h2> 编译时核心设计原则</h2>
<h3> 前言</h3>
<h4> 模版编译的核心流程</h4>
<p>正常流程</p>
<figure><img src="https://shinoimg.yyshino.top/img/image-20240503142131784.png" alt="image-20240503142131784" tabindex="0" loading="lazy"><figcaption>image-20240503142131784</figcaption></figure>
<p>Vue中</p>
<figure><img src="https://shinoimg.yyshino.top/img/image-20240503142248493.png" alt="image-20240503142248493" tabindex="0" loading="lazy"><figcaption>image-20240503142248493</figcaption></figure>
<h3> 框架实现</h3>
<p>接下来我们通过parseChildren方法处理所有的子节点,整个处理的过程分为两大块:</p>
<ol>
<li>构建有限自动状态机解析模板</li>
<li>扫描 token 生成 AST 结构</li>
</ol>
<h3> 总结</h3>
<p>我们知道，整个 compiler 的过程，就是一个把：源代码（template）转化为目标代码（render函数）的过程。</p>
<p>在这个过程中，主要经历了三个大的步骤：</p>
<ol>
<li>解析( parse ) template模板,生成AST</li>
<li>转化(transform) AST,得到JavaScript AST</li>
<li>生成（generate） render 函数这</li>
</ol>
<p>三步是非常复杂的一个过程，内部的实现涉及到了非常复杂的计算方法，并且会涉及到一些我们现在还没有了解过得概念，比如：自动状态机。</p>
<p>这些内容我们都会放到下一章进行讲解，本章我们只需要知道 compiler 的作用，以及三大步骤即可都在干什么即可。</p>
<h2> 构建compile编译器</h2>
<h3> 总结</h3>
<p>我们知道整个编辑器的处理过程分成了三部分：</p>
<ol>
<li>解析模板 template 为 AST1,在这一步过程中,我们使用了
<ol>
<li>有限自动状态机解析模板得到了 tokens
<ol>
<li>通过扫描 tokens 最终得到了`AST王</li>
</ol>
</li>
</ol>
</li>
<li>转化 AST 为 JavaScript AST
<ol>
<li>这一步是为了最终生成 render 函数做准备</li>
<li>利用了深度优先的方式，进行了自下向上的逐层转化</li>
</ol>
</li>
<li>生成 render 函数
<ol>
<li>这一步是最后的解析环节,我们需要对JavaScript AST进行处理,得到最终的render函数</li>
</ol>
</li>
</ol>
<h2> 深入编辑器处理逻辑</h2>
<h2> 参考</h2>
<ul>
<li>https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy</li>
<li>https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty</li>
<li>https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect</li>
<li>https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model</li>
<li>https://cn.vuejs.org/guide/extras/rendering-mechanism.html#virtual-dom</li>
</ul>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/image-20240425162252067.png" type="image/png"/>
    </item>
    <item>
      <title>有限状态机</title>
      <link>https://v-blog.yyshino.top/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/02-%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E8%87%AA%E5%8A%A8%E6%9C%BA.html</link>
      <guid>https://v-blog.yyshino.top/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/02-%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E8%87%AA%E5%8A%A8%E6%9C%BA.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">有限状态机</source>
      <description>简介 有限状态自动机-维基百科 有限状态机（英语：finite-state machine，缩写：FSM）又称有限状态自动机（英语：finite-state automaton，缩写：FSA），简称状态机，是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。</description>
      <category>设计模式</category>
      <pubDate>Fri, 03 May 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 简介</h2>
<blockquote>
<p><a href="https://zh.wikipedia.org/wiki/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA" target="_blank" rel="noopener noreferrer">有限状态自动机-维基百科</a></p>
<p><strong>有限状态机</strong>（英语：finite-state machine，<a href="https://zh.wikipedia.org/wiki/%E7%B8%AE%E5%AF%AB" target="_blank" rel="noopener noreferrer">缩写</a>：<strong>FSM</strong>）又称<strong>有限状态自动机</strong>（英语：finite-state automaton，<a href="https://zh.wikipedia.org/wiki/%E7%B8%AE%E5%AF%AB" target="_blank" rel="noopener noreferrer">缩写</a>：<strong>FSA</strong>），简称<strong>状态机</strong>，是表示有限个<a href="https://zh.wikipedia.org/wiki/%E7%8A%B6%E6%80%81" target="_blank" rel="noopener noreferrer">状态</a>以及在这些状态之间的转移和动作等行为的<a href="https://zh.wikipedia.org/wiki/%E8%AE%A1%E7%AE%97%E6%A8%A1%E5%9E%8B_(%E6%95%B0%E5%AD%A6)" target="_blank" rel="noopener noreferrer">数学计算模型</a>。</p>
</blockquote>
<p>直接看阮一峰老师的 JavaScript与有限状态机</p>
<p><a href="https://www.ruanyifeng.com/blog/2013/09/finite-state_machine_for_javascript.html" target="_blank" rel="noopener noreferrer">阮一峰的网络日志- JavaScript与有限状态机</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>WeakMap与Map的区别</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/24-WeakMap%E4%B8%8EMap%E7%9A%84%E5%8C%BA%E5%88%AB.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/24-WeakMap%E4%B8%8EMap%E7%9A%84%E5%8C%BA%E5%88%AB.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">WeakMap与Map的区别</source>
      <description>WeakMap与Map的区别 WeakMap key必须是对象 key是弱引用 弱引用与强引用 弱引用：不会影响垃圾回收机制。即： WeakMap 的 key 不再存在任何引用时，会被直接回收。 强引用：会影响垃圾回收机制。存在强应用的对象永远不会被回收。 对比示例 &amp;lt;script&amp;gt; let obj1 = { name: &amp;apos;张三&amp;apos; } let obj2 = { name: &amp;apos;张三&amp;apos; } // 强引用 const map = new Map() // 弱引用 const weakMap = new WeakMap() map.set(obj1,&amp;apos;value&amp;apos;) weakMap.set(obj2,&amp;apos;value&amp;apos;) obj1 = null obj2 = null console.log(&amp;apos;map&amp;apos;,map) // Map(1) {{…} =&amp;gt; &amp;apos;value&amp;apos;} console.log(&amp;apos;weakMap&amp;apos;,weakMap) // WeakMap {} 空 /** 此时 WeakMap 中不存在任何值，即: obj2不存在其他引用时， WeakMap 不会阻止垃圾回收，基于obj2的引用将会被清除。这就证明了 WeakMap 的 弱引用特性。 */ &amp;lt;/script&amp;gt;</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 25 Apr 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> WeakMap与Map的区别</h3>
<h4> WeakMap</h4>
<ol>
<li>key必须是对象</li>
<li>key是弱引用</li>
</ol>
<h5> 弱引用与强引用</h5>
<ul>
<li>
<p>弱引用：不会影响垃圾回收机制。即： WeakMap 的 key 不再存在任何引用时，会被直接回收。</p>
</li>
<li>
<p>强引用：会影响垃圾回收机制。存在强应用的对象永远不会被回收。</p>
</li>
</ul>
<h3> 对比示例</h3>
<div class="language-html line-numbers-mode" data-ext="html"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>手写reduce</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/25-%E6%89%8B%E5%86%99Reduce.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/25-%E6%89%8B%E5%86%99Reduce.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">手写reduce</source>
      <description>MDN参考 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce 语法 reduce(callbackFn) reduce(callbackFn, initialValue)</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 25 Apr 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> MDN参考</h3>
<p>https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce</p>
<h4> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#%E8%AF%AD%E6%B3%95" target="_blank" rel="noopener noreferrer">语法</a></h4>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><h4> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#%E5%8F%82%E6%95%B0" target="_blank" rel="noopener noreferrer">参数</a></h4>
<ul>
<li>
<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#callbackfn" target="_blank" rel="noopener noreferrer"><code>callbackFn</code></a></p>
<p>为数组中每个元素执行的函数。其返回值将作为下一次调用 <code>callbackFn</code> 时的 <code>accumulator</code> 参数。对于最后一次调用，返回值将作为 <code>reduce()</code> 的返回值。该函数被调用时将传入以下参数：<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#accumulator" target="_blank" rel="noopener noreferrer"><code>accumulator</code></a>上一次调用 <code>callbackFn</code> 的结果。在第一次调用时，如果指定了 <code>initialValue</code> 则为指定的值，否则为 <code>array[0]</code> 的值。<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#currentvalue" target="_blank" rel="noopener noreferrer"><code>currentValue</code></a>当前元素的值。在第一次调用时，如果指定了 <code>initialValue</code>，则为 <code>array[0]</code> 的值，否则为 <code>array[1]</code>。<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#currentindex" target="_blank" rel="noopener noreferrer"><code>currentIndex</code></a><code>currentValue</code> 在数组中的索引位置。在第一次调用时，如果指定了 <code>initialValue</code> 则为 <code>0</code>，否则为 <code>1</code>。<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#array" target="_blank" rel="noopener noreferrer"><code>array</code></a>调用了 <code>reduce()</code> 的数组本身。</p>
</li>
<li>
<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#initialvalue" target="_blank" rel="noopener noreferrer"><code>initialValue</code></a> 可选</p>
<p>第一次调用回调时初始化 <code>accumulator</code> 的值。如果指定了 <code>initialValue</code>，则 <code>callbackFn</code> 从数组中的第一个值作为 <code>currentValue</code> 开始执行。如果没有指定 <code>initialValue</code>，则 <code>accumulator</code> 初始化为数组中的第一个值，并且 <code>callbackFn</code> 从数组中的第二个值作为 <code>currentValue</code> 开始执行。在这种情况下，如果数组为空（没有第一个值可以作为 <code>accumulator</code> 返回），则会抛出错误。</p>
</li>
</ul>
<h4> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#%E8%BF%94%E5%9B%9E%E5%80%BC" target="_blank" rel="noopener noreferrer">返回值</a></h4>
<p>使用“reducer”回调函数遍历整个数组后的结果。</p>
<h4> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#%E5%BC%82%E5%B8%B8" target="_blank" rel="noopener noreferrer">异常</a></h4>
<ul>
<li>
<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError" target="_blank" rel="noopener noreferrer"><code>TypeError</code></a></p>
<p>如果数组为空且未提供 <code>initialValue</code>，则会抛出异常。</p>
</li>
</ul>
<h3> 手写实现</h3>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>手写call、apply、bind</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/26-%E6%89%8B%E5%86%99call%E3%80%81bind%E3%80%81apply.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/26-%E6%89%8B%E5%86%99call%E3%80%81bind%E3%80%81apply.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">手写call、apply、bind</source>
      <description>手写call、apply、bind 总结 三者都可以改变函数的this对象指向 三者第一个参数都是this要指向的对象，如果如果没有这个参数或参数为undefined或null，则默认指向全局window 三者都可以传参，但是apply是数组，而call是参数列表，且apply和call是一次性传入参数，而bind可以分为多次传入 bind是返回绑定this之后的函数，apply、call 则是立即执行</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 25 Apr 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 手写call、apply、bind</h3>
<h4> 总结</h4>
<ul>
<li>三者都可以改变函数的<code>this</code>对象指向</li>
<li>三者第一个参数都是<code>this</code>要指向的对象，如果如果没有这个参数或参数为<code>undefined</code>或<code>null</code>，则默认指向全局<code>window</code></li>
<li>三者都可以传参，但是<code>apply</code>是数组，而<code>call</code>是参数列表，且<code>apply</code>和<code>call</code>是一次性传入参数，而<code>bind</code>可以分为多次传入</li>
<li><code>bind</code>是返回绑定this之后的函数，<code>apply</code>、<code>call</code> 则是立即执行</li>
</ul>
<h4> call</h4>
<p>定义：<code>call</code>方法的第一个参数也是<code>this</code>的指向，后面传入的是一个参数列表</p>
<p>跟<code>apply</code>一样，改变<code>this</code>指向后原函数会立即执行，且此方法只是临时改变<code>this</code>指向一次</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> apply</h4>
<p>定义： 调用一个具有给定 this 值的函数，及以一个数组的形式提供的参数。</p>
<p>改变<code>this</code>指向后原函数会立即执行，且此方法只是临时改变<code>this</code>指向一次</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> bind</h4>
<p>定义：创建一个新的函数，在 bind 被调用时，这个新函数的 this 被指定为 bind()的第一个参数，而其余参数将作为新函数的参数，供调用时使用。</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>并发请求</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/27-%E5%B9%B6%E5%8F%91%E8%AF%B7%E6%B1%82.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/27-%E5%B9%B6%E5%8F%91%E8%AF%B7%E6%B1%82.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">并发请求</source>
      <description>/** * 在某些场景下，页面的请求数可能会有很多， * 比如：抓取、分片上传等，如果我们需要等一个请求完成后， * 再发起下一个请求，是必效率会比较低。 * 为了提升效率，我们可以同时发出多个请求，但数量又不能太多， * 如若某请求完成，剩余等待的请求继续补位执行， * 将请求的结果（不论成功或失败），按原有数组的顺序返回。 */ /** * 11-并发事件 * @param {string[]} urls 请求地址数组 * @param {number} max 最大并发数 */ function consurRequest(urls, max) { return new Promise((resolve,reject) =&amp;gt; { // 判断传入urls长度，为空直接返回[] if(urls.length === 0){ resolve([]) return } const results = [] let count = 0 // 标识请求的完成数量 let index = 0 // 下一个请求 // 发送请求 async function request(){ // 退出逻辑 if(index === urls.length){ return } // 存储 index，保持后续请求结果数组与urls数组对应 const i = index const url = urls[index] index++ try { const resp = await fetch(url) // resp加入到 results中 results[i] = resp } catch (err) { // err 加入到results results[i] = err console.log(&amp;apos;err&amp;apos;,err) } finally { // 判断是否所以的请求都完成 count++ // if(count === urls.length){ if(count === urls.length){ resolve(results) return } request() } } const times = Math.min(max,urls.length) let promiseList = [] for(let i = 0; i &amp;lt; times; i++){ promiseList.push(request()) } Promise.race(promiseList).then(() =&amp;gt; { }).catch((err) =&amp;gt; { reject(err) }) }) }</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 25 Apr 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>Vue响应式实现</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-5Vue/01-Vue%E5%93%8D%E5%BA%94%E5%BC%8F%E5%AE%9E%E7%8E%B0.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-5Vue/01-Vue%E5%93%8D%E5%BA%94%E5%BC%8F%E5%AE%9E%E7%8E%B0.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Vue响应式实现</source>
      <description>原理 Proxy与Object.defineProperty Proxy Proxy 将代理一个对象（被代理对象），得到一个新的对象（代理对象），同时拥有被代理对象中所有的属性。 当想要修改对象的指定属性时,我们应该使用代理对象进行修改 代理对象 的任何一个属性都可以触发 handler 的 getter 和 setter Object.defineProperty Object.defineProperty 为指定对象的指定属性设置属性描述符 当想要修改对象的指定属性时，可以使用原对象进行修改 通过属性描述符，只有 被监听 的指定属性，才可以触发 getter 和 setter</description>
      <category>FrontEnd</category>
      <pubDate>Wed, 24 Apr 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 原理</h3>
<h4> <code>Proxy</code>与<code>Object.defineProperty</code></h4>
<ol>
<li><code>Proxy</code>
<ol>
<li><code>Proxy</code> 将代理一个对象（被代理对象），得到一个新的对象（代理对象），同时拥有被代理对象中所有的属性。</li>
<li>当想要修改对象的指定属性时,我们应该使用代理对象进行修改</li>
<li>代理对象 的任何一个属性都可以触发 <code>handler</code> 的 <code>getter</code> 和 <code>setter</code></li>
</ol>
</li>
<li><code>Object.defineProperty</code>
<ol>
<li><code>Object.defineProperty</code> 为指定对象的指定属性设置属性描述符</li>
<li>当想要修改对象的指定属性时，可以使用原对象进行修改</li>
<li>通过属性描述符，只有 被监听 的指定属性，才可以触发 <code>getter</code> 和 <code>setter</code></li>
</ol>
</li>
</ol>
<p>所以当 vue3 通过 Proxy 实现响应性核心 API 之后，vue 将 不会 再存在新增属性时失去响应性的问题。</p>
<h4> <code>Reflect</code></h4>
<p>https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect</p>
<h4> <code>Reflect</code>+<code>Proxy</code></h4>
<p>当我们期望监听代理对象的 getter 和 setter 时,不应该使用 target[key] ,因为它在某些时刻（比如 fullName）下是不可靠的。而 应该使用 Reflect ，借助它的 get 和 set 方法，使用receiver （proxy 实例） 作为 this，已达到期望的结果（触发三次 getter）</p>
<p>最后如果我们想要“安全”的使用Proxy,还需要配合Reflect一起才可以,因为一旦我们在被代理对象的内部，通过 this 触发 getter 和 setter 时，也需要被监听到。</p>
]]></content:encoded>
    </item>
    <item>
      <title>TS路径映射如何实现</title>
      <link>https://v-blog.yyshino.top/posts/TypeScript/02-TS%E8%B7%AF%E5%BE%84%E6%98%A0%E5%B0%84%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0.html</link>
      <guid>https://v-blog.yyshino.top/posts/TypeScript/02-TS%E8%B7%AF%E5%BE%84%E6%98%A0%E5%B0%84%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">TS路径映射如何实现</source>
      <category>FrontEnd</category>
      <pubDate>Wed, 24 Apr 2024 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>MVC-MVP-MVVM</title>
      <link>https://v-blog.yyshino.top/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/01-MVC-MVP-MVVM.html</link>
      <guid>https://v-blog.yyshino.top/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/01-MVC-MVP-MVVM.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">MVC-MVP-MVVM</source>
      <description>搬运自 MVC，MVP 和 MVVM 的图示 作者： 阮一峰 日期： 2015年2月 1日</description>
      <category>设计模式</category>
      <pubDate>Wed, 24 Apr 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>搬运自 <a href="https://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html" target="_blank" rel="noopener noreferrer">MVC，MVP 和 MVVM 的图示</a></p>
<p>作者： <a href="https://www.ruanyifeng.com/" target="_blank" rel="noopener noreferrer">阮一峰</a></p>
<p>日期： <a href="https://www.ruanyifeng.com/blog/2015/02/" target="_blank" rel="noopener noreferrer">2015年2月 1日</a></p>
</blockquote>
<h1> MVC，MVP 和 MVVM 的图示</h1>
<p>作者： <a href="https://www.ruanyifeng.com/" target="_blank" rel="noopener noreferrer">阮一峰</a></p>
<p>日期： <a href="https://www.ruanyifeng.com/blog/2015/02/" target="_blank" rel="noopener noreferrer">2015年2月 1日</a></p>
<p>复杂的软件必须有清晰合理的架构，否则无法开发和维护。</p>
<p><a href="https://zh.wikipedia.org/wiki/MVC" target="_blank" rel="noopener noreferrer">MVC</a>（Model-View-Controller）是最常见的软件架构之一，业界有着广泛应用。它本身<a href="https://www.ruanyifeng.com/blog/2007/11/mvc.html" target="_blank" rel="noopener noreferrer">很容易理解</a>，但是要讲清楚，它与衍生的 MVP 和 MVVM 架构的区别就不容易了。</p>
<p>昨天晚上，我读了<a href="http://blog.nodejitsu.com/scaling-isomorphic-javascript-code/" target="_blank" rel="noopener noreferrer">《Scaling Isomorphic Javascript Code》</a>，突然意识到，它们的区别非常简单。我用几段话，就可以说清。</p>
<figure><img src="https://www.ruanyifeng.com/blogimg/asset/2015/bg2015020102.jpg" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<p>（题图：摄于瓦伦西亚，西班牙，2014年8月）</p>
<h2> 一、MVC</h2>
<p>MVC模式的意思是，软件可以分成三个部分。</p>
<figure><img src="https://www.ruanyifeng.com/blogimg/asset/2015/bg2015020104.png" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<blockquote>
<ul>
<li>视图（View）：用户界面。</li>
<li>控制器（Controller）：业务逻辑</li>
<li>模型（Model）：数据保存</li>
</ul>
</blockquote>
<p>各部分之间的通信方式如下。</p>
<figure><img src="https://www.ruanyifeng.com/blogimg/asset/2015/bg2015020105.png" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<blockquote>
<ol>
<li>View 传送指令到 Controller</li>
<li>Controller 完成业务逻辑后，要求 Model 改变状态</li>
<li>Model 将新的数据发送到 View，用户得到反馈</li>
</ol>
</blockquote>
<p>所有通信都是单向的。</p>
<h2> 二、互动模式</h2>
<p>接受用户指令时，MVC 可以分成两种方式。一种是通过 View 接受指令，传递给 Controller。</p>
<figure><img src="https://www.ruanyifeng.com/blogimg/asset/2015/bg2015020106.png" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<p>另一种是直接通过controller接受指令。</p>
<figure><img src="https://www.ruanyifeng.com/blogimg/asset/2015/bg2015020107.png" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<h2> 三、实例：Backbone</h2>
<p>实际项目往往采用更灵活的方式，以 <a href="https://documentcloud.github.com/backbone" target="_blank" rel="noopener noreferrer">Backbone.js</a> 为例。</p>
<figure><img src="https://www.ruanyifeng.com/blogimg/asset/2015/bg2015020108.png" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ol>
<li>用户可以向 View 发送指令（DOM 事件），再由 View 直接要求 Model 改变状态。</li>
<li>用户也可以直接向 Controller 发送指令（改变 URL 触发 hashChange 事件），再由 Controller 发送给 View。</li>
<li>Controller 非常薄，只起到路由的作用，而 View 非常厚，业务逻辑都部署在 View。所以，Backbone 索性取消了 Controller，只保留一个 Router（路由器） 。</li>
</ol>
<h2> 四、MVP</h2>
<p>MVP 模式将 Controller 改名为 Presenter，同时改变了通信方向。</p>
<figure><img src="https://www.ruanyifeng.com/blogimg/asset/2015/bg2015020109.png" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ol>
<li>各部分之间的通信，都是双向的。</li>
<li>View 与 Model 不发生联系，都通过 Presenter 传递。</li>
<li>View 非常薄，不部署任何业务逻辑，称为"被动视图"（Passive View），即没有任何主动性，而 Presenter非常厚，所有逻辑都部署在那里。</li>
</ol>
<h2> 五、MVVM</h2>
<p>MVVM 模式将 Presenter 改名为 ViewModel，基本上与 MVP 模式完全一致。</p>
<figure><img src="https://www.ruanyifeng.com/blogimg/asset/2015/bg2015020110.png" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<p>唯一的区别是，它采用双向绑定（data-binding）：View的变动，自动反映在 ViewModel，反之亦然。<a href="https://angularjs.org/" target="_blank" rel="noopener noreferrer">Angular</a> 和 <a href="http://emberjs.com/" target="_blank" rel="noopener noreferrer">Ember</a> 都采用这种模式。</p>
]]></content:encoded>
      <enclosure url="https://www.ruanyifeng.com/blogimg/asset/2015/bg2015020102.jpg" type="image/jpeg"/>
    </item>
    <item>
      <title>Vue源码分析</title>
      <link>https://v-blog.yyshino.top/posts/Vue/Vue2-%E6%BA%90%E7%A0%81-01.html</link>
      <guid>https://v-blog.yyshino.top/posts/Vue/Vue2-%E6%BA%90%E7%A0%81-01.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Vue源码分析</source>
      <description>vue源码分析</description>
      <category>FrontEnd</category>
      <pubDate>Tue, 23 Apr 2024 13:10:19 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 框架的基本概念</h3>
<p>在本章中，我们对整个 vue 框架设计中的一些基本概念都做了一个了解。明确了如下基本概念：</p>
<ol>
<li>命令式</li>
<li>声明式</li>
<li>心智负担</li>
<li>框架设计与取舍之间的关系</li>
<li>运行时</li>
<li>编译时</li>
<li>运行时 + 编译时</li>
<li>副作用</li>
<li>reactivity、 runtime、compiler 三者之间的运行关系</li>
<li>扩展：良好的 ts 支持</li>
</ol>
<p>当我们把这些基本概念了解清楚之后，那么下一章我们就可以准备开始构建我们的 vue 3 框架了。</p>
<h4> 编程范式之命令式编程</h4>
<h4> 编程范式之声明式编程</h4>
<h4> 命令式 VS声明式</h4>
<h4> 什么是运行时</h4>
<h4> 什么是编译时</h4>
<h4> 运行时+编译时</h4>
<h5> 为什么Vue要设计成一个运行时+编译时的框架呢</h5>
<ul>
<li>针对于 纯运行时 而言：因为不存在编译器，所以我们只能够提供一个复杂的 JS 对象。</li>
<li>针对于 纯编译时 而言：因为缺少运行时，所以它只能把分析差异的操作，放到 编译时 进行，同样因为省略了运行时，所以速度可能会更快。但是这种方式这将损失灵活性（具体可查看第六章虚拟 DOM ，或可点击 这里 查看官方示例)。比如 svelte，它就是一个纯编译时的框架，但是它的实际运行速度可能达不到理论上的速度。</li>
<li>运行时 + 编译时：比如 vue 或 react 都是通过这种方式来进行构建的，使其可以在保持灵活性的基础上，尽量的进行性能的优化，从而达到一种平衡。</li>
</ul>
<h4> 11：副作用</h4>
<p>副作用指的是：当我们对数据进行<code>setter</code>或<code>getter</code>操作时，所产生的一系列后果</p>
<p>副作用是可以产生多个的</p>
<ul>
<li>setter：赋值</li>
<li>getter：取值</li>
<li>副作用可以产生多个吗?
<ul>
<li>可以的</li>
</ul>
</li>
</ul>
<h4> 12: Vue3框架设计概述</h4>
<h3> 源码解析</h3>
<h4> 目录结构</h4>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> Debugger</h4>
<ol>
<li>下载 vue 源代码，推荐通过 该仓库 下载指定版本（注意：出错)I直接下载 ZIP文件会导致 build</li>
<li>为源代码开启 <code>sourcemap</code>，以方便后续进行 debugger
<ul>
<li>参考<code>minimist</code>文档，在<code>package.json</code>中的<code>script build</code>命令后加上<code>-s</code>即可</li>
</ul>
</li>
<li>在 <code>packages/vue/examples</code> 中，创建文件，导入 ../../dist/vue.global.js ，书写测试实例</li>
<li>通过Live Server 启动服务</li>
<li>在浏览器控制台的Sources中查看运行代码,并进行debugger</li>
</ol>
<h3> 手写mini-vue</h3>
<ul>
<li>
<p>vue-next-mini</p>
</li>
<li>
<p>为框架进行配置-导入ts</p>
</li>
<li>
<p>引入prettier、eslint</p>
</li>
<li>
<p>模块打包器<code>rollup</code></p>
<ul>
<li><code>rollup.config.js</code> 文档参考<a href="https://www.rollupjs.com/configuration-options/" target="_blank" rel="noopener noreferrer">https://www.rollupjs.com/configuration-options/</a></li>
</ul>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>手写Promise相关方法</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/23-%E6%89%8B%E5%86%99Promise%E7%9B%B8%E5%85%B3%E6%96%B9%E6%B3%95.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/23-%E6%89%8B%E5%86%99Promise%E7%9B%B8%E5%85%B3%E6%96%B9%E6%B3%95.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">手写Promise相关方法</source>
      <description>Promise.all 特点： Promise.all() 方法接收一个promise的iterable类型（MDN） 只返回一个promise实例 当传入的参数promise全部成功时，最后的结果才会成功（成功的结果是所有的promise的成功的结果组成的数组），只要有一个promise失败，all返回的实例就是一个失败的promise（失败的结果是传入的参数中的第一个失败的promise的结果） let p1 = new Promise(resolve =&amp;gt; { setTimeout(resolve, 200, 1) }); let p2 = new Promise((resolve, reject) =&amp;gt; reject(2)); let p3 = 3; console.log(Promise.all([p1, p2, p3]));//all方法 let myAll = function(parr) { let result = [],//最后成功的结果 count = 0,//累加器，与len比较判断是否全部成功了 len = parr.length; return new Promise((resolve, reject) =&amp;gt; { for (let p of parr) {// 依次测试传入的参数（转化为promise）是否是成功的 Promise.resolve(p).then(res =&amp;gt; { result[count] = res;// 成功就加入到结果中 count++;// 累加器加一 if (count == len) {// 如果相等，说明都成功了，可以走成功resolve resolve(res); } }, err =&amp;gt; { // 只要有一个失败了，直接走失败reject reject(err); }) } }) } console.log(myAll([p1, p2, p3]));</description>
      <category>FrontEnd</category>
      <pubDate>Fri, 19 Apr 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> Promise.all</h3>
<p>特点：</p>
<ul>
<li>Promise.all() 方法接收一个promise的iterable类型（MDN）</li>
<li>只返回一个promise实例</li>
<li>当传入的参数promise全部成功时，最后的结果才会成功（成功的结果是所有的promise的成功的结果组成的数组），只要有一个promise失败，all返回的实例就是一个失败的promise（失败的结果是传入的参数中的第一个失败的promise的结果）</li>
</ul>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> Promise.race（比比谁先改变状态！）</h3>
<h6> 特点：</h6>
<ol>
<li><strong>传入的参数和返回的结果形式和all方法一样</strong></li>
<li><strong>区别：只要传入的promise有一个状态改变了，最后的结果就会立即改变</strong></li>
<li><strong>例如有一个成功，直接走race的resolve，如果有一个失败，直接走reject</strong></li>
</ol>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> Promise.any</h3>
<p>特点：
几乎和all方法“一样”
区别：all是所有都成功最后才成功,一个失败了，最后就失败，allSettled是只要有一个成功了最后就是成功了，遇到失败的还是继续监测，直到找到成功的或者检查完。
好理解一点就是，all方法类似于Array的every方法，any类似于Array的some方法</p>
<h3> Promise.allSettled</h3>
<p>特点：
传参和返回值和all方法一样</p>
<p>返回值都是已成功状态（不论传入的是已成功还是已失败的promise）</p>
<p>一旦所指定的 promises 集合中每一个 promise 已经完成，无论是成功的达成或被拒绝，未决议的 Promise将被异步完成。那时，所返回的 promise 的处理器将传入一个数组作为输入，该数组包含原始 promises 集中每个 promise 的结果。
对于每个结果对象，都有一个 status 字符串。如果它的值为 fulfilled，则结果对象上存在一个 value 。如果值为 rejected，则存在一个 reason 。value（或 reason ）反映了每个 promise 决议（或拒绝）的值。</p>
<h3> Promise.prototype.finally</h3>
<p>特点：
在promise结束时，无论结果是fulfilled或者是rejected，都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
避免了同样的语句需要在then()和catch()中各写一次的情况。</p>
<h3> 参考</h3>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>文件上传难点分析</title>
      <link>https://v-blog.yyshino.top/front_end_interview/%E9%A1%B9%E7%9B%AE%E9%9A%BE%E7%82%B9/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/%E9%A1%B9%E7%9B%AE%E9%9A%BE%E7%82%B9/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">文件上传难点分析</source>
      <description>文件上传 断点续传 大文件分包上传 遇到问题 前后端上传请求超时限制，一次性传输大小限制。 网络抖动等，失败后需要重新上传。 http1.1版本， TCP连接默认是open的，所有请求都通过同一个连接进行数据传输，如果前面的请求被阻塞了，后面的请求也得不到响应，也叫HTTP/1.1 中的队头阻塞问题，除非建立多个连接，但是多个连接会浪费资源。 无进度条，用户体验极差。</description>
      <category>面经</category>
      <pubDate>Thu, 18 Apr 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 文件上传</h2>
<ul>
<li>
<p>断点续传</p>
</li>
<li>
<p>大文件分包上传</p>
<ul>
<li>
<p>遇到问题</p>
<ol>
<li>
<p>前后端上传请求超时限制，一次性传输大小限制。</p>
</li>
<li>
<p>网络抖动等，失败后需要重新上传。</p>
</li>
<li>
<p>http1.1版本， TCP连接默认是open的，所有请求都通过同一个连接进行数据传输，如果前面的请求被阻塞了，后面的请求也得不到响应，也叫HTTP/1.1 中的队头阻塞问题，除非建立多个连接，但是多个连接会浪费资源。</p>
</li>
<li>
<p>无进度条，用户体验极差。</p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<h3> 分包上传</h3>
<h4> 前端</h4>
<p>获取文件的二进制内容，然后对其内容拆分成指定大小的切片文件，最后将每个切片上传到服务端即可。</p>
<p>流程：获取文件 ➡️ 分片 ➡️ 上传</p>
<p>需要优化的点</p>
<ul>
<li>中断后无需重新上传（断点续传）</li>
<li>上传过的文件无需上传（秒传）</li>
<li>显示上传进度</li>
</ul>
<h4> 后端</h4>
<p>根据切片文件的唯一标识在后端将多个相同文件的切片还原成一个文件</p>
<h4> 示例代码</h4>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 断点续传</h3>
<p><strong>以上代码还需要继续优化的点：断点续传、秒传、上传进度和暂停</strong></p>
<p><strong>1、断点续传</strong></p>
<p><strong>为什么需要断点续传？</strong></p>
<ul>
<li>即使将大文件拆分成切片上传，我们仍需等待所有切片上传完毕，在等待过程中，可能发生一系列导致部分切片上传失败的情形，如网络故障、页面关闭等。由于切片未全部上传，因此无法通知服务端合成文件。这种情况下可以通过断点续传来进行处理。断点续传指的是：可以从已经上传部分开始继续上传未完成的部分，而没有必要从头开始上传，节省上传时间。</li>
</ul>
<p><strong>怎么实现断点续传？</strong></p>
<p>由于整个上传过程是按切片维度进行的，且mkfile接口是在所有切片上传完成后由客户端主动调用的，因此断点续传的实现也十分简单：</p>
<ul>
<li>在切片上传成功后，保存已上传的切片信息</li>
<li>当下次传输相同文件时，遍历切片列表，只选择未上传的切片进行上传</li>
<li>所有切片上传完毕后，再调用mkfile接口通知服务端进行文件合并</li>
<li>因此问题就落在了如何保存已上传切片的信息了，保存一般有两种策略
<ul>
<li>1.可以通过locaStorage等方式保存在前端浏览器中，这种方式不依赖于服务端，实现起来也比较方便，缺点在于如果用户清除了本地文件，会导致上传记录丢失</li>
<li>2.服务端本身知道哪些切片已经上传，因此可以由服务端额外提供一个根据文件context查询已上传切片的接口，在上传文件前调用该文件的历史上传记录</li>
</ul>
</li>
</ul>
<h4> 示例代码</h4>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 秒传</h3>
<p><strong>什么是秒传？</strong></p>
<ul>
<li>已经上传过的文件，并且在后端已经拼接完成，如果再次上传的话后端不做处理，直接返回拼接好的文件的信息即可，这里主要后端实现，由于篇幅关系，这里不做过多描述。</li>
</ul>
<p><strong>3、上传进度和暂停</strong></p>
<ul>
<li>通过xhr.upload中的progress方法可以实现监控每一个切片上传进度。</li>
<li>上传暂停的实现也比较简单，通过xhr.abort可以取消当前未完成上传切片的上传，实现上传暂停的效果，恢复上传就跟断点续传类似，先获取已上传的切片列表，然后重新发送未上传的切片。</li>
<li>由于篇幅关系，上传进度和暂停的功能这里就先不实现了。</li>
</ul>
<h3> 参考</h3>
<p>https://cloud.tencent.com/developer/article/2391382</p>
]]></content:encoded>
    </item>
    <item>
      <title>style-scoped原理与作用</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-5Vue/07-style-scoped%E5%8E%9F%E7%90%86%E4%B8%8E%E4%BD%9C%E7%94%A8.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-5Vue/07-style-scoped%E5%8E%9F%E7%90%86%E4%B8%8E%E4%BD%9C%E7%94%A8.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">style-scoped原理与作用</source>
      <description>作用：实现组件的私有化，不对全局造成样式污染，表示当前style属性只属于当前模块。 原理：scoped会在DOM结构及css样式上加上唯一性的标记【data-v-something】属性，即CSS带属性选择器，以此完成类似作用域的选择方式，从而达到样式私有化，不污染全局的作用。</description>
      <category>FrontEnd</category>
      <pubDate>Mon, 01 Apr 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>作用：实现组件的私有化，不对全局造成样式污染，表示当前style属性只属于当前模块。</p>
<p>原理：scoped会在DOM结构及css样式上加上唯一性的标记【data-v-something】属性，即CSS带属性选择器，以此完成类似作用域的选择方式，从而达到样式私有化，不污染全局的作用。</p>
]]></content:encoded>
    </item>
    <item>
      <title>算法指标</title>
      <link>https://v-blog.yyshino.top/front_end_interview/0-0%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%AE%97%E6%B3%95/01-%E7%AE%97%E6%B3%95%E6%8C%87%E6%A0%87.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/0-0%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%AE%97%E6%B3%95/01-%E7%AE%97%E6%B3%95%E6%8C%87%E6%A0%87.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">算法指标</source>
      <description>算法指标</description>
      <category>FrontEnd</category>
      <pubDate>Wed, 20 Mar 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 算法</h2>
<p>算法（Algorithm）是指用来操作数据、解决程序问题的一组方法。对于同一个问题，使用不同的算法，也许最终得到的结果是一样的，但在过程中消耗的资源和时间却会有很大的区别。</p>
<h2> 算法的指标</h2>
<p>主要还是从算法所占用的「时间」和「空间」两个维度去考量。</p>
<ul>
<li>时间维度：是指执行当前算法所消耗的时间，我们通常用「时间复杂度」来描述。</li>
<li>空间维度：是指执行当前算法需要占用多少内存空间，我们通常用「空间复杂度」来描述。</li>
</ul>
<h3> 时间复杂度</h3>
<p>首先要说的是，时间复杂度的计算并不是计算程序具体运行的时间，而是算法执行语句的次数。
当我们面前有多个算法时，我们可以通过计算时间复杂度，判断出哪一个算法在具体执行时花费时间最多和最少。</p>
<p><strong>通常我们计算时间复杂度都是计算最坏情况</strong></p>
<p>常见的时间复杂度</p>
<ul>
<li>常数阶O(1),</li>
<li>对数阶O(log2 n),</li>
<li>线性阶O(n),</li>
<li>线性对数阶O(n log2 n),</li>
<li>平方阶O(n^2)，</li>
<li>立方阶O(n^3)</li>
<li>k次方阶O(n^K),</li>
<li>指数阶O(2^n)。</li>
</ul>
<h3> 空间复杂度</h3>
<p>空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。</p>
<p>计算方法：</p>
<ol>
<li>忽略常数，用O(1)表示</li>
<li>递归算法的空间复杂度=递归深度N*每次递归所要的辅助空间</li>
<li>对于单线程来说，递归有运行时堆栈，求的是递归最深的那一次压栈所耗费的空间的个数，因为递归最深的那一次所耗费的空间足以容纳它所有递归过程。</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>手写常用排序算法</title>
      <link>https://v-blog.yyshino.top/front_end_interview/0-0%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%AE%97%E6%B3%95/02-%E6%89%8B%E5%86%99%E5%B8%B8%E7%94%A8%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/0-0%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%AE%97%E6%B3%95/02-%E6%89%8B%E5%86%99%E5%B8%B8%E7%94%A8%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">手写常用排序算法</source>
      <description>算法指标</description>
      <category>FrontEnd</category>
      <category>算法</category>
      <pubDate>Wed, 20 Mar 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 冒泡排序</h3>
<p>在未排序区域中，从前向后不断对比相邻数据，将较大值不断向后移动，直至移动到未排序区域的最后一位。</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 选择排序</h3>
<p>遍历未排序区域，通过对比相邻数据，记录较小值的下标，每遍历一遍就能找出未排序区域中最小的数据，将记录的未排序区域的最小值与未排序区域的第一个元素交换位置，该位为有序，未排序区域长度减一，直至完成排序。</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 插入排序</h3>
<p>将第一个数据视为已排序区域，取第一个未排序区域中的数据，从它前一个位置依次向前寻找，直至找到第一个小于等于它的数据，或超出数组范围（已排序区域中没有比它小的值），则将它从未排序区域移除，插入到第一个小于等于它的位置后，或第一位。直至全部排序结束。</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 归并排序</h3>
<p>通过递归将数组不断一分为二，直至每个数组只有一个元素，这时数组即为有序的。在合并时因为两个数组都是有序的，只需比较两个有序数组中的第一个元素，将较小的放入合并后的数组中，即可合并出一个有序数组。</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 快速排序</h3>
<p>从未排序区域中取一个值视为基准值（通常取最中间位置的值），将剩余数据中，小于该值的数据放入leftArr中，将大于等于该值的数据放入rightArr中，此时基准值即为有序。再不断对leftArr和rightArr分别重复该步骤，直至数组中只有一个元素（数组中只有一个元素时为有序数组），将leftArr、基准值、rightArr按顺序合并成一个数组，该数组即为有序。</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 参考</h3>
<p>https://juejin.cn/post/7127636022996762654</p>
<p>https://www.cnblogs.com/pangqianjin/p/14998643.html#:~:text=JavaScript%E6%89%8B%E5%86%99%E5%87%A0%E7%A7%8D%E5%B8%B8%E8%A7%81%E7%9A%84%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%EF%BC%9A%E5%86%92%E6%B3%A1%E3%80%81%E9%80%89%E6%8B%A9%E3%80%81%E6%8F%92%E5%85%A5%E3%80%81%E5%B8%8C%E5%B0%94%E3%80%81%E5%BD%92%E5%B9%B6%E3%80%81%E5%BF%AB%E6%8E%92%201%20%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%202%20%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%203,%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%204%20%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F%205%20%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%206%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F</p>
]]></content:encoded>
    </item>
    <item>
      <title>深度优先遍历</title>
      <link>https://v-blog.yyshino.top/front_end_interview/0-0%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%AE%97%E6%B3%95/05-%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/0-0%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%AE%97%E6%B3%95/05-%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">深度优先遍历</source>
      <description>算法指标</description>
      <category>FrontEnd</category>
      <pubDate>Wed, 20 Mar 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 前序遍历</h3>
<p><strong>根左右</strong></p>
<div class="language-typescript line-numbers-mode" data-ext="ts"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 中序遍历</h3>
<p><strong>左根右</strong></p>
<h4> 递归实现</h4>
<div class="language-typescript line-numbers-mode" data-ext="ts"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 栈实现</h4>
<div class="language-typescript line-numbers-mode" data-ext="ts"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 后续遍历</h3>
<p><strong>左右根</strong></p>
<h4> 递归实现</h4>
<div class="language-typescript line-numbers-mode" data-ext="ts"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 栈实现</h4>
<p>计算根左右的结果，再利用<code>reverse</code>反转得到后续遍历的结果</p>
<div class="language-typescript line-numbers-mode" data-ext="ts"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>广度优先遍历</title>
      <link>https://v-blog.yyshino.top/front_end_interview/0-0%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%AE%97%E6%B3%95/06-%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/0-0%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%AE%97%E6%B3%95/06-%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">广度优先遍历</source>
      <description>算法指标</description>
      <category>FrontEnd</category>
      <pubDate>Wed, 20 Mar 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 广度优先遍历 - 层序遍历</h2>
<p>思路：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>Vue3快速过度到Nuxt3</title>
      <link>https://v-blog.yyshino.top/posts/Vue/Vue3%E5%BF%AB%E9%80%9F%E8%BF%87%E5%BA%A6%E5%88%B0Nuxt3.html</link>
      <guid>https://v-blog.yyshino.top/posts/Vue/Vue3%E5%BF%AB%E9%80%9F%E8%BF%87%E5%BA%A6%E5%88%B0Nuxt3.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Vue3快速过度到Nuxt3</source>
      <description>Vue3快速过度到Nuxt3</description>
      <category>FrontEnd</category>
      <pubDate>Tue, 05 Mar 2024 12:21:47 GMT</pubDate>
      <content:encoded><![CDATA[<h2> Vue3 =&gt; Nuxt3</h2>
<p>vue3 过渡到 Nuxt3</p>
<h3> 语法部分</h3>
<h4> 常用</h4>
<ul>
<li><code>vue3</code> <code>setup</code>已经默认将大部分所需要的钩子导入了，不需要额外导入也能使用</li>
<li><code>router-link</code>: 路由跳转，不会预加载页面 | <code>NuxtLink</code>: 路由跳转，是<code>router-link</code>的封装，会预加载页面。</li>
<li><code>router.push</code> 和 <code>navigateTo</code>, <code>navigateTo</code>会预加载页面，一般需要<code>async</code>异步，比如<code>async navigateTo('/')</code></li>
</ul>
<h4> 坑</h4>
<ul>
<li><code>onBeforeRouteUpdate</code>、<code>onBeforeRouteLeave</code>将不起作用，可能是bug (2023年12月19日22:02:13-setup环境中-可能是我使用方式不对)</li>
<li>函数/组件或，如果涉及到 只有客户端才能访问到的操作如 window对象、dom操作(document、ref)等，函数需要在<code>onMounted</code>/<code>nextTick</code>中调用 | 组件需要使用<code>ClientOnly</code>组件进行包裹</li>
<li>第三方库/插件，如果涉及到 只有客户端才能访问到的操作如 window对象、dom操作(document、ref)等，需要在<code>plugin</code>目录下添加相应的<code>ts</code>文件<code>xxx.client.ts</code>，并在<code>nuxt.config.ts</code>中添加相应的配置如 <code>plugin:[{ src: '~/plugins/xxx.client', mode: 'client' }]</code></li>
<li>如果你需要使用<code>@nuxt/image</code>，请注意<code>public</code>目录和<code>assets</code>目录的区别
<ul>
<li><code>public/</code>包含的文件在根目录中提供，并且不会被构建过程修改。如果您想从服务器上提供资产，请查看"public/"目录。</li>
<li><code>assets/</code>存储在目录中的图像不会使用 Nuxt Image 进行处理，因为这些图像是由 webpack 管理的。</li>
<li>详情请查看https://nuxt.com/docs/guide/directory-structure/public https://image.nuxt.com/get-started/providers#local-images</li>
</ul>
</li>
<li>如果你有同时使用到<code>slot</code>和<code>client-only</code>组件，且<code>slot</code>包裹着<code>client-only</code>组件，需要注意<code>slot</code>会导致<code>client-only</code>组件失效（这个问题我是使用element plus时遇到的，在<code>el-form</code>组件中使用<code>client-only</code>失效，以及在<code>el-skeleton</code> <code>&lt;template #default&gt;&lt;/template&gt;</code>中使用<code>client-only</code>也是失效，因此我怀疑是<code>slot</code>影响的结果）解决也比较简单将<code>client-only</code>移动到使用<code>slot</code>的组件的外层即可</li>
</ul>
<h3> 插件使用/模块使用</h3>
<p>一些常用的库或组件，也许官方会或者一些社区大佬已经完成了对Nuxt3的兼容详细可查看 <a href="https://nuxt.com/modules" target="_blank" rel="noopener noreferrer">https://nuxt.com/modules</a>。如果需要使用一些冷门库或者其他没有对Nuxt3兼容的库，你需要手动实现，比如我使用quill 编辑器，因为它使用到了document，但是这个对象只有在客户端才能访问，当nuxt服务端加载时就会报错：500 document is not defined；</p>
<p><strong>解决方法</strong></p>
<ul>
<li>使用nuxt中的和<a href="https://nuxt.com/docs/api/components/client-only" target="_blank" rel="noopener noreferrer">client-only</a>组件：我们可以在将quill封装为一个组件，在组件中初始化，在我们需要使用到的地方使用<code>client-only</code>组件进行包裹即可。</li>
<li>使用<a href="https://nuxt.com/docs/guide/directory-structure/plugins" target="_blank" rel="noopener noreferrer">plugins</a>文件夹： 可通过创建一个<code>xxx.client.ts</code>，这个文件的资源将在客户端中加载，你可以在这里面引入仅在客户端加载的资源。最后将这个文件添加到<code>nuxt.config.ts</code>中的plugin对象中。</li>
</ul>
<div class="language-typescript line-numbers-mode" data-ext="ts"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 注意</h3>
<ul>
<li>注意时效性，本篇于2024年3月5日完成</li>
<li>未完结待补充</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>github webhooks转发工具</title>
      <link>https://v-blog.yyshino.top/posts/Go/Go-github%20webhooks%E8%BD%AC%E5%8F%91%E5%B7%A5%E5%85%B7.html</link>
      <guid>https://v-blog.yyshino.top/posts/Go/Go-github%20webhooks%E8%BD%AC%E5%8F%91%E5%B7%A5%E5%85%B7.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">github webhooks转发工具</source>
      <description>Golang小工具</description>
      <category>Go</category>
      <pubDate>Thu, 04 Jan 2024 21:41:07 GMT</pubDate>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">github webhooks转发工具</p>
<p>转发github issue和comment 到企业微信机器人</p>
</div>
<!-- more -->
<h2> 准备工作</h2>
<ul>
<li>Golang开发工具</li>
<li>阿里云云函数FC</li>
<li>github webhooks链接以及秘钥（我这里只打开了Issue和comment）</li>
</ul>
<h2> 主要工作（难点）</h2>
<ul>
<li>解析JSON，拼接字符串</li>
<li>云函数踩坑</li>
<li>本地测试比较困难（需要公网ip服务器能够被github那边ping通）</li>
<li>sha256加密解密</li>
</ul>
<h2> 完整代码</h2>
<div class="language-golang line-numbers-mode" data-ext="golang"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 效果预览</h2>
<figure><img src="https://shinoimg.yyshino.top/img/202401041822905.png" alt="image-20240104182247831" tabindex="0" loading="lazy"><figcaption>image-20240104182247831</figcaption></figure>
<h2> 参考链接以及使用工具链接</h2>
<ul>
<li>https://webhook.site/</li>
<li>部署平台阿里云函数 https://fcnext.console.aliyun.com/overview</li>
</ul>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202401041822905.png" type="image/png"/>
    </item>
    <item>
      <title>GLSL</title>
      <link>https://v-blog.yyshino.top/posts/GLSL/01-GLSL%E4%BB%8B%E7%BB%8D.html</link>
      <guid>https://v-blog.yyshino.top/posts/GLSL/01-GLSL%E4%BB%8B%E7%BB%8D.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">GLSL</source>
      <description>GLSL介绍</description>
      <category>图形学</category>
      <pubDate>Mon, 25 Dec 2023 15:04:23 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 介绍</h2>
<p>GLSL 使用标准的 C/C++ 语句集。它有选择语句（if-else和switch-case）、迭代语句（for、while和do-while）和跳转语句（break、continue和return）。这些语句基本上按照 C++ 定义的方式工作（例如，您可以在for语句中声明变量），但也有一些限制。例如，您可以在 C++ 中的 if条件中声明变量，但不能在 GLSL 中声明。</p>
<p>请注意，跳转语句列表中没有goto 。GLSL 没有goto结构。</p>
<p>C/C++函数模型允许函数是递归的。也就是说，函数A可以调用函数B，函数B本身又调用函数A。确实，函数A可以调用<em>自己</em>。显然，必须有一些条件来防止无限递归，但 C/C++ 允许这样做。</p>
<p>GLSL**没有。**GLSL 内存模型不允许递归函数调用。这允许 GLSL 在不允许递归的硬件上执行。它允许 GLSL 在无法任意写入内存时运行，这对大多数着色器硬件都是如此（尽管随着时间的推移它变得不那么真实了）。</p>
<p>所以，在 GLSL 中没有递归。任何形式的。</p>
<h2> 参考</h2>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>浏览器理论知识</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-0%E6%B5%8F%E8%A7%88%E5%99%A8/02-%E6%B5%8F%E8%A7%88%E5%99%A8%E7%90%86%E8%AE%BA%E7%9F%A5%E8%AF%86.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-0%E6%B5%8F%E8%A7%88%E5%99%A8/02-%E6%B5%8F%E8%A7%88%E5%99%A8%E7%90%86%E8%AE%BA%E7%9F%A5%E8%AF%86.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">浏览器理论知识</source>
      <description>浏览器理论知识</description>
      <category>浏览器</category>
      <pubDate>Mon, 18 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 输入 URL 到页面加载显示完成发生了什么</h2>
<ul>
<li>DNS 解析</li>
<li>TCP 连接</li>
<li>发送 HTTP 请求</li>
<li>服务器处理请求并返回 HTTP 报文</li>
<li>浏览器解析渲染页面</li>
<li>连接结束</li>
</ul>
<h1> 跨域</h1>
<p>同源策略</p>
<p>跨域问题其实就是浏览器的同源策略所导致的。</p>
<blockquote>
<p>「同源策略」是一个重要的安全策略，它用于限制一个[origin]的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档，减少可能被攻击的媒介。 --来源 MDN</p>
</blockquote>
<p><strong>「protocol（协议）、domain（域名）、port（端口）三者一致。」</strong> 一致的情况下我们叫同源。</p>
<h1> 一个图片_url访问后直接下载怎样实现</h1>
<p>请求的返回头里面，用于浏览器解析的重要参数就是 OSS 的 API 文档里面的返回 http 头，决定用户下载行为的参数。 下载的情况下：</p>
<div class="language-http line-numbers-mode" data-ext="http"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h1> fetch发送2次请求的原因</h1>
<p>fetch 发送 post 请求的时候，总是发送 2 次，第一次状态码是 204，第二次才成功？</p>
<p>原因很简单，因为你用 fetch 的 post 请求的时候，<strong>导致 fetch 第一次发送了一个 Options 请求</strong>，<strong>询问服务器是否支持修改的请求头，如果服务器支持，则在第二次中发送真正的请求。</strong></p>
<h1> Cookie_sessionStorage_localStorage的区别</h1>
<h2> 共同点：</h2>
<p>都是保存在浏览器端，并且是同源的</p>
<h2> 区别：</h2>
<ul>
<li>cookie 数据始终在同源的 http 请求中携带（即使不需要），即 <strong>cookie 在浏览器 和服务器间来回传递</strong>。</li>
<li><strong>sessionStorage 和 localStorage 不会自动把数据发给服务器，仅 在本地保存</strong></li>
<li>cookie 数据还有<strong>路径（path）的概念</strong>，可以限制 cookie 只属于某个路径下,</li>
</ul>
<p>存储大小限制也不同</p>
<ul>
<li>cookie 数据不能超过 4K，同时因为每次 http 请求都会携带 cookie， 所以 cookie 只适合保存很小的数据，如回话标识。</li>
<li>webStorage 虽然也有存储大小的限制，但是比 cookie 大得多，可以达到 5M 或更大 数据</li>
</ul>
<p>有效期不同</p>
<ul>
<li>sessionStorage：仅在<strong>当前浏览器窗口关闭前有效</strong>，自然也就不可能持久保持，</li>
<li>localStorage： 始终有效，<strong>窗口或浏览器关闭也一直保存，因此用作持久数据</strong>；</li>
<li>cookie：<strong>只在设置的 cookie 过期时间之前一直有效</strong>，即使窗口或浏览器关闭。（key：本身就是一个回话过程，关 闭浏览器后消失，session 为一个回话，当页面不同即使是同一页面打开两次，也被视为 同一次回话）</li>
</ul>
<p>作用域不同</p>
<ul>
<li>sessionStorage：<strong>不在不同的浏览器窗口中共享，即使是同一个页面；</strong></li>
<li>localStorage：在所有同源窗口都是共享的；</li>
<li>cookie：也是在所有同源窗口中共享的</li>
</ul>
<h2> 补充说明一下 cookie 的作用：</h2>
<p><strong>保存用户登录状态</strong>。例如将用户 id 存储于一个 cookie 内，这样当用户下次访问该页面 时就不需要重新登录了，现在很多论坛和社区都提供这样的功能。 cookie 还可以设置 过期时间，当超过时间期限后，cookie 就会自动消失。因此，系统往往可以提示用户保 持登录状态的时间：常见选项有一个月、三个 月、一年等。</p>
<p><strong>跟踪用户行为</strong>。例如一个天气预报网站，能够根据用户选择的地区显示当地的天气情况。 如果每次都需要选择所在地是烦琐的，当利用了 cookie 后就会显得很人性化了，系统能 够记住上一次访问的地区，当下次再打开该页面时，它就会自动显示上次用户所在地区 的天气情况。因为一切都是在后台完成，所以这样的页面就像为某个用户所定制的一样，使用起来非常方便定制页面。如果网站提供了换肤或更换布局的功能，那么可以使 用 cookie 来记录用户的选项，例如：背景色、分辨率等。当用户下次访问时，仍然可以 保存上一次访问的界面风格。</p>
<h1> cookie_session区别</h1>
<ol>
<li>cookie 数据存放在客户的浏览器上，session 数据放在服务器上。</li>
<li>cookie 不是很安全，别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗 考虑到安全应当使用 session。</li>
<li>session 会在一定时间内保存在服务器上。当访问增多，会比较占用你服务器的性能 考虑到减轻服务器性能方面，应当使用 COOKIE。</li>
<li>单个 cookie 保存的数据不能超过 4K，很多浏览器都限制一个站点最多保存 20 个 cookie。</li>
</ol>
<h1> XSS和CRSF攻击防御</h1>
<p>XSS, 即为（Cross Site Scripting）, 中文名为<strong>跨站脚本</strong>, 是发生在目标用户的<strong>浏览器层面 上</strong>的，当渲染 DOM 树的过程成发生了不在预期内执行的 JS 代码时，就发生了 XSS 攻击。 大多数 XSS 攻击的主要方式是<strong>嵌入一段远程或者第三方域上的 JS 代码</strong>。实际上是在目 标网站的作用域下执行了这段 JS 代码</p>
<p>CSRF（Cross Site Request Forgery，<strong>跨站请求伪造</strong>），字面理解意思就是在别的站点伪造 了一个请求。专业术语来说就是<strong>在受害者访问一个网站时，其 Cookie 还没有过期的情 况下，攻击者伪造一个链接地址发送受害者并欺骗让其点击，从而形成 CSRF 攻击</strong></p>
<h2> 防御</h2>
<p>XSS 防御的总体思路是：对<strong>输入(和 URL 参数)进行过滤，对输出进行编码</strong>。也就是对提交的所有内容进行过滤，对 url 中的参数进行过滤，过滤掉会导致脚本执行的相关内容； 然后对动态输出到页面的内容进行 html 编码，使脚本无法在浏览器中执行。虽然对输 入过滤可以被绕过，但是也还是会拦截很大一部分的 XSS 攻击。</p>
<p>防御 CSRF 攻击主要有三种策略：</p>
<ul>
<li>验证 HTTP Referer 字段；</li>
<li>在请求地址中添加 token 并 验证；</li>
<li>在 HTTP 头中自定义属性并验证</li>
</ul>
<h1> Cookie如何防范XSS攻击</h1>
<h2> XSS攻击</h2>
<p>XSS（跨站脚本攻击）是指攻击者在返回的 HTML 中嵌入 javascript 脚本，</p>
<h2> Cookie如何防范XSS攻击</h2>
<p>为了减轻这些攻击，需要在 HTTP 头部配上，<code>set-cookie： httponly-</code>这个属性可以防止 XSS,<strong>它会禁止 javascript 脚本来访问 cookie</strong>。 secure - 这个属性告诉浏览器仅在请求为 https 的时候发送 cookie。 结果应该是这样的：<code>Set-Cookie=&lt;cookie-value&gt;....</code></p>
<h1> 一句话概括RESTFUL</h1>
<p>就是用 URL 定位资源，用 HTTP 描述操作。</p>
]]></content:encoded>
    </item>
    <item>
      <title>介绍页</title>
      <link>https://v-blog.yyshino.top/intro.html</link>
      <guid>https://v-blog.yyshino.top/intro.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">介绍页</source>
      <description>介绍页 将你的个人介绍和档案放置在此处。</description>
      <pubDate>Fri, 15 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 介绍页</h1>
<p>将你的个人介绍和档案放置在此处。</p>
]]></content:encoded>
      <enclosure url="https://v-blog.yyshino.top/assets/images/cover3.jpg" type="image/jpeg"/>
    </item>
    <item>
      <title>Demo</title>
      <link>https://v-blog.yyshino.top/demo/</link>
      <guid>https://v-blog.yyshino.top/demo/</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Demo</source>
      <category>使用指南</category>
      <pubDate>Fri, 15 Dec 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>面试题整合</title>
      <link>https://v-blog.yyshino.top/front_end_interview/</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">面试题整合</source>
      <category>使用指南</category>
      <pubDate>Fri, 15 Dec 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>博客文章</title>
      <link>https://v-blog.yyshino.top/posts/</link>
      <guid>https://v-blog.yyshino.top/posts/</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">博客文章</source>
      <category>使用指南</category>
      <pubDate>Fri, 15 Dec 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>读书笔记</title>
      <link>https://v-blog.yyshino.top/reading_notes/</link>
      <guid>https://v-blog.yyshino.top/reading_notes/</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">读书笔记</source>
      <category>使用指南</category>
      <pubDate>Fri, 15 Dec 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>计算机网络理论知识</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-0%E6%B5%8F%E8%A7%88%E5%99%A8/01-%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E7%90%86%E8%AE%BA%E7%9F%A5%E8%AF%86.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-0%E6%B5%8F%E8%A7%88%E5%99%A8/01-%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E7%90%86%E8%AE%BA%E7%9F%A5%E8%AF%86.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">计算机网络理论知识</source>
      <description>计算机网络理论知识</description>
      <category>浏览器</category>
      <pubDate>Wed, 13 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 说一下HTTP和HTTPS</h1>
<p>HTTPS的SSL加密是在传输层实现的。</p>
<h2> 基本概念</h2>
<ol>
<li>HTTP： 超文本传输协议，是互联网上应用最为广泛的一种网络协议，是一个客户端和服务器端请求和应答的标准（TCP），用于从 WWW 服务器传输超文本到本地浏览器的传输协议，它可以使浏览器更加高效，使网络传输减少。</li>
<li>HTTPS：是以安全为目标的 HTTP 通道，简单讲是 HTTP 的安全版，即 HTTP 下加入 SSL 层，HTTPS 的安全基础是 SSL，因此加密的详细内容就需要 SSL。</li>
<li>HTTPS协议的主要作用：建立一个信息安全通道，来确保数组的传输，确保网站的真实性。</li>
</ol>
<h2> 区别</h2>
<p>HTTP数据都是未加密的，也就是明文的，网景公司设置了 SSL 协议来对 HTTP协议传输的数据进行加密处理，简单来说 HTTPS 协议是由 HTTP 和 SSL 协议构建的可进行加密传输和身份认证的网络协议，比HTTP 协议安全性更高</p>
<h3> 主要区别</h3>
<ol>
<li>HTTPS协议需要ca证书，费用较高</li>
<li>HTTP是超文本传输协议，信息是明文传输，HTTPS 则是具有安全性的 ssl 加密传输协议。</li>
<li>使用不同的链接方式，端口也不同，一般而言，HTTP 协议的端口为 80，HTTPS 的端口为 443</li>
<li>HTTP连接很简单，是无状态的；HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议，比HTTP安全。</li>
</ol>
<h2> 工作原理</h2>
<p>客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤</p>
<ol>
<li>客户端使用https url访问服务器，则要求 web 服务器<strong>建立 ssl 链接</strong>。</li>
<li>web 服务器接收到客户端的请求之后，会将网站的<strong>证书</strong>（证书中包含了公钥），<strong>返回或者说传输给客户端</strong>。</li>
<li>客户端和 web 服务器端开始<strong>协商 SSL 链接的安全等级</strong>，也就是加密等级。</li>
<li>客户端浏览器通过双方协商一致的安全等级，<strong>建立会话密钥</strong>，然后通过网站的公钥来<strong>加密会话密钥</strong>，并<strong>传送给网站</strong>。</li>
<li>web 服务器通过自己的私钥解密出会话密钥。</li>
<li>web 服务器通过会话密钥加密与客户端之间的通信。</li>
</ol>
<h2> HTTPS的优点</h2>
<ol>
<li>使用 HTTPS 协议<strong>可认证用户和服务器</strong>，<strong>确保数据发送到正确的客户机和服务器</strong>；</li>
<li>HTTPS 协议是由 SSL+HTTP 协议构建的可进行<strong>加密传输、身份认证的网络协议</strong>，要比 http 协议安全，<strong>可防止数据在传输过程中不被窃取、改变，确保数据的完整性</strong>。</li>
<li>HTTPS 是现行架构下最安全的解决方案，虽然不是绝对安全，但它大幅增加了中间人攻击的成本。</li>
<li>谷歌曾在 2014 年 8 月份调整搜索引擎算法，并称“比起同等 HTTP 网站，采用 HTTPS 加密的网站在搜索结果中的排名将会更高”。</li>
</ol>
<h2> HTTPS的缺点</h2>
<ol>
<li><strong>https 握手阶段比较费时</strong>，会使页面加载时间延长 50%，增加 10%~20%的耗电。</li>
<li><strong>https 缓存不如 http 高效</strong>，会增加数据开销。 SSL 证书也需要钱，功能越强大的证书费用越高。</li>
<li><strong>SSL 证书需要绑定 IP</strong>，不能再同一个 ip 上绑定多个域名，ipv4 资源支持不了这种消耗。</li>
</ol>
<h1> HTTP 支持的方法</h1>
<ul>
<li>GET</li>
<li>POST</li>
<li>HEAD</li>
<li>OPTIONS</li>
<li>PUT</li>
<li>DELETE</li>
<li>TRACE</li>
<li>CONNECT</li>
</ul>
<h2> Get和Post的区别</h2>
<ul>
<li>get <strong>参数</strong>通过 url 传递，post 放在 request body 中。</li>
<li>get 请求在 url 中传递的<strong>参数</strong>是有长度限制的，而 post 没有。</li>
<li>get 比 post 更<strong>不安全</strong>，因为<strong>参数</strong>直接暴露在 url 中，所以不能用来传递敏感信息。</li>
<li>get 请求只能进行 url <strong>编码</strong>，而 post 支持多种编码方式</li>
<li>get会将数据<strong>缓存</strong>起来，而post不会</li>
<li>get 请求参数会被完整保留在浏览历史记录里，而 post 中的参数不会被保留。</li>
<li>GET 和 POST 本质上就是 TCP 链接，并无差别。但是由于 <strong>HTTP 的规定和浏览器/服务器 的限制</strong>，导致他们在应用过程中体现出一些不同。 GET 产生一个 <strong>TCP 数据包</strong>；POST 产生两个 TCP 数据包。</li>
</ul>
<h2> HTTP请求的方式_HEAD方式</h2>
<p>head：类似于 get 请求，只不过返回的响应中没有具体的内容，用户获取报头 options：允许客户端查看服务器的性能，比如说服务器支持的请求方式等等。</p>
<h1> HTTP2.0</h1>
<p>简要概括：http2.0 是基于 1999 年发布的 http1.0 之后的首次更新</p>
<ul>
<li><strong>提升访问速度</strong>（可以对于，请求资源所需时间更少，访问速度更快，相比 http1.0）</li>
<li><strong>允许多路复用</strong>：多路复用允许同时通过单一的 HTTP/2 连接发送多重请求-响应信息。</li>
<li>改善了：<strong>在 http1.1 中，浏览器客户端在同一时间，针对同一域名下的请求有一定数量限制（连接数量），超过限制会被阻塞</strong>。</li>
<li><strong>二进制分帧</strong>：HTTP2.0 会将所有的传输信息分割为更小的信息或者帧，并对他们进行二进制编码首部压缩服务器端推送</li>
</ul>
<h1> HTTP常用请求头</h1>
<table>
<thead>
<tr>
<th>协议头</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>Accept</td>
<td>可接受的响应内容类型（Content-Types）</td>
</tr>
<tr>
<td>Accept-Charset</td>
<td>可接受的字符集</td>
</tr>
<tr>
<td>Accept-Encoding</td>
<td>可接受的响应内容的编码方式</td>
</tr>
<tr>
<td>Accept-Language</td>
<td>可接受的响应内容语言列表</td>
</tr>
<tr>
<td>Accept-Datetime</td>
<td>可接受的按照时间来表示的响应内容版本</td>
</tr>
<tr>
<td>Authorization</td>
<td>用于表示 HTTP 协议中需要认证资源的认证信息</td>
</tr>
<tr>
<td>Cache-Control</td>
<td>用来指定当前的请求/回复中的，是否使用缓存机制。</td>
</tr>
<tr>
<td>Connection</td>
<td>客户端（浏览器）想要优先使用的连接类型</td>
</tr>
<tr>
<td>Cookie</td>
<td>由之前服务器通过Set-Cookie（见下文）设置的一个HTTP协议Cookie</td>
</tr>
<tr>
<td>Content-Length</td>
<td>以 8 进制表示的请求体的长度</td>
</tr>
<tr>
<td>Content-MD5</td>
<td>请求体的内容的二进制 MD5 散列值（数字签名），以 Base64 编码的结果</td>
</tr>
<tr>
<td>Content-Type</td>
<td>请求体的 MIME 类型 （用于 POST 和 PUT 请求</td>
</tr>
<tr>
<td>Date</td>
<td>发送该消息的日期和时间（以 RFC 7231 中定义的"HTTP 日期"格式 来发送）</td>
</tr>
<tr>
<td>Expect</td>
<td>表示客户端要求服务器做出特定的行为</td>
</tr>
<tr>
<td>Form</td>
<td>发起此请求的用户的邮件地址</td>
</tr>
<tr>
<td>Host</td>
<td>表示服务器的域名以及服务器所监听的端口号。如果所请求的端口 是对应的服务的标准端口（80），则端口号可以省略。</td>
</tr>
<tr>
<td>If-Match</td>
<td>仅当客户端提供的实体与服务器上对应的实体相匹配时，才进行对 应的操作。主要用于像 PUT 这样的方法中，仅当从用户上次更新 某个资源后，该资源未被修改的情况下，才更新该资源。</td>
</tr>
<tr>
<td>If-Modified-Since</td>
<td>允许在对应的资源未被修改的情况下返回 304 未修改</td>
</tr>
<tr>
<td>If-None-Match</td>
<td>允许在对应的内容未被修改的情况下返回 304 未修改（ 304 Not Modified ），参考 超文本传输协议 的实体标</td>
</tr>
<tr>
<td>If-Range</td>
<td>如果该实体未被修改过，则向返回所缺少的那一个或多个部分。否 则，返回整个新的实体</td>
</tr>
<tr>
<td>If-Unmodified-Since</td>
<td>仅当该实体自某个特定时间以来未被修改的情况下，才发送回应。</td>
</tr>
<tr>
<td>Max-Forwards</td>
<td>限制该消息可被代理及网关转发的次数。</td>
</tr>
<tr>
<td>Origin</td>
<td>发起一个针对跨域资源共享的请求（该请求要求服务器在响应中加 入一个 Access-Control-Allow-Origin 的消息头，表示访问控制所允许 的来源）。</td>
</tr>
<tr>
<td>Pragma</td>
<td>与具体的实现相关，这些字段可能在请求/回应链中的任何时候产 生。</td>
</tr>
<tr>
<td>Proxy-Authorization</td>
<td>用于向代理进行认证的认证信息。</td>
</tr>
<tr>
<td>Range</td>
<td>表示请求某个实体的一部分，字节偏移以 0 开始。</td>
</tr>
<tr>
<td>Referer</td>
<td>表示浏览器所访问的前一个页面，可以认为是之前访问页面的链接 将浏览器带到了当前页面。Referer 其实是 Referrer 这个单词，但 RFC 制作标准时给拼错了，后来也就将错就错使用 Referer 了。</td>
</tr>
<tr>
<td>TE</td>
<td>浏览器预期接受的传输时的编码方式：可使用回应协议头 Transfer-Encoding 中的值（还可以使用"trailers"表示数据传输时的分 块方式）用来表示浏览器希望在最后一个大小为 0 的块之后还接收 到一些额外的字段。</td>
</tr>
<tr>
<td>User-Agent</td>
<td>浏览器的身份标识字符串</td>
</tr>
<tr>
<td>Upgrade</td>
<td>要求服务器升级到一个高版本协议。</td>
</tr>
<tr>
<td>Via</td>
<td>告诉服务器，这个请求是由哪些代理发出的。</td>
</tr>
<tr>
<td>Warning</td>
<td>一个一般性的警告，表示在实体内容体中可能存在错误。</td>
</tr>
</tbody>
</table>
<h1> HTTP返回码</h1>
<h2> 1XX</h2>
<table>
<thead>
<tr>
<th>返回码</th>
<th>状态码英文</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>100</td>
<td>Continue</td>
<td>继续。客户端应继续其请求</td>
</tr>
<tr>
<td>101</td>
<td>Switching Protocols</td>
<td>切换协议。服务器根据客户端的请求切换协议。只能切换到更 高级的协议，例如，切换到 HT</td>
</tr>
</tbody>
</table>
<h2> 2XX</h2>
<table>
<thead>
<tr>
<th>返回码</th>
<th>状态码英文</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>200</td>
<td>OK</td>
<td>请求成功。一般用于 GET 与 POST 请求</td>
</tr>
<tr>
<td>201</td>
<td>Created</td>
<td>创建。成功请求并创建了新的资源</td>
</tr>
<tr>
<td>202</td>
<td>Accepted</td>
<td>已接受。已经接受请求，但未处理完成</td>
</tr>
<tr>
<td>203</td>
<td>Non-Authoritative Information</td>
<td>非授权信息。请求成功。但返回的 meta 信息不在原 始的服务器，而是一个副本</td>
</tr>
<tr>
<td>204</td>
<td>No Content</td>
<td>无内容。服务器成功处理，但未返回内容。在未更新网页的情况下， 可确保浏览器继续显示当前文档</td>
</tr>
<tr>
<td>205</td>
<td>Reset Content</td>
<td>重置内容。服务器处理成功，用户终端（例如：浏览器）应重置文 档视图。可通过此返回码清除浏览器的表单域</td>
</tr>
<tr>
<td>206</td>
<td>Partial Content</td>
<td>部分内容。服务器成功处理了部分 GET 请求</td>
</tr>
</tbody>
</table>
<h2> 3XX</h2>
<table>
<thead>
<tr>
<th>返回码</th>
<th>状态码英文</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>300</td>
<td>Multiple Choices</td>
<td>多种选择。请求的资源可包括多个位置，相应可返回一个资源特 征与地址的列表用于用户终端（例如：浏览器）选择</td>
</tr>
<tr>
<td>301</td>
<td>Moved Permanently</td>
<td>永久移动。请求的资源已被永久的移动到新 URI，返回信息会 包括新的 URI，浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI 代替</td>
</tr>
<tr>
<td>302</td>
<td>Found</td>
<td>临时移动。与 301 类似。但资源只是临时被移动。客户端应继续使用原有 URI</td>
</tr>
<tr>
<td>303</td>
<td>See Other</td>
<td>查看其它地址。与 301 类似。使用 GET 和 POST 请求查看</td>
</tr>
<tr>
<td>304</td>
<td>Not Modified</td>
<td>未修改。所请求的资源未修改，服务器返回此状态码时，不会返回 任何资源。客户端通常会缓存访问过的资源，通过提供一个头信息指出客户端希望只返 回在指定日期之后修改的资源</td>
</tr>
<tr>
<td>305</td>
<td>Use Proxy</td>
<td>使用代理。所请求的资源必须通过代理访问</td>
</tr>
<tr>
<td>306</td>
<td>Unused</td>
<td>已经废弃的HTPP状态码</td>
</tr>
<tr>
<td>307</td>
<td>Temporaty Redirrect</td>
<td>临时重定向。与 302 类似。使用 GET 请求重定向 400 Bad Request 客户端请求的语法错误，服务器无法理解</td>
</tr>
</tbody>
</table>
<h2> 4XX</h2>
<table>
<thead>
<tr>
<th>返回码</th>
<th>状态码英文</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>400</td>
<td>Bad Request</td>
<td>客户端请求的语法错误，服务器无法理解</td>
</tr>
<tr>
<td>401</td>
<td>Unauthorized</td>
<td>请求要求用户的身份认证</td>
</tr>
<tr>
<td>402</td>
<td>Payment Required</td>
<td>保留，将来使用</td>
</tr>
<tr>
<td>403</td>
<td>Forbidden</td>
<td>服务器理解请求客户端的请求，但是拒绝执行此请求</td>
</tr>
<tr>
<td>404</td>
<td>Not Found</td>
<td>服务器无法根据客户端的请求找到资源（网页）。通过此代码，网站 设计人员可设置"您所请求的资源无法找到"的个性页面</td>
</tr>
<tr>
<td>405</td>
<td>Method Not Allowed</td>
<td>客户端请求中的方法被禁止</td>
</tr>
<tr>
<td>406</td>
<td>Not Acceptable</td>
<td>服务器无法根据客户端请求的内容特性完成请求</td>
</tr>
<tr>
<td>407</td>
<td>Proxy Authenticaltion</td>
<td>请求要求代理的身份认证，与 401 类似，但请求者 应当使用代理进行授权</td>
</tr>
<tr>
<td>408</td>
<td>Request Time-out</td>
<td>服务器等待客户端发送的请求时间过长，超时</td>
</tr>
<tr>
<td>409</td>
<td>Conflict</td>
<td>服务器完成客户端的 PUT 请求是可能返回此代码，服务器处理请求时发 生了冲突</td>
</tr>
<tr>
<td>410</td>
<td>Gone</td>
<td>客户端请求的资源已经不存在。410 不同于 404，如果资源以前有现在被永 久删除了可使用 410 代码，网站设计人员可通过 301 代码指定资源的新位置</td>
</tr>
<tr>
<td>411</td>
<td>Length Required</td>
<td>服务器无法处理客户端发送的不带 Content-Length 的请求信</td>
</tr>
<tr>
<td>412</td>
<td>Precondition Failed</td>
<td>客户端请求信息的先决条件错误</td>
</tr>
<tr>
<td>413</td>
<td>Request Entity Too Large</td>
<td>由于请求的实体过大，服务器无法处理，因此拒绝请求。 为防止客户端的连续请求，服务器可能会关闭连接。如果只是服务器暂时无法处理，则 会包含一个 Retry-After</td>
</tr>
<tr>
<td>414</td>
<td>Request-URI Too Large</td>
<td>请求URL过长（URL通常为网址），服务器无法处理</td>
</tr>
<tr>
<td>415</td>
<td>Unsupported Media Type</td>
<td>服务器无法处理请求附带的媒体格式</td>
</tr>
<tr>
<td>416</td>
<td>Requested range not satisfiable</td>
<td>客户端请求的范围无效</td>
</tr>
<tr>
<td>417</td>
<td>Expectation Failed</td>
<td>服务器不支持请求的功能，无法完成请求</td>
</tr>
</tbody>
</table>
<h2> 5XX</h2>
<table>
<thead>
<tr>
<th>返回码</th>
<th>状态码英文</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>500</td>
<td>Internal Server Error</td>
<td>服务器内部错误，无法完成请求</td>
</tr>
<tr>
<td>501</td>
<td>Not Implemented</td>
<td>服务器不支持请求的功能，无法完成请求</td>
</tr>
<tr>
<td>502</td>
<td>Bad GateWay</td>
<td>作为网关或者代理工作的服务器尝试执行请求时，从远程服务器接到一个无效的响应</td>
</tr>
<tr>
<td>503</td>
<td>Service Unavailable</td>
<td>由于超载或系统维护，服务器暂时的无法处理客户端的请求。 延时的长度可包含在服务器的 Retry-After 头信息中</td>
</tr>
<tr>
<td>504</td>
<td>Gateway Time-out</td>
<td>充当网关或代理的服务器，未及时从远端服务器获取请求</td>
</tr>
<tr>
<td>505</td>
<td>HTTP Version not supported</td>
<td>服务器不支持请求的 HTTP 协议的版本，无法完成处 理</td>
</tr>
</tbody>
</table>
<h2> 对比 304 和 200</h2>
<p>状态码 304：如果客户端发送了一个带条件的 GET 请求且该请求已被允许，而文档的 内容（自上次访问以来或者根据请求的条件）并没有改变，则服务器应当返回这个状态 码。即客户端和服务器端只需要传输很少的数据量来做文件的校验，如果文件没有修改 过，则不需要返回全量的数据。</p>
<p>状态码 200：请求已成功，请求所希望的响应头或数据体将随此响应返回。即返回的数 据为全量的数据，如果文件不通过 GZIP 压缩的话，文件是多大，则要有多大传输量。</p>
<h2> 301和302的区别</h2>
<ul>
<li>301 Moved Permanently 被请求的资源<strong>已永久移动到新位置</strong>，并且将来任何对此资源的引 用都应该使用本响应返回的若干个 URI 之一。如果可能，拥有链接编辑功能的客户端应 当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定，否则这个响应也是可缓存的。</li>
<li>302 Found 请求的资源现在<strong>临时</strong>从不同的 URI 响应请求。由于这样的重定向是临时的， 客户端应当继续向原有地址发送以后的请求。只有在 Cache-Control 或 Expires 中进行了指定的情况下，这个响应才是可缓存的</li>
</ul>
<p>字面上的区别就是 301 是永久重定向，而 302 是临时重定向。 301 比较常用的场景是使用域名跳转。302 用来做临时跳转 比如未登陆的用户访问用户 中心重定向到登录页面。</p>
<h1> HTTP强缓存和协商缓存</h1>
<p>缓存分为两种：强缓存和协商缓存（弱缓存），根据响应的 header 内容来决定</p>
<table>
<thead>
<tr>
<th></th>
<th>获取资源的形式</th>
<th>状态码</th>
<th>发送请求到服务器</th>
</tr>
</thead>
<tbody>
<tr>
<td>强缓存</td>
<td>从缓存取</td>
<td>200（from cache）</td>
<td>否，直接从缓存取</td>
</tr>
<tr>
<td>协商缓存</td>
<td>从缓存取</td>
<td>304（not modified）</td>
<td>是通过服务器来告知缓存是否可用</td>
</tr>
</tbody>
</table>
<ul>
<li>强缓存相关字段有 <code>expires</code>，<code>cache-control</code>。如果 cache-control 与 expires 同时存在的话， cache-control 的优先级高于 expires</li>
<li>协商缓存相关字段有 <code>Last-Modified/If-Modified-Since</code>，<code>Etag/If-None-Match</code></li>
</ul>
<h2> 强缓存、协商缓存什么时候用哪个</h2>
<p>因为服务器上的资源不是一直固定不变的，大多数情况下它会更新，这个时候如果我们 还访问本地缓存，那么对用户来说，那就相当于资源没有更新，用户看到的还是旧的资 源；所以我们希望服务器上的资源更新了浏览器就请求新的资源，没有更新就使用本地 的缓存，以最大程度的减少因网络请求而产生的资源浪费。</p>
<h2> 浏览器缓存</h2>
<p>浏览器缓存是浏览器在本地磁盘对用户最近请求过的文档进行存储，当访问者再次访问同一页面时，浏览器就可以直接从本地磁盘加载文档。</p>
<p>所以根据上面的特点，浏览器缓存有下面的优点：</p>
<ol>
<li>减少冗余的数据传输</li>
<li>减少服务器负担</li>
<li>加快客户端加载网页的速度</li>
</ol>
<p>浏览器缓存是Web性能优化的重要方式。那么浏览器缓存的过程究竟是怎么样的呢？</p>
<p>在浏览器第一次发起请求时，本地无缓存，向web服务器发送请求，服务器起端响应请求，浏览器端缓存。过程如下：</p>
<figure><img src="https://segmentfault.com/img/bVCrP5?w=411&amp;h=369" alt="浏览器第一次请求" tabindex="0" loading="lazy"><figcaption>浏览器第一次请求</figcaption></figure>
<p>在第一次请求时，服务器会将页面最后修改时间通过<code>Last-Modified</code>标识由服务器发送给客户端，客户端记录修改时间；服务器还会生成一个Etag，并发送给客户端。</p>
<p>浏览器后续再次进行请求时：</p>
<figure><img src="https://segmentfault.com/img/bVuuo2" alt="强缓存、协商缓存" tabindex="0" loading="lazy"><figcaption>强缓存、协商缓存</figcaption></figure>
<p>浏览器缓存主要分为强<strong>强缓存</strong>（也称<strong>本地缓存</strong>）和<strong>协商缓存</strong>（也称<strong>弱缓存</strong>）。根据上图，浏览器在第一次请求发生后，再次发送请求时：</p>
<ul>
<li>浏览器请求某一资源时，会先获取该资源缓存的header信息，然后根据header中的<code>Cache-Control</code>和<code>Expires</code>来判断是否过期。若没过期则直接从缓存中获取资源信息，包括缓存的header的信息，所以此次请求不会与服务器进行通信。这里判断是否过期，则是强缓存相关。后面会讲<code>Cache-Control</code>和<code>Expires</code>相关。</li>
<li>如果显示已过期，浏览器会向服务器端发送请求，这个请求会携带第一次请求返回的有关缓存的header字段信息，比如客户端会通过<code>If-None-Match</code>头将先前服务器端发送过来的Etag发送给服务器，服务会对比这个客户端发过来的Etag是否与服务器的相同，若相同，就将<code>If-None-Match</code>的值设为false，返回状态304，客户端继续使用本地缓存，不解析服务器端发回来的数据，若不相同就将<code>If-None-Match</code>的值设为true，返回状态为200，客户端重新机械服务器端返回的数据；客户端还会通过<code>If-Modified-Since</code>头将先前服务器端发过来的最后修改时间戳发送给服务器，服务器端通过这个时间戳判断客户端的页面是否是最新的，如果不是最新的，则返回最新的内容，如果是最新的，则返回304，客户端继续使用本地缓存。</li>
</ul>
<p>::: Tips
以下引用自思否
作者：<a href="https://segmentfault.com/u/puhongru" target="_blank" rel="noopener noreferrer"><strong>puhongru</strong></a>
文章：<a href="https://segmentfault.com/a/1190000008956069" target="_blank" rel="noopener noreferrer">HTTP强缓存和协商缓存 - JavaScript学习笔记 - SegmentFault 思否</a>
:::</p>
<h2> 强缓存</h2>
<p>强缓存是利用http头中的<code>Expires</code>和<code>Cache-Control</code>两个字段来控制的，用来表示资源的缓存时间。强缓存中，普通刷新会忽略它，但不会清除它，需要强制刷新。浏览器强制刷新，请求会带上<code>Cache-Control:no-cache</code>和<code>Pragma:no-cache</code></p>
<h3> Expires</h3>
<p><code>Expires</code>是http1.0的规范，它的值是一个绝对时间的GMT格式的时间字符串。如我现在这个网页的<code>Expires</code>值是：<code>expires:Fri, 14 Apr 2017 10:47:02 GMT</code>。这个时间代表这这个资源的失效时间，只要发送请求时间是在<code>Expires</code>之前，那么本地缓存始终有效，则在缓存中读取数据。所以这种方式有一个明显的缺点，由于失效的时间是一个绝对时间，所以当服务器与客户端时间偏差较大时，就会导致缓存混乱。如果同时出现<code>Cache-Control:max-age</code>和<code>Expires</code>，那么<code>max-age</code>优先级更高。如我主页的response headers部分如下：</p>
<div class="language-http line-numbers-mode" data-ext="http"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>那么表示资源可以被缓存的最长时间为691200秒，会优先考虑<code>max-age</code>。</p>
<h3> Cache-Control</h3>
<p>Cache-Control是在http1.1中出现的，主要是利用该字段的max-age值来进行判断，它是一个相对时间，例如Cache-Control:max-age=3600，代表着资源的有效期是3600秒。cache-control除了该字段外，还有下面几个比较常用的设置值：</p>
<ul>
<li>no-cache：不使用本地缓存。需要使用缓存协商，先与服务器确认返回的响应是否被更改，如果之前的响应中存在ETag，那么请求的时候会与服务端验证，如果资源未被更改，则可以避免重新下载。</li>
<li>no-store：直接禁止游览器缓存数据，每次用户请求该资源，都会向服务器发送一个请求，每次都会下载完整的资源。</li>
<li>public：可以被所有的用户缓存，包括终端用户和CDN等中间代理服务器。</li>
<li>private：只能被终端用户的浏览器缓存，不允许CDN等中继缓存服务器对其缓存。
Cache-Control与Expires可以在服务端配置同时启用，同时启用的时候Cache-Control优先级高。</li>
</ul>
<h2> 协商缓存</h2>
<p>协商缓存就是由服务器来确定缓存资源是否可用，所以客户端与服务器端要通过某种标识来进行通信，从而让服务器判断请求资源是否可以缓存访问。</p>
<p><strong>普通刷新会启用弱缓存，忽略强缓存。只有在地址栏或收藏夹输入网址、通过链接引用资源等情况下，浏览器才会启用强缓存</strong>，这也是为什么有时候我们更新一张图片、一个js文件，页面内容依然是旧的，但是直接浏览器访问那个图片或文件，看到的内容却是新的。</p>
<p>这个主要涉及到两组header字段：<code>Etag</code>和<code>If-None-Match</code>、<code>Last-Modified</code>和<code>If-Modified-Since</code>。上面以及说得很清楚这两组怎么使用啦~复习一下：</p>
<h3> <code>Etag</code>和<code>If-None-Match</code></h3>
<p>Etag/If-None-Match返回的是一个校验码。ETag可以保证每一个资源是唯一的，资源变化都会导致ETag变化。服务器根据浏览器上送的If-None-Match值来判断是否命中缓存。</p>
<p>与Last-Modified不一样的是，当服务器返回304 Not Modified的响应时，<strong>由于ETag重新生成过，response header中还会把这个ETag返回，即使这个ETag跟之前的没有变化。</strong></p>
<h3> Last-Modify/If-Modify-Since</h3>
<p>浏览器第一次请求一个资源的时候，服务器返回的header中会加上Last-Modify，Last-modify是一个时间标识该资源的最后修改时间，例如Last-Modify: Thu,31 Dec 2037 23:59:59 GMT。</p>
<p>当浏览器再次请求该资源时，request的请求头中会包含If-Modify-Since，该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后，根据资源的最后修改时间判断是否命中缓存。</p>
<p>如果命中缓存，则返回304，并且不会返回资源内容，并且不会返回Last-Modify。</p>
<h3> 为什么要有Etag</h3>
<p>你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新，为什么还需要Etag呢？HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题：</p>
<ul>
<li>一些文件也许会周期性的更改，但是他的内容并不改变(仅仅改变的修改时间)，这个时候我们并不希望客户端认为这个文件被修改了，而重新GET；</li>
<li>某些文件修改非常频繁，比如在秒以下的时间内进行修改，(比方说1s内修改了N次)，If-Modified-Since能检查到的粒度是s级的，这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)；</li>
<li>某些服务器不能精确的得到文件的最后修改时间。</li>
</ul>
<p>Last-Modified与ETag是可以一起使用的，<strong>服务器会优先验证ETag</strong>，一致的情况下，才会继续比对Last-Modified，最后才决定是否返回304。</p>
<p>另外觉得一篇很好的文章，从缓存策略来学习HTTP缓存：<a href="https://link.segmentfault.com/?enc=SIoGbBWMLF33fwouI6ieZw%3D%3D.fBnr74UTKxhy8iA5UURSt4%2ByqjDYTGZrlTkEnCLISdMs%2FObOZlQ4cyDSu2J%2B1aVQ" target="_blank" rel="noopener noreferrer">HTTP基于缓存策略三要素分解法</a></p>
<h1> TCP三次握手</h1>
<h2> TCP三次握手</h2>
<p>客户端和服务端都需要直到各自可收发，因此需要三次握手。</p>
<figure><img src="https://shinoimg.yyshino.top/img/202304231122523.png" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<p>从图片可以得到三次握手可以简化为：<strong>C 发起请求连接</strong> | <strong>S 确认，发起连接</strong> | <strong>C 确认</strong></p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 三次握手的作用</h2>
<h3> 过程</h3>
<ol>
<li>第一次握手：客户端主动链接服务器，发送初始序列号<code>seq=x</code>与<code>SYN=1</code>同步请求标志，并进入同步已发送<code>SYN_SENT</code>状态，等待服务器确认。</li>
<li>第二次握手：服务端收到消息后发送确认标志<code>ACK=1</code>与同步请求标志<code>SYN=1</code>，发送自己的序列号<code>seq=y</code>以及客户端确认序号<code>ack=x+1</code>，此时服务器进入同步收到<code>SYN_RECV</code>状态。</li>
<li>第三次握手：客户端收到消息后发送确认标志<code>ACK=1</code>，发送自己的序列号<code>seq=x+1</code>与服务器确认号<code>ack=y+1</code>，发送过后即确认链接已建立状态<code>ESTABLISHED</code>，服务端接收确认信息后进入链接已建立状态<code>ESTABLISHED</code></li>
</ol>
<h3> 解释</h3>
<ol>
<li>第一次握手：客户端：“兄弟，待会咱们出去玩吧，能看到我的消息吗，能就吱一声，让我知道我有发消息的能力”</li>
<li>第二次握手：服务端：“吱，走走走咱们去哪玩？我收到你的消息了，你有发消息的能力，要不你再给我回个消息，让我也确定我有发消息的能力”</li>
<li>第三次握手：客户端：“咱们先去河里摸鱼玩，然后上山摘点果子。我也收到你的消息了，你这发消息的能力也没问题，咱俩的发消息的能力都没问题，可以愉快的玩耍了”</li>
</ol>
<h2> 四次挥手</h2>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol>
<li>第一次挥手：客户端发出释放标识<code>FIN=1</code>，自己的序列号<code>seq=u</code>，进入终止等待<code>FIN-WAIT-1</code>状态</li>
<li>第二次挥手：服务端收到消息后发出<code>ACK=1</code>确认标志和客户端的确认号<code>ack=u+1</code>，自己的序列号<code>seq=v</code>，进入关闭等待<code>CLOSE-WAIT</code>状态，客户端收到消息后进入终止等待<code>FIN-WAIT-2</code>状态</li>
<li>第三次挥手：服务器发送释放标识<code>FIN=1</code>信号，确认标志<code>ACK=1</code>，确认序号<code>ack=u+1</code>，自己的序列号<code>seq=w</code>，服务器进入最后确认<code>LAST-ACK</code>状态</li>
<li>第四次挥手：客户端收到回复后，发送确认标志<code>ACK=1</code>，确认序号<code>ack=w+1</code>，自己的序列号<code>seq=u+1</code>，客户端进入时间等待<code>TIME-WAIT</code>状态，经过<code>2</code>个最长报文段寿命后，客户端<code>CLOSE</code>。服务器收到确认后，立刻进入<code>CLOSE</code>状态。</li>
</ol>
<h3> 参考</h3>
<p><a href="https://blog.touchczy.top/#/Browser/TCP%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B" target="_blank" rel="noopener noreferrer">TCP三次握手 (touchczy.top)</a></p>
<h1> TCP和UDP的区别</h1>
<ol>
<li>TCP 是<strong>面向连接</strong>的，udp 是<strong>无连接</strong>的即发送数据前不需要先建立链接。</li>
<li>TCP <strong>提供可靠的服务</strong>。也就是说，通过 TCP 连接传送的数据，无差错，不丢失， 不重复，且按序到达;UDP <strong>尽最大努力交付，即不保证可靠交付</strong>。 并且因为 tcp 可靠， 面向连接，不会丢失数据因此适合大数据量的交换。</li>
<li>TCP 是<strong>面向字节流</strong>，UDP <strong>面向报文，并且网络出现拥塞不会使得发送速率降低</strong>（因此会出现丢包，对实时的应用比如 IP 电话和视频会议等）。</li>
<li>TCP 只能是 <strong>1 对 1 的</strong>，UDP 支持 <strong>1 对 1,1 对多</strong>。</li>
<li>TCP 的<strong>首部较大为 20 字节</strong>，而 UDP 只有 <strong>8 字节</strong>。</li>
<li>TCP 是<strong>面向连接的可靠性传输</strong>，而 UDP <strong>是不可靠的</strong>。</li>
</ol>
<h1> WebSocket的实现和应用</h1>
<h2> 什么是WebSocket</h2>
<p>WebSocket 是HTML5的协议，支持持久性连接，http 协议不支持持久性连接。Http1.0 和 HTTP1.1 都不支持持久性的链接，HTTP1.1 中的 keep-alive，将多个 http 请求合并为 1 个</p>
<h2> WebSocket 是什么样的协议，具体有什么优点？</h2>
<p>HTTP 的生命周期通过 Request 来界定，也就是 Request 一个 Response，那么在 Http1.0 协议中，这次 Http 请求就结束了。在 Http1.1 中进行了改进，是的有一个 connection： Keep-alive，也就是说，在一个 Http 连接中，可以发送多个 Request，接收多个 Response。 但是必须记住，在 Http 中一个 Request 只能对应有一个 Response，而且这个 Response 是被动的，不能主动发起。</p>
<p><strong>WebSocket 是基于 Http 协议的，或者说借用了 Http 协议来完成一部分握手，在握手阶段 与 Http 是相同的。我们来看一个 websocket 握手协议的实现</strong>，基本是 2 个属性，upgrade， connection。</p>
<div class="language-http line-numbers-mode" data-ext="http"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://segmentfault.com/img/bVCrP5?w=411&amp;h=369" type="image/"/>
    </item>
    <item>
      <title>SEO优化</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-0.5%E4%BC%98%E5%8C%96/02-SEO%E4%BC%98%E5%8C%96.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-0.5%E4%BC%98%E5%8C%96/02-SEO%E4%BC%98%E5%8C%96.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">SEO优化</source>
      <description>SEO优化 meta元信息处理 html头部中 &amp;lt;title&amp;gt;&amp;lt;/title&amp;gt; &amp;lt;meta name=&amp;quot;keywords&amp;quot; content=&amp;quot;&amp;quot; /&amp;gt; &amp;lt;meta name=&amp;quot;description&amp;quot; content=&amp;quot;&amp;quot; /&amp;gt;</description>
      <category>FrontEnd</category>
      <pubDate>Tue, 12 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> SEO优化</h2>
<h3> meta元信息处理</h3>
<p>html头部中</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Vue-Router中</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>UED优化</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-0.5%E4%BC%98%E5%8C%96/01-%E4%BC%98%E5%8C%96.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-0.5%E4%BC%98%E5%8C%96/01-%E4%BC%98%E5%8C%96.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">UED优化</source>
      <description>01-UED优化 常见名词介绍 UI： User Interface 用户界面 UID： User Interface Design 用户界面设计 ID：Interaction design 交互设计 UE or UX： User Experience 用户体验 UED： User Experience Design 用户体验设计</description>
      <category>FrontEnd</category>
      <pubDate>Mon, 11 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>01-UED优化
常见名词介绍</p>
<ul>
<li>
<p><strong>UI： User Interface 用户界面</strong></p>
</li>
<li>
<p><strong>UID： User Interface Design 用户界面设计</strong></p>
</li>
<li>
<p><strong>ID：Interaction design 交互设计</strong></p>
</li>
<li>
<p><strong>UE or UX： User Experience 用户体验</strong></p>
</li>
<li>
<p><strong>UED： User Experience Design 用户体验设计</strong></p>
</li>
</ul>
<p>UED优化</p>
<ul>
<li>优化首屏加载
<ul>
<li>骨架屏</li>
<li>SSR服务端渲染</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>堆栈</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/14-%E5%A0%86%E6%A0%88.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/14-%E5%A0%86%E6%A0%88.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">堆栈</source>
      <description>堆栈的区别 栈（操作系统）：由操作系统自动分配释放存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈（ 简单数据类型存放到栈里面 ） 堆（操作系统）：存储复杂类型(对象)，一般由程序员分配释放，若程序员不释放，由垃圾回收机制回收（ 复杂数据类型存放到堆里面）</description>
      <category>FrontEnd</category>
      <pubDate>Tue, 05 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 堆栈的区别</h3>
<ol>
<li><strong>栈（操作系统）</strong>：由<strong>操作系统自动分配释放</strong>存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈（ 简单数据类型存放到栈里面 ）</li>
<li><strong>堆（操作系统）</strong>：存储复杂类型(对象)，<strong>一般由程序员分配释放，若程序员不释放，由垃圾回收机制回收</strong>（ 复杂数据类型存放到堆里面）</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>闭包陷阱</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-6React/04-%E9%97%AD%E5%8C%85%E9%99%B7%E9%98%B1.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-6React/04-%E9%97%AD%E5%8C%85%E9%99%B7%E9%98%B1.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">闭包陷阱</source>
      <description>闭包陷阱 当异步函数获取 state 时，可能不是当前最新的 state 可使用 useRef 来解决 （要提前了解JS 闭包） 深入原理 useState 产生的数据也是 Immutable 的，通过数组第二个参数 Set 一个新值后，原来的值会形成一个新的引用在下次渲染时。 但由于对 state 的读取没有通过 this. 的方式，使得 每次 setTimeout 都读取了当时渲染闭包环境的数据，虽然最新的值跟着最新的渲染变了，但旧的渲染里，状态依然是旧值。</description>
      <category>FrontEnd</category>
      <pubDate>Tue, 05 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 闭包陷阱</h3>
<ul>
<li>当异步函数获取 state 时，可能不是当前最新的 state</li>
<li>可使用 useRef 来解决</li>
<li>（要提前了解JS 闭包）</li>
</ul>
<h3> 深入原理</h3>
<ol>
<li><code>useState</code> 产生的数据也是 Immutable 的，通过数组第二个参数 Set 一个新值后，原来的值会形成一个新的引用在下次渲染时。</li>
<li>但由于对 state 的读取没有通过 <code>this.</code> 的方式，使得 <strong>每次 <code>setTimeout</code> 都读取了当时渲染闭包环境的数据，虽然最新的值跟着最新的渲染变了，但旧的渲染里，状态依然是旧值。</strong></li>
</ol>
<h3> 对比Class Component</h3>
<p>首先对 Class Component 进行解释：</p>
<ol>
<li>首先 state 是 Immutable 的，<code>setState</code> 后一定会生成一个全新的 state 引用。</li>
<li>但 Class Component 通过 <code>this.state</code> 方式读取 state，<strong>这导致了每次代码执行都会拿到最新的 state 引用</strong>，所以快速点击三次的结果是 <code>3 3 3</code>。</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>原型链</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/01.5-%E5%8E%9F%E5%9E%8B%E9%93%BE.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/01.5-%E5%8E%9F%E5%9E%8B%E9%93%BE.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">原型链</source>
      <description>构造函数 什么是构造函数 constructor 返回创建实例对象时构造函数的引用。此属性的值是对函数本身的引用，而不是一个包含函数名称的字符串。 原型 prototype JavaScript 是一种基于原型的语言 (prototype-based language)，这个和 Java 等基于类的语言不一样。 每个对象拥有一个原型对象，对象以其原型为模板，从原型继承方法和属性，这些属性和方法定义在对象的构造器函数的 prototype 属性上，而非对象实例本身。</description>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 构造函数</h2>
<h3> 什么是构造函数</h3>
<p><code>constructor</code> 返回创建实例对象时构造函数的引用。此属性的值是对函数本身的引用，而不是一个包含函数名称的字符串。</p>
<h2> 原型</h2>
<h3> prototype</h3>
<p><code>JavaScript</code> 是一种<strong>基于原型的语言</strong> (prototype-based language)，这个和 <code>Java</code> 等基于类的语言不一样。</p>
<p>每个对象拥有一个<strong>原型对象</strong>，对象以其原型为模板，从原型继承方法和属性，这些属性和方法定义在对象的构造器函数的 <code>prototype</code> 属性上，而非对象实例本身。</p>
<h2> 原型链</h2>
<p>每个对象拥有一个原型对象，通过 <code>__proto__</code> 指针指向上一个原型，并从中继承方法和属性，同时原型对象也可能拥有原型，这样一层一层，最终指向 <code>null</code>。这种关系被称为<strong>原型链 (prototype chain)</strong>，通过原型链一个对象会拥有定义在其他对象中的属性和方法。</p>
<p>场景：现在有一个Parent函数，它拥有<code>prototype</code>属性指向它的原型也就是<code>Parent.prototype</code>，Parent.prototype拥有<code>constructor</code>属性指向函数本身。我们通过<code>new</code>创建它的实例对象p，实例对象p拥有<code>_proto_</code>属性也就是对象的原型链，指向Parent的原型<code>Parent.prototype</code>。</p>
]]></content:encoded>
    </item>
    <item>
      <title>并发模型与事件循环</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/13.5%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B%E4%B8%8E%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/13.5%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B%E4%B8%8E%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">并发模型与事件循环</source>
      <description>并发模型与事件循环 JavaScript 有一个基于事件循环的并发模型，事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其他语言中的模型截然不同，比如 C 和 Java。 运行时概念</description>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 并发模型与事件循环</h1>
<p>JavaScript 有一个基于<strong>事件循环</strong>的并发模型，事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其他语言中的模型截然不同，比如 C 和 Java。</p>
<h2> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E8%BF%90%E8%A1%8C%E6%97%B6%E6%A6%82%E5%BF%B5" target="_blank" rel="noopener noreferrer">运行时概念</a></h2>
<p>接下来的内容解释了这个理论模型。现代 JavaScript 引擎实现并着重优化了以下描述的这些语义。</p>
<h3> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E5%8F%AF%E8%A7%86%E5%8C%96%E6%8F%8F%E8%BF%B0" target="_blank" rel="noopener noreferrer">可视化描述</a></h3>
<figure><img src="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop/the_javascript_runtime_environment_example.svg" alt="Stack, heap, queue" tabindex="0" loading="lazy"><figcaption>Stack, heap, queue</figcaption></figure>
<h3> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E6%A0%88" target="_blank" rel="noopener noreferrer">栈</a></h3>
<p>函数调用形成了一个由若干帧组成的栈。</p>
<p>JSCopy to Clipboard</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当调用 <code>bar</code> 时，第一个帧被创建并压入栈中，帧中包含了 <code>bar</code> 的参数和局部变量。当 <code>bar</code> 调用 <code>foo</code> 时，第二个帧被创建并被压入栈中，放在第一个帧之上，帧中包含 <code>foo</code> 的参数和局部变量。当 <code>foo</code> 执行完毕然后返回时，第二个帧就被弹出栈（剩下 <code>bar</code> 函数的调用帧）。当 <code>bar</code> 也执行完毕然后返回时，第一个帧也被弹出，栈就被清空了。</p>
<h3> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E5%A0%86" target="_blank" rel="noopener noreferrer">堆</a></h3>
<p>对象被分配在堆中，堆是一个用来表示一大块（通常是非结构化的）内存区域的计算机术语。</p>
<h3> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E9%98%9F%E5%88%97" target="_blank" rel="noopener noreferrer">队列</a></h3>
<p>一个 JavaScript 运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。</p>
<p>在 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF" target="_blank" rel="noopener noreferrer">事件循环</a> 期间的某个时刻，运行时会从最先进入队列的消息开始处理队列中的消息。被处理的消息会被移出队列，并作为输入参数来调用与之关联的函数。正如前面所提到的，调用一个函数总是会为其创造一个新的栈帧。</p>
<p>函数的处理会一直进行到执行栈再次为空为止；然后事件循环将会处理队列中的下一个消息（如果还有的话）。</p>
<h2> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF" target="_blank" rel="noopener noreferrer">事件循环</a></h2>
<p>之所以称之为 <strong>事件循环</strong>，是因为它经常按照类似如下的方式来被实现：</p>
<p>JSCopy to Clipboard</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>queue.waitForMessage()</code> 会同步地等待消息到达 (如果当前没有任何消息等待被处理)。</p>
<h3> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E6%89%A7%E8%A1%8C%E8%87%B3%E5%AE%8C%E6%88%90" target="_blank" rel="noopener noreferrer">"执行至完成"</a></h3>
<p>每一个消息完整地执行后，其他消息才会被执行。这为程序的分析提供了一些优秀的特性，包括：当一个函数执行时，它不会被抢占，只有在它运行完毕之后才会去运行任何其他的代码，才能修改这个函数操作的数据。这与 C 语言不同，例如，如果函数在线程中运行，它可能在任何位置被终止，然后在另一个线程中运行其他代码。</p>
<p>这个模型的一个缺点在于当一个消息需要太长时间才能处理完毕时，Web 应用程序就无法处理与用户的交互，例如点击或滚动。为了缓解这个问题，浏览器一般会弹出一个“这个脚本运行时间过长”的对话框。一个良好的习惯是缩短单个消息处理时间，并在可能的情况下将一个消息裁剪成多个消息。</p>
<h3> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E6%B7%BB%E5%8A%A0%E6%B6%88%E6%81%AF" target="_blank" rel="noopener noreferrer">添加消息</a></h3>
<p>在浏览器里，每当一个事件发生并且有一个事件监听器绑定在该事件上时，一个消息就会被添加进消息队列。如果没有事件监听器，这个事件将会丢失。所以当一个带有点击事件处理器的元素被点击时，就会像其他事件一样产生一个类似的消息。</p>
<p>函数 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/setTimeout" target="_blank" rel="noopener noreferrer"><code>setTimeout</code></a> 接受两个参数：待加入队列的消息和一个时间值（可选，默认为 0）。这个时间值代表了消息被实际加入到队列的最小延迟时间。如果队列中没有其他消息并且栈为空，在这段延迟时间过去之后，消息会被马上处理。但是，如果有其他消息，<code>setTimeout</code> 消息必须等待其他消息处理完。因此第二个参数仅仅表示最少延迟时间，而非确切的等待时间。</p>
<p>下面的例子演示了这个概念（<code>setTimeout</code> 并不会在计时器到期之后直接执行）：</p>
<p>JSCopy to Clipboard</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E9%9B%B6%E5%BB%B6%E8%BF%9F" target="_blank" rel="noopener noreferrer">零延迟</a></h3>
<p>零延迟并不意味着回调会立即执行。以 0 为第二参数调用 <code>setTimeout</code> 并不表示在 0 毫秒后就立即调用回调函数。</p>
<p>其等待的时间取决于队列里待处理的消息数量。在下面的例子中，<code>"这是一条消息"</code> 将会在回调获得处理之前输出到控制台，这是因为延迟参数是运行时处理请求所需的最小等待时间，但并不保证是准确的等待时间。</p>
<p>基本上，<code>setTimeout</code> 需要等待当前队列中所有的消息都处理完毕之后才能执行，即使已经超出了由第二参数所指定的时间。</p>
<p>JSCopy to Clipboard</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E5%A4%9A%E4%B8%AA%E8%BF%90%E8%A1%8C%E6%97%B6%E4%BA%92%E7%9B%B8%E9%80%9A%E4%BF%A1" target="_blank" rel="noopener noreferrer">多个运行时互相通信</a></h3>
<p>一个 web worker 或者一个跨域的 <code>iframe</code> 都有自己的栈、堆和消息队列。两个不同的运行时只能通过 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage" target="_blank" rel="noopener noreferrer"><code>postMessage</code></a> 方法进行通信。如果另一个运行时侦听 <code>message</code> 事件，则此方法会向该运行时添加消息。</p>
<h2> <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop#%E6%B0%B8%E4%B8%8D%E9%98%BB%E5%A1%9E" target="_blank" rel="noopener noreferrer">永不阻塞</a></h2>
<p>JavaScript 的事件循环模型与许多其他语言不同的一个非常有趣的特性是，它永不阻塞。处理 I/O 通常通过事件和回调来执行，所以当一个应用正等待一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API" target="_blank" rel="noopener noreferrer">IndexedDB</a> 查询返回或者一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest" target="_blank" rel="noopener noreferrer">XHR</a> 请求返回时，它仍然可以处理其他事情，比如用户输入。</p>
<p>由于历史原因有一些例外，如 <code>alert</code> 或者同步 XHR，但应该尽量避免使用它们。注意，<a href="https://stackoverflow.com/questions/2734025/is-javascript-guaranteed-to-be-single-threaded/2734311#2734311" target="_blank" rel="noopener noreferrer">例外的例外也是存在的</a>（但通常是实现错误而非其他原因）。</p>
]]></content:encoded>
      <enclosure url="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop/the_javascript_runtime_environment_example.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>JS数据类型</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/15-JS%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/15-JS%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">JS数据类型</source>
      <description>JS数据类型 【数据类型】 基本类型：String、Number、Boolean、Null、Undefined、Symbol、BigInt 引用类型：Object、Array 【区别】 ①基本类型存储在栈中，空间小，操作频繁。 ②引用类型存储在堆中，空间大，在栈中存储了指针，指向在堆中的起始地址 ③Symbol唯一性，不可枚举，使用getOwnPropertySymbols获取== ④BigInt也是ES6新出的一种数据类型，这种数据类型的特点就是数据涵盖的范围大，能够解决超出普通数据类型范围报错的问题。（注意：BigInt和Number之间不能进行混合操作）</description>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> JS数据类型</h2>
<p>【数据类型】</p>
<p>基本类型：String、Number、Boolean、Null、Undefined、<mark>Symbol</mark>、BigInt</p>
<p>引用类型：Object、Array</p>
<p>【区别】</p>
<p>①基本类型存储在栈中，空间小，操作频繁。</p>
<p>②引用类型存储在堆中，空间大，在栈中存储了指针，指向在堆中的起始地址</p>
<p>③Symbol唯一性，不可枚举，使用getOwnPropertySymbols获取==</p>
<p>④BigInt也是ES6新出的一种数据类型，这种数据类型的特点就是数据涵盖的范围大，能够解决超出普通数据类型范围报错的问题。（注意：BigInt和Number之间不能进行混合操作）</p>
]]></content:encoded>
    </item>
    <item>
      <title>script标签</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/16-script%E6%A0%87%E7%AD%BE.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/16-script%E6%A0%87%E7%AD%BE.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">script标签</source>
      <description>script标签 src 这个属性定义引用外部脚本的 URI，这可以用来代替直接在文档中嵌入脚本。指定了 src 属性的 script 元素标签内不应该再有嵌入的脚本。 type 该属性定义 script 元素包含或src引用的脚本语言。 text 和 textContent 属性类似，本属性用于设置元素的文本内容。 defer 这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后，触发 DOMContentLoaded事件前执行。 警告： 如果缺少 src 属性（即内嵌脚本），该属性不应被使用，因为这种情况下它不起作用。defer 属性对模块脚本没有作用 —— 他们默认 defer。 async 对于普通脚本，如果存在 async 属性，那么普通脚本会被并行请求，并尽快解析和执行。 对于模块脚本，如果存在 async 属性，那么脚本及其所有依赖都会在延缓队列中执行，因此它们会被并行请求，并尽快解析和执行。 该属性能够消除解析阻塞的 Javascript。解析阻塞的 Javascript 会导致浏览器必须加载并且执行脚本，之后才能继续解析。defer 在这一点上也有类似的作用。 crossorigin 那些没有通过标准CORS (en-US)检查的正常script 元素传递最少的信息到 window.onerror。可以使用本属性来使那些将静态资源放在另外一个域名的站点打印错误信息。</description>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> script标签</h2>
<ol>
<li><code>src</code> 这个属性定义引用外部脚本的 URI，这可以用来代替直接在文档中嵌入脚本。指定了 src 属性的 script 元素标签内不应该再有嵌入的脚本。</li>
<li><code>type</code> 该属性定义 script 元素包含或<code>src</code>引用的脚本语言。</li>
<li><code>text</code> 和 textContent 属性类似，本属性用于设置元素的文本内容。</li>
<li><strong><code>defer</code> 这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后，触发 <code>DOMContentLoaded</code>事件前执行。</strong>
<ol>
<li><strong>警告：</strong> 如果缺少 <code>src</code> 属性（即内嵌脚本），该属性不应被使用，因为这种情况下它不起作用。<code>defer</code> 属性对模块脚本没有作用 —— 他们默认 defer。</li>
</ol>
</li>
<li><strong><code>async</code></strong>
<ol>
<li><strong>对于普通脚本，如果存在 <code>async</code> 属性，那么普通脚本会被并行请求，并尽快解析和执行。</strong></li>
<li><strong>对于模块脚本，如果存在 <code>async</code> 属性，那么脚本及其所有依赖都会在延缓队列中执行，因此它们会被并行请求，并尽快解析和执行。</strong></li>
<li><strong>该属性能够消除解析阻塞的 Javascript。解析阻塞的 Javascript 会导致浏览器必须加载并且执行脚本，之后才能继续解析。<code>defer</code> 在这一点上也有类似的作用。</strong></li>
</ol>
</li>
<li><code>crossorigin</code> 那些没有通过标准<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank" rel="noopener noreferrer">CORS (en-US)</a>检查的正常<code>script</code> 元素传递最少的信息到 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Window/error_event" target="_blank" rel="noopener noreferrer"><code>window.onerror</code></a>。可以使用本属性来使那些将静态资源放在另外一个域名的站点打印错误信息。</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>深拷贝与浅拷贝</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/17-%E6%B7%B1%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B5%85%E6%8B%B7%E8%B4%9D.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/17-%E6%B7%B1%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B5%85%E6%8B%B7%E8%B4%9D.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">深拷贝与浅拷贝</source>
      <description>深拷贝与浅拷贝</description>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 深拷贝与浅拷贝</h2>
]]></content:encoded>
    </item>
    <item>
      <title>lodash</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/18-lodash.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/18-lodash.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">lodash</source>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>深度克隆函数</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/19-%E6%B7%B1%E5%BA%A6%E5%85%8B%E9%9A%86%E5%87%BD%E6%95%B0.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/19-%E6%B7%B1%E5%BA%A6%E5%85%8B%E9%9A%86%E5%87%BD%E6%95%B0.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">深度克隆函数</source>
      <description>原生方法实现 原生方法实现深拷贝，主要是使用JSON.parse()与JSON.stringify()，首先将对象序列化为JSON字符串，再将JSON字符串反序列化为对象，使用这种方式效率比较高，但是会有一些问题，对于循环引用的对象无法实现深拷贝，对于被拷贝的对象，如果对象中有属性为Date对象，此种方式深拷贝会将时间对象转化为字符串；如果对象中有属性为RegExp对象、Error对象，此种方式深拷贝会得到一个空对象；如果对象中有属性为function对象、undefined、Symbol值，此种方式深拷贝会忽略这些值；如果对象中有属性为NaN、Infinity、-Infinity，此种方式深拷贝会将结果转为null。</description>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 原生方法实现</h2>
<p>原生方法实现深拷贝，主要是使用<code>JSON.parse()</code>与<code>JSON.stringify()</code>，首先将对象序列化为<code>JSON</code>字符串，再将<code>JSON</code>字符串反序列化为对象，使用这种方式效率比较高，但是会有一些问题，对于循环引用的对象无法实现深拷贝，对于被拷贝的对象，如果对象中有属性为<code>Date</code>对象，此种方式深拷贝会将时间对象转化为字符串；如果对象中有属性为<code>RegExp</code>对象、<code>Error</code>对象，此种方式深拷贝会得到一个空对象；如果对象中有属性为<code>function</code>对象、<code>undefined</code>、<code>Symbol</code>值，此种方式深拷贝会忽略这些值；如果对象中有属性为<code>NaN</code>、<code>Infinity</code>、<code>-Infinity</code>，此种方式深拷贝会将结果转为<code>null</code>。</p>
]]></content:encoded>
    </item>
    <item>
      <title>ES6新特性</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/20-ES6%E6%96%B0%E7%89%B9%E6%80%A7.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/20-ES6%E6%96%B0%E7%89%B9%E6%80%A7.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">ES6新特性</source>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>this指向</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/21-this%E6%8C%87%E5%90%91.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/21-this%E6%8C%87%E5%90%91.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">this指向</source>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>进入和退出全屏</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/22-%E8%BF%9B%E5%85%A5%E9%80%80%E5%87%BA%E5%85%A8%E5%B1%8F.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/22-%E8%BF%9B%E5%85%A5%E9%80%80%E5%87%BA%E5%85%A8%E5%B1%8F.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">进入和退出全屏</source>
      <description>window.addEventListener(&amp;quot;dblclick&amp;quot;, () =&amp;gt; { const fullscreenElement = document.fullscreenElement; // 双击全屏，退出全屏 if (!fullscreenElement) { // 请求全屏的dom元素.requestFullscreen() 即可进入全屏 renderer.domElement.requestFullscreen() } else { // 退出全屏 document.exitFullscreen() } })</description>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>常用的12中工具类型</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-4TypeScript/TypeScript%20%E5%9F%BA%E7%A1%80.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-4TypeScript/TypeScript%20%E5%9F%BA%E7%A1%80.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">常用的12中工具类型</source>
      <description>TypeScript中最常用的12中工具类型 工具类型 描述 发布版本 Awaited&amp;lt;Type&amp;gt; 获取 Promise 中的结果类型 4.5 Partial&amp;lt;Type&amp;gt; 将Type中的所有属性设置为可选属性,返回一个新的类型。 2.1 Required&amp;lt;Type&amp;gt; 将 Type 中的所有属性设置为必选属性，返回一个新的类型。 2.8 Readonly&amp;lt;Type&amp;gt; 将Type中的所有属性设置为只读属性,返回一个新的类型。 2.1 Record&amp;lt;Keys, Type&amp;gt; 新建一个由Keys指定的属性和Type指定的值组成的对象类型。 2.1 Pick&amp;lt;Type, Keys&amp;gt; 从 Type 中选择一个或多个属性，并返回一个新的类型。 2.1 Omit&amp;lt;Type, Keys&amp;gt; 从 Type 中删除一个或多个属性，并返回一个新的类型。 3.5 Exclude&amp;lt;UnionType, ExcludedMembers&amp;gt; 从UnionType中排除ExcludedMembers 中的所有类型,并返回一个新的类型。 2.8 Extract&amp;lt;UnionType, ExtractedMembers&amp;gt; 从 UnionType 中提取 ExtractedMembers 中的所有类型，并返回一个新的类型。 2.8 NonNullable&amp;lt;Type&amp;gt; 从Type 中排除 null 和 undefined 类型，并返回一个新的类型。 2.8 Parameters&amp;lt;Type&amp;gt; 获取函数类型 Type 的参数类型，以元组类型返回。 3.1 ReturnType&amp;lt;Type&amp;gt; 获取函数类型 Type 的返回值类型。 2.8</description>
      <category>FrontEnd</category>
      <pubDate>Sat, 02 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> TypeScript中最常用的12中工具类型</h2>
<table>
<thead>
<tr>
<th>工具类型</th>
<th>描述</th>
<th>发布版本</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Awaited&lt;Type&gt;                       </code></td>
<td>获取 Promise 中的结果类型</td>
<td>4.5</td>
</tr>
<tr>
<td><code>Partial&lt;Type&gt;                       </code></td>
<td>将Type中的所有属性设置为<strong>可选属性</strong>,返回一个新的类型。</td>
<td>2.1</td>
</tr>
<tr>
<td><code>Required&lt;Type&gt;                      </code></td>
<td>将 Type 中的所有属性设置为<strong>必选属性</strong>，返回一个新的类型。</td>
<td>2.8</td>
</tr>
<tr>
<td><code>Readonly&lt;Type&gt;                      </code></td>
<td>将Type中的所有属性设置为<strong>只读属性</strong>,返回一个新的类型。</td>
<td>2.1</td>
</tr>
<tr>
<td><code>Record&lt;Keys, Type&gt;                  </code></td>
<td>新建一个由Keys指定的属性和Type指定的值组成的对象类型。</td>
<td>2.1</td>
</tr>
<tr>
<td><code>Pick&lt;Type, Keys&gt;                    </code></td>
<td>从 Type 中选择一个或多个属性，并返回一个新的类型。</td>
<td>2.1</td>
</tr>
<tr>
<td><code>Omit&lt;Type, Keys&gt;                    </code></td>
<td>从 Type 中删除一个或多个属性，并返回一个新的类型。</td>
<td>3.5</td>
</tr>
<tr>
<td><code>Exclude&lt;UnionType, ExcludedMembers&gt; </code></td>
<td>从UnionType中排除ExcludedMembers 中的所有类型,并返回一个新的类型。</td>
<td>2.8</td>
</tr>
<tr>
<td><code>Extract&lt;UnionType, ExtractedMembers&gt;</code></td>
<td>从 UnionType 中提取 ExtractedMembers 中的所有类型，并返回一个新的类型。</td>
<td>2.8</td>
</tr>
<tr>
<td><code>NonNullable&lt;Type&gt;                   </code></td>
<td>从Type 中排除 null 和 undefined 类型，并返回一个新的类型。</td>
<td>2.8</td>
</tr>
<tr>
<td><code>Parameters&lt;Type&gt;                    </code></td>
<td>获取函数类型 Type 的参数类型，以元组类型返回。</td>
<td>3.1</td>
</tr>
<tr>
<td><code>ReturnType&lt;Type&gt;                    </code></td>
<td>获取函数类型 Type 的返回值类型。</td>
<td>2.8</td>
</tr>
</tbody>
</table>
<h1> interface和type的区别</h1>
<p>大家使用 typescript 总会使用到 interface 和 type，<a href="https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md" target="_blank" rel="noopener noreferrer">官方规范</a> 稍微说了下两者的区别</p>
<blockquote>
<ul>
<li>An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot.</li>
<li>An interface can have multiple merged declarations, but a type alias for an object type literal cannot.
但是没有太具体的例子。</li>
</ul>
</blockquote>
<p>明人不说暗话，直接上区别。</p>
<h2> 相同点</h2>
<h3> 都可以描述一个对象或者函数</h3>
<h4> interface</h4>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> type</h4>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 拓展（extends）与 交叉类型（Intersection Types）</h3>
<p>interface 可以 extends， 但 type 是不允许 extends 和 implement 的，<strong>但是 type 缺可以通过交叉类型 实现 interface 的 extend 行为</strong>，并且两者并不是相互独立的，也就是说 interface 可以 extends type, type 也可以 与 interface 类型 交叉 。</p>
<p><strong>虽然效果差不多，但是两者语法不同</strong>。</p>
<h4> interface extends interface</h4>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> type 与 type 交叉</h4>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> interface extends type</h4>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> type 与 interface 交叉</h4>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 不同点</h2>
<h3> type 可以而 interface 不行</h3>
<ul>
<li>type 可以声明基本类型别名，联合类型，元组等类型</li>
</ul>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>type 语句中还可以使用 typeof 获取实例的 类型进行赋值</li>
</ul>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>其他骚操作</li>
</ul>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> interface 可以而 type 不行</h3>
<p>interface 能够声明合并</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h1> 总结</h1>
<p>一般来说，如果不清楚什么时候用interface/type，能用 interface 实现，就用 interface , 如果不能就用 type 。其他更多详情参看 <a href="https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md" target="_blank" rel="noopener noreferrer">官方规范文档</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>React内置Hooks</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-6React/03-React%E5%86%85%E7%BD%AEHooks.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-6React/03-React%E5%86%85%E7%BD%AEHooks.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">React内置Hooks</source>
      <description>useState 特点 会触发页面更新，重新渲染，触发state更新 state是异步更新，state更新可能会被合并，使用函数state更新不会被合并 不可变数据（重要！！！）- 不去修改 state的值，而是传入一个新的值 // 修改对象 const [userInfo, setUserInfo] = useState({ name: &amp;apos;666&amp;apos;, age: 20 }) function changeAge() { // 不可变数据 - 不去修改 state的值，而是传入一个新的值 - 重要 setUserInfo({ ...userInfo, // 解构语法 age: 22, }) // ...解构 } // 修改数组 const [list, setList] = useState([&amp;apos;x&amp;apos;, &amp;apos;y&amp;apos;]) function addItem() { // 不可变数据 - 不去修改 state的值，而是传入一个新的值 - 重要 // list.push(&amp;apos;z&amp;apos;) // 修改失败 // setList(list.push(&amp;apos;z&amp;apos;)) // 修改失败 push返回的不是一个数组，而是长度 // setList(list.concat(&amp;apos;z&amp;apos;)) // 修改成功 cancat将元素添加到末尾并反回一个新数组 setList([...list, &amp;apos;z&amp;apos;]) // 修改成功 解构 }</description>
      <category>FrontEnd</category>
      <pubDate>Fri, 01 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h4> useState</h4>
<p>特点</p>
<ul>
<li>
<p>会触发页面更新，重新渲染，触发state更新</p>
</li>
<li>
<p>state是异步更新，state更新可能会被合并，使用函数state更新不会被合并</p>
</li>
<li>
<p>不可变数据（重要！！！）- 不去修改 state的值，而是传入一个新的值</p>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
</ul>
<p>如果说 一个变量 不用于 JSX 中显示，那就不要用 setState 来管理它，用useRef</p>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h5> state增删查改</h5>
<p><strong>数组</strong></p>
<p>增</p>
<ul>
<li>concat</li>
</ul>
<p>删</p>
<ul>
<li>filter</li>
</ul>
<p>查</p>
<ul>
<li>filter</li>
</ul>
<p>改</p>
<ul>
<li>map</li>
</ul>
<p>状态提升：数据源在父组件中，子组件只需要负责展示。操作、数据由父组件实现、传递，子组件调用、渲染</p>
<h5> immer</h5>
<p>Immer 简化了不可变数据结构的处理。特别是对于 JS 语法没那么熟悉的人。</p>
<h4> useEffect</h4>
<h5> useEffect执行两次</h5>
<ul>
<li>
<p>React 18开始, useEffect在==<strong>开发环境</strong>==下会执行两次</p>
</li>
<li>
<p>模拟组件创建、销毁、再创建的完整流程,及早暴露问题</p>
</li>
<li>
<p>生产环境下会执行一次</p>
</li>
<li>
<p>当组件渲染完成时,加载一个Ajax网络请求</p>
</li>
<li>
<p>当某个state更新时,加载一个Ajax网络请求使用</p>
</li>
<li>
<p>useEffect 实现</p>
</li>
</ul>
<h4> 其他内置Hooks</h4>
<h5> useRef</h5>
<ul>
<li>一般用于操作DOM</li>
<li>也可传入普通JS变量,但更新不会触发 rerender</li>
<li>要和Vue3 ref区分开(如果你用过Vue3)</li>
</ul>
<h5> useMemo</h5>
<ul>
<li>函数组件，每次state更新都会重新执行函数</li>
<li>useMemo 可以缓存数据，不用每次执行函数都重新生成</li>
<li>可用于计算量较大的场景，缓存提高性能</li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h5> useCallback</h5>
<ul>
<li>和 useMemo 作用一样</li>
<li>专门用于缓存函数</li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>Diff算法</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-5Vue/02-Diff%E7%AE%97%E6%B3%95.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-5Vue/02-Diff%E7%AE%97%E6%B3%95.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Diff算法</source>
      <description>Diff算法 【diff算法的理解】 diff算法用来计算出Virtual DOM中改变的部分，然后针对该部分进行DOM操作，而不用重新渲染整个页面，渲染整个DOM结构的过程中开销是很大的，需要浏览器对DOM结构进行重绘与回流，而diff算法能够使得操作过程中只更新修改的那部分DOM结构而不更新整个DOM，这样能够最小化操作DOM结构，能够最大程度上减少浏览器重绘与回流的规模。 【原理】 diff算法：当数据发生改变时，set方法让调用Dep.notify通知所有订阅者Watcher数据发生更新，订阅者就会调用patch进行比较，然后将相应的部分渲染到真实DOM结构。</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> Diff算法</h2>
<h3> 【diff算法的理解】</h3>
<p>diff算法用来计算出Virtual DOM中改变的部分，然后针对该部分进行DOM操作，而不用重新渲染整个页面，渲染整个DOM结构的过程中开销是很大的，需要浏览器对DOM结构进行重绘与回流，而diff算法能够使得操作过程中只更新修改的那部分DOM结构而不更新整个DOM，这样能够最小化操作DOM结构，能够最大程度上减少浏览器重绘与回流的规模。</p>
<h3> 【原理】</h3>
<p>diff算法：当数据发生改变时，set方法让调用Dep.notify通知所有订阅者Watcher数据发生更新，订阅者就会调用patch进行比较，然后将相应的部分渲染到真实DOM结构。</p>
<p>虚拟DOM：diff算法的基础是Virtual DOM，Virtual DOM是一棵以JavaScript对象作为基础的树，每一个节点称为VNode，用对象属性来描述节点，实际上它是一层对真实DOM的抽象，最终可以通过渲染操作使这棵树映射到真实环境上，简单来说Virtual DOM就是一个Js对象，用以描述整个文档。 在浏览器中构建页面时需要使用DOM节点描述整个文档。</p>
<h3> 【修改原则】</h3>
<p>vue优化优化时间复杂度O(n)是通过一定策略进行的，React中提到了两个假设，在Vue中同样适用：</p>
<ul>
<li>两个不同类型的元素将产生不同的树。</li>
<li>通过渲染器附带key属性，开发者可以示意哪些子元素可能是稳定的。</li>
</ul>
<p>通俗点说就是：</p>
<ul>
<li>只进行统一层级的比较，如果跨层级的移动则视为创建和删除操作。</li>
<li>如果是不同类型的元素，则认为是创建了新的元素，而不会递归比较他们的孩子。</li>
<li>如果是列表元素等比较相似的内容，可以通过key来唯一确定是移动还是创建或删除操作。</li>
</ul>
<p>比较后会出现几种情况，然后进行相应的操作：</p>
<ul>
<li>此节点被添加或移除-&gt;添加或移除新的节点。</li>
<li>属性被改变-&gt;旧属性改为新属性。</li>
<li>文本内容被改变-&gt;旧内容改为新内容。</li>
<li>节点tag或key是否改变-&gt;改变则移除后创建新元素。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>精读《Vue3.0 Function API》</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-5Vue/06-%E7%B2%BE%E8%AF%BB%E3%80%8AVue3.0%20Function%20API.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-5Vue/06-%E7%B2%BE%E8%AF%BB%E3%80%8AVue3.0%20Function%20API.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">精读《Vue3.0 Function API》</source>
      <description>文章转自 https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/109.%E7%B2%BE%E8%AF%BB%E3%80%8AVue3.0%20Function%20API%E3%80%8B.md 1. 引言 Vue 3.0 的发布引起了轩然大波，让我们解读下它的 function api RFC 详细了解一下 Vue 团队是怎么想的吧！</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>文章转自 https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/109.%E7%B2%BE%E8%AF%BB%E3%80%8AVue3.0%20Function%20API%E3%80%8B.md</p>
</blockquote>
<h1> 1. 引言</h1>
<p>Vue 3.0 的发布引起了轩然大波，让我们解读下它的 <a href="https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md#comparison-with-react-hooks" target="_blank" rel="noopener noreferrer">function api RFC</a> 详细了解一下 Vue 团队是怎么想的吧！</p>
<p>首先官方回答了几个最受关注的问题：</p>
<p><strong>Vue 3.0 是否有 break change，就像 Python 3 / Angular 2 一样？</strong></p>
<p>不，100% 兼容 Vue 2.0，且暂未打算废弃任何 API（未来也不）。之前有草案试图这么做，但由于用户反馈太猛，被撤回了。</p>
<p><strong>Vue 3.0 的设计盖棺定论了吗？</strong></p>
<p>没有呀，这次精读的稿子就是 RFC（Request For Comments），翻译成中文就是 “意见征求稿”，还在征求大家意见中哦。</p>
<p><strong>这 RFC 咋这么复杂？</strong></p>
<p>RFC 是写给贡献者/维护者的，要考虑许多边界情况与细节，所以当然会复杂很多喽！当然 Vue 本身使用起来还是很简单的。</p>
<blockquote>
<p>Vue 本身 Mutable + Template 就注定了是个用起来简单（约定 + 自然），实现起来复杂（解析 + 双绑）的框架。</p>
</blockquote>
<p><strong>这次改动很像在模仿 React，为啥不直接用 React？</strong></p>
<p>首先 Template 机制还是没变，其次模仿的是 Hooks 而不是 React 全部，如果你不喜欢这个改动，那你更不会喜欢用 React。</p>
<p>PS: 问这个问题的人，一定没有同时理解 React 与 Vue，其实这两个框架到现在差别蛮大的，后面精读会详细说明。</p>
<p>下面正式进入 Vue 3.0 Function API 的介绍。</p>
<h1> 2. 概述</h1>
<p>Vue 函数式基本 Demo：</p>
<div class="language-vue line-numbers-mode" data-ext="vue"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>函数式风格的入口是 <code>setup</code> 函数，采用了函数式风格后可以享受如下好处：类型自动推导、减少打包体积。</p>
<p><code>setup</code> 函数返回值就是注入到页面模版的变量。我们也可以返回一个函数，通过使用 <code>value</code> 这个 API 产生属性并修改：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>要注意的是，<code>value()</code> 返回的是一个对象，通过 <code>.value</code> 才能访问到其真实值。</p>
<p>为何 <code>value()</code> 返回的是 Wrappers 而非具体值呢？原因是 Vue 采用双向绑定，只有对象形式访问值才能保证访问到的是最终值，这一点类似 React 的 <code>useRef()</code> API 的 <code>.current</code> 规则。</p>
<p>那既然所有 <code>value()</code> 返回的值都是 Wrapper，那直接给模版使用时要不要调用 <code>.value</code> 呢？<strong>答案是否定的，直接使用即可，模版会自动 <code>Unwrapping</code>:</strong></p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>接下来是 <strong>Hooks</strong>，下面是一个使用 Hooks 实现获得鼠标实时位置的例子：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，<code>useMouse</code> 将所有与 “处理鼠标位置” 相关的逻辑都封装了进去，乍一看与 React Hooks 很像，但是有两个区别：</p>
<ol>
<li><code>useMouse</code> 函数内改变 <code>x</code>、<code>y</code> 后，不会重新触发 <code>setup</code> 执行。</li>
<li><code>x</code> <code>y</code> 拿到的都是 Wrapper 而不是原始值，且这个值会动态变化。</li>
</ol>
<p>另一个重要 API 就是 <strong><code>watch</code></strong>，它的作用类似 React Hooks 的 <strong>useEffect</strong>，但实现原理和调用时机其实完全不一样。</p>
<p><code>watch</code> 的目的是监听某些变量变化后执行逻辑，比如当 <code>id</code> 变化后重新取数：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>之所以要 <code>watch</code>，因为在 Vue 中，<code>setup</code> 函数仅执行一次，所以不像 React Function Component，每次组件 <code>props</code> 变化都会重新执行，因此无论是在变量、<code>props</code> 变化时如果想做一些事情，都需要包裹在 <code>watch</code> 中。</p>
<p>后面还有 <code>unwatching</code>、生命周期函数、依赖注入，都是一些语法定义，感兴趣可以继续<a href="https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md#dependency-injection" target="_blank" rel="noopener noreferrer">阅读原文</a>，笔者就不赘述了。</p>
<h1> 3. 精读</h1>
<p>对于 Vue 3.0 的 Function API + Hooks 与 React Function Component + Hooks，笔者做一些对比。</p>
<h2> Vue 与 React 逻辑结构</h2>
<p>React Function Component 与 Hooks，虽然在实现原理上，与 Vue3.0 存在 Immutable 与 Mutable、JSX 与 Template 的区别，但逻辑理解上有着相通之处。</p>
<div class="language-typescript line-numbers-mode" data-ext="ts"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>虽然在 Vue 中，<code>setup</code> 函数仅执行一次，看上去与 React 函数完全不一样（React 函数每次都执行），但其实 Vue 将渲染层（Template）与数据层（setup）分开了，而 React 合在了一起。</p>
<p>我们可以利用 React Hooks 将数据层与渲染层完全隔离：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这源于 JSX 与 Template 的根本区别。JSX 使模版与 JS 可以写在一起，因此数据层与渲染层可以耦合在一起写（也可以拆分），但 Vue 采取的 Template 思路使数据层强制分离了，这也使代码分层更清晰了。</p>
<p>而实际上 Vue3.0 的 <code>setup</code> 函数也是可选的，再配合其支持的 TSX 功能，与 React 真的只有 Mutable 的区别了：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我们很难评价 Template 与 JSX 的好坏，但为了更透彻的理解 Vue 与 React，需要抛开 JSX&amp;Template，Mutable&amp;Immutable 去看，其实去掉这两个框架无关的技术选型，React@16 与 Vue@3 已经非常像了。</p>
<blockquote>
<p>Vue3.0 的精髓是学习了 React Hooks 概念，因此正好可以用 Hooks 在 React 中模拟 Vue 的 setup 函数。</p>
</blockquote>
<p>关于这两套技术选型，已经是相对完美的组合，不建议在 JSX 中再实现类似 Mutable + JSX 的花样来（因为喜欢 Mutable 可以用 Vue 呀）：</p>
<ul>
<li>Vue：Mutable + Template</li>
<li>React：Immutable + JSX</li>
</ul>
<p>真正影响编码习惯的就是 Mutable 与 Immutable，使用 Vue 就坚定使用 Mutable，使用 React 就坚定使用 Immutable，这样能最大程度发挥两套框架的价值。</p>
<h2> Vue Hooks 与 React Hooks 的差异</h2>
<p>先看 React Hooks 的简单语法：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Vue Hooks 的简单语法：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>之所以 React 返回的 <code>count</code> 是一个数字，是因为 Immutable 规则，而 Vue 返回的 <code>count</code> 是个对象，拥有 <code>count.value</code> 属性，也是因为 Vue Mutable 规则导致，这使得 Vue 定义的所有变量都类似 React 中 <code>useRef</code> 定义变量，因此不存 React <code>capture value</code> 的特性。</p>
<blockquote>
<p>关于 capture value 更多信息，可以阅读 <a href="https://github.com/dt-fe/weekly/blob/v2/095.%E7%B2%BE%E8%AF%BB%E3%80%8AFunction%20VS%20Class%20%E7%BB%84%E4%BB%B6%E3%80%8B.md#capture-props" target="_blank" rel="noopener noreferrer">精读《Function VS Class 组件》 Capute Value 介绍</a></p>
</blockquote>
<p>另外，对于 Hooks 的值变更机制也不同，我们看 Vue 的代码：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>由于 <code>setup</code> 函数仅执行一次，怎么做到当 <code>useMouse</code> 导致 <code>x</code>、<code>y</code> 值变化时，可以在 <code>setup</code> 中拿到最新的值？</p>
<p>在 React 中，<code>useMouse</code> 如果修改了 <code>x</code> 的值，那么使用 <code>useMouse</code> 的函数就会被重新执行，以此拿到最新的 <code>x</code>，而在 Vue 中，将 Hooks 与 Mutable 深度结合，通过包装 <code>x.value</code>，使得当 <code>x</code> 变更时，引用保持不变，仅值发生了变化。所以 Vue 利用 Proxy 监听机制，可以做到 <code>setup</code> 函数不重新执行，但 Template 重新渲染的效果。</p>
<p>这就是 Mutable 的好处，Vue Hooks 中，不需要 <code>useMemo</code> <code>useCallback</code> <code>useRef</code> 等机制，仅需一个 <code>value</code> 函数，直观的 Mutable 修改，就可以实现 React 中一套 Immutable 性能优化后的效果，这个是 Mutable 的魅力所在。</p>
<h2> Vue Hooks 的优势</h2>
<p>笔者对 RFC 中对 Vue、React Hooks 的对比做一个延展解释：</p>
<p>首先最大的不同：<code>setup</code> 仅执行一遍，而 React Function Component 每次渲染都会执行。</p>
<p><strong>Vue 的代码使用更符合 JS 直觉。</strong></p>
<p>这句话直截了当戳中了 JS 软肋，JS 并非是针对 Immutable 设计的语言，所以 Mutable 写法非常自然，而 Immutable 的写法就比较别扭。</p>
<p>当 Hooks 要更新值时，Vue 只要用等于号赋值即可，而 React Hooks 需要调用赋值函数，<strong>当对象类型复杂时，还需借助第三方库才能保证进行了正确的 Immutable 更新。</strong></p>
<p><strong>对 Hooks 使用顺序无要求，而且可以放在条件语句里。</strong></p>
<p>对 React Hooks 而言，调用必须放在最前面，而且不能被包含在条件语句里，这是因为 React Hooks 采用下标方式寻找状态，一旦位置不对或者 Hooks 放在了条件中，就无法正确找到对应位置的值。</p>
<p>而 Vue Function API 中的 Hooks 可以放在任意位置、任意命名、被条件语句任意包裹的，因为其并不会触发 <code>setup</code> 的更新，只在需要的时候更新自己的引用值即可，而 Template 的重渲染则完全继承 Vue 2.0 的依赖收集机制，它不管值来自哪里，只要用到的值变了，就可以重新渲染了。</p>
<p><strong>不会再每次渲染重复调用，减少 GC 压力。</strong></p>
<p>这确实是 React Hooks 的一个问题，所有 Hooks 都在渲染闭包中执行，每次重渲染都有一定性能压力，而且频繁的渲染会带来许多闭包，虽然可以依赖 GC 机制回收，但会给 GC 带来不小的压力。</p>
<p>而 Vue Hooks 只有一个引用，所以存储的内容就非常精简，也就是占用内存小，而且当值变化时，也不会重新触发 <code>setup</code> 的执行，所以确实不会造成 GC 压力。</p>
<p><strong>必须要总包裹 <code>useCallback</code> 函数确保不让子元素频繁重渲染。</strong></p>
<p>React Hooks 有一个问题，就是完全依赖 Immutable 属性。<strong>而在 Function Component 内部创建函数时，每次都会创建一个全新的对象，这个对象如果传给子组件，必然导致子组件无法做性能优化。</strong> 因此 React 采取了 <code>useCallback</code> 作为优化方案：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>只有当第二个依赖参数变化时才返回新引用。但第二个依赖参数需要 lint 工具确保依赖总是正确的（关于为何要对依赖诚实，感兴趣可以移步 <a href="https://github.com/dt-fe/weekly/blob/v2/104.%E7%B2%BE%E8%AF%BB%E3%80%8AFunction%20Component%20%E5%85%A5%E9%97%A8%E3%80%8B.md#%E6%B0%B8%E8%BF%9C%E5%AF%B9%E4%BE%9D%E8%B5%96%E9%A1%B9%E8%AF%9A%E5%AE%9E" target="_blank" rel="noopener noreferrer">精读《Function Component 入门》 - 永远对依赖诚实</a>）。</p>
<p>回到 Vue 3.0，由于 <code>setup</code> 仅执行一次，因此函数本身只会创建一次，不存在多实例问题，不需要 <code>useCallback</code> 的概念，更不需要使用 <a href="https://www.npmjs.com/package/eslint-plugin-react-hooks" target="_blank" rel="noopener noreferrer">lint 插件</a> 保证依赖书写正确，这对开发者是实实在在的友好。</p>
<p><strong>不需要使用 <code>useEffect</code> <code>useMemo</code> 等进行性能优化，所有性能优化都是自动的。</strong></p>
<p>这也是实在话，毕竟 Mutable + 依赖自动收集就可以做到最小粒度的精确更新，根本不会触发不必要的 Rerender，因此 <code>useMemo</code> 这个概念也不需要了。</p>
<p>而 <code>useEffect</code> 也需要传递第二个参数 “依赖项”，在 Vue 中根本不需要传递 “依赖项”，所以也不会存在用户不小心传错的问题，更不需要像 React 写一个 lint 插件保证依赖的正确性。（这也是笔者想对 React Hooks 吐槽的点，React 团队如何保障每个人都安装了 lint？就算装了 lint，如果 IDE 有 BUG，导致没有生效，随时可能写出依赖不正确的 “危险代码”，造成比如死循环等严重后果）</p>
<h1> 4. 总结</h1>
<p>通过对比 Vue Hooks 与 React Hooks 可以发现，Vue 3.0 将 Mutable 特性完美与 Hooks 结合，规避了一些 React Hooks 的硬伤。所以我们可以说 Vue 借鉴了 React Hooks 的思想，但创造出来的确实一个更精美的艺术品。</p>
<p>但 React Hooks 遵循的 Immutable 也有好的一面，就是每次渲染中状态被稳定的固化下来了，不用担心状态突然变更带来的影响（其实反而要注意状态用不变更带来的影响），对于数据记录、程序运行的稳定性都有较高的可预期性。</p>
<p>最后，对于喜欢 Mutable 的开发者，Vue 3.0 是你的最佳选择，基于 React + Mutable 搞的一些小轮子做到顶级可能还不如 Vue 3.0。对于 React 开发者来说，坚持你们的 Immutable 信仰吧，Vue 3.0 已经将 Mutable 发挥到极致，只有将 React Immutable 特性发挥到极致才能发挥 React 的最大价值。</p>
<blockquote>
<p>讨论地址是：<a href="https://github.com/dt-fe/weekly/issues/173" target="_blank" rel="noopener noreferrer">精读《Vue3.0 Function API》 · Issue #173 · dt-fe/weekly</a></p>
</blockquote>
<p><strong>如果你想参与讨论，请 <a href="https://github.com/dt-fe/weekly" target="_blank" rel="noopener noreferrer">点击这里</a>，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。</strong></p>
<blockquote>
<p>关注 <strong>前端精读微信公众号</strong></p>
</blockquote>
<img width="200" src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
<blockquote>
<p>版权声明：自由转载-非商用-非衍生-保持署名（<a href="https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh" target="_blank" rel="noopener noreferrer">创意共享 3.0 许可证</a>）</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>Diff算法</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-5Vue/08-nextTick%E5%8E%9F%E7%90%86.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-5Vue/08-nextTick%E5%8E%9F%E7%90%86.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Diff算法</source>
      <description>$nextTick原理 事件循环 宏任务微任务 等待虚拟dom渲染完成</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> $nextTick原理</h3>
<ol>
<li>事件循环
<ol>
<li>宏任务微任务</li>
<li>等待虚拟dom渲染完成</li>
</ol>
</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>JSX与Vue模板的区别</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-6React/01-JSX%E4%B8%8EVue%E6%A8%A1%E6%9D%BF%E7%9A%84%E5%8C%BA%E5%88%AB.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-6React/01-JSX%E4%B8%8EVue%E6%A8%A1%E6%9D%BF%E7%9A%84%E5%8C%BA%E5%88%AB.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">JSX与Vue模板的区别</source>
      <category>FrontEnd</category>
      <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Function Component入门</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-6React/02-Function%20Component%E5%85%A5%E9%97%A8.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-6React/02-Function%20Component%E5%85%A5%E9%97%A8.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Function Component入门</source>
      <description>本文引自https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/104.%E7%B2%BE%E8%AF%BB%E3%80%8AFunction%20Component%20%E5%85%A5%E9%97%A8%E3%80%8B.md 1. 引言 如果你在使用 React 16，可以尝试 Function Component 风格，享受更大的灵活性。但在尝试之前，最好先阅读本文，对 Function Component 的思维模式有一个初步认识，防止因思维模式不同步造成的困扰。</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>本文引自https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/104.%E7%B2%BE%E8%AF%BB%E3%80%8AFunction%20Component%20%E5%85%A5%E9%97%A8%E3%80%8B.md</p>
</blockquote>
<h1> 1. 引言</h1>
<p>如果你在使用 React 16，可以尝试 Function Component 风格，享受更大的灵活性。但在尝试之前，最好先阅读本文，对 Function Component 的思维模式有一个初步认识，防止因思维模式不同步造成的困扰。</p>
<h1> 2. 精读</h1>
<h3> 什么是 Function Component？</h3>
<p>Function Component 就是以 Function 的形式创建的 React 组件：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>也就是，一个返回了 JSX 或 <code>createElement</code> 的 Function 就可以当作 React 组件，这种形式的组件就是 Function Component。</p>
<blockquote>
<p>所以我已经学会 Function Component 了吗？</p>
</blockquote>
<p>别急，故事才刚刚开始。</p>
<h3> 什么是 Hooks？</h3>
<p>Hooks 是辅助 Function Component 的工具。比如 <code>useState</code> 就是一种 Hook，它可以用来管理状态：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>useState</code> 返回的结果是数组，数组的第一项是 <strong>值</strong>，第二项是 <strong>赋值函数</strong>，<code>useState</code> 函数的第一个参数就是 <strong>默认值</strong>，也支持回调函数。更详细的介绍可以参考 <a href="https://reactjs.org/docs/hooks-rules.html#explanation" target="_blank" rel="noopener noreferrer">Hooks 规则解读</a>。</p>
<h3> 先赋值再 setTimeout 打印</h3>
<p>我们再将 <code>useState</code> 与 <code>setTimeout</code> 结合使用，看看有什么发现。</p>
<p>创建一个按钮，点击后让计数器自增，<strong>但是延时 3 秒后再打印出来</strong>：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果我们 <strong>在三秒内连续点击三次</strong>，那么 <code>count</code> 的值最终会变成 <code>3</code>，而随之而来的输出结果是。。？</p>
<div class="language-plain line-numbers-mode" data-ext="plain"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>嗯，好像对，但总觉得有点怪？</p>
<h3> 使用 Class Component 方式实现一遍呢？</h3>
<p>敲黑板了，回到我们熟悉的 Class Component 模式，实现一遍上面的功能：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>嗯，结果应该等价吧？3 秒内快速点击三次按钮，这次的结果是：</p>
<div class="language-plain line-numbers-mode" data-ext="plain"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>怎么和 Function Component 结果不一样？</p>
<p><strong>这是用好 Function Component 必须迈过的第一道坎，请确认完全理解下面这段话：</strong></p>
<p>首先对 Class Component 进行解释：</p>
<ol>
<li>首先 state 是 Immutable 的，<code>setState</code> 后一定会生成一个全新的 state 引用。</li>
<li>但 Class Component 通过 <code>this.state</code> 方式读取 state，<strong>这导致了每次代码执行都会拿到最新的 state 引用</strong>，所以快速点击三次的结果是 <code>3 3 3</code>。</li>
</ol>
<p>那么对 Function Component 而言：</p>
<ol>
<li><code>useState</code> 产生的数据也是 Immutable 的，通过数组第二个参数 Set 一个新值后，原来的值会形成一个新的引用在下次渲染时。</li>
<li>但由于对 state 的读取没有通过 <code>this.</code> 的方式，使得 <strong>每次 <code>setTimeout</code> 都读取了当时渲染闭包环境的数据，虽然最新的值跟着最新的渲染变了，但旧的渲染里，状态依然是旧值。</strong></li>
</ol>
<p>为了更容易理解，我们来模拟三次 Function Component 模式下点击按钮时的状态：</p>
<p>第一次点击，共渲染了 2 次，<code>setTimeout</code> 生效在第 <code>1</code> 次渲染，此时状态为：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第二次点击，共渲染了 3 次，<code>setTimeout</code> 生效在第 <code>2</code> 次渲染，此时状态为：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第三次点击，共渲染了 4 次，<code>setTimeout</code> 生效在第 <code>3</code> 次渲染，此时状态为：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，每一个渲染都是一个独立的闭包，在独立的三次渲染中，<code>count</code> 在每次渲染中的值分别是 <code>0 1 2</code>，所以无论 <code>setTimeout</code> 延时多久，打印出来的结果永远是 <code>0 1 2</code>。</p>
<p>理解了这一点，我们就能继续了。</p>
<h3> 如何让 Function Component 也打印 <code>3 3 3</code>？</h3>
<p>所以这是不是代表 Function Component 无法覆盖 Class Component 的功能呢？完全不是，<strong>我希望你读完本文后，不仅能解决这个问题，更能理解为什么用 Function Component 实现的代码更佳合理、优雅</strong>。</p>
<p>第一种方案是借助一个新 Hook - <code>useRef</code> 的能力：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这种方案的打印结果就是 <code>3 3 3</code>。</p>
<p>想要理解为什么，首先要理解 <code>useRef</code> 的功能：<strong>通过 <code>useRef</code> 创建的对象，其值只有一份，而且在所有 Rerender 之间共享</strong>。</p>
<p>所以我们对 <code>count.current</code> 赋值或读取，读到的永远是其最新值，而与渲染闭包无关，因此如果快速点击三下，必定会返回 <code>3 3 3</code> 的结果。</p>
<p>但这种方案有个问题，就是使用 <code>useRef</code> 替代了 <code>useState</code> 创建值，那么很自然的问题就是，如何不改变原始值的写法，达到同样的效果呢？</p>
<h3> 如何不改造原始值也打印 <code>3 3 3</code>？</h3>
<p>一种最简单的做法，就是新建一个 <code>useRef</code> 的值给 <code>setTimeout</code> 使用，而程序其余部分还是用原始的 <code>count</code>:</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过这个例子，我们引出了一个新的，也是 <strong>最重要的 Hook - <code>useEffect</code></strong>，请务必深入理解这个函数。</p>
<p><code>useEffect</code> 是处理副作用的，其执行时机在 <strong>每次 Render 渲染完毕后</strong>，换句话说就是每次渲染都会执行，只是实际在真实 DOM 操作完毕后。</p>
<p>我们可以利用这个特性，在每次渲染完毕后，将 <code>count</code> 此时最新的值赋给 <code>currentCount.current</code>，这样就使 <code>currentCount</code> 的值自动同步了 <code>count</code> 的最新值。</p>
<p>为了确保大家准确理解 <code>useEffect</code>，笔者再啰嗦一下，将其执行周期拆解到每次渲染中。假设你在三秒内快速点击了三次按钮，那么你需要在大脑中模拟出下面这三次渲染都发生了什么：</p>
<p>第一次点击，共渲染了 2 次，<code>useEffect</code> 生效在第 <code>2</code> 次渲染：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第二次点击，共渲染了 3 次，<code>useEffect</code> 生效在第 <code>3</code> 次渲染：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第三次点击，共渲染了 4 次，<code>useEffect</code> 生效在第 <code>4</code> 次渲染：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>注意对比与上面章节展开的 <code>setTimeout</code> 渲染时有什么不同。</p>
<p>要注意的是，<code>useEffect</code> 也随着每次渲染而不同的，<strong>同一个组件不同渲染之间，<code>useEffect</code> 内闭包环境完全独立</strong>。对于本次的例子，<code>useEffect</code> 共执行了 <strong>四次</strong>，经历了如下四次赋值最终变成 <code>3</code>:</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>请确保理解了这句话再继续往下阅读：</p>
<ul>
<li><strong><code>setTimeout</code> 的例子，三次点击触发了四次渲染，但 <code>setTimeout</code> 分别生效在第 1、2、3 次渲染中，因此值是 <code>0 1 2</code></strong>。</li>
<li><strong><code>useEffect</code> 的例子中，三次点击也触发了四次渲染，但 <code>useEffect</code> 分别生效在第 1、2、3、4 次渲染中，最终使 <code>currentCount</code> 的值变成 <code>3</code></strong>。</li>
</ul>
<h3> 用自定义 Hook 包装 <code>useRef</code></h3>
<p>是不是觉得每次都写一堆 <code>useEffect</code> 同步数据到 <code>useRef</code> 很烦？是的，想要简化，就需要引出一个新的概念：<strong>自定义 Hooks</strong>。</p>
<p>首先介绍一下，自定义 Hooks 允许创建自定义 Hook，只要函数名遵循以 <code>use</code> 开头，且返回非 JSX 元素，就是 Hooks 啦！<strong>自定义 Hooks 内还可以调用包括内置 Hooks 在内的所有自定义 Hooks</strong>。</p>
<p>也就是我们可以将 <code>useEffect</code> 写到自定义 Hook 里：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里又引出一个新的概念，<strong>就是 <code>useEffect</code> 的第二个参数，dependences</strong>。dependences 这个参数定义了 <code>useEffect</code> 的依赖，在新的渲染中，只要所有依赖项的引用都不发生变化，<code>useEffect</code> 就不会被执行，且当依赖项为 <code>[]</code> 时，<code>useEffect</code> 仅在初始化执行一次，后续的 Rerender 永远也不会被执行。</p>
<p>这个例子中，我们告诉 React：仅当 <code>value</code> 的值变化了，再将其最新值同步给 <code>ref.current</code>。</p>
<p>那么这个自定义 Hook 就可以在任何 Function Component 调用了：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>封装以后代码清爽了很多，而且最重要的是将逻辑封装起来，我们只要理解 <code>useCurrentValue</code> 这个 Hook 可以产生一个值，其最新值永远与入参同步。</p>
<p>看到这里，也许有的小伙伴已经按捺不住迸发的灵感了：<strong>将 <code>useEffect</code> 第二个参数设置为空数组，这个自定义 Hook 就代表了 <code>didMount</code> 生命周期！</strong></p>
<p>是的，但笔者建议大家 <strong>不要再想生命周期的事情</strong>，这样会阻碍你更好的理解 Function Component。因为下一个话题，就是要告诉你：<strong>永远要对 <code>useEffect</code> 的依赖诚实，被依赖的参数一定要填上去，否则会产生非常难以察觉与修复的 BUG。</strong></p>
<h3> 将 <code>setTimeout</code> 换成 <code>setInterval</code> 会怎样</h3>
<p>我们回到起点，将第一个 <code>setTimeout</code> Demo 中换成 <code>setInterval</code>，看看会如何：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>这个例子将引发学习 Function Component 的第二个拦路虎，理解了它，才深入理解了 Function Component 的渲染原理。</strong></p>
<p>首先介绍一下引入的新概念，<strong><code>useEffect</code> 函数的返回值</strong>。它的返回值是一个函数，这个函数在 <code>useEffect</code> 即将重新执行时，会先执行上一次 Rerender <code>useEffect</code> 第一个回调的返回函数，再执行下一次渲染的 <code>useEffect</code> 第一个回调。</p>
<p>以两次连续渲染为例介绍，展开后的效果是这样的：</p>
<p>第一次渲染：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第二次渲染：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>然而本 Demo 将 <code>useEffect</code> 的第二个参数设置为了 <code>[]</code>，那么其返回函数只会在这个组件被销毁时执行</strong>。</p>
<p>读懂了前面的例子，应该能想到，这个 Demo 希望利用 <code>[]</code> 依赖，将 <code>useEffect</code> 当作 <code>didMount</code> 使用，再结合 <code>setInterval</code> 每次时 <code>count</code> 自增，这样期望将 <code>count</code> 的值每秒自增 1。</p>
<p>然而结果是：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>理解了 <code>setTimeout</code> 例子的读者应该可以自行推导出原因：<code>setInterval</code> 永远在第一次 Render 的闭包中，<code>count</code> 的值永远是 <code>0</code>，也就是等价于：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然而罪魁祸首就是 <strong>没有对依赖诚实</strong> 导致的。例子中 <code>useEffect</code> 明明依赖了 <code>count</code>，依赖项却非要写 <code>[]</code>，所以产生了很难理解的错误。</p>
<p>所以改正的办法就是 <strong>对依赖诚实</strong>。</p>
<h3> 永远对依赖项诚实</h3>
<p>一旦我们对依赖诚实了，就可以得到正确的效果：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我们将 <code>count</code> 作为了 <code>useEffect</code> 的依赖项，就得到了正确的结果：</p>
<div class="language-plain line-numbers-mode" data-ext="plain"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>既然漏写依赖的风险这么大，自然也有保护措施，那就是 <a href="https://www.npmjs.com/package/eslint-plugin-react-hooks" target="_blank" rel="noopener noreferrer">eslint-plugin-react-hooks</a> 这个插件，会自动订正你的代码中的依赖，想不对依赖诚实都不行！</p>
<p>然而对这个例子而言，代码依然存在 BUG：每次计数器都会重新实例化，如果换成其他费事操作，性能成本将不可接受。</p>
<h3> 如何不在每次渲染时重新实例化 <code>setInterval</code>?</h3>
<p>最简单的办法，就是利用 <code>useState</code> 的第二种赋值用法，不直接依赖 <code>count</code>，而是以函数回调方式进行赋值：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这这写法真正做到了：</p>
<ol>
<li>不依赖 <code>count</code>，所以对依赖诚实。</li>
<li>依赖项为 <code>[]</code>，只有初始化会对 <code>setInterval</code> 进行实例化。</li>
</ol>
<p>而之所以输出还是正确的 <code>1 2 3 ...</code>，原因是 <code>setCount</code> 的回调函数中，<code>c</code> 值永远指向最新的 <code>count</code> 值，因此没有逻辑漏洞。</p>
<p>但是聪明的同学仔细一想，就会发现一个新问题：如果存在两个以上变量需要使用时，这招就没有用武之地了。</p>
<h3> 同时使用两个以上变量时？</h3>
<p>如果同时需要对 <code>count</code> 与 <code>step</code> 两个变量做累加，那 <code>useEffect</code> 的依赖必然要写上一种某一个值，频繁实例化的问题就又出现了：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这个例子中，由于 <code>setCount</code> 只能拿到最新的 <code>count</code> 值，而为了每次都拿到最新的 <code>step</code> 值，就必须将 <code>step</code> 申明到 <code>useEffect</code> 依赖中，导致 <code>setInterval</code> 被频繁实例化。</p>
<p>这个问题自然也困扰了 React 团队，所以他们拿出了一个新的 Hook 解决问题：<code>useReducer</code>。</p>
<h3> 什么是 useReducer</h3>
<p>先别联想到 Redux。只考虑上面的场景，看看为什么 React 团队要将 <code>useReducer</code> 列为内置 Hooks 之一。</p>
<p>先介绍一下 <code>useReducer</code> 的用法:</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p><code>useReducer</code> 返回的结构与 <code>useState</code> 很像，只是数组第二项是 <code>dispatch</code>，而接收的参数也有两个，初始值放在第二位，第一位就是 <code>reducer</code>。</p>
<p><code>reducer</code> 定义了如何对数据进行变换，比如一个简单的 <code>reducer</code> 如下：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样就可以通过调用 <code>dispatch({ type: 'increment' })</code> 的方式实现 <code>count</code> 自增了。</p>
<p>那么回到这个例子，我们只需要稍微改写一下用法即可：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，我们通过 <code>reducer</code> 的 <code>tick</code> 类型完成了对 <code>count</code> 的累加，而在 <code>useEffect</code> 的函数中，竟然完全绕过了 <code>count</code>、<code>step</code> 这两个变量。所以 <code>useReducer</code> 也被称为解决此类问题的 “黑魔法”。</p>
<p>其实不管被怎么称呼也好，其本质是让函数与数据解耦，<strong>函数只管发出指令，而不需要关心使用的数据被更新时，需要重新初始化自身。</strong></p>
<p>仔细的读者会发现这个例子还是有一个依赖的，那就是 <code>dispatch</code>，然而 <code>dispatch</code> 引用永远也不会变，因此可以忽略它的影响。<strong>这也体现了无论如何都要对依赖保持诚实。</strong></p>
<p>这也引发了另一个注意项：<strong>尽量将函数写在 <code>useEffect</code> 内部</strong>。</p>
<h3> 将函数写在 <code>useEffect</code> 内部</h3>
<p>为了避免遗漏依赖，必须将函数写在 <code>useEffect</code> 内部，这样 <a href="https://www.npmjs.com/package/eslint-plugin-react-hooks" target="_blank" rel="noopener noreferrer">eslint-plugin-react-hooks</a> 才能通过静态分析补齐依赖项：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>getFetchUrl</code> 这个函数依赖了 <code>count</code>，而如果将这个函数定义在 <code>useEffect</code> 外部，无论是机器还是人眼都难以看出 <code>useEffect</code> 的依赖项包含 <code>count</code>。</p>
<p>然而这就引发了一个新问题：将所有函数都写在 <code>useEffect</code> 内部岂不是非常难以维护？</p>
<h3> 如何将函数抽到 <code>useEffect</code> 外部？</h3>
<p>为了解决这个问题，我们要引入一个新的 Hook：<code>useCallback</code>，它就是解决将函数抽到 <code>useEffect</code> 外部的问题。</p>
<p>我们先看 <code>useCallback</code> 的用法：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，<code>useCallback</code> 也有第二个参数 - 依赖项，我们将 <code>getFetchUrl</code> 函数的依赖项通过 <code>useCallback</code> 打包到新的 <code>getFetchUrl</code> 函数中，那么 <strong><code>useEffect</code> 就只需要依赖 <code>getFetchUrl</code> 这个函数，就实现了对 <code>count</code> 的间接依赖。</strong></p>
<p>换句话说，我们利用了 <code>useCallback</code> 将 <code>getFetchUrl</code> 函数抽到了 <code>useEffect</code> 外部。</p>
<h3> 为什么 <code>useCallback</code> 比 <code>componentDidUpdate</code> 更好用</h3>
<p>回忆一下 Class Component 的模式，我们是如何在函数参数变化时进行重新取数的：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上面的代码经常用 Class Component 的人应该很熟悉，然而暴露的问题可不小。</p>
<p>我们需要理解 <strong><code>props.count</code> <code>props.step</code> 被 <code>props.fetchData</code> 函数使用了，因此在 <code>componentDidUpdate</code> 时，判断这两个参数发生了变化就触发重新取数。</strong></p>
<p>然而问题是，这种理解成本是不是过高了？如果父级函数 <code>fetchData</code> 不是我写的，在不读源码的情况下，我怎么知道它依赖了 <code>props.count</code> 与 <code>props.step</code> 呢？<strong>更严重的是，如果某一天 <code>fetchData</code> 多依赖了 <code>params</code> 这个参数，下游函数将需要全部在 <code>componentDidUpdate</code> 覆盖到这个逻辑，否则 <code>params</code> 变化时将不会重新取数</strong>。可以想象，这种方式维护成本巨大，甚至可以说几乎无法维护。</p>
<p>换成 Function Component 的思维吧！试着用上刚才提到的 <code>useCallback</code> 解决问题：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看出来，当 <code>fetchData</code> 的依赖变化后，按下保存键，<a href="https://www.npmjs.com/package/eslint-plugin-react-hooks" target="_blank" rel="noopener noreferrer">eslint-plugin-react-hooks</a> 会自动补上更新后的依赖，<strong>而下游的代码不需要做任何改变，下游只需要关心依赖了 <code>fetchData</code> 这个函数即可，至于这个函数依赖了什么，已经封装在 <code>useCallback</code> 后打包透传下来了。</strong></p>
<p>不仅解决了维护性问题，而且对于 <strong>只要参数变化，就重新执行某逻辑，是特别适合用 <code>useEffect</code> 做的，使用这种思维思考问题会让你的代码更 “智能”，而使用分裂的生命周期进行思考，会让你的代码四分五裂，而且容易漏掉各种时机。</strong></p>
<p><code>useEffect</code> 对业务的抽象非常方便，笔者举几个例子：</p>
<ol>
<li>依赖项是查询参数，那么 <code>useEffect</code> 内可以进行取数请求，那么只要查询参数变化了，列表就会自动取数刷新。注意我们将取数时机从触发端改成了接收端。</li>
<li>当列表更新后，重新注册一遍拖拽响应事件。也是同理，依赖参数是列表，只要列表变化，拖拽响应就会重新初始化，这样我们可以放心的修改列表，而不用担心拖拽事件失效。</li>
<li>只要数据流某个数据变化，页面标题就同步修改。同理，也不需要在每次数据变化时修改标题，而是通过 <code>useEffect</code> “监听” 数据的变化，这是一种 <strong>“控制反转”</strong> 的思维。</li>
</ol>
<p>说了这么多，其本质还是利用了 <code>useCallback</code> 将函数独立抽离到 <code>useEffect</code> 外部。</p>
<p>那么进一步思考，<strong>可以将函数抽离到整个组件的外部吗？</strong></p>
<p>这也是可以的，需要灵活运用自定义 Hooks 实现。</p>
<h3> 将函数抽到组件外部</h3>
<p>以上面的 <code>fetchData</code> 函数为例，如果要抽到整个组件的外部，就不是利用 <code>useCallback</code> 做到了，而是利用自定义 Hooks 来做：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，我们将 <code>useCallback</code> 打包搬到了自定义 Hook <code>useFetch</code> 中，那么函数中只需要一行代码就能实现一样的效果了：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>随着使用越来越方便，我们可以将精力放到性能上。观察可以发现，<code>count</code> 与 <code>step</code> 都会频繁变化，每次变化就会导致 <code>useFetch</code> 中 <code>useCallback</code> 依赖的变化，进而导致重新生成函数。然而实际上这种函数是没必要每次都重新生成的，反复生成函数会造成大量性能损耗。</p>
<p>换一个例子就可以看得更清楚：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>假设我们使用 <a href="https://github.com/SortableJS/Sortable" target="_blank" rel="noopener noreferrer">Sortablejs</a> 对某个区域进行拖拽监听，这个函数每次都重复执行的性能损耗非常大，<strong>然而这个函数内部可能因为仅仅要上报一些日志，所以依赖了没有实际被使用的 <code>count</code> <code>step</code> 变量：</strong></p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这种情况，函数的依赖就特别不合理。<strong>虽然依赖变化应该触发函数重新执行，但如果函数重新执行的成本非常高，而依赖只是可有可无的点缀，得不偿失。</strong></p>
<h3> 利用 Ref 保证耗时函数依赖不变</h3>
<p>一种办法是通过将依赖转化为 Ref：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这种方式比较取巧，**将需要更新的区域与耗时区域分离，**再将需更新的内容通过 Ref 提供给耗时的区域，实现性能优化。</p>
<p>然而这样做对函数的改动成本比较高，有一种更通用的做法解决此类问题。</p>
<h3> 通用的自定义 Hooks 解决函数重新实例化问题</h3>
<p>我们可以利用 <code>useRef</code> 创造一个自定义 Hook 代替 <code>useCallback</code>，<strong>使其依赖的值变化时，回调不会重新执行，却能拿到最新的值！</strong></p>
<p>这个神奇的 Hook 写法如下：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>再次体会到自定义 Hook 的无所不能。</p>
<p>首先看这一段：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当 <code>fn</code> 回调函数变化时， <code>ref.current</code> 重新指向最新的 <code>fn</code> 这个逻辑中规中矩。重点是，当依赖 <code>dependencies</code> 变化时，也重新为 <code>ref.current</code> 赋值，此时 <code>fn</code> 内部的 <code>dependencies</code> 值是最新的，而下一段代码：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>又仅执行一次（ref 引用不会改变），所以每次都可以返回 <code>dependencies</code> 是最新的 <code>fn</code>，并且 <code>fn</code> 还不会重新执行。</p>
<p>假设我们对 <code>useEventCallback</code> 传入的回调函数称为 X，<strong>则这段代码的含义，就是使每次渲染的闭包中，回调函数 X 总是拿到的总是最新 Rerender 闭包中的那个，所以依赖的值永远是最新的，而且函数不会重新初始化。</strong></p>
<blockquote>
<p><strong>React <a href="https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback" target="_blank" rel="noopener noreferrer">官方不推荐使用此范式</a>，因此对于这种场景，利用 <code>useReducer</code>，将函数通过 <code>dispatch</code> 中调用。</strong> 还记得吗？<code>dispatch</code> 是一种可以绕过依赖的黑魔法，我们在 “什么是 useReducer” 小节提到过。</p>
</blockquote>
<p>随着对 Function Component 的使用，你也渐渐关心到函数的性能了，这很棒。那么下一个重点自然是关注 Render 的性能。</p>
<h3> 用 memo 做 PureRender</h3>
<p>在 Fucntion Component 中，Class Component 的 <code>PureComponent</code> 等价的概念是 <code>React.memo</code>，我们介绍一下 <code>memo</code> 的用法：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>使用 <code>memo</code> 包裹的组件，会在自身重渲染时，对每一个 <code>props</code> 项进行浅对比，如果引用没有变化，就不会触发重渲染。所以 <code>memo</code> 是一种很棒的性能优化工具。</p>
<p>下面就介绍一个看似比 <code>memo</code> 难用，但真正理解后会发现，其实比 <code>memo</code> 更好用的渲染优化函数：<code>useMemo</code>。</p>
<h3> 用 useMemo 做局部 PureRender</h3>
<p>相比 <code>React.memo</code> 这个异类，<code>React.useMemo</code> 可是正经的官方 Hook：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，我们利用 <code>useMemo</code> 包裹渲染代码，这样即便函数 <code>Child</code> 因为 <code>props</code> 的变化重新执行了，只要渲染函数用到的 <code>props.fetchData</code> 没有变，就不会重新渲染。</p>
<p>这里发现了 <code>useMemo</code> 的第一个好处：<strong>更细粒度的优化渲染</strong>。</p>
<p>所谓更细粒度的优化渲染，是指函数 <code>Child</code> 整体可能用到了 <code>A</code>、<code>B</code> 两个 <code>props</code>，而渲染仅用到了 <code>B</code>，那么使用 <code>memo</code> 方案时，<code>A</code> 的变化会导致重渲染，而使用 <code>useMemo</code> 的方案则不会。</p>
<p>而 <code>useMemo</code> 的好处还不止这些，这里先留下伏笔。我们先看一个新问题：当参数越来越多时，使用 <code>props</code> 将函数、值在组件间传递非常冗长：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>虽然 <code>Child</code> 可以通过 <code>memo</code> 或 <code>useMemo</code> 进行优化，<strong>但当程序复杂时，可能存在多个函数在所有 Function Component 间共享的情况</strong>，此时就需要新 Hook: <code>useContext</code> 来拯救了。</p>
<h3> 使用 Context 做批量透传</h3>
<p>在 Function Component 中，可以使用 <code>React.createContext</code> 创建一个 Context:</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>其中 <code>null</code> 是初始值，一般置为 <code>null</code> 也没关系。接下来还有两步，分别是在根节点使用 <code>Store.Provider</code> 注入，与在子节点使用官方 Hook <code>useContext</code> 拿到注入的数据：</p>
<p>在根节点使用 <code>Store.Provider</code> 注入：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在子节点使用 <code>useContext</code> 拿到注入的数据（也就是拿到 <code>Store.Provider</code> 的 <code>value</code>）：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样就不需要在每个函数间进行参数透传了，公共函数可以都放在 Context 里。</p>
<p>但是当函数多了，<code>Provider</code> 的 <code>value</code> 会变得很臃肿，我们可以结合之前讲到的 <code>useReducer</code> 解决这个问题。</p>
<h3> 使用 <code>useReducer</code> 为 Context 传递内容瘦身</h3>
<p>使用 <code>useReducer</code>，所有回调函数都通过调用 <code>dispatch</code> 完成，那么 Context 只要传递 <code>dispatch</code> 一个函数就好了：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这下无论是根节点的 <code>Provider</code>，还是子元素调用都清爽很多：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>你也许很快就想到，将 <code>state</code> 也通过 <code>Provider</code> 注入进去岂不更妙？是的，但此处请务必注意潜在性能问题。</p>
<h3> 将 <code>state</code> 也放到 Context 中</h3>
<p>稍稍改造下，将 <code>state</code> 也放到 Context 中，这下赋值与取值都非常方便了！</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>对 <code>Count</code> <code>Step</code> 这两个子元素而言，可需要谨慎一些，假如我们这么实现这两个子元素：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>其结果是：<strong>无论点击 <code>incCount</code> 还是 <code>incStep</code>，都会同时触发这两个组件的 Rerender。</strong></p>
<p>其问题在于：<code>memo</code> 只能挡在最外层的，而通过 <code>useContext</code> 的数据注入发生在函数内部，会 <strong>绕过 <code>memo</code>。</strong></p>
<p>当触发 <code>dispatch</code> 导致 <code>state</code> 变化时，所有使用了 <code>state</code> 的组件内部都会强制重新刷新，此时想要对渲染次数做优化，只有拿出 <code>useMemo</code> 了！</p>
<h3> <code>useMemo</code> 配合 <code>useContext</code></h3>
<p>使用 <code>useContext</code> 的组件，如果自身不使用 <code>props</code>，就可以完全使用 <code>useMemo</code> 代替 <code>memo</code> 做性能优化：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>对这个例子来说，点击对应的按钮，<strong>只有使用到的组件才会重渲染，效果符合预期。</strong> 结合 <a href="https://www.npmjs.com/package/eslint-plugin-react-hooks" target="_blank" rel="noopener noreferrer">eslint-plugin-react-hooks</a> 插件使用，连 <code>useMemo</code> 的第二个参数依赖都是自动补全的。</p>
<p>读到这里，不知道你是否联想到了 <a href="https://github.com/reduxjs/redux" target="_blank" rel="noopener noreferrer">Redux</a> 的 <code>Connect</code>?</p>
<p>我们来对比一下 <code>Connect</code> 与 <code>useMemo</code>，会发现惊人的相似之处。</p>
<p>一个普通的 Redux 组件：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>一个普通的 Function Component 组件：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这两段代码的效果完全一样，Function Component 除了更简洁之外，还有一个更大的优势：<strong>全自动的依赖推导</strong>。</p>
<p>Hooks 诞生的一个原因，就是为了便于静态分析依赖，简化 Immutable 数据流的使用成本。</p>
<p>我们看 <code>Connect</code> 的场景：</p>
<p>由于不知道子组件使用了哪些数据，因此需要在 <code>mapStateToProps</code> 提前写好，而当需要使用数据流内新变量时，组件里是无法访问的，我们要回到 <code>mapStateToProps</code> 加上这个依赖，再回到组件中使用它。</p>
<p>而 <code>useContext</code> + <code>useMemo</code> 的场景：</p>
<p>由于注入的 <code>state</code> 是全量的，Render 函数中想用什么都可直接用，在按保存键时，<a href="https://www.npmjs.com/package/eslint-plugin-react-hooks" target="_blank" rel="noopener noreferrer">eslint-plugin-react-hooks</a> 会通过静态分析，在 <code>useMemo</code> 第二个参数自动补上代码里使用到的外部变量，比如 <code>state.count</code>、<code>dispatch</code>。</p>
<p>另外可以发现，Context 很像 Redux，那么 Class Component 模式下的异步中间件实现的异步取数怎么利用 <code>useReducer</code> 做呢？答案是：做不到。</p>
<p>当然不是说 Function Component 无法实现异步取数，而是用的工具错了。</p>
<h3> 使用自定义 Hook 处理副作用</h3>
<p>比如上面抛出的异步取数场景，在 Function Component 的最佳做法是封装成一个自定义 Hook：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，自定义 Hook 拥有完整生命周期，我们可以将取数过程封装起来，只暴露状态 - 是否在加载中：<code>isLoading</code> 是否取数失败：<code>isError</code> 数据：<code>data</code>。</p>
<p>在组件中使用起来非常方便：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果这个值需要存储到数据流，在所有组件之间共享，我们可以结合 <code>useEffect</code> 与 <code>useReducer</code>：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>到此，Function Component 的入门概念就讲完了，最后附带一个彩蛋：Function Component 的 DefaultProps 怎么处理？</p>
<h3> Function Component 的 DefaultProps 怎么处理？</h3>
<p>这个问题看似简单，实则不然。我们至少有两种方式对 Function Component 的 DefaultProps 进行赋值，下面一一说明。</p>
<p>首先对于 Class Component，DefaultProps 基本上只有一种大家都认可的写法：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然而在 Function Component 就五花八门了。</p>
<h4> 利用 ES6 特性在参数定义阶段赋值</h4>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>这种方法看似很优雅，其实有一个重大隐患：<strong>没有命中的 <code>props</code> 在每次渲染引用都不同。</strong></p>
<p>看这种场景：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>只要 <code>type</code> 的引用不变，<code>useEffect</code> 就不会频繁的执行。现在通过父元素刷新导致 <code>Child</code> 跟着刷新，我们发现，<strong>每次渲染都会打印出日志，也就意味着每次渲染时，<code>type</code> 的引用是不同的。</strong></p>
<p>有一种不太优雅的方式可以解决：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>此时不断刷新父元素，只会打印出一次日志，因为 <code>type</code> 的引用是相同的。</p>
<p><strong>我们使用 DefaultProps 的本意必然是希望默认值的引用相同，</strong> 如果不想单独维护变量的引用，还可以借用 React 内置的 <code>defaultProps</code> 方法解决。</p>
<h4> 利用 React 内置方案</h4>
<p>React 内置方案能较好的解决引用频繁变动的问题：</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上面的例子中，不断刷新父元素，只会打印出一次日志。</p>
<p><strong>因此建议对于 Function Component 的参数默认值，建议使用 React 内置方案解决，因为纯函数的方案不利于保持引用不变。</strong></p>
<p>最后补充一个父组件 “坑” 子组件的经典案例。</p>
<h3> 不要坑了子组件</h3>
<p>我们做一个点击累加的按钮作为父组件，那么父组件每次点击后都会刷新：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>另外我们将 <code>schema = { b: 1 }</code> 传递给子组件，这个就是埋的一个大坑。</p>
<p>子组件的代码如下：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>只要父级 <code>props.schema</code> 变化就会打印日志。结果自然是，父组件每次刷新，子组件都会打印日志，也就是 <strong>子组件 <code>[props.schema]</code> 完全失效了，因为引用一直在变化。</strong></p>
<p>其实 <strong>子组件关心的是值，而不是引用，所以一种解法是改写子组件的依赖：</strong></p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样可以保证子组件只渲染一次。</p>
<p>可是真正罪魁祸首是父组件，我们需要利用 Ref 优化一下父组件：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样 <code>schema</code> 的引用能一直保持不变。如果你完整读完了本文，应该可以充分理解第一个例子的 <code>schema</code> 在每个渲染快照中都是一个新的引用，而 Ref 的例子中，<code>schema</code> 在每个渲染快照中都只有一个唯一的引用。</p>
<h1> 3. 总结</h1>
<p>所以使用 Function Component 你入门了吗？</p>
<p>本次精读留下的思考题是：Function Component 开发过程中还有哪些容易犯错误的细节？</p>
<blockquote>
<p>讨论地址是：<a href="https://github.com/dt-fe/weekly/issues/157" target="_blank" rel="noopener noreferrer">精读《Function Component 入门》 · Issue #157 · dt-fe/weekly</a></p>
</blockquote>
<p><strong>如果你想参与讨论，请 <a href="https://github.com/dt-fe/weekly" target="_blank" rel="noopener noreferrer">点击这里</a>，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。</strong></p>
<blockquote>
<p>关注 <strong>前端精读微信公众号</strong></p>
</blockquote>
<img width="200" src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
<p><strong>special Sponsors</strong></p>
<ul>
<li><a href="https://e.coding.net/?utm_source=weekly" target="_blank" rel="noopener noreferrer">DevOps 全流程平台</a></li>
</ul>
<blockquote>
<p>版权声明：自由转载-非商用-非衍生-保持署名（<a href="https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh" target="_blank" rel="noopener noreferrer">创意共享 3.0 许可证</a>）</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>CICD流</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-7%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/01-CICD%E6%B5%81.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-7%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/01-CICD%E6%B5%81.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">CICD流</source>
      <description>GitHub CI/CD流 我使用的是腾讯云服务器 云服务创建密匙对：在管理云服务器界面，创建SSH密钥（公匙和私匙），并绑定在该云服务器 上传Github：将项目上传Github，并在项目中创建.github文件，在.github文件下创建workflows文件，在workflows下添加push.yaml，添加以代码 创建Github密匙： 新建两个密匙REMOTE_HOST PRIVATE_KEY REMOTE_HOST 为云服务器ip地址 PRIVATE_KEY 为私钥（.pem文件内容） image-20230614091014044</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>GitHub CI/CD流</p>
<p>我使用的是腾讯云服务器</p>
<ol>
<li>
<p>云服务创建密匙对：在管理云服务器界面，创建SSH密钥（公匙和私匙），并绑定在该云服务器</p>
</li>
<li>
<p>上传Github：将项目上传Github，并在项目中创建<code>.github</code>文件，在<code>.github</code>文件下创建<code>workflows</code>文件，在<code>workflows</code>下添加<code>push.yaml</code>，添加以代码</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div></li>
<li>
<p>创建Github密匙：</p>
<ul>
<li>新建两个密匙<code>REMOTE_HOST</code> <code>PRIVATE_KEY</code></li>
<li><code>REMOTE_HOST</code> 为云服务器ip地址</li>
<li><code>PRIVATE_KEY</code> 为私钥（.pem文件内容）</li>
</ul>
<figure><img src="https://shinoimg.yyshino.top/img/202306140910899.png" alt="image-20230614091014044" tabindex="0" loading="lazy"><figcaption>image-20230614091014044</figcaption></figure>
<ol start="4">
<li></li>
</ol>
</li>
</ol>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202306140910899.png" type="image/png"/>
    </item>
    <item>
      <title>精读《持续集成 vs 持续交付 vs 持续部署》</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-7%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%20vs%20%E6%8C%81%E7%BB%AD%E4%BA%A4%E4%BB%98%20vs%20%E6%8C%81%E7%BB%AD%E9%83%A8%E7%BD%B2%E3%80%8B.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-7%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%20vs%20%E6%8C%81%E7%BB%AD%E4%BA%A4%E4%BB%98%20vs%20%E6%8C%81%E7%BB%AD%E9%83%A8%E7%BD%B2%E3%80%8B.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">精读《持续集成 vs 持续交付 vs 持续部署》</source>
      <description>转自https://github.com/ascoders/weekly/tree/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF 一、摘要 相信大家以前应该接触过持续集成（Continuous integration）持续交付（continuous delivery）持续发布（continuous deployment）的概念，下面我们来说说三者的差异以及团队如何入手 CI/CD。 作者：猫神。 二、差异 2.1 CI 持续集成</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>转自https://github.com/ascoders/weekly/tree/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF</p>
</blockquote>
<h1> 一、摘要</h1>
<p>相信大家以前应该接触过持续集成（Continuous integration）持续交付（continuous delivery）持续发布（continuous deployment）的概念，下面我们来说说三者的差异以及团队如何入手 CI/CD。</p>
<p>作者：猫神。</p>
<h1> 二、差异</h1>
<h3> 2.1 CI 持续集成</h3>
<p>开发者尽量时时刻刻合并开发分支至主干分支。避免直到发布日才开始合并，掉入集成地狱。无论何时新分支集成至项目，持续集成可以自动化测试持续验证应用是否正常。</p>
<h3> 2.2 CD 持续交付</h3>
<p>持续交付是持续集成的扩展，可以保证稳定的发布产品新特性。这意味着基于自动化测试，你可以也可以一键自动化发布。理论上，持续交付可以决定是按天，按周，按双周发布产品。如果确实希望能够享受持续交付的好处，那么应该尽快发布到新产品中。一旦出现问题时能尽早排除。</p>
<h3> 2.3 CD 持续部署</h3>
<p>持续部署是持续交付的下一步。通过这一步，每个新特性都自动的部署到产品中。但是如果出现未通过的测试用例将会终止自动部署。持续部署可以加速用户反馈新特性，避免发布日带来的压力。开发可以着力于开发系统，开发结束后几分钟就可以触达到用户。</p>
<h1> 三、协作</h1>
<p>CI/CD 具体是个什么样的流程呢，如下图所示，差异仅在于是否自动部署。</p>
<img src="https://img.alicdn.com/tfs/TB1sNtaTrPpK1RjSZFFXXa5PpXa-884-426.png">
<p>现在开发都讲究投入产出比，那么 CI/CD 具体需要做些什么呢？</p>
<h3> Continuous Intergretion 持续集成</h3>
<p>投入：</p>
<ul>
<li>
<p>需要为每个新特性编写测试用例</p>
</li>
<li>
<p>需要搭建持续集成服务器，监控主干仓库，并自动运行测试用例</p>
</li>
<li>
<p>开发需要尽量频繁的合并分支，至少一天一次</p>
</li>
</ul>
<p>产出：</p>
<ul>
<li>更少的 bug，因为自动化测试可以回归测试产品</li>
<li>编译部署产品更简化，因为集成的问题都尽早的解决了</li>
<li>开发者可以尽量减少上下文切换，因为构建的时候就暴露问题，尽早解决了</li>
<li>测试成本降低，因为 CI 服务器可以一秒运行几百个测试用例</li>
<li>测试团队花更少的时间测试，可以重点关注测试上的改进。</li>
</ul>
<h3> Continuous delivery 持续交付</h3>
<p>投入：</p>
<ul>
<li>需要有持续集成的基础，测试用例需要覆盖足够的代码</li>
<li>部署需要自动化，用户只需要手动触发，剩余的部署应该自动化</li>
<li>团队需要增加新特性标志，避免未完成的新特性进入待发布的产品</li>
</ul>
<p>产出：</p>
<ul>
<li>部署软件变得非常简单。团队不需要花费 n 天准备发布。</li>
<li>可以提高发布频率，加速新特性触达用户进程。</li>
<li>小的更改，对决策的压力要小得多，可以更快地迭代。</li>
</ul>
<h3> Continuous deployment 持续部署</h3>
<p>投入：</p>
<ul>
<li>测试必须要做到足够。测试的质量将决定发布的质量。</li>
<li>文档建设需要和产品部署保持同步。</li>
<li>新特性的发布需要协调其他部门，包括售后支持&amp;市场&amp;推广等。</li>
</ul>
<p>产出：</p>
<ul>
<li>快速的发布节奏，因为每个新特性一旦完成都会自动的发布给用户。</li>
<li>发布风险降低，修复问题更容易，因为每次变更都是小步迭代发布。</li>
<li>用户可以看到持续性的优化和质量提升，而不是非要等到按月，按季度，甚至按年</li>
</ul>
<p>如果开发的是一个新项目，暂时还没有任何用户，那么每次提交代码后发布将会特别简单，可以随时随地发布。一旦产品开始开发后，就需要提高测试文化，并确保在构建应用程序时增加代码覆盖率。当您准备好面向用户发布时，您将有一个非常好的连续部署过程，在该过程中，所有新的更改都将在自动发布到生产环境之前进行测试。</p>
<p>如果正在开发的是一个老系统，就需要放慢节奏，开始打造持续集成&amp;持续交付。首先可以完成一些简单可自动化执行的单元测试，不需要考虑复杂的端到端的测试。另外，应该尽快尝试自动化部署，搭建可以自动化部署的临时环境。因为自动化部署，可以让开发者去优化测试用例，而不是停下来联调发布。
一旦开始按日发布产品，我们可以考虑持续部署，但一定要保证团队已经准备好这种方式，文档 &amp; 售后支持 &amp; 市场。这些步骤都需要加入到新产品发布节奏中，因为和用户直接打交道的是他们。</p>
<h1> 四、如何开始持续集成</h1>
<h2> 4.1 了解测试类型</h2>
<p>为了获得 CI 的所有好处，每次代码变更后，我们需要自动运行测试用例。我们需要在每个分支运行测试用例，而不是仅仅在主干分支。这样可以最快速的找到问题，最小化问题影响面。
在初始阶段并不需要实现所有的测试类型。一开始可以以单元测试入手，随着时间扩展覆盖面。</p>
<ul>
<li>单元测试：范围非常小，验证每个独立方法级别的操作。</li>
<li>集成测试：保证模块间运行正常，包括多个模块、多个服务。</li>
<li>验收测试：与集成测试类似，但是仅关注业务 case，而不是模块内部本身。</li>
<li>UI 测试：从用户的角度保证呈现正确运行。</li>
</ul>
<p>并不是所有的测试都是对等的，实际运行中可以做些取舍。</p>
<img src="https://img.alicdn.com/tfs/TB18278TmrqK1RjSZK9XXXyypXa-346-269.png">
<p>单元测试实现起来既快成本又低，因为它们主要是对小代码块进行检查。另一方面，UI 测试实施起来很复杂，运行起来很慢，因为它们通常需要启动一个完整的环境以及多个服务来模拟浏览器或移动行为。因此，实际情况可能希望限制复杂的 UI 测试的数量，并依赖基础上良好的单元测试来快速构建，并尽快获得开发人员的反馈。</p>
<h3> 4.2 自动运行测试</h3>
<p>要采用持续集成，您需要对推回到主分支的每个变更运行测试。要做到这一点，您需要有一个服务来监视您的存储库，并听取对代码库的新推送。您可以从企业预置型解决方案和云端解决方案中进行选择。您需要考虑以下因素来选择服务器：</p>
<ul>
<li>代码托管在哪里？CI 服务可以访问您的代码库吗？您对代码的生存位置有特殊的限制吗？</li>
<li>应用程序需要哪些操作系统和资源？应用程序环境是否受支持？能安装正确的依赖项来构建和测试软件吗？</li>
<li>测试需要多少资源？一些云应用程序可能对您可以使用的资源有限制。如果软件消耗大量资源，可能希望将 CI 服务器宿主在防火墙后面。</li>
<li>团队中有多少开发人员？当团队实践 CI 时，每天都会将许多更改推回到主存储库中。对于开发人员来说，要获得快速的反馈，您需要减少构建的队列时间，并且您需要使用能够提供正确并发性的服务或服务器。
在过去，通常需要安装一个独立的 CI 服务器，如 Bamboo 或 Jenkins，但现在您可以在云端找到更简单的解决方案。例如，如果您的代码托管在 BitBucket 云上，那么您可以使用存储库中的 Pipelines 功能在每次推送时运行测试，而无需配置单独的服务器或构建代理，也无需限制并发性。</li>
<li>使用代码覆盖率查找未测试的代码。一旦您采用了自动化测试，最好将它与一个测试覆盖工具结合起来，帮助了解测试套件覆盖了多少代码库。代码覆盖率定在 80%以上是很好的，但要注意不要将高覆盖率与良好的测试套件混淆。代码覆盖工具将帮助您找到未经测试的代码，但在一天结束的时候，测试的质量会产生影响。如果刚开始，不要急于获得代码库的 100%覆盖率，而是使用测试覆盖率工具来找出应用程序的关键部分，这些部分还没有测试并从那里开始。</li>
<li>重构是一个添加测试的机会。如果您将要对应用程序进行重大更改，那么应该首先围绕可能受到影响的特性编写验收测试。这将为您提供一个安全网，以确保在重构代码或添加新功能后，原始行为不会受到影响。</li>
</ul>
<h1> 五、接受 CI 文化</h1>
<p>自动化测试是 CI 的关键，但同时也需要团队成员接受 CI 文化，并不是心血来潮晒两天鱼，并且需要保证编译畅通无阻。QA 可以帮助团队建设测试文化。他们不再需要手动测试应用程序的琐碎功能，现在他们可以投入更多的时间来提供支持开发人员的工具，并帮助他们采用正确的测试策略。一旦开始采用持续集成，QA 工程师将能够专注于使用更好的工具和数据集促进测试，并帮助开发人员提高编写更好代码的能力。</p>
<ul>
<li>尽早集成。如果很长时间不合并代码，代码冲突的风险就越高，代码冲突的范围就越广。如果发现某些分支会影响已经存在的分支，需要增加发布关闭标签，避免发布时两个分支冲突。</li>
<li>保证编译时时刻刻畅通。一旦发现任何编译问题，立刻修复，否则可能会带来更多的错误。测试套件需要尽快反馈测试结果，或者优先返回短时间测试（单元测试）的结果，否则开发者可能就切换回开发了。一旦编译出错，需要通知给开发者，或者更进一步给出一个 dashboard，每个人都可以在这里查看编译结果。</li>
<li>把测试用例纳入流程的一部分。确保每个分支都有自动化测试用例。似乎编写测试用例拖慢了项目节奏，但是它可以减少回归时间，减少每次迭代带来的 bug。而且每次测试通过后，将会非常有信息合并到主干分支，因为新增的内容不影响以前的功能。</li>
<li>修 bug 的时候编写测试用例。把 bug 的每个场景都编写成测试用例，避免再次出现。</li>
</ul>
<h1> 六、集成测试 5 个步骤</h1>
<ol>
<li>从最严格的代码部分入手测试</li>
<li>搭建一个自动构建的服务自动运行测试用例，在每次提交代码后。</li>
<li>确保团队成员每天合并变更</li>
<li>代码出现问题及时修复</li>
<li>为每个新实现的操作编写测试用例。
可能看着很简单，但是要求团队能够真正落地。一开始你需要放慢发布的脚步，需要和 pd、用户沟通确保不上线没有测试用例的新功能。我们的建议是从小处入手，通过简单的测试来适应新的例程，然后再着手实现更复杂更难管理的测试套件。</li>
</ol>
<h1> 七、说说笔者的团队</h1>
<p>以上文章主要是说明团队实现 CI/CD 的取舍和可行性步骤。下面来说说希望 CI/CD 给笔者团队带来什么样的变化。目前笔者团队已经实现前端项目发布编译工程化，采用的是基于 webpack 的自建工具云构建模式。但现在面临的问题是 1. 交互的系统比较多，交互系统提供的接入源变更后，需要人工通知其他系统手动触发编译，而且每次手动编译都需要在本地切换到指定分支，然后手动触发云构建，2. 多人协作，分支拆分较细，需要手动合并分支，触发编译。整个流程冗长，而且中间存在人力沟通成本，容易产生沟通误差。所以首先希望解决的是 CI 自动化，当依赖变更后或者分支合并后，自动集成，自动编译。当然生产环境暂时还不敢瞎搞，但大部分重复编译的工作量主要集中在预发环境，所以手动部署生产环境的成本还是可以接受的。CI 自动化之前，需要提供系统之间交互的单元测试用例，每次 CI 后自动运行单元测试用例，最好能打通 QA 的测试用例，进行回归测试。流程对比如下：</p>
<img src="https://img.alicdn.com/tfs/TB1jIw.TgDqK1RjSZSyXXaxEVXa-1950-662.png">
可以看出引入CI后，我们的成本是需要搭建CI服务器，新增单元测试、打通回归测试案例，但前者可以加快系统编译效率，后者可以进一步的提升代码质量，减少回归测试时间，这些成本都是可以接受的。市面上已有很多开源持续集成工具，例如我们熟悉的Jenkins，还有TeamCity、Travis CI、GO CD、Bamboo、Gitlab CI、CircleCI……等等等等。目前还在继续调研中，这片文章应该会有第二篇，说说后续的实践和CD。
<blockquote>
<p>讨论地址是：<a href="https://github.com/dt-fe/weekly/issues/147" target="_blank" rel="noopener noreferrer">精读《持续集成 vs 持续交付 vs 持续部署》 · Issue #147 · dt-fe/weekly</a></p>
</blockquote>
<p><strong>如果你想参与讨论，请 <a href="https://github.com/dt-fe/weekly" target="_blank" rel="noopener noreferrer">点击这里</a>，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。</strong></p>
<blockquote>
<p>关注 <strong>前端精读微信公众号</strong></p>
</blockquote>
<img width="200" src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
<p><strong>special Sponsors</strong></p>
<ul>
<li><a href="https://e.coding.net/?utm_source=weekly" target="_blank" rel="noopener noreferrer">DevOps 全流程平台</a></li>
</ul>
<blockquote>
<p>版权声明：自由转载-非商用-非衍生-保持署名（<a href="https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh" target="_blank" rel="noopener noreferrer">创意共享 3.0 许可证</a>）</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title></title>
      <link>https://v-blog.yyshino.top/front_end_interview/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E4%B9%A0%E6%80%BB%E7%BB%93/%E4%B8%AD%E5%9B%BD%E7%8E%B0%E4%BB%A3%E5%8C%96%E5%8F%91%E5%B1%95%E5%8F%AF%E8%A7%86%E5%8C%96-%E5%89%8D%E7%AB%AF%E7%AD%94%E8%BE%A9.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E4%B9%A0%E6%80%BB%E7%BB%93/%E4%B8%AD%E5%9B%BD%E7%8E%B0%E4%BB%A3%E5%8C%96%E5%8F%91%E5%B1%95%E5%8F%AF%E8%A7%86%E5%8C%96-%E5%89%8D%E7%AB%AF%E7%AD%94%E8%BE%A9.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml"></source>
      <description>项目首页 首页我们是使用PS进行绘制，通过img标签展示，CSS配置自适应。 词云页 3D词云实现 πr平方 首先是将词云数据进行存储并渲染在页面上，设置圆心坐标，以及最大圆半径，通过Math对象上的三角函方法计算词云运动轨迹。 创建Tag类，标识x,y,z三维坐标，其中z用于计算为缩放比例和透明度，x，y为横纵坐标，设计move()标签旋转方法传入最大圆半径，标签数量、球心横坐标、球心纵坐标，动态修改标签横坐标、纵坐标实现旋转 监听鼠标移动事件mousemove，设置旋转角度。</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 项目首页</h3>
<p>首页我们是使用PS进行绘制，通过img标签展示，CSS配置自适应。</p>
<h3> 词云页</h3>
<h4> 3D词云实现</h4>
<p>πr平方</p>
<ol>
<li>首先是将词云数据进行存储并渲染在页面上，设置圆心坐标，以及最大圆半径，通过Math对象上的三角函方法计算词云运动轨迹。</li>
<li>创建Tag类，标识x,y,z三维坐标，其中z用于计算为缩放比例和透明度，x，y为横纵坐标，设计move()标签旋转方法传入最大圆半径，标签数量、球心横坐标、球心纵坐标，动态修改标签横坐标、纵坐标实现旋转</li>
<li>监听鼠标移动事件<code>mousemove</code>，设置旋转角度。</li>
</ol>
<h4> 普通词云</h4>
<p>通过<code>echarts-wordCloud</code> 插件,传入配置、遮罩图片（maskImage）以及数据，由ECharts生成</p>
<h3> 四个现代化</h3>
<h4> 基本图表实现（折线、柱形、饼图）</h4>
<p>传入配置、以及数据、由ECharts生成。</p>
<h4> 象形柱图</h4>
<p>利用svg图标替换象形图默认展示的形状，具体操作是在symbol属性下设置svg数据。</p>
<h4> 立体柱形图</h4>
<p>由三部分组成，上下深浅菱形象形图以及中间的柱形图构成。</p>
<h4> 时间轴</h4>
<p>时间轴的配置项由基础配置<code>BaseOption</code>和变量配置<code>options</code>,基础配置用于配置时间轴样式以及不同时间线下的图表默认样式</p>
<h3> 人口规模</h3>
<h3> 共同富裕</h3>
<h4> 地图</h4>
<p>通过ECharts注册高德地图GeoJSON数据，并配置地图别名，可在type属性下填写该别名由ECharts生成地图</p>
<h3> 物资精神文明</h3>
<h3> 和平发展</h3>
<h4> 水球图</h4>
<p>音标<code>ˈlɪkwɪd fɪlm*/*</code></p>
<p>利用ECharts插件<code>echarts-liquidfill </code>插件传入样式以及数据，由ECharts生成</p>
<h3> 前端架构</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202304290037360.png" alt="前端" tabindex="0" loading="lazy"><figcaption>前端</figcaption></figure>
<p>我们的采用Vue3+TypeScript+ECharts进行前端架构，Vue3用于搭建页面实现可视化图表的布局、二次封装ECharts图表组件便于代码复用,TypeScript保证代码的可读性和维护性，ECharts个性化订制图表主题，实现20多种图表。我们是项目数据利用python爬虫爬取数据存储在Excel表格中。地图数据通过Axios发起请求获取高德地图GeoJSON 地图数据动态展示地图。</p>
<h2> ECharts技术</h2>
<p>二次封装ECharts，通过将图表封装成一个组件，接收父组件传递的长、宽、图表数据来控制，这一图表的</p>
<h3> 地图</h3>
<p>GeoJSON 是一种对各种地理数据结构进行编码的格式。GeoJSON 对象可以表示几何、特征或者特征集合。GeoJSON 支持下面几何类型：点、线、面、多点、多线、多面和几何集合。GeoJSON 里的特征包含一个几何对象和其他属性，特征集合表示一系列特征。</p>
<p>一个完整的 GeoJSON 数据结构总是一个（JSON 术语里的）对象。在 GeoJSON 里，对象由 <strong>名/值对（name/value pair）</strong>——也称作成员的集合组成。对每个成员来说，名称总是字符串。成员的值要么是字符串、数字、对象、数组，要么是下面文本常量中的一个：<code>true</code>, <code>false</code> 和 <code>null</code>。数组由元素（element）组成，其中每个元素都是如上所述的值。</p>
<h2> Vue3</h2>
<p>页面搭建、可视化图表布局</p>
<h2> TypeScript</h2>
<p>类型系统和错误捕获、代码和补全、更好的工程化支持，在此基础上为项目提供更好的可维护性。</p>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202304290037360.png" type="image/png"/>
    </item>
    <item>
      <title></title>
      <link>https://v-blog.yyshino.top/front_end_interview/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E4%B9%A0%E6%80%BB%E7%BB%93/%E5%9F%BA%E4%BA%8E%E5%8C%BA%E5%9D%97%E9%93%BE-%E5%89%8D%E7%AB%AF%E7%AD%94%E8%BE%A9.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E4%B9%A0%E6%80%BB%E7%BB%93/%E5%9F%BA%E4%BA%8E%E5%8C%BA%E5%9D%97%E9%93%BE-%E5%89%8D%E7%AB%AF%E7%AD%94%E8%BE%A9.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml"></source>
      <description>遇到的问题 回答问题先说结论 http://101.35.227.27/ 跨域问题： 由于我们项目擦用前后端分离，前端和后端的服务不在同一个源（两个 URL 的协议、端口 (en-US)（如果有指定的话）和主机都相同）下，当前端访问后端接口时会产生跨域问题。</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 遇到的问题</h3>
<p><strong>回答问题先说结论</strong></p>
<p>http://101.35.227.27/</p>
<h3> 跨域问题：</h3>
<p>由于我们项目擦用前后端分离，前端和后端的服务不在<strong>同一个源</strong>（两个 URL 的<a href="https://developer.mozilla.org/zh-CN/docs/Glossary/Protocol" target="_blank" rel="noopener noreferrer">协议</a>、<a href="https://developer.mozilla.org/en-US/docs/Glossary/Port" target="_blank" rel="noopener noreferrer">端口 (en-US)</a>（如果有指定的话）和<a href="https://developer.mozilla.org/zh-CN/docs/Glossary/Host" target="_blank" rel="noopener noreferrer">主机</a>都相同）下，当前端访问后端接口时会产生跨域问题。</p>
<p>前端解决方案：</p>
<ol>
<li>
<p>本地开发时，利用Vite/Webpack 配置代理DevServer，将/api下的接口请求，重写并代理转发到后端接口所在端口</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol>
<li>
<p>原理： webpack中的<code>proxy</code>只是一层代理，用于把指定的<code>path</code>，代理去后端提供的地址，背后使用node来做server。可能有人疑惑，为什么只适用本地开发？因为该技术只是在webpack打包阶段在本地临时生成了node server，来实现类似nginx 的<code>proxy_pass</code>的反向代理效果</p>
<p><code>proxy</code>工作原理实质上是利用<code>http-proxy-middleware</code> 这个http代理中间件，实现请求转发给其他服务器。例如：本地主机A为<code>http://localhost:3000</code>，该主机浏览器发送一个请求，接口为<code>/api</code>，这个请求的数据（响应）在另外一台服务器B<code>http://10.231.133.22:80</code>上，这时，就可以通过A主机设置webpack proxy，直接将请求发送给B主机。</p>
</li>
</ol>
</li>
<li>
<p>部署到云服务器上时，利用Nginx反向代理，重写/api路径下的请求，代理转发到相应后端接口地址</p>
<ul>
<li>
<p>原理：<strong>反向代理（Reverse Proxy）方式是指以代理服务器来接受internet上的连接请求，然后将请求转发给内部网络上的服务器，并将从服务器上得到的结果返回给internet上请求连接的客户端，此时代理服务器对外就表现为一个服务器。</strong></p>
<p>举个例子，比如我想访问 <code>http://www.test.com/readme</code>，但<code>www.test.com</code>上并不存在readme页面，于是他是偷偷从另外一台服务器上取回来，然后作为自己的内容返回用户，但用户并不知情。这里所提到的 <code>www.test.com</code> 这个<a href="https://cloud.tencent.com/act/pro/domain-sales?from=20065&amp;from_column=20065" target="_blank" rel="noopener noreferrer">域名</a>对应的服务器就设置了反向代理功能。</p>
<p><strong>结论就是，反向代理服务器对于客户端而言它就像是原始服务器，并且客户端不需要进行任何特别的设置</strong>。客户端向反向代理的命名空间(name-space)中的内容发送普通请求，接着反向代理服务器将判断向何处(原始服务器)转交请求，并将获得的内容返回给客户端，就像这些内容原本就是它自己的一样。</p>
<p><strong>正向代理，既然有反向代理，就肯定有正向代理。什么叫正向代理呢？</strong></p>
<p>正向代理（Forward Proxy）通常都被简称为代理，就是在用户无法正常访问外部资源，比方说受到GFW的影响无法访问twitter的时候，我们可以通过代理的方式，让用户绕过防火墙，从而连接到目标网络或者服务。</p>
<p><strong>正向代理的工作原理就像一个跳板</strong>，比如：我访问不了google.com，但是我能访问一个代理服务器A，A能访问google.com，于是我先连上代理服务器A，告诉他我需要google.com的内容，A就去取回来，然后返回给我。从网站的角度，只在代理服务器来取内容的时候有一次记录，有时候并不知道是用户的请求，也隐藏了用户的资料，这取决于代理告不告诉网站。</p>
<p><strong>结论就是，正向代理是一个位于客户端和原始服务器(origin server)之间的服务器</strong>。为了从原始服务器取得内容，客户端向代理发送一个请求并指定目标(原始服务器)，然后代理向原始服务器转交请求并将获得的内容返回给客户端。</p>
</li>
<li>
<p>[通俗易懂的Nginx工作原理 - 腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1427219#:~:text=通俗易懂的Nginx工作原理 1 1.1 概念 反向代理（Reverse Proxy）方式是指以代理服务器来接受internet上的连接请求，然后将请求转发给内部网络上的服务器，并将从服务器上得到的结果返回给internet上请求连接的客户端，此时代理服务器对外就表现为一个服务器。 ... 2,... 7 3.2 一个简单的HTTP请求 ... 8 3.3 请求完整处理过程)</p>
</li>
</ul>
</li>
</ol>
<h3> 选择node-<a href="https://so.csdn.net/so/search?q=sass&amp;spm=1001.2101.3001.7020" target="_blank" rel="noopener noreferrer">sass</a>的版本</h3>
<p><strong>node版本与node-sass的版本需要对应</strong>。这一点非常重要，版本不对应会出现各种各样的问题，导致node-sass下载失败。<strong>由于我使用的node版本是14.16.0，所以选择node-sass 4.14.1</strong>。</p>
<h3> 动画图片来源</h3>
<p>图标字体：阿里巴巴矢量图标</p>
<p>svg可伸缩矢量图形：使用的是unDraw免费开源图形库</p>
<p>动态图片来源<code>lottiefiles</code>，轻量级的，可扩展的动画，为您的广告和社交媒体lottiefiles带走了复杂的运动设计。它可以让你以最简单的方式创建、编辑、测试、协作和发布Lottie。</p>
<h3> 持久化存储用户信息</h3>
<p>页面跳转时，你的用户的导航栏的用户信息如何获取？</p>
<p>通过登录时获取的用户信息，然后存储在<code>localStorage</code>之中，页面跳转时在Mounted中调用<code>localStorage.get()</code>传入key进行获取。</p>
<ol>
<li>增/改。登录后获取用户信息，通过调用<code>localStorage.setItem()</code>传入key和用户信息对象进行存储。除非手动删除或者设置过期时间，否则不会因浏览器的关闭而清空。</li>
<li>查。需要获取时调用<code>localStorage.getItem()</code>传入key，获取相应对象。</li>
<li>删。<code>localStorage.removeItem()</code></li>
</ol>
<h3> ElementUI</h3>
<h3> 无限滚动（Infinite Scroll）</h3>
<ol>
<li>触底触发load()方法</li>
<li>load()方法中添加新的数据</li>
<li>当没有数据可以添加时，显示触底动画</li>
</ol>
<h3> 骨架屏的实现</h3>
<ol>
<li>预先编译好一段相同结构的纯HTMLCSS代码</li>
<li>通过v-if这个Vue指令，在数据还获取的这段时间展示预先编译好的这段代码，获取到之后取反标识，展示我们的帖子数据。</li>
</ol>
<p>浏览器在获取和渲染大量的数据时，是需要时间的，而这段时间页面通常会显示白屏。为了解决这个问题，我们通过预先编译好一段相同结构的纯HTMLCSS代码，然后定义一个标识（标识数据是否获取到了），通过v-if这个Vue指令，在数据还获取的这段时间展示预先编译好的这段代码，获取到之后取反标识，展示我们的帖子数据。</p>
<h3> 文件的上传功能模块</h3>
<p>短篇不支持富文本，而长篇支持富文本。逻辑这部分基本相同。</p>
<p>文件上传逻辑</p>
<ol>
<li>添加请求头 <code>'Conten-Type': "multipart/form-data"</code>，由于上传文件需要一定时间设置<code>timeout</code>超时时间为180秒，保证文件上传的成功性。</li>
<li>文件数据转换为<code>FormData</code>格式</li>
<li>调用后端接口上传文件</li>
</ol>
<p>文件上传时的异常处理：</p>
<ol>
<li>
<p>文件上传成功，但发布失败，此时调用oss文件删除接口</p>
</li>
<li>
<p>文件上传失败</p>
</li>
</ol>
<h3> 帖子详情查看</h3>
<p>逻辑：通过获取文件后缀，进行判断，动态渲染目标文件。</p>
<p>文件预览</p>
<ul>
<li>
<p>图片</p>
<ul>
<li>通过嵌入img标签</li>
</ul>
</li>
<li>
<p>视频</p>
<ul>
<li>通过第三方库Video库</li>
</ul>
</li>
<li>
<p>文档类</p>
<ul>
<li>
<p>通过嵌入IFrame标签，提供文档链接，使用微软提供的Office Online进行预览</p>
</li>
<li>
<p>使用微软提供的Office Online平台只需要一个网址即可在线查看Xls,doc,PPT等文档</p>
<div class="language-url line-numbers-mode" data-ext="url"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div></li>
</ul>
</li>
</ul>
<h3> 搜索功能</h3>
<p>调用后台接口获取数据</p>
<p>输入搜索信息，通过JavaScript 中的<code>include</code>查找搜索信息、<code>filter</code>方法过滤数组。动态搜索帖子</p>
<h3> 后台</h3>
<h3> 地图显示</h3>
<p>通过百度地图开放平台获取的API key，结合Bmap插件，由ECharts生成。</p>
<p>百度地图审图号：</p>
<h3> ECharts图表展示</h3>
<p>通过传入ECharts配置文件，调用后端接口获取数据，由ECharts初始化并渲染图表。</p>
<p>地图是通过百度开放平台获取API key，结合Bmap插件和ECharts渲染地图。</p>
<h3> 导出Excel</h3>
<ul>
<li>调用接口设置 请求头 <code>responseType:'blob'</code>接收后台传递过来的文件数据，存入blob对象中</li>
<li>通过<code>window.URL.createObjectURL(blob)</code>传入blob对象生成url</li>
<li>动态创建<code>a</code>标签接收接收blob url，设置download属性，模拟a标签点击事件，下载Excel文件</li>
<li>最后删除a标签</li>
</ul>
<h3> 解决问题</h3>
<p>联盟链特点：</p>
<p>既可以</p>
<p>成本可控</p>
]]></content:encoded>
    </item>
    <item>
      <title>虚拟DOM</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-5Vue/03-%E8%99%9A%E6%8B%9FDOM.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-5Vue/03-%E8%99%9A%E6%8B%9FDOM.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">虚拟DOM</source>
      <description>Vue中的虚拟DOM Virtual DOM是一棵以JavaScript对象作为基础的树，每一个节点称为VNode，用对象属性来描述节点，实际上它是一层对真实DOM的抽象，最终可以通过渲染操作使这棵树映射到真实环境上，简单来说Virtual DOM就是一个Js对象，用以描述整个文档。 虚拟DOM优缺点 优点 Virtual DOM在牺牲(牺牲很关键)部分性能的前提下，增加了可维护性，这也是很多框架的通性。 实现了对DOM的集中化操作，在数据改变时先对虚拟DOM进行修改，再反映到真实的DOM，用最小的代价来更新DOM，提高效率。 打开了函数式UI编程的大门。 可以渲染到DOM以外的端，使得框架跨平台，比如ReactNative，React VR等。 可以更好的实现SSR，同构渲染等。 组件的高度抽象化。</description>
      <category>FrontEnd</category>
      <pubDate>Wed, 29 Nov 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Vue中的虚拟DOM</p>
<p><code>Virtual DOM</code>是一棵以<code>JavaScript</code>对象作为基础的树，每一个节点称为<code>VNode</code>，用对象属性来描述节点，实际上它是一层对真实<code>DOM</code>的抽象，最终可以通过渲染操作使这棵树映射到真实环境上，简单来说<code>Virtual DOM</code>就是一个<code>Js</code>对象，用以描述整个文档。</p>
<h3> 虚拟DOM优缺点</h3>
<h4> 优点</h4>
<ul>
<li><code>Virtual DOM</code>在牺牲(牺牲很关键)部分性能的前提下，增加了可维护性，这也是很多框架的通性。</li>
<li>实现了对<code>DOM</code>的集中化操作，在数据改变时先对虚拟<code>DOM</code>进行修改，再反映到真实的<code>DOM</code>，用最小的代价来更新<code>DOM</code>，提高效率。</li>
<li>打开了函数式<code>UI</code>编程的大门。</li>
<li>可以渲染到<code>DOM</code>以外的端，使得框架跨平台，比如<code>ReactNative</code>，<code>React VR</code>等。</li>
<li>可以更好的实现<code>SSR</code>，同构渲染等。</li>
<li>组件的高度抽象化。</li>
</ul>
<h4> 缺点</h4>
<ul>
<li>首次渲染大量<code>DOM</code>时，由于多了一层虚拟<code>DOM</code>的计算，会比<code>innerHTML</code>插入慢。</li>
<li>虚拟<code>DOM</code>需要在内存中的维护一份<code>DOM</code>的副本，多占用了部分内存。</li>
<li>如果虚拟<code>DOM</code>大量更改，这是合适的。但是单一的、频繁的更新的话，虚拟<code>DOM</code>将会花费更多的时间处理计算的工作。所以如果你有一个<code>DOM</code>节点相对较少页面，用虚拟<code>DOM</code>，它实际上有可能会更慢，但对于大多数单页面应用，这应该都会更快。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>TypeScript中最常用的12中工具类型</title>
      <link>https://v-blog.yyshino.top/posts/TypeScript/01-%E5%B8%B8%E7%94%A8%E7%9A%8412%E4%B8%AD%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%9E%8B.html</link>
      <guid>https://v-blog.yyshino.top/posts/TypeScript/01-%E5%B8%B8%E7%94%A8%E7%9A%8412%E4%B8%AD%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%9E%8B.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">TypeScript中最常用的12中工具类型</source>
      <description>TypeScript中最常用的12中工具类型</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 14 Sep 2023 15:42:32 GMT</pubDate>
      <content:encoded><![CDATA[<h2> TypeScript中最常用的12中工具类型</h2>
<table>
<thead>
<tr>
<th>工具类型</th>
<th>描述</th>
<th>发布版本</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Awaited&lt;Type&gt;</code></td>
<td>获取 Promise 中的结果类型</td>
<td>4.5</td>
</tr>
<tr>
<td><code>Partial&lt;Type&gt; </code></td>
<td>将Type中的所有属性设置为<strong>可选属性</strong>,返回一个新的类型。</td>
<td>2.1</td>
</tr>
<tr>
<td><code>Required&lt;Type&gt;</code></td>
<td>将 Type 中的所有属性设置为<strong>必选属性</strong>，返回一个新的类型。</td>
<td>2.8</td>
</tr>
<tr>
<td><code>Readonly&lt;Type&gt;</code></td>
<td>将Type中的所有属性设置为<strong>只读属性</strong>,返回一个新的类型。</td>
<td>2.1</td>
</tr>
<tr>
<td><code>Record&lt;Keys, Type&gt;</code></td>
<td>新建一个由Keys指定的属性和Type指定的值组成的对象类型。</td>
<td>2.1</td>
</tr>
<tr>
<td><code>Pick&lt;Type, Keys&gt;</code></td>
<td>从 Type 中选择一个或多个属性，并返回一个新的类型。</td>
<td>2.1</td>
</tr>
<tr>
<td><code>Omit&lt;Type, Keys&gt;</code></td>
<td>从 Type 中删除一个或多个属性，并返回一个新的类型。</td>
<td>3.5</td>
</tr>
<tr>
<td><code>Exclude&lt;UnionType, ExcludedMembers&gt;</code></td>
<td>从UnionType中排除ExcludedMembers 中的所有类型,并返回一个新的类型。</td>
<td>2.8</td>
</tr>
<tr>
<td><code>Extract&lt;UnionType, ExtractedMembers&gt;</code></td>
<td>从 UnionType 中提取 ExtractedMembers 中的所有类型，并返回一个新的类型。</td>
<td>2.8</td>
</tr>
<tr>
<td><code>NonNullable&lt;Type&gt;</code></td>
<td>从Type 中排除 null 和 undefined 类型，并返回一个新的类型。</td>
<td>2.8</td>
</tr>
<tr>
<td><code>Parameters&lt;Type&gt;</code></td>
<td>获取函数类型 Type 的参数类型，以元组类型返回。</td>
<td>3.1</td>
</tr>
<tr>
<td><code>ReturnType&lt;Type&gt;</code></td>
<td>获取函数类型 Type 的返回值类型。</td>
<td>2.8</td>
</tr>
</tbody>
</table>
]]></content:encoded>
    </item>
    <item>
      <title>uni-app Plugin</title>
      <link>https://v-blog.yyshino.top/posts/MiniProgram/MiniApp-uniapp-Plugin.html</link>
      <guid>https://v-blog.yyshino.top/posts/MiniProgram/MiniApp-uniapp-Plugin.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">uni-app Plugin</source>
      <description>uni-app Plugin</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 14 Sep 2023 15:40:27 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 滚动标签</h2>
<h3> 自定义 tab 选项卡 2</h3>
<h2> 图表</h2>
<h3> uCharts</h3>
<h4> 更新数据-加载动画踩坑</h4>
<h5> 加载动画说明</h5>
<p>官方组件共提供了5种加载动画样式（详见页面底部），加载动画展示的逻辑如下：</p>
<ol>
<li>如果是 uniCloud 数据，从发送网络请求到返回数据期间展示</li>
<li>如果是自行传入的 chartData 数据，<strong>当 chartData.series == [] 的时候展示 loading，也就是说初始化图表的时候，如果您没有数据，可以通过先传个空数组来展示 loading 效果</strong>，当 chartData.series 有数据后会自动隐藏 loading 图标。</li>
</ol>
<blockquote>
<p>如果您由始至终不想展示加载动画，可以赋值 loadingType = 0 来关闭动画效果。</p>
</blockquote>
<h3> App打包</h3>
<ul>
<li>'test'导出失败，失败原因：App打包时，<strong>项目中文件名不能含有中文或全角字符，请确认。</strong></li>
</ul>
<h3> Android平台签名证书(.keystore)生成指南</h3>
<p>https://ask.dcloud.net.cn/article/id-35777</p>
<p>新版jdk的keytool没有md5</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>VSCode</title>
      <link>https://v-blog.yyshino.top/posts/Plugin/Plugin-VSCode.html</link>
      <guid>https://v-blog.yyshino.top/posts/Plugin/Plugin-VSCode.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">VSCode</source>
      <description>VSCode</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 14 Sep 2023 15:40:01 GMT</pubDate>
      <content:encoded><![CDATA[<h2> VSCode</h2>
<h3> vscode打开setting.json</h3>
<p><code>Ctrl Shift P</code></p>
<p>输入<code>settings.json</code></p>
<h3> vscode配置 eslint+prettierrc自动格式化vue3、ts、tsx文件</h3>
<p>https://blog.csdn.net/m0_45236510/article/details/114704838</p>
<figure><img src="https://shinoimg.yyshino.top/img/202306191150783.png" alt="vscode配置" tabindex="0" loading="lazy"><figcaption>vscode配置</figcaption></figure>
<figure><img src="https://shinoimg.yyshino.top/img/202306191150004.png" alt="vscode配置" tabindex="0" loading="lazy"><figcaption>vscode配置</figcaption></figure>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202306191150783.png" type="image/png"/>
    </item>
    <item>
      <title>常用linux&amp;windows命令</title>
      <link>https://v-blog.yyshino.top/posts/Linux/Linux-%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4.html</link>
      <guid>https://v-blog.yyshino.top/posts/Linux/Linux-%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">常用linux&amp;windows命令</source>
      <description>常用linux&amp;windows命令</description>
      <category>Computer</category>
      <pubDate>Thu, 14 Sep 2023 15:36:09 GMT</pubDate>
      <content:encoded><![CDATA[<p>记录我的常用linux&amp;windows命令</p>
<table>
<thead>
<tr>
<th>描述</th>
<th>命令</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>查看端口占用</td>
<td>`netstat -aon</td>
<td>findstr "3000"`</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
]]></content:encoded>
    </item>
    <item>
      <title>DNS介绍</title>
      <link>https://v-blog.yyshino.top/reading_notes/DNS%E4%BB%8B%E7%BB%8D.html</link>
      <guid>https://v-blog.yyshino.top/reading_notes/DNS%E4%BB%8B%E7%BB%8D.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">DNS介绍</source>
      <description>DNS介绍</description>
      <category>浏览器</category>
      <pubDate>Tue, 01 Aug 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 什么是DNS</h3>
<p><strong>域名系统</strong> (DNS - Domain Name System) 是 Internet 电话簿。人们通过例如 <code>baidu.com</code> 或 <code>qq.com</code> 等域名在线访问信息。Web 浏览器通过 Internet 协议 (IP) 地址进行交互。DNS 将域名转换为 IP 地址，以便浏览器能够加载 Internet 资源。</p>
<p>连接到 Internet 的每个设备都有一个唯一 IP 地址，其他计算机可使用该 IP 地址查找此设备。DNS 服务器使人们无需存储例如 <code>192.168.1.1</code>（IPv4 中）等 IP 地址或更复杂的较新字母数字 IP 地址，例如 <code>2400:cb00:2048:1::c629:d7a2</code>（IPv6 中）。</p>
<h3> DNS如何工作</h3>
<p>DNS 解析过程涉及将主机名（例如 <code>www.example.com</code>）转换为计算机友好的 IP 地址（例如 <code>192.168.1.1</code>）。Internet 上的每个设备都被分配了一个 IP 地址，必须有该地址才能找到相应的 Internet 设备 - 就像使用街道地址来查找特定住所一样。当用户想要加载网页时，用户在 Web 浏览器中键入的内容（<code>example.com</code>）与查找 <code>example.com</code> 网页所需的机器友好地址之间必须进行转换。</p>
<h3> DNS服务器类别</h3>
<p>::: Tip</p>
<p>所有 DNS 服务器都属于以下四个类别之一：递归解析器、根域名服务器、TLD 域名服务器和权威性域名服务器。在典型 DNS 查找中（当没有正在进行的高速缓存时），这四个 DNS 服务器协同工作来完成将指定域的 IP 地址提供给客户端的任务（客户端通常是一个存根解析器 - 内置于操作系统的简单解析器）。</p>
<p>:::</p>
<ul>
<li><strong><a href="">DNS 解析器（DNS recursor）</a></strong> - 该解析器可被视为被要求去图书馆的某个地方查找特定图书的图书馆员。DNS 解析器是一种服务器，旨在通过 Web 浏览器等应用程序接收客户端计算机的查询。然后，解析器一般负责发出其他请求，以便满足客户端的 DNS 查询。</li>
<li><strong><a href="">根域名服务器（Root nameserver）</a></strong> - 根域名服务器是将人类可读的主机名转换（解析）为 IP 地址的第一步。可将其视为指向不同书架的图书馆中的索引 - 一般其作为对其他更具体位置的引用。</li>
<li><strong><a href="">TLD 域名服务器（TLD nameserver）</a></strong> - 顶级域服务器（TLD）可被视为图书馆中的特定书架。此域名服务器是搜索特定 IP 地址的下一步，其托管主机名的最后一部分（在 <code>example.com</code> 中，TLD 服务器为 「com」）。</li>
<li><strong><a href="">权威性域名服务器（Authoritative nameserver ）</a></strong> - 可将这个最终域名服务器视为书架上的字典，其中特定名称可被转换成其定义。权威性域名服务器是域名服务器查询中的最后一站。如果权威性域名服务器能够访问请求的记录，则其会将已请求主机名的 IP 地址返回到发出初始请求的 DNS 解析器（图书管理员）。</li>
</ul>
<h3> DNS查找IP地址步骤</h3>
<p>::: Tip 注意</p>
<p>通常，DNS 查找信息将本地缓存在查询计算机内，或者远程缓存在 DNS 基础设施内。DNS 查找通常有 8 个步骤。缓存 DNS 信息时，将从 DNS 查找过程中跳过一些步骤，从而使该过程更快。以下示例概述了不缓存任何内容时的所有 8 个步骤。</p>
<p>:::</p>
<blockquote>
<p>DNS服务器根据域名的层级，进行分级查询。</p>
<p>需要明确的是，每一级域名都有自己的NS（Name Server的缩写）记录，NS记录指向该级域名的域名服务器。</p>
</blockquote>
<ol>
<li>用户在 Web 浏览器中键入 <code>example.com</code>，查询传输到 Internet 中，并被 DNS 递归解析器接收。</li>
<li>接着，解析器向 DNS 根域名服务器（.）发起查询请求。</li>
<li>然后，根服务器返回其域信息的顶级域（TLD）DNS 服务器（例如 .com 或 .net）的 NS 记录和 A 记录给该解析器。例如：在访问 <code>example.com</code> 时，我们的请求指向 .com TLD 服务器。</li>
<li>然后，解析器向 .com TLD 服务器发出请求。</li>
<li>TLD 服务器随后返回域名 <code>example.com</code> 的<strong>权威性域名服务器</strong> NS 记录和 A 记录。</li>
<li>最后，递归解析器将查询发送到域的域名服务器。</li>
<li><code>example.com</code> 的 IP 地址从<strong>权威性域名服务器</strong>返回到解析器。</li>
<li>然后 DNS 解析器使用最初请求的域的 IP 地址响应 Web 浏览器。</li>
</ol>
<blockquote>
<p>DNS 查找经过这 8 个步骤返回 <code>example.com</code> 的 IP 地址后，浏览器便能发出对该网页的请求。</p>
</blockquote>
<ol start="9">
<li>浏览器向该 IP 地址发出 HTTP 请求。</li>
<li>位于该 IP 的服务器返回将在浏览器中呈现的网页（第 10 步）。</li>
</ol>
<h3> DNS查询的类型</h3>
<p>典型 DNS 查找中会出现三种类型的查询。通过组合使用这些查询，优化的 DNS 解析过程可缩短传输距离。在理想情况下，可以使用缓存的记录数据，从而使 DNS 域名服务器能够返回非递归查询。</p>
<ol>
<li><strong>递归查询</strong> - 在递归查询中，DNS 客户端要求 DNS 服务器（一般为 DNS 递归解析器）将使用所请求的资源记录响应客户端，或者如果解析器无法找到该记录，则返回错误消息。</li>
<li><strong>迭代查询</strong> - 在这种情况下，DNS 客户端将允许 DNS 服务器返回其能够给出的最佳应答。如果所查询的 DNS 服务器与查询名称不匹配，则其将返回对较低级别域名空间具有权威性的 DNS 服务器的引用。然后，DNS 客户端将对引用地址进行查询。此过程继续使用查询链中的其他 DNS 服务器，直至发生错误或超时为止。</li>
<li><strong>非递归查询</strong> - 当 DNS 解析器客户端查询 DNS 服务器以获取其有权访问的记录时通常会进行此查询，因为其对该记录具有权威性，或者该记录存在于其缓存内。DNS 服务器通常会缓存 DNS 记录，以防止更多带宽消耗和上游服务器上的负载。</li>
</ol>
<h3> DNS缓存方式</h3>
<h4> DNS高速缓存</h4>
<p>缓存的目的是将数据临时存储在某个位置，从而提高数据请求的性能和可靠性。DNS 高速缓存涉及将数据存储在更靠近请求客户端的位置，以便能够更早地解析 DNS 查询，并且能够避免在 DNS 查找链中进一步向下的额外查询，从而缩短加载时间并减少带宽 CPU 消耗。DNS 数据可缓存到各种不同的位置上，每个位置均将存储 DNS 记录并保存由生存时间（TTL）决定的一段时间。</p>
<h4> 浏览器DNS缓存</h4>
<p>现代 Web 浏览器设计为默认将 DNS 记录缓存一段时间。目的很明显；越靠近 Web 浏览器进行 DNS 缓存，为检查缓存并向 IP 地址发出正确请求而必须采取的处理步骤就越少。发出对 DNS 记录的请求时，浏览器缓存是针对所请求的记录而检查的第一个位置。</p>
<p>在 Chrome 浏览器中，您可以转到 <code>chrome://net-internals/#dns</code> 查看 DNS 缓存的状态。</p>
<h4> 操作系统（OS）级DNS缓存</h4>
<p>操作系统级 DNS 解析器是 DNS 查询离开您计算机前的第二站，也是本地最后一站。操作系统内旨在处理此查询的过程通常称为「存根解析器」或 「DNS 客户端」。当存根解析器获取来自某个应用程序的请求时，其首先检查自己的缓存，以便查看是否有此记录。如果没有，则将本地网络外部的 DNS 查询（设置了递归标记）发送到 Internet 服务提供商（ISP）内部的 DNS 递归解析器。</p>
<h3> 总结</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202303291607036.png" alt="DNS运行机制" tabindex="0" loading="lazy"><figcaption>DNS运行机制</figcaption></figure>
<h3> 参考</h3>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202303291607036.png" type="image/png"/>
    </item>
    <item>
      <title>三次握手与四次挥手</title>
      <link>https://v-blog.yyshino.top/reading_notes/%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E4%B8%8E%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B.html</link>
      <guid>https://v-blog.yyshino.top/reading_notes/%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E4%B8%8E%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">三次握手与四次挥手</source>
      <description>三次握手与四次挥手</description>
      <category>浏览器</category>
      <pubDate>Sat, 01 Jul 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<figure><img src="https://shinoimg.yyshino.top/img/202303302013554.jpg" alt="photo_2023-03-30_20-10-24" tabindex="0" loading="lazy"><figcaption>photo_2023-03-30_20-10-24</figcaption></figure>
<figure><img src="https://shinoimg.yyshino.top/img/202303302013904.jpg" alt="photo_2023-03-30_20-10-51" tabindex="0" loading="lazy"><figcaption>photo_2023-03-30_20-10-51</figcaption></figure>
<figure><img src="https://shinoimg.yyshino.top/img/202303302013860.jpg" alt="photo_2023-03-30_20-10-56" tabindex="0" loading="lazy"><figcaption>photo_2023-03-30_20-10-56</figcaption></figure>
<figure><img src="https://shinoimg.yyshino.top/img/202303302013715.jpg" alt="photo_2023-03-30_20-11-00" tabindex="0" loading="lazy"><figcaption>photo_2023-03-30_20-11-00</figcaption></figure>
<p>三次握手与四次挥手，虽然我工作中目前还没有涉及网络编程这一方面的东西，但是难免面试老是要被问到，算是个高频的必备知识点了。</p>
<p>三次握手，目的有3个。</p>
<p>**第一个目的是为了防止在网络阻塞时，历史连接乱入，导致造成混乱。**如果是两次握手，则在第二次握手时不加以校验，服务端与一个旧的连接建立通信。有了三次握手，能够在第二次握手时校验，校验成功则正常进行第三次握手，否则发送RST信号。</p>
<p>**第二个目的是为了同步双方的初始化序列号。**从这个层面上来说，可以把3次握手看作是4次握手的精简版，即两轮SYN-ACK，各自发送SYN并接收应答信号。只不过中间第二次握手的应答和第三次握手的SYN可以合二为一。（相较于4次挥手，由于被动方可能还有数据要发送，所以FIN和ACK必须分离，所以是4次）</p>
<p>**第三个目的是为了避免资源的浪费。**在网络阻塞情况下，客户端可能多次发送SYN请求，如果只有两次握手，由于服务端无法得知客户端是否接受到ACK信号并建立起连接，只能在每次接受到SYN信号时都主动建立一个连接。故在SYN冗余情况下，重复分配资源，造成浪费。而三次握手的机制得以在第二次握手时进行校验，校验成功通过第三次握手应答后才建立起连接，避免了资源浪费。</p>
<p>四次挥手，原因是上面目的二提到的，通信双方其中一方主动请求断开连接时，另一方可能还有数据要发送，故需要把被动方的ACK和FIN分开成两次挥手。</p>
<p>主动发起断连的一方会有TIME_WAIT的状态，需要等待2MSL，原因是：一方面，等待足够长的时间，能够让滞留在网络中的历史数据消失，以至于下一次在相同端口建立TCP连接时，旧数据不至于在握手时造成数据混乱。另一方面，2MSL确保被动关闭连接的一方能够被正确关闭。因为第四次挥手的ACK信号可能由于网络阻塞或数据丢失而没到达被动方，被动方将一直处于LAST_ACK的状态。本来会有个超时重发机智，重新进行第三次挥手发送FIN信号，重新等待接收四次挥手的ACK，但如果主动关闭方老早就断连了，则永远没人处理重发的信号，被动方永远等在LAST_ACK。</p>
<p>而2MSL正好是第三次挥手和第四次挥手的往返时间之和</p>
<p>转自https://t.me/https1024/11474</p>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202303302013554.jpg" type="image/jpeg"/>
    </item>
    <item>
      <title>Vue开发者如何学习React</title>
      <link>https://v-blog.yyshino.top/posts/React/03-Vue%E5%BC%80%E5%8F%91%E8%80%85%E5%A6%82%E4%BD%95%E5%AD%A6%E4%B9%A0React.html</link>
      <guid>https://v-blog.yyshino.top/posts/React/03-Vue%E5%BC%80%E5%8F%91%E8%80%85%E5%A6%82%E4%BD%95%E5%AD%A6%E4%B9%A0React.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Vue开发者如何学习React</source>
      <description>Vue开发者如何学习React</description>
      <category>FrontEnd</category>
      <pubDate>Sun, 18 Jun 2023 15:35:43 GMT</pubDate>
      <content:encoded><![CDATA[<h2> Vue开发者如何学习React</h2>
<p>https://juejin.cn/post/7234687030017376312</p>
<p>React其实很简单</p>
<ul>
<li>任何领域,强大的、高效的东西,一定是简单的·</li>
<li>React就是JS ,外加一点模板语言JSX (像HTML)说</li>
<li>React 难的人，可能JS 语法都不熟练</li>
</ul>
<h2> React核心价值</h2>
<p>React核心价值:</p>
<p>1-组件化</p>
<p>2-数据驱动视图</p>
<ul>
<li>$ UI = f(state) $</li>
<li>理解：函数接收一个参数（数据），返回视图（UI、HTML）</li>
<li>好处
<ul>
<li>定义好数据和 UI 的显示规则，即 UI = f(state)</li>
<li>只关注业务数据的修改，不用再操作 DOM，增加开发效率</li>
<li>尤其对于DOM 结构复杂的大型项目</li>
</ul>
</li>
</ul>
<h2> 创建项目</h2>
<h3> 创建项目</h3>
<p>使用Create-React-App 创建 React 项目</p>
<p><code>npx create-react-app react-ts-demo --template typescript</code></p>
<p>使用 Vite 创建 React 项目</p>
<p><code>npm create vite my-react --template react-ts</code></p>
<p>使用 eslint prettier husty 等，制定编码规则</p>
<h3> 制定编码规则</h3>
<ul>
<li>
<p>安装相关插件</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
<li>
<p>项目根目录添加并配置<code>.eslintrc.js</code> 安装prettier插件、eslint插件</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
<li>
<p>配置<code>package.json</code>脚本</p>
<ul>
<li>
<p>正常情况下</p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
<li>
<p>如果上面的不行，可以试一试下面的</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div></li>
</ul>
</li>
<li>
<p>配置vscode，保存自动触发eslint</p>
<ul>
<li>
<p>项目根目录新建<code>.vscode</code>文件</p>
</li>
<li>
<p>新建<code>setting.json</code></p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
</ul>
</li>
<li>
<p>自定义prettier</p>
<ul>
<li>
<p>项目根目录新建<code>.prettierrc.js</code></p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
<li>
<p>或者新增<code>.prettierrc</code></p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
</ul>
</li>
</ul>
<h4> husky</h4>
<p>简介</p>
<ul>
<li>一个 git hook 工具.在</li>
<li>git commit 之前执行自定义的命令</li>
<li>如执行代码风格的检查，避免提交非规范代码</li>
</ul>
<h5> 使用</h5>
<p>git-hook</p>
<p>安装依赖</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>参考文档 https://github.com/typicode/husky 增加三个 <code>pre-commit</code> 命令</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以故意制造一个错误：定义一个未使用变量（eslint 配置文件 <code>rules</code> 增加 <code>'no-unused-vars': 'error',</code>）<br>
然后执行 <code>git commit</code> 试试</p>
<h2> commit-lint</h2>
<p>参考文档 https://github.com/conventional-changelog/commitlint#getting-started 安装设置即可</p>
<p>commit 规则查看 <code>node_modules/@commitlint/config-conventional</code> （在 <code>commitlint.config.js</code> 中有配置）</p>
<p>尝试 <code>git commit -m "test"</code> 会失败，再尝试 <code>git commit -m "chore: commit lint"</code> 会成功</p>
<p>常见类型 <a href="https://github.com/conventional-changelog/commitlint/tree/master/@commitlint/config-conventional#type-enum" target="_blank" rel="noopener noreferrer">commitlint-config-conventional (based on the Angular convention)</a> 可以有以下:</p>
<table>
<thead>
<tr>
<th style="text-align:left">类型</th>
<th style="text-align:left">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">build</td>
<td style="text-align:left">编译相关的修改，例如发布版本、对项目构建或者依赖的改动</td>
</tr>
<tr>
<td style="text-align:left">chore</td>
<td style="text-align:left">其他修改, 比如改变构建流程、或者增加依赖库、工具等</td>
</tr>
<tr>
<td style="text-align:left">ci</td>
<td style="text-align:left">持续集成修改</td>
</tr>
<tr>
<td style="text-align:left">docs</td>
<td style="text-align:left">文档修改</td>
</tr>
<tr>
<td style="text-align:left">feat</td>
<td style="text-align:left">新特性、新功能</td>
</tr>
<tr>
<td style="text-align:left">fix</td>
<td style="text-align:left">修改bug</td>
</tr>
<tr>
<td style="text-align:left">perf</td>
<td style="text-align:left">优化相关，比如提升性能、体验</td>
</tr>
<tr>
<td style="text-align:left">refactor</td>
<td style="text-align:left">代码重构</td>
</tr>
<tr>
<td style="text-align:left">revert</td>
<td style="text-align:left">回滚到上一个版本</td>
</tr>
<tr>
<td style="text-align:left">style</td>
<td style="text-align:left">代码格式修改, 注意不是 css 修改</td>
</tr>
<tr>
<td style="text-align:left">test</td>
<td style="text-align:left">测试用例修改</td>
</tr>
</tbody>
</table>
<p>These can be modified by <a href="https://github.com/conventional-changelog/commitlint#config" target="_blank" rel="noopener noreferrer">your own configuration</a>.</p>
<h2> 条件循环</h2>
<p>条件</p>
<ul>
<li>&amp;&amp;</li>
<li>三元表达式</li>
<li>函数</li>
</ul>
<p>循环</p>
<ul>
<li>定义数组+map遍历</li>
</ul>
<h2> React Hooks</h2>
<h3> 内置Hooks</h3>
<h4> useState</h4>
<p>特点</p>
<ul>
<li>
<p>会触发页面更新，重新渲染，触发state更新</p>
</li>
<li>
<p>state是异步更新，state更新可能会被合并，使用函数state更新不会被合并</p>
</li>
<li>
<p>不可变数据（重要！！！）- 不去修改 state的值，而是传入一个新的值</p>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
</ul>
<p>如果说 一个变量 不用于 JSX 中显示，那就不要用 setState 来管理它，用useRef</p>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h5> state增删查改</h5>
<p><strong>数组</strong></p>
<p>增</p>
<ul>
<li>concat</li>
</ul>
<p>删</p>
<ul>
<li>filter</li>
</ul>
<p>查</p>
<ul>
<li>filter</li>
</ul>
<p>改</p>
<ul>
<li>map</li>
</ul>
<p>状态提升：数据源在父组件中，子组件只需要负责展示。操作、数据由父组件实现、传递，子组件调用、渲染</p>
<h5> immer</h5>
<p>Immer 简化了不可变数据结构的处理。特别是对于 JS 语法没那么熟悉的人。</p>
<h4> useEffect</h4>
<h5> useEffect执行两次</h5>
<ul>
<li>
<p>React 18开始, useEffect在==<strong>开发环境</strong>==下会执行两次</p>
</li>
<li>
<p>模拟组件创建、销毁、再创建的完整流程,及早暴露问题</p>
</li>
<li>
<p>生产环境下会执行一次</p>
</li>
<li>
<p>当组件渲染完成时,加载一个Ajax网络请求</p>
</li>
<li>
<p>当某个state更新时,加载一个Ajax网络请求使用</p>
</li>
<li>
<p>useEffect 实现</p>
</li>
</ul>
<p>示例</p>
<div class="language-jsx line-numbers-mode" data-ext="jsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 其他内置Hooks</h4>
<h5> useRef</h5>
<ul>
<li>一般用于操作DOM</li>
<li>也可传入普通JS变量,但更新不会触发 rerender</li>
<li>要和Vue3 ref区分开(如果你用过Vue3)</li>
</ul>
<h5> useMemo</h5>
<ul>
<li>函数组件，每次state更新都会重新执行函数</li>
<li>useMemo 可以缓存数据，不用每次执行函数都重新生成</li>
<li>可用于计算量较大的场景，缓存提高性能</li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h5> useCallback</h5>
<ul>
<li>和 useMemo 作用一样</li>
<li>专门用于缓存函数</li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 自定义Hooks（复用代码）</h3>
<p>封装常用Hooks，比如工具库</p>
<h3> 第三方Hooks（提高效率）</h3>
<ul>
<li>ahooks （国内）</li>
<li>React use （国外）</li>
</ul>
<h3> Hooks使用规则</h3>
<ul>
<li>必须用useXxx格式来命名</li>
<li>只能在两个地方调用Hook（组件内Hooks内）</li>
<li>必须保证每次的调用顺序一致（不能放在 if for 内部）</li>
</ul>
<h2> 闭包陷阱</h2>
<ul>
<li>当异步函数获取 state 时，可能不是当前最新的 state</li>
<li>可使用 useRef 来解决</li>
<li>（要提前了解JS 闭包）</li>
</ul>
<h2> React中使用CSS</h2>
<ul>
<li>内联 style 的方式</li>
<li>引入 CSS 文件，使用 className</li>
<li>尽量不要用内联 style</li>
</ul>
<p>className条件判断-第三方包</p>
<ul>
<li>classnames</li>
<li>clax</li>
</ul>
<p>尽量不要使用内联style</p>
<ul>
<li>内联 style 代码多，性能差，扩展性不好</li>
<li>外链 CSS 文件可复用代码，可单独缓存文件</li>
<li>PS :这和React无关,学HTML CSS时就应该知道</li>
</ul>
<h3> 普通CSS的问题</h3>
<ul>
<li>React 使用组件化开发</li>
<li>多个组件，就需要多个 CSS 文件</li>
<li>多个CSS文件很容易造成 className 重复,不好管理</li>
</ul>
<p>解决方案</p>
<ul>
<li>普通CSS的问题-className会重复.</li>
<li>解决方案 CSS Module</li>
<li>增加Sass支持</li>
</ul>
<h3> CSS Module</h3>
<ul>
<li>每个CSS文件都当做单独的模块,命令xxx.module.css</li>
<li>为每个 className 增加后缀名，不让它们重复</li>
<li>Create-React-App原生支持CSS Module</li>
</ul>
<h3> CSS-in-JS</h3>
<ul>
<li>一种解决方案（而非工具名称），有好几个工具.</li>
<li>在JS 中写 CSS ,带来极大的灵活性</li>
<li>它和内联style完全不一样,也不会有内联style的问题</li>
</ul>
<p>优缺点</p>
<ul>
<li>优点:用JS写,有逻辑有变量,非常灵活,</li>
<li>缺点：JSX 和样式代码混在一块，代码较多；增加了编译成本；</li>
<li>适用场景：需要灵活变换样式</li>
</ul>
<p>工具</p>
<ul>
<li>styled-component</li>
<li>styled-jsx</li>
<li>emotion</li>
</ul>
<h2> React-Router</h2>
<p>目标：为系统增加路由，支持多页面定义和切换</p>
<p>内容：</p>
<ul>
<li>
<p>路由设计，网址和页面的关系</p>
</li>
<li>
<p>增加页面和 Layout 模板使用</p>
</li>
<li>
<p>React-router 增加路由配置</p>
</li>
</ul>
<p>Hooks</p>
<ul>
<li>useNavigate——跳转页面</li>
<li>useParams——获取params参数</li>
<li>useSearchParams——获取query参数</li>
</ul>
<h2> React表单组件、受控组件</h2>
<h3> 受控组件</h3>
<p>表单值与state状态联动</p>
<h3> 表单组件</h3>
<p><code>input</code>-<code>textarea</code></p>
<ul>
<li>value 绑定 useState</li>
<li>监听<code>onChange()</code>事件，传入event事件对象，setState传入event.target.value新数据，更新数据</li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>radio</code></p>
<ul>
<li></li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>checkbox</code></p>
<ul>
<li></li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>select</code></p>
<ul>
<li></li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>form</code></p>
<ul>
<li></li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 状态管理</h2>
<ul>
<li>React内置功能Context和useReducer</li>
<li>第三方工具Redux和 MobX</li>
<li>使用 Redux 管理用户状态</li>
</ul>
<p>解决问题：</p>
<ul>
<li>页面足够复杂：组件很多，嵌套层级很深</li>
<li>通过 props 层层传递不合适</li>
<li>需要状态管理,即集中、统一管理页面数据</li>
</ul>
<h3> Context</h3>
<ul>
<li>可跨层级传递，而不像 props 层层传递</li>
<li>类似于Vue 的 provide/inject</li>
<li>例如:切换主题、切换语言</li>
</ul>
<h3> useReducer</h3>
<ul>
<li>useState的代替方案</li>
<li>数据结构简单时用useState ,复杂时用useReducer</li>
<li>简化版的 redux</li>
</ul>
<h4> useReducer概念</h4>
<ul>
<li>state或store</li>
<li>action</li>
<li>reducer</li>
<li>dispatch</li>
</ul>
<h4> 使用总结</h4>
<ul>
<li>类似于redux的流程和API</li>
<li>结合Context 解决跨组件问题</li>
<li>state dispatch 默认没有模块化,数据混在一起</li>
</ul>
<h3> Redux</h3>
<p>Redux和useReducer概念一致</p>
<ul>
<li>state或store</li>
<li>action</li>
<li>reducer</li>
<li>dispatch</li>
</ul>
<h4> Context + useReducer 代替 Redux ?</h4>
<ul>
<li>社区热议的话题</li>
<li>简单场景可以,节省代码体积,更简单</li>
<li>复杂场景仍然建议用 Redux</li>
</ul>
<h3> Mobx</h3>
<p>声明式的修改数据，像Vue | 有一定学习成本</p>
<p>概念</p>
<ul>
<li>state 数据</li>
<li>action 动作</li>
<li>derivation 派生: computed observer</li>
</ul>
<h2> 项目实战</h2>
<p>imooc——React 仿问卷星 电子书</p>
<h3> 路由设计</h3>
<h5> 页面对应的路由</h5>
<figure><img src="https://shinoimg.yyshino.top/img/202306252125537.png" alt="image-20230625212522036" tabindex="0" loading="lazy"><figcaption>image-20230625212522036</figcaption></figure>
<ul>
<li>首页 <code>/</code></li>
<li>登录 <code>/login</code></li>
<li>注册 <code>/register</code></li>
<li>问卷管理
<ul>
<li>我的问卷 <code>/manage/list</code></li>
<li>星标问卷 <code>/manage/star</code></li>
<li>回收站 <code>/manage/trash</code></li>
</ul>
</li>
<li>问卷详情
<ul>
<li>编辑问卷 <code>/question/edit/:id</code> （动态路由）</li>
<li>问卷统计 <code>/question/stat/:id</code></li>
</ul>
</li>
<li>404</li>
</ul>
<h3> Layout 模板</h3>
<ul>
<li>MainLayout</li>
<li>ManageLayout</li>
<li>QuestionLayout</li>
</ul>
<h3> 搜索、分页、下滑加载</h3>
<p>改变浏览器url，而不是直接操作列表中的数据</p>
<p>好处：</p>
<ul>
<li>降低不同组件之间的耦合</li>
<li>刷新时，防止数据丢失（从url中获取）</li>
</ul>
<p>示例</p>
<h4> 搜索</h4>
<ul>
<li>从url获取params参数</li>
<li>搜索时再url添加params参数</li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 分页</h4>
<p>前提：使用antd组件库的pagination</p>
<ul>
<li>从url获取params参数</li>
<li>监听onChange事件（Pagination封装的方法，当pageSize、page改变时执行）。在url中添加params参数</li>
</ul>
<div class="language-tsx line-numbers-mode" data-ext="tsx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> LoadMore</h4>
<ul>
<li>每次请求累加列表</li>
</ul>
<h3> AJAX</h3>
<h4> Mock</h4>
<ul>
<li>搭建mock服务（作为临时的服务端）
<ul>
<li>重要性
<ul>
<li>工作中,场景前后端并行开发,而非串行</li>
<li>前后端商议好API格式,双方各自开发。前端使用mock服务</li>
<li>待前后端都开发完,再对接联调功能</li>
</ul>
</li>
<li>技术选型
<ul>
<li>使用mock.js
<ul>
<li>只能劫持 XMLHttpRequest ,不能劫持 fetch ,有局限性</li>
<li>要在生产环境（上线时）注释掉，否则线上请求也被劫持</li>
<li>结论:不建议在项目中直接使用mock.js</li>
</ul>
</li>
<li>使用nodejs服务+ mock.js
<ul>
<li>使用mock.js的Random能力</li>
<li>定义 nodejs代码结构,考虑多模块的扩展性</li>
<li>刻意延迟1s ,模拟loading效果</li>
</ul>
</li>
<li>使用在线 mock 平台
<ul>
<li>如Fast-mock Y-API Swagger (国外)</li>
<li>可能不稳定、不维护，或者网络不稳定</li>
<li>可能存在敏感数据泄漏的风险</li>
</ul>
</li>
<li>总结
<ul>
<li>直接在前端使用 mock.js - 不推荐</li>
<li>使用 nodejs + mock.js - 推荐</li>
<li>使用在线mock平台-不推荐(除非公司内部的)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>使用Ajax和服务端通讯，并应用于现有功能</li>
<li>API设计（使用Restful API设计）</li>
<li>实战：为列表页、登录页、注册页，增加Ajax请求</li>
</ul>
<h4> 基础知识和工具</h4>
<ul>
<li>
<p>HTTP 协议，前后端通讯的桥梁</p>
</li>
<li>
<p>API : XMLHttpRequest和fetch</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
<li>
<p>常用工具 axios</p>
</li>
</ul>
<h4> API设计</h4>
<h3> 用户</h3>
<h4> JWT</h4>
<ul>
<li>JSON Web Token</li>
<li>登录成功后，服务端返回一个 token（令牌，一段字符串）</li>
<li>以后每次请求带着这个token,以表明自己的身份</li>
</ul>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202306252125537.png" type="image/png"/>
    </item>
    <item>
      <title>为什么学习CSharp</title>
      <link>https://v-blog.yyshino.top/posts/CSharp/01-%E4%B8%BA%E4%BB%80%E4%B9%88%E5%AD%A6%E4%B9%A0CSharp.html</link>
      <guid>https://v-blog.yyshino.top/posts/CSharp/01-%E4%B8%BA%E4%BB%80%E4%B9%88%E5%AD%A6%E4%B9%A0CSharp.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">为什么学习CSharp</source>
      <description>为什么学习CSharp</description>
      <category>AfterEnd</category>
      <pubDate>Sat, 17 Jun 2023 17:33:18 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 为什么学习CSharp</h2>
<p>最近一直在找前端方向的工作，奈何一直找不到[哭]。现实的重压到了我的头上，焦虑焦虑焦虑。有时候想想又还好，有时候又很焦虑。大一的时候学习的CSharp+Unity本来想一直坚持下去，但是之后被ShaderLab以及美术、建模啥的啥的劝退，然后学习了前端，学了一年自信了不少，结果找不到工作又给我打回去了[哭]。目前想的是把这两结合起来吧。</p>
<p>:::为什么不学Java？</p>
<p>​	太卷了，太卷了。Java比前端还卷我不想去蹭</p>
<p>:::</p>
<h2> 类型和变量</h2>
<p>类型定义 C# 中的任何数据的结构和行为。 类型的声明可以包含其成员、基类型、它实现的接口和该类型允许的操作。 变量是用于引用特定类型的实例的标签。</p>
<p>C# 有两种类型：<em>值类型</em>和<em>引用类型</em>。 值类型的变量直接包含它们的数据。 引用类型的变量存储对数据（称为“对象”）的引用。 对于引用类型，两个变量可以引用同一个对象；对一个变量执行的运算可能会影响另一个变量引用的对象。 借助值类型，每个变量都有自己的数据副本；因此，对一个变量执行的运算不会影响另一个变量（<code>ref</code> 和 <code>out</code> 参数变量除外）。</p>
<p>标识符是变量名称。 标识符是不包含任何空格的 unicode 字符序列。 如果标识符的前缀为 <code>@</code>，则该标识符可以是 C# 保留字。 在与其他语言交互时，使用保留字作为标识符很有用。</p>
<p>C# 的值类型进一步分为：简单类型、枚举类型、结构类型、可以为 null 的值类型和元组值类型。 C# 引用类型又细分为类类型、接口类型、数组类型和委托类型。</p>
<p>以下大纲概述了 C# 的类型系统。</p>
<ul>
<li>值类型
<ul>
<li>简单类型
<ul>
<li><a href="https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/integral-numeric-types" target="_blank" rel="noopener noreferrer">有符号整型</a>：<code>sbyte</code>、<code>short</code>、<code>int</code>、<code>long</code></li>
<li><a href="https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/integral-numeric-types" target="_blank" rel="noopener noreferrer">无符号整型</a>：<code>byte</code>、<code>ushort</code>、<code>uint</code>、<code>ulong</code></li>
<li><a href="https://learn.microsoft.com/zh-cn/dotnet/standard/base-types/character-encoding-introduction" target="_blank" rel="noopener noreferrer">Unicode 字符</a>：<code>char</code>，表示 UTF-16 代码单元</li>
<li><a href="https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types" target="_blank" rel="noopener noreferrer">IEEE 二进制浮点</a>：<code>float</code>、<code>double</code></li>
<li><a href="https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types" target="_blank" rel="noopener noreferrer">高精度十进制浮点数</a>：<code>decimal</code></li>
<li>布尔值：<code>bool</code>，表示布尔值（<code>true</code> 或 <code>false</code>）</li>
</ul>
</li>
<li>枚举类型
<ul>
<li><code>enum E {...}</code> 格式的用户定义类型。 <code>enum</code> 类型是一种包含已命名常量的独特类型。 每个 <code>enum</code> 类型都有一个基础类型（必须是八种整型类型之一）。 <code>enum</code> 类型的值集与基础类型的值集相同。</li>
</ul>
</li>
<li>结构类型
<ul>
<li>格式为 <code>struct S {...}</code> 的用户定义类型</li>
</ul>
</li>
<li>可以为 null 的值类型
<ul>
<li>值为 <code>null</code> 的其他所有值类型的扩展</li>
</ul>
</li>
<li>元组值类型
<ul>
<li>格式为 <code>(T1, T2, ...)</code> 的用户定义类型</li>
</ul>
</li>
</ul>
</li>
<li>引用类型
<ul>
<li>类类型
<ul>
<li>其他所有类型的最终基类：<code>object</code></li>
<li><a href="https://learn.microsoft.com/zh-cn/dotnet/standard/base-types/character-encoding-introduction" target="_blank" rel="noopener noreferrer">Unicode 字符串</a>：<code>string</code>，表示 UTF-16 代码单元序列</li>
<li>格式为 <code>class C {...}</code> 的用户定义类型</li>
</ul>
</li>
<li>接口类型
<ul>
<li>格式为 <code>interface I {...}</code> 的用户定义类型</li>
</ul>
</li>
<li>数组类型
<ul>
<li>一维、多维和交错。 例如：<code>int[]</code>、<code>int[,]</code> 和 <code>int[][]</code></li>
</ul>
</li>
<li>委托类型
<ul>
<li>格式为 <code>delegate int D(...)</code> 的用户定义类型</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>00-WebGL</title>
      <link>https://v-blog.yyshino.top/posts/WebGl/00-WebGL.html</link>
      <guid>https://v-blog.yyshino.top/posts/WebGl/00-WebGL.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">00-WebGL</source>
      <description>00-WebGPU</description>
      <category>FrontEnd</category>
      <pubDate>Fri, 16 Jun 2023 20:20:03 GMT</pubDate>
    </item>
    <item>
      <title>00-WebGPU</title>
      <link>https://v-blog.yyshino.top/posts/WebGl/00-WebGPU.html</link>
      <guid>https://v-blog.yyshino.top/posts/WebGl/00-WebGPU.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">00-WebGPU</source>
      <description>00-WebGPU</description>
      <category>FrontEnd</category>
      <pubDate>Fri, 16 Jun 2023 20:20:03 GMT</pubDate>
    </item>
    <item>
      <title>ThreeJS入门</title>
      <link>https://v-blog.yyshino.top/posts/WebGl/01-ThreeJS%E5%9F%BA%E7%A1%80.html</link>
      <guid>https://v-blog.yyshino.top/posts/WebGl/01-ThreeJS%E5%9F%BA%E7%A1%80.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">ThreeJS入门</source>
      <description>ThreeJS通用模板</description>
      <category>FrontEnd</category>
      <pubDate>Fri, 16 Jun 2023 20:20:03 GMT</pubDate>
      <content:encoded><![CDATA[<h2> ThreeJS入门学习</h2>
<h2> 概念</h2>
<h2> 基础知识</h2>
<p>场景：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>相机：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>物体|材质|纹理：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>渲染器：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>辅助工具：GUI（第三方）、坐标轴辅助器</p>
<p>dat.gui</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 灯光阴影</h2>
<p>需满足以下条件：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 粒子效果</h2>
<h3> 将粒子绑定在普通几何体上</h3>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 将粒子绑定在BufferGeometry上</h3>
<p><code>BufferAttribute( array : TypedArray, itemSize : Integer, normalized : Boolean )</code></p>
<p>array -- 必须是 <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/TypedArray" target="_blank" rel="noopener noreferrer">TypedArray</a>. 类型，用于实例化缓存。
该队列应该包含：<code>itemSize * numVertices</code>个元素，numVertices 是 <a href="https://threejs.org/docs/index.html#api/zh/core/BufferGeometry" target="_blank" rel="noopener noreferrer">BufferGeometry</a>中的顶点数目</p>
<p>itemSize -- 队列中与顶点相关的数据值的大小。举例，如果 attribute 存储的是三元组（例如顶点空间坐标、法向量或颜色值）则itemSize的值应该是3。</p>
<p>normalized -- (可选) 指明缓存中的数据如何与GLSL代码中的数据对应。例如，如果array是 UInt16Array类型，且normalized的值是 true，则队列中的值将会从 0 - +65535 映射为 GLSL 中的 0.0f - +1.0f。 如果array是 Int16Array (有符号)，则值将会从 -32768 - +32767 映射为 -1.0f - +1.0f。若 normalized 的值为 false，则数据映射不会归一化，而会直接映射为 float 值，例如，32767 将会映射为 32767.0f.</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 光线投射Raycaster</h2>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>GSAP动画入门</title>
      <link>https://v-blog.yyshino.top/posts/WebGl/02-GSAP%E5%8A%A8%E7%94%BB%E5%85%A5%E9%97%A8.html</link>
      <guid>https://v-blog.yyshino.top/posts/WebGl/02-GSAP%E5%8A%A8%E7%94%BB%E5%85%A5%E9%97%A8.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">GSAP动画入门</source>
      <description>GSAP动画入门</description>
      <category>FrontEnd</category>
      <pubDate>Fri, 16 Jun 2023 20:20:03 GMT</pubDate>
    </item>
    <item>
      <title>图形学入门</title>
      <link>https://v-blog.yyshino.top/posts/WebGl/%E5%9B%BE%E5%BD%A2%E5%AD%A6%E5%85%A5%E9%97%A8.html</link>
      <guid>https://v-blog.yyshino.top/posts/WebGl/%E5%9B%BE%E5%BD%A2%E5%AD%A6%E5%85%A5%E9%97%A8.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">图形学入门</source>
      <description>图形学入门</description>
      <category>图形学</category>
      <pubDate>Fri, 16 Jun 2023 20:20:03 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 介绍</h2>
<figure><img src="https://shinoimg.yyshino.top/img/202306131450651.png" alt="image-20230613145006968" tabindex="0" loading="lazy"><figcaption>image-20230613145006968</figcaption></figure>
<h2> Unity中的Shader</h2>
<p>Unity的Shader使用的是Shaderlab，底层是对HLSL进行了封装加入了一些新的东西，它可以被自动的转化为上述的任何一种语言。比如你使用的是Android那么，你的程序会被自动的编译为Android平台支持的 OpenGL ES 或者 Vulkan 代码。</p>
<p>代码模板介绍</p>
<div class="language-shaderlab line-numbers-mode" data-ext="shaderlab"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202306131450651.png" type="image/png"/>
    </item>
    <item>
      <title>WebGL踩坑</title>
      <link>https://v-blog.yyshino.top/posts/WebGl/04-%E8%B8%A9%E5%9D%91.html</link>
      <guid>https://v-blog.yyshino.top/posts/WebGl/04-%E8%B8%A9%E5%9D%91.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">WebGL踩坑</source>
      <description>WebGL踩坑</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 15 Jun 2023 20:20:03 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 2023年6月13日21:26:25</h2>
<h5> .<a href="https://threejs.org/docs/index.html#api/zh/core/BufferGeometry.setAttribute" target="_blank" rel="noopener noreferrer">setAttribute</a> ( name : String, attribute : <a href="https://threejs.org/docs/index.html#api/zh/core/BufferAttribute" target="_blank" rel="noopener noreferrer">BufferAttribute</a> ) : this</h5>
<p>为当前几何体设置一个 attribute 属性。在类的内部，有一个存储 <a href="https://threejs.org/docs/index.html#api/zh/core/BufferGeometry.attributes" target="_blank" rel="noopener noreferrer">.attributes</a> 的 hashmap， 通过该 hashmap，遍历 attributes 的速度会更快。而使用该方法，可以向 hashmap 内部增加 attribute。 所以，你需要使用该方法来添加 attributes。</p>
<p>name 为物体对象上存在的属性</p>
]]></content:encoded>
    </item>
    <item>
      <title>GLSL 中文手册</title>
      <link>https://v-blog.yyshino.top/posts/WebGl/05-GLSL%E8%AF%AD%E6%B3%95%E7%AE%80%E4%BB%8B.html</link>
      <guid>https://v-blog.yyshino.top/posts/WebGl/05-GLSL%E8%AF%AD%E6%B3%95%E7%AE%80%E4%BB%8B.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">GLSL 中文手册</source>
      <description>GLSL 中文手册</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 15 Jun 2023 20:20:03 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 文章转自 <a href="https://github.com/wshxbqq" target="_blank" rel="noopener noreferrer">wshxbqq</a> 大佬的 https://github.com/wshxbqq/GLSL-Card</h3>
<h1> GLSL 中文手册</h1>
<h3> 基本类型:</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>void</strong></td>
<td>空类型,即不返回任何值</td>
</tr>
<tr>
<td><strong>bool</strong></td>
<td>布尔类型 true,false</td>
</tr>
<tr>
<td><strong>int</strong></td>
<td>带符号的整数 signed integer</td>
</tr>
<tr>
<td><strong>float</strong></td>
<td>带符号的浮点数 floating scalar</td>
</tr>
<tr>
<td><strong>vec2, vec3, vec4</strong></td>
<td>n维浮点数向量 n-component floating point vector</td>
</tr>
<tr>
<td><strong>bvec2, bvec3, bvec4</strong></td>
<td>n维布尔向量 Boolean vector</td>
</tr>
<tr>
<td><strong>ivec2, ivec3, ivec4</strong></td>
<td>n维整数向量 signed integer vector</td>
</tr>
<tr>
<td><strong>mat2, mat3, mat4</strong></td>
<td>2x2, 3x3, 4x4 浮点数矩阵 float matrix</td>
</tr>
<tr>
<td><strong>sampler2D</strong></td>
<td>2D纹理 a 2D texture</td>
</tr>
<tr>
<td><strong>samplerCube</strong></td>
<td>盒纹理 cube mapped texture</td>
</tr>
</tbody>
</table>
<h3> 基本结构和数组:</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>结构</td>
<td>struct type-name{} 类似c语言中的 结构体</td>
</tr>
<tr>
<td>数组</td>
<td>float foo[3] glsl只支持1维数组,数组可以是结构体的成员</td>
</tr>
</tbody>
</table>
<h3> 向量的分量访问:</h3>
<p>glsl中的向量(vec2,vec3,vec4)往往有特殊的含义,比如可能代表了一个空间坐标(x,y,z,w),或者代表了一个颜色(r,g,b,a),再或者代表一个纹理坐标(s,t,p,q)
所以glsl提供了一些更人性化的分量访问方式.</p>
<p><code>vector.xyzw</code>  其中xyzw 可以任意组合</p>
<p><code>vector.rgba</code>  其中rgba 可以任意组合</p>
<p><code>vector.stpq</code>  其中rgba 可以任意组合</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 运算符:</h3>
<table>
<thead>
<tr>
<th>优先级(越小越高)</th>
<th>运算符</th>
<th>说明</th>
<th>结合性</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><strong>()</strong></td>
<td>聚组:a*(b+c)</td>
<td>N/A</td>
</tr>
<tr>
<td>2</td>
<td><strong>[] () . ++ --</strong></td>
<td>数组下标__[]<strong>,方法参数__fun(arg1,arg2,arg3)</strong>,属性访问__a.b__,自增/减后缀__a++  a--__</td>
<td>L - R</td>
</tr>
<tr>
<td>3</td>
<td><strong>++ -- + - !</strong></td>
<td>自增/减前缀__++a  --a__,正负号(一般正号不写)<strong>a ,-a</strong>,取反__!false__</td>
<td>R - L</td>
</tr>
<tr>
<td>4</td>
<td><strong>* /</strong></td>
<td>乘除数学运算</td>
<td>L - R</td>
</tr>
<tr>
<td>5</td>
<td><strong>+ -</strong></td>
<td>加减数学运算</td>
<td>L - R</td>
</tr>
<tr>
<td>7</td>
<td><strong>&lt; &gt; &lt;= &gt;=</strong></td>
<td>关系运算符</td>
<td>L - R</td>
</tr>
<tr>
<td>8</td>
<td><strong>== !=</strong></td>
<td>相等性运算符</td>
<td>L - R</td>
</tr>
<tr>
<td>12</td>
<td><strong>&amp;&amp;</strong></td>
<td>逻辑与</td>
<td>L - R</td>
</tr>
<tr>
<td>13</td>
<td><strong>^^</strong></td>
<td>逻辑排他或(用处基本等于!=)</td>
<td>L - R</td>
</tr>
<tr>
<td>14</td>
<td><strong>||</strong></td>
<td>逻辑或</td>
<td>L - R</td>
</tr>
<tr>
<td>15</td>
<td><strong>? :</strong></td>
<td>三目运算符</td>
<td>L - R</td>
</tr>
<tr>
<td>16</td>
<td><strong>= += -= *= /=</strong></td>
<td>赋值与复合赋值</td>
<td>L - R</td>
</tr>
<tr>
<td>17</td>
<td><strong>,</strong></td>
<td>顺序分配运算</td>
<td>L - R</td>
</tr>
</tbody>
</table>
<p><em>ps 左值与右值:</em></p>

<h3> 基础类型间的运算:</h3>
<p>glsl中,没有隐式类型转换,原则上glsl要求任何表达式左右两侧(l-value),(r-value)的类型必须一致 也就是说以下表达式都是错误的:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>下面来分别说说可能遇到的情况:</strong></p>
<p><strong>1.<code>float</code> 与 <code>int</code>:</strong></p>
<p>float与float , int与int之间是可以直接运算的,但float与int不行.它们需要进行一次显示转换.即要么把float转成int: <strong>int(1.0)</strong>
,要么把int转成float: <strong>float(1)</strong> ,以下表达式都是正确的:</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>2.<code>float</code>  与 <code>vec(向量)</code> <code>mat(矩阵)</code>:</strong></p>
<p>vec,mat这些类型其实是由float复合而成的,当它们与float运算时,其实就是在每一个分量上分别与float进行运算,这就是所谓的<code>逐分量</code>运算.glsl里
大部分涉及vec,mat的运算都是<code>逐分量</code>运算,但也并不全是. 下文中就会讲到特例.</p>
<p><code>逐分量</code>运算是线性的,这就是说 vec 与 float 的运算结果是还是 vec.</p>
<p>int 与 vec,mat之间是不可运算的, 因为vec和mat中的每一个分量都是 float 类型的. 无法与int进行逐分量计算.</p>
<p>下面枚举了几种 float 与 vec,mat 运算的情况</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>3. <code>vec(向量)</code> 与 <code>vec(向量)</code>:</strong></p>
<p>两向量间的运算首先要保证操作数的阶数都相同.否则不能计算.例如: vec3*vec2 vec4+vec3 等等都是不行的.</p>
<p>它们的计算方式是两操作数在同位置上的分量分别进行运算,其本质还是逐分量进行的,这和上面所说的float类型的
逐分量运算可能有一点点差异,相同的是 vec 与 vec 运算结果还是 vec, 且阶数不变.</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="http://wshxbqq-wshxbqq.stor.sinaapp.com/2016-08-08_16-15-35_329___2.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<p><strong>3. <code>vec(向量)</code> 与 <code>mat(矩阵)</code>:</strong></p>
<p>要保证操作数的阶数相同,且vec与mat间只存在乘法运算.</p>
<p>它们的计算方式和线性代数中的矩阵乘法相同,不是逐分量运算.</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>向量与矩阵的乘法规则如下:</p>
<figure><img src="http://wshxbqq-wshxbqq.stor.sinaapp.com/2016-08-08_16-15-36_966___3.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<figure><img src="http://wshxbqq-wshxbqq.stor.sinaapp.com/2016-08-08_16-15-36_284___4.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<p><strong>4. <code>mat(矩阵)</code> 与 <code>mat(矩阵)</code>:</strong></p>
<p>要保证操作数的阶数相同.</p>
<p>在mat与mat的运算中, 除了乘法是线性代数中的矩阵乘法外.其余的运算任为逐分量运算.简单说就是只有乘法是特殊的,其余都和vec与vec运算类似.</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>矩阵乘法规则如下:</p>
<figure><img src="http://wshxbqq-wshxbqq.stor.sinaapp.com/2016-08-08_16-15-36_985___5.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure>
<h3> 变量限定符:</h3>
<table>
<thead>
<tr>
<th>修饰符</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>none</strong></td>
<td>(默认的可省略)本地变量,可读可写,函数的输入参数既是这种类型</td>
</tr>
<tr>
<td><strong>const</strong></td>
<td>声明变量或函数的参数为只读类型</td>
</tr>
<tr>
<td><strong>attribute</strong></td>
<td>只能存在于vertex shader中,一般用于保存顶点或法线数据,它可以在数据缓冲区中读取数据</td>
</tr>
<tr>
<td><strong>uniform</strong></td>
<td>在运行时shader无法改变uniform变量, 一般用来放置程序传递给shader的变换矩阵，材质，光照参数等等.</td>
</tr>
<tr>
<td><strong>varying</strong></td>
<td>主要负责在vertex 和 fragment 之间传递变量</td>
</tr>
</tbody>
</table>
<p><strong>const</strong>:</p>
<p>和C语言类似,被const限定符修饰的变量初始化后不可变,除了局部变量,函数参数也可以使用const修饰符.但要注意的是结构变量可以用const修饰,
但结构中的字段不行.</p>
<p>const变量必须在声明时就初始化 <code>const vec3 v3 = vec3(0.,0.,0.)</code></p>
<p>局部变量只能使用const限定符.</p>
<p>函数参数只能使用const限定符.</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>attribute</strong>:</p>
<p>attribute变量是<code>全局</code>且<code>只读</code>的,它只能在vertex shader中使用,只能与浮点数,向量或矩阵变量组合,
一般attribute变量用来放置程序传递来的模型顶点,法线,颜色,纹理等数据它可以访问数据缓冲区
(还记得__gl.vertexAttribPointer__这个函数吧)</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>uniform</strong>:</p>
<p>uniform变量是<code>全局</code>且<code>只读</code>的,在整个shader执行完毕前其值不会改变,他可以和任意基本类型变量组合,
一般我们使用uniform变量来放置外部程序传递来的环境数据(如点光源位置,模型的变换矩阵等等)
这些数据在运行中显然是不需要被改变的.</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>varying</strong>:</p>
<p>varying类型变量是 vertex shader 与 fragment shader 之间的信使,一般我们在 vertex shader 中修改它然后在fragment shader使用它,但不能在
fragment shader中修改它.</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>要注意全局变量限制符只能为 const、attribute、uniform和varying中的一个.不可复合.</p>
<h3> 函数参数限定符:</h3>
<p>函数的参数默认是以拷贝的形式传递的,也就是值传递,任何传递给函数参数的变量,其值都会被复制一份,然后再交给函数内部进行处理.
我们可以为参数添加限定符来达到传递引用的目的,glsl中提供的参数限定符如下:</p>
<table>
<thead>
<tr>
<th>限定符</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>&lt; none: default &gt;</td>
<td>默认使用 in 限定符</td>
</tr>
<tr>
<td>in</td>
<td>复制到函数中在函数中可读写</td>
</tr>
<tr>
<td>out</td>
<td>返回时从函数中复制出来</td>
</tr>
<tr>
<td>inout</td>
<td>复制到函数中并在返回时复制出来</td>
</tr>
</tbody>
</table>
<p><code>in</code> 是函数参数的默认限定符,最终真正传入函数形参的其实是实参的一份拷贝.在函数中,修改in修饰的形参不会影响到实参变量本身.</p>
<p><code>out</code> 它的作用是向函数外部传递新值,out模式下传递进来的参数是write-only的(可写不可读).就像是一个"坑位",坑位中的值需要函数给他赋予.
在函数中,修改out修饰的形参会影响到实参本身.</p>
<p><code>inout</code> inout下,形参可以被理解为是一个带值的"坑位",及可读也可写,在函数中,修改inout修饰的形参会影响到实参本身.</p>
<h3> glsl的函数:</h3>
<p>glsl允许在程序的最外部声明函数.函数不能嵌套,不能递归调用,且必须声明返回值类型(无返回值时声明为void) 在其他方面glsl函数与c函数非常类似.</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 构造函数:</h3>
<p>glsl中变量可以在声明的时候初始化,<code>float pSize = 10.0</code> 也可以先声明然后等需要的时候在进行赋值.</p>
<p>聚合类型对象如(向量,矩阵,数组,结构) 需要使用其构造函数来进行初始化. <code>vec4 color = vec4(0.0, 1.0, 0.0, 1.0);</code></p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 类型转换:</h3>
<p>glsl可以使用构造函数进行显式类型转换,各值如下:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 精度限定:</h3>
<p>glsl在进行光栅化着色的时候,会产生大量的浮点数运算,这些运算可能是当前设备所不能承受的,所以glsl提供了3种浮点数精度,我们可以根据不同的设备来使用合适的精度.</p>
<p>在变量前面加上 <code>highp</code> <code>mediump</code> <code>lowp</code> 即可完成对该变量的精度声明.</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我们一般在片元着色器(fragment shader)最开始的地方加上 <code>precision mediump float;</code> 便设定了默认的精度.这样所有没有显式表明精度的变量
都会按照设定好的默认精度来处理.</p>
<p><strong>如何确定精度:</strong></p>
<p>变量的精度首先是由精度限定符决定的,如果没有精度限定符,则要寻找其右侧表达式中,已经确定精度的变量,一旦找到,那么整个表达式都将在该精度下运行.如果找到多个,
则选择精度较高的那种,如果一个都找不到,则使用默认或更大的精度类型.</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>invariant关键字:</strong></p>
<p>由于shader在编译时会进行一些内部优化,可能会导致同样的运算在不同shader里结果不一定精确相等.这会引起一些问题,尤其是vertx shader向fragmeng shader传值的时候.
所以我们需要使用<code>invariant</code> 关键字来显式要求计算结果必须精确一致. 当然我们也可使用 <code>#pragma STDGL invariant(all)</code>来命令所有输出变量必须精确一致,
但这样会限制编译器优化程度,降低性能.</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>限定符的顺序:</strong></p>
<p>当需要用到多个限定符的时候要遵循以下顺序:</p>
<p>1.在一般变量中: invariant &gt; storage &gt; precision</p>
<p>2.在参数中: storage &gt; parameter &gt; precision</p>
<p>我们来举例说明:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 预编译指令:</h3>
<p>以 # 开头的是预编译指令,常用的有:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>比如 <strong>#version 100</strong> 他的意思是规定当前shader使用 GLSL ES 1.00标准进行编译,如果使用这条预编译指令,则他必须出现在程序的最开始位置.</p>
<p><strong>内置的宏:</strong></p>
<p><code>__LINE__</code> : 当前源码中的行号.</p>
<p><code>__VERSION__</code> : 一个整数,指示当前的glsl版本 比如 100  ps: 100 = v1.00</p>
<p><code>GL_ES</code> : 如果当前是在 OPGL ES 环境中运行则 GL_ES 被设置成1,一般用来检查当前环境是不是 OPENGL ES.</p>
<p><code>GL_FRAGMENT_PRECISION_HIGH</code> : 如果当前系统glsl的片元着色器支持高浮点精度,则设置为1.一般用于检查着色器精度.</p>
<p>实例:</p>
<p>1.如何通过判断系统环境,来选择合适的精度:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>2.自定义宏:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 内置的特殊变量</h3>
<p>glsl程序使用一些特殊的内置变量与硬件进行沟通.他们大致分成两种 一种是 <code>input</code>类型,他负责向硬件(渲染管线)发送数据.
另一种是<code>output</code>类型,负责向程序回传数据,以便编程时需要.</p>
<p><strong>在 vertex Shader 中:</strong></p>
<p>output 类型的内置变量:</p>
<table>
<thead>
<tr>
<th>变量</th>
<th>说明</th>
<th>单位</th>
</tr>
</thead>
<tbody>
<tr>
<td>highp vec4 <code>gl_Position</code>;</td>
<td>gl_Position 放置顶点坐标信息</td>
<td>vec4</td>
</tr>
<tr>
<td>mediump float <code>gl_PointSize</code>;</td>
<td>gl_PointSize 需要绘制点的大小,(只在gl.POINTS模式下有效)</td>
<td>float</td>
</tr>
</tbody>
</table>
<p><strong>在 fragment Shader 中:</strong></p>
<p>input 类型的内置变量:</p>
<table>
<thead>
<tr>
<th>变量</th>
<th>说明</th>
<th>单位</th>
</tr>
</thead>
<tbody>
<tr>
<td>mediump vec4 <code>gl_FragCoord</code>;</td>
<td>片元在framebuffer画面的相对位置</td>
<td>vec4</td>
</tr>
<tr>
<td>bool <code>gl_FrontFacing</code>;</td>
<td>标志当前图元是不是正面图元的一部分</td>
<td>bool</td>
</tr>
<tr>
<td>mediump vec2 <code>gl_PointCoord</code>;</td>
<td>经过插值计算后的纹理坐标,点的范围是0.0到1.0</td>
<td>vec2</td>
</tr>
</tbody>
</table>
<p>output 类型的内置变量:</p>
<table>
<thead>
<tr>
<th>变量</th>
<th>说明</th>
<th>单位</th>
</tr>
</thead>
<tbody>
<tr>
<td>mediump vec4 <code>gl_FragColor</code>;</td>
<td>设置当前片点的颜色</td>
<td>vec4 RGBA color</td>
</tr>
<tr>
<td>mediump vec4 <code>gl_FragData[n]</code></td>
<td>设置当前片点的颜色,使用glDrawBuffers数据数组</td>
<td>vec4 RGBA color</td>
</tr>
</tbody>
</table>
<h3> 内置的常量</h3>
<p>glsl提供了一些内置的常量,用来说明当前系统的一些特性. 有时我们需要针对这些特性,对shader程序进行优化,让程序兼容度更好.</p>
<p><strong>在 vertex Shader 中:</strong></p>
<p>1.const mediump int <code>gl_MaxVertexAttribs</code>&gt;=8</p>
<p>gl_MaxVertexAttribs 表示在vertex shader(顶点着色器)中可用的最大attributes数.这个值的大小取决于 OpenGL ES 在某设备上的具体实现,
不过最低不能小于 8 个.</p>
<p>2.const mediump int <code>gl_MaxVertexUniformVectors</code> &gt;= 128</p>
<p>gl_MaxVertexUniformVectors 表示在vertex shader(顶点着色器)中可用的最大uniform vectors数. 这个值的大小取决于 OpenGL ES 在某设备上的具体实现,
不过最低不能小于 128 个.</p>
<p>3.const mediump int <code>gl_MaxVaryingVectors</code> &gt;= 8</p>
<p>gl_MaxVaryingVectors 表示在vertex shader(顶点着色器)中可用的最大varying vectors数. 这个值的大小取决于 OpenGL ES 在某设备上的具体实现,
不过最低不能小于 8 个.</p>
<p>4.const mediump int <code>gl_MaxVertexTextureImageUnits</code> &gt;= 0</p>
<p>gl_MaxVaryingVectors 表示在vertex shader(顶点着色器)中可用的最大纹理单元数(贴图). 这个值的大小取决于 OpenGL ES 在某设备上的具体实现,
甚至可以一个都没有(无法获取顶点纹理)</p>
<p>5.const mediump int <code>gl_MaxCombinedTextureImageUnits</code> &gt;= 8</p>
<p>gl_MaxVaryingVectors 表示在 vertex Shader和fragment Shader总共最多支持多少个纹理单元. 这个值的大小取决于 OpenGL ES 在某设备上的具体实现,
不过最低不能小于 8 个.</p>
<p><strong>在 fragment Shader 中:</strong></p>
<p>1.const mediump int <code>gl_MaxTextureImageUnits</code> &gt;= 8</p>
<p>gl_MaxVaryingVectors 表示在 fragment Shader(片元着色器)中能访问的最大纹理单元数,这个值的大小取决于 OpenGL ES 在某设备上的具体实现,
不过最低不能小于 8 个.</p>
<p>2.const mediump int <code>gl_MaxFragmentUniformVectors</code> &gt;= 16</p>
<p>gl_MaxFragmentUniformVectors 表示在 fragment Shader(片元着色器)中可用的最大uniform vectors数,这个值的大小取决于 OpenGL ES 在某设备上的具体实现,
不过最低不能小于 16 个.</p>
<p>3.const mediump int <code>gl_MaxDrawBuffers</code> = 1</p>
<p>gl_MaxDrawBuffers 表示可用的drawBuffers数,在OpenGL ES 2.0中这个值为1, 在将来的版本可能会有所变化.</p>
<p>glsl中还有一种内置的uniform状态变量, <code>gl_DepthRange</code> 它用来表明全局深度范围.</p>
<p>结构如下:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>除了 gl_DepthRange 外的所有uniform状态常量都已在glsl 1.30 中<code>废弃</code>.</p>
<h3> 流控制</h3>
<p>glsl的流控制和c语言非常相似,这里不必再做过多说明,唯一不同的是片段着色器中有一种特殊的控制流<code>discard</code>.
使用discard会退出片段着色器，不执行后面的片段着色操作。片段也不会写入帧缓冲区。</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 内置函数库</h3>
<p>glsl提供了非常丰富的函数库,供我们使用,这些功能都是非常有用且会经常用到的. 这些函数按功能区分大改可以分成7类:</p>
<p><strong>通用函数:</strong></p>
<p>下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>T abs(T x)</td>
<td>返回x的绝对值</td>
</tr>
<tr>
<td>T sign(T x)</td>
<td>比较x与0的值,大于,等于,小于 分别返回 1.0 ,0.0,-1.0</td>
</tr>
<tr>
<td>T floor(T x)</td>
<td>返回&lt;=x的最大整数</td>
</tr>
<tr>
<td>T ceil(T x)</td>
<td>返回&gt;=等于x的最小整数</td>
</tr>
<tr>
<td>T fract(T x)</td>
<td>获取x的小数部分</td>
</tr>
<tr>
<td>T mod(T x, T y) <br> T mod(T x, float y)</td>
<td>取x,y的余数</td>
</tr>
<tr>
<td>T min(T x, T y) <br> T min(T x, float y)</td>
<td>取x,y的最小值</td>
</tr>
<tr>
<td>T max(T x, T y) <br> T max(T x, float y)</td>
<td>取x,y的最大值</td>
</tr>
<tr>
<td>T clamp(T x, T minVal, T maxVal) <br>T clamp(T x, float minVal,float maxVal)</td>
<td>min(max(x, minVal), maxVal),返回值被限定在 minVal,maxVal之间</td>
</tr>
<tr>
<td>T mix(T x, T y, T a) <br>  T mix(T x, T y, float a)</td>
<td>取x,y的线性混合,x*(1-a)+y*a</td>
</tr>
<tr>
<td>T step(T edge, T x)  <br> T step(float edge, T x)</td>
<td>如果 x&lt;edge 返回 0.0 否则返回1.0</td>
</tr>
<tr>
<td>T smoothstep(T edge0, T edge1, T x) <br> T smoothstep(float edge0,float edge1, T x)</td>
<td>如果x&lt;edge0 返回 0.0 如果x&gt;edge1返回1.0, 否则返回Hermite插值</td>
</tr>
</tbody>
</table>
<p><strong>角度&amp;三角函数:</strong></p>
<p>下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>T radians(T degrees)</td>
<td>角度转弧度</td>
</tr>
<tr>
<td>T degrees(T radians)</td>
<td>弧度转角度</td>
</tr>
<tr>
<td>T sin(T angle)</td>
<td>正弦函数,角度是弧度</td>
</tr>
<tr>
<td>T cos(T angle)</td>
<td>余弦函数,角度是弧度</td>
</tr>
<tr>
<td>T tan(T angle)</td>
<td>正切函数,角度是弧度</td>
</tr>
<tr>
<td>T asin(T x)</td>
<td>反正弦函数,返回值是弧度</td>
</tr>
<tr>
<td>T acos(T x)</td>
<td>反余弦函数,返回值是弧度</td>
</tr>
<tr>
<td>T atan(T y, T x)<br>  T atan(T y_over_x)</td>
<td>反正切函数,返回值是弧度</td>
</tr>
</tbody>
</table>
<p><strong>指数函数:</strong></p>
<p>下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>T pow(T x, T y)</td>
<td>返回x的y次幂 x<sub>y</sub></td>
</tr>
<tr>
<td>T exp(T x)</td>
<td>返回x的自然指数幂 e<sub>x</sub></td>
</tr>
<tr>
<td>T log(T x)</td>
<td>返回x的自然对数 ln</td>
</tr>
<tr>
<td>T exp2(T x)</td>
<td>返回2的x次幂 2<sub>x</sub></td>
</tr>
<tr>
<td>T log2(T x)</td>
<td>返回2为底的对数 log2</td>
</tr>
<tr>
<td>T sqrt(T x)</td>
<td>开根号 √x</td>
</tr>
<tr>
<td>T inversesqrt(T x)</td>
<td>先开根号,在取倒数,就是 1/√x</td>
</tr>
</tbody>
</table>
<p><strong>几何函数:</strong></p>
<p>下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>float length(T x)</td>
<td>返回矢量x的长度</td>
</tr>
<tr>
<td>float distance(T p0, T p1)</td>
<td>返回p0  p1两点的距离</td>
</tr>
<tr>
<td>float dot(T x, T y)</td>
<td>返回x y的点积</td>
</tr>
<tr>
<td>vec3 cross(vec3 x, vec3 y)</td>
<td>返回x y的叉积</td>
</tr>
<tr>
<td>T normalize(T x)</td>
<td>对x进行归一化,保持向量方向不变但长度变为1</td>
</tr>
<tr>
<td>T faceforward(T N, T I, T Nref)</td>
<td>根据 矢量 N 与Nref 调整法向量</td>
</tr>
<tr>
<td>T reflect(T I, T N)</td>
<td>返回 I - 2 * dot(N,I) * N, 结果是入射矢量 I 关于法向量N的 镜面反射矢量</td>
</tr>
<tr>
<td>T refract(T I, T N, float eta)</td>
<td>返回入射矢量I关于法向量N的折射矢量,折射率为eta</td>
</tr>
</tbody>
</table>
<p><strong>矩阵函数:</strong></p>
<p>mat可以为任意类型矩阵.</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>mat matrixCompMult(mat x, mat y)</td>
<td>将矩阵 x 和 y的元素逐分量相乘</td>
</tr>
</tbody>
</table>
<p><strong>向量函数:</strong></p>
<p>下文中的 类型 T可以是 vec2, vec3, vec4, 且可以逐分量操作.</p>
<p>bvec指的是由bool类型组成的一个向量:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>bvec lessThan(T x, T y)</td>
<td>逐分量比较x &lt; y,将结果写入bvec对应位置</td>
</tr>
<tr>
<td>bvec lessThanEqual(T x, T y)</td>
<td>逐分量比较 x &lt;= y,将结果写入bvec对应位置</td>
</tr>
<tr>
<td>bvec  greaterThan(T x, T y)</td>
<td>逐分量比较 x &gt; y,将结果写入bvec对应位置</td>
</tr>
<tr>
<td>bvec greaterThanEqual(T x, T y)</td>
<td>逐分量比较  x &gt;= y,将结果写入bvec对应位置</td>
</tr>
<tr>
<td>bvec equal(T x, T y) <br> bvec equal(bvec x, bvec y)</td>
<td>逐分量比较 x == y,将结果写入bvec对应位置</td>
</tr>
<tr>
<td>bvec notEqual(T x, T y) <br> bvec notEqual(bvec x, bvec y)</td>
<td>逐分量比较 x!= y,将结果写入bvec对应位置</td>
</tr>
<tr>
<td>bool any(bvec x)</td>
<td>如果x的任意一个分量是true,则结果为true</td>
</tr>
<tr>
<td>bool all(bvec x)</td>
<td>如果x的所有分量是true,则结果为true</td>
</tr>
<tr>
<td>bvec not(bvec x)</td>
<td>bool矢量的逐分量取反</td>
</tr>
</tbody>
</table>
<p><strong>纹理查询函数:</strong></p>
<p>图像纹理有两种 一种是平面2d纹理,另一种是盒纹理,针对不同的纹理类型有不同访问方法.</p>
<p>纹理查询的最终目的是从sampler中提取指定坐标的颜色信息. 函数中带有Cube字样的是指 需要传入盒状纹理. 带有Proj字样的是指带投影的版本.</p>
<p>以下函数只在vertex shader中可用:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>以下函数只在fragment shader中可用:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在 vertex shader 与 fragment shader 中都可用:</p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 官方的shader范例:</h3>
<p>下面的shader如果你可以一眼看懂,说明你已经对glsl语言基本掌握了.</p>
<p><strong>Vertex Shader:</strong></p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Fragment Shader:</strong></p>
<div class="language-cpp line-numbers-mode" data-ext="cpp"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 转载自</h2>
<h4> 文章转自 <a href="https://github.com/wshxbqq" target="_blank" rel="noopener noreferrer">wshxbqq</a> 大佬的 https://github.com/wshxbqq/GLSL-Card</h4>
]]></content:encoded>
      <enclosure url="http://wshxbqq-wshxbqq.stor.sinaapp.com/2016-08-08_16-15-35_329___2.png" type="image/png"/>
    </item>
    <item>
      <title>什么是PBR</title>
      <link>https://v-blog.yyshino.top/posts/WebGl/03-PBR%E7%89%A9%E7%90%86%E6%B8%B2%E6%9F%93.html</link>
      <guid>https://v-blog.yyshino.top/posts/WebGl/03-PBR%E7%89%A9%E7%90%86%E6%B8%B2%E6%9F%93.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">什么是PBR</source>
      <description>什么是PBR</description>
      <category>FrontEnd</category>
      <pubDate>Wed, 14 Jun 2023 20:20:03 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong>文章转自<a href="https://zhuanlan.zhihu.com/p/342484575" target="_blank" rel="noopener noreferrer">知乎@云鸽</a></strong></p>
<figure><img src="https://pic1.zhimg.com/v2-dfba50aeb6400474d5e795124c13b7c9_720w.jpg?source=172ae18b" alt="什么是PBR？" tabindex="0" loading="lazy"><figcaption>什么是PBR？</figcaption></figure>
<h1> 什么是PBR？</h1>
<h2> <strong>一、什么是PBR？</strong></h2>
<ul>
<li>基于物理渲染</li>
<li>以前的渲染是在模仿灯光的外观</li>
<li>现在是在模仿光的实际行为</li>
<li>试图形看起来更真实</li>
</ul>
<h2> 二、PBR组成部分</h2>
<figure><img src="https://pic4.zhimg.com/80/v2-b7b17aa4bcf4fe29eefbfea179cdb5ef_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li><strong>灯光属性</strong>：直接照明、间接照明、直接高光、间接高光、阴影、环境光闭塞</li>
<li><strong>表面属性</strong>：基础色、法线、高光、粗糙度、金属度</li>
</ul>
<h2> 三、灯光属性</h2>
<h3> <strong>1、光线类型</strong></h3>
<figure><img src="https://pic3.zhimg.com/80/v2-bc3859883c4469f524a9add59e02264e_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<p><strong>入射光</strong></p>
<ul>
<li>直接照明：直接从光源发射阴影物体表面的光</li>
<li>间接照明：环境光和直接光经过反弹第二次进入的光</li>
</ul>
<p><strong>反射光</strong></p>
<ul>
<li>镜面光：在经过表面反射聚焦在同一方向上进入人眼的高亮光</li>
<li>漫发射：光被散射并沿着各个方向离开表面</li>
</ul>
<h3> 2、光与表面相互作用类型</h3>
<figure><img src="https://pic4.zhimg.com/80/v2-c0e39c006e20d5f7d6859dc916c57daf_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li><strong>直接漫反射</strong>：从源头到四面八方散发出来的直接高光</li>
<li><strong>直接高光</strong>：直接来自光源并被集中反射的光</li>
<li><strong>间接漫反射</strong>：来自环境的光被表面散射的光</li>
<li><strong>间接高光</strong>：来自环境光并被集中反射的光</li>
</ul>
<p><strong>（1）直接漫反射</strong></p>
<figure><img src="https://pic1.zhimg.com/80/v2-b9e003068afecb890494dca97ccee074_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li>直接来自光源的光</li>
<li>撞击表面后散落在各个方向</li>
<li>在着色器中使用简单的数学计算</li>
</ul>
<p><strong>（2）直接高光</strong></p>
<figure><img src="https://pic1.zhimg.com/80/v2-07d9ad373a3291dd4770a5e365a0b1d0_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li>直接来自光源的光</li>
<li>反射在一个更集中的方向上</li>
<li>在着色器中使用简单的数学计算</li>
</ul>
<p><em>直接镜面反射的计算成本比漫反射低很多</em></p>
<p><strong>（3）间接漫反射</strong></p>
<figure><img src="https://pic1.zhimg.com/80/v2-fafb6cf25c8b55c0ab962639ff1c761c_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li>来自环境中各个方向的光</li>
<li>撞击表面后散落在各个方向</li>
<li>因为计算昂贵，所以引擎的全局照明解决方案通常会离线渲染，并被烘培成灯光地图</li>
</ul>
<p><strong>（4）镜面反射</strong></p>
<figure><img src="https://pic3.zhimg.com/80/v2-fced27594b2cbb762cfaf8a07e68b3aa_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li>来自环境中各个方向的光</li>
<li>反射在一个更集中的方向上</li>
<li>引擎中使用反射探头，平面反射，SSR，或射线追踪计算</li>
</ul>
<h2> 四、表面属性</h2>
<p><strong>（1）基础色</strong></p>
<figure><img src="https://pic4.zhimg.com/80/v2-43e5a13366d92e3203852e5ff5d36c6b_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li>定义表面的漫反射颜色</li>
<li>真实世界的材料不会比20暗或比240 sRGB亮</li>
<li>粗糙表面具有更高的最低~ 50srgb</li>
<li>超出范围的值不能正确发光，所以保持在范围内是至关重要的</li>
</ul>
<figure><img src="https://pic4.zhimg.com/80/v2-cd6e2f1a3c1c17337c3068da6ac6684b_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<p><em><strong>基础色贴图制作注意点：</strong></em></p>
<ul>
<li>不包括任何照明或阴影</li>
<li>基本颜色纹理看起来应该非常平坦</li>
<li>使用真实世界的度量或获取最佳结果的数据</li>
</ul>
<p><strong>（2）法线</strong></p>
<figure><img src="https://pic4.zhimg.com/80/v2-237debda9015e0193e3366721cb72097_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li>定义曲面的形状每个像素代表一个矢量</li>
<li>该矢量指示表面所面对的方向即使网格是完全平坦的</li>
<li>法线贴图会使表面显得凹凸不平</li>
<li>用于添加表面形状的细节，这里三角形是实现不了的</li>
<li>因为它们表示矢量数据，所以法线贴图是无法手工绘制的</li>
</ul>
<p><strong>（3）镜面</strong></p>
<figure><img src="https://pic4.zhimg.com/80/v2-4a54d693e4105195fbc9e7ac89bfd0f3_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li>用于直接和间接镜面照明的叠加</li>
<li>当直视表面时，定义反射率</li>
<li>非金属表面反射约4%的光</li>
<li>0.5代表4%的反射</li>
<li>1.0代表8%的反射但对于大多数物体来说太高了</li>
<li>在掠射角下，所有表面都是100%反射的，内置于引擎中的菲涅耳项</li>
</ul>
<figure><img src="https://pic4.zhimg.com/80/v2-db82b88d8114f058b177efd4df8bfac7_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<p><em><strong>镜面贴图制作注意点：</strong></em></p>
<ul>
<li>高光贴图应该大多在0.5</li>
<li>使用深色的阴影来遮盖不应该反光的裂缝</li>
<li>一个裂缝贴图乘以0.5就是一个很好的高光贴图</li>
</ul>
<p><strong>（4）粗糙度</strong></p>
<figure><img src="https://pic4.zhimg.com/80/v2-8a4d45439343efe7dac2683043845b73_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li>表面在微观尺度上的粗糙度</li>
<li>白色是粗糙的</li>
<li>黑色是光滑的</li>
<li>控制反射的“焦点”</li>
<li>平滑=强烈的反射</li>
<li>粗糙=模糊的，漫反射</li>
</ul>
<figure><img src="https://pic1.zhimg.com/80/v2-9d33bcda62552312986705455c04ed48_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<p><em><strong>粗糙度贴图制作注意点：</strong></em></p>
<ul>
<li>没有技术限制-完全艺术的选择</li>
<li>艺术家可以使用这张地图来定义表面的“特征”，并展示它的历史</li>
<li>考虑一下被打磨光滑、磨损或老化的表面</li>
</ul>
<p><strong>（5）金属度</strong></p>
<figure><img src="https://pic2.zhimg.com/80/v2-c6ff366bb6cbba9abef1a0ac54a377f1_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<ul>
<li>两个不同的着色器通过金属度混合他们</li>
<li>基本色变成高光色而不是漫反射颜色</li>
<li>金属漫反射是黑色的</li>
<li>在底色下，镜面范围可达100%</li>
<li>大多数金属的反光性在60%到100%之间</li>
<li>确保对金属颜色值使用真实世界的测量值，并保持它们明亮</li>
<li>当金属为1时，镜面输入将被忽略</li>
</ul>
<figure><img src="https://pic4.zhimg.com/80/v2-b1f869506e78bac6fd105456caa4e62f_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<p><em><strong>粗糙度贴图制作注意点：</strong></em></p>
<ul>
<li>将着色器切换到金属模式</li>
<li>灰度值会很奇怪，最好使用纯白色或黑色</li>
<li>当金属色为白色时，请确保使用正确的金属底色值</li>
<li>没有黑暗金属这回事</li>
<li>所有金属均为180srgb或更亮</li>
</ul>
<h2> 五、非金属和金属对比</h2>
<figure><img src="https://pic3.zhimg.com/80/v2-3cdc5b485f8c47bf03dbd97f8739d726_720w.webp" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<p><strong>非金属</strong></p>
<ul>
<li>基础颜色=漫反射</li>
<li>镜面反射=0-8%</li>
</ul>
<p><strong>金属</strong></p>
<ul>
<li>基础颜色=0-100%的镜面反射</li>
<li>镜面=0%</li>
<li>漫反射总是黑色的</li>
</ul>
<h2> 六、总结</h2>
<ol>
<li>PBR是基于物理渲染的着色模型，PBR着色模型分为<strong>材质</strong>和<strong>灯光</strong>两个属性。</li>
<li>材质部分由：***基础色、法线、高光、粗糙度、金属度来定义***材质表面属性的。</li>
<li>灯光部分是由：***直接照明、间接照明、直接高光、间接高光、阴影、环境光闭塞***来定义照明属性的。</li>
<li>通常我们写材质的时候只需要关注材质部分的属性即可，灯光属性都是引擎定义好的直接使用即可。</li>
<li>PBR渲染模型不但指的是PBR材质，还有灯光，两者缺一不可。</li>
</ol>
<p><strong>本文章整理自作者Ben'Cloward视频教程：</strong></p>
<p><a href="https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DfePsD_8p9vM%26list%3DPL78XDi0TS4lFlOVKsNC6LR4sCQhetKJqs%26index%3D2" target="_blank" rel="noopener noreferrer">https://www.youtube.com/watch?v=fePsD_8p9vM&amp;list=PL78XDi0TS4lFlOVKsNC6LR4sCQhetKJqs&amp;index=2www.youtube.com/watch?v=fePsD_8p9vM&amp;list=PL78X</a></p>
<p><strong>文章转自<a href="https://zhuanlan.zhihu.com/p/342484575" target="_blank" rel="noopener noreferrer">知乎@云鸽</a></strong></p>
]]></content:encoded>
      <enclosure url="https://pic1.zhimg.com/v2-dfba50aeb6400474d5e795124c13b7c9_720w.jpg?source=172ae18b" type="image/"/>
    </item>
    <item>
      <title>Linux常用知识</title>
      <link>https://v-blog.yyshino.top/posts/Computer/Linux/Computer-Lunux%E5%B8%B8%E7%94%A8%E7%9F%A5%E8%AF%86.html</link>
      <guid>https://v-blog.yyshino.top/posts/Computer/Linux/Computer-Lunux%E5%B8%B8%E7%94%A8%E7%9F%A5%E8%AF%86.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Linux常用知识</source>
      <description>Linux常用知识</description>
      <category>Computer</category>
      <pubDate>Tue, 06 Jun 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> Linux常用知识</h2>
<p>Linux操作系统的创始人和主要设计者是 <strong>Linus Torvalds</strong></p>
<img src="https://bkimg.cdn.bcebos.com/pic/5882b2b7d0a20cf4f664615276094b36adaf9943?x-bce-process=image/resize,m_lfit,w_536,limit_1" alt="林纳斯·本纳第克特·托瓦兹" style="zoom:50%;">
<p>——图片来自百度百科</p>
<p>LInux内核遵循 GPL 许可条例。</p>
<h2> Linux联机帮助命令 <code>man</code></h2>
<p>你可以使用 <em>man [命令]</em> 来查看各个命令的使用文档，如 ：man cp。</p>
<h2> 一行内运行多个命令</h2>
<p>在Linux中，有三种方法可以在一行中运行多个命令:</p>
<table>
<thead>
<tr>
<th></th>
<th>示例</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>;</code></td>
<td><code>Command 1;Command 2</code></td>
<td>首先运行Command1，<strong>然后</strong>运行Command2</td>
</tr>
<tr>
<td><code>&amp;&amp;</code></td>
<td><code>Command 1 &amp;&amp; Command 2</code></td>
<td>当Command1<strong>运行成功</strong>并结束，然后运行Command2</td>
</tr>
<tr>
<td>`</td>
<td></td>
<td>`</td>
</tr>
</tbody>
</table>
<h2> shell中的几种标准输出重定向方式</h2>
<table>
<thead>
<tr>
<th style="text-align:left">命令</th>
<th style="text-align:left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">command &gt; file</td>
<td style="text-align:left">将输出重定向到 file。<strong>注意</strong>任何file内的已经存在的内容将被新内容<strong>替代</strong>。如果要将新内容添加在文件末尾，请使用&gt;&gt;操作符。</td>
</tr>
<tr>
<td style="text-align:left">command &lt; file</td>
<td style="text-align:left">将输入重定向到 file。</td>
</tr>
<tr>
<td style="text-align:left">command &gt;&gt; file</td>
<td style="text-align:left">将输出以追加的方式重定向到 file。</td>
</tr>
<tr>
<td style="text-align:left">n &gt; file</td>
<td style="text-align:left">将文件描述符为 n 的文件重定向到 file。</td>
</tr>
<tr>
<td style="text-align:left">n &gt;&gt; file</td>
<td style="text-align:left">将文件描述符为 n 的文件以追加的方式重定向到 file。</td>
</tr>
<tr>
<td style="text-align:left">n &gt;&amp; m</td>
<td style="text-align:left">将输出文件 m 和 n 合并。</td>
</tr>
<tr>
<td style="text-align:left">n &lt;&amp; m</td>
<td style="text-align:left">将输入文件 m 和 n 合并。</td>
</tr>
<tr>
<td style="text-align:left">&lt;&lt; tag</td>
<td style="text-align:left">将开始标记 tag 和结束标记 tag 之间的内容作为输入。</td>
</tr>
</tbody>
</table>
<h2> 重定向管道的输出到标准输出和指定的文件中</h2>
<p>使用<code>tee</code>命令</p>
<p>如果您只关心标准输出</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 文件的访问权限</h2>
<p>在 Linux 中第一个字符代表这个文件是目录、文件或链接文件等等。</p>
<ul>
<li><strong>当为 d 则是目录</strong></li>
<li><strong>当为 - 则是文件；</strong></li>
<li><strong>若是 l 则表示为链接文档(link file)；</strong></li>
<li>若是 <strong>b</strong> 则表示为装置文件里面的可供储存的接口设备(可随机存取装置)；</li>
<li>若是 <strong>c</strong> 则表示为装置文件里面的串行端口设备，例如键盘、鼠标(一次性读取装置)。</li>
</ul>
<p>接下来的字符中，以三个为一组，且均为 <strong>rwx</strong> 的三个参数的组合。其中， <strong>r</strong> 代表可读(read)、 <strong>w</strong> 代表可写(write)、 <strong>x</strong> 代表可执行(execute)。 要注意的是，这三个权限的位置不会改变，如果没有权限，就会出现减号 <strong>-</strong> 而已。</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305121545561.jpeg" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<h3> 十位权限表示</h3>
<p>常见的权限表示形式有：</p>
<p>每个文件的属性由左边第一部分的 10 个字符来确定（如下图）。</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305121546416.png" alt="363003_1227493859FdXT" tabindex="0" loading="lazy"><figcaption>363003_1227493859FdXT</figcaption></figure>
<p>从左至右用 <strong>0-9</strong> 这些数字来表示。</p>
<p>第 <strong>0</strong> 位确定文件类型</p>
<p>第 <strong>1-3</strong> 位确定<strong>属主</strong>（该文件的所有者）拥有该文件的权限。</p>
<p>第<strong>4-6</strong>位确定<strong>属组</strong>（所有者的同组用户）拥有该文件的权限，</p>
<p>第<strong>7-9</strong>位确定<strong>其他用户</strong>拥有该文件的权限。</p>
<p>其中，第 <strong>1、4、7</strong> 位表示读权限，如果用 <strong>r</strong> 字符表示，则有读权限，如果用 <strong>-</strong> 字符表示，则没有读权限；第 <strong>2、5、8</strong> 位表示写权限，如果用 <strong>w</strong> 字符表示，则有写权限，如果用 <strong>-</strong> 字符表示没有写权限；第 <strong>3、6、9</strong> 位表示可执行权限，如果用 <strong>x</strong> 字符表示，则有执行权限，如果用 <strong>-</strong> 字符表示，则没有执行权限。</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 新建文件的默认权限</h2>
<p>当新建一个文件或目录时,系统会为其设置最初的权限。文件的初始权限由文件创建掩码(creation mask)决定。掩码是一个9位二进制数字，通常用八进制数字表示，如022。掩码中的位与权限字符串相对应,掩码中为1的位限制对应的权限位的权限。例如,<strong>掩码022 表示组用户和其他人没有 w 权限,对其他权限不做限制。</strong>
文件创建时的默认权限有以下几种情况:</p>
<ul>
<li>可执行文件：通过编译程序生成的可执行文件,它的默认权限是 777-掩码。例如，若掩码为 022,则新文件的权限就是 755。</li>
<li>非可执行文件：对于非可执行文件(如文本文件、数据文件等),在创建时默认是没有x权限的,对这类文件的x权限需要显式地赋予，即通过 chmod 命令将其改为可执行文件。因此新建文件的权限是<code>(777-掩码)&amp;666</code>。这里的是“按位与”运算，即先用<code>777-掩码</code>求出权限,再滤掉所有 x 位。例如,若掩码为 022,则新文件的权限就是 <code>(777-022)&amp; 666 = 644</code>。若掩码为 003,则新文件的权限就是<code>(777-003)&amp;666=664</code>。</li>
<li>目录：同可执行文件一样,新建目录的默认权限是 777-掩码。若掩码为 022,则新目录的权限就是 755。</li>
</ul>
<p>用户登录时,系统自动地为其设置了掩码,通常是 022。用户可以用命令修改掩码从而改变新建文件的默认权限,使之具有合适的安全性限制。</p>
<h3> 文件的其他属性</h3>
<p>除了文件名、文件类型、归属关系和存取权限外,文件还有其他一些属性,包括</p>
<ol>
<li>文件的时间标签，用于记录文件的时间属性，分为:
<ul>
<li>修改时间(modify time):文件内容被修改的最后时间</li>
<li>访问时间(access time):文件最近一次被访问的时间</li>
<li>变更时间(change time):文件属性变更的最近时间。</li>
</ul>
</li>
<li>文件的大小,即文件所占用的字节数。</li>
<li>文件的连接数,即此文件硬链接的数目。</li>
</ol>
<h2> vi的工作模式</h2>
<p>vi的工作模式
vi 是一个多模式的软件,它有3 种基本工作模式。在不同的工作模式下,它对输人的内容有不同的解释。的基本工作模式如下:</p>
<ol>
<li>
<p>命令模式</p>
<p>命令模式(normal mode)用于完成各种文本编辑工作。在命令模式下,输人的任何字符都作为命令来解释执行,屏幕上不显示输入内容。</p>
</li>
<li>
<p>输人模式</p>
<p>输人模式(insert mode)用于完成文本录人工作。在输入模式下,输人的任何字符都将作为文件的内容被保存,并显示在屏幕上。</p>
</li>
<li>
<p>末行模式</p>
<p>末行模式(last line mode)也称为 ex模式。在末行模式下,光标停留在屏幕的最未行,在此接收输人的命令并执行。末行模式用于执行一些全局性操作,如文件操作、参数设置、查找替换、拷贝粘贴、执行 Shell 命令等</p>
</li>
</ol>
<h3> grep命令</h3>
<p>Linux grep (global regular expression) 命令用于<strong>查找文件里符合条件的字符串或正则表达式。</strong></p>
<p>grep 指令用于查找内容包含指定的范本样式的文件，如果发现某文件的内容符合所指定的范本样式，预设 grep 指令会把含有范本样式的那一列显示出来。若不指定任何文件名称，或是所给予的文件名为 <strong>-</strong>，则 grep 指令会从标准输入设备读取数据。</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 简述虚拟存储器的特征</h2>
<ul>
<li>离散性：每进程的地址空间是离散的存在于内存中的。</li>
<li>多次性：每进程分为多个段，分多次调入内存的。</li>
<li>对换性：内存里的数据可与磁盘上的数据调换运行。</li>
<li>虚拟性：使得逻辑上可利用的内存空间大大超过了实际的物理内存空间。</li>
</ul>
<h2> fock()函数的特点是什么</h2>
<ul>
<li>一次调用，两次返回</li>
<li>fork()系统调用，创建一个新的子进程</li>
</ul>
<h2> GCC</h2>
<p>Linux下GCC的使用</p>
<p>gcc</p>
<p>-o：指定生成文件的名称</p>
<p>-E：激活预处理。生成预处理文件（ .i 文件）</p>
<p>-S：激活预处理、编译。生成汇编代码（ .s 文件）</p>
<p>-c：激活预处理、编译、汇编。生成目标文件（ .o 文件）</p>
<p>-I：指定头文件目录</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305131600144.png" alt="image-20230513160005625" tabindex="0" loading="lazy"><figcaption>image-20230513160005625</figcaption></figure>
<p>预编译</p>
<p><code>gcc -E hello.c -o hello.i</code></p>
<p>编译</p>
<p><code>gcc -S hello.i -o hello.s</code></p>
<p>汇编</p>
<p><code>gcc -c hello.s -o hello.o</code></p>
<p>链接</p>
<p><code>gcc hello.o -o hello.out</code></p>
<p>执行</p>
<p><code>./hello.out</code></p>
<p>一次性完成</p>
<p><code>gcc hello.c -o hello.out</code></p>
<p>执行</p>
<p><code>./hello</code></p>
<p>多个文件的一起编译</p>
<p><code>gcc print.c main.c -o main print</code></p>
<p>分别编译各个源文件，再对编译后输出的目标文件（.o）</p>
<p><code>gcc -c print.c -o print.o</code></p>
<p><code>gcc -c main.c -o main.o</code></p>
<p><code>gcc print.o main.o -o main print</code></p>
<p>如果头文件和源文件不在一同目录中，如何解决</p>
<p>主要是要加 -I 指定头文件目录：</p>
<p><code>gcc print.c main.c -o main print -I myInclude </code></p>
<p>makefile（Makefile）</p>
<div class="language-makefile line-numbers-mode" data-ext="makefile"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> Shell脚本</h2>
<h2> 参考</h2>
<div class="language-url line-numbers-mode" data-ext="url"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202305121545561.jpeg" type="image/jpeg"/>
    </item>
    <item>
      <title>读书笔记-05-数据结构与算法JavaScript描述</title>
      <link>https://v-blog.yyshino.top/reading_notes/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-05-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95JavaScript%E6%8F%8F%E8%BF%B0.html</link>
      <guid>https://v-blog.yyshino.top/reading_notes/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-05-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95JavaScript%E6%8F%8F%E8%BF%B0.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">读书笔记-05-数据结构与算法JavaScript描述</source>
      <description>读书笔记-05-数据结构与算法JavaScript描述</description>
      <category>FrontEnd</category>
      <pubDate>Thu, 01 Jun 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 数据结构与算法JavaScript描述</h2>
<h2> 简介</h2>
<p>在前端工程师中，常常有一种声音：“我为什么要学习数据结构与算法？没有数据结构与 算法，我一样很好地完成了工作？</p>
<p>实际上，算法是一个十分宽泛的概念，我们写的任何程序都可称为算法，甚至往冰箱里面 放一头大象，也要经过开门、放入、关门这样的规划，这也可以视为一种简单的算法。可 以说，简单的算法是人类的本能。而算法知识的学习则是吸取前人的经验，对复杂的问题 进行归类、抽象，帮助我们脱离刀耕火种时代，系统掌握算法的一个过程。</p>
<p>随着自身成长和职业发展，不论是做前端、服务端还是客户端，任何一个程序员都会开始 面对更加复杂的问题，算法和数据结构知识就变得不可或缺了。</p>
<p>我一直认为前端工程师则是最需要重视算法和数据结构基础的人。因为历史原因，不少前 端工程师是从视觉设计、网站编辑转过来的，在学校没有学过相应的基础课程，而数据结 构与算法的经典名著大部分又没照顾到入门的需要，所以前端工程师如果自身不重视算法 和数据结构这样的基础知识，很可能陷入数年从事单一重复劳动毫无成长这样的职业发展 困境。在移动浪潮到来之后，用户体验要求越来越高，对前端提出了更高的要求，前端这 个职能，必须提高自身才能继续发展，未来的网页 UI，绝对不是靠几个选择器操作加超链 接就能应付的。越来越复杂的产品和基础库，需要坚实的数据结构与算法基础才能驾驭。</p>
<p>本书对前端工程师是非常好的数据结构与算法入门书，它的难度非常适合前端工程师补习 基础知识。全书仅 200 页，对于有渴求数据结构与算法的前端工程师来说这是非常不错的 开始。特别值得一提的是每章后面的小练习，题目不多但是非常有可操作性。</p>
<h2> 第一章 JavaScript的编程环境和模型</h2>
<p>基础语法</p>
<h2> 第二章 数组</h2>
<p>数组的标准定义是：一个存储元素的线性集合（collection），元素可以通过索引来任意存 取，索引通常是数字，用来计算元素之间存储位置的偏移量。几乎所有的编程语言都有类 似的数据结构。然而 JavaScript 的数组却略有不同。</p>
<p>JavaScript 中的数组是一种特殊的对象，用来表示偏移量的索引是该对象的属性，索引可 能是整数。然而，这些数字索引在内部被转换为字符串类型，这是因为 JavaScript 对象中 的属性名必须是字符串。数组在 JavaScript 中只是一种特殊的对象，所以效率上不如其他 语言中的数组高。</p>
<p>JavaScript 中的数组，严格来说应该称作对象，是特殊的 JavaScript 对象，在内部被归类为数 组。由于 Array 在 JavaScript 中被当作对象，因此它有许多属性和方法可以在编程时使用。</p>
<figure><img src="https://shinoimg.yyshino.top/img/202304162249084.png" alt="02-JavaScript数组-01" tabindex="0" loading="lazy"><figcaption>02-JavaScript数组-01</figcaption></figure>
<figure><img src="https://shinoimg.yyshino.top/img/202304162249028.png" alt="02-JavaScript数组-02" tabindex="0" loading="lazy"><figcaption>02-JavaScript数组-02</figcaption></figure>
<h2> 第三章 列表</h2>
<figure><img src="https://shinoimg.yyshino.top/img/202304171930023.png" alt="02-JavaScript列表-01" tabindex="0" loading="lazy"><figcaption>02-JavaScript列表-01</figcaption></figure>
<h2> 第四章 栈</h2>
<figure><img src="https://shinoimg.yyshino.top/img/202304181023645.png" alt="image-20230418102303188" tabindex="0" loading="lazy"><figcaption>image-20230418102303188</figcaption></figure>
<figure><img src="https://shinoimg.yyshino.top/img/202304181025304.png" alt="04-JavaScript栈" tabindex="0" loading="lazy"><figcaption>04-JavaScript栈</figcaption></figure>
<h2> 第五章 队列</h2>
<figure><img src="https://shinoimg.yyshino.top/img/202304181030037.png" alt="image-20230418103041921" tabindex="0" loading="lazy"><figcaption>image-20230418103041921</figcaption></figure>
<h2> 第六章 列表</h2>
<h2> 第七章 字典 Dictionary</h2>
<h2> 第八章 散列</h2>
<h2> 第九章 集合</h2>
<h2> 第十章 二叉树</h2>
<figure><img src="https://shinoimg.yyshino.top/img/202304181456986.png" alt="image-20230418145622900" tabindex="0" loading="lazy"><figcaption>image-20230418145622900</figcaption></figure>
<h3> 遍历二叉树</h3>
<h4> 中序遍历</h4>
<p>中序遍历按照节点上的键值，以升序访问BST 上的所有节点</p>
<figure><img src="https://shinoimg.yyshino.top/img/202304181628159.png" alt="image-20230418162735265" tabindex="0" loading="lazy"><figcaption>image-20230418162735265</figcaption></figure>
<h4> 先序遍历</h4>
<p>先序遍历先访问根节点，然后以同样方式访问左子树和右子树</p>
<figure><img src="https://shinoimg.yyshino.top/img/202304181629097.png" alt="image-20230418162822218" tabindex="0" loading="lazy"><figcaption>image-20230418162822218</figcaption></figure>
<h4> 后序遍历</h4>
<p>后序遍历先访问叶子节点，从左子树到右子树，再到根节点</p>
<figure><img src="https://shinoimg.yyshino.top/img/202304181629980.png" alt="image-20230418162852773" tabindex="0" loading="lazy"><figcaption>image-20230418162852773</figcaption></figure>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202304162249084.png" type="image/png"/>
    </item>
    <item>
      <title>计算机体系结构</title>
      <link>https://v-blog.yyshino.top/posts/Computer/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/Computer-%E8%8C%83%E5%9B%B4.html</link>
      <guid>https://v-blog.yyshino.top/posts/Computer/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/Computer-%E8%8C%83%E5%9B%B4.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">计算机体系结构</source>
      <description>计算机体系结构</description>
      <category>Computer</category>
      <pubDate>Wed, 31 May 2023 11:07:13 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 第一章</h2>
<h3> 简答题：发展<mark>并行性的三种途径</mark></h3>
<ol>
<li>并行性：（parallelism）指问题中具有可以同时运算或操作的特性。并行性包含同时性和并发性两层含义。<strong>（同时性（Simultaneity）：两个或多个事件在同一时刻发生。并发性（Concurrency）：两个或多个事件在同一时间间隔内发生。 ）</strong></li>
<li>发展并行性的三种途径：
<ol>
<li><strong>时间重叠：</strong>（Time Interleaving）是在并行性概念中引入时间因素，让两个或多个任务在时间上相互错开，轮流使用同一套设备的各个组成部分，以加快硬件周转而赢得速度。</li>
<li><strong>资源重复：</strong>（Resource Replication）是在并行性概念中引入空间因素，通过重复设置硬件资源来提高可靠性或性能。</li>
<li><strong>资源共享：</strong>（Resource Sharing）是利用软件的方法让多个任务按一定时间顺序轮流使用同一套设备，以提高设备利用率和系统性能。</li>
</ol>
</li>
</ol>
<h3> 作业1 10分</h3>
<ol>
<li>先求平均CPI</li>
<li>Se 改变前的CPI/改变后 | Fe 变化前CPI占总CPI的比例</li>
<li>Sn=1/[(1-Fe) + Fe/Se]</li>
</ol>
<p><img src="https://shinoimg.yyshino.top/img/202305221720631.png" alt="image-20230522172017903" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305221723101.png" alt="image-20230522172332372" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305221724615.png" alt="image-20230522172407920" loading="lazy"></p>
<h3> 看一下时间，空间局部性</h3>
<ul>
<li>时间局部性：程序中近期被访问的信息项很可能马上将被再次访问。</li>
<li>空间局部性：指那些在访问地址上相邻近的信息项很可能会被一起访问。</li>
</ul>
<h3> <strong>神威太湖之光 选择</strong></h3>
<p>2017年6月19日，全球超级计算机500强榜单公布，<mark>中国国家超级计算无锡中心的“神威·太湖之光”和国家超算广州中心的“天河二号”第三次携手夺得冠亚军</mark> ，美国“泰坦”20多年来首次跌出前三｡</p>
<p>2022年超算“前沿”运算峰值速度超过每秒100亿亿次（1102.00Pflop/s）。</p>
<h2> 第二章</h2>
<h3> 引入数据表的原则2个，简答题（2.1.3）</h3>
<ol>
<li><mark>是否有利于提高系统效率，是否减少了实现时间和存储空间</mark>
<ul>
<li>举例1：两个200*200的二维定点数组相加<code>A=A+B</code></li>
<li>无阵列：需循环执行4*(200*200)=16万条指令</li>
<li>有阵列：1条向量指令，减少16万次取指时间</li>
</ul>
</li>
<li>看引入数据表示后，<mark>其通用性和利用率是否高</mark>；
<ul>
<li>通用性：是否仅局限于一种数据结构的支持。</li>
<li>利用率：引入某种数据表示后，该数据类型操作的次数</li>
<li>数据结构的发展总是优先于机器的数据表示，应尽可能为数据结构提供更多的支持</li>
</ul>
</li>
</ol>
<h3> <strong>2.1.4 10分必考</strong></h3>
<p><img src="https://shinoimg.yyshino.top/img/202305231556836.png" alt="image-20230523155637021" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305231557914.png" alt="image-20230523155701786" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305172020149.png" alt="image-20230517202018036" loading="lazy"></p>
<h3> 操作码的优化</h3>
<p><strong>哈夫曼树啥的 10分（可参考期中考试）</strong></p>
<h4> Huffman编码</h4>
<p>画huffman树</p>
<p>计算平均码长（根据使用频度和操作码码长计算）</p>
<h4> 扩展编码</h4>
<p>两种长的扩展编码，在huffman编码的基础上，根据使用频度值分为两部分</p>
<h3> risi有哪些技术，ppt四个标题（重叠窗口技术）</h3>
<ul>
<li><strong>重叠寄存器窗口</strong>技术</li>
<li><strong>延迟转移</strong>（Delayed Branch）</li>
<li><strong>比较转移指令</strong></li>
<li><strong>优化编译</strong></li>
</ul>
<h2> 第四章</h2>
<h3> 页面替换算法</h3>
<ul>
<li>
<p>页面替换发生时间：</p>
<ul>
<li>当发生页面失效时，要从磁盘中调入一页到主存。如果主存所有页面都已经被占用，必须从主存储器中淘汰掉一个不常使用的页面，以便腾出主存空间来存放新调入的页面。</li>
</ul>
</li>
<li>
<p>替换算法的确定</p>
<ul>
<li>主存的命中率</li>
<li>是否便于实现，软、硬件成本</li>
</ul>
</li>
</ul>
<h4> 随机算法（RAND）</h4>
<ul>
<li>随机算法（Random ，RAND）：用软的或硬的随机数产生器来形成主存中要被替换页的页号。
<ul>
<li>简单，易于实现</li>
<li>没有利用历史信息</li>
<li>命中率低，很少使用</li>
</ul>
</li>
</ul>
<h4> 先进先出算法（FIFO）</h4>
<ul>
<li>先进先出算法（First-In First-Out ，FIFO）：<strong>选择最早装入主存的页作为被替换的页</strong>。
<ul>
<li>配置计数器字段</li>
<li>虽然利用历史信息，但不一定反映出程序的局部性</li>
</ul>
</li>
</ul>
<p>命中：主存中连续两次数据相同那么他就是命中</p>
<p>命中率：命中页/总页</p>
<p>示例：</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305241551191.png" alt="image-20230524155155369" tabindex="0" loading="lazy"><figcaption>image-20230524155155369</figcaption></figure>
<p>命中率= 5/10=50%</p>
<h4> 近期最少使用算法（LRU）</h4>
<ul>
<li>近期最少使用算法（Least Recently Used ，LRU）：<strong>选择近期最少访问的页作为被替换的页</strong>。
<ul>
<li>配有计数器字段。</li>
<li>比较正确反映程序的局部性。</li>
</ul>
</li>
</ul>
<p>主存页面数=主存容量/页面大小</p>
<p>虚页地址=(虚存地址/页面大小)取整</p>
<p>示例：</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305241547401.png" alt="image-20230524154745662" tabindex="0" loading="lazy"><figcaption>image-20230524154745662</figcaption></figure>
<h4> 优化替换算法（OPT）</h4>
<ul>
<li>优化替换算法（Optimal Replacement Algorithm， OPT）:是在时刻t找出主存中每个页将要用到时刻ti，然后选择其中ti-t最大的那一页作为替换页。
<ul>
<li>理想化算法</li>
</ul>
</li>
</ul>
<p>说明：</p>
<ul>
<li>命中率与地址流有关
<ul>
<li>例如：一个循环程序，FIFO、LRU的命中率明显低于OPT</li>
<li>颠簸现象：连续不断出现页面失效。</li>
</ul>
</li>
<li>命中率与分配给程序的主存页数有关。
<ul>
<li>主存页数增加，LRU命中率提高，至少不会下降，而FIFO不一定。</li>
</ul>
</li>
</ul>
<h3> 堆栈性算法</h3>
<p>定义：设A是一个长度为L的任意虚页地址流，t为已处理过t-1个虚页的时间点，n为分配给该虚页地址流的实页数，Bt(n)表示在t时刻，在n页的主存中的页面集合，Lt表示在t时间点已处理过的虚页中页面相异的页数，若替换算法满足：</p>
<p>$n &lt; Lt时 Bt(n) \subset Bt(n+1)$</p>
<p>$n &gt;= Lt时 Bt(n) = Bt(n+1)$</p>
<p>则称此算法为堆栈型替换算法。（LRU和OPT属于堆栈性算法）</p>
<p>上为栈顶</p>
<p>下为栈底</p>
<p>压栈</p>
<p>出栈</p>
<p>示例</p>
<p>设某程序包含5个虚页，其页地址流为4，5，3，2，5，1，3，2，2，5，1，3。当使用LRU算法替换时，为获得最高的命中率，至少应分配给该程序几个实页？其可能的最高命中率为多少？</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305241626776.png" alt="image-20230524162611047" tabindex="0" loading="lazy"><figcaption>image-20230524162611047</figcaption></figure>
<h3> 高速缓冲存储器</h3>
<p><strong>4.3要求全部掌握，对应第四章题目需要会，期中考试中也有</strong></p>
<ul>
<li><strong>不需要任何替换算法的地址映像？直接相联 选择题</strong></li>
<li><strong>对比法需要会画电路图</strong></li>
</ul>
<h4> Cache、组相联映像</h4>
<ol>
<li>主存、Cache地址中各个字段的含义、位数及其映像的对应关系</li>
<li>主存、Cache空间块的映像对应关系</li>
<li>Cache各块随时间的使用状况</li>
<li>块失效又发生争用的时刻（既不是命中又不是失效的时刻，即替换）</li>
<li>此期间Cache之命中率</li>
</ol>
<p>示例</p>
<p>4-15：有 Cache 存储器。主存有 0～7 共 8 块，Cache 有 4 块，采用组相联映像，分2组。假设Cache已先后访问并预取进了主存的第 5，1，3，7块，现访存块地址流又为1,2,4,1,3,7,0,1,2,5,4,6时</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305241929015.png" alt="image-20230524192918248" tabindex="0" loading="lazy"><figcaption>image-20230524192918248</figcaption></figure>
<h3> 什么叫cache的一致性问题</h3>
<p>一般情况下，Cache中存放的是主存的部分副本，因此，Cache块应该与相应主存块的内容保持一致。但是在某些情况下，Cache块与相应主存块的内容会不相同，也就是产生了Cache的一致性问题。</p>
<ul>
<li>多Cache的一致性问题</li>
</ul>
<p>每一个处理机都有自己专用的Cache，但主存中同一个信息块在多个Cache中都有时，会出现信息不一致情况</p>
<p>写直达法----保证一个</p>
<p>进程迁移----将一个尚未执行完而被挂起的进程调度到另一个空闲的处理机上去执行</p>
<p>对于进程迁移的Cache不一致性----禁止进程迁移</p>
<h2> 第五章 标量处理机</h2>
<h3> 什么叫做流水线的瓶颈段，怎么解决瓶颈端，简答</h3>
<p>定义：流水线各段执行时间不相等</p>
<p>解决：</p>
<ol>
<li>细分瓶颈段</li>
<li>重复设置瓶颈段（增加分配器和收集器）（二是将瓶颈子过程多套并联）</li>
</ol>
<h3> 非流水线的时空调度 10分</h3>
<p><strong>第五章绝对有大题，要把作业做会，期中考试也有可以做，画时空图，计算几个性能指标，求冲突向量，禁止表等</strong></p>
<h3> 时空图</h3>
<h3> 性能指标：</h3>
<ul>
<li><strong>吞吐率</strong></li>
</ul>
<p>各段执行时间相等，输入连续任务情况下：</p>
<p><strong>吞吐率公式：</strong>$ T P = n / (k+n-1) \Delta t$ （n为任务数，$T_k$为完成n个任务所用的时间。k 为流水线的段数，$\Delta t$为每个功能子段的执行时间（也称为1拍））</p>
<p><strong>最大吞吐率：</strong>$ T P = \displaystyle \lim^{}_{n \to \infty} n / (k+n-1) \Delta t = 1/\Delta t$</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305221526885.png" alt="image-20230522152613663" tabindex="0" loading="lazy"><figcaption>image-20230522152613663</figcaption></figure>
<ul>
<li><strong>加速比</strong></li>
</ul>
<figure><img src="https://shinoimg.yyshino.top/img/202305221533907.png" alt="image-20230522153259500" tabindex="0" loading="lazy"><figcaption>image-20230522153259500</figcaption></figure>
<ul>
<li><strong>效率</strong></li>
</ul>
<figure><img src="https://shinoimg.yyshino.top/img/202305221539588.png" alt="image-20230522153932513" tabindex="0" loading="lazy"><figcaption>image-20230522153932513</figcaption></figure>
<h3> 禁止表与冲突向量</h3>
<p><img src="https://shinoimg.yyshino.top/img/202305221619340.png" alt="image-20230522161921583" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305221620889.png" alt="image-20230522162020009" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305221621282.png" alt="image-20230522162058723" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305221621968.png" alt="image-20230522162139622" loading="lazy"></p>
<p>延迟禁止表：对预约表中每一行取差，得到一组数，再对这组数去重即可得到延迟禁止表</p>
<p>冲突向量：找出禁止表中最大的数，代表冲突向量的二进制位数，然后根据禁止表中的数在冲突向量的相应位数取一其余补零（从右往左取）。</p>
<p>流水状态图：</p>
<ol>
<li>
<p>求状态转移</p>
<ol>
<li>
<p>求出初始状态状态转移（看冲突向量中0的位数（从右往左数），当前状态先右移0的位数，再或上冲突向量，重复操作得到初始状态的转移（我这里简称一级状态）</p>
</li>
<li>
<p>对一级状态重复上述操作，得到二级，以此类推</p>
</li>
</ol>
</li>
<li>
<p>画状态转移图</p>
<ol>
<li>根据状态转移，画出状态转移图</li>
</ol>
</li>
<li>
<p>调度方案：从初始向量出发最后回到初始向量或者在新的向量循环</p>
<ol>
<li>根据状态转移图，写出调度方案以及对应的平均延迟</li>
<li>求最小平均延迟（单位：拍），以及最大吞吐率（1/最小平均延迟）（单位：任务/拍）</li>
</ol>
</li>
<li>
<p>流水时空图、实际吞吐率、效率</p>
<ol>
<li>根据预约表画出流水时空图</li>
<li>实际吞吐率：（看时间轴也就是x轴，时间/总时间）</li>
<li>效率：（面积/总面积）</li>
</ol>
</li>
</ol>
<p>示例：</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305251520207.png" alt="image-20230525152047035" tabindex="0" loading="lazy"><figcaption>image-20230525152047035</figcaption></figure>
<h2> 第六章 向量处理机</h2>
<p>@6-2 简答or选择</p>
<h3> CRAY-1向量流水处理</h3>
<p>解题思路：</p>
<ol>
<li>记
<ol>
<li>相加6拍</li>
<li>相乘7拍</li>
<li>求倒数（除）14拍</li>
<li>存储器读数6拍</li>
<li>打入寄存器及启动功能部件各一拍</li>
</ol>
</li>
<li>并行、链接、串行
<ol>
<li>并行：相邻各个指令之间没有联系（功能部件或指令没有相同）</li>
<li>链接：只要不出现功能部件和Vi冲突，有“先写后读”的向量指令可以采用链接方式执行</li>
<li>串行：出现功能部件或Vi冲突</li>
</ol>
</li>
<li>区别
<ol>
<li>链接和并行可以看做一个整体只需要加一次(N-1)；每一个串行的指令都需要加 (N-1)，N为向量长度；</li>
<li>并行指令可以同时执行，取最长时间（拍）</li>
</ol>
</li>
</ol>
<p>Vi冲突：并行工作的各向量指令，源向量或结果向量使用了相同的Vi</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>功能部件冲突：同一个功能部件被要求并行工作的多条指令所使用</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>链接方式：只要不出现功能部件和Vi冲突，有“先写后读”的向量指令可以采用链接方式执行</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305291342747.png" alt="image-20230529134211222" tabindex="0" loading="lazy"><figcaption>image-20230529134211222</figcaption></figure>
<p>6.3 可能有画图 10分</p>
<h3> ILLIAC IV 阵列机传递信号</h3>
<p>步骤：</p>
<ol>
<li>画出互联结构图</li>
<li>第一步看与他直接相连的处理器，剩下以此类推。</li>
</ol>
<p>示例</p>
<p>6-3 画出 16 台处理器仿 ILLIAC IV的模式进行互连的互连结构图，列出 PE。分别只经一步、二步和三步传送，就能将信息传送到的各处理器号。</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305311856096.png" alt="image-20230531185448451" tabindex="0" loading="lazy"><figcaption>image-20230531185448451</figcaption></figure>
<h3> 书p234 6-5 考</h3>
<h4> Cube 立方体置换</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202305291750559.png" alt="image-20230529175044088" tabindex="0" loading="lazy"><figcaption>image-20230529175044088</figcaption></figure>
<h4> PM2I 置换</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202305291805582.png" alt="image-20230529180523615" tabindex="0" loading="lazy"><figcaption>image-20230529180523615</figcaption></figure>
<h4> Shuffle 混洗交换单级网络</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202305291806965.png" alt="image-20230529180635428" tabindex="0" loading="lazy"><figcaption>image-20230529180635428</figcaption></figure>
<h2> 第7章 多处理机</h2>
<p>10分综合题 参考作业 7-7 7-8</p>
<h3> 并行性分析 FORK JOIN 时间关系图</h3>
<p>FORK JOIN：</p>
<ol>
<li>先画依赖图（）</li>
<li>根据依赖图写FORK JOIN</li>
</ol>
<p>时间关系图：</p>
<ol>
<li>看题目条件（几个处理机/CPU）根据FORK JOIN来画</li>
<li>FORK结束时，会在空闲CPU打开新的任务（第一个任务会在当前CPU执行，第二个会在空闲CPU上执行）</li>
<li>对比不同CPU处理任务的时间，时间短的会被优先释放</li>
</ol>
<h4> （1）数据相关</h4>
<p>如果Pi的左部变量在Pj的右部变量集内，且Pj数据相关于Pi，例：</p>
<p>​    Pi: A=B+D</p>
<p>​    Pj: C=A*E</p>
<p>Pi，Pj不能并行，也不能交换执行次序</p>
<h4> （2）数据反相关</h4>
<p>如果Pj的左部变量在Pi的右部变量集内，例：</p>
<p>​    Pi: C=A+B</p>
<p>​    Pj: A=D+E</p>
<p>方案：每个P的操作结果先暂存于自己的局部存储器中，不急于去修改原来存放在共享主存中单元的内容，要求局存向共享主存写同步。 Pi，Pj 可并行，不能交换串行</p>
<h4> （3）数据输出相关</h4>
<p>如果Pi的左部变量和Pj的左部变量相同，例：</p>
<p>​    Pi: C=A+B</p>
<p>​    Pj: C=D+E</p>
<p>只要保证Pi先写，Pj后写， Pi，Pj 即可并行</p>
<h3> 并行性程序设计语言</h3>
<p>派生：FORK 汇合：JOIN</p>
<p>FORK m：派生出标号为m开始的新进程。</p>
<ul>
<li>
<p>准备好新进程启动和执行的必须信息</p>
</li>
<li>
<p>将空闲处理机分配给派生的新进程，如没有空闲的处理机，则进程排队等待</p>
</li>
<li>
<p>继续在原处理机上执行FORK m语句的原进程</p>
</li>
</ul>
<p>JOIN n：</p>
<ul>
<li>
<p>附有计数器，初值为0</p>
</li>
<li>
<p>执行一次JOIN语句，计数器加1，并与n比较</p>
</li>
<li>
<p>如相等，则允许进程通过JOIN语句，计数器清0，进程继续执行。</p>
</li>
<li>
<p>若不等，则执行JOIN语句的进程结束，释放处理机。</p>
</li>
</ul>
<p>7.9也可以看看</p>
<h2> 结尾</h2>
<p>ps 例如6.3是参考PPT， 6-3则是书上题目</p>
<p><strong>我按照上课听的课随便写的，只有大题应该是全的，小题没咋讲，还是多看看PPT啥的，大题作业基本都是考试范围，可以去把作业做做</strong></p>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202305221720631.png" type="image/png"/>
    </item>
    <item>
      <title>编译原理</title>
      <link>https://v-blog.yyshino.top/posts/Computer/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/Computer-%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86.html</link>
      <guid>https://v-blog.yyshino.top/posts/Computer/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/Computer-%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">编译原理</source>
      <description>编译原理</description>
      <category>Computer</category>
      <pubDate>Thu, 11 May 2023 15:27:09 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 编译原理</h2>
<h2> 第二章 语法分析</h2>
<h3> 闭包正闭包</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051439678.png" alt="image-20230530130426745" tabindex="0" loading="lazy"><figcaption>image-20230530130426745</figcaption></figure>
<h3> 文法与语言</h3>
<p>文法：是描述语言的语法结构的形式规则</p>
<p>文法的概念</p>
<ol>
<li>
<p>非终结符：</p>
<ul>
<li>
<p>出现在规则的左部、用一括起来、表示一定语法概念的词。</p>
</li>
<li>
<p>非终结符集合用$V_N$表示。</p>
</li>
</ul>
</li>
<li>
<p>终结符</p>
<ul>
<li>语言中不可再分割的字符串(包括单个字符组成的串)。注：终结符是组成句子的基本单位。</li>
<li>终结符集合用$V_T$表示。</li>
</ul>
</li>
<li>
<p>开始符号</p>
<ul>
<li>表示所定义的语法范畴的非终结符。</li>
<li>注：开始符号又称为识别符号。</li>
</ul>
</li>
<li>
<p>产生式</p>
<ul>
<li>是用来定义符号串之间关系的一组(语法)规则。</li>
<li>形式：A→a （A产生α）</li>
</ul>
</li>
<li>
<p>推导</p>
<ul>
<li>推导是从开始符号开始，通过使用产生式的右部取代左部,最终能产生语言的一个句子的过程。</li>
<li>最左（右）推导：每次使用一个规则，以其右部取代符号串最左(右)非终结符。</li>
<li>注:最左推导和最右推导称为规范推导。</li>
</ul>
</li>
<li>
<p>归约</p>
<ul>
<li>归约是推导的逆过程，即，从给定的源语言的句子开始,通过规则的左部取代右部,最终达到开始符号的过程。</li>
<li>最左(右)归约是最右(左)推导的逆过程。</li>
<li>注：最左归约和最右归约称为规范归约。</li>
</ul>
</li>
<li>
<p>句型、句子和语言</p>
<p><img src="https://shinoimg.yyshino.top/img/202305301324321.png" alt="image-20230530132449857" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305301404477.png" alt="image-20230530140452684" loading="lazy"></p>
</li>
<li>
<p>文法规则的扩充</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305301453346.png" alt="image-20230530145309031" tabindex="0" loading="lazy"><figcaption>image-20230530145309031</figcaption></figure>
</li>
<li>
<p>元语言符号</p>
<ul>
<li>用来说明文法符号之间关系的符号,如, “→”和“|”称为元语言符号。</li>
</ul>
</li>
</ol>
<p>Chomsky对文法的分类</p>
<p>对产生式施加的限制不同可以分为</p>
<p>0型文法（α-&gt;β）（也被称短语文法，任何0型语言都是递归可枚举的，反之，递归可枚举集必定是0型语言）</p>
<p>1型文法（αAβ-&gt;αBβ）（上下文有关）</p>
<p>2型文法（A-&gt;α）（上下文无关文法）</p>
<p>3型文法（正规文法）</p>
<ul>
<li>A-&gt;Bα|α</li>
<li>A-&gt;αB|α</li>
</ul>
<p>包含关系：0 包含 1 包含 2 包含 3</p>
<p>正规文法</p>
<p>上下文无关</p>
<ul>
<li>产生式的左部一定是一个非终结符</li>
<li>产生式的右部可以是任意的字符串组合</li>
</ul>
<p>如果是正规文法那么一定是上下文无关文法</p>
<h3> 文法构造与文法简化</h3>
<p>文法构造</p>
<ul>
<li>观察法（找规律）</li>
</ul>
<p>文法简化</p>
<p>构造无ε产生式的上下无关文法</p>
<h3> 语法树(AST)与文法的二义性</h3>
<p>语法树</p>
<ol>
<li>先写出最左/右推导</li>
<li>由最左/右推导画出语法树</li>
</ol>
<h2> 第三章 词法分析</h2>
<h3> 正规文法和有限自动机</h3>
<h3> 正规文法</h3>
<p>正规文法：Chomsky 3型文法（左线型右线型）</p>
<p>正规集：</p>
<p>正规式：-设A是非空的有限字母表，A={a;l i=1,2,.....n},则</p>
<ol>
<li>$\epsilon$, $\empty$和$a_i(i=1,2,......n)$都是正规式。</li>
<li>若$\alpha$、β是正规式,则$\alpha | \beta$、$\alpha \cdot \beta$、$\alpha<sup>*$、$\beta</sup>*$也是正规式。</li>
<li>正规式只能通过有限次使用1, 2规则获得。</li>
</ol>
<p>注： 1)“|”读作为“或”，也可写作为“+”或“," ；”$\cdot$”读作连接。</p>
<p>2)仅由字母表$A=a_i(i=1,2,......n)$上的正规式α所组成的语言称作正规集,记作$ L(\alpha) $。</p>
<p>3)利用正规集相同，可用来证明相应正规式等价。</p>
<p>定理1：</p>
<p>若$ \alpha、\beta、\gamma $是正规式则下述等价式成立</p>
<ol>
<li>
<p>类式加法满足的性质：交换律结合律</p>
</li>
<li>
<p>类式乘法满足的性质：交换律结合律</p>
</li>
<li>
<p>$ \epsilon \alpha = \alpha \epsilon = \alpha$</p>
</li>
<li>
<p>$((\alpha)<sup>*)</sup>* = \alpha^*$</p>
</li>
<li>
<p>$ \alpha^* = \alpha^+ + \epsilon $</p>
</li>
<li>
<p>$ (\alpha + \beta)^* = (\alpha^* + \beta<sup>*)</sup>* = (\alpha^* \beta<sup>*)</sup>*$</p>
</li>
</ol>
<p>定理2：</p>
<p>若$\alpha、\beta、\gamma)$是字母表A上的正规式,且$\epsilon \notin L(\gamma)$,</p>
<p>则$\alpha = \beta | \alpha \gamma$当且仅$\alpha = \beta \gamma^*$</p>
<p>则$\alpha = \beta | \gamma \alpha $当且仅$\alpha = \gamma^* \beta $</p>
<p>正规文法转换成相应正规式</p>
<ol>
<li>由产生式写出对应的联立方程组（产生符号改为等号）</li>
<li>根据定理解方程组</li>
<li>进行代入工作，消除所要求的方程式中的非终结符，</li>
</ol>
<h3> 有限自动机</h3>
<p>有限自动机是一种识别装置，它能准确地识别正规集。它为词法分析程序的构造提供了方法和工具。</p>
<figure><img src="https://shinoimg.yyshino.top/img/202306031046227.png" alt="image-20230603104623548" tabindex="0" loading="lazy"><figcaption>image-20230603104623548</figcaption></figure>
<h4> 确定有限自动机DFA</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202306031148101.png" alt="image-20230603114811721" tabindex="0" loading="lazy"><figcaption>image-20230603114811721</figcaption></figure>
<p>注：这里确定的含义，就是状态转换关系f是一个函数，即对于给定的当前状态s和当前读入的符号a，有唯一确定的下一状态s’。</p>
<p>状态转换关系表示</p>
<ul>
<li>
<p>状态转换矩阵：DFA的映射关系由一个矩阵来完成</p>
</li>
<li>
<p>状态转换图</p>
</li>
</ul>
<p>注:</p>
<p>1)用矩阵表示转换便于计算机处理,但不直观,而用状态转换图表示比较直观。</p>
<p>2)在整个状态转换图中只有一个初始状态结点，用“→”射入的结点表示初始状态。可有若干终止状态(也可能没有),用双圆圈表示。若初始状态结点同时又是终止状态结点，则表示空串$\epsilon $可为相应DFA识别。</p>
<h5> 构造DFA</h5>
<p>根据语言描述构造DFA</p>
<ol>
<li>根据语言描述，画状态转换图</li>
<li>由状态转换图，写DFA</li>
</ol>
<p>示例</p>
<p><img src="https://shinoimg.yyshino.top/img/202306031209719.png" alt="image-20230603120905796" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202306031210537.png" alt="image-20230603121020149" loading="lazy"></p>
<h4> 不确定有限自动机NFA</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202306031218446.png" alt="image-20230603121826937" tabindex="0" loading="lazy"><figcaption>image-20230603121826937</figcaption></figure>
<p>注：</p>
<p>1)非确定主要是指后继状态可有多个。</p>
<ol start="2">
<li>DFA是NFA的特例。</li>
</ol>
<p>与DFA的主要区别在于：NFA的初始态是一个集合而不是唯一的$s_0$</p>
<p>（2）两自动机等价：</p>
<ul>
<li>任何两个有限自动机M和M'，若它们识别的语言相同(L(M)=L(M'))，则称M和M'等价。</li>
<li>注:存在判定任何两个有限自动机等价性的算法。</li>
</ul>
<h5> NFA确定化</h5>
<p>(1)定理</p>
<p>对于每个NFA M,存在一个DFA M',使得L(M)=L(M')。即，设L是一NFA接受的正规集,则存在一个DFA接受L。</p>
<p>步骤：</p>
<ol>
<li>先写转换表
<ol>
<li>由初态开始，对应字母表中每一个元素，取并集(∪)，得到相应的映射态</li>
<li>再对映射态进行第一步操作</li>
<li>重复1、2步，直到没有新的状态产生</li>
</ol>
</li>
<li>由转换表得DFA
<ol>
<li>DFA初态为NFA初态，DFA终态为包含NFA终态的所有态</li>
</ol>
</li>
</ol>
<h4> 确定有限自动机的化简</h4>
<h5> 划分法</h5>
<ol>
<li>将DFA M中的状态划分为互不相交的子集，每个子集内部的状态都等价;而在不同子集间的状态均不等价。</li>
<li>从每个子集中任选一个状态作为代表，消去其它等价状态。</li>
<li>把那些原来射入其它等价状态的弧改为射入相应的代表状态。</li>
</ol>
<h3> 正规表达式替换有限自动机的构造</h3>
<p>正规 -&gt; NFA 规则</p>
<figure><img src="https://shinoimg.yyshino.top/img/202306041051439.png" alt="image-20230604105144454" tabindex="0" loading="lazy"><figcaption>image-20230604105144454</figcaption></figure>
<h2> 自上而下语法分析</h2>
<p>4、语法分析的方式</p>
<p>1）自上而下语法分析</p>
<ul>
<li>反复使用不同产生式进行推导以谋求与输入符号串相匹配。</li>
</ul>
<p>2）自下而上语法分析</p>
<ul>
<li>对输入符号串寻找不同产生式进行归约直到文法开始符号。</li>
</ul>
<p>注：这里所说的输入符号指词法分析所识别的单词。</p>
<h3> FIRST集，FOLLOW集，SELECT集</h3>
<h4> FIRST集</h4>
<ol>
<li>看右侧第一个字符
<ul>
<li>如果是终结符，等于当前终结符</li>
<li>如果是非终结符，继续推导，</li>
</ul>
</li>
<li>有|（或）链接或者有多个，取并集</li>
</ol>
<h4> FOLLOW集</h4>
<p>Follow(A)为非终结符A后跟符号的集合,Follow(A)是所有句型中出现在紧接A之后的终结符或’#’</p>
<p>求解规则</p>
<p>（1）对于文法的开始符号S,置#于Follow（S）中；</p>
<p>（2）若A-&gt;αBβ是一个产生式，则把First(β) \ {ε} 加入到Follow（B）中</p>
<p>（3）若A-&gt;αB是一个产生式，或A-&gt;αBβ是一个产生式且β=&gt;ε，则把Follow(A)加入到Follow(B)中</p>
<p>理解求解规则</p>
<p>（1）对于开始符号，首先将#放入Follow集中</p>
<p>（2）形如A-&gt;αBβ</p>
<p>（α可以是终结符或者非终结符或者直接为空，β可以是终结符或者非终结符，</p>
<p>注意β不能为空，B后面要有东西，
注意β不能为空，B后面要有东西，
注意β不能为空，B后面要有东西）</p>
<h4> SELECT集</h4>
<h3> LL(1)文法</h3>
<p>要证明此文法是不是LL(1)文法,就看各组左部相同的SELECT集的交集是否为空,若都为空,则是LL(1)文法</p>
<p>LL(1)分析表</p>
<ol>
<li>写出SELECT集</li>
<li>根据文法写出分析表第一行、第一列
<ol>
<li>第一行为终结符，从上至下依次填入</li>
<li>第一列为非终结符，从上至下依次填入</li>
</ol>
</li>
<li>根据SELECT集...</li>
</ol>
<h3> LR(0)</h3>
<h4> LR(0)项目集</h4>
<p>相关概念</p>
<p>LR(0)项目：右部标有 $\cdot $ 的产生式 =&gt; 称为一个项目</p>
<ul>
<li>如果 $ \cdot $ 在<mark>非终结符前</mark>，则称为一个特殊的项目-<mark>移进项目</mark>。如$A \rightarrow \cdot aBD$</li>
<li>如果 $ \cdot $ 在<mark>终结符前</mark>，则称为一个特殊的项目-<mark>待约项目</mark>。如$A \rightarrow a \cdot BD$</li>
<li>如果 $ \cdot $ 在最右侧，则称为一个特殊的项目-<mark>归约项目</mark>。如$A \rightarrow aBD \cdot$</li>
</ul>
<p>增广文法</p>
<p>如果G 是一个以S为开始符号的文法，则G的增广文法 G'就是在G中加上新开始符号S'和产生式S'→S而得到的文法</p>
<ul>
<li>引入这个新的开始产生式的目的是使得文法开始符号仅出现在一个产生式的左边，从而使得分析器只有一个接受状态</li>
</ul>
<p>示例</p>
<div class="language-css line-numbers-mode" data-ext="css"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>LR(0)自动机 DFA</p>
<ol>
<li>拓广文法（S'和分解）</li>
<li>由$I_0$开始</li>
</ol>
<h4> LR(0)分析表</h4>
<ul>
<li>第一行I下填写下标数字、Action下填写终结符、Goto下填写非终结符</li>
<li>根据LR(0)自动机图填写
<ul>
<li>$I_n$里含有归约项目，则在第n行填写归约项目对应的拓广文法序号$r_m$</li>
<li>$I_n$里移进项目或待归约项目指向$I_m$，看它识别的是终结符还是非终结符（也就是LR(0)自动机的箭头上对应的字符）
<ul>
<li>终结符：在第n行，Action下的相应终结符列，填写$s_m$</li>
<li>非终结符：在第n行，Goto下的相应非终结符列，填写$m$</li>
</ul>
</li>
<li>出现移进归约冲突时（），计算文法的Goto下非终结符FOLLOW集，看对应的终结符是否属于是否属于当前集合，不属于则删除</li>
</ul>
</li>
</ul>
<h3> SLR(1)</h3>
<ul>
<li>SLR(1)遇到终结符属于FOLLOW集的才采取归约</li>
<li>LR(0)是所有终结符都采取归约</li>
</ul>
<p>因此，只需要在LR(0)的基础上计算FOLLOW集，然后删除Action下不属于FOLLOW集（箭头右侧的非终结符的FOLLOW集）的归约项（r开头的）</p>
<p>s不受影响</p>
<h3> LR(1)分析表</h3>
<p>拓广文法—项目规范族（<mark>带向前搜索符</mark>）——LR(1)分析表</p>
<p>如何写向前搜索符？</p>
<ol>
<li>文法开始符Follow集</li>
<li>”先看前再看后“
<ol>
<li>$A \rightarrow \alpha \cdot \beta,a $
<ol>
<li>$\beta$ 为空，照抄</li>
<li>$\beta$ 不为空$First(\beta)$</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>LALR(1)</p>
<p>拓广文法—项目规范族（在带向前搜索符的基础上<mark>合并同心集</mark>）——LR(1)分析表</p>
<p>如何区别这四种文法</p>
<figure><img src="https://shinoimg.yyshino.top/img/202306052039922.png" alt="image-20230605203917556" tabindex="0" loading="lazy"><figcaption>image-20230605203917556</figcaption></figure>
<p>实践步骤：</p>
<p>根据DFA图判断</p>
<ol>
<li>看<mark>是否存在冲突项目</mark>（移进归约|归约归约）
<ul>
<li>不存在=&gt;LR(0)文法</li>
<li>存在（不是LR(0)文法）无法构表</li>
</ul>
</li>
<li>存在冲突项目，根据<mark>FOLLOW集</mark>来判断
<ul>
<li>$= \empty $是SLR(1)文法</li>
<li>$\neq \empty$无法构表</li>
</ul>
</li>
<li>存在冲突项目，根据<mark>向前搜索符</mark>来判断
<ul>
<li>$= \empty $是LR(1)文法</li>
<li>$\neq \empty$无法构表</li>
</ul>
</li>
<li>如果构建的带向前搜索符的项目集规范族存在冲突项目（规约-规约），那就不是LR(1)，也不是LALR (1)。</li>
</ol>
<p>根据分析表判断</p>
<ol>
<li>LR(0)的$R_n$占整行(Action行)</li>
<li>SLR(1)根据FOLLOW集填写$R_n$</li>
<li>LR(1)不同行可能出现相同的$R_n$状态</li>
</ol>
<h2> 参考</h2>
<div class="language-url line-numbers-mode" data-ext="url"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202401051439678.png" type="image/png"/>
    </item>
    <item>
      <title>编译原理作业题汇总</title>
      <link>https://v-blog.yyshino.top/posts/Computer/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/%E4%BD%9C%E4%B8%9A%E9%A2%98%E6%B1%87%E6%80%BB.html</link>
      <guid>https://v-blog.yyshino.top/posts/Computer/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/%E4%BD%9C%E4%B8%9A%E9%A2%98%E6%B1%87%E6%80%BB.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">编译原理作业题汇总</source>
      <description>编译原理作业题汇总</description>
      <category>Computer</category>
      <pubDate>Thu, 11 May 2023 15:27:09 GMT</pubDate>
      <content:encoded><![CDATA[<p>作业</p>
<h3> 作业一二三</h3>
<p>构造文法：</p>
<p>观察法（找规律）</p>
<figure><img src="https://shinoimg.yyshino.top/img/202401051440382.png" alt="image-20230530103507976" tabindex="0" loading="lazy"><figcaption>image-20230530103507976</figcaption></figure>
<h3> 作业四</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051442402.png" alt="image-20230530103714151" tabindex="0" loading="lazy"><figcaption>image-20230530103714151</figcaption></figure>
<h3> 作业五</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051441371.png" alt="image-20230530103912363" tabindex="0" loading="lazy"><figcaption>image-20230530103912363</figcaption></figure>
<h3> 作业六</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051441870.png" alt="image-20230530103928358" tabindex="0" loading="lazy"><figcaption>image-20230530103928358</figcaption></figure>
<h3> 作业七</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051441055.png" alt="image-20230530104050202" tabindex="0" loading="lazy"><figcaption>image-20230530104050202</figcaption></figure>
<h3> 作业八</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051442202.png" alt="image-20230530104150223" tabindex="0" loading="lazy"><figcaption>image-20230530104150223</figcaption></figure>
<h3> 作业九</h3>
<p>请把文法的定义和自动机的定义抄写一边。</p>
<h3> 作业十</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051442606.png" alt="image-20230530104224912" tabindex="0" loading="lazy"><figcaption>image-20230530104224912</figcaption></figure>
<h3> 作业十一</h3>
<p>请把FIRST集合的定义抄写三遍，并理解记忆。</p>
<h3> 作业十二</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051441373.png" alt="image-20230530104240471" tabindex="0" loading="lazy"><figcaption>image-20230530104240471</figcaption></figure>
<h4> 还有这个题目，请写出FIRST和FOLLOW集</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202401051441997.png" alt="image-20230530104308533" tabindex="0" loading="lazy"><figcaption>image-20230530104308533</figcaption></figure>
<h3> 作业十三</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051441671.png" alt="image-20230530104357107" tabindex="0" loading="lazy"><figcaption>image-20230530104357107</figcaption></figure>
<h3> 作业十四</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051442340.png" alt="image-20230530104421677" tabindex="0" loading="lazy"><figcaption>image-20230530104421677</figcaption></figure>
<h3> 作业十五</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051442835.png" alt="image-20230530104500146" tabindex="0" loading="lazy"><figcaption>image-20230530104500146</figcaption></figure>
<h3> 作业十六</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051442831.png" alt="image-20230530104533635" tabindex="0" loading="lazy"><figcaption>image-20230530104533635</figcaption></figure>
<h3> 作业十七</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051442847.png" alt="image-20230530104558416" tabindex="0" loading="lazy"><figcaption>image-20230530104558416</figcaption></figure>
<h3> 作业十八</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202401051442662.png" alt="image-20230530104613152" tabindex="0" loading="lazy"><figcaption>image-20230530104613152</figcaption></figure>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202401051440382.png" type="image/png"/>
    </item>
    <item>
      <title>选填简答</title>
      <link>https://v-blog.yyshino.top/posts/Computer/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/%E9%80%89%E5%A1%AB%E7%AE%80%E7%AD%94.html</link>
      <guid>https://v-blog.yyshino.top/posts/Computer/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/%E9%80%89%E5%A1%AB%E7%AE%80%E7%AD%94.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">选填简答</source>
      <description>选填简答</description>
      <category>Computer</category>
      <pubDate>Thu, 11 May 2023 15:27:09 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 选填</h2>
<h3> 文法分类</h3>
<p>0型文法（α-&gt;β）（也被称短语文法，任何0型语言都是递归可枚举的，反之，递归可枚举集必定是0型语言）</p>
<p>1型文法（αAβ-&gt;αBβ）（上下文有关）</p>
<p>2型文法（A-&gt;α）（上下文无关文法）</p>
<p>3型文法（正规文法）</p>
<h2> 简答</h2>
<h3> 符号表定义和作用</h3>
<p>符号表定义：符号表是存储语义信息的重要数据结构,是用来存放语言程序中出现的有关标记符的属性信息,这些信息集中反应标识符的语义特征属性。</p>
<p>符号表作用：</p>
<ol>
<li>符号表自创建后便开始被用于收集符号的属性信息,不同阶段会有不同的信息。</li>
<li>在语义分析中，符号表所登记的内容是进行上下文语义合法性检查的依据。</li>
<li>在目标代码生成阶段,符号表是对符号名进行地址分配的依据。</li>
</ol>
<h3> 什么是代码优化技术？常用的代码优化技术？</h3>
<p>定义：对代码进行等价变换，使得变换后的代码具有更高的时间效率和空间效率。</p>
<p>常用：</p>
<ul>
<li>删除多余运算;</li>
<li>常量合并;</li>
<li>常量传播;</li>
<li>代数化简;</li>
<li>控制流优化;</li>
<li>删除无用赋值;</li>
<li>强度削弱;</li>
<li>使用目标机惯用指令</li>
</ul>
<h3> 目标代码的形式</h3>
<p>①可立即执行的机器语言代码；</p>
<p>②汇编语言代码；</p>
<p>③待装配的机器语言代码</p>
<h3> 什么是语法制导翻译和属性文法，它们的关系如何？</h3>
]]></content:encoded>
    </item>
    <item>
      <title>计算机体系结构选填简答</title>
      <link>https://v-blog.yyshino.top/posts/Computer/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/%E9%80%89%E5%A1%AB%E7%AE%80%E7%AD%94.html</link>
      <guid>https://v-blog.yyshino.top/posts/Computer/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/%E9%80%89%E5%A1%AB%E7%AE%80%E7%AD%94.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">计算机体系结构选填简答</source>
      <description>计算机体系结构选填简答</description>
      <category>Computer</category>
      <pubDate>Thu, 11 May 2023 15:27:09 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 选填</h2>
<p>从下到上</p>
<p>微程序机器</p>
<p>传统</p>
<p>汇编</p>
<h3> 神威太湖之光 选择</h3>
<p>2017年6月19日，全球超级计算机500强榜单公布，<mark>中国国家超级计算无锡中心的“神威·太湖之光”和国家超算广州中心的“天河二号”第三次携手夺得冠亚军</mark> ，美国“泰坦”20多年来首次跌出前三｡</p>
<p>2022年超算“前沿”运算峰值速度超过每秒100亿亿次（1102.00Pflop/s）。</p>
<p><strong>不需要任何替换算法的地址映像？直接相联 选择题</strong></p>
<p>Flynn按照指令和数据流不同的组织方式，计算机系统可分为四类</p>
<ul>
<li>单指令流单数据流机器（SISD）</li>
<li>单指令流多数据流机器（SIMD）</li>
<li>多指令流单数据流机器（MISD）</li>
<li>多指令流多数据流机器（MIMD）</li>
</ul>
<p>采用比较对法来实现Cache替换策略时,如果组内块数为3,则需要多少个与门，多少个（）触发器来实现。</p>
<p>触发器：块数(块数-1)/2</p>
<p>与门：每一组需要的与门数目为：P-1=3，P=4</p>
<p>3,3</p>
<h2> 简答</h2>
<h3> 什么叫cache的一致性问题</h3>
<p>一般情况下，Cache中存放的是主存的部分副本，因此，Cache块应该与相应主存块的内容保持一致。但是在某些情况下，Cache块与相应主存块的内容会不相同，也就是产生了Cache的一致性问题。</p>
<h3> 发展并行性的三种途径：</h3>
<ol>
<li><strong>时间重叠：</strong>（Time Interleaving）是在并行性概念中引入时间因素，让两个或多个任务在时间上相互错开，轮流使用同一套设备的各个组成部分，以加快硬件周转而赢得速度。</li>
<li><strong>资源重复：</strong>（Resource Replication）是在并行性概念中引入空间因素，通过重复设置硬件资源来提高可靠性或性能。</li>
<li><strong>资源共享：</strong>（Resource Sharing）是利用软件的方法让多个任务按一定时间顺序轮流使用同一套设备，以提高设备利用率和系统性能。</li>
</ol>
<h3> 看一下时间，空间局部性</h3>
<ul>
<li>时间局部性：程序中近期被访问的信息项很可能马上将被再次访问。</li>
<li>空间局部性：指那些在访问地址上相邻近的信息项很可能会被一起访问。</li>
</ul>
<h3> 引入数据表的原则2个，简答题（2.1.3）</h3>
<ol>
<li><mark>是否有利于提高系统效率，是否减少了实现时间和存储空间</mark>；</li>
<li>看引入数据表示后，<mark>其通用性和利用率是否高</mark>；</li>
</ol>
<h3> risi有哪些技术，ppt四个标题（重叠窗口技术）</h3>
<ul>
<li><strong>重叠寄存器窗口</strong>技术</li>
<li><strong>延迟转移</strong>（Delayed Branch）</li>
<li><strong>比较转移指令</strong></li>
<li><strong>优化编译</strong></li>
</ul>
<p>Flynn按照指令和数据流不同的组织方式，计算机系统可分为四类</p>
<ul>
<li>单指令流单数据流机器（SISD）</li>
<li>单指令流多数据流机器（SIMD）</li>
<li>多指令流单数据流机器（MISD）</li>
<li>多指令流多数据流机器（MIMD）</li>
</ul>
<p>分别引入Cache和虚拟存储器的原因</p>
<ul>
<li>Cache：为了弥补主存和CPU之间速度不匹配</li>
<li>虚拟存储器：解决主存容量不能满足程序运行的需要</li>
</ul>
<h3> 什么叫做流水线的瓶颈段，怎么解决瓶颈端，简答</h3>
<p>定义：流水线各段执行时间不相等</p>
<p>解决：</p>
<ol>
<li>细分瓶颈段</li>
<li>重复设置瓶颈段（增加分配器和收集器）（二是将瓶颈子过程多套并联）</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>Linux</title>
      <link>https://v-blog.yyshino.top/posts/Computer/Linux/Computer-Linux.html</link>
      <guid>https://v-blog.yyshino.top/posts/Computer/Linux/Computer-Linux.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Linux</source>
      <description>Linux</description>
      <category>Computer</category>
      <pubDate>Tue, 09 May 2023 22:56:20 GMT</pubDate>
      <content:encoded><![CDATA[<h2> Linux</h2>
<h2> 一 Linux基本命令</h2>
<h3> 网络测试</h3>
<p>ping</p>
<p>ipconfig</p>
<h3> 帮助命令</h3>
<p><code>--help</code></p>
<p><code>man</code>命令</p>
<p><code>info</code>命令</p>
<h2> 三 Linux系统 统计 查询</h2>
<h3> grep命令</h3>
<p>Linux grep (global regular expression) 命令用于<strong>查找文件里符合条件的字符串或正则表达式。</strong></p>
<p>grep 指令用于查找内容包含指定的范本样式的文件，如果发现某文件的内容符合所指定的范本样式，预设 grep 指令会把含有范本样式的那一列显示出来。若不指定任何文件名称，或是所给予的文件名为 <strong>-</strong>，则 grep 指令会从标准输入设备读取数据。</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> find命令</h3>
<p>Linux find 命令用于在指定目录下查找文件和目录。</p>
<p>它可以使用不同的选项来过滤和限制查找的结果。</p>
<p><code>find -name 'main*'</code></p>
<p><code>find -name practice -print</code></p>
<p><code>find ~ -atime 0</code></p>
<p><code>find -size 16</code></p>
<p><code>find -type c</code></p>
<h3> wc命令</h3>
<p>Linux wc命令用于计算字数。</p>
<p>利用wc指令我们可以计算文件的Byte数、字数、或是列数，若不指定文件名称、或是所给予的文件名为"-"，则wc指令会从标准输入设备读取数据。</p>
<h3> sort命令</h3>
<p>Linux sort 命令用于将文本文件内容加以排序。</p>
<p>sort 可针对文本文件的内容，以行为单位来排序。</p>
<h2> 四 Linux vi编辑器的使用</h2>
<p>所有的 Unix Like 系统都会内建 vi 文书编辑器，其他的文书编辑器则不一定会存在。</p>
<p>但是目前我们使用比较多的是 vim 编辑器。</p>
<p>vim 具有程序编辑的能力，可以主动的以字体颜色辨别语法的正确性，方便程序设计。</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305092313708.gif" alt="img" tabindex="0" loading="lazy"><figcaption>img</figcaption></figure>
<h3> vi [文件名]</h3>
<p>命令模式（Command mode）：输入字符都作为命令来解释执行。不显示输入内容。</p>
<p>输入模式（Insert mode）：输入的任何字符都将作为文件内容被保存，并显示在屏幕上。</p>
<p>末行模式（Last line mode）：在屏幕最末行接收行命令输入，并执行。</p>
<p>练习3种工模间的切换</p>
<h3> 退出相关命令</h3>
<p>三个退出命令的区别</p>
<p><code>:q</code></p>
<p>如果文件有修改，:q 会提示有修改，是否退出，输入y退出</p>
<p><code>:wq</code></p>
<p>:wq 如果文件设置为只读了的话，用 :wq命令是不能保存并退出的</p>
<p><code>:q!</code></p>
<p>若曾修改过档案，又不想储存，使用 ! 为强制离开不储存档案。</p>
<h3> 一般模式</h3>
<p>可用的光标移动、复制粘贴、搜索替换等</p>
<h3> 增删查改</h3>
<h4> 插入/删除命令（iao,dd dw x)</h4>
<table>
<thead>
<tr>
<th>命令</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>dd</td>
<td>剪切游标所在的那一整行(常用)，用 p/P 可以粘贴。</td>
</tr>
<tr>
<td>dw</td>
<td></td>
</tr>
<tr>
<td>iao</td>
<td></td>
</tr>
<tr>
<td>x</td>
<td>在一行字当中，x 为向后删除一个字符 (相当于 [del] 按键)， X 为向前删除一个字符(相当于 [backspace] 亦即是退格键) (常用)</td>
</tr>
</tbody>
</table>
<h4> 查询</h4>
<h4> 修改/替换命令</h4>
<table>
<thead>
<tr>
<th>命令</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>/word</td>
<td>向光标之下寻找一个名称为 word 的字符串。例如要在档案内搜寻 vbird 这个字符串，就输入 /vbird 即可！ (常用)</td>
</tr>
<tr>
<td>?word</td>
<td>向光标之上寻找一个字符串名称为 word 的字符串。</td>
</tr>
<tr>
<td>n</td>
<td>这个 n 是英文按键。代表重复前一个搜寻的动作。举例来说， 如果刚刚我们执行 /vbird 去向下搜寻 vbird 这个字符串，则按下 n 后，会向下继续搜寻下一个名称为 vbird 的字符串。如果是执行 ?vbird 的话，那么按下 n 则会向上继续搜寻名称为 vbird 的字符串！</td>
</tr>
<tr>
<td>N</td>
<td>这个 N 是英文按键。与 n 刚好相反，为『反向』进行前一个搜寻动作。 例如 /vbird 后，按下 N 则表示『向上』搜寻 vbird 。</td>
</tr>
</tbody>
</table>
<p>示例</p>
<table>
<thead>
<tr>
<th>示例命令</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>:n1,n2s/word1/word2/g</td>
<td>n1 与 n2 为数字。在第 n1 与 n2 行之间寻找 word1 这个字符串，并将该字符串取代为 word2 ！举例来说，在 100 到 200 行之间搜寻 vbird 并取代为 VBIRD 则： 『:100,200s/vbird/VBIRD/g』。(常用)</td>
</tr>
<tr>
<td><strong>:1,$s/word1/word2/g</strong> 或 <strong>:%s/word1/word2/g</strong></td>
<td>从第一行到最后一行寻找 word1 字符串，并将该字符串取代为 word2 ！(常用)</td>
</tr>
<tr>
<td><strong>:1,$s/word1/word2/gc</strong> 或 <strong>:%s/word1/word2/gc</strong></td>
<td>从第一行到最后一行寻找 word1 字符串，并将该字符串取代为 word2 ！且在取代前显示提示字符给用户确认 (confirm) 是否需要取代！(常用)</td>
</tr>
</tbody>
</table>
<h3> 复制撤销</h3>
<h4> 拷贝/粘贴命令| 撤销/重做命令</h4>
<table>
<thead>
<tr>
<th>命令</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>yy</td>
<td>复制游标所在的那一行(常用)</td>
</tr>
<tr>
<td>nyy</td>
<td>n 为数字。复制光标所在的向下 n 行，例如 20yy 则是复制 20 行(常用)</td>
</tr>
<tr>
<td>y1G</td>
<td>复制游标所在行到第一行的所有数据</td>
</tr>
<tr>
<td>yG</td>
<td>复制游标所在行到最后一行的所有数据</td>
</tr>
<tr>
<td>y0</td>
<td>复制光标所在的那个字符到该行行首的所有数据</td>
</tr>
<tr>
<td>y$</td>
<td>复制光标所在的那个字符到该行行尾的所有数据</td>
</tr>
<tr>
<td>p, P</td>
<td>p 为将已复制的数据在光标下一行贴上，P 则为贴在游标上一行！ 举例来说，我目前光标在第 20 行，且已经复制了 10 行数据。则按下 p 后， 那 10 行数据会贴在原本的 20 行之后，亦即由 21 行开始贴。但如果是按下 P 呢？ 那么原本的第 20 行会被推到变成 30 行。 (常用)</td>
</tr>
<tr>
<td>J</td>
<td>将光标所在行与下一行的数据结合成同一行</td>
</tr>
<tr>
<td>c</td>
<td>重复删除多个数据，例如向下删除 10 行，[ 10cj ]</td>
</tr>
<tr>
<td>u</td>
<td>复原前一个动作。(常用)</td>
</tr>
<tr>
<td>[Ctrl]+r</td>
<td>重做上一个动作。(常用)</td>
</tr>
<tr>
<td></td>
<td>这个 u 与 [Ctrl]+r 是很常用的指令！一个是复原，另一个则是重做一次～ 利用这两个功能按键，你的编辑，嘿嘿！很快乐的啦！</td>
</tr>
<tr>
<td>.</td>
<td>不要怀疑！这就是小数点！意思是重复前一个动作的意思。 如果你想要重复删除、重复贴上等等动作，按下小数点『.』就好了！ (常用)</td>
</tr>
</tbody>
</table>
<h3> sed命令</h3>
<p>Linux sed 命令是利用脚本来处理文本文件。</p>
<p>sed 可依照脚本的指令来处理、编辑文本文件。</p>
<p>Sed 主要用来自动编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。</p>
<p>语法</p>
<div class="language-linux line-numbers-mode" data-ext="linux"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p><a href="https://www.runoob.com/linux/linux-comm-sed.html" target="_blank" rel="noopener noreferrer">菜鸟教程sed命令</a></p>
<h3> awk命令</h3>
<p>AWK 是一种处理文本文件的语言，是一个强大的文本分析工具。</p>
<p>之所以叫 AWK 是因为其取了三位创始人 Alfred Aho，Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符。</p>
<p>语法</p>
<div class="language-linux line-numbers-mode" data-ext="linux"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><a href="https://www.runoob.com/linux/linux-comm-awk.html" target="_blank" rel="noopener noreferrer">菜鸟教程awk命令</a></p>
<h2> 五 Linux用户组管理</h2>
<ol>
<li>查看用户账号文件―&gt;/etc/passwd，说明其一行的格式及各域作用</li>
<li>查看影子文件―&gt;/etc/shadow，说明其中一行的格式及各域作用</li>
<li>查看组文件―&gt;/etc/group，说明其中一行的格式及各域作用</li>
</ol>
<h3> 用户相关命令练习</h3>
<ol>
<li>练习添加新用户命令：useradd；</li>
<li>练习设置用户口令命令：passwd；</li>
<li>练习切换用户身份命令：su；</li>
<li>练习删除用户命令：userdel；</li>
<li>练习修改用户信息命令：usermod；</li>
</ol>
<h3> 组相关命令练习</h3>
<ol>
<li>练习添加组命令：groupadd；</li>
<li>练习删除组命令：groupdel；</li>
<li>练习修改组信息命令：groupmod；</li>
<li>添加/删除组成员：gpasswd；</li>
</ol>
<h3> 文件相关命令练习</h3>
<ol>
<li>修改权限命令：chmod</li>
<li>改变文件拥有者：chown；</li>
<li>更改文件所属的组：chgrp；</li>
</ol>
<h3> 用户组管理</h3>
<ol>
<li>建立一个标准的组group1，GID=900；</li>
<li>建立一个标准组group2，选项为默认，观察该组的信息有什么变化；</li>
<li>新建用户ah、xh，再新建一个组group3，把root、u1、user2用户添加到group1组中，把ah、xh添加到group2组，</li>
<li>把group3组改名为g3，GID=1000；</li>
<li>查看user2所属于的组，并记录；</li>
<li>删除user1组与g3组，观察有什么情况发生；</li>
</ol>
<h2> 六 Linux常用软件安装过程</h2>
<h3> whereis&amp;which</h3>
<p>whereis: 可以帮助快速的找到某个命令的二进制文件、帮助页面、源码所在路径。</p>
<p>which: 也是可以显示命令的二进制文件的路,shows the full path of (shell) commands.</p>
<h3> systemctl &amp;service (sv==servicename)</h3>
<p>添加开机启动 systemctl enable sv</p>
<p>禁止开机启动 systemctl disable sv</p>
<p>查看服务文件内容 systemctl cat sv</p>
<p>------------------------</p>
<p>立即启动服务 systemctl start sv service sv start</p>
<p>立即停止服务 systemctl stop sv service sv stop</p>
<p>重新启动服务 systemctl restart sv service sv restart</p>
<p>重新加载服务配置 systemctl reload sv service sv reload</p>
<p>查看服务状态 systemctl status sv service sv status</p>
<h3> 查看端口占用</h3>
<p>netstat -anp |grep :80 ,不存在 netstat 可 yum install net-tools</p>
<p>或 ss -anp | grep :80</p>
<h3> 防火墙相关</h3>
<p>开放端口 firewall-cmd --permanent --zone=public --add-port=80/tcp</p>
<p>开放多个端口 firewall-cmd --permanent --zone=public --add-port=8080-8083/tcp</p>
<p>关闭某个端口 firewall-cmd --permanent --zone=public --remove-port=81/tcp</p>
<p>修改后重载配置 firewall-cmd --reload</p>
<p>列出开放的端口 firewall-cmd --list-ports</p>
<h3> yum相关命令</h3>
<p>设置yum源 : 参考阿里 或华为镜像中说明</p>
<p>安装 yum install softwarename</p>
<p>卸载 yum remove softwarename</p>
<p>列出所有 yum list package</p>
<p>列出已有 yum list installed</p>
<p>查找 yum serach jdk</p>
<h3> tar 压缩/解压缩</h3>
<p>对应 .tar 文件：</p>
<p>解包：tar xvf FileName.tar</p>
<p>打包：tar cvf FileName.tar DirName</p>
<p>.tar.gz 和 .tgz文件：</p>
<p>解压：tar -zxvf FileName.tar.gz</p>
<p>压缩：tar -zcvf FileName.tar.gz DirName</p>
<h3> 任务</h3>
<p>安装apache</p>
<p>安装python3</p>
<p>安装jdk8</p>
<p>安装tomcat</p>
<p>安装nginx</p>
<p>安装MySQL</p>
<h2> 八 Linux 进程管理与定时任务</h2>
<h3> 静态显示系统进程信息</h3>
<h4> ps命令</h4>
<p>inux ps （英文全拼：process status）命令用于显示当前进程的状态，类似于 windows 的任务管理器。</p>
<p>语法</p>
<div class="language-linux line-numbers-mode" data-ext="linux"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h3> 动态显示系统进程信息</h3>
<h4> top命令</h4>
<p>使用top命令，实时显示系统各个进程的资源占用情况。说明输出信息中列的含意。</p>
<p>在top基本视图中，按h键进入另一个视图。</p>
<p>在top基本视图中，按f键进入另一个视图，在这里可以编辑基本视图中的显示字段。</p>
<p>在top基本视图中，按c键进入另一个视图,可以显示进程的路径。</p>
<p>在top基本视图中，按k键，可以在不退出top命令的情况下杀死某个正在运行的进程。</p>
<p>在top基本视图中，按b键，高亮显示当前正在运行的进程。</p>
<h3> 其他命令练习</h3>
<ol>
<li>Kill</li>
<li>killall</li>
<li>jobs</li>
<li>fg</li>
<li>bg</li>
<li>nice</li>
<li>renice</li>
</ol>
<h3> 定时任务练习</h3>
<h4> 定时任务1</h4>
<p>利用at设置一个任务自动化，在当天11：00钟，在根目录下自动创建一个abc目录，并进入到abc目录中，建立一个空的文件test，同时对该文件进行打包成test.tar;</p>
<h4> 定时任务2</h4>
<p>Cron应用：每周2,4,6早上3点重新启动系统</p>
<p>题目6 –
请回答如下问题：</p>
<ol>
<li>
<p>什么是进程？
一般来讲，进程定义为正在运行的程序的实例，简单地说，进程就是一个正在运行的程序。程序被触发后，运行者的权限与属性、程序的程序码与所需数据等都会被加载内存中， 操作系统并给予这个内存内的单元一个识别码 (PID)，可以说，进程就是一个正在运行中的程序。</p>
</li>
<li>
<p>进程与程序的区别？
程序：通常为二进制，放置在储存媒体中 (如硬盘、光盘、软盘、磁带等)， 为实体文件的型态存在；
进程：程序被触发后，运行者的权限与属性、程序的程序码与所需数据等都会被加载内存中， 操作系统并给予这个内存内的单元一个识别码 (PID)，可以说，进程就是一个正在运行中的程序。</p>
</li>
<li>
<p>进程的三种状态？
运行态：进程占有CPU且正在运行；
就绪态：进程具备运行状态，等待系统分配CPU以便运行；
等待态：又叫阻塞态或睡眠态，指进程不具备运行条件，正在等待某个事件的完成；</p>
</li>
<li>
<p>如何后台启动进程</p>
</li>
</ol>
<ol>
<li>
<p>Nohup</p>
</li>
<li>
<p>Setsid</p>
</li>
<li>
<p>Subshell</p>
</li>
</ol>
<h2> 九 Linux 下 GCC 的使用</h2>
<h3> gcc</h3>
<p>-o：指定生成文件的名称</p>
<p>-E：激活预处理。生成预处理文件（ .i 文件）</p>
<p>-S：激活预处理、编译。生成汇编代码（ .s 文件）</p>
<p>-c：激活预处理、编译、汇编。生成目标文件（ .o 文件）</p>
<p>-I：指定头文件目录</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305092351782.png" alt="image-20230509235123641" tabindex="0" loading="lazy"><figcaption>image-20230509235123641</figcaption></figure>
<h3> 预处理（编译）</h3>
<p>使编译器将 C 源代码中的包含的头文件如stdio.h编译进来，替换宏(如符号常量)。</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><h3> 编译</h3>
<p>GCC 首先要检查代码的规范性、是否有语法错误等，以确定代码的实际要做的工作，在检查无误后，GCC 把代码翻译成汇编语言。</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><h3> 汇编</h3>
<p>把编译阶段生成的 ”.s” 文件转成二进制目标代码。</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><h3> 链接</h3>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 一次性完成</h3>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 多个文件编译</h3>
<p>现在有 3 个文件，分别是 main.c，print.c，print.h</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果头文件和源文件不在一同目录中，如何解决？</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> makefile</h3>
<p>在使用gcc命令时，一个工程又有多个文件（比如100个文件），如果按照上述编译方法，往往需要输入很多指令，而且修改文件也不方便，因此引入makefile文件解决该问题。</p>
<h3> 父子进程</h3>
<p>使用fork创建子进程，功能是：
父进程输出main,子进程输出child
使用gcc编译，并运行
注意事项
查看一个函数功能的方法：man fork
需要包含的头文件：#include &lt;unistd.h&gt;
fork返回值
成功时，父进程返回子进程的进程ID号；子进程返回0。
失败时，父进程返回-1，子进程创建失败。</p>
<h2> 十 LInux Shell程序设计</h2>
<h3> 变量</h3>
<h4> 定义变量</h4>
<p>定义变量时，变量名不加美元符号（$，PHP语言中变量需要），如：</p>
<p>your_name="runoob.com"</p>
<p>注意，变量名和等号之间不能有空格，这可能和你熟悉的所有编程语言都不一样。同时，变量名的命名须遵循如下规则：</p>
<ul>
<li>命名只能使用英文字母，数字和下划线，首个字符不能以数字开头。</li>
<li>中间不能有空格，可以使用下划线 <strong>_</strong>。</li>
<li>不能使用标点符号。</li>
<li>不能使用bash里的关键字（可用help命令查看保留关键字）。</li>
</ul>
<h4> 使用变量</h4>
<p>使用一个定义过的变量，只要在变量名前面加美元符号即可</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>变量名外面的花括号是可选的，加不加都行，加花括号是为了帮助解释器识别变量的边界，比如下面这种情况：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 只读变量</h4>
<p>使用 readonly 命令可以将变量定义为只读变量，只读变量的值不能被改变。</p>
<p>下面的例子尝试更改只读变量，结果报错：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 删除变量</h4>
<p>使用 unset 命令可以删除变量。语法：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h3> 特定变量</h3>
<h3> if语句</h3>
<h3> 两数求和</h3>
<h3> 删除文件</h3>
<h3> 求所有参数的和</h3>
<h3> 求1-100的和</h3>
<h3> 统计文件数量</h3>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202305092313708.gif" type="image/gif"/>
    </item>
    <item>
      <title>Linux涉及命令</title>
      <link>https://v-blog.yyshino.top/posts/Computer/Linux/%E6%B6%89%E5%8F%8A%E5%91%BD%E4%BB%A4.html</link>
      <guid>https://v-blog.yyshino.top/posts/Computer/Linux/%E6%B6%89%E5%8F%8A%E5%91%BD%E4%BB%A4.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">Linux涉及命令</source>
      <description>Linux涉及命令</description>
      <category>Computer</category>
      <pubDate>Sun, 07 May 2023 15:04:23 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 总览</h2>
<p><code>cd</code> <code>pwd</code> <code>ls</code> <code>mkdir</code> <code>rmdir</code> <code>rm</code></p>
<p><code>cat</code>、<code>more</code>、<code>less</code></p>
<p><code>cp</code>、<code>rm</code>、<code>mv</code></p>
<p><code>touch</code></p>
<p><code>?</code>、<code>*</code>、<code>[]</code></p>
<p><code>find</code>、<code>wc</code>、<code>sort</code></p>
<p><code>cat</code>、<code>tee</code>、<code>last</code></p>
<p>vi编辑器三种模式</p>
<p><code>:q</code> <code>:wq</code> <code>:q!</code></p>
<p><code>iao</code>、<code>dd</code>、<code>dw</code>、<code>x</code></p>
<p><code>y</code>、<code>p</code>、<code>J</code>、<code>u</code>、<code>Ctrl r</code></p>
<p><code>/ect/passwd</code> <code>etc/shadow</code> <code>/etc/group</code></p>
<p><code>useradd</code> <code>passwd</code> <code>su</code> <code>userdel</code> <code>usermod</code></p>
<p><code>groupadd</code> <code>groupdel</code> <code>groupmod</code> <code>gpasswd</code></p>
<p><code>chmod</code> <code>chown</code> <code>chgrp</code></p>
<p><code>ps</code> <code>top</code></p>
<p><code>kill</code> <code>killall</code> <code>jobs</code> <code>fg</code> <code>bg</code> <code>nice</code> <code>renice</code></p>
<p><code>tar -czvf</code> <code>tar -xzvf</code></p>
<p><code>at</code></p>
<p><code>crontab</code></p>
]]></content:encoded>
    </item>
    <item>
      <title>软件工程-重点</title>
      <link>https://v-blog.yyshino.top/posts/Computer/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/Computer-%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B.html</link>
      <guid>https://v-blog.yyshino.top/posts/Computer/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/Computer-%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">软件工程-重点</source>
      <description>软件工程-重点</description>
      <category>Computer</category>
      <pubDate>Sun, 07 May 2023 15:04:23 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 第一章 绪论</h2>
<p>软件工程的定义：软件工程师用科学知识和技术来定义、开发、维护软件的一门学科</p>
<h3> 软件生命周期</h3>
<p>软件生命周期：指软件产品从<strong>考虑其概念</strong>开始，到该软件产品<strong>不再使用</strong>为止的整个时期，一般包括<strong>概念</strong>阶段、<strong>分析与设计</strong>阶段、<strong>构造</strong>阶段、<strong>移交和运行</strong>阶段等不同时期。</p>
<h3> 软件生命周期模型</h3>
<h4> 定义</h4>
<p>软件生命周期模型：从一个<strong>特定角度</strong>提出的对<strong>软件过程</strong>的<strong>概括描述</strong>，是对<strong>软件开发实际过程</strong>的<strong>抽象</strong>，包括构成软件过程的各种<strong>活动</strong>、<strong>软件工件</strong>以及<strong>参与角色</strong>等。</p>
<h4> 与开发方法的关系</h4>
<h4> 种类</h4>
<ul>
<li>
<p>瀑布模型：线性的整体化模型</p>
</li>
<li>
<p>增量模型：非整体化的开放模型</p>
</li>
<li>
<p>螺旋结构：风险驱动模型</p>
</li>
<li>
<p>喷泉模型：以用户需求为动力的对象驱动模型</p>
</li>
<li>
<p>基于知识的模型：是瀑布模型和知识的结合</p>
</li>
<li>
<p>变换模型：形式化开发模型</p>
</li>
<li>
<p>统一过程模型：基于统一建模语言的软件开发过程，它是用例驱动和风险驱动的，以构架为中心，采用迭代和增量的软件开发过程开发模型：面向对象的开发方法</p>
</li>
</ul>
<h4> 1. 瀑布模型</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202305050944574.png" alt="image-20230505094410772" tabindex="0" loading="lazy"><figcaption>image-20230505094410772</figcaption></figure>
<p>瀑布模型：将软件生存周期的各项活动规定为依照固定顺序连接的若干阶段工作，最终得到 软件产品。</p>
<p>特点</p>
<ol>
<li>阶段间具有顺序性和依赖性。</li>
<li>推迟实现的观点。</li>
<li>质量保证的观点
<ul>
<li>每个阶段必须完成规定的文档;</li>
<li>每个阶段结束前完成文档审查,</li>
<li>及早改正错误。</li>
</ul>
</li>
</ol>
<p>瀑布模型优缺点：</p>
<p>1、瀑布模型的优点（强迫开发人员使用规范的方法，严格规定了每个阶段必须提交的文
档，要求每个阶段）</p>
<ul>
<li>可以强迫开发人员采用规范的方法;</li>
<li>严格规定了每个阶段必须提交的文档;</li>
<li>要求每个阶段交出的所有产品都必须经过质量保证小组的仔细验证。</li>
</ul>
<p>2、瀑布模型的缺点</p>
<ul>
<li>在软件开发的初期阶段就要求做出正确、 全面、完整的需求分析对许多应用软件来说
是极其困难的。</li>
<li>在需求分析阶段，当需求确定后，无法及时验证需求是否正确、完整。</li>
<li>作为整体开发的瀑布模型，由于不支持产品的演化，缺乏灵活性，对开发过程中很难
发现的错误，只有在最终产品运行时才能暴露出来， 从而使软件产品难以维护</li>
</ul>
<h4> 2. 增量模型</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202305050946894.png" alt="image-20230505094644540" tabindex="0" loading="lazy"><figcaption>image-20230505094644540</figcaption></figure>
<p>增量模型：先完成一个系统子集的开发，再按同 样的开发步骤增加功能 (系统子集),如 此递增下去直至满足全部系统需求。</p>
<p>增量模型优缺点：</p>
<p>优点：</p>
<ol>
<li>短时间内可提交完成部分功能</li>
<li>逐渐增加产品功能，用户适应产品快。</li>
</ol>
<p>缺点：</p>
<ol>
<li>增量构件划分以及集成困难。</li>
<li>容易退化为边做边改模型。</li>
</ol>
<h4> 3. 螺旋模型</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202305050948202.png" alt="image-20230505094813134" tabindex="0" loading="lazy"><figcaption>image-20230505094813134</figcaption></figure>
<p>简化的螺旋模型</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305050949390.png" alt="image-20230505094948557" tabindex="0" loading="lazy"><figcaption>image-20230505094948557</figcaption></figure>
<p>螺旋模型：在每个阶段之前都增 加了风险分析 过程的快速原型模型。 看作增加了风险分析 的快速原型模型。</p>
<p>螺旋模型优缺点:
优点：</p>
<ol>
<li>利于把软件质量作为软件
开发目标。</li>
<li>减少测试</li>
<li>维护和开发不分开</li>
</ol>
<p>缺点：</p>
<ol>
<li>风险估计困难</li>
</ol>
<h4> 4. 喷泉模型</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202305050954674.png" alt="image-20230505095441036" tabindex="0" loading="lazy"><figcaption>image-20230505095441036</figcaption></figure>
<p>改进的喷泉模型</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305050955610.png" alt="image-20230505095518806" tabindex="0" loading="lazy"><figcaption>image-20230505095518806</figcaption></figure>
<p>喷泉模型：典型的<strong>面向对象软件过程模型</strong>。 体现迭代和无缝的特性。</p>
<h4> 5. 基于知识的模型</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202305050958611.png" alt="image-20230505095801703" tabindex="0" loading="lazy"><figcaption>image-20230505095801703</figcaption></figure>
<h4> 6. 变换模型</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202305050959938.png" alt="image-20230505095905166" tabindex="0" loading="lazy"><figcaption>image-20230505095905166</figcaption></figure>
<h2> 第二章 软件要求定义</h2>
<h3> 纯收入</h3>
<p>衡量工程价值的另一项经济指标是工程的纯收入，也就是在整个生命周期之内的系统累计经验效益（折合成现在值）之差。</p>
<h3> 投资回收期</h3>
<p>通常用投资回收期衡量一项开发工程的价值。所谓投资回收期就是使累计的经济效益等于最初投资所需要的时间。</p>
<h3> 流程图</h3>
<h4> 流程图的三种结构</h4>
<p><img src="https://shinoimg.yyshino.top/img/202305052117001.png" alt="image-20230505211729505" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061358049.png" alt="image-20230506135834972" loading="lazy"></p>
<h3> 系统流程图</h3>
<p>系统流程图：是一种描绘物理系统的图，用图形符号以黑盒子形式描绘物理系统的各部 件，<strong>表达数据在系统各部件之间流动的情况</strong>。而不是对数据进行加工处理 的控制过程。</p>
<p>作用：描述物理系统的工具，用于可行性研究和需求分析阶段。</p>
<h4> 系统流程图的符号</h4>
<p><img src="https://shinoimg.yyshino.top/img/202305071616622.png" alt="image-20230507161645181" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305051013767.png" alt="image-20230505101317158" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305051015538.png" alt="image-20230505101502672" loading="lazy"></p>
<h3> 需求分析问题识别（11种）</h3>
<ol>
<li>功能需求分析</li>
<li>性能需求分析</li>
<li>数据分析</li>
<li>环境需求分析</li>
<li>输入，输出分析</li>
<li>安全分析</li>
<li>文档分析</li>
<li>进度分析</li>
<li>资源分析</li>
<li>质量控制分析</li>
<li>界面需求分析</li>
</ol>
<h2> 第三章 软件设计</h2>
<h3> 耦合性和内聚性</h3>
<h4> 特点</h4>
<h4> 定义</h4>
<h4> 强弱</h4>
<h3> 模块间耦合类型</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202305051334251.png" alt="image-20230505133413946" tabindex="0" loading="lazy"><figcaption>image-20230505133413946</figcaption></figure>
<ul>
<li>
<p><strong>非直接耦合</strong>：不直接联系。</p>
</li>
<li>
<p><strong>数据耦合</strong>：通过<strong>数据参数</strong>（注意 是参数，不是区域也不是子结构！）联系。</p>
</li>
<li>
<p><strong>标记耦合</strong>：传递的信息是子结构（结构体、对象等）。</p>
</li>
<li>
<p><strong>控制耦合</strong>：A模块控制B模块</p>
</li>
<li>
<p><strong>外部耦合</strong>：访问同一外部变量，与公共耦合不同的是，一个是区域，一个是变量。</p>
</li>
<li>
<p><strong>公共耦合</strong>：访问同一公共区域</p>
</li>
<li>
<p><strong>内容耦合</strong>：内容重叠，</p>
<ul>
<li>如果发生下列情形，两个模块之间就发生了内容耦合。
<ol>
<li>一个模块<strong>直接访问</strong>另一个模块的<strong>内部数据</strong>；</li>
<li>一个模块不通过正常入口转到另一模块内部；</li>
<li>两个模块有一部分程序<strong>代码重叠</strong>(只可能出现在汇编语言中)；</li>
<li>一个模块有<strong>多个入口</strong>。</li>
</ol>
</li>
</ul>
</li>
</ul>
<h3> 模块的内聚性类型</h3>
<p>模块间的耦合和模块的内聚是度量模块独立性的两个准则。内聚是模块功能强度的度量，即模块内部各个元素彼此结合的紧密程度。一个模块内部各元素之间的紧密程度越高，则其内聚性越高，模块独立性越好。</p>
<p>一般来讲，聚合类型共分七种， 以下为<strong>从弱到强</strong>的排序：</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305051345472.png" alt="image-20230505134547257" tabindex="0" loading="lazy"><figcaption>image-20230505134547257</figcaption></figure>
<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>
<li><strong>功能内聚</strong>：指模块内的所有元素共同作用完成一个功能，缺一不可。</li>
</ul>
<p>总结： 容易混淆的是过程内聚和顺序内聚， 虽然都是按固定顺序执行， 但顺序内聚要求<strong>前一个功能的输出是下一个功能的输入</strong>。</p>
<figure><img src="https://shinoimg.yyshino.top/img/202305051356440.png" alt="image-20230505135632080" tabindex="0" loading="lazy"><figcaption>image-20230505135632080</figcaption></figure>
<h3> 软件结构准则</h3>
<ol>
<li>模块独立性准则</li>
<li>控制范围与作用范围之间的准则</li>
<li>软件结构的形态准则</li>
<li>模块大小的准则</li>
<li>模块的接口准则</li>
</ol>
<h4> 算法描述（4种方法）</h4>
<ul>
<li>盒图（N-S图）</li>
<li>程序流程图</li>
<li>PAD图</li>
<li>自然语言描述</li>
</ul>
<p>扩展（了解）</p>
<ol>
<li>自然语言描述算法：使用自然语言描述的好处是任何人都能看懂。当然相比于伪代码或者程序语言而言，使用自然语言描述有时会显得繁琐。</li>
<li>流程图描述算法：流程图（Flow Diagram）是一种通用的图形符号表示法是一种非正式的，可以清楚描述步骤和判断。</li>
<li>伪代码描述算法：伪代码（Pseudocode）是一种非正式的，类似于英语结构的，用于描述模块结构图的语言。</li>
<li>程序语言描述算法：程序语言描述算法，实际上就是用程序语言实现算法。不同的编程语言其语法不尽相同。</li>
</ol>
<h4> 模块属性</h4>
<ol>
<li><strong>接口</strong></li>
<li><strong>功能</strong></li>
<li><strong>逻辑</strong></li>
<li><strong>状态</strong></li>
</ol>
<p>扩展（了解）</p>
<ol>
<li>名称：模块的名称，通常是一个有意义的、描述性的名称，用于唯一标识和识别模块。</li>
<li>功能：模块所提供的具体功能或服务的描述，包括模块的输入、输出以及对外部系统或用户的交互方式。</li>
<li>接口：模块与其他模块或外部系统之间的交互接口，描述了模块的输入、输出、调用的函数或方法等信息。</li>
<li>数据结构：模块内部使用的数据结构，包括数据类型、变量、常量等的定义和描述。</li>
<li>依赖关系：模块与其他模块之间的依赖关系，描述了模块所依赖的其他模块或外部组件，以及被依赖的关系。</li>
<li>耦合度：模块与其他模块之间的耦合程度，描述了模块之间的关联程度和依赖程度，例如紧密耦合、松散耦合等。</li>
<li>内聚度：模块内部功能之间的相关性和一致性，描述了模块内部功能的组织和关联程度，例如高内聚、低内聚等。</li>
<li>复用性：模块的可重用程度，描述了模块是否可以在不同的系统或项目中进行复用，以提高开发效率和质量。</li>
<li>可测试性：模块的可测试程度，描述了模块是否易于进行单元测试、集成测试和功能测试等，以保证模块的质量和稳定性。</li>
</ol>
<h2> 第五章 软件测试</h2>
<h3> 黑白盒测试</h3>
<h4> 白盒方法</h4>
<p><strong>逻辑覆盖</strong></p>
<ol>
<li><strong>语句覆盖</strong>:被测试程序中的<strong>每条语句至少执行一次</strong>。</li>
<li><strong>判定覆盖</strong>:使得被测程序中<strong>每个判定表达式至少获得一次“真”值和“假”值</strong></li>
<li><strong>条件覆盖</strong>:使得判定表达式中<strong>每个条件的各种可能的值至少出现一次</strong>。</li>
<li><strong>判定/条件覆盖</strong>:使得判定表达式中的<strong>每个条件的所有可能取值至少出现一次</strong>，并使<strong>每个判定表达式所有可能的结果也至少出现一次</strong>。（判定+条件）</li>
<li><strong>条件组合覆盖</strong>:设计足够多的测试用例，使得<strong>每个判定表达式中条件的各种可能 的值的组合都至少出现一次</strong>。</li>
<li><strong>路径覆盖</strong>:覆盖被测程序中<strong>所有可能的路径</strong>。</li>
</ol>
<h5> 控制结构</h5>
<p><strong>基本路径测试</strong></p>
<p>通过分析由控制构造的环路的复杂性，导出基本路径集合，从而设计测试用例，保证这些路径至少通过一次。</p>
<p>基本测试步骤</p>
<ol>
<li>以详细设计或源程序为基础，到处程序流程图的拓扑结构——程序图（流图）</li>
<li>计算程序图G的环路复杂性V(G)</li>
<li>确定只包含独立路线的基本路径集</li>
<li>设计测试用例</li>
</ol>
<p><strong>示例：</strong></p>
<p><img src="https://shinoimg.yyshino.top/img/202305091357567.png" alt="image-20230509135720991" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091357358.png" alt="image-20230509135740924" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091401808.png" alt="image-20230509140106388" loading="lazy"></p>
<p><strong>条件测试</strong></p>
<p><strong>循环测试</strong></p>
<h4> 黑盒方法</h4>
<h5> 等价类划分法</h5>
<p><strong>划分等价类的规则（理解即可）</strong></p>
<ul>
<li>
<p>如果输入条件规定了取值范围，可定义一个有效等价类和两个无效等价类。</p>
</li>
<li>
<p>如果输入条件代表集合的某个元素，则可定义一个有效等价类和一个无效等价类。</p>
</li>
<li>
<p>如规定了输入数据的一组值，且程序对不同输入值做不同处理，则每个允许的输入值是一个有效等价类，并有一个无效等价类(所有不允许的输入值的集合)。</p>
</li>
<li>
<p>如果规定了输入数据必须遵循的规则，可确定一个有效等价类（符合规则）和若干个无效等价类（从不同角度违反规则)。</p>
</li>
<li>
<p>如已划分的等价类各元素在程序中的处理方式不同，则应将此等价类进一步划分成更小的等价类。</p>
</li>
</ul>
<p><strong>设计测试用例：</strong></p>
<ol>
<li>形成等价类表，每一等价类规定一个唯一的编号；</li>
<li>设计一测试用例，使<strong>其尽可能多地覆盖尚未覆盖的有效等价类</strong>，重复这一步骤，直到所有有效等价类均被测试用例所覆盖；</li>
<li>设计一新测试用例，使其<strong>只覆盖一个无效等价类</strong>，重复这一步骤直到所有无效等价类均被覆盖；</li>
</ol>
<p><strong>示例：</strong></p>
<p>报表日期输入条件的等价类表</p>
<p><img src="https://shinoimg.yyshino.top/img/202305091227115.png" alt="image-20230509122713268" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091230025.png" alt="image-20230509123003189" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091230987.png" alt="image-20230509123032153" loading="lazy"></p>
<h5> 边界值分析法</h5>
<ul>
<li>输入等价类和输出等价类的边界就是应该着重测试的程序边界情况。选取的测 试数据应该刚好等于、刚好小于、刚好大于边界值</li>
</ul>
<p>边界值分析原则（了解即可）：</p>
<p>（1）如果输入条件规定了值的范围，可以选择正好等于边界值的数据作为合理的测试用例，同时选择刚好越过边界值得数据作为不合理的测试用例。
（2）如果输入条件指出了输入数据的个数，则按最大个数、最小个数、比最大个数多1、比最小个数少1等情况设计测试用例。
（3）对每个输出条件分别按照以上两个原则确定输出值的边界情况。
（4）如果程序的需求说明给出的输入或输出域是个有序集合（如线性表、链表等），则应选集合中第一个和最后一个元素作为测试用例。</p>
<p>常见的错误推测法</p>
<p>（1）零值、缺省值、空白、空值等测试值易使程序出错。</p>
<p>（2）分析规格说明书中的漏洞，编写测试数据。</p>
<p>（3）根据尚未发现的软件错误与已发现的软件错误成正比的规律，再进一步测试时重点测试已发现错误的程序段。</p>
<p>（4）等价类划分与边界值分析容易忽略组合的测试数据，因而可以采用判定表和判定树列出测试数据。</p>
<p>（5）与人工代码审查相结合，两个模块中共享的变量已被修改的，可以用来做测试用例。</p>
<h5> 因果图法</h5>
<p>因果图适合于描述对于多种输入条件的组合，相应产生多个动作的形式来设计测试用例。因果图方法最终生成的是判定表。</p>
<p><strong>示例：</strong></p>
<p><img src="https://shinoimg.yyshino.top/img/202305091240684.png" alt="image-20230509124000206" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091240049.png" alt="image-20230509124025312" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091241662.png" alt="image-20230509124134113" loading="lazy"></p>
<p>黑盒测试与白盒测试优缺点比较</p>
<table>
<thead>
<tr>
<th></th>
<th>黑盒测试</th>
<th>白盒测试</th>
</tr>
</thead>
<tbody>
<tr>
<td>优点</td>
<td>1. 适用于各阶段测试 2. 从产品功能角度测试 3. 容易入手生成测试数据</td>
<td>1. 可构成测试数据使特定程序部分得到测试 2. 有一定的充分性度量手段 3. 可获得较多工具支持</td>
</tr>
<tr>
<td>缺点</td>
<td></td>
<td></td>
</tr>
<tr>
<td>性质</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<h3> 系统测试（讲了一页）</h3>
<p>系统测试：将子系统组装为一个完整的系统进行测试。子系统测试和系统测试总 称为“集成测试” 。</p>
<p>系统测试的特点：功能测试、性能测试、安全测试、回归测试</p>
<h4> 集成测试</h4>
<p>目标：发现与接口有关的问题</p>
<p>实施者：独立的测试机构或第三方人员</p>
<p>集成方法：</p>
<ol>
<li>非渐增测试</li>
<li>渐增测试
<ol>
<li>自顶向下集成:从主控模块开始,沿着程序的控制层次</li>
<li>自顶向下移动,逐步添加新的模块自底向上集成:从最底层模块开始组装</li>
</ol>
</li>
</ol>
<p>自顶向下与自底向上相结合的方法：</p>
<ol>
<li>上层模块使用自顶向下方法</li>
<li>下层模块采用自底向上方法</li>
</ol>
<p>回归测试: 重新执行已经做过测试的某个子集，以保证程序的变化没有带来非预 期的副作用。</p>
<h3> 测试用例的定义</h3>
<p>测试用例的定义:是一组输入和期待的结果,它根据引起故障和检查的目的来使用组件。</p>
<h3> 软件错误类型</h3>
<ul>
<li>
<p>功能错(需求分析错误)</p>
</li>
<li>
<p>软件结构错</p>
</li>
<li>
<p>数据错</p>
</li>
<li>
<p>编码错</p>
</li>
<li>
<p>软件集成错</p>
</li>
<li>
<p>测试定义与测试执行错误</p>
</li>
</ul>
<h3> 软件可测试的特性</h3>
<ol>
<li><strong>可操作性</strong></li>
<li><strong>可观察性</strong></li>
<li><strong>可控制性</strong></li>
<li><strong>可分解性</strong></li>
<li><strong>简单易理解性</strong></li>
</ol>
<h3> 调试时修改错误的原则</h3>
<p><strong>修改错误的原则</strong></p>
<ul>
<li><strong>注意错误的群集现象，在错误近邻检查</strong></li>
<li><strong>找到错误的本质并修改</strong></li>
<li><strong>采用回归测试，避免因修改而引起的新错误</strong></li>
</ul>
<p>调试原则</p>
<ul>
<li>在调试方面，许多原则本质上是心理学方面的问题，调试由两部分组成，调试原则也分成两组。</li>
<li>确定错误的性质和位置的原则</li>
<li>用头脑去分析思考与错误征兆有关的信息</li>
<li>避开死胡同</li>
<li>只把调试工具当做辅助手段来使用，利用调试工具，可以帮助思考，但不能代替思考</li>
<li>避免用试探法，最多只能把它当做最后手段</li>
</ul>
<h3> 测试组件（单元测试）</h3>
<p>单元测试（模块测试）：将每个模块作为一个单独的实体进行测试。发现的错误编码和详细设计阶段的错误</p>
<p>测试依据：详细设计文档</p>
<p>测试技术（设计测试用例的方法）：白盒测试技术</p>
<p>着重点:</p>
<ol>
<li>模块接口</li>
<li>局部数据结构</li>
<li>重要的执行通路</li>
<li>出错处理通路</li>
<li>边界条件</li>
</ol>
<h2> 第六章 软件维护</h2>
<h3> 软件维护的副作用</h3>
<p>编码、文档、数据的副作用</p>
<p>维护是为了延长软件的寿命，让软件创造更多的价值，但是维护会产生潜在的错误或
其他不希望出现的情况，这称为维护的副作用。维护的副作用有编码副作用、数据副作用
和文档副作用三种。</p>
<p>1．编码副作用</p>
<p><strong>使用程序设计语言修改源程序时可能会引入错误</strong>。在修改程序的标号、标识符、运算
符、边界条件和程序的时序关系等时要特别仔细，避免引入新的错误。</p>
<p>2．数据副作用</p>
<p><strong>修改数据结构时可能会造成软件设计与数据结构不匹配，因而导致软件错误</strong>。如在修
改局部量、全局量、记录或文件的格式、初始化控制或指针、输入输出或子程序的参数等
时，容易导致设计与数据不一致。</p>
<p>3．文档副作用</p>
<p><strong>对数据流、软件结构、模块逻辑或其他任何特性进行修改时，必须对相关的文档进行相应的修改，否则会导致文档与程序功能不匹配，文档不能反映软件当前的状态</strong>。因此，必须在软件交付之前对软件配置进行评审，以减少文档的副作用。</p>
<h2> 第八章 结构化方法</h2>
<h3> DFD数据流图</h3>
<p>描述信息流和数据从输入到<strong>输出过程</strong>所经受的<strong>变换</strong>。没有任何具体物理部件， 只是描绘数据在软件中<strong>流动</strong>和<strong>被处理</strong>的<strong>逻辑过程</strong>。</p>
<p><img src="https://shinoimg.yyshino.top/img/202305072014710.png" alt="image-20230507201433883" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305072017492.png" alt="image-20230507201712307" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061649989.png" alt="image-20230506164933741" loading="lazy"></p>
<h3> 数据字典</h3>
<p>数据字典：是关于数据的信息集合，即对数据流图中包含的所有元素定义的集合。</p>
<p>1.数据字典的内容：数据流、数据流分量（数据元素）、数据存储、处理。</p>
<p>数据字典用途: 在软件分析和设计的过程中给人提供关于数据的描述信息。</p>
<p>① 作为分析阶段的工具</p>
<p>② 估计改变一个数据将产生的影响</p>
<p>③ 是数据库开发的第一步</p>
<p><img src="https://shinoimg.yyshino.top/img/202305061650107.png" alt="image-20230506165036891" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061651523.png" alt="image-20230506165102431" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061653076.png" alt="image-20230506165341822" loading="lazy"></p>
<h3> 结构化语言判定表判定数</h3>
<h3> 判断表判定树</h3>
<p>判定表：当算法中包含多重嵌套的条件选择时判定表却能够清晰地表示复杂的条件组合与应做的动作之间的对应关系。</p>
<p>组成：</p>
<ul>
<li><strong>左上部列出所有条件,左下部是所有可能的动作。</strong></li>
<li><strong>右上部是表示各种条件组合,右下部是和每种条件组合相对应的动作。</strong></li>
</ul>
<p>判定树</p>
<p><strong>依据判断表来画</strong></p>
<p><strong>依据条件分类</strong></p>
<h3> SC结构图</h3>
<p><img src="https://shinoimg.yyshino.top/img/202305062051097.png" alt="image-20230506205150739" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305062052105.png" alt="image-20230506205243117" loading="lazy"></p>
<h4> 面向数据流设计方法也称为结构化设计方法（SD）</h4>
<p>数据流图分类</p>
<p>（1）变换流：由输入、变换中心和输出三部分组成。</p>
<p>（2）事务流：在多种事务中选择一个执行。</p>
<p>变换分析: 把具有变换流特点的数据流图映射成软件结构。</p>
<h3> 变换型数据流图 =&gt; 软件结构图</h3>
<h3> 事物型数据流图 =&gt; 软件结构图</h3>
]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202305050944574.png" type="image/png"/>
    </item>
    <item>
      <title>软件工程</title>
      <link>https://v-blog.yyshino.top/posts/Computer/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/Computer-%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B-%E7%94%BB%E5%9B%BE%E6%80%BB%E7%BB%93.html</link>
      <guid>https://v-blog.yyshino.top/posts/Computer/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/Computer-%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B-%E7%94%BB%E5%9B%BE%E6%80%BB%E7%BB%93.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">软件工程</source>
      <description>软件工程</description>
      <category>Computer</category>
      <pubDate>Fri, 05 May 2023 09:37:19 GMT</pubDate>
      <content:encoded><![CDATA[<h3> 软件工程</h3>
<h3> 系统流程图</h3>
<p>系统流程图：是一种描绘物理系统的图，用图形符号以黑盒子形式描绘物理系统的各部 件，表达数据在系统各部件之间流动的情况。而不是对数据进行加工处理 的控制过程。</p>
<p>作用：描述物理系统的工具，用于可行性研究和需求分析阶段。</p>
<h4> 系统流程图的符号</h4>
<p><img src="https://shinoimg.yyshino.top/img/202305071616622.png" alt="image-20230507161645181" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305051013767.png" alt="image-20230505101317158" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305051015538.png" alt="image-20230505101502672" loading="lazy"></p>
<h3> 流程图的三种结构</h3>
<p><img src="https://shinoimg.yyshino.top/img/202305052117001.png" alt="image-20230505211729505" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061358049.png" alt="image-20230506135834972" loading="lazy"></p>
<h3> 问题分析图（PAD）</h3>
<p><img src="https://shinoimg.yyshino.top/img/202305061400982.png" alt="image-20230506140002642" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061400947.png" alt="image-20230506140031247" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061401163.png" alt="image-20230506140120009" loading="lazy"></p>
<h3> 盒图</h3>
<p><img src="https://shinoimg.yyshino.top/img/202305061402554.png" alt="image-20230506140248797" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061406080.png" alt="image-20230506140319788" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061403756.png" alt="" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061405172.png" alt="image-20230506140426091" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061409955.png" alt="image-20230506140911587" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061409172.png" alt="image-20230506140950994" loading="lazy"></p>
<h3> DFD数据流图</h3>
<p>描述信息流和数据从输入到<strong>输出过程</strong>所经受的<strong>变换</strong>。没有任何具体物理部件， 只是描绘数据在软件中<strong>流动</strong>和<strong>被处理</strong>的<strong>逻辑过程</strong>。</p>
<p><img src="https://shinoimg.yyshino.top/img/202305072014710.png" alt="image-20230507201433883" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061649823.png" alt="image-20230506164905254" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061649989.png" alt="image-20230506164933741" loading="lazy"></p>
<h3> 数据字典</h3>
<p>数据字典：是关于数据的信息集合，即对数据流图中包含的所有元素定义的集合。</p>
<p>1.数据字典的内容：数据流、数据流分量（数据元素）、数据存储、处理。</p>
<p>数据字典用途: 在软件分析和设计的过程中给人提供关于数据的描述信息。</p>
<p>① 作为分析阶段的工具</p>
<p>② 估计改变一个数据将产生的影响</p>
<p>③ 是数据库开发的第一步</p>
<p><img src="https://shinoimg.yyshino.top/img/202305061650107.png" alt="image-20230506165036891" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061651523.png" alt="image-20230506165102431" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305061653076.png" alt="image-20230506165341822" loading="lazy"></p>
<h3> SC结构图</h3>
<p><img src="https://shinoimg.yyshino.top/img/202305062052105.png" alt="image-20230506205243117" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305062051097.png" alt="image-20230506205150739" loading="lazy"></p>
<h4> 面向数据流设计方法也称为结构化设计方法（SD）</h4>
<p>数据流图分类</p>
<p>（1）变换流：由输入、变换中心和输出三部分组成。</p>
<p>（2）事务流：在多种事务中选择一个执行。</p>
<p>变换分析: 把具有变换流特点的数据流图映射成软件结构。</p>
<h3> 变换型数据流图 =&gt; 软件结构图</h3>
<h3> 事物型数据流图 =&gt; 软件结构图</h3>
<h3> 实体-联系图（E-R图）</h3>
<ul>
<li>
<p>实体：描述数据对象。</p>
</li>
<li>
<p>属性：描述数据对象的性质。</p>
</li>
<li>
<p>联系：描述数据对象之间的交互方式。</p>
<ul>
<li>一对一联系1:1</li>
<li>一对多联系1:M</li>
<li>多对多联系M:N</li>
</ul>
</li>
<li>
<p>表示方式</p>
<ul>
<li>矩形方框：实体</li>
<li>菱形框：联系</li>
<li>圆角矩形：属性</li>
</ul>
</li>
</ul>
<h4> 示例</h4>
<figure><img src="https://shinoimg.yyshino.top/img/202305061921296.png" alt="image-20230506192130993" tabindex="0" loading="lazy"><figcaption>image-20230506192130993</figcaption></figure>
<h3> 判断表判定树</h3>
<p>判定表：当算法中包含多重嵌套的条件选择时判定表却能够清晰地表示复杂的条件组合与应做的动作之间的对应关系。</p>
<p>组成：</p>
<ul>
<li>左上部列出所有条件,左下部是所有可能的动作。</li>
<li>右上部是表示各种条件组合,右下部是和每种条件组合相对应的动作。</li>
</ul>
<p>判定树</p>
<p>依据判断表来画</p>
<p>依据条件分类</p>
<h3> 黑盒测试</h3>
<h4> 黑盒方法</h4>
<h5> 等价类划分法</h5>
<p><strong>划分等价类的规则（理解即可）</strong></p>
<ul>
<li>
<p>如果输入条件规定了取值范围，可定义一个有效等价类和两个无效等价类。</p>
</li>
<li>
<p>如果输入条件代表集合的某个元素，则可定义一个有效等价类和一个无效等价类。</p>
</li>
<li>
<p>如规定了输入数据的一组值，且程序对不同输入值做不同处理，则每个允许的输入值是一个有效等价类，并有一个无效等价类(所有不允许的输入值的集合)。</p>
</li>
<li>
<p>如果规定了输入数据必须遵循的规则，可确定一个有效等价类（符合规则）和若干个无效等价类（从不同角度违反规则)。</p>
</li>
<li>
<p>如已划分的等价类各元素在程序中的处理方式不同，则应将此等价类进一步划分成更小的等价类。</p>
</li>
</ul>
<p><strong>设计测试用例：</strong></p>
<ol>
<li>形成等价类表，每一等价类规定一个唯一的编号；</li>
<li>设计一测试用例，使<strong>其尽可能多地覆盖尚未覆盖的有效等价类</strong>，重复这一步骤，直到所有有效等价类均被测试用例所覆盖；</li>
<li>设计一新测试用例，使其<strong>只覆盖一个无效等价类</strong>，重复这一步骤直到所有无效等价类均被覆盖；</li>
</ol>
<p><strong>示例：</strong></p>
<p>报表日期输入条件的等价类表</p>
<p><img src="https://shinoimg.yyshino.top/img/202305091227115.png" alt="image-20230509122713268" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091230025.png" alt="image-20230509123003189" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091230987.png" alt="image-20230509123032153" loading="lazy"></p>
<h5> 边界值分析法</h5>
<ul>
<li>输入等价类和输出等价类的边界就是应该着重测试的程序边界情况。选取的测 试数据应该刚好等于、刚好小于、刚好大于边界值</li>
</ul>
<p>边界值分析原则（了解即可）：</p>
<p>（1）如果输入条件规定了值的范围，可以选择正好等于边界值的数据作为合理的测试用例，同时选择刚好越过边界值得数据作为不合理的测试用例。
（2）如果输入条件指出了输入数据的个数，则按最大个数、最小个数、比最大个数多1、比最小个数少1等情况设计测试用例。
（3）对每个输出条件分别按照以上两个原则确定输出值的边界情况。
（4）如果程序的需求说明给出的输入或输出域是个有序集合（如线性表、链表等），则应选集合中第一个和最后一个元素作为测试用例。</p>
<p>常见的错误推测法</p>
<p>（1）零值、缺省值、空白、空值等测试值易使程序出错。</p>
<p>（2）分析规格说明书中的漏洞，编写测试数据。</p>
<p>（3）根据尚未发现的软件错误与已发现的软件错误成正比的规律，再进一步测试时重点测试已发现错误的程序段。</p>
<p>（4）等价类划分与边界值分析容易忽略组合的测试数据，因而可以采用判定表和判定树列出测试数据。</p>
<p>（5）与人工代码审查相结合，两个模块中共享的变量已被修改的，可以用来做测试用例。</p>
<h5> 因果图法</h5>
<p>因果图适合于描述对于多种输入条件的组合，相应产生多个动作的形式来设计测试用例。因果图方法最终生成的是判定表。</p>
<p><strong>示例：</strong></p>
<p><img src="https://shinoimg.yyshino.top/img/202305091240684.png" alt="image-20230509124000206" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091240049.png" alt="image-20230509124025312" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091241662.png" alt="image-20230509124134113" loading="lazy"></p>
<h3> 白盒测试</h3>
<p><strong>逻辑覆盖</strong></p>
<ol>
<li><strong>语句覆盖</strong>:被测试程序中的<strong>每条语句至少执行一次</strong>。</li>
<li><strong>判定覆盖</strong>:使得被测程序中<strong>每个判定表达式至少获得一次“真”值和“假”值</strong></li>
<li><strong>条件覆盖</strong>:使得判定表达式中<strong>每个条件的各种可能的值至少出现一次</strong>。</li>
<li><strong>判定/条件覆盖</strong>:使得判定表达式中的<strong>每个条件的所有可能取值至少出现一次</strong>，并使<strong>每个判定表达式所有可能的结果也至少出现一次</strong>。（判定+条件）</li>
<li><strong>条件组合覆盖</strong>:设计足够多的测试用例，使得<strong>每个判定表达式中条件的各种可能 的值的组合都至少出现一次</strong>。</li>
<li><strong>路径覆盖</strong>:覆盖被测程序中<strong>所有可能的路径</strong>。</li>
</ol>
<h5> 控制结构</h5>
<p><strong>基本路径测试</strong></p>
<p>通过分析由控制构造的环路的复杂性，导出基本路径集合，从而设计测试用例，保证这些路径至少通过一次。</p>
<p>基本测试步骤</p>
<ol>
<li>以详细设计或源程序为基础，到处程序流程图的拓扑结构——程序图（流图）</li>
<li>计算程序图G的环路复杂性V(G)</li>
<li>确定只包含独立路线的基本路径集</li>
<li>设计测试用例</li>
</ol>
<p><strong>示例：</strong></p>
<p><img src="https://shinoimg.yyshino.top/img/202305091357567.png" alt="image-20230509135720991" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091357358.png" alt="image-20230509135740924" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305091401808.png" alt="image-20230509140106388" loading="lazy"></p>
<h3> 状态装换图（了解）</h3>
<p>状态：系统的行为模式，包括初态、终态、中间状态。
事件：是指在某个特定时刻发生的事情，即对系统从一个状态转换到另一个状态
的事件抽象。
表示方式</p>
<ul>
<li>初态：实心圆</li>
<li>终态：同心圆，内为实心</li>
<li>状态：圆角矩形</li>
</ul>
<p><strong>在一张状态图中只能有一个初态 而终态可以有0至多个。</strong></p>
<h3> 层次方框图（了解）</h3>
<figure><img src="https://shinoimg.yyshino.top/img/202305061936270.png" alt="image-20230506193557790" tabindex="0" loading="lazy"><figcaption>image-20230506193557790</figcaption></figure>
<h3> 层次图与IPO图（了解）</h3>
<p>层次图:用方框和连线表示,连线表示上下层的调用关系。</p>
<p><img src="https://shinoimg.yyshino.top/img/202305062050435.png" alt="image-20230506205047034" loading="lazy"><img src="https://shinoimg.yyshino.top/img/202305062051408.png" alt="image-20230506205123268" loading="lazy"></p>
<h2> 参考</h2>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://shinoimg.yyshino.top/img/202305071616622.png" type="image/png"/>
    </item>
    <item>
      <title>画一条0.5px的线</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-2CSS/02-%E7%94%BB%E4%B8%80%E6%9D%A10.5px%E7%9A%84%E7%BA%BF.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-2CSS/02-%E7%94%BB%E4%B8%80%E6%9D%A10.5px%E7%9A%84%E7%BA%BF.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">画一条0.5px的线</source>
      <description>画一条0.5px的线</description>
      <category>CSS</category>
      <pubDate>Tue, 02 May 2023 00:21:18 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 画一条0.5px的线</h2>
<h2> 参考</h2>
]]></content:encoded>
    </item>
    <item>
      <title>JS垃圾回收机制</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/13-JS%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/13-JS%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">JS垃圾回收机制</source>
      <description>JS垃圾回收机制</description>
      <category>JS</category>
      <pubDate>Tue, 25 Apr 2023 13:19:19 GMT</pubDate>
      <content:encoded><![CDATA[<h2> JS垃圾回收机制</h2>
<p>如上文所述自动寻找是否一些内存“不再需要”的问题是无法判定的。因此，垃圾回收实现只能有限制的解决一般问题。本节将解释必要的概念，了解主要的垃圾回收算法和它们的局限性。</p>
<h3> 引用</h3>
<p>垃圾回收算法主要依赖于引用的概念。在内存管理的环境中，一个对象如果有访问另一个对象的权限（隐式或者显式），叫做一个对象引用另一个对象。例如，一个 Javascript 对象具有对它<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain" target="_blank" rel="noopener noreferrer">原型</a>的引用（隐式引用）和对它属性的引用（显式引用）。</p>
<p>在这里，“对象”的概念不仅特指 JavaScript 对象，还包括函数作用域（或者全局词法作用域）。</p>
<h3> 引用计数垃圾收集</h3>
<p>这是最初级的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象（零引用），对象将被垃圾回收机制回收。</p>
<h4> 限制：循环引用</h4>
<p>该算法有个限制：无法处理循环引用的事例。在下面的例子中，两个对象被创建，并互相引用，形成了一个循环。它们被调用之后会离开函数作用域，所以它们已经没有用了，可以被回收了。然而，引用计数算法考虑到它们互相都有至少一次引用，所以它们不会被回收。</p>
<h3> 标记 - 清除算法</h3>
<p>这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。</p>
<p>这个算法假定设置一个叫做根（root）的对象（在 Javascript 里，根是全局对象）。垃圾回收器将定期从根开始，找所有从根开始引用的对象，然后找这些对象引用的对象……从根开始，垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。</p>
<p>这个算法比前一个要好，因为“有零引用的对象”总是不可获得的，但是相反却不一定，参考“循环引用”。</p>
<p>从 2012 年起，所有现代浏览器都使用了标记 - 清除垃圾回收算法。所有对 JavaScript 垃圾回收算法的改进都是基于标记 - 清除算法的改进，并没有改进标记 - 清除算法本身和它对“对象是否不再需要”的简化定义。</p>
<h4> 循环引用不再是问题了</h4>
<p>在上面的示例中，函数调用返回之后，两个对象从全局对象出发无法获取。因此，他们将会被垃圾回收器回收。第二个示例同样，一旦 div 和其事件处理无法从根获取到，他们将会被垃圾回收器回收。</p>
<h4> 限制：那些无法从根对象查询到的对象都将被清除</h4>
<p>尽管这是一个限制，但实践中我们很少会碰到类似的情况，所以开发者不太会去关心垃圾回收机制。</p>
]]></content:encoded>
    </item>
    <item>
      <title>防抖节流</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/12-%E9%98%B2%E6%8A%96%E8%8A%82%E6%B5%81.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/12-%E9%98%B2%E6%8A%96%E8%8A%82%E6%B5%81.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">防抖节流</source>
      <description>防抖节流</description>
      <category>JS</category>
      <pubDate>Tue, 25 Apr 2023 12:21:17 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 防抖节流</h2>
<h3> 本质</h3>
<p>防抖<code>debounce</code>与节流<code>throttle</code>都是控制事件处理函数执行频率的方法，当函数会进行<code>DOM</code>操作或者具有请求服务器等行为并且作为高频事件例如<code>onscroll</code>触发的事件处理函数时，就需要进行事件处理函数执行频率的控制，否则会造成大量的资源浪费致使性能下降，<strong>当然无论是防抖与节流实质上并没有减少事件触发次数，而是通过减少事件处理函数的执行次数从而提高性能。</strong></p>
<h3> 解决问题</h3>
<p>针对高频度触发事件问题（例如页面 scroll ，屏幕 resize，监听用户输入等），两种常用的解决方法，防抖和节流。</p>
<h2> 防抖</h2>
<h3> 非立即防抖</h3>
<p>当持续触发事件的时候，事件处理函数是完全不执行的，等最后一次触发结束的一段时间之后，再去执行。最常见的例子就是搜索建议功能，当用户进行持续输入时，并不会请求服务器进行搜索建议的计算，直至用户输入完成后的<code>N</code>毫秒后才会将数据传输至后端并返回搜索建议。</p>
<p><strong>实现思路：每次触发事件时都取消之前的延时调用方法并重设定时器。</strong></p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 立即防抖</h3>
<p>当持续触发事件的时候，事件处理函数会立即执行，然后不再执行事件处理函数，直至最后一次事件触发之后的一段时间后才允许再次执行事件处理函数。
<strong>实现思路：判断是否存在定时器，没有则执行事件处理函数，然后无论是否已经存在定时器都需要重设定时器。</strong></p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 节流</h2>
<p>当事件持续触发时，节流操作可以稀释事件处理函数执行频率，假设在<code>1s</code>内<code>onmousemove</code>事件触发了<code>100</code>次，通过节流就可以使得<code>onmousemove</code>事件的事件处理函数每<code>100ms</code>触发一次，也就是在<code>1s</code>内<code>onmousemove</code>事件的事件处理函数只执行<code>10</code>次。</p>
<h3> 时间戳实现</h3>
<p><strong>实现思路：通过时间戳记录上次事件处理函数执行时间，事件触发时若时间差大于执行周期则执行事件处理函数并赋值执行时间为当前时间戳。</strong></p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 定时器实现</h3>
<p><strong>实现思路：判断是否存在定时器，没有则执行事件处理函数并重设定时器。</strong></p>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 参考</h2>
<p><a href="https://blog.touchczy.top/#/JavaScript/%E9%98%B2%E6%8A%96%E4%B8%8E%E8%8A%82%E6%B5%81?id=%E9%98%B2%E6%8A%96%E4%B8%8E%E8%8A%82%E6%B5%81" target="_blank" rel="noopener noreferrer">防抖与节流 (touchczy.top)</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>JS的new操作符做了什么</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/08-JS%E7%9A%84new%E6%93%8D%E4%BD%9C%E7%AC%A6%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/08-JS%E7%9A%84new%E6%93%8D%E4%BD%9C%E7%AC%A6%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">JS的new操作符做了什么</source>
      <description>JS的new操作符做了什么</description>
      <category>JS</category>
      <pubDate>Mon, 24 Apr 2023 20:35:41 GMT</pubDate>
      <content:encoded><![CDATA[<h2> JS的new操作符做了什么</h2>
<p>new 操作符新建了一个空对象，这个对象原型指向构造函数的 prototype，执行构造函数 后返回这个对象。</p>
<ol>
<li>创建一个空的简单<code>JavaScript</code>对象即<code>{}</code>。</li>
<li>链接该对象(即设置该对象的构造函数)到另一个对象。</li>
<li>将步骤<code>1</code>新创建的对象作为<code>this</code>的上下文<code>context</code>。</li>
<li>如果该函数没有返回对象，则返回步骤<code>1</code>创建的对象。</li>
</ol>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>call apply bind的作用和区别</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/09-call%E5%92%8Capply%E5%92%8Cbind%E7%9A%84%E4%BD%9C%E7%94%A8%E5%8C%BA%E5%88%AB.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/09-call%E5%92%8Capply%E5%92%8Cbind%E7%9A%84%E4%BD%9C%E7%94%A8%E5%8C%BA%E5%88%AB.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">call apply bind的作用和区别</source>
      <description>call apply bind的作用和区别</description>
      <category>JS</category>
      <pubDate>Mon, 24 Apr 2023 20:35:41 GMT</pubDate>
      <content:encoded><![CDATA[<h2> call和apply和bind的作用区别</h2>
<h2> 【共同点】</h2>
<p>call apply bind三个方法都可以用来改变函数的this指向</p>
<h2> 【区别】</h2>
<p>1、call apply改变this，直接调用，bind改变this,不直接调用，返回函数
2、传参不同。call参数是单个的，apply参数是数组（apply 将参数作为一个数组传递，call 将参数直接传递，使用逗号分隔。bind 仅将对象绑定，并不立即执行，其返回值是一个函数，传参方式与 call 相同）</p>
]]></content:encoded>
    </item>
    <item>
      <title>JS的各种位置</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/10-JS%E7%9A%84%E5%90%84%E7%A7%8D%E4%BD%8D%E7%BD%AE.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/10-JS%E7%9A%84%E5%90%84%E7%A7%8D%E4%BD%8D%E7%BD%AE.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">JS的各种位置</source>
      <description>JS的各种位置</description>
      <category>JS</category>
      <pubDate>Mon, 24 Apr 2023 20:35:41 GMT</pubDate>
      <content:encoded><![CDATA[<h2> JS的各种位置</h2>
<ul>
<li>clientHeight：表示的是可视区域的高度，不包含 border 和滚动条</li>
<li>offsetHeight：表示可视区域的高度，包含了 border 和滚动条</li>
<li>scrollHeight：表示了所有区域的高度，包含了因为滚动被隐藏的部分。</li>
<li>clientTop：表示边框 border 的厚度，在未指定的情况下一般为 0</li>
<li>scrollTop：滚动后被隐藏的高度，获取对象相对于由 offsetParent属性指定的父坐标(css 定位的元素或 body 元素)距离顶端的高度。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>JS拖拽功能的实现</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/11-JS%E6%8B%96%E6%8B%BD%E5%8A%9F%E8%83%BD%E7%9A%84%E5%AE%9E%E7%8E%B0.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/11-JS%E6%8B%96%E6%8B%BD%E5%8A%9F%E8%83%BD%E7%9A%84%E5%AE%9E%E7%8E%B0.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">JS拖拽功能的实现</source>
      <description>JS拖拽功能的实现</description>
      <category>JS</category>
      <pubDate>Mon, 24 Apr 2023 20:35:41 GMT</pubDate>
      <content:encoded><![CDATA[<h2> JS拖拽功能的实现</h2>
<p>首先是三个事件，分别是 mousedown，mousemove，mouseup 当鼠标点击按下的时候，需要一个 tag 标识此时已经按下，可以执行 mousemove 里面的 具体方法。 clientX，clientY 标识的是鼠标的坐标，分别标识横坐标和纵坐标，并且我们用 offsetX 和 offsetY 来表示元素的元素的初始坐标，</p>
<p>移动的举例应该是： 鼠标移动时候的坐标-鼠标按下去时候的坐标。 也就是说定位信息为： 鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的 offetLeft. 还有一点也是原理性的东西，也就是拖拽的同时是绝对定位，我们改变的是绝对定位条件下的 left 以及 top 等等值。</p>
<p>补充：也可以通过 html5 的拖放（Drag)</p>
<h2> 代码实现</h2>
<div class="language-html line-numbers-mode" data-ext="html"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 参考</h2>
<p><a href="https://www.nowcoder.com/questionTerminal/fff24c04f16e4c48b2397e744a7ce336" target="_blank" rel="noopener noreferrer">js拖拽功能的实现__牛客网 (nowcoder.com)</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>mouseover和mouseenter的区别</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/07-mouseover%E5%92%8Cmouseenter%E7%9A%84%E5%8C%BA%E5%88%AB.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/07-mouseover%E5%92%8Cmouseenter%E7%9A%84%E5%8C%BA%E5%88%AB.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">mouseover和mouseenter的区别</source>
      <description>mouseover和mouseenter的区别</description>
      <category>JS</category>
      <pubDate>Mon, 24 Apr 2023 20:33:41 GMT</pubDate>
      <content:encoded><![CDATA[<h2> mouseover和mouseenter的区别</h2>
<ul>
<li>mouseover：当鼠标移入元素或其子元素都会触发事件，所以<strong>有一个重复触发，冒泡的过程</strong>。对应的移除事件是 mouseout</li>
<li>mouseenter：当鼠标移除元素本身（不包含元素的子元素）会触发事件，也就是<strong>不会冒泡</strong>，对应的移除事件是 mouseleave</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>如何让事件先冒泡后捕获</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/04-%E5%A6%82%E4%BD%95%E8%AE%A9%E4%BA%8B%E4%BB%B6%E5%85%88%E5%86%92%E6%B3%A1%E5%90%8E%E6%8D%95%E8%8E%B7.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/04-%E5%A6%82%E4%BD%95%E8%AE%A9%E4%BA%8B%E4%BB%B6%E5%85%88%E5%86%92%E6%B3%A1%E5%90%8E%E6%8D%95%E8%8E%B7.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">如何让事件先冒泡后捕获</source>
      <description>如何让事件先冒泡后捕获</description>
      <category>JS</category>
      <pubDate>Mon, 24 Apr 2023 20:26:04 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 如何让事件先冒泡后捕获</h2>
<p>在 DOM 标准事件模型中，是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果，对于同一个事件，监听捕获和冒泡，分别对应相应的处理函数，<strong>监听到捕获事件，先暂缓 执行</strong>，直到冒泡事件被捕获后再执行捕获之间。</p>
]]></content:encoded>
    </item>
    <item>
      <title>事件委托</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/05-%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/05-%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">事件委托</source>
      <description>事件委托</description>
      <category>JS</category>
      <pubDate>Mon, 24 Apr 2023 20:26:04 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 事件委托</h2>
<p>简介：事件委托指的是，不在事件的发生地（直接 dom）上设置监听函数，而是在其<strong>父元素上设置监听函数</strong>，通过事件冒泡，父元素可以监听到子元素上事件的触发，通过判断事件发生元素 DOM 的类型，来做出不同的响应</p>
<p>举例：最经典的就是 ul 和 li 标签的事件监听，比如我们在添加事件时候，采用事件委托机制，不会在 li 标签上直接添加，而是在 ul 父元素上添加。</p>
<p>好处：比较合适动态元素的绑定，新添加的子元素也会有监听函数，也可以有事件触发机制。</p>
]]></content:encoded>
    </item>
    <item>
      <title>图片的懒加载和预加载</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/06-%E5%9B%BE%E7%89%87%E7%9A%84%E6%87%92%E5%8A%A0%E8%BD%BD%E5%92%8C%E9%A2%84%E5%8A%A0%E8%BD%BD.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/06-%E5%9B%BE%E7%89%87%E7%9A%84%E6%87%92%E5%8A%A0%E8%BD%BD%E5%92%8C%E9%A2%84%E5%8A%A0%E8%BD%BD.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">图片的懒加载和预加载</source>
      <description>图片的懒加载和预加载</description>
      <category>JS</category>
      <pubDate>Mon, 24 Apr 2023 20:26:04 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 图片的懒加载和预加载</h2>
<ul>
<li>预加载：提前加载图片，当用户需要查看时可直接从本地缓存中渲染。</li>
<li>懒加载：懒加载的主要目的是作为服务器前端的优化，减少请求数或延迟请求数。</li>
</ul>
<h2> 两种技术的本质</h2>
<p>两者的行为是相反的一个是提前加载，一个是迟缓甚至不加载。 <strong>懒加载对服务器前端有一定的缓解压力作用</strong>，<strong>预加载则会增加服务器前端压力</strong>。</p>
]]></content:encoded>
    </item>
    <item>
      <title>类的创建和继承</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/02-%E7%B1%BB%E7%9A%84%E5%88%9B%E5%BB%BA%E5%92%8C%E7%BB%A7%E6%89%BF.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/02-%E7%B1%BB%E7%9A%84%E5%88%9B%E5%BB%BA%E5%92%8C%E7%BB%A7%E6%89%BF.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">类的创建和继承</source>
      <description>类的创建和继承</description>
      <category>JS</category>
      <pubDate>Mon, 24 Apr 2023 20:16:23 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 类的创建和继承</h2>
<ul>
<li>原型链继承：通过将子类的原型对象指向父类的实例，实现继承访问父类属性方法等。</li>
<li>构造器函数继承：当子类构造函数被调用时，借助call或者apply调用父类构造方法实现对于this的拓展。</li>
<li>实例继承：为父类实例增加成员与方法，作为实例返回。</li>
<li>拷贝继承：通过直接将父类的属性拷贝到子类的原型中实现继承。</li>
<li>原型式继承：通过共享原型对象实现继承。</li>
<li>组合式继承：组合原型链继承和借用构造函数继承，结合了两种模式的优点，传参和复用。</li>
<li>寄生组合式继承：通过寄生方式，砍掉父类的实例属性，在调用两次父类的构造的时候，就不会初始化两次实例方法和属性，避免的组合继承的缺点。</li>
</ul>
<h2> 【原型链继承】</h2>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过将子类的原型对象指向父类的实例，实现继承访问父类属性方法等。</p>
<h3> 缺点：</h3>
<ul>
<li>子类实例化时无法向父类的构造函数传参。</li>
<li>所有子类实例都会共享父类的原型对象中的属性。</li>
<li>无法实现多继承。</li>
</ul>
<h2> 【构造器函数继承】</h2>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当子类构造函数被调用时，借助call或者apply调用父类构造方法实现对于this的拓展。</p>
<h3> 优点：</h3>
<ul>
<li>子类实例不会共享父类属性方法。</li>
<li>实例化子类时可以向父类构造函数传参。</li>
<li>通过调用多个父类构造函数可以实现多继承。</li>
</ul>
<h3> 缺点：</h3>
<ul>
<li>只继承了父类的构造函数的属性和方法，没有继承父类原型的属性和方法。</li>
<li>每个子类都有父类实例函数的副本，拷贝了父类函数而不是引用，影响性能。</li>
<li>实例并不是父类的实例，只是子类的实例。</li>
</ul>
<h2> 【实例继承】</h2>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>为父类实例增加成员与方法，作为实例返回。</p>
<h3> 优点：</h3>
<ul>
<li>实例化子类时可以向父类构造函数传参。</li>
<li>子类的实例化方式可以为new Child()或直接调用Child()。</li>
</ul>
<h3> 缺点：</h3>
<ul>
<li>不支持多继承。</li>
<li>实例是父类的实例，不是子类的实例。</li>
<li>同样也是将父类的成员与方法做了实例化拷贝。</li>
</ul>
<h2> 【拷贝继承】</h2>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过直接将父类的属性拷贝到子类的原型中实现继承。</p>
<h3> 优点：</h3>
<ul>
<li>支持多继承。</li>
<li>实例化子类时可以向父类构造函数传参。</li>
</ul>
<h3> 缺点：</h3>
<ul>
<li>无法获取父类不可枚举的方法。</li>
<li>同样也是将父类的成员与方法做了实例化并拷贝。</li>
</ul>
<h2> 【原型式继承】</h2>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过共享原型对象实现继承。</p>
<h3> 优点：</h3>
<ul>
<li>实现了方法与属性的复用。</li>
<li>父类新增原型方法与属性，子类都能访问到。</li>
</ul>
<h3> 缺点：</h3>
<ul>
<li>不能继承父构造函数的实例对象的成员。</li>
<li>所有子类实例都会共享父类的原型对象中的属性。</li>
</ul>
<h2> 【组合继承】</h2>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>组合原型链继承和借用构造函数继承，结合了两种模式的优点，传参和复用。</p>
<h3> 优点：</h3>
<ul>
<li>原型方法可以复用。</li>
<li>既是子类的实例，也是父类的实例。</li>
<li>实例化子类时可以向父类构造函数传参。</li>
<li>可以继承实例属性和方法，也可以继承原型属性和方法。</li>
</ul>
<h3> 缺点：</h3>
<ul>
<li>调用了两次父类构造函数，生成了两份实例，子类的构造函数的拷贝会代替原型上的父类构造函数的实例。</li>
</ul>
<h2> 【寄生组合继承】</h2>
<div class="language-javascript line-numbers-mode" data-ext="js"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过寄生方式，砍掉父类的实例属性，在调用两次父类的构造的时候，就不会初始化两次实例方法和属性，避免的组合继承的缺点。</p>
<h3> 优点：</h3>
<ul>
<li>这种方式的高效率体现它只调用了一次 Parent 构造函数，并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。</li>
<li>与此同时，原型链还能保持不变，还能够正常使用 instanceof 和 isPrototypeOf。</li>
</ul>
<h3> 缺点：</h3>
<ul>
<li>相对比较复杂。</li>
</ul>
<h2> 参考</h2>
<p><a href="https://blog.touchczy.top/#/JavaScript/Js%E7%BB%A7%E6%89%BF%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F?id=js%E7%BB%A7%E6%89%BF%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F" target="_blank" rel="noopener noreferrer">Js继承的实现方式 (touchczy.top)</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>前端中的事件流</title>
      <link>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/03-%E5%89%8D%E7%AB%AF%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E6%B5%81.html</link>
      <guid>https://v-blog.yyshino.top/front_end_interview/1-3JavaScript/03-%E5%89%8D%E7%AB%AF%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E6%B5%81.html</guid>
      <source url="https://v-blog.yyshino.top/rss.xml">前端中的事件流</source>
      <description>前端中的事件流</description>
      <category>JS</category>
      <pubDate>Mon, 24 Apr 2023 20:16:23 GMT</pubDate>
      <content:encoded><![CDATA[<h2> 前端中的事件流</h2>
<p>HTML 中与 javascript 交互是通过事件驱动来实现的，例如鼠标点击事件 onclick、页面 的滚动事件 onscroll 等等，可以向文档或者文档中的元素添加事件侦听器来预订事件。 想要知道这些事件是在什么时候进行调用的，就需要了解一下“事件流”的概念。</p>
<h2> JS事件流模型</h2>
<p><strong>事件捕获</strong><code>Event Capturing</code>是一种从上而下的传播方式，以<code>click</code>事件为例，其会从最外层根节向内传播到达点击的节点，为从最外层节点逐渐向内传播直到目标节点的方式。
<strong>事件冒泡</strong><code>Event Bubbling</code>是一种从下往上的传播方式，同样以<code>click</code>事件为例，事件最开始由点击的节点，然后逐渐向上传播直至最高层节点。</p>
<h2> DOM0级模型</h2>
<p>也称为原始事件模型，这种方式较为简单且兼容所有浏览器，但是却将界面与逻辑耦合在一起，可维护性差。</p>
<h2> IE事件模型</h2>
<p><code>IE8</code>及之前的版本是不支持捕获事件的，<code>IE</code>事件模型共有两个过程:
<strong>事件处理阶段</strong><code>target phase</code>，事件到达目标元素, 触发目标元素的监听事件。
<strong>事件冒泡阶段</strong><code>bubbling phase</code>事件从目标元素冒泡到<code>document</code>，依次执行经过的节点绑定的事件。</p>
<h2> DOM2级模型（）</h2>
<p>什么是事件流：事件流描述的是从页面中接收事件的顺序,<strong>DOM2 级事件流</strong>包括下面几个 阶段。</p>
<ul>
<li><strong>事件捕获阶段</strong></li>
<li><strong>处于目标阶段</strong></li>
<li><strong>事件冒泡阶段</strong></li>
</ul>
<p>addEventListener：addEventListener 是 DOM2 级事件新增的指定事件处理程序的操作， 这个方法接收 3 个参数：要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true，表示在捕获阶段调用事件处理程序；如果是 false，表示 在冒泡阶段调用事件处理程序。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>