<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>博客</title><description>博客|江辰|独立开发者|前网易高级开发工程师</description><link>https://github.com/xuya227939/</link><item><title>创业艰难，道阻且长</title><link>https://github.com/posts/%E5%88%9B%E4%B8%9A%E8%89%B0%E9%9A%BE%E9%81%93%E9%98%BB%E4%B8%94%E9%95%BF/</link><guid isPermaLink="true">https://github.com/posts/%E5%88%9B%E4%B8%9A%E8%89%B0%E9%9A%BE%E9%81%93%E9%98%BB%E4%B8%94%E9%95%BF/</guid><description>创业真的好难！时间过得飞快，转眼半年多了。</description><pubDate>Sun, 13 Jul 2025 15:39:34 GMT</pubDate><content:encoded>&lt;p&gt;创业真的好难！时间过得飞快，转眼半年多了。&lt;/p&gt;
&lt;p&gt;去年 9 月离职后，先放松了几个月，11 月开始动手做第一款产品 —— SnapVee 社媒提取器。
初衷是帮助内容创作者更高效地获取社交媒体上的视频、字幕、封面图等信息。&lt;/p&gt;
&lt;p&gt;到了 3 月，决定继续深入做下去，陆续加入了音频提取、字幕翻译、视频总结等功能。但实际效果并不理想，用户只把它当作一个「社媒下载工具」，难以支撑持续增长与付费——而这类工具，竞品多、免费多、头部强（比如 BD 下载器）。&lt;/p&gt;
&lt;p&gt;我尝试做差异化，但没有找到突破口。
最后决定砍掉所有扩展功能，只保留最基础的提取功能，免费开放。&lt;/p&gt;
&lt;p&gt;4 月解散团队。
5 月开始构思新的方向——一个端到端的社媒创作链路，覆盖：脚本生成、图文创作、视频生成、数据分析，全链路闭环。&lt;/p&gt;
&lt;p&gt;刚开始从「AI Agent 自动生成文案」切入，思路类似天工智能体 —— 用户提问 + 辅助信息输入，自动生成社媒文案。但很快发现一个关键问题：「去 AI 味很难」&lt;/p&gt;
&lt;p&gt;6 月开始做深入竞品调研，发现：你想做的事，已经有人在做，而且不止一家，有的体验比你好、功能比你全、团队还比你大。&lt;/p&gt;
&lt;p&gt;这时候，我有点道心破碎了。&lt;/p&gt;
&lt;p&gt;在做产品这条路上，有很长一段时间，你是一个人在黑暗中走，没有用户反馈、没有数据指引，也不知道自己是不是在往对的方向走。&lt;/p&gt;
&lt;p&gt;我自认为，我在心性，心态，坚韧，思维上还算不错了，但在创业面前，不值一提，于是，摆烂了一段时间。&lt;/p&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>创业</category><author>江辰</author></item><item><title>串串房维权</title><link>https://github.com/posts/%E4%B8%B2%E4%B8%B2%E6%88%BF%E7%BB%B4%E6%9D%83/</link><guid isPermaLink="true">https://github.com/posts/%E4%B8%B2%E4%B8%B2%E6%88%BF%E7%BB%B4%E6%9D%83/</guid><pubDate>Sat, 27 Jul 2024 09:41:34 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/6dd73bae-13c2-4452-9d22-ccdd9489a1f9.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;五月底租了一个串串房，其中经历了漫长的维权过程。想了想，发出来，给大家分享下。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;事件起因&lt;/h2&gt;
&lt;p&gt;五月底，本人通过某房地产中介公司，租了滨浦新村五苑的房子，中介带看房的当天，觉得还可以。没有啥刺鼻的味道，装修也都是新的（画重点，此处伏笔）。第二天和房东约好时间，付了 7200 租金 + 1800 的中介费，定好房子。房租 3600/月。&lt;/p&gt;
&lt;h2&gt;事情经过&lt;/h2&gt;
&lt;p&gt;六月第一周，周末搬家，早上搬完的，搬完家的当天下午，在房间里待了一会儿，感觉味道非常刺鼻！本人和表哥都有预感，该不会是串串房吧！仔细闻了下，是家具引起的刺鼻气味（二手家具，质量堪忧），注意：甲醛无色无味哦！后面，再次确认中介这房装修了多久，中介说装修了蛮久，就是不知道具体时间。然后问了房东，房东说刚装修两个多月！刚装修两个多月就出租，坑人阿！&lt;/p&gt;
&lt;h2&gt;维权&lt;/h2&gt;
&lt;p&gt;房东，先是跟房东协商，反复沟通。甚至，我都打算买甲醛测量了（注意，先跟房东沟通哦，以免不必要的资金损耗），跟房东讲述其中利害关系，房东最终同意，退还租金&lt;/p&gt;
&lt;p&gt;中介，再就是找到房地产中介协商，第一次和第二次我都是去线下协商，协商不通过，不可能退还中介费。我就拨打 12345 维权，12345 效率极高（此处点个赞），房地产那边也是同意退中介费了（电话沟通），直接跟中介公司店长沟通，中介公司店长一直拖着，不肯给，多次电话沟通，答应退还，并给了相关退还时间。然后，一二再，再二三的拖着，实在忍无可忍，我就发出警告！发出警告没多久，加了我微信，把中介费推给我了！&lt;/p&gt;
&lt;p&gt;最终是在七月第三周，完成了整个维权过程。&lt;/p&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;维权过程中，不要害怕，要敢于维权哦&lt;/li&gt;
&lt;li&gt;维权过程中，时刻录音（保留好证据&lt;/li&gt;
&lt;li&gt;维权过程中，一定要理性，冷静沟通，跟对方阐述其中利害关系&lt;/li&gt;
&lt;li&gt;多次沟通无效，申请 12345 热线的帮助，只要是合理诉求，都是会受理的哦&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/0d425553-ef1a-4b4c-b466-67f981b4820f.jpg&quot; alt=&quot;房东&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/a54bc318-011f-401b-ba01-066d4d49214f.jpg&quot; alt=&quot;中介公司&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/35727bd6-39d2-4dd8-aff7-76778c27ca40.jpg&quot; alt=&quot;中介公司&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/0c2b8df4-faff-4877-b071-e7ebd58e6557.jpg&quot; alt=&quot;中介公司&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/f8ace59b-eed3-4f1f-b893-87bdc0d54197.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/188f1b49-0563-4adc-a5c9-11565f0e410a.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/3f401d1f-a2d5-4cea-9fcc-3b319c2502d2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/45032487-465b-4bc5-86d4-bf7a8247ac38.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/6e51c80a-0939-4c9c-940f-c59af8f056f6.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content:encoded><category>串串房</category><category>12345</category><category>甲醛房</category><author>江辰</author></item><item><title>【从前端入门到全栈】Node.js 之核心概念</title><link>https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88nodejs-%E4%B9%8B%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5/</link><guid isPermaLink="true">https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88nodejs-%E4%B9%8B%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5/</guid><pubDate>Tue, 16 Apr 2024 10:40:31 GMT</pubDate><content:encoded>&lt;h2&gt;从前端入门到全栈-系列介绍&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;你会学到什么？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可能学不到什么东西，该系列是作者本人工作和学习积累，用于复习&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;作者介绍&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;江辰，前网易高级前端工程师&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;系列介绍&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在的 Web 前端已经离不开 Node.js，我们广泛使用的 Babel、Webpack、工程化都是基于 Node 的，各个互联网大厂也早已大规模落地 Node 项目。因此，想要成为一名优秀的前端工程师，提升个人能力、进入大厂，掌握 Node.js 技术非常有必要。&lt;/p&gt;
&lt;p&gt;Node.js 不仅可以用来完善手头的开发环境，实现减少代码和 HTTP 请求，降低网页请求消耗的时间，提升服务质量。还可以扩展前端工程师的工作领域，用作 HTTP 服务，让前端也能完成一部分后端的工作，减少对后端的依赖，降低沟通成本，提升开发效率。&lt;/p&gt;
&lt;p&gt;而且，Node.js 和浏览器的 JavaScript 只是运行时环境不同，编程语言都是 JavaScript ，所以掌握 Node.js 基础对前端工程师来说并不难，难点在于应用。由于浏览器的 JavaScript 主要是负责内容呈现与交互，而 Node.js 应用领域包括工具开发、Web 服务开发和客户端开发，这些都与传统的 Web 前端领域不一样，用来应对不同的问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;适宜人群&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;对 Node.js 感兴趣的 JavaScript 程序员&lt;/li&gt;
&lt;li&gt;希望拓展知识边界，往全栈方向发展的前端工程师&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Node（正式名称 Node.js）是一个开源的、跨平台的运行时环境，有了它，开发人员可以使用 JavaScript 创建各种服务器端工具和应用程序。 此运行时主要用于浏览器上下文之外（即可以直接运行于计算机或服务器操作系统上）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;阻塞和非阻塞 I/O&lt;/h2&gt;
&lt;h3&gt;非阻塞 I/O&lt;/h3&gt;
&lt;p&gt;在非阻塞 I/O 中，如果数据不可用（例如，文件还未准备好写入，或网络包尚未到达），I/O 调用会立即返回，线程可以继续执行其他任务。线程需要定期检查 I/O 操作的完成情况。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const fs = require(&quot;fs&quot;);

fs.readFile(&apos;input.txt&apos;, function (err, data) {
    if (err) return console.error(err);
    console.log(data.toString());
});

console.log(&quot;程序执行结束!&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;阻塞 IO&lt;/h3&gt;
&lt;p&gt;在阻塞 I/O 中，调用 I/O 操作的线程会被挂起，直到 I/O 操作完成。在此期间，线程不能做任何其他工作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const fs = require(&quot;fs&quot;);

const data = fs.readFileSync(&apos;input.txt&apos;);

console.log(data.toString());
console.log(&quot;程序执行结束!&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;事件循环&lt;/h2&gt;
&lt;p&gt;Node.js 的事件循环是其工作原理的核心。事件循环允许 Node.js 进行非阻塞 I/O 操作，即使 JavaScript 是单线程的。每当在 Node.js 中发生一个 I/O 事件（如 API 调用返回结果，文件读取完成等）时，会触发回调函数，加入到事件循环等待执行，任务可以根据它们在事件循环中的调度方式和执行时间被分为微任务（microtasks）和宏任务（macrotasks）。&lt;/p&gt;
&lt;h3&gt;微任务（Microtasks）&lt;/h3&gt;
&lt;p&gt;微任务是在当前执行栈剩余任务执行完毕后立即执行的，这意味着他们会在事件循环的下一阶段之前执行。微任务通过他们的执行机制确保了任务执行的顺序和预测性。&lt;/p&gt;
&lt;p&gt;例子包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;process.nextTick()&lt;/code&gt; （这是 Node.js 特有的，其他环境没有）&lt;/li&gt;
&lt;li&gt;Promises 的回调，例如 &lt;code&gt;Promise.then()&lt;/code&gt;, &lt;code&gt;Promise.catch()&lt;/code&gt;, &lt;code&gt;Promise.finally()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;queueMicrotask()&lt;/code&gt;（这是一个将任务排入微任务队列的方法）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;微任务的特点是它们的任务优先级很高，会在当前事件循环迭代的宏任务之后，下一迭代宏任务之前执行。这也意味着微任务的执行可能会导致阻塞宏任务的开始，如果微任务连续产生（比如，一个微任务在执行时创建了另一个微任务），可能会导致 I/O 饥饿。&lt;/p&gt;
&lt;h3&gt;宏任务（Macrotasks）&lt;/h3&gt;
&lt;p&gt;宏任务通常表示一个封装了一个较大操作的任务，这些任务将会按照队列的顺序一个接一个地执行。宏任务之间的间隔，是处理浏览器渲染和用户互动操作的机会。&lt;/p&gt;
&lt;p&gt;例子包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setTimeout()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setInterval()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setImmediate()&lt;/code&gt;（Node.js 特有）&lt;/li&gt;
&lt;li&gt;I/O 操作，如读写文件、网络请求等&lt;/li&gt;
&lt;li&gt;用户交互，如点击、滚动事件等&lt;/li&gt;
&lt;li&gt;定时器&lt;/li&gt;
&lt;li&gt;&lt;code&gt;requestAnimationFrame()&lt;/code&gt;（浏览器特有）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每一个事件循环迭代通常只执行一个宏任务。在一个宏任务执行完成后，事件循环会处理所有已经排队的微任务，之后才会开始新的宏任务。&lt;/p&gt;
&lt;h3&gt;区别&lt;/h3&gt;
&lt;p&gt;关键区别在于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;微任务的执行时间是在当前宏任务执行完后，当前事件循环阶段结束前，它们拥有更高的优先级并且是在同一事件循环迭代中执行的。&lt;/li&gt;
&lt;li&gt;宏任务每次事件循环迭代会新开始一个，它们的执行可能会被微任务延迟。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种区分可以确保异步操作的适当处理顺序，微任务提供了一种在当前操作完成立即处理任务的方法，而宏任务则安排了一种更为分散的任务处理方式。对于开发者来说，理解这两者的区别有助于编写更高效、响应更快的应用程序。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(&quot;首先执行&quot;);

// 在定时器队列中安排一个宏任务（macro-task）
setTimeout(() =&amp;gt; {
  console.log(&quot;由 setTimeout() 安排的宏任务&quot;);
}, 0);

// 在检查队列中安排一个宏任务
setImmediate(() =&amp;gt; {
  console.log(&quot;由 setImmediate() 安排的宏任务&quot;);
});

// 安排一个微任务（micro-task），它会在当前操作结束后立即执行
process.nextTick(() =&amp;gt; {
  console.log(&quot;由 process.nextTick() 安排的微任务&quot;);
});

console.log(&quot;最后执行&quot;);

// 执行结果
首先执行
最后执行
由 process.nextTick() 安排的微任务
由 setImmediate() 安排的宏任务
由 setTimeout() 安排的宏任务
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;和 Event Loop 的区别&lt;/h3&gt;
&lt;p&gt;两者的主要区别在于它们的用途：Node.js 事件循环主要处理非阻塞 I/O 操作，而浏览器事件循环主要关注 UI 渲染和用户交互。因此，虽然两者都是事件循环，但它们由于环境的特性，采取了略有不同的策略。&lt;/p&gt;
&lt;h2&gt;中间件&lt;/h2&gt;
&lt;p&gt;Node.js 中中间件是一个非常关键的概念，特别是在使用如 Express.js 这类的框架构建 web 应用时。中间件基本上是一个函数，它可以访问请求对象（request），响应对象（response），和 web 应用中处理请求-响应周期的流程中的下一个中间件函数，通常以 &lt;code&gt;next&lt;/code&gt; 变量表示。&lt;/p&gt;
&lt;p&gt;以下是中间件的一些基本点和它们的工作原理：&lt;/p&gt;
&lt;h3&gt;接入请求-响应周期&lt;/h3&gt;
&lt;p&gt;中间件的主要用途是在服务器收到请求和发送响应之间的某个时点执行一些代码，也就是说，它们介于请求到达服务器和最终发送响应的过程之间。&lt;/p&gt;
&lt;h3&gt;修改请求和响应&lt;/h3&gt;
&lt;p&gt;中间件能够修改请求和响应对象。它们可以添加头信息、处理身份验证、进行日志记录、设置 cookies、解析请求体中的数据等等。&lt;/p&gt;
&lt;h3&gt;分类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;应用级中间件&lt;/strong&gt;：绑定到 &lt;code&gt;app&lt;/code&gt; 实例的中间件，可用于任何请求的路径和方法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路由级中间件&lt;/strong&gt;：与应用级中间件相似，但它绑定于 &lt;code&gt;express.Router()&lt;/code&gt; 的实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误处理中间件&lt;/strong&gt;：专门用来处理应用中发生的错误。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内置中间件&lt;/strong&gt;：Express 内置的中间件，如 &lt;code&gt;express.static&lt;/code&gt; 用于提供静态资源，或 &lt;code&gt;express.json&lt;/code&gt; 用于解析请求体中的 JSON。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第三方中间件&lt;/strong&gt;：由社区提供的中间件，需要通过 npm 安装，用来添加更多功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用方法&lt;/h3&gt;
&lt;p&gt;中间件可以用 &lt;code&gt;app.use()&lt;/code&gt; 和 &lt;code&gt;app.METHOD()&lt;/code&gt;（其中 METHOD 是 HTTP 动词，例如 GET、POST 等）来应用。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const app = express();

// 应用级中间件
app.use(function (req, res, next) {
  console.log(&quot;时间：&quot;, Date.now());
  next();
});

// 路由级中间件
app.get(
  &quot;/user/:id&quot;,
  function (req, res, next) {
    // 如果用户 ID 是 0，跳到下一个路由
    if (req.params.id === &quot;0&quot;) next(&quot;route&quot;);
    // 否则将控制权传递给堆栈中的下一个中间件
    else next();
  },
  function (req, res, next) {
    // 渲染普通页面
    res.send(&quot;常规用户&quot;);
  }
);

// 错误处理中间件
app.use(function (err, req, res, next) {
  console.error(err.stack);
  res.status(500).send(&quot;Something broke!&quot;);
});

const server = app.listen(3000, () =&amp;gt;
  console.log(&quot;Server running on port 3000&quot;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;顺序很重要&lt;/h3&gt;
&lt;p&gt;中间件的执行顺序取决于它们在代码中的顺序。&lt;code&gt;next()&lt;/code&gt; 函数被调用时，执行会传递给堆栈中的下一个中间件。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;next&lt;/code&gt; 和路由完成&lt;/h3&gt;
&lt;p&gt;当中间件完成时，它必须调用 &lt;code&gt;next()&lt;/code&gt; 来传递控制权给下一个中间件，除非它完全结束了请求-响应周期。否则，请求将会被挂起。&lt;/p&gt;
&lt;p&gt;理解中间件如何工作，以及如何去使用它们，对于构建有效和功能丰富的 web 应用程序是非常有帮助的。通过组合使用多个中间件，你可以为你的应用程序构建可重用、模块化的功能部件。&lt;/p&gt;
&lt;h2&gt;Buffer 和 Stream&lt;/h2&gt;
&lt;h3&gt;Buffer&lt;/h3&gt;
&lt;p&gt;在 Node.js 中，Buffer 是一个用于处理二进制数据流的类。在早期的 JavaScript 中，并没有用于处理原始数据的八位字节序列的机制。但在服务器端，你需要处理像 TCP 流或文件系统等涉及二进制数据的操作。Buffer 类为这些二进制数据提供了一个接口。&lt;/p&gt;
&lt;p&gt;Buffer 是一个类似于数组的对象，它可以存储任意类型的字节。因为 JavaScript 的字符串是以 UTF-16 编码，所以如果要处理像 UTF-8、Base64、二进制数据或其他任何格式的字符串，使用 Buffer 是非常需要的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 简单的 Buffer 示例
const buf = Buffer.from(&quot;Hello, World!&quot;);

console.log(buf); // 打印出 Buffer 对象
console.log(buf.toString()); // 将 Buffer 对象转换为字符串
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Stream&lt;/h3&gt;
&lt;p&gt;在 Node.js 中，Stream 是一个抽象接口，被 Node 中的许多对象实现。流可以是可读的、可写的，或者既可读又可写。它们是处理流式数据的优雅方式，比如文件的读写、网络通信等。
流处理数据时不必等待所有的数据下载完毕，而是一旦有数据就开始处理，这是非常高效的数据处理方法，因为它减少了内存的使用，并且在某些情况下可以减少总体的处理时间。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const fs = require(&apos;fs&apos;);
const readableStream = fs.createReadStream(&apos;file.txt&apos;);
const writableStream = fs.createWriteStream(&apos;new-file.txt&apos;);

readableStream.on(&apos;data&apos;, function(chunk) {
    writableStream.write(chunk);
});

readableStream.on(&apos;end&apos;, function() {
    writableStream.end();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;单线程&lt;/h2&gt;
&lt;p&gt;Node.js 被认为是单线程的，这是因为 JavaScript 在 Node.js 环境中的执行模型基于一个主单线程，单线程意味着有一个主线程来处理所有的 JavaScript 代码。所有的异步操作（例如 I/O、网络请求、文件系统操作）在开始时被提交到主线程之外的地方，在 Node.js 中通常由底层的 C++ APIs 来处理这些操作。一旦这些异步操作完成（或部分完成，比如流数据），它们的回调函数将返回到事件队列中排队等待执行。&lt;/p&gt;
&lt;h2&gt;多线程和并发&lt;/h2&gt;
&lt;p&gt;虽然 Node.js 主线程是单线程的，但这并不意味着 Node.js 不能执行多线程的操作或者处理并发。通过 Node.js 的 child_process 模块，你可以启动子进程来执行 CPU 密集型任务。此外，Node.js v10.5.0 以后引入了 worker_threads 模块，允许直接在 Node 应用程序中使用多个线程。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;文章同步更新平台：掘金、CSDN、知乎、思否、博客，公众号（野生程序猿江辰）
我的联系方式，v：Jiang9684，欢迎和我一起学习交流&lt;/p&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>从前端入门到全栈</category><author>江辰</author></item><item><title>Ubuntu 下安装 WeChat</title><link>https://github.com/posts/ubuntu-%E4%B8%8B%E5%AE%89%E8%A3%85-wechat/</link><guid isPermaLink="true">https://github.com/posts/ubuntu-%E4%B8%8B%E5%AE%89%E8%A3%85-wechat/</guid><pubDate>Thu, 14 Mar 2024 16:50:19 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;工作电脑上装了 Ubuntu 系统，如何安装微信成了问题。目前业界主流有两种方案，&lt;a href=&quot;https://www.yydnas.cn/2023/08/2023.08.16-Ubuntu%E5%AE%89%E8%A3%85%E5%BE%AE%E4%BF%A1%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%B3%95/index.html&quot;&gt;参考 Ubuntu 安装微信的两种方法
&lt;/a&gt;，我选择的是 Wine 安装方式，但 BUG 太多了（比如无法正常截图，表情无法正常显示，无法放入图片等等），无奈，看看 Linux 社区有没有微信发行版&lt;/p&gt;
&lt;p&gt;最近 WeChat 新版本现已上架统信应用商店，UOS 用户可直接在统信应用商店搜索“微信（Universal）”下载体验！（支持 AMD64/ARM/Loongarch 三大主流架构）&lt;/p&gt;
&lt;p&gt;本次微信（Universal）UOS 版是基于原生跨平台方案开展的一次大型版本重构与更新，以提高软件功能开发与迭代速度，旨在逐步实现微信 Windows/Mac/Linux 版本在功能与更新节奏保持一致。&lt;/p&gt;
&lt;p&gt;什么？ WeChat 发行了 Linux Beta 版？（第三方发布的）&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;开始尝试&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://aur.archlinux.org/packages/wechat-beta-bwrap&quot;&gt;beta 安装地址&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 安装下载的软件包
$ sudo dpkg -i bf103d215f344e4e8d0e17a7a37eebe8.deb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装过程中，出现错误，碰到了 &lt;code&gt;deepin-elf-verify &amp;gt;= 1.2.0&lt;/code&gt;，&lt;code&gt;libssl &amp;gt;= 1.1.0&lt;/code&gt;，两个软件版本要求版本号&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(正在读取数据库 ... 系统当前共安装有 226651 个文件和目录。)
准备解压 e3546883de40401381ecd621a74d0cfe.deb  ...
正在解压 com.tencent.weixin (2.1.5) 并覆盖 (2.1.5) ...
dpkg: 依赖关系问题使得 com.tencent.weixin 的配置工作不能继续：
 com.tencent.weixin 依赖于 deepin-elf-verify (&amp;gt;= 1.1.1-1)；然而：
  软件包 deepin-elf-verify 尚未配置。

dpkg: 处理软件包 com.tencent.weixin (--install)时出错：
 依赖关系问题 - 仍未被配置
正在处理用于 mailcap (3.70+nmu1ubuntu1) 的触发器 ...
正在处理用于 gnome-menus (3.36.0-1ubuntu3) 的触发器 ...
正在处理用于 desktop-file-utils (0.26-1ubuntu3) 的触发器 ...
正在处理用于 hicolor-icon-theme (0.17-2) 的触发器 ...
正在处理用于 libc-bin (2.35-0ubuntu3.6) 的触发器 ...
在处理时有错误发生：
 com.tencent.weixin
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;解决 deepin-elf-verify&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://mirrors.bfsu.edu.cn/deepin/pool/main/d/deepin-elf-verify/&quot;&gt;deepin-elf-verify.deb&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;解决 libssl&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/&quot;&gt;libssl1.1-udeb_1.1.1f-1ubuntu2_amd64.udeb&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;更新下两个软件的版本就行&lt;/p&gt;
&lt;p&gt;安装完之后，就会有相应的图标&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/9a8f3add-28fc-4380-938b-7293758bd6ff.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;微信扫码之后，TM 反复登录，我屌！！！随后报错，有朋友能告诉我如何解决吗？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/1a004c5d-0a67-4641-ae29-dc65f953abc6.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;cnm 张一龙&lt;/p&gt;
&lt;p&gt;文章同步更新平台：掘金、CSDN、知乎、思否、博客，公众号（野生程序猿江辰）
我的联系方式，v：Jiang9684，欢迎和我一起学习交流&lt;/p&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>Ubuntu</category><category>Wechat</category><author>江辰</author></item><item><title>toB开发范式</title><link>https://github.com/posts/tob%E5%BC%80%E5%8F%91%E8%8C%83%E5%BC%8F/</link><guid isPermaLink="true">https://github.com/posts/tob%E5%BC%80%E5%8F%91%E8%8C%83%E5%BC%8F/</guid><pubDate>Sun, 10 Mar 2024 17:46:39 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;B 端开发，也被称为后台开发或者企业级开发，是针对企业或者组织的业务需求进行的软件开发。在 B 端开发中，我们通常关注的是系统的功能性、稳定性、可扩展性以及安全性，从面向过程编程 -&amp;gt; 面向对象编程 + 组合式编程&lt;/p&gt;
&lt;p&gt;以下是 B 端开发体系的一些主要元素：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;技术栈&lt;/strong&gt;：B 端开发通常涉及到复杂的技术栈，包括编程语言（ JavaScript ）、框架（React）等等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;架构设计&lt;/strong&gt;：因为 B 端业务的复杂性，所以架构设计尤为重要。包括如何有效地分层（视图层，逻辑层，服务层）、如何进行微服务化、如何保证系统稳定性等等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：B 端开发需要关注系统的性能。这不仅包括服务响应速度的提升，还包括如何在高并发环境下保持系统的稳定性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;安全&lt;/strong&gt;：B 端开发需要对安全进行严格的考虑，包括数据的加密存储、传输安全、权限控制等等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;设计模式&lt;/strong&gt;：针对一些复杂的场景设计，设计模式的建立，往往会带来可靠的代码维护和扩展。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;其他&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;理解和掌握 B 端开发体系需要一点时间，但是这对于追求成为一名优秀的前端开发者来说，是非常必要的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/8610a878-43dd-4113-9485-cc9f17b8fcf6.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们以典型的 B 端页面组成为例，上层搜索组件，下层列表展示 + 分页&lt;/p&gt;
&lt;h2&gt;技术栈&lt;/h2&gt;
&lt;p&gt;iCE + React + Zustand + SWR + Ant Design + react-intl&lt;/p&gt;
&lt;h2&gt;架构设计&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/7e7fb884-dc58-4b23-acd0-8cdd64a2aad3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;视图层&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// Container 容器
/*
 * @Author: Jiang
 * @Date: 2024-03-05 16:09:33
 * @Last Modified by: Jiang
 * @Last Modified time: 2024-04-27 17:09:04
 */

import { useState } from &quot;react&quot;;
import Search from &quot;./components/search/&quot;;
import List from &quot;./components/list&quot;;
import styles from &quot;./index.module.less&quot;;

const Container = () =&amp;gt; {
  const [condition, setCondition] = useState(null);
  const { setStoreFilterConditions, setAdvConditionGroups } = tradeStore();

  return (
    &amp;lt;div className={styles.logPageView}&amp;gt;
      &amp;lt;Search /&amp;gt;
      &amp;lt;List /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Container;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/*
 * 列表组件
 * @Author: Jiang
 * @Date: 2024-03-05 16:09:33
 * @Last Modified by: Jiang
 * @Last Modified time: 2024-03-05 16:10:03
 */

import { useState } from &quot;react&quot;;
import Search from &quot;./components/search&quot;;
import List from &quot;./components/list&quot;;
import styles from &quot;./index.module.less&quot;;

const List = () =&amp;gt; {
  const [condition, setCondition] = useState(null);
  const { setStoreFilterConditions, setAdvConditionGroups } = tradeStore();

  return (
    &amp;lt;div className={styles.logPageView}&amp;gt;
      &amp;lt;div&amp;gt;123&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default List;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;逻辑层&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { create } from &quot;zustand&quot;;
import containerService from &quot;@/services/system/cacheService&quot;;

const initialState = {
  tabCondition: &quot;&quot;,
};

export const containerStore = create()((set) =&amp;gt; ({
  ...initialState,
  getLocation: ({ location }) =&amp;gt; {
    set((state) =&amp;gt; ({ ...state, tabCondition: location }));
  },
  reset: () =&amp;gt; {
    set(initialState);
  },
}));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;服务层&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;export default {
  async getQueryField(param) {
    const queryParam = {
      catalog: param.catalog,
      codes:
        typeof param.codes === &quot;string&quot; ? param.codes.split(&quot;,&quot;) : param.codes,
    };
    const url = `${config.tradecURL}${config.apiVersion}/databases/query-fields`;
    const data = await request.post(url, queryParam);
    return query_field_format(data);
  },
  // 修改自定义列
  async updateDisplayField(param) {
    let res = await request.post(
      `${config.tradecURL}${config.apiVersion}/databases/custom-query-columns`,
      param
    );
    return {
      list: displayFieldFormat(res.columns, res.id),
      type: res.type ? res.type : &quot;COMPACT&quot;,
    };
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;性能优化&lt;/h2&gt;
&lt;p&gt;核心，避免重复渲染，useState，useRef，useEffect，这种常用的狗子，很多都搞不清楚&lt;/p&gt;
&lt;h3&gt;useState&lt;/h3&gt;
&lt;p&gt;useState 是一个 React Hook，可让您向组件添加状态变量，重点是会重新渲染页面&lt;/p&gt;
&lt;h3&gt;useRef&lt;/h3&gt;
&lt;p&gt;useRef 它返回一个可变的 ref 对象，其 .current 属性被初始化为传入的参数（initialValue）。返回的 ref 对象在组件的整个生命周期内保持不变。&lt;/p&gt;
&lt;h3&gt;useEffect&lt;/h3&gt;
&lt;p&gt;在&lt;code&gt;useEffect&lt;/code&gt;的依赖数组中，使用的是浅比较来决定是否触发副作用。也就是说，实际比较的是引用，而不是引用的内容。如果指定为数组或对象的引用，那么只有当引用改变（指向一个新的对象或数组）时，才会触发副作用。即使新旧对象或数组的内容完全一样，只要引用不相同，也会触发副作用。&lt;/p&gt;
&lt;h2&gt;安全&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;界面安全（手机号隐私，用户隐私）&lt;/li&gt;
&lt;li&gt;Http 请求明文&lt;/li&gt;
&lt;li&gt;Cookie 注入&lt;/li&gt;
&lt;li&gt;CSV 脚本注入&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;设计模式&lt;/h2&gt;
&lt;p&gt;推荐一本书籍，人人都懂设计模式：从生活中领悟设计模式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;订阅者模式&lt;/li&gt;
&lt;li&gt;组合模式&lt;/li&gt;
&lt;li&gt;单例模式&lt;/li&gt;
&lt;li&gt;代理模式&lt;/li&gt;
&lt;li&gt;策略模式&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;h3&gt;UI&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;热区点击&lt;/li&gt;
&lt;li&gt;中英文，文字间距排版&lt;/li&gt;
&lt;li&gt;鼠标样式（对于链接，鼠标移动上去的小手）&lt;/li&gt;
&lt;li&gt;按钮 Loading&lt;/li&gt;
&lt;li&gt;占位符，比如没有数据的时候，显示 - ，特别是表格上的数据&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;逻辑&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;翻页，比如翻到第二页，删除所有选项，请求的仍然是第二页的数据&lt;/li&gt;
&lt;li&gt;代码做好降级方案，比如对象或数组，做判空处理&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;toB 端开发，其实很简单&lt;/p&gt;
&lt;p&gt;文章同步更新平台：掘金、CSDN、知乎、思否、博客，公众号（野生程序猿江辰）
我的联系方式，v：Jiang9684，欢迎和我一起学习交流&lt;/p&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>React</category><category>toB</category><category>TOB</category><author>江辰</author></item><item><title>【软件工程管理】技术负债</title><link>https://github.com/posts/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B%E7%AE%A1%E7%90%86%E6%8A%80%E6%9C%AF%E8%B4%9F%E5%80%BA/</link><guid isPermaLink="true">https://github.com/posts/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B%E7%AE%A1%E7%90%86%E6%8A%80%E6%9C%AF%E8%B4%9F%E5%80%BA/</guid><pubDate>Sun, 03 Mar 2024 15:49:22 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://docs.pingcode.com/blog/software-development-management/55292.html&quot;&gt;原文地址&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在本篇指南中，你将会学习到如何预防、修复和管理技术债务的方法，了解技术债务的种类、技术债务的成本、关键指标以及处理技术债务的工具。&lt;/p&gt;
&lt;p&gt;“技术债务”在某些人眼中可能有不太好的形象，这和学生贷款或房贷在人们心目中的形象有些相似。但是，与财务贷款类似，有时选择接受技术债务也可能是一个明智的决策。&lt;/p&gt;
&lt;p&gt;我们将会解答以下几个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;技术债务到底是什么？&lt;/li&gt;
&lt;li&gt;技术债务有哪些类型？&lt;/li&gt;
&lt;li&gt;为什么会有技术债务？&lt;/li&gt;
&lt;li&gt;技术债务会带来哪些影响？&lt;/li&gt;
&lt;li&gt;团队应当如何管理技术债务？&lt;/li&gt;
&lt;li&gt;管理技术债务的最佳工具是什么？&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;什么是技术债务？&lt;/h2&gt;
&lt;p&gt;技术债务，可以简单地理解为，为了能更快速地发布产品，我们选择接受的某些技术上的妥协或短期解决方案的成本。&lt;/p&gt;
&lt;p&gt;简而言之，技术债务（也可称之为技术负担或代码债务）是软件开发过程中不可避免的一部分。&lt;/p&gt;
&lt;p&gt;重要的是，我们应当在积累这种“债务”时要小心谨慎，当我们选择接受它时，要确保有有效的管理方式，以及一个清晰的偿还计划。&lt;/p&gt;
&lt;p&gt;基于上述内容，我们可以得到一个技术债务的简化定义：技术债务就是那些你认为对你构成负担的代码部分。&lt;/p&gt;
&lt;h2&gt;关于预防技术债务的重要说明&lt;/h2&gt;
&lt;p&gt;许多人问我们：“应该如何预防技术债务？”&lt;/p&gt;
&lt;p&gt;但这个问题往往并不是最核心的问题。这是因为这个问题背后的默认思维是：技术债务就是不好的。&lt;/p&gt;
&lt;p&gt;但我希望大家不要误解，确实，技术债务在某些情况下可能是有害的。但如果我们把它比作经济中的债务，可以发现有时候为了某些目的，选择贷款是必要的。例如，我们可能需要这笔钱来改善我们的住房或者开展新的生意。很多时候，这样的贷款决策是明智的。&lt;/p&gt;
&lt;p&gt;所以，我们更应该问的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“如何避免积累不好的技术债务？”&lt;/li&gt;
&lt;li&gt;“在什么情况下，选择承受一些技术债务是明智的？”&lt;/li&gt;
&lt;li&gt;“我们该如何管理已经存在的技术债务？”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了更好地应对技术债务，我们需要深入了解它是如何产生的。为了达到这个目的，我们需要探讨技术债务的不同类型。&lt;/p&gt;
&lt;h2&gt;了解不同类型的技术债务&lt;/h2&gt;
&lt;p&gt;多年来，技术债务已经以多种方式被描述。在 2007 年，技术债务主要被划分为两大类：有意和无意之债。而到了 2014 年，这个分类扩展到了 13 种，包括但不限于架构债务、缺陷债务、人员债务和测试债务。&lt;/p&gt;
&lt;p&gt;现在，人们普遍接受的分类技术债务的方式是基于马丁·福勒（Martin Fowler）提出的“技术债务象限”。&lt;/p&gt;
&lt;h2&gt;什么是马丁·福勒的技术债务象限？&lt;/h2&gt;
&lt;p&gt;福勒的观点是，我们不应该过于关注什么算是技术债务，而是要去评估这个债务是否是在明智的情况下产生的。他基于“故意与否”和“审慎与否”的维度来定义技术债务，从而提出了四种类型的技术债务：&lt;/p&gt;
&lt;p&gt;鲁莽和有意
鲁莽和无意
谨慎和有意
谨慎和无意
当团队明确了解自己为什么要承担某些技术债务，以及如何在未来去偿还这些债务时，这通常被视为谨慎地承担技术债务。相反，如果团队没有这样的明确认识和计划，那么他们就是在鲁莽地积累技术债务。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/3e9bf894-8d32-4042-8a53-b80e8a2efd91.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;谨慎的团队会有目的地、计划性地承担技术债务，并且他们会有明确的偿还策略。而轻率的团队可能会对待他们的软件开发像是使用没有还款计划的信用卡一样。&lt;/p&gt;
&lt;p&gt;最终真正需要我们关心和警惕的技术债务，是那些没有被明确记录、没有得到妥善管理的，或者那些随着时间不断累加、造成越来越大问题的技术债务。&lt;/p&gt;
&lt;h2&gt;为什么会产生技术债务？&lt;/h2&gt;
&lt;p&gt;接下来，我们将深入探索为何团队可能会产生技术债务。&lt;/p&gt;
&lt;p&gt;有很多因素可能导致技术债务的形成，这些原因并不都与加速发布有关。例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;采用过时的技术&lt;/li&gt;
&lt;li&gt;做出错误的架构决策&lt;/li&gt;
&lt;li&gt;编写过于复杂的代码&lt;/li&gt;
&lt;li&gt;缺少足够的测试&lt;/li&gt;
&lt;li&gt;技能和经验的不足&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当技术债务没有得到妥善地追踪和管理时，它会开始成为一个问题。不追踪技术债务会导致其快速积累，带来更大的后续问题，这些问题可能具有指数级的增长。以下是一些导致技术债务积累增长的因素：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;问题的隐蔽性：代码库的知识只存在于某些工程师的脑海中，或者团队可能根本不知道某些技术问题的存在。&lt;/li&gt;
&lt;li&gt;缺乏代码质量文化：公司文化可能不鼓励人们关心技术债务或追求高代码质量。&lt;/li&gt;
&lt;li&gt;不佳的管理流程：处理技术债务的方法不当，如把所有问题都归入一个待办事项列表。&lt;/li&gt;
&lt;li&gt;时间投资不足：工程师可能会觉得很难为技术任务（例如重构）辩解所需的时间和资金投入。&lt;/li&gt;
&lt;li&gt;缺乏背景知识：代码库中的问题可能没有与相关的代码关联，使得问题难以被定位和修复。&lt;/li&gt;
&lt;li&gt;问题追踪不当：工程师可能没有适当的工具或资源来管理和追踪技术债务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所有这些问题都归结为一个核心问题：低效或低质量的问题追踪方法。这一主题稍后会进一步讨论。&lt;/p&gt;
&lt;h2&gt;了解技术债务的真正影响&lt;/h2&gt;
&lt;p&gt;技术债务不仅仅是代码问题，它对整个组织的工程资源和其他部门也有广泛的影响。&lt;/p&gt;
&lt;p&gt;如果没有适当的策略来管理技术债务，那么技术债务就会逐渐失控。一个有效的管理策略从出色的问题追踪开始。使用特定的技术债务管理工具的团队发布速度比那些没有明确技术债务策略的团队快三倍。而如果技术债务管理没有得到适当的关注和计划，那么项目的整体成本可能会增加超过 50%。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/378abf09-a015-49c1-a46d-4a5258652d4f.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技术债务会如何影响你的代码：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复杂性：增加的复杂性会放大其他问题，从而导致技术债务更快地累积。&lt;/li&gt;
&lt;li&gt;缺陷：代码中的错误会直接影响产品。&lt;/li&gt;
&lt;li&gt;代码质量：代码质量的下降会影响产品，并使技术债务更快地累积。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;技术债务会如何影响你的产品：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;功能性：如果功能不能正确工作，这会影响产品的可用性、客户的满意度和品牌的声誉。&lt;/li&gt;
&lt;li&gt;交付：如果产品或功能的交付被延迟，那么依赖于你交付的客户和其他团队（例如市场营销或销售团队）都会受到影响。&lt;/li&gt;
&lt;li&gt;停机时间：任何停机时间都会立刻损害品牌的声誉。&lt;/li&gt;
&lt;li&gt;风险：技术债务可能增加一些严重事件的风险，比如数据安全漏洞。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;技术债务会如何影响你的团队：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发速度变慢：由于工程师需要花费额外的时间来处理技术债务，所以发布新功能的速度会减慢。&lt;/li&gt;
&lt;li&gt;团队士气：工程师通常不喜欢处理技术债务，因为这会影响他们的积极性和工作满意度。&lt;/li&gt;
&lt;li&gt;留存率：如果工程师对处理技术债务感到不满意，他们更可能会选择离职，这进一步影响了团队的稳定性和人力资源部门。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;技术债务会如何影响你的业务：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户满意度：技术债务往往与客户满意度负相关，即技术债务增加时，客户满意度可能会降低。&lt;/li&gt;
&lt;li&gt;业务效率：由于技术债务，预测和计划可能变得更加困难，而且它还可能影响与产品相关的其他系统。&lt;/li&gt;
&lt;li&gt;品牌和声誉：品牌和声誉一旦受损，很难恢复到原来的状态。&lt;/li&gt;
&lt;li&gt;财务：技术债务会增加开发成本，而且由于技术债务带来的各种负面影响，企业的收入也可能会减少。&lt;/li&gt;
&lt;li&gt;生存：技术债务可能导致严重的财务和业务问题，甚至有导致破产的风险。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;技术债务问题追踪不足的迹象&lt;/h2&gt;
&lt;p&gt;要想有效管理并减少技术债务，首先我们需要对其进行追踪。&lt;/p&gt;
&lt;p&gt;追踪问题是良好的技术债务管理的核心原则。以下是一些危险信号，可能表明你需要更好地跟踪团队中的技术债务：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/e821685c-1be1-449a-a390-7175aabf703c.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果这描述的正是你的团队，不必过于担忧 —— 我们每天都与各种工程团队进行沟通，这种情况非常常见。&lt;/p&gt;
&lt;h2&gt;如何管理和减少技术债务&lt;/h2&gt;
&lt;p&gt;你的核心工程师们头脑中掌握了多少代码库的知识？&lt;/p&gt;
&lt;p&gt;优秀的技术债务管理首先依赖于整个团队在问题追踪方面的出色表现。&lt;/p&gt;
&lt;p&gt;领导者应该使他们的工程师更容易地：(1)发现代码库中的问题、(2)报告和登记这些问题，以及(3)优先并解决这些问题。&lt;/p&gt;
&lt;p&gt;你的目标应当是能对以下四个问题给予肯定的答复：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当工程师能轻易地意识到他们正在处理含有技术债务的代码吗？&lt;/li&gt;
&lt;li&gt;工程师们报告技术债务的过程简单明了吗？&lt;/li&gt;
&lt;li&gt;你的团队是否有一个平台或空间来讨论代码库中的问题？&lt;/li&gt;
&lt;li&gt;技术债务的处理工作是否被持续地融入到你的日常工作流程中？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果上述问题的答案并非都是肯定的，那么你的团队就面临着加重现有技术债务的风险。接下来，我们将探讨顶尖团队应该遵循的最佳实践。&lt;/p&gt;
&lt;h2&gt;良好的问题跟踪是基础&lt;/h2&gt;
&lt;p&gt;当面对技术债务时，透明性至关重要。你无法修复那些你看不到的问题。&lt;/p&gt;
&lt;p&gt;你应该使用那些可以帮助工程师在创建问题时，为其提供尽可能多的背景信息的问题追踪工具。&lt;/p&gt;
&lt;h2&gt;敏捷中的技术债务&lt;/h2&gt;
&lt;p&gt;为了在敏捷开发中成功管理技术债务，团队需要快速地且定期地处理和减少技术债务。&lt;/p&gt;
&lt;p&gt;为了达到这个目的，你可以采纳以下两种策略：&lt;/p&gt;
&lt;p&gt;在每次的冲刺周期中，确保有 15-20%的资源被用于代码重构和错误修正。
定期，例如每季度，安排一个完整的冲刺周期专门用来处理技术债务。
为了确定哪些问题应该被优先处理，你可以使用代码库的可视化工具。这些工具可以帮助你根据代码的特定部分或其对团队士气的潜在影响来确定优先级。&lt;/p&gt;
&lt;p&gt;你也需要重新定义项目或任务“完成”的标准。只有当某个任务或项目达到了一个可管理和可接受的技术债务水平时，才能认为它真正完成。而且，尽管积累一些技术债务在某些情况下是可以接受的，但这些债务必须被妥善记录，并且这样做是基于明确和合理的判断。&lt;/p&gt;
&lt;p&gt;跟踪关键的技术指标可以帮助你识别技术债务、为其提供商业上的修复理由，并跟踪这种修复的成功率。&lt;/p&gt;
&lt;p&gt;与所有成功的管理策略一样，组织应该知道哪些关键指标最能帮助他们控制技术债务。&lt;/p&gt;
&lt;h2&gt;跟踪技术债务指标&lt;/h2&gt;
&lt;p&gt;以下是关于技术债务的几个主要指标：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;债务指数：它表示已经解决的问题与所有问题的比例。&lt;/li&gt;
&lt;li&gt;代码质量：这是多个指标的集合，用于衡量代码的总体质量和复杂度。这些指标包括代码的循环复杂度、类之间的耦合程度、代码的总行数以及继承的深度。&lt;/li&gt;
&lt;li&gt;代码覆盖率：表示每个项目有多少位工程师做出了贡献。&lt;/li&gt;
&lt;li&gt;技术债务比率：这是一个估算技术债务未来成本的指标，其中 5%被视为一个良好的标准。&lt;/li&gt;
&lt;li&gt;内聚性：高内聚性的代码意味着代码中的大部分元素都是高度相关的，这通常表示代码易于维护、重用且稳定。这也可以用来评估项目的架构是否设置得当。&lt;/li&gt;
&lt;li&gt;变动频率：这个指标反映了代码被修改或重写的频率，这可以作为一个指示器来了解哪部分代码可能是导致大部分问题或漏洞的根源。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;应对技术债务的工具&lt;/h2&gt;
&lt;p&gt;目前市场上有很多优秀的工具，这些工具可以帮助我们跟踪和管理所谓的“技术债务”。&lt;/p&gt;
&lt;p&gt;三个主要领域会特别受益于这些工具。以下是对这三个领域的详细介绍：&lt;/p&gt;
&lt;h3&gt;更多、更好地跟踪问题&lt;/h3&gt;
&lt;p&gt;开发人员主要是在编程编辑器里工作，这也是创建、查阅和管理问题的最好地方。&lt;/p&gt;
&lt;p&gt;通过这类工具，用户可以更便捷地将问题与相关代码关联起来，并深入了解这些问题如何影响到代码库或团队的效率。&lt;/p&gt;
&lt;h3&gt;Git 分析&lt;/h3&gt;
&lt;p&gt;当代码中出现缺陷或漏洞时，我们都希望能够尽快发现。&lt;/p&gt;
&lt;p&gt;静态代码分析工具能帮助我们分析代码库，从中挖掘出潜在的风险和团队之间的交互模式。这些分析结果不仅有深度，而且是可操作的，我们可以直接基于这些结果来进行相应的改进。&lt;/p&gt;
&lt;h3&gt;代码质量&lt;/h3&gt;
&lt;p&gt;如果你刚开始关注代码质量，那么使用代码质量工具是一个很好的起点。这些工具可能不会提供持续解决技术债务的整体策略，但它们可以自动对代码进行分析，并指出其中的问题。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;如果忽略技术债务，你的组织可能会面临一系列的问题，包括团队士气低迷、产品推向市场的时间延长、因为架构设计不恰当而导致的系统灵活性降低，以及安全性问题。&lt;/p&gt;
&lt;p&gt;为了妥善处理技术债务，你应该采取以下措施：为团队创建一个方便的环境，使他们能够轻松地标识、跟踪并优先解决代码中的问题；定期召开会议讨论技术债务问题；并使用为此特别设计的工具来协助你处理技术债务。&lt;/p&gt;
</content:encoded><category>软件工程管理</category><author>江辰</author></item><item><title>搭建自托管密码管理器</title><link>https://github.com/posts/%E6%90%AD%E5%BB%BA%E8%87%AA%E6%89%98%E7%AE%A1%E5%AF%86%E7%A0%81%E7%AE%A1%E7%90%86%E5%99%A8/</link><guid isPermaLink="true">https://github.com/posts/%E6%90%AD%E5%BB%BA%E8%87%AA%E6%89%98%E7%AE%A1%E5%AF%86%E7%A0%81%E7%AE%A1%E7%90%86%E5%99%A8/</guid><pubDate>Sat, 16 Dec 2023 19:43:49 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;之前一直在用 iPhone 备忘录作为账号密码管理，但在有些场景下用起来不是很顺手，比如会误修改文本，修改完后，自动给保存，找不到原始文本。用起来不是很方便。看看业界有那些密码管理方案。1Password，付费，用不起。经朋友推荐，尝试尝试 bitwarden 自搭建，免费版平替 1Password&lt;/p&gt;
&lt;h2&gt;准备工作&lt;/h2&gt;
&lt;h3&gt;服务器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Tips：bitwarden 官方推荐配置最低 2GRAM 起步&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;准备一台靠谱的服务器&lt;/p&gt;
&lt;h3&gt;域名&lt;/h3&gt;
&lt;p&gt;注册一个域名用于访问你的服务器，然后进入域名 DNS 解析后台，新增一条 A 记录指向你的服务器 IP (这一步最好先做，因为部分 DNS 生效会比较久)&lt;/p&gt;
&lt;p&gt;如果没有域名，裸 IP 号 + 端口也不是不行&lt;/p&gt;
&lt;h3&gt;SSL 证书&lt;/h3&gt;
&lt;p&gt;各大云厂商都有固定的免费额度 SSL 证书申请，如果没有的话，&lt;a href=&quot;https://github.com/dani-garcia/vaultwarden/wiki/Private-CA-and-self-signed-certs-that-work-with-Chrome&quot;&gt;请点击这&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;再不行，bitwarden 可以一键生成 SSL&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;这里以 CentOS7 为例&lt;/p&gt;
&lt;h3&gt;安装 Docker&lt;/h3&gt;
&lt;p&gt;如果你的服务器上已经安装了 &lt;code&gt;Docker&lt;/code&gt; 和 &lt;code&gt;Docker Compose&lt;/code&gt;，这一步就可以跳过了。&lt;code&gt;Docker&lt;/code&gt; 要求 &lt;code&gt;CentOS&lt;/code&gt; 系统的内核版本高于 3.10，可以运行 uname -r 查看版本。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tips：安装，如果超时的话，建议使用国内镜像安装&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1、（可选）更新系统的软件包
yum update -y

# 2、安装 docker 依赖的软件包
yum install -y yum-utils device-mapper-persistent-data lvm2

# 3、添加 docker 的 yum 源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 4、通过 yum 安装 docker
sudo yum install docker-ce docker-ce-cli containerd.io
# 如上述命令出错，可能是系统上已有旧版本 Docker，需要先卸载旧版本
# 卸载命令：yum remove docker docker-common docker-selinux docker-engine docer-io

# 5、测试 docker 是否安装成功 (查看版本号)
docker version
# 有 client 和 service 两部分表示 docker 安装并启动成功了（有部分错误不用管）

# 6、启动 Docker 服务并设置开机启动
sudo systemctl start docker
sudo systemctl enable docker

# 7、查看Docker是否启动
sudo systemctl status docker
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安装 Docker Compose&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Tips：安装，如果超时的话，建议使用国内镜像安装&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1、首先前往 https://github.com/docker/compose/releases/latest 查看最新的 docker-compose 版本号，比如截稿时最新版本为 2.1.1。

# 2、下载最新版本的 docker-compose，你需要将下面的 2.1.1 替换成最新的版本号
sudo curl -L &quot;https://github.com/docker/compose/releases/download/2.1.1/docker-compose-$(uname -s)-$(uname -m)&quot; -o /usr/local/bin/docker-compose

# 3、授予可执行权限
sudo chmod +x /usr/local/bin/docker-compose

# 4、测试是否安装成功（可能需要重启系统）
docker-compose -v
# 安装成功会显示 docker-compose 版本
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;阿里云&lt;/h3&gt;
&lt;h4&gt;安装 Docker&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 1、运行以下命令，下载docker-ce的yum源
sudo wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 2、运行以下命令，安装Docker
sudo yum -y install docker-ce

# 3、测试 docker 是否安装成功 (查看版本号)
docker version
# 有 client 和 service 两部分表示 docker 安装并启动成功了（有部分错误不用管）

# 4、设置开机启动
sudo systemctl start docker
sudo systemctl enable docker

# 5、查看Docker是否启动
sudo systemctl status docker
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;安装 Docker Compose&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Tips：仅 Python 3 及以上版本支持 docker-compose，并请确保已安装 pip。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1、安装setuptools
sudo pip3 install -U pip setuptools

# 2、安装docker-compose
sudo pip3 install docker-compose

# 3、验证docker-compose是否安装成功
docker-compose --version
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;验证&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/045073cd-4c51-496f-8e22-1c90ae610d52.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;起飞&lt;/h2&gt;
&lt;h3&gt;安装官方 Bitwarden&lt;/h3&gt;
&lt;p&gt;确认服务器已成功安装好 &lt;code&gt;Docker&lt;/code&gt; 和 &lt;code&gt;Docker Compose&lt;/code&gt; 之后，我们就可以来安装 Bitwarden 了。其实 Bitwarden 官方就已经提供了非常方便的一键安装脚本，我们只需执行即可。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tips：由于 docker 镜像的体积比较大，国内服务器可能遇到中途卡住不动的情况&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可通过，输入域名网址 &lt;code&gt;https://go.btwrdn.co/bw-sh&lt;/code&gt;，下载到本地，再上传到服务器，执行 &lt;code&gt;./bitwarden.sh install&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1、下载 Bitwarden 的安装脚本
curl -Lso bitwarden.sh https://go.btwrdn.co/bw-sh &amp;amp;&amp;amp; chmod 700 bitwarden.sh

# 2、执行安装程序
./bitwarden.sh install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，安装脚本会一步步提示你输入，下面是部分重点选项&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enter the domain name for your Bitwarden instance
输入你要给 Bitwarden 分配的域名，这里例子为 bitwarden.iplaysoft.com&lt;/li&gt;
&lt;li&gt;Do you want to use Let’s Encrypt to generate a free SSL certificate? (y/n)
是否使用 Let&apos;s Encrypt 自动生成免费的 SSL 证书，一般选 y (你有自己的证书也可以选 n，后面需要配置证书的路径)&lt;/li&gt;
&lt;li&gt;Enter the database name for your Bitwarden instance
输入用于 Bitwarden 的数据库名称&lt;/li&gt;
&lt;li&gt;Enter your installation id / Enter your installation key
你需要访问 https://bitwarden.com/host 获取一组安装 ID 和安装密钥 Key (官网被 xx 无法访问的话，只能各显神通了)&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;修改配置文件 (端口号、SSL 证书等)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果你需要使用自己的 SSL 证书、修改默认的端口号等需求，可以编辑配置文件 &lt;code&gt;./bwdata/config.yml&lt;/code&gt;。&lt;strong&gt;比如你的服务器本身就有服务占用了 80 和 443 端口，那么可以修改配置里的 http_port 和 https_port 端口号来避免冲突&lt;/strong&gt;。比如我改成 9980 和 9443，这样日后我访问时的域名就是 https://bitwarden.iplaysoft.com:9980。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要注意的是，腾讯云、阿里云等机器默认的「安全组规则」会阻止非常用端口的访问，如使用非 80/443 端口，需要登录后台修改安全组规则允许你设置的端口才行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;另外，如果你使用「宝塔面板」，或者懂得修改 Nginx 的配置，也可以对你的 bitwarden 服务进行“反代”，有建站经验的朋友，可以查查相关的资料，搞定应该不难。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改完后，必须执行一下 &lt;code&gt;./bitwarden.sh rebuild&lt;/code&gt; 命令才能让新配置生效。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;(可选) 修改环境配置文件：&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;另外，在环境配置文件 &lt;code&gt;./bwdata/env/global.override.env&lt;/code&gt; 里还有诸如 SMTP、启用 Yubico、启用系统管理员门户、是否禁止用户注册等设置。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;其中，如果你的 Bitwarden 打算是私人使用，建议在注册完自己账号之后，改成 “禁止用户注册”，对应的项为：&lt;code&gt;globalSettings__disableUserRegistration=false&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改此文件后，需要重启 Bitwarden 容器才能生效，重启命令为：&lt;code&gt;./bitwarden.sh restart&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;启动 Bitwarden 服务&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;./bitwarden.sh start
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;验证所有容器是否正常运行&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/95a27a98-50d0-476d-b0fb-bb6e8f8fb0b8.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果运行不正常，请查看日志，日志服务在 &lt;code&gt;bitwarden/logs/&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;安装第三方 Bitwarden&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dani-garcia/vaultwarden&quot;&gt;vaultwarden&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;机器配置不达标，比如启动容器之后，&lt;code&gt;mssql&lt;/code&gt; 无法正常工作，估计就是 RAM 配置不达标，官方 &lt;code&gt;mssql&lt;/code&gt; 占 RAM 1.3G 内存。无法运行官方 Bitwarden，这时推荐使用第三方 Bitwarden&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1、下载 vaultwarden 的安装脚本
docker run -d --name vaultwarden -v /vw-data/:/data/ -p 80:80 vaultwarden/server:latest

# 2、启动容器
docker start vaultwarden
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;自定义配置&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;docker run -d --name bitwarden \
  -e SIGNUPS_ALLOWED=false \
  -e TZ=Asia/Shanghai \
  -e SIGNUPS_ALLOWED=false \
  -e INVITATIONS_ALLOWED=false \
  -e SMTP_USERNAME=你的邮箱 \
  -e SMTP_PASSWORD=邮箱密码 \
  -v /vw-data/:/data/ \
  -p 9980:80 \
  vaultwarden/server:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;验证容器是否正常运行&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/8bb3e4f8-d144-43ed-8cfe-e978d5e6336f.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;反向代理&lt;/h2&gt;
&lt;p&gt;如果你有自己的 Nginx 服务，需要反向代理 官方 bitwarden 和第三方 bitwarden，以及配置自己的 SSL 证书，可以参考以下配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;user root;
worker_processes auto;
pid /run/nginx.pid;
#include /usr/share/nginx/modules/*.conf;
events {
    worker_connections 512;
}
http {
    etag on;
    expires 7d;
    gzip on;
    gzip_min_length 1k;
    gzip_buffers   2 8k;
    client_max_body_size 50m;
    gzip_comp_level 2;
    gzip_types   text/plain application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg;
    gzip_vary off;
    log_format  main  &apos;$remote_addr - $remote_user [$time_local] &quot;$request&quot; &apos;
                      &apos;$status $body_bytes_sent &quot;$http_referer&quot; &apos;
                      &apos;&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;&apos;;
    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;
    #include             /etc/nginx/mime.types;
    include             mime.types;
    default_type        application/octet-stream;
    #include /etc/nginx/conf.d/*.conf;

    server {
       listen 80;
       server_name bitwarden.iplaysoft.com;
       rewrite ^(.*)$ https://$host$1 permanent;
   }

   server {
        listen 443 ssl;
        server_name  bitwarden.iplaysoft.com;
        index index.html;
        ssl_certificate   /usr/local/webserver/nginx/cert/vd.downfuture.com.pem;
        ssl_certificate_key  /usr/local/webserver/nginx/cert/vd.downfuture.com.key;
        ssl_session_timeout 5m;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        access_log /usr/local/webserver/nginx/logs/nginx_access.log;
        error_log /usr/local/webserver/nginx/logs/nginx_error.log error;

        error_page 404 /404.html;
            location = /40x.html {
        }
        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }

        location / {
            proxy_pass http://服务器公网IP:9980;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;bitwarden 强大的自托管程序，让我拥有了免费密码管理器，感谢！&lt;/p&gt;
&lt;p&gt;文章同步更新平台：掘金、CSDN、知乎、思否、博客，公众号（野生程序猿江辰）
我的联系方式，v：Jiang9684，欢迎和我一起学习交流&lt;/p&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>密码管理器</category><author>江辰</author></item><item><title>【从前端入门到全栈】网站 SEO 提升</title><link>https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E7%BD%91%E7%AB%99-seo-%E6%8F%90%E5%8D%87/</link><guid isPermaLink="true">https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E7%BD%91%E7%AB%99-seo-%E6%8F%90%E5%8D%87/</guid><pubDate>Fri, 20 Oct 2023 18:36:57 GMT</pubDate><content:encoded/><category>从前端入门到全栈</category><author>江辰</author></item><item><title>pm2 The &quot;data&quot; argument must be of type string or an instance of Buffer 问题排查</title><link>https://github.com/posts/pm2-the-data-argument-must-be-of-type-string-or-an-instance-of-buffer-%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5/</link><guid isPermaLink="true">https://github.com/posts/pm2-the-data-argument-must-be-of-type-string-or-an-instance-of-buffer-%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5/</guid><pubDate>Sun, 15 Oct 2023 22:54:13 GMT</pubDate><content:encoded>&lt;h2&gt;问题&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;pm2 The &quot;data&quot; argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received type number
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;解决&lt;/h2&gt;
&lt;p&gt;在本地运行 Node.js 发现并没有这个问题，后面随想可能是 PM2 启动的时候，路径查找不到&lt;/p&gt;
&lt;p&gt;两个问题要解决：
1、PM2 版本过低，服务器上的 PM2 版本还是 2
通过 pm2 update 更新到了 5&lt;/p&gt;
&lt;p&gt;2、每次上传大文件，Node.js 进程一定会挂&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
 apps: [
     {
         watch: false,
     }
 ]
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;watch 设置为 false，即可解决&lt;/p&gt;
</content:encoded><category>PM2</category><author>江辰</author></item><item><title>告前端同学的小记</title><link>https://github.com/posts/%E5%91%8A%E5%89%8D%E7%AB%AF%E5%90%8C%E5%AD%A6%E7%9A%84%E5%B0%8F%E8%AE%B0/</link><guid isPermaLink="true">https://github.com/posts/%E5%91%8A%E5%89%8D%E7%AB%AF%E5%90%8C%E5%AD%A6%E7%9A%84%E5%B0%8F%E8%AE%B0/</guid><pubDate>Sun, 15 Oct 2023 17:44:07 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/6180249e-9a23-4ae1-a0d4-7999faf42614.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;转载周爱民老师《告前端同学书》，有些思绪需要发散，随联想到自己的过往。&lt;/p&gt;
&lt;p&gt;文中提到&apos;领域&apos;一词，用在前端，个人认为非常合适。领域一词在我印象中，还需追溯到初中时期，我读的一本叫《吞噬星空》的小说，书籍大概 400 多万字，读起来却是昼夜不分（这也是我唯一看完的一本小说），此书也提到了领域，行星级武者可以展开自己的领域。我个人所理解的领域，用大白话通俗解释，就是当你的本领到达一定阶段时，你会在这个范围拥有一定的话语权。类似的阿里技术等级序列也是，通常来说，P7 一般拥有自己的领域。&lt;/p&gt;
&lt;p&gt;从疫情后开始，互联网的就业环境非常糟糕。前端就业，每况愈下，不断传出前端已死。之前，我也有发文，并不认同前端已死，如今还是持有该观点。周爱民老师的观点也与我一致。&lt;/p&gt;
&lt;p&gt;我从 16 年入行至今，快 7 年的时光，经历了从 jQuery 时代到前端三大框架，如今相持不下的，只剩 React 和 Vue 。Angular 基本被社区抛弃，算是赶上了前端黄金时期的末尾阶段，前五年我是在做一个大前端方向，微信小程序、H5、Web、APP 开发都有涉及，前端工程化链路搭建也小有涉及。所谓的技术广泛度。也向往大厂。想见识见识大厂的工作氛围，大厂的技术链路到底是怎样的。后面，我进去了，但也经历了裁员，之前的想法是想靠大厂背书 + 前端工具链路这个方向，做可持续性的发展。随后，我搞了微前端，想在这个领域深入下去，但这条路走失败了，让我不得不重新思考，未来的出路在那？35 岁以后呢？我可不想去送外卖，开滴滴。&lt;/p&gt;
&lt;p&gt;后面，我接触到了 WebGL，前端 3D 图形化的概念和场景非常吸引我，我也慢慢开始在往这个方向去做探索 + 转型，转型是一件非常痛苦的事，因为你要从自己的舒适区进入到坑洼区，你不得不去做很多东西，从 0 - 1 。辛运的是，我还年轻（但也不年轻了）。我个人坚信选择大于努力，时代会不断的造就一批人，之前的土木，现今的互联网，自媒体，直播带货等。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/c4583b68-456b-43f0-9767-1588ed85364f.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;很多人问我搞矩阵有什么用，你的目的是什么？&lt;/p&gt;
&lt;p&gt;其实很简单，学习。我本人的学习模式是，需要通过大量的实战摸索 + 理论知识，梳理成一套成熟的方法论，单纯的去学理论知识，对于我本人而言，非常枯燥，况且 3D 图形化知识体系，非常困难。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这是在一个新阶段的前夜。故此，有很多信息并不那么明朗，比如说像前后端分离这样的标志性事件并没有出现，亦或者出现了也还没有形成典型影响。我倾向于认为引领新时代的，或者说开启下一个阶段的运动将会发生在交互领域，也就是说新的交互方式决定了前端的未来。之前行业里在讲的 VR 和 AR（虚拟现实和增强实景）是在这个方向上的典型技术，但不唯于此。几乎所有在交互方式上的变革，都会成为人们认识与改变这个世界的全新动力，像语音识别、视觉捕捉、脑机接口等等，这些半成熟的或者实验性的技术都在影响着我们对&apos;交互&apos;的理解，从而重新定义了前端&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;周爱民老师这段文字，讲得非常透彻。AI、VR、AR 的不断发展，对前端的要求越来越高，如果还是停留在写业务页面的思想层面上，估计很快会被替代，大量的培训班造就一批人，已经告诉了我们。培训几个月，稍微伪造下履历，就可以快速上手业务开发，那么对比一个五年的，你有什么优势？，何况随着低代码不断的演进，让本不会写代码的一批人，也可以通过低代码引擎平台，快速生成页面，如果 AI 再持续迭代呢？这是一件很可怕的事&lt;/p&gt;
&lt;p&gt;前端的很多理念来自于后端，并没有造就出真正属于自己的革命。底子很薄，真正要下功夫的其实是系统、网络、编译、机器学习、图形化等层面，或许 WASM + RUST 又会产生一点不一样的东西&lt;/p&gt;
&lt;p&gt;在这个行业待了很久，见识了很多人转到其他行业。互联网的发展非常迅速，如果做不到持之以恒的学习，那将会被慢慢淘汰。以汽车为例，燃油车&quot;固若金汤&quot;，谁会想到互联网 + 纯电，基本上算是革了燃油车的命，这些传统燃油车商，也要做出相应的改变，以应对时代的变化。&lt;/p&gt;
&lt;p&gt;眼光高远而脚踏实地&lt;/p&gt;
</content:encoded><category>前端</category><author>江辰</author></item><item><title>【从前端入门到全栈】登录 Token 鉴权设计</title><link>https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E7%99%BB%E5%BD%95-token-%E9%89%B4%E6%9D%83%E8%AE%BE%E8%AE%A1/</link><guid isPermaLink="true">https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E7%99%BB%E5%BD%95-token-%E9%89%B4%E6%9D%83%E8%AE%BE%E8%AE%A1/</guid><pubDate>Fri, 29 Sep 2023 23:13:13 GMT</pubDate><content:encoded/><category>从前端入门到全栈</category><author>江辰</author></item><item><title>【从前端入门到全栈】网站版本更新通知</title><link>https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E7%BD%91%E7%AB%99%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E9%80%9A%E7%9F%A5/</link><guid isPermaLink="true">https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E7%BD%91%E7%AB%99%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E9%80%9A%E7%9F%A5/</guid><pubDate>Fri, 29 Sep 2023 23:12:45 GMT</pubDate><content:encoded/><category>从前端入门到全栈</category><author>江辰</author></item><item><title>2023年中前端面试真题之编码篇</title><link>https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8B%E7%BC%96%E7%A0%81%E7%AF%87/</link><guid isPermaLink="true">https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8B%E7%BC%96%E7%A0%81%E7%AF%87/</guid><pubDate>Sun, 03 Sep 2023 22:28:52 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;人的一生，总是难免有浮沉。不会永远如旭日东升，也不会永远痛苦潦倒。反复地一浮一沉，对于一个人来说，正是磨练。因此，浮在上面的，不必骄傲；沉在底下的，更用不着悲观。必须以率直、谦虚的态度，乐观进取、向前迈进。——松下幸之助&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;大家好，我是江辰，在如今的互联网大环境下，想必大家都或多或少且有感受，浮躁的社会之下，只有不断的保持心性，才能感知不同的收获，互勉。&lt;/p&gt;
&lt;p&gt;2023 年中最新的面试题集锦，时刻做好准备。&lt;/p&gt;
&lt;p&gt;本文首发于微信公众号：野生程序猿江辰&lt;/p&gt;
&lt;p&gt;欢迎大家点赞，收藏，关注&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/aaebcde3-9c4f-4146-b1bb-91bd39a63e9f.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;文章列表&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/161&quot;&gt;2023 年中前端面试真题之 JS 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/162&quot;&gt;2023 年中前端面试真题之 CSS 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/163&quot;&gt;2023 年中前端面试真题之 HTML 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/164&quot;&gt;2023 年中前端面试真题之 React 篇&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;实现简易版 Promise&lt;/h2&gt;
&lt;p&gt;以下是一个基本的 Promise 实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyPromise {
  constructor(executor) {
    this.status = &quot;pending&quot;;
    this.value = undefined;
    this.onResolveCallbacks = [];
    this.onRejectCallbacks = [];

    const resolve = (value) =&amp;gt; {
      if (this.status === &quot;pending&quot;) {
        this.status = &quot;fulfilled&quot;;
        this.value = value;
        this.onResolveCallbacks.forEach((callback) =&amp;gt; callback(this.value));
      }
    };

    const reject = (reason) =&amp;gt; {
      if (this.status === &quot;pending&quot;) {
        this.status = &quot;rejected&quot;;
        this.value = reason;
        this.onRejectCallbacks.forEach((callback) =&amp;gt; callback(this.value));
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === &quot;fulfilled&quot;) {
      onFulfilled(this.value);
    } else if (this.status === &quot;rejected&quot;) {
      onRejected(this.value);
    } else {
      this.onResolveCallbacks.push(onFulfilled);
      this.onRejectCallbacks.push(onRejected);
    }
  }
}

// 示例用法
const promise = new MyPromise((resolve, reject) =&amp;gt; {
  // 异步操作，比如请求数据
  setTimeout(() =&amp;gt; {
    resolve(&quot;成功&quot;);
    // 或者 reject(&apos;失败&apos;);
  }, 1000);
});

promise.then(
  (value) =&amp;gt; {
    console.log(&quot;成功:&quot;, value);
  },
  (reason) =&amp;gt; {
    console.log(&quot;失败:&quot;, reason);
  }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是一个非常基本的 Promise 实现，仅用于演示目的。在实际应用中，要考虑更多的细节和错误处理。现代 JavaScript 已经内置了 Promise，通常不需要手动实现它。&lt;/p&gt;
&lt;h2&gt;实现函数节流&lt;/h2&gt;
&lt;p&gt;函数节流是一种控制函数执行频率的技术，确保函数在一定时间间隔内最多执行一次。以下是一个简单的 JavaScript 函数节流的实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function throttle(func, delay) {
  let timerId;
  let lastExecTime = 0;

  return function (...args) {
    const now = Date.now();
    if (now - lastExecTime &amp;gt;= delay) {
      func.apply(this, args);
      lastExecTime = now;
    } else {
      clearTimeout(timerId);
      timerId = setTimeout(() =&amp;gt; {
        func.apply(this, args);
        lastExecTime = Date.now();
      }, delay);
    }
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述 &lt;code&gt;throttle&lt;/code&gt; 函数接受两个参数：&lt;code&gt;func&lt;/code&gt; 是要节流的函数，&lt;code&gt;delay&lt;/code&gt; 是执行的时间间隔（以毫秒为单位）。&lt;/p&gt;
&lt;p&gt;使用这个节流函数，您可以包装需要进行节流的函数，以确保它们不会在短时间内被频繁执行。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 原始函数，可能会频繁触发
function handleResize() {
  console.log(&quot;窗口大小改变了&quot;);
}

// 使用节流包装后的函数
const throttledResize = throttle(handleResize, 200); // 200毫秒的节流间隔

// 监听窗口大小改变事件，使用节流函数
window.addEventListener(&quot;resize&quot;, throttledResize);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，&lt;code&gt;handleResize&lt;/code&gt; 函数将在 200 毫秒内最多执行一次，无论窗口大小改变多频繁。这有助于减少频繁的函数调用，提高性能。&lt;/p&gt;
&lt;h2&gt;实现函数防抖&lt;/h2&gt;
&lt;p&gt;函数防抖是一种控制函数执行频率的技术，确保函数在一定时间间隔内只执行一次。以下是一个简单的 JavaScript 函数防抖的实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function debounce(func, delay) {
  let timerId;

  return function (...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() =&amp;gt; {
      func.apply(this, args);
    }, delay);
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述 &lt;code&gt;debounce&lt;/code&gt; 函数接受两个参数：&lt;code&gt;func&lt;/code&gt; 是要防抖的函数，&lt;code&gt;delay&lt;/code&gt; 是等待的时间间隔（以毫秒为单位）。&lt;/p&gt;
&lt;p&gt;使用这个防抖函数，您可以包装需要进行防抖的函数，以确保它们只在一定时间间隔后被执行。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 原始函数，可能会频繁触发
function handleInput(value) {
  console.log(&quot;输入值为:&quot;, value);
}

// 使用防抖包装后的函数
const debouncedInput = debounce(handleInput, 300); // 300毫秒的防抖间隔

// 监听输入事件，使用防抖函数
document.querySelector(&quot;input&quot;).addEventListener(&quot;input&quot;, (event) =&amp;gt; {
  debouncedInput(event.target.value);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，&lt;code&gt;handleInput&lt;/code&gt; 函数将在用户停止输入 300 毫秒后执行，从而减少了频繁的函数调用，提高了性能。&lt;/p&gt;
&lt;h2&gt;实现观察者模式&lt;/h2&gt;
&lt;p&gt;观察者模式是一种设计模式，其中一个主题（被观察者）维护了一个观察者列表，并在状态变化时通知观察者。以下是一个简单的 JavaScript 观察者模式的实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    this.observers = this.observers.filter((obs) =&amp;gt; obs !== observer);
  }

  notify(data) {
    this.observers.forEach((observer) =&amp;gt; observer.update(data));
  }
}

class Observer {
  constructor(name) {
    this.name = name;
  }

  update(data) {
    console.log(`${this.name} 收到更新，数据为:`, data);
  }
}

// 示例用法
const subject = new Subject();

const observer1 = new Observer(&quot;观察者1&quot;);
const observer2 = new Observer(&quot;观察者2&quot;);

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notify(&quot;新数据更新了&quot;); // 观察者1 收到更新，数据为: 新数据更新了
// 观察者2 收到更新，数据为: 新数据更新了

subject.removeObserver(observer1);

subject.notify(&quot;又有新数据更新了&quot;); // 只有观察者2会收到更新
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述代码创建了一个简单的观察者模式实现，包括一个主题类 &lt;code&gt;Subject&lt;/code&gt; 和一个观察者类 &lt;code&gt;Observer&lt;/code&gt;。主题可以添加、移除观察者，并在状态变化时通知所有观察者。&lt;/p&gt;
&lt;p&gt;在示例中，我们创建了一个主题 &lt;code&gt;subject&lt;/code&gt;，并添加了两个观察者 &lt;code&gt;observer1&lt;/code&gt; 和 &lt;code&gt;observer2&lt;/code&gt;。当主题状态发生变化时，它会通知所有观察者。&lt;/p&gt;
&lt;p&gt;这只是一个基本的示例，实际应用中，您可能需要更复杂的实现以满足特定需求。&lt;/p&gt;
&lt;h2&gt;实现发布订阅模式&lt;/h2&gt;
&lt;p&gt;订阅者模式也被称为发布-订阅模式，它是一种设计模式，其中一个主题（发布者）维护了一个订阅者列表，并在事件发生时通知所有订阅者。以下是一个简单的 JavaScript 订阅者模式的实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Publisher {
  constructor() {
    this.subscribers = [];
  }

  subscribe(subscriber) {
    this.subscribers.push(subscriber);
  }

  unsubscribe(subscriber) {
    this.subscribers = this.subscribers.filter((sub) =&amp;gt; sub !== subscriber);
  }

  publish(eventData) {
    this.subscribers.forEach((subscriber) =&amp;gt; subscriber.notify(eventData));
  }
}

class Subscriber {
  constructor(name) {
    this.name = name;
  }

  notify(eventData) {
    console.log(`${this.name} 收到通知，事件数据为:`, eventData);
  }
}

// 示例用法
const publisher = new Publisher();

const subscriber1 = new Subscriber(&quot;订阅者1&quot;);
const subscriber2 = new Subscriber(&quot;订阅者2&quot;);

publisher.subscribe(subscriber1);
publisher.subscribe(subscriber2);

publisher.publish(&quot;新事件发生了&quot;); // 订阅者1 收到通知，事件数据为: 新事件发生了
// 订阅者2 收到通知，事件数据为: 新事件发生了

publisher.unsubscribe(subscriber1);

publisher.publish(&quot;又有新事件发生了&quot;); // 只有订阅者2会收到通知
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上述代码中，我们创建了一个简单的订阅者模式实现，包括一个发布者类 &lt;code&gt;Publisher&lt;/code&gt; 和一个订阅者类 &lt;code&gt;Subscriber&lt;/code&gt;。发布者可以添加、移除订阅者，并在事件发生时通知所有订阅者。&lt;/p&gt;
&lt;p&gt;示例中，我们创建了一个发布者 &lt;code&gt;publisher&lt;/code&gt;，并添加了两个订阅者 &lt;code&gt;subscriber1&lt;/code&gt; 和 &lt;code&gt;subscriber2&lt;/code&gt;。当发布者发布事件时，它会通知所有订阅者。&lt;/p&gt;
&lt;p&gt;这只是一个基本的示例，实际应用中，您可以根据需要扩展订阅者模式以满足特定需求。&lt;/p&gt;
&lt;h2&gt;实现 new 关键字&lt;/h2&gt;
&lt;p&gt;要实现 JavaScript 中 &lt;code&gt;new&lt;/code&gt; 操作符的基本功能，您可以编写一个函数，该函数接受构造函数和构造函数参数，并返回一个新的对象实例。以下是一个示例的实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function myNew(constructor, ...args) {
  // 创建一个新对象，并将其原型指向构造函数的原型
  const obj = Object.create(constructor.prototype);

  // 调用构造函数，将新对象绑定到构造函数的上下文中
  const result = constructor.apply(obj, args);

  // 如果构造函数返回的是一个对象，则返回该对象；否则返回新创建的对象
  return typeof result === &quot;object&quot; ? result : obj;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，您可以使用 &lt;code&gt;myNew&lt;/code&gt; 函数来模拟 &lt;code&gt;new&lt;/code&gt; 操作符的行为。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 使用 myNew 模拟 new 操作符
const person1 = myNew(Person, &quot;Alice&quot;, 30);
const person2 = myNew(Person, &quot;Bob&quot;, 25);

console.log(person1); // 输出: Person { name: &apos;Alice&apos;, age: 30 }
console.log(person2); // 输出: Person { name: &apos;Bob&apos;, age: 25 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个 &lt;code&gt;myNew&lt;/code&gt; 函数首先创建一个新对象 &lt;code&gt;obj&lt;/code&gt;，然后将新对象的原型指向构造函数 &lt;code&gt;constructor&lt;/code&gt; 的原型。接下来，它调用构造函数，并将新对象绑定到构造函数的上下文中。最后，它检查构造函数的返回值，如果是对象则返回该对象，否则返回新创建的对象。&lt;/p&gt;
&lt;p&gt;这是一个简单的 &lt;code&gt;new&lt;/code&gt; 操作符的模拟实现，实际上，&lt;code&gt;new&lt;/code&gt; 还涉及到原型链等更复杂的特性，但这个示例可以演示基本的原理。&lt;/p&gt;
&lt;h2&gt;实现 DeepClone&lt;/h2&gt;
&lt;p&gt;深拷贝（deep clone）是一种在复制对象时，不仅复制对象本身，还递归复制对象内部所有嵌套的对象和属性的操作。以下是一个简单的 JavaScript 深拷贝的实现示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function deepClone(obj, hash = new WeakMap()) {
  // 如果是基本数据类型或 null，则直接返回
  if (obj === null || typeof obj !== &quot;object&quot;) {
    return obj;
  }

  // 如果已经拷贝过这个对象，则直接返回之前的拷贝结果，防止循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }

  // 根据对象的类型创建新的对象
  const clone = Array.isArray(obj) ? [] : {};

  // 将新对象添加到哈希表
  hash.set(obj, clone);

  // 递归拷贝对象的属性
  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      clone[key] = deepClone(obj[key], hash);
    }
  }

  return clone;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个 &lt;code&gt;deepClone&lt;/code&gt; 函数可以深度复制包括对象、数组和嵌套结构在内的复杂数据类型。它使用了一个哈希表 &lt;code&gt;hash&lt;/code&gt; 来防止循环引用，确保不会陷入无限递归。&lt;/p&gt;
&lt;p&gt;示例用法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const originalObj = {
  name: &quot;John&quot;,
  age: 30,
  address: {
    street: &quot;123 Main St&quot;,
    city: &quot;New York&quot;,
  },
};

const clonedObj = deepClone(originalObj);

console.log(clonedObj); // 输出深拷贝后的对象
console.log(originalObj === clonedObj); // 输出 false，说明是不同的对象
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请注意，这只是一个简单的深拷贝实现示例，实际应用中可能需要更复杂的处理，以应对各种数据类型和情况。&lt;/p&gt;
&lt;h2&gt;实现函数 Curry&lt;/h2&gt;
&lt;p&gt;函数柯里化（Currying）是一种将接受多个参数的函数转换为一系列接受单个参数的函数的技术。以下是一个简单的 JavaScript 函数柯里化的实现示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function curry(fn) {
  return function curried(...args) {
    if (args.length &amp;gt;= fn.length) {
      return fn.apply(this, args);
    } else {
      return function (...moreArgs) {
        return curried.apply(this, args.concat(moreArgs));
      };
    }
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个 &lt;code&gt;curry&lt;/code&gt; 函数接受一个函数 &lt;code&gt;fn&lt;/code&gt;，然后返回一个柯里化后的函数。当柯里化后的函数被调用时，它将检查传入的参数数量是否足够执行原始函数 &lt;code&gt;fn&lt;/code&gt;。如果参数足够，它会直接调用 &lt;code&gt;fn&lt;/code&gt;；如果参数不够，它将返回一个新的函数，等待更多参数传入，并持续追加参数，直到参数足够。&lt;/p&gt;
&lt;p&gt;示例用法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function add(a, b, c) {
  return a + b + c;
}

const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 输出 6
console.log(curriedAdd(1, 2)(3)); // 输出 6
console.log(curriedAdd(1)(2, 3)); // 输出 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在示例中，我们首先使用 &lt;code&gt;curry&lt;/code&gt; 函数将 &lt;code&gt;add&lt;/code&gt; 函数柯里化，然后可以通过多种方式调用 &lt;code&gt;curriedAdd&lt;/code&gt; 来实现加法操作。&lt;/p&gt;
&lt;p&gt;这只是一个简单的函数柯里化的实现示例，实际应用中，您可能需要更复杂的处理，以应对不同的函数和参数情况。&lt;/p&gt;
&lt;h2&gt;实现 Call&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;call&lt;/code&gt; 是 JavaScript 中用于调用函数的方法，它允许您指定函数内部的 &lt;code&gt;this&lt;/code&gt; 值并传递参数。以下是一个简单的 JavaScript &lt;code&gt;call&lt;/code&gt; 方法的模拟实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Function.prototype.myCall = function (context, ...args) {
  // 如果没有传递上下文对象，则使用全局对象（浏览器环境下为 window）
  context = context || globalThis;

  // 将当前函数作为上下文对象的一个属性
  const uniqueKey = Symbol(&quot;uniqueKey&quot;);
  context[uniqueKey] = this;

  // 调用函数，并传递参数
  const result = context[uniqueKey](...args);

  // 删除临时属性
  delete context[uniqueKey];

  return result;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个模拟的 &lt;code&gt;myCall&lt;/code&gt; 方法可以添加到 &lt;code&gt;Function.prototype&lt;/code&gt; 上，以使所有函数都能够调用它。它接受一个上下文对象 &lt;code&gt;context&lt;/code&gt; 和一系列参数 &lt;code&gt;args&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;示例用法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function greet(greeting) {
  console.log(`${greeting}, ${this.name}`);
}

const person = { name: &quot;Alice&quot; };

// 使用 myCall 来调用 greet 函数，并指定上下文对象为 person
greet.myCall(person, &quot;Hello&quot;); // 输出: Hello, Alice
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在示例中，我们通过 &lt;code&gt;myCall&lt;/code&gt; 方法来调用 &lt;code&gt;greet&lt;/code&gt; 函数，并指定 &lt;code&gt;person&lt;/code&gt; 对象作为上下文对象，这使得 &lt;code&gt;this&lt;/code&gt; 在函数内部指向了 &lt;code&gt;person&lt;/code&gt; 对象。&lt;/p&gt;
&lt;p&gt;请注意，这只是一个简单的 &lt;code&gt;call&lt;/code&gt; 方法模拟实现，实际的 &lt;code&gt;call&lt;/code&gt; 方法还可以处理更多参数和特殊情况。&lt;/p&gt;
&lt;h2&gt;实现数组拍平&lt;/h2&gt;
&lt;p&gt;在 JavaScript 中，您可以使用递归或循环来实现数组的拍平（Flatten）。以下是一些拍平数组的方法：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;递归方法：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function flattenArray(arr) {
  let result = [];

  for (let i = 0; i &amp;lt; arr.length; i++) {
    if (Array.isArray(arr[i])) {
      // 如果当前元素是数组，递归拍平
      result = result.concat(flattenArray(arr[i]));
    } else {
      // 如果不是数组，直接添加到结果数组中
      result.push(arr[i]);
    }
  }

  return result;
}

const nestedArray = [1, [2, [3, 4], 5], 6];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray); // 输出: [1, 2, 3, 4, 5, 6]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用&lt;code&gt;reduce&lt;/code&gt;方法：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function flattenArray(arr) {
  return arr.reduce(function (flat, toFlatten) {
    return flat.concat(
      Array.isArray(toFlatten) ? flattenArray(toFlatten) : toFlatten
    );
  }, []);
}

const nestedArray = [1, [2, [3, 4], 5], 6];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray); // 输出: [1, 2, 3, 4, 5, 6]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用 ES6 的&lt;code&gt;Array.flat&lt;/code&gt;方法：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const nestedArray = [1, [2, [3, 4], 5], 6];
const flattenedArray = nestedArray.flat(Infinity);
console.log(flattenedArray); // 输出: [1, 2, 3, 4, 5, 6]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述方法都可以将嵌套的数组拍平成一个一维数组。选择哪种方法取决于您的项目需求和对兼容性的要求。如果您的环境支持 ES6 的&lt;code&gt;Array.flat&lt;/code&gt;方法，那是最简单的方式。如果需要兼容旧的环境，可以使用递归或&lt;code&gt;reduce&lt;/code&gt;方法。&lt;/p&gt;
&lt;h2&gt;封装 Hooks 定时器&lt;/h2&gt;
&lt;p&gt;要封装一个可以在多个组件中共享的自定义 Hooks 定时器，您可以创建一个名为&lt;code&gt;useTimer&lt;/code&gt;的自定义 Hooks。以下是一个示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useState, useEffect } from &quot;react&quot;;

function useTimer(initialCount = 0, interval = 1000) {
  const [count, setCount] = useState(initialCount);

  useEffect(() =&amp;gt; {
    const timer = setInterval(() =&amp;gt; {
      setCount((prevCount) =&amp;gt; prevCount + 1);
    }, interval);

    // 在组件卸载时清除定时器
    return () =&amp;gt; {
      clearInterval(timer);
    };
  }, [interval]);

  return count;
}

export default useTimer;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个&lt;code&gt;useTimer&lt;/code&gt;自定义 Hooks 接受两个参数：&lt;code&gt;initialCount&lt;/code&gt;（初始计数值，默认为 0）和&lt;code&gt;interval&lt;/code&gt;（定时器间隔，默认为 1000 毫秒）。它返回一个表示定时器计数值的&lt;code&gt;count&lt;/code&gt;状态变量。&lt;/p&gt;
&lt;p&gt;您可以在多个组件中使用&lt;code&gt;useTimer&lt;/code&gt;来创建定时器。以下是一个示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import useTimer from &quot;./useTimer&quot;; // 导入自定义Hooks

function TimerComponent() {
  const count = useTimer(); // 使用自定义Hooks创建定时器

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;定时器示例&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;计数：{count}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default TimerComponent;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上述示例中，我们导入了自定义 Hooks&lt;code&gt;useTimer&lt;/code&gt;，然后在&lt;code&gt;TimerComponent&lt;/code&gt;组件中使用它创建了一个定时器。每个使用&lt;code&gt;useTimer&lt;/code&gt;的组件都会独立拥有自己的定时器，但它们可以共享相同的定时器逻辑。&lt;/p&gt;
&lt;p&gt;您可以在需要的多个组件中使用&lt;code&gt;useTimer&lt;/code&gt;来创建和管理定时器，以便在整个应用程序中实现共享的定时器功能。&lt;/p&gt;
</content:encoded><category>前端面试</category><author>江辰</author></item><item><title>2023年中前端面试真题之Vue篇</title><link>https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8Bvue%E7%AF%87/</link><guid isPermaLink="true">https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8Bvue%E7%AF%87/</guid><pubDate>Sun, 03 Sep 2023 22:28:18 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;人的一生，总是难免有浮沉。不会永远如旭日东升，也不会永远痛苦潦倒。反复地一浮一沉，对于一个人来说，正是磨练。因此，浮在上面的，不必骄傲；沉在底下的，更用不着悲观。必须以率直、谦虚的态度，乐观进取、向前迈进。——松下幸之助&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;大家好，我是江辰，在如今的互联网大环境下，想必大家都或多或少且有感受，浮躁的社会之下，只有不断的保持心性，才能感知不同的收获，互勉。&lt;/p&gt;
&lt;p&gt;2023 年中最新的面试题集锦，时刻做好准备。&lt;/p&gt;
&lt;p&gt;本文首发于微信公众号：野生程序猿江辰&lt;/p&gt;
&lt;p&gt;欢迎大家点赞，收藏，关注&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/aaebcde3-9c4f-4146-b1bb-91bd39a63e9f.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;文章列表&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/161&quot;&gt;2023 年中前端面试真题之 JS 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/162&quot;&gt;2023 年中前端面试真题之 CSS 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/163&quot;&gt;2023 年中前端面试真题之 HTML 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/164&quot;&gt;2023 年中前端面试真题之 React 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/166&quot;&gt;2023 年中前端面试真题之编码篇&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Vue.js 与其他前端框架（如 React 和 Angular）相比有什么优势和区别？&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;简单性和易用性：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vue.js 是一款轻量级框架，容易学习和上手。它提供了直观的 API 和清晰的文档，使开发者可以迅速构建应用程序。
React 和 Angular 在某些方面更复杂，需要更多的学习成本。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;渐进式框架：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vue.js 被称为渐进式框架，允许你逐步采用它的特性。这意味着你可以在现有项目中集成 Vue.js，而不必一次性重写整个应用。
React 和 Angular 在集成到现有项目时可能需要更多的工作。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;双向数据绑定：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vue.js 提供了直接的双向数据绑定，使数据在视图和模型之间保持同步。这使得开发人员更容易管理应用程序的状态。
React 和 Angular 也支持数据绑定，但它们的实现方式略有不同。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;组件化开发：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vue.js、React 和 Angular 都鼓励组件化开发，但 Vue.js 在这方面表现出色。Vue 组件的定义非常简单，易于复用和维护。
React 使用 JSX 来创建组件，Angular 使用模板。这些框架的组件系统也很强大，但可能需要更多的配置。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;生态系统和社区：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;React 和 Angular 有庞大的生态系统和活跃的社区支持，有丰富的第三方库和插件。
Vue.js 的生态系统也在不断壮大，虽然相对较小，但社区也非常积极。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;性能：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vue.js 在性能方面表现良好，具有虚拟 DOM 机制，可以高效地更新视图。
React 也使用虚拟 DOM，性能也很出色。Angular 在某些情况下可能需要更多的性能优化工作。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;工具和生态系统：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vue.js 提供了一些强大的工具，如 Vue CLI，用于快速搭建项目，并与 Vue Router 和 Vuex 等官方库集成。
React 和 Angular 也有类似的工具和库，但 Vue 的工具生态系统在某些方面更加直观和易用。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用案例：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vue.js 适用于中小型应用程序和单页面应用程序（SPA），以及需要快速原型开发的项目。
React 和 Angular 适用于各种规模的应用，包括大型企业级应用。总之，选择使用哪个前端框架取决于项目的需求和团队的偏好。Vue.js 在简单性、易用性和渐进式开发方面具有优势，适合许多项目，但 React 和 Angular 在大型应用和企业级项目中也有其优势。&lt;/p&gt;
&lt;h2&gt;Vue 实例与组件之间的区别是什么？它们如何进行通信？&lt;/h2&gt;
&lt;p&gt;Vue.js 中的 Vue 实例（Vue Instance）和组件（Components）是两个不同的概念，它们之间有一些重要的区别，同时也有不同的方式来进行通信。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Vue 实例（Vue Instance）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 实例是 Vue.js 的核心概念之一。它是一个独立的 Vue 对象，用来管理应用的状态、行为和生命周期。&lt;/li&gt;
&lt;li&gt;通常，一个 Vue 应用的根实例会被创建，它管理整个应用的数据和方法。你可以使用 &lt;code&gt;new Vue()&lt;/code&gt; 来创建一个 Vue 实例。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 组件（Components）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;组件是 Vue.js 中的可复用的代码块，用于构建用户界面。每个组件都有自己的状态、行为和模板。&lt;/li&gt;
&lt;li&gt;组件可以像标签一样在模板中使用，允许你构建复杂的用户界面，将界面分解成可维护的部分。&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;Vue.component&lt;/code&gt; 或使用单文件组件 (&lt;code&gt;.vue&lt;/code&gt; 文件) 的方式定义组件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;通信方式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 Vue.js 中，Vue 实例和组件之间可以通过以下方式进行通信：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Props（属性）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;父组件可以通过 props 向子组件传递数据。子组件通过 props 接收数据并在自己的模板中使用。&lt;/li&gt;
&lt;li&gt;这是一种单向数据流的方式，父组件向子组件传递数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 自定义事件：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;子组件可以通过触发自定义事件来向父组件通知事件发生。父组件可以监听这些事件并执行相应的操作。&lt;/li&gt;
&lt;li&gt;这是一种从子组件到父组件的通信方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 状态管理（如 Vuex）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于大型应用程序，可以使用状态管理库如 Vuex 来管理应用的状态。它提供了一个集中的状态存储，所有组件都可以访问和修改其中的数据。&lt;/li&gt;
&lt;li&gt;这是一种跨组件通信的高级方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 依赖注入：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue.js 提供了依赖注入机制，允许你在祖先组件中注册一些数据，然后在后代组件中访问这些数据，而不需要通过 props 一层层传递。&lt;/li&gt;
&lt;li&gt;依赖注入通常用于一些全局配置或主题样式的传递。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;总结：&lt;/strong&gt;
Vue 实例是整个应用的根对象，而组件是应用中的可复用模块。它们之间的通信主要通过 props 和自定义事件来实现，但对于更复杂的状态管理，可以使用 Vuex 或其他状态管理库。&lt;/p&gt;
&lt;h2&gt;Vue 中的声明周期钩子函数是什么？它们的执行顺序是怎样的？&lt;/h2&gt;
&lt;p&gt;Vue.js 中的生命周期钩子函数是一组特定的函数，它们允许你在组件的不同生命周期阶段执行代码。这些钩子函数可以用于执行初始化、数据加载、DOM 操作等任务。Vue 组件的生命周期钩子函数按照以下顺序执行：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;beforeCreate（创建前）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在组件实例被创建之前立即调用。&lt;/li&gt;
&lt;li&gt;此时组件的数据和事件还未初始化。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;created（创建后）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在组件实例被创建后立即调用。&lt;/li&gt;
&lt;li&gt;组件的数据已经初始化，但此时还未挂载到 DOM。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;beforeMount（挂载前）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在组件挂载到 DOM 之前立即调用。&lt;/li&gt;
&lt;li&gt;此时模板编译完成，但尚未将组件渲染到页面上。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;mounted（挂载后）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在组件挂载到 DOM 后立即调用。&lt;/li&gt;
&lt;li&gt;此时组件已经渲染到页面上，可以进行 DOM 操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;beforeUpdate（更新前）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在组件数据更新之前立即调用。&lt;/li&gt;
&lt;li&gt;在此钩子函数内，你可以访问之前的状态，但此时尚未应用最新的数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;updated（更新后）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在组件数据更新后立即调用。&lt;/li&gt;
&lt;li&gt;此时组件已经重新渲染，可以进行 DOM 操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;beforeDestroy（销毁前）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在组件销毁之前立即调用。&lt;/li&gt;
&lt;li&gt;此时组件仍然可用，你可以执行一些清理工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;destroyed（销毁后）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在组件销毁后立即调用。&lt;/li&gt;
&lt;li&gt;此时组件已经被完全销毁，不再可用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些生命周期钩子函数允许你在不同的阶段执行代码，以满足应用程序的需求。例如，在 &lt;code&gt;created&lt;/code&gt; 钩子中可以进行数据初始化，而在 &lt;code&gt;mounted&lt;/code&gt; 钩子中可以进行 DOM 操作。请注意，不同的生命周期钩子适合不同的用途，应根据需要选择合适的钩子函数来执行相应的任务。&lt;/p&gt;
&lt;h2&gt;Vue 的双向数据绑定是如何实现的？请举例说明。&lt;/h2&gt;
&lt;p&gt;Vue.js 的双向数据绑定是通过其特有的响应式系统来实现的。这个系统使用了 ES6 的 Proxy 对象或者 Object.defineProperty()方法，以便在数据变化时通知视图进行更新。这意味着当你修改数据模型时，与之相关联的视图会自动更新，反之亦然。&lt;/p&gt;
&lt;p&gt;下面是一个简单的示例，演示了如何在 Vue.js 中实现双向数据绑定：&lt;/p&gt;
&lt;p&gt;HTML 模板：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;app&quot;&amp;gt;
  &amp;lt;input v-model=&quot;message&quot; type=&quot;text&quot; /&amp;gt;
  &amp;lt;p&amp;gt;{{ message }}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vue 实例的 JavaScript 代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;new Vue({
  el: &quot;#app&quot;,
  data: {
    message: &quot;Hello, Vue!&quot;,
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，我们使用了&lt;code&gt;v-model&lt;/code&gt;指令将&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;元素与 Vue 实例中的&lt;code&gt;message&lt;/code&gt;属性双向绑定。这意味着当你在输入框中输入文本时，&lt;code&gt;message&lt;/code&gt;的值会自动更新，同时当&lt;code&gt;message&lt;/code&gt;的值变化时，文本也会自动更新。&lt;/p&gt;
&lt;p&gt;当你在输入框中输入文字时，Vue 会自动将输入的值更新到&lt;code&gt;message&lt;/code&gt;属性中，因此实现了从视图到数据的更新。反过来，如果你在 JavaScript 代码中修改了&lt;code&gt;message&lt;/code&gt;属性的值，视图中的文本也会自动更新，实现了从数据到视图的更新。&lt;/p&gt;
&lt;p&gt;这种双向数据绑定使得数据与视图保持同步，大大简化了前端开发中处理用户输入和数据展示的任务。&lt;/p&gt;
&lt;h2&gt;Vue 中的计算属性和观察者的作用是什么？它们有什么区别？&lt;/h2&gt;
&lt;p&gt;在 Vue.js 中，计算属性（Computed Properties）和观察者（Watchers）都用于处理数据的变化，但它们有不同的作用和用途。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;计算属性（Computed Properties）：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;计算属性是 Vue.js 中的一种属性类型，它的值是基于其他数据属性计算而来的，类似于一个函数。计算属性的主要作用是将计算逻辑封装起来，以便在模板中直接引用，而且它们具有缓存机制，只有在依赖的数据发生变化时才会重新计算。&lt;/p&gt;
&lt;p&gt;主要特点和作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用于派生或计算基于现有数据属性的值。&lt;/li&gt;
&lt;li&gt;具有缓存机制，只有在相关数据发生变化时才会重新计算，提高性能。&lt;/li&gt;
&lt;li&gt;在模板中可以像普通属性一样直接引用。&lt;/li&gt;
&lt;li&gt;计算属性一般用于简单的数据转换、筛选、格式化等操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;{{ fullName }}&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      firstName: &quot;John&quot;,
      lastName: &quot;Doe&quot;,
    };
  },
  computed: {
    fullName() {
      return this.firstName + &quot; &quot; + this.lastName;
    },
  },
};
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;观察者（Watchers）：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;观察者是 Vue.js 中的一种方式，用于在数据变化时执行自定义的异步或开销较大的操作。你可以监听一个或多个数据属性的变化，并在数据变化时执行特定的函数。&lt;/p&gt;
&lt;p&gt;主要特点和作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用于在数据变化时执行自定义的操作，例如异步请求或复杂的数据处理。&lt;/li&gt;
&lt;li&gt;不具有缓存机制，每次数据变化都会触发执行。&lt;/li&gt;
&lt;li&gt;需要手动编写观察者函数来处理数据变化。&lt;/li&gt;
&lt;li&gt;可以监听多个数据属性的变化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;{{ message }}&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      value: &quot;Initial Value&quot;,
      message: &quot;&quot;,
    };
  },
  watch: {
    value(newValue, oldValue) {
      // 在value属性变化时执行的操作
      this.message = &quot;Value changed: &quot; + newValue;
    },
  },
};
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;区别：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;计算属性&lt;/strong&gt;主要用于对数据的转换和派生，具有缓存机制，只有在相关数据变化时才会重新计算，适合用于简单的数据处理。它们在模板中可以像普通属性一样直接引用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;观察者&lt;/strong&gt;用于在数据变化时执行自定义的操作，没有缓存机制，每次数据变化都会触发执行。适合处理复杂的异步操作或需要监听多个数据变化的情况。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;根据具体的需求，你可以选择使用计算属性或观察者来处理数据变化。通常，计算属性是首选，因为它们更简单且性能更高，而只有在需要特殊处理数据变化时才使用观察者。&lt;/p&gt;
&lt;h2&gt;谈谈你对 Vue 组件的理解。如何创建一个 Vue 组件？&lt;/h2&gt;
&lt;p&gt;Vue 组件是 Vue.js 应用中的可复用模块，它将一个页面拆分成多个独立的部分，每个部分有自己的状态、模板和行为。组件化是 Vue.js 的核心概念之一，它使前端开发更加模块化、可维护和可重用。&lt;/p&gt;
&lt;p&gt;创建一个 Vue 组件的基本步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;定义组件：&lt;/strong&gt; 首先，你需要定义一个 Vue 组件。组件可以使用 &lt;code&gt;Vue.component&lt;/code&gt; 方法或者使用单文件组件（.vue 文件）来定义。以下是一个使用 &lt;code&gt;Vue.component&lt;/code&gt; 定义组件的示例：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;Vue.component(&apos;my-component&apos;, { // 组件的选项 template: &apos;
&amp;lt;div&amp;gt;This is a custom component&amp;lt;/div&amp;gt;
&apos; })
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;在模板中使用组件：&lt;/strong&gt; 一旦定义了组件，你可以在父组件的模板中使用它。例如：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;my-component&amp;gt;&amp;lt;/my-component&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;传递数据给组件：&lt;/strong&gt; 你可以通过组件的 props 来传递数据给组件，使组件可以接收外部数据并在模板中使用。例如：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;my-component :message=&quot;message&quot;&amp;gt;&amp;lt;/my-component&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      message: &quot;Hello from parent component&quot;,
    };
  },
};
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在组件内部，你可以使用 &lt;code&gt;props&lt;/code&gt; 来接收这个数据，并在模板中使用它：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;{{ message }}&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  props: [&quot;message&quot;],
};
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;组件的生命周期：&lt;/strong&gt; 组件也具有生命周期钩子函数，允许你在不同的生命周期阶段执行代码。这些钩子函数包括 &lt;code&gt;beforeCreate&lt;/code&gt;、&lt;code&gt;created&lt;/code&gt;、&lt;code&gt;beforeMount&lt;/code&gt;、&lt;code&gt;mounted&lt;/code&gt; 等，用于执行初始化、数据加载、DOM 操作等任务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自定义事件：&lt;/strong&gt; 组件之间可以通过自定义事件进行通信。子组件可以触发自定义事件，而父组件可以监听这些事件并执行相应的操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;组件之间的通信：&lt;/strong&gt; 除了 props 和自定义事件，你还可以使用 Vuex 这样的状态管理工具来实现组件之间的通信和数据共享。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总之，Vue 组件是 Vue.js 应用中的核心概念之一，它使前端开发更加模块化和可维护，允许你将界面拆分成多个可复用的部分，每个部分都有自己的状态和行为。创建和使用组件是 Vue.js 开发中的重要部分，帮助你构建更高效和可维护的前端应用程序。&lt;/p&gt;
&lt;h2&gt;Vue 中的指令是什么？列举一些常用的指令，并简要介绍它们的作用。&lt;/h2&gt;
&lt;p&gt;在 Vue.js 中，指令（Directives）是一种特殊的 token，可以在模板中使用，以表示对 DOM 元素的行为。指令以 &lt;code&gt;v-&lt;/code&gt; 开头，后面跟着指令的名称，例如 &lt;code&gt;v-bind&lt;/code&gt;、&lt;code&gt;v-if&lt;/code&gt; 等。指令用于将模板中的数据与 DOM 元素进行绑定，控制元素的显示、隐藏、渲染和行为等。&lt;/p&gt;
&lt;p&gt;以下是一些常用的 Vue 指令以及它们的作用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;v-bind&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用：用于绑定元素的属性，将元素的属性值与 Vue 实例的数据进行绑定。&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;&amp;lt;img v-bind:src=&quot;imageUrl&quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;v-model&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用：用于实现表单元素与 Vue 实例数据的双向绑定，使用户输入能够自动更新数据，反之亦然。&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;&amp;lt;input v-model=&quot;message&quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;v-for&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用：用于循环渲染一个数组或对象的数据，生成多个元素。&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;&amp;lt;li v-for=&quot;item in items&quot;&amp;gt;{{ item }}&amp;lt;/li&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;v-if&lt;/code&gt; / &lt;code&gt;v-else-if&lt;/code&gt; / &lt;code&gt;v-else&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用：用于根据条件控制元素的显示和隐藏，类似于 JavaScript 中的条件语句。&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;&amp;lt;div v-if=&quot;show&quot;&amp;gt;This is shown&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;v-show&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用：用于根据条件控制元素的显示和隐藏，不同于&lt;code&gt;v-if&lt;/code&gt;，它是通过 CSS 的 &lt;code&gt;display&lt;/code&gt; 属性来控制，不会销毁和重新创建元素。&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;&amp;lt;div v-show=&quot;isVisible&quot;&amp;gt;This is shown&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;v-on&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用：用于监听 DOM 事件，并在事件触发时执行指定的方法。&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;&amp;lt;button v-on:click=&quot;handleClick&quot;&amp;gt;Click me&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;v-pre&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用：跳过此元素和其子元素的编译过程，直接将其作为原始 HTML 输出。&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;&amp;lt;div v-pre&amp;gt;{{ message }}&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;v-cloak&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用：在元素和 Vue 实例之间保持隐藏，直到 Vue 编译完成。&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;&amp;lt;div v-cloak&amp;gt;{{ message }}&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;v-once&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用：只渲染元素和组件一次，不再进行响应式更新。&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;&amp;lt;span v-once&amp;gt;{{ message }}&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些指令使你能够轻松地在模板中操作 DOM 元素，根据数据的变化实现视图的动态更新。每个指令都有自己的特定作用，让你能够以声明性的方式定义页面的交互和逻辑。你可以根据需要在模板中使用这些指令，从而构建强大的 Vue.js 应用程序。&lt;/p&gt;
&lt;h2&gt;Vuex 是什么？它的作用是什么？请描述 Vuex 应用程序的基本结构。&lt;/h2&gt;
&lt;p&gt;Vuex 是一个专为 Vue.js 应用程序开发的状态管理库。它主要用于管理 Vue.js 应用中的共享状态（如数据、状态、配置信息等），以便更好地组织、维护和跟踪应用中的数据流。Vuex 的核心思想是将应用中的状态集中存储在一个全局的 store 中，使得状态的变化可预测且可维护。&lt;/p&gt;
&lt;p&gt;Vuex 的主要作用包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;集中式状态管理：&lt;/strong&gt; Vuex 允许将应用的状态存储在一个单一的地方，称为 store。这个 store 是一个响应式的状态树，多个组件可以共享并访问这个状态，而不需要通过 props 层层传递数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;状态变化可追踪：&lt;/strong&gt; Vuex 使用了严格的状态变化追踪机制，每次状态发生变化时都会有明确的记录和日志，方便开发者追踪和调试应用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;组件通信：&lt;/strong&gt; Vuex 提供了一种统一的方式来管理组件之间的通信。组件可以通过提交 mutations 来修改状态，也可以通过派发 actions 来触发异步操作，并且这些操作都是可预测且可控制的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;中间件：&lt;/strong&gt; Vuex 支持中间件，可以在状态变化时执行一些额外的逻辑，例如日志记录、数据持久化等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一个基本的 Vuex 应用程序通常包括以下组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;State（状态）：&lt;/strong&gt; 存储应用程序的状态数据，通常是一个 JavaScript 对象。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mutations（突变）：&lt;/strong&gt; 用于修改状态的方法。每个 mutation 都有一个类型（type）和一个处理函数，用来执行实际的状态修改操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Actions（动作）：&lt;/strong&gt; 类似于 mutations，但是它可以包含异步操作，通常用于处理与服务器交互、数据获取等。Actions 负责提交 mutations 来修改状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Getters（计算属性）：&lt;/strong&gt; 用于从状态中派生出一些新的数据，类似于计算属性，可以被组件直接使用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Store（存储）：&lt;/strong&gt; 将状态、mutations、actions、getters 集中管理的对象，是 Vuex 的核心。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面是一个简单的 Vuex 应用程序的基本结构示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Vue from &quot;vue&quot;;
import Vuex from &quot;vuex&quot;;

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0,
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    },
  },
  actions: {
    incrementAsync(context) {
      setTimeout(() =&amp;gt; {
        context.commit(&quot;increment&quot;);
      }, 1000);
    },
  },
  getters: {
    doubleCount(state) {
      return state.count * 2;
    },
  },
});

export default store;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上述示例中，我们定义了一个包含状态、突变、动作和计算属性的 Vuex store。这个 store 可以在 Vue 组件中被引用，并用于管理和操作应用程序的状态。 Vuex 的使用可以极大地简化状态管理和组件通信，特别是在大型应用程序中。&lt;/p&gt;
&lt;h2&gt;Vue Router 是什么？它的作用是什么？请描述 Vue Router 的基本使用方法。&lt;/h2&gt;
&lt;p&gt;Vue Router 是 Vue.js 官方的路由管理库，用于构建单页应用程序（SPA）。它允许你在 Vue 应用中实现页面之间的导航、路由跳转和 URL 的管理。Vue Router 的主要作用是将不同的视图组件与应用的不同路由（URL 地址）进行关联，从而实现页面之间的切换和导航。&lt;/p&gt;
&lt;p&gt;Vue Router 的基本使用方法包括以下步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;安装 Vue Router：&lt;/strong&gt; 首先，在你的 Vue.js 项目中安装 Vue Router。你可以使用 npm 或 yarn 进行安装：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install vue-router
# 或者
yarn add vue-router
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建路由配置：&lt;/strong&gt; 在你的项目中创建一个路由配置文件，通常命名为 &lt;code&gt;router.js&lt;/code&gt;，并导入 Vue 和 Vue Router：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Vue from &quot;vue&quot;;
import VueRouter from &quot;vue-router&quot;;

Vue.use(VueRouter);

const routes = [
  {
    path: &quot;/&quot;, // 路由路径
    component: Home, // 对应的视图组件
  },
  {
    path: &quot;/about&quot;,
    component: About,
  },
  // 其他路由配置
];

const router = new VueRouter({
  routes, // 使用配置文件中的路由规则
});

export default router;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建视图组件：&lt;/strong&gt; 为每个路由路径创建对应的视图组件。这些组件可以是普通的 Vue 组件，例如 &lt;code&gt;Home.vue&lt;/code&gt; 和 &lt;code&gt;About.vue&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;在根组件中使用 Router：&lt;/strong&gt; 在根 Vue 实例中使用 Vue Router，通常是在 &lt;code&gt;main.js&lt;/code&gt; 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Vue from &quot;vue&quot;;
import App from &quot;./App.vue&quot;;
import router from &quot;./router&quot;; // 导入路由配置

new Vue({
  el: &quot;#app&quot;,
  router, // 使用路由配置
  render: (h) =&amp;gt; h(App),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用 &lt;code&gt;&amp;lt;router-link&amp;gt;&lt;/code&gt; 和 &lt;code&gt;&amp;lt;router-view&amp;gt;&lt;/code&gt;：&lt;/strong&gt; 在模板中使用 &lt;code&gt;&amp;lt;router-link&amp;gt;&lt;/code&gt; 标签来创建导航链接，使用 &lt;code&gt;&amp;lt;router-view&amp;gt;&lt;/code&gt; 标签来渲染当前路由的视图组件。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;router-link to=&quot;/&quot;&amp;gt;Home&amp;lt;/router-link&amp;gt;
    &amp;lt;router-link to=&quot;/about&quot;&amp;gt;About&amp;lt;/router-link&amp;gt;

    &amp;lt;router-view&amp;gt;&amp;lt;/router-view&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;导航和路由跳转：&lt;/strong&gt; 你可以使用 &lt;code&gt;&amp;lt;router-link&amp;gt;&lt;/code&gt; 来实现路由导航，也可以在组件中使用 &lt;code&gt;this.$router.push()&lt;/code&gt; 方法来进行编程式的路由跳转。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些是 Vue Router 的基本使用方法。它允许你在 Vue.js 应用中轻松实现页面之间的导航和路由切换，使单页应用程序的开发更加方便和可维护。通过定义路由配置和关联视图组件，你可以构建出丰富的单页应用程序，将不同的视图组件与不同的 URL 路由进行关联。&lt;/p&gt;
&lt;h2&gt;Vue2 和 Vue3 的区别？&lt;/h2&gt;
&lt;p&gt;Vue.js 2 和 Vue.js 3 之间存在一些重要的区别和改进。以下是一些主要的区别和特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;性能优化：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 在底层进行了许多性能优化，包括虚拟 DOM 的升级，使其更快速和高效。&lt;/li&gt;
&lt;li&gt;Vue 3 引入了懒加载（Lazy Loading）和静态提升（Static Hoisting）等优化策略，进一步提高了性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Composition API：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 引入了 Composition API，这是一个基于函数的 API，可以更灵活地组织和重用组件逻辑。&lt;/li&gt;
&lt;li&gt;Composition API 允许开发者按功能划分代码，提高了代码的可读性和维护性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;更小的包体积：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 的核心库体积更小，因此加载更快。&lt;/li&gt;
&lt;li&gt;Vue 3 支持按需加载，使得只引入需要的功能，进一步减小包体积。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Teleport：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 引入了 Teleport，允许将组件的内容渲染到 DOM 中的任何位置，这在处理模态框、弹出菜单等场景中非常有用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fragments：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 支持 Fragments，允许组件返回多个根元素，而不需要额外的容器元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全局 API 的修改：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 对全局 API 进行了一些修改，使其更符合现代 JavaScript 的标准。&lt;/li&gt;
&lt;li&gt;例如，&lt;code&gt;Vue.component&lt;/code&gt; 现在改为 &lt;code&gt;app.component&lt;/code&gt;，&lt;code&gt;Vue.directive&lt;/code&gt; 改为 &lt;code&gt;app.directive&lt;/code&gt;，&lt;code&gt;Vue.mixin&lt;/code&gt; 改为 &lt;code&gt;app.mixin&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;新的生命周期钩子：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 引入了新的生命周期钩子，如 &lt;code&gt;onBeforeMount&lt;/code&gt; 和 &lt;code&gt;onBeforeUpdate&lt;/code&gt;，以提供更精确的控制和更好的性能优化机会。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TypeScript 支持改进：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 对 TypeScript 的支持更加完善，提供了更好的类型推断和类型检查。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;响应式系统的改进：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 对响应式系统进行了改进，提供了更好的 TypeScript 支持，并且更加高效。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总的来说，Vue.js 3 在性能、开发体验和可维护性等方面都有显著的改进。然而，Vue 2 仍然是一个稳定的版本，具有广泛的生态系统和支持，开发者可以根据项目需求来选择使用哪个版本。如果你正在开始一个新项目，Vue 3 可能是一个更好的选择，因为它具备了许多优势和改进。如果你正在维护一个 Vue 2 项目，也可以考虑逐渐迁移到 Vue 3，以获得性能和开发体验上的改进。&lt;/p&gt;
&lt;h2&gt;你能列举一些 Vue3 中的新特性吗？&lt;/h2&gt;
&lt;p&gt;以下是 Vue.js 3 中一些重要的新特性和改进：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Composition API：&lt;/strong&gt; Composition API 是 Vue 3 最引人注目的新特性之一。它允许你按功能划分代码，将相关的代码逻辑组织在一起，提高了可维护性和代码复用性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Teleport：&lt;/strong&gt; Teleport 是一个新的特性，允许你将组件的内容渲染到 DOM 中的其他位置。这对于创建模态框、弹出菜单等组件非常有用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fragments：&lt;/strong&gt; Vue 3 支持 Fragments，允许一个组件返回多个根元素，而不需要额外的包装容器元素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全局 API 的修改：&lt;/strong&gt; Vue 3 对全局 API 进行了一些修改，使其更符合现代 JavaScript 的标准。例如，&lt;code&gt;Vue.component&lt;/code&gt; 现在改为 &lt;code&gt;app.component&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;性能优化：&lt;/strong&gt; Vue 3 在底层进行了许多性能优化，包括虚拟 DOM 的升级，懒加载和静态提升等策略，使应用程序更快速和高效。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;响应式系统改进：&lt;/strong&gt; Vue 3 对响应式系统进行了改进，提供了更好的 TypeScript 支持和更高效的响应式数据追踪。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TypeScript 支持：&lt;/strong&gt; Vue 3 对 TypeScript 的支持更加完善，提供了更好的类型推断和类型检查。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;更小的包体积：&lt;/strong&gt; Vue 3 的核心库体积更小，加载更快，并且支持按需加载，减小了包体积。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;生命周期钩子的改进：&lt;/strong&gt; Vue 3 引入了新的生命周期钩子，如 &lt;code&gt;onBeforeMount&lt;/code&gt; 和 &lt;code&gt;onBeforeUpdate&lt;/code&gt;，提供了更精确的控制和性能优化的机会。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Suspense：&lt;/strong&gt; Vue 3 支持 Suspense 特性，允许你优雅地处理异步组件的加载状态，提供更好的用户体验。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自定义渲染器：&lt;/strong&gt; Vue 3 允许你创建自定义渲染器，这使得你可以在不同的目标环境中使用 Vue，例如服务器端渲染（SSR）或原生应用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;V-model 的改进：&lt;/strong&gt; Vue 3 改进了 v-model 的语法，使其更加灵活，可以用于自定义组件的双向绑定。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些新特性和改进使 Vue.js 3 成为一个更加强大、高效和灵活的前端框架，有助于开发者构建更优秀的单页应用和用户界面。&lt;/p&gt;
&lt;h2&gt;请解释 Composition API 是什么以及它的优势是什么？&lt;/h2&gt;
&lt;p&gt;Composition API 是 Vue.js 3 中引入的一种新的组件组织方式，它允许你按功能划分和组织组件的代码逻辑。这是一种基于函数的 API 风格，与传统的 Options API 相对立，它的主要优势包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;更灵活的代码组织：&lt;/strong&gt; Composition API 允许你将一个组件的代码逻辑分成多个功能相关的部分，每个部分都是一个独立的函数。这使得代码更加清晰，易于维护和测试。你可以更容易地重用代码逻辑，将其应用于多个组件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;更好的类型推断：&lt;/strong&gt; Composition API 配合 TypeScript 使用时，&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Vue 3 中有哪些性能优化措施？&lt;/h2&gt;
&lt;p&gt;Vue 3 在性能优化方面引入了许多新特性和改进，以提高应用程序的性能。以下是一些 Vue 3 中的性能优化措施：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;虚拟 DOM 重写&lt;/strong&gt;：Vue 3 的虚拟 DOM 实现进行了重写，使其更快速和轻量化。这意味着渲染和更新性能更高。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;静态树提升&lt;/strong&gt;：Vue 3 可以检测静态的子树，并将其提升为静态 vnode，以避免不必要的重新渲染和对比操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;树懒加载&lt;/strong&gt;：Vue 3 支持树懒加载，只在需要时才会渲染子组件，减少了初始渲染的负担。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;更好的事件处理&lt;/strong&gt;：Vue 3 采用了更高效的事件监听和处理方式，提高了事件处理性能。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编译器优化&lt;/strong&gt;：Vue 3 的模板编译器进行了优化，生成更有效的渲染函数，减少了运行时的开销。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fragment 和 Teleport&lt;/strong&gt;：Vue 3 引入了 Fragment 和 Teleport，这些特性可以帮助你更有效地组织你的组件，减少不必要的嵌套和渲染节点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Suspense&lt;/strong&gt;：Vue 3 中的 Suspense 特性允许你在异步组件加载时显示占位符，这有助于提高用户体验，同时减少了不必要的渲染。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;响应式系统重写&lt;/strong&gt;：Vue 3 的响应式系统进行了重写，使其更快速和可扩展。它采用了 Proxy 代理，比 Vue 2 的 Object.defineProperty 更高效。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Composition API&lt;/strong&gt;：Vue 3 引入了 Composition API，允许你更灵活地组织和重用代码，这有助于提高代码的性能和可维护性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tree-Shaking&lt;/strong&gt;：由于 Vue 3 采用了 ES 模块的方式组织代码，因此 Webpack 等构建工具可以更容易地进行 Tree-Shaking，只包含应用程序实际使用的代码，减小了包的大小。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些性能优化措施使 Vue 3 成为一个更快速和高效的前端框架，有助于构建更具响应性和流畅性的 Web 应用程序。但请注意，性能优化也取决于你的具体应用程序和使用方式，因此在实际项目中，你可能需要进一步的性能分析和调整。&lt;/p&gt;
&lt;h2&gt;什么是 Teleport 和 Fragments，它们在 Vue 3 中的作用是什么？&lt;/h2&gt;
&lt;p&gt;在 Vue 3 中，Teleport 和 Fragments 是两个新的特性，它们分别用于改善组件的渲染结构和渲染位置的控制。以下是它们的作用和用法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Teleport（传送门）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：Teleport 允许你将组件的内容渲染到 DOM 结构的不同位置，而不受父组件的限制。这对于处理模态框、对话框、通知消息等需要在页面的不同位置渲染的情况非常有用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用法&lt;/strong&gt;：你可以在模板中使用 &lt;code&gt;&amp;lt;teleport&amp;gt;&lt;/code&gt; 元素，并将其 &lt;code&gt;to&lt;/code&gt; 属性设置为一个目标选择器，以指定内容应该被渲染到哪个 DOM 元素中。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;button @click=&quot;showModal&quot;&amp;gt;Show Modal&amp;lt;/button&amp;gt;
    &amp;lt;teleport to=&quot;#modal-container&quot;&amp;gt;
      &amp;lt;Modal v-if=&quot;isModalVisible&quot; @close=&quot;closeModal&quot; /&amp;gt;
    &amp;lt;/teleport&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在上面的示例中，Modal 组件的内容会被渲染到页面中具有 &lt;code&gt;id=&quot;modal-container&quot;&lt;/code&gt; 的 DOM 元素内部。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fragments（片段）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：Fragments 允许你在不引入额外的 DOM 元素的情况下，将多个子元素包裹在一个父元素中。这有助于减少 DOM 结构的嵌套，使代码更清晰和简洁。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用法&lt;/strong&gt;：你可以使用 &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; 元素或 Vue 3 提供的特殊语法 &lt;code&gt;v-fragment&lt;/code&gt; 来创建一个 Fragment。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;Paragraph 1&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;Paragraph 2&amp;lt;/p&amp;gt;
    &amp;lt;v-fragment&amp;gt;
      &amp;lt;p&amp;gt;Paragraph 3&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;Paragraph 4&amp;lt;/p&amp;gt;
    &amp;lt;/v-fragment&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上面的示例中，&lt;code&gt;&amp;lt;v-fragment&amp;gt;&lt;/code&gt; 包裹了两个 &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 元素，但最终渲染的 DOM 结构中并不会包含额外的父元素。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Teleport 和 Fragments 是 Vue 3 中的两个强大的工具，它们有助于更灵活、更清晰地管理组件的渲染结构，同时提高了代码的可读性和维护性。这两个特性在处理复杂布局和可复用组件时尤其有用。&lt;/p&gt;
&lt;h2&gt;Vue 3 中对全局 API 进行了哪些修改？如何使用这些修改后的 API？&lt;/h2&gt;
&lt;p&gt;Vue 3 对全局 API 进行了一些修改，以提供更好的性能和功能。以下是一些主要的修改和如何使用这些修改后的 API：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建 Vue 实例&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改前（Vue 2）&lt;/strong&gt;：在 Vue 2 中，你可以使用 &lt;code&gt;new Vue()&lt;/code&gt; 创建根 Vue 实例。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改后（Vue 3）&lt;/strong&gt;：在 Vue 3 中，你可以使用 &lt;code&gt;createApp()&lt;/code&gt; 来创建应用实例，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createApp } from &quot;vue&quot;;
const app = createApp(App);
app.mount(&quot;#app&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全局组件的注册&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改前（Vue 2）&lt;/strong&gt;：在 Vue 2 中，你可以使用 &lt;code&gt;Vue.component()&lt;/code&gt; 全局注册组件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改后（Vue 3）&lt;/strong&gt;：在 Vue 3 中，你可以使用 &lt;code&gt;app.component()&lt;/code&gt; 注册全局组件，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.component(&quot;my-component&quot;, MyComponent);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;过滤器&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;修改前（Vue 2）&lt;/strong&gt;：Vue 2 中支持过滤器，但在 Vue 3 中已经移除了过滤器的概念。你可以使用计算属性或方法来代替过滤器的功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;混入（Mixins）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改前（Vue 2）&lt;/strong&gt;：在 Vue 2 中，你可以使用 &lt;code&gt;mixins&lt;/code&gt; 选项来混入组件的选项。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改后（Vue 3）&lt;/strong&gt;：在 Vue 3 中，你可以使用 &lt;code&gt;mix&lt;/code&gt; 函数来实现类似的功能，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineComponent, ref, mix } from &quot;vue&quot;;

const mixin = {
  data() {
    return {
      message: &quot;Hello from mixin&quot;,
    };
  },
};

const MyComponent = defineComponent({
  mixins: [mixin],
  setup() {
    const count = ref(0);
    return {
      count,
    };
  },
  template: `
    &amp;lt;div&amp;gt;
      {{ message }}
      {{ count }}
    &amp;lt;/div&amp;gt;
  `,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自定义指令&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改前（Vue 2）&lt;/strong&gt;：在 Vue 2 中，你可以使用 &lt;code&gt;Vue.directive()&lt;/code&gt; 注册全局自定义指令。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改后（Vue 3）&lt;/strong&gt;：在 Vue 3 中，你可以使用 &lt;code&gt;app.directive()&lt;/code&gt; 注册全局自定义指令，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.directive(&quot;my-directive&quot;, {
  // 自定义指令的定义
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些是一些主要的全局 API 修改。在 Vue 3 中，全局 API 的使用方式与 Vue 2 有一些不同，因此在迁移项目或编写新的 Vue 3 代码时，需要注意这些变化。你需要根据具体的情况来使用新的 API，以确保你的应用能够充分利用 Vue 3 的功能和性能优势。&lt;/p&gt;
&lt;h2&gt;请解释 Vue 3 中的响应式系统是如何工作的？&lt;/h2&gt;
&lt;p&gt;Vue 3 的响应式系统是其核心功能之一，它允许你在应用程序中实现数据与视图的自动同步。下面是 Vue 3 中的响应式系统如何工作的简要解释：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;初始化&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当你创建一个 Vue 3 组件或应用程序时，Vue 会初始化一个响应式系统的实例。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据定义&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你通过在组件的 &lt;code&gt;setup&lt;/code&gt; 函数中创建响应式数据。这可以通过 &lt;code&gt;ref&lt;/code&gt;、&lt;code&gt;reactive&lt;/code&gt;、或 &lt;code&gt;computed&lt;/code&gt; 来实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据依赖追踪&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当组件渲染时，Vue 会自动追踪数据属性的依赖关系。这意味着 Vue 知道哪些数据属性被用于渲染视图。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;响应式依赖收集&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 会在组件渲染期间收集数据属性的依赖，构建一个依赖关系图。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据变更时触发&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当响应式数据属性发生变化时，Vue 会通知依赖于该数据属性的视图更新。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;批量更新&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 会将多个数据变更的通知进行批处理，以最小化 DOM 更新操作，提高性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;异步更新队列&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 使用微任务队列（如 &lt;code&gt;Promise&lt;/code&gt; 或 &lt;code&gt;nextTick&lt;/code&gt;）来处理数据更新，确保在同一事件循环中的多次数据变更只触发一次视图更新。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;视图更新&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一旦数据变更通知到视图，Vue 3 会重新渲染相关的组件部分，使其与最新的数据保持同步。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;计算属性和侦听器&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 允许你使用计算属性（&lt;code&gt;computed&lt;/code&gt;）和侦听器（&lt;code&gt;watch&lt;/code&gt;）来处理数据的派生和监听变化，这些特性也依赖于响应式系统来工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总的来说，Vue 3 的响应式系统通过数据依赖追踪和自动的视图更新机制，实现了数据与视图之间的自动同步。这使得开发者可以更专注于数据的处理，而不必手动操作 DOM，提高了开发效率并改善了代码的可维护性。&lt;/p&gt;
&lt;h2&gt;Ref 和 Reactive 的区别是什么？&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ref&lt;/code&gt; 和 &lt;code&gt;reactive&lt;/code&gt; 是 Vue 3 中用于创建响应式数据的两种不同方式，它们有一些重要的区别：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;引用类型&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ref&lt;/code&gt;：&lt;code&gt;ref&lt;/code&gt; 用于创建单个响应式数据。它将一个普通的 JavaScript 值（如数字、字符串等）包装在一个具有 &lt;code&gt;.value&lt;/code&gt; 属性的对象中，使其成为响应式数据。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reactive&lt;/code&gt;：&lt;code&gt;reactive&lt;/code&gt; 用于创建一个包含多个属性的响应式对象。它接受一个普通 JavaScript 对象，并返回一个响应式代理对象，这个代理对象可以让对象内的属性变成响应式数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;访问方式&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ref&lt;/code&gt;：你可以通过 &lt;code&gt;.value&lt;/code&gt; 属性来访问 &lt;code&gt;ref&lt;/code&gt; 中的值。例如：&lt;code&gt;myRef.value&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reactive&lt;/code&gt;：你可以直接访问 &lt;code&gt;reactive&lt;/code&gt; 对象内的属性。例如：&lt;code&gt;myReactiveObj.someProperty&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用途&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ref&lt;/code&gt;：通常用于包装基本数据类型，如数字、字符串、布尔值等，或者用于包装需要通过 &lt;code&gt;.value&lt;/code&gt; 来更新的数据。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reactive&lt;/code&gt;：通常用于创建包含多个属性的响应式数据对象，比如复杂的配置对象或组件的状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code&gt;ref&lt;/code&gt; 创建响应式数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { ref } from &quot;vue&quot;;

const count = ref(0); // 创建一个包装数字的 ref
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code&gt;reactive&lt;/code&gt; 创建响应式对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { reactive } from &quot;vue&quot;;

const person = reactive({
  name: &quot;Alice&quot;,
  age: 30,
}); // 创建一个包含多个属性的响应式对象
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总的来说，&lt;code&gt;ref&lt;/code&gt; 用于创建单个响应式数据，通常用于包装基本数据类型。而 &lt;code&gt;reactive&lt;/code&gt; 用于创建包含多个属性的响应式对象，通常用于复杂的数据结构或组件状态的管理。选择使用哪种方式取决于你的具体需求和数据结构。&lt;/p&gt;
&lt;h2&gt;Vue 3 对 TypeScript 的支持有哪些改进？如何在 Vue 3 中使用 TypeScript？&lt;/h2&gt;
&lt;p&gt;Vue 3 对 TypeScript 的支持有很多改进，使得在使用 TypeScript 和 Vue 3 结合开发变得更加流畅和类型安全。以下是一些关键的改进和使用 TypeScript 的指南：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 类型推断和类型声明&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 提供了更强大的类型推断，允许你获得更准确的类型检查。&lt;/li&gt;
&lt;li&gt;Vue 3 本身附带了 TypeScript 声明文件，因此你不需要额外安装声明文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 单文件组件&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单文件组件（.vue 文件）中的 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 部分可以使用 TypeScript 编写。&lt;/li&gt;
&lt;li&gt;你可以为组件的 &lt;code&gt;props&lt;/code&gt;、&lt;code&gt;data&lt;/code&gt;、&lt;code&gt;methods&lt;/code&gt; 等部分添加类型声明，以获得更好的类型检查。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 提供更多的类型定义&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 提供了丰富的类型定义，包括用于 &lt;code&gt;ref&lt;/code&gt;、&lt;code&gt;reactive&lt;/code&gt;、&lt;code&gt;computed&lt;/code&gt;、&lt;code&gt;watch&lt;/code&gt;、&lt;code&gt;provide&lt;/code&gt;、&lt;code&gt;inject&lt;/code&gt; 等功能的类型定义。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Composition API&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 的 Composition API 具有强大的 TypeScript 支持，可以更容易地编写可复用的逻辑。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;defineComponent&lt;/code&gt; 函数可以轻松定义类型安全的组件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. 类型安全的 Props&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在组件中，可以使用 &lt;code&gt;PropType&lt;/code&gt; 来定义 props 的类型。&lt;/li&gt;
&lt;li&gt;使用 TypeScript 的可选属性和默认值来确保 props 的类型安全。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;6. 自动化类型推断&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 可以自动推断许多属性的类型，减少了手动添加类型声明的需要。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;7. 类型安全的钩子函数&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 支持类型安全的生命周期钩子函数，如 &lt;code&gt;onMounted&lt;/code&gt;、&lt;code&gt;onUpdated&lt;/code&gt; 等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;8. TypeScript 装饰器支持&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 支持 TypeScript 装饰器，可以用于创建 mixin、自定义指令等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;9. 丰富的 TypeScript 文档&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 文档中提供了丰富的 TypeScript 示例和说明，方便开发者更好地了解如何在 Vue 3 中使用 TypeScript。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用 TypeScript 的指南&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安装 Vue 3：确保你的项目中安装了 Vue 3 和 TypeScript。&lt;/li&gt;
&lt;li&gt;创建组件：使用 &lt;code&gt;.vue&lt;/code&gt; 文件或者 Composition API 来创建组件，可以添加类型声明来定义组件的 props 和数据。&lt;/li&gt;
&lt;li&gt;利用编辑器支持：使用支持 TypeScript 的编辑器（如 VS Code）来获得更好的类型检查和自动补全。&lt;/li&gt;
&lt;li&gt;遵循 Vue 3 文档：查阅 Vue 3 的官方文档，其中有关于如何使用 TypeScript 的详细说明和示例。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总的来说，Vue 3 提供了强大的 TypeScript 支持，使得在 Vue 3 项目中使用 TypeScript 变得更加容易和可靠。你可以利用这些功能来提高代码质量、可维护性和开发效率。&lt;/p&gt;
&lt;h2&gt;请解释 Vue 3 中如何创建自定义指令和自定义组件。&lt;/h2&gt;
&lt;p&gt;Vue 3 中新增了一些生命周期钩子函数，以扩展组件的生命周期管理和逻辑。以下是新增的生命周期钩子以及它们的用途：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;beforeMount&lt;/code&gt;&lt;/strong&gt;（新增）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用途：在组件挂载之前调用。在此阶段，虚拟 DOM 已经准备好，但尚未渲染到真实 DOM 中。可用于执行一些准备工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;beforeUpdate&lt;/code&gt;&lt;/strong&gt;（新增）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用途：在组件更新之前调用。在此阶段，虚拟 DOM 已经更新，但尚未渲染到真实 DOM 中。可用于执行更新前的准备工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;updated&lt;/code&gt;&lt;/strong&gt;（新增）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用途：在组件更新之后调用。在此阶段，组件的数据已经同步到视图中。可用于执行一些与更新后的 DOM 相关的操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;beforeUnmount&lt;/code&gt;&lt;/strong&gt;（新增）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用途：在组件卸载之前调用。在此阶段，组件仍然完全可用。可用于执行一些清理工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;unmounted&lt;/code&gt;&lt;/strong&gt;（新增）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用途：在组件卸载之后调用。在此阶段，组件的所有资源已被释放，不再可用。可用于执行一些最终的清理工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些新增的生命周期钩子函数主要用于更细粒度的生命周期管理，允许你在组件不同生命周期阶段执行特定的操作。例如，你可以在 &lt;code&gt;beforeMount&lt;/code&gt; 钩子中执行一些与渲染前准备相关的操作，或者在 &lt;code&gt;updated&lt;/code&gt; 钩子中执行一些与更新后 DOM 操作相关的任务。&lt;/p&gt;
&lt;p&gt;除了新增的生命周期钩子，Vue 3 仍然支持 Vue 2 中的其他生命周期钩子，如 &lt;code&gt;created&lt;/code&gt;、&lt;code&gt;mounted&lt;/code&gt;、&lt;code&gt;beforeDestroy&lt;/code&gt; 和 &lt;code&gt;destroyed&lt;/code&gt; 等。这些生命周期钩子允许你更灵活地管理组件的生命周期，以满足不同的需求。&lt;/p&gt;
</content:encoded><category>前端面试</category><author>江辰</author></item><item><title>2023年中前端面试真题之React篇</title><link>https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8Breact%E7%AF%87/</link><guid isPermaLink="true">https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8Breact%E7%AF%87/</guid><pubDate>Sun, 03 Sep 2023 22:28:09 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;人的一生，总是难免有浮沉。不会永远如旭日东升，也不会永远痛苦潦倒。反复地一浮一沉，对于一个人来说，正是磨练。因此，浮在上面的，不必骄傲；沉在底下的，更用不着悲观。必须以率直、谦虚的态度，乐观进取、向前迈进。——松下幸之助&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;大家好，我是江辰，在如今的互联网大环境下，想必大家都或多或少且有感受，浮躁的社会之下，只有不断的保持心性，才能感知不同的收获，互勉。&lt;/p&gt;
&lt;p&gt;2023 年中最新的面试题集锦，时刻做好准备。&lt;/p&gt;
&lt;p&gt;本文首发于微信公众号：野生程序猿江辰&lt;/p&gt;
&lt;p&gt;欢迎大家点赞，收藏，关注&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/aaebcde3-9c4f-4146-b1bb-91bd39a63e9f.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;文章列表&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/161&quot;&gt;2023 年中前端面试真题之 JS 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/162&quot;&gt;2023 年中前端面试真题之 CSS 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/163&quot;&gt;2023 年中前端面试真题之 HTML 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/165&quot;&gt;2023 年中前端面试真题之 Vue 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/166&quot;&gt;2023 年中前端面试真题之编码篇&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;什么时候使用状态管理器？&lt;/h2&gt;
&lt;p&gt;从项目的整体架构来看，要选择适合项目背景的极速。如果项目背景不适合使用状态管理器，那就没有一定的必要性去使用，比如微信小程序等，可以从以下几个维度来看&lt;/p&gt;
&lt;h3&gt;用户的使用方式复杂&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不同身份的用户有不同的使用方式（比如普通用户和管理员）&lt;/li&gt;
&lt;li&gt;多个用户之间可以协作&lt;/li&gt;
&lt;li&gt;与服务器大量交互，或者使用了 WebSocket&lt;/li&gt;
&lt;li&gt;View 要从多个来源获取数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;从组件角度看&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;某个组件的状态，需要共享&lt;/li&gt;
&lt;li&gt;某个状态需要在任何地方都可以拿到&lt;/li&gt;
&lt;li&gt;一个组件需要改变全局状态&lt;/li&gt;
&lt;li&gt;一个组件需要改变另一个组件的状态&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;什么渲染劫持？&lt;/h2&gt;
&lt;p&gt;什么是渲染劫持，渲染劫持的概念是控制组件从另一个组件输出的能力，当然这个概念一般和 react 中的高阶组件（HOC）放在一起解释比较有明了。&lt;/p&gt;
&lt;p&gt;高阶组件可以在 render 函数中做非常多的操作，从而控制原组件的渲染输出，只要改变了原组件的渲染，我们都将它称之为一种渲染劫持。&lt;/p&gt;
&lt;p&gt;实际上，在高阶组件中，组合渲染和条件渲染都是渲染劫持的一种，通过反向继承，不仅可以实现以上两点，还可以增强由原组件 render 函数产生的 React 元素。&lt;/p&gt;
&lt;p&gt;实际的操作中通过操作 state、props 都可以实现渲染劫持&lt;/p&gt;
&lt;h2&gt;怎么实现 React 组件的国际化呢？&lt;/h2&gt;
&lt;p&gt;依赖于 i18next 的方案，对于庞大的业务项目有个很蛋疼的问题，那就是 json 文件的维护。每次产品迭代都需要增加新的配置，那么这份配置由谁来维护，怎么维护，都会有很多问题，而且如果你的项目要支持几十个国家的语言，那么这几十份文件又怎么维护。&lt;/p&gt;
&lt;p&gt;所以现在大厂比较常用的方案是，使用 AST，每次开发完新版本，通过 AST 去扫描所有的代码，找出代码中的中文，以中文为 key，调用智能翻译服务，去帮项目自动生成 json 文件。这样，再也不需要人为去维护 json 文件，一切都依赖工具进行自动化。目前已经有大厂开源，比如滴滴的 di18n，阿里的 kiwi&lt;/p&gt;
&lt;h2&gt;React 如何进行代码拆分？拆分的原则是什么？&lt;/h2&gt;
&lt;p&gt;我认为 react 的拆分前提是代码目录设计规范，模块定义规范，代码设计规范，符合程序设计的一般原则，例如高内聚、低耦合等等。&lt;/p&gt;
&lt;p&gt;在我们的 react 项目中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 api 层面我们单独封装，对外暴露 http 请求的结果。&lt;/li&gt;
&lt;li&gt;数据层我们使用的 mobx 封装处理异步请求和业务逻辑处理。&lt;/li&gt;
&lt;li&gt;试图层，尽量使用 mobx 层面的传递过来的数据，修改逻辑。&lt;/li&gt;
&lt;li&gt;静态类型的资源单独放置&lt;/li&gt;
&lt;li&gt;公共组件、高阶组件、插件单独放置&lt;/li&gt;
&lt;li&gt;工具类文件单独放置&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;React 中在哪捕获错误？&lt;/h2&gt;
&lt;p&gt;官网例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return &amp;lt;h1&amp;gt;Something went wrong.&amp;lt;/h1&amp;gt;;
    }

    return this.props.children;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ErrorBoundary&amp;gt;
  &amp;lt;MyWidget /&amp;gt;
&amp;lt;/ErrorBoundary&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是错误边界不会捕获：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try{}catch(err){}
///异步代码（例如 setTimeout 或 requestAnimationFrame 回调函数）
///服务端渲染
///它自身抛出来的错误（并非它的子组件)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;为什么说 React 中的 props 是只读的？&lt;/h2&gt;
&lt;p&gt;保证 react 的单向数据流的设计模式，使状态更可预测。如果允许自组件修改，那么一个父组件将状态传递给好几个子组件，这几个子组件随意修改，就完全不可预测，不知道在什么地方修改了状态，所以我们必须像纯函数一样保护 props 不被修改&lt;/p&gt;
&lt;h2&gt;怎样使用 Hooks 获取服务端数据？&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import React, { useState, useEffect } from &apos;react&apos;;
import axios from &apos;axios&apos;;
function App() {
  const [data, setData] = useState({ hits: [] });
  useEffect(async () =&amp;gt; {
    const result = await axios(
      &apos;https://api/url/to/data&apos;,
    );
    setData(result.data);
  });
  return (
    &amp;lt;ul&amp;gt;
      {data.hits.map(item =&amp;gt; (
        &amp;lt;li key={item.objectID}&amp;gt;
          &amp;lt;a href={item.url}&amp;gt;{item.title}&amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}
export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用 Hooks 要遵守哪些原则？&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;只在最顶层使用 Hook&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不要在循环，条件或嵌套函数中调用 Hook， 确保总是在你的 React 函数的最顶层调用他们。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;只在 React 函数中调用 Hook&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不要在普通的 JavaScript 函数中调用 Hook。你可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 在 React 的函数组件中调用 Hook&lt;/li&gt;
&lt;li&gt;✅ 在自定义 Hook 中调用其他 Hook&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;React Fiber 它的目的是解决什么问题？&lt;/h2&gt;
&lt;p&gt;React15 的 &lt;code&gt;StackReconciler&lt;/code&gt; 方案由于递归不可中断问题，如果 Diff 时间过长（JS 计算时间），会造成页面 UI 的无响应（比如输入框）的表现，&lt;code&gt;vdom&lt;/code&gt; 无法应用到 &lt;code&gt;dom&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，React16 实现了新的基于 &lt;code&gt;requestIdleCallback&lt;/code&gt; 的调度器（因为 &lt;code&gt;requestIdleCallback&lt;/code&gt; 兼容性和稳定性问题，自己实现了 &lt;code&gt;polyfill&lt;/code&gt;），通过任务优先级的思想，在高优先级任务进入的时候，中断 &lt;code&gt;reconciler&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;为了适配这种新的调度器，推出了 &lt;code&gt;FiberReconciler&lt;/code&gt;，将原来的树形结构（vdom）转换成 Fiber 链表的形式（child/sibling/return），整个 Fiber 的遍历是基于循环而非递归，可以随时中断。&lt;/p&gt;
&lt;p&gt;更加核心的是，基于 Fiber 的链表结构，对于后续（React 17 lane 架构）的异步渲染和 （可能存在的）worker 计算都有非常好的应用基础&lt;/p&gt;
&lt;h2&gt;说出几点你认为的 React 最佳实践&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://react.dev/learn/thinking-in-react&quot;&gt;参考官网&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;React 为什么要搞一个 Hooks？&lt;/h2&gt;
&lt;p&gt;官网回答：&lt;/p&gt;
&lt;h3&gt;动机&lt;/h3&gt;
&lt;p&gt;Hook 解决了我们五年来编写和维护成千上万的组件时遇到的各种各样看起来不相关的问题。无论你正在学习 React，或每天使用，或者更愿尝试另一个和 React 有相似组件模型的框架，你都可能对这些问题似曾相识。&lt;/p&gt;
&lt;h4&gt;在组件之间复用状态逻辑很难&lt;/h4&gt;
&lt;p&gt;React 没有提供将可复用性行为“附加”到组件的途径（例如，把组件连接到 store）。如果你使用过 React 一段时间，你也许会熟悉一些解决此类问题的方案，比如 render props 和 高阶组件。但是这类方案需要重新组织你的组件结构，这可能会很麻烦，使你的代码难以理解。如果你在 React DevTools 中观察过 React 应用，你会发现由 providers，consumers，高阶组件，render props 等其他抽象层组成的组件会形成“嵌套地狱”。尽管我们可以在 DevTools 过滤掉它们，但这说明了一个更深层次的问题：React 需要为共享状态逻辑提供更好的原生途径。&lt;/p&gt;
&lt;p&gt;你可以使用 Hook 从组件中提取状态逻辑，使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。&lt;/p&gt;
&lt;h4&gt;复杂组件变得难以理解&lt;/h4&gt;
&lt;p&gt;我们经常维护一些组件，组件起初很简单，但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。例如，组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是，同一个 componentDidMount 中可能也包含很多其它的逻辑，如设置事件监听，而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分，而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug，并且导致逻辑不一致。&lt;/p&gt;
&lt;p&gt;在多数情况下，不可能将组件拆分为更小的粒度，因为状态逻辑无处不在。这也给测试带来了一定挑战。同时，这也是很多人将 React 与状态管理库结合使用的原因之一。但是，这往往会引入了很多抽象概念，需要你在不同的文件之间来回切换，使得复用变得更加困难。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，Hook 将组件中相互关联的部分拆分成更小的函数（比如设置订阅或请求数据），而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态，使其更加可预测。&lt;/p&gt;
&lt;h4&gt;难以理解的 class&lt;/h4&gt;
&lt;p&gt;除了代码复用和代码管理会遇到困难外，我们还发现 class 是学习 React 的一大屏障。你必须去理解 JavaScript 中 this 的工作方式，这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案，这些代码非常冗余。大家可以很好地理解 props，state 和自顶向下的数据流，但对 class 却一筹莫展。即便在有经验的 React 开发者之间，对于函数组件与 class 组件的差异也存在分歧，甚至还要区分两种组件的使用场景。&lt;/p&gt;
&lt;p&gt;另外，React 已经发布五年了，我们希望它能在下一个五年也与时俱进。就像 Svelte，Angular，Glimmer 等其它的库展示的那样，组件预编译会带来巨大的潜力。尤其是在它不局限于模板的时候。最近，我们一直在使用 Prepack 来试验 component folding，也取得了初步成效。但是我们发现使用 class 组件会无意中鼓励开发者使用一些让优化措施无效的方案。class 也给目前的工具带来了一些问题。例如，class 不能很好的压缩，并且会使热重载出现不稳定的情况。因此，我们想提供一个使代码更易于优化的 API。&lt;/p&gt;
&lt;p&gt;为了解决这些问题，Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲，React 组件一直更像是函数。而 Hook 则拥抱了函数，同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案，无需学习复杂的函数式或响应式编程技术&lt;/p&gt;
&lt;h2&gt;状态管理解决了什么问题？&lt;/h2&gt;
&lt;h3&gt;专注 view 层&lt;/h3&gt;
&lt;p&gt;React 官网是这么简介的。JavaScript library for building user interfaces.专注 view 层 的特点决定了它不是一个全能框架，相比 angular 这种全能框架，React 功能较简单，单一。比如说没有前端路由，没有状态管理，没有一站式开发文档等。&lt;/p&gt;
&lt;h3&gt;f(state) = view&lt;/h3&gt;
&lt;p&gt;react 组件是根据 state （或者 props）去渲染页面的，类似于一个函数，输入 state，输出 view。不过这不是完整意义上的 MDV（Model Driven View），没有完备的 model 层。顺便提一句，感觉现在的组件化和 MDV 在前端开发中正火热，大势所趋...&lt;/p&gt;
&lt;h3&gt;state 自上而下流向、Props 只读&lt;/h3&gt;
&lt;p&gt;从我们最开始写 React 开始，就了解这条特点了。state 流向是自组件从外到内，从上到下的，而且传递下来的 props 是只读的，如果你想更改 props，只能上层组件传下一个包装好的 setState 方法。不像 angular 有 ng-model, vue 有 v-model， 提供了双向绑定的指令。React 中的约定就是这样，你可能觉得这很繁琐，不过 state 的流向却更清晰了，单向数据流在大型 spa 总是要讨好一些的。&lt;/p&gt;
&lt;p&gt;这些特点决定了，React 本身是没有提供强大的状态管理功能的，原生大概是三种方式。&lt;/p&gt;
&lt;h2&gt;函数式组件有没有生命周期？&lt;/h2&gt;
&lt;p&gt;它没有提供生命周期概念，不像 class 组件继承 React.component，可以让你使用生命周期以及特意强调相关概念&lt;/p&gt;
&lt;h2&gt;immutable 的原理是什么？&lt;/h2&gt;
&lt;p&gt;使用字典树持久化数据结构，更新时可优化对象生成逻辑，降低成本&lt;/p&gt;
&lt;h2&gt;怎么防止 HTML 被转义？&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;说说你是如何提高组件的渲染效率的&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/746a2794-4969-45c3-ac75-2eb47111b1a2.png&quot; alt=&quot;render&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;是什么&lt;/h3&gt;
&lt;p&gt;react 基于虚拟 DOM 和高效 Diff 算法的完美配合，实现了对 DOM 最小粒度的更新，大多数情况下，React 对 DOM 的渲染效率足以我们的业务日常&lt;/p&gt;
&lt;p&gt;复杂业务场景下，性能问题依然会困扰我们。此时需要采取一些措施来提升运行性能，避免不必要的渲染则是业务中常见的优化手段之一&lt;/p&gt;
&lt;h3&gt;如何做&lt;/h3&gt;
&lt;h4&gt;类组件：&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;继承 PureComponent&lt;/li&gt;
&lt;li&gt;使用 shouldComponentUpdate 优化&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;函数组件：&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;memo 模拟 PureComponent&lt;/li&gt;
&lt;li&gt;使用 useMemo 缓存变量&lt;/li&gt;
&lt;li&gt;使用 useCallback 缓存函数&lt;/li&gt;
&lt;li&gt;循环添加 key, key 最好用数组项的唯一值，不推荐用 index&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;在实际开发过程中，前端性能问题是一个必须考虑的问题，随着业务的复杂，遇到性能问题的概率也在增高&lt;/p&gt;
&lt;p&gt;除此之外，建议将页面进行更小的颗粒化，如果一个过大，当状态发生修改的时候，就会导致整个大组件的渲染，而对组件进行拆分后，粒度变小了，也能够减少子组件不必要的渲染&lt;/p&gt;
&lt;h2&gt;说说对高阶组件（HOC）的理解？&lt;/h2&gt;
&lt;p&gt;高阶函数（Higher-order function），至少满足下列一个条件的函数&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;接受一个或多个函数作为输入&lt;/li&gt;
&lt;li&gt;输出一个函数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 React 中，高阶组件即接受一个或多个组件作为参数并且返回一个组件，本质也就是一个函数，并不是一个组件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const EnhancedComponent = highOrderComponent(WrappedComponent);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述代码中，该函数接受一个组件 &lt;code&gt;WrappedComponent&lt;/code&gt; 作为参数，返回加工过的新组件 &lt;code&gt;EnhancedComponent&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;高阶组件的这种实现方式，本质上是一个装饰者设计模式&lt;/p&gt;
&lt;h2&gt;说说对 React refs 的理解？&lt;/h2&gt;
&lt;p&gt;Refs 在计算机中称为弹性文件系统（英语：Resilient File System，简称 ReFS）&lt;/p&gt;
&lt;p&gt;React 中的 Refs 提供了一种方式，允许我们访问 DOM 节点或在 render 方法中创建的 React 元素&lt;/p&gt;
&lt;p&gt;本质为 ReactDOM.render()返回的组件实例，如果是渲染组件则返回的是组件实例，如果渲染 dom 则返回的是具体的 dom 节点&lt;/p&gt;
&lt;p&gt;class&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return &amp;lt;div ref=&quot;myref&quot; /&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;hooks&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function App(props) {
  const myref = useRef()
  return (
    &amp;lt;&amp;gt;
      &amp;lt;div ref={myref}&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>前端面试</category><author>江辰</author></item><item><title>2023年中前端面试真题之HTML篇</title><link>https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8Bhtml%E7%AF%87/</link><guid isPermaLink="true">https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8Bhtml%E7%AF%87/</guid><pubDate>Sat, 02 Sep 2023 14:03:05 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;人的一生，总是难免有浮沉。不会永远如旭日东升，也不会永远痛苦潦倒。反复地一浮一沉，对于一个人来说，正是磨练。因此，浮在上面的，不必骄傲；沉在底下的，更用不着悲观。必须以率直、谦虚的态度，乐观进取、向前迈进。——松下幸之助&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;大家好，我是江辰，在如今的互联网大环境下，想必大家都或多或少且有感受，浮躁的社会之下，只有不断的保持心性，才能感知不同的收获，互勉。&lt;/p&gt;
&lt;p&gt;2023 年中最新的面试题集锦，时刻做好准备。&lt;/p&gt;
&lt;p&gt;本文首发于微信公众号：野生程序猿江辰&lt;/p&gt;
&lt;p&gt;欢迎大家点赞，收藏，关注&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/aaebcde3-9c4f-4146-b1bb-91bd39a63e9f.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;文章列表&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/161&quot;&gt;2023 年中前端面试真题之 JS 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/162&quot;&gt;2023 年中前端面试真题之 CSS 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/164&quot;&gt;2023 年中前端面试真题之 React 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/165&quot;&gt;2023 年中前端面试真题之 Vue 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/166&quot;&gt;2023 年中前端面试真题之编码篇&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;meta viewport 是做什么用的，怎么写？&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目的 是为了在移动端不让用户缩放页面使用的&lt;/p&gt;
&lt;p&gt;解释每个单词的含义&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;with=device-width 将布局视窗（layout viewport）的宽度设置为设备屏幕分辨率的宽度&lt;/li&gt;
&lt;li&gt;initial-scale=1 页面初始缩放比例为屏幕分辨率的宽度&lt;/li&gt;
&lt;li&gt;maximum-scale=1 指定用户能够放大的最大比例&lt;/li&gt;
&lt;li&gt;minimum-scale=1 指定用户能够缩小的最大比例&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;浏览器乱码的原因是什么?如何解决?&lt;/h2&gt;
&lt;h3&gt;编码格式不匹配&lt;/h3&gt;
&lt;p&gt;浏览器打开网页时,需要根据网页源代码的编码格式来解码。如果网页的编码格式与浏览器的编码格式不匹配,就会出现乱码。比如,网页的编码格式为 UTF-8,而浏览器的编码格式是 GB2312,那么就会出现乱码。&lt;/p&gt;
&lt;h3&gt;网页编码错误&lt;/h3&gt;
&lt;p&gt;在编写网页的时候,如果编码出现错误,也会导致浏览器打开网页时出现乱码。比如,在写 HTML 代码时,如果忘记给中文字符指定编码格式,就会出现乱码。&lt;/p&gt;
&lt;h3&gt;字体缺失&lt;/h3&gt;
&lt;p&gt;有些网页会使用比较特殊的字体,如果浏览器中没有这个字体,就会找不到对应的字符,从而出现乱码。&lt;/p&gt;
&lt;h2&gt;iframe 有那些优点和缺点?&lt;/h2&gt;
&lt;h3&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;可以在页面上独立显示一个页面或者内容,不会与页面其他元素产生冲突。&lt;/li&gt;
&lt;li&gt;可以在多个页面中重用同一个页面或者内容,可以减少代码的冗余。&lt;/li&gt;
&lt;li&gt;加载是异步的,页面可以在不等待 iframe 加载完成的情况下进行展示。&lt;/li&gt;
&lt;li&gt;方便地实现跨域访问&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;搜索引擎可能无法正确解析 iframe 中的内容&lt;/li&gt;
&lt;li&gt;会阻塞主页面的 onload 事件&lt;/li&gt;
&lt;li&gt;和主页面共享连接池,影响页面并行加载&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;HTML5 新特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;语义化标签&lt;/li&gt;
&lt;li&gt;增强型表单(如可以通过 input 的 type 属性指定类型是 color 还是 date 或者 url 等)&lt;/li&gt;
&lt;li&gt;媒体元素标签(video,audio)&lt;/li&gt;
&lt;li&gt;canvas,svg&lt;/li&gt;
&lt;li&gt;svg 绘图&lt;/li&gt;
&lt;li&gt;地理等位(navigator.geolocation.getCurrentPosition(callback))&lt;/li&gt;
&lt;li&gt;拖放 API(给标签元素设置属性 draggable 值为 true,能够实现对目标元素的拖动)&lt;/li&gt;
&lt;li&gt;Web Worker(可以开启一个子线程运行脚本)&lt;/li&gt;
&lt;li&gt;Web Storage(即 sessionStorage 与 localStorage)&lt;/li&gt;
&lt;li&gt;Websocket(双向通信协议,可以让浏览器接收服务端的请求)&lt;/li&gt;
&lt;li&gt;dom 查询(document.querySelector()和 document.querySelectorAll())&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;如何使用 HTML5 中的 Canvas 元素绘制图形？&lt;/h2&gt;
&lt;p&gt;Canvas 元素允许在网页上使用 JavaScript 绘制图形和动画。以下是一个简单的绘制矩形的示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;canvas id=&quot;myCanvas&quot; width=&quot;200&quot; height=&quot;200&quot;&amp;gt;&amp;lt;/canvas&amp;gt;
&amp;lt;script&amp;gt;
  var canvas = document.getElementById(&quot;myCanvas&quot;);
  var ctx = canvas.getContext(&quot;2d&quot;);
  ctx.fillStyle = &quot;red&quot;;
  ctx.fillRect(50, 50, 100, 100);
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，使用 &lt;code&gt;document.getElementById()&lt;/code&gt; 方法获取 &lt;code&gt;Canvas&lt;/code&gt; 元素，并通过 &lt;code&gt;getContext(“2d”)&lt;/code&gt; 获取 2D 绘图上下文。
然后，使用 &lt;code&gt;fillStyle&lt;/code&gt; 属性设置填充颜色，&lt;code&gt;fillRect()&lt;/code&gt; 方法绘制一个矩形。&lt;/p&gt;
&lt;h2&gt;什么是 data-属性？&lt;/h2&gt;
&lt;p&gt;在 JavaScript 框架变得流行之前，前端开发者经常使用 &lt;code&gt;data-&lt;/code&gt; 属性，把额外数据存储在 DOM 自身中。当时没有其他 Hack 手段（比如使用非标准属性或 DOM 上额外属性）。这样做是为了将自定义数据存储到页面或应用中，对此没有其他更适当的属性或元素。&lt;/p&gt;
&lt;p&gt;而现在，不鼓励使用 &lt;code&gt;data-&lt;/code&gt; 属性。原因之一是，用户可以通过在浏览器中利用检查元素，轻松地修改属性值，借此修改数据。数据模型最好存储在 JavaScript 本身中，并利用框架提供的数据绑定，使之与 DOM 保持更新。&lt;/p&gt;
&lt;h2&gt;请描述 cookie、sessionStorage 和 localStorage 的区别。&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;cookie&lt;/th&gt;
&lt;th&gt;localStorage&lt;/th&gt;
&lt;th&gt;sessionStorage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;由谁初始化&lt;/td&gt;
&lt;td&gt;客户端或服务器，服务器可以使用 &lt;code&gt;Set-Cookie&lt;/code&gt; 请求头。&lt;/td&gt;
&lt;td&gt;客户端&lt;/td&gt;
&lt;td&gt;客户端&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;过期时间&lt;/td&gt;
&lt;td&gt;手动设置&lt;/td&gt;
&lt;td&gt;永不过期&lt;/td&gt;
&lt;td&gt;当前页面关闭时&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;在当前浏览器会话（browser sessions）中是否保持不变&lt;/td&gt;
&lt;td&gt;取决于是否设置了过期时间&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;是否随着每个 HTTP 请求发送给服务器&lt;/td&gt;
&lt;td&gt;是，Cookies 会通过 &lt;code&gt;Cookie&lt;/code&gt; 请求头，自动发送给服务器&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;容量（每个域名）&lt;/td&gt;
&lt;td&gt;4kb&lt;/td&gt;
&lt;td&gt;5MB&lt;/td&gt;
&lt;td&gt;5MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;访问权限&lt;/td&gt;
&lt;td&gt;任意窗口&lt;/td&gt;
&lt;td&gt;任意窗口&lt;/td&gt;
&lt;td&gt;当前页面窗口&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;请描述 script、script async 和 script defer 的区别。&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; - HTML 解析中断，脚本被提取并立即执行。执行结束后，HTML 解析继续。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script async&amp;gt;&lt;/code&gt; - 脚本的提取、执行的过程与 HTML 解析过程并行，脚本执行完毕可能在 HTML 解析完毕之前。当脚本与页面上其他脚本独立时，可以使用 &lt;code&gt;async&lt;/code&gt;，比如用作页面统计分析。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script defer&amp;gt;&lt;/code&gt; - 脚本仅提取过程与 HTML 解析过程并行，脚本的执行将在 HTML 解析完毕后进行。如果有多个含 &lt;code&gt;defer&lt;/code&gt; 的脚本，脚本的执行顺序将按照在 document 中出现的位置，从上到下顺序执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意：没有 &lt;code&gt;src&lt;/code&gt; 属性的脚本，&lt;code&gt;async&lt;/code&gt; 和 &lt;code&gt;defer&lt;/code&gt; 属性会被忽略。&lt;/p&gt;
&lt;h2&gt;为什么最好把 CSS 的 link 标签放在 head 之间？为什么最好把 JS 的 script 标签恰好放在 body 之前，有例外情况吗？&lt;/h2&gt;
&lt;p&gt;把 &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; 标签放在 &lt;code&gt;&amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;&lt;/code&gt; 之间是规范要求的内容。此外，这种做法可以让页面逐步呈现，提高了用户体验。将样式表放在文档底部附近，会使许多浏览器（包括 Internet Explorer）不能逐步呈现页面。一些浏览器会阻止渲染，以避免在页面样式发生变化时，重新绘制页面中的元素。这种做法可以防止呈现给用户空白的页面或没有样式的内容。&lt;/p&gt;
&lt;p&gt;把 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 标签恰好放在 &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; 之前&lt;/p&gt;
&lt;p&gt;脚本在下载和执行期间会阻止 HTML 解析。把 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 标签放在底部，保证 HTML 首先完成解析，将页面尽早呈现给用户。&lt;/p&gt;
&lt;p&gt;例外情况是当你的脚本里包含 &lt;code&gt;document.write()&lt;/code&gt; 时。但是现在，&lt;code&gt;document.write()&lt;/code&gt; 不推荐使用。同时，将 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 标签放在底部，意味着浏览器不能开始下载脚本，直到整个文档 &lt;code&gt;（document）&lt;/code&gt; 被解析。也许，对此比较好的做法是，&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 使用 &lt;code&gt;defer&lt;/code&gt; 属性，放在 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 中。&lt;/p&gt;
&lt;h2&gt;什么是渐进式渲染（progressive rendering）？&lt;/h2&gt;
&lt;p&gt;渐进式渲染是用于提高网页性能（尤其是提高用户感知的加载速度），以尽快呈现页面的技术。&lt;/p&gt;
&lt;p&gt;在以前互联网带宽较小的时期，这种技术更为普遍。如今，移动终端的盛行，而移动网络往往不稳定，渐进式渲染在现代前端开发中仍然有用武之地。&lt;/p&gt;
&lt;p&gt;一些举例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;图片懒加载——页面上的图片不会一次性全部加载。当用户滚动页面到图片部分时，JavaScript 将加载并显示图像。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;确定显示内容的优先级（分层次渲染）——为了尽快将页面呈现给用户，页面只包含基本的最少量的 CSS、脚本和内容，然后可以使用延迟加载脚本或监听 &lt;code&gt;DOMContentLoaded&lt;/code&gt; / &lt;code&gt;load&lt;/code&gt; 事件加载其他资源和内容。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;异步加载 HTML 片段——当页面通过后台渲染时，把 HTML 拆分，通过异步请求，分块发送给浏览器。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>前端面试</category><author>江辰</author></item><item><title>2023年中前端面试真题之CSS篇</title><link>https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8Bcss%E7%AF%87/</link><guid isPermaLink="true">https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8Bcss%E7%AF%87/</guid><pubDate>Fri, 01 Sep 2023 20:13:58 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;人的一生，总是难免有浮沉。不会永远如旭日东升，也不会永远痛苦潦倒。反复地一浮一沉，对于一个人来说，正是磨练。因此，浮在上面的，不必骄傲；沉在底下的，更用不着悲观。必须以率直、谦虚的态度，乐观进取、向前迈进。——松下幸之助&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;大家好，我是江辰，在如今的互联网大环境下，想必大家都或多或少且有感受，浮躁的社会之下，只有不断的保持心性，才能感知不同的收获，互勉。&lt;/p&gt;
&lt;p&gt;2023 年中最新的面试题集锦，时刻做好准备。&lt;/p&gt;
&lt;p&gt;本文首发于微信公众号：野生程序猿江辰&lt;/p&gt;
&lt;p&gt;欢迎大家点赞，收藏，关注&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/aaebcde3-9c4f-4146-b1bb-91bd39a63e9f.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;文章列表&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/161&quot;&gt;2023 年中前端面试真题之 JS 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/163&quot;&gt;2023 年中前端面试真题之 HTML 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/164&quot;&gt;2023 年中前端面试真题之 React 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/165&quot;&gt;2023 年中前端面试真题之 Vue 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/166&quot;&gt;2023 年中前端面试真题之编码篇&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CSS 选择器的优先级是如何计算的？&lt;/h2&gt;
&lt;p&gt;浏览器通过优先级规则，判断元素展示哪些样式。优先级通过 4 个维度指标确定，我们假定以 &lt;code&gt;a、b、c、d&lt;/code&gt; 命名，分别代表以下含义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;a&lt;/code&gt;表示是否使用内联样式（inline style）。如果使用，&lt;code&gt;a&lt;/code&gt; 为 1，否则为 0。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;b&lt;/code&gt;表示 ID 选择器的数量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;c&lt;/code&gt;表示类选择器、属性选择器和伪类选择器数量之和。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;d&lt;/code&gt;表示标签（类型）选择器和伪元素选择器之和。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;优先级的结果并非通过以上四个值生成一个得分，而是每个值分开比较。&lt;code&gt;a、b、c、d&lt;/code&gt; 权重从左到右，依次减小。判断优先级时，从左到右，一一比较，直到比较出最大值，即可停止。所以，如果 &lt;code&gt;b&lt;/code&gt; 的值不同，那么 &lt;code&gt;c&lt;/code&gt; 和 &lt;code&gt;d&lt;/code&gt; 不管多大，都不会对结果产生影响。比如 &lt;code&gt;0，1，0，0&lt;/code&gt; 的优先级高于 &lt;code&gt;0，0，10，10&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;当出现优先级相等的情况时，最晚出现的样式规则会被采纳。如果你在样式表里写了相同的规则（无论是在该文件内部还是其它样式文件中），那么最后出现的（在文件底部的）样式优先级更高，因此会被采纳。&lt;/p&gt;
&lt;p&gt;在写样式时，我会使用较低的优先级，这样这些样式可以轻易地覆盖掉。尤其对写 UI 组件的时候更为重要，这样使用者就不需要通过非常复杂的优先级规则或使用 &lt;code&gt;!important&lt;/code&gt; 的方式，去覆盖组件的样式了。&lt;/p&gt;
&lt;h2&gt;重置（resetting）CSS 和 标准化（normalizing）CSS 的区别是什么？你会选择哪种方式，为什么？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;重置（Resetting）&lt;/strong&gt;： 重置意味着除去所有的浏览器默认样式。对于页面所有的元素，像 margin、padding、font-size 这些样式全部置成一样。你将必须重新定义各种元素的样式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标准化（Normalizing）&lt;/strong&gt;： 标准化没有去掉所有的默认样式，而是保留了有用的一部分，同时还纠正了一些常见错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当需要实现非常个性化的网页设计时，我会选择重置的方式，因为我要写很多自定义的样式以满足设计需求，这时候就不再需要标准化的默认样式了。&lt;/p&gt;
&lt;h2&gt;请阐述 Float 定位的工作原理。&lt;/h2&gt;
&lt;p&gt;浮动（float）是 CSS 定位属性。浮动元素从网页的正常流动中移出，但是保持了部分的流动性，会影响其他元素的定位（比如文字会围绕着浮动元素）。这一点与绝对定位不同，绝对定位的元素完全从文档流中脱离。&lt;/p&gt;
&lt;p&gt;CSS 的 &lt;code&gt;clear&lt;/code&gt; 属性通过使用 &lt;code&gt;left、right、both&lt;/code&gt;，让该元素向下移动（清除浮动）到浮动元素下面。&lt;/p&gt;
&lt;p&gt;如果父元素只包含浮动元素，那么该父元素的高度将塌缩为 0。我们可以通过清除（clear）从浮动元素后到父元素关闭前之间的浮动来修复这个问题。&lt;/p&gt;
&lt;p&gt;有一种 hack 的方法，是自定义一个 &lt;code&gt;.clearfix&lt;/code&gt; 类，利用伪元素选择器 &lt;code&gt;::after&lt;/code&gt; 清除浮动。另外还有一些方法，比如添加空的 &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; 和设置浮动元素父元素的 &lt;code&gt;overflow&lt;/code&gt; 属性。与这些方法不同的是，&lt;code&gt;clearfix&lt;/code&gt; 方法，只需要给父元素添加一个类，定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.clearfix::after {
  content: &apos;&apos;;
  display: block;
  clear: both;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;值得一提的是，把父元素属性设置为 &lt;code&gt;overflow: auto&lt;/code&gt; 或 &lt;code&gt;overflow: hidden&lt;/code&gt;，会使其内部的子元素形成块格式化上下文（Block Formatting Context），并且父元素会扩张自己，使其能够包围它的子元素。&lt;/p&gt;
&lt;h2&gt;请阐述 z-index 属性，并说明如何形成层叠上下文（stacking context）。&lt;/h2&gt;
&lt;p&gt;CSS 中的 &lt;code&gt;z-index&lt;/code&gt; 属性控制重叠元素的垂直叠加顺序。&lt;code&gt;z-index&lt;/code&gt; 只能影响 &lt;code&gt;position&lt;/code&gt; 值不是 &lt;code&gt;static&lt;/code&gt; 的元素。&lt;/p&gt;
&lt;p&gt;没有定义 &lt;code&gt;z-index&lt;/code&gt; 的值时，元素按照它们出现在 DOM 中的顺序堆叠（层级越低，出现位置越靠上）。非静态定位的元素（及其子元素）将始终覆盖静态定位（static）的元素，而不管 HTML 层次结构如何。&lt;/p&gt;
&lt;p&gt;层叠上下文是包含一组图层的元素。 在一组层叠上下文中，其子元素的 &lt;code&gt;z-index&lt;/code&gt; 值是相对于该父元素而不是 document root 设置的。每个层叠上下文完全独立于它的兄弟元素。如果元素 B 位于元素 A 之上，则即使元素 A 的子元素 C 具有比元素 B 更高的 z-index 值，元素 C 也永远不会在元素 B 之上.&lt;/p&gt;
&lt;p&gt;每个层叠上下文是自包含的：当元素的内容发生层叠后，整个该元素将会在父层叠上下文中按顺序进行层叠。少数 CSS 属性会触发一个新的层叠上下文，例如 opacity 小于 1，filter 不是 none，transform 不是 none。&lt;/p&gt;
&lt;p&gt;每个层叠上下文是自包含的：当元素的内容发生层叠后，整个该元素将会在父层叠上下文中按顺序进行层叠。少数 CSS 属性会触发一个新的层叠上下文，例如 &lt;code&gt;opacity&lt;/code&gt; 小于 1，&lt;code&gt;filter&lt;/code&gt; 不是 &lt;code&gt;none&lt;/code&gt;，&lt;code&gt;transform&lt;/code&gt; 不是 &lt;code&gt;none&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;请阐述块格式化上下文（Block Formatting Context）及其工作原理。&lt;/h2&gt;
&lt;p&gt;块格式上下文（BFC）是 Web 页面的可视化 CSS 渲染的部分，是块级盒布局发生的区域，也是浮动元素与其他元素交互的区域。&lt;/p&gt;
&lt;p&gt;一个 HTML 盒（Box）满足以下任意一条，会创建块格式化上下文：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;float 的值不是 none.&lt;/li&gt;
&lt;li&gt;position 的值不是 static 或 relative.&lt;/li&gt;
&lt;li&gt;display 的值是 table-cell、table-caption、inline-block、flex、或 inline-flex。&lt;/li&gt;
&lt;li&gt;overflow 的值不是 visible。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 BFC 中，每个盒的左外边缘都与其包含的块的左边缘相接。&lt;/p&gt;
&lt;p&gt;两个相邻的块级盒在垂直方向上的边距会发生合并（collapse）。&lt;/p&gt;
&lt;h2&gt;有哪些清除浮动的技术，都适用哪些情况？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;空 div 方法：&amp;lt;div style=&quot;clear:both;&quot;&amp;gt;&amp;lt;/div&amp;gt;。&lt;/li&gt;
&lt;li&gt;Clearfix 方法：上文使用.clearfix 类已经提到。&lt;/li&gt;
&lt;li&gt;overflow: auto 或 overflow: hidden 方法：上文已经提到。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在大型项目中，我会使用 Clearfix 方法，在需要的地方使用 &lt;code&gt;.clearfix&lt;/code&gt;。设置 &lt;code&gt;overflow: hidden&lt;/code&gt; 的方法可能使其子元素显示不完整，当子元素的高度大于父元素时。&lt;/p&gt;
&lt;h2&gt;请解释什么是精灵图（css sprites），以及如何实现？&lt;/h2&gt;
&lt;p&gt;精灵图，也称雪碧图。因常见碳酸饮料雪碧的英文名也是 Sprite，因此也有人会使用雪碧图的非正式译名。&lt;/p&gt;
&lt;p&gt;精灵图是把多张图片整合到一张上的图片。它被运用在众多使用了很多小图标的网站上（Gmail 在使用）。实现方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用生成器将多张图片打包成一张精灵图，并为其生成合适的 CSS。&lt;/li&gt;
&lt;li&gt;每张图片都有相应的 CSS 类，该类定义了 &lt;code&gt;background-image&lt;/code&gt;、&lt;code&gt;background-position&lt;/code&gt; 和 &lt;code&gt;background-size&lt;/code&gt; 属性。&lt;/li&gt;
&lt;li&gt;使用图片时，将相应的类添加到你的元素中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;减少加载多张图片的 HTTP 请求数（一张精灵图只需要一个请求）。但是对于 HTTP2 而言，加载多张图片不再是问题。&lt;/li&gt;
&lt;li&gt;提前加载资源，防止在需要时才在开始下载引发的问题，比如只出现在:hover 伪类中的图片，不会出现闪烁。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;如何解决不同浏览器的样式兼容性问题？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在确定问题原因和有问题的浏览器后，使用单独的样式表，仅供出现问题的浏览器加载。这种方法需要使用服务器端渲染。&lt;/li&gt;
&lt;li&gt;使用已经处理好此类问题的库，比如 Bootstrap。&lt;/li&gt;
&lt;li&gt;使用 autoprefixer 自动生成 CSS 属性前缀。&lt;/li&gt;
&lt;li&gt;使用 Reset CSS 或 Normalize.css。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;如何为功能受限的浏览器提供页面？ 使用什么样的技术和流程？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;优雅的降级：为现代浏览器构建应用，同时确保它在旧版浏览器中正常运行。&lt;/li&gt;
&lt;li&gt;渐进式增强：构建基于用户体验的应用，但在浏览器支持时添加新增功能。&lt;/li&gt;
&lt;li&gt;利用 caniuse.com 检查特性支持。&lt;/li&gt;
&lt;li&gt;使用 autoprefixer 自动生成 CSS 属性前缀。&lt;/li&gt;
&lt;li&gt;使用 Modernizr 进行特性检测。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;有什么不同的方式可以隐藏内容（使其仅适用于屏幕阅读器）？&lt;/h2&gt;
&lt;p&gt;这些方法与可访问性（a11y）有关。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;width: 0; height: 0：使元素不占用屏幕上的任何空间，导致不显示它。&lt;/li&gt;
&lt;li&gt;position: absolute; left: -99999px： 将它置于屏幕之外。&lt;/li&gt;
&lt;li&gt;text-indent: -9999px：这只适用于 block 元素中的文本。&lt;/li&gt;
&lt;li&gt;Metadata： 例如通过使用 Schema.org，RDF 和 JSON-LD。&lt;/li&gt;
&lt;li&gt;WAI-ARIA：如何增加网页可访问性的 W3C 技术规范。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;即使 WAI-ARIA 是理想的解决方案，我也会采用绝对定位方法，因为它具有最少的注意事项，适用于大多数元素，而且使用起来非常简单。&lt;/p&gt;
&lt;h2&gt;你熟悉制作 SVG 吗？&lt;/h2&gt;
&lt;p&gt;是的，你可以使用内联 CSS、嵌入式 CSS 部分或外部 CSS 文件对形状进行着色（包括指定对象上的属性）。在网上大部分 SVG 使用的是内联 CSS，不过每个类型都有优点和缺点。&lt;/p&gt;
&lt;p&gt;通过设置 &lt;code&gt;fill&lt;/code&gt; 和 &lt;code&gt;stroke&lt;/code&gt; 属性，可以完成基本着色操作。&lt;code&gt;fill&lt;/code&gt; 可以设置内部的颜色，&lt;code&gt;stroke&lt;/code&gt; 可以设置周围绘制的线条的颜色。你可以使用与 &lt;code&gt;HTML&lt;/code&gt; 中使用的 CSS 颜色命名方案相同的 CSS 颜色命名方案：颜色名称（即&lt;code&gt;red&lt;/code&gt;）、RGB 值（即&lt;code&gt;rgb(255,0,0)&lt;/code&gt;）、十六进制值、RGBA 值等等。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;rect
  x=&quot;10&quot;
  y=&quot;10&quot;
  width=&quot;100&quot;
  height=&quot;100&quot;
  stroke=&quot;blue&quot;
  fill=&quot;purple&quot;
  fill-opacity=&quot;0.5&quot;
  stroke-opacity=&quot;0.8&quot;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;编写高效的 CSS 应该注意什么？&lt;/h2&gt;
&lt;p&gt;首先，浏览器从最右边的选择器，即关键选择器（key selector），向左依次匹配。根据关键选择器，浏览器从 &lt;code&gt;DOM&lt;/code&gt; 中筛选出元素，然后向上遍历被选元素的父元素，判断是否匹配。选择器匹配语句链越短，浏览器的匹配速度越快。避免使用标签和通用选择器作为关键选择器，因为它们会匹配大量的元素，浏览器必须要进行大量的工作，去判断这些元素的父元素们是否匹配。&lt;/p&gt;
&lt;p&gt;BEM (Block Element Modifier)原则上建议为独立的 CSS 类命名，并且在需要层级关系时，将关系也体现在命名中，这自然会使选择器高效且易于覆盖。&lt;/p&gt;
&lt;p&gt;搞清楚哪些 CSS 属性会触发重新布局（reflow）、重绘（repaint）和合成（compositing）。在写样式时，避免触发重新布局的可能。&lt;/p&gt;
&lt;h2&gt;使用 CSS 预处理的优缺点分别是什么？&lt;/h2&gt;
&lt;h3&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;提高 CSS 可维护性。&lt;/li&gt;
&lt;li&gt;易于编写嵌套选择器。&lt;/li&gt;
&lt;li&gt;引入变量，增添主题功能。可以在不同的项目中共享主题文件。&lt;/li&gt;
&lt;li&gt;通过混合（Mixins）生成重复的 CSS。&lt;/li&gt;
&lt;li&gt;将代码分割成多个文件。不进行预处理的 CSS，虽然也可以分割成多个文件，但需要建立多个 HTTP 请求加载这些文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;需要预处理工具。&lt;/li&gt;
&lt;li&gt;重新编译的时间可能会很慢。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;描述伪元素及其用途。&lt;/h2&gt;
&lt;p&gt;CSS 伪元素是添加到选择器的关键字，去选择元素的特定部分。它们可以用于装饰（&lt;code&gt;:first-line&lt;/code&gt;，&lt;code&gt;:first-letter&lt;/code&gt;）或将元素添加到标记中（与 content:...组合），而不必修改标记（&lt;code&gt;:before&lt;/code&gt;，&lt;code&gt;:after&lt;/code&gt;）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;:first-line 和:first-letter 可以用来修饰文字。&lt;/li&gt;
&lt;li&gt;上面提到的.clearfix 方法中，使用 clear: both 来添加不占空间的元素。&lt;/li&gt;
&lt;li&gt;使用:before 和 after 展示提示中的三角箭头。鼓励关注点分离，因为三角被视为样式的一部分，而不是真正的 DOM。如果不使用额外的 HTML 元素，- 只用 CSS 样式绘制三角形是不太可能的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;说说你对盒模型的理解，以及如何告知浏览器使用不同的盒模型渲染布局。&lt;/h2&gt;
&lt;p&gt;CSS 盒模型描述了以文档树中的元素而生成的矩形框，并根据排版模式进行布局。每个盒子都有一个内容区域（例如文本，图像等）以及周围可选的 &lt;code&gt;padding&lt;/code&gt;、&lt;code&gt;border&lt;/code&gt; 和 &lt;code&gt;margin&lt;/code&gt; 区域。&lt;/p&gt;
&lt;p&gt;CSS 盒模型负责计算：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;块级元素占用多少空间。&lt;/li&gt;
&lt;li&gt;边框是否重叠，边距是否合并。&lt;/li&gt;
&lt;li&gt;盒子的尺寸。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;盒模型有以下规则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;块级元素的大小由 &lt;code&gt;width&lt;/code&gt;、&lt;code&gt;height&lt;/code&gt;、&lt;code&gt;padding&lt;/code&gt;、&lt;code&gt;border&lt;/code&gt; 和 &lt;code&gt;margin&lt;/code&gt; 决定。&lt;/li&gt;
&lt;li&gt;如果没有指定 &lt;code&gt;height&lt;/code&gt;，则块级元素的高度等于其包含子元素的内容高度加上 &lt;code&gt;padding&lt;/code&gt;（除非有浮动元素，请参阅下文）。&lt;/li&gt;
&lt;li&gt;如果没有指定 &lt;code&gt;width&lt;/code&gt;，则非浮动块级元素的宽度等于其父元素的宽度减去父元素的 &lt;code&gt;padding&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;元素的 &lt;code&gt;height&lt;/code&gt; 是由内容的 &lt;code&gt;height&lt;/code&gt; 来计算的。&lt;/li&gt;
&lt;li&gt;元素的 &lt;code&gt;width&lt;/code&gt; 是由内容的 &lt;code&gt;width&lt;/code&gt; 来计算的。&lt;/li&gt;
&lt;li&gt;默认情况下，&lt;code&gt;padding&lt;/code&gt; 和 &lt;code&gt;border&lt;/code&gt; 不是元素 &lt;code&gt;width&lt;/code&gt; 和 &lt;code&gt;height&lt;/code&gt; 的组成部分。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;* { box-sizing: border-box; }会产生怎样的效果？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;元素默认应用了 &lt;code&gt;box-sizing: content-box&lt;/code&gt;，元素的宽高只会决定内容（content）的大小。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;box-sizing: border-box&lt;/code&gt; 改变计算元素 &lt;code&gt;width&lt;/code&gt; 和 &lt;code&gt;height&lt;/code&gt; 的方式，&lt;code&gt;border&lt;/code&gt; 和 &lt;code&gt;padding&lt;/code&gt; 的大小也将计算在内。&lt;/li&gt;
&lt;li&gt;元素的 height = 内容（content）的高度 + 垂直方向的 padding + 垂直方向 border 的宽度&lt;/li&gt;
&lt;li&gt;元素的 width = 内容（content）的宽度 + 水平方向的 padding + 水平方向 border 的宽度&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;display 的属性值都有哪些？&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;none&lt;/code&gt;, &lt;code&gt;block&lt;/code&gt;, &lt;code&gt;inline&lt;/code&gt;, &lt;code&gt;inline-block&lt;/code&gt;, &lt;code&gt;table&lt;/code&gt;, &lt;code&gt;table-row&lt;/code&gt;, &lt;code&gt;table-cell&lt;/code&gt;, &lt;code&gt;list-item&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;inline 和 inline-block 有什么区别？&lt;/h2&gt;
&lt;p&gt;我把 block 也加入其中，为了获得更好的比较。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;block&lt;/th&gt;
&lt;th&gt;inline-block&lt;/th&gt;
&lt;th&gt;inline&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;大小&lt;/td&gt;
&lt;td&gt;填充其父容器的宽度。&lt;/td&gt;
&lt;td&gt;取决于内容。&lt;/td&gt;
&lt;td&gt;取决于内容。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;定位&lt;/td&gt;
&lt;td&gt;从新的一行开始，并且不允许旁边有 HTML 元素（除非是&lt;code&gt;float&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;与其他内容一起流动，并允许旁边有其他元素。&lt;/td&gt;
&lt;td&gt;与其他内容一起流动，并允许旁边有其他元素。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;能否设置 &lt;code&gt;width&lt;/code&gt; 和 &lt;code&gt;height&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;能&lt;/td&gt;
&lt;td&gt;能&lt;/td&gt;
&lt;td&gt;不能。 设置会被忽略。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可以使用 &lt;code&gt;vertical-align&lt;/code&gt; 对齐&lt;/td&gt;
&lt;td&gt;不可以&lt;/td&gt;
&lt;td&gt;可以&lt;/td&gt;
&lt;td&gt;可以&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;边距（margin）和填充（padding）&lt;/td&gt;
&lt;td&gt;各个方向都存在&lt;/td&gt;
&lt;td&gt;各个方向都存在&lt;/td&gt;
&lt;td&gt;只有水平方向存在。垂直方向会被忽略。 尽管 &lt;code&gt;border&lt;/code&gt; 和 &lt;code&gt;padding&lt;/code&gt; 在 &lt;code&gt;content&lt;/code&gt; 周围，但垂直方向上的空间取决于 &lt;code&gt;&apos;line-height&apos;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;浮动（float）&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;就像一个 &lt;code&gt;block&lt;/code&gt; 元素，可以设置垂直边距和填充。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;relative、fixed、absolute 和 static 四种定位有什么区别？&lt;/h2&gt;
&lt;p&gt;经过定位的元素，其 &lt;code&gt;position&lt;/code&gt; 属性值必然是 &lt;code&gt;relative&lt;/code&gt;、&lt;code&gt;absolute&lt;/code&gt;、&lt;code&gt;fixed&lt;/code&gt; 或 &lt;code&gt;sticky&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;static&lt;/code&gt;：默认定位属性值。该关键字指定元素使用正常的布局行为，即元素在文档常规流中当前的布局位置。此时 top, right, bottom, left 和 z-index 属性无效。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;relative&lt;/code&gt;：该关键字下，元素先放置在未添加定位时的位置，再在不改变页面布局的前提下调整元素位置（因此会在此元素未添加定位时所在位置留下空白）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;absolute&lt;/code&gt;：不为元素预留空间，通过指定元素相对于最近的非 static 定位祖先元素的偏移，来确定元素位置。绝对定位的元素可以设置外边距（margins），且不会与其他边距合并。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;fixed&lt;/code&gt;：不为元素预留空间，而是通过指定元素相对于屏幕视口（viewport）的位置来指定元素位置。元素的位置在屏幕滚动时不会改变。打印时，元素会出现在的每页的固定位置。fixed 属性会创建新的层叠上下文。当元素祖先的 transform 属性非 none 时，容器由视口改为该祖先。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;sticky&lt;/code&gt;：盒位置根据正常流计算(这称为正常流动中的位置)，然后相对于该元素在流中的 flow root（BFC）和 containing block（最近的块级祖先元素）定位。在所有情况下（即便被定位元素为 &lt;code&gt;table&lt;/code&gt; 时），该元素定位均不对后续元素造成影响。当元素 B 被粘性定位时，后续元素的位置仍按照 B 未定位时的位置来确定。&lt;code&gt;position: sticky&lt;/code&gt; 对 table 元素的效果与 &lt;code&gt;position: relative&lt;/code&gt; 相同。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;你了解 CSS Flexbox 和 Grid 吗？&lt;/h2&gt;
&lt;p&gt;了解。Flexbox 主要用于一维布局，而 Grid 则用于二维布局。&lt;/p&gt;
&lt;p&gt;Flexbox 解决了 CSS 中的许多常见问题，例如容器中元素的垂直居中，粘性定位（sticky）的页脚等。Bootstrap 和 Bulma 基于 Flexbox，这是创建布局的推荐方式。我之前曾使用过 Flexbox，但在使用 &lt;code&gt;flex-grow&lt;/code&gt; 时遇到了一些浏览器不兼容问题（Safari），我必须使用 &lt;code&gt;inline-blocks&lt;/code&gt; 和手动计算百分比宽度，来重写我的代码，这种体验不是很好。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Grid&lt;/code&gt; 创建基于栅格的布局，是迄今为止最直观的方法（最好是！），但目前浏览器支持并不广泛。&lt;/p&gt;
&lt;h2&gt;请解释在编写网站时，响应式与移动优先的区别。&lt;/h2&gt;
&lt;p&gt;请注意，这两种方法不是互斥的。&lt;/p&gt;
&lt;p&gt;使一个网站响应意味着网站会根据设备屏幕的尺寸会自行调整一些元素的尺寸和功能，通常是通过 CSS 媒体查询的视口宽度，例如，使字体在小屏幕上变小。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@media (min-width: 601px) {
  .my-class {
    font-size: 24px;
  }
}

@media (max-width: 600px) {
  .my-class {
    font-size: 12px;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;移动优先策略同样也指的是响应式，但是它建议我们应该默认定义移动设备的所有样式，仅仅添加特定的规则用来适配其他设备，下面是前一个例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.my-class {
  font-size: 12px;
}

@media (min-width: 600px) {
  .my-class {
    font-size: 24px;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;移动优先策略有 2 大优势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在移动设备上有更好的性能，因为应用于它们的规则无需针对任何媒体查询的验证。&lt;/li&gt;
&lt;li&gt;它让你强制编写与响应 CSS 规则相关的更干净的代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;响应式设计与自适应设计有何不同？&lt;/h2&gt;
&lt;p&gt;响应式设计和自适应设计都以提高不同设备间的用户体验为目标，根据视窗大小、分辨率、使用环境和控制方式等参数进行优化调整。&lt;/p&gt;
&lt;p&gt;响应式设计的适应性原则：网站应该凭借一份代码，在各种设备上都有良好的显示和使用效果。响应式网站通过使用媒体查询，自适应栅格和响应式图片，基于多种因素进行变化，创造出优良的用户体验。就像一个球通过膨胀和收缩，来适应不同大小的篮圈。&lt;/p&gt;
&lt;p&gt;自适应设计更像是渐进式增强的现代解释。与响应式设计单一地去适配不同，自适应设计通过检测设备和其他特征，从早已定义好的一系列视窗大小和其他特性中，选出最恰当的功能和布局。与使用一个球去穿过各种的篮筐不同，自适应设计允许使用多个球，然后根据不同的篮筐大小，去选择最合适的一个。&lt;/p&gt;
&lt;h2&gt;什么情况下，用 translate()而不用绝对定位？什么时候，情况相反。&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;translate()&lt;/code&gt; 是 &lt;code&gt;transform&lt;/code&gt; 的一个值。改变 &lt;code&gt;transform&lt;/code&gt; 或 &lt;code&gt;opacity&lt;/code&gt; 不会触发浏览器重新布局（reflow）或重绘（repaint），只会触发复合（compositions）。而改变绝对定位会触发重新布局，进而触发重绘和复合。&lt;code&gt;transform&lt;/code&gt; 使浏览器为元素创建一个 GPU 图层，但改变绝对定位会使用到 CPU。 因此 &lt;code&gt;translate()&lt;/code&gt; 更高效，可以缩短平滑动画的绘制时间。&lt;/p&gt;
&lt;p&gt;当使用 &lt;code&gt;translate()&lt;/code&gt; 时，元素仍然占据其原始空间（有点像 &lt;code&gt;position：relative&lt;/code&gt;），这与改变绝对定位不同。&lt;/p&gt;
</content:encoded><category>前端面试</category><author>江辰</author></item><item><title>2023年中前端面试真题之JS篇</title><link>https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8Bjs%E7%AF%87/</link><guid isPermaLink="true">https://github.com/posts/2023%E5%B9%B4%E4%B8%AD%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9C%9F%E9%A2%98%E4%B9%8Bjs%E7%AF%87/</guid><pubDate>Fri, 01 Sep 2023 16:06:47 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;人的一生，总是难免有浮沉。不会永远如旭日东升，也不会永远痛苦潦倒。反复地一浮一沉，对于一个人来说，正是磨练。因此，浮在上面的，不必骄傲；沉在底下的，更用不着悲观。必须以率直、谦虚的态度，乐观进取、向前迈进。——松下幸之助&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;大家好，我是江辰，在如今的互联网大环境下，想必大家都或多或少且有感受，浮躁的社会之下，只有不断的保持心性，才能感知不同的收获，共勉。&lt;/p&gt;
&lt;p&gt;2023 年中最新的面试题集锦，时刻做好准备。&lt;/p&gt;
&lt;p&gt;本文首发于微信公众号：野生程序猿江辰&lt;/p&gt;
&lt;p&gt;欢迎大家点赞，收藏，关注&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/aaebcde3-9c4f-4146-b1bb-91bd39a63e9f.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;文章列表&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/162&quot;&gt;2023 年中前端面试真题之 CSS 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/163&quot;&gt;2023 年中前端面试真题之 HTML 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/164&quot;&gt;2023 年中前端面试真题之 React 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/165&quot;&gt;2023 年中前端面试真题之 Vue 篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xuya227939/blog/issues/166&quot;&gt;2023 年中前端面试真题之编码篇&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;请简述 JavaScript 中的 this&lt;/h2&gt;
&lt;p&gt;JS 中的 &lt;code&gt;this&lt;/code&gt; 是一个相对复杂的概念，不是简单几句能解释清楚的。粗略地讲，函数的调用方式决定了 &lt;code&gt;this&lt;/code&gt; 的值。我阅读了网上很多关于 &lt;code&gt;this&lt;/code&gt; 的文章，&lt;strong&gt;Arnav Aggrawal&lt;/strong&gt; 写的比较清楚。&lt;code&gt;this&lt;/code&gt; 取值符合以下规则：&lt;/p&gt;
&lt;p&gt;在调用函数时使用 &lt;code&gt;new&lt;/code&gt; 关键字，函数内的 &lt;code&gt;this&lt;/code&gt; 是一个全新的对象。
如果 &lt;code&gt;apply、call&lt;/code&gt; 或 &lt;code&gt;bind&lt;/code&gt; 方法用于调用、创建一个函数，函数内的 &lt;code&gt;this&lt;/code&gt; 就是作为参数传入这些方法的对象。
当函数作为对象里的方法被调用时，函数内的 &lt;code&gt;this&lt;/code&gt; 是调用该函数的对象。比如当 &lt;code&gt;obj.method()&lt;/code&gt; 被调用时，函数内的 &lt;code&gt;this&lt;/code&gt; 将绑定到 &lt;code&gt;obj&lt;/code&gt; 对象。
如果调用函数不符合上述规则，那么 &lt;code&gt;this&lt;/code&gt; 的值指向全局对象（&lt;code&gt;global object&lt;/code&gt;）。浏览器环境下 &lt;code&gt;this&lt;/code&gt; 的值指向 &lt;code&gt;window&lt;/code&gt; 对象，但是在严格模式下(&lt;code&gt;&apos;use strict&apos;&lt;/code&gt;)，&lt;code&gt;this&lt;/code&gt; 的值为 &lt;code&gt;undefined&lt;/code&gt;。
如果符合上述多个规则，则较高的规则（1 号最高，4 号最低）将决定 &lt;code&gt;this&lt;/code&gt; 的值。
如果该函数是 &lt;code&gt;ES2015&lt;/code&gt; 中的箭头函数，将忽略上面的所有规则，&lt;code&gt;this&lt;/code&gt; 被设置为它被创建时的上下文。&lt;/p&gt;
&lt;h2&gt;说说你对 AMD 和 CommonJS 的了解。&lt;/h2&gt;
&lt;p&gt;它们都是实现模块体系的方式，直到 &lt;code&gt;ES2015&lt;/code&gt; 出现之前，&lt;code&gt;JavaScript&lt;/code&gt; 一直没有模块体系。&lt;code&gt;CommonJS&lt;/code&gt; 是同步的，而 &lt;code&gt;AMD（Asynchronous Module Definition）&lt;/code&gt; 从全称中可以明显看出是异步的。&lt;code&gt;CommonJS&lt;/code&gt; 的设计是为服务器端开发考虑的，而 &lt;code&gt;AMD&lt;/code&gt; 支持异步加载模块，更适合浏览器。&lt;/p&gt;
&lt;p&gt;我发现 &lt;code&gt;AMD&lt;/code&gt; 的语法非常冗长，&lt;code&gt;CommonJS&lt;/code&gt; 更接近其他语言 &lt;code&gt;import&lt;/code&gt; 声明语句的用法习惯。大多数情况下，我认为 &lt;code&gt;AMD&lt;/code&gt; 没有使用的必要，因为如果把所有 &lt;code&gt;JavaScript&lt;/code&gt; 都捆绑进一个文件中，将无法得到异步加载的好处。此外，&lt;code&gt;CommonJS&lt;/code&gt; 语法上更接近 &lt;code&gt;Node&lt;/code&gt; 编写模块的风格，在前后端都使用 &lt;code&gt;JavaScript&lt;/code&gt; 开发之间进行切换时，语境的切换开销较小。&lt;/p&gt;
&lt;p&gt;我很高兴看到 &lt;code&gt;ES2015&lt;/code&gt; 的模块加载方案同时支持同步和异步，我们终于可以只使用一种方案了。虽然它尚未在浏览器和 &lt;code&gt;Node&lt;/code&gt; 中完全推出，但是我们可以使用代码转换工具进行转换。&lt;/p&gt;
&lt;h2&gt;请解释下面代码为什么不能用作 IIFE：function foo(){ }();，需要作出哪些修改才能使其成为 IIFE？&lt;/h2&gt;
&lt;p&gt;IIFE（Immediately Invoked Function Expressions）代表立即执行函数。 &lt;code&gt;JavaScript&lt;/code&gt; 解析器将 &lt;code&gt;function foo(){ }();&lt;/code&gt; 解析成 &lt;code&gt;function foo(){ }和();&lt;/code&gt; 。其中，前者是函数声明；后者（一对括号）是试图调用一个函数，却没有指定名称，因此它会抛出 &lt;code&gt;Uncaught SyntaxError: Unexpected token )&lt;/code&gt; 的错误。&lt;/p&gt;
&lt;p&gt;修改方法是：再添加一对括号，形式上有两种：&lt;code&gt;(function foo(){ })()&lt;/code&gt; 和 &lt;code&gt;(function foo(){ }())&lt;/code&gt;。以上函数不会暴露到全局作用域，如果不需要在函数内部引用自身，可以省略函数的名称。&lt;/p&gt;
&lt;p&gt;你可能会用到 &lt;code&gt;void&lt;/code&gt; 操作符：&lt;code&gt;void function foo(){ }();&lt;/code&gt;。但是，这种做法是有问题的。表达式的值是 &lt;code&gt;undefined&lt;/code&gt;，所以如果你的 &lt;code&gt;IIFE&lt;/code&gt; 有返回值，不要用这种做法。例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const foo = void (function bar() {
  return &apos;foo&apos;;
})();

console.log(foo); // undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;null、undefined 和未声明变量之间有什么区别？如何检查判断这些状态值？&lt;/h2&gt;
&lt;p&gt;当你没有提前使用 &lt;code&gt;var、let&lt;/code&gt; 或 &lt;code&gt;const&lt;/code&gt; 声明变量，就为一个变量赋值时，该变量是未声明变量（&lt;code&gt;undeclared variables&lt;/code&gt;）。未声明变量会脱离当前作用域，成为全局作用域下定义的变量。在严格模式下，给未声明的变量赋值，会抛出 &lt;code&gt;ReferenceError&lt;/code&gt; 错误。和使用全局变量一样，使用未声明变量也是非常不好的做法，应当尽可能避免。要检查判断它们，需要将用到它们的代码放在 &lt;code&gt;try/catch&lt;/code&gt; 语句中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function foo() {
  x = 1; // 在严格模式下，抛出 ReferenceError 错误
}

foo();
console.log(x); // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当一个变量已经声明，但没有赋值时，该变量的值是 &lt;code&gt;undefined&lt;/code&gt;。如果一个函数的执行结果被赋值给一个变量，但是这个函数却没有返回任何值，那么该变量的值是 &lt;code&gt;undefined&lt;/code&gt;。要检查它，需要使用严格相等（&lt;code&gt;===&lt;/code&gt;）；或者使用 &lt;code&gt;typeof&lt;/code&gt;，它会返回 &lt;code&gt;&apos;undefined&apos;&lt;/code&gt; 字符串。请注意，不能使用非严格相等（&lt;code&gt;==&lt;/code&gt;）来检查，因为如果变量值为 &lt;code&gt;null&lt;/code&gt;，使用非严格相等也会返回 &lt;code&gt;true&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === &apos;undefined&apos;); // true

console.log(foo == null); // true. 错误，不要使用非严格相等！

function bar() {}
var baz = bar();
console.log(baz); // undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;null&lt;/code&gt; 只能被显式赋值给变量。它表示空值，与被显式赋值 &lt;code&gt;undefined&lt;/code&gt; 的意义不同。要检查判断 &lt;code&gt;null&lt;/code&gt; 值，需要使用严格相等运算符。请注意，和前面一样，不能使用非严格相等（&lt;code&gt;==&lt;/code&gt;）来检查，因为如果变量值为 &lt;code&gt;undefined&lt;/code&gt;，使用非严格相等也会返回 &lt;code&gt;true&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var foo = null;
console.log(foo === null); // true

console.log(foo == undefined); // true. 错误，不要使用非严格相等！
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;作为一种个人习惯，我从不使用未声明变量。如果定义了暂时没有用到的变量，我会在声明后明确地给它们赋值为 &lt;code&gt;null&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;什么是闭包（closure），为什么使用闭包？&lt;/h2&gt;
&lt;p&gt;闭包是函数和声明该函数的词法环境的组合。词法作用域中使用的域，是变量在代码中声明的位置所决定的。闭包是即使被外部函数返回，依然可以访问到外部（封闭）函数作用域的函数。&lt;/p&gt;
&lt;p&gt;为什么使用闭包？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用闭包实现数据私有化或模拟私有方法。这个方式也称为模块模式（&lt;code&gt;module pattern&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;部分参数函数（&lt;code&gt;partial applications&lt;/code&gt;）柯里化（&lt;code&gt;currying&lt;/code&gt;）.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;请说明.forEach 循环和.map()循环的主要区别，它们分别在什么情况下使用？&lt;/h2&gt;
&lt;p&gt;为了理解两者的区别，我们看看它们分别是做什么的。&lt;/p&gt;
&lt;h3&gt;forEach&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;遍历数组中的元素。&lt;/li&gt;
&lt;li&gt;为每个元素执行回调。&lt;/li&gt;
&lt;li&gt;无返回值。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const a = [1, 2, 3];
const doubled = a.forEach((num, index) =&amp;gt; {
  // 执行与 num、index 相关的代码
});

// doubled = undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;map&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;遍历数组中的元素&lt;/li&gt;
&lt;li&gt;通过对每个元素调用函数，将每个元素“映射（map）”到一个新元素，从而创建一个新数组。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const a = [1, 2, 3];
const doubled = a.map((num) =&amp;gt; {
  return num * 2;
});

// doubled = [2, 4, 6]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.forEach&lt;/code&gt; 和 &lt;code&gt;.map()&lt;/code&gt; 的主要区别在于 &lt;code&gt;.map()&lt;/code&gt; 返回一个新的数组。如果你想得到一个结果，但不想改变原始数组，用 &lt;code&gt;.map()&lt;/code&gt;。如果你只需要在数组上做迭代修改，用 &lt;code&gt;forEach&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;匿名函数的典型应用场景是什么？&lt;/h2&gt;
&lt;p&gt;匿名函数可以在 IIFE 中使用，来封装局部作用域内的代码，以便其声明的变量不会暴露到全局作用域。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(function () {
  // 一些代码。
})();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匿名函数可以作为只用一次，不需要在其他地方使用的回调函数。当处理函数在调用它们的程序内部被定义时，代码具有更好地自闭性和可读性，可以省去寻找该处理函数的函数体位置的麻烦。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setTimeout(function () {
  console.log(&apos;Hello world!&apos;);
}, 1000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匿名函数可以用于函数式编程或 Lodash（类似于回调函数）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const arr = [1, 2, 3];
const double = arr.map(function (el) {
  return el * 2;
});
console.log(double); // [2, 4, 6]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;.call 和.apply 有什么区别？&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;.call&lt;/code&gt; 和 &lt;code&gt;.apply&lt;/code&gt; 都用于调用函数，第一个参数将用作函数内 &lt;code&gt;this&lt;/code&gt; 的值。然而，&lt;code&gt;.call&lt;/code&gt; 接受逗号分隔的参数作为后面的参数，而 &lt;code&gt;.apply&lt;/code&gt; 接受一个参数数组作为后面的参数。一个简单的记忆方法是，从 &lt;code&gt;call&lt;/code&gt; 中的 C 联想到逗号分隔（&lt;code&gt;comma-separated&lt;/code&gt;），从 &lt;code&gt;apply&lt;/code&gt; 中的 A 联想到数组（&lt;code&gt;array&lt;/code&gt;）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function add(a, b) {
  return a + b;
}

console.log(add.call(null, 1, 2)); // 3
console.log(add.apply(null, [1, 2])); // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;请说明 Function.prototype.bind 的用法。&lt;/h2&gt;
&lt;p&gt;摘自 MDN：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;bind()方法创建一个新的函数, 当被调用时，将其 this 关键字设置为提供的值，在调用新函数时，在任何提供之前提供一个给定的参数序列。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;根据我的经验，将 &lt;code&gt;this&lt;/code&gt; 的值绑定到想要传递给其他函数的类的方法中是非常有用的。在 &lt;code&gt;React&lt;/code&gt; 组件中经常这样做。&lt;/p&gt;
&lt;h2&gt;请尽可能详细地解释 Ajax。&lt;/h2&gt;
&lt;p&gt;Ajax（asynchronous JavaScript and XML）是使用客户端上的许多 Web 技术，创建异步 Web 应用的一种 Web 开发技术。借助 Ajax，Web 应用可以异步（在后台）向服务器发送数据和从服务器检索数据，而不会干扰现有页面的显示和行为。通过将数据交换层与表示层分离，Ajax 允许网页和扩展 Web 应用程序动态更改内容，而无需重新加载整个页面。实际上，现在通常将 XML 替换为 JSON，因为 JavaScript 对 JSON 有原生支持优势。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;XMLHttpRequest API&lt;/code&gt; 经常用于异步通信。此外还有最近流行的 &lt;code&gt;fetch API&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;使用 Ajax 的优缺点分别是什么？&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;交互性更好。来自服务器的新内容可以动态更改，无需重新加载整个页面。&lt;/li&gt;
&lt;li&gt;减少与服务器的连接，因为脚本和样式只需要被请求一次。&lt;/li&gt;
&lt;li&gt;状态可以维护在一个页面上。JavaScript 变量和 DOM 状态将得到保持，因为主容器页面未被重新加载。&lt;/li&gt;
&lt;li&gt;基本上包括大部分 SPA 的优点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;动态网页很难收藏。&lt;/li&gt;
&lt;li&gt;如果 JavaScript 已在浏览器中被禁用，则不起作用。&lt;/li&gt;
&lt;li&gt;有些网络爬虫不执行 JavaScript，也不会看到 JavaScript 加载的内容。&lt;/li&gt;
&lt;li&gt;基本上包括大部分 SPA 的缺点&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;请说明 JSONP 的工作原理，它为什么不是真正的 Ajax？&lt;/h2&gt;
&lt;p&gt;JSONP（带填充的 JSON）是一种通常用于绕过 Web 浏览器中的跨域限制的方法，因为 Ajax 不允许跨域请求。&lt;/p&gt;
&lt;p&gt;JSONP 通过 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 标签发送跨域请求，通常使用 &lt;code&gt;callback&lt;/code&gt; 查询参数，例如：&lt;code&gt;https://example.com?callback=printData&lt;/code&gt;。 然后服务器将数据包装在一个名为 &lt;code&gt;printData&lt;/code&gt; 的函数中并将其返回给客户端。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- https://mydomain.com --&amp;gt;
&amp;lt;script&amp;gt;
  function printData(data) {
    console.log(`My name is ${data.name}!`);
  }
&amp;lt;/script&amp;gt;

&amp;lt;script src=&quot;https://example.com?callback=printData&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 文件加载自 https://example.com?callback=printData
printData({name: &apos;Yang Shun&apos;});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;客户端必须在其全局范围内具有 &lt;code&gt;printData&lt;/code&gt; 函数，并且在收到来自跨域的响应时，该函数将由客户端执行。&lt;/p&gt;
&lt;p&gt;JSONP 可能具有一些安全隐患。由于 JSONP 是纯 JavaScript 实现，它可以完成 JavaScript 所能做的一切，因此需要信任 JSONP 数据的提供者。&lt;/p&gt;
&lt;p&gt;现如今，跨来源资源共享（CORS） 是推荐的主流方式，JSONP 已被视为一种比较 hack 的方式。&lt;/p&gt;
&lt;h2&gt;请解释变量提升（hoisting）。&lt;/h2&gt;
&lt;p&gt;变量提升（hoisting）是用于解释代码中变量声明行为的术语。使用 &lt;code&gt;var&lt;/code&gt; 关键字声明或初始化的变量，会将声明语句“提升”到当前作用域的顶部。 但是，只有声明才会触发提升，赋值语句（如果有的话）将保持原样。我们用几个例子来解释一下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 用 var 声明得到提升
console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1

// 用 let/const 声明不会提升
console.log(bar); // ReferenceError: bar is not defined
let bar = 2;
console.log(bar); // 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;函数声明会使函数体提升，但函数表达式（以声明变量的形式书写）只有变量声明会被提升。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 函数声明
console.log(foo); // [Function: foo]
foo(); // &apos;FOOOOO&apos;
function foo() {
  console.log(&apos;FOOOOO&apos;);
}
console.log(foo); // [Function: foo]

// 函数表达式
console.log(bar); // undefined
bar(); // Uncaught TypeError: bar is not a function
var bar = function () {
  console.log(&apos;BARRRR&apos;);
};
console.log(bar); // [Function: bar]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;请描述事件冒泡。&lt;/h2&gt;
&lt;p&gt;当一个事件在 DOM 元素上触发时，如果有事件监听器，它将尝试处理该事件，然后事件冒泡到其父级元素，并发生同样的事情。最后直到事件到达祖先元素。事件冒泡是实现事件委托的原理（event delegation）。&lt;/p&gt;
&lt;h2&gt;==和===的区别是什么&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;==&lt;/code&gt; 是抽象相等运算符，而 &lt;code&gt;===&lt;/code&gt; 是严格相等运算符。&lt;code&gt;==&lt;/code&gt; 运算符是在进行必要的类型转换后，再比较。&lt;code&gt;===&lt;/code&gt; 运算符不会进行类型转换，所以如果两个值不是相同的类型，会直接返回 &lt;code&gt;false&lt;/code&gt; 。使用 &lt;code&gt;==&lt;/code&gt; 时，可能发生一些特别的事情，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 == &apos;1&apos;; // true
1 == [1]; // true
1 == true; // true
0 == &apos;&apos;; // true
0 == &apos;0&apos;; // true
0 == false; // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我的建议是从不使用 &lt;code&gt;==&lt;/code&gt; 运算符，除了方便与 &lt;code&gt;null&lt;/code&gt; 或 &lt;code&gt;undefined&lt;/code&gt; 比较时，&lt;code&gt;a == null&lt;/code&gt; 如果 &lt;code&gt;a&lt;/code&gt; 为 &lt;code&gt;null&lt;/code&gt; 或 &lt;code&gt;undefined&lt;/code&gt; 将返回 &lt;code&gt;true&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var a = null;
console.log(a == null); // true
console.log(a == undefined); // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;请解释关于 JavaScript 的同源策略。&lt;/h2&gt;
&lt;p&gt;同源策略可防止 JavaScript 发起跨域请求。源被定义为 URI、主机名和端口号的组合。此策略可防止页面上的恶意脚本通过该页面的文档对象模型，访问另一个网页上的敏感数据。&lt;/p&gt;
&lt;h2&gt;你对 Promises 及其 polyfill 的掌握程度如何？&lt;/h2&gt;
&lt;p&gt;掌握它的工作原理。&lt;code&gt;Promise&lt;/code&gt; 是一个可能在未来某个时间产生结果的对象：操作成功的结果或失败的原因（例如发生网络错误）。 &lt;code&gt;Promise&lt;/code&gt; 可能处于以下三种状态之一：&lt;code&gt;fulfilled&lt;/code&gt;、&lt;code&gt;rejected&lt;/code&gt; 或 &lt;code&gt;pending&lt;/code&gt;。 用户可以对 &lt;code&gt;Promise&lt;/code&gt; 添加回调函数来处理操作成功的结果或失败的原因。&lt;/p&gt;
&lt;p&gt;一些常见的 &lt;code&gt;polyfill&lt;/code&gt; 是 &lt;code&gt;$.deferred&lt;/code&gt;、Q 和 Bluebird，但不是所有的 polyfill 都符合规范。ES2015 支持 Promises，现在通常不需要使用 polyfills。&lt;/p&gt;
&lt;h2&gt;Promise 代替回调函数有什么优缺点？&lt;/h2&gt;
&lt;h3&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;避免可读性极差的回调地狱。&lt;/li&gt;
&lt;li&gt;使用.then()编写的顺序异步代码，既简单又易读。&lt;/li&gt;
&lt;li&gt;使用 Promise.all()编写并行异步代码变得很容易。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;轻微地增加了代码的复杂度（这点存在争议）。&lt;/li&gt;
&lt;li&gt;在不支持 ES2015 的旧版浏览器中，需要引入 polyfill 才能使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;请解释同步和异步函数之间的区别。&lt;/h2&gt;
&lt;p&gt;同步函数阻塞，而异步函数不阻塞。在同步函数中，语句完成后，下一句才执行。在这种情况下，程序可以按照语句的顺序进行精确评估，如果其中一个语句需要很长时间，程序的执行会停滞很长时间。&lt;/p&gt;
&lt;p&gt;异步函数通常接受回调作为参数，在调用异步函数后立即继续执行下一行。回调函数仅在异步操作完成且调用堆栈为空时调用。诸如从 Web 服务器加载数据或查询数据库等重负载操作应该异步完成，以便主线程可以继续执行其他操作，而不会出现一直阻塞，直到费时操作完成的情况（在浏览器中，界面会卡住）。&lt;/p&gt;
&lt;h2&gt;什么是事件循环？调用堆栈和任务队列之间有什么区别？&lt;/h2&gt;
&lt;p&gt;事件循环是一个单线程循环，用于监视调用堆栈并检查是否有工作即将在任务队列中完成。如果调用堆栈为空并且任务队列中有回调函数，则将回调函数出队并推送到调用堆栈中执行。&lt;/p&gt;
&lt;h2&gt;使用 let、var 和 const 创建变量有什么区别？&lt;/h2&gt;
&lt;p&gt;用 &lt;code&gt;var&lt;/code&gt; 声明的变量的作用域是它当前的执行上下文，它可以是嵌套的函数，也可以是声明在任何函数外的变量。&lt;code&gt;let&lt;/code&gt; 和 &lt;code&gt;const&lt;/code&gt; 是块级作用域，意味着它们只能在最近的一组花括号（function、if-else 代码块或 for 循环中）中访问。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function foo() {
  // 所有变量在函数中都可访问
  var bar = &apos;bar&apos;;
  let baz = &apos;baz&apos;;
  const qux = &apos;qux&apos;;

  console.log(bar); // bar
  console.log(baz); // baz
  console.log(qux); // qux
}

console.log(bar); // ReferenceError: bar is not defined
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;if (true) {
  var bar = &apos;bar&apos;;
  let baz = &apos;baz&apos;;
  const qux = &apos;qux&apos;;
}

// 用 var 声明的变量在函数作用域上都可访问
console.log(bar); // bar
// let 和 const 定义的变量在它们被定义的语句块之外不可访问
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;var&lt;/code&gt; 会使变量提升，这意味着变量可以在声明之前使用。&lt;code&gt;let&lt;/code&gt;和 &lt;code&gt;const&lt;/code&gt; 不会使变量提升，提前使用会报错。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(foo); // undefined

var foo = &apos;foo&apos;;

console.log(baz); // ReferenceError: can&apos;t access lexical declaration &apos;baz&apos; before initialization

let baz = &apos;baz&apos;;

console.log(bar); // ReferenceError: can&apos;t access lexical declaration &apos;bar&apos; before initialization

const bar = &apos;bar&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用 &lt;code&gt;var&lt;/code&gt; 重复声明不会报错，但 &lt;code&gt;let&lt;/code&gt; 和 &lt;code&gt;const&lt;/code&gt; 会。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var foo = &apos;foo&apos;;
var foo = &apos;bar&apos;;
console.log(foo); // &quot;bar&quot;

let baz = &apos;baz&apos;;
let baz = &apos;qux&apos;; // Uncaught SyntaxError: Identifier &apos;baz&apos; has already been declared
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;let&lt;/code&gt; 和 &lt;code&gt;const&lt;/code&gt; 的区别在于：&lt;code&gt;let&lt;/code&gt; 允许多次赋值，而 &lt;code&gt;const&lt;/code&gt; 只允许一次。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 这样不会报错。
let foo = &apos;foo&apos;;
foo = &apos;bar&apos;;

// 这样会报错。
const baz = &apos;baz&apos;;
baz = &apos;qux&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;你能给出一个使用箭头函数的例子吗，箭头函数与其他函数有什么不同&lt;/h2&gt;
&lt;p&gt;一个很明显的优点就是箭头函数可以简化创建函数的语法，我们不需要在箭头函数前面加上 &lt;code&gt;function&lt;/code&gt; 关键词。并且箭头函数的 &lt;code&gt;this&lt;/code&gt; 会自动绑定到当前作用域的上下文中，这和普通的函数不一样。普通函数的 &lt;code&gt;this&lt;/code&gt; 是在执行的时候才能确定的。箭头函数的这个特点对于回调函数来说特别有用，特别对于 &lt;code&gt;React&lt;/code&gt; 组件而言。&lt;/p&gt;
&lt;h2&gt;高阶函数（higher-order）的定义是什么？&lt;/h2&gt;
&lt;p&gt;高阶函数是将一个或多个函数作为参数的函数，它用于数据处理，也可能将函数作为返回结果。高阶函数是为了抽象一些重复执行的操作。一个典型的例子是 &lt;code&gt;map&lt;/code&gt;，它将一个数组和一个函数作为参数。&lt;code&gt;map&lt;/code&gt; 使用这个函数来转换数组中的每个元素，并返回一个包含转换后元素的新数组。JavaScript 中的其他常见示例是 &lt;code&gt;forEach&lt;/code&gt;、&lt;code&gt;filter&lt;/code&gt; 和 &lt;code&gt;reduce&lt;/code&gt;。高阶函数不仅需要操作数组的时候会用到，还有许多函数返回新函数的用例。&lt;code&gt;Function.prototype.bind&lt;/code&gt; 就是一个例子。&lt;/p&gt;
&lt;h3&gt;Map 示例&lt;/h3&gt;
&lt;p&gt;假设我们有一个由名字组成的数组，我们需要将每个字符转换为大写字母。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const names = [&apos;irish&apos;, &apos;daisy&apos;, &apos;anna&apos;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不使用高阶函数的方法是这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const transformNamesToUppercase = function (names) {
  const results = [];
  for (let i = 0; i &amp;lt; names.length; i++) {
    results.push(names[i].toUpperCase());
  }
  return results;
};
transformNamesToUppercase(names); // [&apos;IRISH&apos;, &apos;DAISY&apos;, &apos;ANNA&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 &lt;code&gt;.map(transformerFn)&lt;/code&gt; 使代码更简明&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const transformNamesToUppercase = function (names) {
  return names.map((name) =&amp;gt; name.toUpperCase());
};
transformNamesToUppercase(names); // [&apos;IRISH&apos;, &apos;DAISY&apos;, &apos;ANNA&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;请给出一个解构（destructuring）对象或数组的例子。&lt;/h2&gt;
&lt;p&gt;解构是 ES6 中新功能，它提供了一种简洁方便的方法来提取对象或数组的值，并将它们放入不同的变量中。&lt;/p&gt;
&lt;h3&gt;数组解构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 变量赋值
const foo = [&apos;one&apos;, &apos;two&apos;, &apos;three&apos;];

const [one, two, three] = foo;
console.log(one); // &quot;one&quot;
console.log(two); // &quot;two&quot;
console.log(three); // &quot;three&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 变量交换
let a = 1;
let b = 3;

[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;对象解构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 变量赋值
const o = {p: 42, q: true};
const {p, q} = o;

console.log(p); // 42
console.log(q); // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;你能举出一个柯里化函数（curry function）的例子吗？它有哪些好处？&lt;/h2&gt;
&lt;p&gt;柯里化（currying）是一种模式，其中具有多个参数的函数被分解为多个函数，当被串联调用时，将一次一个地累积所有需要的参数。这种技术帮助编写函数式风格的代码，使代码更易读、紧凑。值得注意的是，对于需要被 curry 的函数，它需要从一个函数开始，然后分解成一系列函数，每个函数都需要一个参数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function curry(fn) {
  if (fn.length === 0) {
    return fn;
  }

  function _curried(depth, args) {
    return function (newArgument) {
      if (depth - 1 === 0) {
        return fn(...args, newArgument);
      }
      return _curried(depth - 1, [...args, newArgument]);
    };
  }

  return _curried(fn.length, []);
}

function add(a, b) {
  return a + b;
}

var curriedAdd = curry(add);
var addFive = curriedAdd(5);

var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>前端面试</category><author>江辰</author></item><item><title>【从前端入门到全栈】前端框架之核心概念</title><link>https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E4%B9%8B%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5/</link><guid isPermaLink="true">https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E4%B9%8B%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5/</guid><pubDate>Sun, 18 Jun 2023 21:47:34 GMT</pubDate><content:encoded>&lt;h2&gt;从前端入门到全栈-系列介绍&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;你会学到什么？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可能学不到什么东西，该系列是作者本人工作和学习积累，用于复习&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;作者介绍&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;江辰，前网易高级前端工程师&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;系列介绍&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在的 Web 前端已经离不开 Node.js，我们广泛使用的 Babel、Webpack、工程化都是基于 Node 的，各个互联网大厂也早已大规模落地 Node 项目。因此，想要成为一名优秀的前端工程师，提升个人能力、进入大厂，掌握 Node.js 技术非常有必要。&lt;/p&gt;
&lt;p&gt;Node.js 不仅可以用来完善手头的开发环境，实现减少代码和 HTTP 请求，降低网页请求消耗的时间，提升服务质量。还可以扩展前端工程师的工作领域，用作 HTTP 服务，让前端也能完成一部分后端的工作，减少对后端的依赖，降低沟通成本，提升开发效率。&lt;/p&gt;
&lt;p&gt;而且，Node.js 和浏览器的 JavaScript 只是运行时环境不同，编程语言都是 JavaScript ，所以掌握 Node.js 基础对前端工程师来说并不难，难点在于应用。由于浏览器的 JavaScript 主要是负责内容呈现与交互，而 Node.js 应用领域包括工具开发、Web 服务开发和客户端开发，这些都与传统的 Web 前端领域不一样，用来应对不同的问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;适宜人群&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;对 Node.js 感兴趣的 JavaScript 程序员&lt;/li&gt;
&lt;li&gt;希望拓展知识边界，往全栈方向发展的前端工程师&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前端框架的核心概念主要包括以下几点，不同的框架可能有所不同，但大多数前端框架都通常包含以下概念：&lt;/p&gt;
&lt;h2&gt;组件&lt;/h2&gt;
&lt;p&gt;组件化思想就是将界面划分为一些独立的、可复用的部分，每部分被称之为一个&quot;组件&quot;。每个组件都可以包含自己的布局（HTML）、样式（CSS）和逻辑（JavaScript）。组件可以独立存在，也可以与其他组件组合，形成完整的应用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/6ae8c199-d384-44a6-b8d3-ca2cd4c1c8ce.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这种方式的优点包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;复用性&lt;/strong&gt;：组件可以在多个地方多次使用，减少了代码量和维护成本。只要维护好一个组件，那么所有使用该组件的地方都会得到更新。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;可维护性&lt;/strong&gt;：因为每个组件都是独立的，所以修改单个组件不会影响到其他组件，这使得维护起来更加容易。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;清晰的结构&lt;/strong&gt;：应用的结构更加清晰，每个部分都对应一个组件，这样比单一的 HTML、CSS 和 JS 文件更加容易理解。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在前端开发中，React、Vue 和 Angular 等主流框架都广泛使用了组件化。&lt;/p&gt;
&lt;p&gt;例如，一个简单的 React 组件可能是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;

class Welcome extends React.Component {
  render() {
    return &amp;lt;h1&amp;gt;Hello, {this.props.name}&amp;lt;/h1&amp;gt;;
  }
}

// 使用Welcome组件
&amp;lt;Welcome name=&quot;Sara&quot; /&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个简单的 Vue 组件可能是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Welcome.vue
&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;hello, {{ name }}&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  props: [&apos;name&apos;]
}
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
p {
  color: blue;
}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样，我们就可以在多个地方使用&lt;code&gt;&amp;lt;Welcome /&amp;gt;&lt;/code&gt;这个组件，并且传入不同的&lt;code&gt;name&lt;/code&gt;属性，显示不同的欢迎信息。这就是组件化思想的简单应用。&lt;/p&gt;
&lt;h2&gt;数据绑定&lt;/h2&gt;
&lt;p&gt;数据绑定是前端框架中一个非常重要的概念，它可以让我们将数据与视图连接起来。在很多前端框架中，你不需要手动操作 DOM 来更新页面，只需要更新数据，视图就会自动变化。按照数据与视图的更新关系，数据绑定通常可以分为单向数据绑定和双向数据绑定。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;单向数据绑定&lt;/strong&gt;：也被称为单向数据流，就是数据只能从一端改变，然后流向另一端。在这种模式中，模型 (Model) 数据会自动更新到视图 (View)，但是视图不会影响模型。也就是说，当我们的数据模型变化时，将会引起视图的更新，但是视图的变化并不会影响数据模型。React 就是采用的单向数据流。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/5ab447b1-dafb-4968-8b44-9db8024dd8ed.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;双向数据绑定&lt;/strong&gt;：双向数据绑定不仅可以做到从 Model 到 View 的数据自动更新，同时也可以做到从 View 到 Model 的同步。也就是说，不仅视图会响应数据的变化，数据也会响应视图的变化。Angular 和 Vue 都有全方位的双向数据绑定体验。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/c64780e0-92f6-4598-9ea2-b953efe8b3f3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;数据绑定的主要优点是减轻了开发者手动操作 DOM 的负担，简化了代码，帮助开发者专注于业务逻辑的实现。同时，数据绑定带来了更好的用户体验，减少了页面闪烁和不必要的页面重载，提高了应用的响应速度。&lt;/p&gt;
&lt;h2&gt;路由&lt;/h2&gt;
&lt;p&gt;路由在 web 开发中是非常重要的一个概念，无论是前端还是后端。但这两者中的路由有一些不同的地方。在这里，我会专门讲解一下前端路由。&lt;/p&gt;
&lt;p&gt;在前端开发中，路由主要用于控制视图（页面或组件）之间的导航。当你点击一个链接，或者在地址栏输入一个 URL 时，路由系统会决定哪个视图将被渲染到浏览器。&lt;/p&gt;
&lt;p&gt;前端路由主要分为两种类型：&lt;strong&gt;Hash 路由&lt;/strong&gt; 和 &lt;strong&gt;History 路由&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hash 路由&lt;/strong&gt;：基于锚链接（URL 中的#标签）和 onhashChange 事件。当锚点值改变时，页面不会重新加载。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;History 路由&lt;/strong&gt;：基于 HTML5 中的 History API（pushState、replaceState、popState 事件）。这种模式相比于 Hash 模式，URL 更加美观。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;前端路由的出现，使得单页面应用（SPA）得以快速发展。因为在 SPA 中，页面不会因为链接的改变而重新加载，只是组件的切换，这就需要前端路由系统来控制这个组件的切换。&lt;/p&gt;
&lt;p&gt;诸如 React Router（React）、Vue Router（Vue.js）、@reach/router（React）等，这些路由库就提供了在前端环境中创建路由的能力。&lt;/p&gt;
&lt;p&gt;所以，理解和掌握前端路由对于前端开发者来说，是非常关键和重要的。&lt;/p&gt;
&lt;h2&gt;状态管理&lt;/h2&gt;
&lt;p&gt;在前端开发中，状态管理是一项非常重要的工作。首先，我们需要明确什么是状态（state）。在前端开发中，状态通常是指影响应用行为或者 UI 的数据 —— 比如用户是不是已经登录了，购物车里有哪些商品，用户搜索的关键词是什么等等，都可以被认为是状态，这些数据会影响应用的行为和界面表现。&lt;/p&gt;
&lt;p&gt;状态管理就是指管理这些状态，包括状态的读取、修改、删除等操作，以及这些操作引发的应用的变化。&lt;/p&gt;
&lt;p&gt;状态管理的需要主要来源于以下原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;复杂性&lt;/strong&gt; - 当应用的规模变大以后，状态的数量和复杂度会增加，如果没有一个好的状态管理方案，开发和维护都会变得非常困难。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;多组件共享状态&lt;/strong&gt; - 在实际开发中，很多状态需要在不同的组件之间共享。如果不使用状态管理库，我们可能需要通过组件的 props 进行繁琐的传递，而且很难保证状态的一致性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;异步操作&lt;/strong&gt; - 很多情况下，我们需要处理异步操作，比如网络请求。如果没有好的状态管理方案，处理这些异步操作会非常复杂。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在 JavaScript 中，常见的状态管理方案有 redux，vuex（用于 vue.js）、mobx 等。&lt;/p&gt;
&lt;p&gt;举个例子，redux 是一个常见的状态管理库，它提供了一个中心化的存储容器，所有的状态都存储在这里，通过定义 action 和 reducer 我们可以对状态进行操作。action 是描述状态改变的对象，而 reducer 则是根据 action 来实际进行状态改变的函数。使用 redux，我们可以方便地在多个组件之间共享状态，处理异步操作，同时 redux 也提供了一系列的中间件和工具方便我们调试应用和优化性能。&lt;/p&gt;
&lt;h2&gt;生命周期&lt;/h2&gt;
&lt;p&gt;在前端开发中，特别是在使用一些流行的前端框架如 React 或 Vue 时，我们经常会听到 &quot;生命周期&quot; 这个词。组件的生命周期，简单来说，就是组件从创建到销毁的过程。在这个过程中会经历如初始化、更新、销毁等多个阶段，每一个阶段都会对应特定的生命周期方法，这些方法给我们提供了在某一时刻操作组件的机会。&lt;/p&gt;
&lt;p&gt;以 React 为例，其生命周期主要分为三个阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;挂载阶段（Mounting）&lt;/strong&gt;：这个阶段是指组件实例被创建并插入 DOM 的阶段，在这个阶段 React 会调用如构造函数（constructor）、静态初始化方法（static getDerivedStateFromProps）、渲染方法（render）以及挂载结束方法（componentDidMount）等生命周期方法。这个阶段只会发生一次，它们按照刚刚提到的顺序被调用。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  // 对应 componentDidMount
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;更新阶段（Updating）&lt;/strong&gt;：这个阶段发生在当 props 或 state 发生改变时，组件会重新渲染。在这个阶段会调用如静态更新方法（static getDerivedStateFromProps）、shouldComponentUpdate、渲染方法（render）、获取快照方法（getSnapshotBeforeUpdate）和更新结束方法（componentDidUpdate）等生命周期方法。这个阶段可以发生多次。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  // 对应 componentDidUpdate
}, [var1, var2]);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;卸载阶段（Unmounting）&lt;/strong&gt;：当组件将要从 DOM 中移除时会进入卸载阶段，此时会调用卸载方法（componentWillUnmount）进行一些清理工作，例如清理定时器、取消网络请求或者清理在 componentDidMount 中创建的任何 DOM 元素。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  return () =&amp;gt; {
    // 对应 componentWillUnmount
  };
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;理解每个生命周期方法的作用以及何时会被调用，能够帮助我们更好地管理和控制组件的行为以及性能。&lt;/p&gt;
&lt;h2&gt;虚拟 DOM&lt;/h2&gt;
&lt;p&gt;虚拟 DOM 是一个很重要的概念，在很多现代的前端框架，比如 React 和 Vue 中，它们都在使用虚拟 DOM 来提高页面渲染的性能。&lt;/p&gt;
&lt;p&gt;在传统的开发方式中，当我们需要更新页面上的某一个元素，通常会直接操作真实的 DOM，这样就会引起页面的重绘和重排，通常会消耗大量的性能。这并不是一个问题，如果我们的页面很简单，或者操作很少。然而，随着 Web 应用的复杂度的提高，频繁的直接操作 DOM 将会大大影响应用的性能，导致用户体验下降。&lt;/p&gt;
&lt;p&gt;这个时候，虚拟 DOM 就派上了用场。虚拟 DOM，并不是真实 DOM 在浏览器中的表现，而是在 JavaScript 中的一种抽象概念，通常表现为一个对象，这个对象就是更为轻量级的对真实 DOM 的描述。&lt;/p&gt;
&lt;p&gt;当状态（state）发生变化需要更新 DOM 时，前端框架会先基于新的状态生成新的虚拟 DOM，然后通过比较新旧虚拟 DOM 的差异（这个过程叫做 Diffing），计算出最小的修改步骤，然后才将这些修改应用到真实的 DOM 上，这个过程叫做 Reconciliation（协调）。&lt;/p&gt;
&lt;p&gt;因此，虚拟 DOM 可以减少不必要的真实 DOM 操作，从而提高性能。虽然构建和比较虚拟 DOM 本身需要一些开销，但是由于 JavaScript 执行速度的提高，这些开销通常小于直接进行大量的 DOM 操作。也就是说，虚拟 DOM 提升了性能，同时因为虚拟 DOM 是 JS 对象，更方便开发和管理，从而提高了开发效率。&lt;/p&gt;
&lt;p&gt;这只是简单概括了一些前端框架的核心概念，例如 React、Vue 和 Angular 常用的一些概念，不同的框架可能会有其他特定的概念和功能。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;文章同步更新平台：掘金、CSDN、知乎、思否、博客，公众号（野生程序猿江辰）
我的联系方式，v：Jiang9684，欢迎和我一起学习交流&lt;/p&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>从前端入门到全栈</category><author>江辰</author></item><item><title>【从前端入门到全栈】Node.js之大文件分片上传</title><link>https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88nodejs%E4%B9%8B%E5%A4%A7%E6%96%87%E4%BB%B6%E5%88%86%E7%89%87%E4%B8%8A%E4%BC%A0/</link><guid isPermaLink="true">https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88nodejs%E4%B9%8B%E5%A4%A7%E6%96%87%E4%BB%B6%E5%88%86%E7%89%87%E4%B8%8A%E4%BC%A0/</guid><pubDate>Sun, 18 Jun 2023 21:46:46 GMT</pubDate><content:encoded>&lt;h2&gt;从前端入门到全栈-系列介绍&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;你会学到什么？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可能学不到什么东西，该系列是作者本人工作和学习积累，用于复习&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;作者介绍&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;江辰，前网易高级前端工程师&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;系列介绍&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在的 Web 前端已经离不开 Node.js，我们广泛使用的 Babel、Webpack、工程化都是基于 Node 的，各个互联网大厂也早已大规模落地 Node 项目。因此，想要成为一名优秀的前端工程师，提升个人能力、进入大厂，掌握 Node.js 技术非常有必要。&lt;/p&gt;
&lt;p&gt;Node.js 不仅可以用来完善手头的开发环境，实现减少代码和 HTTP 请求，降低网页请求消耗的时间，提升服务质量。还可以扩展前端工程师的工作领域，用作 HTTP 服务，让前端也能完成一部分后端的工作，减少对后端的依赖，降低沟通成本，提升开发效率。&lt;/p&gt;
&lt;p&gt;而且，Node.js 和浏览器的 JavaScript 只是运行时环境不同，编程语言都是 JavaScript ，所以掌握 Node.js 基础对前端工程师来说并不难，难点在于应用。由于浏览器的 JavaScript 主要是负责内容呈现与交互，而 Node.js 应用领域包括工具开发、Web 服务开发和客户端开发，这些都与传统的 Web 前端领域不一样，用来应对不同的问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;适宜人群&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;对 Node.js 感兴趣的 JavaScript 程序员&lt;/li&gt;
&lt;li&gt;希望拓展知识边界，往全栈方向发展的前端工程师&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;定义&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;分片上传是把一个大的文件分成若干块，一块一块的传输，这样做的好处就是在文件上传/下载过程中，遇到不可抗力，比如网络中断，服务器异常，或者其他原因导致操作中断；再次操作时，可以从已经上传/下载的部分开始继续上传/下载未完成的部分，而没有必要从头开始上传/下载。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;步骤&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;前端部分&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先，我们需要使用 &lt;code&gt;HTML&lt;/code&gt; 的 &lt;code&gt;File API&lt;/code&gt;，通过 &lt;code&gt;input&lt;/code&gt; 标签获取用户上传的大文件。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;slice&lt;/code&gt; 方法将大文件分割成若干个小文件块，即分片。&lt;/li&gt;
&lt;li&gt;再使用 &lt;code&gt;fetch&lt;/code&gt; 或 &lt;code&gt;axios&lt;/code&gt; 等方法向服务器发送 POST 请求，上传文件的分片。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;后端部分&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 &lt;code&gt;express&lt;/code&gt; 或类似的框架接收前端上传的文件分片。&lt;/li&gt;
&lt;li&gt;将接收到的文件分片保存到服务器的一个临时位置。&lt;/li&gt;
&lt;li&gt;图所接收到所有的文件分片后，将这些分片合并成一个完整的文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;流程图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/xuya227939/blog/assets/16217324/8686c839-1474-44c1-9836-5848f143a201&quot; alt=&quot;无标题-2023-03-19-0835&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import React, { useState } from &quot;react&quot;;
import axios from &quot;axios&quot;;

const FileUploader = () =&amp;gt; {
  const [file, setFile] = useState(null);

  const onSubmit = async (event) =&amp;gt; {
    event.preventDefault();

    if (!file) {
      // 没有文件则直接返回
      return;
    }

    // 以1MB为一个分片
    const blockSize = 1024 * 1024 * 1;
    const fileSize = file.size;

    let start = 0;
    let end = blockSize;

    while (start &amp;lt; fileSize) {
      const chunk = file.slice(start, end);

      const formData = new FormData();
      formData.append(&quot;file&quot;, chunk, file.name);

      // 分片上传
      await axios.post(&quot;/upload&quot;, formData);

      start = end;
      end = start + blockSize;
    }

    // 合并
    await axios.get(`/merge?filename=${file.name}`);
  };

  const onChange = (event) =&amp;gt; {
    setFile(event.target.files[0]);
  };

  return (
    &amp;lt;form onSubmit={onSubmit}&amp;gt;
      &amp;lt;input type=&quot;file&quot; onChange={onChange} /&amp;gt;
      &amp;lt;button type=&quot;submit&quot;&amp;gt;上传&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
};

export default FileUploader;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const fs = require(&quot;fs&quot;);
const path = require(&quot;path&quot;);
const multer = require(&quot;multer&quot;);

const upload = multer({ dest: &quot;uploads/&quot; });
const app = express();

app.post(&quot;/upload&quot;, upload.single(&quot;file&quot;), (req, res) =&amp;gt; {
  const chunk = req.file; // 获取上传的文件分片
  const { originalname } = req.file;
  const chunkDirPath = path.resolve(__dirname, &quot;chunks&quot;);
  const chunkPath = path.resolve(chunkDirPath, originalname);

  // 如果 chunks 文件夹不存在，就创建一个
  if (!fs.existsSync(chunkDirPath)) {
    fs.mkdirSync(chunkDirPath);
  }

  // 将文件分片保存到 chunks 文件夹下
  fs.renameSync(chunk.path, chunkPath);

  res.sendStatus(200);
});

app.get(&quot;/merge&quot;, (req, res) =&amp;gt; {
  const { filename } = req.query;
  const chunkDirPath = path.resolve(__dirname, &quot;chunks&quot;);
  const filePath = path.resolve(__dirname, &quot;files&quot;, filename);

  fs.readdirSync(chunkDirPath).forEach((chunkPath) =&amp;gt; {
    // 读取每一个文件分片，然后将它们一起写入一个新文件，完成合并
    fs.appendFileSync(
      filePath,
      fs.readFileSync(path.resolve(chunkDirPath, chunkPath))
    );
    // 合并后删除文件分片
    fs.unlinkSync(path.resolve(chunkDirPath, chunkPath));
  });

  // 删除用于保存文件分片的文件夹
  fs.rmdirSync(chunkDirPath);

  res.sendStatus(200);
});

app.listen(8000, () =&amp;gt; {
  console.log(&quot;Server listening on port 8000&quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;请注意这是一种简化的实现，无法处理有些复杂情况，如并发上传，断点续传，错误恢复等。如果你需要这些功能，可能需要引入更完善的库或服务，如 tus-js-client，Plupload，Resumable.js 或 UpChunk。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;需要注意的点&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 上传进度：&lt;/strong&gt; 用户上传大文件时，提供一个进度条可以改善用户体验。你可以使用 XMLHttpRequest 或 Fetch 的 API 获得正在上传的进度信息，并实时展示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 错误处理和重试：&lt;/strong&gt; 你需要处理可能会发生的错误，比如网络问题或者服务器错误。为了避免用户再次上传文件，可以实现重试机制，当某个分片上传失败时，可以重新上传这个分片。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 并发控制：&lt;/strong&gt; 如果你一次上传多个分片，可能会消耗大量的用户网络带宽。你需要控制同时上传分片的数量，比如使用 Promise.all。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 断点续传：&lt;/strong&gt; 用户在上传大文件时，可能会中断，比如网络连接可能会断开，或者用户关闭了页面。你可以实现断点续传，让用户可以在重新打开页面时，继续上次的上传，而不需要从头开始。通常需要后台支持，记录已经上传的分片。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 安全性：&lt;/strong&gt; 文件上传是一个重要的安全问题，你应该检查并限制用户上传的文件类型，防止上传恶意文件。同时，你应该在服务器端再次检查文件。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;文章同步更新平台：掘金、CSDN、知乎、思否、博客，公众号（野生程序猿江辰）
我的联系方式，v：Jiang9684，欢迎和我一起学习交流&lt;/p&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>从前端入门到全栈</category><author>江辰</author></item><item><title>桌面、生产力和泛娱乐</title><link>https://github.com/posts/%E6%A1%8C%E9%9D%A2%E7%94%9F%E4%BA%A7%E5%8A%9B%E5%92%8C%E6%B3%9B%E5%A8%B1%E4%B9%90-/</link><guid isPermaLink="true">https://github.com/posts/%E6%A1%8C%E9%9D%A2%E7%94%9F%E4%BA%A7%E5%8A%9B%E5%92%8C%E6%B3%9B%E5%A8%B1%E4%B9%90-/</guid><pubDate>Wed, 07 Jun 2023 12:52:41 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/307e78e5-19bf-4f53-b694-7650e0f38edb.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;感谢室友送的显示器支架和宜家办公桌！周末的时候，总算把家庭生产工具组装起来了，之前用的 MBP2017 款，实在太老了。性能渐渐缺失，最近 618 大促，更换了一套生产力 + 娱乐设备，致力提升在家办公体验，桌面有点乱，总算能兼顾生产和娱乐&lt;/p&gt;
&lt;h3&gt;显示器&lt;/h3&gt;
&lt;p&gt;我用过 4k 显示器之后，发现再也回不去了。1080，2k 都是些什么东西？（手动狗头）趁着 618 大促期间，购入了红米 4k 显示器，最开始买的是 WESCOM 27 英寸 4K（感觉性价比很高阿，800 多大洋就能有 27 寸 4k），显示器到手之后，测试发现，整体画面偏白，举个简单的例子，微信的消息框，你看不到它的背景色，我调教了很久，发现都不行，也就是色域太低。还在七天无理由内，赶紧退货。然后又考虑买那台呢？戴尔，lG？细细思考之后，我不是专业的设计人士，没有必要去追求极致的参数，最终选定了红米 4k，价格也很合适，性价比拉满，到手测试之后，很满意，比 WESCOM 好太多，毕竟一分价钱一分货&lt;/p&gt;
&lt;h3&gt;显示器支架手臂&lt;/h3&gt;
&lt;p&gt;看过很多手臂来支撑显示器，觉得很酷，有没有！而且还能把桌面上的空间给空出来，最终选定了北弧的显示器支架&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/c23912f2-9b4e-438f-b48d-f65bae705d84.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如图所示，很有科幻感。装上之后，脖子舒服不少，还能各个方向调动，如果后面再把手臂提升下，接入 chatGPT + 语音会咋样？或许正如钢铁侠中吧？空间层次感上升+1&lt;/p&gt;
&lt;h3&gt;桌面主机&lt;/h3&gt;
&lt;p&gt;用过两台 macbook pro ，一直想换 mac mini，M 系列的 mac mini ，主打就是一个性价比，而且不占空间。这次终于尝试了，确实很舒服，mac mini + 双显示器，体验拉满，性能又够，平时剪剪视频，写写代码，很优雅&lt;/p&gt;
&lt;h3&gt;三合一无线充&lt;/h3&gt;
&lt;p&gt;网上看过很多款无线充。看了评论，基本比较差劲，比如功率不够，充电发热等等。最终目光锁定在了贝尔金的三合一无线充，跟苹果官方合作的厂商，京东自营有卖，价格偏贵，我买的二手，解放了很多线&lt;/p&gt;
&lt;p&gt;清单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac Mini M2 25G + 512G&lt;/li&gt;
&lt;li&gt;红米 4K 显示器&lt;/li&gt;
&lt;li&gt;红米 1080 显示器&lt;/li&gt;
&lt;li&gt;阿米诺迷你洛键盘 68 键&lt;/li&gt;
&lt;li&gt;阿米洛手托&lt;/li&gt;
&lt;li&gt;罗技 Master 3s&lt;/li&gt;
&lt;li&gt;Switch Oled 喷喷限定&lt;/li&gt;
&lt;li&gt;Xbox Series X&lt;/li&gt;
&lt;li&gt;飞利浦桌面音箱&lt;/li&gt;
&lt;li&gt;绿联扩展坞&lt;/li&gt;
&lt;li&gt;希捷 2TB 移动硬盘&lt;/li&gt;
&lt;li&gt;iPad mini6&lt;/li&gt;
&lt;li&gt;贝尔金三合一无线充&lt;/li&gt;
&lt;li&gt;Apple Watch S7&lt;/li&gt;
&lt;li&gt;iPhone 12 mini&lt;/li&gt;
&lt;li&gt;AirPods 2&lt;/li&gt;
&lt;li&gt;宜家办公桌：140x65&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>办公桌面</category><author>江辰</author></item><item><title>【从前端入门到全栈】全栈是什么？- 系列必读</title><link>https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E5%85%A8%E6%A0%88%E6%98%AF%E4%BB%80%E4%B9%88--%E7%B3%BB%E5%88%97%E5%BF%85%E8%AF%BB/</link><guid isPermaLink="true">https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E5%85%A8%E6%A0%88%E6%98%AF%E4%BB%80%E4%B9%88--%E7%B3%BB%E5%88%97%E5%BF%85%E8%AF%BB/</guid><pubDate>Sun, 14 May 2023 19:53:04 GMT</pubDate><content:encoded>&lt;p&gt;你好，我是刘江，我在互联网上的昵称是江辰，很高兴和你在这个课程见面&lt;/p&gt;
&lt;p&gt;先介绍下我自己，最早我是在爱用科技公司工作，负责的是爱用商品业务线，在这家公司待了两年左右的时间，从一个完全不懂技术的应届生到全栈开发工程师，这段经历为我的全栈技能打下了一个很好的基础。&lt;/p&gt;
&lt;p&gt;后来，我辗转进入多家公司任职，负责过很多产品的开发，比如有 ToB 视频产品、CRM 订单系统、直播电商等等。有从 0 到 1 ，也有半途进入。完整开发过微信小程序、H5、Web，也有自己偶尔捣鼓 APP，过往的经历，使我能自己一个人独挑大梁。这么多领域的工作让我快速成长，并积累了大量的宝贵经验。&lt;/p&gt;
&lt;p&gt;入行这么多年，从后端到前端，从设计开发到线上运维，我都参与其中，带领过团队攻克很多壁垒。以全栈开发工程师的角色参与了不少的核心业务产品，也能让我在过需求评审的时候，站在前端的角度去思考后端设计，或是从后端的角度思考前端设计，再或是跟产品，设计同学极限拉扯&lt;/p&gt;
&lt;p&gt;那提到全栈工程师，你可能第一时间会想到 &quot;全能&quot; &quot;无所不能&quot;等一些词语，也可能听到过其他人对它或褒或贬的评价，那么，我们应该怎么充分的认知这个角色呢？我想和你聊聊我的理解&lt;/p&gt;
&lt;p&gt;我们来看看 chatGPT（3.5） 理解的全栈开发：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;全栈开发（Full-Stack Development）是指开发人员能够熟练掌握多个领域的技能，可以开发完整的应用程序，包括前端、后端和数据库等多个层面。全栈开发人员需要掌握多种编程语言、框架和技术，并能够快速适应不同的开发环境和工具。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;全栈开发可以实现更高效和协调的工作流程，可以更快地创建和部署应用程序，并且可以更好地理解整个应用程序的架构和流程。全栈开发人员不仅需要深入了解前端技术，如 HTML、CSS、JavaScript 及其框架，还需要熟悉后端技术，如数据库、服务器端脚本语言（如 PHP、Python、Java 等）及其框架。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;总的来说，全栈开发人员需要掌握多个领域的技能，能够独立完成整个应用程序的开发和部署，从而提高工作效率和技术竞争力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;提取几个关键字眼，&lt;strong&gt;掌握多个领域的技能&lt;/strong&gt;、&lt;strong&gt;可以开发完整的应用程序&lt;/strong&gt;、&lt;strong&gt;能适应不同的开发环境和工具&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从我的角度上来理解这段话，本质就是一个工程师不局限于某个领域的技能，从而扩展多个领域，点缀多棵技能树；使自己的路走的更加广阔&lt;/p&gt;
&lt;p&gt;那职业发展路线上来说，为什么我推荐你应该成为全栈开发工程师呢？&lt;/p&gt;
&lt;h3&gt;提高自己的综合能力&lt;/h3&gt;
&lt;p&gt;全栈开发工程师需要具备前端、后端、数据库、网络、安全等方面的知识和技能，能够更全面地理解软件开发的全过程，包括需求分析、系统设计、编码实现、测试和部署等环节，通过多项能力的学习过程，不断的总结和复盘知识点，能让你出现一门新的技术，快速学习，掌握核心知识点&lt;/p&gt;
&lt;p&gt;以我为例，最开始接触 Java（学校），PHP（工作后），发现这些语言无非是些基础知识、能做什么事、怎么做的，我经常会拿这样类似的语言模板去套，方便让我快速入门，经过这些整理之后，不管是后端语言，还是前端语言，对我而言都是实现业务的手段工具&lt;/p&gt;
&lt;h3&gt;高效的团队协作&lt;/h3&gt;
&lt;p&gt;全栈开发工程师可以更好地协调前后端的开发工作，使得前后端的开发能够更加高效地协作，从而提高项目的效率。&lt;/p&gt;
&lt;p&gt;举例来说，当你是前端角色，如果你拥有后端知识。恰巧跟你经常合作的后端同学突然遇到个问题，卡住了。又恰好你懂，可以帮助他，对吧，关系就这么起来了。作为后端角色来说，依然如此。&lt;/p&gt;
&lt;p&gt;我个人经常会遇到后端、运维问我一些问题，比如问这个接口报错了，报错信息，以常见的为例：入库操作错误、没有权限操作服务器上的文件、Nginx/Docker 服务挂了，这都是很常见的问题，拥有全面的知识点，能让你在团队中建立威信&lt;/p&gt;
&lt;h3&gt;就业和创业&lt;/h3&gt;
&lt;p&gt;由上面可知，全栈开发工程师本身拥有多技能属性，团队会非常欢迎这样的人。往往可以站在自己的角度来思考问题，又可以站在他人的角度来思考问题，这无疑提高了团队的沟通和配合，这使得他们的职业机会更加广泛，可以在不同领域的公司中找到合适的工作。&lt;/p&gt;
&lt;p&gt;创业，全栈开发工程师是创业的最佳技术角色，有了产品原型和基础设计，可以迅速实现第一个版本&lt;/p&gt;
&lt;p&gt;总之，全栈开发工程师的优势在于综合能力、团队协作、职业机会和发展前景等方面。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/pulse/full-stack-development-2022-key-trends-most-in-demand-at-careervira&quot;&gt;根据领英 2022 年的就业报告，“全栈开发者”也在热门新兴工作名单上。至于未来前景，美国劳工统计局表示，网络开发人员的就业市场将增长 13%（至少到 2030 年），这比平均水平要快。&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;最后，我个人认为对于很多程序员来说，全栈开发工程师这个职位对于自己的发展的而言，是个非常好的进阶方向。&lt;/p&gt;
</content:encoded><category>从前端入门到全栈</category><author>江辰</author></item><item><title>【从前端入门到全栈】系列介绍</title><link>https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E7%B3%BB%E5%88%97%E4%BB%8B%E7%BB%8D/</link><guid isPermaLink="true">https://github.com/posts/%E4%BB%8E%E5%89%8D%E7%AB%AF%E5%85%A5%E9%97%A8%E5%88%B0%E5%85%A8%E6%A0%88%E7%B3%BB%E5%88%97%E4%BB%8B%E7%BB%8D/</guid><pubDate>Sun, 14 May 2023 18:17:36 GMT</pubDate><content:encoded>&lt;ol&gt;
&lt;li&gt;你会学到什么？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可能学不到什么东西，该系列是作者本人工作和学习积累，用于复习&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;作者介绍&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;江辰，网易高级前端工程师&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;系列介绍&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在的 Web 前端已经离不开 Node.js，我们广泛使用的 Babel、Webpack、工程化都是基于 Node 的，各个互联网大厂也早已大规模落地 Node 项目。因此，想要成为一名优秀的前端工程师，提升个人能力、进入大厂，掌握 Node.js 技术非常有必要。&lt;/p&gt;
&lt;p&gt;Node.js 不仅可以用来完善手头的开发环境，实现减少代码和 HTTP 请求，降低网页请求消耗的时间，提升服务质量。还可以扩展前端工程师的工作领域，用作 HTTP 服务，让前端也能完成一部分后端的工作，减少对后端的依赖，降低沟通成本，提升开发效率。&lt;/p&gt;
&lt;p&gt;而且，Node.js 和浏览器的 JavaScript 只是运行时环境不同，编程语言都是 JavaScript ，所以掌握 Node.js 基础对前端工程师来说并不难，难点在于应用。由于浏览器的 JavaScript 主要是负责内容呈现与交互，而 Node.js 应用领域包括工具开发、Web 服务开发和客户端开发，这些都与传统的 Web 前端领域不一样，用来应对不同的问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;适宜人群&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;对 Node.js 感兴趣的 JavaScript 程序员&lt;/li&gt;
&lt;li&gt;希望拓展知识边界，往全栈方向发展的前端工程师&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>从前端入门到全栈</category><author>江辰</author></item><item><title>EventSource 引发的一系列事件</title><link>https://github.com/posts/eventsource-%E5%BC%95%E5%8F%91%E7%9A%84%E4%B8%80%E7%B3%BB%E5%88%97%E4%BA%8B%E4%BB%B6/</link><guid isPermaLink="true">https://github.com/posts/eventsource-%E5%BC%95%E5%8F%91%E7%9A%84%E4%B8%80%E7%B3%BB%E5%88%97%E4%BA%8B%E4%BB%B6/</guid><pubDate>Tue, 25 Apr 2023 16:51:18 GMT</pubDate><content:encoded>&lt;h3&gt;背景&lt;/h3&gt;
&lt;p&gt;大家好，我是江辰，最近小小的实现了下 chatGPT 的问答式回复，调研了前端如何实现这种问答式请求，有几种方案，Http、EventSource、WebSocket，三种实现方案各有优缺点，Http 和 WebSocket ，想必大家耳闻能详，这里我讲讲 EventSource&lt;/p&gt;
&lt;h3&gt;EventSource&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;EventSource 是服务器推送的一个网络事件接口。一个 EventSource 实例会对 HTTP 服务开启一个持久化的连接，以 text/event-stream 格式发送事件，会一直保持开启直到被要求关闭。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一旦连接开启，来自服务端传入的消息会以事件的形式分发至你代码中。如果接收消息中有一个事件字段，触发的事件与事件字段的值相同。如果没有事件字段存在，则将触发通用事件。&lt;/p&gt;
&lt;p&gt;与 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets_API&quot;&gt;WebSockets&lt;/a&gt;,不同的是，服务端推送是单向的。数据信息被单向从服务端到客户端分发。当不需要以消息形式将数据从客户端发送到服务器时，这使它们成为绝佳的选择。例如，对于处理社交媒体状态更新，新闻提要或将数据传递到客户端存储机制（如 IndexedDB 或 Web 存储）之类的，EventSource 无疑是一个有效方案。&lt;/p&gt;
&lt;p&gt;--- 引自 MDN&lt;/p&gt;
&lt;p&gt;对比 WebSocket，它就是&lt;strong&gt;简单，方便&lt;/strong&gt;，在特定的一些场景下，比如聊天消息或市场价格，这就是 EventSource 擅长的&lt;/p&gt;
&lt;h4&gt;使用方式&lt;/h4&gt;
&lt;p&gt;它的使用方式极其简单&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const evtSource = new EventSource(&apos;sse.php&apos;);
const eventList = document.querySelector(&apos;ul&apos;);

evtSource.onmessage = function(e) {
 let newElement = document.createElement(&quot;li&quot;);

  newElement.textContent = &quot;message: &quot; + e.data;
  eventList.appendChild(newElement);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对吧，几行代码搞定，如何携带参数，在 &lt;code&gt;new EventSource(&apos;sse.php?id=123&apos;);&lt;/code&gt; 其中 &lt;code&gt;id=123&lt;/code&gt;，就是我们要给链接传的参数&lt;/p&gt;
&lt;p&gt;问题来了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/235046901-e3a94d7e-20a3-4fdb-a666-bf24db31c069.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当我实现之后，发现它在不断的&lt;strong&gt;自动重连&lt;/strong&gt;？搜了很多文档，想不通，为何会自动重连，这里伏笔。想不通，ok，我就换个思路，改用 Axios 实现&lt;/p&gt;
&lt;h3&gt;axios&lt;/h3&gt;
&lt;p&gt;axios 实现如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const streamToString = async (readableStream) =&amp;gt; {
  return new Promise((resolve, reject) =&amp;gt; {
    const chunks = [];
    readableStream.on(&quot;data&quot;, (data) =&amp;gt; {
      chunks.push(data);
    });
    readableStream.on(&quot;end&quot;, () =&amp;gt; {
      resolve(Buffer.concat(chunks).toString(&apos;base64&apos;))
    });
    readableStream.on(&quot;error&quot;, reject);
  });
}


axios({
  method: &apos;get&apos;,
  url:`//xxx/api/chat/stream?prompt=${textarea.current.value.trim()}`,
  headers: { &apos;Content-Type&apos;: &apos;application/x-www-form-urlencoded&apos; },
  responseType: &apos;stream&apos;
}).then(async res =&amp;gt; {
  const raw = await streamToString(res.data);
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时还不知问题的**严重性！**实现完之后，发现不对劲啊，&lt;code&gt;readableStream.on is not a fucntion&lt;/code&gt;，？？？（黑人问号脸），遂打印 log 看看输出的 &lt;code&gt;res.data&lt;/code&gt; 是啥，字符串？根本不是一个方法啊，但看网上实现，是这样啊，没错？又看了几遍，都是这样实现的，很懵，直到看了下 axios 的 issue，&lt;a href=&quot;https://github.com/axios/axios/issues/479&quot;&gt;传送门&lt;/a&gt;，2016 年就有人提出了这个问题，也就是说 axios 在浏览器侧一直没有实现 steram，我内心 cnm，网上的文档都是假的！！！&lt;/p&gt;
&lt;p&gt;也就是说，按照目前 MDN 说法，&lt;code&gt;responseType &lt;/code&gt; 支持的类型有，&lt;code&gt;arraybuffer、blob、document、json、text、ms-stream&lt;/code&gt;，其中 &lt;code&gt;ms-stream&lt;/code&gt;，此响应类型仅允许用于下载请求，并且仅受 Internet Explorer 支持&lt;/p&gt;
&lt;p&gt;坑坑坑，又要开始了其他方案，想想 Fetch 能不能行，浏览器原生支持哦！&lt;/p&gt;
&lt;h3&gt;Fetch&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API&quot;&gt;Fetch API&lt;/a&gt; 提供了一个 JavaScript 接口，用于访问和操纵 HTTP 管道的一些具体部分，例如请求和响应。它还提供了一个全局 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/fetch&quot;&gt;fetch()&lt;/a&gt; 方法，该方法提供了一种简单，合理的方式来跨网络异步获取资源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这种功能以前是使用 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest&quot;&gt;XMLHttpRequest&lt;/a&gt; 实现的。Fetch 提供了一个更理想的替代方案，可以很容易地被其他技术使用，例如 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API&quot;&gt;Service Workers&lt;/a&gt;。Fetch 还提供了专门的逻辑空间来定义其他与 HTTP 相关的概念，例如 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS&quot;&gt;CORS&lt;/a&gt; 和 HTTP 的扩展。&lt;/p&gt;
&lt;p&gt;--- 引自 MDN&lt;/p&gt;
&lt;p&gt;利用 Fetch 实现了如下代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const response = await fetch(`//xxx/api/chat/stream?prompt=${textarea.current.value.trim()}`);
const reader = response.body.getReader();

const eventList = document.querySelector(&apos;ul&apos;);
while (true) {
  const { value, done } = await reader.read();
  const utf8Decoder = new TextDecoder(&apos;utf-8&apos;);
  let data: any = value ? utf8Decoder.decode(value, {stream: true}) : &apos;&apos;;
  try {
    data = JSON.parse(data)
    if (data.id || !data.content) {
      return
    }

    let newElement = document.createElement(&quot;li&quot;);
    newElement.textContent = &quot;message: &quot; + data.content;
    eventList.appendChild(newElement);
  } catch (e) {
  }
  if (done) {
    break;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现没有问题，在我电脑上也跑通了，能稳定接收服务端消息，不会自动重连，&lt;strong&gt;万事大吉&lt;/strong&gt;，转交朋友试用
。。。。&lt;/p&gt;
&lt;p&gt;交给朋友试用，反馈说，会出现回复不全？？？，调试搞起&lt;/p&gt;
&lt;p&gt;浏览器侧接收的消息
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/235064685-7a527f0d-5e74-46ef-b350-e8b2844401dc.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;抓包看的消息
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/235064723-f26b5c86-7c07-4fbd-861e-ad6f3a47cb75.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;对比看，浏览器侧**丢包！丢包了！！！**几番排查下来，不知为何会丢包，而且是只有 Windows 上会丢包（必现），macOS 上不会，不懂了呀，我们自己测试 Win 下 ping 都是稳定的，有懂的同学，可以告知下，谢谢！&lt;/p&gt;
&lt;h3&gt;最终解决方案&lt;/h3&gt;
&lt;p&gt;又回到 EventSource，没错，又回来了，折腾下来发现，每次收完消息，你必须手动关闭下，&lt;code&gt;evtSource.close();&lt;/code&gt;，才不会自动重连，而且自动重连就是 EventSource 的特性之一，害，伏笔解决了。这个关闭有个前提是，服务端下发字段告诉你，能关闭，你才能关闭哦，折腾啊！！！&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;通过这次的学习，让我对 EventSource 以及 Fetch、Axios 有了一次深刻的认知，大家看完觉得还不错的话，欢迎点赞，收藏哦
文章同步更新平台：掘金、CSDN、知乎、思否、博客，公众号（野生程序猿江辰）
我的联系方式，v：Jiang9684，欢迎和我一起学习交流&lt;/p&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>EventSource</category><author>江辰</author></item><item><title>Win CMD 常用命令</title><link>https://github.com/posts/win-cmd-%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</link><guid isPermaLink="true">https://github.com/posts/win-cmd-%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</guid><pubDate>Tue, 04 Apr 2023 15:42:42 GMT</pubDate><content:encoded>&lt;h3&gt;查看进程号&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;输入端口查看&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;输入端口号，如 8080，查看对应得 pid 号&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; netstat -aon | findstr 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;输入服务查看&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;tasklist | findstr nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;杀掉进程&lt;/h3&gt;
&lt;p&gt;[/F] 强制删除&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;taskkill /F /pid 17416
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Nginx 启动命令&lt;/h3&gt;
&lt;p&gt;找到 Nginx 安装目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;start .\nginx.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Nginx 停止命令&lt;/h3&gt;
&lt;p&gt;找到 Nginx 安装目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; .\nginx.exe -s stop
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;文件夹无法删除&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/229761007-84dd0cce-6cef-4ec3-a213-fb78d14a51e2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;比如这种文件夹无法删除，提示需要管理员权限或要删除得文件夹不存在&lt;/p&gt;
&lt;p&gt;通过新建记事本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DEL /F /A /Q \\?\%1
RD /S /Q \\?\%1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另存为 &lt;code&gt;del.bat&lt;/code&gt; 可执行文件，通过把要删除得项目，拖拽到可执行文件中，即可删除&lt;/p&gt;
</content:encoded><category>Win</category><category>CMD</category><author>江辰</author></item><item><title>人生思考</title><link>https://github.com/posts/%E4%BA%BA%E7%94%9F%E6%80%9D%E8%80%83/</link><guid isPermaLink="true">https://github.com/posts/%E4%BA%BA%E7%94%9F%E6%80%9D%E8%80%83/</guid><pubDate>Sun, 02 Apr 2023 21:18:55 GMT</pubDate><content:encoded>&lt;h2&gt;传送门&lt;/h2&gt;
&lt;h3&gt;前言&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;时光流逝，没想到已经一年多了，很久很久未在技术社区更新过文章，惭愧。也终于有勇气写个流水账了。在过去的一年中，也在不断的思考，自己将来（35 岁后）能做什么？乃至于我在失业期间，也想过是否要留学？考研？（毕竟还年轻，本人 97 年）甚至是说专门待业 1-2 年，学习英语 run 到外企，再通过外企 remove 国外？一直认为国内 35 岁以后，还在一线开发，不现实。理由如下：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;精力比不上年轻人&lt;/li&gt;
&lt;li&gt;刚从学校出来的哪种激情也不在&lt;/li&gt;
&lt;li&gt;身体也越来越不行&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;国内这个焦虑确实蛮严重的，起码对我本人来说，一直存在。受限于学历的影响， 想做管理，也很难，爬又爬不上去。&lt;/p&gt;
&lt;p&gt;下面就简单聊聊过去一年发生了什么&lt;/p&gt;
&lt;h3&gt;失业&lt;/h3&gt;
&lt;p&gt;我本人从未想过会被裁员，现在我也这么认为（当时的我不会被裁员），直到现实给我重拳出击。去年，B 站大概有三波裁员潮，分别是年初，年中，年末。年中的时候，我组内有一位非常优秀的同学被裁了。哪会儿其实蛮慌的，生怕是自己。而且是在上海疫情期间。会经历裁员 + 疫情双重 Buffer 的叠加，很容易让人崩溃。&lt;/p&gt;
&lt;p&gt;在上海疫情期间，我人已经在崩溃的边缘，我去过方舱，四月份阳的，在方舱待了一周左右的时间，真实体会过，什么叫人间疾苦。也压根不敢跟同事，家人，网上说，怕被歧视，哪会儿在小区的时候，就被居委会的人给歧视了。&lt;/p&gt;
&lt;p&gt;年末，通知我被裁员。裁员的前几天，我领导跟我们组内的人都聊了聊，看谁能主动承担这个名额，领个大礼包。到我的时候，我当然是不愿意的，我有车贷，去年 11 月底提的车，所以很希望有一份稳定的工作，能够维持基本的生计。但是吧，裁员这事，一般是综合维度来考量，我复盘了一些衡量的点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;刚入职（一年内&lt;/li&gt;
&lt;li&gt;跟领导的关系&lt;/li&gt;
&lt;li&gt;性价比&lt;/li&gt;
&lt;li&gt;可替代性（工作内容&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;大概以上这些，具体原因，直到现在我也不清楚，当时也想了解清楚具体原因，但都不说，也让我不要再问，ok，哪我也就不想再知道了&lt;/p&gt;
&lt;p&gt;通知我被裁员的时候，我跟前端老板聊了蛮多的，当时我还跟天哥（敖天羽）聊了下（她内推我进来的）看是否能活水，转到她部门下，就不让活水，后面心态就放平了，哪就这样吧。技术总监过来找我的时候，言下之意，你肯定要走的，早走，早超生吧，大概这意思，对大家都好。其实他不找我，我也要走的，没有拖的意义，而且我个人觉得没必要浪费时间，再到后面 HR 过来找我的时候，我一轮谈判就同意了，不想跟他们再耗下去。&lt;/p&gt;
&lt;h2&gt;面试&lt;/h2&gt;
&lt;p&gt;在去年 12 月中旬被裁员之后，整个人就很 emo 的状态，做什么事都没有干劲，整天就是把自己锁在屋里，刷刷剧，打打游戏，饿了就叫外卖，简单的途径获取多巴胺和内啡肽，满足一天的基本需求。&lt;/p&gt;
&lt;p&gt;现在回想起来，要是当时自驾从上海到新疆-西藏，整个人会舒服很多，又或者是专门开一个月的滴滴，体会下提前下岗再就业，这是我在最有闲的时间，没有去做的一些事，有点遗憾。希望后面可以补上，哈哈哈哈&lt;/p&gt;
&lt;p&gt;12 月下旬简单投了投简历，比如投了长沙腾讯云，想回老家看看机会，也收到了面试邀约，但由于本人心理没准备好，就拒绝了面试，想等到年后再面，年后再看就没机会了&lt;/p&gt;
&lt;p&gt;躺平了 12 月份，1 月份，过完年后，差不多到 2 月份了，再看机会，再投简历，很不幸，各大社区传来前端已死&lt;/p&gt;
&lt;h4&gt;脉脉&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/229353100-f3fed57d-25a9-49d3-8db6-185c131678fb.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;BOSS 直聘&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/229352982-b18540d9-3857-4c61-aa54-8e9787602584.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;拉勾&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/229353058-4f426c10-5d0c-45f7-993f-6acc32c6d8c2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/229353160-3850d4f5-2360-40c7-9ec0-7643ae2c53ef.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;很不幸，前端目前就是这么个现状，招聘软件上，基本都是已读不回，打招呼，没反应&lt;/p&gt;
&lt;p&gt;我是这么看待前端已死的。先说结论，我本人是反对前端已死，理由大概如下：&lt;/p&gt;
&lt;h4&gt;技术出海&lt;/h4&gt;
&lt;p&gt;最近几年，大厂的科技产品都在出海，寻石问路。抖音，拼多多，都做的不错，不再局限国内的人口红利，谋求更多的发展空间。同理，我们做技术的是否也可以寻找技术出海？我的答案是肯定的。国外项目平台有很多，比如 Upwork，在这个平台上，我们可以看到各个国家的机会，不会英语也没关系，借助翻译软件，简历包装等，寻求更多合作的机会，不要把自己局限住！&lt;/p&gt;
&lt;h4&gt;核心竞争力&lt;/h4&gt;
&lt;p&gt;作为一名技术人，关键是创造自己的核心竞争力，什么叫核心竞争力，也就是你跟别人与众不同的地方，别人能做的，你能做。别人不能做的，你也能做，哪自然择优而优，这对所有公司来说都是这样，本质上就是花最小的代价，选择更合适的的人。举个简单的例子，我们以上面这份大厂 JD（Job Describe）为例，基础部分就不讲了，关注几个指标：学历、工作经验、C 端业务经验，加分项。通过这几个条件，可以筛选出一批人，如果你对加分项更加深入，哪你的优势很大&lt;/p&gt;
&lt;h4&gt;人口红利&lt;/h4&gt;
&lt;p&gt;我们国内有个特点，哪个行业挣钱，哪个行业火，一堆人会进来（培训机构到处宣传），人口优势固然存在，这是一大优势，也是一大弱势。优势就不讲了。弱势就是会导致这个行业出奇的内卷。以互联网行业为例，早几年会个 ajax ，写个静态页面，专科以上学历，一堆人要。现在呢，你得懂源码、你得统招全日制学历以上、你得会多个技能，这都是基本的要求，哪如何找到差一点，比如熟练英语的读写，你又比一些人要优秀，机会更多，可以关注外企机会&lt;/p&gt;
&lt;h4&gt;技术发展趋势&lt;/h4&gt;
&lt;p&gt;作为一名技术人，一定要关注前沿技术的发展趋势，为什么要关注？简单来说，这关乎你的生存空间。近几年 web3、区块链、元宇宙发展很快，如果你跟不上时代，很快就会被淘汰，很简单的道理。比如你看各大招聘软件上，各种 web3 的岗位，工资很高以及要求熟练英语读写&lt;/p&gt;
&lt;p&gt;再说回来，2 月份，3 月份，基本盘，就没啥面试机会和合适的岗位。靠朋友内推，面试了几家，技术面到 HR 面聊完，就没啥消息。面试纯看缘分&lt;/p&gt;
&lt;p&gt;2 月底开始面的网易，网易是三轮技术面 + HR 面，整个流程持续了大概 20 多天，在三月份的时候入职了，属实场景复刻。去年也差不多这个时间点面的网易严选，同样是三轮技术面 + HR 面&lt;/p&gt;
&lt;h3&gt;未来&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;博客改造升级&lt;/li&gt;
&lt;li&gt;组件库重构&lt;/li&gt;
&lt;li&gt;脚手架升级&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;持续关注前沿技术&lt;/p&gt;
&lt;p&gt;也很庆幸自己是在 30 岁前经历了裁员，也让我更加坚定，互勉&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;我其实是个非常不喜欢面试的人，面试这东西，个人觉得很看眼缘，我是一个很容易紧张的人，如果面试官不能给我有效的引导，我其实内心蛮慌的，这次能够有幸进入网易，真的很感谢老板，面试官 + 运气不错&lt;/p&gt;
&lt;p&gt;文章更新平台：掘金、CSDN、知乎、思否、&lt;a href=&quot;https://github.com/xuya227939/blog&quot;&gt;博客&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我的联系方式，v：Jiang9684，欢迎入群交流，和我沟通&lt;/p&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>思考</category><author>江辰</author></item><item><title>win node-sass 安装失败</title><link>https://github.com/posts/win-node-sass-%E5%AE%89%E8%A3%85%E5%A4%B1%E8%B4%A5/</link><guid isPermaLink="true">https://github.com/posts/win-node-sass-%E5%AE%89%E8%A3%85%E5%A4%B1%E8%B4%A5/</guid><pubDate>Fri, 24 Mar 2023 15:34:18 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;事件起因是由于最近在看一块业务代码，用到了 Vue + 装饰器，业务代码很庞大，很复杂，出于原子化原则，先从简单的 Demo 看起，随即从 GitHub 上拉了一个模板代码下来看看，git clone, yarn ，yarn 安装依赖，报错，发现是 node-sass 安装失败，这玩意确实很难安装，主要是不兼容 Python3 ，记录下解决流程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;报错提示&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Can&apos;t find Python executable &quot;python&quot;&lt;/p&gt;
&lt;p&gt;报找不到 python 命令，从官网下载 python, &lt;a href=&quot;https://www.python.org/download/releases/2.7/&quot;&gt;Python2.7&lt;/a&gt;，注意，这里要看下你的 node-sass 版本是多少的，对应 python2.7 还是 python3.x，找到自己的电脑所属版本，比如 win64, win32, macOS 等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;gyp ERR! stack Error: &lt;code&gt;C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;第二步蛮复杂的，Win 上缺少相关的编译环境，先运行 &lt;code&gt;npm install -g node-gyp&lt;/code&gt;，然后运行 &lt;code&gt;npm install --global --production windows-build-tools&lt;/code&gt; 可以自动安装跨平台的编译器，注：第二句执行下载好 msi 文件卡着不懂不安装 ， 手动去对应的目录底下安装一下 在执行一边。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Node Sass version 8.0.0 is incompatible with ^4.0.0 版本不兼容&lt;/p&gt;
&lt;p&gt;安装的依赖版本不对，安装了 node-sass 高版本，重新手动安装下低版本&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;相关链接&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;https://blog.csdn.net/zeroheitao/article/details/112545324&lt;/li&gt;
&lt;li&gt;https://proustibat.medium.com/how-to-fix-error-node-sass-does-not-yet-support-your-current-environment-os-x-64-bit-3.with-c1b3298e4af0&lt;/li&gt;
&lt;li&gt;https://stackoverflow.com/questions/37415134/error-node-sass-does-not-yet-support-your-current-environment-windows-64-bit-w&lt;/li&gt;
&lt;li&gt;https://www.zhaojun.ink/archives/node-sass-install&lt;/li&gt;
&lt;li&gt;https://www.python.org/download/releases/2.7/&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>node-sass</category><author>江辰</author></item><item><title>聊聊我对前端已死的看法</title><link>https://github.com/posts/%E8%81%8A%E8%81%8A%E6%88%91%E5%AF%B9%E5%89%8D%E7%AB%AF%E5%B7%B2%E6%AD%BB%E7%9A%84%E7%9C%8B%E6%B3%95/</link><guid isPermaLink="true">https://github.com/posts/%E8%81%8A%E8%81%8A%E6%88%91%E5%AF%B9%E5%89%8D%E7%AB%AF%E5%B7%B2%E6%AD%BB%E7%9A%84%E7%9C%8B%E6%B3%95/</guid><pubDate>Wed, 22 Mar 2023 09:42:22 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;谁也不曾设想，今年的就业环境比起去年更差劲。客户端已死、前端已死、后端已死、BOSS 直聘打招呼不回，拉勾拉不动。互联网上各种不好的消息充斥着人们的神经，一定要保证自己的核心竞争力，才能在一波又一波的浪潮冲击下，生存！生存！生存！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;技术出海&lt;/h3&gt;
&lt;p&gt;最近几年，大厂的科技产品都在出海，寻石问路。抖音，拼多多，都做的不错，不再局限国内的人口红利，谋求更多的发展空间。同理，我们做技术的是否也可以寻找技术出海？我的答案是肯定的。国外项目平台有很多，比如 Upwork，在这个平台上，我们可以看到各个国家的机会，不会英语也没关系，借助翻译软件，简历包装等，寻求更多合作的机会，不要把自己局限住！&lt;/p&gt;
&lt;h3&gt;核心竞争力&lt;/h3&gt;
&lt;p&gt;作为一名技术人，关键是创造自己的核心竞争力，什么叫核心竞争力，也就是你跟别人与众不同的地方，别人能做的，你能做。别人不能做的，你也能做，哪自然择优而优，这对所有公司来说都是这样，本质上就是花最小的代价，选择更合适的的人。举个简单的例子，我们以上面这份大厂 JD（Job Describe）为例，基础部分就不讲了，关注几个指标：学历、工作经验、C 端业务经验，加分项。通过这几个条件，可以筛选出一批人，如果你对加分项更加深入，哪你的优势很大&lt;/p&gt;
&lt;h3&gt;人口红利&lt;/h3&gt;
&lt;p&gt;我们国内有个特点，哪个行业挣钱，哪个行业火，一堆人会进来（培训机构到处宣传），人口优势固然存在，这是一大优势，也是一大弱势。优势就不讲了。弱势就是会导致这个行业出奇的内卷。以互联网行业为例，早几年会个 ajax ，写个静态页面，专科以上学历，一堆人要。现在呢，你得懂源码、你得统招全日制学历以上、你得会多个技能，这都是基本的要求，哪如何找到差一点，比如熟练英语的读写，你又比一些人要优秀，机会更多，可以关注外企机会&lt;/p&gt;
&lt;h3&gt;技术发展趋势&lt;/h3&gt;
&lt;p&gt;作为一名技术人，一定要关注前沿技术的发展趋势，为什么要关注？简单来说，这关乎你的生存空间。近几年 web3、区块链、元宇宙发展很快，如果你跟不上时代，很快就会被淘汰，很简单的道理。比如你看各大招聘软件上，各种 web3 的岗位，工资很高以及要求熟练英语读写&lt;/p&gt;
&lt;p&gt;完。&lt;/p&gt;
</content:encoded><category>前端</category><author>江辰</author></item><item><title>大厂面试题</title><link>https://github.com/posts/%E5%A4%A7%E5%8E%82%E9%9D%A2%E8%AF%95%E9%A2%98/</link><guid isPermaLink="true">https://github.com/posts/%E5%A4%A7%E5%8E%82%E9%9D%A2%E8%AF%95%E9%A2%98/</guid><pubDate>Wed, 21 Sep 2022 10:44:44 GMT</pubDate><content:encoded>&lt;h2&gt;掌学&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;讲讲项目中如何减少 Webpack 打包体积&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JS 有哪些基本数据类型&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Undefined、Null、Boolean、Number 和 String、Object&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- 通过什么方式可以判断出 Object 和 Array（引申到 instanceof 的源码实现）
- Object.prototype.toString.call 为什么是 Object.prototype 而不是 Object.toString.call
    首先你要明白 Object 是 js 中所有其他数据类型的父类。意思是所有的数据类型都继承了 Object，但是无论是 string 还是 array 都是会重写这个 tostring 方法的。从此处就可以说你用的两者就完全不同。
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;递归是什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;递归的定义&lt;/li&gt;
&lt;li&gt;递归有什么缺点，如何解决？（引申出尾递归优化和迭代）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲闭包是什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;闭包引申的垃圾回收机制&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;var、let、const 的区别&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;var 有什么缺陷？&lt;/li&gt;
&lt;li&gt;为什么引入 let 、const？&lt;/li&gt;
&lt;li&gt;引申到 JS 源码词法环境上讲&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;es5 如何实现继承&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;原型继承方式（这里要注意下，很有可能让你手写）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function Person() {
        this.name = &apos;123&apos;;
    }

    Person.prototype.getName = function() {
        console.log(this.name);
    }

    function Child() {};

    Child.prototype = new Parent();

    var child = new Child();
    child.getName();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;寄生组合继承&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function Person(name) {
        this.name = name;
    }

    Person.prototype.getName = function() {
        console.log(this.name);
    }

    function Child(age, name) {
        this.age = age;
        Person.apply(this, name);
    };

    Child.prototype = new Person();


    var child1 = new Child(18, &apos;Faker&apos;);
    child1.getName();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;引申到 es6 的 Class 语法糖&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;es6 有哪些新的特性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;讲讲箭头函数和普通函数的区别&lt;/li&gt;
&lt;li&gt;箭头函数可以修改 this 指向吗？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;map 和 waekMap 的区别&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;map 和 set 的应用场景&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JS 中 this 指向&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 setTimeout 和 setInterval 的差异&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;setInterval 如果漏写销毁，则会存在内存泄露问题（内存泄露和内存溢出有什么区别？内存溢出，坑满了，没有坑，内存泄露：有人占着茅坑不拉屎）&lt;/li&gt;
&lt;li&gt;例举了一个具体例子，比如 setInterval 的不准确性，200ms 插入一个 700ms 同步任务，此时队列栈为空，下一个定时任务多久执行，会导致 setInterval 执行不准确问题，使用 setTimeout 递归解决&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CSS 重绘和重排如何理解&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用过函数节流和防抖吗？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;localStorage, sessionStorage, Cookie 之间的区别&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用域不同，localStorage 所有窗口，sessionStorage 当前窗口&lt;/li&gt;
&lt;li&gt;大小不同，localStorage、sessionStorage 5M, Cookie 5kb 左右&lt;/li&gt;
&lt;li&gt;生命周期不同，localStorage 页面销毁，也有效，sessionStorage 页面销毁，消失&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 React Fiber 架构&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲深浅 Copy&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何实现一个深 Copy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲对 diff 的理解&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲对 Hooks 的理解&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 Class 生命周期&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;反转二叉树的实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一个迷宫，最短路径生成
我大概想了下，暴力解决法&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊业务上的事?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊最复杂的业务如何处理的？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如何做性能优化?&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;HR 面&lt;/h3&gt;
&lt;p&gt;聊聊期望薪资，达不到，后续就没下文了&lt;/p&gt;
&lt;h2&gt;泛为科技&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;JS 有哪些基本数据类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下面宽高各是多少&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;style&amp;gt;
        .box {
            width: 100px;
            height: 100px;
            padding: 10px;
            margin: 10px;
            background-color: #f00;
            box-sizing: content-box;
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div class=&quot;box&quot;&amp;gt;12312312&amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;1.1 如果 box-sizing 改成 border-box，宽高各是多少
1.2 讲讲盒模型的内容
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;content-box：宽高 120&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;border-box: 宽高 100&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;下面输出结果是什么？&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    var count = 100;
    var obj = {
        count: 200,
        getCount: function() {
            console.log(this.count);
        }
    }

    const c = obj.getCount;
    obj.getCount();
    c();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：200, 100&lt;/p&gt;
&lt;p&gt;这题可以注意下演变：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let count = 100;
    var obj = {
        count: 200,
        getCount: function() {
            console.log(this.count);
        }
    }

    const c = obj.getCount;
    obj.getCount();
    c();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：200, undefined&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;下面输出结果是什么？&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    var obj1 = { a: 100 };
    var obj2 = Object.assign({}, obj1);
    var obj3 = obj2;
    obj3.a = 200;
    console.log(obj1);
    console.log(obj2);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：200，100&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;span 标签在浏览器中偏移量是多少&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;style&amp;gt;
        .test {
            margin: 20px;
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;span class=&quot;test&quot;&amp;gt;&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下面打印结果是多少&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    var p1 = new Promise((resolve) =&amp;gt; {
        resolve(1);
    });
    var p2 = new Promise((resolve) =&amp;gt; {
        setTimeout(() =&amp;gt; {
            resolve(2);
        }, 0);
    });
    var p3 = new Promise((resolve) =&amp;gt; {
        resolve(3);
    });
    Promise.all([p1, p2, p3]).then((res) =&amp;gt; {
        console.log(res);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下面打印结果是多少&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    async function promise2() {
        function p1() {
            return new Promise((resolve) =&amp;gt; {
                resolve(1);
            });
        }

        function p2() {
            return new Promise((resolve, reject) =&amp;gt; {
                reject(2);
            });
        }

        function p3() {
            return new Promise((resolve, reject) =&amp;gt; {
                resolve(3);
            });
        }

        try {
            var p11 = await p1();
            var p22 = await p2();
            var p33 = await p3();
            console.log(p11);
            console.log(p22);
            console.log(p33);
        } catch(e) {};
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;es5 的继承如何实现&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;挂&lt;/p&gt;
&lt;h2&gt;彩云科技&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;在横线出补全代码，打印 hello&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function fun() {
    return new Promise((resolve, reject) =&amp;gt; {
        setTimeout(function() {
            -------
       });
    })
}

fun().then(res  =&amp;gt; {
    console.log(res);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下面打印结果是多少&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var obj1 = {
    name: &apos;123&apos;,
    say: function() {
       console.log(this.name);
    }
}

var obj2 = {
    name: &apos;456&apos;,
    say: obj1.say
}

obj2.say();
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下面打印结果是多少&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function fun() {
  temp = 0;
}
fun();
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下面打印结果是多少&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var obj = {
    name: &apos;张三&apos;
}

function fun(o) {
    o.name = &apos;李四&apos;;
}

fun(obj);

console.log(obj.name);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下面横线出请补全代码，以实现继承&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function a() {
    this.name = &apos;张三&apos;;
}

a.prototype.say = function() {
    console.log(&apos;我的名字&apos; + this.name);
}

function b() {
    this.age = 18;
}

b.prototype = _____;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;请描述下 new 的执行原理&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function _new() {
    var obj = new Object();
    consturctor.prototype = arguments;
    obj._proto_ = consturctor.prototype;
    var res = obj.apply(this, arguments);
    return typeof res == &apos;object&apos; ? res : obj;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;为什么 Object.prototype.toString.call 可以判断出变量，而不是通过 Object.toString.call ?&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;这是因为 toString 为 Object 的原型方法，而 Array ，function 等类型作为 Object 的实例，都重写了 toString 方法。不同的对象类型调用 toString 方法时，根据原型链的知识，调用的是对应的重写之后的 toString 方法（function 类型返回内容为函数体的字符串，Array 类型返回元素组成的字符串.....），而不会去调用 Object 上原型 toString 方法（返回对象的具体类型），所以采用 obj.toString()不能得到其对象类型，只能将 obj 转换为字符串类型；因此，在想要得到对象的具体类型时，应该调用 Object 上原型 toString 方法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊前端脚手架&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;带团队中有哪些难点
3.1 如何培养实习生，系统的介绍了&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;浏览器缓存原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊迭代流程&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你有什么想问我的吗&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;拿到 offer&lt;/p&gt;
&lt;h2&gt;兴盛优选&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;聊聊 vite 和 webpack 的区别&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vite 是通过访问到特定的执行环境，而编译相关的文件，导致它的编译速度带来了显著的提升&lt;/li&gt;
&lt;li&gt;webpack 重编译，轻运行，在编译过程中会去通过 babel 递归遍历所有的文件（深度遍历），进行转换，导致在编译阶段的时间很长&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为什么 &lt;code&gt;marign 0 auto&lt;/code&gt; 无法垂直居中&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;控制 z-index 的规则有哪些&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;元素替换概念有了解吗？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;模块化有了解吗，讲讲 import 和 require 的区别？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;import
&lt;ul&gt;
&lt;li&gt;import 是值 copy&lt;/li&gt;
&lt;li&gt;必须写在函数的顶层&lt;/li&gt;
&lt;li&gt;导出整个&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;require
&lt;ul&gt;
&lt;li&gt;地址引用&lt;/li&gt;
&lt;li&gt;可以写在任何地方&lt;/li&gt;
&lt;li&gt;可以对象式导出&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;移动端 1px 像素如何解决？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;viewport + rem 实现&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单页应用如何提高加载速度？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;讲了下骨架屏，骨架屏的原理是什么？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 Vue 的 nextTick 原理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本质上通过 微观任务进行拦截掉下一帧的渲染时间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一个元素隐藏有几种方式？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;opacity: 0&lt;/li&gt;
&lt;li&gt;display: none&lt;/li&gt;
&lt;li&gt;js if&lt;/li&gt;
&lt;li&gt;围绕着这几种属性又讲了下 重排和重绘&lt;/li&gt;
&lt;li&gt;重排和重绘的产生原因有哪集中以及解决方案有几种&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 BFC&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bfc 产生的条件&lt;/li&gt;
&lt;li&gt;bfc 的解决方案&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲闭包&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;闭包的应用场景有哪些&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;webpack 的原理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;loader 和 plugin 的区别&lt;/li&gt;
&lt;li&gt;多 thunk 如何生成&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Git 如何覆盖某次 commit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 GC 原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实际业务场景题，讲讲你的思路&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这里就不展开了，公司的具体场景&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JS 如何解决数值精度问题&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲事件循环&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;css 选择器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;display 有哪些属性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;长列表滚动，你怎么优化的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;由于业务中的经验没有涉及到这块，只能浅显的讲了讲虚拟滚动&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊你做的性能优化&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这里聊了很久，面试官也跟我探讨了如何处理，有没有更好的思路，主要围绕着 STAR 法则讲述即可&lt;/li&gt;
&lt;li&gt;聊到 web-view 的时候，又牵扯到了 web-view 的小程序架构&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你工作中最有成就感的一件事，除了上面的，还有吗？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我是按简历中写的来，这里也聊了很久&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;挂&lt;/p&gt;
&lt;h2&gt;众安保险&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;讲讲 JS 有哪些类型&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基本类型&lt;/li&gt;
&lt;li&gt;复杂类型&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1.1 JS 有哪些方法可以判断类型&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;讲讲箭头函数和普通函数区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JS 异步处理是如何演进的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;aync/await 有什么缺点
无法暂停
报错，后面的代码就无法执行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;居中有那些方式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;三列布局如何实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;setState 原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hooks 为何有一些规则使用条件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;算法题，如何实现单向链表反转&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 ts 的泛型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲事件循环&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;实际场景算法题&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TS 的应用场景&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mobx 和 Redux 的原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;函数式组件特点，和 HOC 区别&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;挂&lt;/p&gt;
&lt;h2&gt;字节&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;CSS 栅格布局&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;flex: 0 1 auto 的含义&lt;/p&gt;
&lt;p&gt;flex-grow：定义元素在一个空间内放大的比例，默认为 0
flex-shrink：定义元素在剩余空间内缩小的比例，默认为 1
flex-basis：定义元素初始化宽度，这个指不可为负数，如果不使用 box-sizing 改变盒模型的话，那么这个属性就决定了 flex 元素的内容盒（content-box）的尺寸&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊了下项目上的东西，如何衡量项目的价值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JS 的输出，this 指向&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var length = 10;
function fn() {
    return this.length + 1;
}
var obj = {
    length: 5,
    test1: function() {
        return fn();
    }
};
obj.test2 = fn;

//下面代码输出是什么
console.log(obj.test1())
console.log(fn()===obj.test2())
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;订阅发布和观察者模式和什么之间的区别，实现一个订阅发布者模式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;柯里话函数的实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;算法题：twoSum 得到两个数的之后等于 target&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;挂&lt;/p&gt;
&lt;h2&gt;xTransfer&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;实现一个函数 calc&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;function calc() {&lt;/p&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于任意参数，实现累乘功能&lt;/li&gt;
&lt;li&gt;对于两次同样的参数，结果缓存，比如 （1, 2, 3）、（3, 1, 2）&lt;/li&gt;
&lt;li&gt;对缓存优化（这里使用 LRU 算法进行缓存优化）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;挂&lt;/p&gt;
&lt;h2&gt;七牛云&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;介绍下项目&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fiber 架构原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mobx 和 Redux 区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;针对项目上的功能，介绍了下，提出了问题，如何解决&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现一个 apply&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;说下事件循环的打印结果&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;针对深 Copy，变种的题目&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;聊聊 React 事件机制原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;深度聊项目
对于项目要充分了解，MVC 和 MVVM 框架原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;谈谈摇树的概念&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对微前端有了解吗&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;性能优化有哪些点，例举下
对性能优化，围绕着项目上做过的性能优化详细举例下&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;阐述下项目背景，带来多大的收益&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊团队管理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊项目规范如何落地&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊开发效率如何提升的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Webpack 热更新机制原理&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;三面&lt;/h3&gt;
&lt;p&gt;产品经理面试，主要聊了聊项目上的情况， 团队管理，印象中最深的事等&lt;/p&gt;
&lt;p&gt;年后推进进度，挂&lt;/p&gt;
&lt;h2&gt;坚果云&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;随便聊了聊基础&lt;/p&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;p&gt;明确需求，明确功能&lt;/p&gt;
&lt;p&gt;使用到哪些技术：React、Redis（缓存消息）、MySQL（存储离线消息）、NoSQL（维护一个用户关系图）、MQ（消息队列，读扩散，写扩散）&lt;/p&gt;
&lt;p&gt;如果我们要做一个聊天功能，怎么做？（你可以放飞自我，想怎么实现都行）&lt;/p&gt;
&lt;p&gt;挂&lt;/p&gt;
&lt;h2&gt;天壤智能&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;讲讲 JS 基本数据类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲闭包&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲原型链&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 es6 特性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;讲讲 Proxy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲引用类型 Copy 问题&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 promise 原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 redux 中间件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 React 15 生命周期&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 useCallBack 和 useMemo 的区别&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;还有用到其他 hooks 吗&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 useRef 和 ref 的区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;印象中最深的一件事&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;p&gt;部门总监面的，问题很尖锐&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;useMemo 和 useCallBack 有什么本质上的区别
都是对象
1.1 如果只用一个，哪个替换哪个？
用 useMemo 替换 useCallBack，函数和值都是对象&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;页面缓存如何做
后端来配置 http 请求头相关字段&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;前端路由原理
本人菜鸡，直接回答不知道&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;项目中用到哪些 NPM 包&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;面试官想了解下你的项目复不复杂&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用过 Canvas 吗&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;HR 面&lt;/h3&gt;
&lt;p&gt;聊聊期望薪资&lt;/p&gt;
&lt;p&gt;过&lt;/p&gt;
&lt;h2&gt;掌门&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;JS 基本数据类型&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;注意下 typeof NaN 输出什么（输出 number）&lt;/li&gt;
&lt;li&gt;列举了下 es5 的数据类型，以及 es6 的数据类型（set 和 map 也是），以及区分方法、type、instanceof、Obect.prototype.toString.call、Array.isArray、利用 constructor 判断是数组还是对象&lt;/li&gt;
&lt;li&gt;symbol 是用来干啥的
ES5 的对象属性名都是字符串，这容易造成属性名的冲突。比如，你使用了一个他人提供的对象，但又想为这个对象添加新的方法（mixin 模式），新方法的名字就有可能与现有方法产生冲突。如果有一种机制，保证每个属性的名字都是独一无二的就好了，这样就从根本上防止属性名的冲突。这就是 ES6 引入 Symbol 的原因&lt;/li&gt;
&lt;li&gt;map 和 set 的区别
这里强调下，map 和 set 本质上不是新的类型，而是一种数据结构
引申到 weakMap 和 weakSet 的区别&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;var、let、const 区别&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- 围绕着 var 的缺陷，介绍了下 let 和 const 的优点
- 讲了下词法作用域来形成 let 和 const 的快级作用于
- Object.freeze 冻结的对象是浅冻结还是深冻结
    const aaa = Object.freeze({ name: { bbb: &apos;123&apos; } });
    aaa.name.bbb = &apos;456&apos;;
    这会修改，第一层不会，比如 aaa.name = &apos;456&apos;;
    实现深冻结
    ```
    // 深冻结函数.
    function deepFreeze(obj) {

        // 取回定义在 obj 上的属性名
        var propNames = Object.getOwnPropertyNames(obj);

        // 在冻结自身之前冻结属性

        propNames.forEach(function(name) {
            var prop = obj[name];

            // 如果prop是个对象，冻结它
            if (typeof prop == &apos;object&apos; &amp;amp;&amp;amp; prop !== null)
                deepFreeze(prop);
        });

        // 冻结自身(no-op if already frozen)
        return Object.freeze(obj);
    }
```
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;箭头函数和普通函数的区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数组上有哪些属性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;length、concat、find、findIndex、flat、join、sort、map、forEach、reduce、pop、push、shift、slice、splice&lt;/li&gt;
&lt;li&gt;forEach、Map、Reduce 之间的区别&lt;/li&gt;
&lt;li&gt;面试官问 for of 和 for in 的区别
for of 用来编译迭代器，一个数据结构只要部署了 Symbol.iterator 属性，就被视为具有 iterator 接口，就可以用 for...of 循环遍历它的成员。也就是说，for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。
for of 不可遍历不可迭代对象，for of 遍历的是值, for in 遍历的是 &lt;code&gt;key&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;什么是递归函数&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;讲了讲尾递归&lt;/li&gt;
&lt;li&gt;递归遇到内存溢出如何解决
通过尾递归解决或改成迭代&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;以下代码输出结果是什么&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;6.1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var obj = {
            say: function() {
                console.log(this);
            },
            say2: () =&amp;gt; {
                console.log(this);
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;6.2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var obj = {
            say: function() {
                console.log(this);
            },
            say2: () =&amp;gt; {
                console.log(this);
        }
    }
    var fn = obj.say;
    fn();
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;以下代码输出结果是什么&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    var name = &apos;123&apos;;
    function test() {
        console.log(name);
        var name = &apos;456&apos;;
    }
    test();

    // undefined，因为 JS 是分声明阶段和编译阶段

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;CSS 实现居中有哪几种方式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;元素定宽高实现的方式，通过绝对定位 + display: table-cell; vertical-align: middle; text-align: center;&lt;/li&gt;
&lt;li&gt;元素不定宽高，通过绝对定位 + flex&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;setState 更新原理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;引申到合并更新 batchUpdate 关键字命中以及同步和异步讲讲&lt;/li&gt;
&lt;li&gt;引申到 render 阶段和 commit 阶段&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;常用的 hooks 有哪些&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性能问题如何解决&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;React 组件传递状态有哪几种方式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 React Fiber 架构&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;STAR 法则讲述，之前的问题，解决了什么问题，怎么解决的，带来了性能上的好处&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;为什么辞职&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;工作经历中碰到哪些挑战&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;开发流程是咋样的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;团队管理上如何带的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;性能优化如何做的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;二叉树的反转实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最短路径的实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;期望薪资多少&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你有什么想问的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你对下份工作的期望是什么&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;HR 面&lt;/h3&gt;
&lt;p&gt;已拿 offer&lt;/p&gt;
&lt;h2&gt;微盟&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;介绍下最有成就感的项目&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;项目上的架构如何设计，围绕着 MVC 层介绍&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现一个 ajax&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;//封装一个ajax请求
function ajax(options) {
    //创建XMLHttpRequest对象
    const xhr = new XMLHttpRequest();

    //初始化参数的内容
    options = options || {};
    options.type = (options.type || &apos;GET&apos;).toUpperCase();
    options.dataType = options.dataType || &apos;json&apos;;
    const params = options.data;

    //发送请求
    if (options.type === &apos;GET&apos;) {
        xhr.open(&apos;GET&apos;, options.url + &apos;?&apos; + params, true);
        xhr.send(null);
    } else if (options.type === &apos;POST&apos;) {
        xhr.open(&apos;POST&apos;, options.url, true);
        xhr.send(params);

    //接收请求
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            let status = xhr.status;
            if (status &amp;gt;= 200 &amp;amp;&amp;amp; status &amp;lt; 300) {
                options.success &amp;amp;&amp;amp; options.success(xhr.responseText, xhr.responseXML);
            } else {
                options.fail &amp;amp;&amp;amp; options.fail(status);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;- 在实现的 ajax 基础上实现调用所有请求，等到所有请求结果成功之后，再返回结果
- 在实现的 ajax 基础上实现请求顺序调用，第一个调用成功之后，再调用第二个，以此类推
  这里实现有问题，没有跟面试官沟通好，下次注意，手写题目，一定要跟面试沟通好
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;挂&lt;/p&gt;
&lt;h2&gt;帷幄匠心&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;p&gt;实现一个 repeat 函数，根据传入的参数，间隔时间，打印次数，来输出 log&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function repeatPrint(msg) {
    console.log(msg);
}

repeat(func, inteval, times){ … }

const r = repeat(repeatPrint, 10, 10);

r(&quot;hello world&quot;);

function repeat(fn, interval, timers) {
    return function(message) {
        for(let i = 0; i &amp;lt; timers; i++) {
            (function() {
                setTimeout(function() {
                    fn(message);
                }, i * 1000);
            })();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用最精炼的代码实现数组非零非负最小值的索引 index&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 例如：[10,21,0,-7,35,7,9,23,18] 输出索引 5, 数值 7 最小
function getIndex(arr) {
    let index = null;
    ...
    return index;
}


function getIndex(arr) {
    return arr.reduce((pre, cur, index) =&amp;gt; {
        return cur &amp;gt; 0 &amp;amp;&amp;amp; cur &amp;lt; pre[&apos;val&apos;] ? { val: cur, i: index } : pre;
    }, { val: Infinity, i: 0})
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;该代码输出结果是什么？&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;const list = [1, 2, 5]
const square = num =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        setTimeout(() =&amp;gt; {
            resolve(num * num)
        }, 1000)
    })
}

function test() {
    list.forEach(async x =&amp;gt; {
        const res = await square(x)
        console.log(res)
    })
}
test()
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;H5 和小程序如何计算首屏加载时间&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;HR 面&lt;/h3&gt;
&lt;p&gt;已拿 offer&lt;/p&gt;
&lt;h2&gt;乐言科技&lt;/h2&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;实现一个 promise.all&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;event-loop 的执行结果&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;根据场景，实现一个 hooks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;场景题, 执行多个 hooks， 渲染出来的结果&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一面答的其实很垃圾，居然进入了二面&lt;/p&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;简单讲讲项目&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;做一道题，手写一唯数组转换树节点&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var array = [
    {pid: 4, id: 6617, name: &quot;a&quot;,subNode:[]},
    {pid: 5, id: 666, name: &quot;a&quot;,subNode:[]},
    {pid: 4, id: 6616, name: &quot;a&quot;,subNode:[]},
    {pid: 6616, id: 66161, name: &quot;a&quot;,subNode:[]},
    {pid: -1, id: 0, name: &quot;a&quot;,subNode:[]},
    {pid: 0, id: 4, name: &quot;a&quot;,subNode:[]},
    {pid: 0, id: 5, name: &quot;a&quot;,subNode:[]},
    {pid: 4, id: 10, name: &quot;a&quot;,subNode:[]},
    {pid: 10, id: 451, name: &quot;a&quot;,subNode:[]},
    {pid: 0, id: 98, name: &quot;a&quot;,subNode:[]},
    {pid: 98, id: 23, name: &quot;a&quot;,subNode:[]},
    {pid: 98, id: 523, name: &quot;a&quot;,subNode:[]}
];

var toTree = function(tarArray) {
	let obj = {};
	tarArray.map((item,index) =&amp;gt; {
		obj[item.id] = item;
	});
	let newArr = [];
	for(let i = 0;i &amp;lt; tarArray.length; i++){
		var item = tarArray[i];
		var parent = obj[item.pid];
		if (parent) {
			if(parent.subNode) {
				parent.subNode.push(item);
			} else {
				parent.subNode = []
				parent.subNode.push(item);
			}
		} else {
			newArr.push(item);
		}
	}
	console.log(newArr);

    return newArr;
}

toTree(array);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;手写题没做出来，挂&lt;/p&gt;
&lt;h2&gt;网易&lt;/h2&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;围绕着简历上讲了很多&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;二面&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;围绕这简历上讲了讲项目&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;三面&lt;/h3&gt;
&lt;p&gt;简单介绍下自己&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;你在智慧展业项目中碰到的挑战是什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个性能优化，之前没有测试过吗？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在智慧展业项目上，以你现在的角度来看，有哪些改进点？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你认为网易严选这款产品，在前端这侧需关注什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户体验&lt;/li&gt;
&lt;li&gt;安全保障&lt;/li&gt;
&lt;li&gt;界面友好&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你入职网易严选团队，最想做哪部分的工作？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工程化方向&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你在以往的工作经历中，有什么主动去解决的一些内容吗？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你对自己未来规划是怎样的？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;能举个例子，展开讲一下，你认为前端技术专家应该是怎样的？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你觉得自己的优点是什么？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你有什么想问我的？&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;字节&lt;/h2&gt;
&lt;p&gt;线索中台部&lt;/p&gt;
&lt;h3&gt;一面&lt;/h3&gt;
&lt;p&gt;自我介绍&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;介绍下如何搞工程化&lt;/li&gt;
&lt;li&gt;session 和 cookie 的区别
&lt;ul&gt;
&lt;li&gt;session 用于在服务端记录用户信息的唯一标识&lt;/li&gt;
&lt;li&gt;cookie 存储在客户端，由于 http 请求是无状态协议，所以通过 cookie 来进行上报用户唯一标识&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ts 有用过吗？
你对 ts 怎么理解？&lt;/li&gt;
&lt;li&gt;redux 怎么理解？&lt;/li&gt;
&lt;li&gt;mobx 怎么理解？&lt;/li&gt;
&lt;li&gt;Taro 源码如何实现的？
Taro 2.x 和 Taro 3 最大区别是什么？&lt;/li&gt;
&lt;li&gt;场景题 1&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const [input, setInput] = useState(&apos;&apos;);
const setValue = useCallback(() =&amp;gt; {
  console.log(input);
}, input);

render() {
    &amp;lt;&amp;gt;
        &amp;lt;div&amp;gt;input&amp;lt;/div&amp;gt;
        &amp;lt;button onClick={setValue}&amp;gt;&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
}

问1：input 输出什么
问2：如何更改，使得 input 获取最新的值
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;场景题 2
根据伪代码，实现一个观察者模式&lt;/li&gt;
&lt;li&gt;算法题，返回只在字符串出现一次的字符
例：&apos;abcdert&apos;，输出 a&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二面&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Mobx 是如何实现数据监听的？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vite 原理是什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vite 一个页面 1000 多个依赖，如何优化？（通过 esbuild 预构建依赖）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Webpack 如何实现 HMR 的？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前端如何实现在某个文件更新之后，打印 console.log（通过 module.hot 来监听文件的更新）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Webpack 原理是什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Webpack 如何实现 A 打包 B 的？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Taro 原理是什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;小程序没有删除、更新、添加，哪么 Taro3.x 是如何自己实现的？（小程序的框架支持自定义组件，我是不是可以做一个通用的自定义组件，让它根据传入的参数不同，变成不同的小程序内置组件。而且自定义组件还支持在自己的模板中引用自己，那么我只需要一个这个通用组件，然后从逻辑层用代码去控制当前组件应该渲染成什么内置组件，再根据它是否有子节点去递归引用自己进行渲染就可以了）&lt;/li&gt;
&lt;li&gt;如何自己实现一个 Taro&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;微信小程序原理是什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为什么微信小程序不支持删除、更新、添加 DOM（因为双线程模型）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;前端如何实现大文件上传？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Blob.prototype.slice 生成切片，上传到服务端，还要记住顺序，这里通过客户前传入切片的 hash ，给到后端，做合并处理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;断点续传，提到断点续传，前端是怎么实现断点续传的？
断点续传的原理在于前端/服务端需要记住已上传的切片，这样下次上传就可以跳过之前已上传的部分，有两种方案实现记忆的功能
前端使用 localStorage 记录已上传的切片 hash
服务端保存已上传的切片 hash，前端每次上传前向服务端获取已上传的切片&lt;/p&gt;
&lt;p&gt;第一种是前端的解决方案，第二种是服务端，而前端方案有一个缺陷，如果换了个浏览器就失去了记忆的效果，所以这里选取后者&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ts 有了解过吗？
没有&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;前端如何实现截图？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我的回答是 canvas 和 svg，但面试官说针对 HTML 来实现&lt;/li&gt;
&lt;li&gt;还有一种方案 headless chrome 实现&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;css position absolute 是对谁定位的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;position fixed 是对谁定位的，怎样能让它不针对窗口定位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;css 如何实现一个正方形盒子（随父元素）自适应&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过 &lt;code&gt;width&lt;/code&gt; 百分比和宽 &lt;code&gt;vw&lt;/code&gt; 百分比 来实现（例子：width：）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;编程题
实现一个 retry 函数，实现 &lt;code&gt;fn&lt;/code&gt; 请求失败，针对 &lt;code&gt;count&lt;/code&gt; 重试，对于重试的 retry，超过 timeout 请求时间，则抛错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function retry(fn, count, timeout) {};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;其他，聊了聊项目上的东西&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你有什么想问我的吗？
问：对于本次面试，你有什么建议给我？
答：面试评价是中级前端工程师，比初级厉害一点，需要深入研究技术，研究的更细，对于很多问题可以深入&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>前端面试</category><author>江辰</author></item><item><title>对数字孪生的理解</title><link>https://github.com/posts/%E5%AF%B9%E6%95%B0%E5%AD%97%E5%AD%AA%E7%94%9F%E7%9A%84%E7%90%86%E8%A7%A3/</link><guid isPermaLink="true">https://github.com/posts/%E5%AF%B9%E6%95%B0%E5%AD%97%E5%AD%AA%E7%94%9F%E7%9A%84%E7%90%86%E8%A7%A3/</guid><pubDate>Mon, 09 May 2022 14:51:50 GMT</pubDate><content:encoded>&lt;h1&gt;数字孪生&lt;/h1&gt;
&lt;h2&gt;来源&lt;/h2&gt;
&lt;p&gt;1991 年， David Gelernter 出版了《 镜像世界》，首次提出数字孪生技术的想法。 但是，Michael Grieves 博士（后来在密歇根大学任教）于 2002 年第一次将数字孪生概念应用于制造业，正式宣布数字孪生软件概念的诞生。 最终，美国宇航局的 John Vickers 在 2010 年引入了一个新名词 &lt;strong&gt;“数字孪生”&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但其实，在更早之前，已经出现了利用数字孪生研究物理对象的核心理念。 实际上，可以说是美国宇航局在 1960 年代太空探索任务中就率先使用了数字孪生技术，每一艘宇宙飞船都有一个完全一样的复刻版本留在地球上，供美国宇航局人员研究和模拟，以便为机组人员提供服务。&lt;/p&gt;
&lt;h2&gt;含义&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;数字孪生是充分利用物理模型、传感器、运行历史等数据，在虚拟空间中完成映射，从而反映相对应的现实物理设备的&lt;strong&gt;生命周期&lt;/strong&gt;过程。数字孪生是一种&lt;strong&gt;超越现实&lt;/strong&gt;的概念。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小结：在当今的世界，我认为已经可以实现&lt;/p&gt;
&lt;h2&gt;工作原理&lt;/h2&gt;
&lt;p&gt;数字孪生是一个&lt;strong&gt;虚拟模型&lt;/strong&gt;，用于准确地反映物理对象。 所研究的对象（例如风力涡轮、工厂、钻井平台、飞机等）会配备各种与重要功能领域相关的传感器。 这些传感器产生与物理对象不同方面的性能相关的数据，如能量输出、温度、天气条件等等。 然后，这些数据将转发到处理系统并应用于数字副本。&lt;/p&gt;
&lt;p&gt;获得这类数据后，虚拟模型就可以用来运行模拟，研究性能问题和生成可能的改进，最终目的是产生有价值的见解 - 而见解又可以反过来应用于原始物理对象。&lt;/p&gt;
&lt;p&gt;小结：利用传感器传输数据模型到处理系统，处理系统产生数字画像，最终带来的价值是&lt;strong&gt;无穷&lt;/strong&gt;的&lt;/p&gt;
&lt;h2&gt;应用场景&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;发电设备设备&lt;/li&gt;
&lt;li&gt;大型物理结构系统&lt;/li&gt;
&lt;li&gt;制造业&lt;/li&gt;
&lt;li&gt;医疗服务&lt;/li&gt;
&lt;li&gt;交通行业&lt;/li&gt;
&lt;li&gt;城市规划&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;未来&lt;/h2&gt;
&lt;p&gt;数字孪生拥有着无可限量的未来！借助下面一段视频，我们可以想象一下&lt;/p&gt;
&lt;p&gt;https://user-images.githubusercontent.com/16217324/167796379-48ac5620-07a8-4595-aa76-bb988a667ffb.mov&lt;/p&gt;
&lt;p&gt;可以延伸到具体场景的思考，硬件上插入传感器，进行动作模组的捕获数据，把数据传入到后台，前台读入数据，在界面上渲染，硬件上的交互进行数据实时性传输，前台可实时看到动作交互，是不是很炫酷，目前该想法在一些建筑公司上，有简单的实现，比如室内大屏可视化，对百叶窗的简单开合等&lt;/p&gt;
&lt;p&gt;可以分位两个维度上来看，一个是&lt;strong&gt;物理世界&lt;/strong&gt;，一个是&lt;strong&gt;数字世界&lt;/strong&gt;，通过传感器把两者之间的壁垒打通，下面也会讲到跟元宇宙之间的差异&lt;/p&gt;
&lt;h1&gt;Web3.0&lt;/h1&gt;
&lt;h2&gt;来源&lt;/h2&gt;
&lt;p&gt;Web3.0 是关于万维网发展的一个概念，主要基于区块链的去中心化、加密货币以及非同质化代币有关系。与区块链有关的 Web3.0 概念是由以太坊联合创始人 &lt;strong&gt;Gavin Wood&lt;/strong&gt; 于 2014 年提出，并于 2021 年受到加密货币爱好者、大型科技公司和风险投资公司的关注。此外有人提出与 Web2.0 有关的 Web3.0。&lt;/p&gt;
&lt;h2&gt;含义&lt;/h2&gt;
&lt;p&gt;这里简单阐述下我对它的理解，Web3.0 时代，万维上的信息可以直接和其他网站进行一个交互，利用区块链的特性，完成信息的读取、写入、防篡改、去中心化，突破了与当今各大互联网企业形成的行业壁垒，加速了信息的发展，用户的数据完全是由自己拥有，举个简单的例子，你在微信上的信息，是存储在腾讯公司上，而不是在你自己手上。所以 Web3.0 是为了解决该问题而诞生的，在 Web3.0 时代，不再有地主，而是所有农民基于“共识”，集体开荒了一块新的土地，在这个土地上，每个人都有产权，根据自己的贡献，比如是否参与了开荒或者耕种，或者是否进行了浇水或者收割，包括最后的存储，买卖，只要在整个生产过程中有贡献，就会根据相应的贡献值获得收益。这是 Web3.0 &lt;strong&gt;理想&lt;/strong&gt;的状态，利好互联网每一位公民，也就是所谓的互联网&lt;strong&gt;乌托邦&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;演进过程&lt;/h2&gt;
&lt;p&gt;Web1.0 -&amp;gt; Web2.0 -&amp;gt; Web3.0&lt;/p&gt;
&lt;h3&gt;Web1.0&lt;/h3&gt;
&lt;p&gt;信息是只读的，意思是用户只能浏览器互联网企业给你提供的内容，你没有互动或修改的能力，所有能力都是平台给你提供，你只能查阅&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/168415590-5c8ddc81-38d7-4025-98a9-0bd71135087d.png&quot; alt=&quot;web1 0&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Web2.0&lt;/h3&gt;
&lt;p&gt;信息可互动，也可以修改，内容创造有用户来产生，内容发放、控制、收益分配，由平台来控制，这就是所谓的平台化&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/168415606-e904ae1d-1a9d-4bf5-90a4-f4dab9b3f9a0.png&quot; alt=&quot;web2 0&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Web3.0&lt;/h3&gt;
&lt;p&gt;去平台化，也就是内容创造、发布、控制、身份、收益，都是用户来决定&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/168415631-3cffd9c2-d0dc-47c4-b440-2174ebfc48a8.png&quot; alt=&quot;web3 0&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;未来&lt;/h2&gt;
&lt;p&gt;透析 Web1.0、Web2.0 的弊端，理解 Web3.0 是为了解决什么问题产生，后面如何发展，个人认为太过&lt;strong&gt;理想化&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;NFT&lt;/h1&gt;
&lt;h2&gt;含义&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;全称为 Non-Fungible Token，指非同质化代币，是用于表示数字资产（包括 jpg 和视频剪辑形式）的唯一加密货币令牌，设计原理是可以买卖。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小结：对 NFT 可以理解为是区块链技术的一类场景应用&lt;/p&gt;
&lt;h2&gt;应用场景&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/167978060-de1bbe45-42c7-4e86-9260-3606a5705c54.png&quot; alt=&quot;wecom-temp-b2df82ec0928335e88a3468c7d30eccf&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;区块链&lt;/h1&gt;
&lt;h2&gt;来源&lt;/h2&gt;
&lt;p&gt;区块链起源于比特币，2008 年 11 月 1 日，一位自称中本聪的人发表了《比特币:一种点对点的电子现金系统》一文，阐述了基于 P2P 网络技术、加密技术、时间戳技术、区块链技术等的电子现金系统的构架理念，这标志着比特币的诞生。两个月后理论步入实践，2009 年 1 月 3 日第一个序号为 0 的创世区块诞生。几天后 2009 年 1 月 9 日出现序号为 1 的区块，并与序号为 0 的创世区块相连接形成了链，标志着区块链的诞生。&lt;/p&gt;
&lt;h2&gt;含义&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;狭义区块链是按照时间顺序，将数据区块以顺序相连的方式组合成的链式数据结构，并以密码学方式保证的不可篡改和不可伪造的分布式账本。广义区块链技术是利用块链式数据结构验证与存储数据，利用分布式节点共识算法生成和更新数据，利用密码学的方式保证数据传输和访问的安全、利用由自动化脚本代码组成的智能合约，编程和操作数据的全新的分布式基础架构与计算范式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小结：Web3.0、NFT、元宇宙，底层逻辑的交易保障，为钱包（身份），提供了可能性&lt;/p&gt;
&lt;h1&gt;元宇宙&lt;/h1&gt;
&lt;h2&gt;来源&lt;/h2&gt;
&lt;p&gt;元宇宙一词诞生于 1992 年的科幻小说《雪崩》，小说描绘了一个庞大的虚拟现实世界，在这里，人们用数字化身来控制，并相互竞争以提高自己的地位，到现在看来，描述的还是超前的未来世界。 关于“元宇宙”，比较认可的思想源头是美国数学家和计算机专家弗诺·文奇教授，在其 1981 年出版的小说《真名实姓》中，创造性地构思了一个通过脑机接口进入并获得感官体验的虚拟世界。&lt;/p&gt;
&lt;p&gt;小结：从最开始 1992 年的科幻小说《雪崩》，到近几年史蒂文·斯皮尔伯格出版的电影**《头号玩家》&lt;strong&gt;，无不阐述一个理念，那就是，通过一个&lt;/strong&gt;接口&lt;strong&gt;进入虚拟世界，最终要解决核心问题之一，如何让人类拥有同等的&lt;/strong&gt;触摸**，甚至是&lt;strong&gt;生理上&lt;/strong&gt;的体验&lt;/p&gt;
&lt;h2&gt;含义&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;元宇宙是与现实世界映射与交互的虚拟世界，具备现实社会体系的数字生活空间。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小结：本质上在现实世界中做的任何事情，理论上在元宇宙都是可以实现&lt;/p&gt;
&lt;h2&gt;应用场景&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;加密数字凭证&lt;/li&gt;
&lt;li&gt;Web3.0&lt;/li&gt;
&lt;li&gt;VR/AR/MR&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里&lt;strong&gt;重点阐述&lt;/strong&gt;下 Web3.0 和元宇宙之间的差异，Web3.0 可以理解为行业表达，元宇宙是下一代互联网的公众表达，两者本质上的差异并不是很大&lt;/p&gt;
&lt;h2&gt;未来&lt;/h2&gt;
&lt;p&gt;https://www.bilibili.com/video/BV1E34y1h7pg&lt;/p&gt;
&lt;p&gt;通过这段视频演示，我们可以想象未来的一切方式，工作，生活，都在虚拟世界完成，简直太美妙了！&lt;/p&gt;
&lt;h1&gt;参考文献&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ibm.com/cn-zh/topics/what-is-a-digital-twin&quot;&gt;什么是数字孪生&lt;/a&gt; IBM&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/341559382&quot;&gt;web3.0 指的是什么？未来将有怎样的发展？&lt;/a&gt; 知乎 律动 BlockBeats&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://baijiahao.baidu.com/s?id=1732510379714532379&amp;amp;wfr=spider&amp;amp;for=pc&quot;&gt;初步认知 Web 3.0、NFT、元宇宙&lt;/a&gt; 和讯网 张栋伟&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>数字孪生</category><author>江辰</author></item><item><title>Tauri 与 Electron</title><link>https://github.com/posts/tauri-%E4%B8%8E-electron/</link><guid isPermaLink="true">https://github.com/posts/tauri-%E4%B8%8E-electron/</guid><pubDate>Tue, 05 Apr 2022 23:36:26 GMT</pubDate><content:encoded>&lt;h1&gt;Tauri&lt;/h1&gt;
&lt;h2&gt;什么是 Tauri ?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Tauri 是一个为所有主流桌面平台构建小型、快速二进制文件的框架。开发人员可以集成任何编译成 HTML、 JS 和 CSS 的前端框架来构建他们的用户界面。应用程序的后端是一个 Rust 二进制文件，具有前端可以与之交互的 API。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;安装方式&lt;/h2&gt;
&lt;h3&gt;Xcode&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ xcode-select --install
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Rust&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ curl --proto &apos;=https&apos; --tlsv1.2 -sSf https://sh.rustup.rs | sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装过程中如果报错 &lt;code&gt;curl: (7) Failed to connect to raw.githubusercontent.com port 443: Connection refused&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;需要开启代理：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 7890 和 789 需要换成你自己的代理端口
$ export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:789
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要确保 Rust 已成功安装，请运行以下命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ rustc --version

$rustc 1.59.0 (9d1b2106e 2022-02-23)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;启动一个新的 Tauri&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ yarn create tauri-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建项目的时候，如果报错 &lt;code&gt;An unexpected error occurred: &quot;https://registry.yarnpkg.com/create-vite: tunneling socket could not be established&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;同样的，需要开启代理，使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ yarn config set proxy http://username:password@host:port
$ yarn config set https-proxy http://username:password@host:port
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按照说明选择您喜欢的 &lt;code&gt;Web&lt;/code&gt; 前端框架，&lt;code&gt;create-tauri-app&lt;/code&gt; 根据您的输入创建模板项目，之后你可以直接去检查 &lt;code&gt;tauri info&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;启动&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ yarn tauri dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;打包&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ yarn tauri build
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Electron&lt;/h1&gt;
&lt;h2&gt;什么是 Electron ?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Electron 框架允许您使用 JavaScript、HTML 和 CSS 编写跨平台的桌面应用程序。它基于 Node.js 和 Chromium，并被 Atom 编辑器和许多其他应用程序使用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;安装方式&lt;/h2&gt;
&lt;h3&gt;创建项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir my-electron-app &amp;amp;&amp;amp; cd my-electron-app
$ yarn init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改 &lt;code&gt;package.json&lt;/code&gt;，&quot;main&quot; 字段为 &lt;code&gt;main.js&lt;/code&gt;，你的 &lt;code&gt;package.json&lt;/code&gt; 应该是如下样子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;my-electron-app&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;Hello World!&quot;,
  &quot;main&quot;: &quot;main.js&quot;,
  &quot;author&quot;: &quot;Jiang Chen&quot;,
  &quot;license&quot;: &quot;MIT&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Electron&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ yarn add --dev electron
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行 &lt;code&gt;Electron&lt;/code&gt;。在 &lt;code&gt;package.json&lt;/code&gt; 配置字段 &lt;code&gt;scripts&lt;/code&gt;，添加 &lt;code&gt;start&lt;/code&gt; 如下命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;electron .&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;根目录新增 main.js&lt;/h3&gt;
&lt;p&gt;代码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.js

// Modules to control application life and create native browser window
const { app, BrowserWindow } = require(&apos;electron&apos;)
const path = require(&apos;path&apos;)

const createWindow = () =&amp;gt; {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, &apos;preload.js&apos;)
    }
  })

  // and load the index.html of the app.
  mainWindow.loadFile(&apos;index.html&apos;)

  // Open the DevTools.
  // mainWindow.webContents.openDevTools()
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() =&amp;gt; {
  createWindow()

  app.on(&apos;activate&apos;, () =&amp;gt; {
    // On macOS it&apos;s common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// Quit when all windows are closed, except on macOS. There, it&apos;s common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on(&apos;window-all-closed&apos;, () =&amp;gt; {
  if (process.platform !== &apos;darwin&apos;) app.quit()
})

// In this file you can include the rest of your app&apos;s specific main process
// code. You can also put them in separate files and require them here.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;根目录新增 index.html&lt;/h3&gt;
&lt;p&gt;代码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!--index.html--&amp;gt;

&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --&amp;gt;
    &amp;lt;meta http-equiv=&quot;Content-Security-Policy&quot; content=&quot;default-src &apos;self&apos;; script-src &apos;self&apos;&quot;&amp;gt;
    &amp;lt;meta http-equiv=&quot;X-Content-Security-Policy&quot; content=&quot;default-src &apos;self&apos;; script-src &apos;self&apos;&quot;&amp;gt;
    &amp;lt;title&amp;gt;Hello World!&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Hello World!&amp;lt;/h1&amp;gt;
    We are using Node.js &amp;lt;span id=&quot;node-version&quot;&amp;gt;&amp;lt;/span&amp;gt;,
    Chromium &amp;lt;span id=&quot;chrome-version&quot;&amp;gt;&amp;lt;/span&amp;gt;,
    and Electron &amp;lt;span id=&quot;electron-version&quot;&amp;gt;&amp;lt;/span&amp;gt;.

    &amp;lt;!-- You can also require other files to run in this process --&amp;gt;
    &amp;lt;script src=&quot;./renderer.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;根目录新增 renderer.js&lt;/h3&gt;
&lt;p&gt;代码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;window.addEventListener(&apos;DOMContentLoaded&apos;, () =&amp;gt; {
  const replaceText = (selector, text) =&amp;gt; {
    const element = document.getElementById(selector)
    if (element) element.innerText = text
  }

  for (const type of [&apos;chrome&apos;, &apos;node&apos;, &apos;electron&apos;]) {
    replaceText(`${type}-version`, process.versions[type])
  }
})

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;启动&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ npm run start
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;打包&lt;/h3&gt;
&lt;h4&gt;添加 Electron Forge 作为应用程序的开发依赖项&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;$ cnpm install --dev @electron-forge/cli
$ npx electron-forge import
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;make 使用 Forge 的命令创建一个打包&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;$ yarn run make
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;两者区别&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;大小对比&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Electron 官方介绍有提到，它基于 Node.js 和 Chromium，很明显的问题，包太大（62.5mb）使用 Chromium 的决定，也是解决 WebView 暂时无法解决的一些兼容性问题&lt;/li&gt;
&lt;li&gt;Tauri，前端是通过系统的 WebView2，后端使用 Rust，包很小（4.32MB）&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;官方文档&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Electron 官方文档和社区迭代，目前比较稳定，发布了多个版本，可以稳定在生产使用&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tauri 作为一款新型桌面端开发框架，1.0.0 版本暂时未出，可以持续关注，尝试做些小工具&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;总结&lt;/h1&gt;
&lt;p&gt;Tauri 作为一款新型桌面端开发框架，踩在了 Rust 和 WebView2 的两位巨人的肩膀上，可以说是时代的产物，Rust 近几年非常受欢迎，Deno 采用 Rust，微软拥抱 Rust 等，微软又开始推广 WebView2，天然的优势&lt;/p&gt;
&lt;h1&gt;结语&lt;/h1&gt;
&lt;p&gt;如果你觉得这篇内容对你挺有启发，别忘记点赞 + 关注&lt;/p&gt;
&lt;p&gt;欢迎添加我的个人微信：Jiang9684，备注昵称 + [岗位或专业]，实例 Faker-前端，通过更快噢，一起交流前端技术&lt;/p&gt;
</content:encoded><category>Tarui</category><category>Electron</category><author>江辰</author></item><item><title>【技术需求】Element 升级 2.x</title><link>https://github.com/posts/%E6%8A%80%E6%9C%AF%E9%9C%80%E6%B1%82element-%E5%8D%87%E7%BA%A72/</link><guid isPermaLink="true">https://github.com/posts/%E6%8A%80%E6%9C%AF%E9%9C%80%E6%B1%82element-%E5%8D%87%E7%BA%A72/</guid><pubDate>Mon, 28 Mar 2022 16:19:09 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;link-center&lt;/code&gt; 项目的技术栈比较老旧，未来可以预测到许多业务需要在最新的 &lt;code&gt;Element&lt;/code&gt; 的基础上进行开发，靠原生 &lt;code&gt;Vue&lt;/code&gt; 手写实现封装花费时间较多且稳定性不是很好，比如实现一个日期组件&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如今大多数开源的 &lt;code&gt;Vue&lt;/code&gt; 应用，如 &lt;code&gt;vue-easytable &lt;/code&gt;、&lt;code&gt;vue-treeselect&lt;/code&gt; 等都依赖于 &lt;code&gt;Vue 2.x&lt;/code&gt; 以上的版本，&lt;code&gt;Vue 2.x&lt;/code&gt; 以下版本显然是没有办法兼容这些组件的&lt;/li&gt;
&lt;li&gt;目前使用的版本也缺乏一些新的特性，升级之后，也是为了更好的适应&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;目标&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;从 &lt;code&gt;1.4.13&lt;/code&gt; 平稳过渡到 &lt;code&gt;Element 2.x&lt;/code&gt; 以上&lt;/li&gt;
&lt;li&gt;兼容现有样式和组件&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;升级&lt;/h2&gt;
&lt;p&gt;移除 &lt;code&gt;package.json&lt;/code&gt; 中的 &lt;code&gt;element-ui&lt;/code&gt; 版本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// package.json
element-ui: &apos;2.15.7&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装最新版本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm install element-ui @2.15.7 -S
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;改动点&lt;/h2&gt;
&lt;h3&gt;引入位置替换&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;样式名称替换&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;在 `src/index.js` 中修改新增的 `theme-chalk` 主题，将 `import &apos;element-ui/lib/theme-default/index.css` 替换为 `
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;import &apos;element-ui/lib/theme-chalk/index.css`&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;.babelrc&lt;/p&gt;
&lt;p&gt;把 &lt;code&gt;theme-default&lt;/code&gt; 替换为 &lt;code&gt;theme-chalk&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;引入优先级问题&lt;/h3&gt;
&lt;p&gt;有的组件样式需要进行定制覆盖，于是就在组件里面用 &lt;code&gt;css scoped&lt;/code&gt; 进行了同类名的样式替换，这样在开发环境下效果是符合预期的，但是打包编译后，优先级就变了。需要在 &lt;code&gt;src/index.js&lt;/code&gt; 修改引入文件路径顺序的问题&lt;/p&gt;
&lt;p&gt;修改前：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Vue from &apos;vue&apos;
import App from &apos;./App.vue&apos;
import ElementUI from &apos;element-ui&apos;
import &apos;element-ui/lib/theme-chalk/index.css&apos;
Vue.use(ElementUI)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改后：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Vue from &apos;vue&apos;
import ElementUI from &apos;element-ui&apos;
import &apos;element-ui/lib/theme-chalk/index.css&apos;
import App from &apos;./App.vue&apos;
Vue.use(ElementUI)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;页面全局报错修正&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;编译报错，&lt;code&gt;Vue packages version mismatch&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个错误是因为在 &lt;code&gt;element ui&lt;/code&gt; 版本升级过后，对应的 &lt;code&gt;Vue&lt;/code&gt; 版本 及 &lt;code&gt;vue-template-compiler&lt;/code&gt; 的版本未升级的原因&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm install vue-template-compile @2.6.14 -S
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;界面渲染失败，控制台报错：&lt;code&gt;_v&lt;/code&gt; 属性不存在&lt;/p&gt;
&lt;p&gt;这里需要注意 &lt;code&gt;Element 2.x&lt;/code&gt; 最低兼容 &lt;code&gt;Vue 2.5.x&lt;/code&gt;，因此升级 Vue 到 2.5 以上&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm uninstall vue
$ npm install vue@2.6.12
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;编译报错, &lt;code&gt;TypeError: VueLoaderPlugin is not a constructor&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// webpack.config.js
const { VueLoaderPlugin } = require(&apos;vue-loader&apos;)

module.exports = {
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: &apos;vue-loader&apos;
            }
        ]
    },
    plugins: [
        // 请确保引入这个插件！
        new VueLoaderPlugin()
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;vue-loader&lt;/code&gt; 也需要升级，以便支持 &lt;code&gt;Element 2.x&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;组件属性变更&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;项目中的 icon 图标：&lt;/p&gt;
&lt;p&gt;升级后很多 &lt;code&gt;icon&lt;/code&gt; 名称发生了变化导致无法显示，需要去文档查看最新的 icon 名称&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Button：&lt;/p&gt;
&lt;p&gt;为了方便使用第三方图标，&lt;code&gt;Button&lt;/code&gt; 的 &lt;code&gt;icon&lt;/code&gt; 属性现在需要传入完整的图标类名&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Checkbox：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Checkbox&lt;/code&gt;的 &lt;code&gt;change&lt;/code&gt; 事件中，参数由 &lt;code&gt;event&lt;/code&gt; 变为了 &lt;code&gt;value&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Input：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;移除了 &lt;code&gt;input&lt;/code&gt; 的 &lt;code&gt;icon&lt;/code&gt; 属性。现在通过 &lt;code&gt;suffix-icon&lt;/code&gt; 或者&lt;code&gt;suffix&lt;/code&gt; 具名插槽来加入尾部图标&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;移除 &lt;code&gt;on-icon-click&lt;/code&gt; 和 &lt;code&gt;click&lt;/code&gt; 事件，现在如果需要为输入框中的图标添加点击事件，请以具名 &lt;code&gt;slot&lt;/code&gt; 的方式添加图标&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;change&lt;/code&gt; 事件现在仅在输入框失去焦点或用户按下回车时触发，与原生 &lt;code&gt;input&lt;/code&gt; 元素一致。如果需要实时响应用户的输入，可以使用 &lt;code&gt;input&lt;/code&gt; 事件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;盒模型从 &lt;code&gt;block&lt;/code&gt; 修改为 &lt;code&gt;inline-block&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Input&lt;/code&gt; 的 &lt;code&gt;id&lt;/code&gt; 属性被传递到了最底层的 &lt;code&gt;input&lt;/code&gt; 元素，需要关注有 &lt;code&gt;id&lt;/code&gt; 的 &lt;code&gt;el-input&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;盒模型从 &lt;code&gt;block&lt;/code&gt; 修改为 &lt;code&gt;inline-block&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Select&lt;/code&gt;，值为对象类型时，需要提供一个 &lt;code&gt;value-key&lt;/code&gt; 作为唯一性标识&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Select&lt;/code&gt;，过滤情况下，&lt;code&gt;placeholder&lt;/code&gt; 为选中选项的 &lt;code&gt;label&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switch：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;由于 &lt;code&gt;on-*&lt;/code&gt; 属性在 JSX 中会被识别为事件，导致 &lt;code&gt;Switch&lt;/code&gt; 所有 &lt;code&gt;on-*&lt;/code&gt; 属性在 JSX 中无法正常工作，所以 &lt;code&gt;on-*&lt;/code&gt; 属性更名为 &lt;code&gt;active-&lt;/code&gt;，对应地，&lt;code&gt;off-&lt;/code&gt; 属性更名为 &lt;code&gt;inactive-*&lt;/code&gt;。受到影响的属性有：&lt;code&gt;on-icon-class、off-icon-class、on-text、off-text、on-color、off-color、on-value、off-value&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;active-text&lt;/code&gt; 和 &lt;code&gt;inactive-text&lt;/code&gt; 属性不再有默认值&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TimePicker：&lt;/p&gt;
&lt;p&gt;点击清空按钮时，&lt;code&gt;change&lt;/code&gt; 中的参数由 &lt;code&gt;’’&lt;/code&gt; 变为 &lt;code&gt;null&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DatePicker：&lt;/p&gt;
&lt;p&gt;同 &lt;code&gt;timepicker&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DateTimePicker：&lt;/p&gt;
&lt;p&gt;同 &lt;code&gt;timepicker&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upload：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Upload&lt;/code&gt; 重构升级，&lt;code&gt;default-file-list&lt;/code&gt; 属性更名为 &lt;code&gt;file-list&lt;/code&gt;, &lt;code&gt;show-upload-list&lt;/code&gt; 属性更名为 &lt;code&gt;show-file-list&lt;/code&gt;，&lt;code&gt;thumbnail-mode&lt;/code&gt; 属性被移除&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Form 组件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Form validateField()&lt;/code&gt; 方法回调的参数更新&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Form&lt;/code&gt; 移除输入框的成功状态&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Table 组件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;移除通过 &lt;code&gt;inline-template&lt;/code&gt; 自定义列模板的功能&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;sort-method&lt;/code&gt; 现在和 &lt;code&gt;Array.sort&lt;/code&gt; 保持一致的逻辑，要求返回一个数字&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将 &lt;code&gt;append slot&lt;/code&gt; 移至 &lt;code&gt;tbody&lt;/code&gt; 元素以外，以保证其只被渲染一次&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;expand&lt;/code&gt; 事件更名为 &lt;code&gt;expand-change&lt;/code&gt;，以保证 &lt;code&gt;API&lt;/code&gt; 的命名一致性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;row-class-name&lt;/code&gt; 和 &lt;code&gt;row-style&lt;/code&gt; 的函数参数改为对象，以保证 &lt;code&gt;API&lt;/code&gt; 的一致性&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tag：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;type&lt;/code&gt; 属性现在支持 &lt;code&gt;success、info、warning 和 danger&lt;/code&gt; 四个值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pagination：&lt;/p&gt;
&lt;p&gt;表单组件的 &lt;code&gt;change&lt;/code&gt; 事件和 &lt;code&gt;Pagination&lt;/code&gt; 的 &lt;code&gt;current-change&lt;/code&gt; 事件现在仅响应用户交互&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Loading：&lt;/p&gt;
&lt;p&gt;非全屏 &lt;code&gt;Loading&lt;/code&gt; 遮罩层的 &lt;code&gt;z-index&lt;/code&gt; 修改为 2000；
全屏 &lt;code&gt;Loading&lt;/code&gt; 遮罩层的 &lt;code&gt;z-index&lt;/code&gt; 值会随页面上的弹出组件动态更新&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tabs：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;盒模型从 &lt;code&gt;inline-block&lt;/code&gt; 修改为 &lt;code&gt;block&lt;/code&gt;，&lt;code&gt;Tab-Pane&lt;/code&gt; 移除 &lt;code&gt;label-content&lt;/code&gt; 属性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Tabs&lt;/code&gt; 现在内部不再维护 &lt;code&gt;tab&lt;/code&gt; 实例，需要在外部通过相关事件去处理&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dropdown：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;menu-align&lt;/code&gt; 属性变更为 &lt;code&gt;placement&lt;/code&gt;，增加更多方位属性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;show-timeout&lt;/code&gt; 和 &lt;code&gt;hide-timeout&lt;/code&gt; 属性现在仅在 &lt;code&gt;trigger&lt;/code&gt; 为 &lt;code&gt;hover&lt;/code&gt; 时生效&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dialog：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Dialog&lt;/code&gt; 的遮罩层现在默认插入至 &lt;code&gt;body&lt;/code&gt; 元素上&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;移除 &lt;code&gt;size&lt;/code&gt; 属性，现在 &lt;code&gt;Dialog&lt;/code&gt; 的尺寸由 &lt;code&gt;width&lt;/code&gt; 和 &lt;code&gt;fullscreen&lt;/code&gt; 控制&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;移除通过 &lt;code&gt;v-model&lt;/code&gt; 控制 &lt;code&gt;Dialog&lt;/code&gt; 显示和隐藏的功能&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tooltip：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;重构 &lt;code&gt;Tooltip&lt;/code&gt;，不再生成额外的 &lt;code&gt;HTML&lt;/code&gt; 标签，确保被 &lt;code&gt;tooltip&lt;/code&gt; 包裹的组件的结构不变&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;el-tooltip&lt;/code&gt; 标签中，子元素如果有 &lt;code&gt;v-if&lt;/code&gt;，则需要为 &lt;code&gt; el-tooltip&lt;/code&gt; 也加上 &lt;code&gt;v-if&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;非兼容性更新带来的警告&lt;/h3&gt;
&lt;p&gt;虽然项目能跑起来了，但是控制台还会有很多警告，一部分是来自 &lt;code&gt;Vue&lt;/code&gt; 本身的警告，还有一些是 &lt;code&gt;Element&lt;/code&gt; 的非兼容属性或者即将废弃的属性所带来的警告，需要我们对这些警告进行修改。常见的有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;v-for&lt;/code&gt; 循环渲染组件时，必须为组件绑定 &lt;code&gt;key&lt;/code&gt; 值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v-for&lt;/code&gt; 绑定的 &lt;code&gt;key&lt;/code&gt; 值中，存在同样的 &lt;code&gt;key&lt;/code&gt; 值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Element&lt;/code&gt; 中的废弃属性带来的警告，比如：&lt;code&gt;input&lt;/code&gt; 中的 &lt;code&gt;icon&lt;/code&gt;改为 &lt;code&gt;suffix-icon&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;测试事项&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;建议把测试时间尽量拉长一点，这样子才能发现项目中潜在的问题&lt;/li&gt;
&lt;li&gt;全量测试&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>技术需求</category><author>江辰</author></item><item><title>React Router v6 探索</title><link>https://github.com/posts/react-router-v6-%E6%8E%A2%E7%B4%A2/</link><guid isPermaLink="true">https://github.com/posts/react-router-v6-%E6%8E%A2%E7%B4%A2/</guid><pubDate>Mon, 28 Mar 2022 01:11:58 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;没事翻了翻 &lt;code&gt;React Router&lt;/code&gt; 的文档，发现已推到了 &lt;code&gt;v6.2.2&lt;/code&gt; 版本，这个版本做了很大的改动，让我们一起看看吧&lt;/p&gt;
&lt;h2&gt;为什么推出 v6&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;推出 &lt;code&gt;v6&lt;/code&gt; 的最大原因是 &lt;code&gt;React Hooks&lt;/code&gt; 的出现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;v6&lt;/code&gt; 写的代码要比 &lt;code&gt;v5&lt;/code&gt; 代码更加紧凑和优雅&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们通过代码来感受下，这是 &lt;code&gt;v6&lt;/code&gt; 写的伪代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Routes, Route, useParams } from &quot;react-router-dom&quot;;

function App() {
    return (
        &amp;lt;Routes&amp;gt;
            &amp;lt;Route path=&quot;blog/:id&quot; element={&amp;lt;Head /&amp;gt;} /&amp;gt;
        &amp;lt;/Routes&amp;gt;
  );
}

function Head() {
    let { id } = useParams();
    return (
        &amp;lt;&amp;gt;
            &amp;lt;Footer /&amp;gt;
        &amp;lt;/&amp;gt;
    );
}

function Footer() {
    let { id } = useParams();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是 &lt;code&gt;v5&lt;/code&gt; 写的伪代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import * as React from &quot;react&quot;;
import { Switch, Route } from &quot;react-router-dom&quot;;

class App extends React.Component {
    render() {
        return (
            &amp;lt;Switch&amp;gt;
                &amp;lt;Route
                    path=&quot;head/:id&quot;
                    render={({ match }) =&amp;gt; (
                        &amp;lt;Head id={match.params.id} /&amp;gt;
                    )}
                /&amp;gt;
            &amp;lt;/Switch&amp;gt;
        );
    }
}

class Head extends React.Component {
    render() {
        return (
            &amp;lt;&amp;gt;
                &amp;lt;Footer id={this.props.id} /&amp;gt;
            &amp;lt;/&amp;gt;
        );
    }
}

class Footer extends React.Component {
    render() {
        return (
            &amp;lt;&amp;gt;
                &amp;lt;ButtonComponent id={this.props.id} /&amp;gt;
            &amp;lt;/&amp;gt;
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个例子表明&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Hooks&lt;/code&gt; 消除了使用 &lt;code&gt;&amp;lt;Route render&amp;gt;&lt;/code&gt; 访问路由器内部状态的需要&lt;/li&gt;
&lt;li&gt;手动传递 &lt;code&gt;props&lt;/code&gt; 将该状态传播到子组件的需要&lt;/li&gt;
&lt;li&gt;应用程序包体积更小&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/fefec7d2-d2f3-48eb-87bf-3c0ee84d29e1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;增加了哪些特性？&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;Switch&amp;gt;&lt;/code&gt; 升级为 &lt;code&gt;&amp;lt;Routes&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Routes 内的所有 &amp;lt;Route&amp;gt; 和 &amp;lt;Link&amp;gt; 是相对的。这使得 &amp;lt;Route path&amp;gt; 和 &amp;lt;Link to&amp;gt; 中的代码更精简、更可预测&lt;/li&gt;
&lt;li&gt;路由是根据最佳匹配，而不是按顺序遍历，这避免了由于路由不可达而导致的错误&lt;/li&gt;
&lt;li&gt;路由可以嵌套在一个地方，而不是分散在不同的组件中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;新钩子 &lt;code&gt;useRoutes&lt;/code&gt; 代替 &lt;code&gt;react-router-config&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;之前：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { lazy } from &apos;react&apos;;
import PrivateRoute from &apos;@components/PrivateRoute/index&apos;;

const Dashboard = lazy(() =&amp;gt; import(&apos;@pages/dashboard/index&apos;));
const Abount = lazy(() =&amp;gt; import(&apos;@pages/abount/index&apos;));

const routes = [
    {
        path: &apos;/home&apos;,
        component: Dashboard
    },
    {
        path: &apos;/about&apos;,
        component: Abount
    },
];

const RouteWithSubRoutes = route =&amp;gt; (
    &amp;lt;PrivateRoute path={route.path} component={route.component} routes={route.routes} /&amp;gt;
);

const routeConfig = routes.map((route, i) =&amp;gt; &amp;lt;RouteWithSubRoutes key={i} {...route} /&amp;gt;);
export default routeConfig;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
function App() {
    let element = useRoutes([
        { path: &apos;/&apos;, element: &amp;lt;Home /&amp;gt; },
        {
            path: &apos;users&apos;,
            element: &amp;lt;Users /&amp;gt;,
            children: [
                { path: &apos;/&apos;, element: &amp;lt;UsersIndex /&amp;gt; },
                { path: &apos;:id&apos;, element: &amp;lt;UserProfile /&amp;gt; },
                { path: &apos;me&apos;, element: &amp;lt;OwnUserProfile /&amp;gt; },
            ]
        }
    ]);
    return element;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就感觉更优雅一些&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用 &lt;code&gt;useNavigate&lt;/code&gt; 代替 &lt;code&gt;useHistory&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;之前&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useHistory } from &quot;react-router-dom&quot;;

function App() {
    let history = useHistory();
    function handleClick() {
        history.push(&quot;/home&quot;);
    }
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;button onClick={handleClick}&amp;gt;go home&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useNavigate } from &quot;react-router-dom&quot;;

function App() {
    let navigate = useNavigate();
    function handleClick() {
        navigate(&quot;/home&quot;);
    }
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;button onClick={handleClick}&amp;gt;go home&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个变化不是很大&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Route 的变化&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;4.1 &lt;code&gt;&amp;lt;Route exact&amp;gt;&lt;/code&gt; 移除，使用 &lt;code&gt;/*&lt;/code&gt; 代替&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Route path=&quot;/*&quot; element={&amp;lt;Home /&amp;gt;} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;`&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4.2 &lt;code&gt;&amp;lt;Route children&amp;gt;&lt;/code&gt; 使用 &lt;code&gt;&amp;lt;Route element&amp;gt;&lt;/code&gt; 代替&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import Profile from &apos;./Profile&apos;;

// v5
&amp;lt;Route path=&quot;:userId&quot; component={&amp;lt;Profile /&amp;gt;} /&amp;gt;

// v6
&amp;lt;Route path=&quot;:userId&quot; element={&amp;lt;Profile /&amp;gt;} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;4.3 Outlet
我们使用一个 &lt;code&gt;&amp;lt;Outlet&amp;gt;&lt;/code&gt; 元素作为占位符。在 &lt;code&gt;&amp;lt;Outlet&amp;gt;&lt;/code&gt; 这种情况下，&lt;code&gt;Users&lt;/code&gt; 组件如何呈现其子路由。因此，将根据当前位置 &lt;code&gt;&amp;lt;Outlet&amp;gt;&lt;/code&gt; 呈现一个 &lt;code&gt;&amp;lt;UserProfile&amp;gt;&lt;/code&gt; 或&lt;code&gt;&amp;lt;OwnUserProfile&amp;gt;&lt;/code&gt; 元素&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.4&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import {
	BrowserRouter,
	Routes,
	Route,
	Link,
	Outlet
} from &apos;react-router-dom&apos;;

function App() {
  	return (
		&amp;lt;BrowserRouter&amp;gt;
			&amp;lt;Routes&amp;gt;
				&amp;lt;Route path=&quot;/&quot; element={&amp;lt;Home /&amp;gt;} /&amp;gt;
				&amp;lt;Route path=&quot;users&quot; element={&amp;lt;Users /&amp;gt;}&amp;gt;
				&amp;lt;Route path=&quot;/&quot; element={&amp;lt;UsersIndex /&amp;gt;} /&amp;gt;
				&amp;lt;Route path=&quot;:id&quot; element={&amp;lt;UserProfile /&amp;gt;} /&amp;gt;
				&amp;lt;Route path=&quot;me&quot; element={&amp;lt;OwnUserProfile /&amp;gt;} /&amp;gt;
				&amp;lt;/Route&amp;gt;
			&amp;lt;/Routes&amp;gt;
		&amp;lt;/BrowserRouter&amp;gt;
  	);
}

function Users() {
  	return (
    	&amp;lt;div&amp;gt;
      		&amp;lt;nav&amp;gt;
				&amp;lt;Link to=&quot;me&quot;&amp;gt;My Profile&amp;lt;/Link&amp;gt;
			&amp;lt;/nav&amp;gt;
			&amp;lt;Outlet /&amp;gt;
    	&amp;lt;/div&amp;gt;
  	);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;体验 v6&lt;/h2&gt;
&lt;p&gt;这里我们使用 &lt;code&gt;create-react-app&lt;/code&gt; 来创建项目，安装好之后，进入项目，安装 &lt;code&gt;react-router-dom@6&lt;/code&gt; 依赖&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npx create-react-app react-app
$ cd react-app
$ npm install react-router-dom@6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/index.js&lt;/code&gt; 在编辑器中打开，&lt;code&gt;BrowserRouter&lt;/code&gt; 从顶部附近导入 &lt;code&gt;react-router-dom&lt;/code&gt; 并将 &lt;code&gt;&amp;lt;APP&amp;gt;&lt;/code&gt; 包装在 &lt;code&gt;&amp;lt;BrowserRouter&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/index.js
import * as React from &quot;react&quot;;
import * as ReactDOM from &quot;react-dom&quot;;
import { BrowserRouter } from &quot;react-router-dom&quot;;
import &quot;./index.css&quot;;
import App from &quot;./App&quot;;
import * as serviceWorker from &quot;./serviceWorker&quot;;

ReactDOM.render(
    &amp;lt;BrowserRouter&amp;gt;
        &amp;lt;App /&amp;gt;
    &amp;lt;/BrowserRouter&amp;gt;,
    document.getElementById(&quot;root&quot;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开 &lt;code&gt;src/App.js&lt;/code&gt; 并用一些路由替换默认标记&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// App.js
import * as React from &quot;react&quot;;
import { Routes, Route, Link } from &quot;react-router-dom&quot;;
import &quot;./App.css&quot;;

function App() {
    return (
        &amp;lt;div className=&quot;App&quot;&amp;gt;
            &amp;lt;h1&amp;gt;Welcome to React Router!&amp;lt;/h1&amp;gt;
                &amp;lt;Routes&amp;gt;
                    &amp;lt;Route path=&quot;/&quot; element={&amp;lt;Home /&amp;gt;} /&amp;gt;
                    &amp;lt;Route path=&quot;about&quot; element={&amp;lt;About /&amp;gt;} /&amp;gt;
                &amp;lt;/Routes&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，仍在 &lt;code&gt;src/App.js&lt;/code&gt;，创建你的路由组件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/App.js
function Home() {
    return (
        &amp;lt;&amp;gt;
            &amp;lt;main&amp;gt;
                &amp;lt;h2&amp;gt;Home&amp;lt;/h2&amp;gt;
            &amp;lt;/main&amp;gt;
            &amp;lt;nav&amp;gt;
                &amp;lt;Link to=&quot;/about&quot;&amp;gt;About&amp;lt;/Link&amp;gt;
            &amp;lt;/nav&amp;gt;
        &amp;lt;/&amp;gt;
    );
}

function About() {
    return (
        &amp;lt;&amp;gt;
            &amp;lt;main&amp;gt;
                &amp;lt;h2&amp;gt;About&amp;lt;/h2&amp;gt;
            &amp;lt;/main&amp;gt;
            &amp;lt;nav&amp;gt;
                &amp;lt;Link to=&quot;/&quot;&amp;gt;Home&amp;lt;/Link&amp;gt;
            &amp;lt;/nav&amp;gt;
        &amp;lt;/&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行 &lt;code&gt;npm start&lt;/code&gt; ，您应该会看到 &lt;code&gt;Home&lt;/code&gt; 标识&lt;/p&gt;
&lt;h2&gt;如何升级 v6&lt;/h2&gt;
&lt;p&gt;官方的迁移指南在这里：&lt;a href=&quot;https://reactrouter.com/docs/en/v6/upgrading/v5&quot;&gt;React Router v6 迁移指南&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;参考文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://reacttraining.com/blog/react-router-v6-pre/&quot;&gt;React Router v6 Preview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactrouter.com/docs/en/v6&quot;&gt;React Router&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;如果你正在用 &lt;code&gt;Hook&lt;/code&gt; 重构你的应用，我的建议是可以尝试&lt;/p&gt;
&lt;h2&gt;重要的事&lt;/h2&gt;
&lt;p&gt;如果你觉得这篇内容对你挺有启发，别忘记点赞 + 关注&lt;/p&gt;
&lt;p&gt;欢迎添加我的个人微信：Jiang9684，一起交流前端技术&lt;/p&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>专科如何进入互联网大厂</title><link>https://github.com/posts/%E4%B8%93%E7%A7%91%E5%A6%82%E4%BD%95%E8%BF%9B%E5%85%A5%E4%BA%92%E8%81%94%E7%BD%91%E5%A4%A7%E5%8E%82/</link><guid isPermaLink="true">https://github.com/posts/%E4%B8%93%E7%A7%91%E5%A6%82%E4%BD%95%E8%BF%9B%E5%85%A5%E4%BA%92%E8%81%94%E7%BD%91%E5%A4%A7%E5%8E%82/</guid><pubDate>Sat, 19 Mar 2022 12:25:23 GMT</pubDate><content:encoded>&lt;h2&gt;跳槽周期跨度&lt;/h2&gt;
&lt;h3&gt;什么时候动了想法&lt;/h3&gt;
&lt;p&gt;大概在去年 11 月份的时候，我负责的业务线一直做不出成绩。而且整个公司的前端技术设施，都是我一手搭建出来的，再待在公司，没什么可成长的空间，就想跳槽了，期间考虑了几个可能性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;出国工作：这个想法来源，主要是看了某位网友肉身出国到新加坡的虾皮&lt;a href=&quot;https://v2ex.com/t/827199&quot;&gt;到新加坡打工两个月的经历分享&lt;/a&gt;，觉得挺有意思，也想尝试下，后面投了一些跨国企业，没啥反馈，估计是卡学历，所以就放弃了，后面再想想弥补外语的可能性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;进入大厂：很多大厂都卡学历，即使是内推，也会被卡面试流程，我被小红书、京东、腾讯、美团，拼多多、虾皮、携程、喜马拉雅卡学历和卡面试进程，所以，能选择的大厂很少，最终入职了 B 站&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;面了哪些公司&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;热身阶段（11 月下旬 - 12 月中旬）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;彩云科技，offer&lt;/li&gt;
&lt;li&gt;天壤智能，offer&lt;/li&gt;
&lt;li&gt;掌门一对一，offer&lt;/li&gt;
&lt;li&gt;帷幄匠心，offer&lt;/li&gt;
&lt;li&gt;驰骛科技，offer&lt;/li&gt;
&lt;li&gt;坚果云，二面挂&lt;/li&gt;
&lt;li&gt;乐言科技，二面挂&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;正式阶段（12 月下旬 - 2 月下旬）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;微盟（海外电商），一面挂&lt;/li&gt;
&lt;li&gt;字节（广告商业化），一面挂&lt;/li&gt;
&lt;li&gt;字节（线索中台），二面挂&lt;/li&gt;
&lt;li&gt;字节（抖音生活服务），二面挂&lt;/li&gt;
&lt;li&gt;网易（严选），四面完，最终评审挂&lt;/li&gt;
&lt;li&gt;七牛云（低代码平台），三面完，后续无反馈&lt;/li&gt;
&lt;li&gt;B 站，offer&lt;/li&gt;
&lt;li&gt;中通快递，offer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;复习备战花了多久，面试到入职，花了多久&lt;/h3&gt;
&lt;p&gt;11 月 - 2 月 一直在陆陆续续准备，持续了 3 个月，主要是准备复习、复盘、刷算法
从开始面试，到入职，差不多 3 个月时间&lt;/p&gt;
&lt;h2&gt;做了哪些准备&lt;/h2&gt;
&lt;p&gt;主要准备了以下几个方面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;简历，仔细梳理了最近的工作经历，项目上做的事情，用到哪些技术&lt;/li&gt;
&lt;li&gt;笔试，主要是算法和编程题&lt;/li&gt;
&lt;li&gt;技术知识点查漏补缺&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;复习了哪些内容，刷了哪些题&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;八股文&lt;/li&gt;
&lt;li&gt;编程题，手写各种方法&lt;/li&gt;
&lt;li&gt;算法题&lt;/li&gt;
&lt;li&gt;React/Mobx/Vite &amp;amp;&amp;amp; Webpack&lt;/li&gt;
&lt;li&gt;自己写的博客&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;八股文&lt;/h4&gt;
&lt;p&gt;这块没啥好说的，把各大社区各位大神经常分享的一些文章看了下，再就是针对计算机基础知识和网络基础知识，做了一些总结，大概看了下面这些文章：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.poetries.top/browser-working-principle/#%E4%BB%80%E4%B9%88%E6%98%AF-dom&quot;&gt;浏览器工作原理与实践&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://time.geekbang.org/column/article/233249&quot;&gt;图解 V8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6844903919789801486&quot;&gt;从多线程到 Event Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://m26bxrpatp.feishu.cn/base/appcn5mUun8tTLsaFG0jrTeUnBg?table=tbllAUETZhGVTWMA&amp;amp;view=vewJHSwJVd&quot;&gt;前端年后面试真题，会 80%直接进大厂副本&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;编程&lt;/h4&gt;
&lt;p&gt;刷了下基本会手写的面试题以及再把面试过程中遇到的手写题总结下，基本上都还好&lt;/p&gt;
&lt;h4&gt;算法&lt;/h4&gt;
&lt;p&gt;算法这块不是我的长项，基本没接触，主要是把 LeetCode 热题 HOT 100，Easy 难度简单刷了下，针对大厂频出的算法题，看不懂的，就背答案，比如经典的反转二叉树、任意两个数的和等于 Target、反转单向链表等&lt;/p&gt;
&lt;p&gt;后面根据一些面经和文档，比如：前端年后面试真题，会 80%直接进大厂副本，做了一些针对性地练习和巩固&lt;/p&gt;
&lt;h4&gt;框架&lt;/h4&gt;
&lt;p&gt;React 部分复习了 setState 原理、 diff 原理，深入了 hooks 和 fiber 原理，当然这里的深入不是去读源码，因为时间来不及，而是参考了比较多的文章：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://react.iamkasong.com/&quot;&gt;React 技术揭秘&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/book/6945998773818490884&quot;&gt;React 进阶实践指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.vitejs.net/&quot;&gt;Vite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://m26bxrpatp.feishu.cn/base/appcn5mUun8tTLsaFG0jrTeUnBg?table=tbllAUETZhGVTWMA&amp;amp;view=vewJHSwJVd&quot;&gt;前端年后面试真题，会 80%直接进大厂副本&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Mobx 由于我日常使用比较多的，也被面试官深入问过，就自己搭建过前端脚手架和看了下 React 进阶实践指南里面有一篇讲了 Mobx。主要了解框架的主体逻辑、响应式原理、依赖收集的实现
此外也总结了一下 Redux 和 Mobx 的特点，因为面试官大概率会问：Redux 和 Mobx 的区别？&lt;/p&gt;
&lt;h4&gt;博客&lt;/h4&gt;
&lt;p&gt;由于本人经常会写播客，这点在投递简历的时候，是非常大的一个加分项，在多个技术社区有同名账号，经常发表一些文章，会受到面试官的青睐，我最近的面试，都有被面试官提到，你的博客写的不错，而且面试官也会从你的博客中来提问你，我就被问到以下问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Webpack 如何实现热更新的？&lt;/li&gt;
&lt;li&gt;React 和 Vue 的选型？&lt;/li&gt;
&lt;li&gt;Redux 和 Mobx 的区别？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题，我都在博客上记录了，所以都是信手拈来&lt;/p&gt;
&lt;h3&gt;重点补了哪些能力和短板&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;技术部分：算法、编程题、对框架的细节思考（不仅仅是停留在使用层面）&lt;/li&gt;
&lt;li&gt;业务部分：将自己对业务的理解和思考进行了总结&lt;/li&gt;
&lt;li&gt;管理部分：归纳和提炼&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;面试中暴露了哪些问题，怎么应对的&lt;/h3&gt;
&lt;h4&gt;思考和表达的连续性&lt;/h4&gt;
&lt;p&gt;前期热身的时候，准备内容的结构化不足，遇到有些没有准备到的问题，临场思考和组织，或者一遍表述一遍思考，导致表达的结构性和连续性较差，表现上就是重点不够、啰嗦、回答不上&lt;/p&gt;
&lt;p&gt;解决：针对几大类问题梳理了整体的思维导图，表达的时候，跟进面试官的侧重点，挑选关键点进行结构
化表达&lt;/p&gt;
&lt;h4&gt;过往经历的思考不足&lt;/h4&gt;
&lt;p&gt;当面试官聊到过往短板问题或者低谷经历时，一开始的回答有点浮于表面，并且有一些规避的回答，受到了面试官的质疑&lt;/p&gt;
&lt;p&gt;解决：认真、客观地重新梳理了对应的问题，从主观、客观层面进行重新组织语言进行回答&lt;/p&gt;
&lt;h3&gt;印象深刻的事情&lt;/h3&gt;
&lt;h4&gt;每个公司面下来的感受&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;字节：我自己的意向是字节跳动，连续面了三个部门，后面字节挂了两个部门，又被其他部门捞起来，，面到后面，越来越没底气，一方面自己的技术深度不够，另一方面，感觉面试官都很年轻，不会往你的优势方面去进行引导（面试经验不足），有的部门，面的感觉还可以，都挂了，很烦&lt;/li&gt;
&lt;li&gt;网易严选：网易严选的流程最长，我面了一个多月，当字节跳动挂了之后，网易严选是我最意向的部门，技术栈和业务方向匹配，该部门接下来一年要做的一些事情，也跟我的职业发展比较匹配，但是，四面完之后，就给我挂了，很遗憾，估计是被其他候选人给 PK 下去了&lt;/li&gt;
&lt;li&gt;七牛云：一面问八股文，手写代码，比如实现一个 call/apply 方法等，二面很常规，问问简历上的事，三面由部门负责人来面试（产品经理），感觉还可以，还让我加了微信，但后面流程一直没有推动，我问了 HR 多次，HR 给我的反馈是对我的印象挺好，希望保持联系，但后面流程就推动不下去了，再联系 HR ，就没回复&lt;/li&gt;
&lt;li&gt;B 站：全程聊的都还可以，比较顺利&lt;/li&gt;
&lt;li&gt;坚果云：印象最深的就是二面了，要开始二面前， HR 先联系我，说是一个开放性的题目，让我二选一，我选择了其中一个，让我做整体的思考，我做了大量的后端知识、服务端知识，但面试官只问前端方面的知识，沟通的不是很顺利，整体面下来，感觉双方的理解都是有问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;哪些经历让自己很难熬&lt;/h3&gt;
&lt;h4&gt;字节和七牛云的 offer 一个都没拿到&lt;/h4&gt;
&lt;p&gt;字节面的感觉还可以，第二天就挂了，很难受，心态有点蹦
七牛云三面完之后，我等了一周左右，没给我反馈，我一直在推进，包括联系三面的面试官，询问结果，就是不给反馈，挂了也没任何反馈&lt;/p&gt;
&lt;h4&gt;最终抉择&lt;/h4&gt;
&lt;p&gt;当时手头上只有掌门一对一的 offer ，掌门一对一，HR 在催我入职， B 站和网易严选还没有面完，无法拒绝，很纠结，当时做的选择就是，先入职掌门一对一，哪会儿中通联系上我，我就再面了一个中通，拿到了中通的 offer，然后再等 B 站和严选的流程了，自己对严选是非常意向的，但严选终面完还是被挂，就很头疼，在面严选的过程，B 站正式 offer 发了，只能选择 B 站&lt;/p&gt;
&lt;h4&gt;如何克服了过程中的困难&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;定好策略，稳住心态
跳槽的过程中不是来一家公司就面一家公司的，而是要根据自己的情况循序渐进。像我这样后面 offer 还比较少的时候，虽然心里也慌，但还是准备了一些兜底的策略，这点非常重要，而且本人是裸辞的情况下，面了很长的时间，心态一定要稳住，特别是互联网寒冬时期&lt;/li&gt;
&lt;li&gt;与人交流，开拓思路
我会和学长、靠谱同事、靠谱猎头进行交流，一方面可以从他们的角度看出自己的不足，另一方面也可以从他们那里得到帮助和思路&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;其他的心得体会和经验&lt;/h2&gt;
&lt;p&gt;面试的 4 个环节：简历准备 - 投递策略 - 面试和复盘 - offer 谈判和选择&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;简历准备
简历准备不是要跳槽的时候才准备的，而是日常工作中要经常做总结，跳槽前对这些总结进行梳理归纳提炼为简历&lt;/li&gt;
&lt;li&gt;投递策略
职业生涯中每个阶段的跳槽，可能会有不同的策略，常规好用的策略是先找几家公司热身，然后再开始投递自己的目标公司，在投递过程中，时间节点也需要有一定的关注&lt;/li&gt;
&lt;li&gt;面试和复盘
&lt;ul&gt;
&lt;li&gt;面试可以通过一些公司热身试错，同时，在面试中要快速捕捉到面试官想挖掘的点，用简炼地话表达出来回应给面试官。如果面试官没有挖到你长项的部分，还需要考虑引导面试官，让自己展现出自己的长项&lt;/li&gt;
&lt;li&gt;在表达方面，如果直接表达不够结构化和清晰，可以先使用笔记或者思维导图梳理，然后自己预演熟练&lt;/li&gt;
&lt;li&gt;复盘很重要，要尽可能让自己犯过的错不再犯第二次，并且，复盘的内容是可以长期积累的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;offer 谈判和选择
&lt;ul&gt;
&lt;li&gt;首先，要大概了解自己的市场价（对于大厂要了解自己的定级，对于中小厂要按市场水平），虽然这个比较难，但还是可能的&lt;/li&gt;
&lt;li&gt;如果有了解自己的市场价，那么在期望薪资这块，就可以报得有底气一些，也比较容易得到较高的涨幅。&lt;/li&gt;
&lt;li&gt;多拿 offer，通过技巧去让厂家自己在 offer 之间互相竞争，是相对容易的。虽然每个公司有自己的薪资体系，但招聘本质上是一个市场行为，厂家也要遵循客观规律&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;跳槽是一项心力、体力、脑力都必须在线的活动&lt;/p&gt;
&lt;h2&gt;资料参考和分享&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6991724298197008421&quot;&gt;2021 前端岗面试整理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode-cn.com/problem-list/2cktkvj/&quot;&gt;LeetCode 热题 HOT 100&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.iamkasong.com/&quot;&gt;React 技术揭秘&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/book/6945998773818490884&quot;&gt;React 进阶实践指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vitejs.cn/&quot;&gt;Vite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://m26bxrpatp.feishu.cn/base/appcn5mUun8tTLsaFG0jrTeUnBg?table=tbllAUETZhGVTWMA&amp;amp;view=vewJHSwJVd&quot;&gt;前端面试真题，会 80% 直接进大厂&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;最后&lt;/h2&gt;
&lt;p&gt;欢迎大家加入 B 站，我在 B 站等你，扫码即可加入我的内推群，随时可跟我交流面试经验&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/fda414a0-de61-4c91-97c2-bb62cc40d589.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;也欢迎大家关注我的&lt;a href=&quot;https://github.com/xuya227939/blog&quot;&gt;博客&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;高频面试题整理&lt;/h2&gt;
&lt;h3&gt;CSS&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;css 重绘和重排如何理解&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下面宽高各是多少&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;style&amp;gt;
        .box {
            width: 100px;
            height: 100px;
            padding: 10px;
            margin: 10px;
            background-color: #f00;
            box-sizing: content-box;
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div class=&quot;box&quot;&amp;gt;12312312&amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;span 标签在浏览器中偏移量是多少&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;style&amp;gt;
        .test {
            margin: 20px;
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;span class=&quot;test&quot;&amp;gt;&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;为什么 &lt;code&gt;marign 0 auto&lt;/code&gt; 无法垂直居中&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;控制 z-index 的规则有哪些&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;元素替换概念有了解吗？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;移动端 1px 像素如何解决？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一个元素隐藏有几种方式？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 BFC&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;css 选择器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;display 有哪些属性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;三列布局如何实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;css 栅格布局&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;flex: 0 1 auto 的含义&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;css 如何实现一个正方形盒子（随父元素）自适应&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;JS&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;js 有哪些基本数据类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲闭包是什么？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;var、let、const 的区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;es5 如何实现继承&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原型继承方式（这里要注意下，很有可能让你手写）&lt;/li&gt;
&lt;li&gt;寄生组合继承&lt;/li&gt;
&lt;li&gt;引申到 es6 的 Class 语法糖&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;es6 有哪些新的特性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;map 和 waekMap 的区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;js 中 this 指向&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 setTimeout 和 setInterval 的差异&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用过函数节流和防抖吗？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;localStorage, sessionStorage, Cookie 之间的区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下面输出结果是什么？&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    var count = 100;
    var obj = {
        count: 200,
        getCount: function() {
            console.log(this.count);
        }
    }

    const c = obj.getCount;
    obj.getCount();
    c();
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下面输出结果是什么？&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    var obj1 = { a: 100 };
    var obj2 = Object.assign({}, obj1);
    var obj3 = obj2;
    obj3.a = 200;
    console.log(obj1);
    console.log(obj2);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下面打印结果是多少&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    var p1 = new Promise((resolve) =&amp;gt; {
        resolve(1);
    });
    var p2 = new Promise((resolve) =&amp;gt; {
        setTimeout(() =&amp;gt; {
            resolve(2);
        }, 0);
    });
    var p3 = new Promise((resolve) =&amp;gt; {
        resolve(3);
    });
    Promise.all([p1, p2, p3]).then((res) =&amp;gt; {
        console.log(res);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下面打印结果是多少&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    async function promise2() {
        function p1() {
            return new Promise((resolve) =&amp;gt; {
                resolve(1);
            });
        }

        function p2() {
            return new Promise((resolve, reject) =&amp;gt; {
                reject(2);
            });
        }

        function p3() {
            return new Promise((resolve, reject) =&amp;gt; {
                resolve(3);
            });
        }

        try {
            var p11 = await p1();
            var p22 = await p2();
            var p33 = await p3();
            console.log(p11);
            console.log(p22);
            console.log(p33);
        } catch(e) {};
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;请描述下 new 的执行原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为什么 Object.prototype.toString.call 可以判断出变量，而不是通过 Object.toString.call ?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 gc 原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;js 如何解决数值精度问题&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲事件循环&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;浏览器&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;浏览器缓存原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单页应用如何提高加载速度？&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;React&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;讲讲 fiber 架构&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲对 diff 的理解&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲对 hooks 的理解&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 class 生命周期&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;setState 原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hooks 为何有一些规则使用条件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊 react 事件机制原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 useCallBack 和 useMemo 的区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 useRef 和 ref 的区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;react 组件传递状态有哪几种方式&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;状态管理&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;mobx 原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;redux 原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;mobx 和 redux 的区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;函数式组件特点，和 hoc 区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讲讲 redux 中间件&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;微信小程序&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;taro 源码如何实现的？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;taro 2.x 和 taro 3 最大区别是什么？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;微信小程序原理是什么？&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;打包工具&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;聊聊 vite 和 webpack 的区别&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;模块化有了解吗，讲讲 import 和 require 的区别？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;webpack 的原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;谈谈摇树的概念&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;webpack 热更新机制原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;vite 原理是什么？&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;设计模式&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;订阅发布和观察者模式和什么之间的区别，实现一个订阅发布者模式&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;编程题&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;如何实现一个深 copy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现一个 new&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;柯里话函数的实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现一个函数 calc&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function calc() {

}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;对于任意参数，实现累乘功能&lt;/li&gt;
&lt;li&gt;对于两次同样的参数，结果缓存，比如 （1, 2, 3）、（3, 1, 2）&lt;/li&gt;
&lt;li&gt;对缓存优化&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;实现一个 apply&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;针对深 Copy，变种的题目&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现一个 ajax&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function ajax(options) { }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;在实现的 ajax 基础上实现调用所有请求，等到所有请求结果成功之后，再返回结果&lt;/li&gt;
&lt;li&gt;在实现的 ajax 基础上实现请求顺序调用，第一个调用成功之后，再调用第二个，以此类推&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;实现一个 repeat 函数，根据传入的参数，间隔时间，打印次数，来输出 log&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;
repeat(func, inteval, times){ … }

const r = repeat(repeatPrint, 10, 10);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;用最精炼的代码实现数组非零非负最小值的索引 index&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 例如：[10,21,0,-7,35,7,9,23,18] 输出索引 5, 数值 7 最小
function getIndex(arr) {
    let index = null;
    ...
    return index;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;该代码输出结果是什么？&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;const list = [1, 2, 5]
const square = num =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        setTimeout(() =&amp;gt; {
            resolve(num * num)
        }, 1000)
    })
}

function test() {
    list.forEach(async x =&amp;gt; {
        const res = await square(x)
        console.log(res)
    })
}
test()
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;手写一唯数组转换树节点&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var array = [
    {pid: 4, id: 6617, name: &quot;a&quot;,subNode:[]},
    {pid: 5, id: 666, name: &quot;a&quot;,subNode:[]},
    {pid: 4, id: 6616, name: &quot;a&quot;,subNode:[]},
    {pid: 6616, id: 66161, name: &quot;a&quot;,subNode:[]},
    {pid: -1, id: 0, name: &quot;a&quot;,subNode:[]},
    {pid: 0, id: 4, name: &quot;a&quot;,subNode:[]},
    {pid: 0, id: 5, name: &quot;a&quot;,subNode:[]},
    {pid: 4, id: 10, name: &quot;a&quot;,subNode:[]},
    {pid: 10, id: 451, name: &quot;a&quot;,subNode:[]},
    {pid: 0, id: 98, name: &quot;a&quot;,subNode:[]},
    {pid: 98, id: 23, name: &quot;a&quot;,subNode:[]},
    {pid: 98, id: 523, name: &quot;a&quot;,subNode:[]}
];

var toTree = function(tarArray) { }

toTree(array);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法题&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;实现一个反转二叉树&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一个迷宫，最短路径生成&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现单向链表反转&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实际场景算法题&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;twoSum 得到两个数的之后等于 target&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;其他&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;聊聊业务上的事&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊最复杂的业务如何处理的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如何做性能优化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊前端脚手架&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;带团队中有哪些难点&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聊聊迭代流程&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Git 如何覆盖某次 commit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;长列表滚动，你怎么优化的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;印象中最深的一件事&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>专科如何进入互联网大厂</category><author>江辰</author></item><item><title>微信小程序 web-view 问题讨论</title><link>https://github.com/posts/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F-web-view-%E9%97%AE%E9%A2%98%E8%AE%A8%E8%AE%BA/</link><guid isPermaLink="true">https://github.com/posts/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F-web-view-%E9%97%AE%E9%A2%98%E8%AE%A8%E8%AE%BA/</guid><pubDate>Thu, 14 Oct 2021 12:51:25 GMT</pubDate><content:encoded>&lt;p&gt;大家好，我是江辰，这篇文章记录一次在真实的线上环境中，关于 web-view 的问题，大家可以跟随作者一起看看心路历程。&lt;/p&gt;
&lt;p&gt;本文首发于微信公众号：野生程序猿江辰&lt;/p&gt;
&lt;p&gt;欢迎大家点赞，收藏，关注&lt;/p&gt;
&lt;h2&gt;问题背景&lt;/h2&gt;
&lt;p&gt;上半年最开始做的一版是展业大厅页面和互动白板页面（以下统称 &lt;code&gt;web-view&lt;/code&gt;）分离，后面由于腾讯那边对交互方式不满意，强调一定要展业大厅页面和白板页面在同一个页面进行交互，最开始我们没有思路，因为在小程序官方中的描述，&lt;code&gt;web-view&lt;/code&gt; 页面不允许叠加任何组件，后面是产品找到一个 demo，发现可以叠加，我这边去翻了下他们的源码（&lt;code&gt;renderingMode: &apos;seperated&apos;&lt;/code&gt;），最终解决了该问题，也就导致后面很多问题的产生。&lt;/p&gt;
&lt;h2&gt;现存问题&lt;/h2&gt;
&lt;h3&gt;web-view 存在的情况&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;安卓更新组件不生效，比如 tab 切换，tab1 切换到 tab2 ，不生效，内容不会更新&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安卓更新图片不生效&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安卓更新样式不生效&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;cover-view 文字消失&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;按钮响应慢，机型性能低的手机比较明显&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;针对问题 2，目前的 &lt;code&gt;hack&lt;/code&gt; 方案，先渲染一张透明的图片，然后再渲染其他图片，可以生效&lt;/p&gt;
&lt;p&gt;针对问题 1、2、3，仅在安卓端出现，苹果手机上没有发现，目前有一个比较 &lt;code&gt;hack&lt;/code&gt; 的方案，通过卸载组件，重新渲染，可以达到目的，但是产生的性能损耗比较大，对交互体验不友好，而且也导致了第四点问题的产生&lt;/p&gt;
&lt;p&gt;针对问题 4 安卓复现频率比较高，苹果出现过一次&lt;/p&gt;
&lt;p&gt;针对问题 5 安卓跟苹果都存在&lt;/p&gt;
&lt;h3&gt;web-view 不存在的情况&lt;/h3&gt;
&lt;p&gt;都正常&lt;/p&gt;
&lt;h2&gt;尝试过的方案&lt;/h2&gt;
&lt;p&gt;针对 &lt;code&gt;cover-view&lt;/code&gt; 文字消失&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置组件宽高&lt;/li&gt;
&lt;li&gt;设置字体颜色和背景颜色&lt;/li&gt;
&lt;li&gt;刷新&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上方案，都不行，也没法在开发者工具上查看 &lt;code&gt;DOM&lt;/code&gt; 视图&lt;/p&gt;
&lt;h3&gt;Console&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/4d21dadd-b397-4e38-87b4-a71e18369ee5.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;元素的宽高都在，偏移位置也正常，就是文字消失&lt;/p&gt;
&lt;h3&gt;DOM&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/e7d6cc0c-c02d-4978-bcf4-2ae87595f5d2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;无法在开发者工具上查看 DOM 视图&lt;/p&gt;
&lt;h2&gt;现象&lt;/h2&gt;
&lt;h3&gt;正常&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/dfbeb70c-5e83-4c77-91e1-b337d1c4a3d7.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;文字消失&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/e3a76308-5456-482d-831a-6d03779ecb23.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个元素的宽高都在，就是文字消失&lt;/p&gt;
&lt;h2&gt;微信小程序架构图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/900a15d9-7bac-4aee-8ce4-1148bfac3851.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;展业小程序架构图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/4a20f41d-6dac-4117-b641-2b990f975fe3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;所有人的视频流不再全部获取，而是只显示四路视频流，其他人员要显示，在成员列表进行切换显示&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重点⼯作中花费精⼒最多的是模块化解耦的重构、我简单说下背景。因为之前我们代码共建的、 但是因为客户这边定制化的需求有很多，并且不是那么简单的能⽤抽象的⽅式把这些⾮通⽤功能的功能实现的、所以我们想出来的⽅案是：把⼩程序代码⾥划分重点模块，把每个模块都做成可插拔的，这样我们只需要把差异化很多的部分抽出来完全独⽴交给⾃⼰开发即可。同时这个⽅案实现好后，如果后续我们要开发新形态的应⽤，可以通过实现模块的⽅式实现⼀套新的应⽤形态&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些优化工作总共时间大概花了一个月左右，完成之后，目前我们的产品能够支持到 &lt;strong&gt;20+&lt;/strong&gt; 人同时进行音视频，这块实际测试过。对我们的产品稳定性越来越好！&lt;/p&gt;
</content:encoded><category>微信小程序</category><author>江辰</author></item><item><title>检测浏览器刷新还是退出代码</title><link>https://github.com/posts/%E6%A3%80%E6%B5%8B%E6%B5%8F%E8%A7%88%E5%99%A8%E5%88%B7%E6%96%B0%E8%BF%98%E6%98%AF%E9%80%80%E5%87%BA%E4%BB%A3%E7%A0%81/</link><guid isPermaLink="true">https://github.com/posts/%E6%A3%80%E6%B5%8B%E6%B5%8F%E8%A7%88%E5%99%A8%E5%88%B7%E6%96%B0%E8%BF%98%E6%98%AF%E9%80%80%E5%87%BA%E4%BB%A3%E7%A0%81/</guid><pubDate>Mon, 27 Sep 2021 15:17:23 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;let beginTime = 0;
let differTime = 0;
window.onunload = function () {
    differTime = new Date().getTime() - beginTime;
    if (differTime &amp;lt;= 5) {
        console.log(&apos;浏览器关闭&apos;);
    } else {
        console.log(&apos;浏览器刷新&apos;);
    }
};
window.onbeforeunload = function () {
    beginTime = new Date().getTime();
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>微信小程序 web-view 下渲染 cover-view，文字消失</title><link>https://github.com/posts/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F-web-view-%E4%B8%8B%E6%B8%B2%E6%9F%93-cover-view%E6%96%87%E5%AD%97%E6%B6%88%E5%A4%B1/</link><guid isPermaLink="true">https://github.com/posts/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F-web-view-%E4%B8%8B%E6%B8%B2%E6%9F%93-cover-view%E6%96%87%E5%AD%97%E6%B6%88%E5%A4%B1/</guid><pubDate>Sun, 26 Sep 2021 11:16:37 GMT</pubDate><content:encoded>&lt;h2&gt;问题背景&lt;/h2&gt;
&lt;p&gt;上半年最开始做的一版是展业大厅页面和互动白板页面（以下统称 web-view）分离，后面由于腾讯那边对交互方式不满意，强调一定要展业大厅页面和白板页面在同一个页面进行交互，最开始我们没有思路，因为在小程序官方中的描述，web-view 页面不允许叠加任何组件，后面是产品找到一个 demo，发现可以叠加，我这边去翻了下他们的源码（renderingMode: &apos;seperated&apos;），最终解决了该问题，也就导致后面很多问题的产生。&lt;/p&gt;
&lt;h2&gt;现有问题&lt;/h2&gt;
&lt;h3&gt;web-view 存在的情况&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;安卓更新组件不生效，比如 tab 切换，tab1 切换到 tab2 ，不生效，内容不会更新&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安卓更新图片不生效&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安卓更新样式不生效&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;cover-view 文字消失&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;按钮响应慢，机型性能低的手机比较明显&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;针对问题 2，目前的 hack 方案，先渲染一张透明的图片，然后再渲染其他图片，可以生效&lt;/p&gt;
&lt;p&gt;针对问题 1、2、3，仅在安卓端出现，苹果手机上没有发现，目前有一个比较 hack 的方案，通过卸载组件，重新渲染，可以达到目的，但是产生的性能损耗比较大，对交互体验不友好，而且也导致了第四点问题的产生&lt;/p&gt;
&lt;p&gt;针对问题 4 安卓复现频率比较高，苹果出现过一次&lt;/p&gt;
&lt;p&gt;针对问题 5 安卓跟苹果都存在&lt;/p&gt;
&lt;h3&gt;web-view 不存在的情况&lt;/h3&gt;
&lt;p&gt;都正常&lt;/p&gt;
&lt;h2&gt;尝试过的方案&lt;/h2&gt;
&lt;p&gt;针对 cover-view 文字消失&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置组件宽高&lt;/li&gt;
&lt;li&gt;设置字体颜色和背景颜色&lt;/li&gt;
&lt;li&gt;刷新&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上方案，都不行，也没法在开发者工具上查看 DOM 视图&lt;/p&gt;
&lt;h3&gt;console&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/134792081-a01ea541-d204-400a-96e1-09edf9c95ff8.jpeg&quot; alt=&quot;WechatIMG13&quot; /&gt;&lt;/p&gt;
&lt;p&gt;元素的宽高都在，偏移位置也正常，就是文字消失&lt;/p&gt;
&lt;h3&gt;DOM&lt;/h3&gt;
&lt;p&gt;&amp;lt;img width=&quot;1024&quot; alt=&quot;WechatIMG3085&quot; src=&quot;https://user-images.githubusercontent.com/16217324/134792083-682f9111-ea88-43a6-a90d-dd5357baf446.png&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;无法在开发者工具上查看 DOM 视图&lt;/p&gt;
&lt;h2&gt;现象&lt;/h2&gt;
&lt;h3&gt;正常&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/134792087-47a6e113-fd23-4da0-b60c-c412c00a1bb4.jpeg&quot; alt=&quot;WechatIMG20&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;文字消失&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/134792095-d09471ef-d4e6-468b-9b86-473120d61a76.jpeg&quot; alt=&quot;WechatIMG19&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个元素的宽高都在，就是文字消失&lt;/p&gt;
&lt;h2&gt;微信小程序架构图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/134792105-ee98881e-38b0-4a5a-81b8-8d169ea68093.jpeg&quot; alt=&quot;WechatIMG3201&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;展业小程序架构图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/134792107-093c03ef-7fff-49fe-b56c-b777ebfca044.png&quot; alt=&quot;mini&quot; /&gt;&lt;/p&gt;
</content:encoded><category>微信小程序</category><author>江辰</author></item><item><title>企业微信自建应用</title><link>https://github.com/posts/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E8%87%AA%E5%BB%BA%E5%BA%94%E7%94%A8/</link><guid isPermaLink="true">https://github.com/posts/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E8%87%AA%E5%BB%BA%E5%BA%94%E7%94%A8/</guid><pubDate>Sun, 18 Jul 2021 14:58:23 GMT</pubDate><content:encoded>&lt;h2&gt;企业微信自建应用&lt;/h2&gt;
&lt;h3&gt;创建应用&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/126058529-a8840285-13a4-4c87-9b59-4ac035cd7960.png&quot; alt=&quot;WechatIMG2483&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进入企业微信，打开【应用管理】，在【自建】下选择【创建应用】&lt;/p&gt;
&lt;h3&gt;完善应用信息&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/126058532-ea351695-2d1f-4ff9-be57-3d25dda15552.png&quot; alt=&quot;WechatIMG2481&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在【可见范围】内【选择部门/成员】，建议选择全公司，后续可修改&lt;/p&gt;
&lt;h3&gt;获取密钥&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/126058537-6f65c9f6-784f-4116-9697-ae53dd0aa2a4.png&quot; alt=&quot;WechatIMG2482&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;配置应用属性&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/126058539-b2f535fa-978d-4a8f-8a6b-e82c03b35435.png&quot; alt=&quot;WechatIMG2484&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/126058541-462406c4-9769-408d-9b98-8c570912e5e9.png&quot; alt=&quot;WechatIMG2485&quot; /&gt;&lt;/p&gt;
&lt;p&gt;把可信任的域名配置下，申请校验文件，放入到域名的根目录，保证 Http 请求能访问该文件即可&lt;/p&gt;
&lt;h3&gt;配置聊天工具侧边栏&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/126058543-d00d2919-a1f6-4e54-b3c4-df4a9a395da0.png&quot; alt=&quot;WechatIMG2486&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/126058544-ecb814ea-e988-4a80-b942-d676ab6dc89d.png&quot; alt=&quot;WechatIMG2488&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;填写页面名称&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;填写页面内容，选择自定义，链接后面需要带上参数，corp_id 和 app_id&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;授权流程&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/126058546-e0d06829-3446-490b-b590-ba6a184a038b.jpeg&quot; alt=&quot;WechatIMG2489&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;构造网页授权链接&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取访问用户身份&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;如何让 localhost 设置为可信域名？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当需要对域名进行校验，比如企业微信或微信公众号的一些可信域名配置，需要通过域名来访问，会非常有用。&lt;/p&gt;
&lt;p&gt;编辑你的本地 hosts，是本地转发到指定域名，这里不要带端口号，如果有端口号，输入域名的时候，带上端口号。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.0.0.1 order.downfuture.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;img width=&quot;706&quot; alt=&quot;WechatIMG2490&quot; src=&quot;https://user-images.githubusercontent.com/16217324/126058551-d570d813-b776-4fe6-b19b-042b5fd0c3d7.png&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;如果访问报这个错误，需要在&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;devServer: {
    // ...
    disableHostCheck: true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置完之后，本地启动开发服务，输入域名和端口号跳转页面，则可以看到修改了，受缓存影响，最好用无痕浏览器噢。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Windows 企业微信浏览器内核版本过低，如何解决？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;由于 Windows 企业微信浏览器内核版本在 53，导致 &lt;code&gt;async&lt;/code&gt; 使用不了，配置 &lt;code&gt;Babel&lt;/code&gt;，支持到 53 即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;presets&quot;: [
        [
            &quot;@babel/preset-env&quot;,
            {
                &quot;targets&quot;: {
                    &quot;edge&quot;: &quot;17&quot;,
                    &quot;firefox&quot;: &quot;60&quot;,
                    &quot;chrome&quot;: &quot;53&quot;,
                    &quot;safari&quot;: &quot;11.1&quot;
                },
                &quot;useBuiltIns&quot;: &quot;usage&quot;,
                &quot;corejs&quot;: 3
            }
        ],
        [&quot;@babel/preset-react&quot;]
    ]
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>企业微信</category><author>江辰</author></item><item><title>MacBook Pro 外接显示屏开启 HiDPI</title><link>https://github.com/posts/macbook-pro-%E5%A4%96%E6%8E%A5%E6%98%BE%E7%A4%BA%E5%B1%8F%E5%BC%80%E5%90%AF-hidpi/</link><guid isPermaLink="true">https://github.com/posts/macbook-pro-%E5%A4%96%E6%8E%A5%E6%98%BE%E7%A4%BA%E5%B1%8F%E5%BC%80%E5%90%AF-hidpi/</guid><pubDate>Fri, 25 Jun 2021 19:29:35 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近向公司申请了一台 2K 显示器，弄来之后，接上 MacBook Pro，结果，由于像素太高，导致整个屏幕都缩放，字体变的非常小，而且也没有达到 Retina 的效果。经查询，苹果需要开启 HiDPI 技术&lt;/p&gt;
&lt;h2&gt;HiDPI&lt;/h2&gt;
&lt;p&gt;本质上是用软件的方式实现单位面积内的高密度像素。用四个像素点来表现一个像素，因此能够更加清晰细腻&lt;/p&gt;
&lt;p&gt;高 PPI(硬件) + HiDPI 渲染(软件) = 更细腻的显示效果(Retina)&lt;/p&gt;
&lt;p&gt;&amp;lt;img width=&quot;640&quot; alt=&quot;WechatIMG2168&quot; src=&quot;https://user-images.githubusercontent.com/16217324/123499689-13fb1980-d66b-11eb-8f33-b473d58ef351.png&quot;&amp;gt;&lt;/p&gt;
&lt;h2&gt;获取外接显示器 DisplayVendorID 和 DisplayProductID&lt;/h2&gt;
&lt;p&gt;在终端输入以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ioreg -l | grep &quot;DisplayVendorID&quot;

$ ioreg -l | grep &quot;DisplayProductID&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果获得两个，那说明的你的 Macbook 还在亮着，可以合盖来排除掉&lt;/p&gt;
&lt;h2&gt;制作外接显示屏系统配置文件&lt;/h2&gt;
&lt;h3&gt;转换 16 进制&lt;/h3&gt;
&lt;p&gt;将 DisplayVendorID 和 DisplayProductID 的数值，转换为 16 进制，&lt;a href=&quot;https://tool.lu/hexconvert/&quot;&gt;在线转换工具&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;创建显示器配置文件夹&lt;/h3&gt;
&lt;p&gt;新建文件夹，命名为：DisplayVendorID-XXXX，其中 XXXX 是刚才转换的 DisplayVendorID 的 16 进制值小写&lt;/p&gt;
&lt;h3&gt;创建显示器配置内容&lt;/h3&gt;
&lt;p&gt;这里需要借助工具来生成，&lt;a href=&quot;https://codeclou.github.io/Display-Override-PropertyList-File-Parser-and-Generator-with-HiDPI-Support-For-Scaled-Resolutions/&quot;&gt;在线生成工具&lt;/a&gt;，将显示器的名称、DisplayVendorID（16 进制） 和 DisplayProductID（16 进制） 对应填写进去，即可获得配置文件，下载文件到刚创建的 DisplayVendorID-XXXX 文件夹内，将 plist 的后缀去掉&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/123499686-0c3b7500-d66b-11eb-96be-234022b2c651.png&quot; alt=&quot;WechatIMG2169&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后需要将文件放到系统的 &lt;code&gt;/System/Library/Displays/Contents/Resources/Overrides/&lt;/code&gt; 文件夹中&lt;/p&gt;
&lt;p&gt;如果 &lt;code&gt;Overrides&lt;/code&gt; 没有权限操作，在终端中输入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ csrutil status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/123499683-05acfd80-d66b-11eb-843e-a70b6fc736f8.png&quot; alt=&quot;WechatIMG2170&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果是关闭状态，则需要开启&lt;/p&gt;
&lt;h3&gt;开启 rootless&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;重启 MacBook，按住 command + R 直到屏幕上出现苹果的标志和进度条，进入 Recovery 模式&lt;/li&gt;
&lt;li&gt;在屏幕左上方的工具栏找到实用工具（左数第 3 个），打开终端&lt;pre&gt;&lt;code&gt;$ csrutil disable
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;重启 MacBook&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;关闭 rootless&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;重启 MacBook，按住 command + R 直到屏幕上出现苹果的标志和进度条，进入 Recovery 模式&lt;/li&gt;
&lt;li&gt;在屏幕左上方的工具栏找到实用工具（左数第 3 个），打开终端&lt;pre&gt;&lt;code&gt;$ csrutil disable
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;重启 MacBook&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果已关闭 rootless，还是不行，那么需要在终端输入以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo mount -rw /
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tips:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;10.15 之后，系统的目录除了几个 rootless 可以修改的，都是只读的，所有对系统的修改都不支持，你的那个目录是在只读分区内的。要是写机器域的文件，在/Library 目录中操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;macOS 有个内核保护机制 rootless，有时候你需要装什么软件时，需要一些 root 权限，但是在 macOS 上 root 虽然权力是最大的，但是苹果还是限制了它一下。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;开启 HiDPI&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xzhih/one-key-hidpi/blob/master/README-zh.md&quot;&gt;one-key-hidpi&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;此脚本的目的是为中低分辨率的屏幕开启 HiDPI 选项，并且具有原生的 HiDPI 设置，不需要 RDM 软件即可在系统显示器设置中设置&lt;/p&gt;
&lt;p&gt;macOS 的 DPI 机制和 Windows 下不一样，比如 1080p 的屏幕在 Windows 下有 125%、150% 这样的缩放选项，而同样的屏幕在 macOS 下，缩放选项里只是单纯的调节分辨率，这就使得在默认分辨率下字体和 UI 看起来很小，降低分辨率又显得模糊&lt;/p&gt;
&lt;p&gt;同时，此脚本也可以通过注入修补后的 EDID 修复闪屏，或者睡眠唤醒后的闪屏问题，当然这个修复因人而异&lt;/p&gt;
&lt;p&gt;开机的第二阶段 logo 总是会稍微放大，因为分辨率是仿冒的&lt;/p&gt;
&lt;h3&gt;使用方法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;远程模式: 在终端输入以下命令回车即可&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;$ bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/xzhih/one-key-hidpi/master/hidpi.sh)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;本地模式: 下载项目解压,双击 &lt;code&gt;hidpi.command&lt;/code&gt; 运行&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/123499664-ec0bb600-d66a-11eb-85cb-524aff4757ef.jpeg&quot; alt=&quot;WechatIMG2171&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/123499670-f5951e00-d66a-11eb-9a74-99e150806caa.jpeg&quot; alt=&quot;WechatIMG2172&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;RDM&lt;/h2&gt;
&lt;p&gt;RDM 全称为 Retina Display Manage，&lt;a href=&quot;http://avi.alkalay.net/software/RDM&quot;&gt;安装地址&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;重启后打开 RDM，选取带雷电符号的 1920x1080，即可开启 HiDPI。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/123499675-fc239580-d66a-11eb-8731-5fe3a320558f.png&quot; alt=&quot;WechatIMG2173&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/123500068-e06dbe80-d66d-11eb-814b-1780aea939ff.jpeg&quot; alt=&quot;src=http___2d zol-img com cn_product_123_500x2000_269_cew9IvrLCwGQY jpg refer=http___2d zol-img com&quot; /&gt;&lt;/p&gt;
</content:encoded><category>HiDPI</category><author>江辰</author></item><item><title>Webpack 如何配置代理</title><link>https://github.com/posts/webpack-%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE%E4%BB%A3%E7%90%86/</link><guid isPermaLink="true">https://github.com/posts/webpack-%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE%E4%BB%A3%E7%90%86/</guid><pubDate>Thu, 10 Jun 2021 09:22:49 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Webpack&lt;/code&gt; 提供的 &lt;code&gt;devServer&lt;/code&gt; 配置，使我们可以非常方便的设置请求代理目标，通过改配置，有时候可以帮我们解决本地环境的跨域问题。&lt;/p&gt;
&lt;h2&gt;正向代理&lt;/h2&gt;
&lt;p&gt;当拥有单独的 &lt;code&gt;API&lt;/code&gt; 后端开发服务器并且希望在同一域上发送 &lt;code&gt;API&lt;/code&gt; 请求时，代理某些 &lt;code&gt;URL&lt;/code&gt; 可能会很有用。&lt;/p&gt;
&lt;p&gt;webpack.dev.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;devServer: {
    // ...
    proxy: {
        &apos;/api2&apos;: {
            target: &apos;http://192.168.10.183:8103&apos;,
            changeOrigin: true
        }
    }
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;axios：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;axios({
    baseURL: &apos;/api2/&apos;,
    url: &apos;/user/login&apos;,
    method: &apos;GET&apos;
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置 &lt;code&gt;proxy&lt;/code&gt;，本地环境中的请求，以 &lt;code&gt;/api&lt;/code&gt; 开头的，都会把请求代理转发到 &lt;code&gt;target&lt;/code&gt; 目标中，但是在浏览器中查看 &lt;code&gt;network&lt;/code&gt;，发现请求依旧没有改变，实际上可以看到控制台打印或看后端 &lt;code&gt;log&lt;/code&gt;，请求已经转发。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/122594087-24265e00-d099-11eb-974c-b46412e4c127.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;proxy&lt;/code&gt;，仅针对本地环境有效，对线上环境无效，一般线上环境是通过 &lt;code&gt;Nginx&lt;/code&gt; 做转发。&lt;/p&gt;
&lt;h2&gt;反向代理&lt;/h2&gt;
&lt;p&gt;当需要对域名进行校验，比如企业微信或微信公众号的一些可信域名配置，需要通过域名来访问，会非常有用。&lt;/p&gt;
&lt;p&gt;编辑你的本地 hosts，是本地转发到指定域名，这里不要带端口号，如果有端口号，输入域名的时候，带上端口号。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.0.0.1	order.downfuture.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/122594621-d3fbcb80-d099-11eb-9e61-08a44d01ea6b.png&quot; alt=&quot;B A CLATTE zsx-test ikandy cn8080commonIndex html#userlogin&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果访问报这个错误，需要在&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;devServer: {
    // ...
    disableHostCheck: true
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置完之后，本地启动开发服务，输入域名和端口号跳转页面，则可以看到修改了，受缓存影响，最好用无痕浏览器噢。&lt;/p&gt;
</content:encoded><category>Webpakc</category><author>江辰</author></item><item><title>配置多入口 Webpack 热更新失效</title><link>https://github.com/posts/%E9%85%8D%E7%BD%AE%E5%A4%9A%E5%85%A5%E5%8F%A3-webpack-%E7%83%AD%E6%9B%B4%E6%96%B0%E5%A4%B1%E6%95%88/</link><guid isPermaLink="true">https://github.com/posts/%E9%85%8D%E7%BD%AE%E5%A4%9A%E5%85%A5%E5%8F%A3-webpack-%E7%83%AD%E6%9B%B4%E6%96%B0%E5%A4%B1%E6%95%88/</guid><pubDate>Thu, 20 May 2021 11:01:36 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Webpack&lt;/code&gt; 对于现代前端开发者，想必是相当熟悉了，在很多项目中，应用非常广泛，而 &lt;code&gt;webpack-dev-server&lt;/code&gt;，相信大家应该也都接触过。最近，作者在配置多入口，热更新在单入口是好使的，结果到了多入口不好使？，然后通过 Google 寻找答案，找到了一篇 &lt;code&gt;issue&lt;/code&gt;，&lt;a href=&quot;https://github.com/webpack/webpack-dev-server/issues/2792&quot;&gt;HMR not working with multiple entries&lt;/a&gt;，跟我的问题类似，好像真的有 BUG？看到作者回复&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/120896702-4362cb80-c655-11eb-878a-c4510eb7df8f.png&quot; alt=&quot;WechatIMG1679&quot; /&gt;&lt;/p&gt;
&lt;p&gt;v4 修复了该问题，我丢，我还在使用 v3，翻看 v4 文档&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/120896712-48277f80-c655-11eb-9d7b-7a9ce1039f71.png&quot; alt=&quot;WechatIMG1680&quot; /&gt;&lt;/p&gt;
&lt;p&gt;此时，只有一个感觉，热更新都多久的东西了，不应该存在多入口，热更新有问题吧？升级到 v4 之后，还是不行，当时我这暴脾气就上来了，直接翻看源码。翻看源码之前，首先要对热更新是个什么，有个基础的了解。&lt;/p&gt;
&lt;h2&gt;模块热更新&lt;/h2&gt;
&lt;p&gt;模块热更新(Hot Module Replacement)是指在浏览器运行过程中，替换、添加或删除模块，而无需重新加载整个页面。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保留在完全重新加载页面期间丢失的应用程序状态&lt;/li&gt;
&lt;li&gt;在源代码中对 &lt;code&gt;CSS/JS&lt;/code&gt; 进行修改，会立刻在浏览器中进行更新，并只更新改变的内容，节省开发时间&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对比 &lt;code&gt;Live Reload&lt;/code&gt; 方案，&lt;code&gt;HMR&lt;/code&gt; 体现了其强大之处，实时模块热刷新和保存应用状态，极大的提高了开发效率和开发体验。&lt;/p&gt;
&lt;h2&gt;启用模块热更新&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;HMR&lt;/code&gt; 的启用十分简单，一个带有热更新功能的 &lt;code&gt;webpack.config.js&lt;/code&gt; 文件的配置如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const path = require(&apos;path&apos;);

module.exports = {
    // ...
    entry: {
        app: [&apos;./src/index.js&apos;]
    },
    devServer: {
        contentBase: path.resolve(__dirname, &apos;dist&apos;),
        hot: true,
        historyApiFallback: true,
        compress: true
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;src/index.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (module.hot) {
    module.hot.accept();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;原理&lt;/h2&gt;
&lt;p&gt;网上关于 &lt;code&gt;Webpack&lt;/code&gt; 热更新原理文章很多，这里就不详细描述了，推荐几个。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tsejx.github.io/webpack-guidebook/principle-analysis/operational-principle/hot-module-replacement&quot;&gt;模块热更新&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6844904008432222215&quot;&gt;轻松理解 webpack 热更新原理&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6844904008432222215&quot;&gt;Webpack HMR 原理解析&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;调试&lt;/h2&gt;
&lt;h3&gt;npm link&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ git clone https://github.com/webpack/webpack-dev-server.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一定要找到你项目中对应的版本包，对号入座噢，否则会报错，把 &lt;code&gt;webpack-dev-server&lt;/code&gt; 项目拉下来之后，尝试在 &lt;code&gt;webpack-dev-server/lib/Server.js&lt;/code&gt; 该文件增加一行 &lt;code&gt;console.log&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/120896791-96d51980-c655-11eb-948c-dd2f3c172414.png&quot; alt=&quot;carbon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后进入根目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd webpack-dev-server
$ npm link
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成软链&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd 项目地址
npm link webpack-dev-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;link 成功之后，会提示下面，更换了 webpack-dev-server 地址&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jiang@JiangdeMacBook-Pro-3 commonVideoClient % cnpm link webpack-dev-server
/Users/jiang/Desktop/commonVideoClient/node_modules/webpack-dev-server -&amp;gt; /usr/local/lib/node_modules/webpack-dev-server -&amp;gt; /Users/jiang/Desktop/webpack-dev-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在项目跑 &lt;code&gt;webpack-dev-server&lt;/code&gt;，在控制台应该就会看到对应的输出了，调试源码非常方便。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm link&lt;/code&gt; 方案，第三方库和项目属于不同的项目，它们有自己的 &lt;code&gt;node_modules&lt;/code&gt;，如果第三方库和项目都使用了同一个依赖，它们会在各自的 &lt;code&gt;node_modules&lt;/code&gt; 去查
找，如果这个依赖不支持多例，应用就会异常。&lt;/p&gt;
&lt;h3&gt;yalc&lt;/h3&gt;
&lt;p&gt;在开发和创作多个包（私有或公共）时，您经常发现自己需要在本地环境中正在处理的其他项目中使用最新/WIP 版本，而无需将这些包发布到远程注册中心。NPM 和 Yarn 使用类似的符号链接包( npm/yarn link)方法解决了这个问题。虽然这在许多情况下可能有效，但它经常带来令人讨厌的约束和依赖解析、文件系统之间的符号链接互操作性等问题。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/120896797-a2284500-c655-11eb-8870-382fd0606309.png&quot; alt=&quot;yalc&quot; /&gt;&lt;/p&gt;
&lt;p&gt;全局安装 yalc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install -g yalc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成 yalc 包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd webpack-dev-server
$ yalc publish
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以在自己本地 &lt;code&gt;/Users/jiang/.yalc/packages/webpack-dev-server&lt;/code&gt;，找到对应的包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd 项目地址
yalc link webpack-dev-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;link 后，可以在自己项目下，找到 &lt;code&gt;.yalc&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;每次手动修改第三方库的代码，都需要手动 link，就很麻烦，对不对？ok，神器来了，&lt;code&gt;nodemon&lt;/code&gt;，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install -g nodemon

nodemon
--ignore dist/
--ignore node_modules/
--watch lib # 观察目录
-C # 只在变更后执行，首次启动不执行命令
-e js,ts,html,less,scss 监控指定后缀名的文件
--debug # 调试
-x &quot;yalc publish&quot; 自定义命令
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，我们来试试这个工具，在 &lt;code&gt;webpack-dev-server&lt;/code&gt;，新增三行可执行命令&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/120896803-a9e7e980-c655-11eb-8ffe-1ad8739681c4.png&quot; alt=&quot;carbon2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;运行下 &lt;code&gt;npm run watch&lt;/code&gt;，然后每次修改，都会自动更新，是不是很舒服？&lt;/p&gt;
&lt;p&gt;&amp;lt;img width=&quot;119&quot; alt=&quot;WeChat7c8e2813667093e82dc47a836e6d5cdb&quot; src=&quot;https://user-images.githubusercontent.com/16217324/120896810-b1a78e00-c655-11eb-8c4f-3a4101336d7d.png&quot;&amp;gt;&lt;/p&gt;
&lt;h3&gt;网页调试&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/120896814-b8360580-c655-11eb-80a0-e2c925c90bac.png&quot; alt=&quot;WechatIMG1776&quot; /&gt;&lt;/p&gt;
&lt;p&gt;找到对应的文件位置和代码行数，通过浏览器进行断点调试，这个就不展开讲了。&lt;/p&gt;
&lt;h2&gt;找到问题&lt;/h2&gt;
&lt;p&gt;经过一番折腾，升级 &lt;code&gt;webpack-dev-server@v4&lt;/code&gt;，原理分析，源码调试，与之前正常的单页应用进行对比，发现都是正常的，还是不行，我就郁闷了，为何呢？突然之间，我悟了，好像多页应用没有在入口进行 &lt;code&gt;module.hot&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;之前在 &lt;code&gt;app.jsx&lt;/code&gt; 中写的 &lt;code&gt;module.hot&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/120896817-bf5d1380-c655-11eb-84b1-c8c477e54b15.png&quot; alt=&quot;carbon3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;改在入口文件 进行 &lt;code&gt;module.hot&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/120896823-c421c780-c655-11eb-92b8-44c6d46647a3.png&quot; alt=&quot;carbon4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ok，成功，喜大普奔。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/120896832-cab03f00-c655-11eb-8ee6-0713c6b92432.jpeg&quot; alt=&quot;WechatIMG1780&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;带着问题，阅读源码是最高效的，这样你在阅读源码的过程中也不会感到无聊，因为你是要解决问题，才会去看源码，对于不懂的代码，一点一点调试，一步一步走下去，再结合现有的一些原理文章（站在巨人的肩膀上）就会找到答案。这次的经历，也算很有意思，感谢小伙伴们的阅读，喜欢的可以点个赞噢 🌟 ～&lt;/p&gt;
</content:encoded><category>Webpack</category><author>江辰</author></item><item><title>如何搭建前端架构和团队体系</title><link>https://github.com/posts/%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E5%92%8C%E5%9B%A2%E9%98%9F%E4%BD%93%E7%B3%BB/</link><guid isPermaLink="true">https://github.com/posts/%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E5%92%8C%E5%9B%A2%E9%98%9F%E4%BD%93%E7%B3%BB/</guid><pubDate>Mon, 03 May 2021 08:48:45 GMT</pubDate><content:encoded>&lt;p&gt;本文首发于微信公众号：野生程序猿江辰&lt;/p&gt;
&lt;p&gt;欢迎大家点赞，收藏，关注&lt;/p&gt;
&lt;h2&gt;入职时的环境&lt;/h2&gt;
&lt;p&gt;这是一家做保险和金融行业的公司，属于传统行业的科技公司，有点外包的性质，当然，也有自己的 &lt;code&gt;SaaS&lt;/code&gt; 产品，由于是传统行业的公司，技术栈相对互联网公司来说，稍微落后一点。我刚来的时候，上一个前端要辞职了，然后做对接工作（告诉我，有啥问题，直接搜代码），我算是接盘侠，前任留下的屎山。其他的，大概有以下几点：&lt;/p&gt;
&lt;h3&gt;前端组 4 个人&lt;/h3&gt;
&lt;p&gt;其中一个归 CTO（做后端） 管，另外两个在广东，我入职的时候，也没有确认，到底要不要带人。我来的时候，就已经在了，后面我领导跟我说，要带下他们，我当时压根就没有带人的想法，也是个坑。&lt;/p&gt;
&lt;h3&gt;技术负债重&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;没有前端工程化体系，开发周期长，开发质量差，维护困难&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;前后端混合项目，剥离前端代码没有剥离干净，后端很多文件都在，不知道重不重要，前端代码运行在服务端，每次修改一行代码，看效果，需要拖到服务器上进行编译，编译大概 1-2 分钟左右，非常痛苦。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完全熟悉该项目的人员已离职（技术和产品），项目交接没有处理好。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;业务逻辑非常混乱，没有相关的产品流程图，全凭记忆。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务器上运行的 &lt;code&gt;Node&lt;/code&gt; 版本非常低，到现在还是 &lt;code&gt;8.x&lt;/code&gt;，各种低版本的库都在，比如 &lt;code&gt;Ant Design&lt;/code&gt; 用的 &lt;code&gt;3.6.2&lt;/code&gt;，在项目开发中遇到穿梭框无法进行树状显示(代码一摸一样，在高版本 &lt;code&gt;3.19.2&lt;/code&gt;，可以显示)。又比如还有这种 &lt;code&gt;&quot;translate.js&quot;: &quot;git+https://github.com/MichelSimonot/translate.js.git”&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;尝试过升级库和卸载其他库，各种报错。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码缺乏注释，一个文件几千行，对 &lt;code&gt;React&lt;/code&gt;，&lt;code&gt;Redux&lt;/code&gt; 使用，欠缺理解。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;有过一次”爆炸”，此项目如果再继续迭代下去，随时可能继续”爆炸”，现在已经是在踩雷开发阶段。&lt;/p&gt;
&lt;p&gt;在 2019 年 10 月 18 号，24：00 发生生产事故，事故表现为，操作特定页面，浏览器崩溃，卡死。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/e2e8acf9-be35-44e7-ab1a-805f0ad50222.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;脚本执行时间非常长，后面经查，是由以下代码引起

```
actions.getAgentListByPage({
    companyId: currentAgent.companyId,
    pageIndex: 1,
    pageSize: 20000,
    searchProvince: currentAgent.province,
    searchCity: currentAgent.city
})
```

页面很多地方存在请求 **_pageSize：20000_** 的情况，该代码是由前任前端编写，具体为何写出这样的代码，原因未知，处理方案给到后端解决，前端配合加入 `workbench` 字段，凌晨 1 点左右得到解决。
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一套项目上，运行着两套系统。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打包出来的项目代码体积有 &lt;strong&gt;49.5MB&lt;/strong&gt;，页面首次加载耗时 &lt;strong&gt;11.4 min&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/9f2425be-5187-4bc0-b41e-293ca6185014.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;项目人员能力较弱&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;测试同学报备 BUG，没有记录可复现步骤。&lt;/li&gt;
&lt;li&gt;任务管理工具平台没有真正利用起来，相关项目需求，BUG 没有整理起来放在上面。&lt;/li&gt;
&lt;li&gt;产品不理解大概的技术实现，没有把产品文档梳理，留存下来，不理解客户的真正需求，以至于技术实现比较鸡肋。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;前后端接口对接，没有相关的文档&lt;/h3&gt;
&lt;h3&gt;产品画的原形 和 UI 设计稿不规范&lt;/h3&gt;
&lt;p&gt;基于以上的原因，向领导提出过重构，没有同意，我认为可能有两个方面的顾虑&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从人力资源条件来讲，并不允许。&lt;/li&gt;
&lt;li&gt;从公司战略角度来讲，能挣钱的项目就是好项目。但是，这并不影响我建设前端工程化体系。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;列举了以上的这些点，烂摊子太多了，好在有一个点，领导的支持力度还不错，下面就看我是如何搭建的。&lt;/p&gt;
&lt;h2&gt;明确自己的任务&lt;/h2&gt;
&lt;p&gt;前端技术建设的核心目的，是为了提高开发效率，保证开发质量，为保障项目高质量按时交付，同时兼顾考虑中长期研发实际情况，结合团队实际能力，为未来做技术储备，为业务发展提供更多的可能性，大概将自己的分为以下四类&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基础架构设计&lt;/strong&gt;，主要目的是从架构层面出发，通过流程化设计，规避常见问题，提高开发效率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工程化设计&lt;/strong&gt;，与代码强相关，主要目的是提高代码质量，增强代码的长期可维护性，降低开发时间和成本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;团队管理&lt;/strong&gt;，通过合理有效的团队管理，提高团队人效比，为未来项目研发、技术发展，进行人才储备、技术研发。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;项目管理&lt;/strong&gt;，进行合理的项目管理，合适的工时排期和迭代计划，提高项目交付质量和效率。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;如何解决&lt;/h2&gt;
&lt;p&gt;首先，要对现有的问题进行梳理归纳，按照问题的优先级进行排序，然后，分阶段性目标进行实现，对于上面的问题，我大概整理了一张表格&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;优先级&lt;/th&gt;
&lt;th&gt;成本&lt;/th&gt;
&lt;th&gt;目标&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;如何打造前端工程化体系&lt;/td&gt;
&lt;td&gt;p0&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;提升整个前端团队的开发效率、按时交付、保证交付质量。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;如何进行团队管理&lt;/td&gt;
&lt;td&gt;p0&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;进行人才储备，提高团队人员技术能力&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;如何进行项目管理&lt;/td&gt;
&lt;td&gt;p1&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;掌控全局，知道项目下的人都在做什么，资源协调&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;团队管理体系搭建&lt;/h2&gt;
&lt;h3&gt;人员管理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;初来乍到&lt;/strong&gt;，首先就是跟大家一起聊聊天，了解他/她的想法，以及个人情况、技术能力、兴趣爱好、性格特点等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;团建聚餐&lt;/strong&gt;，经常请大家喝奶茶/咖啡，不定时的组织活动，通常是聚餐（个人出钱），为下面的工作，好开展。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;导师帮带&lt;/strong&gt;，新人进来后，安排一个人带着他，答复常见问题，由简单的需求再到核心模块的负责，一点一点施展压力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;新人适应&lt;/strong&gt;，负责安排新成员的发展方向，并在新人入职的前几周，了解项目框架和开发模式，再安排其做基于现有页面的优化，帮助其了解不同人负责的业务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;责任划分&lt;/strong&gt;，明确团队里人员定位，并使其知晓，根据成员能力不同，态度不同，安排适合其的任务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;前端周会&lt;/strong&gt;，每周一次，组织大家开前端周会，在这个会上，过下大家目前手头上的事情，有没有遇到什么问题，需要协调的一些资源，进度把控等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;技术分享&lt;/strong&gt;，不定时的前端技术分享，主题不限，并把相关分享后的资料，上传到前端文档管理，方便日后的人员进行查看。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;权限管理&lt;/h3&gt;
&lt;p&gt;主要是指代码权限控制，目的是确保代码安全，问题可控可避免可追溯&lt;/p&gt;
&lt;p&gt;具体管理举措有以下几条：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;公司仓库&lt;/strong&gt;，代码属于公司财产，对代码进行权限隔离，启用内网 &lt;code&gt;GitLab&lt;/code&gt;，默认关闭所有外网访问权限，针对每个项目，按实际需要给开发赋予指定权限。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;提交权限&lt;/strong&gt;，允许开发在自己仓库下提交，但涉及到公司仓库的合并，需要发起 &lt;code&gt;PR&lt;/code&gt;，然后在组长进行 &lt;code&gt;CR&lt;/code&gt; 后，才能提交到主仓库。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;发布权限&lt;/strong&gt;，对于将要发布到生产环境，权限给到组长，只允许组长进行发布。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;前端工程化体系搭建&lt;/h2&gt;
&lt;p&gt;刚入职的时候，由于上面的项目框架问题太多，之前也尝试过解决，但，解决不了，领导也意识到了这点，而且也有新项目进来，就让我重新搞一套项目框架。所以，我自研了一套基于 &lt;code&gt;Webpack&lt;/code&gt; 的项目框架和工程化体系，做这件事的目的，就如我上面提到过的一样，提升整个前端团队的开发效率、按时交付、保证交付质量。&lt;/p&gt;
&lt;h3&gt;基础架构设计&lt;/h3&gt;
&lt;h4&gt;前后端接口对接&lt;/h4&gt;
&lt;p&gt;前后端开发联调有一个严重问题，就是后端接口变动或者字段改动时，没有在事前事后通知相应前端开发，测试人员，导致效率底下，并且会出现各种异常情况。&lt;/p&gt;
&lt;p&gt;因此，通过梳理开发流程，出接口文档，作为对接标准。&lt;/p&gt;
&lt;p&gt;我们使用 &lt;code&gt;apiDoc&lt;/code&gt; 来作为前后端联调标准。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/08f2e5a6-f707-400c-8117-03bb078109e0.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但在实际情况中，还是会有一些接口文档和实际接口不符的情况发生，导致一些问题产生，这个我们也在思考。&lt;/p&gt;
&lt;h4&gt;Git 分支管理规范化&lt;/h4&gt;
&lt;p&gt;我们使用的是 &lt;code&gt;Git Flow&lt;/code&gt; 分支管理策略&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Git Flow&lt;/code&gt; 最开始是由 &lt;code&gt;Vincent Driessen&lt;/code&gt; 发行并广受欢迎，这个模型是在 2010 年构思出来的，而现在距今已有 10 多年了，而 &lt;code&gt;Git&lt;/code&gt; 本身才诞生不久。在过去的十年中，&lt;code&gt;Git Flow&lt;/code&gt; 在许多软件团队中非常流行&lt;/p&gt;
&lt;h5&gt;分支命名规范&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;master 分支：master 分支只有一个，名称即为 master。GitHub 现在叫 main&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;develop 分支：develop 分支只有一个，名称即为 develop&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;feature 分支：feature/&amp;lt;功能名&amp;gt;，例如：feature/login，以便其他人可以看到你的工作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hotfix 分支：hotfix/日期，例如：hotfix/0104&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;分支说明&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;master || main 分支：存储正式发布的产品，&lt;code&gt;master || main&lt;/code&gt; 分支上的产品要求随时处于可部署状态。&lt;code&gt;master || main&lt;/code&gt; 分支只能通过与其他分支合并来更新内容，禁止直接在 &lt;code&gt;master || main&lt;/code&gt; 分支进行修改。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;develop 分支：汇总开发者完成的工作成果，&lt;code&gt;develop&lt;/code&gt; 分支上的产品可以是缺失功能模块的半成品，但是已有的功能模块不能是半成品。&lt;code&gt;develop&lt;/code&gt; 分支只能通过与其他分支合并来更新内容，禁止直接在 &lt;code&gt;develop&lt;/code&gt; 分支进行修改。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;feature 分支：当要开发新功能时，从 master 分支创建一个新的 &lt;code&gt;feature&lt;/code&gt; 分支，并在 &lt;code&gt;feature&lt;/code&gt; 分支上进行开发。开发完成后，需要将该 &lt;code&gt;feature&lt;/code&gt; 分支合并到 &lt;code&gt;develop&lt;/code&gt; 分支，最后删除该 &lt;code&gt;feature&lt;/code&gt; 分支。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;release 分支：当 &lt;code&gt;develop&lt;/code&gt; 分支上的项目准备发布时，从 &lt;code&gt;develop&lt;/code&gt; 分支上创建一个新的 &lt;code&gt;release&lt;/code&gt; 分支，新建的 &lt;code&gt;release&lt;/code&gt; 分支只能进行质量测试、bug 修复、文档生成等面向发布的任务，不能再添加功能。这一系列发布任务完成后，需要将 &lt;code&gt;release&lt;/code&gt; 分支合并到 &lt;code&gt;master&lt;/code&gt; 分支上，并根据版本号为 &lt;code&gt;master&lt;/code&gt; 分支添加 &lt;code&gt;tag&lt;/code&gt;，然后将 &lt;code&gt;release&lt;/code&gt; 分支创建以来的修改合并回 &lt;code&gt;develop&lt;/code&gt; 分支，最后删除 &lt;code&gt;release&lt;/code&gt; 分支。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hotfix 分支：当 &lt;code&gt;master&lt;/code&gt; 分支中的产品出现需要立即修复的 bug 时，从 &lt;code&gt;master&lt;/code&gt; 分支上创建一个新的 &lt;code&gt;hotfix&lt;/code&gt; 分支，并在 &lt;code&gt;hotfix&lt;/code&gt; 分支上进行 BUG 修复。修复完成后，需要将 &lt;code&gt;hotfix&lt;/code&gt; 分支合并到 &lt;code&gt;master&lt;/code&gt; 分支和 &lt;code&gt;develop&lt;/code&gt; 分支，并为 &lt;code&gt;master&lt;/code&gt; 分支添加新的版本号 &lt;code&gt;tag&lt;/code&gt;，最后删除 &lt;code&gt;hotfix&lt;/code&gt; 分支。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;提交信息规范&lt;/h5&gt;
&lt;p&gt;提交信息应该描述“做了什么”和“这么做的原因”，必要时还可以加上“造成的影响”，主要由 &lt;strong&gt;3&lt;/strong&gt; 个部分组成：&lt;code&gt;Header&lt;/code&gt;、&lt;code&gt;Body&lt;/code&gt; 和 &lt;code&gt;Footer&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;Header
Header 部分只有 1 行，格式为&lt;code&gt;&amp;lt;type&amp;gt;(&amp;lt;scope&amp;gt;): &amp;lt;subject&amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;type 用于说明提交的类型，共有 8 个候选值：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;feat：新功能（feature）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;fix：问题修复&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;docs：文档&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;style：调整格式（不影响代码运行）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;refactor：重构&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;test：增加测试&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;chore：构建过程或辅助工具的变动&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;revert：撤销以前的提交&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;scope 用于说明提交的影响范围，内容根据具体项目而定。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;subject 用于概括提交内容。&lt;/p&gt;
&lt;p&gt;Body 省略&lt;/p&gt;
&lt;p&gt;Footer 省略&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/9822ee48-ca25-4645-bf44-dda042512c81.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样做起来的好处，这个项目下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于分支，每个人在做什么，我看分支就清楚。&lt;/li&gt;
&lt;li&gt;对于修改内容，看前缀就知道这个文件改动了什么。&lt;/li&gt;
&lt;li&gt;对于版本迭代，看 &lt;code&gt;Tag&lt;/code&gt; 都上线了什么内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总之，一目了然。&lt;/p&gt;
&lt;h4&gt;开发人员基本流程&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/1dbd6341-eb84-4dea-baca-42aa5b94dbb6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在这个流程中，开发人员只对个人仓库拥有可控权，无法直接改变公司仓库代码，当需要提交到公司仓库下时，需要发起 &lt;code&gt;PR&lt;/code&gt; 请求，经过组长 &lt;code&gt;CR&lt;/code&gt; 后，将其代码合并到公司仓库下。&lt;/p&gt;
&lt;p&gt;主分支代码和线上代码进行隔离，由组长将指定版本的 &lt;code&gt;Tag&lt;/code&gt; 发布到生产环境，再通过运营人员直接从 &lt;code&gt;GitLab&lt;/code&gt; 上拉取指定的 &lt;code&gt;Tag&lt;/code&gt;，然后打包发布。&lt;/p&gt;
&lt;p&gt;通过以上流程，前端代码能保证高质量，高稳定性的状态，运行在服务器端。&lt;/p&gt;
&lt;h3&gt;工程化设计&lt;/h3&gt;
&lt;p&gt;要根据实际业务情况和团队规模，技术水平来做，关键是要形成一个闭环，所谓闭环就是从零开始到上线再到迭代的全链路，有很多节点，这些节点需要根据实际情况进行设计，避免过度设计。&lt;/p&gt;
&lt;h4&gt;定制 Webpack 项目框架&lt;/h4&gt;
&lt;p&gt;为何不是 create-react-app&lt;/p&gt;
&lt;p&gt;create-react-app 是基于 &lt;code&gt;webpack&lt;/code&gt; 的打包层方案，包含 &lt;code&gt;build、dev、lint&lt;/code&gt; 等，他在打包层把体验做到了极致，但是不包含路由，不是框架，也不支持配置。所以，如果大家想基于他修改部分配置，或者希望在打包层之外也做技术收敛时，就会遇到困难。&lt;/p&gt;
&lt;p&gt;为何不是 umi&lt;/p&gt;
&lt;p&gt;umi 提供的功能很多，这也导致它太过于臃肿。而且你还要去学它的封装化配置，而不是学原生第三方库的配置，如果你只想要一些简单的功能，追求更高的可玩性，哪 umi 不太适合。&lt;/p&gt;
&lt;p&gt;所以，我自己定制了一套脚手架，实现了以下功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;快速上手&lt;/strong&gt;，只要了解 React、Mobx、Webpack 和 React Router，就可以快速搭建中后台管理平台&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路由系统&lt;/strong&gt;，基于 react-router 实现的路由系统&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loading&lt;/strong&gt;，不需要重复写组件 Loading 判断&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;国际化&lt;/strong&gt;，基于 react-intl-universal 实现的国际化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络请求&lt;/strong&gt;，基于 axios 实现的请求拦截&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;页面交互&lt;/strong&gt;，基于 mobx 实现的数据交互方式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UI&lt;/strong&gt;，使用业界最著名的 ant-design&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码规范校验&lt;/strong&gt;，使用 eslint、pre-commit、lint-staged、prettier、stylelint&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模拟请求数据&lt;/strong&gt;，基于 mockjs 实现&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;打包工具&lt;/strong&gt;，目前最流行的 Webpack&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解决了以下的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;约束开发人员代码规范&lt;/li&gt;
&lt;li&gt;方便提供给其他开发使用标准的脚手架，并提供技术支持&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完成整个编码过程的一个闭环：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编码前：编码规范，最佳实践&lt;/li&gt;
&lt;li&gt;编码中：自研项目框架、代码校验&lt;/li&gt;
&lt;li&gt;编码后：发布部署工具 JenKins，手动发布或 CI/CD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些节点要视实际情况，以最小成本去做，然后逐步升级。比如编码规范，我们是采用业界比较著名的 &lt;code&gt;Airbnb JavaScript&lt;/code&gt; 代码规范，搭配&lt;code&gt;eslint、pre-commit、lint-staged、prettier、stylelint&lt;/code&gt; 去进行约束。&lt;/p&gt;
&lt;p&gt;这套项目框架，目前开发体验非常爽，在我司多个产品线上，投入使用，并已开源，&lt;strong&gt;&lt;a href=&quot;https://github.com/xuya227939/tristana.git&quot;&gt;框架地址&lt;/a&gt;&lt;/strong&gt;，演示页面比较少，大佬们觉得不错的话，可以给个 Star 🌟，也欢迎给项目提 issues ～&lt;/p&gt;
&lt;h4&gt;业务场景&lt;/h4&gt;
&lt;p&gt;我们是做 &lt;code&gt;ToB&lt;/code&gt; 业务，存在页面上大量使用表单的场景，所以，把我们的表单页面做成可配置化，实现了大部分页面表单配置化，减少前端人力资源投入。&lt;/p&gt;
&lt;p&gt;针对公司的实际业务场景，其他子系统不会特别复杂，页面也不会多，共享一套账号体系，这里采用的思路是只有一个项目，不分主从系统，通过 &lt;code&gt;Webpack&lt;/code&gt; 配置多页面，不同的子系统进入的首页内容不一样，加载内容不一样，菜单导航，则通过后端对每个租户进行区分，来做到租户看到的菜单系统不一样。&lt;/p&gt;
&lt;p&gt;如果子系统特别复杂，有主从系统概念，可以考虑使用微服务设计，这里不做过多介绍。&lt;/p&gt;
&lt;h4&gt;静态资源&lt;/h4&gt;
&lt;p&gt;除了业务代码以外，前端还会有一些公共静态资源，例如 &lt;code&gt;React&lt;/code&gt; 资源，&lt;code&gt;Ant Design&lt;/code&gt; 资源，&lt;code&gt;BizCharts&lt;/code&gt; 资源，以及一些图片文件等。&lt;/p&gt;
&lt;p&gt;对于这些文件，是所有项目所共享的，假如这些文件分散在各个项目里，既没必要，也容易导致不同项目依赖文件不统一。&lt;/p&gt;
&lt;p&gt;我们是放在 &lt;code&gt;S3&lt;/code&gt; 上，做 &lt;code&gt;CDN&lt;/code&gt; 静态资源加速，然后前端项目通过引入&lt;code&gt;url&lt;/code&gt; 来使用这些资源，这样可以减轻自己的服务器网络带宽消耗。&lt;/p&gt;
&lt;h2&gt;项目管理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;任务分配&lt;/strong&gt;，产品把相关的需求，经过讨论，可行性分析，通过项目管理工具，放到迭代计划中，录入开发工时，测试工时。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;文档管理&lt;/strong&gt;，采用项目管理工具自带的文档，要求做到文档可以团队编辑，可以查看到编辑历史。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;项目周会&lt;/strong&gt;，过大家手上目前的迭代进度，遇到的问题，需要协调的资源，风险控制等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;项目复盘&lt;/strong&gt;，复盘首先是要做的是事实陈述，开始诊断、分析存在差异的原因，找出导致成功或失败的根本原因后进行规律总结。明白为什么会成功、哪些关键行为起了作用，这些行为有没有适用条件，对于提高后续行动的成功率有没有价值。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;熟悉产品线业务&lt;/h3&gt;
&lt;p&gt;所谓技术服务业务，找产品了解现有的业务流程以及痛点，甚至未来要做的一些产品规划，好的技术架构，要考虑各种各样的业务场景，怎样才能结合业务的复杂度，设计出颗粒度更加细化的组件。&lt;/p&gt;
&lt;p&gt;画出产品架构图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/697d90c7-35af-4deb-8939-d3123e405f6d.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;提升相关人员的能力&lt;/h3&gt;
&lt;h4&gt;产品人员&lt;/h4&gt;
&lt;p&gt;需求频繁且混乱，决策摇摆不定、动辄推倒重来。市面上一个好的产品经理是很贵的，没个三四万是拿不下一个真正靠谱的能抗住复杂产品线的产品经理，但是很多公司老板是不愿意花这个钱，一般就会招个工作一两年的产品经理先过来，顶个位置把这个工具给做出来就行了。恰恰因为这样一个认知导致产品经理这一层他既没话语权，又不能让自己闲着，所以层出不穷的需求全堆上来了，而对于公司长久型的产品架构就把控不住，如果一个产品经理无法起到，上对客户负责，下对开发负责，不会对所有需求进行筛选，把需求只会丢给开发，不会进行工时把控和质量把控。甚至对现有产品有什么功能，都不了解，那么就不是一个合格的产品。&lt;/p&gt;
&lt;p&gt;所以对产品经理的要求非常严格，因为一个公司，如果战略方向把握住了，那么核心是要看产品，能否把握住市场方向，非常关键。这样才能决定你是否能占有市场，由于我司是做一个 &lt;code&gt;ToB SaaS&lt;/code&gt; 化的平台，所以，必须要求产品经理清楚的了解客户实际需求，需求背后的实际场景，提炼出来哪些是共性的需求，哪些是客户定制化的需求，然后再讨论，再进行落地实际的开发。&lt;/p&gt;
&lt;h4&gt;测试人员&lt;/h4&gt;
&lt;p&gt;对测试人员，尽量覆盖全所有场景，保证核心流程畅通，要求能找到复现步骤，提高开发解决 BUG 的效率。&lt;/p&gt;
&lt;h3&gt;设计规范&lt;/h3&gt;
&lt;p&gt;由于我司采用的是 &lt;code&gt;Ant Design&lt;/code&gt; UI 库，所以设计标准，尽量都是按照 &lt;code&gt;Ant Design&lt;/code&gt; 现成组件和样式来做，避免开发二次修改，参考这个链接 &lt;a href=&quot;https://ant.design/docs/spec/introduce-cn&quot;&gt;Ant Design 设计原则&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;某个列表页&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/47711af2-e2f7-4c1f-9fda-fce7d91a8511.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;普通的列表，和设计，产品都约定好，上面是筛选，下面是按钮，底部是表格展示。&lt;/p&gt;
&lt;p&gt;某个详情页&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/782bea66-8ca7-4b0f-8dee-84eb68b5a24d.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;详情页大量会使用到表单，所以直接使用 &lt;code&gt;Ant Design&lt;/code&gt; 的 &lt;code&gt;From&lt;/code&gt; 表单组件。&lt;/p&gt;
&lt;p&gt;表单每行放多少个，都是以 &lt;code&gt;Ant Design&lt;/code&gt; 组件来的。&lt;/p&gt;
&lt;p&gt;这样带来的好处就是尽量避免定制化的开发，所有列表和详情都是按照这种风格来进行开发。&lt;/p&gt;
&lt;h2&gt;成果&lt;/h2&gt;
&lt;h3&gt;开发效率&lt;/h3&gt;
&lt;p&gt;组内人员开发效率，较之前提升了一倍左右，普通的列表页面（搜索、展示、弹窗 ），包含接口联调 + 自测，大概 1 天左右完成，详情页面，复杂一点的表单交互，表单组件联动，大概在 2 天左右完成，包含接口联调 + 自测，目前我们也在探索 &lt;code&gt;Vite&lt;/code&gt;，&lt;code&gt;Snowpack&lt;/code&gt;，极大的提升开发体验。&lt;/p&gt;
&lt;h3&gt;系统情况&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SaaS&lt;/code&gt; 系统，首次无缓存加载耗时 &lt;strong&gt;3.55s&lt;/strong&gt; ，三个系统（ 30 多个页面，14 个公用组件）打包出来的体积在 &lt;strong&gt;9.3MB&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当然还有优化空间&lt;/p&gt;
&lt;h3&gt;设计规范&lt;/h3&gt;
&lt;p&gt;目前大部分页面不需要设计资源投入，尽量按照 &lt;code&gt;Ant Design&lt;/code&gt; 标准来，减少设计人员的工作投入。&lt;/p&gt;
&lt;h3&gt;项目文档&lt;/h3&gt;
&lt;p&gt;目前所有的产品文档，技术文档都非常规范，可以溯源，以及当时在什么样的场景下，为什么要做出这样的解决方案。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;上面这些，包含其他的，大概花了一年多的时间，建设完成，我们目前的基建状况如下表所示&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/af51d70d-eddb-41be-868b-084028f8dec0.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;全文完&lt;/p&gt;
</content:encoded><category>前端架构</category><author>江辰</author></item><item><title>4 年前端如何选择方向</title><link>https://github.com/posts/4-%E5%B9%B4%E5%89%8D%E7%AB%AF%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E6%96%B9%E5%90%91/</link><guid isPermaLink="true">https://github.com/posts/4-%E5%B9%B4%E5%89%8D%E7%AB%AF%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E6%96%B9%E5%90%91/</guid><pubDate>Wed, 28 Apr 2021 17:45:28 GMT</pubDate><content:encoded>&lt;p&gt;背景：在第一家公司呆了两年（含实习），由于技术无法得到成长，拿着存款 1 万，裸辞。迫于不会面试，换过三家公司，在第二家呆了六个月，身体不适，离开。当前这家，已呆满两年，想尝试进入大厂，也面临职业选择问题。&lt;/p&gt;
&lt;h2&gt;最有收获的三个问题与回复&lt;/h2&gt;
&lt;h4&gt;问题一：我如今四年工作经验，目前担任的是项目负责人/前端组长，关于未来的方向是选择前端技术专家还是项目管理？&lt;/h4&gt;
&lt;p&gt;我的想法：偏项目管理去走，因为我目前在做项目管理的一些事，负责把控项目进度，人力资源规划等。&lt;/p&gt;
&lt;p&gt;Scott 的分析与建议：Scott 分析了我的技术深度和广度，项目管理上是否有一套自己的方法论。&lt;/p&gt;
&lt;p&gt;建议我往前端技术专家这条路走，理由是年轻，技术深度也不够，如果盲目转型项目管理，遇到一些技术比较厉害的人，很难让人服众，比如我现在的老大，完全不懂技术，对项目开发排期，基本是由开发说了算，因为你心理没底，这个技术到底要多少天？如果你技术吃透了，别人就忽悠不了你。&lt;/p&gt;
&lt;h4&gt;问题二：项目负责人如何去管理项目，如何建立自己的威信？&lt;/h4&gt;
&lt;p&gt;我的想法：无。&lt;/p&gt;
&lt;p&gt;Scott 的分析与建议：Scott 分析了我的团队规模，是否有实权。&lt;/p&gt;
&lt;p&gt;建议我少管理，现在的我还年轻，如果太早介入项目管理，反而会导致我的侧重点在项目上，而非技术，这样会带来很严重的问题，技术深度不够。会让我在技术上荒废掉，哪样就没啥意义，何况现在前端的技术迭代非常快（前端娱乐圈）。&lt;/p&gt;
&lt;p&gt;担任一个中间人，传递消息，知道每个人手头上的事，排期计划，遇到有何困难，汇报上去即可。&lt;/p&gt;
&lt;p&gt;因为没有实权，没有 KPI 考核权，底下的人，压根不听你的，无法树立威严。&lt;/p&gt;
&lt;h4&gt;问题三：我最近想跳槽进大厂，请问有何建议？&lt;/h4&gt;
&lt;p&gt;我的想法：想今年面一波所有中小公司，然后尝试大厂，如果能拿到合适的 offer 就进去。&lt;/p&gt;
&lt;p&gt;Scott 的分析与建议：Scott 分析了我的工作履历和技术能力，建议是不跳槽，因为我之前的跳槽过于频繁，工作履历来不太好看，让别人会觉得你是个不稳定的人，而大厂对于这方面看得很重，另一方面，我今年也没太多时间去准备面试。&lt;/p&gt;
&lt;h2&gt;聊完的整体感受&lt;/h2&gt;
&lt;p&gt;让人有种豁然开朗的感觉，之前我太侧重项目管理，以致我的技术成长反而慢下来了，一直在想项目管理应该如何去管。但我又没有实权，底下的人，压根就不听我的。现在我知道应该怎么去做了。非常感谢 Scott 的指点。&lt;/p&gt;
&lt;h2&gt;接下来做的事&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;不再去想项目管理上的事，只做一个中间人。&lt;/li&gt;
&lt;li&gt;技术继续深入，把现有技术栈原理吃透，了解新的技术。&lt;/li&gt;
&lt;li&gt;把前端技术栈体系整理出来，打造前端基建和规范.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>职业规划</category><author>江辰</author></item><item><title>关于 tristana</title><link>https://github.com/posts/%E5%85%B3%E4%BA%8E-tristana/</link><guid isPermaLink="true">https://github.com/posts/%E5%85%B3%E4%BA%8E-tristana/</guid><pubDate>Mon, 19 Apr 2021 10:20:19 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;大概在 2019 年的时候，为公司搭建了一套项目框架，截止到今天，用起来很不错，最近 &lt;code&gt;Vite&lt;/code&gt; 太火，折腾了下，花了两天的时间，用 &lt;code&gt;Vite&lt;/code&gt; 替换了 &lt;code&gt;Webpack&lt;/code&gt;（&lt;code&gt;Webpack5&lt;/code&gt;、&lt;code&gt;Webpack4&lt;/code&gt; 都有，切换分支即可），体验直接起飞，基于 &lt;code&gt;Vite&lt;/code&gt; + &lt;code&gt;React&lt;/code&gt; + &lt;code&gt;Ant Design&lt;/code&gt; + &lt;code&gt;Mobx&lt;/code&gt; + &lt;code&gt;ESLint&lt;/code&gt; + &lt;code&gt;TypeScript&lt;/code&gt; 的项目框架。&lt;/p&gt;
&lt;h2&gt;特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;快速开始&lt;/strong&gt;，只要您了解 &lt;code&gt;react&lt;/code&gt;、&lt;code&gt;mobx&lt;/code&gt;、&lt;code&gt;webpack&lt;/code&gt; 和 &lt;code&gt;react router&lt;/code&gt;，就可以快速搭建中后台管理平台。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;路由匹配&lt;/strong&gt;，包括 &lt;code&gt;url&lt;/code&gt; 输入，&lt;code&gt;js&lt;/code&gt; 跳转，菜单切换。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Loading&lt;/strong&gt;，不需要重复写组件 &lt;code&gt;loading&lt;/code&gt; 判断。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://order.downfuture.com/&quot;&gt;tristana&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;项目地址&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xuya227939/tristana.git&quot;&gt;tristana&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;关于命名&lt;/h2&gt;
&lt;p&gt;由于本人非常喜欢玩 LOL 射手小炮，所以叫 tristana&lt;/p&gt;
&lt;h2&gt;能否使用在生产环境？&lt;/h2&gt;
&lt;p&gt;当然，目前我司多个产品线在使用中。&lt;/p&gt;
&lt;h2&gt;启动&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ git clone https://github.com/xuya227939/tristana.git

$ cd tristana

$ git checkout vite

$ npm install

$ npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;打包&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ npm run build
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;是否支持 IE8?&lt;/h2&gt;
&lt;p&gt;不支持&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;大佬们觉得不错的话，可以给个 Star 🌟，也欢迎给项目提 issues ~&lt;/p&gt;
</content:encoded><category>Tristana</category><author>江辰</author></item><item><title>前端模块化</title><link>https://github.com/posts/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96/</link><guid isPermaLink="true">https://github.com/posts/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96/</guid><pubDate>Mon, 12 Apr 2021 21:52:38 GMT</pubDate><content:encoded>&lt;h2&gt;模块化的背景&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Javascript 程序本来很小——在早期，它们大多被用来执行独立的脚本任务，在你的 web 页面需要的地方提供一定交互，所以一般不需要多大的脚本。过了几年，我们现在有了运行大量 Javascript 脚本的复杂程序，还有一些被用在其他环境（例如 Node.js）。因此，近年来，有必要开始考虑提供一种将 JavaScript 程序拆分为可按需导入的单独模块的机制。Node.js 已经提供这个能力很长时间了，还有很多的 Javascript 库和框架 已经开始了模块的使用（例如， CommonJS 和基于 AMD 的其他模块系统 如 RequireJS, 以及最新的 Webpack 和 Babel）。好消息是，最新的浏览器开始原生支持模块功能了，这会是一个好事情 — 浏览器能够最优化加载模块，使它比使用库更有效率：使用库通常需要做额外的客户端处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;什么是模块化?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;将一个复杂的程序依据一定的规则封装成代码块(文件), 并进行组合在一起，代码块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与其它模块通信&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;原生如何实现模块化&lt;/h2&gt;
&lt;h3&gt;function 模式&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;将不同的功能封装成不同的全局函数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;天生的模块化&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;污染全局命名空间, 易引起命名冲突&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;模块成员之间看不出直接关系&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;例子&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function function() {
    //...
}

function function2() {
    //...
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;namespace 模式&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;p&gt;减少了全局变量，解决命名冲突&lt;/p&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;p&gt;数据不安全(外部可以直接修改模块内部的数据)&lt;/p&gt;
&lt;h4&gt;例子&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;let module = {
    name: &apos;Faker&apos;,
    getName() {
        console.log(`${this.name}`)
    }
}

module.name = &apos;other Name&apos; // 能直接修改模块内部的数据
module.getName() // other Name

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;IIFE（立即调用函数表达式） 模式&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;p&gt;数据是私有的, 外部只能通过暴露的方法操作&lt;/p&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;p&gt;如果当前这个模块依赖另一个模块怎么办？&lt;/p&gt;
&lt;h3&gt;引入 script&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;p&gt;相比于使用一个 &lt;code&gt;js&lt;/code&gt; 文件，这种多个 &lt;code&gt;js&lt;/code&gt; 文件实现最简单的模块化的思想是进步的。&lt;/p&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;请求过多&lt;/strong&gt;，如果我们要依赖多个模块，那样就会发送多个请求，导致请求过多&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;依赖关系不明显&lt;/strong&gt;，我们不知道他们的具体依赖关系是什么，因为不了解他们之间的依赖关系导致加载先后顺序出错。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;难以维护&lt;/strong&gt;，以上两种原因就导致了很难维护，很可能出现牵一发而动全身的情况导致项目出现严重的问题。&lt;/p&gt;
&lt;h4&gt;例子&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&quot;jquery.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;jquery_scroller.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;main.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;other1.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;other2.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;other3.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;模块化规范&lt;/h2&gt;
&lt;h3&gt;CommonJS&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Node 应用由模块组成，采用 CommonJS 模块规范。每个文件就是一个模块，有自己的作用域。在一个文件里面定义的变量、函数、类，都是私有的，对其他文件不可见。在服务器端，模块的加载是运行时同步加载的；在浏览器端，模块需要提前编译打包处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;特点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;所有代码都运行在模块作用域，不会污染全局作用域。&lt;/li&gt;
&lt;li&gt;模块可以多次加载，但是只会在第一次加载时运行一次，然后运行结果就被缓存了，以后再加载，就直接读取缓存结果。要想让模块再次运行，必须清除缓存。&lt;/li&gt;
&lt;li&gt;模块加载的顺序，按照其在代码中出现的顺序。&lt;/li&gt;
&lt;li&gt;对外抛出的变量，是一个值的地址，不是引用地址，内部修改的变量对外导出的变量不影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;基本语法&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;暴露模块：&lt;code&gt;module.exports = value&lt;/code&gt; 或 &lt;code&gt;exports.xxx = value&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;引入模块：&lt;code&gt;require(xxx)&lt;/code&gt;, 如果是第三方模块，&lt;code&gt;xxx&lt;/code&gt; 为模块名；如果是自定义模块，&lt;code&gt;xxx&lt;/code&gt; 为模块文件路径&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此处我们有个疑问：&lt;code&gt;CommonJS&lt;/code&gt; 暴露的模块到底是什么? &lt;code&gt;CommonJS&lt;/code&gt; 规范规定，每个模块内部，&lt;code&gt;module&lt;/code&gt; 变量代表当前模块。这个变量是一个对象，它的 &lt;code&gt;exports&lt;/code&gt; 属性（即 module.exports）是对外的接口。加载某个模块，其实是加载该模块的 &lt;code&gt;module.exports&lt;/code&gt; 属性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// example.js
const count = 5;
const add = function () {
    return count;
};
module.exports.count = count;
module.exports.add = add;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面代码通过 &lt;code&gt;module.exports&lt;/code&gt; 输出变量 &lt;code&gt;count&lt;/code&gt; 和函数 &lt;code&gt;add&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const example = require(&apos;./example.js&apos;);
console.log(example.count); // 5
console.log(example.add()); // 5
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;CommonJS 模块的加载机制&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;require&lt;/code&gt; 命令是 &lt;code&gt;CommonJS&lt;/code&gt; 规范之中，用来加载其他模块的命令。它其实不是一个全局命令，而是指向当前模块的 &lt;code&gt;module.require&lt;/code&gt; 命令，而后者又调用 &lt;code&gt;Node&lt;/code&gt; 的内部命令 &lt;code&gt;Module.\_load&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Module._load = function(request, parent, isMain) {
    // 1. 检查 Module._cache，是否缓存之中有指定模块
    // 2. 如果缓存之中没有，就创建一个新的Module实例
    // 3. 将它保存到缓存
    // 4. 使用 module.load() 加载指定的模块文件，
    //    读取文件内容之后，使用 module.compile() 执行文件代码
    // 5. 如果加载/解析过程报错，就从缓存删除该模块
    // 6. 返回该模块的 module.exports
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的第 4 步，采用 module.compile()执行指定模块的脚本，逻辑如下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Module.prototype._compile = function(content, filename) {
    // 1. 生成一个require函数，指向module.require
    // 2. 加载其他辅助方法到require
    // 3. 将文件内容放到一个函数之中，该函数可调用 require
    // 4. 执行该函数
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的第 1 步和第 2 步，&lt;code&gt;require&lt;/code&gt; 函数及其辅助方法主要如下。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;require(): 加载外部模块&lt;/li&gt;
&lt;li&gt;require.resolve()：将模块名解析到一个绝对路径&lt;/li&gt;
&lt;li&gt;require.main：指向主模块&lt;/li&gt;
&lt;li&gt;require.cache：指向所有缓存的模块&lt;/li&gt;
&lt;li&gt;require.extensions：根据文件的后缀名，调用不同的执行函数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一旦 &lt;code&gt;require&lt;/code&gt; 函数准备完毕，整个所要加载的脚本内容，就被放到一个新的函数之中，这样可以避免污染全局环境。该函数的参数包括 &lt;code&gt;require&lt;/code&gt;、&lt;code&gt;module&lt;/code&gt;、&lt;code&gt;exports&lt;/code&gt;，以及其他一些参数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Module.\_compile&lt;/code&gt; 方法是同步执行的，所以 &lt;code&gt;Module.\_load&lt;/code&gt; 要等它执行完成，才会向用户返回 &lt;code&gt;module.exports&lt;/code&gt; 的值。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.js
let counter = require(&apos;./lib&apos;).counter;
let incCounter = require(&apos;./lib&apos;).incCounter;

console.log(counter); // 3
incCounter();
console.log(counter); // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面代码说明，&lt;code&gt;counter&lt;/code&gt; 输出以后，&lt;code&gt;lib.js&lt;/code&gt; 模块内部的变化就影响不到 &lt;code&gt;counter&lt;/code&gt; 了。这是因为 &lt;code&gt;counter&lt;/code&gt; 是一个原始类型的值，会被缓存。除非写成一个函数，才能得到内部变动后的值。&lt;/p&gt;
&lt;h4&gt;CommonJS 模块的缓存&lt;/h4&gt;
&lt;p&gt;第一次加载某个模块时，&lt;code&gt;Node&lt;/code&gt; 会缓存该模块。以后再加载该模块，就直接从缓存取出该模块的 &lt;code&gt;module.exports&lt;/code&gt; 属性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require(&apos;./example.js&apos;);
require(&apos;./example.js&apos;).message = &quot;hello&quot;;
require(&apos;./example.js&apos;).message
// &quot;hello&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面代码中，连续三次使用 &lt;code&gt;require&lt;/code&gt; 命令，加载同一个模块。第二次加载的时候，为输出的对象添加了一个 &lt;code&gt;message&lt;/code&gt; 属性。但是第三次加载的时候，这个 &lt;code&gt;message&lt;/code&gt; 属性依然存在，这就证明 &lt;code&gt;require&lt;/code&gt; 命令并没有重新加载模块文件，而是输出了缓存。&lt;/p&gt;
&lt;p&gt;如果想要多次执行某个模块，可以让该模块输出一个函数，然后每次 &lt;code&gt;require&lt;/code&gt; 这个模块的时候，重新执行一下输出的函数。&lt;/p&gt;
&lt;p&gt;所有缓存的模块保存在 &lt;code&gt;require.cache&lt;/code&gt; 之中，如果想删除模块的缓存，可以像下面这样写。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 删除指定模块的缓存
delete delete require.cache[require.resolve(&quot;./a.js&quot;)];

// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
    delete delete require.cache[require.resolve(key)];
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，缓存是根据绝对路径识别模块的，如果同样的模块名，但是保存在不同的路径，&lt;code&gt;require&lt;/code&gt; 命令还是会重新加载该模块。&lt;/p&gt;
&lt;h4&gt;CommonJS 模块的循环加载&lt;/h4&gt;
&lt;p&gt;如果发生模块的循环加载，即 A 加载 B，B 又加载 A，则 B 将加载 A 的不完整版本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// a.js
exports.x = &apos;a1&apos;;
console.log(&apos;a.js &apos;, require(&apos;./b.js&apos;).x);
exports.x = &apos;a2&apos;;

// b.js
exports.x = &apos;b1&apos;;
console.log(&apos;b.js &apos;, require(&apos;./a.js&apos;).x);
exports.x = &apos;b2&apos;;

// main.js
console.log(&apos;main.js &apos;, require(&apos;./a.js&apos;).x);
console.log(&apos;main.js &apos;, require(&apos;./b.js&apos;).x);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面代码是三个 &lt;code&gt;JavaScript&lt;/code&gt; 文件。其中，&lt;code&gt;a.js&lt;/code&gt; 加载了 &lt;code&gt;b.js&lt;/code&gt;，而 &lt;code&gt;b.js&lt;/code&gt; 又加载 &lt;code&gt;a.js&lt;/code&gt;。这时，&lt;code&gt;Node&lt;/code&gt; 返回 &lt;code&gt;a.js&lt;/code&gt; 的不完整版本，所以执行结果如下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ node main.js
b.js a1
a.js b2
main.js a2
main.js b2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改 &lt;code&gt;main.js&lt;/code&gt;，再次加载 &lt;code&gt;a.js&lt;/code&gt; 和 &lt;code&gt;b.js&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.js
console.log(&apos;main.js &apos;, require(&apos;./a.js&apos;).x);
console.log(&apos;main.js &apos;, require(&apos;./b.js&apos;).x);
console.log(&apos;main.js &apos;, require(&apos;./a.js&apos;).x);
console.log(&apos;main.js &apos;, require(&apos;./b.js&apos;).x);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行上面代码，结果如下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ node main.js
b.js  a1
a.js  b2
main.js  a2
main.js  b2
main.js  a2
main.js  b2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面代码中，第二次加载 &lt;code&gt;a.js&lt;/code&gt; 和 &lt;code&gt;b.js&lt;/code&gt; 时，会直接从缓存读取 &lt;code&gt;exports&lt;/code&gt; 属性，所以 &lt;code&gt;a.js&lt;/code&gt; 和 &lt;code&gt;b.js&lt;/code&gt; 内部的 &lt;code&gt;console.log&lt;/code&gt; 语句都不会执行了。&lt;/p&gt;
&lt;h3&gt;ES6 模块化&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;ES6 模块的设计思想是尽量的静态化，使得编译时就能确定模块的依赖关系，以及输入和输出的变量。CommonJS 和 AMD 模块，都只能在运行时确定这些东西。比如，CommonJS 模块就是对象，输入时必须查找对象属性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;特点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ES6&lt;/code&gt; 模块的设计思想是尽量的静态化，使得编译时就能确定模块的依赖关系，以及输入和输出的变量。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;基本语法&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;export&lt;/code&gt; 命令用于规定模块的对外接口，&lt;code&gt;import&lt;/code&gt; 命令用于输入其他模块提供的功能。&lt;/p&gt;
&lt;p&gt;export&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let basicNum = 0;
let add = function(a, b) {
    return a + b;
};
export { basicNum, add };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;import&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { basicNum, add } from &apos;./math&apos;;
function test(ele) {
    ele.textContent = add(99 + basicNum);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如上例所示，使用 &lt;code&gt;import&lt;/code&gt; 命令的时候，用户需要知道所要加载的变量名或函数名，否则无法加载。为了给用户提供方便，让他们不用阅读文档就能加载模块，就要用到 &lt;code&gt;export default&lt;/code&gt; 命令，为模块指定默认输出。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default function () {
    console.log(&apos;foo&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ES6 模块与 CommonJS 模块的差异&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CommonJS&lt;/code&gt; 模块输出的是一个值的拷贝，&lt;code&gt;ES6&lt;/code&gt; 模块输出的是值的引用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CommonJS&lt;/code&gt; 模块是运行时加载，&lt;code&gt;ES6&lt;/code&gt; 模块是编译时输出接口。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第二个差异是因为 &lt;code&gt;CommonJS&lt;/code&gt; 加载的是一个对象（即 &lt;code&gt;module.exports&lt;/code&gt; 属性），该对象只有在脚本运行完才会生成。而 &lt;code&gt;ES6&lt;/code&gt; 模块不是对象，它的对外接口只是一种静态定义，在代码静态解析阶段就会生成。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;CommonJS&lt;/code&gt; 规范主要用于服务端编程，加载模块是同步的，并不适合在浏览器环境，因为同步意味着阻塞加载，浏览器资源是异步加载的。
&lt;code&gt;ES6&lt;/code&gt; 在语言标准的层面上，实现了模块功能，而且实现得相当简单，完全可以取代 &lt;code&gt;CommonJS&lt;/code&gt; 和 &lt;code&gt;AMD&lt;/code&gt; 规范，成为浏览器和服务器通用的模块解决方案。&lt;/p&gt;
</content:encoded><category>模块化</category><author>江辰</author></item><item><title>SaaS架构图</title><link>https://github.com/posts/saas%E6%9E%B6%E6%9E%84%E5%9B%BE/</link><guid isPermaLink="true">https://github.com/posts/saas%E6%9E%B6%E6%9E%84%E5%9B%BE/</guid><pubDate>Tue, 30 Mar 2021 11:10:43 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/112928035-87fb5380-9148-11eb-8035-dbf7e9c311f5.png&quot; alt=&quot;通用系统架构图&quot; /&gt;&lt;/p&gt;
</content:encoded><category>SaaS</category><author>江辰</author></item><item><title>从零搭建 Vite + React 开发环境</title><link>https://github.com/posts/%E4%BB%8E%E9%9B%B6%E6%90%AD%E5%BB%BA-vite--react-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/</link><guid isPermaLink="true">https://github.com/posts/%E4%BB%8E%E9%9B%B6%E6%90%AD%E5%BB%BA-vite--react-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/</guid><pubDate>Sun, 14 Feb 2021 12:40:55 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;大概在 2019 年，自己搭建 &lt;code&gt;React&lt;/code&gt; 开发环境的想法萌芽，到目前为止，公司的很多项目上，也在使用中，比较稳定。为什么要自己造轮子？起初是因为自己并不满意市面上的脚手架。另外，造轮子对于自己也有一些技术上的帮助，学别人二次封装的东西，不如直接使用底层的库，这样也有助于自己系统的学习一遍知识，最近 &lt;code&gt;Vite&lt;/code&gt; 很火，所以用 &lt;code&gt;Vite&lt;/code&gt; 搭建一波，废话不多说，直接进入正文，如何搭建自己的开发环境。&lt;/p&gt;
&lt;h2&gt;初始化&lt;/h2&gt;
&lt;p&gt;创建文件夹并进入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir tristana &amp;amp;&amp;amp; cd tristana
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化 &lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装 &lt;code&gt;vite&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm install vite vite-plugin-babel-import vite-plugin-imp --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建以下目录结构、文件和内容：&lt;/p&gt;
&lt;h3&gt;project&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;tristana
|- package.json
|- index.html
|- vite.config.js
|- /src
   |- index.js

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;src/index.js&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;document.getElementById(&quot;root&quot;).append(&quot;React&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;index.html&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;
        &amp;lt;title&amp;gt;tristana&amp;lt;/title&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;script type=&quot;module&quot; src=&quot;/src/index.jsx&quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script&amp;gt;
            window.global = window;
        &amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;vite.config.js&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &apos;vite&apos;;

const path = require(&apos;path&apos;);
export default defineConfig({
    plugins: [
        reactRefresh()
    ]
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;package.json&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
    // ...
    &quot;scripts&quot;: {
        &quot;build&quot;: &quot;vite build&quot;,
    },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后根目录终端输入：&lt;code&gt;npm run build&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在浏览器中打开 &lt;code&gt;dist&lt;/code&gt; 目录下的 &lt;code&gt;index.html&lt;/code&gt;，如果一切正常，你应该能看到以下文本：&lt;code&gt;&apos;React&apos;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; 目前放在 &lt;code&gt;dist&lt;/code&gt; 目录下，但它是手动创建的，下面会教你如何生成 &lt;code&gt;index.html&lt;/code&gt; 而非手动编辑它。&lt;/p&gt;
&lt;h2&gt;Vite 核心功能&lt;/h2&gt;
&lt;h3&gt;热更新&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ npm install @vitejs/plugin-react-refresh --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;vite.config.js&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import reactRefresh from &apos;@vitejs/plugin-react-refresh&apos;;
export default defineConfig({
    // ...
    plugins: [
        reactRefresh(),
    ],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;alias&lt;/h3&gt;
&lt;h4&gt;vite.config.js&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &apos;vite&apos;;
const path = require(&apos;path&apos;);
export default defineConfig({
    resolve: {
        alias: {
            &apos;@&apos;: path.resolve(__dirname, &apos;src&apos;)
        }
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;开发服务&lt;/h3&gt;
&lt;h4&gt;package.json&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;{
    // ...
    &quot;scripts&quot;: {
        &quot;dev&quot;: &quot;vite&quot;,
    },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;.jsx 文件&lt;/h2&gt;
&lt;h3&gt;安装依赖&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ npm install @babel/preset-react react react-dom --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;.babelrc&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;presets&quot;: [&quot;@babel/preset-env&quot;, &quot;@babel/preset-react&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;src/App.jsx&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;src&lt;/code&gt; 目录下，新增 &lt;code&gt;App.jsx&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { Component } from &quot;react&quot;;

class App extends Component {
    render() {
        return (
            &amp;lt;div&amp;gt;
                &amp;lt;h1&amp;gt; Hello, World! &amp;lt;/h1&amp;gt;
            &amp;lt;/div&amp;gt;
        );
    }
}

export default App;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;src/index.js&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom&quot;;
import App from &quot;./App.jsx&quot;;
ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById(&quot;root&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;React Router&lt;/h2&gt;
&lt;h3&gt;安装依赖&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ npm install react-router history --save
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;src/index.js&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom&quot;;
import { Router, Route, Link } from &quot;react-router&quot;;
import { createBrowserHistory } from &quot;history&quot;;
import App from &quot;./App.jsx&quot;;

const About = () =&amp;gt; {
    return &amp;lt;&amp;gt;About&amp;lt;/&amp;gt;;
};

ReactDOM.render(
    &amp;lt;Router history={createBrowserHistory()}&amp;gt;
        &amp;lt;Route path=&quot;/&quot; component={App} /&amp;gt;
        &amp;lt;Route path=&quot;/about&quot; component={About} /&amp;gt;
    &amp;lt;/Router&amp;gt;,
    document.getElementById(&quot;root&quot;)
);

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;MobX&lt;/h2&gt;
&lt;h3&gt;安装依赖&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ npm install mobx mobx-react babel-preset-mobx --save
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;.babelrc&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;presets&quot;: [&quot;@babel/preset-env&quot;, &quot;@babel/preset-react&quot;, &quot;mobx&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;src/store.js&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;src&lt;/code&gt; 目录下新建 &lt;code&gt;store.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { observable, action, makeObservable } from &quot;mobx&quot;;

class Store {

    constructor() {
        makeObservable(this);
    }

    @observable
    count = 0;

    @action(&quot;add&quot;)
    add = () =&amp;gt; {
        this.count = this.count + 1;
    };

    @action(&quot;reduce&quot;)
    reduce = () =&amp;gt; {
        this.count = this.count - 1;
    };
}
export default new Store();

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;index.js&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { Provider } from &quot;mobx-react&quot;;
import Store from &quot;./store&quot;;
// ...
ReactDOM.render(
    &amp;lt;Provider store={Store}&amp;gt;
        &amp;lt;Router history={createBrowserHistory()}&amp;gt;
        &amp;lt;Route path=&quot;/&quot; component={App} /&amp;gt;
        &amp;lt;Route path=&quot;/about&quot; component={About} /&amp;gt;
        &amp;lt;/Router&amp;gt;
    &amp;lt;/Provider&amp;gt;,
    document.getElementById(&quot;root&quot;)
);

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;src/App.jsx&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import React, { Component } from &quot;react&quot;;
import { observer, inject } from &quot;mobx-react&quot;;

@inject(&quot;store&quot;)
@observer
class App extends Component {
    render() {
        return (
            &amp;lt;div&amp;gt;
                &amp;lt;div&amp;gt;{this.props.store.count}&amp;lt;/div&amp;gt;
                &amp;lt;button onClick={this.props.store.add}&amp;gt;add&amp;lt;/button&amp;gt;
                &amp;lt;button onClick={this.props.store.reduce}&amp;gt;reduce&amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        );
    }
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ant Design&lt;/h2&gt;
&lt;h3&gt;安装依赖&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ npm install antd vite-plugin-babel-import vite-plugin-imp --save
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;vite.config.js&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &apos;vite&apos;;
import vitePluginImp from &apos;vite-plugin-imp&apos;;

const path = require(&apos;path&apos;);
export default defineConfig({
    // ...
    plugins: [
        vitePluginImp({
            libList: [
                {
                    libName: &apos;antd&apos;,
                    libDirectory: &apos;es&apos;,
                    style: name =&amp;gt; `antd/es/${name}/style`
                }
            ]
        })
    ],
    css: {
        preprocessorOptions: {
            less: {
                javascriptEnabled: true
            }
        }
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;src/App.jsx&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ...
import { DatePicker } from &quot;antd&quot;;
import &quot;antd/dist/antd.css&quot;;

@inject(&quot;store&quot;)
@observer
class App extends Component {
    render() {
        return (
            &amp;lt;div&amp;gt;
                &amp;lt;DatePicker /&amp;gt;
            &amp;lt;/div&amp;gt;
        );
    }
}

export default App;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;TypeScript&lt;/h2&gt;
&lt;h3&gt;安装依赖&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ npm install typescript @babel/preset-typescript --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;.babelrc&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;presets&quot;: [
        // ...
        &quot;@babel/preset-typescript&quot;
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;tsconfig.json&lt;/h3&gt;
&lt;p&gt;在根目录下，新增 &lt;code&gt;tsconfig.json&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;compilerOptions&quot;: {
        &quot;emitDecoratorMetadata&quot;: true,
        &quot;experimentalDecorators&quot;: true,
        &quot;target&quot;: &quot;ES5&quot;,
        &quot;allowSyntheticDefaultImports&quot;: true,
        &quot;strict&quot;: true,
        &quot;forceConsistentCasingInFileNames&quot;: true,
        &quot;allowJs&quot;: true,
        &quot;outDir&quot;: &quot;./dist/&quot;,
        &quot;esModuleInterop&quot;: true,
        &quot;noImplicitAny&quot;: false,
        &quot;sourceMap&quot;: true,
        &quot;module&quot;: &quot;esnext&quot;,
        &quot;moduleResolution&quot;: &quot;node&quot;,
        &quot;isolatedModules&quot;: true,
        &quot;importHelpers&quot;: true,
        &quot;lib&quot;: [&quot;esnext&quot;, &quot;dom&quot;, &quot;dom.iterable&quot;],
        &quot;skipLibCheck&quot;: true,
        &quot;jsx&quot;: &quot;react&quot;,
        &quot;typeRoots&quot;: [&quot;node&quot;, &quot;node_modules/@types&quot;],
        &quot;rootDirs&quot;: [&quot;./src&quot;],
        &quot;baseUrl&quot;: &quot;./src&quot;
    },
    &quot;include&quot;: [&quot;./src/**/*&quot;],
    &quot;exclude&quot;: [&quot;node_modules&quot;]
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;src/App.jsx&lt;/h3&gt;
&lt;p&gt;更换文件后缀 &lt;code&gt;App.jsx&lt;/code&gt; -&amp;gt; &lt;code&gt;App.tsx&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { Component } from &quot;react&quot;;
import { observer, inject } from &quot;mobx-react&quot;;
import { DatePicker } from &quot;antd&quot;;
import &quot;antd/dist/antd.css&quot;;

@inject(&quot;store&quot;)
@observer
class App extends Component {
    props: any;
    render() {
        return (
            &amp;lt;div&amp;gt;
                &amp;lt;DatePicker /&amp;gt;
                &amp;lt;div&amp;gt;{this.props.store.count}&amp;lt;/div&amp;gt;
                &amp;lt;button onClick={this.props.store.add}&amp;gt;add&amp;lt;/button&amp;gt;
                &amp;lt;button onClick={this.props.store.reduce}&amp;gt;reduce&amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        );
    }
}

export default App;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;代码规范&lt;/h2&gt;
&lt;p&gt;代码校验、代码格式化、&lt;code&gt;Git&lt;/code&gt; 提交前校验、&lt;code&gt;Vscode&lt;/code&gt;配置、编译校验&lt;/p&gt;
&lt;h3&gt;ESLint&lt;/h3&gt;
&lt;h4&gt;安装依赖&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;$ npm install @typescript-eslint/parser eslint eslint-plugin-standard @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-promise  --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;.eslintrc.js&lt;/h4&gt;
&lt;p&gt;在根目录下，新增 &lt;code&gt;.eslintrc.js&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
    extends: [&quot;eslint:recommended&quot;, &quot;plugin:react/recommended&quot;],
    env: {
        browser: true,
        commonjs: true,
        es6: true,
    },
    globals: {
        $: true,
        process: true,
        __dirname: true,
    },
    parser: &quot;@typescript-eslint/parser&quot;,
    parserOptions: {
        ecmaFeatures: {
            jsx: true,
            modules: true,
        },
        sourceType: &quot;module&quot;,
        ecmaVersion: 6,
    },
    plugins: [&quot;react&quot;, &quot;standard&quot;, &quot;promise&quot;, &quot;@typescript-eslint&quot;],
    settings: {
        &quot;import/ignore&quot;: [&quot;node_modules&quot;],
        react: {
            version: &quot;latest&quot;,
        },
    },
    rules: {
        quotes: [2, &quot;single&quot;],
        &quot;no-console&quot;: 0,
        &quot;no-debugger&quot;: 1,
        &quot;no-var&quot;: 1,
        semi: [&quot;error&quot;, &quot;always&quot;],
        &quot;no-irregular-whitespace&quot;: 0,
        &quot;no-trailing-spaces&quot;: 1,
        &quot;eol-last&quot;: 0,
        &quot;no-unused-vars&quot;: [
        1,
        {
            vars: &quot;all&quot;,
            args: &quot;after-used&quot;,
        },
        ],
        &quot;no-case-declarations&quot;: 0,
        &quot;no-underscore-dangle&quot;: 0,
        &quot;no-alert&quot;: 2,
        &quot;no-lone-blocks&quot;: 0,
        &quot;no-class-assign&quot;: 2,
        &quot;no-cond-assign&quot;: 2,
        &quot;no-const-assign&quot;: 2,
        &quot;no-delete-var&quot;: 2,
        &quot;no-dupe-keys&quot;: 2,
        &quot;use-isnan&quot;: 2,
        &quot;no-duplicate-case&quot;: 2,
        &quot;no-dupe-args&quot;: 2,
        &quot;no-empty&quot;: 2,
        &quot;no-func-assign&quot;: 2,
        &quot;no-invalid-this&quot;: 0,
        &quot;no-redeclare&quot;: 2,
        &quot;no-spaced-func&quot;: 2,
        &quot;no-this-before-super&quot;: 0,
        &quot;no-undef&quot;: 2,
        &quot;no-return-assign&quot;: 0,
        &quot;no-script-url&quot;: 2,
        &quot;no-use-before-define&quot;: 2,
        &quot;no-extra-boolean-cast&quot;: 0,
        &quot;no-unreachable&quot;: 1,
        &quot;comma-dangle&quot;: 2,
        &quot;no-mixed-spaces-and-tabs&quot;: 2,
        &quot;prefer-arrow-callback&quot;: 0,
        &quot;arrow-parens&quot;: 0,
        &quot;arrow-spacing&quot;: 0,
        camelcase: 0,
        &quot;jsx-quotes&quot;: [1, &quot;prefer-double&quot;],
        &quot;react/display-name&quot;: 0,
        &quot;react/forbid-prop-types&quot;: [
        2,
        {
            forbid: [&quot;any&quot;],
        },
        ],
        &quot;react/jsx-boolean-value&quot;: 0,
        &quot;react/jsx-closing-bracket-location&quot;: 1,
        &quot;react/jsx-curly-spacing&quot;: [
        2,
        {
            when: &quot;never&quot;,
            children: true,
        },
        ],
        &quot;react/jsx-indent&quot;: [&quot;error&quot;, 4],
        &quot;react/jsx-key&quot;: 2,
        &quot;react/jsx-no-bind&quot;: 0,
        &quot;react/jsx-no-duplicate-props&quot;: 2,
        &quot;react/jsx-no-literals&quot;: 0,
        &quot;react/jsx-no-undef&quot;: 1,
        &quot;react/jsx-pascal-case&quot;: 0,
        &quot;react/jsx-sort-props&quot;: 0,
        &quot;react/jsx-uses-react&quot;: 1,
        &quot;react/jsx-uses-vars&quot;: 2,
        &quot;react/no-danger&quot;: 0,
        &quot;react/no-did-mount-set-state&quot;: 0,
        &quot;react/no-did-update-set-state&quot;: 0,
        &quot;react/no-direct-mutation-state&quot;: 2,
        &quot;react/no-multi-comp&quot;: 0,
        &quot;react/no-set-state&quot;: 0,
        &quot;react/no-unknown-property&quot;: 2,
        &quot;react/prefer-es6-class&quot;: 2,
        &quot;react/prop-types&quot;: 0,
        &quot;react/react-in-jsx-scope&quot;: 2,
        &quot;react/self-closing-comp&quot;: 0,
        &quot;react/sort-comp&quot;: 0,
        &quot;react/no-array-index-key&quot;: 0,
        &quot;react/no-deprecated&quot;: 1,
        &quot;react/jsx-equals-spacing&quot;: 2,
    },
};


&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;.eslintignore&lt;/h4&gt;
&lt;p&gt;在根目录下，新增 &lt;code&gt;.eslintignore&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/assets
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;.vscode&lt;/h4&gt;
&lt;p&gt;在根目录下新增 &lt;code&gt;.vscode 文件夹&lt;/code&gt;，然后新增 &lt;code&gt;.vscode/settings.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;eslint.validate&quot;: [
        &quot;javascript&quot;,
        &quot;javascriptreact&quot;,
        &quot;typescript&quot;,
        &quot;typescriptreact&quot;
    ]
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Perttier&lt;/h3&gt;
&lt;h4&gt;安装依赖&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;$ npm install prettier --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;prettier.config.js&lt;/h4&gt;
&lt;p&gt;在根目录下，新增 &lt;code&gt;prettier.config.js&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
    // 一行最多 100 字符
    printWidth: 100,
    // 使用 4 个空格缩进
    tabWidth: 4,
    // 不使用缩进符，而使用空格
    useTabs: false,
    // 行尾需要有分号
    semi: true,
    // 使用单引号
    singleQuote: true,
    // 对象的 key 仅在必要时用引号
    quoteProps: &apos;as-needed&apos;,
    // jsx 不使用单引号，而使用双引号
    jsxSingleQuote: false,
    // 末尾不需要逗号
    trailingComma: &apos;none&apos;,
    // 大括号内的首尾需要空格
    bracketSpacing: true,
    // jsx 标签的反尖括号需要换行
    jsxBracketSameLine: false,
    // 箭头函数，只有一个参数的时候，也需要括号
    arrowParens: &apos;avoid&apos;,
    // 每个文件格式化的范围是文件的全部内容
    rangeStart: 0,
    rangeEnd: Infinity,
    // 不需要写文件开头的 @prettier
    requirePragma: false,
    // 不需要自动在文件开头插入 @prettier
    insertPragma: false,
    // 使用默认的折行标准
    proseWrap: &apos;preserve&apos;,
    // 根据显示样式决定 html 要不要折行
    htmlWhitespaceSensitivity: &apos;css&apos;,
    // 换行符使用 lf
    endOfLine: &apos;lf&apos;
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;stylelint&lt;/h3&gt;
&lt;h4&gt;安装依赖&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;$ npm install stylelint stylelint-config-standard stylelint-config-prettier --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;stylelint.config.js&lt;/h4&gt;
&lt;p&gt;在根目录下，新增 &lt;code&gt;stylelint.config.js&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
    extends: [&apos;stylelint-config-standard&apos;, &apos;stylelint-config-prettier&apos;],
    ignoreFiles: [
        &apos;**/*.ts&apos;,
        &apos;**/*.tsx&apos;,
        &apos;**/*.png&apos;,
        &apos;**/*.jpg&apos;,
        &apos;**/*.jpeg&apos;,
        &apos;**/*.gif&apos;,
        &apos;**/*.mp3&apos;,
        &apos;**/*.json&apos;
    ],
    rules: {
        &apos;at-rule-no-unknown&apos;: [
            true,
            {
                ignoreAtRules: [&apos;extends&apos;, &apos;ignores&apos;]
            }
        ],
        indentation: 4,
        &apos;number-leading-zero&apos;: null,
        &apos;unit-allowed-list&apos;: [&apos;em&apos;, &apos;rem&apos;, &apos;s&apos;, &apos;px&apos;, &apos;deg&apos;, &apos;all&apos;, &apos;vh&apos;, &apos;%&apos;],
        &apos;no-eol-whitespace&apos;: [
            true,
            {
                ignore: &apos;empty-lines&apos;
            }
        ],
        &apos;declaration-block-trailing-semicolon&apos;: &apos;always&apos;,
        &apos;selector-pseudo-class-no-unknown&apos;: [
            true,
            {
                ignorePseudoClasses: [&apos;global&apos;]
            }
        ],
        &apos;block-closing-brace-newline-after&apos;: &apos;always&apos;,
        &apos;declaration-block-semicolon-newline-after&apos;: &apos;always&apos;,
        &apos;no-descending-specificity&apos;: null,
        &apos;selector-list-comma-newline-after&apos;: &apos;always&apos;,
        &apos;selector-pseudo-element-colon-notation&apos;: &apos;single&apos;
    }
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;lint-staged、pre-commit&lt;/h3&gt;
&lt;h4&gt;安装依赖&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;$ npm install lint-staged prettier eslint pre-commit --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;package.json&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;{
    // ...
    &quot;scripts&quot;: {
        &quot;lint:tsx&quot;: &quot;eslint --ext .tsx src &amp;amp;&amp;amp; eslint --ext .ts src&quot;,
        &quot;lint:css&quot;: &quot;stylelint --aei .less .css src&quot;,
        &quot;precommit&quot;: &quot;lint-staged&quot;,
        &quot;precommit-msg&quot;: &quot;echo &apos;Pre-commit checks...&apos; &amp;amp;&amp;amp; exit 0&quot;
    },
    &quot;pre-commit&quot;: [
        &quot;precommit&quot;,
        &quot;precommit-msg&quot;
    ],
    &quot;lint-staged&quot;: {
        &quot;*.{js,jsx,ts,tsx}&quot;: [
            &quot;eslint --fix&quot;,
            &quot;prettier --write&quot;,
            &quot;git add&quot;
        ],
        &quot;*.{css,less}&quot;: [
            &quot;stylelint --fix&quot;,
            &quot;prettier --write&quot;,
            &quot;git add&quot;
        ]
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;eslint-webpack-plugin&lt;/h3&gt;
&lt;h4&gt;安装依赖&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;$ npm install eslint-webpack-plugin --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;vite.config.ts&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &apos;vite&apos;;
const ESLintPlugin = require(&apos;eslint-webpack-plugin&apos;);

const path = require(&apos;path&apos;);
export default defineConfig({
    // ...
    plugins: [
        new ESLintPlugin()
    ]
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;搭建这个的过程，也是遇到了不少坑，收获也是蛮多的，希望这个教程能够帮助更多的同学，少采点坑。基于这套项目框架可以开发各种各样的业务页面，非常流畅，集成了非常非常多的库，完善了路由配置，网络请求，组件点击加载等等。
完整的可以看这个&lt;a href=&quot;https://github.com/xuya227939/tristana&quot;&gt;tristana&lt;/a&gt;，大佬们觉得不错的话，可以给个 Star 🌟，也欢迎给项目提 issues ～&lt;/p&gt;
</content:encoded><category>React</category><category>Vite</category><author>江辰</author></item><item><title>项目0-1</title><link>https://github.com/posts/%E9%A1%B9%E7%9B%AE0-1/</link><guid isPermaLink="true">https://github.com/posts/%E9%A1%B9%E7%9B%AE0-1/</guid><pubDate>Sun, 07 Feb 2021 23:57:31 GMT</pubDate><content:encoded>&lt;h2&gt;流程图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/107151912-29afc100-69a0-11eb-92f7-a0ddce688a81.png&quot; alt=&quot;task&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;人员定位&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;产品：跟进项目排期、上线验收、线上数据监控&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UI：出设计稿、出交互设计&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;开发：保证功能按时提测&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;测试：保证主流程通顺&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运营：为公司带来营收&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;名词解释&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;产品定位：该项目产品是工具类、视频类、服务类等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;竞品分析：以智慧展业跟顾小问为例&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运营推广：运营人员去各大网站、社区进行推广&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>项目0-1</category><author>江辰</author></item><item><title>什么是Serverless</title><link>https://github.com/posts/%E4%BB%80%E4%B9%88%E6%98%AFserverless/</link><guid isPermaLink="true">https://github.com/posts/%E4%BB%80%E4%B9%88%E6%98%AFserverless/</guid><pubDate>Mon, 18 Jan 2021 14:54:31 GMT</pubDate><content:encoded>&lt;p&gt;原文：&lt;a href=&quot;https://aws.amazon.com/cn/blogs/china/iaas-faas-serverless/&quot;&gt;从 IaaS 到 FaaS—— Serverless 架构的前世今生&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;今天，大多数公司在开发应用程序并将其部署在服务器上的时候，无论是选择公有云还是私有的数据中心，都需要提前了解究竟需要多少台服务器、多大容量的存储和数据库的功能等。并需要部署运行应用程序和依赖的软件到基础设施之上。假设我们不想在这些细节上花费精力，是否有一种简单的架构模型能够满足我们这种想法？这个答案已经存在，这就是今天软件架构世界中新鲜但是很热门的一个话题——Serverless（无服务器）架构。&lt;/p&gt;
&lt;h2&gt;什么是 Serverless&lt;/h2&gt;
&lt;p&gt;无服务器计算（或简称 serverless），是一种执行模型，在该模型中，云服务商（AWS，Azure 或 Google Cloud）负责通过动态分配资源来执行一段代码，并且仅收取运行代码所使用资源的费用。该代码通常运行在无状态的容器中，能够被包括 HTTP 请求、数据库事件、队列服务、监控报警、文件上传、调度事件（cron 任务）等各种事件触发。简单地说，这个架构的就是要让开发人员关注代码的运行而不需要管理任何的基础设施。从这种架构技术出现的两年多时间来看，这个技术已经有了非常广泛的应用，例如移动应用的后端和物联网应用等。简而言之，无服务器架构的出现不是为了取代传统的应用。然而，从具有高度灵活性的使用模式及事件驱动的特点出发，开发人员／架构师应该重视这个新的计算范例，它可以帮助我们达到减少部署、提高扩展性并减少代码后面的基础设施的维护负担。被发送到云服务商执行的代码通常是以函数的形式，因此，无服务器计算有时是指 “函数即服务” 或者 FAAS。&lt;/p&gt;
&lt;h2&gt;Serverless 的历史&lt;/h2&gt;
&lt;p&gt;Serverless 这个概念并不容易理解。很容易让人混淆硬件服务器及软件上的服务与其所谓的“服务器”差别。在这里强调的所谓“无服务器”指的是我们的代码不会明确地部署在某些特定的软件或者硬件的服务器上。运行代码托管的环境是由例如 AWS 这样的云计算厂商所提供的。&lt;/p&gt;
&lt;p&gt;Serverless 这个词第一次被使用大约是 2012 年由 Ken Form 所写的一篇名为《Why The Future of Software and Apps is Serverless》的文章。这篇文章谈到的内容是关于持续集成及源代码控制等内容，并不是我们今天所特指的这一种架构模式。但 Amazon 在 2014 年发布的 AWS Lambda 让“Serverless”这一范式提高到一个全新的层面，为云中运行的应用程序提供了一种全新的系统体系结构。至此再也不需要在服务器上持续运行进程以等待 HTTP 请求或 API 调用，而是可以通过某种事件机制触发代码的执行，通常这只需要在 AWS 的某台服务器上配置一个简单的功能。此后 Ant Stanley 在 2015 年 7 月的名为《Server are Dead…》的文章中更是围绕着 AWS Lambda 及刚刚发布的 AWS API Gateway 这两个服务解释了他心目中的 Serverless，“Server are dead…they just don’t know it yet”。到了 2015 年 10 月份，在那一年的 AWS re:Invent 大会上，Serverless 的这个概念更是反复出现在了很多场合。印象中就包括了“（ARC308）The Serverless Company Using AWS Lambda”及“（DVO209）JAWS: The Monstrously Scalable Serverless Framework”这些演讲当中。随着这个概念的进一步发酵，2016 年 10 月在伦敦举办了第一届的 Serverlessvconf。在两天时间里面，来自全世界 40 多位演讲嘉宾为开发者分享了关于这个领域进展。&lt;/p&gt;
&lt;p&gt;在 Serverless 的世界里面，AWS 扮演了一个非常重要的角色。但是 AWS 并不是唯一的 Serverless 架构服务的供应商。其他厂商，例如 Google Cloud Functions、Microsoft Azure Functions、IBM OpenWhisk、Iron.io 和 Webtask 等各种开源平台都提供了类似的服务。&lt;/p&gt;
&lt;h2&gt;Serverless 与 FaaS&lt;/h2&gt;
&lt;p&gt;微服务（MicroService）是软件架构领域业另一个热门的话题。如果说微服务是以专注于单一责任与功能的小型功能块为基础，利用模块化的方式组合出复杂的大型应用程序，那么我们还可以进一步认为 Serverless 架构可以提供一种更加“代码碎片化”的软件架构范式，我们称之为 Function as a Services（FaaS）。而所谓的“函数”（Function）提供的是相比微服务更加细小的程序单元。例如，可以通过微服务代表为某个客户执行所有 CRUD 操作所需的代码，而 FaaS 中的“函数”可以代表客户所要执行的每个操作：创建、读取、更新，以及删除。当触发“创建账户”事件后，将通过 AWS Lambda 函数的方式执行相应的“函数”。从这一层意思来说，我们可以简单地将 Serverless 架构与 FaaS 概念等同起来。&lt;/p&gt;
&lt;h2&gt;FaaS 与 PaaS 的比较&lt;/h2&gt;
&lt;p&gt;FaaS 与 PaaS 的概念在某些方面有许多相似的地方。人们甚至认为 FaaS 就是另一种形式的 PaaS。但是 Intent Media 的工程副总裁 Mike Roberts 有自己的不同看法：“大部分 PaaS 应用无法针对每个请求启动和停止整个应用程序，而 FaaS 平台生来就是为了实现这样的目的。”&lt;/p&gt;
&lt;p&gt;FaaS 和 PaaS 在运维方面最大的差异在于缩放能力。对于大部分 PaaS 平台，用户依然需要考虑缩放。但是对于 FaaS 应用，这种问题完全是透明的。就算将 PaaS 应用设置为自动缩放，依然无法在具体请求的层面上进行缩放，而 FaaS 应用在成本方面效益就高多了。AWS 云架构战略副总裁 Adrian Cockcroft 曾经针对两者的界定给出了一个简单的方法：“如果你的 PaaS 能够有效地在 20 毫秒内启动实例并运行半秒,那么就可以称之为 Serverless”。&lt;/p&gt;
&lt;h2&gt;Serverless 架构的优点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;降低运营成本：
Serverless 是非常简单的外包解决方案。它可以让您委托服务提供商管理服务器、数据库和应用程序甚至逻辑，否则您就不得不自己来维护。由于这个服务使用者的数量会非常庞大，于是就会产生规模经济效应。在降低成本上包含了两个方面，即基础设施的成本和人员（运营/开发）的成本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;降低开发成本：
IaaS 和 PaaS 存在的前提是，服务器和操作系统管理可以商品化。Serverless 作为另一种服务的结果是整个应用程序组件被商品化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;扩展能力：
Serverless 架构一个显而易见的优点即“横向扩展是完全自动的、有弹性的、且由服务提供者所管理”。从基本的基础设施方面受益最大的好处是，您只需支付您所需要的计算能力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;更简单的管理：
Serverless 架构明显比其他架构更简单。更少的组件，就意味着您的管理开销会更少。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“绿色”的计算：
按照《福布斯》杂志的统计，在商业和企业数据中心的典型服务器仅提供 5%～ 15%的平均最大处理能力的输出。这无疑是一种资源的巨大浪费。随着 Serverless 架构的出现，让服务提供商提供我们的计算能力最大限度满足实时需求。这将使我们更有效地利用计算资源。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Serverless 的架构范式&lt;/h2&gt;
&lt;p&gt;移动应用后台 Serverless 参考架构&lt;/p&gt;
&lt;p&gt;&amp;lt;img width=&quot;650&quot; alt=&quot;WechatIMG1138&quot; src=&quot;https://user-images.githubusercontent.com/16217324/104881851-01363780-599d-11eb-954e-a4224ff0f00e.png&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;实时文件处理 Serverless 参考架构&lt;/p&gt;
&lt;p&gt;&amp;lt;img width=&quot;650&quot; alt=&quot;WechatIMG1139&quot; src=&quot;https://user-images.githubusercontent.com/16217324/104881857-04312800-599d-11eb-9941-e3da28242ebf.png&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;Web 应用 Serverless 参考架构&lt;/p&gt;
&lt;p&gt;&amp;lt;img width=&quot;650&quot; alt=&quot;WechatIMG1140&quot; src=&quot;https://user-images.githubusercontent.com/16217324/104881863-07c4af00-599d-11eb-94aa-74a3370df7ef.png&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;物联网应用后台参考架构&lt;/p&gt;
&lt;p&gt;&amp;lt;img width=&quot;650&quot; alt=&quot;WechatIMG1141&quot; src=&quot;https://user-images.githubusercontent.com/16217324/104881871-0abf9f80-599d-11eb-9af2-d72ac23884af.png&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;实时流处理 Serverless 参考架构&lt;/p&gt;
&lt;p&gt;&amp;lt;img width=&quot;650&quot; alt=&quot;WechatIMG1142&quot; src=&quot;https://user-images.githubusercontent.com/16217324/104881873-0dba9000-599d-11eb-96fb-2cd6d33ba306.png&quot;&amp;gt;&lt;/p&gt;
</content:encoded><category>Serverless</category><author>江辰</author></item><item><title>Git 分支管理策略</title><link>https://github.com/posts/git-%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86%E7%AD%96%E7%95%A5/</link><guid isPermaLink="true">https://github.com/posts/git-%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86%E7%AD%96%E7%95%A5/</guid><pubDate>Wed, 06 Jan 2021 18:16:04 GMT</pubDate><content:encoded>&lt;h2&gt;在 Git 中管理分支流程的主要方法&lt;/h2&gt;
&lt;h3&gt;Git Flow&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Git Flow&lt;/code&gt; 最开始是由 Vincent Driessen 发行并广受欢迎，这个模型是在 2010 年构思出来的，而现在距今已有 10 多年了，而 &lt;code&gt;Git&lt;/code&gt; 本身才诞生不久。在过去的十年中，&lt;code&gt;Git Flow&lt;/code&gt; 在许多软件团队中非常流行。&lt;/p&gt;
&lt;h3&gt;GitHub Flow&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;GitHub Flow&lt;/code&gt; 是由 &lt;code&gt;GitHub&lt;/code&gt; 发行，并得到 Vincent Driessen 推荐，&lt;code&gt;GitHub Flow&lt;/code&gt; 最近几年在社区非常流行。&lt;/p&gt;
&lt;p&gt;它们都有一个特点，都是采用”功能驱动式开发”（Feature-driven development，简称 FDD），有兴趣的可以自行了解一下。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GitHub Flow&lt;/code&gt; 的较大优点就是简单，只有一个 &lt;code&gt;master&lt;/code&gt; 分支为长期分支，对于需要”可持续性发布”的产品，可以说是最合适的流程。&lt;/p&gt;
&lt;h2&gt;分支命名规范&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;master 分支：&lt;code&gt;master&lt;/code&gt; 分支只有一个，名称即为 &lt;code&gt;master&lt;/code&gt;。GitHub 现在叫 main（保护黑人）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;develop 分支：&lt;code&gt;develop&lt;/code&gt; 分支只有一个，名称即为 &lt;code&gt;develop&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;feature 分支：feature/&amp;lt;功能名&amp;gt;，例如：&lt;code&gt;feature/login&lt;/code&gt;，以便其他人可以看到你的工作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hotfix 分支：hotfix/日期，例如：&lt;code&gt;hotfix/0104&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;分支说明&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;master || main 分支：存储正式发布的产品，&lt;code&gt;master || main&lt;/code&gt; 分支上的产品要求随时处于可部署状态。&lt;code&gt;master || main&lt;/code&gt; 分支只能通过与其他分支合并来更新内容，禁止直接在 &lt;code&gt;master || main&lt;/code&gt; 分支进行修改。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;develop 分支：汇总开发者完成的工作成果，&lt;code&gt;develop&lt;/code&gt; 分支上的产品可以是缺失功能模块的半成品，但是已有的功能模块不能是半成品。&lt;code&gt;develop&lt;/code&gt; 分支只能通过与其他分支合并来更新内容，禁止直接在 &lt;code&gt;develop&lt;/code&gt; 分支进行修改。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;feature 分支：当要开发新功能时，从 master 分支创建一个新的 &lt;code&gt;feature&lt;/code&gt; 分支，并在 &lt;code&gt;feature&lt;/code&gt; 分支上进行开发。开发完成后，需要将该 &lt;code&gt;feature&lt;/code&gt; 分支合并到 &lt;code&gt;develop&lt;/code&gt; 分支，最后删除该 &lt;code&gt;feature&lt;/code&gt; 分支。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;release 分支：当 &lt;code&gt;develop&lt;/code&gt; 分支上的项目准备发布时，从 &lt;code&gt;develop&lt;/code&gt; 分支上创建一个新的 &lt;code&gt;release&lt;/code&gt; 分支，新建的 &lt;code&gt;release&lt;/code&gt; 分支只能进行质量测试、bug 修复、文档生成等面向发布的任务，不能再添加功能。这一系列发布任务完成后，需要将 &lt;code&gt;release&lt;/code&gt; 分支合并到 &lt;code&gt;master&lt;/code&gt; 分支上，并根据版本号为 &lt;code&gt;master&lt;/code&gt; 分支添加 &lt;code&gt;tag&lt;/code&gt;，然后将 &lt;code&gt;release&lt;/code&gt; 分支创建以来的修改合并回 &lt;code&gt;develop&lt;/code&gt; 分支，最后删除 &lt;code&gt;release&lt;/code&gt; 分支。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hotfix 分支：当 &lt;code&gt;master&lt;/code&gt; 分支中的产品出现需要立即修复的 bug 时，从 &lt;code&gt;master&lt;/code&gt; 分支上创建一个新的 &lt;code&gt;hotfix&lt;/code&gt; 分支，并在 &lt;code&gt;hotfix&lt;/code&gt; 分支上进行 BUG 修复。修复完成后，需要将 &lt;code&gt;hotfix&lt;/code&gt; 分支合并到 &lt;code&gt;master&lt;/code&gt; 分支和 &lt;code&gt;develop&lt;/code&gt; 分支，并为 &lt;code&gt;master&lt;/code&gt; 分支添加新的版本号 &lt;code&gt;tag&lt;/code&gt;，最后删除 &lt;code&gt;hotfix&lt;/code&gt; 分支。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;提交信息规范&lt;/h2&gt;
&lt;p&gt;提交信息应该描述“做了什么”和“这么做的原因”，必要时还可以加上“造成的影响”，主要由 &lt;strong&gt;3&lt;/strong&gt; 个部分组成：&lt;code&gt;Header&lt;/code&gt;、&lt;code&gt;Body&lt;/code&gt; 和 &lt;code&gt;Footer&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;Header
Header 部分只有 1 行，格式为&lt;code&gt;&amp;lt;type&amp;gt;(&amp;lt;scope&amp;gt;): &amp;lt;subject&amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;type 用于说明提交的类型，共有 8 个候选值：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;feat：新功能（feature）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;fix：问题修复&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;docs：文档&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;style：调整格式（不影响代码运行）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;refactor：重构&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;test：增加测试&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;chore：构建过程或辅助工具的变动&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;revert：撤销以前的提交&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;scope 用于说明提交的影响范围，内容根据具体项目而定。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;subject 用于概括提交内容。&lt;/p&gt;
&lt;p&gt;Body 省略&lt;/p&gt;
&lt;p&gt;Footer 省略&lt;/p&gt;
&lt;h2&gt;Fork&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ git clone http://git@github.com:xxx/LiveCenterMobile.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Fork&lt;/code&gt; 出来的仓库完全属于你的，你可以任意修改该仓库的代码及配置，除非你向主库提交 &lt;code&gt;PR&lt;/code&gt; 并被通过，你才可以将你 &lt;code&gt;Fork&lt;/code&gt; 仓库修改的代码合并到主库，否则不会对主库产生任何影响。&lt;/p&gt;
&lt;p&gt;在控制台输入 &lt;code&gt;git remote -v&lt;/code&gt; 命令查看当前远端仓库的地址，输出如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ origin  http://git@github.com:xxx/LiveCenterMobile.git (fetch)
$ origin  http://git@github.com:xxx/LiveCenterMobile.git (push)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后设置一个名字为 &lt;code&gt;upstream&lt;/code&gt; 的上游地址，也就是项目主库的地址：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git remote add upstream http://git@github.com:fe/LiveCenterMobile.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再次执行 &lt;code&gt;git remote -v&lt;/code&gt; 控制台输出如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ origin  http://git@github.com:xxx/LiveCenterMobile.git (fetch)
$ origin  http://git@github.com:xxx/LiveCenterMobile.git (push)
$ upstream        http://git@github.com:fe/LiveCenterMobile.git (fetch)
$ upstream        http://git@github.com:fe/LiveCenterMobile.git (push)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置这个的目的就是，当主库代码有更新，本地需要同步主库的代码，并及时更新到 &lt;code&gt;origin&lt;/code&gt;（远端）仓库，保证自己托管空间下本地和远端仓库的代码都是最新的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git fetch upstream （同步远端代码）
$ git checkout master
$ git merge upstream/master || git pull upstream master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后本地仓库推送到远端（&lt;code&gt;origin&lt;/code&gt;）仓库：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git push origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个时候可以在本地进行开发了，通常我们规定：&lt;/p&gt;
&lt;p&gt;开发新功能要从 &lt;code&gt;master&lt;/code&gt; 分支上新建一个 &lt;code&gt;feat/[name-desc]&lt;/code&gt; 临时分支
BUG 修复 从 &lt;code&gt;master&lt;/code&gt; 分支新建一个 &lt;code&gt;hotfix/[name-desc]&lt;/code&gt; 临时分支&lt;/p&gt;
&lt;h2&gt;新建 feat || hotfix 分支&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ git checkout -b feat/login master || git checkout -b feat/login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;feat/login&lt;/code&gt; 分支进行功能迭代，提交代码到本地仓库，当功能开发完成之后，若有其他人在同一个项目上进行某功能的开发，那么在 &lt;code&gt;PUSH&lt;/code&gt; 本地代码之前，检查 &lt;code&gt;feat/login&lt;/code&gt; 分支是否落后于上游（&lt;code&gt;upstream&lt;/code&gt;） &lt;code&gt;develop&lt;/code&gt; 分支：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git checkout develop
$ git pull
$ git log feat/login..develop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果没有输出任何提交信息的话，即表示 &lt;code&gt;feat/login&lt;/code&gt; 分支 相对于 &lt;code&gt;develop&lt;/code&gt; 分支是最新的。如果有输出的话去执行 &lt;code&gt;git merge --no-ff&lt;/code&gt; ，提交路线图就会分叉，为了得到一个干净清爽的提交路线图，合并前最好先执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git checkout feat/login
$ git rebase develop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git checkout feat/login
$ git pull upstream develop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会将整个工作分支移到 &lt;code&gt;develop&lt;/code&gt; 分支的前面（&lt;code&gt;HEAD&lt;/code&gt;），可以使 &lt;code&gt;feat/login&lt;/code&gt; 分支与上游（&lt;code&gt;upstream&lt;/code&gt;） &lt;code&gt;develop&lt;/code&gt; 分支同步，如果有冲突，就解决冲突，最后将代码推送到远端（&lt;code&gt;origin&lt;/code&gt;）分支&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git push origin feat/login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样 &lt;code&gt;PR&lt;/code&gt; 通过之后，&lt;code&gt;feat/login&lt;/code&gt; 分支的代码执行合并操作之后，&lt;code&gt;commit&lt;/code&gt; 历史更新到最新的 &lt;code&gt;develop&lt;/code&gt; 分支之后，最终得到一个干净舒服的提交路线图。&lt;/p&gt;
&lt;h2&gt;删除 feat || hotfix 分支&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;PR Merge&lt;/code&gt; 之后，对于已经合到 &lt;code&gt;upstream/develop&lt;/code&gt; 的远端 &lt;code&gt;feature&lt;/code&gt; 分支，可以将其进行删除了：&lt;/p&gt;
&lt;p&gt;本地分支删除：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git branch -D feat/login  # 不检查状态，直接删除
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;远端分支删除：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git push origin :feat/login
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;分支同步&lt;/h2&gt;
&lt;p&gt;一段时间内，有多个小伙伴通过 &lt;code&gt;PR Merge&lt;/code&gt; 的方式将代码合并到 &lt;code&gt;upstream&lt;/code&gt; 上游 &lt;code&gt;develop&lt;/code&gt; 分支了，此时我们要将 &lt;code&gt;upstream&lt;/code&gt; 上游 &lt;code&gt;develop&lt;/code&gt; 分支的代码同步到本地和远端，以保证自己仓库下的代码是最新的。&lt;/p&gt;
&lt;p&gt;此时先要将 &lt;code&gt;upstream&lt;/code&gt; 上游的改动同步到本地，再 &lt;code&gt;push&lt;/code&gt; 到远端 &lt;code&gt;origin&lt;/code&gt; 分支：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git checkout develop
$ git pull upstream develop
$ git push origin develop
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;撤回某个 commit&lt;/h2&gt;
&lt;p&gt;在开发过程中，会提交非常多 &lt;code&gt;commit&lt;/code&gt;，当项目快要上线的时候，突然某个 &lt;code&gt;commit&lt;/code&gt; 不需要合并进去，那么就需要通过&lt;code&gt;git revert&lt;/code&gt; 来进行撤回。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git revert &amp;lt;commit&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;回滚&lt;/h2&gt;
&lt;p&gt;在线上版本突然出现某个 BUG 的时候，经过长时间的研究，还未解决，可能需要回滚到上一个版本的代码，那么就需要通过&lt;code&gt;git reset&lt;/code&gt;解决，这是一个非常危险的操作，当你用 &lt;code&gt;git reset&lt;/code&gt; 来重设更改时(提交不再被任何引用或引用日志所引用)，无法获得原来的样子——这个撤销是永远的。使用这个命令的时候务必要小心，因为这是少数几个可能会造成工作丢失的命令之一。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git reset &amp;lt;commit&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Git Flow 流程图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/103768812-27cfa680-505e-11eb-8fe9-7e94befa0125.jpg&quot; alt=&quot;10321609851083_ pic_hd&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Git 常用命令&lt;/h2&gt;
&lt;h3&gt;新建代码库&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$ git init 在当前目录新建一个 Git 代码库&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git clone &lt;code&gt;&amp;lt;url&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;LiveCenterMobile&amp;gt;&lt;/code&gt; 克隆仓库&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;添加/删除文件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$ git add &lt;code&gt;&amp;lt;file1&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;file2&amp;gt;&lt;/code&gt; || git add . || git &lt;code&gt;&amp;lt;dir&amp;gt;&lt;/code&gt; 添加指定文件、所有文件、目录到暂存区&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git rm &lt;code&gt;&amp;lt;file1&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;file2&amp;gt;&lt;/code&gt; 删除工作区文件，并且将这次删除放入暂存区&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;代码提交&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$ git commit -m &lt;code&gt;&amp;lt;message&amp;gt;&lt;/code&gt; 提交暂存区文件到仓库&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git commit -v 提交时显示所有 diff 信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git reset --hard&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;分支&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$ git branch 列出本地所有分支&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git branch -r 列出远端所有分支&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git branch -a 列出本地和远程所有分支&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git branch &lt;code&gt;&amp;lt;branch-name&amp;gt;&lt;/code&gt; 新建一个分支，但依然停留在当前分支&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git checkout -b &lt;code&gt;&amp;lt;branch&amp;gt;&lt;/code&gt; 新建一个分支，并切换到该分支&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git branch &lt;code&gt;&amp;lt;branch&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;commit&amp;gt;&lt;/code&gt; 新建一个分支，指向指定 commit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git checkout &lt;code&gt;&amp;lt;branch-name&amp;gt;&lt;/code&gt; 切换到指定分支，并更新工作区&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git merge &lt;code&gt;&amp;lt;branch&amp;gt;&lt;/code&gt; 合并指定分支到当前分支&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git branch -d &lt;code&gt;&amp;lt;branch-name&amp;gt;&lt;/code&gt; 删除分支&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;查看信息&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$ git status 显示所有变更文件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git log 显示当前分支的版本历史&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git diff 显示暂存区和工作区的差异&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;远端同步&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$ git fetch &lt;code&gt;&amp;lt;remote&amp;gt;&lt;/code&gt; 更新远端仓库所有变动&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git remote -v 显示所有远程仓库&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git remote add &lt;code&gt;&amp;lt;shortname&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;url&amp;gt;&lt;/code&gt; 增加一个新的远程仓库，并命名&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git pull &lt;code&gt;&amp;lt;remote&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;branch&amp;gt;&lt;/code&gt; 拉取远端仓库的变化，并与本地分支合并&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git push &lt;code&gt;&amp;lt;remote&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;branch&amp;gt;&lt;/code&gt; 推送本地代码到远端仓库指定分支&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git push &lt;code&gt;&amp;lt;remote&amp;gt;&lt;/code&gt; --force 强行推送本地代码到远端仓库指定分支，即使有冲突&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;撤销&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$ git checkout &lt;code&gt;&amp;lt;file&amp;gt;&lt;/code&gt; 恢复暂存区的指定文件到工作区&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git checkout . 恢复暂存区的所有文件到工作区&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git reset &lt;code&gt;&amp;lt;file&amp;gt;&lt;/code&gt; 重置暂存区的指定文件，与上一次 commit 保持一致，但工作区不变&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git reset --hard 重置暂存区与工作区，与上一次 commit 保持一致&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git reset &lt;code&gt;&amp;lt;commit&amp;gt;&lt;/code&gt; 重置当前分支的指针为指定 commit，同时重置暂存区，但工作区不变&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git checkout . &amp;amp;&amp;amp; git clean -df 本地修改了一些文件，其中包含修改、新增、删除的，想要丢弃&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ git revert &lt;code&gt;&amp;lt;commit&amp;gt;&lt;/code&gt; 后者的所有变化都将被前者抵消，并且应用到当前分支&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;还原&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;$ git reflog --no-abbrev 查看当前所有提交的 &lt;code&gt;commit&lt;/code&gt;，包括已删除的分支&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://guides.github.com/&quot;&gt;GitHub Guides&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/39148914&quot;&gt;团队协作中的 Github flow 工作流程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://guides.github.com/introduction/flow/&quot;&gt;了解 GitHub 流程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cnblogs.com/miracle77hp/articles/11163532.html&quot;&gt;Git 常用命令及方法大全&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;博客&lt;/h2&gt;
&lt;p&gt;欢迎关注我的&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;博客&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Git</category><author>江辰</author></item><item><title>Webpack4 性能优化实践</title><link>https://github.com/posts/webpack4-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%AE%9E%E8%B7%B5/</link><guid isPermaLink="true">https://github.com/posts/webpack4-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%AE%9E%E8%B7%B5/</guid><pubDate>Sat, 07 Nov 2020 15:47:57 GMT</pubDate><content:encoded>&lt;h1&gt;为什么需要性能优化&lt;/h1&gt;
&lt;p&gt;在使用 &lt;code&gt;Webpack&lt;/code&gt; 时，如果不注意性能优化，可能会产生性能问题，会导致在开发体验上不是非常丝滑，性能问题主要是编译速度慢，打包体积过大，因此性能优化也主要从这些方面来分析。本文主要是自己平时的工作积累和参考别人的文章，而进行总结，基于 &lt;code&gt;Webpack4&lt;/code&gt; 版本。&lt;/p&gt;
&lt;h1&gt;构建分析&lt;/h1&gt;
&lt;h2&gt;编译速度分析&lt;/h2&gt;
&lt;p&gt;对 &lt;code&gt;Webpack&lt;/code&gt; 构建速度进行优化的首要任务就是去知道哪些地方值得我们注意。
&lt;code&gt;speed-measure-webpack-plugin&lt;/code&gt; 插件能够测量 Webpack 构建速度&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; SMP  ⏱
General output time took 38.3 secs

 SMP  ⏱  Plugins
HtmlWebpackPlugin took 1.31 secs
CopyPlugin took 0.016 secs
OptimizeCssAssetsWebpackPlugin took 0.002 secs
ContextReplacementPlugin took 0.001 secs
MiniCssExtractPlugin took 0 secs
DefinePlugin took 0 secs

 SMP  ⏱  Loaders
_babel-loader@8.1.0@babel-loader took 29.98 secs
  module count = 1503
_babel-loader@8.1.0@babel-loader, and
_eslint-loader@3.0.4@eslint-loader took 18.74 secs
  module count = 86
_css-loader@3.6.0@css-loader, and
_less-loader@5.0.0@less-loader took 16.45 secs
  module count = 64
modules with no loaders took 2.24 secs
  module count = 7
_file-loader@5.1.0@file-loader took 1.03 secs
  module count = 17
_style-loader@1.3.0@style-loader, and
_css-loader@3.6.0@css-loader, and
_less-loader@5.0.0@less-loader took 0.102 secs
  module count = 64
_html-webpack-plugin@3.2.0@html-webpack-plugin took 0.021 secs
  module count = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;居然达到了惊人的 &lt;strong&gt;38.3&lt;/strong&gt; 秒，虽然有点不是很准确，但是非常慢。发现 &lt;code&gt;babel-loader、eslint-loader、css-loader、less-loader&lt;/code&gt; 占据了大头。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const webpackBase = require(&apos;./webpack.base.conf&apos;);
const path = require(&apos;path&apos;);

const SpeedMeasureWebpackPlugin = require(&apos;speed-measure-webpack-plugin&apos;);
const smp = new SpeedMeasureWebpackPlugin();

module.exports = smp.wrap({
    // 配置源码显示方式
    devtool: &apos;eval-source-map&apos;,
    mode: &apos;development&apos;,
    entry: {
        app: [&apos;./src/index.jsx&apos;]
    },
    output: {
        path: path.resolve(__dirname, &apos;dist&apos;),
        filename: &apos;index.js&apos;
    },
    resolve: webpackBase.resolve,
    module: webpackBase.module,
    stats: webpackBase.stats,
    optimization: webpackBase.optimization,
    plugins: [
        webpackBase.plugins.html,
        webpackBase.plugins.miniCssExtract,
        webpackBase.plugins.optimizeCssAssets,
        // webpackBase.plugins.progressBarPlugin,
        webpackBase.plugins.ContextReplacementPlugin,
        webpackBase.plugins.DefinePlugin,
        // webpackBase.plugins.AntdDayjsWebpackPlugin,
        webpackBase.plugins.CopyPlugin
        // webpackBase.plugins.HotModuleReplacementPlugin
    ],
    devServer: webpackBase.devServer,
    watchOptions: webpackBase.watchOptions,
    externals: webpackBase.externals
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;打包体积分析&lt;/h2&gt;
&lt;p&gt;通过 &lt;code&gt;webpack-bundle-analyzer&lt;/code&gt; 插件能够在 &lt;code&gt;Webpack&lt;/code&gt; 构建结束后生成构建产物体积报告，配合可视化的页面，能够直观知道产物中的具体占用体积。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const BundleAnalyzerPlugin = require(&apos;webpack-bundle-analyzer&apos;).BundleAnalyzerPlugin;
module.exports = {
  plugins: bundleAnalyzer: new BundleAnalyzerPlugin({ analyzerPort: 8081 })],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果图如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/98435967-3bb86400-2112-11eb-8508-6e3c5c6754e4.jpg&quot; alt=&quot;5061604735941_ pic_hd&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看出一个很明显的问题就是 &lt;code&gt;Ant Design、TRTC、Mobx&lt;/code&gt; 这些库，没有排除。&lt;/p&gt;
&lt;p&gt;打包体积如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/98436005-9356cf80-2112-11eb-8f01-5b8347890498.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;如何优化&lt;/h1&gt;
&lt;h2&gt;缩小构建目标&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;优化 &lt;code&gt;resolve.modules&lt;/code&gt; 配置（减少模块搜索层级和不必要的编译工作）&lt;/li&gt;
&lt;li&gt;优化 &lt;code&gt;resolve.extensions&lt;/code&gt; 配置&lt;/li&gt;
&lt;li&gt;增加缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const path = require(&apos;path&apos;);
module.exports = {
    resolve: {
        // 自动解析确定的扩展
        extensions: [&apos;.js&apos;, &apos;.jsx&apos;, &apos;.css&apos;, &apos;.less&apos;, &apos;.json&apos;],
        alias: {
            // 创建 import 或 require 的别名，来确保模块引入变得更简单
            &apos;react&apos;: path.resolve( __dirname ,&apos;./node_modules/react/dist/react.min.js&apos;)
        },
        // 当从 npm 包导入模块时，此选项将决定在 `package.json` 中使用哪个字段导入模块
        // 默认值为 browser -&amp;gt; module -&amp;gt; main
        mainFields: [&apos;main&apos;]
    },
    module: {
        rules: [
            {
                // 排除node_modules模块
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                // 开启缓存
                loader: &apos;babel-loader?cacheDirectory=true&apos;
            }
        ]
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用 thread-loader，开启多进程&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;thread-loader&lt;/code&gt; 会将你的 &lt;code&gt;loader&lt;/code&gt; 放置在一个 &lt;code&gt;worker&lt;/code&gt; 池里面运行，每个 &lt;code&gt;worker&lt;/code&gt; 都是一个单独的有 &lt;strong&gt;600ms&lt;/strong&gt; 限制的 &lt;code&gt;node.js&lt;/code&gt; 进程。同时跨进程的数据交换也会被限制。请在高开销的 &lt;code&gt;loader&lt;/code&gt; 中使用，否则效果不佳。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve(&apos;src&apos;),
        use: [
          &apos;thread-loader&apos;,
          // your expensive loader (e.g babel-loader)
        ],
      },
    ],
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用 hard-source-webpack-plugin&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;Webpack4&lt;/code&gt; &lt;code&gt;中，hard-source-webpack-plugin&lt;/code&gt; 是 &lt;code&gt;DLL&lt;/code&gt; 的更好替代者。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hard-source-webpack-plugin&lt;/code&gt; 是 &lt;code&gt;Webpack&lt;/code&gt; 的插件，为模块提供中间缓存步骤。为了查看结果，您需要使用此插件运行 &lt;code&gt;Webpack&lt;/code&gt; 两次：第一次构建将花费正常的时间。第二次构建将显着加快（大概提升 &lt;strong&gt;90%&lt;/strong&gt; 的构建速度）。不过该插件很久没更新了，不太建议使用。&lt;/p&gt;
&lt;h2&gt;去掉 eslint-loader&lt;/h2&gt;
&lt;p&gt;由于我项目中使用了 &lt;code&gt;eslint-loader&lt;/code&gt; 如果配置了 &lt;code&gt;precommit&lt;/code&gt;，其实可以去掉的。&lt;/p&gt;
&lt;h2&gt;通过 externals 把相关的包，排除&lt;/h2&gt;
&lt;p&gt;Webpack&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
    // externals 排除对应的包，注：排除掉的包必须要用script标签引入下
    externals: {
        react: &apos;React&apos;,
        &apos;react-dom&apos;: &apos;ReactDOM&apos;,
        &apos;trtc-js-sdk&apos;: &apos;TRTC&apos;,
        bizcharts: &apos;BizCharts&apos;,
        antd: &apos;antd&apos;,
        mobx: &apos;mobx&apos;,
        &apos;mobx-react&apos;: &apos;mobxReact&apos;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;index.html&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;zh&quot;&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;
        &amp;lt;meta
            name=&quot;viewport&quot;
            content=&quot;width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0&quot;
        /&amp;gt;
        &amp;lt;meta name=&quot;baidu-site-verification&quot; content=&quot;ptk9VJudKz&quot; /&amp;gt;
        &amp;lt;link
            rel=&quot;stylesheet&quot;
            href=&quot;https://xxx/antd.min3.26.20.css&quot;
        /&amp;gt;
        &amp;lt;title&amp;gt;webpack&amp;lt;/title&amp;gt;
        &amp;lt;script
            type=&quot;text/javascript&quot;
            src=&quot;https://xxx/17.0.0react.production.min.js&quot;
        &amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script
            type=&quot;text/javascript&quot;
            src=&quot;https://xxx/17.0.0react-dom.production.min.js&quot;
        &amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script
            type=&quot;text/javascript&quot;
            src=&quot;https://xxx/BizCharts3.5.8.js&quot;
        &amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script
            type=&quot;text/javascript&quot;
            src=&quot;https://xxx/trtc4.6.7.js&quot;
        &amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script
            type=&quot;text/javascript&quot;
            src=&quot;https://xxx/moment2.29.1.min.js&quot;
        &amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script
            type=&quot;text/javascript&quot;
            src=&quot;https://xxx/moment2.29.1zh-cn.js&quot;
        &amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script
            type=&quot;text/javascript&quot;
            src=&quot;https://xxx/polyfill.min7.8.0.js&quot;
        &amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script
            type=&quot;text/javascript&quot;
            src=&quot;https://xxx/antd.min3.26.20.js&quot;
        &amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script
            type=&quot;text/javascript&quot;
            src=&quot;https://xxx/mobx.umd.min5.13.1.js&quot;
        &amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script
            type=&quot;text/javascript&quot;
            src=&quot;https://xxx/mobx-react.index.min5.4.4.js&quot;
        &amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;JS 压缩&lt;/h2&gt;
&lt;p&gt;从 &lt;code&gt;Webpack4&lt;/code&gt; 开始，默认情况下使用 &lt;code&gt;terser&lt;/code&gt; &lt;code&gt;压缩生产环境下的输出结果。Terser&lt;/code&gt; 是一款兼容 &lt;code&gt;ES2015 +&lt;/code&gt; 的 &lt;code&gt;JavaScript&lt;/code&gt; 压缩器。与 &lt;code&gt;UglifyJS&lt;/code&gt;（许多项目的早期标准）相比，它是面向未来的选择。有一个 &lt;code&gt;UglifyJS&lt;/code&gt; 的分支—— &lt;code&gt;uglify-es&lt;/code&gt;，但由于它不再维护，于是就从这个分支诞生出了一个独立分支，它就是 &lt;code&gt;terser&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const TerserPlugin = require(&apos;terser-webpack-plugin&apos;);

module.exports = {
    optimization: {
        minimizer: [
            // 压缩js
            new TerserPlugin({
                test: /\.(jsx|js)$/,
                extractComments: true,
                parallel: true,
                cache: true
            })
        ]
    },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;CSS 压缩&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Webpack&lt;/code&gt; 4.0 以后，官方推荐使用 &lt;code&gt;mini-css-extract-plugin&lt;/code&gt; 插件来打包 &lt;code&gt;CSS&lt;/code&gt; 文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const MiniCssExtractPlugin = require(&apos;mini-css-extract-plugin&apos;);

module.exports = {
    module: {
        rules: [
            {
                test: /\.(css|less)$/,
                use: [MiniCssExtractPlugin.loader]
            }
        ]
    },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;FAQ&lt;/h1&gt;
&lt;h3&gt;Ant Design 无法加载&lt;/h3&gt;
&lt;p&gt;请确保加载顺序，&lt;code&gt;Moment、Polyfill&lt;/code&gt; 放在 &lt;code&gt;Ant Design&lt;/code&gt; 前面加载&lt;/p&gt;
&lt;h3&gt;MobX 无法加载&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;MobX&lt;/code&gt; 引入 &lt;code&gt;mobx.umd.min.js&lt;/code&gt; &lt;code&gt;库，mobx-react&lt;/code&gt; 需要引入&lt;/p&gt;
&lt;h1&gt;package.json&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;name&quot;: &quot;webpack&quot;,
    &quot;version&quot;: &quot;1.0.0&quot;,
    &quot;private&quot;: true,
    &quot;main&quot;: &quot;index.js&quot;,
    &quot;dependencies&quot;: {
        &quot;antd&quot;: &quot;^3.26.20&quot;,
        &quot;babel-eslint&quot;: &quot;^10.0.3&quot;,
        &quot;babel-loader&quot;: &quot;^8.0.0&quot;,
        &quot;babel-plugin-import&quot;: &quot;^1.13.0&quot;,
        &quot;babel-plugin-react-css-modules&quot;: &quot;^5.2.6&quot;,
        &quot;bizcharts&quot;: &quot;^3.5.8&quot;,
        &quot;china-division&quot;: &quot;^2.3.1&quot;,
        &quot;compression-webpack-plugin&quot;: &quot;^3.0.1&quot;,
        &quot;copy-webpack-plugin&quot;: &quot;^5.1.1&quot;,
        &quot;css-loader&quot;: &quot;^3.2.0&quot;,
        &quot;eslint&quot;: &quot;^6.8.0&quot;,
        &quot;eslint-config-prettier&quot;: &quot;^6.11.0&quot;,
        &quot;eslint-config-standard&quot;: &quot;^14.1.0&quot;,
        &quot;eslint-loader&quot;: &quot;^3.0.4&quot;,
        &quot;eslint-plugin-import&quot;: &quot;^2.20.0&quot;,
        &quot;eslint-plugin-promise&quot;: &quot;^4.2.1&quot;,
        &quot;eslint-plugin-react&quot;: &quot;^7.17.0&quot;,
        &quot;eslint-plugin-standard&quot;: &quot;^4.0.1&quot;,
        &quot;html-webpack-plugin&quot;: &quot;^3.2.0&quot;,
        &quot;less&quot;: &quot;^3.8.1&quot;,
        &quot;less-loader&quot;: &quot;^5.0.0&quot;,
        &quot;lint-staged&quot;: &quot;^10.0.8&quot;,
        &quot;mini-css-extract-plugin&quot;: &quot;^0.8.0&quot;,
        &quot;mobx&quot;: &quot;^5.13.1&quot;,
        &quot;mobx-react&quot;: &quot;^5.4.4&quot;,
        &quot;optimize-css-assets-webpack-plugin&quot;: &quot;^5.0.1&quot;,
        &quot;pre-commit&quot;: &quot;^1.2.2&quot;,
        &quot;progress-bar-webpack-plugin&quot;: &quot;^1.12.1&quot;,
        &quot;react&quot;: &quot;^17.0.0&quot;,
        &quot;react-dom&quot;: &quot;^17.0.0&quot;,
        &quot;speed-measure-webpack-plugin&quot;: &quot;^1.3.1&quot;,
        &quot;style-loader&quot;: &quot;^1.2.1&quot;,
        &quot;terser-webpack-plugin&quot;: &quot;^2.2.1&quot;,
        &quot;trtc-js-sdk&quot;: &quot;^4.6.7&quot;,
        &quot;viewerjs&quot;: &quot;^1.5.0&quot;,
        &quot;webpack&quot;: &quot;^4.41.2&quot;,
        &quot;webpack-bundle-analyzer&quot;: &quot;^3.6.0&quot;,
        &quot;webpack-cli&quot;: &quot;^3.3.10&quot;,
        &quot;webpack-dev-server&quot;: &quot;^3.10.1&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;最终效果&lt;/h1&gt;
&lt;p&gt;打包体积：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4fe2853c7aaa4168b4cb6a964e70d4c5~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;5381605008681_ pic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;打包体积由原先 &lt;strong&gt;2.1M&lt;/strong&gt; 变成了 &lt;strong&gt;882KB&lt;/strong&gt;，可以说效果非常巨大。&lt;/p&gt;
&lt;p&gt;包依赖：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/98758900-c64add00-240a-11eb-9cf2-ef9eb99e8a8d.jpg&quot; alt=&quot;5441605062491_ pic_hd&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Ant Design、TRTC、Mobx&lt;/code&gt; 这些库也没了&lt;/p&gt;
&lt;p&gt;编译速度：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SMP  ⏱
General output time took 10.67 secs

 SMP  ⏱  Plugins
HtmlWebpackPlugin took 1.69 secs
BundleAnalyzerPlugin took 0.091 secs
CopyPlugin took 0.011 secs
MiniCssExtractPlugin took 0.003 secs
OptimizeCssAssetsWebpackPlugin took 0.002 secs
DefinePlugin took 0.001 secs
ContextReplacementPlugin took 0 secs

 SMP  ⏱  Loaders
_babel-loader@8.1.0@babel-loader took 8.26 secs
  module count = 277
_babel-loader@8.1.0@babel-loader, and
_eslint-loader@3.0.4@eslint-loader took 7.18 secs
  module count = 86
_css-loader@3.6.0@css-loader, and
_less-loader@5.0.0@less-loader took 1.94 secs
  module count = 28
modules with no loaders took 0.728 secs
  module count = 12
_file-loader@5.1.0@file-loader took 0.392 secs
  module count = 17
_style-loader@1.3.0@style-loader, and
_css-loader@3.6.0@css-loader, and
_less-loader@5.0.0@less-loader took 0.052 secs
  module count = 28
_html-webpack-plugin@3.2.0@html-webpack-plugin took 0.026 secs
  module count = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译速度由原先 &lt;strong&gt;38.3 secs&lt;/strong&gt;（实际编译速度大概 15 秒左右），减少到 &lt;strong&gt;10.67 secs&lt;/strong&gt;（实际编译速度 10 秒左右）。&lt;/p&gt;
&lt;h1&gt;国内外公共 CDN 地址&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bootcdn.cn/&quot;&gt;BootCDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cdnjs.com/&quot;&gt;cdnjs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;参考资料&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tsejx.github.io/webpack-guidebook/&quot;&gt;Webpack Guidebook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/g9n7RIMldSDl-1XPlZRJKg&quot;&gt;Webpack 核心知识有哪些？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;博客&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;博客&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Webpack4</category><author>江辰</author></item><item><title>Taro init遇到权限问题（mac环境）</title><link>https://github.com/posts/taro-init%E9%81%87%E5%88%B0%E6%9D%83%E9%99%90%E9%97%AE%E9%A2%98mac%E7%8E%AF%E5%A2%83/</link><guid isPermaLink="true">https://github.com/posts/taro-init%E9%81%87%E5%88%B0%E6%9D%83%E9%99%90%E9%97%AE%E9%A2%98mac%E7%8E%AF%E5%A2%83/</guid><pubDate>Mon, 26 Oct 2020 14:21:27 GMT</pubDate><content:encoded>&lt;h2&gt;问题描述&lt;/h2&gt;
&lt;p&gt;Taro init 遇到没有权限创建项目，具体报错如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(node:71338) UnhandledPromiseRejectionWarning: Error: EACCES: permission denied, mkdir &apos;/usr/local/lib/node_modules/@tarojs/cli/templates/taro-temp&apos;
(node:71338) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:71338) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;解决办法&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}&lt;/code&gt;&lt;/p&gt;
</content:encoded><category>React</category><category>Taro</category><author>江辰</author></item><item><title>Taro实现列表下拉刷新无限滚动</title><link>https://github.com/posts/taro%E5%AE%9E%E7%8E%B0%E5%88%97%E8%A1%A8%E4%B8%8B%E6%8B%89%E5%88%B7%E6%96%B0%E6%97%A0%E9%99%90%E6%BB%9A%E5%8A%A8/</link><guid isPermaLink="true">https://github.com/posts/taro%E5%AE%9E%E7%8E%B0%E5%88%97%E8%A1%A8%E4%B8%8B%E6%8B%89%E5%88%B7%E6%96%B0%E6%97%A0%E9%99%90%E6%BB%9A%E5%8A%A8/</guid><pubDate>Mon, 03 Aug 2020 10:18:47 GMT</pubDate><content:encoded>&lt;h2&gt;订单页&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;order.jsx&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Taro, { Component } from &apos;@tarojs/taro&apos;;
import { View } from &apos;@tarojs/components&apos;;
import { AtTabs, AtTabsPane } from &apos;taro-ui&apos;;
import request from &apos;../../request&apos;;
import { STATUS } from &apos;./../../config&apos;;
import List from &apos;./components/list&apos;;
import &apos;./index.less&apos;;

class Index extends Component {
    constructor(props) {
        super(props);
        this.state = {
            current: 0,
            datas: {},
            // 是否滚动到底部
            isScrollToLower: false,
            isRender: false
        };
        // 由于taro-ui 无法解决刷新五次的问题，所以通过这种方式解决
        this.refresherTriggered = false;
        this.refresherTriggered2 = false;
        this.refresherTriggered3 = false;
        this.refresherTriggered4 = false;
        this.refresherTriggered5 = false;
        this.user = Taro.getStorageSync(&apos;user&apos;) ? JSON.parse(Taro.getStorageSync(&apos;user&apos;)) : {};
        this.searchParams = {
            pageIndex: 1,
            pageSize: 10,
            agentId: this.user.agent ? (this.user.agent.agentRole.level != &apos;1&apos; ? &apos;&apos; : this.user.agent._id) : &apos;&apos;,
            searchTenant: this.user.agent ? this.user.agent.tenant._id : &apos;&apos;,
            searchOrgAccount: this.user.agent ? this.user.agent.orgAccount._id : &apos;&apos;,
            searchAgent: this.user.agent ? this.user.agent._id : &apos;&apos;,
            state: &apos;all&apos;
        };
    }

    // 下拉刷新
    onRefresherRefresh = async () =&amp;gt; {
        const { current } = this.state;
        if(current == 0) this.refresherTriggered = true;
        if(current == 1) this.refresherTriggered2 = true;
        if(current == 2) this.refresherTriggered3 = true;
        if(current == 3) this.refresherTriggered4 = true;
        if(current == 4) this.refresherTriggered5 = true;
        this.setState({ isRender: !this.state.isRender });

        let res = await this.getList(this.searchParams);
        if(current == 0) this.refresherTriggered = false;
        if(current == 1) this.refresherTriggered2 = false;
        if(current == 2) this.refresherTriggered3 = false;
        if(current == 3) this.refresherTriggered4 = false;
        if(current == 4) this.refresherTriggered5 = false;

        this.setState({
            datas: res.result
        });
    }

    componentDidMount() {
        Taro.setNavigationBarTitle({
            title: &apos;工作台&apos;
        });

        this.getList(this.searchParams).then(res =&amp;gt; {
            this.setState({
                datas: res.result
            });
        });
    }

    // 获取工单列表
    getList(data) {
        return new Promise((resolve, reject) =&amp;gt; {
            request({
                method: &apos;GET&apos;,
                url: &apos;report/list&apos;,
                data
            }).then(res =&amp;gt; {
                if (res.errCode == 0) resolve(res);

                if (res.errCode != 0) {
                    Taro.showToast({
                        title: res.errInfo,
                        icon: &apos;none&apos;,
                        duration: 2000
                    });
                    reject(res.errInfo);
                }
            });
        });
    }

    handleClick = async (val) =&amp;gt; {
        this.searchParams.state = STATUS[val];
        let res = await this.getList(this.searchParams);

        this.setState({
            datas: res.result,
            current: val
        });
    }

    itemClick = ({ flowId, item }) =&amp;gt; {
        Taro.navigateTo({
            url: `/pages/WorkDetails/index?flowId=${flowId}&amp;amp;item=${JSON.stringify(item)}`
        });
    }

    // 滚动到底触发
    onScrollToLower = async () =&amp;gt; {
        const { datas } = this.state;
        let pageIndex = this.searchParams.pageIndex + 1;
        if ((pageIndex - 1) * datas.pageSize &amp;lt; datas.count) {
            this.searchParams.pageIndex++;
            this.setState({ isScrollToLower: true });
            let res = await this.getList(this.searchParams);
            setTimeout(() =&amp;gt; {
                this.setState({
                    datas: {
                        ...res.result,
                        list: datas.list.concat(res.result.list)
                    },
                    isScrollToLower: false
                });
            }, 500);
        }
    }

    render() {
        const { current, datas, isScrollToLower } = this.state;
        // const tabList = [{ title: datas &amp;amp;&amp;amp; datas.stateList &amp;amp;&amp;amp; datas.stateList.all ? `全部(${datas.stateList.all})` : &apos;全部(0)&apos; }, { title: datas &amp;amp;&amp;amp; datas.stateList &amp;amp;&amp;amp; datas.stateList.untreated ? `待处理(${datas.stateList.untreated})` : &apos;待处理(0)&apos; }, { title: datas &amp;amp;&amp;amp; datas.stateList &amp;amp;&amp;amp; datas.stateList.checking ? `处理中(${datas.stateList.checking})` : &apos;处理中(0)&apos; }, { title: datas &amp;amp;&amp;amp; datas.stateList &amp;amp;&amp;amp; datas.stateList.checked ? `已处理(${datas.stateList.checked})` : &apos;已处理(0)&apos; }, { title: datas &amp;amp;&amp;amp; datas.stateList &amp;amp;&amp;amp; datas.stateList.closed ? `已撤销(${datas.stateList.closed})` : &apos;已撤销(0)&apos; }];

        const tabList = [{ title: &apos;全部&apos; }, { title: &apos;待处理&apos; }, { title: &apos;处理中&apos; }, { title: &apos;已处理&apos; }, { title: &apos;已撤销&apos; }];
        return (
            &amp;lt;View className=&quot;work-orders-container&quot;&amp;gt;
                &amp;lt;AtTabs current={current} tabList={tabList} onClick={this.handleClick}&amp;gt;
                    {
                        tabList.map((item, index) =&amp;gt; {
                            let refresherTriggered = false;
                            if(index == 0) refresherTriggered = this.refresherTriggered;
                            if(index == 1) refresherTriggered = this.refresherTriggered2;
                            if(index == 2) refresherTriggered = this.refresherTriggered3;
                            if(index == 3) refresherTriggered = this.refresherTriggered4;
                            if(index == 4) refresherTriggered = this.refresherTriggered5;

                            return (
                                &amp;lt;AtTabsPane key={index.title} current={current} index={index} &amp;gt;
                                    &amp;lt;List
                                        onScrollToLower={this.onScrollToLower}
                                        datas={datas}
                                        isScrollToLower={isScrollToLower}
                                        refresherTriggered={refresherTriggered}
                                        onRefresherRefresh={this.onRefresherRefresh}
                                        itemClick={this.itemClick}
                                    /&amp;gt;
                                &amp;lt;/AtTabsPane&amp;gt;
                            );
                        })
                    }
                &amp;lt;/AtTabs&amp;gt;
            &amp;lt;/View&amp;gt;
        );
    }
}

export default Index;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;order.less&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;page {
    overflow: hidden;
}

.work-orders-container {
    background-color: #fafafa;

    .tab-list {
        margin-bottom: 24px;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;卡片页&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;list.jsx&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { View, ScrollView } from &apos;@tarojs/components&apos;;
import { AtList } from &apos;taro-ui&apos;;
import RowCard from &apos;../../../component/RowCard&apos;;
import &apos;./index.less&apos;;

// 列表
const List = (props) =&amp;gt; {
    const { onScrollToLower, datas, isScrollToLower, refresherTriggered, onRefresherRefresh, itemClick } = props;
    return (
        &amp;lt;ScrollView
            scrollY
            scrollWithAnimation
            scrollTop={0}
            refresherEnabled
            refresherTriggered={refresherTriggered}
            onRefresherRefresh={onRefresherRefresh}
            style={{ height: &apos;100vh&apos; }}
            onScrollToLower={onScrollToLower}
        &amp;gt;
            &amp;lt;View&amp;gt;
                {
                    datas &amp;amp;&amp;amp; datas.list &amp;amp;&amp;amp; datas.list.map(item =&amp;gt; {
                        return (
                            &amp;lt;AtList
                                className=&quot;tab-list&quot;
                                key={item._id}
                            &amp;gt;
                                &amp;lt;View onClick={() =&amp;gt; itemClick({ flowId: item.flowId, item })}&amp;gt;
                                    &amp;lt;RowCard
                                        title=&quot;业务类型：&quot;
                                    &amp;gt;
                                        &amp;lt;View&amp;gt;{item.taskType}&amp;lt;/View&amp;gt;
                                    &amp;lt;/RowCard&amp;gt;
                                    &amp;lt;RowCard
                                        title=&quot;客户姓名：&quot;
                                    &amp;gt;
                                        &amp;lt;View&amp;gt;{item.insurantName}&amp;lt;/View&amp;gt;
                                    &amp;lt;/RowCard&amp;gt;
                                    &amp;lt;RowCard
                                        title=&quot;联系电话：&quot;
                                    &amp;gt;
                                        &amp;lt;View&amp;gt;{item.insurantPhone}&amp;lt;/View&amp;gt;
                                    &amp;lt;/RowCard&amp;gt;
                                    &amp;lt;RowCard
                                        title=&quot;案件号：&quot;
                                    &amp;gt;
                                        &amp;lt;View&amp;gt;{item.reportIdArray}&amp;lt;/View&amp;gt;
                                    &amp;lt;/RowCard&amp;gt;
                                    &amp;lt;RowCard
                                        title=&quot;创建时间：&quot;
                                    &amp;gt;
                                        &amp;lt;View&amp;gt;{item.ctime}&amp;lt;/View&amp;gt;
                                    &amp;lt;/RowCard&amp;gt;
                                    &amp;lt;RowCard
                                        title=&quot;工单状态：&quot;
                                    &amp;gt;
                                        &amp;lt;View&amp;gt;{item.state}&amp;lt;/View&amp;gt;
                                    &amp;lt;/RowCard&amp;gt;
                                &amp;lt;/View&amp;gt;
                            &amp;lt;/AtList&amp;gt;
                        );
                    })
                }
                {
                    datas &amp;amp;&amp;amp; datas.list &amp;amp;&amp;amp; datas.list.length == 0 &amp;amp;&amp;amp;
                    &amp;lt;View className=&quot;empty-content&quot;&amp;gt;
                        没有内容
                    &amp;lt;/View&amp;gt;
                }
                {
                    isScrollToLower &amp;amp;&amp;amp;
                    &amp;lt;View className=&quot;scroll-lower&quot;&amp;gt;
                        正在加载中
                    &amp;lt;/View&amp;gt;
                }
            &amp;lt;/View&amp;gt;
        &amp;lt;/ScrollView&amp;gt;
    );
};

export default List;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;list.less&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.scroll-lower {
    display: flex;
    justify-content: center;
    color: #a8a8a8;
    height: 150px;
}

.empty-content {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 300px;
    font-size: 28px;
    color: #a8a8a8;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>React</category><category>Taro</category><category>Taro UI</category><author>江辰</author></item><item><title>命令行 MySQL 基本操作(CentOS)</title><link>https://github.com/posts/%E5%91%BD%E4%BB%A4%E8%A1%8C-mysql-%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9Ccentos/</link><guid isPermaLink="true">https://github.com/posts/%E5%91%BD%E4%BB%A4%E8%A1%8C-mysql-%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9Ccentos/</guid><pubDate>Fri, 24 Jul 2020 10:54:53 GMT</pubDate><content:encoded>&lt;h2&gt;安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ brew install mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;登录&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ mysql -uroot -p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;连接到 mysql 数据库，默认没有密码的，直接按回车进入。&lt;/p&gt;
&lt;h2&gt;启动数据库&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ service mysqld start
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;停止数据库&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ service mysqld stop
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;重启数据库&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ service mysqld restart 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;显示所有数据库&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ show databases;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/88373512-8e237e00-cdca-11ea-9f5f-a97a5cd0aef4.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;进入数据库&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ use mysql;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;显示所有表&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ show tables;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/88373543-9b406d00-cdca-11ea-83ab-dc217892ffd7.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;创建表名和字段&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ CREATE TABLE data (images VARCHAR(20));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;修改字段大小&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ ALTER TABLE data images MODIFY VARCHAR(100);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;查询表&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ select * from card;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;修改用户密码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ mysql -u root -p

$ show databases;

$ use mysql;

$ desc user;  # 用户表存放用户和面膜

$ update user set authentication_string=PASSWORD(&apos;123&apos;) where user = &apos;root&apos;;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>MySQL</category><category>CentOS</category><author>江辰</author></item><item><title>微信如何打开 VConsole</title><link>https://github.com/posts/%E5%BE%AE%E4%BF%A1%E5%A6%82%E4%BD%95%E6%89%93%E5%BC%80-vconsole/</link><guid isPermaLink="true">https://github.com/posts/%E5%BE%AE%E4%BF%A1%E5%A6%82%E4%BD%95%E6%89%93%E5%BC%80-vconsole/</guid><pubDate>Tue, 21 Jul 2020 07:15:37 GMT</pubDate><content:encoded>&lt;p&gt;微信打开这个网址&lt;code&gt;http://debugx5.qq.com&lt;/code&gt;或扫描下方二维码&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/87995302-b046a300-cb21-11ea-9811-8ada4639a290.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进去后，选择中间&quot;信息&quot;，一直往下翻，把 VConsole 打开，把下面两个 Content Cache 关掉。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/87995370-e5eb8c00-cb21-11ea-917a-db678581c11f.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后，打开你要调试的页面，就能看到右下角有个绿色按钮 VConsole。&lt;/p&gt;
</content:encoded><category>微信</category><author>江辰</author></item><item><title>如何搭建前端异常监控系统</title><link>https://github.com/posts/%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA%E5%89%8D%E7%AB%AF%E5%BC%82%E5%B8%B8%E7%9B%91%E6%8E%A7%E7%B3%BB%E7%BB%9F/</link><guid isPermaLink="true">https://github.com/posts/%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA%E5%89%8D%E7%AB%AF%E5%BC%82%E5%B8%B8%E7%9B%91%E6%8E%A7%E7%B3%BB%E7%BB%9F/</guid><pubDate>Sat, 04 Jul 2020 09:31:50 GMT</pubDate><content:encoded>&lt;h2&gt;什么是异常&lt;/h2&gt;
&lt;p&gt;是指用户在使用应用时，无法得到预期的结果。不同的异常带来的后果程度不同，轻则引起用户使用不悦，重则导致产品无法使用，从而使用户丧失对产品的认可。&lt;/p&gt;
&lt;h2&gt;为什么要处理异常&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;增强用户体验&lt;/li&gt;
&lt;li&gt;远程定位问题&lt;/li&gt;
&lt;li&gt;无法复现问题，特别是移动端，各种原因，可能是系统版本，机型等等&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;前端有哪些异常&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;异常&lt;/th&gt;
&lt;th&gt;频率&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript 异常（语法错误、代码错误）&lt;/td&gt;
&lt;td&gt;经常&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;静态资源加载异常（img、js、css）&lt;/td&gt;
&lt;td&gt;偶尔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ajax 请求异常&lt;/td&gt;
&lt;td&gt;偶尔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;promise 异常&lt;/td&gt;
&lt;td&gt;较少&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iframe 异常&lt;/td&gt;
&lt;td&gt;较少&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;如何捕获异常&lt;/h2&gt;
&lt;h3&gt;try-catch&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;try-catch&lt;/code&gt; 只能捕获同步运行错误，对语法和异步错误却捕获不到。&lt;/p&gt;
&lt;p&gt;1、同步运行错误&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
    kill;
} catch(err) {
    console.error(&apos;try: &apos;, err);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;code&gt;try: ReferenceError: kill is not defined&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2、无法捕获语法错误&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
    let name = &apos;1;
} catch(err) {
    console.error(&apos;try: &apos;, err);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;code&gt;Unterminated string constant&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;编译器能够阻止运行语法错误。&lt;/p&gt;
&lt;p&gt;3、无法捕获异步错误&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
    setTimeout(() =&amp;gt; {
        undefined.map(v =&amp;gt; v);
    }, 1000);
} catch(err) {
    console.error(&apos;try: &apos;, err);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;code&gt;Uncaught TypeError: Cannot read property &apos;map&apos; of undefined&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;window.onerror&lt;/h3&gt;
&lt;p&gt;当 &lt;code&gt;JavaScript&lt;/code&gt; 运行时错误（包括语法错误）发生时，&lt;code&gt;window&lt;/code&gt; 会触发一个 &lt;code&gt;ErrorEvent&lt;/code&gt; 接口的 &lt;code&gt;error&lt;/code&gt; 事件，并执行 &lt;code&gt;window.onerror()&lt;/code&gt; 若该函数返回 &lt;code&gt;true&lt;/code&gt;，则阻止执行默认事件处理函数。&lt;/p&gt;
&lt;p&gt;1、同步运行错误&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
* @param {String}  message   错误信息
* @param {String}  source    出错文件
* @param {Number}  lineno    行号
* @param {Number}  colno     列号
* @param {Object}  error     error对象
*/
window.onerror = (message, source, lineno, colno, error) =&amp;gt; {
    console.error(&apos;捕获异常：&apos;, message, source, lineno, colno, error);
    return true;
};

kill;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：捕获异常： &lt;code&gt;Uncaught ReferenceError: kill is not defined&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2、无法捕获语法错误&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
* @param {String}  message   错误信息
* @param {String}  source    出错文件
* @param {Number}  lineno    行号
* @param {Number}  colno     列号
* @param {Object}  error     error对象
*/
window.onerror = (message, source, lineno, colno, error) =&amp;gt; {
    console.error(&apos;捕获异常：&apos;, message, source, lineno, colno, error);
    return true;
};

let name = &apos;1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;code&gt;Unterminated string constant&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;编译器能够阻止运行语法错误。&lt;/p&gt;
&lt;p&gt;3、异步错误&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
* @param {String}  message   错误信息
* @param {String}  source    出错文件
* @param {Number}  lineno    行号
* @param {Number}  colno     列号
* @param {Object}  error     error对象
*/
window.onerror = (message, source, lineno, colno, error) =&amp;gt; {
    console.error(&apos;捕获异常：&apos;, message, source, lineno, colno, error);
    return true;
};

setTimeout(() =&amp;gt; {
    undefined.map(v =&amp;gt; v);
}, 1000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;code&gt;捕获异常： Uncaught TypeError: Cannot read property &apos;map&apos; of undefined&lt;/code&gt;`&lt;/p&gt;
&lt;h3&gt;window.addEventListener(&apos;error&apos;)&lt;/h3&gt;
&lt;p&gt;当一项资源（如 &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 或 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; ）加载失败，加载资源的元素会触发一个 &lt;code&gt;Event&lt;/code&gt; 接口的 &lt;code&gt;error&lt;/code&gt; 事件，并执行该元素上的 &lt;code&gt;onerror()&lt;/code&gt; 处理函数。这些 &lt;code&gt;error&lt;/code&gt; 事件不会向上冒泡到 &lt;code&gt;window&lt;/code&gt;，不过（至少在 Firefox 中）能被单一的 &lt;code&gt;window&lt;/code&gt;.&lt;code&gt;addEventListener&lt;/code&gt; 捕获。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
window.addEventListener(&apos;error&apos;, (err) =&amp;gt; {
    console.error(&apos;捕获异常：&apos;, err);
}, true);
&amp;lt;/script&amp;gt;
&amp;lt;img src=&quot;./test.jpg&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;code&gt;捕获异常：Event {isTrusted: true, type: &quot;error&quot;, target: img, currentTarget: Window, eventPhase: 1, …}&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;window.addEventListener(&apos;unhandledrejection&apos;)&lt;/h3&gt;
&lt;p&gt;当 &lt;code&gt;Promise&lt;/code&gt; 被 &lt;code&gt;reject&lt;/code&gt; 且没有 &lt;code&gt;reject&lt;/code&gt; 处理器的时候，会触发 &lt;code&gt;unhandledrejection&lt;/code&gt; 事件；这可能发生在 &lt;code&gt;window&lt;/code&gt; 下，但也可能发生在 &lt;code&gt;Worker&lt;/code&gt; 中。 这对于调试回退错误处理非常有用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;window.addEventListener(&quot;unhandledrejection&quot;, (err) =&amp;gt; {
    err.preventDefault();
    console.error(&apos;捕获异常：&apos;, err);
});

Promise.reject(&apos;promise&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;code&gt;捕获异常：PromiseRejectionEvent {isTrusted: true, promise: Promise, reason: &quot;promise&quot;, type: &quot;unhandledrejection&quot;, target: Window, …}&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Vue&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Vue.config.errorHandler = (err, vm, info) =&amp;gt; {
  console.error(&apos;捕获异常：&apos;, err, vm, info);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;React&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;React16&lt;/code&gt;，提供了一个内置函数 &lt;code&gt;componentDidCatch&lt;/code&gt; ，使用它可以非常简单的获取到 &lt;code&gt;React&lt;/code&gt; 下的错误信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;componentDidCatch(error, info) {
    console.error(&apos;捕获异常：&apos;, error, info);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，推荐&lt;code&gt;ErrorBoundary&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;用户界面中的 &lt;code&gt;JavaScript&lt;/code&gt; 错误不应破坏整个应用程序。为了为 &lt;code&gt;React&lt;/code&gt; 用户解决此问题，&lt;code&gt;React16&lt;/code&gt; 引入了“错误边界”的新概念。&lt;/p&gt;
&lt;p&gt;新建 &lt;code&gt;ErrorBoundary.jsx&lt;/code&gt; 组件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { Result, Button } from &apos;antd&apos;;

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false, info: &apos;&apos; };
    }

    static getDerivedStateFromError(error) {
        return { hasError: true };
    }

    componentDidCatch(error, info) {
        this.setState({
            info: error + &apos;&apos;
        });
    }

    render() {
        if (this.state.hasError) {
            // 你可以渲染任何自定义的降级 UI
            return (
                &amp;lt;Result
                    status=&quot;500&quot;
                    title=&quot;500&quot;
                    subTitle={this.state.info}
                    extra={&amp;lt;Button type=&quot;primary&quot;&amp;gt;Report feedback&amp;lt;/Button&amp;gt;}
                /&amp;gt;
            );
        }

        return this.props.children;
    }
}

export default ErrorBoundary;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ErrorBoundary&amp;gt;
    &amp;lt;App /&amp;gt;
&amp;lt;/ErrorBoundary&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意&lt;/p&gt;
&lt;p&gt;错误边界不会捕获以下方面的错误：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事件处理程序&lt;/li&gt;
&lt;li&gt;异步代码（例如 &lt;code&gt;setTimeout&lt;/code&gt; 或 &lt;code&gt;requestAnimationFrame&lt;/code&gt; 回调）&lt;/li&gt;
&lt;li&gt;服务器端渲染&lt;/li&gt;
&lt;li&gt;在错误边界本身（而不是其子级）中引发的错误&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;iframe&lt;/h3&gt;
&lt;p&gt;由于浏览器设置的“同源策略”，无法非常优雅的处理 &lt;code&gt;iframe&lt;/code&gt; 异常，除了基本属性（例如其宽度和高度）之外，无法从 &lt;code&gt;iframe&lt;/code&gt; 获得很多信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
    document.getElementById(&quot;myiframe&quot;).onload = () =&amp;gt; {
        const self = document.getElementById(&apos;myiframe&apos;);

        try {
            (self.contentWindow || self.contentDocument).location.href;
        } catch(err) {
            console.log(&apos;捕获异常：&apos; + err);
        }
    };
&amp;lt;/script&amp;gt;

&amp;lt;iframe id=&quot;myiframe&quot; src=&quot;https://nibuzhidao.com&quot; frameBorder=&quot;0&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Sentry&lt;/h3&gt;
&lt;p&gt;业界非常优秀的一款监控异常的产品，作者也是用的这款，文档齐全。&lt;/p&gt;
&lt;h2&gt;需要上报哪些信息&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;错误 id&lt;/li&gt;
&lt;li&gt;用户 id&lt;/li&gt;
&lt;li&gt;用户名&lt;/li&gt;
&lt;li&gt;用户 IP&lt;/li&gt;
&lt;li&gt;设备&lt;/li&gt;
&lt;li&gt;错误信息&lt;/li&gt;
&lt;li&gt;游览器&lt;/li&gt;
&lt;li&gt;系统版本&lt;/li&gt;
&lt;li&gt;应用版本&lt;/li&gt;
&lt;li&gt;机型&lt;/li&gt;
&lt;li&gt;时间戳&lt;/li&gt;
&lt;li&gt;异常级别（error、warning、info）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;异常上报&lt;/h2&gt;
&lt;p&gt;1、Ajax 发送数据&lt;/p&gt;
&lt;p&gt;2、动态创建 img 标签&lt;/p&gt;
&lt;p&gt;如果异常数据量大，导致服务器负载高，调整发送频率（可以考虑把异常信息存储在客户端，设定时间阀值，进行上报）或设置采集率（采集率应该通过实际情况来设定，随机数，或者某些用户特征都是不错的选择）。&lt;/p&gt;
&lt;h2&gt;流程图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2020/7/5/1731cfac32a820ce?w=1118&amp;amp;h=982&amp;amp;f=png&amp;amp;s=61280&quot; alt=&quot;异常监控流程图&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://jartto.wang/2018/11/20/js-exception-handling/index.html&quot;&gt;如何优雅处理前端异常？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web&quot;&gt;MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cn.vuejs.org/&quot;&gt;Vue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;博客&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;欢迎关注我的博客&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>异常监控</category><author>江辰</author></item><item><title>如何基于tristana项目模板，打造一个基本列表展示</title><link>https://github.com/posts/%E5%A6%82%E4%BD%95%E5%9F%BA%E4%BA%8Etristana%E9%A1%B9%E7%9B%AE%E6%A8%A1%E6%9D%BF%E6%89%93%E9%80%A0%E4%B8%80%E4%B8%AA%E5%9F%BA%E6%9C%AC%E5%88%97%E8%A1%A8%E5%B1%95%E7%A4%BA/</link><guid isPermaLink="true">https://github.com/posts/%E5%A6%82%E4%BD%95%E5%9F%BA%E4%BA%8Etristana%E9%A1%B9%E7%9B%AE%E6%A8%A1%E6%9D%BF%E6%89%93%E9%80%A0%E4%B8%80%E4%B8%AA%E5%9F%BA%E6%9C%AC%E5%88%97%E8%A1%A8%E5%B1%95%E7%A4%BA/</guid><pubDate>Wed, 17 Jun 2020 10:28:49 GMT</pubDate><content:encoded>&lt;p&gt;基于 tristana 这套项目模板，引导大家如何创建列表展示，搜索，自动处理 loading 状态等。全部代码在仓库下。&lt;/p&gt;
&lt;p&gt;最终效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/76682388-c6ee6200-6636-11ea-9737-ebee3bab0b19.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;网站效果预览：https://order.downfuture.com&lt;/p&gt;
&lt;h2&gt;开始之前&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;确保 node 版本是 8.4 或以上&lt;/li&gt;
&lt;li&gt;用 cnpm 或 yarn 能节约你安装依赖的时间&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Step 1. 安装 tristana 并创建项目&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ git clone https://github.com/Cherry-Team/tristana.git
$ cd tristana
$ cnpm i
$ npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器会自动开启，并打开 http://localhost:8080&lt;/p&gt;
&lt;h2&gt;Step 2. 生成 Dashboard 路由&lt;/h2&gt;
&lt;p&gt;我们要新增路由，新建页面文件和在 routeConfig 引入该文件即可。&lt;/p&gt;
&lt;p&gt;新建 &lt;code&gt;src/pages/Dashboard/index.js&lt;/code&gt;，内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { Component } from &apos;react&apos;;
class Index extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            &amp;lt;section&amp;gt;
                123
            &amp;lt;/section&amp;gt;
        );
    }
}

export default Index;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在&lt;code&gt;src/routeConfig&lt;/code&gt; 文件下，引入该组件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 路由配置文件
import React, { lazy } from &apos;react&apos;;
import PrivateRoute from &apos;./components/PrivateRoute/index&apos;;

const Dashboard = lazy(() =&amp;gt; import(/* webpackChunkName: &quot;Dashboard&quot;*/&apos;./pages/Dashboard/index&apos;));

const routes = [
    {
        // 仪表盘页
        path: &apos;/dashboard&apos;,
        component: Dashboard
    }
];

const RouteWithSubRoutes = route =&amp;gt; {
    return &amp;lt;PrivateRoute exact path={route.path} component={route.component} /&amp;gt;;
};

const routeConfig = routes.map((route, i) =&amp;gt; &amp;lt;RouteWithSubRoutes key={i} {...route} /&amp;gt;);
export default routeConfig;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后访问 &lt;a href=&quot;http://localhost:8080/#/dashboard&quot;&gt;http://localhost:8080/#/dashboard&lt;/a&gt;，你会看到 123 的输出。&lt;/p&gt;
&lt;h2&gt;Step 3. 构造 mobx 和 service&lt;/h2&gt;
&lt;p&gt;新增 &lt;code&gt;src/mobx/Dashboard/store.js&lt;/code&gt;，内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { observable, action, runInAction } from &apos;mobx&apos;;
import BasicStore, { initLoading } from &apos;../basicStore&apos;;
import { isResultError } from &apos;../../utils/index&apos;;
import * as api from &apos;../../servers/dashboard&apos;;
class DashBoardStore extends BasicStore {
    @observable list = [];

    @initLoading
    @action
    async getTable() {
        const list = await api.getTable();
        runInAction(() =&amp;gt; {
            this.list = isResultError(list);
        });
    }
}

export default DashBoardStore;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;basicStor 类，是我单独封装的，用于监听每个请求，这样就不用手动处理 loading。&lt;/p&gt;
&lt;p&gt;然后在&lt;code&gt;src/mobx/rootStore.js&lt;/code&gt;引入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import DashboardStore from &apos;./Dashboard/store&apos;;
class Store {
    constructor() {
        this.dashboardStore = new DashboardStore();
    }
}
export default new Store();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新建 &lt;code&gt;src/servers/dashboard.js&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import request from &apos;../request&apos;;

// 获取表格数据
export function getTable(params = {}) {
    return request({
        url: &apos;getTable&apos;,
        method: &apos;POST&apos;,
        data: params
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 4. 页面组件引入 mobx&lt;/h2&gt;
&lt;p&gt;在&lt;code&gt;src/pages/Dashboard/index.js&lt;/code&gt;文件中，引入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { Component } from &apos;react&apos;;
import { inject, observer } from &apos;mobx-react&apos;;

@inject(&apos;dashboardStore&apos;)
@observer
class Index extends Component {
    constructor(props) {
        super(props);
    }

    componentDidMount() {
        const { dashboardStore } = this.props;
        dashboardStore.getTable();
    }

    render() {
        const { dashboardStore: { list }, dashboardStore } = this.props;
        return (
            &amp;lt;section&amp;gt;
                123
            &amp;lt;/section&amp;gt;
        );
    }
}

export default Index;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 5. 添加界面，让列表展现出来&lt;/h2&gt;
&lt;p&gt;在 src/pages/Dashboard/index.js 文件中，引入筛选组件和列表组件：&lt;/p&gt;
&lt;p&gt;筛选组件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 筛选项
const OrderSearch = (props) =&amp;gt; {
    const { handleSubmit } = props;
    return (
        &amp;lt;section className=&quot;search&quot;&amp;gt;
            &amp;lt;Form
                layout=&quot;inline&quot;
                name=&quot;orderListSearch&quot;
                onFinish={handleSubmit}
            &amp;gt;
                &amp;lt;Form.Item
                    label=&quot;订单编号&quot;
                    name=&quot;orderId&quot;
                &amp;gt;
                    &amp;lt;Input placeholder=&quot;请输入订单编号&quot; style={{ width: colWidth }} /&amp;gt;
                &amp;lt;/Form.Item&amp;gt;
                &amp;lt;Form.Item
                    label=&quot;客户名称&quot;
                    name=&quot;customerName&quot;
                &amp;gt;
                    &amp;lt;Input placeholder=&quot;请输入客户名称&quot; style={{ width: colWidth }} /&amp;gt;
                &amp;lt;/Form.Item&amp;gt;
                &amp;lt;Form.Item
                    label=&quot;下单方式&quot;
                    name=&quot;placeOrder&quot;
                &amp;gt;
                    &amp;lt;Select
                        allowClear
                        style={{ width: colWidth }}
                        placeholder=&quot;请选择下单方式&quot;
                    &amp;gt;
                        &amp;lt;Option value={1}&amp;gt;自主下单&amp;lt;/Option&amp;gt;
                        &amp;lt;Option value={2}&amp;gt;代下单&amp;lt;/Option&amp;gt;
                    &amp;lt;/Select&amp;gt;
                &amp;lt;/Form.Item&amp;gt;
                &amp;lt;section className=&quot;button&quot;&amp;gt;
                    &amp;lt;Form.Item&amp;gt;
                        &amp;lt;Button
                            htmlType=&quot;submit&quot;
                        &amp;gt;
                            重置
                        &amp;lt;/Button&amp;gt;
                        &amp;lt;Button
                            htmlType=&quot;submit&quot;
                            className=&quot;btn-left&quot;
                        &amp;gt;
                            导出
                        &amp;lt;/Button&amp;gt;
                        &amp;lt;Button
                            type=&quot;primary&quot;
                            htmlType=&quot;submit&quot;
                            className=&quot;btn-left&quot;
                        &amp;gt;
                            搜索
                        &amp;lt;/Button&amp;gt;
                    &amp;lt;/Form.Item&amp;gt;
                &amp;lt;/section&amp;gt;
            &amp;lt;/Form&amp;gt;
        &amp;lt;/section&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;列表组件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 订单表格
const OrderTable = ({ list, isLoading }) =&amp;gt;  {

    // 表格列配置
    const columns = [
        {
            title: &apos;订单编号&apos;,
            dataIndex: &apos;orderId&apos;
        },
        {
            title: &apos;客户名称&apos;,
            dataIndex: &apos;customerName&apos;
        },
        {
            title: &apos;下单方式&apos;,
            dataIndex: &apos;placeOrder&apos;
        },
        {
            title: &apos;商品名称&apos;,
            dataIndex: &apos;goodsName&apos;
        },
        {
            title: &apos;价格&apos;,
            dataIndex: &apos;price&apos;
        },
        {
            title: &apos;下单时间&apos;,
            dataIndex: &apos;placeOrderTime&apos;
        }
    ];

    return (
        &amp;lt;Table
            columns={columns}
            dataSource={list || []}
            loading={isLoading}
            rowKey=&quot;orderId&quot;
        /&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Index 类下引入两个组件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;render() {
    const { dashboardStore: { list }, dashboardStore } = this.props;
    return (
        &amp;lt;section className=&quot;dashboard&quot;&amp;gt;
            &amp;lt;OrderSearch handleSubmit={this.handleSubmit} /&amp;gt;
            &amp;lt;OrderTable list={list} isLoading={dashboardStore.isLoading.get(&apos;getTable&apos;)} /&amp;gt;
        &amp;lt;/section&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dashboardStore.isLoading.get(&apos;getTable&apos;)&lt;/code&gt; 用于判断请求是否完成，bool 值，true || false&lt;/p&gt;
&lt;p&gt;完&lt;/p&gt;
</content:encoded><category>Tristana</category><author>江辰</author></item><item><title>基于 Taro 实现签字，轨迹回放</title><link>https://github.com/posts/%E5%9F%BA%E4%BA%8E-taro-%E5%AE%9E%E7%8E%B0%E7%AD%BE%E5%AD%97%E8%BD%A8%E8%BF%B9%E5%9B%9E%E6%94%BE/</link><guid isPermaLink="true">https://github.com/posts/%E5%9F%BA%E4%BA%8E-taro-%E5%AE%9E%E7%8E%B0%E7%AD%BE%E5%AD%97%E8%BD%A8%E8%BF%B9%E5%9B%9E%E6%94%BE/</guid><pubDate>Tue, 16 Jun 2020 14:02:18 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近公司要在小程序上实现签字笔迹追踪，看了下网上关于 Taro 如何实现的文章，代码都很乱，很杂，所以，想记录下自己是如何实现的，并附上源码。&lt;/p&gt;
&lt;h2&gt;效果演示&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/84784978-82030e80-b01d-11ea-8815-baf3ff328455.gif&quot; alt=&quot;演示&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;源码&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Taro, { Component } from &apos;@tarojs/taro&apos;;
import { View, Canvas } from &apos;@tarojs/components&apos;;
import &apos;./index.less&apos;;

// 图片url（或filepath）转base64
const fileTobase64 = path =&amp;gt; {
    return &apos;data:image/jpeg;base64,&apos; + Taro.getFileSystemManager().readFileSync(path, &apos;base64&apos;);
};

// 签字轨迹
const historyTrajectory = {
    historyData:[
        {
            //操作时间
            time: 0,
            /**
            * 操作类型
            * 绘图：mapping
            */
            operation: &apos;mapping&apos;,//操作类型
            /**
            * 绘制路径
            * startX：开始x坐标
            * startY：开y纵坐标
            * currentX：目标位置的 x 坐标
            * currentY：目标位置的 y 坐标
            * z：1代表画线时鼠标处于move状态，0代表处于松开状态
            * colorStr：线的填充颜色
            * pen：笔迹大小
            */
            lineArr: {
                startX: 0,
                startY: 0,
                currentX: 0,
                currentY: 0,
                z: 0,
                colorStr: &apos;#000&apos;,
                pen: 3
            }
        }
    ]
};

export default class Signature extends Component {
    constructor(props) {
        super(props);
        this.state = { };
        this.pen = 3;
        this.context = &apos;&apos;;
        this.signPath = &apos;&apos;;
        this.moveToX = 0;
        this.moveToY = 0;
        // 签字开始时间
        this.startDate = new Date().getTime();
        this.timer = &apos;&apos;;
        this.colorStr = &apos;#000&apos;;
    }

    componentDidMount() {
        const context = Taro.createCanvasContext(&apos;myCanvas&apos;, this.$scope);
        this.context = context;
        this.context.draw();
        this.context.lineWidth = this.pen;
    }

    // 手指触摸动作开始
    touchStart = (e) =&amp;gt; {
        this.context.lineWidth = this.pen;
        this.context.moveTo(e.changedTouches[0].x, e.changedTouches[0].y);

        this.moveToX = Math.floor(e.changedTouches[0].x);
        this.moveToY = Math.floor(e.changedTouches[0].y);
        historyTrajectory.historyData.push({
            time: new Date().getTime() - this.startDate,
            operation: &apos;mapping&apos;,
            lineArr: {
                startX: this.moveToX,
                startY: this.moveToY,
                currentX: Math.floor(e.changedTouches[0].x),
                currentY: Math.floor(e.changedTouches[0].y),
                z: 1,
                colorStr: this.colorStr,
                pen: this.pen
            }
        });
    }

    // 手指触摸后移动
    touchMove = (e) =&amp;gt; {
        const lineToX = Math.floor(e.changedTouches[0].x);
        const lineToY = Math.floor(e.changedTouches[0].y);
        this.context.lineWidth = this.pen;
        this.context.lineTo(lineToX, lineToY);
        this.context.stroke();
        this.context.draw(true);
        this.context.moveTo(lineToX, lineToY);

        historyTrajectory.historyData.push({
            time: new Date().getTime() - this.startDate,
            operation: &apos;mapping&apos;,
            lineArr: {
                startX: this.moveToX,
                startY: this.moveToY,
                currentX: lineToX,
                currentY: lineToY,
                z: 1,
                colorStr: this.colorStr,
                pen: this.pen
            }
        });

        this.moveToX = lineToX;
        this.moveToY = lineToY;
    }

    onTouchEnd = () =&amp;gt; {

    }

    // 清空画布
    clearCanvas = () =&amp;gt; {
        this.signPath = &apos;&apos;;
        this.context.draw();
        historyTrajectory.historyData = [];
        historyTrajectory.historyData.push({
            time: 0,
            operation: &apos;mapping&apos;,
            lineArr: {
                startX: 0,
                startY: 0,
                currentX: 0,
                currentY: 0,
                z: 0,
                colorStr: this.colorStr,
                pen: this.pen
            }
        });
    }

    replay = () =&amp;gt; {
        this.signPath = &apos;&apos;;
        this.context.draw();
        clearInterval(this.timer);
        const startDate = new Date().getTime();
        let i = 0;
        let len = historyTrajectory.historyData.length;
        this.timer = setInterval(() =&amp;gt; {
            const curTime = new Date().getTime() - startDate;
            if (curTime &amp;gt;= historyTrajectory.historyData[i].time) {
                switch (historyTrajectory.historyData[i].operation) {
                    case &apos;mapping&apos;:
                        this.context.setStrokeStyle(historyTrajectory.historyData[i].lineArr.colorStr);
                        this.context.lineWidth = historyTrajectory.historyData[i].lineArr.pen;
                        this.context.moveTo(historyTrajectory.historyData[i].lineArr.startX, historyTrajectory.historyData[i].lineArr.startY);
                        this.context.lineTo(historyTrajectory.historyData[i].lineArr.currentX, historyTrajectory.historyData[i].lineArr.currentY);
                        this.context.stroke();
                        this.context.draw(true);
                        break;
                }
                i++;
            }
            if(i &amp;gt;= len) {
                clearInterval(this.timer);
            }
        }, 1);
    }

    // 获取签字base64
    confirm() {
        Taro.canvasToTempFilePath({
            x: 0,
            y: 0,
            canvasId: &apos;myCanvas&apos;,
            success: (res) =&amp;gt; {
                console.log(&apos;cofirm：&apos;, fileTobase64(res.tempFilePath));
            },
            fail: (error) =&amp;gt; {
                console.error(&apos;btnConfirm：&apos;, error);
            }
        }, this.$scope);
    }

    render() {
        return (
            &amp;lt;View className=&quot;signature-container&quot;&amp;gt;
                &amp;lt;View className=&quot;canvas-area&quot; style={{ height: (Taro.getSystemInfoSync().screenHeight - 150 + &apos;px&apos;) }}&amp;gt;
                    &amp;lt;Canvas
                        canvasId=&quot;myCanvas&quot;
                        className=&quot;my-canvas&quot;
                        disableScroll=&quot;false&quot;
                        onTouchStart={this.touchStart}
                        onTouchMove={this.touchMove}
                        onTouchEnd={this.onTouchEnd}
                    /&amp;gt;
                &amp;lt;/View&amp;gt;
                &amp;lt;View className=&quot;canvas-btn&quot;&amp;gt;
                    &amp;lt;View className=&quot;btn&quot; onClick={this.confirm}&amp;gt;确认&amp;lt;/View&amp;gt;
                    &amp;lt;View className=&quot;btn&quot; onClick={this.clearCanvas}&amp;gt;清除&amp;lt;/View&amp;gt;
                    &amp;lt;View className=&quot;btn&quot; onClick={this.replay}&amp;gt;回放&amp;lt;/View&amp;gt;
                &amp;lt;/View&amp;gt;
            &amp;lt;/View&amp;gt;
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;index.less&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.signature-container {

    padding: 24px;

    .canvas-area {
        overflow: hidden;
        width: 100%;
        border: 2px dashed #dcdcdc;
    }

    .my-canvas {
        width: 100%;
        height: 100%;
    }

    .canvas-btn {
        display: flex;
        justify-content: space-around;
        align-items: center;
        margin-top: 24px;

        .btn {
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 28px;
            border-radius: 5px;
            flex: 1;
            background-color: #359a64;
            color: #fff;
            height: 60px;
            margin: 0 24px;
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Taro</category><author>江辰</author></item><item><title>Webpack 如何配置热更新</title><link>https://github.com/posts/webpack-%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE%E7%83%AD%E6%9B%B4%E6%96%B0/</link><guid isPermaLink="true">https://github.com/posts/webpack-%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE%E7%83%AD%E6%9B%B4%E6%96%B0/</guid><pubDate>Wed, 20 May 2020 18:23:46 GMT</pubDate><content:encoded>&lt;h2&gt;什么是 HMR&lt;/h2&gt;
&lt;p&gt;是指 &lt;code&gt;Hot Module Replacement&lt;/code&gt;，缩写为 &lt;code&gt;HMR&lt;/code&gt;。对于你需要更新的模块，进行一个&quot;热&quot;替换，所谓的热替换是指在不需要刷新页面的情况下，对某个改动进行无缝更新。如果你没有配置 &lt;code&gt;HMR&lt;/code&gt;，那么你每次改动，都需要刷新页面，才能看到改动之后的结果，对于调试来说，非常麻烦，而且效率不高，最关键的是，你在界面上修改的数据，随着刷新页面会丢失，而如果有类似 &lt;code&gt;Webpack&lt;/code&gt; 热更新的机制存在，那么，则是修改了代码，不会导致刷新，而是保留现有的数据状态，只将模块进行更新替换。也就是说，既保留了现有的数据状态，又能看到代码修改后的变化。&lt;/p&gt;
&lt;p&gt;总结：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;加载页面时保存应用程序状态&lt;/li&gt;
&lt;li&gt;只更新改变的内容，节省调试时间&lt;/li&gt;
&lt;li&gt;修改样式更快，几乎等同于在浏览器中更改样式&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;安装依赖&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ npm install webpack webpack-dev-server --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;dependencies&quot;: {
    &quot;webpack&quot;: &quot;^4.41.2&quot;,
    &quot;webpack-dev-server&quot;: &quot;^3.10.1&quot;
},
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;配置&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;webpack&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;devServer: {
    contentBase: path.resolve(__dirname, &apos;dist&apos;),
    hot: true,
    historyApiFallback: true,
    compress: true
},
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hot&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt;，代表开启热更新&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;contentBase&lt;/code&gt; 表示告诉服务器从哪里提供内容。（也就是服务器启动的根目录，默认为当前执行目录，一般不需要设置）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;historyApiFallback&lt;/code&gt; 使用 &lt;code&gt;HTML5&lt;/code&gt; 历史记录 &lt;code&gt;API&lt;/code&gt; 时，&lt;code&gt;index.html&lt;/code&gt; 很可能必须提供该页面来代替任何 404 响应&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;compress&lt;/code&gt; 对所有服务启用 &lt;code&gt;gzip&lt;/code&gt; 压缩&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;plugins: {
    HotModuleReplacementPlugin: new webpack.HotModuleReplacementPlugin()
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置热更新插件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module: {
    rules: [
        {
            test: /\.(css|less)$/,
            use: [
                process.env.NODE_ENV == &apos;development&apos; ? { loader: &apos;style-loader&apos; } : MiniCssExtractPlugin.loader,
                {
                    loader: &apos;css-loader&apos;,
                    options: {
                        importLoaders: 1
                    }
                }
            ]
        }
    ]
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;style-loader&lt;/code&gt; 库实现了 &lt;code&gt;HMR&lt;/code&gt; 接口，当通过 &lt;code&gt;HMR&lt;/code&gt; 收到更新时，它将用新样式替换旧样式。区分开发环境和生产环境，用不同 &lt;code&gt;loader。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/index.jsx&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (module.hot) {
    module.hot.accept();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;入口文件，新增上面代码，就可以了，非常简单。&lt;/p&gt;
&lt;h2&gt;react-hot-loader&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;react-hot-loader&lt;/code&gt; 插件，&lt;a href=&quot;https://github.com/gaearon/react-hot-loader&quot;&gt;传送门&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;如何使用&lt;/h3&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm install react-hot-loader --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置 &lt;code&gt;babelrc&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;plugins&quot;: [&quot;react-hot-loader/babel&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将根组件标记为热导出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { hot } from &apos;react-hot-loader/root&apos;;
const App = () =&amp;gt; &amp;lt;div&amp;gt;Hello World!&amp;lt;/div&amp;gt;;
export default hot(App);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;React&lt;/code&gt; 和 &lt;code&gt;React Dom&lt;/code&gt; 之前，确保需要 &lt;code&gt;React&lt;/code&gt; 热加载程序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// webpack.config.js
module.exports = {
  entry: [&apos;react-hot-loader/patch&apos;, &apos;./src&apos;],
  // ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;遇到问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;如果遇到 &lt;code&gt;You cannot change &amp;lt;Router history&amp;gt;&lt;/code&gt; ，那么应该这样配置：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import { hot } from &apos;react-hot-loader/root&apos;;
const Routes = () =&amp;gt; {};
export default hot(Routes);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;配置完热更新之后，遇到&lt;code&gt;webpack&lt;/code&gt;自动编译两次问题，很大概率出现，具体原因，没有分析，找到一个讨巧的解决办法，配置：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;watchOptions: {
    aggregateTimeout: 600
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也有可能是其他问题，比如你在&lt;code&gt;index.html&lt;/code&gt;页面，重复引入了&lt;code&gt;index.js&lt;/code&gt;，又或者是全局安装了&lt;code&gt;webpack-dev-server&lt;/code&gt;，与本地&lt;code&gt;webpack-dev-server&lt;/code&gt;重复，卸载全局&lt;code&gt;webpack-dev-server&lt;/code&gt;，即可。&lt;/p&gt;
&lt;h2&gt;案例&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xuya227939/tristana&quot;&gt;Tristana&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;博客&lt;/h2&gt;
&lt;p&gt;欢迎关注我的&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;博客&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Webpack</category><author>江辰</author></item><item><title>微信小程序 Canvas 签字</title><link>https://github.com/posts/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F-canvas-%E7%AD%BE%E5%AD%97/</link><guid isPermaLink="true">https://github.com/posts/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F-canvas-%E7%AD%BE%E5%AD%97/</guid><pubDate>Wed, 13 May 2020 09:53:52 GMT</pubDate><content:encoded>&lt;p&gt;最近迫于签字结果返回太慢，所以需要把旋转图片由原先后端处理改为前端处理，查阅文档，研究一番，发现小程序上旋转，主要是通过 canvas 的 translate、rotate 完成。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/81764019-29fb5880-9503-11ea-80d6-939a977247c5.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Canvas 坐标系统，由上图可得，Canvas 2D 环境中坐标系统和 Web 的坐标系统是一致的，有以下几个特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;坐标原点 (0,0) 在左上角&lt;/li&gt;
&lt;li&gt;X 坐标向右方增长&lt;/li&gt;
&lt;li&gt;Y 坐标向下方延伸&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;伪代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;wx.canvasToTempFilePath({
    x: 0,
    y: 0,
    canvasId: &apos;myCanvas&apos;,
    success: (res) =&amp;gt; {
        wx.getImageInfo({
            src: res.tempFilePath,
            success: (imageRes) =&amp;gt; {
                const height = imageRes.height;
                const width = imageRes.width;
                this.setState({
                    canvasHeight: width,
                    canvasWidth: height
                });
                const context2 = wx.createCanvasContext(&apos;myCanvas2&apos;, this.$scope);
                context2.translate(height / 2, width / 2);
                context2.rotate(270 * Math.PI / 180);
                context2.drawImage(imageRes.path, -width / 2, -height / 2, width, height);
                context2.draw(false, () =&amp;gt; {
                    this.getTempFilePath();
                });
            }
        });
    },
    fail: (error) =&amp;gt; {
        console.error(&apos;canvasToTempFilePathfail&apos;, error);
    }
}, this);

// 获取图片url，安卓有性能问题，所以需要延迟
getTempFilePath(errorCount = 5) {
    setTimeout(() =&amp;gt; {
        const { signInfo } = this.props;
        const { signers, signerNames } = this.state;
        wx.canvasToTempFilePath({
            x: 0,
            y: 0,
            canvasId: &apos;myCanvas2&apos;,
            success: (res) =&amp;gt; {
                const signPic = this.fileTobase64(res.tempFilePath);
            },
            fail: (err) =&amp;gt; {
                if(errorCount &amp;lt; 7) {
                    this.getTempFilePath(++errorCount);
                }
                console.error(&apos;canvasToTempFilePathfail&apos;, err);
            }
        }, this);
    }, errorCount * 100);
}

fileTobase64 = path =&amp;gt; {
    return &apos;data:image/jpeg;base64,&apos; + Taro.getFileSystemManager().readFileSync(path, &apos;base64&apos;);
};

render() {
    return (
        &amp;lt;Canvas
            canvasId=&quot;myCanvas&quot;
            style={{ width: &apos;305px&apos;, height: wx.getSystemInfoSync().windowHeight + &apos;px&apos; }}
            id=&quot;myCanvas&quot;
            disableScroll=&quot;false&quot;
            onTouchStart={(e) =&amp;gt; this.touchStart(e)}
            onTouchMove={(e) =&amp;gt; this.touchMove(e)}
        /&amp;gt;
        &amp;lt;Canvas
            canvasId=&quot;myCanvas2&quot;
            id=&quot;myCanvas2&quot;
            disableScroll=&quot;false&quot;
            style={{ width: canvasWidth + &apos;px&apos;, height: canvasHeight + &apos;px&apos; }}
        /&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;旋转角度代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;wx.getImageInfo({
    src: path,
    success: (res) =&amp;gt; {
        let canvasContext = wx.createCanvasContext(&apos;my-canvas&apos;, _this);
        // 下面按比例写死宽度高度是为了压缩图片提升上传速度，可按实际需求更改
        let rate = res.height / res.width;
        let width = 500;
        let height = 500 * rate;
        // 根据orientation值处理图片
        switch (res.orientation) {
            case &apos;up&apos;:
                // 不需要旋转
                _this.setData({
                    canvasWidth: width,
                    canvasHeight: height,
                });
                canvasContext.drawImage(path, 0, 0, width, height);
                break;
            case &apos;down&apos;:
                // 需要旋转180度
                _this.setData({
                    canvasWidth: width,
                    canvasHeight: height,
                });
                canvasContext.translate(width / 2, height / 2);
                canvasContext.rotate(180 * Math.PI / 180);
                canvasContext.drawImage(path, -width / 2, -height / 2, width, height);
                break;
            case &apos;left&apos;:
                // 顺时针旋转270度
                _this.setData({
                    canvasWidth: height,
                    canvasHeight: width,
                });
                canvasContext.translate(height / 2, width / 2);
                canvasContext.rotate(270 * Math.PI / 180);
                canvasContext.drawImage(path, -width / 2, -height / 2, width, height);
                break;
            case &apos;right&apos;:
                // 顺时针旋转90度
                _this.setData({
                    canvasWidth: height,
                    canvasHeight: width,
                });
                canvasContext.translate(height / 2, width / 2);
                canvasContext.rotate(90 * Math.PI / 180);
                canvasContext.drawImage(path, -width / 2, -height / 2, width, height);
                break;
        }
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Canvas 2d&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;export default class Signature extends Component&amp;lt;IProps, IState&amp;gt; {
    constructor(props: IProps | Readonly&amp;lt;IProps&amp;gt;) {
        super(props);
        this.state = {
        };
    }

    componentDidMount() {

        // 放到下一个时间切片执行
        wx.nextTick(() =&amp;gt; {
            const query = wx.createSelectorQuery();
            query
                .select(&apos;#myCanvas&apos;)
                .fields({ node: true, size: true })
                .exec(res =&amp;gt; {
                    if (res &amp;amp;&amp;amp; res[0]) {
                        this.initCanvas(res);
                    } else {
                        wx.nextTick(() =&amp;gt; {
                            query
                                .select(&apos;#myCanvas&apos;)
                                .fields({ node: true, size: true })
                                .exec(res =&amp;gt; {
                                    if (res &amp;amp;&amp;amp; res[0]) this.initCanvas(res);
                                });
                        });
                    }
                });
        });
    }

    initCanvas = (res: any) =&amp;gt; {
        const canvas = res[0].node;
        const ctx = canvas.getContext(&apos;2d&apos;);

        const dpr = wx.getSystemInfoSync().pixelRatio;
        canvas.width = res[0].width * dpr;
        canvas.height = res[0].height * dpr;
        this.context = ctx;
        this.context.lineWidth = 4.5;
        ctx.scale(dpr, dpr);
    };

    // 手指触摸动作开始
    touchStart = (e: any) =&amp;gt; {
        this.context.moveTo(e.changedTouches[0].x, e.changedTouches[0].y);
    };

    // 手指触摸后移动
    touchMove = (e: any) =&amp;gt; {
        this.context.lineTo(e.changedTouches[0].x, e.changedTouches[0].y);
        this.context.stroke();
        this.isClear = false;
        this.context.draw(true);
        this.context.moveTo(e.changedTouches[0].x, e.changedTouches[0].y);
    };

    // 清空画布
    clearCanvas = () =&amp;gt; {
        this.signPath = &apos;&apos;;
        this.isClear = true;
        this.context.draw();
        this.context.lineWidth = 4.5;
    };

    btnConfirm() {

        const query = wx.createSelectorQuery();
        query
            .select(&apos;#myCanvas&apos;)
            .fields({ node: true, size: true })
            .exec(res =&amp;gt; {
                const canvas = res[0].node;
                wx.canvasToTempFilePath(
                    {
                        x: 0,
                        y: 0,
                        canvas,
                        success: (res: { tempFilePath: string }) =&amp;gt; {
                            this.signPath = res.tempFilePath;

                        },
                        fail: () =&amp;gt; {}
                    },
                    this.$scope
                );
            });
    }

    render() {
        return (
            &amp;lt;Canvas
                type=&quot;2d&quot;
                style={{ width: &apos;100%&apos;, height: &apos;100%&apos; }}
                id=&quot;myCanvas&quot;
                disableScroll={false}
                onTouchStart={this.touchStart}
                onTouchMove={this.touchMove}
            /&amp;gt;
        );
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自定义组件，没有 &lt;code&gt;onReady&lt;/code&gt; 方法，放在 &lt;code&gt;didmout&lt;/code&gt; 执行，如果 &lt;code&gt;didmout&lt;/code&gt; 获取不到 &lt;code&gt;Canvas&lt;/code&gt; 实例，则通过 &lt;code&gt;wx.nextTick&lt;/code&gt;，放到下一个时间切片获取。&lt;/p&gt;
&lt;p&gt;如果不是自定义组件，页面组件，则通过 &lt;code&gt;onReady&lt;/code&gt; 方法获取，&lt;code&gt;Canvas 2d&lt;/code&gt; 方案，该方案有很大的问题，无法清除画布，微信小程序官方 BUG，截止到 &lt;code&gt;2021.06.03&lt;/code&gt;，还未解决。&lt;/p&gt;
</content:encoded><category>微信小程序</category><author>江辰</author></item><item><title>解决前端如何通过游览器下载视频地址</title><link>https://github.com/posts/%E8%A7%A3%E5%86%B3%E5%89%8D%E7%AB%AF%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87%E6%B8%B8%E8%A7%88%E5%99%A8%E4%B8%8B%E8%BD%BD%E8%A7%86%E9%A2%91%E5%9C%B0%E5%9D%80/</link><guid isPermaLink="true">https://github.com/posts/%E8%A7%A3%E5%86%B3%E5%89%8D%E7%AB%AF%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87%E6%B8%B8%E8%A7%88%E5%99%A8%E4%B8%8B%E8%BD%BD%E8%A7%86%E9%A2%91%E5%9C%B0%E5%9D%80/</guid><pubDate>Thu, 07 May 2020 16:18:37 GMT</pubDate><content:encoded>&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 下载视频地址
export function downloadVideoUrl(url) {
    return new Promise((resolve, reject) =&amp;gt; {
        let xhr = new XMLHttpRequest();
        xhr.open(&apos;GET&apos;, url);
        xhr.responseType = &apos;blob&apos;;
        xhr.onload = function(e) {
            if (e.currentTarget.status == 200) {
                var link = document.createElement(&apos;a&apos;);
                link.href = window.URL.createObjectURL(new Blob([xhr.response]));
                link.download = &apos;视频.mp4&apos;;
                link.click();
                link.remove();
                resolve();
            }

            if(e.currentTarget.status != 200) reject();
        };
        xhr.send();
    });
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>下载视频</category><author>江辰</author></item><item><title>如何在 MobX 中组织 Stores</title><link>https://github.com/posts/%E5%A6%82%E4%BD%95%E5%9C%A8-mobx-%E4%B8%AD%E7%BB%84%E7%BB%87-stores/</link><guid isPermaLink="true">https://github.com/posts/%E5%A6%82%E4%BD%95%E5%9C%A8-mobx-%E4%B8%AD%E7%BB%84%E7%BB%87-stores/</guid><pubDate>Thu, 16 Apr 2020 11:46:49 GMT</pubDate><content:encoded>&lt;h2&gt;Stores(存储)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Store&lt;/code&gt; 可以在任何 &lt;code&gt;Flux&lt;/code&gt; 系架构中找到，可以与 MVC 模式中的控制器进行比较。 &lt;code&gt;Store&lt;/code&gt; 的主要职责是将逻辑和状态从组件中移至一个独立的，可测试的单元，这个单元在 &lt;code&gt;JavaScript&lt;/code&gt; 前端和后端中都可以使用。&lt;/p&gt;
&lt;h2&gt;单一 Stores&lt;/h2&gt;
&lt;p&gt;RootStore&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { observable, action, configure } from &apos;mobx&apos;;

// configure({ enforceActions: &apos;always&apos; });

class RootStore {

    @observable
    name = &apos;123&apos;;

    @action(&apos;name&apos;)
    updateCount() {
        this.name = &apos;456&apos;;
    }
}
export default new RootStore();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stores 注入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import ReactDOM from &apos;react-dom&apos;;
import { Provider } from &apos;mobx-react&apos;;
import RootStore from &apos;./mobx/rootStore&apos;;

ReactDOM.render(
    &amp;lt;Provider rootStore={RootStore}&amp;gt;
        ...
    &amp;lt;/Provider&amp;gt;,
    document.getElementById(&apos;root&apos;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;页面引入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { Component } from &apos;react&apos;;
import { observer, inject } from &apos;mobx-react&apos;;

@inject(&apos;rootStore&apos;)
@observer
class Index extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            &amp;lt;&amp;gt;
            &amp;lt;/&amp;gt;
        );
    }
}

export default Index;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;好理解，容易入手，经典的 MVC 模式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 Store 越来越大，那么非常不好维护。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;单一 Stores 进阶版&lt;/h2&gt;
&lt;p&gt;RootStore&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { observable, action } from &apos;mobx&apos;;
import { configure } from &apos;mobx&apos;;

configure({ enforceActions: &apos;always&apos; });

class RootStore {

    @observable
    test = &apos;123&apos;;

    @action(&apos;test&apos;)
    updateCount() {
        this.test = &apos;456&apos;;
    }
}
export default new RootStore();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;页面 Store&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { observable, action } from &apos;mobx&apos;;

class DashBoardStore extends BasicStore {
    @observable
    name = &apos;Faker&apos;;

    @action
    updateName() {
        this.name = &apos;Jiang&apos;;
    }
}

export default DashBoardStore;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注入 RootStore&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import ReactDOM from &apos;react-dom&apos;;
import { Provider } from &apos;mobx-react&apos;;
import RootStore from &apos;./mobx/rootStore&apos;;

ReactDOM.render(
    &amp;lt;Provider rootStore={RootStore}&amp;gt;
        ...
    &amp;lt;/Provider&amp;gt;,
    document.getElementById(&apos;root&apos;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;页面引入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { Component } from &apos;react&apos;;
import { observer, inject } from &apos;mobx-react&apos;;
import DashboardStore from &apos;../../mobx/Dashboard/store&apos;;

@inject(&apos;rootStore&apos;)
@observer
class Index extends Component {
    constructor(props) {
        super(props);
        this.dashboardStore = new DashboardStore();
    }

    componentDidMount() {
        this.dashboardStore.updateName();
    }

    render() {
        return (
            &amp;lt;&amp;gt;
            &amp;lt;/&amp;gt;
        );
    }
}

export default Index;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;每个页面对应一个 &lt;code&gt;Store&lt;/code&gt;，&lt;code&gt;Store&lt;/code&gt; 不会非常庞大&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;各个 &lt;code&gt;Store&lt;/code&gt; 相对独立&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不同页面需要共享的数据存入 &lt;code&gt;RootStore&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在进入页面，会对 &lt;code&gt;Store&lt;/code&gt; 初始化&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;组件侵入性，需要改变 &lt;code&gt;React&lt;/code&gt; 组件原本的结构，例如所有需要响应数据变动的组件都需要使用 &lt;code&gt;observer&lt;/code&gt; 装饰，组件本地状态也需要 &lt;code&gt;observable&lt;/code&gt; 装饰，以及数据操作方式等等，对 &lt;code&gt;Mobx&lt;/code&gt; 耦合较深，日后切换框架或重构的成本很高&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;状态可以被随意修改，通过&lt;code&gt;configure({ enforceActions: &apos;always&apos; });&lt;/code&gt;杜绝在 &lt;code&gt;Action&lt;/code&gt; 以外对 &lt;code&gt;Store&lt;/code&gt; 的修改&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;多 Store 组合&lt;/h2&gt;
&lt;p&gt;RootStore&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import UserStore from &apos;./User/store&apos;;
import StatisticalCenterStore from &apos;./StatisticalCenter/store&apos;;
import AccountSettingStore from &apos;./AccountSetting/store&apos;;
import CallRecordStore from &apos;./CallRecord/store&apos;;
class RootStore {
    constructor() {
        this.userStore = new UserStore();
        this.statisticalCenterStore = new StatisticalCenterStore();
        this.accountSettingStore = new AccountSettingStore();
        this.callRecordStore = new CallRecordStore();
    }
}
export default new RootStore();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stores 注入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import ReactDOM from &apos;react-dom&apos;;
import { Provider } from &apos;mobx-react&apos;;
import RootStore from &apos;./mobx/rootStore&apos;;

ReactDOM.render(
    &amp;lt;Provider {...RootStore}&amp;gt;
        ...
    &amp;lt;/Provider&amp;gt;,
    document.getElementById(&apos;root&apos;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;页面引入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { inject, observer } from &apos;mobx-react&apos;;

@inject(&apos;userStore&apos;)
@inject(&apos;statisticalCenterStore&apos;)
@observer
class Index extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = { };
    }

    render() {
        return (
            &amp;lt;&amp;gt;
            &amp;lt;/&amp;gt;
        );
    }
}

export default Index;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;每个页面对应一个 &lt;code&gt;Store&lt;/code&gt;，&lt;code&gt;Store&lt;/code&gt; 不会非常庞大&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;各个 &lt;code&gt;Store&lt;/code&gt; 相对独立&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;那个页面需要那个 &lt;code&gt;Store&lt;/code&gt;，引入即可&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不刷新游览器，页面状态一直保持着&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;组件侵入性，需要改变 &lt;code&gt;React&lt;/code&gt; 组件原本的结构，例如所有需要响应数据变动的组件都需要使用 &lt;code&gt;observer&lt;/code&gt; 装饰，组件本地状态也需要 &lt;code&gt;observable&lt;/code&gt; 装饰，以及数据操作方式等等，对 &lt;code&gt;Mobx&lt;/code&gt; 耦合较深， 日后切换框架或重构的成本很高&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无数据快照，如果要重置 &lt;code&gt;Store&lt;/code&gt;，那么得写&lt;code&gt;reset action&lt;/code&gt;，一个个变量还原，当然也可以通过 &lt;code&gt;mobx-state-tree&lt;/code&gt; 实现&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;中性：&lt;/p&gt;
&lt;p&gt;状态可以被随意修改：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;直接在视图层给状态赋值，比如我有 A，B 两个页面，都要修改 C 页面，那么，我在 A 和 B 页面修改 C 的 Store，很方便，但是，如果不制定一套规范，如果数据改变，要追踪来源，很困难，而且很容易产生意想不到的情况。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过&lt;code&gt;configure({ enforceActions: &apos;always&apos; });&lt;/code&gt;杜绝在 &lt;code&gt;Action&lt;/code&gt; 以外对 &lt;code&gt;Store&lt;/code&gt; 的修改&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;博客&lt;/h2&gt;
&lt;p&gt;欢迎关注我的&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;博客&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Mobx</category><category>Stores</category><author>江辰</author></item><item><title>解决如何知道iframe下载完成</title><link>https://github.com/posts/%E8%A7%A3%E5%86%B3%E5%A6%82%E4%BD%95%E7%9F%A5%E9%81%93iframe%E4%B8%8B%E8%BD%BD%E5%AE%8C%E6%88%90/</link><guid isPermaLink="true">https://github.com/posts/%E8%A7%A3%E5%86%B3%E5%A6%82%E4%BD%95%E7%9F%A5%E9%81%93iframe%E4%B8%8B%E8%BD%BD%E5%AE%8C%E6%88%90/</guid><pubDate>Wed, 15 Apr 2020 13:52:30 GMT</pubDate><content:encoded>&lt;h2&gt;问题&lt;/h2&gt;
&lt;p&gt;当使用 iframe 作为文件下载的载体时，如何知道文件已经下载完毕，现有的 iframe 的&lt;code&gt;onload&lt;/code&gt;方法具有兼容性问题，在 Chrome、IE 下无法监听&lt;code&gt;onload&lt;/code&gt;事件监听文件下载完毕，因为&lt;code&gt;onload&lt;/code&gt;事件本身也是对 iframe 中的 html 结构的加载进度监听。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const url = &apos;http://www.example.com/file.zip&apos;;
const iframe = document.createElement(&apos;iframe&apos;);
iframe.src = url;
iframe.style.display = &apos;none&apos;;
iframe.onload = function() {
    console.log(&apos;start downloading...&apos;);
    document.body.removeAttribute(iframe);
}
document.body.appendChild(iframe);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当 Chrome、IE 下时，如果 HTTP 文件头中包含 Content-disposition: attachment；即下载文件的链接的话，不会触发这个事件&lt;code&gt;onload&lt;/code&gt;事件。&lt;/p&gt;
&lt;h2&gt;关于 Content-disposition&lt;/h2&gt;
&lt;p&gt;Content-disposition 是 MIME 协议的扩展，MIME 协议指示 MIME 用户代理如何显示附加的文件。Content-disposition 其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名，文件直接在浏览器上显示或者在访问时弹出文件下载对话框。 Content-Disposition 为属性名 disposition-type 是以什么方式下载，如 attachment 为以附件方式下载 disposition-parm 为默认保存时的文件名服务端向客户端游览器发送文件时，如果是浏览器支持的文件类型，一般会默认使用浏览器打开，比如 txt、jpg 等，会直接在浏览器中显示，当代码里面使用 Content-Disposition 来确保浏览器弹出下载对话框的时候。 &lt;code&gt;response.addHeader(‘Content-Disposition’, ‘attachment’);&lt;/code&gt;，一定要确保没有做过关于禁止浏览器缓存的操作。 代码如下: &lt;code&gt;response.setHeader(‘Pragma’, ‘No-cache’);response.setHeader(‘Cache-Control’, ‘No-cache’); response.setDateHeader(‘Expires’, 0); &lt;/code&gt;不然会发现下载功能在 Opera 和 FireFox 里面好好的没问题，在 IE 下面就是不行。&lt;/p&gt;
&lt;h2&gt;解决 1：利用 Cookie&lt;/h2&gt;
&lt;p&gt;Nginx 配置如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;add_header Set-Cookie &quot;fileDownloaded=1; path=/;&quot; always;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后端将文件下载进度放在 Cookie 中，通过轮询 Cookie 的方式，对文件下载进度进行获取，判断文件是否已经下载完毕。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let link = document.createElement(&apos;iframe&apos;);
link.src = &apos;/your/file/path/name.zip&apos;;
link.style.display = &apos;none&apos;;
document.body.appendChild(link);

let timer = setInterval(() =&amp;gt; {
    // 通过判断是否有这个cookie来确定是否下载完成
    if (Cookies.get(&apos;fileDownloaded&apos;)) {
        console.log(&apos;下载完成啦&apos;);
        clearInterval(timer);
        document.body.removeChild(link);
        Cookies.remove(&apos;fileDownloaded&apos;);
    }
}, 500);`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;缺陷：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;需要后端配合&lt;/li&gt;
&lt;li&gt;如果客户端禁用了 Cookie，则该方案完全失效；在无痕浏览模式下，读取 Cookie，甚至代码报错。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;解决 2：轮询监听 readyState&lt;/h2&gt;
&lt;p&gt;定时器轮询监听 readyState 的状态，如果是 complete 或者 interactive 说明文件加载完成。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let iframe = document.createElement(&apos;iframe&apos;);
iframe.src = path;
iframe.style.display = &apos;none&apos;;
document.body.appendChild(iframe);
const timer = setInterval(() =&amp;gt; {
    const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
    if (iframeDoc.readyState == &apos;complete&apos; || iframeDoc.readyState == &apos;interactive&apos;) {
        document.body.removeAttribute(iframe);
        clearInterval(timer);
        resolve(&apos;success&apos;);
    }
}, 1000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该种方法比较好，因为不需要后端进行配合，且不依赖与 Cookie 等变量带来的问题，且能实现我们的需求。&lt;/p&gt;
</content:encoded><category>iframe</category><author>江辰</author></item><item><title>解决canvas，toDataURL跨域问题</title><link>https://github.com/posts/%E8%A7%A3%E5%86%B3canvastodataurl%E8%B7%A8%E5%9F%9F%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://github.com/posts/%E8%A7%A3%E5%86%B3canvastodataurl%E8%B7%A8%E5%9F%9F%E9%97%AE%E9%A2%98/</guid><pubDate>Sun, 05 Apr 2020 23:03:45 GMT</pubDate><content:encoded>&lt;h2&gt;图片服务器需要配置 Access-Control-Allow-Origin&lt;/h2&gt;
&lt;p&gt;设置通配符，允许任何域名访问&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;header(&quot;Access-Control-Allow-Origin: *&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;指定域名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;header(&quot;Access-Control-Allow-Origin: www.xxx.com&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时，Chrome 浏览器不会有 Access-Control-Allow-Origin 相关的错误信息，但是，还会有其他的跨域错误信息。&lt;/p&gt;
&lt;h2&gt;HTML crossOrigin 属性解决资源跨域问题&lt;/h2&gt;
&lt;p&gt;在 HTML5 中，有些元素提供了&lt;code&gt;CORS(Cross-Origin Resource Sharing)&lt;/code&gt;属性，这些元素包括&amp;lt;img&amp;gt;，&amp;lt;video&amp;gt;，&amp;lt;script&amp;gt;等，而提供的属性名就是 crossOrigin 属性。&lt;/p&gt;
&lt;p&gt;因此，上面的跨域问题可以这么处理：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const canvas = document.createElement(&apos;canvas&apos;);
const ctx = canvas.getContext(&apos;2d&apos;);
const img = new Image();
img.crossOrigin = &apos;anonymous&apos;;
img.onload = () =&amp;gt; {
    canvas.width = img.width;
    canvas.height = img.height;
    ctx.drawImage(img, 0, 0, img.width, img.height);
    resolve(canvas.toDataURL());
};
img.src = url;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，只要&lt;code&gt;crossOrigin&lt;/code&gt;的属性值不是&lt;code&gt;use-credentials&lt;/code&gt;，全部都会解析为 anonymous，包括空字符串。&lt;/p&gt;
&lt;h2&gt;url 加时间戳，清空缓存&lt;/h2&gt;
&lt;p&gt;如果上面还是没有解决跨域问题，那么要考虑图片是否被浏览器缓存住了。&lt;/p&gt;
&lt;p&gt;没有加时间戳的请求&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/78502895-06235500-7796-11ea-85a7-a21076268933.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;加上时间戳的请求
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/78502902-150a0780-7796-11ea-9f2c-625bf6da8051.png&quot; alt=&quot;94630f5b463e7c0db0c2b04d978b6a7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;因此，代码可以这么写&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const canvas = document.createElement(&apos;canvas&apos;);
const ctx = canvas.getContext(&apos;2d&apos;);
const img = new Image();
img.crossOrigin = &apos;Anonymous&apos;;
img.onload = () =&amp;gt; {
    canvas.width = img.width;
    canvas.height = img.height;
    ctx.drawImage(img, 0, 0, img.width, img.height);
    resolve(canvas.toDataURL());
};
img.src = url + &apos;?time=&apos; + new Date().valueOf();
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Canvas</category><author>江辰</author></item><item><title>解决toDataURL空白问题</title><link>https://github.com/posts/%E8%A7%A3%E5%86%B3pdfjs%E8%BD%ACcanvas%E5%9B%BE%E7%89%87todataurl%E7%A9%BA%E7%99%BD%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://github.com/posts/%E8%A7%A3%E5%86%B3pdfjs%E8%BD%ACcanvas%E5%9B%BE%E7%89%87todataurl%E7%A9%BA%E7%99%BD%E9%97%AE%E9%A2%98/</guid><pubDate>Sat, 04 Apr 2020 16:19:30 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;最近，笔者接到一个需求，需要在前端把 PDF 转成图片，再把图片转成 Base64，传给小程序。然后发现，只有 PDF.js 这个库，才能满足我们的需求。&lt;/p&gt;
&lt;h2&gt;核心代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;renderPage(url) {
    return new Promise((resolve, reject) =&amp;gt; {
        const loadingTask = pdfjsLib.getDocument(url);
        loadingTask.promise.then((pdf) =&amp;gt; {
            const pages = pdf.numPages;
            // 添加canvas, 根据pdf的页数添加
            for (let i = 1; i &amp;lt;= pages; i++) {
                const canvas = document.createElement(&apos;canvas&apos;);
                const showPdf = document.getElementById(&apos;show-pdf&apos;);
                canvas.setAttribute(&apos;id&apos;, &apos;canvas&apos; + i.toString());
                showPdf.appendChild(canvas);
            }
            let count = 0;
            for (let i = 1; i &amp;lt;= pages; i++) {
                pdf.getPage(i).then((page) =&amp;gt; {
                    const viewport = page.getViewport({ scale: 1.5 });
                    const canvas = document.getElementById((&apos;canvas&apos; + i).toString());
                    const context = canvas.getContext(&apos;2d&apos;);
                    canvas.height = viewport.height;
                    canvas.width = viewport.width;
                    const renderContext = {
                        canvasContext: context,
                        viewport: viewport
                    };
                    page.render(renderContext)
                });
            }
        });
    });
}


// 推送
push(record) {
    this.renderPage(record.url).then(() =&amp;gt; {
        const canvas = document.querySelectorAll(&apos;#show-pdf &amp;gt; canvas&apos;);
        let datas = [];
        for(let i = 0; i &amp;lt; canvas.length; i++) {
            datas.push(canvas[i].toDataURL(&quot;image/png&quot;));
        }
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码写完之后，调试，发现获取出来的 Base64，无法还原图片。才疏学浅，死活找不到原因。找了好久，终于看到一篇文章，要看全文，还得关注公众号，获取二维码解锁。emmm，然后，我就解锁了。看到最最关键的两行代码，抱着试试的心态，写上。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/78422123-90cd5c80-768f-11ea-9e47-7b2b8410da34.jpg&quot; alt=&quot;481585839610_ pic_hd&quot; /&gt;&lt;/p&gt;
&lt;p&gt;居然成功了！！！&lt;/p&gt;
&lt;p&gt;然后，反思我的代码哪里不对，几次尝试之后，找到问题了，page.render 绘制有延迟。。。把 page.render 代码修改如下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;page.render(renderContext).promise.then(() =&amp;gt; {
    count++;
    // 已全部完成绘制
    if(count == pages) {
        resolve();
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大功告成，完美解决。&lt;/p&gt;
</content:encoded><category>Canvans</category><author>江辰</author></item><item><title>基于React、Mobx、Webpack 和 React-Router的项目模板</title><link>https://github.com/posts/%E5%9F%BA%E4%BA%8Ereactmobxwebpack-%E5%92%8C-react-router%E7%9A%84%E9%A1%B9%E7%9B%AE%E6%A8%A1%E6%9D%BF/</link><guid isPermaLink="true">https://github.com/posts/%E5%9F%BA%E4%BA%8Ereactmobxwebpack-%E5%92%8C-react-router%E7%9A%84%E9%A1%B9%E7%9B%AE%E6%A8%A1%E6%9D%BF/</guid><pubDate>Sun, 09 Feb 2020 16:09:49 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;自己利用业余时间，基于 React、Ant、Webpack、Mobx、React-Router 写了一个后台管理模板，接下来会出三部曲系列，分为前端、后端、运维到发布，从零搭建 React 后台管理模板，目前已在公司内部搭建了几套项目，并都已上线，希望这个系列，帮助自己梳理各技术最新知识点，同时也希望对看到的人有所帮助。&lt;/p&gt;
&lt;h2&gt;项目效果：&lt;/h2&gt;
&lt;p&gt;登录页：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/76694589-93035300-66af-11ea-9cdc-9f5f816772a8.png&quot; alt=&quot;b1350cab0e03f4550fc4df4e732ec66&quot; /&gt;&lt;/p&gt;
&lt;p&gt;404 页：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/76694593-a0204200-66af-11ea-8e57-0bb35313f93f.png&quot; alt=&quot;1f9521098f4b515ccd7dbf96f466f6f&quot; /&gt;&lt;/p&gt;
&lt;p&gt;列表页：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/76694597-ad3d3100-66af-11ea-8a8e-a3ae644bdcee.png&quot; alt=&quot;715ecc9a20f23420ceef4677f0b3277&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;GitHub 源码地址&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Cherry-Team/tristana&quot;&gt;Tristana&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;在线预览地址&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://order.downfuture.com&quot;&gt;Tristana&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;技术栈&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Ant&lt;/li&gt;
&lt;li&gt;React-Router&lt;/li&gt;
&lt;li&gt;Mobx&lt;/li&gt;
&lt;li&gt;Webpack&lt;/li&gt;
&lt;li&gt;ES6&lt;/li&gt;
&lt;li&gt;Babel&lt;/li&gt;
&lt;li&gt;Axios&lt;/li&gt;
&lt;li&gt;Eslint&lt;/li&gt;
&lt;li&gt;Stylelint&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;项目结构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;│  .babelrc                     // babelrc配置
│  .eslintrc.json               // eslint配置
│  .gitignore                   // git忽略配置
│  package.json                 // 依赖包配置
│  README.md                    // 项目说明
│  stylelint.config.js          // stylelint配置
│
├─dist                          // 打包输出目录
├─public                        // 项目公开目录
│      index.html
│
├─script                        // webpack配置
│      webpack.base.conf.js     // webpack通用配置
│      webpack.dev.js           // webpack开发环境配置
│      webpack.prod.js          // webpack生产环境配置
│
└─src                           // src入口目录
    │  config.js                // 基础配置文件
    │  index.jsx                // 项目入口文件
    │  request.jsx              // 接口请求配置
    │  routeConfig.jsx          // 路由配置
    │
    ├─assets                    // 静态资源
    │  └─images
    │          sign_bg.jpg
    │
    ├─components                // 公用组件
    │  ├─LayoutHeader
    │  │      index.jsx
    │  │      index.less
    │  │
    │  ├─PrivateRoute
    │  │      index.jsx
    │  │
    │  └─Socket
    │          index.jsx
    │
    ├─mobx                      // mobx配置
    │  │  basicStore.js
    │  │  rootStore.js
    │  │
    │  ├─AddGoods
    │  │      store.js
    │  │
    │  ├─CounterStore
    │  │      store.js
    │  │
    │  └─Dashboard
    │          store.js
    │
    ├─pages                      // 页面组件
    │  ├─AddGoods
    │  │      index.jsx
    │  │      index.less
    │  │
    │  ├─Counter
    │  │      index.jsx
    │  │      index.less
    │  │
    │  ├─Dashboard
    │  │      index.jsx
    │  │      index.less
    │  │
    │  ├─Home
    │  │  │  index.jsx
    │  │  │  index.less
    │  │  │
    │  │  └─components
    │  │          menu.jsx
    │  │
    │  └─User
    │          error.jsx
    │          error.less
    │          login.jsx
    │          login.less
    │
    ├─servers                     // 接口配置
    │      dashboard.js
    │
    ├─styles                      // 公共样式
    │      index.less
    │
    └─utils                       // 工具库
            index.js
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;各大库版本&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&quot;dependencies&quot;: {
    &quot;@babel/cli&quot;: &quot;^7.8.0&quot;,
    &quot;@babel/core&quot;: &quot;^7.8.0&quot;,
    &quot;@babel/plugin-proposal-class-properties&quot;: &quot;^7.8.0&quot;,
    &quot;@babel/plugin-proposal-decorators&quot;: &quot;^7.8.0&quot;,
    &quot;@babel/plugin-proposal-json-strings&quot;: &quot;^7.8.0&quot;,
    &quot;@babel/plugin-syntax-dynamic-import&quot;: &quot;^7.8.0&quot;,
    &quot;@babel/plugin-syntax-import-meta&quot;: &quot;^7.8.0&quot;,
    &quot;@babel/plugin-transform-runtime&quot;: &quot;^7.8.0&quot;,
    &quot;@babel/polyfill&quot;: &quot;^7.8.0&quot;,
    &quot;@babel/preset-env&quot;: &quot;^7.8.2&quot;,
    &quot;@babel/preset-react&quot;: &quot;^7.8.0&quot;,
    &quot;@babel/preset-stage-2&quot;: &quot;^7.8.0&quot;,
    &quot;antd&quot;: &quot;^4.0.0&quot;,
    &quot;axios&quot;: &quot;^0.19.2&quot;,
    &quot;babel-eslint&quot;: &quot;^10.0.3&quot;,
    &quot;babel-loader&quot;: &quot;^8.0.0&quot;,
    &quot;babel-plugin-import&quot;: &quot;^1.13.0&quot;,
    &quot;babel-plugin-react-css-modules&quot;: &quot;^5.2.6&quot;,
    &quot;classnames&quot;: &quot;^2.2.6&quot;,
    &quot;clean-webpack-plugin&quot;: &quot;^3.0.0&quot;,
    &quot;compression-webpack-plugin&quot;: &quot;^3.0.1&quot;,
    &quot;core-js&quot;: &quot;^3.6.4&quot;,
    &quot;cross-env&quot;: &quot;^6.0.3&quot;,
    &quot;css-loader&quot;: &quot;^3.2.0&quot;,
    &quot;dayjs&quot;: &quot;^1.8.15&quot;,
    &quot;eslint&quot;: &quot;^6.8.0&quot;,
    &quot;eslint-config-standard&quot;: &quot;^14.1.0&quot;,
    &quot;eslint-loader&quot;: &quot;^3.0.3&quot;,
    &quot;eslint-plugin-import&quot;: &quot;^2.20.0&quot;,
    &quot;eslint-plugin-jsx-a11y&quot;: &quot;^6.2.3&quot;,
    &quot;eslint-plugin-node&quot;: &quot;^11.0.0&quot;,
    &quot;eslint-plugin-promise&quot;: &quot;^4.2.1&quot;,
    &quot;eslint-plugin-react&quot;: &quot;^7.17.0&quot;,
    &quot;eslint-plugin-standard&quot;: &quot;^4.0.1&quot;,
    &quot;file-loader&quot;: &quot;^5.0.2&quot;,
    &quot;history&quot;: &quot;^4.7.2&quot;,
    &quot;html-webpack-plugin&quot;: &quot;^3.2.0&quot;,
    &quot;install&quot;: &quot;^0.12.2&quot;,
    &quot;is-promise&quot;: &quot;^2.1.0&quot;,
    &quot;less&quot;: &quot;^3.8.1&quot;,
    &quot;less-loader&quot;: &quot;^5.0.0&quot;,
    &quot;lint-staged&quot;: &quot;^10.0.8&quot;,
    &quot;mini-css-extract-plugin&quot;: &quot;^0.8.0&quot;,
    &quot;mobx&quot;: &quot;^5.13.1&quot;,
    &quot;mobx-react&quot;: &quot;^6.1.4&quot;,
    &quot;npm&quot;: &quot;^6.10.2&quot;,
    &quot;optimize-css-assets-webpack-plugin&quot;: &quot;^5.0.1&quot;,
    &quot;postcss-loader&quot;: &quot;^3.0.0&quot;,
    &quot;pre-commit&quot;: &quot;^1.2.2&quot;,
    &quot;progress-bar-webpack-plugin&quot;: &quot;^1.12.1&quot;,
    &quot;prop-types&quot;: &quot;^15.7.2&quot;,
    &quot;react&quot;: &quot;^16.12.0&quot;,
    &quot;react-dom&quot;: &quot;^16.12.0&quot;,
    &quot;react-router&quot;: &quot;^5.1.2&quot;,
    &quot;react-router-dom&quot;: &quot;^5.1.2&quot;,
    &quot;react-scripts&quot;: &quot;^3.0.0&quot;,
    &quot;speed-measure-webpack-plugin&quot;: &quot;^1.3.1&quot;,
    &quot;stylelint&quot;: &quot;^13.0.0&quot;,
    &quot;stylelint-config-standard&quot;: &quot;^19.0.0&quot;,
    &quot;terser-webpack-plugin&quot;: &quot;^2.2.1&quot;,
    &quot;webpack&quot;: &quot;^4.41.2&quot;,
    &quot;webpack-bundle-analyzer&quot;: &quot;^3.6.0&quot;,
    &quot;webpack-cli&quot;: &quot;^3.3.10&quot;,
    &quot;webpack-dev-server&quot;: &quot;^3.10.1&quot;
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;下篇文章&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog/issues/1&quot;&gt;React 全家桶建站教程系列-前端&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;最后&lt;/h2&gt;
&lt;p&gt;如果你觉得还不错或者有帮助的同学，欢迎关注、多多 star；欢迎你关注我的&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;Blog&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>React</category><category>Mobx</category><category>Webpack</category><author>江辰</author></item><item><title>MobX 源码解析-observable</title><link>https://github.com/posts/mobx-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-observable/</link><guid isPermaLink="true">https://github.com/posts/mobx-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-observable/</guid><pubDate>Fri, 06 Dec 2019 17:56:13 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近一直在用 &lt;code&gt;MobX&lt;/code&gt; 开发中小型项目，开发起来真的，真的很爽，响应式更新，性能快，样板代码减少(相对 &lt;code&gt;Redux&lt;/code&gt;)。所以，想趁 2019 年结束前把 &lt;code&gt;MobX&lt;/code&gt; 源码研究一遍。&lt;/p&gt;
&lt;h2&gt;Tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;由于 &lt;code&gt;MobX&lt;/code&gt; 的源码很大，因此只会把个人认为比较重要的部分截取说明&lt;/li&gt;
&lt;li&gt;阅读的 &lt;code&gt;MobX&lt;/code&gt; 源码版本@5.15.0&lt;/li&gt;
&lt;li&gt;由于本人对 &lt;code&gt;TypeScript&lt;/code&gt; 经验尚浅，所以我会将其编译成 &lt;code&gt;JavaScript&lt;/code&gt; 阅读&lt;/li&gt;
&lt;li&gt;下面会用 &lt;code&gt;mobx-source&lt;/code&gt; 简称代替 &lt;code&gt;Mobx&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;如何调试源码&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$ git clone https://github.com/mobxjs/mobx.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$ cd mobx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$ cnpm i&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;查看 &lt;code&gt;package.json&lt;/code&gt;，发现执行脚本有&lt;code&gt;quick-build&lt;/code&gt; 和 &lt;code&gt;small-build&lt;/code&gt;，我选择的是&lt;code&gt;small-build&lt;/code&gt;，&lt;code&gt;cnpm run small-build&lt;/code&gt; 然后在根目录下会生成 &lt;code&gt;.build.es5&lt;/code&gt; 和 &lt;code&gt;.build.es6&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
    &quot;quick-build&quot;: &quot;tsc --pretty&quot;,
    &quot;small-build&quot;: &quot;node scripts/build.js&quot;
},
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;把 &lt;code&gt;.build.es6&lt;/code&gt; 改名为 &lt;code&gt;mobx-source&lt;/code&gt; 放到我写好的脚手架中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2019/12/8/16ee3607057477ff?w=639&amp;amp;h=260&amp;amp;f=png&amp;amp;s=29533&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;引入绝对路径&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import { observable, action } from &apos;../../mobx-source/mobx&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;然后就可以愉快的调试源码了&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;function createObservable(v, arg2, arg3) {
    debugger;
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;让我们从计数器开始，看看 &lt;code&gt;MobX&lt;/code&gt; 最基础的使用方式
&lt;code&gt;React&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@inject(&apos;counterStore&apos;)
@observer
class Index extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        const { counterStore } = this.props;
        return (
            &amp;lt;section&amp;gt;
                &amp;lt;button onClick={() =&amp;gt; counterStore.add()}&amp;gt;+&amp;lt;/button&amp;gt;
                &amp;lt;span&amp;gt;count is: {counterStore.obj.count}&amp;lt;/span&amp;gt;
                &amp;lt;button onClick={() =&amp;gt; counterStore.reduce()}&amp;gt;-&amp;lt;/button&amp;gt;
            &amp;lt;/section&amp;gt;
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MobX&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { observable, action } from &apos;../../mobx-source/mobx&apos;;
class CounterStore {
    @observable obj = {
        count: 0
    };

    @action
    add() {
        this.obj.count++;
    }

    @action
    reduce() {
        this.obj.count--;
    }
}

export default CounterStore;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;界面如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2019/12/8/16ee3606f0275295?w=176&amp;amp;h=92&amp;amp;f=png&amp;amp;s=1784&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;功能非常简单，实现也非常简单。通过 &lt;code&gt;observable&lt;/code&gt; 对 &lt;code&gt;count&lt;/code&gt; 进行了监听，只要 &lt;code&gt;count&lt;/code&gt; 产生了数据变化，就会自动刷新界面。那么，&lt;code&gt;MobX&lt;/code&gt; 是如何做到的呢？让我们一步步来分析。&lt;/p&gt;
&lt;h2&gt;observable&lt;/h2&gt;
&lt;p&gt;首先，看入口文件，&lt;code&gt;mobx-source -&amp;gt; mobx.js&lt;/code&gt;，发现&lt;code&gt;observable，action，runInAction&lt;/code&gt; 等其他方法都是从 &lt;code&gt;internal&lt;/code&gt; 引入的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export { observable, action, runInAction } from &quot;./internal&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开 &lt;code&gt;internal.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export * from &quot;./api/action&quot;;
export * from &quot;./api/autorun&quot;;
export * from &quot;./api/observable&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后看 &lt;code&gt;api/observable&lt;/code&gt; 这个文件，发现 &lt;code&gt;export const observable = createObservable;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function createObservable(v, arg2, arg3) {
    // @observable someProp;
    if (typeof arguments[1] === &quot;string&quot; || typeof arguments[1] === &quot;symbol&quot;) {
        return deepDecorator.apply(null, arguments);
    }
    // it is an observable already, done
    if (isObservable(v))
        return v;
    // something that can be converted and mutated?
    const res = isPlainObject(v)
        ? observable.object(v, arg2, arg3)
        : Array.isArray(v)
            ? observable.array(v, arg2)
            : isES6Map(v)
                ? observable.map(v, arg2)
                : isES6Set(v)
                    ? observable.set(v, arg2)
                    : v;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;createObservable&lt;/code&gt; 主要做了以下几件事：&lt;/p&gt;
&lt;p&gt;1、如果被观察的对象是 &lt;code&gt;string&lt;/code&gt; 或 &lt;code&gt;symbol&lt;/code&gt; ，那么执行 &lt;code&gt;deepDecorator.apply(null, arguments);&lt;/code&gt;
&lt;code&gt;export const deepDecorator = createDecoratorForEnhancer(deepEnhancer); &lt;/code&gt; deepEnhancer 方法内部会判断当前修改的值类型，来走不同的工厂方法。&lt;/p&gt;
&lt;p&gt;2、如果第一个参数已经是一个可被观察的对象，那么返回这个对象。&lt;/p&gt;
&lt;p&gt;3、对第一个参数进行类型(&lt;code&gt;object、array、map、set&lt;/code&gt;)判断，然后调用不同的工厂方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const observableFactories = {
    box(value, options) {
        ...
    },
    array(initialValues, options) {
        ...
    },
    map(initialValues, options) {
        ...
    },
    set(initialValues, options) {
        ...
    },
    object(props, decorators, options) {
        ...
    },
    ref: refDecorator,
    shallow: shallowDecorator,
    deep: deepDecorator,
    struct: refStructDecorator
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，我们来分析 &lt;code&gt;createDecoratorForEnhancer&lt;/code&gt; 方法，主要有两个参数，第一个默认为 &lt;code&gt;true&lt;/code&gt;，第二个是个函数。&lt;code&gt;res.enhancer = enhancer;&lt;/code&gt;，会把上面传的&lt;code&gt;deepEnhancer&lt;/code&gt;，在此处进行挂载。根据变量不同类型，调用 &lt;code&gt;observable&lt;/code&gt; 的不同参数，如 &lt;code&gt;object, array&lt;/code&gt; 来进行劫持。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function createDecoratorForEnhancer(enhancer) {
    invariant(enhancer);
    const decorator = createPropDecorator(true, (target, propertyName, descriptor, _decoratorTarget, decoratorArgs) =&amp;gt; {
        if (process.env.NODE_ENV !== &quot;production&quot;) {
            invariant(!descriptor || !descriptor.get, `@observable cannot be used on getter (property &quot;${stringifyKey(propertyName)}&quot;), use @computed instead.`);
        }
        const initialValue = descriptor
            ? descriptor.initializer
                ? descriptor.initializer.call(target)
                : descriptor.value
            : undefined;
        asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer);
    });
    const res =
    // Extra process checks, as this happens during module initialization
    typeof process !== &quot;undefined&quot; &amp;amp;&amp;amp; process.env &amp;amp;&amp;amp; process.env.NODE_ENV !== &quot;production&quot;
        ? function observableDecorator() {
            // This wrapper function is just to detect illegal decorator invocations, deprecate in a next version
            // and simply return the created prop decorator
            if (arguments.length &amp;lt; 2)
                return fail(&quot;Incorrect decorator invocation. @observable decorator doesn&apos;t expect any arguments&quot;);
            return decorator.apply(null, arguments);
        }
        : decorator;
    res.enhancer = enhancer;
    return res;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;createPropDecorator&lt;/code&gt; 方法创建属性拦截器，&lt;code&gt;addHiddenProp&lt;/code&gt; 方法为目标对象添加 &lt;code&gt;Symbol(mobx pending decorators)&lt;/code&gt; 属性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function createPropDecorator(propertyInitiallyEnumerable, propertyCreator) {
    return function decoratorFactory() {
        let decoratorArguments;
        const decorator = function decorate(target, prop, descriptor, applyImmediately
        // This is a special parameter to signal the direct application of a decorator, allow extendObservable to skip the entire type decoration part,
        // as the instance to apply the decorator to equals the target
        ) {
            ...
            if (!Object.prototype.hasOwnProperty.call(target, mobxPendingDecorators)) {
                const inheritedDecorators = target[mobxPendingDecorators];
                addHiddenProp(target, mobxPendingDecorators, Object.assign({}, inheritedDecorators));
            }
            target[mobxPendingDecorators][prop] = {
                prop,
                propertyCreator,
                descriptor,
                decoratorTarget: target,
                decoratorArguments
            };
            return createPropertyInitializerDescriptor(prop, propertyInitiallyEnumerable);
        };
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于上面我定义的变量是对象，所以 Mobx 会把这个对象拦截，执行 &lt;code&gt;observableFactories.object&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;object(props, decorators, options) {
    if (typeof arguments[1] === &quot;string&quot;)
        incorrectlyUsedAsDecorator(&quot;object&quot;);
    const o = asCreateObservableOptions(options);
    if (o.proxy === false) {
        return extendObservable({}, props, decorators, o);
    }
    else {
        const defaultDecorator = getDefaultDecoratorFromObjectOptions(o);
        const base = extendObservable({}, undefined, undefined, o);
        const proxy = createDynamicObservableObject(base);
        extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator);
        return proxy;
    }
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;asCreateObservableOptions&lt;/code&gt; 创建一个可观察的对象，由于已经是 &lt;code&gt;object&lt;/code&gt; 了，所以 &lt;code&gt;proxy&lt;/code&gt; 为 &lt;code&gt;undefined&lt;/code&gt;，则进 &lt;code&gt;else&lt;/code&gt;， &lt;code&gt;const base = extendObservable({}, undefined, undefined, o);&lt;/code&gt; 加工处理下 &lt;code&gt;o&lt;/code&gt; 对象，转成 &lt;code&gt;Symbol&lt;/code&gt; 数据类型，然后看 &lt;code&gt;createDynamicObservableObject&lt;/code&gt;，很关键的方法，这个函数内部就是利用 &lt;code&gt;Proxy&lt;/code&gt; 来创建拦截器，对这个对象的属性 &lt;code&gt;has， get， set， deleteProperty， ownKeys，preventExtensions&lt;/code&gt; 方法进行了代理拦截。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function createDynamicObservableObject(base) {
    const proxy = new Proxy(base, objectProxyTraps);
    base[$mobx].proxy = proxy;
    return proxy;
}

const objectProxyTraps = {
    has(target, name) {
        ...
    },
    get(target, name) {
        ...
    },
    set(target, name, value) {
        ...
    },
    deleteProperty(target, name) {
        ...
    },
    ownKeys(target) {
        ...
    },
    preventExtensions(target) {
        ...
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator);&lt;/code&gt;，会对对象属性遍历，来创建拦截器，而且这里面会牵扯到一个事务的概念，后面会分析事务。&lt;/p&gt;
&lt;h2&gt;博客&lt;/h2&gt;
&lt;p&gt;欢迎关注我的&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;博客&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Mobx</category><author>江辰</author></item><item><title>基于MobX 封装 Action 接口 Loading</title><link>https://github.com/posts/%E5%9F%BA%E4%BA%8Emobx-%E5%B0%81%E8%A3%85-action-%E6%8E%A5%E5%8F%A3-loading/</link><guid isPermaLink="true">https://github.com/posts/%E5%9F%BA%E4%BA%8Emobx-%E5%B0%81%E8%A3%85-action-%E6%8E%A5%E5%8F%A3-loading/</guid><pubDate>Tue, 19 Nov 2019 17:37:42 GMT</pubDate><content:encoded>&lt;h1&gt;代码如下&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;import { action, observable } from &apos;mobx&apos;;

export default class BasicStore {
    @observable isLoading  = observable.map({ });

    @action
    changeLoadingStatus (loadingType, type) {
        this.isLoading.set(loadingType, type);
    }
}

// 初始化loading
export function initLoading(target, key, descriptor) {
    const oldValue = descriptor.value;

    descriptor.value = async function(...args) {
        this.changeLoadingStatus(key, true);
        let res;
        try {
            res = await oldValue.apply(this, args);
        } catch (error) {
            // 做一些错误上报之类的处理
            throw error;
        } finally {
            this.changeLoadingStatus(key, false);
        }
        return res;
    };

    return descriptor;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Mobx</category><author>江辰</author></item><item><title>VS Code 提高前端开发效率插件</title><link>https://github.com/posts/vs-code-%E6%8F%90%E9%AB%98%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%8F%92%E4%BB%B6/</link><guid isPermaLink="true">https://github.com/posts/vs-code-%E6%8F%90%E9%AB%98%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%8F%92%E4%BB%B6/</guid><pubDate>Fri, 15 Nov 2019 09:45:16 GMT</pubDate><content:encoded>&lt;h2&gt;Auto Close Tag&lt;/h2&gt;
&lt;p&gt;自动添加 &lt;code&gt;HTML/XML&lt;/code&gt; 关闭标记，与 &lt;code&gt;Visual Studio IDE&lt;/code&gt; 或 &lt;code&gt;Sublime&lt;/code&gt; 文本相同&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/76220978-5076ec00-6253-11ea-8a0c-3a45fbf8cad0.gif&quot; alt=&quot;usage&quot; /&gt;&lt;/p&gt;
&lt;p&gt;键入开始标签的结束括号后，将自动插入结束标签。&lt;/p&gt;
&lt;h2&gt;Auto Rename Tag&lt;/h2&gt;
&lt;p&gt;自动重命名配对的 &lt;code&gt;HTML/XML&lt;/code&gt; 标记&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2020/3/9/170bfe5fe438ed72?w=1440&amp;amp;h=938&amp;amp;f=gif&amp;amp;s=158502&quot; alt=&quot;usage&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Beautify&lt;/h2&gt;
&lt;p&gt;为 &lt;code&gt;Visual Studio&lt;/code&gt; 代码美化代码&lt;/p&gt;
&lt;p&gt;选中需要美化的代码，右键 &lt;code&gt;Format Document&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;GitLens&lt;/h2&gt;
&lt;p&gt;增强 &lt;code&gt;Visual Studio&lt;/code&gt; 代码中内置的 &lt;code&gt;Git&lt;/code&gt; 功能-通过 &lt;code&gt;Git&lt;/code&gt; 责怪注释和代码镜头一目了然地可视化代码作者，无缝导航和浏览 &lt;code&gt;Git&lt;/code&gt; 存储库，通过强大的比较命令获得有价值的见解，等等&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2020/3/9/170bfe50b60cb655?w=683&amp;amp;h=118&amp;amp;f=png&amp;amp;s=5530&quot; alt=&quot;7bf310ecae2e4fb92499bdcc3ea723e&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;JavaScript (ES6) code snippets&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ES6&lt;/code&gt; 语法中 &lt;code&gt;JavaScript&lt;/code&gt; 的代码段&lt;/p&gt;
&lt;h2&gt;Path Autocomplete&lt;/h2&gt;
&lt;p&gt;提供 &lt;code&gt;Visual Studio&lt;/code&gt; 代码的路径完成。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2020/3/9/170bfe66dd619f57?w=817&amp;amp;h=439&amp;amp;f=gif&amp;amp;s=251155&quot; alt=&quot;path-autocomplete&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Path Intellisense&lt;/h2&gt;
&lt;p&gt;自动完成文件名的 &lt;code&gt;Visual Studio&lt;/code&gt; 代码插件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2020/3/9/170bfe66dd73b02d?w=480&amp;amp;h=270&amp;amp;f=gif&amp;amp;s=87934&quot; alt=&quot;iaHeUiDeTUZuo&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;React-Native/React/Redux snippets for es6/es7&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;JS/TS&lt;/code&gt; 中使用 &lt;code&gt;ES7&lt;/code&gt; 语法对 &lt;code&gt;React&lt;/code&gt;、&lt;code&gt;Redux&lt;/code&gt; 和 &lt;code&gt;Graphql&lt;/code&gt; 进行简单扩展&lt;/p&gt;
&lt;h2&gt;StandardJS - JavaScript Standard Style&lt;/h2&gt;
&lt;p&gt;将 &lt;code&gt;JavaScript&lt;/code&gt; 标准样式集成到 &lt;code&gt;Visual Studio&lt;/code&gt; 代码中。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;安装 &quot;JavaScript 标准样式&quot; 扩展&lt;/p&gt;
&lt;p&gt;如果您不知道如何在 &lt;code&gt;Visual Studio&lt;/code&gt; 中安装扩展，请查看文档。&lt;/p&gt;
&lt;p&gt;您将需要重新加载 &lt;code&gt;Visual Studio&lt;/code&gt; 才能使用新的扩展。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安装 standard 或 semistandard&lt;/p&gt;
&lt;p&gt;这可以在全局或本地完成。我们建议您在本地安装它们（即保存在项目的中 &lt;code&gt;devDependencies&lt;/code&gt;），以确保在开发项目时其他开发人员也已安装它们。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;禁用内置的 Visual Studio 验证器&lt;/p&gt;
&lt;p&gt;为此，请 &lt;code&gt;&quot;javascript.validate.enable&quot;: false&lt;/code&gt; 在 &lt;code&gt;Visual Studio&lt;/code&gt; 中进行设置 &lt;code&gt;settings.json&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Vetur&lt;/h2&gt;
&lt;p&gt;VS 代码的 Vue 工具&lt;/p&gt;
&lt;h2&gt;vscode wxml&lt;/h2&gt;
&lt;p&gt;微信 &lt;code&gt;wxml&lt;/code&gt; 支持 &lt;code&gt;/vscode&lt;/code&gt; 片段&lt;/p&gt;
&lt;h2&gt;vscode-fileheader&lt;/h2&gt;
&lt;p&gt;插入标题注释，并自动更新时间。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2020/3/9/170bfe7f0061bd4a?w=921&amp;amp;h=510&amp;amp;f=gif&amp;amp;s=120076&quot; alt=&quot;fileheader&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;“settings.json”&lt;/code&gt; 中，设置并修改创建者的名称。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;fileheader.Author&quot;: &quot;Jiang&quot;,
&quot;fileheader.LastModifiedBy&quot;: &quot;Jiang&quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;热键&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ctrl+alt+i
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;vscode-icons&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Visual Studio&lt;/code&gt; 代码的图标&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2020/3/9/170bfe50cd3f173d?w=1920&amp;amp;h=1041&amp;amp;f=png&amp;amp;s=180390&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;wxml&lt;/h2&gt;
&lt;p&gt;微信小程序 &lt;code&gt;wxml&lt;/code&gt; 格式化以及高亮组件(高度自定义)&lt;/p&gt;
&lt;h2&gt;ESLint&lt;/h2&gt;
&lt;p&gt;将 &lt;code&gt;ESLint JavaScript&lt;/code&gt; 集成到 &lt;code&gt;Visual Studio&lt;/code&gt; 代码中。&lt;/p&gt;
&lt;p&gt;以下设置为包括 &lt;code&gt;ESLint&lt;/code&gt; 在内的所有提供程序都启用了自动修复：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;editor.codeActionsOnSave&quot;: {
    &quot;source.fixAll&quot;: true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相反，此配置仅在 &lt;code&gt;ESLint&lt;/code&gt; 上将其打开：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;editor.codeActionsOnSave&quot;: {
    &quot;source.fixAll.eslint&quot;: true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;您还可以通过以下方式有选择地禁用 ESLint：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;editor.codeActionsOnSave&quot;: {
    &quot;source.fixAll&quot;: true,
    &quot;source.fixAll.eslint&quot;: false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Import Cost&lt;/h2&gt;
&lt;p&gt;在编辑器中显示导入/要求包大小&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/76226912-799a7b00-6259-11ea-986b-a5613e35312c.gif&quot; alt=&quot;import-cost&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Beautify css/sass/scss/less&lt;/h2&gt;
&lt;p&gt;美化 &lt;code&gt;CSS&lt;/code&gt;、&lt;code&gt;Sass&lt;/code&gt; 和更少的代码（&lt;code&gt;Visual Studio&lt;/code&gt; 代码的扩展）&lt;/p&gt;
&lt;p&gt;选中需要美化的代码，右键 &lt;code&gt;Format Document&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;TSLint&lt;/h2&gt;
&lt;p&gt;对 &lt;code&gt;Visual Studio&lt;/code&gt; 代码的 &lt;code&gt;TSLint&lt;/code&gt; 支持&lt;/p&gt;
&lt;h2&gt;Settings Sync&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;GitHub Gist&lt;/code&gt; 跨多台计算机同步设置、代码段、主题、文件图标、启动、键绑定、工作区和扩展名。&lt;/p&gt;
&lt;h2&gt;CSS Peek&lt;/h2&gt;
&lt;p&gt;允许查看 &lt;code&gt;CSS ID&lt;/code&gt; 和类字符串作为从 &lt;code&gt;HTML&lt;/code&gt; 文件到相应 &lt;code&gt;CSS&lt;/code&gt; 的定义。允许查看和转到定义。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/76229270-e105fa00-625c-11ea-8b48-6a48506b725c.gif&quot; alt=&quot;symbolProvider&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Stylelint&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;stylelint&lt;/code&gt; 对 &lt;code&gt;lint CSS/SCSS/Less&lt;/code&gt; 的 &lt;code&gt;Visual Studio&lt;/code&gt; 代码扩展，进行格式校验。&lt;/p&gt;
&lt;h2&gt;博客&lt;/h2&gt;
&lt;p&gt;欢迎关注我的&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;博客&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>React</category><category>VS Code</category><author>江辰</author></item><item><title>MobX or Redux</title><link>https://github.com/posts/mobx-or-redux/</link><guid isPermaLink="true">https://github.com/posts/mobx-or-redux/</guid><pubDate>Fri, 04 Oct 2019 22:14:34 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在过去的项目中一直用的都是 &lt;code&gt;Redux&lt;/code&gt;，觉得挺不错的，按照官方推荐的一些写法，再加上团队风格，打造了一套关于 &lt;code&gt;Redux&lt;/code&gt; 的架构，但是，现在觉得写 &lt;code&gt;Action&lt;/code&gt;、&lt;code&gt;Reducer&lt;/code&gt; 太繁琐，随着业务不断的增量，相应的文件和代码也会不断的增加，而且对新人来说不是非常友好(理解 &lt;code&gt;Redux&lt;/code&gt; 比较困难)，听说一方诸侯 &lt;code&gt;MobX&lt;/code&gt; 非常不错，所以在尝试使用了，目前项目中两套架构都是并存，写下自己的一些感想。&lt;/p&gt;
&lt;h2&gt;都解决什么问题？&lt;/h2&gt;
&lt;p&gt;1、组件之间复用状态非常困难&lt;/p&gt;
&lt;p&gt;&lt;code&gt;React&lt;/code&gt; 本身没有提供将可复用性状态“附加”到组件的途径（例如，把组件连接到 &lt;code&gt;Store&lt;/code&gt;）。如果你使用过 &lt;code&gt;React&lt;/code&gt; 一段时间，你也许会熟悉一些解决此类问题的方案，比如 &lt;code&gt;Props&lt;/code&gt; 和 &lt;code&gt;HOC&lt;/code&gt;。但是这类方案需要重新组织你的组件结构，这可能会很麻烦，使你的代码难以理解。写下这片博客的时候，&lt;code&gt;React&lt;/code&gt; 已提供 &lt;code&gt;Hook&lt;/code&gt;，但是本人觉得这都是些 &lt;code&gt;hack&lt;/code&gt; 方案。&lt;/p&gt;
&lt;p&gt;2、复杂组件变得难以理解&lt;/p&gt;
&lt;p&gt;我们经常维护一些组件，组件起初很简单，但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。例如，组件常常在 &lt;code&gt;componentDidMount&lt;/code&gt; 和 &lt;code&gt;componentDidUpdate&lt;/code&gt; 中获取数据。但是，同一个 &lt;code&gt;componentDidMount&lt;/code&gt; 中可能也包含很多其它的逻辑，如设置事件监听，而之后需在 &lt;code&gt;componentWillUnmount&lt;/code&gt; 中清除。相互关联且需要对照修改的代码被进行了拆分，而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 BUG，并且导致逻辑不一致。&lt;/p&gt;
&lt;p&gt;在多数情况下，不可能将组件拆分为更小的粒度，因为状态逻辑无处不在。&lt;/p&gt;
&lt;h2&gt;Redux&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Redux&lt;/code&gt; 由 &lt;code&gt;Flux&lt;/code&gt; 演变而来，但受 Elm 的启发，避开了 &lt;code&gt;Flux&lt;/code&gt; 的复杂性。它主要有以下三个核心概念：&lt;/p&gt;
&lt;p&gt;1、Actions&lt;/p&gt;
&lt;p&gt;一个 &lt;code&gt;JavaScript&lt;/code&gt; 对象，描述发生的动作，主要包含 &lt;code&gt;type&lt;/code&gt; 和 &lt;code&gt;payload&lt;/code&gt; 两个属性。
&lt;code&gt;payload&lt;/code&gt; 可以是普通的数据或是函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const GET_LIST = &apos;getList&apos;;
return {
    type: GET_LIST,
    payload: api.getList(params)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、Reducer&lt;/p&gt;
&lt;p&gt;定义应用状态如何响应不同动作（&lt;code&gt;Action&lt;/code&gt;），如何更新状态；&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;switch (action.type) {
  case GET_LIST:
  	return Object.assign({}, state, { list: action.payload.result });
  default:
    retur state;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3、Store&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const initialState = {
    orderListData: {}
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;存储组件的数据，主要提供以下功能：&lt;/p&gt;
&lt;p&gt;3.1. 维护应用状态并支持访问状态(&lt;code&gt;getState()&lt;/code&gt;)；&lt;/p&gt;
&lt;p&gt;3.2. 支持监听 &lt;code&gt;Action&lt;/code&gt; 的分发，更新状态(&lt;code&gt;dispatch(action)&lt;/code&gt;)；&lt;/p&gt;
&lt;p&gt;3.3. 支持订阅 &lt;code&gt;Store&lt;/code&gt; 的变更(&lt;code&gt;subscribe(listener)&lt;/code&gt;);&lt;/p&gt;
&lt;p&gt;4、异步流&lt;/p&gt;
&lt;p&gt;由于 &lt;code&gt;Redux&lt;/code&gt; 所有对 &lt;code&gt;Store&lt;/code&gt; 状态的变更，都应该通过 &lt;code&gt;Action&lt;/code&gt; 触发，异步任务（通常都是业务或获取数据任务）也不例外，而为了不将业务或数据相关的任务混入 &lt;code&gt;React&lt;/code&gt; 组件中，就需要使用其他框架配合管理异步任务流程，如 &lt;code&gt;redux-thunk&lt;/code&gt;、&lt;code&gt;redux-saga&lt;/code&gt;、&lt;code&gt;redux-promise&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;5、数据流向&lt;/p&gt;
&lt;p&gt;&amp;lt;img width=&quot;640&quot; alt=&quot;one-way-data-flow-04fe46332c1ccb3497ecb04b94e55b97&quot; src=&quot;https://github.com/xuya227939/blog/assets/16217324/3ce51a12-63b2-42ec-bb19-d8e7a1661638&quot;&amp;gt;&lt;/p&gt;
&lt;h3&gt;优点&lt;/h3&gt;
&lt;p&gt;1、流程规范，按照官方推荐的规范和结合团队风格打造一套属于自己的流程。&lt;/p&gt;
&lt;p&gt;2、函数式编程，在 &lt;code&gt;Reducer&lt;/code&gt; 中，接受输入，然后输出，不会有副作用发生，幂等性。&lt;/p&gt;
&lt;p&gt;3、可追踪性，很容易追踪产生 BUG 的原因。&lt;/p&gt;
&lt;h3&gt;缺点&lt;/h3&gt;
&lt;p&gt;1、流畅太繁琐，需要写各种 &lt;code&gt;Action&lt;/code&gt;，&lt;code&gt;Reducer&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;2、要想完成异步数据，得配合其他库。&lt;/p&gt;
&lt;h2&gt;MobX&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;MobX&lt;/code&gt; 是一个经过战火洗礼的库，它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。其中核心概念也非常简单，主要有以下几个：&lt;/p&gt;
&lt;p&gt;1、Store&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;observable&lt;/code&gt; 很像把对象的属性变成 &lt;code&gt;Excel&lt;/code&gt; 的单元格。 但和单元格不同的是，这些值不只是原始值，还可以是引用值，比如对象和数组。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { observable } from &quot;mobx&quot;;

class Todo {
    @observable title = &apos;&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、Computed values&lt;/p&gt;
&lt;p&gt;当添加了一个新的 &lt;code&gt;todo&lt;/code&gt; 或者某个 &lt;code&gt;todo&lt;/code&gt; 的 &lt;code&gt;finished&lt;/code&gt; 属性发生变化时，&lt;code&gt;MobX&lt;/code&gt; 会确保 &lt;code&gt;unfinishedTodoCount&lt;/code&gt; 自动更新。 像这样的计算可以类似于 &lt;code&gt;MS Excel&lt;/code&gt; 这样电子表格程序中的公式。每当只有在需要它们的时候，它们才会自动更新。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TodoList {
    @observable todos = [];
    @computed get unfinishedTodoCount() {
        return this.todos.filter(todo =&amp;gt; !todo.finished).length;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3、Reactions&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Reactions&lt;/code&gt; 和计算值很像，但它不是产生一个新的值，而是会产生一些副作用，比如打印到控制台、网络请求、递增地更新 &lt;code&gt;React&lt;/code&gt; 组件树以修补 &lt;code&gt;DOM&lt;/code&gt;、等等。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;autorun(() =&amp;gt; {
    console.log(&quot;Tasks left: &quot; + todos.unfinishedTodoCount)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4、Actions&lt;/p&gt;
&lt;p&gt;描述要发生的动作&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TodoList {
    @observable title = &apos;&apos;;

    @action
    changeTitle() {
        this.title = &apos;test&apos;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5、异步流&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MobX&lt;/code&gt; 不需要额外配置另外的库。&lt;/p&gt;
&lt;p&gt;6、数据流向&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/xuya227939/blog/assets/16217324/439042e4-7125-44a4-834c-2758dc126208&quot; alt=&quot;flow2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;优点&lt;/h3&gt;
&lt;p&gt;1、学习成本少，基础知识非常简单，跟 &lt;code&gt;Vue&lt;/code&gt; 一样的核心原理，响应式编程。&lt;/p&gt;
&lt;p&gt;2、写更少的代码，完成更多的事。不会跟 &lt;code&gt;Redux&lt;/code&gt; 一样写非常多的样板代码。&lt;/p&gt;
&lt;p&gt;3、使组件更加颗粒化拆分。&lt;/p&gt;
&lt;h3&gt;缺点&lt;/h3&gt;
&lt;p&gt;1、过于自由，&lt;code&gt;MobX&lt;/code&gt; 提供的约定及模版代码很少，如果团队不做一些约定，容易导致团队代码风格不统一。&lt;/p&gt;
&lt;p&gt;2、可拓展，可维护性，也许你会担心 &lt;code&gt;Mobx&lt;/code&gt; 能不能适应后期项目发展壮大呢？确实 &lt;code&gt;Mobx&lt;/code&gt; 更适合用在中小型项目中，但这并不表示其不能支撑大型项目，关键在于大型项目通常需要特别注意可拓展性，可维护性，相比而言，规范的 &lt;code&gt;Redux&lt;/code&gt; 更有优势，而 &lt;code&gt;Mobx&lt;/code&gt; 更自由，需要我们自己制定一些规则来确保项目后期拓展，维护难易程度；&lt;/p&gt;
&lt;h2&gt;案例&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xuya227939/lucian&quot;&gt;Redux 项目模板&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xuya227939/tristana&quot;&gt;MobX 项目模板&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;对于 &lt;code&gt;Redux&lt;/code&gt; 更规范，更靠谱，应该使用 &lt;code&gt;Redux&lt;/code&gt; 或 &lt;code&gt;Redux&lt;/code&gt; 模版太多，太复杂了，应该选择 &lt;code&gt;Mobx&lt;/code&gt; 这类推断，我们都应该避免，也应该要避免这些，这些都是相对而言，每个框架和库都有各自的实现，特色，及其适用场景，正如 Redux 流程更复杂，但熟悉流程后就更能把握它的一些基础／核心理念，使用起来可能更有心得及感悟；而 &lt;code&gt;Mobx&lt;/code&gt; 简单化，把大部分东西隐藏起来，如果不去特别研究就不能接触到它的核心／基本思想，也许使得开发者一直停留在使用层次。&lt;/p&gt;
&lt;p&gt;所以无论是技术栈还是框架类库，并没有绝对的比较我们就应该选择什么，抛弃什么，我们应该更关注它们解决什么问题，它们解决问题的关注点，或者说实现方式是什么，它们的优缺点还有什么，哪一个更适合当前项目，以及项目未来发展。&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;p&gt;1、&lt;a href=&quot;https://juejin.im/post/6844903562095362056&quot;&gt;你需要 Mobx 还是 Redux？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2、&lt;a href=&quot;https://cn.mobx.js.org/&quot;&gt;MobX&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;3、&lt;a href=&quot;https://react-1251415695.cos-website.ap-chengdu.myqcloud.com/&quot;&gt;React&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;4、&lt;a href=&quot;https://www.redux.org.cn/&quot;&gt;Redux&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;博客&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;欢迎关注我的博客&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Redux</category><category>Mobx</category><author>江辰</author></item><item><title>Decorating class property failed</title><link>https://github.com/posts/decorating-class-property-failed/</link><guid isPermaLink="true">https://github.com/posts/decorating-class-property-failed/</guid><pubDate>Tue, 01 Oct 2019 20:21:33 GMT</pubDate><content:encoded>&lt;h2&gt;How to fix&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;export default new DashBoardStore();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;update is&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default DashBoardStore();
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>mobx</category><author>江辰</author></item><item><title>关于 TRTC (实时音视频通话模式)的实践</title><link>https://github.com/posts/%E5%85%B3%E4%BA%8E-trtc-%E5%AE%9E%E6%97%B6%E9%9F%B3%E8%A7%86%E9%A2%91%E9%80%9A%E8%AF%9D%E6%A8%A1%E5%BC%8F%E7%9A%84%E5%AE%9E%E8%B7%B5/</link><guid isPermaLink="true">https://github.com/posts/%E5%85%B3%E4%BA%8E-trtc-%E5%AE%9E%E6%97%B6%E9%9F%B3%E8%A7%86%E9%A2%91%E9%80%9A%E8%AF%9D%E6%A8%A1%E5%BC%8F%E7%9A%84%E5%AE%9E%E8%B7%B5/</guid><pubDate>Tue, 17 Sep 2019 13:49:35 GMT</pubDate><content:encoded>&lt;p&gt;本文首发于微信公众号：野生程序猿江辰&lt;/p&gt;
&lt;p&gt;欢迎大家点赞，收藏，关注&lt;/p&gt;
&lt;h2&gt;什么是 TRTC&lt;/h2&gt;
&lt;p&gt;腾讯实时音视频（Tencent Real-Time Communication，TRTC）将腾讯 21 年来在网络与音视频技术上的深度积累，以多人音视频通话和低延时互动直播两大场景化方案，通过腾讯云服务向开发者开放，致力于帮助开发者快速搭建低成本、低延时、高品质的音视频互动解决方案。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;TRTC 流程图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.mdnice.com/user/27515/6f70c232-65d3-4a95-a218-28fdabbc5734.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;加入房间&lt;/h2&gt;
&lt;h3&gt;创建流&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;this.client = TRTC.createClient({
    mode: &apos;videoCall&apos;,
    sdkAppId,
    userId,
    userSig
});
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;mode: 实时音视频通话模式，设置为&lt;code&gt;‘videoCall’&lt;/code&gt;，互动直播模式，设置为 &lt;code&gt;&apos;live&apos;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;sdkAppId: 您从腾讯云申请的 &lt;code&gt;sdkAppId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;userId: 用户 ID，随机生成，一个房间内不允许重复的 &lt;code&gt;userId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;userSig: 用户签名，基于后台算法生成，防盗刷&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;加入&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;this.client
    .join({ roomId })
    .catch(error =&amp;gt; {
        console.error(&apos;进房失败 &apos; + error);
    })
    .then(() =&amp;gt; {
        console.log(&apos;进房成功&apos;);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;roomId：后台生成的房间 Id，不能重复&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;发布本地流&lt;/h2&gt;
&lt;h3&gt;本地推流&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;this.localStream = TRTC.createStream({ userId: this.userId, audio: true, video: true });
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;userId: 用户 ID，随机生成，一个房间内不允许重复的 &lt;code&gt;userId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;audio: 是否从麦克风采集音频&lt;/li&gt;
&lt;li&gt;video: 是否从摄像头采集视频&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;初始化本地音视频流&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;this.localStream
    .initialize()
    .catch(err =&amp;gt; {
        console.error(&apos;初始化本地流失败 &apos; + error);
    })
    .then((res) =&amp;gt; {
        console.log(&apos;初始化本地流成功&apos;);
        this.localStream.play(&apos;localVideo&apos;);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;localVideo: 绑定的 &lt;code&gt;div id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;发布&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;this.client
    .publish(this.localStream)
    .catch(err =&amp;gt; {
        console.error(&apos;本地流发布失败 &apos; + error);
    })
    .then((res) =&amp;gt; {
        console.log(&apos;本地流发布成功&apos;);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;本地流发布成功之后，可以注册本地推流函数，每三秒执行一次，处理异常情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;订阅远端流&lt;/h2&gt;
&lt;h3&gt;远端流增加&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;this.client.on(&apos;stream-added&apos;, event =&amp;gt; {
    this.remoteStream = event.stream;
    //订阅远端流
    this.client.subscribe(this.remoteStream);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;远端流订阅&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;this.client.on(&apos;stream-subscribed&apos;, event =&amp;gt; {
    console.log(&apos;log&apos;, &apos;onRemoteStreamUpdate：&apos; + event);
    this.remoteStream = event.stream;
    this.id = this.remoteStream.getId();
    const remoteVideoDom = document.querySelector(&apos;#remoteVideo&apos;);
    if(!document.querySelector(`#remoteStream-${this.id}`)) {
        const div = document.createElement(&apos;div&apos;);
        div.setAttribute(&apos;style&apos;, &apos;position: absolute; right: 0; left: 0; top: 0; width: 100%; height: 100%&apos;);
        div.setAttribute(&apos;id&apos;, `remoteStream-${this.id}`);
        remoteVideoDom.appendChild(div);
    }
    const videoLoading = document.querySelector(&apos;#video-loading&apos;);
    videoLoading.setAttribute(&apos;style&apos;, &apos;display: none;&apos;);
    // 播放远端流
    this.remoteStream.play(`remoteStream-${this.id}`);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以在远端流监听成功之后，注册远端流状态变化函数，处理异常情况。&lt;/p&gt;
&lt;h2&gt;退出&lt;/h2&gt;
&lt;h3&gt;取消发布本地流&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;this.client.unpublish(this.localStream)
    .catch((err) =&amp;gt; {
        console.log(&apos;error&apos;, &apos;unpublish error：&apos; + err);
    })
    .then((res) =&amp;gt; {
        // 取消发布本地流成功
        console.log(&apos;log&apos;, &apos;unpublish error：&apos; + res);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;退出房间&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;this.client.leave();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;异常处理&lt;/h2&gt;
&lt;h3&gt;本地流监听&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 每隔3秒获取本地推流情况
this.localTimer = setInterval(() =&amp;gt; {
    this.client.getLocalVideoStats().then(stats =&amp;gt; {
        for (let userId in stats) {
            console.log(new Date(), &apos;getLocalVideoStats&apos;, &apos;userId: &apos; + userId +
        &apos;bytesSent: &apos; + stats[userId].bytesSent + &apos;local userId&apos; + this.userId);
            if(this.userId == userId &amp;amp;&amp;amp; stats[userId].bytesSent == 0) {
                this.onEvent(&apos;leave&apos;);
            }

            const bytesSentSR = (stats[userId].bytesSent - this.bytesSent) / 3000;

            if(this.userId == userId &amp;amp;&amp;amp; bytesSentSR &amp;gt;= 20 &amp;amp;&amp;amp; bytesSentSR &amp;lt;= 59) {

            }

            if(this.userId == userId) {
                this.bytesSent =  stats[userId].bytesSent;
            }
        }
    });
}, 3000);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;可在本地流发布成功后，注册本地推流变化函数，处理异常情况&lt;/li&gt;
&lt;li&gt;bytesSent: 如果发送单位为 &lt;code&gt;0&lt;/code&gt;，则表示本地断网&lt;/li&gt;
&lt;li&gt;公式: 目前发送字节数 - 上一次发送字节数 / 3000&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;远端流监听&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;this.remoteTimer = setInterval(() =&amp;gt; {
    this.client.getRemoteVideoStats().then(stats =&amp;gt; {
        for (let userId in stats) {
            console.log(&apos;getRemoteVideoStats&apos;, &apos;userId: &apos; + userId +
        &apos; bytesReceived: &apos; + stats[userId].bytesReceived +
        &apos; packetsReceived: &apos; + stats[userId].packetsReceived +
        &apos; packetsLost: &apos; + stats[userId].packetsLost);
        }
    });
}, 3000);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;bytesReceived: 如果接受单位为 0，则表示对方断网&lt;/li&gt;
&lt;li&gt;可在远端流监听成功之后，注册远端流状态变化函数，处理异常情况&lt;/li&gt;
&lt;li&gt;公式: 目前接收字节数 - 上一次接收字节数 / 3000&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目前通过 TRTC 的事件通知，搭配 &lt;code&gt;Socket&lt;/code&gt;，能做到对异常处理有较好的支持。&lt;/p&gt;
&lt;h2&gt;TRTC 兼容性&lt;/h2&gt;
&lt;h3&gt;Android(H5)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;摄像头不匹配，比如，华为手机三个后置加一个前置，调用 &lt;code&gt;TRTC&lt;/code&gt; 的获取摄像头接口，返回的却是 &lt;strong&gt;6&lt;/strong&gt; 个，并且没有 &lt;code&gt;Label&lt;/code&gt; 标注那个是后置，那个是前置，厂商问题，需要特殊适配。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;必须使用微信游览器打开 &lt;code&gt;H5&lt;/code&gt; 页面，其他游览器会偶尔崩溃以及其他问题(猜测微信游览器做了适配)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;华为 P30 部分机型，存在微信游览器环境下没有默认打开腾讯 &lt;code&gt;X5&lt;/code&gt; 内核，需要进行特殊处理。打开方案：1、可以在手机设置、应用管理、微信、麦克风和摄像头权限重新开启。2、通过扫描 &lt;code&gt;X5&lt;/code&gt; 内核开启二维码，引导开启。否则会发布流失败，因为 &lt;code&gt;X5&lt;/code&gt; 内核关闭，导致没有权限获取。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;TRTC&lt;/code&gt; 对大部分机型能够有较好的支持。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;iOS(H5)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;必须使用 &lt;code&gt;Safari&lt;/code&gt; 游览器，其他游览器会出现各种问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;需要用户手动触发播放，这时候需要在 &lt;code&gt;video&lt;/code&gt; 组件上加上 &lt;code&gt;autoplay&lt;/code&gt;、&lt;code&gt;muted&lt;/code&gt;、&lt;code&gt;playsinline&lt;/code&gt;、&lt;code&gt;controls&lt;/code&gt;(SDK，4.0.0 版本以下)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Video
    id=&quot;remoteVideo&quot;
    autoplay
    muted
    playsinline
    controls
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;切换前后置摄像头需要根据 &lt;code&gt;Label&lt;/code&gt; 标签进行区分，获取前后置摄像头的 &lt;code&gt;deviceId&lt;/code&gt;，切换流程如下:&lt;/p&gt;
&lt;p&gt;1、获取摄像头&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    TRTC.getCameras().then(devices =&amp;gt; {
        this.cameras = devices;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、选择摄像头&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;this.localStream.switchDevice(&apos;video&apos;, deviceId)
    .catch(err =&amp;gt; {
        console.log(&apos;error&apos;, &apos;switchDevice error：&apos; + err);
    })
    .then((res) =&amp;gt; {
        console.log(&apos;log&apos;, &apos;switchDevice success&apos; + res);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;小程序&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;React&lt;/code&gt; 技术栈(我只使用了 &lt;code&gt;Taro&lt;/code&gt;)能够支持视频播放，但推荐更好的 &lt;code&gt;Vue&lt;/code&gt; 技术栈，因为 &lt;code&gt;Vue&lt;/code&gt; 有官方封装的组件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;手机兼容性比较好，微信环境加持。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;云端混流&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;request({
    url: `http://fcgi.video.qcloud.com/common_access?appid=${liveSign.appId}&amp;amp;interface=Mix_StreamV2&amp;amp;t=${liveSign.t}&amp;amp;sign=${liveSign.liveSign}`,
    method: &apos;POST&apos;,
    headers: {
        &apos;content-type&apos;: &apos;application/json&apos;,
    },
    body: JSON.stringify(params)
}, (error, response, body) =&amp;gt; {
    res.send({errCode: 0});
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过 &lt;code&gt;http://fcgi.video.qcloud.com/common_access&lt;/code&gt; 接口，我们能够完美的监听房间内发生的情况，录制好的视频，会上传到腾讯的云点播平台，同时也支持客户自行导出。&lt;/p&gt;
</content:encoded><category>TRTC</category><author>江辰</author></item><item><title>JS 数组扁平化之简单方法实现</title><link>https://github.com/posts/js-%E6%95%B0%E7%BB%84%E6%89%81%E5%B9%B3%E5%8C%96%E4%B9%8B%E7%AE%80%E5%8D%95%E6%96%B9%E6%B3%95%E5%AE%9E%E7%8E%B0/</link><guid isPermaLink="true">https://github.com/posts/js-%E6%95%B0%E7%BB%84%E6%89%81%E5%B9%B3%E5%8C%96%E4%B9%8B%E7%AE%80%E5%8D%95%E6%96%B9%E6%B3%95%E5%AE%9E%E7%8E%B0/</guid><pubDate>Tue, 10 Sep 2019 10:14:20 GMT</pubDate><content:encoded>&lt;h2&gt;什么是扁平化&lt;/h2&gt;
&lt;p&gt;一句话解释，数组扁平化是指将一个多维数组(含嵌套)变为一维数组&lt;/p&gt;
&lt;h2&gt;扁平化之 ES5&lt;/h2&gt;
&lt;h3&gt;toString&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const arr = [1, 2, 3, [4, 5, [6, 7]]];

const flatten = arr.toString().split(&apos;,&apos;);

console.log(flatten);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：简单，方便，对原数据没有影响&lt;/p&gt;
&lt;p&gt;缺点：最好数组元素全是数字或字符，不会跳过空位&lt;/p&gt;
&lt;h3&gt;join&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const arr = [1, 2, 3, [4, 5, [6, 7]]];

const flatten = arr.join(&apos;,&apos;).split(&apos;,&apos;);

console.log(flatten);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点和缺点同 toString&lt;/p&gt;
&lt;h2&gt;扁平化之 ES6&lt;/h2&gt;
&lt;h3&gt;flat&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const arr = [1, 2, 3, [4, 5, [6, 7]]];

const flatten = arr.flat(Infinity);

console.log(flatten);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：会跳过空位，返回新数组，不会修改原数组。&lt;/p&gt;
&lt;p&gt;缺点：无&lt;/p&gt;
&lt;h3&gt;扩展运算符(...)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const arr = [1, 2, 3, [4, 5]];

console.log([].concat(...arr));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：简单，方便&lt;/p&gt;
&lt;p&gt;缺点：只能扁平化一层&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;推荐使用 &lt;code&gt;ES6&lt;/code&gt; 的 &lt;code&gt;flat&lt;/code&gt; 方法&lt;/p&gt;
&lt;h2&gt;博客&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;欢迎关注我的博客&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>JS</category><author>江辰</author></item><item><title>Node.js 爬虫获取网页内容乱码</title><link>https://github.com/posts/nodejs-%E7%88%AC%E8%99%AB%E8%8E%B7%E5%8F%96%E7%BD%91%E9%A1%B5%E5%86%85%E5%AE%B9%E4%B9%B1%E7%A0%81/</link><guid isPermaLink="true">https://github.com/posts/nodejs-%E7%88%AC%E8%99%AB%E8%8E%B7%E5%8F%96%E7%BD%91%E9%A1%B5%E5%86%85%E5%AE%B9%E4%B9%B1%E7%A0%81/</guid><pubDate>Thu, 08 Aug 2019 09:47:41 GMT</pubDate><content:encoded>&lt;h2&gt;返回的 html 乱码&lt;/h2&gt;
&lt;p&gt;网页内容格式是 GBK 和头部用 gzip 压缩，设置属性&lt;code&gt;gzip: true&lt;/code&gt;和&lt;code&gt;encoding:null&lt;/code&gt;，再通过 iconv 转成 utf8&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install request
npm install iconv-lite
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;const request = require(&apos;request&apos;);
const iconv = require(&apos;iconv-lite&apos;);
const options = {
        url: `http://xxxx`,
        proxy: &apos;http://127.0.0.1:8888&apos;,
        secureProtocol: &apos;TLSv1_method&apos;,
        gzip: true,
        encoding: null
    };
request.get(options, function (err, response, data) {
        const result = iconv.decode(data, &apos;utf-8&apos;).toString();
        console.log(result);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;返回参数乱码&lt;/h2&gt;
&lt;p&gt;去掉 encoding 参数即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const request = require(&apos;request&apos;);
const iconv = require(&apos;iconv-lite&apos;);
const options = {
        url: `http://xxxx`,
        proxy: &apos;http://127.0.0.1:8888&apos;,
        secureProtocol: &apos;TLSv1_method&apos;,
        gzip: true
    };
request.get(options, function (err, response, data) {
        console.log(data.toString());
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果头部没有压缩过的，去掉 gzip 参数，然后再把返回的参数&lt;code&gt;data.toString()&lt;/code&gt;一下&lt;/p&gt;
</content:encoded><category>Node.js</category><author>江辰</author></item><item><title>EPROTO 3928:error:1408F10B:SSL routines:ssl3</title><link>https://github.com/posts/-error-write/</link><guid isPermaLink="true">https://github.com/posts/-error-write/</guid><pubDate>Fri, 26 Jul 2019 13:26:49 GMT</pubDate><content:encoded>&lt;h2&gt;How fix&lt;/h2&gt;
&lt;p&gt;node.js use request package and options add a secureProtocol is &apos;TLSv1_method&apos;&lt;/p&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;const request = require(&apos;request&apos;);
const options = {
    url: &apos;https://10.134.136.112:8888/casserver/login?service=http%3A%2F%2F10.134.137.120%3A8000%2Fpiccclaim%2Fj_acegi_security_check%3BPICC_CLAIM_Cookie%3DWTXhd5pQY4SwQJpdKGxMQhXvl0L4Qp7pJhPrprm0ptmCqlW7JHkS%21566150954%21-472537668&apos;,
    // proxy: &apos;http://127.0.0.1:8888&apos;
    secureProtocol: &apos;TLSv1_method&apos;
};
request.get(options, function (err, response, data) {
    console.log(data, err, response);
});
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Node.js</category><author>江辰</author></item><item><title>JS 语法规范</title><link>https://github.com/posts/js-%E8%AF%AD%E6%B3%95%E8%A7%84%E8%8C%83/</link><guid isPermaLink="true">https://github.com/posts/js-%E8%AF%AD%E6%B3%95%E8%A7%84%E8%8C%83/</guid><pubDate>Tue, 23 Jul 2019 10:59:08 GMT</pubDate><content:encoded>&lt;h2&gt;采用小写驼峰式命名&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
studentInfo

// bad
studentinfo
STUDENTINFO
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常量命名方式&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
const COL_NUM = 10;

// bad
const col_num = 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用字面量&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
const obj = {
    name:&apos;faker&apos;
}

// bad
let obj = {};
obj.name = &apos;faker&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;函数参数使用解构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
function createPerson({ name, age }) {
    // ...
}
createPerson({
    name: &apos;Faker&apos;,
    age: 18,
});

// bad
function createPerson(name, age) {
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用参数默认值&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
function createMicrobrewery(name = &apos;faker&apos;) {
    // ...
}

// bad
function createMicrobrewery(name) {
    const breweryName = name || &apos;faker&apos;;
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;函数式编程&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
const programmerOutput = [
    {
        name: &apos;Uncle Bobby&apos;,
        linesOfCode: 500
    }, {
        name: &apos;Suzie Q&apos;,
        linesOfCode: 1500
    }, {
        name: &apos;Jimmy Gosling&apos;,
        linesOfCode: 150
    }, {
        name: &apos;Gracie Hopper&apos;,
        linesOfCode: 1000
    }
];
let totalOutput = programmerOutput
  .map(output =&amp;gt; output.linesOfCode)
  .reduce((totalLines, lines) =&amp;gt; totalLines + lines, 0)

// bad
const programmerOutput = [
    {
        name: &apos;Uncle Bobby&apos;,
        linesOfCode: 500
    }, {
        name: &apos;Suzie Q&apos;,
        linesOfCode: 1500
    }, {
        name: &apos;Jimmy Gosling&apos;,
        linesOfCode: 150
    }, {
        name: &apos;Gracie Hopper&apos;,
        linesOfCode: 1000
    }
];

let totalOutput = 0;

for (let i = 0; i &amp;lt; programmerOutput.length; i++) {
    totalOutput += programmerOutput[i].linesOfCode;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;缩进&lt;/h2&gt;
&lt;p&gt;统一使用一个 &lt;code&gt;tab&lt;/code&gt; 作为缩进&lt;/p&gt;
&lt;h2&gt;空格&lt;/h2&gt;
&lt;p&gt;二元运算符两侧必须有一个空格，一元运算符与操作对象之间不允许有空格。
用作代码块起始的左花括号 { 前必须有一个空格。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// good
var a = !arr.length;
a++;
a = b + c;

// good
if (condition) {
}

while (condition) {
}

function funcName() {
}

// bad
if (condition){
}

while (condition){
}

function funcName(){
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;禁止使用 var，使用 let、const 代替&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
let a = 123;

// bad
var a = 123;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;JS 中使用单引号&apos;，在 DOM 中使用双引号&quot;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
const str = &apos;我是一个字符串&apos;;
&amp;lt;div className=&quot;div&quot; /&amp;gt;

// bad
const str = &quot;我是一个字符串&quot;;
&amp;lt;div className=&apos;div&apos; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用模板字符拼接字符串``&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
const name = &apos;faker&apos;;
const str = `我叫${a}`;

// bad
const name = &apos;faker&apos;;
const str = &apos;我叫&apos; + a;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;变量命名语义化&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
const student = &apos;faker&apos;;

// bad
const a = &apos;faker&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;注释&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;单行注释：必须独占一行。&lt;code&gt;//&lt;/code&gt; 后跟一个空格，缩进与下一行被注释说明的代码一致&lt;/li&gt;
&lt;li&gt;多行注释：避免使用 &lt;code&gt;/_..._/&lt;/code&gt; 这样的多行注释。有多行注释内容时，使用多个单行注释&lt;/li&gt;
&lt;li&gt;文档化注释：为了便于代码阅读和自文档化，以下内容必须包含以 &lt;code&gt;/\*_..._/&lt;/code&gt; 形式的块注释中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;每个 JS 文件在头部需要给出该页面的信息&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
/*
 * 充值记录页面
 * @Author: Jiang
 * @Date: 2019-06-12 15:21:19
 * @Last Modified by: Jiang
 * @Last Modified time: 2024-04-27 15:42:23
*/

// bad
无任何注释
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;不要省略分号&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// good
const student = &apos;faker&apos;;

// bad
const student = &apos;faker&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;博客&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xuya227939/LiuJiang-Blog&quot;&gt;欢迎关注我的博客&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>JS</category><author>江辰</author></item><item><title>CircleCI 自动化部署</title><link>https://github.com/posts/circleci-%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2/</link><guid isPermaLink="true">https://github.com/posts/circleci-%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2/</guid><pubDate>Mon, 08 Jul 2019 21:13:56 GMT</pubDate><content:encoded>&lt;h2&gt;Curl 请求如何设置&lt;/h2&gt;
&lt;h3&gt;终端设置代理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ export https_proxy=http://127.0.0.1:8888
$ export http_proxy=http://127.0.0.1:8888
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;取消终端代理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ unset http_proxy
$ unset https_proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Charles Proxy 设置&lt;/h3&gt;
&lt;p&gt;输入端口&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/60028211-85e1a680-96d1-11e9-96c0-fa3315b78f49.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;curl 发起请求&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST 127.0.0.1:8888 -H &quot;Content-Type: application/x-www-form-urlencoded&quot; -i https://ip:8118/mcpinterf/user/login -d &quot;userId=45729907&amp;amp;imeiNo=99001123927671&amp;amp;simNo=89860315540101069256&amp;amp;pwd=eb4UecxU3vg9fgszuQqk7w..&amp;amp;loginFlag=0&amp;amp;timestamp=1560829091595&quot; -k
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;End&lt;/h3&gt;
&lt;p&gt;然后在 Charles 界面即可看到&lt;/p&gt;
&lt;h2&gt;Postman 如何设置&lt;/h2&gt;
&lt;p&gt;打开 Postman 设置界面，然后进行代理设置，如下图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/60029480-ce9a5f00-96d3-11e9-8de8-7d23a1f5ff78.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
</content:encoded><category>CircleCI</category><author>江辰</author></item><item><title>使用 react.lazy 打包之后得文件如何不显示数字</title><link>https://github.com/posts/%E4%BD%BF%E7%94%A8-reactlazy-%E6%89%93%E5%8C%85%E4%B9%8B%E5%90%8E%E5%BE%97%E6%96%87%E4%BB%B6%E5%A6%82%E4%BD%95%E4%B8%8D%E6%98%BE%E7%A4%BA%E6%95%B0%E5%AD%97/</link><guid isPermaLink="true">https://github.com/posts/%E4%BD%BF%E7%94%A8-reactlazy-%E6%89%93%E5%8C%85%E4%B9%8B%E5%90%8E%E5%BE%97%E6%96%87%E4%BB%B6%E5%A6%82%E4%BD%95%E4%B8%8D%E6%98%BE%E7%A4%BA%E6%95%B0%E5%AD%97/</guid><pubDate>Fri, 28 Jun 2019 13:17:10 GMT</pubDate><content:encoded>&lt;h2&gt;解决方法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/* webpackChunkName: &quot;name&quot;*/&apos; &apos;文件路径&apos;
const Chart = lazy(() =&amp;gt; import(/* webpackChunkName: &quot;chart&quot;*/&apos;./pages/Chart/index&apos;));
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>Charles 如何扑获 curl 和 Postman 请求</title><link>https://github.com/posts/charles-%E5%A6%82%E4%BD%95%E6%89%91%E8%8E%B7-curl-%E5%92%8C-postman-%E8%AF%B7%E6%B1%82/</link><guid isPermaLink="true">https://github.com/posts/charles-%E5%A6%82%E4%BD%95%E6%89%91%E8%8E%B7-curl-%E5%92%8C-postman-%E8%AF%B7%E6%B1%82/</guid><pubDate>Mon, 24 Jun 2019 22:44:56 GMT</pubDate><content:encoded>&lt;h2&gt;Curl 请求如何设置&lt;/h2&gt;
&lt;h3&gt;终端设置代理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ export https_proxy=http://127.0.0.1:8888
$ export http_proxy=http://127.0.0.1:8888
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;取消终端代理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ unset http_proxy
$ unset https_proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Charles Proxy 设置&lt;/h3&gt;
&lt;p&gt;输入端口&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/60028211-85e1a680-96d1-11e9-96c0-fa3315b78f49.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;curl 发起请求&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST 127.0.0.1:8888 -H &quot;Content-Type: application/x-www-form-urlencoded&quot; -i https://ip:8118/mcpinterf/user/login -d &quot;userId=45729907&amp;amp;imeiNo=99001123927671&amp;amp;simNo=89860315540101069256&amp;amp;pwd=eb4UecxU3vg9fgszuQqk7w..&amp;amp;loginFlag=0&amp;amp;timestamp=1560829091595&quot; -k
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;End&lt;/h3&gt;
&lt;p&gt;然后在 Charles 界面即可看到&lt;/p&gt;
&lt;h2&gt;Postman 如何设置&lt;/h2&gt;
&lt;p&gt;打开 Postman 设置界面，然后进行代理设置，如下图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/60029480-ce9a5f00-96d3-11e9-8de8-7d23a1f5ff78.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>16进制和字符串互转</title><link>https://github.com/posts/16%E8%BF%9B%E5%88%B6%E5%92%8C%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BA%92%E8%BD%AC/</link><guid isPermaLink="true">https://github.com/posts/16%E8%BF%9B%E5%88%B6%E5%92%8C%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BA%92%E8%BD%AC/</guid><pubDate>Mon, 24 Jun 2019 16:21:20 GMT</pubDate><content:encoded>&lt;h3&gt;字符串转 16 进制&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function strToHexCharCode(str) {
    if(str === &apos;&apos;) return &apos;&apos;;
    let hexCharCode = [];
    hexCharCode.push(&apos;0x&apos;);
    for(var i = 0; i &amp;lt; str.length; i++) {
        hexCharCode.push((str.charCodeAt(i)).toString(16));
    }
    return hexCharCode.join(&apos;&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;16 进制转字符串&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function hexCharCodeToStr(hexCharCodeStr) {
    const trimedStr = hexCharCodeStr.trim();
    const rawStr = trimedStr.substr(0, 2).toLowerCase() === &apos;0x&apos; ? trimedStr.substr(2) : trimedStr;
    const len = rawStr.length;
    if (len % 2 !== 0) {
        throw(&quot;Illegal Format ASCII Code!&quot;);
    }
    let curCharCode;
    let resultStr = [];
    for (let i = 0; i &amp;lt; len; i = i + 2) {
        curCharCode = parseInt(rawStr.substr(i, 2), 16);
        resultStr.push(String.fromCharCode(curCharCode));
    }
    return resultStr.join(&quot;&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>Eslint for React</title><link>https://github.com/posts/eslint-for-react/</link><guid isPermaLink="true">https://github.com/posts/eslint-for-react/</guid><pubDate>Thu, 13 Jun 2019 18:22:14 GMT</pubDate><content:encoded>&lt;h1&gt;React 脚手架配置 eslint&lt;/h1&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install -g eslint
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;.eslintrc.json&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;extends&quot;: &quot;eslint:recommended&quot;,
    &quot;env&quot;: {
        &quot;browser&quot;: true,
        &quot;commonjs&quot;: true,
        &quot;es6&quot;: true
    },
    &quot;globals&quot;: {
        &quot;$&quot;: true,
        &quot;process&quot;: true,
        &quot;__dirname&quot;: true
    },
    &quot;parser&quot;: &quot;babel-eslint&quot;,
    &quot;parserOptions&quot;: {
        &quot;ecmaFeatures&quot;: {
            &quot;jsx&quot;: true,
            &quot;modules&quot;: true
        },
        &quot;sourceType&quot;: &quot;module&quot;,
        &quot;ecmaVersion&quot;: 6
    },
    &quot;plugins&quot;: [
        &quot;react&quot;,
        &quot;standard&quot;,
        &quot;promise&quot;
    ],
    &quot;rules&quot;: {
        &quot;quotes&quot;: [
            2,
            &quot;single&quot;
        ],
        &quot;no-console&quot;: 0,
        &quot;no-debugger&quot;: 1,
        &quot;no-var&quot;: 2,
        &quot;semi&quot;: 2,
        &quot;no-irregular-whitespace&quot;: 0,
        &quot;no-trailing-spaces&quot;: 0,
        &quot;eol-last&quot;: 0,
        &quot;no-unused-vars&quot;: [
            2,
            {
                &quot;vars&quot;: &quot;all&quot;,
                &quot;args&quot;: &quot;after-used&quot;
            }
        ],
        &quot;no-case-declarations&quot;: 0,
        &quot;no-underscore-dangle&quot;: 0,
        &quot;no-alert&quot;: 2,
        &quot;no-lone-blocks&quot;: 0,
        &quot;no-class-assign&quot;: 2,
        &quot;no-cond-assign&quot;: 2,
        &quot;no-const-assign&quot;: 2,
        &quot;no-delete-var&quot;: 2,
        &quot;no-dupe-keys&quot;: 0,
        &quot;no-duplicate-case&quot;: 2,
        &quot;no-dupe-args&quot;: 2,
        &quot;no-empty&quot;: 2,
        &quot;no-func-assign&quot;: 2,
        &quot;no-invalid-this&quot;: 0,
        &quot;no-redeclare&quot;: 2,
        &quot;no-spaced-func&quot;: 2,
        &quot;no-this-before-super&quot;: 0,
        &quot;no-undef&quot;: 2,
        &quot;no-return-assign&quot;: 0,
        &quot;no-script-url&quot;: 2,
        &quot;no-use-before-define&quot;: 2,
        &quot;jsx-quotes&quot;: [
            2,
            &quot;prefer-double&quot;
        ],
        &quot;react/display-name&quot;: 0,
        &quot;react/forbid-prop-types&quot;: [
            2,
            {
                &quot;forbid&quot;: [
                    &quot;any&quot;
                ]
            }
        ],
        &quot;react/jsx-boolean-value&quot;: 0,
        &quot;react/jsx-closing-bracket-location&quot;: 1,
        &quot;react/jsx-curly-spacing&quot;: [
            2,
            {
                &quot;when&quot;: &quot;never&quot;,
                &quot;children&quot;: true
            }
        ],
        &quot;indent&quot;: [&quot;error&quot;, 4, {&quot;SwitchCase&quot;: 1}],
        &quot;react/jsx-indent&quot;: [&quot;error&quot;, 4],
        &quot;react/jsx-key&quot;: 2,
        &quot;react/jsx-no-bind&quot;: 0,
        &quot;react/jsx-no-duplicate-props&quot;: 2,
        &quot;react/jsx-no-literals&quot;: 0,
        &quot;react/jsx-no-undef&quot;: 1,
        &quot;react/jsx-pascal-case&quot;: 0,
        &quot;react/jsx-sort-props&quot;: 0,
        &quot;react/jsx-uses-react&quot;: 1,
        &quot;react/jsx-uses-vars&quot;: 2,
        &quot;react/no-danger&quot;: 0,
        &quot;react/no-did-mount-set-state&quot;: 0,
        &quot;react/no-did-update-set-state&quot;: 0,
        &quot;react/no-direct-mutation-state&quot;: 2,
        &quot;react/no-multi-comp&quot;: 0,
        &quot;react/no-set-state&quot;: 0,
        &quot;react/no-unknown-property&quot;: 2,
        &quot;react/prefer-es6-class&quot;: 2,
        &quot;react/prop-types&quot;: 0,
        &quot;react/react-in-jsx-scope&quot;: 2,
        &quot;react/self-closing-comp&quot;: 0,
        &quot;react/sort-comp&quot;: 0,
        &quot;no-extra-boolean-cast&quot;: 0,
        &quot;react/no-array-index-key&quot;: 0,
        &quot;react/no-deprecated&quot;: 1,
        &quot;react/jsx-equals-spacing&quot;: 2,
        &quot;no-unreachable&quot;: 1,
        &quot;comma-dangle&quot;: 2,
        &quot;no-mixed-spaces-and-tabs&quot;: 2,
        &quot;prefer-arrow-callback&quot;: 0,
        &quot;arrow-parens&quot;: 0,
        &quot;arrow-spacing&quot;: 0,
        &quot;camelcase&quot;: 0
    },
    &quot;settings&quot;: {
        &quot;import/ignore&quot;: [
            &quot;node_modules&quot;
        ],
        &quot;react&quot;: {
            &quot;version&quot;: &quot;latest&quot;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Eslint</category><author>江辰</author></item><item><title>使用 Sourcetree 提示需要输入密码</title><link>https://github.com/posts/%E4%BD%BF%E7%94%A8-sourcetree-%E6%8F%90%E7%A4%BA%E9%9C%80%E8%A6%81%E8%BE%93%E5%85%A5%E5%AF%86%E7%A0%81/</link><guid isPermaLink="true">https://github.com/posts/%E4%BD%BF%E7%94%A8-sourcetree-%E6%8F%90%E7%A4%BA%E9%9C%80%E8%A6%81%E8%BE%93%E5%85%A5%E5%AF%86%E7%A0%81/</guid><pubDate>Mon, 10 Jun 2019 11:30:47 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/59170575-e3261580-8b71-11e9-8610-0c9679a1ef54.png&quot; alt=&quot;image&quot; /&gt;
使用公司&lt;code&gt;GitLab&lt;/code&gt;提交的时候，每次都需要输入密码&lt;/p&gt;
&lt;h2&gt;解决方案(mac)&lt;/h2&gt;
&lt;h3&gt;命令行解决&lt;/h3&gt;
&lt;p&gt;输入以下命令
&lt;code&gt;git config --global credential.helper osxkeychain&lt;/code&gt;
执行完成后，再次在 SourceTree 里面输入一下 GitLab 里面的密码。注意勾选选项“store password in keychain”。
这个时候，会跳出钥匙串的对话框，这个时候要输入的密码，是 mac 的开机密码。并且一定要勾选始终允许。否则，还是要一直跳出现在的这个登陆窗口了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/59170661-41eb8f00-8b72-11e9-89bf-d9db9f437f9b.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;还有一种就是 Keychain 中产生了冲突，使勾选失效&lt;/h3&gt;
&lt;p&gt;将已失效的 git.a. Access Key for git 删除，再次操作输入密码后新的密码就会存储在 Keychain，以后就不用每次远程操作都手动输入密码了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/59170836-f2f22980-8b72-11e9-9765-0cfbf1631e3b.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>Babel6 如何升级 7</title><link>https://github.com/posts/babel6-%E5%A6%82%E4%BD%95%E5%8D%87%E7%BA%A7-7/</link><guid isPermaLink="true">https://github.com/posts/babel6-%E5%A6%82%E4%BD%95%E5%8D%87%E7%BA%A7-7/</guid><pubDate>Wed, 05 Jun 2019 13:25:00 GMT</pubDate><content:encoded>&lt;h2&gt;Babel&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ npm install babel-upgrade -g
$ babel-upgrade --write
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后会发现 package.json 依赖包，自动给转换到了最新版。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/58931894-117fab80-8794-11e9-8dee-67a3abb6d498.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Babel7 新增了&lt;code&gt;babel.config.js&lt;/code&gt;，这里我没有用到，所以还是选择使用&lt;code&gt;.babelrc&lt;/code&gt;文件。
最终配置如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;presets&quot;: [
        [
            &quot;@babel/env&quot;,
            {
                &quot;targets&quot;: {
                    &quot;edge&quot;: &quot;17&quot;,
                    &quot;firefox&quot;: &quot;60&quot;,
                    &quot;chrome&quot;: &quot;67&quot;,
                    &quot;safari&quot;: &quot;11.1&quot;
                },
                &quot;useBuiltIns&quot;: &quot;usage&quot;
            }
        ],
        [
            &quot;@babel/preset-react&quot;,
        ],
    ],
    &quot;plugins&quot;: [
        [&quot;@babel/plugin-proposal-class-properties&quot;],
        [&quot;@babel/plugin-transform-runtime&quot;],
        [&quot;@babel/plugin-syntax-import-meta&quot;],
        [&quot;@babel/plugin-syntax-dynamic-import&quot;],
        [&quot;@babel/plugin-proposal-json-strings&quot;],
        [
            &quot;import&quot;, {
                &quot;libraryName&quot;: &quot;antd&quot;,
                &quot;libraryDirectory&quot;: &quot;es&quot;,
                &quot;style&quot;: &quot;css&quot;,
            }
        ],
    ]
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里引入了&lt;code&gt;Ant&lt;/code&gt;，解决&lt;code&gt;Ant&lt;/code&gt;按需加载&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
    &quot;import&quot;, {
        &quot;libraryName&quot;: &quot;antd&quot;,
        &quot;libraryDirectory&quot;: &quot;es&quot;,
        &quot;style&quot;: &quot;css&quot;,
    }
],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Webpack 配置如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module: {
    rules: [
        {
            test: /\.js|jsx$/,
            exclude: /(node_modules)/,
            loader: &apos;babel-loader&apos;,
        },
    ],
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;遇到的坑&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/58932163-2741a080-8795-11e9-85ef-984d2db54afb.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;无法识别&lt;code&gt;JSX&lt;/code&gt;语法，因为在&lt;code&gt;.babelrc&lt;/code&gt;文件中没有引入&lt;code&gt;@babel/preset-react&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;完美升级 babel7&lt;/p&gt;
</content:encoded><category>Babel</category><author>江辰</author></item><item><title>ERR_SSL_PROTOCOL_ERROR</title><link>https://github.com/posts/err_ssl_protocol_error/</link><guid isPermaLink="true">https://github.com/posts/err_ssl_protocol_error/</guid><pubDate>Sun, 12 May 2019 20:15:52 GMT</pubDate><content:encoded>&lt;p&gt;起因是因为阿里云机器快要到期了，然后重新买了台低配的机器，在上面跑我的服务。根据快照直接进行创建，发现新机器和老机器的数据一摸一样，这功能真舒服，根据自定义镜像创建机器。购买完毕，启动服务。然后就遇到了下面这个问题&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/57581972-d6ba8880-74f1-11e9-8cf3-c27d0fa62827.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;尝试 Google、百度解决方法，发现没有跟我一样的。&lt;/li&gt;
&lt;li&gt;尝试重新申请 SSL 证书和重新配置 Nginx SSL 也是一样。&lt;/li&gt;
&lt;li&gt;实在没辙，下午先放着了，晚上再解决。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到了晚上，查看了下 9000 端口占用情况，发现正常。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/57582015-5c3e3880-74f2-11e9-87e7-7df938115799.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;把 PM2 进程管理给停止，然后重新 npm start，发现可以正常访问了，刺激！&lt;/p&gt;
&lt;p&gt;然后怀疑跟 PM2 有关系，一直使用的是&lt;code&gt;pm2 start all&lt;/code&gt;以为开启了，才发现 all 其实是管理。&lt;/p&gt;
&lt;p&gt;需要重新&lt;code&gt;pm2 start ./bin/www --watch&lt;/code&gt; 一下才行。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/57582054-bb03b200-74f2-11e9-96c7-555ffdf86e15.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;还有可能就是证书无效了，重新配置下证书。&lt;/p&gt;
</content:encoded><category>ERR_SSL</category><author>江辰</author></item><item><title>VS Code 用户自定义配置推荐</title><link>https://github.com/posts/vs-code-%E7%94%A8%E6%88%B7%E8%87%AA%E5%AE%9A%E4%B9%89%E9%85%8D%E7%BD%AE%E6%8E%A8%E8%8D%90/</link><guid isPermaLink="true">https://github.com/posts/vs-code-%E7%94%A8%E6%88%B7%E8%87%AA%E5%AE%9A%E4%B9%89%E9%85%8D%E7%BD%AE%E6%8E%A8%E8%8D%90/</guid><pubDate>Tue, 07 May 2019 10:31:15 GMT</pubDate><content:encoded>&lt;h2&gt;settings.json&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;{
    // 启用或禁用在 VS Code 中重命名或移动文件时自动更新 import 语句的路径
    &quot;javascript.updateImportsOnFileMove.enabled&quot;: &quot;always&quot;,
    // 禁止vscode的默认制表符
    &quot;editor.detectIndentation&quot;: false,
    // 自动换行
    &quot;editor.wordWrap&quot;: &quot;on&quot;,
    // 字体大小
    &quot;editor.fontSize&quot;: 16,
    // 选中糟糕的-
    &quot;editor.wordSeparators&quot;: &quot;./\\()\&quot;&apos;:,.;&amp;lt;&amp;gt;~!@#$%^&amp;amp;*|+=[]{}`~?&quot;,
    // 启用后，将不会显示扩展程序建议的通知。
    &quot;extensions.ignoreRecommendations&quot;: true,
    // 关闭默认的js验证，js中使用flow或ts语法会报错，需要关闭
    &quot;javascript.validate.enable&quot;: false,
    &quot;fileheader.Author&quot;: &quot;Jiang&quot;,
    &quot;fileheader.LastModifiedBy&quot;: &quot;Jiang&quot;,
    &quot;files.associations&quot;: {
        &quot;*.wxml&quot;: &quot;xml&quot;,
        &quot;*.wxss&quot;: &quot;css&quot;,
        &quot;*.cjson&quot;: &quot;jsonc&quot;,
        &quot;*.wxs&quot;: &quot;javascript&quot;
    },
    &quot;eslint.trace.server&quot;: &quot;messages&quot;,
    &quot;eslint.validate&quot;: [
        &quot;javascript&quot;,
        &quot;javascriptreact&quot;
    ],
    &quot;typescript.tsdk&quot;: &quot;node_modules/typescript/lib&quot;,
    &quot;emmet.includeLanguages&quot;: {
        &quot;wxml&quot;: &quot;html&quot;
    },
    &quot;[json]&quot;: {
        &quot;editor.defaultFormatter&quot;: &quot;HookyQR.beautify&quot;
    },
    &quot;[css]&quot;: {
        &quot;editor.defaultFormatter&quot;: &quot;HookyQR.beautify&quot;
    },
    &quot;[html]&quot;: {
        &quot;editor.defaultFormatter&quot;: &quot;HookyQR.beautify&quot;
    },
    &quot;[less]&quot;: {
        &quot;editor.defaultFormatter&quot;: &quot;michelemelluso.code-beautifier&quot;
    },
    &quot;[javascript]&quot;: {
        &quot;editor.defaultFormatter&quot;: &quot;HookyQR.beautify&quot;
    },
    &quot;workbench.colorTheme&quot;: &quot;Monokai&quot;,
    &quot;workbench.iconTheme&quot;: &quot;vscode-icons&quot;,
    &quot;[javascriptreact]&quot;: {
        &quot;editor.defaultFormatter&quot;: &quot;vscode.typescript-language-features&quot;
    },
    &quot;[typescriptreact]&quot;: {
        &quot;editor.defaultFormatter&quot;: &quot;vscode.typescript-language-features&quot;
    },
    &quot;[jsonc]&quot;: {
        &quot;editor.defaultFormatter&quot;: &quot;vscode.json-language-features&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>VS Code</category><author>江辰</author></item><item><title>PhantomJS not found on PATH</title><link>https://github.com/posts/phantomjs-not-found-on-path/</link><guid isPermaLink="true">https://github.com/posts/phantomjs-not-found-on-path/</guid><pubDate>Mon, 06 May 2019 16:02:32 GMT</pubDate><content:encoded>&lt;p&gt;今天 Win 上进行&lt;code&gt;npm install&lt;/code&gt;的时候遇到一个问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PhantomJS not found on PATH
Downloading https://github.com/Medium/phantomjs/releases/download/v2.1.1/phantomjs-2.1.1-windows.zip
Saving to C:\Users\hezhi\AppData\Local\Temp\phantomjs\phantomjs-2.1.1-windows.zip
Receiving...
  [=---------------------------------------] 2%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个 phantomjs-2.1.1-windows.zip 包一直下载不了，原来是天朝的网给墙了。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://phantomjs.org/download.html&quot;&gt;http://phantomjs.org/download.html&lt;/a&gt; 通过这个网址进行下载对应的包&lt;/p&gt;
&lt;p&gt;把包放进此路径下&lt;code&gt;C:\Users\hezhi\AppData\Local\Temp\phantomjs\&lt;/code&gt;(Win)&lt;/p&gt;
&lt;p&gt;解压，然后复制 phantomjs 所在的路径&lt;code&gt;C:\Users\hezhi\AppData\Local\Temp\phantomjs\phantomjs-2.1.1-windows\phantomjs-2.1.1-windows\bin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;再设置环境变量，添加刚才复制的路径。
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/57213064-3ad3dd00-7018-11e9-81ad-f873201c364a.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
</content:encoded><category>PhantomJS</category><author>江辰</author></item><item><title>阿里云 OSS 如何通过 Node.js 上传图片</title><link>https://github.com/posts/%E9%98%BF%E9%87%8C%E4%BA%91-oss-%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87-nodejs-%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87/</link><guid isPermaLink="true">https://github.com/posts/%E9%98%BF%E9%87%8C%E4%BA%91-oss-%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87-nodejs-%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87/</guid><pubDate>Fri, 29 Mar 2019 11:45:46 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;const multiparty = require(&apos;multiparty&apos;);
require(&apos;../../conf/util.js&apos;);
require(&apos;../../conf/oss.js&apos;);
const fs = require(&apos;fs&apos;);
const fsE = require(&apos;fs-extra&apos;);

global.router.put(`${global.api}uploadAvatar`, function (req, res) {
    let datas = {};
    const {
        account,
        avatar
    } = req.session;
    if (!(fs.existsSync(global.location + account))) {
        fs.mkdir(global.location + account);
    }
    const form = new multiparty.Form({
        uploadDir: global.location + account
    });
    form.parse(req, function (err, fields, files) {
        let filesTmp = files.filedata;
        filepath = filesTmp[0].path;
        const img = filesTmp[0].path.split(&apos;/&apos;)[2];
        const result = put(filepath, account, img);
        result.then((value) =&amp;gt; {
            if (value.res.statusCode === 200) {
                let updateSql = &apos;UPDATE users SET avatar = ? WHERE account = ?;&apos;;
                let newAvatar = `//img.downfuture.com/${account}/${img}-avatar2`;
                req.getConnection(function (err, conn) {
                    conn.query(`${updateSql}`, [newAvatar, account], function (err, result) {
                        unlinkFile(account);
                        if (avatar.indexOf(&apos;user.png&apos;) == -1) {
                            let bucket = avatar.split(&apos;/&apos;)[3] + &apos;/&apos; + avatar.split(&apos;/&apos;)[4];
                            deleteFile(bucket.split(&apos;-&apos;)[0]);
                        }
                        req.session.avatar = newAvatar;
                        datas.avatar = newAvatar;
                        datas.code = 200;
                        res.json(datas);
                    });
                });
            } else {
                unlinkFile(account);
                // fix:错误回参不对，需要给到具体报错参数
                datas.avatar = &apos;//img.downfuture.com/images/user.png&apos;;
                datas.error = value;
                datas.code = 500;
                res.json(datas);
            }
        });
    });
});

function unlinkFile(account) {
    fsE.remove(global.location + account);
}

async function deleteFile(bucket) {
    try {
        const result = await ossClient.delete(bucket);
    } catch (err) {
        console.error(err);
    }
}

async function put(filepath, account, img) {
    try {
        let result = await ossClient.put(`${account}/${img}`, filepath);
        return result;
    } catch (err) {
        return err;
    }
}

module.exports = global.router;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>阿里云</category><author>江辰</author></item><item><title>JS 实现 deepCopy</title><link>https://github.com/posts/js-%E5%AE%9E%E7%8E%B0-deepcopy/</link><guid isPermaLink="true">https://github.com/posts/js-%E5%AE%9E%E7%8E%B0-deepcopy/</guid><pubDate>Tue, 12 Mar 2019 10:18:02 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;function getType(obj) {
    // 为啥不用typeof? typeof无法区分数组和对象
    if(Object.prototype.toString.call(obj) == &apos;[object Object]&apos;) {
        return &apos;Object&apos;;
    }

    if(Object.prototype.toString.call(obj) == &apos;[object Array]&apos;) {
        return &apos;Array&apos;;
    }
    return &apos;nomal&apos;;
};

function deepCopy(obj) {
    if (getType(obj) == &apos;nomal&apos;) {
        return obj;
    } else {
        var newObj = getType(obj) == &apos;Object&apos; ? {} : [];
        for(var key in obj) {
            // 为啥要用hasOwnProperty？不需要从对象的原型链上进行复制
            if(obj.hasOwnProperty(key)) {
                newObj[key] = deepCopy(obj[key]);
            }
        }
    }
    return newObj;
}


var object = [
  {
    title: &apos;test&apos;,
    checked: false
  }
];

deepCopy(object);
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>JS</category><author>江辰</author></item><item><title>Weex如何实现dialog</title><link>https://github.com/posts/weex%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0dialog/</link><guid isPermaLink="true">https://github.com/posts/weex%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0dialog/</guid><pubDate>Sat, 09 Mar 2019 08:41:04 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&quot;modal-wrapper&quot; v-if=&quot;show&quot;&amp;gt;
        &amp;lt;div class=&quot;modal-mask&quot; /&amp;gt;
        &amp;lt;div class=&quot;dialog-box&quot; :style=&quot;{top:top + &apos;px&apos;}&quot;&amp;gt;
            &amp;lt;slot /&amp;gt;
            &amp;lt;div class=&quot;dialog-footer&quot; v-if=&quot;single&quot;&amp;gt;
                &amp;lt;div class=&quot;footer-btn cancel&quot;
                    @click=&quot;secondaryClicked&quot;&amp;gt;
                    &amp;lt;text
                        class=&quot;btn-text&quot;
                        :style=&quot;{ color: secondBtnColor }&quot;
                    &amp;gt;{{ cancelText }}&amp;lt;/text&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div
                    class=&quot;footer-btn confirm&quot;
                    @click=&quot;primaryClicked&quot;
                &amp;gt;
                    &amp;lt;text
                        class=&quot;btn-text&quot;
                        :style=&quot;{ color: mainBtnColor }&quot;
                    &amp;gt;{{ confirmText }}&amp;lt;/text&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;div class=&quot;dialog-footer&quot; v-if=&quot;!single&quot;&amp;gt;
                &amp;lt;div class=&quot;footer-btn confirm&quot;
                    @click=&quot;primaryClicked&quot;&amp;gt;
                    &amp;lt;text
                        class=&quot;btn-text&quot;
                        :style=&quot;{ color: mainBtnColor }&quot;
                    &amp;gt;{{ confirmText }}&amp;lt;/text&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script&amp;gt;
/**
 * 二次确认弹窗组件
 * 支持自定义标题、内容、二次确认按钮文案和样式
 * show 是否显示， 默认不显示
 * top 距离顶部的距离，默认400
 * secondBtnColor  取消按钮样式
 * mainBtnColor 确定按钮样式
 * cancelText 取消文案，默认取消
 * confirmText 确定文案，默认确定
 * single 单个按钮 or 两个按钮，默认两个按钮
*/
export default {
    name: &apos;Modal&apos;,
    props: {
        show: {
            type: Boolean,
            default: false,
        },

        top: {
            type: Number,
            default: 400
        },

        secondBtnColor: {
            type: String,
            default: &apos;#666666&apos;
        },

        mainBtnColor: {
            type: String,
            default: &apos;#0081FF&apos;
        },

        cancelText: {
            type: String,
            default: &apos;取消&apos;
        },

        confirmText: {
            type: String,
            default: &apos;确定&apos;
        },

        single: {
            type: Boolean,
            default: true
        },
    },

    methods: {
        secondaryClicked () {
            this.$emit(&apos;secondaryClicked&apos;, {
                type: &apos;cancel&apos;
            });
        },

        primaryClicked () {
            this.$emit(&apos;primaryClicked&apos;, {
                type: &apos;confirm&apos;
            });
        },
    },
};
&amp;lt;/script&amp;gt;
&amp;lt;style scoped&amp;gt;
.modal-mask {
    width: 750px;
    position: fixed;
    left: 0;
    top: 0;
    bottom: 0;
    right: 0;
    opacity: 1;
    background-color: rgba(0, 0, 0, 0.6);
}

.dialog-box {
    z-index: 9999;
    position: fixed;
    left: 96px;
    width: 558px;
    border-radius:8px;
    background-color: #FFFFFF;
}

.dialog-title {
    font-size: 36px;
    text-align: center;
}

.dialog-content {
    padding-top: 36px;
    padding-bottom: 36px;
    padding-left: 36px;
    padding-right: 36px;
}

.dialog-footer {
    flex-direction: row;
    align-items: center;
    border-top-color: #F3F3F3;
    border-top-width: 1px;
}

.footer-btn {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    flex: 1;
    height: 92px;
}

.cancel {
    border-right-color: #F3F3F3;
    border-right-width: 1px;
}

.btn-text {
    font-size: 36px;
    color: #666666;
    text-align: center;
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Weex</category><author>江辰</author></item><item><title>Ant Design表格列拖拽，部分源码</title><link>https://github.com/posts/ant-design%E8%A1%A8%E6%A0%BC%E5%88%97%E6%8B%96%E6%8B%BD%E9%83%A8%E5%88%86%E6%BA%90%E7%A0%81/</link><guid isPermaLink="true">https://github.com/posts/ant-design%E8%A1%A8%E6%A0%BC%E5%88%97%E6%8B%96%E6%8B%BD%E9%83%A8%E5%88%86%E6%BA%90%E7%A0%81/</guid><pubDate>Wed, 09 Jan 2019 22:50:08 GMT</pubDate><content:encoded>&lt;h2&gt;难点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;如何与现有的自定义列、拖拽功能保持兼容。&lt;/li&gt;
&lt;li&gt;如何与现有的代码和接口数据结构保持兼容。&lt;/li&gt;
&lt;li&gt;拖动列宽的时候，如何阻止拖拽事件发生。&lt;/li&gt;
&lt;li&gt;其他边缘情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;源码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 添加onmousedown事件和样式
const ResizeableTitle = (props) =&amp;gt; {
    const { width, onMouseDown, index, ...restProps } = props;
    if(!width) {
        return &amp;lt;th {...restProps} /&amp;gt;;
    }
    if(width.toString().indexOf(&apos;%&apos;) &amp;gt; -1) {
        return &amp;lt;th {...restProps} /&amp;gt;;
    }
    const propsClassName = `${restProps.className} react-resizable-th`;
    const className = &apos;react-resizable&apos;;
    const key = `react-${index}`;
    // 通过cloneElement，添加span标签
    return React.cloneElement(
        &amp;lt;th {...restProps} /&amp;gt;,
        {
            class: propsClassName,
        },
        [
            restProps.children,
            &amp;lt;span
                key={key}
                className={className}
                onMouseDown={onMouseDown}
                index={index}
                width={width}
                oldWidth={width}
            /&amp;gt;
        ]
    );
};
this.newColums = (newColums) =&amp;gt; {
    return newColums.map((col, index) =&amp;gt; ({
        ...col,
        onHeaderCell: column =&amp;gt; ({
            width: column.width,
            onMouseDown: this.onMouseDown,
            index
        }),
    }));
};
// 重新渲染列
components = {
    header: {
        cell: ResizeableTitle,
    },
};

// 获取列宽
getColumnWidth = (columnNames) =&amp;gt; {
    const widths = [];
    if(document.getElementsByTagName(&apos;colgroup&apos;).length &amp;gt; 0) {
        const children = document.getElementsByTagName(&apos;colgroup&apos;)[0].children;
        if(children.length &amp;gt; 0) {
            for(let i = 0, len = children.length; i &amp;lt; len; i++) {
                if(children[i].className != &apos;ant-table-expand-icon-col&apos;) {
                    widths.push({
                        key: columnNames[i],
                        width: Number(children[i].style.width.match(/[0-9]+/)[0])
                    });
                }
            }
        }
    }
    document.onmousemove = null;
    document.onmouseup = null;
    return widths;
}

// 获取table宽度、当前点击列宽、鼠标x轴位置
onMouseDown = (e) =&amp;gt; {
    e.stopPropagation();
    e.preventDefault();
    const table = document.getElementsByTagName(&apos;table&apos;);
    if(table.length === 0) {
        return;
    }
    if(!(table[0].getAttribute(&apos;style&apos;))) {
        return;
    }
    const colgroup = document.getElementsByTagName(&apos;colgroup&apos;);
    if(colgroup.length === 0 || colgroup.length &amp;lt; 2) {
        return;
    }
    if(colgroup[0].children.length === 0) {
        return;
    }
    const target = e.target;
    const width = Number(target.getAttribute(&apos;width&apos;));
    const oldWidth = Number(target.getAttribute(&apos;oldWidth&apos;));
    const tableWidth = Number(table[0].getAttribute(&apos;style&apos;).match(/[0-9]+/));
    this.nativeEventX = e.nativeEvent.x;
    this.onMouseMove(table, colgroup, target, width, oldWidth, tableWidth);
    this.onMouseUp();
}

/**
 * @description 列宽拖动
 * @param table 表格
 * @param colgroup 表格
 * @param target 当前点击col
 * @param width col宽度        400px
 * @param oldWidth col宽度     400px
 * @param tableWidth 表格宽度  400px
 * @memberof FilterWrap
 */
onMouseMove = (table, colgroup, target, width, oldWidth, tableWidth) =&amp;gt; {
    document.onmousemove = (e) =&amp;gt; {
        let index = Number(target.getAttribute(&apos;index&apos;));
        // 展开行特殊处理
        if(colgroup[0].children[0].className === &apos;ant-table-expand-icon-col&apos;) {
            index++;
        }
        const col = colgroup[0].children[index];
        const col2 = colgroup[1].children[index];
        const currentX = (e.clientX) - this.nativeEventX;
        let newWidth;
        document.body.style.cursor = &apos;col-resize&apos;;
        newWidth = width + currentX;
        // 如果移动的距离小于原始宽度
        if(newWidth &amp;lt; oldWidth) {
            return;
        }
        // 负数转换正数
        if(newWidth &amp;lt; 0) {
            newWidth = -(newWidth);
        }
        const colWidth = `width: ${newWidth}px; min-width: ${newWidth}px`;
        const newTableWidth = `width: ${tableWidth + currentX}px;`;
        col.setAttribute(&apos;style&apos;, colWidth);
        col2.setAttribute(&apos;style&apos;, colWidth);
        table[0].setAttribute(&apos;style&apos;, newTableWidth);
        table[1].setAttribute(&apos;style&apos;, newTableWidth);
        target.setAttribute(&apos;width&apos;, `${newWidth}`);
    };
}

onMouseUp = () =&amp;gt; {
    document.onmouseup = () =&amp;gt; {
        // 拖动结束，进行保存
        document.body.style.cursor = &apos;default&apos;;
        document.onmousemove = null;
        document.onmouseup = null;
        let params = {};
        const { columns } = this.state;
        const { pageId, actions } = this.props;
        const postData = this.getDataIndex(columns);
        params.type = pageId;
        params.columnNames = postData;
        params.columnWidth = this.getColumnWidth(postData);
        // 发起接口存储列数据
        actions &amp;amp;&amp;amp; actions.updateCustomList(params);
    };
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>Ant Design</category><author>江辰</author></item><item><title>前端如何支持PDF、Excel、Word在线预览</title><link>https://github.com/posts/%E5%89%8D%E7%AB%AF%E5%A6%82%E4%BD%95%E6%94%AF%E6%8C%81pdfexcelword%E5%9C%A8%E7%BA%BF%E9%A2%84%E8%A7%88/</link><guid isPermaLink="true">https://github.com/posts/%E5%89%8D%E7%AB%AF%E5%A6%82%E4%BD%95%E6%94%AF%E6%8C%81pdfexcelword%E5%9C%A8%E7%BA%BF%E9%A2%84%E8%A7%88/</guid><pubDate>Thu, 27 Dec 2018 15:54:14 GMT</pubDate><content:encoded>&lt;h2&gt;注意一下几点：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;url 必须经过 encodeURIComponent 转移，且是能够打开的文件域名。&lt;/li&gt;
&lt;li&gt;谷歌文件在线预览，必须使用代理，各种文件都支持。&lt;/li&gt;
&lt;li&gt;不想用代理，可以用微软这个，但是微软这个，不支持最新的 xlsx 格式，xls 格式可以。&lt;/li&gt;
&lt;li&gt;谷歌格式：https://docs.google.com/viewer?url=[url]&lt;/li&gt;
&lt;li&gt;微软格式：https://view.officeapps.live.com/op/view.aspx?src=[url]&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;例子(Word)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;谷歌：
https://docs.google.com/viewer?url=http%3A%2F%2Fsruserfiletest.oss-cn-hangzhou.aliyuncs.com%2Fcrm%2Fcc604886ae8d4be9afffab02313d646d.docx%3FExpires%3D1545898717%26OSSAccessKeyId%3DLTAIm573A7RmsqeQ%26Signature%3DVOaSsvyYy9f%252BF6R1GcSnCG%252BaVI4%253D

微软：
https://view.officeapps.live.com/op/view.aspx?src=http%3A%2F%2Fsruserfiletest.oss-cn-hangzhou.aliyuncs.com%2Fcrm%2Fcc604886ae8d4be9afffab02313d646d.docx%3FExpires%3D1545898717%26OSSAccessKeyId%3DLTAIm573A7RmsqeQ%26Signature%3DVOaSsvyYy9f%252BF6R1GcSnCG%252BaVI4%253D
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;例子(Excel)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;谷歌：
https://docs.google.com/viewer?url=http%3A%2F%2Fsruserfiletest.oss-cn-hangzhou.aliyuncs.com%2Fcrm%2F981f08e66ffa4f64934b37e543f5700b.xlsx%3FExpires%3D1545898717%26OSSAccessKeyId%3DLTAIm573A7RmsqeQ%26Signature%3DFgItdsB%252BPrm2%252BOQShja1HkfqKyY%253D

微软：
https://view.officeapps.live.com/op/view.aspx?src=http%3A%2F%2Fsruserfiletest.oss-cn-hangzhou.aliyuncs.com%2Fcrm%2F981f08e66ffa4f64934b37e543f5700b.xlsx%3FExpires%3D1545898717%26OSSAccessKeyId%3DLTAIm573A7RmsqeQ%26Signature%3DFgItdsB%252BPrm2%252BOQShja1HkfqKyY%253D
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PDF&lt;/h2&gt;
&lt;p&gt;window.open([url])&lt;/p&gt;
</content:encoded><category>前端</category><category>Excel</category><category>Word</category><author>江辰</author></item><item><title>图片和文件预览组件(部分源码)，可拖动，缩小，放大</title><link>https://github.com/posts/%E5%9B%BE%E7%89%87%E5%92%8C%E6%96%87%E4%BB%B6%E9%A2%84%E8%A7%88%E7%BB%84%E4%BB%B6%E9%83%A8%E5%88%86%E6%BA%90%E7%A0%81%E5%8F%AF%E6%8B%96%E5%8A%A8%E7%BC%A9%E5%B0%8F%E6%94%BE%E5%A4%A7/</link><guid isPermaLink="true">https://github.com/posts/%E5%9B%BE%E7%89%87%E5%92%8C%E6%96%87%E4%BB%B6%E9%A2%84%E8%A7%88%E7%BB%84%E4%BB%B6%E9%83%A8%E5%88%86%E6%BA%90%E7%A0%81%E5%8F%AF%E6%8B%96%E5%8A%A8%E7%BC%A9%E5%B0%8F%E6%94%BE%E5%A4%A7/</guid><pubDate>Wed, 26 Dec 2018 15:05:59 GMT</pubDate><content:encoded>&lt;h2&gt;源码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/*
 * 图片预览组件
 * @Author: Jiang
 * @since: 2018-12-21 13:54:00
 */
import React from &apos;react&apos;;
import { Modal, Icon } from &apos;antd&apos;;
import PropTypes from &apos;prop-types&apos;;
import config from &apos;Common/config&apos;;
import { redirect } from &apos;Common/util&apos;;
import &apos;./index.less&apos;;
// 判断游览器
const USER_AGENT = navigator.userAgent;
export default class PreviewImage extends React.Component {
    constructor(props) {
        super(props);
        // 是否按下
        this.isDown = false;
        this.IMG_WIDTH = 300;
        this.IMG_HEIGHT = 300;
        // 放大或缩小比例
        this.zoomOut = 0;
        this.state = {
            // 移动的时候，更改距离和放大缩小
            imgStyle: {
                position: &apos;relative&apos;,
                left: &apos;0px&apos;,
                top: &apos;0px&apos;
            },
            // 放大缩小比例
            num: 0,
            // 是否显示缩小放大比例
            isShowRate: false,
            // true 为图片模式， false为pdf|word|xls预览模式
            isShowPreView: false
        };
        // 图片按下事件
        this.handlerImgDown = this.handlerImgDown.bind(this);
        // 图片加载
        this.onload = this.onload.bind(this);
    }

    componentDidMount() {
        const { url, destruction } = this.props;
        // true 打开弹窗 false 预览pdf or xls or word
        if(this.getType(url)) {
            const getType = this.getType(url);
            if(document.addEventListener &amp;amp;&amp;amp; USER_AGENT.indexOf(&apos;Firefox&apos;) &amp;gt; -1) {
                document.addEventListener(&apos;DOMMouseScroll&apos;, this.onScroll, false);
            } else {
                document.body.onmousewheel = this.onScroll;
            }
            this.setState({
                isShowPreView: getType
            });
        } else {
            this.openUrl(url);
            destruction();
        }
    }

    // 销毁
    componentWillUnmount() {
        document.onmousemove = null;
        document.onmouseup = null;
        document.body.onmousewheel = null;
        document.removeEventListener(&apos;DOMMouseScroll&apos;, this.onScroll, false);
    }

    // 取得文件后缀名
    getFileSuffix = url =&amp;gt; {
        const fileArr = url.split(&apos;.&apos;);
        return fileArr[fileArr.length - 1].toLocaleLowerCase();
    }

    // 根据文件类型是跳转页面还是预览图片
    getType = url =&amp;gt; {
        if(url) {
            let suffix = this.getFileSuffix(url);
            if(suffix.indexOf(&apos;?&apos;) &amp;gt; -1) {
                suffix = suffix.split(&apos;?&apos;)[0];
            }
            return config.FILE_TYPE.IMAGE.indexOf(suffix) &amp;gt; -1;
        }
    }

    // 缩小或放大
    onScroll = (e) =&amp;gt; {
        const { num, imgStyle } = this.state;
        const event = e || window.event;
        let style = {};
        let wheelDelta;
        let wheelDeltaNumber;
        let rate;
        // firefox and safari 的滚动轴值不一样，其余的都是1200
        if(USER_AGENT.indexOf(&apos;Firefox&apos;) &amp;gt; -1) {
            // wheelDelta = 3 or -3
            wheelDelta = event.detail;
            wheelDeltaNumber = 30;
        } else if(USER_AGENT.indexOf(&apos;Safari&apos;) &amp;gt; -1) {
            // wheelDelta = 360 or -360
            wheelDelta = event.wheelDelta;
            wheelDeltaNumber = 3600;
        } else {
            // wheelDelta = 120 or -120
            wheelDelta = event.wheelDelta;
            wheelDeltaNumber = 1200;
        }
        // 如果比例达到100或者为1的时候，停止继续放大或缩小 &amp;gt; 0 放大, &amp;lt; 0 缩小
        if((num === 100 &amp;amp;&amp;amp; wheelDelta &amp;gt; 0) || (num === 1 &amp;amp;&amp;amp; wheelDelta &amp;lt; 0)) {
            return;
        }
        // 计算放大或缩小比例
        this.zoomOut = (this.zoomOut + wheelDelta / wheelDeltaNumber);
        style.width = this.IMG_WIDTH * (1 + this.zoomOut) + &apos;px&apos;;
        style.height = this.IMG_HEIGHT * (1 + this.zoomOut) + &apos;px&apos;;
        style.top = imgStyle.top;
        style.left = imgStyle.left;
        style.position = &apos;relative&apos;;
        // 换算成百分比，显示
        if(this.zoomOut.toFixed(1) * 100 &amp;lt; 0) {
            rate = 10 + (this.zoomOut.toFixed(1) * 100) / 10;
        } else {
            rate = parseInt(this.zoomOut.toFixed(1) * 100);
        }
        // 不允许出现百分之0
        rate = rate === 0 ? rate = 10 : rate;
        this.setState({
            imgStyle: style,
            num: rate,
            isShowRate: true
        });
    }

    // 打开pdf、xls、word等文件
    openUrl = url =&amp;gt; {
        if(url) {
            redirect(url, &apos;_blank&apos;);
        }
    }

    // 图片按下事件
    handlerImgDown = (e) =&amp;gt; {
        e.preventDefault();
        const previewImg = document.getElementsByClassName(&apos;preview-image&apos;);
        this.isDown = true;
        this.currentX = e.clientX;
        this.currentY = e.clientY;
        this.offsetLeft = parseInt(previewImg[0].offsetLeft);
        this.offsetTop = parseInt(previewImg[0].offsetTop);
        this.handlerImgMove();
        // 移除事件
        document.onmouseup = () =&amp;gt; {
            document.onmousemove = null;
            document.onmouseup = null;
            this.isDown = false;
        };
    }

    // 图片移动
    handlerImgMove = () =&amp;gt; {
        document.onmousemove = (e) =&amp;gt; {
            if(this.isDown) {
                const { imgStyle } = this.state;
                let style = {};
                style.width = imgStyle.width + &apos;px&apos;;
                style.height = imgStyle.height + &apos;px&apos;;
                style.left = e.clientX - (this.currentX - this.offsetLeft) + &apos;px&apos;;
                style.top = e.clientY - (this.currentY - this.offsetTop) + &apos;px&apos;;
                style.position = &apos;relative&apos;;
                this.setState({
                    imgStyle: style
                });
            }
        };
    }

    // 获取图片真实宽高
    onload = (e) =&amp;gt; {
        this.IMG_WIDTH = e.target.width;
        this.IMG_HEIGHT = e.target.height;
    }

    render() {
        const { imgStyle, isShowRate, num } = this.state;
        const { url, hideModal } = this.props;
        const { isShowPreView } = this.state;
        return (
            &amp;lt;div className=&quot;c-c-preview-image&quot;&amp;gt;
                {
                    isShowPreView &amp;amp;&amp;amp;
                    &amp;lt;Modal
                        wrapClassName=&quot;c-c-preview-image-modal&quot;
                        visible={true}
                        centered={true}
                        closable={false}
                        width=&quot;100%&quot;
                        onCancel={hideModal}
                        footer={null}
                    &amp;gt;
                        &amp;lt;img
                            draggable=&quot;false&quot;
                            alt=&quot;img&quot;
                            className=&quot;preview-image select-cursor&quot;
                            onMouseDown={this.handlerImgDown}
                            src={url}
                            onLoad={this.onload}
                            style={imgStyle}
                        /&amp;gt;
                        {
                            isShowRate &amp;amp;&amp;amp;
                            &amp;lt;div className=&quot;viewer-tooltip&quot;&amp;gt;{num}%&amp;lt;/div&amp;gt;
                        }
                    &amp;lt;/Modal&amp;gt;
                }
                &amp;lt;Icon onClick={hideModal} className=&quot;close&quot; type=&quot;close&quot; /&amp;gt;
            &amp;lt;/div&amp;gt;
        );
    }
}

PreviewImage.propTypes = {
    // src or pdf or xls or word
    url: PropTypes.string,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;less&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@import &quot;../../../common/asset/variable.less&quot;;

.c-c-preview-image {
    .close {
        position: fixed;
        padding: 15px;
        top: 0px;
        right: 0px;
        font-size: 32px;
        color: #333;
        cursor: pointer;
        z-index: 1002;
    }
}

.c-c-preview-image-modal {
    overflow: hidden;
    user-select: none;

    .viewer-tooltip {
        background-color: rgba(0,0,0,.8);
        border-radius: 10px;
        color: #fff;
        font-size: 12px;
        height: 20px;
        left: 50%;
        line-height: 20px;
        margin-left: -25px;
        margin-top: -10px;
        position: absolute;
        text-align: center;
        top: 50%;
        width: 50px;
        z-index: 1001;
    }

    .ant-modal-close-x {
        font-size: 32px;
    }

    .select-cursor {
        cursor: move;
        cursor: grab;
    }

    .ant-modal-close {
        position: fixed;
        color: #333;
    }

    .ant-modal-body {
        padding: 0px;
    }

    .ant-modal-content {
        background-color: transparent;
        box-shadow: none;
        position: absolute;
        user-select: none;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>精通JS (持续更新)</title><link>https://github.com/posts/%E7%B2%BE%E9%80%9Ajs-%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0/</link><guid isPermaLink="true">https://github.com/posts/%E7%B2%BE%E9%80%9Ajs-%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0/</guid><pubDate>Fri, 21 Dec 2018 11:26:56 GMT</pubDate><content:encoded>&lt;h2&gt;作用域&lt;/h2&gt;
&lt;p&gt;作用域是一套规则，用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对
变量进行赋值，那么就会使用 LHS 查询;如果目的是获取变量的值，就会使用 RHS 查询。
LHS 查询&lt;code&gt;var a = 2;&lt;/code&gt;，简单理解就是赋值
RHS 查询&lt;code&gt;function foo(a) {};  foo(2); &lt;/code&gt;，简单理解就是查询值&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var a = 2;
function foo() {
   console.log(a);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;全局作用域中创建了一个 a 变量。在 foo 函数中可以访问到这个变量 a。因为 console.log(a)在执行的时候，会在当前的作用域(foo)下进行查找，如果没有查找到该变量，会继续往上进行查找。直到没有找到为止。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function foo() {
   var a = 2;
}
console.log(a);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候输出 a，会提示 a is not defined，因为在当前作用域以及上一层作用域当中都没有 a 这个变量。&lt;/p&gt;
&lt;h3&gt;作用域嵌套&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/61353405-55d98d80-a8a2-11e9-9fb7-e7ff2ec7cdb9.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样子就是作用域嵌套，函数 fn3 可以访问最外层的变量,a，b，c&lt;/p&gt;
&lt;h2&gt;强制转换和隐式转换&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;const b = &apos;123&apos;;
Number(b);    // 123
const c = &apos;123&apos;;
c * 1;  // 123
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;== 与 ===&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;== 允许强制转换条件下进行值比较。
例如： [1,2,3] == &apos;1,2,3&apos; // true，或许你会好奇到底是 [1,2,3] == [1,2,3] or &apos;1,2,3&apos; == &apos;1,2,3&apos;，结果是 [1,2,3] == [1,2,3]。&lt;/li&gt;
&lt;li&gt;=== 不允许强制转换类型进行比较值。
例如：[1,2,3] === &apos;1,2,3&apos; // false
参考&lt;a href=&quot;http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.1&quot;&gt;http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;undefined 出现的四种情况&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;变量声明未进行初始化&lt;/li&gt;
&lt;li&gt;取对象某个值或数组中某个元素不存在时&lt;/li&gt;
&lt;li&gt;函数没有返回值&lt;/li&gt;
&lt;li&gt;引用没有提供实参值给形参&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;JS 七种基本数据类型(以最新的 es6 为例)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;string&lt;/li&gt;
&lt;li&gt;number&lt;/li&gt;
&lt;li&gt;boolean&lt;/li&gt;
&lt;li&gt;null&lt;/li&gt;
&lt;li&gt;undefined&lt;/li&gt;
&lt;li&gt;object&lt;/li&gt;
&lt;li&gt;symbol&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;var    a;
typeof a;        //    &apos;undefined&apos;

a = 123;
typeof a;        //    &apos;number&apos;

a = &apos;123&apos;;
typeof a;        //    &apos;string&apos;

a = {};
typeof a;        //    &apos;object&apos;

a = Symbol;
typeof a;        //    &apos;symbol&apos;;

a = true;
typeof a;        //    &apos;boolean&apos;;

a = null;
typeof a;        //    &apos;object&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;条件&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;if (a == 2) {
    // 做一些事情
} else if (a == 10) {
    // 做另一些事请
} else if (a == 42) {
    // 又是另外一些事情
} else {
    // 这里是备用方案
}
switch (a) {
    case 2:
        // 做一些事情
        break;
    case 10:
        // 做另一些事请
        break;
    case 42:
        // 又是另外一些事情
        break;
    default:
        // 这里是备用方案
}
// 如果条件超过2个以上， 改成使用switch， 语句结构更加清晰。
var a = 42;
var b = (a &amp;gt; 41) ? &quot;hello&quot; : &quot;world&quot;;
if (a &amp;gt; 41) {
    b = &quot;hello&quot;;
} else {
    b = &quot;world&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;this&lt;/h2&gt;
&lt;h3&gt;this 指向&lt;/h3&gt;
&lt;p&gt;一句话理解，this 的指向是由所引用的对象来指定的(es5 中)&lt;/p&gt;
&lt;h3&gt;默认绑定&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function foo() {
    console.log(this.a);
}

var a = 2;

foo();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;隐含绑定&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function foo() {
    console.log(this.a);
}

var obj2 = {
    a: 42,
    foo: foo
};

var obj1 = {
    a: 2,
    obj2: obj2
};

obj1.obj2.foo(); // 42
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;隐含丢失&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function foo() {
    console.log(this.a);
}

var obj = {
    a: 2,
    foo: foo
};

var bar = obj.foo; // 函数引用！

var a = &quot;oops, global&quot;; // `a` 也是一个全局对象的属性

bar(); // &quot;oops, global&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;强制绑定&lt;/h3&gt;
&lt;p&gt;通过 call、apply、bind&lt;/p&gt;
&lt;h2&gt;事件&lt;/h2&gt;
&lt;p&gt;网景和微软曾经的战争还是比较火热的，当时，网景主张捕获方式，微软主张冒泡方式。后来 w3c 采用折中的方式，平息了战火，制定了统一的标准——先捕获再冒泡&lt;/p&gt;
&lt;h3&gt;事件流&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/50534108-be655500-0b72-11e9-831a-391288f1359f.png&quot; alt=&quot;image&quot; /&gt;
通常使用事件冒泡来进行事件处理，这样可以最大限度支持各大游览器&lt;/p&gt;
&lt;h3&gt;事件处理程序&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;var button = document.getElementById(&apos;button&apos;);
button.onClick = () =&amp;gt; {
    console.log(&apos;我是DOM0级事件处理程序&apos;);
}
button.onClick = null;
button.addEventListener(&apos;click&apos;, () =&amp;gt; {
    console.log(&apos;我是DOM2级事件处理程序&apos;);
}, false);
button.removeEventListener(&apos;click&apos;, handler, false)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;事件委托&lt;/h3&gt;
&lt;p&gt;假设一间寝室，寝室长负责点外卖，那么委托的对象就是寝室长，寝室长拿到外卖之后，再分发到个人，这就是事件委托原理。
优点：性能提升&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;li click=&quot;&quot;&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li click=&quot;&quot;&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li click&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li click&amp;gt;&amp;lt;/li&amp;gt;
...
假设有1000个li标签，每个li标签都有onclick事件，内存开销大大增加。
&amp;lt;ul click&amp;gt;
   &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
   &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
   &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
   &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过事件委托的话，则只在 parentNode 上添加一个事件就可以完成所有的事。性能大大提高。&lt;/p&gt;
&lt;h3&gt;事件冒泡&lt;/h3&gt;
&lt;p&gt;就跟气泡一样，慢慢浮出水面。所以称之为冒泡事件 targetEvent -&amp;gt; ParentNode -&amp;gt; Body -&amp;gt; Html -&amp;gt; Document&lt;/p&gt;
&lt;h3&gt;事件监听&lt;/h3&gt;
&lt;p&gt;关于事件监听，W3C 规范中定义了 3 个事件阶段，依次是捕获阶段、目标阶段、冒泡阶段。
起初 Netscape 制定了 JavaScript 的一套事件驱动机制（即事件捕获）。随即 IE 也推出了自己的一套事件驱动机制（即事件冒泡）。最后 W3C 规范了两种事件机制，分为捕获阶段、目标阶段、冒泡阶段。IE8 以前 IE 一直坚持自己的事件机制（前端人员一直头痛的兼容性问题），IE9 以后 IE 也支持了 W3C 规范。
addEventListener 事件监听函数&lt;/p&gt;
&lt;h2&gt;对象&lt;/h2&gt;
&lt;h3&gt;内建对象&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;String&lt;/li&gt;
&lt;li&gt;Number&lt;/li&gt;
&lt;li&gt;Boolean&lt;/li&gt;
&lt;li&gt;Object&lt;/li&gt;
&lt;li&gt;Function&lt;/li&gt;
&lt;li&gt;Array&lt;/li&gt;
&lt;li&gt;Date&lt;/li&gt;
&lt;li&gt;RegExp&lt;/li&gt;
&lt;li&gt;Error&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它们实际上仅仅是内建的函数。这些内建函数的每一个都可以被用作构造器（也就是一个可以通过 new 操作符调用的函数 —— 参照第二章），其结果是一个新 构建 的相应子类型的对象。例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var a = &quot;123&quot;;
typeof a; // &quot;string&quot;
a instanceof String; // false

var aa = new String(&quot;123&quot;);
typeof aa; // &quot;object&quot;
aa instanceof String; // true

// 考察 object 子类型
Object.prototype.toString.call(strObject);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;面试的时候，可能会遇到面试官问你 var a = &apos;123&apos;与 new String 的区别&lt;/p&gt;
</content:encoded><category>JS</category><author>江辰</author></item><item><title>前端技术架构选型</title><link>https://github.com/posts/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84%E9%80%89%E5%9E%8B/</link><guid isPermaLink="true">https://github.com/posts/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84%E9%80%89%E5%9E%8B/</guid><pubDate>Tue, 20 Nov 2018 17:46:12 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/48764959-1bf0c100-ecec-11e8-821b-3e6cb4972a54.png&quot; alt=&quot;web&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;可以从以下几个指标，来选择一套适合自己团队的项目。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是否符合团队的技术栈&lt;/li&gt;
&lt;li&gt;是否符合项目需求&lt;/li&gt;
&lt;li&gt;生态圈是否完善、社区是否活跃&lt;/li&gt;
&lt;li&gt;开发效率是否会降低&lt;/li&gt;
&lt;li&gt;团队的学习能力如何&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>前端技术架构</category><author>江辰</author></item><item><title>GitHub 本地配置和全局配置</title><link>https://github.com/posts/github-%E6%9C%AC%E5%9C%B0%E9%85%8D%E7%BD%AE%E5%92%8C%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE/</link><guid isPermaLink="true">https://github.com/posts/github-%E6%9C%AC%E5%9C%B0%E9%85%8D%E7%BD%AE%E5%92%8C%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE/</guid><pubDate>Thu, 25 Oct 2018 16:43:53 GMT</pubDate><content:encoded>&lt;p&gt;原文地址：https://segmentfault.com/a/1190000002994742&lt;/p&gt;
&lt;p&gt;还需要设置项目本地的配置和全局配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git config --local user.name &quot;&quot;
$ git config --local user.email &quot;&quot;
$ git config --local user.password &quot;&quot;

$ git config --global user.name &quot;&quot;
$ git config --global user.email &quot;&quot;
$ git config --global user.password &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>JS 运行机制类文章</title><link>https://github.com/posts/js-%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%88%B6%E7%B1%BB%E6%96%87%E7%AB%A0/</link><guid isPermaLink="true">https://github.com/posts/js-%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%88%B6%E7%B1%BB%E6%96%87%E7%AB%A0/</guid><pubDate>Sat, 29 Sep 2018 14:51:02 GMT</pubDate><content:encoded>&lt;p&gt;这一次，彻底弄懂 JavaScript 执行机制&lt;/p&gt;
&lt;p&gt;https://juejin.im/post/59e85eebf265da430d571f89&lt;/p&gt;
&lt;p&gt;JavaScript 是如何工作的：在 V8 引擎里 5 个优化代码的技巧&lt;/p&gt;
&lt;p&gt;https://github.com/xitu/gold-miner/blob/master/TODO/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code.md&lt;/p&gt;
&lt;p&gt;JavaScript 是如何工作的：内存管理 + 处理常见的 4 种内存泄漏&lt;/p&gt;
&lt;p&gt;https://github.com/xitu/gold-miner/blob/master/TODO/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks.md&lt;/p&gt;
&lt;p&gt;从浏览器多进程到 JS 单线程，JS 运行机制最全面的一次梳理 (值得反复阅读)&lt;/p&gt;
&lt;p&gt;https://juejin.im/post/5a6547d0f265da3e283a1df7?utm_medium=fe&amp;amp;utm_source=weixinqun&lt;/p&gt;
&lt;p&gt;深入了解 JavaScript 引擎精华 (值得反复阅读)&lt;/p&gt;
&lt;p&gt;http://developer.51cto.com/art/201806/576835.htm&lt;/p&gt;
&lt;p&gt;跟着 Event loop 规范理解浏览器中的异步机制&lt;/p&gt;
&lt;p&gt;https://juejin.im/post/5b5873a1e51d4519133fbc35&lt;/p&gt;
&lt;p&gt;[译] JavaScript 是如何工作的：深入网络层 + 如何优化性能和安全&lt;/p&gt;
&lt;p&gt;https://juejin.im/post/5b02ae48518825429d1f9aff?utm_medium=fe&amp;amp;utm_source=weixinqun&lt;/p&gt;
&lt;p&gt;微任务和宏任务&lt;/p&gt;
&lt;p&gt;https://juejin.im/entry/5b860983e51d4538980c22aa&lt;/p&gt;
</content:encoded><category>JS</category><author>江辰</author></item><item><title>CSS-垂直居中</title><link>https://github.com/posts/%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD/</link><guid isPermaLink="true">https://github.com/posts/%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD/</guid><pubDate>Thu, 27 Sep 2018 19:14:57 GMT</pubDate><content:encoded>&lt;h2&gt;line-height&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;
      7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
div:nth-of-type(1) {
  height: 40px;
  line-height: 40px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：兼容性好
缺点：只能用于单行行内内容；要知道高度的值&lt;/p&gt;
&lt;h2&gt;vertical-align&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;
      7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
div:nth-of-type(1) {
  height: 60px;
  vertical-align: middle;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：兼容性好
缺点：需要添加 font-size: 0; 才可以完全的垂直居中；&lt;/p&gt;
&lt;h2&gt;绝对定位&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;
      7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
div:nth-of-type(1) {
  position: relative;
  height: 100px;
}

div:nht-of-type(1) p {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    height: 50px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：简单;兼容性较好(ie8+)
缺点：脱离文档流&lt;/p&gt;
&lt;h2&gt;flex&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;
      7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
div:nth-of-type(1) {
  display: flex;
  align-items: center;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：大众主流；强大；简单
缺点：PC 端兼容性不好，移动端（Android4.0+）&lt;/p&gt;
</content:encoded><category>CSS</category><author>江辰</author></item><item><title>水平居中</title><link>https://github.com/posts/%E6%B0%B4%E5%B9%B3%E5%B1%85%E4%B8%AD/</link><guid isPermaLink="true">https://github.com/posts/%E6%B0%B4%E5%B9%B3%E5%B1%85%E4%B8%AD/</guid><pubDate>Thu, 27 Sep 2018 19:14:10 GMT</pubDate><content:encoded>&lt;h2&gt;text-align&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;
      7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
div:nth-of-type(1) {
  text-align: center;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：兼容性好
缺点：只对行内元素有效；属性会影响到后续内容；子元素的宽度必须小于父元素&lt;/p&gt;
&lt;h2&gt;绝对定位&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;
      7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
div:nth-of-type(1) {
  position: relative;
}

div:nth-of-type(1) p {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：不管是块级还是行内元素都可以实现
缺点：代码较多；脱离文档流；使用 transform 兼容性不好（ie9+）&lt;/p&gt;
&lt;h2&gt;margin&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;
      7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
div:nth-of-type(1) {
  width: 100px;
  margin: 0 auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：兼容性好
缺点：必须设置宽度，子元素的宽度必须小于父元素&lt;/p&gt;
&lt;h2&gt;flex&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;
      7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
div:nth-of-type(1) {
  display: flex;
  justify-content: center;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优点：大众主流；强大；简单
缺点：PC 端兼容性不好，移动端（Android4.0+）&lt;/p&gt;
</content:encoded><category>CSS</category><author>江辰</author></item><item><title>修改滚动条样式</title><link>https://github.com/posts/%E4%BF%AE%E6%94%B9%E6%BB%9A%E5%8A%A8%E6%9D%A1%E6%A0%B7%E5%BC%8F/</link><guid isPermaLink="true">https://github.com/posts/%E4%BF%AE%E6%94%B9%E6%BB%9A%E5%8A%A8%E6%9D%A1%E6%A0%B7%E5%BC%8F/</guid><pubDate>Thu, 20 Sep 2018 20:02:42 GMT</pubDate><content:encoded>&lt;h2&gt;滚动条组成&lt;/h2&gt;
&lt;p&gt;::-webkit-scrollbar 滚动条整体部分
::-webkit-scrollbar-thumb 滚动条里面的小方块，能向上向下移动（或往左往右移动，取决于是垂直滚动条还是水平滚动条）
::-webkit-scrollbar-track 滚动条的轨道（里面装有 Thumb）
::-webkit-scrollbar-button 滚动条的轨道的两端按钮，允许通过点击微调小方块的位置。
::-webkit-scrollbar-track-piece 内层轨道，滚动条中间部分（除去）
::-webkit-scrollbar-corner 边角，即两个滚动条的交汇处
::-webkit-resizer 两个滚动条的交汇处上用于通过拖动调整元素大小的小控件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
::-webkit-scrollbar
{
    width: 16px;
    height: 16px;
    background-color: #F5F5F5;
}

/*定义滚动条轨道 内阴影+圆角*/
::-webkit-scrollbar-track
{
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
    border-radius: 10px;
    background-color: #F5F5F5;
}

/*定义滑块 内阴影+圆角*/
::-webkit-scrollbar-thumb
{
    border-radius: 10px;
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
    background-color: #555;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;详细设置&lt;/h2&gt;
&lt;p&gt;定义滚动条就是利用伪元素与伪类，那什么是伪元素和伪类呢？&lt;/p&gt;
&lt;p&gt;伪类大家应该很熟悉:link,:focus,:hover，此外 CSS3 中又增加了许多伪类选择器，如:nth-child，:last-child，:nth-last-of-type()等。&lt;/p&gt;
&lt;p&gt;CSS 中的伪元素大家以前看过：:first-line,:first-letter,:before,:after。那么在 CSS3 中，伪元素进行了调整，在以前的基础上增加了一个“：”也就是现在变成了“::first-letter,::first-line,::before,::after”，另外 CSS3 还增加了一个“::selection”。两个“：：”和一个“：”在 css3 中主要用来区分伪类和伪元素。&lt;/p&gt;
&lt;p&gt;webkit 的伪类和伪元素的实现很强，可以把滚动条当成一个页面元素来定义，再结合一些高级的 CSS3 属性，比如渐变、圆角、RGBa 等等。然后如果有些地方要用图片，可以把图片也可以转换成 Base64，不然每次都得加载那个多个图片，增加请求数。&lt;/p&gt;
&lt;p&gt;任何对象都可以设置：边框、阴影、背景图片等等，创建的滚动条任然会按照操作系统本身的设置来完成其交互的行为。下面的伪类可以应用到上面的伪元素中。有点小复杂，具体怎么写可以看第一个 demo，那里也有注释。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:horizontal
//horizontal伪类适用于任何水平方向上的滚动条

:vertical
//vertical伪类适用于任何垂直方向的滚动条

:decrement
//decrement伪类适用于按钮和轨道碎片。表示递减的按钮或轨道碎片，例如可以使区域向上或者向右移动的区域和按钮

:increment
//increment伪类适用于按钮和轨道碎片。表示递增的按钮或轨道碎片，例如可以使区域向下或者向左移动的区域和按钮

:start
//start伪类适用于按钮和轨道碎片。表示对象（按钮 轨道碎片）是否放在滑块的前面

:end
//end伪类适用于按钮和轨道碎片。表示对象（按钮 轨道碎片）是否放在滑块的后面

:double-button
//double-button伪类适用于按钮和轨道碎片。判断轨道结束的位置是否是一对按钮。也就是轨道碎片紧挨着一对在一起的按钮。

:single-button
//single-button伪类适用于按钮和轨道碎片。判断轨道结束的位置是否是一个按钮。也就是轨道碎片紧挨着一个单独的按钮。

:no-button
no-button伪类表示轨道结束的位置没有按钮。

:corner-present
//corner-present伪类表示滚动条的角落是否存在。

:window-inactive
//适用于所有滚动条，表示包含滚动条的区域，焦点不在该窗口的时候。

::-webkit-scrollbar-track-piece:start {
/*滚动条上半边或左半边*/
}

::-webkit-scrollbar-thumb:window-inactive {
/*当焦点不在当前区域滑块的状态*/
}

::-webkit-scrollbar-button:horizontal:decrement:hover {
/*当鼠标在水平滚动条下面的按钮上的状态*/
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考来源&lt;/h2&gt;
&lt;p&gt;https://www.cnblogs.com/kugeliu/p/7196656.html&lt;/p&gt;
</content:encoded><category>修改滚动条样式</category><author>江辰</author></item><item><title>前端跨域</title><link>https://github.com/posts/%E8%B7%A8%E5%9F%9F/</link><guid isPermaLink="true">https://github.com/posts/%E8%B7%A8%E5%9F%9F/</guid><pubDate>Mon, 17 Sep 2018 21:52:56 GMT</pubDate><content:encoded>&lt;h2&gt;跨域&lt;/h2&gt;
&lt;p&gt;跨域是指：a 页面想要访问 b 页面，但是这两个页面的域名、端口、协议不同。而浏览器为了保证安全，只允许同源访问。所以就出现了跨域的问题。&lt;/p&gt;
&lt;h2&gt;同源策略&lt;/h2&gt;
&lt;p&gt;指 ab 页面域名、端口、协议一样。&lt;/p&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;h4&gt;jsonp 解决：jsonp 利用 script 标签可以访问任何链接的原理，通过目标服务器设置一个 callback，来进行跨域。&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;//Js 客户端 方法一
&amp;lt;meta content=&quot;text/html; charset=utf-8&quot; http-equiv=&quot;Content-Type&quot; /&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
    function jsonpCallback(result) {
        //alert(result);
        for(var i in result) {
            alert(i+&quot;:&quot;+result[i]);//循环输出a:1,b:2,etc.
        }
    }
    var JSONP=document.createElement(&quot;script&quot;);
    JSONP.type=&quot;text/javascript&quot;;
    JSONP.src=&quot;http://crossdomain.com/services.php?callback=jsonpCallback&quot;;
    document.getElementsByTagName(&quot;head&quot;)[0].appendChild(JSONP);
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;//Js 客户端 方案二
&amp;lt;meta content=&quot;text/html; charset=utf-8&quot; http-equiv=&quot;Content-Type&quot; /&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
    function jsonpCallback(result) {
        alert(result.a);
        alert(result.b);
        alert(result.c);
        for(var i in result) {
            alert(i+&quot;:&quot;+result[i]);//循环输出a:1,b:2,etc.
        }
    }
&amp;lt;/script&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;http://crossdomain.com/services.php?callback=jsonpCallback&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;//Jq 客户端 方案二
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;jquery.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
    $.ajax({
        url:&quot;http://crossdomain.com/services.php&quot;,
        dataType:&apos;jsonp&apos;,
        data:&apos;&apos;,
        jsonp:&apos;callback&apos;,
        success:function(result) {
            for(var i in result) {
                alert(i+&quot;:&quot;+result[i]);//循环输出a:1,b:2,etc.
            }
        },
        timeout:3000
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
//服务端返回JSON数据
$arr=array(&apos;a&apos;=&amp;gt;1,&apos;b&apos;=&amp;gt;2,&apos;c&apos;=&amp;gt;3,&apos;d&apos;=&amp;gt;4,&apos;e&apos;=&amp;gt;5);
$result=json_encode($arr);
//echo $_GET[&apos;callback&apos;].&apos;(&quot;Hello,World!&quot;)&apos;;
//echo $_GET[&apos;callback&apos;].&quot;($result)&quot;;
//动态执行回调函数
$callback=$_GET[&apos;callback&apos;];
echo $callback.&quot;($result)&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;通过设置 Access-Control-Allow-Origin&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;允许任何域名进行访问
header(&apos;Access-Control-Allow-Origin:*&apos;);
// 响应类型
header(&apos;Access-Control-Allow-Methods:POST&apos;);
// 响应头设置
header(&apos;Access-Control-Allow-Headers:x-requested-with,content-type&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;也可以设置特定的域名进行访问
const ALLOW_ORIGIN = [
  &apos;https://downfuture.com&apos;,
  &apos;http://www.downfuture.com&apos;,
  &apos;https://www.downfuture.com&apos;,
  &apos;https://www.downfuture.com:9000&apos;,
  &apos;https://downfuture.com:9000&apos;,
  &apos;downfuture.com:9000&apos;,
  &apos;localhost:9000&apos;,
  &apos;http://localhost:8080&apos;
];
let headers = ALLOW_ORIGIN[x];
header(&quot;Access-Control-Allow-Origin&quot;,  headers);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;通过 Nginx 反向代理&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;server {
        listen   8094;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
       location / {
            root   html;
            index  index.html index.htm;
        }
        location /apis {
            rewrite  ^.+apis/?(.*)$ /$1 break;
            include  uwsgi_params;
           proxy_pass   http://localhost:1894;
        }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>前端跨域</category><author>江辰</author></item><item><title>网站性能优化几个点</title><link>https://github.com/posts/%E7%BD%91%E7%AB%99%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%87%A0%E4%B8%AA%E7%82%B9/</link><guid isPermaLink="true">https://github.com/posts/%E7%BD%91%E7%AB%99%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%87%A0%E4%B8%AA%E7%82%B9/</guid><pubDate>Thu, 16 Aug 2018 10:18:12 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;减少重排和重绘&lt;/li&gt;
&lt;li&gt;图片优化：体积调整、建立最佳格式，比如 jpeg 就比 png 好、降低质量、压缩&lt;/li&gt;
&lt;li&gt;减少 http 请求：组合资源：css 文件合并、js 文件合并&lt;/li&gt;
&lt;li&gt;CDN 加速&lt;/li&gt;
&lt;li&gt;资源打包压缩(js 压缩、HTML 压缩、JS 压缩)&lt;/li&gt;
&lt;li&gt;雪碧图&lt;/li&gt;
&lt;li&gt;使用字体图标&lt;/li&gt;
&lt;li&gt;页面渲染优化&lt;/li&gt;
&lt;li&gt;异步加载&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>性能优化</category><author>江辰</author></item><item><title>网页缓存</title><link>https://github.com/posts/%E7%BC%93%E5%AD%98/</link><guid isPermaLink="true">https://github.com/posts/%E7%BC%93%E5%AD%98/</guid><pubDate>Mon, 23 Jul 2018 15:36:15 GMT</pubDate><content:encoded>&lt;h2&gt;本地缓存&lt;/h2&gt;
&lt;p&gt;sessionStorage 、localStorage 、cookie&lt;/p&gt;
&lt;h2&gt;共同点&lt;/h2&gt;
&lt;p&gt;都是保存在浏览器端，且同源的。&lt;/p&gt;
&lt;h2&gt;区别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;cookie 数据始终在同源的 http 请求中携带（即使不需要），即 cookie 在浏览器和服务器间来回传递；cookie 数据还有路径（path）的概念，可以限制 cookie 只属于某个路径下。存储大小限制也不同，cookie 数据不能超过 4k，同时因为每次 http 请求都会携带 cookie，所以 cookie 只适合保存很小的数据，如会话标识。而 sessionStorage 和 localStorage 不会自动把数据发给服务器，仅在本地保存。sessionStorage 和 localStorage 虽然也有存储大小的限制，但比 cookie 大得多，可以达到 5M 或更大&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据有效期不同：sessionStorage：仅在当前浏览器窗口关闭前有效，自然也就不可能持久保持；localStorage：始终有效，窗口或浏览器关闭也一直保存，因此用作持久数据；cookie 只在设置的 cookie 过期时间之前一直有效，即使窗口或浏览器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;作用域不同：sessionStorage 不在不同的浏览器窗口中共享，即使是同一个页面；localStorage 在所有同源窗口中都是共享的；cookie 也是在所有同源窗口中都是共享的。Web Storage 支持事件通知机制，可以将数据更新的通知发送给监听者。Web Storage 的 api 接口使用更方便。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>网页缓存</category><author>江辰</author></item><item><title>JS 垃圾回收机制</title><link>https://github.com/posts/js-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/</link><guid isPermaLink="true">https://github.com/posts/js-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/</guid><pubDate>Mon, 23 Jul 2018 15:30:03 GMT</pubDate><content:encoded>&lt;h2&gt;标记清除&lt;/h2&gt;
&lt;p&gt;标记清除是指对变量进行标记，当变量进入作用域中的时候，标记&quot;进入环境&quot;，从逻辑上来讲，永远不能是释放进入环境的变量所占用的内存，因为只要执行流进入相应的环境，就会用到它们。比如闭包。当变量离开环境的时候，则标记为&quot;离开环境&quot;。
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后，它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量，原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作，销毁那些带标记的值，并回收他们所占用的内存空间。&lt;/p&gt;
&lt;h2&gt;引用计数&lt;/h2&gt;
&lt;p&gt;另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时，则这个值的引用次数就是 1。相反，如果包含对这个值引用的变量又取得了另外一个值，则这个值的引用次数就减 1。当这个引用次数变成 0 时，则说明没有办法再访问这个值了，因而就可以将其所占的内存空间给收回来。这样，垃圾收集器下次再运行时，它就会释放那些引用次数为 0 的值所占的内存。&lt;/p&gt;
</content:encoded><category>GC</category><author>江辰</author></item><item><title>CSS 盒模型</title><link>https://github.com/posts/css-%E7%9B%92%E6%A8%A1%E5%9E%8B/</link><guid isPermaLink="true">https://github.com/posts/css-%E7%9B%92%E6%A8%A1%E5%9E%8B/</guid><pubDate>Mon, 23 Jul 2018 15:20:49 GMT</pubDate><content:encoded>&lt;h2&gt;盒模型&lt;/h2&gt;
&lt;p&gt;盒模型分 IE 盒模型(怪异盒模型)和 W3C 盒模型，属性分别是 margin、border、padding、content，两者的区别在于，IE 盒模型：&lt;code&gt;width=content + padding + border&lt;/code&gt;，而 w3c 盒模型：&lt;code&gt;width=content&lt;/code&gt;，这个和现实当中的盒子比较类似，所以称之为盒模型。w3c 在 CSS3 中新增了一个属性,box-sizing。包含两个属性：&lt;code&gt;content-box&lt;/code&gt;和&lt;code&gt;border-box&lt;/code&gt;。&lt;code&gt;content-box：width=width&lt;/code&gt;，&lt;code&gt;border-box：width=content+padding+border&lt;/code&gt;&lt;/p&gt;
</content:encoded><category>CSS</category><author>江辰</author></item><item><title>JS 实现队列</title><link>https://github.com/posts/js-%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97/</link><guid isPermaLink="true">https://github.com/posts/js-%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97/</guid><pubDate>Thu, 19 Jul 2018 10:08:43 GMT</pubDate><content:encoded>&lt;h2&gt;队列&lt;/h2&gt;
&lt;p&gt;队列是一种列表，与栈相反，特点表现为先入先出(First-in-First-out，FIFO)结构。常见的例子就是银行排队，先到人的先办理业务。&lt;/p&gt;
&lt;h2&gt;实现&lt;/h2&gt;
&lt;p&gt;使用数组实现，js 中的数组相对于其他语言，有它自己的优势，比如 push()方法，向数组末尾追加元素并更新数组长度，shift()方法，取出数据第一项元素。所以，利用数组就很容易实现队列。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function queue() {
    this.dataStore = [];
    this.length = length;
    this.iqueue = iqueue;
    this.oqueue = oqueue;
    this.front = front;
    this.back = back;
    this.clear = clear;
}

function length() {
    return this.dataStore.length;
}

function iqueue(element) {
    return this.dataStore.push(element);
}

function oqueue() {
    return this.dataStore.shift();
}

function front() {
    return this.dataStore[0];
}

function back() {
    return this.dataStore[this.dataStore.length - 1];
}

function clear() {
    return this.dataStore = [];
}

var q = new queue();
console.log(q.iqueue(1));
console.log(q.length());
console.log(q.front());
console.log(q.oqueue());
console.log(q.length());
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用队列实现舞蹈员入场问题&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function dancer(name, sex) {
    this.name = name;
    this.sex = sex;
}


function getDancer(males, females) {
    var datas = [{
            name: &apos;a&apos;,
            sex: 1
        },
        {
            name: &apos;b&apos;,
            sex: 0
        },
        {
            name: &apos;c&apos;,
            sex: 1
        },
        {
            name: &apos;d&apos;,
            sex: 0
        }
    ]
    for (var i = 0, len = datas.length; i &amp;lt; len; i++) {
        if (datas[i].sex === 0) {
            males.iqueue(new dancer(datas[i].name, datas[i].sex));
        } else {
            females.iqueue(new dancer(datas[i].name, datas[i].sex));
        }
    }
}

function dance(males, females) {
    var person;
    while (males.length() != 0 &amp;amp;&amp;amp; females.length() != 0) {
        person = males.oqueue();
        console.log(&apos;Males dancer is:&apos; + person.name);
        person = females.oqueue();
        console.log(&apos;Females dancer is:&apos; + person.name);
    }
}
var males = new queue();
var females = new queue();
getDancer(males, females);
dance(males, females);
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>JS</category><author>江辰</author></item><item><title>JS 实现出入栈操作</title><link>https://github.com/posts/js-%E5%AE%9E%E7%8E%B0%E5%87%BA%E5%85%A5%E6%A0%88%E6%93%8D%E4%BD%9C/</link><guid isPermaLink="true">https://github.com/posts/js-%E5%AE%9E%E7%8E%B0%E5%87%BA%E5%85%A5%E6%A0%88%E6%93%8D%E4%BD%9C/</guid><pubDate>Wed, 18 Jul 2018 09:22:58 GMT</pubDate><content:encoded>&lt;h2&gt;栈&lt;/h2&gt;
&lt;p&gt;栈是一种特殊的列表，栈内的元素只能通过列表的一端访问，这一端称之为栈顶。栈被称为一种后入先出(LIFO，last-in-first-out)的数据结构。盘子就是最好的例子，最后叠入的盘子，总是最先出去。&lt;/p&gt;
&lt;h2&gt;实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function stack() {
    this.dataStore = []; //初始化数组
    this.topa = 0; //栈位
    this.pop = pop; //出栈
    this.push = push; //入栈
    this.clear = clear; //清楚栈
    this.length = length; //返回栈的长度
}

function pop() {
    return this.dataStore[--this.topa];
}

function push(element) {
    return this.dataStore[this.topa++] = element;
}

function length() {
    return this.topa;
}

function clear() {
    this.topa = 0;
}
var s = new stack();
s.push(1);
s.topa;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;利用栈的特点很容易实现回文&lt;/h2&gt;
&lt;p&gt;回文是指，正过来和反过来都是一样，比如&lt;code&gt;dad，racecar&lt;/code&gt;，比如&lt;code&gt;dad&lt;/code&gt;，在栈中就是
&lt;code&gt;[d] [a] [d]&lt;/code&gt;
通过循环读取，判断与传入的字符串是否相等，即判断是否是回文。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function isPalindremo(word) {
    var s = new stack();
    for (var i = 0, len = word.length; i &amp;lt; len; i++) {
        s.push(word[i]);
    }
    var rword = &apos;&apos;;
    while (s.length() &amp;gt; 0) {
        rword += s.pop();
    }
    if (rword === word) {
        return true;
    } else {
        return false;
    }
}
isPalindremo(&apos;dad&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;判断括号是否匹配&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function isBrackets(expresseion) {
    var s = new stack();
    var b = false;
    var c = false;
    for (var i = 0, len = expresseion.length; i &amp;lt; len; i++) {
        if (expresseion[i] == &apos;(&apos;) {
            b = true;
        }
        if (expresseion[i] == &apos;)&apos;) {
            c = true;
        }
        s.push(expresseion[i]);
    }
    if (!(b &amp;amp;&amp;amp; c)) {
        s.push(&apos;)&apos;);
        return s.length();
    } else {
        return false;
    }
}
console.log(isBrackets(&apos;123 + (a+x+x5+a&apos;));
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>JS</category><author>江辰</author></item><item><title>JS 实现列表操作</title><link>https://github.com/posts/js-%E5%AE%9E%E7%8E%B0%E5%88%97%E8%A1%A8%E6%93%8D%E4%BD%9C/</link><guid isPermaLink="true">https://github.com/posts/js-%E5%AE%9E%E7%8E%B0%E5%88%97%E8%A1%A8%E6%93%8D%E4%BD%9C/</guid><pubDate>Tue, 17 Jul 2018 16:54:16 GMT</pubDate><content:encoded>&lt;h2&gt;列表&lt;/h2&gt;
&lt;p&gt;人们经常使用列表，比如待办事项列表、购物车等，如果数据不太多的话，列表就显得尤为有用。&lt;/p&gt;
&lt;h2&gt;JS 实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function list() {
    this.dataStore = []; //初始化数组
    this.clear = clear; //清除列表
    this.remove = remove; //移除列表中的元素
    this.find = find; //寻找列表中的元素
    this.length = length; //返回列表的长度
}

function find(element) {
    for (var i = 0, len = this.dataStore.length; i &amp;lt; len; i++) {
        if (this.dataStore[i] === element) {
            return i;
        }
    }
    return -1;
}

function remove(element) {
    for (var i = 0, len = this.dataStore.length; i &amp;lt; len; i++) {
        if (this.dataStore[i] === element) {
            this.dataStore.splice(i, 1);
        }
    }
    return this.dataStore;
}

function length() {
    return this.dataStore.length;
}

function clear() {
    this.dataStore = [];
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>JS</category><author>江辰</author></item><item><title>JS 实现各种排序</title><link>https://github.com/posts/js-%E5%AE%9E%E7%8E%B0%E5%90%84%E7%A7%8D%E6%8E%92%E5%BA%8F/</link><guid isPermaLink="true">https://github.com/posts/js-%E5%AE%9E%E7%8E%B0%E5%90%84%E7%A7%8D%E6%8E%92%E5%BA%8F/</guid><pubDate>Mon, 16 Jul 2018 11:23:52 GMT</pubDate><content:encoded>&lt;h2&gt;冒泡排序&lt;/h2&gt;
&lt;p&gt;冒泡排序是一种把数字两两交换的排序，时间复杂度为 O(n2)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function bubbleSort(array) {
    if (array.length &amp;lt; 1) {
        return;
    }
    var temp;
    var len = array.length;
    for (var i = 0; i &amp;lt; len; i++) {
        for (var j = 0; j &amp;lt; len; j++) {
            if (array[i] &amp;lt; array[j]) {
                temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
    }
    return array;
}
console.log(bubbleSort([3, 8, 5, 2, 1, 4]));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;快速排序&lt;/h2&gt;
&lt;p&gt;快速排序其实是二分排序和冒泡排序的变种，基本思想就是左边放最小数据，右边放最大数据，分别对左边和右边进行递归。然后再组成最小数据集。时间复杂度为 O(n2)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function quickSort(array) {
    if (array.length &amp;lt; 1) {
        return array;
    }
    var len = array.length;
    var ban = Math.floor(len / 2);
    var num = array[ban];
    var left = [],
        right = [],
        mid = [];
    for (var i = 0; i &amp;lt; len; i++) {
        if (array[i] &amp;lt; num) {
            left.push(array[i]);
        } else if (array[i] &amp;gt; num) {
            right.push(array[i]);
        } else {
            mid.push(array[i]);
        }
    }
    return [].concat(quickSort(left), mid, quickSort(right));
}
console.log(quickSort([3, 8, 5, 2, 1, 4]));
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>JS</category><author>江辰</author></item><item><title>JS 实现斐波那契数列</title><link>https://github.com/posts/js-%E5%AE%9E%E7%8E%B0%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97/</link><guid isPermaLink="true">https://github.com/posts/js-%E5%AE%9E%E7%8E%B0%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97/</guid><pubDate>Mon, 16 Jul 2018 11:05:07 GMT</pubDate><content:encoded>&lt;h2&gt;斐波那契数列&lt;/h2&gt;
&lt;p&gt;0,1,1,2,3,5,8 像这样的数列就是斐波那契数列，特点是第 n 项等于前两项的和。&lt;/p&gt;
&lt;h2&gt;递归实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function fib(n) {
    if (n == 0) {
        return 0;
    }
    if (n == 1) {
        return 1;
    }
    return fib(n - 1) + fib(n - 2);
}
console.log(fib(5));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;迭代实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function fib(n) {
    var a = 0,
        b = 1,
        total = 0;
    if (n &amp;lt; 2) {
        return n;
    }
    for (var i = 2; i &amp;lt;= n; i++) {
        total = a + b;
        a = b;
        b = total;
    }
    return total;
}
console.log(fib(5));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;斐波那契数列最好不要用递归去实现，因为它在重复计算，而迭代计算的效率则要高很多并且时间复杂度为 O(n)，不信？输入个 100 看看？所以，面试的时候用迭代去写会让面试官更称心如意。&lt;/p&gt;
</content:encoded><category>JS</category><author>江辰</author></item><item><title>Could not execute GraphicsMagick:ImageMagick</title><link>https://github.com/posts/could-not-execute-graphicsmagickimagemagick/</link><guid isPermaLink="true">https://github.com/posts/could-not-execute-graphicsmagickimagemagick/</guid><pubDate>Wed, 27 Jun 2018 09:35:12 GMT</pubDate><content:encoded>&lt;h2&gt;How to fix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Linux-CentOS：&lt;code&gt;$ yum install GraphicsMagick &amp;amp;&amp;amp; yum install ImageMagick&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mac：&lt;code&gt;$ brew install imagemagick &amp;amp;&amp;amp; brew install graphicsmagick&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>ImageMagick</category><author>江辰</author></item><item><title>Nginx &amp; Node.js &amp; Express 配置 HTTPS</title><link>https://github.com/posts/nginx--nodejs--express-%E9%85%8D%E7%BD%AE-https/</link><guid isPermaLink="true">https://github.com/posts/nginx--nodejs--express-%E9%85%8D%E7%BD%AE-https/</guid><pubDate>Mon, 25 Jun 2018 17:19:07 GMT</pubDate><content:encoded>&lt;h2&gt;购买&lt;/h2&gt;
&lt;p&gt;以阿里云示例，免费 SSL 证书购买地址：https://common-buy.aliyun.com/?spm=5176.2020520163.cas.1.zTLyhO&amp;amp;commodityCode=cas#/buy
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41887383-f4876ba0-7932-11e8-83ab-0386210b2957.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;补全&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41887438-27d7149c-7933-11e8-8472-c9fc41f4b7dc.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;购买好证书之后，进行资料补全。 1.输入域名 2.填写资料 3.域名验证类型，选择 DNS 4.系统生成 CSR&lt;/p&gt;
&lt;h2&gt;下载&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41887470-5606c998-7933-11e8-8c71-e9af1965c202.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;补全好之后，进行下载证书&lt;/p&gt;
&lt;h2&gt;Nginx 配置&lt;/h2&gt;
&lt;p&gt;1.在 nginx 目录下新增 cert 目录 2.把下载好的包上传至 cert 目录下 3.修改 nginx.conf，替换为以下内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 443;
    server_name localhost;
    ssl on;
    root html;
    index index.html index.htm;
    ssl_certificate   cert/214799830030327.pem;      #访问的证书目录
    ssl_certificate_key  cert/214799830030327.key; #访问的证书目录
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location / {
        root html;
        index index.html index.htm;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.重启 nginx
5.https 访问您的站点。&lt;/p&gt;
&lt;h2&gt;Node.js 配置&lt;/h2&gt;
&lt;p&gt;1.安装 node.js 2.编辑 web.js 内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var https = require(&apos;https&apos;);
var fs = require(&apos;fs&apos;);
var options = {
    key: fs.readFileSync(&apos;213949634960268.key&apos;),
    cert: fs.readFileSync(&apos;213949634960268.pem&apos;)
};
var a = https.createServer(options, function (req, res) {
    res.writeHead(200);
    res.end(&quot;hello world\n&quot;);
}).listen(443);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.启动
node web.js 4.访问您的站点&lt;/p&gt;
&lt;h2&gt;Express 配置&lt;/h2&gt;
&lt;p&gt;1.修改 &lt;code&gt;/bin/www&lt;/code&gt; 文件，写入以下代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require(&apos;../app&apos;);
var debug = require(&apos;debug&apos;)(&apos;myapp:server&apos;);
var https = require(&apos;https&apos;);
const fs = require(&apos;fs&apos;);

/**
 * Get port from environment and store in Express.
 */

const options = {
    key: fs.readFileSync(&apos;/etc/nginx/cert/214799830030327.key&apos;),
    cert: fs.readFileSync(&apos;/etc/nginx/cert/214799830030327.pem&apos;)
};

var port = normalizePort(process.env.PORT || &apos;9000&apos;);
app.set(&apos;port&apos;, port);

/**
 * Create HTTP server.
 */

//var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

// server.listen(port);
// server.on(&apos;error&apos;, onError);
// server.on(&apos;listening&apos;, onListening);


var servers = https.createServer(options, app);


servers.listen(port);
servers.on(&apos;error&apos;, onError);
servers.on(&apos;listening&apos;, onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
    var port = parseInt(val, 10);

    if (isNaN(port)) {
        // named pipe
        return val;
    }

    if (port &amp;gt;= 0) {
        // port number
        return port;
    }

    return false;
}

/**
 * Event listener for HTTP server &quot;error&quot; event.
 */

function onError(error) {
    if (error.syscall !== &apos;listen&apos;) {
        throw error;
    }

    var bind = typeof port === &apos;string&apos; ?
        &apos;Pipe &apos; + port :
        &apos;Port &apos; + port;

    // handle specific listen errors with friendly messages
    switch (error.code) {
        case &apos;EACCES&apos;:
            console.error(bind + &apos; requires elevated privileges&apos;);
            process.exit(1);
            break;
        case &apos;EADDRINUSE&apos;:
            console.error(bind + &apos; is already in use&apos;);
            process.exit(1);
            break;
        default:
            throw error;
    }
}

/**
 * Event listener for HTTP server &quot;listening&quot; event.
 */

function onListening() {
    var addr = servers.address();
    var bind = typeof addr === &apos;string&apos; ?
        &apos;pipe &apos; + addr :
        &apos;port &apos; + addr.port;
    debug(&apos;Listening on &apos; + bind);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.重新访问您的站点&lt;/p&gt;
&lt;h2&gt;示例&lt;/h2&gt;
&lt;p&gt;https://downfuture.com:9000/api/v1/getCard&lt;/p&gt;
&lt;h2&gt;遇到问题&lt;/h2&gt;
&lt;p&gt;配置好了，访问您的站点出现无法访问网站的报错，可能是安全组没有开放 443 端口。&lt;/p&gt;
</content:encoded><category>Nginx</category><author>江辰</author></item><item><title>React 如何进行上传图片</title><link>https://github.com/posts/react-%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87/</link><guid isPermaLink="true">https://github.com/posts/react-%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87/</guid><pubDate>Fri, 15 Jun 2018 16:16:03 GMT</pubDate><content:encoded>&lt;h2&gt;标签&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;input
 id=&quot;upload-file&quot;
 accept=&quot;image/*&quot;
 type=&quot;file&quot;
 ref=&quot;upload&quot;
 hidden=&quot;hidden&quot;
 onChange={this.upload.bind(this, 1)}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;input&lt;/code&gt; //标签的 type 设置为 file 属性
&lt;code&gt;accept&lt;/code&gt; //属性，支持很多类型，这里设置为只上传图片
&lt;code&gt;hidden&lt;/code&gt; //隐藏文字，做下面这种效果的时候，就需要隐藏文字。
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41458033-0f9a7d52-70b8-11e8-9037-ca6443298f97.png&quot; alt=&quot;image&quot; /&gt;
onChange //上传完成后的回调&lt;/p&gt;
&lt;h2&gt;JS 代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;upload() {
    let files;
    files = this.refs.upload.files
    let count = files.length;
    let formData = new FormData();
    for (let i = 0; i &amp;lt; count; i++) {
        files[i].thumb = URL.createObjectURL(files[i]);
        formData.append(&apos;filedata&apos;, files[i]);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里主要是通过 &lt;code&gt;this.refs.upload&lt;/code&gt;来获取上传之后的文件，然后通过&lt;code&gt;createObjectURL&lt;/code&gt; 静态方法创建一个 &lt;code&gt;DOMString&lt;/code&gt;(mac 测试通过 input 上传过来&lt;code&gt;webkitRelativePath&lt;/code&gt; 是空的)，然后追加进 formData。再通过&lt;code&gt;send(body: formData)&lt;/code&gt;方法传进后端&lt;/p&gt;
&lt;h2&gt;后端&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;const express = require(&apos;express&apos;);
const multiparty = require(&apos;multiparty&apos;);
const gm = require(&apos;gm&apos;).subClass({
    imageMagick: true
});
const fs = require(&apos;fs&apos;);
router.put(`uploadImages`, function (req, res) {
    let datas = {};
    if (!(fs.existsSync(&apos;./images/&apos;))) {
        fs.mkdir(&apos;./images/&apos;, function (err, status) {

        });
    }
    const form = new multiparty.Form({
        uploadDir: &apos;./images/&apos;
    });
    form.parse(req, function (err, fields, files) {
        const filesTmp = files.filedata;
        if (err) {
            throw err;
        } else {
            const relPath = filesTmp;
            for (let i in relPath) {
                gm(relPath[i].path)
                    .resize(240, 240)
                    .noProfile()
                    .write(relPath[i].path, function (err, data) {
                        if (err) {
                            throw err;
                        }
                        console.log(data);
                    });
            }
        }
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后端用的是 node.js，express 框架。fs 模块，来进行判断是否存在该文件夹，如果不存在，则创建。
&lt;code&gt;fs.existsSync()&lt;/code&gt; 返回值为 true or false &lt;code&gt;fs.mkdir()&lt;/code&gt; 创建文件夹 multiparty 模块来解析 form 表单
gm 进行裁剪图片。&lt;/p&gt;
&lt;h2&gt;错误处理&lt;/h2&gt;
&lt;p&gt;1、&lt;code&gt;Error: unsupported content-type&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个错误是因为你的 content-type 设置错了，设置成&lt;code&gt;multipart/form-data&lt;/code&gt;即可。&lt;/p&gt;
&lt;p&gt;2、设置完成之后，还是不行。
去掉&lt;code&gt;headers&lt;/code&gt;的设置
&lt;code&gt;body: formData&lt;/code&gt; //body 的内容为表单内容&lt;/p&gt;
&lt;p&gt;3、上传一次图片之后，无法上传第二次，是因为 value 此时有值，没有进行清空处理，在上传成功回调里，进行&lt;code&gt;e.target.value = &apos;&apos;;&lt;/code&gt;&lt;/p&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>React全家桶建站教程-发布</title><link>https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-%E5%8F%91%E5%B8%83/</link><guid isPermaLink="true">https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-%E5%8F%91%E5%B8%83/</guid><pubDate>Fri, 08 Jun 2018 10:21:42 GMT</pubDate><content:encoded>&lt;h2&gt;购买机器&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;这里以阿里云机器为例，其他云的机器也没问题，注意是 CentOS 系统即可。&lt;/li&gt;
&lt;li&gt;如果没有机器的话，请购买。有机器请略过，直接往下看。
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41752782-1b3f8e60-75fc-11e8-8e0b-a5eb131a9d6a.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41752807-3dc63f9c-75fc-11e8-935c-275677a07681.png&quot; alt=&quot;image&quot; /&gt;&lt;/li&gt;
&lt;li&gt;云盘：默认高效云盘 40G&lt;/li&gt;
&lt;li&gt;下一步，默认配置，确认订单即可。&lt;/li&gt;
&lt;li&gt;等待个 15 分钟左右，系统给你分配机器。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;配置&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;修改密码
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41753002-7699f560-75fd-11e8-8053-865414ef1ec9.png&quot; alt=&quot;image&quot; /&gt;
接着进入机器，选择实例。
然后点击管理。
修改密码在基本信息-&amp;gt;更多-&amp;gt;修改密码&lt;/li&gt;
&lt;li&gt;配置安全组
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41753095-f586e478-75fd-11e8-9d66-4a312735dc4b.png&quot; alt=&quot;image&quot; /&gt;
点击本实例安全组，默认有一个安全组，点击配置规则。
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41753136-2852dc68-75fe-11e8-928f-c72352bb8669.png&quot; alt=&quot;image&quot; /&gt;
点击右上角的快速创建规则，配置-1、80、22 端口&lt;br /&gt;
22 ssh 访问机器的端口
80 让你的 ip 地址可以通过 http 访问
-1 代表不限制端口&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;登录&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41753211-8acad95e-75fe-11e8-859a-b7b52aabab08.png&quot; alt=&quot;image&quot; /&gt;
通过机器公网 ip 和密码进行访问，访问成功之后会有上图提示。&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;CentOS 自带 yum 命令，非常方便使用。
&lt;code&gt;$ yum install nginx&lt;/code&gt; //安装 nginx
&lt;code&gt;$ yum install -y lrzsz&lt;/code&gt; //安装上传下载命令。
&lt;code&gt;$ yum install -y unzip zip&lt;/code&gt; //安装解压和压缩命令
&lt;code&gt;$ yum install -y nodejs&lt;/code&gt; //安装 nodejs 和 npm
&lt;code&gt;$ yum install git&lt;/code&gt; //安装 git 管理工具，会提示是否正确，选 y
查看安装版本
&lt;code&gt;# node -v&lt;/code&gt;
v9.4.0
&lt;code&gt;# npm -v&lt;/code&gt;
5.6.0
&lt;code&gt;# nginx -v&lt;/code&gt;
nginx version: nginx/1.12.2&lt;/p&gt;
&lt;h2&gt;打包和上传&lt;/h2&gt;
&lt;p&gt;1、&lt;s&gt;https://github.com/xuya227939/ak47&lt;/s&gt; 前端案例，可以直接打包的。&lt;code&gt;$ npm i&lt;/code&gt;，&lt;code&gt;$ npm run build&lt;/code&gt;打包，会生成一个 build 文件夹，压缩这个文件夹。
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41753425-024796ba-7600-11e8-9607-b4dc2b80d0a9.png&quot; alt=&quot;image&quot; /&gt;
这个文件夹存放了静态页面和 js
不想这么麻烦的话，这里提供了压缩包
https://github.com/xuya227939/blog/tree/master/examples/build
2、上传到服务器
&lt;code&gt;$ cd /data/sight&lt;/code&gt; 我是放在这个文件夹中，如果没有的话，&lt;code&gt;$ cd /&lt;/code&gt; 进入根目录，&lt;code&gt;$ mkdir data&lt;/code&gt;，&lt;code&gt;$ cd /data&lt;/code&gt;，&lt;code&gt;$ mkdir sight&lt;/code&gt;，&lt;code&gt;$ rz&lt;/code&gt; 上传文件夹
3、解压
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41753511-6b4cef98-7600-11e8-9afd-6fcc171b43dd.png&quot; alt=&quot;image&quot; /&gt;
上传之后，你的结构应该看起来像上图的样子。
&lt;code&gt;$ unzip build.zip&lt;/code&gt; 通过 unzip 解压文件&lt;/p&gt;
&lt;h2&gt;Nginx 配置&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ cd /etc/nginx&lt;/code&gt; 进入 nginx 目录
&lt;code&gt;$ echo &apos;&apos;-&amp;gt; nginx.conf&lt;/code&gt; 清空 nginx.conf 的配置
&lt;code&gt;$ vim nginx.conf&lt;/code&gt; 如果没有 vim 命令，安装一下 &lt;code&gt;$ yum install vim&lt;/code&gt;
写入以下配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;user nginx;
worker_processes auto;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
  worker_connections 1024;
}
http {
  gzip on;
  gzip_min_length 1k;
  gzip_buffers   4 16k;
  gzip_comp_level 2;
  gzip_types   text/plain application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/jpg image/gif image/png;
  gzip_vary on;
  log_format  main  &apos;$remote_addr - $remote_user [$time_local] &quot;$request&quot; &apos;
                      &apos;$status $body_bytes_sent &quot;$http_referer&quot; &apos;
                      &apos;&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;&apos;;
  sendfile            on;
  tcp_nopush          on;
  tcp_nodelay         on;
  keepalive_timeout   65;
  types_hash_max_size 2048;
  include             /etc/nginx/mime.types;
  default_type        application/octet-stream;
  include /etc/nginx/conf.d/*.conf;
  server {
    listen       80 default_server;
    server_name  localhost;
    root         /data/sight/build;
    include /etc/nginx/default.d/*.conf;
    access_log  /data/logs/access.log  main;
    error_log /data/logs/error.log;
    index index.html index.php;
    location ~ .*\.(gif|jpg|png|jpeg)$ {
      access_log on;
      expires 30d;
      root /data/images/;#指定图片存放路径
      client_max_body_size    10m;
      client_body_buffer_size 1280k;
    }
    location ~* ^.+\.(eot|ttf|otf|woff|svg)$ {
      access_log off;
      expires max;
    }
    location / {
      root /data/sight/build;
      try_files $uri /index.html;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header Host $host;
      index index.html index.htm index.php;
    }
    error_page 404 /404.html;
      location = /40x.html {
    }
    error_page 500 502 503 504 /50x.html;
      location = /50x.html {
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;启动 nginx&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ service nginx start&lt;/code&gt; //Redirecting to /bin/systemctl start nginx.service 提示这个，代表成功。
然后访问下你的公网 ip，试试？
&lt;img src=&quot;https://user-images.githubusercontent.com/16217324/41753836-5cfa5082-7602-11e8-804f-1ce3526e974a.png&quot; alt=&quot;image&quot; /&gt;
出现这个，就代表发布成功啦！！！&lt;/p&gt;
&lt;h2&gt;注意&lt;/h2&gt;
&lt;p&gt;每次修改完 nginx.conf 文件之后，都需要重启才可以生效噢。
&lt;code&gt;$ service nginx restart&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;问题处理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;无法访问此网站。可能是 nginx 没有开启&lt;/li&gt;
&lt;li&gt;Permission denied, please try again. 解决办法参考&lt;a href=&quot;https://help.aliyun.com/knowledge_detail/41487.html&quot;&gt;https://help.aliyun.com/knowledge_detail/41487.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;如果访问某个 ip 地址的端口，无法访问的话，安全组没有开放该端口&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;是不是感觉到非常简单？&lt;/p&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>React全家桶建站教程-Redux&amp;Saga</title><link>https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-reduxsaga/</link><guid isPermaLink="true">https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-reduxsaga/</guid><pubDate>Fri, 08 Jun 2018 10:16:34 GMT</pubDate><content:encoded>&lt;h2&gt;Redux 介绍&lt;/h2&gt;
&lt;p&gt;Redux 是 JavaScript 状态容器，提供可预测化的状态管理。Redux 除了和 React 一起用外，还支持其它界面库。 它体小精悍（只有 2kB，包括依赖）。&lt;/p&gt;
&lt;h2&gt;方法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;action //通过 action 把数据传递给 reducer&lt;/li&gt;
&lt;li&gt;reducer //纯函数式，负责把数据发送给 render&lt;/li&gt;
&lt;li&gt;dispatch //触发器&lt;/li&gt;
&lt;li&gt;store //数据源&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Redux 例子&lt;/h2&gt;
&lt;p&gt;https://github.com/xuya227939/blog/tree/master/examples/react-redux/my-app&lt;/p&gt;
&lt;h2&gt;Redux 安装&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ sudo npm install -g create-react-app&lt;/code&gt;
&lt;code&gt;$ create-react-app my-app&lt;/code&gt;
&lt;code&gt;$ cd my-app&lt;/code&gt;
&lt;code&gt;$ npm install --save redux&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;1.修改 App.js，引用官方代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { createStore } from &apos;redux&apos;;
function counter(state = 0, action) {
  switch (action.type) {
  case &apos;INCREMENT&apos;:
    return state + 1
  case &apos;DECREMENT&apos;:
    return state - 1
  default:
    return state
  }
}
let store = createStore(counter)
store.subscribe(() =&amp;gt;
  console.log(store.getState())
)
store.dispatch({ type: &apos;INCREMENT&apos; })
store.dispatch({ type: &apos;INCREMENT&apos; })
store.dispatch({ type: &apos;DECREMENT&apos; })
const BasicExample = () =&amp;gt; (
  &amp;lt;div&amp;gt;123&amp;lt;/div&amp;gt;
)
export default BasicExample
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.npm start 调出开发者工具，看 console.log 输出。&lt;/p&gt;
&lt;h2&gt;Saga 介绍&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;官方介绍：是一个旨在使应用程序副作用（即数据获取等异步事件和访问浏览器缓存等不纯的内容）更容易管理，更高效执行，易于测试并更好地处理故障的库。&lt;/li&gt;
&lt;li&gt;个人理解：类似于 thunk，就是解决异步 callBack 过多的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;方法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;call //发送异步请求&lt;/li&gt;
&lt;li&gt;takeEvery //每次发送 saga，都会更新数据&lt;/li&gt;
&lt;li&gt;takeLatest //会取消上次的 saga，更新最后一个 saga&lt;/li&gt;
&lt;li&gt;put //类似 dispatch，发送数据到 reducer&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Saga 例子&lt;/h2&gt;
&lt;p&gt;这里以计数器为例子
https://github.com/xuya227939/blog/tree/master/examples/saga/my-app&lt;/p&gt;
&lt;h2&gt;Saga 安装&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ sudo npm install -g create-react-app&lt;/code&gt;
&lt;code&gt;$ create-react-app my-app&lt;/code&gt;
&lt;code&gt;$ cd my-app&lt;/code&gt;
&lt;code&gt;$ npm install --save redux&lt;/code&gt;
&lt;code&gt;$ npm install --save redux-saga&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;export function* addFun() {
  yield put({
    type: &quot;ADD&quot;
  });
}
function* homeSaga() {
  yield takeEvery(&quot;ADD_SAGA&quot;, addFun);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;function*&lt;/code&gt; Generato 函数
&lt;code&gt;homeSaga函数&lt;/code&gt; 监听 action
&lt;code&gt;addFun函数&lt;/code&gt; 逻辑处理&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yield put({
    type: &quot;ADD&quot;
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类似 action 的 dispatch，发送数据到 reducer&lt;/p&gt;
&lt;h2&gt;问题处理&lt;/h2&gt;
&lt;p&gt;1.如果报类似这样的错，react-scripts command not found 那么就 $ rm -rf node_modules 模块，重新安装下 $ npm i，再重新 npm start&lt;/p&gt;
&lt;h2&gt;欢迎在此 issue 下进行交流、学习&lt;/h2&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Redux：只能通过 dispatch 去改变，让这一切变得可以预测。这是我认为 redux 做得最好的地方，类似时间管理器的概念。任何时刻发生的事，都可以预测到结果。方便追踪 BUG 的产生。而不像原先 H5 一样，任何人都可以任意修改数据。从而是数据的发生变得很混乱。&lt;/li&gt;
&lt;li&gt;saga：核心功能，解决了异步事件的回调地狱和浏览器刷新这些副作用。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>React全家桶建站教程-Router</title><link>https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-router/</link><guid isPermaLink="true">https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-router/</guid><pubDate>Fri, 08 Jun 2018 10:16:04 GMT</pubDate><content:encoded>&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;React Router 是一个基于 React 之上的强大路由库，它可以让你向应用中快速地添加视图和数据流，同时保持页面与 URL 间的同步。&lt;/p&gt;
&lt;h2&gt;例子&lt;/h2&gt;
&lt;p&gt;https://github.com/xuya227939/blog/tree/master/examples/react-router/my-app&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ sudo npm install -g create-react-app&lt;/code&gt;
&lt;code&gt;$ create-react-app my-app&lt;/code&gt;
&lt;code&gt;$ cd my-app&lt;/code&gt;
&lt;code&gt;$ npm install react-router-dom&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;1.引用的官方代码，在 App.js 插入以下代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;
import {
  BrowserRouter as Router,
  Route,
  Link
} from &apos;react-router-dom&apos;

const Home = () =&amp;gt; (
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;Home&amp;lt;/h2&amp;gt;
  &amp;lt;/div&amp;gt;
)

const About = () =&amp;gt; (
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;About&amp;lt;/h2&amp;gt;
  &amp;lt;/div&amp;gt;
)

const Topic = ({ match }) =&amp;gt; (
  &amp;lt;div&amp;gt;
    &amp;lt;h3&amp;gt;{match.params.topicId}&amp;lt;/h3&amp;gt;
  &amp;lt;/div&amp;gt;
)

const Topics = ({ match }) =&amp;gt; (
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;Topics&amp;lt;/h2&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;Link to={`${match.url}/rendering`}&amp;gt;
          Rendering with React
        &amp;lt;/Link&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;Link to={`${match.url}/components`}&amp;gt;
          Components
        &amp;lt;/Link&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;Link to={`${match.url}/props-v-state`}&amp;gt;
          Props v. State
        &amp;lt;/Link&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;

    &amp;lt;Route path={`${match.path}/:topicId`} component={Topic}/&amp;gt;
    &amp;lt;Route exact path={match.path} render={() =&amp;gt; (
      &amp;lt;h3&amp;gt;Please select a topic.&amp;lt;/h3&amp;gt;
    )}/&amp;gt;
  &amp;lt;/div&amp;gt;
)

const BasicExample = () =&amp;gt; (
  &amp;lt;Router&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;Link to=&quot;/&quot;&amp;gt;Home&amp;lt;/Link&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;Link to=&quot;/about&quot;&amp;gt;About&amp;lt;/Link&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;Link to=&quot;/topics&quot;&amp;gt;Topics&amp;lt;/Link&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;

      &amp;lt;hr/&amp;gt;

      &amp;lt;Route exact path=&quot;/&quot; component={Home}/&amp;gt;
      &amp;lt;Route path=&quot;/about&quot; component={About}/&amp;gt;
      &amp;lt;Route path=&quot;/topics&quot; component={Topics}/&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/Router&amp;gt;
)
export default BasicExample
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.npm start&lt;/p&gt;
&lt;h2&gt;标签&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Link //类似 a 标签的跳转。&lt;/li&gt;
&lt;li&gt;Router //与 Route 一样都是 react 组件，它的 history 对象是整个路由系统的核心，它暴露了很多属性和方法在路由系统中使用；&lt;/li&gt;
&lt;li&gt;Route //path 属性表示路由组件所对应的路径，可以是绝对或相对路径，相对路径可继承；&lt;/li&gt;
&lt;li&gt;4.0 版本之后，history 通过父组件传递进来，this.props.history.push(&apos;/user&apos;); //进行路由之间跳转&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;问题处理&lt;/h2&gt;
&lt;p&gt;1.如果报类似这样的错，react-scripts command not found 那么就 $ rm -rf node_modules 模块，重新安装下 $ npm i，再重新 npm start&lt;/p&gt;
&lt;h2&gt;欢迎在此 issue 下进行交流、学习&lt;/h2&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;使用 react-router 可以更方便的管理页面刷新、跳转。&lt;/p&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>React全家桶建站教程-Webpack</title><link>https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-webpack/</link><guid isPermaLink="true">https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-webpack/</guid><pubDate>Fri, 08 Jun 2018 09:47:55 GMT</pubDate><content:encoded>&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;打包工具，时下流行。&lt;/p&gt;
&lt;h2&gt;例子&lt;/h2&gt;
&lt;p&gt;https://github.com/xuya227939/blog/tree/master/examples/webpack/my-app&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ sudo npm install -g create-react-app //全局安装的话，需要权限，所以使用sudo
$ create-react-app my-app
$ cd my-app

$ npm install webpack         //安装webpack包
$ npm install webpack-cli   //脚本
$ npm install html-webpack-plugin       //生成html插件
$ npm install extract-text-webpack-plugin@next   //抽离css，ant的css代码体积非常大，这样抽离之后，减少了代码体积。
$ npm install babel-preset-es2015   //预先加载es6编译的相关模块，这个软件包已被弃用，但并不影响使用。
$ npm install babel-preset-react      //编译react
$ npm install babel-preset-stage-3  //这个看你想要支持什么语法了，就选择0-3的其中一种。
$ npm install babel-plugin-transform-class-properties   //此插件转换es2015静态类属性以及使用es2016属性初始值设定项语法声明的属性。
$ npm install style-loader        //将css插入页面
$ npm install file-loader          //文件打包
$ npm install babel-loader     //转化es6代码
$ npm install babel-polyfill    //如果想使用 new Set()，Object.assign语法，就得用到它
$ npm install babel-plugin-import  //支持import 引入插件
$ npm install webpack-dev-server  //提供web服务
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;觉得麻烦？两条命令搞定 1.&lt;code&gt;$ npm install webpack  webpack-cli html-webpack-plugin babel-preset-es2015 babel-preset-react babel-preset-stage-3 babel-plugin-transform-class-properties style-loader file-loader babel-loader babel-polyfill babel-plugin-import webpack-dev-server&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2.&lt;code&gt;$ npm install extract-text-webpack-plugin@next&lt;/code&gt;
这个得单独安装下，不能一起安装，因为这种 &lt;code&gt;$ npm install extract-text-webpack-plugin&lt;/code&gt; 方式安装的版本是 3.0.2 与 4.12.0 版本的 webpack 不兼容&lt;/p&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;1.在根目录下，新建 webpack.config.js 文件并写入下面代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const path = require(&apos;path&apos;);
const webpack = require(&apos;webpack&apos;);
const HtmlWebpackPlugin = require(&apos;html-webpack-plugin&apos;);
const ExtractTextPlugin = require(&quot;extract-text-webpack-plugin&quot;);   //引入对应插件
module.exports = {
  devtool: &apos;source-map&apos;,       //模式选择，这里选择原始代码，因为开发环境不需要去混淆代码。
  mode: &apos;development&apos;,        //环境区分，是开发环境development还是生产环境production
  entry: [&apos;babel-polyfill&apos;, &apos;./src/index.js&apos;],   //入口文件
  output: {
    filename: &apos;[name].js&apos;,    //输出文件
    hashDigestLength: 7,   //hash值设置
    path: path.resolve(__dirname, &apos;build&apos;)         //输出文件路径
  },
  module: {
    rules: [
      {
        //匹配js或jsx文件进行编译转换
        test: /\.js|jsx$/,
        exclude: /(node_modules)/,
        use: {
          loader: &apos;babel-loader&apos;,
          options: {
            presets: [&apos;react&apos;, &apos;es2015&apos;, &apos;babel-preset-env&apos;, &apos;stage-3&apos;],
            plugins: [[&quot;transform-class-properties&quot;],[&quot;import&quot;,{ &quot;libraryName&quot;: &quot;antd&quot;, &quot;libraryDirectory&quot;: &quot;es&quot;, &quot;style&quot;: &quot;css&quot; }]]
          }
        }
      },
      {
        //匹配css文件，进行抽离css
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: &quot;style-loader&quot;,
          use: &quot;css-loader&quot;
        })
      },
      {
       //匹配图片
        test: /\.(png|svg|jpg|gif|jpeg)$/,
        use: [
          &apos;file-loader&apos;
        ]
      },
      {
       //匹配字体
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [
          &apos;file-loader&apos;
        ]
      }
    ]
  },
  plugins: [
    //输出特定的html文件
    new HtmlWebpackPlugin({
      title: &apos;my-app&apos;,
      template: &apos;public/index.html&apos;
    }),
    //抽离的css文件名
    new ExtractTextPlugin({
      filename: &apos;[name].css&apos;
    }),

    new webpack.NamedModulesPlugin()    //当开启 HMR 的时候使用该插件会显示模块的相对路径
  ],
  devServer: {      //虚拟服务器
    hot: false,        //热模块更新作用。即修改或模块后，保存会自动更新 true开启，false关闭
    historyApiFallback: true,         //如果为 true ，页面出错不会弹出 404 页面
    compress: true      //如果为 true ，开启虚拟服务器时，为你的代码进行压缩。加快开发流程和优化的作用
  }
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.修改 public/index.html
把 link 屏蔽&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- &amp;lt;link rel=&quot;manifest&quot; href=&quot;%PUBLIC_URL%/manifest.json&quot;&amp;gt;
 &amp;lt;link rel=&quot;shortcut icon&quot; href=&quot;%PUBLIC_URL%/favicon.ico&quot;&amp;gt; --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.修改 package.json
把 scripts 下的 &lt;code&gt; &quot;start&quot;: &quot;react-scripts start&quot;,&lt;/code&gt; 替换为 &lt;code&gt;&quot;start&quot;: &quot;webpack-dev-server --open --config webpack.config.js&quot;,&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;4.npm start&lt;/p&gt;
&lt;h2&gt;问题处理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果提示这个错误 Cannot find module &apos;webpack/bin/config-yargs&apos;，可能是 Webpack 与 webpack-dev-server 版本不兼容导致，我是安装了 &lt;code&gt;$ npm install webpack-dev-server@3.1.3&lt;/code&gt; 版本解决的。webpack 版本是 4.12.0&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;特别要注意下这个插件 extract-text-webpack-plugin，&lt;code&gt;$ npm install extract-text-webpack-plugin&lt;/code&gt; 这种方式安装的版本是 3.0.2，与 4.12.0 版本的 webpack 不兼容，所以需要安装它的^4.0.0-beta.0 版本。&lt;code&gt;$ npm install extract-text-webpack-plugin@next&lt;/code&gt;，即可。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unknown plugin &quot;import&quot; specified in &quot;base&quot; at 1, attempted to resolve relative to 如果遇到这个错，请确保你安装了 babel-plugin-import，如果没有，则安装下 &lt;code&gt;$ npm install babel-plugin-import&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Failed to decode param &apos;/%PUBLIC_URL%/favicon.ico&apos; 如果报这个错的话，先把 public/index.html 页面的 Link 屏蔽了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Module build failed (from ./node_modules/babel-loader/lib/index.js) Cannot find module &apos;babel-core&apos; ，如果报这个错的话，则 &lt;code&gt;$ rm -rf node_modules/&lt;/code&gt; &lt;code&gt;$ npm i&lt;/code&gt; 重新安装下，再 &lt;code&gt;$ npm start&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;想知道如何区分生产 or 开发环境？如何压缩 js 代码？如何打包代码发布吗？&lt;/h2&gt;
&lt;p&gt;&lt;s&gt;https://github.com/xuya227939/ak47&lt;/s&gt;&lt;/p&gt;
&lt;h2&gt;欢迎在此 issue 下进行交流、学习&lt;/h2&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;webpack 还是简单易学的。&lt;/p&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>React全家桶建站教程-React&amp;Ant</title><link>https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-reactant/</link><guid isPermaLink="true">https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-reactant/</guid><pubDate>Fri, 08 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;这里使用到的 UI 库是蚂蚁金服开源的 ant-design，为啥使用？我觉得是使用人数比较多，坑比较少吧。&lt;/p&gt;
&lt;h2&gt;例子&lt;/h2&gt;
&lt;p&gt;https://github.com/xuya227939/blog/tree/master/examples/react/my-app&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ sudo npm install -g create-react-app //全局安装的话，需要权限，所以使用sudo
$ create-react-app my-app
$ cd my-app
$ npm install antd
$ npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;1.引用官方代码，修改 App.js 文件，引入 ant 组件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { Component } from &apos;react&apos;;
import Button from &apos;antd/lib/button&apos;;
import &apos;./App.css&apos;;

class App extends Component {
  render() {
    return (
      &amp;lt;div className=&quot;App&quot;&amp;gt;
        &amp;lt;Button type=&quot;primary&quot;&amp;gt;Button&amp;lt;/Button&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.引用官方代码，修改 App.css&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@import &apos;~antd/dist/antd.css&apos;;
.App {
  text-align: center;
}

.App-logo {
  animation: App-logo-spin infinite 20s linear;
  height: 80px;
}

.App-header {
  background-color: #222;
  height: 150px;
  padding: 20px;
  color: white;
}

.App-title {
  font-size: 1.5em;
}

.App-intro {
  font-size: large;
}

@keyframes App-logo-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你就可以看到蓝色的按钮了。&lt;/p&gt;
&lt;h2&gt;问题处理&lt;/h2&gt;
&lt;p&gt;1.如果报类似这样的错，react-scripts command not found 那么就 $ rm -rf node_modules 模块，重新安装下 $ npm i，再重新 npm start&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;react 入门，首先从搭建 react 开始。&lt;/p&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>React全家桶建站教程-Nginx</title><link>https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-nginx/</link><guid isPermaLink="true">https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-nginx/</guid><pubDate>Thu, 07 Jun 2018 13:41:45 GMT</pubDate><content:encoded>&lt;h2&gt;介绍&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;轻量级，同样起 web 服务，比 apache 占用更少的内存及资源&lt;/li&gt;
&lt;li&gt;抗并发，nginx 处理请求是异步非阻塞的，而 apache 则是阻塞型的，在高并发下 nginx 能保持低资源低消耗高性能&lt;/li&gt;
&lt;li&gt;高度模块化的设计，编写模块相对简单&lt;/li&gt;
&lt;li&gt;社区活跃，各种高性能模块出品迅速&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;例子&lt;/h2&gt;
&lt;p&gt;https://github.com/xuya227939/blog/tree/master/examples/nginx&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;本地：&lt;code&gt;$ brew install nginx&lt;/code&gt;
ECS(CentOs 系统)：&lt;code&gt;$ yum install nginx&lt;/code&gt; 如果没有机器的话，现在有活动噢！学生好像可以每个月 10 块购买机器。
https://promotion.aliyun.com/ntms/act/group/team.html?group=9Az1ljXGL5&lt;/p&gt;
&lt;h2&gt;配置&lt;/h2&gt;
&lt;p&gt;1.清空 nginx 的默认配置项&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ 本地：cd /usr/local/etc/nginx/&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ ECS：cd /etc/nginx/&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ echo &apos;&apos;-&amp;gt;nginx.conf&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ vim nginx.conf&lt;/code&gt; //好像 mac 需要安装 vim 命令，忘了。如果没有的话，安装一下或者使用 &lt;code&gt;$ vi nginx.conf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2.写入代码&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;events {
  worker_connections 1024;    #默认最大的并发数为1024
}
http {
  include             mime.types;     #设定mime类型,类型由mime.type文件定义
  default_type        application/octet-stream;   #默认返回值
  server {
    listen       2000 default_server;         #监听端口
    root         /Users/jiang/Desktop/WorkSpace;    #要访问的html文件所在目录
    access_log  logs/access.log;    #成功日志
    error_log logs/error.log;            #失败日志
    index index.html index.php;    #默认找寻文件
    location / {                              #访问2000端口的重定向
      root /Users/jiang/Desktop/WorkSpace;   #重定向访问的html文件所在目录
      try_files $uri /index.html;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header Host $host;
      index index.html index.htm index.php;
    }
    error_page 404 /404.html;
      location = /40x.html {
    }
    error_page 500 502 503 504 /50x.html;
      location = /50x.html {
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;:wq // 保存并退出&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;3.开启
&lt;code&gt;$ sudo nginx&lt;/code&gt; 开启&lt;br /&gt;
&lt;code&gt;open() &quot;/usr/local/Cellar/nginx/1.15.0/logs/access.log&quot; failed (2: No such file or directory)&lt;/code&gt; 如果报了类似这样的错，说明该目录下没有文件。需要创建。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ cd /usr/local/Cellar/nginx/1.15.0&lt;/code&gt; //进入该目录&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ mkdir logs &amp;amp;&amp;amp; chmod 777 logs&lt;/code&gt; //创建 logs 目录并赋予最高权限组
然后 sudo nginx 开启，访问 &lt;a href=&quot;http://localhost:2000/&quot;&gt;http://localhost:2000/&lt;/a&gt;，如果报 500，查看错误日志。
&lt;code&gt;$ tail -f /usr/local/Cellar/nginx/1.15.0/logs/error.log&lt;/code&gt;
&lt;code&gt;/Users/jiang/Desktop/WorkSpace/index.html&quot; failed (13: Permission denied)&lt;/code&gt; 如果遇到这个问题的话
nginx 没有权限访问该日志，需要赋予最高权限 &lt;code&gt;chmod 777  /Users/jiang/Desktop/WorkSpace &amp;amp;&amp;amp; chmod 777 /Users/jiang/Desktop/WorkSpace/index.html&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;3.访问&lt;a href=&quot;http://localhost:2000/&quot;&gt;http://localhost:2000/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;命令&lt;/h2&gt;
&lt;p&gt;1.Mac 上操作 nginx&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ sudo nginx -s reload&lt;/code&gt; //刷新 nginx，每次修改完 nginx，默认不生效的，需要刷新才行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ sudo nginx&lt;/code&gt; //开启 nginx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ sudo nginx -s stop&lt;/code&gt; //停止 nginx&lt;/p&gt;
&lt;p&gt;2.CentOs 上操作 nginx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ service nginx start&lt;/code&gt; //开启 nginx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ service nginx stop&lt;/code&gt; //停止 nginx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ service nginx restart&lt;/code&gt; //重启 nginx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;$ service nginx reload&lt;/code&gt; //刷新 nginx&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ECS 机器上配置 nginx&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;user nginx;
worker_processes auto;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
  worker_connections 1024;
}
http {
  gzip on;
  gzip_min_length 1k;
  gzip_buffers   4 16k;
  gzip_comp_level 2;
  gzip_types   text/plain application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/jpg image/gif image/png;
  gzip_vary on;
  log_format  main  &apos;$remote_addr - $remote_user [$time_local] &quot;$request&quot; &apos;
                      &apos;$status $body_bytes_sent &quot;$http_referer&quot; &apos;
                      &apos;&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;&apos;;
  sendfile            on;
  tcp_nopush          on;
  tcp_nodelay         on;
  keepalive_timeout   65;
  types_hash_max_size 2048;
  include             /etc/nginx/mime.types;
  default_type        application/octet-stream;
  include /etc/nginx/conf.d/*.conf;
  server {
    listen       80 default_server;
    server_name  localhost;
    root         /data/sight/build;
    include /etc/nginx/default.d/*.conf;
    access_log  /data/logs/access.log  main;
    error_log /data/logs/error.log;
    index index.html index.php;
    location ~ .*\.(gif|jpg|png)$ {
      access_log on;
      expires 30d;
      root /data/images/;#指定图片存放路径
      client_max_body_size    10m;
      client_body_buffer_size 1280k;
    }
    location ~* ^.+\.(eot|ttf|otf|woff|svg)$ {
      access_log off;
      expires max;
    }
    location / {
      root /data/sight/build;
      try_files $uri /index.html;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header Host $host;
      index index.html index.htm index.php;
    }
    error_page 404 /404.html;
      location = /40x.html {
    }
    error_page 500 502 503 504 /50x.html;
      location = /50x.html {
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;多了个图片服务器和开启了 gzip 压缩。&lt;/p&gt;
&lt;h2&gt;欢迎在此 issue 下进行交流、学习&lt;/h2&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;看完之后，是不是觉得特别简单，容易上手？只需要你编写下简单指令和块指令即可。&lt;/p&gt;
</content:encoded><category>React</category><author>江辰</author></item><item><title>React全家桶建站教程-Express</title><link>https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-express/</link><guid isPermaLink="true">https://github.com/posts/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B-express/</guid><pubDate>Thu, 07 Jun 2018 11:37:22 GMT</pubDate><content:encoded>&lt;h2&gt;介绍&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;丰富的 HTTP 快捷方法和任意排列组合的 Connect 中间件，让你创建健壮、友好的 API 变得既快速又简单。&lt;/li&gt;
&lt;li&gt;Express 是一个基于 Node.js 平台的极简、灵活的 web 应用开发框架，它提供一系列强大的特性，帮助你创建各种 Web 和移动设备应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;例子&lt;/h2&gt;
&lt;p&gt;https://github.com/xuya227939/blog/tree/master/examples/express/myapp&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ sudo npm install express-generator -g  //因为是在mac下安装的，所以要注意权限问题，使用sudo
$ express myapp  //通过express生成器，生成项目
$ cd myapp
$ npm i   //安装相关依赖
$ npm install compression  //安装compression  压缩请求
$ npm start  //开启
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问 &lt;a href=&quot;http://localhost:3000&quot;&gt;http://localhost:3000&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;在 app.js 中使用如下代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  const compression = require(&apos;compression&apos;);
  app.use(compression());
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;app.use(&apos;/&apos;, function(req, res) {
  const count = 24;
  // const count = req.body.count;
  let listData = [];
  for (let i = 0; i &amp;lt; count; i++) {
    listData.push({
      src: &apos;&apos;,
      avatar: &apos;https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png&apos;,
      title: `Title Jiang ${i}`,
      description:&apos;Rise n’ shine and don’t forget to smile&apos;,
      star: i * 2,
      like: i * 3
    });
  }
  let data = {};
  data.listData = listData;
  data.count = count;
  res.send(JSON.stringify(data));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 app.js 中替换 app.use(&apos;/users&apos;, indexRouter); 即可，然后 npm start 访问下&lt;a href=&quot;http://localhost:3000&quot;&gt;http://localhost:3000&lt;/a&gt; 就会看到输出了。&lt;/p&gt;
&lt;h2&gt;更新代码&lt;/h2&gt;
&lt;p&gt;通过 npm start 开启之后，你会发现你修改代码之后，刷新没有效果？这是因为 npm start 不支持动态更改代码，这时候就需要 supervisor 来管理 node 进程&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm install supervisor
$ supervisor bin/www
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后试试？
线上的话，通过 pm2 管理。
&lt;code&gt;$yum install pm2 &lt;/code&gt;
在根目录下新建 start.json &lt;code&gt;$ vim start.json&lt;/code&gt;
输入以下代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
   &quot;apps&quot; : [{
        &quot;name&quot;        : &quot;app&quot;,
        &quot;script&quot;      : &quot;bin/www&quot;,
        &quot;log_date_format&quot;  : &quot;YYYY-MM-DD HH:mm:SS&quot;,
        &quot;log_file&quot;   : &quot;logs/success.log&quot;,
        &quot;error_file&quot; : &quot;logs/error.log&quot;,
        &quot;out_file&quot;   : &quot;logs/out.log&quot;,
        &quot;pid_file&quot;   : &quot;logs/app.pid&quot;,
        &quot;watch&quot;      :  true
    }]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;$ :wq&lt;/code&gt; //保存并退出&lt;/p&gt;
&lt;h2&gt;pm2 常用命令&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$ pm2 start start.json&lt;/code&gt; //进行启动，帮你管理 node 进程。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$ pm2 stop all&lt;/code&gt; //停止所有应用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$ pm2 restart all&lt;/code&gt; //重启所有应用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$ pm2 log&lt;/code&gt; //查看应用日志。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;欢迎在此 issue 下进行交流、学习&lt;/h2&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;&lt;s&gt;https://github.com/xuya227939/m4a1&lt;/s&gt; 可以参考这个项目。
通过 express 框架，建立后端服务速度还是蛮快的。简单方便，适合初学者入门。&lt;/p&gt;
</content:encoded><category>React</category><author>江辰</author></item></channel></rss>