forked from Lrunlin/blog
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathblog.sql
262 lines (219 loc) · 240 KB
/
blog.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
-- phpMyAdmin SQL Dump
-- version 4.8.5
-- https://www.phpmyadmin.net/
--
-- 主机: 127.0.0.1:3306
-- 生成日期: 2022-04-04 05:46:33
-- 服务器版本: 5.7.26
-- PHP 版本: 7.2.18
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- 数据库: `blog`
--
-- --------------------------------------------------------
--
-- 表的结构 `admin`
--
DROP TABLE IF EXISTS `admin`;
CREATE TABLE IF NOT EXISTS `admin` (
`admin` varchar(30) CHARACTER SET latin1 NOT NULL COMMENT '管理员账号',
`password` varchar(100) CHARACTER SET latin1 NOT NULL COMMENT '管理员密码',
PRIMARY KEY (`admin`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- 转存表中的数据 `admin`
--
INSERT INTO `admin` (`admin`, `password`) VALUES
('liurunlin', '1bbd886460827015e5d605ed44252251');
-- --------------------------------------------------------
--
-- 表的结构 `api`
--
DROP TABLE IF EXISTS `api`;
CREATE TABLE IF NOT EXISTS `api` (
`id` varchar(60) NOT NULL,
`name` varchar(60) NOT NULL,
`content` mediumtext NOT NULL,
`time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- 转存表中的数据 `api`
--
INSERT INTO `api` (`id`, `name`, `content`, `time`) VALUES
('c3619ff70e01a240c74cd3ad4be4fd06', '获取文章列表', '<p><b><font size=\"4\">获取文章列表</font></b></p><p><b><font size=\"4\">接口地址:https://blog-api.blogweb.cn/article/page/:page</font></b></p><p><b><font size=\"4\">返回格式:json<br>请求方式:get<br>请求示例:https://blog-api.blogweb.cn/article/page/1</font></b></p><p><br></p><table border=\"0\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"><tbody><tr><th>名称</th><th>必填</th><th>类型</th><th>默认</th><th>说明</th></tr><tr><td>page</td><td>是</td><td>number</td><td>1</td><td>查询文章的页数</td></tr></tbody></table><p><br></p><p>返回示例:</p><pre><code class=\"language-json\"><xmp>{\n \"success\": true,\n \"message\": \"查询第1页的文章\",\n \"total\": 277,\n \"data\": [\n {\n \"type\": [\n \"Vue\",\n \"React\"\n ], \"image\": false,\n \"introduce\": \"测试填充内容测试填充内容测试填充内容测试填充内容测试填充内容测试填充内容测试填充内容测试填充内容测试填充内容测试填充\",\n \"id\": 289,\n \"router\": \"0.9046657241333818\",\n \"author\": \"admin\",\n \"time\": \"2021-11-25 13:20:35\",\n \"title\": \"title289\",\n \"view_count\": 31\n },\n ]\n}</xmp></code></pre>', '2022-01-02 22:30:30');
-- --------------------------------------------------------
--
-- 表的结构 `article`
--
DROP TABLE IF EXISTS `article`;
CREATE TABLE IF NOT EXISTS `article` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`router` varchar(60) NOT NULL,
`author` varchar(60) NOT NULL,
`type` varchar(60) NOT NULL,
`article` mediumtext CHARACTER SET utf8mb4 NOT NULL,
`time` datetime DEFAULT NULL,
`title` varchar(200) NOT NULL,
`view_count` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `router` (`router`)
) ENGINE=InnoDB AUTO_INCREMENT=75 DEFAULT CHARSET=utf8;
--
-- 转存表中的数据 `article`
--
INSERT INTO `article` (`id`, `router`, `author`, `type`, `article`, `time`, `title`, `view_count`) VALUES
(1, '1638719606363', 'admin', 'React,填坑,前端', '<div><p>最近我在使用React+antd重写博客网站,在编写一个表单页面时遇到了如下报错:Warning: [antd: Upload] `value` is not a valid prop, do you mean `fileList`?</p><p><img data-src=\"https://assets.blogweb.cn/image/1638719284810.jpg\" src=\"1638719284810.jpg\"><br></p><p>在查找后解决方法如下:</p><p><br><img data-src=\"https://assets.blogweb.cn/image/1638719315132.jpg\" src=\"1638719315132.jpg\"><br>报错是因为Upload组件被嵌套在表单中造成的只需要在对应的Form.Item组件上添加 valuePropName=\"fileList\" 属性就可以了</p><p>但是虽然解决了初始化后报错问题,但是在上传图片时会出现新的报错,这是我们需要在添加一个属性:</p><p><img data-src=\"https://assets.blogweb.cn/image/1639285540271.jpg\" src=\"1639285540271.jpg\"></p><p><img data-src=\"https://assets.blogweb.cn/image/1638721290817.jpg\" src=\"1638721290817.jpg\"></p><p>getValueFromEvent={normFile}</p><p>主要就是处理上传时的状态使展示上传后的预览图片能顺利进行</p></div>', '2021-12-05 00:00:00', 'Warning: [antd: Upload] `value` is not a valid prop, do you mean `fileList`?', 340),
(2, '1627106874249', 'admin', 'TypeScript,填坑', '<div><p>在使用TypeScript开发项目时,在两个毫不相干的模块中定义相同变量tsLint会抛出:无法重新声明块范围变量“xxx”的错误</p><p><img src=\"1627106233101.jpg\"><br></p><p>在Google一番后找到了两种方法</p><ol><li><p>修改tsconfig.json一顿瞎改(亲测无用)</p></li><li><p>先分析原因可能是TS认为这些文件会被合并成一个文件,并没有会被认为是模块,那我们是不是可以去寻找一种方法让TS认为这两个毫不相干的文件是两个独立的模块</p></li></ol><p>我们只需要在文件的尾部加上<i>export {};</i></p><p><img src=\"1627106725102.jpg\"><br>就是这个样子,这样TS就会认为这些文件都是独立的模块了<br></p></div>', '2021-07-24 00:00:00', 'TypeScript解决无法重新声明块范围变量“XXX”问题', 277),
(3, 'cookiesessionjwt', 'admin', 'JavaScript,Node,讲解,后端', '<div><h3 data-we-empty-p=\"\" id=\"onqwo\">为什么使用权限验证?<br></h3><p>前后端分离很多接口会暴露在公网上,为了防止用户直接请求,或者被别有用心的人使用通常开发者会为登录后的用户签发一个token,客户端在发起请求的时候携带,后台确认请求者的身份判断是否执行<br></p><h3 id=\"1t57e\">常见的权限验证方式<br></h3><p>1.JWT (JsonWebToken)<br>2.Session、Cookie<br></p><h3 id=\"tp84u\">基于Cookie Session的验证方式<br></h3><p>1.用户输入用户名与密码,发送给服务器。<br>2.服务器验证用户名和密码,正确的就创建一个session会话,同时会把这个会话的ID保存到客户端浏览器中,因为保存的地方是浏览器的cookie,所以这种认证方式叫做基于cookie的认证方式。<br>3.后续的请求中,浏览器会发送会话ID到服务器,服务器上如果能找到对应的ID的会话,那么服务器就会返回需要的数据给浏览器。<br>4.当用户退出登录,会话会同时在客户端和服务器端被销毁<br></p><h3 id=\"g091p\"><a href=\"https://jwt.io/\" target=\"_blank\">JWT</a></h3><p>1.在用户登录成功后将签发的token返回,将验证信息存储在客户端<br>2.用户发起请求时携带token后端对token进行验证并执行对应的方法<br>3用户退出登录或者修改重要信息后在客户端销毁token<br><br></p><h3 id=\"lhdmg\">优缺点对比<br></h3><p>1.session默认储存在内存中(可以修改为保存 为文件或者Redis中),如果把代码部署在多台服务器上,session保存到了其中某一台服务器的内存中,JWT只要有秘钥就可以实现单点登录<br>2.JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权 限,一旦JWT签发,在有效期内将会一直有效,所以开发时经常设置专门的黑名单<br>3.推荐使用JWT非对称加密本站使用RS256配置公钥私钥来校验是否管理员登录<br></p></div>', '2021-10-12 00:00:00', 'Cookie-Session比较JWT', 116),
(4, 'axiosasyncawait', 'admin', 'AJAX,JavaScript', '<div><p>在最近的vue开发中ajax库选择了axios,需要根据回调函数的参数执行一个很长的代码块,执行函数加上axios参数代码量非常大不便于后期的优化和代码维护,于是我上网寻求axios异步的放法,被告知axios是promise返回值没有同步,如果代码量大可以尝试自行封装,于是研究了async和await</p><p>ES6Promise:</p><pre type=\"JavaScript\"><code><span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Promise</span>(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">resolve, reject</span>) </span>{\r\n <span class=\"hljs-built_in\">console</span>.log(<span class=\"hljs-number\">1111</span>);\r\n resolve(<span class=\"hljs-number\">2222</span>);\r\n}).then(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">value</span>) </span>{\r\n <span class=\"hljs-built_in\">console</span>.log(value);\r\n <span class=\"hljs-keyword\">return</span> <span class=\"hljs-number\">3333</span>;\r\n})</code></pre><p>生成一个异步函数如果执行成功就执行then中的函数如果失败就执行catch中的函数</p><p><b>async</b>就是将一个普通函数返回为promise,当然在学习async和await时你需要先了解promise的用法</p><pre type=\"JavaScript\"><code> <span class=\"hljs-tag\"><<span class=\"hljs-name\">script</span>></span><span class=\"javascript\">\r\n <span class=\"hljs-keyword\">async</span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">test</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">return</span> <span class=\"hljs-string\">\'a\'</span>\r\n }\r\n test().then(<span class=\"hljs-function\"><span class=\"hljs-params\">res</span> =></span> {\r\n <span class=\"hljs-built_in\">console</span>.log(res);\r\n })\r\n </span><span class=\"hljs-tag\"></<span class=\"hljs-name\">script</span>></span></code></pre><p>test函数加上async会被转化为promise其中的return返回值就是then函数的参数</p><p><b>await</b>只能使用在promise中(包括async的返回函数)其用途和他的中文含义差不多:等待,意思是必须等到加await的函数结束promise才会继续执行</p><pre type=\"JavaScript\"><code><span class=\"hljs-keyword\">import</span> axios <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\'axios\'</span>;\r\n\r\n<span class=\"hljs-keyword\">async</span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">createType</span>(<span class=\"hljs-params\">getData</span>) </span>{\r\n <span class=\"hljs-keyword\">let</span> data;\r\n <span class=\"hljs-keyword\">await</span> axios({\r\n <span class=\"hljs-attr\">method</span>: <span class=\"hljs-string\">\"POST\"</span>,\r\n <span class=\"hljs-attr\">url</span>: <span class=\"hljs-string\">\'/create-type\'</span>,\r\n <span class=\"hljs-attr\">data</span>: {\r\n <span class=\"hljs-attr\">type</span>: getData.type\r\n }\r\n }).then(<span class=\"hljs-function\"><span class=\"hljs-params\">res</span> =></span> {\r\n data = res.data;\r\n })\r\n <span class=\"hljs-keyword\">return</span> data;\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> createType;</code></pre><ol><li>引入axios将</li><li>createType转化为promise<br></li><li>设置变量data准备作为返回值</li><li>为axios函数添加await等待axios完全执行完createType才会返回data变量</li><li>请求成功后将axios的请求值赋值给变量data</li><li>将整个函数导出方便复用</li></ol><p><b>项目导入函数</b></p><pre type=\"JavaScript\"><div class=\"code-title\">JavaScript</div><code><span class=\"hljs-keyword\">import</span> createHtml <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"@/modules/function/createHtml\"</span>;\r\n<span class=\"hljs-keyword\">import</span> updataHtml <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"@/modules/function/updataHtml.js\"</span>;\r\n<span class=\"hljs-keyword\">import</span> updataArticle <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"@/modules/article/updata-article\"</span>;</code></pre><p><b>ajax函数使用</b></p><pre type=\"JavaScript\"><code> createType({ <span class=\"hljs-keyword\">type</span>: <span class=\"hljs-keyword\">type</span>.value }).then((res) => {\r\n\r\n\r\n });</code></pre><p>!!:函数内对象为正常的函数传参</p></div>', '2021-03-19 00:00:00', '使用async和await封装axios', 118),
(5, 'createreactapp', 'admin', 'React', '<div><p>最近在学习react分享一下脚手架安装:</p><ol><li>进入指定文件夹</li><li>npm install create-<b>react</b>-app -g / yarn add create-react-app(先确认是否安装了node和npm)<br></li><li>create-react-app 项目名称 (ts:create-react-app 项目名称 --template typescript)</li><li>创建成功</li><li>安装插件 yarn add axios react-router </li><li>!!:对于css预编译选择sass的小伙伴需要注意,node-sass安装有网络问题和可能安装失败,推荐使用yarn(在node-sass 5.x版本得到解决但是node版本需要15或更高版本)</li></ol><div><img src=\"47e5184d9a37deced0c969572ed8a46f.webp\"></div></div><p>对于现在市场上的产品选对框架也非常重要在很多TOC产品的开发中网站需要保证自身SEO可以考虑使用React的第三方框架Next.js或者阿里开源的框架Umi.js(本站使用Next.js开发)</p><p>目前React开发通常选择的库为:</p><ol><li>TypeScript(必选)</li><li>组件库:通常使用antd design</li><li>CSS方案:styled-components或者最近比较流行的tailwind css预编译Scss居多</li><li>请求工具:axios或者封装fetch</li><li>状态管理:Redux已不再流行,非大型项目可以考虑All in Hooks使用useContext和useReducer实现</li></ol>', '2021-04-17 00:00:00', 'React脚手架安装', 59),
(6, 'expresscancelbodyparser', 'admin', 'Node,填坑', '<div><p>在后端使用Express编写代码的时候想要接收post参数大家一般都会只用npm插件:body-parser。但是:</p><p><img src=\"1630122432129.jpg\"></p><p>虽然感觉奇怪,但是并不影响使用,也就不去管他了,有一次我好奇查了一下,发现在Express 4.x官方将这个插件整合到官方包里面了</p><pre><code class=\"NodeJS\"><span class=\"hljs-keyword\">app</span>.<span class=\"hljs-keyword\">use</span>(express.urlencoded({extended: false})); <span class=\"hljs-comment\">// 现在就方便多了,不需要在安装第三方</span>\r\n<span class=\"hljs-keyword\">app</span>.<span class=\"hljs-keyword\">use</span>(express.json());\r\n</code></pre><p>这样的话就可以正常使用啦,在Express中接收POST请求的参数<br></p></div>', '2021-08-28 00:00:00', 'Express4中解决bodyParser已被弃用报错', 61),
(7, 'developmenttrendofCSS', 'admin', 'CSS,讲解,前端', '<p>今天来看看2021年,CSS在不同方面的使用趋势~ </p>\n<p><strong>测试依据:</strong></p>\n<blockquote>\n<p>本文所有指标均来自 HTTP Archive 数据集。HTTP Archive 是一个社区运行的项目,自 2010 年以来一直在跟踪网络的构建方式。在幕后使用 WebPageTest 和 Lighthouse,每月测试大约 820 万个网站的元数据,并将其包含在公共 BigQuery 数据库中进行分析。 </p>\n<p>测试网站:820万个;</p>\n<p>数据处理:39.5TB。</p>\n</blockquote>\n<h2>一、用法</h2>\n<h3>1. 样式表大小<br><br><img src=\"https://cdn.blogweb.cn/image/8bc81d4da94725abc83c0bc9bf2b3427.webp\"></h3><p>CSS文件每年的大小都在不断增长,中等页面的CSS文件大约70KB,较大页面的CSS文件大约250KB。与2020年相比,CSS文件大小的中位数增加了7.9%,同时,移动端CSS所有的百分比都略低于Web端CSS。 </p>\n<p>在所有测试到的CSS文件中,最大的Web页面CSS文件大小为 64,628 KB,最大的移动页面CSS文件大小为17,823 KB。</p>\n<h3>2. 预处理器</h3>\n<p>页面的CSS大小并为受到预处理器的显著影响。17%的Web页面和16.5%的移动页面包含预处理器,略高于去年的15%。具体使用的样式如下: <img src=\"https://cdn.blogweb.cn/image/f6711680189db114b186874cfa95514f.webp\"></p>\n<h3>3. 样式表数量</h3>\n<p>下面是每个页面使用的外部样式表的数量分布: </p><p><br><img src=\"https://cdn.blogweb.cn/image/575178c723939c09599d6545b5b3a1c0.webp\"><br> </p><p>今年每个页面的样式表分布相对于去年有所增加,第50-90百分位的都增加了一个,第10-15百分位的都没有变化。 </p>\n<p>今年,外部样式表数量最多的记录是2368,比2020年的1379,增加了大约一倍。</p>\n<h3>4. 样式表规则</h3>\n<p>下面是每个页面的样式规则数量分布:</p><p> <img src=\"https://cdn.blogweb.cn/image/b2a6d8cb0a77ffe08147d64592491e28.webp\"><br> </p><p>与去年相比,较高百分位的数量几乎没有变化,较低百分位的数量略有上升。在多数百分位的数量分布中,Web页面相对于移动页面的样式规则更多一点。</p>\n<h2>二、选择器</h2>\n<h3>1. class</h3>\n<p>最常用的class名称如下: </p><p><img src=\"https://cdn.blogweb.cn/image/0ce7b4f0799181dd07a170af18f31ae0.webp\"></p>\n<h3>2. id</h3>\n<p>最常用的id名称如下: <br><br><img src=\"https://cdn.blogweb.cn/image/d59b109a63e3d935a4d8338f11e4ad7f.webp\"></p>\n<h3>3. 属性选择器</h3>\n<p>最常用的属性选择器如下: <br><br><img src=\"https://cdn.blogweb.cn/image/b9c2b41f57e48cdef964ac75fe3e295e.webp\"><br> 最常用的属性选择器是type,它可以用于选择表单控件,如复选框、单选按钮、文本输入等。</p>\n<h3>4. 伪类</h3>\n<p>最常用的伪类如下:</p><p> <img src=\"https://cdn.blogweb.cn/image/771adf86f9491ac9d9ebf7e9bd9ada1b.webp\"></p><p> </p><p>和 2020 年一样,用户操作伪类:hover、:focus和:active占据了前三名,它们都出现在至少三分之二的页面中。</p>\n<h3>5. 伪元素</h3>\n<p>最常用的伪元素如下: </p><p><img src=\"https://cdn.blogweb.cn/image/f05445341a715962da0de7ecb4a8da5f.webp\"></p>\n<h3>6. !important</h3>\n<p>每个页面使用!important的比例如下: </p><p> <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7e03759824e542dcaa31f165491b70a5~tplv-k3u1fbpfcp-watermark.awebp\"></p><p>调查发现,移动页面中最多有17,990 条!important规则,Web页面中最多有17,648 条!important规则。</p>\n<p>使用!important规则的样式属性分布如下: </p><p><img src=\"https://cdn.blogweb.cn/image/beedc96fce1a292fbe66dabf2d3f38fc.webp\"></p>\n<h2>三、值和单位</h2>\n<h3>1. 长度</h3>\n<p>最受欢迎的长度单位:</p><p> <img src=\"https://cdn.blogweb.cn/image/57666ec4cf9349679b50a7f2c4d8d8bd.webp\"><br> </p><p>像素单位仍然是迄今为止最常用的长度单位,出现在了大约71%的页面中。</p>\n<p>下面是每个属性的长度类型分布:</p><p> <img src=\"https://cdn.blogweb.cn/image/b5fc17e569c7adedd16c7cde08d7b7e2.webp\"></p>\n<p>下面是最常用的字体相对长度单位分布:</p><p> <img src=\"https://cdn.blogweb.cn/image/a44020c025e8d75363fccce68cac83d1.webp\"></p>\n<p>0长度的使用单位分布如下:</p><p> <img src=\"https://cdn.blogweb.cn/image/aa246dffeb6fe651c3b20e90885b17d0.webp\"><br> </p><p>接近88%的值都直接省略了单位,剩下的有单位的几乎都是px。</p>\n<h3>2. 计算</h3>\n<p>下面是使用calc()函数的最常用的属性:</p><p> <img src=\"https://cdn.blogweb.cn/image/33f3dd5e0ec6079ecf7086f72a3391cd.webp\"><br> </p><p>去过去几年一样,最常用的用法calc()是用来设置width的。 </p>\n<p>下面是calc()函数中最常用的长度单位:</p><p> <img src=\"https://cdn.blogweb.cn/image/b387c4b5a9a70a20cd3b9c3b7736905f.webp\"></p>\n<p>calc()函数中单位数量的分布如下:</p><p> <img src=\"https://cdn.blogweb.cn/image/6a60f180d99792deea75ce631df2a927.webp\"><br> </p><p>calc()值是相对简单的,绝大多数使用两种不同单位进行计算,例如从百分比值的计算结果中减去像素。总共 99% 的calc()表达式使用了一种或两种单位类型。</p>\n<h3>3. 全局关键字</h3>\n<p>下面是最常用的全局关键字以及其分布:</p><p> <img src=\"https://cdn.blogweb.cn/image/dabcbb0b39e8c571382c099102b112e7.webp\"></p>\n<h3>4. 颜色</h3>\n<p>最常用的颜色格式如下: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9992b5a7c91e4a22b72fd856078cf863~tplv-k3u1fbpfcp-watermark.awebp\"> 很长一段时间内,#RRGGBB格式一直是使用最多的颜色格式,超过了一半的颜色声明会使用这种格式。颜色值的 75% 都是使用十六进制和 RGB 语法表示的。 </p>\n<p>最常用的颜色关键字值: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/06b73b27f2a040b8ac9b6d57d3aa5214~tplv-k3u1fbpfcp-watermark.awebp\"></p>\n<h2>四、图片</h2>\n<h3>1. CSS中图片格式</h3>\n<p>下面是CSS中图像格式比例分布: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45db0ba612d84b34ad0192db3fabac89~tplv-k3u1fbpfcp-watermark.awebp\"></p>\n<h3>2. CSS中图片格式</h3>\n<p>下面是CSS样式文件在中加载的图像数量分布: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/65fd72b3531d45d3b224155529f6b773~tplv-k3u1fbpfcp-watermark.awebp\"> 大多数的CSS不会加载大量的图片。在调查中,站点图片最多的CSS,其Web页面CSS中加载了6088张图片,移动页面CSS中加载了6089张图片。</p>\n<h3>3. CSS中图片大小</h3>\n<p>下面是通过CSS加载的外部图片的大小分布: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/220aef20b8cd42018bea4fbebe2ce5cf~tplv-k3u1fbpfcp-watermark.awebp\"> 总体来说,移动页面会比Web页面CSS加载的外部图片略小。在调查中,有一个页面CSS加载的图片总大小达到314,386.1 KB。 </p>\n<p>下面是按照图片格式在移动页面上通过CSS加载的外部图像大小分布: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa7a827b08cb4eb997f4e02daba716ec~tplv-k3u1fbpfcp-watermark.awebp\"> 有趣的是,在第 90 个百分位数时,GIF 图像平均比 SVG 文件还要小。</p>\n<h3>4. 渐变</h3>\n<p>下面是常见的使用渐变的属性: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9f85c5002a5949568fdca1244d1d42fc~tplv-k3u1fbpfcp-watermark.awebp\"> 下面是最常用的渐变值: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/78f5f9ec3fc446158d4511c837f1da4a~tplv-k3u1fbpfcp-watermark.awebp\"></p>\n<h2>五、布局</h2>\n<p>下面是最长使用的布局类型:\n<img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4e6d5412586e4cfc86140eb192a68f4b~tplv-k3u1fbpfcp-watermark.awebp\"> 需要注意,这里并不是页面主要布局的方式,而是说position: absolute出现在我们分析的页面的 93% 的样式中。同样,display: grid 出现在 36% 的页面样式中,但这并不意味着所有页面的 37% 是 Grid 布局,只是该组合出现在样式表的某些地方。</p>\n<h3>1. Flexbox和Grid布局</h3>\n<p>下面是在移动设备上采用Flexbox和Grid布局的比例分布: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/03794b934fcf4efb848fba4c4a175868~tplv-k3u1fbpfcp-watermark.awebp\"> 使用Flexbox和Grid布局一直在持续增长。2019 年,Flexbox 布局的采用率为 41%;2020 年为 63%。今年,Flexbox 在移动端和桌面端分别达到 71% 和 73%。同时,Grid 布局的采用率每年都在会翻倍,从 2% 到 4%,现在是 8%。</p>\n<h3>2. 多列布局</h3>\n<p>使用多列布局的页面的百分比:20%。</p>\n<h3>3. border-box数量</h3>\n<p>每个页面中使用box-sizing: border-box的数量分布: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf793d0755d34109ba7fb71d64530ae4~tplv-k3u1fbpfcp-watermark.awebp\"></p>\n<h3>4. 过渡和动画</h3>\n<p>动画仍然是被广泛使用的,animation属性在 77% 的移动页面和 73% 的桌面页面上使用。transition属性在 85% 的移动页面和 90% 的桌面页面上使用。 </p>\n<p>最常使用过渡的属性: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a3f15a55b8744639983706e2bb4af046~tplv-k3u1fbpfcp-watermark.awebp\"> </p><p>过渡持续时间的分布: <img src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f431043ebeb844089efd1a8fa1117e4d~tplv-k3u1fbpfcp-watermark.awebp\"> </p><p>即使在第 90 个百分位,过渡持续时间的中位数也仅为半秒。 </p>\n<p>延迟过渡的分布:</p><p> <img src=\"1e9f73eb25872724e7fd38ecb771cc76.webp\"><br> </p><p>可以看到,最高的过渡延迟中位数是 1.7 秒,第 10 个百分位数的中位数延迟大约不到负三分之一秒,这表明大量过渡是在生成的动画中途开始的。 </p>\n<p>过渡计时功能的分布:</p><p> <img src=\"a426fd97a8a5a8fbc4f68bbb8eeebdb5.webp\"></p>\n<p>最常用的动画类型:</p><p> <img src=\"b0407d3e4b6f420eda4f81d2cb7f9974.webp\"></p>\n<h2>六、响应式设计</h2>\n<h3>1. 媒体查询功能</h3>\n<p>最常用的用作媒体查询的功能如下:</p><p> <img src=\"8588234c6f343b2c3582e17425287eca.webp\"><br> </p><p>max-width和min-width是迄今为止最受欢迎的查询功能。</p>\n<h3>2. 媒体查询断点</h3>\n<p>最常用的媒体查询断点值:</p><p> <img src=\"2348c37e6fad619f122e694e2eb9c5aa.webp\"><br> </p><p>迄今为止最常见的断点是 767 和 768 px,这与 iPad 在纵向模式下的分辨率非常吻合。767px 被大量用作最大宽度断点,而很少用作最小宽度值。786px经常被用作最小和最大断点。</p>\n<h3>3. 媒体查询属性</h3>\n<p>通过媒体查询最常更改的属性:</p><p> <img src=\"a59f9de1fbb2a471145dd3e68e6c82ed.webp\"><br> </p><p>最常设置的属性是display,紧随其后的是color, width, 和height。</p>\n<h2>七、特征查询</h2>\n<p>特征查询 ( @supports) 的使用继续保持增长。2019 年,有 30% 的页面在使用,2020年为 39%。2021 年,将近 48% 的页面使用特征查询来决定在什么上下文中应用哪种 CSS。 </p>\n<p>最常使用的特征查询的依据分布如下:</p><p> <img src=\"9b737b40747597b133a8bcec31e4fe92.webp\"><br> </p><p>粘性定位是最受欢迎的查询依据,占所有特征查询的一半以上。</p>\n<h2>八、自定义属性</h2>\n<p>2019-2021 年自定义属性使用的变化如下:</p><p> <img src=\"d371d7b59c806444e8d3ad5e2e77d30b.webp\"><br> </p><p>今年,28.6% 的移动页面和 28.3% 的桌面页面定义了自定义属性(也就是CSS变量)。并且,35.2% 的移动页面和 35.6% 的桌面页面包含至少一个var()值。</p>\n<h3>1. 命名</h3>\n<p>对于自定义属性的命名,最常使用的自定义属性名称如下:</p><p> <img src=\"b116b723bb4db0307112cbe8eda154a8.webp\"><br> </p><p>使用最多的自定义属性是--wp开头的,这些都是WordPress网站中的属性。</p>\n<h3>2. 用法</h3>\n<p>最常用自定义属性的属性如下:</p><p> <img src=\"d7ee41cfd0e769c925057c65c1ddd4c4.webp\"><br> </p><p>自定义属性值类型的分布如下:</p><p> <img src=\"2cc63c9ec3ed3121b7fb9418f7110c5d.webp\"></p>\n<h3>3. 复杂</h3>\n<p>自定义属性是可以包含其他自定义属性的:</p>\n<pre><code class=\"language-copyable\">:root {\n --base-hue: 335; /* depth = 0 */\n --base-color: hsl(var(--base-hue) 90% 50%); /* depth = 1 */\n --background: linear-gradient(var(--base-color), black); /* depth = 2 */\n}<br></code></pre>\n<p>自定义属性深度的分布如下:</p><p> <img src=\"b4159ae945da648e8f969c3a8f2f54bc.webp\"><br> </p><p>绝大多数自定义属性的值深度为零:它们没有在自己的值中包含其他自定义属性的值。近三分之一有一个深度级别,除此之外,几乎没有深度为两个或更多的自定义属性值。 </p>\n<p>除此之外,使用自定义属性值的选择器, 60% 设置在根元素上(使用:root或html选择器),大约 5% 设置在<code><body></code>元素上。其余的应用于根元素的某些后代而不是<code><body></code>. 这意味着大约三分之二的自定义属性被用作实际上是全局常量。</p>\n<h2>九、国家化</h2>\n<p>对于不同语言,其可能存在不用的书写形式,比如:</p>\n<ul>\n<li>英文字符是从左到右书写的;</li>\n<li>阿拉伯语、希伯来语和乌尔都语都是从右到左书写,</li>\n<li>汉语、蒙古语、日语可以从上到下书写。</li>\n</ul>\n<p>对于这些复杂的情况,HTML 和 CSS 都提供了对应的处理方法。</p>\n<h3>1. 方向</h3>\n<p>可以使用 CSS 的direction属性来强制执行文本的方向。该属性在 11% 的元素上被使用,在 3% 的页面上的元素上被使用。在使用 CSS 设置方向的页面中,92% 的<code><html></code>元素和 82% 的<code><body></code>元素被设置为ltr(从左到右),只有 9%页面中将该属性设置为rtl(从右到左)。</p>\n<h3>2. 逻辑属性</h3>\n<p>另一个对国际化有用的 CSS 特性就是“逻辑”属性。比如margin-block-start、padding-inline-end等,以及text-align属性的属性值start和end等。这些属性和值都和文本流的方向相关。 </p>\n<p>下面是逻辑属性的属性类型分布:</p><p> <img src=\"e8105d46a496dea3db9cd73d354b43e5.webp\"><br> </p><p>统计发现,只有 4% 的页面使用了逻辑属性。在这些页面中,大约 33% 的页面使用它来设置text-align为start或end。另外 46% 设置了逻辑边距和填充。</p>\n<h2>十、CSS in JS</h2>\n<p>下面是使用的CSS-in-JS 库的分布情况:</p><p> <img src=\"a9575b854b974e5555893bb4fdf64b6a.webp\"><br> </p><p>统计发现,大约 3% 的页面使用了 CSS-in-JS。其中Styled Component就占了一半多。</p>\n<h2>十一、简写</h2>\n<h3>1. 简写属性</h3>\n<p>有些样式表中混合了简写属性background和普通属性background-size,在相应的简写属性之后出现的普通属性最常见的如下: <img src=\"ca7d13636838c9567837d715a97890f0.webp\"></p>\n<h3>2. background</h3>\n<p>可以看到,background相关的普通属性是使用最多的,下面是最常用的background属性:</p><p> <img src=\"a415a2de9bda1ef6c9edcaed719bdfa5.webp\"></p>\n<h3>3. padding和margin</h3>\n<p>下面是最常用的padding和margin属性:</p><p> <img src=\"3bd5ada3879a28888a6371216c22eee3.webp\"></p>\n<h3>4. font</h3>\n<p>下面是最常用的字体属性:</p><p> <img src=\"1775b9d37bc99c635eae4a1c79f085ea.webp\"></p>\n<h3>5. Flexbox</h3>\n<p>下面是最常用的 Flexbox 相关属性:</p><p> <img src=\"cfce4ad03a92b5c3a6474c0aac9bb3fa.webp\"></p>\n<h3>6. Grid</h3>\n<p>下面是最常用的 Grid 相关属性:</p><p> <img src=\"8ac6a969c350dcb4fcfae6197b21a9a5.webp\"></p>\n<h2>十二、错误类型</h2>\n<p>下面来看CSS中常见的一些错误。</p>\n<h3>1. 重复声明</h3>\n<p>“声明重复”的数量——通过确定有多少声明使用相同的属性和值,以及有多少声明在页面的内部是唯一的,从而粗略估计样式表的效率。 </p>\n<p>下面是每个页面重复声明的分布:</p><p> <img src=\"4071ca2ec2a426b00b187f20139577cb.webp\"></p>\n<h3>2. 不存在的属性</h3>\n<p>最常见的不存在的属性如下:</p><p> <img src=\"d090c4c57135b92044f5df44f47b88e4.webp\"></p>\n<h2>十三、预处理器Sass</h2>\n<p>Sass是最流行的CSS预处理器之一,最常用的 Sass 函数调用如下:</p><p> <img src=\"24a884c54312e1c276b9abbaabb27841.webp\"><br> </p><p>可以发现, Sass 函数中有 28% 是修改颜色的函数(例如darken、mix),另外 6% 用于读取颜色(例如alpha、blue)。</p>\n<p>下面是 Sass 中最常用的流控制结构:</p><p> <img src=\"7c9630043ee4781d5ade30013cd09ecc.webp\"><br> </p><p>下面是 Sass 中最常用的规则嵌套:</p><p> <img src=\"7963e3733c76cee6f3efddfe4c75043e.webp\"><br> Sass 的一个主要优点就是能够将规则嵌套在其他规则中,从而避免编写重复的选择器模式。统计发现,87% 的 Sass 样式表使用了规则嵌套。 </p>\n<p>这就是2021年CSS的使用趋势,期待 CSS 在未来的发展~</p>\n<p>关于本文:</p>\n<blockquote>\n<p>原文:<a href=\"https://link.juejin.cn?target=https%3A%2F%2Falmanac.httparchive.org%2Fen%2F2021%2Fcss\" target=\"_blank\">almanac.httparchive.org/en/2021/css</a></p>\n<p>作者:Eric A. Meyer、Shuvam Manna</p>\n<p>译者:CUGGZ</p>\n</blockquote>', '2021-12-26 00:00:00', '2021年 CSS 使用趋势', 87),
(8, 'leetcode4', 'admin', 'JavaScript,Leetcode', '<div><p><a href=\"https://leetcode-cn.com/problems/median-of-two-sorted-arrays/\" target=\"_blank\">原题地址</a><br></p><p><img src=\"1f2rd85v0.png\"><br></p><p><img src=\"1f2rd8vtq.png\"><br></p><p>解:</p><ol><li>一共有两个数组,需要进行数组合并</li><li>数组需要排序,而且需要判断数组长度的奇数偶数</li><li>如果是奇数就去数组长度除二减0.5如果是偶数就取两个数一个是数组长度除二减一,一个是数组长度除二</li><li>例如:[1,2,3,4,5]取数组长度的一半减0.5就是索引值2也就是数组3</li><li>[1,2,3,4]取数组长度除二减一,一个是数组长度除二,也就是2,和4</li></ol><pre type=\"JavaScript\"><code><span class=\"hljs-keyword\">var</span> findMedianSortedArrays = <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">nums1, nums2</span>) </span>{\r\n <span class=\"hljs-keyword\">let</span> arr = nums1.concat(nums2); <span class=\"hljs-comment\">//合并数组</span>\r\n arr = arr.sort(<span class=\"hljs-function\">(<span class=\"hljs-params\">n1, n2</span>) =></span> n1 - n2); <span class=\"hljs-comment\">//排序</span>\r\n <span class=\"hljs-keyword\">let</span> isOdd = arr.length % <span class=\"hljs-number\">2</span>; <span class=\"hljs-comment\">//检测数组是不是奇数</span>\r\n <span class=\"hljs-keyword\">let</span> index = isOdd ? arr.length / <span class=\"hljs-number\">2</span> - <span class=\"hljs-number\">0.5</span> : [arr.length / <span class=\"hljs-number\">2</span> - <span class=\"hljs-number\">1</span>, arr.length / <span class=\"hljs-number\">2</span>]; <span class=\"hljs-comment\">//算出奇数偶数,算出要取的索引值</span>\r\n <span class=\"hljs-keyword\">let</span> value = isOdd ? arr[index] : (arr[index[<span class=\"hljs-number\">0</span>]] + arr[index[<span class=\"hljs-number\">1</span>]]) / <span class=\"hljs-number\">2</span>\r\n <span class=\"hljs-keyword\">return</span> value;\r\n};\r\n<span class=\"hljs-built_in\">console</span>.log(findMedianSortedArrays([<span class=\"hljs-number\">1</span>, <span class=\"hljs-number\">2</span>], [<span class=\"hljs-number\">3</span>, <span class=\"hljs-number\">4</span>]));</code></pre></div>', '2021-04-09 00:00:00', 'leetcode第四题寻找两个正序数组的中位数', 83),
(9, 'leetcode14', 'admin', 'JavaScript,Leetcode', '<div><p><img src=\"1630120983915.jpg\"></p><p><a href=\"https://leetcode-cn.com/problems/longest-common-prefix/submissions/\" target=\"_blank\">原题地址</a><br></p><p><br><img src=\"1630120506400.jpg\"><br><br></p><pre><code class=\"JavaScript\"><span class=\"hljs-comment\">/**\r\n * @param {string[]} strs\r\n * @return {string}\r\n */</span>\r\n<span class=\"hljs-built_in\">var</span> longestCommonPrefix = function (strs) {\r\n <span class=\"hljs-built_in\">var</span> <span class=\"hljs-built_in\">str</span> = <span class=\"hljs-string\">\"\"</span>;\r\n\r\n <span class=\"hljs-keyword\">for</span> (let <span class=\"hljs-keyword\">index</span> = <span class=\"hljs-number\">0</span>; <span class=\"hljs-keyword\">index</span> < strs[<span class=\"hljs-number\">0</span>].split(<span class=\"hljs-string\">\'\'</span>).length; <span class=\"hljs-keyword\">index</span>++) {\r\n let item = strs[<span class=\"hljs-number\">0</span>].split(<span class=\"hljs-string\">\'\'</span>)[<span class=\"hljs-keyword\">index</span>];\r\n let result = <span class=\"hljs-literal\">true</span>;\r\n <span class=\"hljs-keyword\">for</span> (let i = <span class=\"hljs-number\">0</span>; i < strs.length; i++) {\r\n <span class=\"hljs-keyword\">if</span> (item != strs[i][<span class=\"hljs-keyword\">index</span>]) {\r\n result = <span class=\"hljs-literal\">false</span>;\r\n <span class=\"hljs-keyword\">break</span>;\r\n }\r\n }\r\n <span class=\"hljs-keyword\">if</span> (result) {\r\n <span class=\"hljs-built_in\">str</span> += item;\r\n } <span class=\"hljs-keyword\">else</span> {\r\n <span class=\"hljs-keyword\">break</span>;\r\n }\r\n }\r\n <span class=\"hljs-keyword\">return</span> <span class=\"hljs-built_in\">str</span>;\r\n};</code></pre><p><br></p><p><br>虽然我的解法比较烂,但是我还是决定要分享一下:</p><ol><li>题目表达非常清楚,寻找公共前缀</li><li>我们将数组中的第一个字符串元素转为数组进行forEach(不转也行,使用for)</li><li>遍历剩余字符串,根据遍历第一个字符串的循环的索引值进行对比,一旦有不相同的就跳出循环,返回最终结果</li></ol><p><br></p></div>', '2021-08-28 00:00:00', 'LeetCode14 最长公共前缀', 35),
(10, 'leetcode1', 'admin', 'Leetcode,JavaScript', '<div><h2 id=\"f68ko\"><p><a href=\"https://leetcode-cn.com/problems/two-sum/\" target=\"_blank\">原题地址</a><br></p><p>要求:</p><img src=\"1f1u3vj5k.png\"></h2><h2 id=\"f68ko\"></h2><h3><span style=\"font-weight: normal;\" id=\"uiik9\">解:</span></h3><pre type=\"JavaScript\"><code><span class=\"hljs-comment\">/**\r\n * <span class=\"hljs-doctag\">@param <span class=\"hljs-type\">{number[]}</span> <span class=\"hljs-variable\">nums</span></span>\r\n * <span class=\"hljs-doctag\">@param <span class=\"hljs-type\">{number}</span> <span class=\"hljs-variable\">target</span></span>\r\n * <span class=\"hljs-doctag\">@return <span class=\"hljs-type\">{number[]}</span></span>\r\n */</span>\r\n<span class=\"hljs-comment\">// var twoSum = function (nums, target) {</span>\r\n<span class=\"hljs-comment\">// for (let i = 0, j = nums.length - 1; i < j; i++) {</span>\r\n<span class=\"hljs-comment\">// for (let x = i + 1, z = nums.length; x < z; x++) {</span>\r\n<span class=\"hljs-comment\">// if (nums[i] + nums[x] === target) {</span>\r\n<span class=\"hljs-comment\">// return [i, x];</span>\r\n<span class=\"hljs-comment\">// }</span>\r\n<span class=\"hljs-comment\">// }</span>\r\n<span class=\"hljs-comment\">// }</span>\r\n<span class=\"hljs-comment\">// };</span>\r\n<span class=\"hljs-keyword\">var</span> twoSum = <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">nums, target</span>) </span>{\r\n <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">let</span> i = <span class=\"hljs-number\">0</span>; i < nums.length; i++) {\r\n <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">let</span> j = i+<span class=\"hljs-number\">1</span>; j < nums.length; j++) {\r\n <span class=\"hljs-keyword\">if</span> (nums[i] + nums[j] == target ) {\r\n <span class=\"hljs-keyword\">return</span> [i, j]\r\n }\r\n }\r\n }\r\n};\r\n<span class=\"hljs-built_in\">console</span>.log(twoSum([<span class=\"hljs-number\">1</span>, <span class=\"hljs-number\">2</span>, <span class=\"hljs-number\">3</span>], <span class=\"hljs-number\">4</span>));</code></pre><p><span style=\"font-weight: normal;\"><i><font size=\"3\">//上面被注释的代码是身边别人写的</font></i></span></p><p>!!:</p><p>主要意思就是一个函数有两个参数一个参数是数组,另一个参数是数字,数组内有几个值也都是数字,他们当中有两个数字加起来的和为函数的第二个参数也就是图片中的target</p><p>注解:</p><pre type=\"JavaScript\"><code><span class=\"hljs-keyword\">//既然是在数组中寻找两个数字的和那么大家第一反应应该都是两个循环</span></code><code><span class=\"hljs-keyword\"><br></span></code><code><span class=\"hljs-keyword\">var</span> twoSum = <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">nums, target</span>) </span>{\r\n <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">let</span> i = <span class=\"hljs-number\">0</span>; i < nums.length; i++) {\r\n <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">let</span> j = i+<span class=\"hljs-number\">1</span>; j < nums.length; j++) {</code><code><br></code><code>//简单的来说就是一直循环数组来判断两数之和,在两数之和为target时返回函数\r\n <span class=\"hljs-keyword\">if</span> (nums[i] + nums[j] == target ) {\r\n <span class=\"hljs-keyword\">return</span> [i, j]\r\n }\r\n }\r\n }\r\n};\r\n<span class=\"hljs-built_in\">console</span>.log(twoSum([<span class=\"hljs-number\">1</span>, <span class=\"hljs-number\">2</span>, <span class=\"hljs-number\">3</span>], <span class=\"hljs-number\">4</span>));</code></pre></div>', '2021-03-29 00:00:00', 'leetcode第一题两数之和', 53),
(11, 'leetcode3', 'admin', 'JavaScript,Leetcode', '<div><p>其实这篇我是有点不好意思发出来的</p><p><b id=\"eewjk\"><font size=\"5\">原因:</font></b></p><p><img src=\"1f26rkn1i.png\"></p><p>后来想了想毕竟做出来了,也是分享一下以后有什么新方法可以在下面接着写。</p><p>原题:<a href=\"https://leetcode-cn.com/problemset/all/\" target=\"_blank\">https://leetcode-cn.com/problemset/all/</a></p><p><img src=\"1f26rnhp9.png\"></p><p>主要就是在一串长字符串里面找最长的子串,</p><pre type=\"JavaScript\"><code><span class=\"hljs-built_in\">var</span> lengthOfLongestSubstring = function (s) {\r\n <span class=\"hljs-built_in\">let</span> arr = [];\r\n <span class=\"hljs-built_in\">let</span> <span class=\"hljs-built_in\">max</span> = <span class=\"hljs-number\">0</span>;\r\n <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-built_in\">let</span> i = <span class=\"hljs-number\">0</span>; i < s.<span class=\"hljs-built_in\">length</span>; i++) {\r\n <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-built_in\">let</span> j = i; j < s.<span class=\"hljs-built_in\">length</span>; j++) {\r\n <span class=\"hljs-keyword\">if</span> (!arr.includes(s[j])) {\r\n arr.<span class=\"hljs-built_in\">push</span>(s[j])\r\n } <span class=\"hljs-keyword\">else</span> {\r\n <span class=\"hljs-built_in\">max</span> = arr.<span class=\"hljs-built_in\">length</span> > <span class=\"hljs-built_in\">max</span> ? arr.<span class=\"hljs-built_in\">length</span> : <span class=\"hljs-built_in\">max</span>;\r\n arr = [];\r\n <span class=\"hljs-built_in\">break</span>;\r\n }\r\n }\r\n }\r\n <span class=\"hljs-built_in\">return</span> s.<span class=\"hljs-built_in\">length</span> == <span class=\"hljs-number\">1</span> ? <span class=\"hljs-number\">1</span> : <span class=\"hljs-built_in\">max</span>;\r\n};</code></pre><p>多次遍历,并且创造一个空数组来暂存数据,每次存入数组是判断数组是否有相同的值,如果有就清空数组,并且判断本次的长度是否长于上次,如果长于上次就更新,之后终止循环,在循环结束后判断,因为题目特意说明\" \"空格也算长度,但是本程序没没有针对单个空格的判断,就只能在return的时候判断长度是否为1了<span style=\"font-size: 1em;\"> </span></p></div>', '2021-04-01 00:00:00', 'leetcode第三题无重复字符的最长子串', 45),
(12, 'nodeexpress', 'admin', 'Node', '<div><p>作为前端程序员在平时也难免会编写一点后台Demo级别的代码,在语言选择上通常会选择Node.js作为主力工具,框架通常会选择老牌框架Express,在本文会讲解一个使用Express创建接口和搭建基本的Express</p><h2 id=\"5bmeq\">创建项目</h2><ol><li>创建一个文件夹后使用cd projectname 进入文件夹<br></li><li>使用npm init创建项目 输入命令行之后一路回车就行</li><li>安装常用的依赖 yarn add express cors nodemon</li><li>创建基本的文件夹结构<br><img src=\"1630914822114.jpg\"><br></li><li>创建一个src文件夹用来存放主要代码,创建一个route来存放接口文件</li><li>将package.json中的script中创建一个start命令:<i>\"start\": \"nodemon --watch ./src ./src/index.js\"</i> 这样使用yarn start就可以启动项目</li></ol><h2 id=\"kztuz\">配置入口</h2><h3 id=\"hrk5w\"> index.js</h3><pre><code class=\"NodeJS\">const express = require(<span class=\"hljs-string\">\"express\"</span>);\r\nconst app = express();//使用Express</code><code class=\"NodeJS\">//接收POST请求的参数\r\napp.use(\r\n express.urlencoded({\r\n extended: false,\r\n })\r\n);\r\napp.use(express.json());\r\n//通常设置robots.txt禁止搜索引擎扫描</code><code class=\"NodeJS\">app.use(<span class=\"hljs-string\">\'/robots.txt\'</span>, express.static(<span class=\"hljs-string\">\'./file/robots.txt\'</span>));\r\n//设置跨域\r\nconst cors = require(<span class=\"hljs-string\">\"cors\"</span>);\r\napp.use(cors());\r\n\r\n//监听端口\r\napp.listen(<span class=\"hljs-number\">3000</span>, () => console.log(`run`));\r\n</code></pre><p>这样是配置基本的入口文件<br></p><ol><li>配置接口</li></ol><p>创建一个.js的文件</p><pre><code class=\"NodeJS\"><span class=\"hljs-keyword\">const</span> express = require(<span class=\"hljs-string\">\'express\'</span>);\r\n<span class=\"hljs-keyword\">const</span> app = express();\r\n<span class=\"hljs-keyword\">const</span> router = express.Router();\r\n\r\nrouter.<span class=\"hljs-keyword\">get</span>(<span class=\"hljs-string\">\'/\'</span>, <span class=\"hljs-keyword\">async</span> (req, res) => {\r\n <span class=\"hljs-keyword\">let</span> <span class=\"hljs-keyword\">get</span> = req.query; <span class=\"hljs-comment\">//接收get请求参数</span>\r\n <span class=\"hljs-keyword\">let</span> post = req.body; <span class=\"hljs-comment\">//接收post请求参数</span>\r\n res.json({\r\n key: <span class=\"hljs-string\">\'value\'</span>\r\n })\r\n})\r\nmodule.exports = router;</code></pre><ol><li>引入express和express.router</li><li>设置路由的请求方式(常用:get,post,put,delete),设置路由请求路径</li><li>req是请求相关信息,我们可以使用req.query和req.body来接收请求参数</li><li>对数据库或相关业务逻辑进行处理之后我们使用res进行返回res常用的返回方式就是json,返回对象前端接收进行服务器渲染,方便前后端分离</li></ol><h4 id=\"r7rkz\">路由引用</h4><p>在index.js中将编写好的接口挂载到入口文件中</p><pre><code class=\"NodeJS\" id=\"vb4de\">app.<span class=\"hljs-keyword\">use</span>(<span class=\"hljs-string\">\'/\'</span>, <span class=\"hljs-keyword\">require</span>(<span class=\"hljs-string\">\'./route/express\'</span>));</code></pre><h4 id=\"jtuzl\">运行 yarn start 运行项目</h4><p>我们使用postman请求一下</p><p><img src=\"1630920797664.jpg\"><br><img src=\"1630920908483.jpg\"><br>成功接收到服务器返回的数据</p><h4 id=\"bp0u8\">批量引用</h4><p>如果有很多接口我们需要一个一个引用是一件很麻烦的事情,我们可以使用循环将route中的文件都视为路由文件统一引用</p><pre><code class=\"NodeJS\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">fileDisplay</span>(<span class=\"hljs-params\">filePath</span>) </span>{\r\n fs.readdir(filePath, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">err, files</span>) </span>{\r\n <span class=\"hljs-keyword\">if</span> (err) {\r\n <span class=\"hljs-built_in\">console</span>.warn(err);\r\n } <span class=\"hljs-keyword\">else</span> {\r\n files.forEach(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">filename</span>) </span>{\r\n <span class=\"hljs-keyword\">var</span> filedir = path.join(filePath, filename);\r\n fs.stat(filedir, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">eror, stats</span>) </span>{\r\n <span class=\"hljs-keyword\">if</span> (eror) {\r\n <span class=\"hljs-built_in\">console</span>.warn(<span class=\"hljs-string\">\'获取文件stats失败\'</span>);\r\n } <span class=\"hljs-keyword\">else</span> {\r\n <span class=\"hljs-keyword\">var</span> isFile= stats.isFile();\r\n <span class=\"hljs-keyword\">var</span> isDir = stats.isDirectory();\r\n <span class=\"hljs-keyword\">if</span> (isFile) {\r\n app.use(<span class=\"hljs-string\">\'/\'</span>, <span class=\"hljs-built_in\">require</span>(filedir));\r\n }\r\n <span class=\"hljs-keyword\">if</span> (isDir) {\r\n fileDisplay(filedir);\r\n }\r\n }\r\n });\r\n });\r\n }\r\n });\r\n}\r\n<span class=\"hljs-keyword\">const</span> fs = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">\'fs\'</span>);\r\n<span class=\"hljs-keyword\">var</span> path= <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">\'path\'</span>);\r\n<span class=\"hljs-keyword\">var</span> filePath = path.resolve(<span class=\"hljs-string\">\'./src/route\'</span>);//填写存放路由的文件夹\r\nfileDisplay(filePath);</code></pre><p><br></p><div><br></div></div>', '2021-09-06 00:00:00', 'Express的使用', 89),
(13, 'leetcode7', 'admin', 'Leetcode,JavaScript', '<div><p><a href=\"https://leetcode-cn.com/problems/reverse-integer/\" target=\"_blank\">原题地址</a><br></p><p><img src=\"1626749852253.jpg\"></p><p><img src=\"1626749882776.jpg\"></p><h2><b id=\"rn9j9\">题解:</b></h2><p><img src=\"1626749926024.jpg\"></p><ol><li>不清楚参数是整数还是负数,我们先用变量保存</li><li>获取参数是绝对值</li><li>因为js中数组有内置的反转函数(reverse),我们将数字转为字符串在转为数组后进行反转,在转为字符串<br></li><li>转为字符串后我们将它与model变量相乘,如果原本的参数大于1就*1,如果原本参数就是小于1的负数就*-1</li><li>因为题目规定了反转好的数字有范围限制,我们来进行范围对比</li><li>**计算符号为求次幂,例如:2**31就是求2的31次幂</li><li>对比后返回计算结果</li></ol><p><br></p></div>', '2021-07-20 00:00:00', 'LeetCode7 整数反转', 34),
(14, 'leetcode8', 'admin', 'JavaScript,Leetcode', '<div> <p><a href=\"https://leetcode-cn.com/problems/string-to-integer-atoi/submissions/\" target=\"_blank\">原题地址</a></p><p><img src=\"1627192598614.jpg\"></p><p>看题可得;</p><ol><li>传进来的参数是字符串,需要返回数字</li><li>我们返回的数字要在范围之内,并且要注意正负</li><li>如果第一个除符号之外的字符,无法转换为数字,直接返回0</li></ol><p><b><font size=\"5\">解:</font></b></p><p><img src=\"1627192542158.jpg\"><br><br></p><p><img src=\"1627192809814.jpg\"></p><p><br></p><ol><li>在JS中有一个投机取巧的小方法:parseInt()</li><li>这个函数能直接解决本题大部分需求<a href=\"https://www.w3school.com.cn/js/jsref_parseInt.asp\" target=\"_blank\">参考</a></li><li>如果无法解析出数字会返回NaN,如果可以解析出数字会直接返回数字我们只需要判断范围就好</li><li>我们定义变量number后直接判断,如果isNaN成立说明没有解析出数字,直接返回0</li><li>如果没返回0,开始判断范围,题目中要求如果超出范围直接返回范围的值就可以,我们使用**符号来求次幂(Math.pow函数也可以达到同样的效果)</li><li>如果不在指定范围内我们返回范围,如果在指定范围内我们只需要返回number本身就可以</li></ol><p>!!:也可以用s.split(\'\')将字符串转为数组后遍历使用变量叠加的方法,需要正则表达式来逐个校验,有兴趣想小伙伴可以试一下</p><p><br></p></div>', '2021-07-25 00:00:00', 'Leetcode8字符串转换整数 (atoi)', 45);
INSERT INTO `article` (`id`, `router`, `author`, `type`, `article`, `time`, `title`, `view_count`) VALUES
(15, 'react', 'admin', 'React', '<div><p><b>什么是React?</b><br></p><p>React是Facebook开源的用于构建用户界面的 JavaScript 库,通过js开控制页面dom,整个网站只有一个HTML文件,通过js来控制页面原素的增删改查</p><p><b>为什么使用React?</b></p><ol><li>组件化开发,方便复用,也是当前WEB开发趋势</li><li>技术成熟,社区活跃,解决方案众多,配件更加齐全</li><li>Facebook大厂维护可靠</li><li>核心库使用简单,支持服务器渲染适合大部分B、C端产品</li></ol><h2 id=\"2vh84\">JSX/TSX</h2><p>jsx是编写React组件的一种方式(tsx是在jsx中使用typescript),可以让我们在js中编写HTML,</p><p>jsx组件为函数组件和类组件,本文主讲解函数组件,在react v16.8之前函数组件是只能写静态UI的不能动态控制DOM,后来React发布了React Hooks</p><pre><code class=\"React\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">componentsname</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我是一个组件<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> componentsname;\r\n</code></pre><p><br></p><p>我们定义一个函数并且导出,这就是一个简单的函数组件,此时页面显示的是一个<i style=\"background-color: rgb(238, 236, 224);\"><div>我是一个组件</div></i>,在jsx中所有展示的UI都需要由函数return出去,为了方便换行return一般都是添加括号,不同于Vue3,React必须有一个根组件,我们在这里可以选择用空标签代替<i style=\"background-color: rgb(238, 236, 224);\"><></></i>(这在React中是被允许的),很多小伙伴都要学过Vue,知道Vue有个很好用的东西叫双向绑定,在React中也为我们提供了对应的方法</p><pre><code class=\"React\"><span class=\"hljs-keyword\">import</span> { useState, Fragment } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">componentsname</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">const</span> [num, setNum] = useState(<span class=\"hljs-number\">0</span>);\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我现在显示的数字是:{num}<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> componentsname;\r\n</code></pre><p><br></p><p>打出这段代码后页面显示的应该是:我现在显示的数字是0,对于这段代码<i style=\"background-color: rgb(238, 236, 224);\">const [num, setNum] = useState(0);</i><span style=\"background-color: rgb(255, 255, 255);\"> 很多小伙伴不是很理解</span></p><p>这个相当于定义了一个变量,第一个参数为变量,第二个参数为触发双向绑定的函数,useState中的参数是初始值,在jsx中也有类似Vue v-on指令的事件绑定</p><h2 id=\"duke9\">JSX中事件绑定</h2><pre><code class=\"React\"><span class=\"hljs-keyword\">import</span> { useState, Fragment } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">componentsname</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">const</span> [num, setNum] = useState(<span class=\"hljs-number\">0</span>);\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我现在显示的数字是:{num}<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">button</span> <span class=\"hljs-attr\">onClick</span>=<span class=\"hljs-string\">{()</span> =></span> setNum(num+1)}><span class=\"hljs-tag\"></<span class=\"hljs-name\">button</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> componentsname;\r\n</code></pre><p><br></p><p>不同的是区别于Vue和原生js,jsx中事件时onEvent,小驼峰命名法,on小写事件名字首字母大写,这样每当我们点击一次按钮数字就会加1</p><p>当然只有在传递参数的时候<i style=\"background-color: rgb(238, 236, 224);\"><button onClick={() => setNum(num + 1)}></button></i>才会写为返回一个函数</p><p>如果触发事件不需要参数:</p><pre><code class=\"React\"><span class=\"hljs-keyword\">import</span> { useState } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">componentsname</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">const</span> [num, setNum] = useState(<span class=\"hljs-number\">0</span>);\r\n <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">show</span>(<span class=\"hljs-params\"></span>) </span>{\r\n alert(<span class=\"hljs-string\">`现在的数字是:<span class=\"hljs-subst\">${num}</span>`</span>)\r\n }\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我现在显示的数字是:{num}<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">button</span> <span class=\"hljs-attr\">onClick</span>=<span class=\"hljs-string\">{show}</span>></span><span class=\"hljs-tag\"></<span class=\"hljs-name\">button</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> componentsname;\r\n</code></pre><p><br></p><p>这样编写也可以绑定事件,前提是函数不需要传递参数</p><h2 id=\"8uvyd\">JSX中条件渲染</h2><p>如果想想v-if一样执行条件渲染该怎么办呢?</p><pre><code class=\"React\"> <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我现在显示的数字是:{num}<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n {num>4&&<span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>num大于4了<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>}\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">button</span> <span class=\"hljs-attr\">onClick</span>=<span class=\"hljs-string\">{()</span> =></span> setNum(num+1)}><span class=\"hljs-tag\"></<span class=\"hljs-name\">button</span>></span></code></pre><p><br></p><p>使用js中的判断方法&&前面是判断条件后面是DOM节点,节点是肯定被转化为true的,只要前面的条件满足节点就会被显示出来,如果想要实现if...else...,可以在函数变量中使用,如果实在return的UI层使用就只能使用三元表达式,因为jsxUI层只支持表达式</p><pre><code class=\"React\"><span class=\"hljs-keyword\">import</span> { useState } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">componentsname</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">const</span> [num, setNum] = useState(<span class=\"hljs-number\">0</span>);\r\n <span class=\"hljs-keyword\">const</span> UI = <span class=\"hljs-function\">() =></span> num > <span class=\"hljs-number\">4</span> && <span class=\"xml\"><span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>num大于4了<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span></span>;\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我现在显示的数字是:{num}<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n {UI}\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">button</span> <span class=\"hljs-attr\">onClick</span>=<span class=\"hljs-string\">{()</span> =></span> setNum(num + 1)}><span class=\"hljs-tag\"></<span class=\"hljs-name\">button</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> componentsname;\r\n</code></pre><p><br></p><p>或者在UI中使用三元表达式</p><pre><code class=\"React\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我现在显示的数字是:{num}<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n {num>4?<span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>num大于4了<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>:null}\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">button</span> <span class=\"hljs-attr\">onClick</span>=<span class=\"hljs-string\">{()</span> =></span> setNum(num + 1)}><span class=\"hljs-tag\"></<span class=\"hljs-name\">button</span>></span>\r\n <span class=\"hljs-tag\"></></span></code></pre><p><br></p><p>相同的需求React中有非常多的实现方法,视开发者或者开发团队的习惯而定</p><h2 id=\"kpdfl\">JSX中循环渲染</h2><p>在循环渲染中我们可以使用js自带的数组函数map</p><pre><code class=\"React\"><span class=\"hljs-keyword\">import</span> { useState, Fragment } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">componentsname</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">const</span> data = [\r\n {\r\n <span class=\"hljs-attr\">name</span>: <span class=\"hljs-string\">\"小刘\"</span>,\r\n <span class=\"hljs-attr\">age</span>: <span class=\"hljs-number\">18</span>,\r\n },\r\n {\r\n <span class=\"hljs-attr\">name</span>: <span class=\"hljs-string\">\"小王\"</span>,\r\n <span class=\"hljs-attr\">age</span>: <span class=\"hljs-number\">17</span>,\r\n },\r\n {\r\n <span class=\"hljs-attr\">name</span>: <span class=\"hljs-string\">\"小红\"</span>,\r\n <span class=\"hljs-attr\">age</span>: <span class=\"hljs-number\">11</span>,\r\n },\r\n ];\r\n <span class=\"hljs-keyword\">const</span> UI = data.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">item, index</span>) =></span> {\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><<span class=\"hljs-name\">Fragment</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">p</span>></span>\r\n 我是第{index + 1}位同学{item.name}\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">p</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">p</span>></span>我今年{item.age}岁了<span class=\"hljs-tag\"></<span class=\"hljs-name\">p</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">Fragment</span>></span></span>\r\n );\r\n });\r\n return <>{UI}</>;//将循环渲染的DOM返回\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> componentsname;\r\n</code></pre><p><br></p><p>这样的话就会出现3个div标签,每个div标签中有两个p标签,分别介绍自己的名字和年龄,但是在控制台式会报出错误的,和Vue一样,在虚拟DOM中节点都会有唯一一个key作为id,一遍更加方便的被React内部操作,这个<b>Fragment</b>也相当于<></>只不过这个标签可以绑定key直接中空标签React是不允许绑定key的。</p><pre><code class=\"React\"><span class=\"xml\"> <span class=\"hljs-tag\"><<span class=\"hljs-name\">Fragment</span> <span class=\"hljs-attr\">key</span>=</span></span><span class=\"hljs-template-variable\">{item.name}</span><span class=\"xml\"><span class=\"hljs-tag\">></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">p</span>></span>\r\n 我是第</span><span class=\"hljs-template-variable\">{index + 1}</span><span class=\"xml\">位同学</span><span class=\"hljs-template-variable\">{item.name}</span><span class=\"xml\">\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">p</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">p</span>></span>我今年</span><span class=\"hljs-template-variable\">{item.age}</span><span class=\"xml\">岁了<span class=\"hljs-tag\"></<span class=\"hljs-name\">p</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">Fragment</span>></span></span></code></pre><p>这下我们将key绑定到Fragment标签上,报错就会显示,页面会展示同学们的年龄和名字,在map中所有需要展示在UI层的DOM都是要return的和原生JS相同</p><p>关于<a href=\"https://blogweb.cn/article/reacthooks\" target=\"_blank\">React函数组件生命周期</a>可以在这篇文章学习</p><h2 id=\"ck1a9\"><b></b>编写CSS样式</h2><p>因为React是一个js库,DOM是由js生成的所以在绑定class的时候我们使用的className:</p><pre><code class=\"React\"><<span class=\"hljs-keyword\">div</span> className=<span class=\"hljs-string\">\"style\"</span>>我是一个红色的<span class=\"hljs-keyword\">div</span></<span class=\"hljs-keyword\">div</span>></code></pre><p>css方案推荐使用sass,可以去我的页面搜索sass相关教程</p><p>有的时候我们也会编写行内css</p><pre><code class=\"JavaScript\"><span class=\"xml\"> <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span> <span class=\"hljs-attr\">style</span>=</span></span><span class=\"hljs-template-variable\">{{<span class=\"hljs-name\">backgroundColor:</span><span class=\"hljs-string\">\'red\'</span>}}</span><span class=\"xml\"><span class=\"hljs-tag\">></span>我是一个红色的div<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span></span></code></pre><p>如果你的属性key不是一个单词(如:background-color),请将他们转为小驼峰backgroundColor,属性的value类型为字符串可以使用字符串拼接,有的小伙伴会有疑问,为什么style绑定的是两个大括号?其实问题很简单,jsx中与变量绑定的属性都是使用大括号,因为绑定的行内样式本省也是一个对象,所以外面的大括号提示这是一个jsx的绑定,里面的大括号是一个对象,记录了需要绑定的样式</p><pre><code class=\"React\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">componentsname</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">const</span> style = {\r\n <span class=\"hljs-attr\">backgroundColor</span>: <span class=\"hljs-string\">\"red\"</span>,\r\n <span class=\"hljs-attr\">color</span>: <span class=\"hljs-string\">\"black\"</span>,\r\n };\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span> <span class=\"hljs-attr\">style</span>=<span class=\"hljs-string\">{style}</span>></span>我是一个红色的div<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> componentsname;</code></pre><p>当然我们也可以使用上面的这种方式来绑定,这种方式更直观的显示出style属性其实绑定的是一个js对象</p><h2 id=\"3rua6\">总结:</h2><ol><li>React与Vue基本相同也是单页面应用</li><li>循环渲染使用js的map,将需要使用的DOM节点return出去</li><li>条件渲染可以使用&&判断,或者使用三元表达式执行更复杂的判断</li><li>事件绑定采用onEvent小驼峰命名,on小写事件名字首字母大写,如果需要传递参数使用()=>function(param)</li><li>绑定class请使用className,绑定行内样式使用style={样式对象}</li></ol></div>', '2021-07-02 00:00:00', 'React入门', 25),
(16, 'nodeffmpeg', 'admin', 'Node,后端', '<p>最近接了一个公司的小项目,上一位程序要留下的最大的问题就是:使用node对音频和视频进行处理?</p><p>他们决定找我丢下之前的包袱重新进行开发,为了这个问题当时研究了大半天,决定分享出来</p><p>主要使用的就是一个叫<a href=\"https://www.ffmpeg.org/\" target=\"_blank\">ffmpeg</a>的工具,而且使用有两种使用方法:</p><p>一种是内置的工具需要下载并且配置环境变量、<span style=\"font-size: 1em;\">另一种是使用</span><a href=\"https://www.npmjs.com/package/ffmpeg\" target=\"_blank\" style=\"font-size: 1em;\">npm内置的包</a></p><p>我当时是没有发现npm包的办法所以使用了cmd命令的方法:</p><p>首先在我们的电脑中安装ffmpag工具(<a href=\"https://zhuanlan.zhihu.com/p/118362010\" target=\"_blank\">方法传送门</a><span style=\"font-size: 1em;\">),安装后使用</span>npm install node-cmd –save安装运行命令的工具</p><pre type=\"JavaScript\"><code> let <span class=\"hljs-keyword\">cmd</span><span class=\"bash\"> = `ffmpeg -i <span class=\"hljs-string\">\"./audio/<span class=\"hljs-variable\">${name}</span>\"</span> -ab 128 <span class=\"hljs-string\">\"./audio/min-<span class=\"hljs-variable\">${name}</span>\"</span>`</span></code></pre><p>这是我当时的使用方法 第一个地址是输入地址,第二个地址是输出地址,128是采样率。</p><p>当时我还加入了 <i></i><span style=\"background-color: rgb(241, 241, 241);\"><i>-f fmt 强迫采用格式fmt </i>将输出格式转为了mp3音频也是同样的处理方法</span></p><p><span style=\"background-color: rgb(241, 241, 241);\">参数参考:</span><span style=\"background-color: rgb(241, 241, 241);\">-r fps 设置帧频 </span></p><p><span style=\"background-color: rgb(241, 241, 241);\">参数查询:</span><a href=\"https://www.jianshu.com/p/049d03705a81\" target=\"_blank\">参数参考</a><br></p><p>设置好cmd命令行后:<br></p><pre type=\"nodeJS\"><code>\n<span class=\"hljs-keyword\">var</span> nodeCmd = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">\'node-cmd\'</span>);\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">runCmdTest</span>(<span class=\"hljs-params\"></span>) </span>{\n nodeCmd.get(\n <font color=\"#98c379\">cmd</font>,\n <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span>(<span class=\"hljs-params\">err, data, stderr</span>)</span>{\n <span class=\"hljs-built_in\">console</span>.log(data);</code><code>//运行后的回调函数\n }\n );\n\n}</code></pre><p><br></p>', '2021-04-18 00:00:00', 'node压缩处理音频和视频', 63),
(17, 'reacthooks', 'admin', 'React,JavaScript', '<div><p>在<b>React v16.8</b>中官方推出了<b>Hooks</b>,该方案会取代大部分的类组件主要解决了函数组件无法使用状态的问题</p><h1 id=\"5qb0u\">React Hooks</h1><p>在官方为我们提供的Hook中,主要解决的就是使用状态和生命周期的问题</p><h2 id=\"inlib\">useState</h2><p>在原来的函数组件中我们只可以做静态展示内容无法实现双向绑定的交互:</p><pre type=\"React\"><code><span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">Component</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我是一个函数组件<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n}\r\n</code></pre><p>因此官方提供了<span style=\"font-size: 1em;\"><b>useState</b>钩子</span><br></p><pre type=\"React\"><code><span class=\"hljs-keyword\">import</span> React, { useState } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">Component</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">const</span> [state, setstate] = useState(<span class=\"hljs-number\">0</span>);\r\n <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">add</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">let</span> num = state + <span class=\"hljs-number\">1</span>;\r\n setstate(num);\r\n }\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我是一个函数组件<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>现在的数字是:{state}<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">button</span> <span class=\"hljs-attr\">onClick</span>=<span class=\"hljs-string\">{add}</span>></span>点我加一<span class=\"hljs-tag\"></<span class=\"hljs-name\">button</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n}\r\n</code></pre><p>对于const定义的数组大多是人还是比较陌生的,这就是官方所给的hooks,数组中第一个参数为设置的值,第二个参数是修改这个值的函数,正向add函数中的那样setState中的参数就是要修改后的值useState函数中的0就是默认的值,当然也相当于类组件中的<span style=\"font-size: 1em;\">getDerivedStateFromProps生命周期可以在useState函数中直接使用props传来的值</span><br></p><h2><span style=\"font-size: 1em;\" id=\"vl34k\">useEffect</span></h2><p><span style=\"font-size: 1em;\" id=\"f91fv\">接下来还是生命周期问题</span>官方提供<span style=\"font-size: 1em;\"><b>useEffect</b>函数来解决</span></p><pre type=\"React\"><code> useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()</span> =></span> {\r\n\r\n \r\n })</code></pre><p>这个函数相当于每次更新都是使用</p><pre type=\"React\"><code> useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()</span> =></span> {\r\n\r\n \r\n },[])</code></pre><p>如果加了这个就只在组件挂载后使用了,可能有人会疑惑第二个参数为啥是一个空数组呢?<br></p><p>这个就行相当于vue中的watch函数</p><pre type=\"React\"><code> useEffect(() => {\r\n\r\n \r\n },[<span class=\"hljs-keyword\">state</span>])</code></pre><p>这样的话就相当于监听state,每次state变化了就会执行函数<br></p><p>除此之外还提供了组件销毁的生命周期</p><pre type=\"React\"><code> useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()</span> =></span> {\r\n <span class=\"hljs-built_in\">console</span>.log(<span class=\"hljs-string\">\"组件创建\"</span>);\r\n <span class=\"hljs-keyword\">return</span> () => {\r\n <span class=\"hljs-built_in\">console</span>.log(<span class=\"hljs-string\">\"组件销毁\"</span>);\r\n };\r\n }, []);</code></pre><p>此方法需要在函数中在返回一个函数可以与组件创建生命周期写在一个函数中<br></p><h2 id=\"4llkr\">useMemo</h2><h2></h2><p>这个Api相当于Vue中的computed,也算是React中的计算属性</p><p>使用方法:</p><pre><p><code>const articleData = useMemo(() => data, [data]);<br></code></p></pre><p><i>一共有两个参数</i>第一个是函数有一个返回值返回的是最终的计算结果,第二个和useEffect的数组差不多,是一个依赖项,只要数组中的元素有变化就会重新计算参数函数,useMemo的返回值为计算结果</p><h2>useRef</h2><p>在React函数组件中我们可以使用useRef来保存变量和获取DOM(相当于类组件中的createRef)</p><p>和createRef相同在我们我们定义的ref也是一个对象</p><pre><code>let ref=useRef(0)//{current:0}</code></pre><p>这样我们就定义了一个ref来存储变量,修改这个变量也是通过修改current</p><p>例如:ref.current=1</p><pre><code>let dom=ref(null)\r\nreturn(\r\n <div ref={dom}>我是一个被useRef选中的div标签</div>\r\n)</code></pre><p>这样的话我们通过获取dom的current属性就可以直接拿到被选中的标签了<br></p><h2>useContext</h2><p>这个Hooks主要用于组件传值不同于props这个Hooks是通过引入使用的所有不会对组件层级有限制</p><p>1.我们需要先定义useContext</p><pre><code>const Count= createContext(0)</code></pre><p>2.使用定义的context包裹需要接收值的组件将要传递的数据通过value传递</p><pre><code><Count.Provider value={data}>\r\n<Route/>\r\n<Route/>\r\n</Count.Provider></code></pre><p>3.使用Context</p><pre><code>function child() {\r\n let data = useContext(Count);\r\n return <div>{data}</div>;\r\n}</code></pre><p>!!:如果是跨JSX文件使用在步骤1 时需要将createContext创建在最外层导出使用时导入</p><h2>useReducer</h2><p>这个Hooks主要是可以配合上文的useContext来实现状态管理类似(redux/vuex)</p><p>useReducer也是有两个参数,第一个参数是进行状态设置,第二个是默认状态,也就是初始值<br></p><p>1.创建useReducer</p><pre><code>const [value, setValue] = useReducer((state, action) => {\r\n value = action;\r\n}, 0);</code></pre><p>state为被操作值</p><p>action为操作时传来的值</p><p>value是计算后的响应式值<br></p><p>setValue是重新计算:setValue(params) //参数为useReducer中的action</p><p>2.修改结果<br></p><pre><code>setValue(value+1)</code></pre><p>如果我们这样触发那么所有使用到value变量的地方都会加1</p><p>!!:我们可以将这两个Hooks结合到一起实现状态管理,与其他状态管理相同主要操作也是发布/订阅,由useReducer进行数据保存和修改,后由useContext来进行发布<br></p><p><br></p><h2>useCallback</h2><p>这个Hooks相当于一个节流函数</p><pre><code>const memoizedCallback = useCallback(\r\n () => {\r\n doSomething(a, b);\r\n },\r\n [a, b],\r\n);</code></pre><p>他返回的相当于一个事件,第二个参数也是依赖项,只有某个依赖项有变化函数才可以执行</p><p><br></p><p><b>这些就是基本的React Hooks剩余两个Hooks为useLayoutEffect和useImperativeHandle以及useDebugValue</b><br></p><p><br></p><p>总结:</p><ol><li>Hooks提高了工作效率,并且让组件更加简单</li><li>并且可以自行解决状态管理问题不需要依赖第三方</li><li>部分函数可以集成到一个Hooks中更加方便抽离</li></ol></div>', '2021-08-25 00:00:00', 'React Hooks', 54),
(18, 'reactuseclass', 'admin', 'React,JavaScript', '<div><p><b>React</b>中主要分为<b>类组件</b>和函数组件,在本文主要讲解为react中使用类组件:</p><p>我们先定义并导出一个叫Com的类组件</p><pre type=\"React\"><code><span class=\"hljs-keyword\">import</span> <span class=\"hljs-type\">React</span>, { <span class=\"hljs-type\">Component</span> } from <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Com</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">Component</span> </span>{\r\n \r\n}\r\nexport <span class=\"hljs-keyword\">default</span> <span class=\"hljs-type\">Com</span>;</code></pre><p><span style=\"font-size: 1em;\">接下来我们需要渲染一些Dom,并且定义一些状态数据,在react中响应式数据定义在state变量中</span><br></p><pre type=\"React\"><code><span class=\"hljs-keyword\">import</span> React, { Component } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Com</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">Component</span> </span>{\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">constructor</span>(<span class=\"hljs-params\">props</span>)</span> {\r\n <span class=\"hljs-built_in\">super</span>(props);\r\n <span class=\"hljs-built_in\">this</span>.state={\r\n <span class=\"hljs-attr\">time</span>:<span class=\"hljs-number\">5</span>\r\n }\r\n }\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">render</span>(<span class=\"hljs-params\"></span>)</span> {\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我是Com组件,现在是{this.state.time}点<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n }\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> Com;</code></pre><p><span style=\"font-size: 1em;\">现在我们输出了一点div,并报出的现在的时间,每当我们修改state中的time属性是div中输出的时间就会随着改变</span><br></p><pre type=\"React\"><code><span class=\"hljs-keyword\">import</span> React, { Component } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Com</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">Component</span> </span>{\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">constructor</span>(<span class=\"hljs-params\">props</span>)</span> {\r\n <span class=\"hljs-built_in\">super</span>(props);\r\n <span class=\"hljs-built_in\">this</span>.state = {\r\n <span class=\"hljs-attr\">time</span>: <span class=\"hljs-number\">5</span>,\r\n };\r\n }\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">componentDidMount</span>(<span class=\"hljs-params\"></span>)</span> {\r\n <span class=\"hljs-built_in\">this</span>.setState({\r\n <span class=\"hljs-attr\">time</span>: <span class=\"hljs-number\">6</span>,\r\n });\r\n }\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">render</span>(<span class=\"hljs-params\"></span>)</span> {\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>我是Com组件,现在是{this.state.time}点<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n }\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> Com;</code></pre><p><span style=\"font-size: 1em;\">我在生命周期函数中添加了一段setState来修改属性,现在渲染出的div中的时间也变成了6点</span><br></p><p>现在还有一个需求,就是每次点击渲染的div文字就需要让time加一,这就需要定义点击事件</p><pre type=\"React\"><code><span class=\"hljs-keyword\">import</span> React, { Component } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Com</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">Component</span> </span>{\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">constructor</span>(<span class=\"hljs-params\">props</span>)</span> {\r\n <span class=\"hljs-built_in\">super</span>(props);\r\n <span class=\"hljs-built_in\">this</span>.state = {\r\n <span class=\"hljs-attr\">time</span>: <span class=\"hljs-number\">5</span>,\r\n };\r\n <span class=\"hljs-built_in\">this</span>.newTime=<span class=\"hljs-built_in\">this</span>.newTime.bind(<span class=\"hljs-built_in\">this</span>);\r\n }\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">componentDidMount</span>(<span class=\"hljs-params\"></span>)</span> {\r\n <span class=\"hljs-built_in\">this</span>.setState({\r\n <span class=\"hljs-attr\">time</span>: <span class=\"hljs-number\">6</span>,\r\n });\r\n }\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">newTime</span>(<span class=\"hljs-params\"></span>)</span>{\r\n <span class=\"hljs-keyword\">let</span> oldTime=<span class=\"hljs-built_in\">this</span>.state.time\r\n <span class=\"hljs-built_in\">this</span>.setState({\r\n <span class=\"hljs-attr\">time</span>:oldTime+<span class=\"hljs-number\">1</span>\r\n })\r\n }\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">render</span>(<span class=\"hljs-params\"></span>)</span> {\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span> <span class=\"hljs-attr\">onClick</span>=<span class=\"hljs-string\">{this.newTime}</span>></span>我是Com组件,现在是{this.state.time}点<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n }\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> Com;</code></pre><p><span style=\"font-size: 1em;\">这样的话就可以绑定自定义事件了,</span><span style=\"font-size: 1em;\">在每次点击时获取当前时间后加一</span><br></p><p>!!:每次使用自定义事件时需要在构造器中使用bind函数进行绑定,将函数挂在到class实例上</p><p><b><font size=\"6\">简写方式:</font></b></p><pre type=\"React\"><code><span class=\"hljs-keyword\">import</span> React, { Component } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Com</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">Component</span> </span>{\r\n state = {\r\n <span class=\"hljs-attr\">time</span>: <span class=\"hljs-number\">5</span>,\r\n };\r\n newTime=<span class=\"hljs-function\">()=></span>{\r\n <span class=\"hljs-keyword\">let</span> oldTime=<span class=\"hljs-built_in\">this</span>.state.time\r\n <span class=\"hljs-built_in\">this</span>.setState({\r\n <span class=\"hljs-attr\">time</span>:oldTime+<span class=\"hljs-number\">1</span>\r\n })\r\n }\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">render</span>(<span class=\"hljs-params\"></span>)</span> {\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span> <span class=\"hljs-attr\">onClick</span>=<span class=\"hljs-string\">{this.newTime}</span>></span>我是Com组件,现在是{this.state.time}点<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span>\r\n <span class=\"hljs-tag\"></></span></span>\r\n );\r\n }\r\n}\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> Com;</code></pre><p><span style=\"font-size: 1em;\">上面的类组件过于繁琐,增加了很多不必要的麻烦,因此我们可以在今后的开发中使用以上方式来简写</span><br></p><ol><li>state无需在写到构造器当中,直接写成实例属性</li><li>事件函数需要在构造器中使用bind绑定指向,直接使用箭头函数</li><li>state和事件都不在依赖构造器构造器可以不用写</li></ol></div>', '2021-05-03 00:00:00', 'React中使用类组件', 27),
(19, 'routerandpage', 'admin', 'JavaScript,讲解', '<h2>前端路由</h2><p>定义:主要用于单页面应用,大部分页面结构不变,只通过JS改变部分内容的使用(例如:react-router,vue-router)<br></p><ul><li>优点:用户体验好,不需要每次都从服务器全部获取,在切换页面时快速展现给用户客户端渲染可以减轻服务器压力<br></li><li>缺点:使用浏览器的前进,后退键的时候会重新发送请求,无法合理地利用缓存刷新无法记住之前滚动的位置(需要配合生命周期手动调整),如果使用客户端渲染不利于SEO,文件大时会出现首页白屏</li></ul><h2>后端路由</h2><p>定义:根据window.location.href找到服务端匹配的模板进行渲染,通过服务器渲染和浏览器路径决定内容</p><ul><li>优点:可以方便的使用AJAX或者服务器渲染最新的HTML对SEO友好</li><li>缺点:文件复用不灵活,如果通过后端语言模板实现组件化就要前后端不分离</li></ul><h2>前后端路由对比</h2><p>从性能和用户体验的层面来比较的话,后端路由每次访问一个新页面的时候都要向服务器发送请求,然后服务器再响应请求,这个过程肯定会有延迟。而前端路由在访问一个新页面的时候仅仅是变换了一下路径而已,没有了网络延迟,对于用户体验来说会有相当大的提升。但是页面使用浏览器自带的刷新和前进后退前端路由就会重新获取资源,这将造成较大的资源浪费。在SEO角度讲单页面应用天生对SEO不友好,因为整个页面都是通过JS渲染的,搜索引擎爬虫只能抓取到一个根节点,但是后端路由是经过后端自带的模板框架渲染的(ejs,jsp)已经在服务器拼接好了HTML对搜索引擎蜘蛛会更倾向与后端路由。</p><h2>单页面应用</h2><p>优势:</p><ol><li>实际上并不存在页面切换问题,因为整个网站只有一个HTML,通过预先加载好的JS控制页面显示会更流畅,而且可以附加各种动画和过度效果,用户体验更好。<br></li><li>有大量脚手架已经,三大框架的成长使得单页面前后端分离已经成为趋势(--本观点写于2021.08.12)</li></ol><p>劣势:</p><ol><li>还是上面重复的SEO问题只通过JS切换对SEO不友好</li><li>所有页面放到一个HTML中通过JS切换,如果测试有误对漏掉一些BUG很可能影响后续功能</li><li>体积较大,首次加载时间较长,首页白屏问题</li></ol><h2>多页面应用</h2><p>优势:</p><ol><li>逻辑清楚页面直接几乎没有相互影响</li><li>单个页面体积小,加载速度快</li></ol><p>劣势:</p><ol><li>代码冗余</li><li>页面切换体验不好</li></ol><h2>总结</h2><p>1.如果选择前端路由后端路由、单页面与多页面?</p><p> 方案的选择要根据业务而定,如果是面向客户的产品(toC)推荐使用多页面后端路由,如果是面向企业的产品(ToB)推荐使用单页面前端路由</p><p>2.如何解决SEO和首页白屏的问题</p><p> 首先要知道客户端渲染无法解决此问题,我们需要的是保证大部分三大框架优点的同时解决此问题,如果你们网站是纯静态的推荐使用预渲染,如果是根据AJAX动态更新的推荐使用NextJs/NuxtJs等服务器渲染框架</p><p>3.页面中传值问题</p><p> 基本分为通过url传值和加密传值,url传值基本为 window.location.href=href/param方法修改路径或者?a=1&b=2,加密传值类似React的state传值</p><p>4.本站的技术栈选择</p><p> 博客网站是及其注重SEO的因此前台选择了NextJs服务器渲染,前后端分离也更加方便了开发,后台管理采用了Vue3客户端渲染主要节约服务器资源以及更快的切换页面<br></p>', '2021-08-12 00:00:00', '一文讲解前端路由、后端路由、单页面应用、多页面应用', 65),
(20, 'nextjs', 'admin', 'React', '<div><p>NextJs是React的服务器渲染框架,区别于官方SSRNext最大的特点是可以渲染出Ajax异步请求渲染出来的结果,本网站目前使用的前端框架就是NextJs</p><p>本文章默认你已将学会了React,如果你不会React可以去<a href=\"https://blogweb.cn/search\" target=\"_blank\">搜索页面</a>去搜索React相关的文章来学习一下React</p><p>下面我讲一下NextJs和React的区别,Reac他和其他两个框架的主要区别就是官方只会提供核心库剩余的像:路由(react-router),状态管理(redex),或者css(css in js、scss)方案都由社区提供,而Next和React最大的区别就是路由,核心库基本没有区别因为在<a href=\"https://www.nextjs.cn/docs/getting-started\" target=\"_blank\">NextJs官网</a>声明了NextJs是兼容React17的</p><h2 id=\"7jbfq\">创建项目</h2><p>区别于React 这里创建项目是使用<span style=\"background-color: rgb(241, 241, 241);\">yarn create next-app </span>create-next-app name(项目名字)(推荐使用yarn因为npm创建项目会遇到一些网络问题,尤其是安装node-sass的时候)</p><h2 id=\"js6jc\"><b></b>安装插件</h2><p>通常我自己喜欢安装ts和sass,使用命令 yarn add typescript sass</p><p>!!:sass版本一定要与node的版本对应</p><h2 id=\"3cgbd\">启动项目</h2><p>cd name(项目名)</p><p>yarn dev</p><h2 id=\"c4d7q\">使用路由</h2><p><br></p><pre><code class=\"React\"><span class=\"hljs-keyword\">import</span> React, { useState, useEffect, useRef } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\r\n<span class=\"hljs-keyword\">import</span> style <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"./index.module.scss\"</span>;\r\n<span class=\"hljs-keyword\">import</span> Link <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"next/link\"</span>;\r\n<span class=\"hljs-comment\">// import router from \"next/router\";</span>\r\n<span class=\"hljs-keyword\">import</span> { Col, Row, BackTop, Button } <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"antd\"</span>;\r\n<span class=\"hljs-keyword\">import</span> {\r\n HomeOutlined,\r\n SearchOutlined,\r\n UserOutlined,\r\n DeploymentUnitOutlined,\r\n MenuUnfoldOutlined,\r\n} <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"@ant-design/icons\"</span>;\r\n<span class=\"hljs-keyword\">import</span> axios <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"axios\"</span>;\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">Header</span>(<span class=\"hljs-params\"></span>) </span>{\r\n <span class=\"hljs-keyword\">const</span> [html, setHtml] = useState<<span class=\"hljs-built_in\">string</span>>(<span class=\"hljs-string\">\"占位的一个注释\"</span>);\r\n <span class=\"hljs-keyword\">const</span> header = React.useRef(<span class=\"hljs-literal\">null</span>);\r\n useEffect(<span class=\"hljs-function\">() =></span> {\r\n <span class=\"hljs-comment\">// console.log(header.current);</span>\r\n\r\n axios.get(<span class=\"hljs-string\">\"/message\"</span>).then(<span class=\"hljs-function\"><span class=\"hljs-params\">res</span> =></span> {\r\n <span class=\"hljs-keyword\">const</span> index: <span class=\"hljs-built_in\">number</span> = <span class=\"hljs-built_in\">Math</span>.floor(<span class=\"hljs-built_in\">Math</span>.random() * res.data.data.length);\r\n setHtml(res.data.data[index].message);\r\n });\r\n }, []);\r\n\r\n <span class=\"hljs-keyword\">const</span> setAnimation = <span class=\"hljs-function\"><span class=\"hljs-params\">e</span> =></span> {\r\n <span class=\"hljs-built_in\">console</span>.log(e);\r\n };\r\n <span class=\"hljs-keyword\">const</span> [active, setActive] = useState<<span class=\"hljs-built_in\">boolean</span>>(<span class=\"hljs-literal\">false</span>);\r\n <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><<span class=\"hljs-name\">header</span>\r\n <span class=\"hljs-attr\">className</span>=<span class=\"hljs-string\">{style.header</span> + ` ${<span class=\"hljs-attr\">active</span> && <span class=\"hljs-attr\">style.header_active</span>}`}\r\n <span class=\"hljs-attr\">ref</span>=<span class=\"hljs-string\">{header}</span>\r\n <span class=\"hljs-attr\">onLoad</span>=<span class=\"hljs-string\">{(e:</span> <span class=\"hljs-attr\">any</span>) =></span> setAnimation(e)}\r\n onClick={(e: any) => setAnimation(e)}\r\n >\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">Button</span>\r\n <span class=\"hljs-attr\">type</span>=<span class=\"hljs-string\">\"primary\"</span>\r\n <span class=\"hljs-attr\">className</span>=<span class=\"hljs-string\">{</span>`<span class=\"hljs-attr\">phone</span> ${<span class=\"hljs-attr\">style.header_switch_ico</span>}`}\r\n <span class=\"hljs-attr\">onClick</span>=<span class=\"hljs-string\">{()</span> =></span> setActive(!active)}\r\n >\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">MenuUnfoldOutlined</span> /></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">Button</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">span</span> <span class=\"hljs-attr\">dangerouslySetInnerHTML</span>=<span class=\"hljs-string\">{{</span> <span class=\"hljs-attr\">__html:</span> `<!<span class=\"hljs-attr\">--</span>${<span class=\"hljs-attr\">html</span>}<span class=\"hljs-attr\">--</span>></span>` }}><span class=\"hljs-tag\"></<span class=\"hljs-name\">span</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">Row</span> <span class=\"hljs-attr\">justify</span>=<span class=\"hljs-string\">\"space-between\"</span> <span class=\"hljs-attr\">className</span>=<span class=\"hljs-string\">{style.nav}</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">Col</span> <span class=\"hljs-attr\">sm</span>=<span class=\"hljs-string\">{8}</span> <span class=\"hljs-attr\">className</span>=<span class=\"hljs-string\">{style.logo}</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">h1</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">a</span> <span class=\"hljs-attr\">href</span>=<span class=\"hljs-string\">\"/\"</span>></span>刘润霖<span class=\"hljs-tag\"></<span class=\"hljs-name\">a</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">h1</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">span</span>></span>\r\n 前端<span class=\"hljs-tag\"><<span class=\"hljs-name\">span</span> <span class=\"hljs-attr\">style</span>=<span class=\"hljs-string\">{{</span> <span class=\"hljs-attr\">textDecoration:</span> \"<span class=\"hljs-attr\">line-through</span>\" }}></span>大佬<span class=\"hljs-tag\"></<span class=\"hljs-name\">span</span>></span>小白\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">span</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">Col</span>></span>\r\n\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">Col</span> <span class=\"hljs-attr\">sm</span>=<span class=\"hljs-string\">{6}</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">nav</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">Link</span> <span class=\"hljs-attr\">href</span>=<span class=\"hljs-string\">\"/\"</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">a</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">HomeOutlined</span> /></span>\r\n 首页\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">a</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">Link</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">Link</span> <span class=\"hljs-attr\">href</span>=<span class=\"hljs-string\">\"/search\"</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">a</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">SearchOutlined</span> /></span> 搜索文章\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">a</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">Link</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">Link</span> <span class=\"hljs-attr\">href</span>=<span class=\"hljs-string\">\"/about\"</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">a</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">UserOutlined</span> /></span> 关于作者\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">a</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">Link</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">Link</span> <span class=\"hljs-attr\">href</span>=<span class=\"hljs-string\">\"/rss\"</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">a</span> <span class=\"hljs-attr\">className</span>=<span class=\"hljs-string\">\"phone\"</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">DeploymentUnitOutlined</span> /></span> 订阅\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">a</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">Link</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">nav</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">Col</span>></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">Row</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">BackTop</span> /></span>\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">header</span>></span></span>\r\n );\r\n}\r\n</code></pre><p><br></p><p>这是我首页的源码</p><p>大家也注意到了每次我们在路由中导入变量是不在是from react-router-dom,而是变成了next/router,next/link等</p><p>router事件基本也是想react中一样不同的是因为是在服务器渲染的所以在next中新加了一个功能:预加载</p><p><i>router.prefetch(\'/path\')</i><br></p><p>主要适用于js编程式导航, 例如:</p><pre><code class=\"React\">importReactfrom<span class=\"hljs-string\">\'react\'</span>\r\n<span class=\"hljs-keyword\">import</span>{ withRouter }<span class=\"hljs-keyword\">from</span><span class=\"hljs-string\">\'next/router\'</span>\r\n \r\nclassMyLinkextendsReact.Component{\r\n <span class=\"hljs-function\"><span class=\"hljs-title\">componentDidMount</span>(<span class=\"hljs-params\"></span>)</span>{\r\n<span class=\"hljs-keyword\">const</span>{ router }=<span class=\"hljs-built_in\">this</span>.props\r\n router.prefetch(<span class=\"hljs-string\">\'/dynamic\'</span>)\r\n}\r\n \r\n <span class=\"hljs-function\"><span class=\"hljs-title\">render</span>(<span class=\"hljs-params\"></span>)</span>{\r\n<span class=\"hljs-keyword\">const</span>{ router }=<span class=\"hljs-built_in\">this</span>.props\r\n<span class=\"hljs-keyword\">return</span>(\r\n<span class=\"xml\"><span class=\"hljs-tag\"><<span class=\"hljs-name\">div</span>></span>\r\n<span class=\"hljs-tag\"><<span class=\"hljs-name\">a</span> <span class=\"hljs-attr\">onClick</span>=<span class=\"hljs-string\">{()</span>=></span>setTimeout(()=> router.push(\'/dynamic\'),100)}>\r\n A route transition will happen after 100ms\r\n<span class=\"hljs-tag\"></<span class=\"hljs-name\">a</span>></span>\r\n<span class=\"hljs-tag\"></<span class=\"hljs-name\">div</span>></span></span>\r\n)\r\n}\r\n}\r\n \r\nexportdefault withRouter(MyLink)</code></pre><p>适用setTimeout进行延迟跳转路由时就是预加载的最佳适用环境。<br></p><p>在Next中没有单独的文件去配置path和components对应</p><p>Next中遵循组件及路由的原则</p><p>在page文件夹中:</p><p><img src=\"1629468639430.jpg\"></p><p>这样的配置就说明我们注册了5个常规路由一个错误时显示的路由</p><p>也可以使用*路由</p><p>在对应的文件夹中使用[...all].tsx</p><p>在本项目我使用了</p><p><img src=\"1629468689332.jpg\"></p><p>这样就相当于注册了article中的所有路由在访问blogweb.cn/article/*</p><p>中凡是article的路由都会进入此文件</p><p><span style=\"font-size: 24px;\">异步请求</span><br></p><p>在Next中最大的特点是会渲染异步请求的结果</p><pre><code class=\"React\"><span class=\"hljs-keyword\">import</span> axios <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"axios\"</span>;\r\n\r\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">Home</span>(<span class=\"hljs-params\">{ data }</span>) </span>{\r\n</code><code class=\"React\"> <span class=\"hljs-keyword\">return</span> (\r\n <span class=\"xml\"><span class=\"hljs-tag\"><></span>\r\n <div>{data+\'\'}</div></span></code><code class=\"React\"><span class=\"xml\"> <span class=\"hljs-tag\"></></span></span>\r\n );\r\n}\r\nHome.getInitialProps = <span class=\"hljs-keyword\">async</span> () => {\r\n <span class=\"hljs-keyword\">let</span> data;\r\n <span class=\"hljs-keyword\">await</span> axios\r\n .get(<span class=\"hljs-string\">\"/article-page/1\"</span>, {\r\n <span class=\"hljs-attr\">params</span>: { <span class=\"hljs-attr\">key</span>: <span class=\"hljs-string\">\"router,title,type,introduce,article,time\"</span> },\r\n })\r\n .then(<span class=\"hljs-function\"><span class=\"hljs-params\">res</span> =></span> {\r\n data = res.data.data;\r\n });\r\n <span class=\"hljs-keyword\">return</span> {\r\n <span class=\"hljs-attr\">data</span>: data,\r\n };\r\n};\r\n</code></pre><p>例如这个demo,官方提供了getInitialProps生命周期,在这个生命周期中我们可以返回变量作为函数的props,axios注意使用async和await<br></p><p><span style=\"font-size: 24px;\">Link标签跳转</span><br></p><pre><code class=\"React\"><span class=\"hljs-tag\"><<span class=\"hljs-name\">Link</span> <span class=\"hljs-attr\">href</span>=<span class=\"hljs-string\">\"/\"</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">a</span>></span>\r\n <span class=\"hljs-tag\"><<span class=\"hljs-name\">HomeOutlined</span> /></span>\r\n 首页\r\n <span class=\"hljs-tag\"></<span class=\"hljs-name\">a</span>></span>\r\n<span class=\"hljs-tag\"></<span class=\"hljs-name\">Link</span>></span></code></pre><p>Link必须有子元素包裹,并且有className或者事件绑定只能绑定到子元素上,如果你的子元素不使用a使用其他标签也可以,相当于为你的字元素添加了一个onclick事件,相当于Vue中router-link的tag属性</p><h2 id=\"rto9a\">CSS解决方案</h2><p>想React一样NextJs支持CSS in Js和CSS模块化引入,但是与React不同的是<i>import \'./index.css\'</i>必须在_app.js中引入</p><h2 id=\"50q3m\">使用@代替src文件夹<br><p><span style=\"font-weight: normal;\">原本Next.js创建之后是不会有src文件夹的但是我们可以创一个(</span><a href=\"https://www.nextjs.cn/docs/advanced-features/src-directory\" target=\"_blank\" style=\"font-weight: normal;\">相关文档</a><span style=\"font-weight: normal;\">),然后将样式、模块、组件路由等文件放进去(总之就是关于项目配置的不要放,关于页面的可以)</span></p><p><span style=\"font-weight: normal;\">TS:</span></p><p><img src=\"1629468586940.jpg\"><br><br></p><p><br></p></h2><p>以上基本就是Next不同于React的点,更多知识点还是要参考于<a href=\"https://www.nextjs.cn/\" target=\"_blank\">文档</a></p></div>', '2021-06-29 00:00:00', 'Next.js的创建与使用', 35);
INSERT INTO `article` (`id`, `router`, `author`, `type`, `article`, `time`, `title`, `view_count`) VALUES
(21, 'studentachievement1', 'admin', '毕业设计,VUE,Node', '<div><h2 id=\"opp3f\">分享毕业设计(学生成绩管理系统):</h2><p>技术栈:<b>Vue3,Node-Express,MySql,Echarts,Element-Plus组件库</b></p><p>添加权限管理,教师成绩录入之后,需要管理员的权限才能重新修改。还有管理员可以添加删除修改教师、学生。管理员、学生、教师可以修改个人信息,修改登录密码。管理员可以做到按学生班级,按学生学号搜索学生,管理员可以添加课程,删除课程。教师可以录入成绩,修改成绩(在管理员开了权限之后才可以修改)。这是上面没有补充的,你看一下</p><p><span style=\"font-weight: normal;\"></span><span style=\"font-size: 1em;\">学生:</span></p><p>1、验证个人信息,登录系统,<br>2、查询/修改个人基本信息,查看成绩<br>3、查询所有成绩,并得到平均分,总分等指标<br>4、下载成绩<br>5、退出系统<br> <br>教师:<br>1、 验证个人信息,登录系统<br>2、 查询/修改个人基本信息,能修改登录密码<br>3、 查看成绩<br>4、 录入成绩<br>5、 修改或更新某一个成绩<br>6、 查询某一科的平均成绩,以及改科目的最高分最低分。并以echarts对成绩进行统计分析<br>7、 打印和下载某一科目的成绩<br>8、 退出系统<br> <br>管理员:<br>1、 验证信息,登录系统<br>2、 添加和修改、删除学生、教师信息<br>3、 录入某一个科的成绩<br>4、 修改/更新某一科的成绩<br>5、 添加通告<br>6、 退出系统<br> <br>要做到成绩排序,分类搜索<br></p><p><span style=\"font-size: 1em;\">以上是客户发给我的需求</span><br></p><p><span style=\"font-size: 1em;\"><b>实现效果图:</b></span></p><p><img src=\"1632720835221.jpg\"></p><p><img src=\"1632720862540.jpg\"></p><p><img src=\"1632720890763.jpg\"></p><p><span style=\"font-size: 1em;\">项目分为管理员,学生,和教师,教师可以察看echarts成绩表,和下载excel图表察看成绩</span></p><p><span style=\"font-size: 1em;\">文件压缩包内包含<b>vue项目,node-express项目,sql数据库文件,</b></span></p><p><span style=\"font-size: 1em;\"><b>仓库地址:</b></span><a href=\"https://github.com/Lrunlin/student-achievement\" target=\"_blank\">点击查看</a></p><p><br></p></div>', '2021-05-14 00:00:00', '毕业设计学生成绩管理系统分享', 165),
(22, 'nodeexpressmysql', 'admin', 'Node,讲解', '<div><p>node连接数据库进行增删改查,安装npm包mysql2(<i>npm install mysql2</i>)</p><p>1.创建连接池配置数据库信息</p><pre type=\"JavaScript\"><code> const mysql = require(<span class=\"hljs-string\">\'mysql2/promise\'</span>);\n const pool = mysql.createPool({\n host: <span class=\"hljs-string\">\'localhost\'</span>,\n user: <span class=\"hljs-string\">\'root\'</span>,<span class=\"hljs-regexp\">//</span>数据库用户名\n database: <span class=\"hljs-string\">\'blog\'</span>,<span class=\"hljs-regexp\">//</span>数据库\n password: <span class=\"hljs-string\">\'\'</span>,<span class=\"hljs-regexp\">//</span>数据库密码\n waitForConnections: true,<span class=\"hljs-regexp\">//</span>是否允许排队等待\n connectionLimit: <span class=\"hljs-number\">10</span>,<span class=\"hljs-regexp\">//</span>最大连接数\n dateStrings: true <span class=\"hljs-regexp\">//</span>时间转字符串(转化格式)\n });\n module.exports = pool;</code></pre><p>2.express创建路由</p><pre type=\"JavaScript\"><code><span class=\"hljs-keyword\">const</span> express = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">\'express\'</span>)\n<span class=\"hljs-keyword\">const</span> app = express()\n<span class=\"hljs-keyword\">const</span> router = express.Router()//创建路由\n<span class=\"hljs-keyword\">let</span> pool = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">\'../../modules/pool\'</span>)//引入配置好的数据库\nrouter.get(<span class=\"hljs-string\">\'/type\'</span>, <span class=\"hljs-function\">(<span class=\"hljs-params\">req, res</span>) =></span> {</code><code>//创建路由使用: http://localhost:端口号/type\n (<span class=\"hljs-keyword\">async</span> () => {</code><code>//query中使用sql语句,rows是查询\n <span class=\"hljs-keyword\">const</span> [rows] = <span class=\"hljs-keyword\">await</span> pool.query(<span class=\"hljs-string\">`select * from articletype ORDER BY time DESC;`</span>);\n res.json({\n//返回出去一段json\n })\n })()\n})\n<span class=\"hljs-built_in\">module</span>.exports = router;</code></pre><p><br></p><p>3.挂载到主路由上面</p><pre type=\"JavaScript\"><code>app.use(<span class=\"hljs-string\">\'/\'</span>,<span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">\'./route/type/read-type\'</span>))\napp.listen(<span class=\"hljs-number\">3000</span>, <span class=\"hljs-function\"><span class=\"hljs-params\">()</span> =></span> <span class=\"hljs-built_in\">console</span>.log(`<span class=\"javascript\">博客接口运行</span>`))</code></pre><p><br></p></div>', '2021-05-09 00:00:00', 'NodeExpress连接MySql', 48),
(23, 'vuescriptsetup', 'admin', 'JavaScript,填坑,Vue', '<div><p>在Vue3组合式api开发中,所有模板中使用的变量都需要return暴露模板,这样会给开发者增加很多心智负担,所以又有了提案script setup</p><p>之前:</p><pre type=\"VUE\"><code><span class=\"xml\"><span class=\"hljs-tag\"><<span class=\"hljs-name\">template</span>></span>\n</span><span class=\"hljs-template-variable\">{{<span class=\"hljs-name\">mes</span>}}</span><span class=\"xml\">\n<span class=\"hljs-tag\"></<span class=\"hljs-name\">template</span>></span>\n<span class=\"hljs-tag\"><<span class=\"hljs-name\">script</span> ></span><span class=\"javascript\">\n<span class=\"hljs-keyword\">import</span> {ref} <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\'vue\'</span>\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> {\n<span class=\"hljs-function\"><span class=\"hljs-title\">setup</span>(<span class=\"hljs-params\"></span>)</span> {\n <span class=\"hljs-keyword\">let</span> mes=ref(<span class=\"hljs-string\">\'我是加载信息\'</span>)\n}\n}\n</span><span class=\"hljs-tag\"></<span class=\"hljs-name\">script</span>></span></span></code></pre><p>提案:</p><pre type=\"VUE\"><code><span class=\"xml\"><span class=\"hljs-tag\"><<span class=\"hljs-name\">template</span>></span>\n </span><span class=\"hljs-template-variable\">{{ mes }</span><span class=\"xml\">}\n<span class=\"hljs-tag\"></<span class=\"hljs-name\">template</span>></span>\n<span class=\"hljs-tag\"><<span class=\"hljs-name\">script</span> <span class=\"hljs-attr\">setup</span>=<span class=\"hljs-string\">\"prop,</span></span></span><span class=\"hljs-template-variable\">{emit}</span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-string\">\"</span>></span><span class=\"javascript\">\n<span class=\"hljs-keyword\">import</span> </span></span><span class=\"hljs-template-variable\">{ref}</span><span class=\"xml\"><span class=\"javascript\"> <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\'vue\'</span>\n <span class=\"hljs-keyword\">let</span> mes=ref(<span class=\"hljs-string\">\'我是加载信息\'</span>)\n emit(函数命,参数)<span class=\"hljs-comment\">//子传父</span>\n <span class=\"hljs-built_in\">console</span>.log(prop.名字);<span class=\"hljs-comment\">//获取父组件的传值</span>\n</span><span class=\"hljs-tag\"></<span class=\"hljs-name\">script</span>></span></span></code></pre><p>但是在我开发中使用发现变量名字无需return,但是props和ctx参数无法使用,还报出了一些错误</p><p>发现相关报错信息狗,去推特询问作者了<a href=\"https://baike.baidu.com/item/%E5%B0%A4%E9%9B%A8%E6%BA%AA/2281470\" target=\"_blank\">尤雨溪</a>,并且得到了回复:</p><p><img src=\"https://cdn.blogweb.cn/image/1f0vk8uh4.jpg\"><br></p><p><span style=\"font-size: 1em;\">简单的看就是我使用的方法过时了,不在使用,在去GitHub察看最新方案后,整理出:</span><br></p><pre type=\"VUE\"><code><span class=\"xml\"><span class=\"hljs-tag\"><<span class=\"hljs-name\">template</span>></span>\n</span><span class=\"hljs-template-variable\">{{mes}</span><span class=\"xml\">}\n<组件></组件>\n<span class=\"hljs-tag\"></<span class=\"hljs-name\">template</span>></span>\n<span class=\"hljs-tag\"><<span class=\"hljs-name\">script</span> <span class=\"hljs-attr\">setup</span>></span><span class=\"javascript\">\n<span class=\"hljs-keyword\">import</span> </span></span><span class=\"hljs-template-variable\">{defineEmit, defineProps, ref}</span><span class=\"xml\"><span class=\"javascript\"> <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\'vue\'</span>\n<span class=\"hljs-keyword\">import</span> 组件 <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\'@/components/组件名字\'</span>\n<span class=\"hljs-keyword\">let</span> mes=ref(<span class=\"hljs-string\">\'我是文字消息\'</span>);\n\n<span class=\"hljs-keyword\">let</span> emit=defineEmit();\nemit(<span class=\"hljs-string\">\'函数名字\'</span>,参数)<span class=\"hljs-comment\">//子>父</span>\n\n<span class=\"hljs-keyword\">let</span> prop=defineProps(</span></span><span class=\"hljs-template-variable\">{\n name:type\n}</span><span class=\"xml\"><span class=\"javascript\">)\n<span class=\"hljs-built_in\">console</span>.log(prop.name);<span class=\"hljs-comment\">//获取父组件的值</span>\n</span><span class=\"hljs-tag\"></<span class=\"hljs-name\">script</span>></span></span></code></pre><p>变化:</p><ol><li>prop,emit等参数需要引入(推荐使用vscode函数内使用会直接帮助您引入)</li><li>组件不在components需要定义只需引入</li><li>双向绑定变量不在需要return全部暴露模板</li></ol></div>', '2021-03-17 00:00:00', 'Vue3组合式apiscript setup提案使用emitprop组件通信', 79),
(25, 'reactvsvue2022', 'admin', 'React,Vue,讲解,前端', '<p> 到了2022年前端框架发展早已稳定为三大框架Angular、React、Vue,三大框架发展趋势也是各有不同,基于TS的Angular大多数插件都是由官方提供,React官方只提供了React核心和Create-react-app,Vue官方提供了基本的三件套(个人认为Pinia可以代替VueX)。</p><p> 这篇文章中我们先把Angular放在一边个人谈一下React对比Vue的看法,现在的Vue3基本能成熟组合式API也可以支撑起大型项目,也有了React所说的Hooks,在我看来Vue3对比其他两个框架所剩的缺点可能只是不支持IE和TS的支持了,前者微软早已宣布2022年6月25日停止支持IE,后者正在被真在流行的TSX解决。</p><h3>先说明一下Vue3对比React的优点:</h3><ol><li>watchEffect、computed对比useEffect,useMemo不需要手动填写依赖,并且可以执行函数停止监听。</li><li>Vue中定义的ref和reactive变量是双向数据流并且可以直接拿到更新后的值,React中的useState返回数组中的两个参数使用起来更加繁琐</li><li>Vue3对比React有更好的性能(数据更新和SSR)</li><li>Vue模板中自带CSS解决方案,React有多种社区方案但是都不能完美解决问题</li><li>国人对Vue的关注度很高,同时又多种教程和问题解答(百度)</li></ol><h3>Vue3对比React的缺点:</h3><ol><li>对比React没有较为活跃的社区</li><li>对比React没有更好的TypeScript的支持<br><br></li></ol> 本站是Next.js+Node.js+MySQL搭建的CSS方案使用的是styled-jsx(Next.js自带但是并不好用)本站是毕业设计,说是为了React而用React只是保证使用的技术栈更多,截止到2022/1/3网站已经经历过7次重构基本上是每学习到一个新技术就要应用到其中。<p> 这次开发中认为Vue3.2及其以后的版本会在未来有更大发展,经过多次社区与其他开发讨论和查阅相关趋势资料总结出如下返回关于主流吹捧React言论反驳的话:</p><ol><li> React开发者所说的大道至简基本无用,所谓的React官方值有核心库其他全靠社区所以React更加灵活状态<br>(反驳)管理和路由等基本方案有几个正经项目不用的,一个库连好的CSS解决方案都没有怎么能能称得上优秀呢?</li><li> 所谓的Vue的功能在React中可通过各种封装实现,说Vue的语法糖太多,糖吃多了,没有糖了就各种不适<br>(反驳)那你为什么不使用原生JS进行开发呢原生JS没有糖那才是大道至简,并且React中很多时候还需要使用memo和useCallback进行性能优化不是一种心智负担吗?</li><li>Vue中结合TS没有props提示和类型明显的限制很麻烦<br>(反驳)Vue3可以结合TSX使用有很好的类型推断,React手动优化在大型项目中难道就很轻松吗?父子组件更新的坑解决了吗?<br><p><br></p></li></ol><div><h2>对于目前前端开发我推荐使用的技术:</h2></div><ol><li>框架:Vue3(TSX)</li><li>SSR:Nuxt.js3版本发布了等过一阵大佬们拍完坑就可以用了</li><li>构建工具:开发环境使用Vite打包使用Webpack</li><li>CSS:Emotion,因为模板自带的样式隔离无法在TSX中使用所以只能使用css-in-js方案</li><li>请求:axios/fetch,axios毋庸置疑是目前请求库中最火热的但是很多产品要求极致性能并且本身也会对请求库进行二次封装如果没有低级浏览器兼容需求的话可以考虑封装fetch<br></li><li>TOB组件库:Element-Plus</li></ol>', '2022-01-01 22:35:16', '2022年React对比Vue', 220),
(40, 'vueoptimizationtechniques', 'admin', 'Vue,前端', '<p>代码是写给人看的附带能在机器上运行,在开发中我们经常出于性能、可读性或者系统健壮程度优化代码</p>\n<blockquote>\n<p>演示代码使用 Vue3 + ts + Vite 编写,但是也会列出适用于 Vue2 的优化技巧,如果某个优化只适用于 Vue3 或者 Vue2,我会在标题中标出来。</p>\n</blockquote>\n<h2 id=\"代码优化\">代码优化</h2>\n<h3 id=\"v-for-中使用-key\">v-for 中使用 key</h3>\n<p>使用 \n<code>v-for</code> 更新已渲染的元素列表时,默认用就地复用策略;列表数据修改的时候,他会根据 \n<code>key</code> 值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素;\n</p>\n<p>使用key的注意事项:</p>\n<ul>\n<li>不要使用可能重复的或者可能变化 \n<code>key</code> 值(控制台也会给出提醒)\n</li>\n<li>如果数组中的数据有状态需要维持时(例如输入框),不要使用数组的 \n<code>index</code> 作为 \n<code>key</code> 值,因为如果在数组中插入或者移除一个元素时,其后面的元素 index 将会变化,这回让vue进行原地复用时错误的绑定状态。\n</li>\n<li>如果数组中没有唯一的 key值可用,且数组更新时不是全量更新而是采用类似push,splice来插入或者移除数据时,可以考虑对其添加一个 key 字段,值为 Symbol() 即可保证唯一。</li>\n</ul>\n<blockquote>\n<p>何时使用何种key?</p>\n</blockquote>\n<p>这是一个非常有考究的问题,首先你要知道 vue 中的 \n<code>原地复用</code> (大概就是 \n<code>虚拟dom</code> 变化时,两个 \n<code>虚拟dom节点</code> 的 \n<code>key</code> 如果一样就不会重新创建节点,而是修改原来的节点)\n</p>\n<p>当我们渲染的数据不需要保持状态时,例如常见的单纯的表格分页渲染(不包含输入,只是展示)、下拉加载更多等场景,那么使用 \n<code>index</code> 作为 \n<code>key</code> 再好不过,因为进入下一页或者上一页时就会原地复用之前的节点,而不是重新创建,如果使用唯一的 \n<code>id</code> 作为 \n<code>key</code> 反而会重新创建dom,性能相对较低。\n</p>\n<p>此外使用 \n<code>index</code> 作为 \n<code>key</code> 我还应该要尽量避免对数组的中间进行 增加/删除 等会影响后面元素key变化的操作。这会让 vue 认为后面所有元素都发生了变化,导致多余的对比和原地复用。\n</p>\n<p>所以使用 index 作为 key 需要满足:</p>\n<ol>\n<li>数据没有独立的状态</li>\n<li>数据不会进行 增加/删除 等会影响后面元素key变化的操作</li>\n</ol>\n<blockquote>\n<p>哪何时使用 id 作为 key 呢?</p>\n</blockquote>\n<p>对于大多数数据的 \n<code>id</code> 都是唯一的,这无疑的一个 \n<code>key</code> 的优选答案。对于任何大多数情况使用 \n<code>id</code> 作为 \n<code>key</code> 都不会出现上面 \n<code>bug</code>。但是如果你需要考虑性能问题,那就就要思考是否应该使用原地复用了。\n</p>\n<p>同样是上面的分页数据展示,如果使用 \n<code>id</code> 作为 \n<code>key</code> ,可想而知每一页的每一条数据 \n<code>id</code> 都是不一样的,所以当换页时两颗 \n<code>虚拟DOM树</code> 的节点的 \n<code>key</code> 完全不一致,\n<code>vue</code> 就会移除原来的节点然后创建新的节点。可想而知效率会更加低下。但是他也有它的优点。唯一的 \n<code>key</code> 可以帮助 \n<code>diff</code> 更加精确的为我们绑定状态,这尤其适合数据有独立的状态的场景,例如带输入框或者单选框的列表数据。\n</p>\n<p>所以何时使用 \n<code>id</code> 作为 \n<code>key</code>?只有一点:\n</p>\n<ol>\n<li>无法使用 \n<code>index</code> 作为 \n<code>key</code> 的时候\n</li>\n</ol>\n<h3 id=\"v-ifv-else-ifv-else-中使用-key\">v-if/v-else-if/v-else 中使用 key</h3>\n<blockquote>\n<p>可能很多人都会忽略这个点</p>\n</blockquote>\n<p>原因:默认情况下,Vue 会尽可能高效的更新 DOM。这意味着其在相同类型的元素之间切换时,会修补已存在的元素,而不是将旧的元素移除然后在同一位置添加一个新元素。如果本不相同的元素被识别为相同,则会出现意料之外的副作用。</p>\n<blockquote>\n<p>如果只有一个 v-if ,没有 v-else 或者 v-if-else的话,就没有必要加 key 了</p>\n</blockquote>\n<p>相对于 v-for 的 key, v-if/v-else-if/v-else 中的 key 相对简单,我们可以直接写入固定的字符串或者数组即可</p>\n<pre><code class=\"language-html\"> <transition>\n <button \n v-if=\'isEditing\'\n v-on:click=\'isEditing = false\'\n >\n Save\n </button>\n <button \n v-else \n v-on:click=\'isEditing = true\'\n >\n Edit\n </button>\n </transition>\n</code></pre>\n<pre><code class=\"language-css\">.v-enter-active, .v-leave-active {\n transition: all 1s;\n}\n.v-enter, .v-leave-to {\n opacity: 0;\n transform: translateY(30px);\n}\n.v-leave-active {\n position: absolute;\n}\n</code></pre>\n<p>例如对于上面的代码, 你会发现虽然对 button 添加了 过渡效果, 但是如果不添加 key 切换时是无法触发过渡的</p>\n<h3 id=\"v-for-和-v-if-不要一起使用(vue2)\">v-for 和 v-if 不要一起使用(Vue2)</h3>\n<blockquote>\n<p>此优化技巧仅限于Vue2,Vue3 中对 v-for 和 v-if 的优先级做了调整</p>\n</blockquote>\n<p>这个大家都知道</p>\n<blockquote>\n<p>\n<strong>永远不要把 \n<code>v-if</code> 和 \n<code>v-for</code> 同时用在同一个元素上。\n</strong> 引至 \n<a href=\"https://cn.vuejs.org/v2/style-guide/#%E9%81%BF%E5%85%8D-v-if-%E5%92%8C-v-for-%E7%94%A8%E5%9C%A8%E4%B8%80%E8%B5%B7%E5%BF%85%E8%A6%81\">Vue2.x风格指南</a>\n</p>\n</blockquote>\n<p>原因是 \n<code>v-for</code> 的 优先级高于 \n<code>v-if</code>,所以当它们使用再同一个标签上是,每一个渲染都会先循环再进行条件判断\n</p>\n<blockquote>\n<p>\n<strong>注意:</strong> Vue3 中 \n<code>v-if</code> 优先级高于 \n<code>v-for</code>,所以当 \n<code>v-for</code> 和 \n<code>v-if</code> 一起使用时效果类似于 \n<code>Vue2</code> 中把 \n<code>v-if</code> 上提的效果\n</p>\n</blockquote>\n<p>例如下面这段代码在 \n<code>Vue2</code> 中是不被推荐的,\n<code>Vue</code> 也会给出对应的警告\n</p>\n<pre><code class=\"language-html\"><ul>\n <li v-for=\'user in users\' v-if=\'user.active\'>\n {{ user.name }}\n </li>\n</ul>\n</code></pre>\n<p>我们应该尽量将 \n<code>v-if</code> 移动到上级 或者 使用 计算属性来处理数据\n</p>\n<pre><code class=\"language-html\"><ul v-if=\'active\'>\n <li v-for=\'user in users\'>\n {{ user.name }}\n </li>\n</ul>\n</code></pre>\n<p>如果你不想让循环的内容多出一个无需有的上级容器,那么你可以选择使用 \n<code>template</code> 来作为其父元素,\n<code>template</code> 不会被浏览器渲染为 \n<code>DOM</code> 节点\n</p>\n<p>如果我想要判断遍历对象里面每一项的内容来选择渲染的数据的话,可以使用 \n<code>computed</code> 来对遍历对象进行过滤\n</p>\n<pre><code class=\"language-html\">// js\nlet usersActive = computed(()=>users.filter(user => user.active))\n\n// template\n<ul>\n <li v-for=\'user in usersActive\'>\n {{ user.name }}\n </li>\n</ul>\n</code></pre>\n<h3 id=\"合理的选择-v-if-和-v-show\">合理的选择 v-if 和 v-show</h3>\n<p>\n<code>v-if</code> 和 \n<code>v-show</code> 的区别相比大家都非常熟悉了; \n<code>v-if</code> 通过直接操作 DOM 的删除和添加来控制元素的显示和隐藏;\n<code>v-show</code> 是通过控制 DOM 的 \n<code>display</code> CSS熟悉来控制元素的显示和隐藏\n</p>\n<p>由于对 DOM 的 添加/删除 操作性能远远低于操作 DOM 的 CSS 属性</p>\n<p>所以当元素需要频繁的 显示/隐藏 变化时,我们使用 \n<code>v-show</code> 来提高性能。\n</p>\n<p>当元素不需要频繁的 显示/隐藏 变化时,我们通过 \n<code>v-if</code> 来移除 DOM 可以节约掉浏览器渲染这个的一部分DOM需要的资源\n</p>\n<h3 id=\"使用简单的-计算属性\">使用简单的 计算属性</h3>\n<blockquote>\n<p>\n<strong>应该把复杂计算属性分割为尽可能多的更简单的 property。</strong>\n</p>\n<ul>\n<li>\n<p>\n<strong>易于测试</strong>\n</p>\n<p>当每个计算属性都包含一个非常简单且很少依赖的表达式时,撰写测试以确保其正确工作就会更加容易。</p>\n</li>\n<li>\n<p>\n<strong>易于阅读</strong>\n</p>\n<p>简化计算属性要求你为每一个值都起一个描述性的名称,即便它不可复用。这使得其他开发者 (以及未来的你) 更容易专注在他们关心的代码上并搞清楚发生了什么。</p>\n</li>\n<li>\n<p>\n<strong>更好的“拥抱变化”</strong>\n</p>\n<p>任何能够命名的值都可能用在视图上。举个例子,我们可能打算展示一个信息,告诉用户他们存了多少钱;也可能打算计算税费,但是可能会分开展现,而不是作为总价的一部分。</p>\n<p>小的、专注的计算属性减少了信息使用时的假设性限制,所以需求变更时也用不着那么多重构了。</p>\n</li>\n</ul>\n<p> 引至 \n<a href=\"https://cn.vuejs.org/v2/style-guide/#%E7%AE%80%E5%8D%95%E7%9A%84%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7%E5%BC%BA%E7%83%88%E6%8E%A8%E8%8D%90\">Vue2风格指南</a>\n</p>\n</blockquote>\n<p>computed 大家后很熟悉, 它会在其表达式中依赖的响应式数据发送变化时重新计算。如果我们在一个计算属性中书写了比较复杂的表达式,那么其依赖的响应式数据也任意变得更多。当其中任何一个依赖项变化时整个表达式都需要重新计算</p>\n<pre><code class=\"language-js\">let price = computed(()=>{\n let basePrice = manufactureCost / (1 - profitMargin)\n return (\n basePrice -\n basePrice * (discountPercent || 0)\n )\n})\n</code></pre>\n<p>当 manufactureCost、profitMargin、discountPercent 中任何一个变化时都会重新计算整个 price。</p>\n<p>但是如果我们改成下面这样</p>\n<pre><code class=\"language-js\">let basePrice = computed(() => manufactureCost / (1 - profitMargin))\nlet discount = computed(() => basePrice * (discountPercent || 0))\nlet finalPrice = computed(() => basePrice - discount)\n</code></pre>\n<p>如果当 discountPercent 变化时,只会 重新计算 discount 和 finalPrice,由于 \n<code>computed</code> 的\n<code>缓存特性</code>,不会重新计算 basePrice\n</p>\n<h3 id=\"functional-函数式组件(vue2)\">functional 函数式组件(Vue2)</h3>\n<blockquote>\n<p>注意,这仅仅在 Vue2 中被作为一种优化手段,在 3.x 中,有状态组件和函数式组件之间的性能差异已经大大减少,并且在大多数用例中是微不足道的。因此,在 SFCs 上使用 \n<code>functional</code> 的开发人员的迁移路径是删除该 attribute,并将 \n<code>props</code> 的所有引用重命名为 \n<code>$props</code>,将 \n<code>attrs</code> 重命名为 \n<code>$attrs</code>。\n</p>\n</blockquote>\n<p>优化前</p>\n<pre><code class=\"language-html\"><template> \n <div class=\'cell\'> \n <div v-if=\'value\' class=\'on\'></div> \n <section v-else class=\'off\'></section> \n </div> \n</template> \n\n<script> \nexport default { \n props: [\'value\'], \n} \n</script>\n</code></pre>\n<p>优化后</p>\n<pre><code class=\"language-html\"><template functional> \n <div class=\'cell\'> \n <div v-if=\'props.value\' class=\'on\'></div> \n <section v-else class=\'off\'></section> \n </div> \n</template> \n\n<script> \nexport default { \n props: [\'value\'], \n} \n</script>\n</code></pre>\n<ul>\n<li>没有this(没有实例)</li>\n<li>没有响应式数据</li>\n</ul>\n<h3 id=\"拆分组件\">拆分组件</h3>\n<p>什么?你写的一个vue文件有一千多行代码?</p>\n<p>合理的拆分组件不仅仅可以优化性能,还能够让代码更清晰可读。单一功能原则嘛</p>\n<p>源自 \n<a href=\"https://slides.com/akryum/vueconfus-2019#/4/0/3\">https://slides.com/akryum/vueconfus-2019#/4/0/3</a>\n</p>\n<p>优化前</p>\n<pre><code class=\"language-html\"><template>\n <div :style=\'{ opacity: number / 300 }\'>\n <div>{{ heavy() }}</div>\n </div>\n</template>\n\n<script>\nexport default {\n props: [\'number\'],\n methods: {\n heavy () { /* HEAVY TASK */ }\n }\n}\n</script>\n</code></pre>\n<p>优化后</p>\n<pre><code class=\"language-html\"><template>\n <div :style=\'{ opacity: number / 300 }\'>\n <ChildComp/>\n </div>\n</template>\n\n<script>\nexport default {\n props: [\'number\'],\n components: {\n ChildComp: {\n methods: {\n heavy () { /* HEAVY TASK */ }\n },\n render (h) {\n return h(\'div\', this.heavy())\n }\n }\n }\n}\n</script>\n</code></pre>\n<p>由于 Vue 的更新是组件粒度的,虽然每一帧都通过数据修改导致了父组件的重新渲染,但是 \n<code>ChildComp</code> 却不会重新渲染,因为它的内部也没有任何响应式数据的变化。所以优化后的组件不会在每次渲染都执行耗时任务\n</p>\n<h3 id=\"使用局部变量\">使用局部变量</h3>\n<p>优化前</p>\n<pre><code class=\"language-html\"><template>\n <div :style=\'{ opacity: start / 300 }\'>{{ result }}</div>\n</template>\n\n<script>\nimport { heavy } from \'@/utils\'\n\nexport default {\n props: [\'start\'],\n computed: {\n base () { return 42 },\n result () {\n let result = this.start\n for (let i = 0; i < 1000; i++) {\n result += heavy(this.base)\n }\n return result\n }\n }\n}\n</script>\n</code></pre>\n<p>优化后</p>\n<pre><code class=\"language-html\"><template>\n <div :style=\'{ opacity: start / 300 }\'>\n {{ result }}</div>\n</template>\n\n<script>\nimport { heavy } from \'@/utils\'\n\nexport default {\n props: [\'start\'],\n computed: {\n base () { return 42 },\n result () {\n const base = this.base\n let result = this.start\n for (let i = 0; i < 1000; i++) {\n result += heavy(base)\n }\n return result\n }\n }\n}\n</script>\n</code></pre>\n<blockquote>\n<p>这里主要是优化前后的组件的计算属性 \n<code>result</code> 的实现差异,优化前的组件多次在计算过程中访问 \n<code>this.base</code>,而优化后的组件会在计算前先用局部变量 \n<code>base</code>,缓存 \n<code>this.base</code>,后面直接访问 \n<code>base</code>。\n</p>\n<p>那么为啥这个差异会造成性能上的差异呢,原因是你每次访问 \n<code>this.base</code> 的时候,由于 \n<code>this.base</code> 是一个响应式对象,所以会触发它的 \n<code>getter</code>,进而会执行依赖收集相关逻辑代码。类似的逻辑执行多了,像示例这样,几百次循环更新几百个组件,每个组件触发 \n<code>computed</code> 重新计算,然后又多次执行依赖收集相关逻辑,性能自然就下降了。\n</p>\n<p>从需求上来说,\n<code>this.base</code> 执行一次依赖收集就够了,把它的 \n<code>getter</code> 求值结果返回给局部变量 \n<code>base</code>,后续再次访问 \n<code>base</code> 的时候就不会触发 \n<code>getter</code>,也不会走依赖收集的逻辑了,性能自然就得到了提升。\n</p>\n<p>引至 \n<a href=\"https://juejin.cn/post/6922641008106668045\">揭秘 Vue.js 九个性能优化技巧</a>\n</p>\n</blockquote>\n<h3 id=\"使用-keepalive\">使用 KeepAlive</h3>\n<p>在一些渲染成本比较高的组件需要被经常切换时,可以使用 \n<code>keep-alive</code> 来缓存这个组件\n</p>\n<p>而在使用 \n<code>keep-alive</code> 后,被 \n<code>keep-alive</code> 包裹的组件在经过第一次渲染后,的 \n<code>vnode</code> 以及 DOM 都会被缓存起来,然后再下一次再次渲染该组件的时候,直接从缓存中拿到对应的 \n<code>vnode</code> 和 DOM,然后渲染,并不需要再走一次组件初始化,\n<code>render</code> 和 \n<code>patch</code> 等一系列流程,减少了 \n<code>script</code> 的执行时间,性能更好。\n</p>\n<blockquote>\n<p>\n<strong>注意:</strong> 滥用 keep-alive 只会让你的应用变得更加卡顿,因为他会长期占用较大的内存\n</p>\n</blockquote>\n<h3 id=\"事件的销毁\">事件的销毁</h3>\n<p>当一个组件被销毁时,我们应该清除组件中添加的 全局事件 和 定时器 等来防止内存泄漏</p>\n<p>Vue3 的 HOOK 可以让我们将事件的声明和销毁写在一起,更加可读</p>\n<pre><code class=\"language-js\">function scrollFun(){ /* ... */}\ndocument.addEventListener(\'scroll\', scrollFun)\n\nonBeforeUnmount(()=>{\n document.removeEventListener(\'scroll\', scrollFun)\n})\n</code></pre>\n<p>Vue2 依然可以通过 \n<code>$once</code> 来做到这样的效果,当然你也可以在 \n<code>optionsAPI</code> beforeDestroy 中销毁事件,但是我更加推荐前者的写法,因为后者会让相同功能的代码更分散\n</p>\n<pre><code class=\"language-js\">function scrollFun(){ /* ... */}\ndocument.addEventListener(\'scroll\', scrollFun)\n\nthis.$once(\'hook:beforeDestroy\', ()=>{\n document.removeEventListener(\'scroll\', scrollFun)\n})\n</code></pre>\n<pre><code class=\"language-js\">function scrollFun(){ /* ... */}\n\nexport default {\n created() {\n document.addEventListener(\'scroll\', scrollFun)\n },\n beforeDestroy(){\n document.removeEventListener(\'scroll\', scrollFun)\n }\n}\n</code></pre>\n<h3 id=\"图片加载\">图片加载</h3>\n<p>图片懒加载:适用于页面上有较多图片且并不是所有图片都在一屏中展示的情况,vue-lazyload 插件给我们提供了一个很方便的图片懒加载指令 v-lazy</p>\n<p>但是并不是所有图片都适合使用懒加载,例如 banner、相册等 更加推荐使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。</p>\n<h3 id=\"使用合适的图片类型\">使用合适的图片类型</h3>\n<p>\n<strong>使用webp格式</strong>:这个没什么好说的,大家都知道WebP的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。\n</p>\n<p>\n<strong>使用交错GIF或者是渐进JPEG</strong>: 还有一种优化用户体验的方式,就是使用交错GIF或者是渐进(Progressive Encoding)JPEG的图片。渐进JPEG文件首先是\n<strong>模糊</strong>的,然后渐渐清晰起来。\n</p>\n<p>\n<strong>Baseline JPEG和Progressive JPEG的区别:</strong>\n</p>\n<p>JPEG文件格式有两种保存方式。他们是Baseline JPEG和Progressive JPEG。</p>\n<p>两种格式有相同尺寸以及图像数据,他们的扩展名也是相同的,唯一的区别是二者显示的方式不同。</p><p>\n<strong>Progressive JPEG的优点:</strong>\n</p>\n<ul>\n<li>用户体验\n一个以progressive方式编码的jpeg文件,在浏览器上的渲染方式是由模糊到清晰的。用户能在渐变的图像当中获得所需信息的反馈。如果内容不是用户所期待的,用户就能提前前往新的页面。</li>\n<li>文件大小\n有实验证明,在JPEG文件小于10KB的时候,使用标准型编码(Huffman表已经被优化)的JPEG文件要小于使用渐变式编码的JPEG文件(发生概率为75%)。当文件大于10KB时,渐变式编码的JPEG文件有94%的概率拥有比标准编码的文件更小的体积。</li>\n</ul>\n<h3 id=\"采用合理的数据处理算法\">采用合理的数据处理算法</h3>\n<p>这个相对比较考验 数据结构和算法 的功底</p>\n<p>例如一个将数组转化为多级结构的方法</p>\n<pre><code class=\"language-js\">/**\n * 数组转树形结构,时间复杂度O(n)\n * @param list 数组\n * @param idKey 元素id键\n * @param parIdKey 元素父id键\n * @param parId 第一级根节点的父id值\n * @return {[]}\n */\nfunction listToTree (list,idKey,parIdKey,parId) {\n let map = {};\n let result = [];\n let len = list.length;\n\n // 构建map\n for (let i = 0; i < len; i++) {\n //将数组中数据转为键值对结构 (这里的数组和obj会相互引用,这是算法实现的重点)\n map[list[i][idKey]] = list[i];\n }\n\n // 构建树形数组\n for(let i=0; i < len; i++) {\n let itemParId = list[i][parIdKey];\n // 顶级节点\n if(itemParId === parId) {\n result.push(list[i]);\n continue;\n }\n // 孤儿节点,舍弃(不存在其父节点)\n if(!map[itemParId]){\n continue;\n }\n // 将当前节点插入到父节点的children中(由于是引用数据类型,obj中对于节点变化,result中对应节点会跟着变化)\n if(map[itemParId].children) {\n map[itemParId].children.push(list[i]);\n } else {\n map[itemParId].children = [list[i]];\n }\n }\n return result;\n}\n</code></pre>\n<h3 id=\"其他\">其他</h3>\n<p>除了上面说的方法以外还有很多优化技巧,只是我在项目并不是太常用</p>\n<ul>\n<li>冻结对象(避免不需要响应式的数据变成响应式)</li>\n<li>长列表渲染-分批渲染</li>\n<li>长列表渲染-动态渲染(\n<a href=\"https://github.com/Akryum/vue-virtual-scroller\">vue-virtual-scroller</a>)\n</li>\n<li>...</li>\n</ul>\n<h2 id=\"首屏体积优化\">首屏/体积优化</h2>\n<p>我在项目中关于首屏优化主要有以下几个优化方向</p>\n<ul>\n<li>体积</li>\n<li>代码分割</li>\n<li>网络</li>\n</ul>\n<h3 id=\"体积优化\">体积优化</h3>\n<ul>\n<li>\n<p>\n<strong>压缩打包代码:</strong>\n<code>webpack</code> 和 \n<code>vite</code> 的生产环境打包默认就会压缩你的代码,这个一般不需要特殊处理,\n<code>webpack</code> 也可以通过对应的压缩插件手动实现\n</p>\n</li>\n<li>\n<p>\n<strong>取消 \n<code>source-map</code>:\n</strong> 可以查看你的打包产物中是否有 .map 文件,如果有你可以将 \n<code>source-map</code> 的值设置为false或者空来关闭代码映射(这个占用的体积是真的大)\n</p>\n</li>\n<li>\n<p>\n<strong>打包启用 \n<code>gizp</code> 压缩:\n</strong> 这个需要服务器也开启允许 \n<code>gizp</code> 传输,不然启用了也没啥用( \n<code>webpack</code> 有对应的 \n<code>gzip</code> 压缩插件,不太版本的 \n<code>webpack</code> 压缩插件可能不同,建议先到官网查询)\n</p>\n</li>\n</ul>\n<h3 id=\"代码分割\">代码分割</h3>\n<p>代码分割的作用的将打包产物分割为一个一个的小产物,其依赖 \n<code>esModule</code>。所以当你使用 \n<code>import()</code> 函数来导入一个文件或者依赖,那么这个文件或者依赖就会被单独打包为一个小产物。 \n<code>路由懒加载</code> 和 \n<code>异步组件</code> 都是使用这个原理。\n</p>\n<ul>\n<li>路由懒加载</li>\n<li>异步组件</li>\n</ul>\n<p>对于 UI库 我一般不会使用按需加载组件,而是比较喜欢 CDN 引入的方式来优化。</p>\n<h3 id=\"网络\">网络</h3>\n<p>\n<strong>CDN:</strong> 首先就是上面的说的 CDN 引入把,开发阶段使用本地库,通过配置 \n<code>外部扩展(Externals)</code> 打包时来排除这些依赖。然后在 html 文件中通过 CDN 的方式来引入它们\n</p>\n<p>\n<strong>Server Push:</strong> HTTP2已经相对成熟了;经过上面的 CDN 引入,我们可以对网站使用 HTTP2 的 Server Push 功能来让浏览器提前加载 这些 CDN 和 其他文件。\n</p>\n<p>\n<strong>开启 gzip:</strong> 这个上面已经说过了,其原理就是当客户端和服务端都支持 gzip 传输时,服务端会优先发送经过 gzip 压缩过的文件,然后客户端接收到在进行解压。\n</p>\n<p>\n<strong>开启缓存:</strong> 一般我使用的是协商缓存,但是这并不适用于所有情况,例如对于使用了 Server Push 的文件,就不能随意的修改其文件名。所以我一般还会将生产的主要文件固定文件名\n</p>\n<h3 id=\"用户体验优化\">用户体验优化</h3>\n<p>我们可以在核心文件加载完成之前,通过展示loading或者骨架屏等方式来提升用户体验。即可缩短白屏时间。</p>\n<p>但是需要注意的是,页面刚开始加载时有许多资源需要加载,如果将loading相关的资源放到dom后的话,有可能会导致loading的资源被其他资源阻塞。</p>\n<p>所以推荐loading相关的css或者js代码最好是内联到html中的头部,这样即可保证展示loading时对应的css和js已经加载完成。并且不推荐loading中使用高性能或者高网络消化的逻辑,这样会延长后面其他资源的解析或者加载时间。</p>', '2022-01-04 21:23:44', '我在项目中用实际用到的22个Vue优化技巧', 80);
INSERT INTO `article` (`id`, `router`, `author`, `type`, `article`, `time`, `title`, `view_count`) VALUES
(45, 'performanceoptimization', 'admin', '前端', '<h3>前言</h3><p> 性能优化是把双刃剑,有好的一面也有坏的一面。好的一面提升网站性能,坏的一面配置麻烦,或者要遵守的规则太多。并且某些性能优化规则并不适用所有场景,需要谨慎使用,请读者带着批判性的眼光来阅读本文。</p><p>本文相关的优化建议的引用资料出处均会在建议后面给出,或者放在文末。</p><p><b>1. 减少 HTTP 请求</b></p><p>一个完整的 HTTP 请求需要经历 DNS 查找,TCP 握手,浏览器发出 HTTP 请求,服务器接收请求,服务器处理请求并发回响应,浏览器接收响应等过程。接下来看一个具体的例子帮助理解 HTTP :</p><figure> <img src=\"47a04ccbc5952e73cede9ef2f8bd1e3a.webp\"></figure><br><p>这是一个 HTTP 请求,请求的文件大小为 28.4KB。</p><p>名词解释:</p><ul><li>Queueing : 在请求队列中的时间。</li><li>Stalled : 从TCP 连接建立完成,到真正可以传输数据之间的时间差,此时间包括代理协商时间。</li><li>Proxy negotiation : 与代理服务器连接进行协商所花费的时间。</li><li>DNS Lookup : 执行DNS查找所花费的时间,页面上的每个不同的域都需要进行DNS查找。</li><li>Initial Connection / Connecting : 建立连接所花费的时间,包括TCP握手/重试和协商SSL。</li><li>SSL : 完成SSL握手所花费的时间。</li><li>Request sent : 发出网络请求所花费的时间,通常为一毫秒的时间。</li><li>Waiting(TFFB) : TFFB 是发出页面请求到接收到应答数据第一个字节的时间。</li><li>Content Download : 接收响应数据所花费的时间。</li></ul><p> 从这个例子可以看出,真正下载数据的时间占比为 <code>13.05 / 204.16 = 6.39%</code>,文件越小,这个比例越小,文件越大,比例就越高。这就是为什么要建议将多个小文件合并为一个大文件,从而减少 HTTP 请求次数的原因。</p><p><b> 2. 使用 HTTP2</b></p><p>HTTP2 相比 HTTP1.1 有如下几个优点:</p><p><b>解析速度快</b></p><p>服务器解析 HTTP1.1 的请求时,必须不断地读入字节,直到遇到分隔符 CRLF 为止。而解析 HTTP2 的请求就不用这么麻烦,因为 HTTP2 是基于帧的协议,每个帧都有表示帧长度的字段。</p><p><b>多路复用</b></p><p>HTTP1.1 同时发起多个请求,建立多个 TCP 连接,因为一个 TCP 连接同时只能处理一个 HTTP1.1 的请求。<br>HTTP2 多个请求可共用一个 TCP 连接,这称为多路复用。同一个请求和响应用一个流来表示,并有唯一的流 ID 来标识。 多个请求和响应在 TCP 连接中可以乱序发送,到达目的地后再通过流 ID 重新组建。</p><p><b>首部压缩</b><br><br>HTTP2 提供了首部压缩功能。例如有如下两个请求:</p><div><pre><code>:authority: unpkg.zhimg.com\n:method: GET\n:path: /[email protected]/dist/zap.js\n:scheme: https\naccept: */*\naccept-encoding: gzip, deflate, br\naccept-language: zh-CN,zh;q=0.9\ncache-control: no-cache\npragma: no-cache\nreferer: https://www.zhihu.com/\nsec-fetch-dest: script\nsec-fetch-mode: no-cors\nsec-fetch-site: cross-site\nuser-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36\n\n/ / /\n\n:authority: zz.bdstatic.com\n:method: GET\n:path: /linksubmit/push.js\n:scheme: https\naccept: */*\naccept-encoding: gzip, deflate, br\naccept-language: zh-CN,zh;q=0.9\ncache-control: no-cache\npragma: no-cache\nreferer: https://www.zhihu.com/\nsec-fetch-dest: script\nsec-fetch-mode: no-cors\nsec-fetch-site: cross-site\nuser-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36</code></pre></div><p>从上面两个请求可看出,有很多数据都是重复的。如果可以把相同的首部存储起来,仅发送它们之间不同的部分,就可以节省不少的流量,加快请求的时间。</p><p>HTTP/2 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送。</p><p>下面再来看一个简化的例子,假设客户端按顺序发送如下请求首部:</p><div><pre><code>Header1:foo\nHeader2:bar\nHeader3:bat</code></pre></div><p>当客户端发送请求时,它会根据首部值创建一张表:</p><table><tbody><tr><th></th><th></th><th></th></tr><tr><td>62</td><td>Header1</td><td>foo</td></tr><tr><td>63</td><td>Header2</td><td>bar</td></tr><tr><td>64</td><td>Header3</td><td>bat</td></tr></tbody></table><p>如果服务器收到了请求,它会照样创建一张表。 当客户端发送下一个请求的时候,如果首部相同,它可以直接发送这样的首部块:</p><div><pre><code>62 63 64</code></pre></div><p>服务器会查找先前建立的表格,并把这些数字还原成索引对应的完整首部。</p><p><b>优先级</b></p><p>HTTP2 可以对比较紧急的请求设置一个较高的优先级,服务器在收到这样的请求后,可以优先处理。</p><p><b>流量控制</b></p><p>由于一个 TCP 连接流量带宽(根据客户端到服务器的网络带宽而定)是固定的,当有多个请求并发时,一个请求占的流量多,另一个请求占的流量就会少。流量控制可以对不同的流的流量进行精确控制。</p><p><b>服务器推送</b></p><p>HTTP2 新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。</p><p>例如当浏览器请求一个网站时,除了返回 HTML 页面外,服务器还可以根据 HTML 页面中的资源的 URL,来提前推送资源。</p><p>现在有很多网站已经开始使用 HTTP2 了,例如知乎:</p><figure><img src=\"92375faac532e9ec9147392322f083a1.webp\"> </figure><p><br>其中 h2 是指 HTTP2 协议,http/1.1 则是指 HTTP1.1 协议。</p><p><b>3. 使用服务端渲染</b></p><p>客户端渲染: 获取 HTML 文件,根据需要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。<br>服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML。</p><ul><li>优点:首屏渲染快,SEO 好。</li><li>缺点:配置麻烦,增加了服务器的计算压力。</li></ul><p> 下面用 Vue SSR 做示例,简单的描述一下 SSR 过程。</p><p><b>客户端渲染过程</b></p><ol><li>访问客户端渲染的网站。</li><li>服务器返回一个包含了引入资源语句和 <code><div id=\"app\"></div></code> 的 HTML 文件。</li><li>客户端通过 HTTP 向服务器请求资源,当必要资源都加载完毕后,执行 <code>new Vue()</code> 开始实例化并渲染页面。</li></ol><p><b> 服务端渲染过程</b></p><ol><li>访问服务端渲染的网站。</li><li>服务器会查看当前路由组件需要哪些资源文件,然后将这些文件的内容填充到 HTML 文件。如果有 ajax 请求,就会执行它进行数据预取并填充到 HTML 文件里,最后返回这个 HTML 页面。</li><li>当客户端接收到这个 HTML 页面时,就开始渲染页面 同时,页面也会加载资源,当必要的资源都加载完毕后,开始执行 <code>new Vue()</code> 开始实例化并接管页面。</li></ol><p> 从上述两个过程中可以看出,区别就在于第二步。客户端渲染的网站会直接返回 HTML 文件,而服务端渲染的网站则会渲染完页面再返回这个 HTML 文件。</p><p><b>这样做的好处是什么?是更快的内容到达时间 (time-to-content)</b>。<br><br>假设你的网站需要加载完 abcd 四个文件才能渲染完毕。并且每个文件大小为 1 M。</p><p>这样一算:客户端渲染的网站需要加载 4 个文件和 HTML 文件才能完成首页渲染,总计大小为 4M(忽略 HTML 文件大小)。而服务端渲染的网站只需要加载一个渲染完毕的 HTML 文件就能完成首页渲染,总计大小为已经渲染完毕的 HTML 文件(这种文件不会太大,一般为几百K,我的个人博客网站(SSR)加载的 HTML 文件为 400K)。<b>这就是服务端渲染更快的原因</b>。</p><p><b>4. 静态资源使用 CDN</b></p><p>内容分发网络(CDN)是一组分布在多个不同地理位置的 Web 服务器。我们都知道,当服务器离用户越远时,延迟越高。CDN 就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间</p><p><b>CDN 原理</b></p><p>当用户访问一个网站时,如果没有 CDN,过程是这样的:</p><ol><li>浏览器要将域名解析为 IP 地址,所以需要向本地 DNS 发出请求。</li><li>本地 DNS 依次向根服务器、顶级域名服务器、权限服务器发出请求,得到网站服务器的 IP 地址。</li><li>本地 DNS 将 IP 地址发回给浏览器,浏览器向网站服务器 IP 地址发出请求并得到资源。</li></ol><figure><img src=\"a00720b969643497c25deaddda5bf1b4.webp\"> </figure><p>如果用户访问的网站部署了 CDN,过程是这样的:</p><ol><li>浏览器要将域名解析为 IP 地址,所以需要向本地 DNS 发出请求。</li><li>本地DNS依次向根服务器、顶级域名服务器、权限服务器发出请求,得到全局负载均衡系统(GSLB)IP地址。</li><li>本地 DNS 再向 GSLB 发出请求,GSLB 的主要功能是根据本地 DNS 的 IP 地址判断用户的位置,筛选出距离用户较近的本地负载均衡系统(SLB),并将该 SLB 的 IP 地址作为结果返回给本地 DNS。</li><li>本地 DNS 将 SLB 的 IP 地址发回给浏览器,浏览器向 SLB 发出请求。</li><li>SLB 根据浏览器请求的资源和地址,选出最优的缓存服务器发回给浏览器。</li><li>浏览器再根据 SLB 发回的地址重定向到缓存服务器。</li><li>如果缓存服务器有浏览器需要的资源,就将资源发回给浏览器。如果没有,就向源服务器请求资源,再发给浏览器并缓存在本地。</li></ol><figure><img src=\"f73404cf54321afa2d67f38607e5a96e.webp\"> </figure><p><b>5. 将 CSS 放在文件头部,JavaScript 文件放在底部</b></p><ul><li>CSS 执行会阻塞渲染,阻止 JS 执行</li><li>JS 加载和执行会阻塞 HTML 解析,阻止 CSSOM 构建</li></ul><p> 如果这些 CSS、JS 标签放在 HEAD 标签里,并且需要加载和解析很久的话,那么页面就空白了。所以 JS 文件要放底部(不阻止 DOM 解析,但会阻塞渲染)等 HTML 解析完了再加载 JS 文件,尽早向用户呈现页面的内容。</p><p>那为什么 CSS 文件还要放在头部呢?</p><p>因为先加载 HTML 再加载 CSS,会让用户第一时间看到的页面是没有样式的、“丑陋”的,为了避免这种情况发生,就要将 CSS 文件放在头部了。<br> 另外,JS 文件也不是不可以放在头部,只要给 script 标签加上 defer 属性就可以了,异步下载,延迟执行。</p><p><b>6. 使用字体图标 iconfont 代替图片图标</b></p><p> 字体图标就是将图标制作成一个字体,使用时就跟字体一样,可以设置属性,例如 font-size、color 等等,非常方便。并且字体图标是矢量图,不会失真。还有一个优点是生成的文件特别小。<br> 压缩字体文件<br> 使用 <a href=\"https://link.zhihu.com/?target=https%3A//link.juejin.cn/%3Ftarget%3Dhttps%253A%252F%252Fgithub.com%252Fpatrickhulce%252Ffontmin-webpack\" target=\"_blank\">fontmin-webpack</a> 插件对字体文件进行压缩(感谢<a href=\"https://link.zhihu.com/?target=https%3A//juejin.im/user/237150239985165\" target=\"_blank\">前端小伟</a>提供)。<br> </p><figure><img src=\"79ce104104cb438e48c35cb086fd986a.webp\"> </figure><p><b>7. 善用缓存,不重复加载相同的资源</b></p><p>避免用户每次访问网站都得请求文件,可以通过添加 Expires 或 max-age 控制行为。Expires 设置了一个时间,只要在这个时间之前,浏览器都不会请求文件,而是直接使用缓存。而 max-age 是一个相对时间,建议使用 max-age 代替 Expires 。</p><p>不过这样会产生一个问题,当文件更新了怎么办?怎么通知浏览器重新请求文件?</p><p>可以通过更新页面中引用的资源链接地址,让浏览器主动放弃缓存,加载新资源。</p><p>具体做法是把资源地址 URL 的修改与文件内容关联起来,只有文件内容变化,才会导致相应 URL 的变更,从而实现文件级别的精确缓存控制。什么东西与文件内容相关呢?很自然的联想到利用<a href=\"https://link.zhihu.com/?target=https%3A//link.juejin.cn/%3Ftarget%3Dhttps%253A%252F%252Fbaike.baidu.com%252Fitem%252F%2525E6%2525B6%252588%2525E6%252581%2525AF%2525E6%252591%252598%2525E8%2525A6%252581%2525E7%2525AE%252597%2525E6%2525B3%252595%252F3286770%253Ffromtitle%253D%2525E6%252591%252598%2525E8%2525A6%252581%2525E7%2525AE%252597%2525E6%2525B3%252595%2526fromid%253D12011257\" target=\"_blank\">数据摘要要算法</a>对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种可以精确到单个文件粒度的缓存控制依据了。</p><p><b>8. 压缩文件</b></p><p> 压缩文件可以减少文件下载时间,让用户体验性更好。</p><p>得益于 webpack 和 node 的发展,现在压缩文件已经非常方便了。</p><p>在 webpack 可以使用如下插件进行压缩:</p><ul><li>JavaScript:UglifyPlugin</li><li>CSS :MiniCssExtractPlugin</li><li>HTML:HtmlWebpackPlugin</li></ul><p> 还可以做得更好。使用 gzip 压缩。通过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识开启这一功能。当然,服务器也得支持这一功能。</p><p>gzip 是目前最流行和最有效的压缩方法。举个例子,我用 Vue 开发的项目构建后生成的 app.js 文件大小为 1.4MB,使用 gzip 压缩后只有 573KB,体积减少了将近 60%。</p><p>附上 webpack 和 node 配置 gzip 的使用方法。</p><p><b>下载插件</b></p><div><pre><code>npm install compression-webpack-plugin --save-dev\nnpm install compression</code></pre></div><p><b>webpack 配置</b></p><div><pre><code>const CompressionPlugin = require(\'compression-webpack-plugin\');\n\nmodule.exports = {\n plugins: [new CompressionPlugin()],\n}</code></pre></div><p><b>node 配置</b></p><div><pre><code>const compression = require(\'compression\')\n// 在其他中间件前使用\napp.use(compression())</code></pre></div><p><b>9. 图片优化</b><br><br> <b>(1). 图片延迟加载</b><br><br> 页面中先不给图片设置路径,只有当图片出现在浏览器的可视区域时,才去加载真正的图片,这就是延迟加载。对于图片很多的网站来说,一次性加载全部图片,会对用户体验造成很大的影响,所以需要使用图片延迟加载。<br> 首先可以将图片这样设置,在页面不可见时图片不会加载:</p><div><pre><code><img data-src=\"https://avatars0.githubusercontent.com/u/22117876?s=460&u=7bd8f32788df6988833da6bd155c3cfbebc68006&v=4\"></code></pre></div><p>等页面可见时,使用 JS 加载图片:</p><div><pre><code>const img = document.querySelector(\'img\')\nimg.src = img.dataset.src</code></pre></div><h3>(2). 响应式图片</h3><p>响应式图片的优点是浏览器能够根据屏幕大小自动加载合适的图片,通过 <code>picture</code> 实现</p><div><pre><code><picture>\n <source srcset=\"banner_w1000.jpg\" media=\"(min-width: 801px)\">\n <source srcset=\"banner_w800.jpg\" media=\"(max-width: 800px)\">\n <img src=\"banner_w800.jpg\" alt=\"\">\n</picture></code></pre></div><p>通过<code>@media</code>实现</p><div><pre><code>@media (min-width: 769px) {\n .bg {\n background-image: url(bg1080.jpg);\n }\n}\n@media (max-width: 768px) {\n .bg {\n background-image: url(bg768.jpg);\n }\n}</code></pre></div><p><b>(3). 调整图片大小</b><br><br> 例如,有一个 1920 * 1080 大小的图片,用缩略图的方式展示给用户,并且当用户鼠标悬停在上面时才展示全图。如果用户从未真正将鼠标悬停在缩略图上,则浪费了下载图片的时间。</p><p>所以,可以用两张图片来实行优化。一开始,只加载缩略图,当用户悬停在图片上时,才加载大图。还有一种办法,即对大图进行延迟加载,在所有元素都加载完成后手动更改大图的 src 进行下载。<br><br><b>(4). 降低图片质量</b><br><br> 例如 JPG 格式的图片,100% 的质量和 90% 质量的通常看不出来区别,尤其是用来当背景图的时候。我经常用 PS 切背景图时, 将图片切成 JPG 格式,并且将它压缩到 60% 的质量,基本上看不出来区别。<br> <br>压缩方法有两种,一是通过 webpack 插件 <code>image-webpack-loader</code>,二是通过在线网站进行压缩。<br> 以下附上 webpack 插件 <code>image-webpack-loader</code> 的用法。</p><div><pre><code>npm i -D image-webpack-loader</code></pre></div><p>webpack 配置</p><div><pre><code>{\n test: /\\.(png|jpe?g|gif|svg)(\\?.*)?$/,\n use:[\n {\n loader: \'url-loader\',\n options: {\n limit: 10000, /* 图片大小小于1000字节限制时会自动转成 base64 码引用*/\n name: utils.assetsPath(\'img/[name].[hash:7].[ext]\')\n }\n },\n /*对图片进行压缩*/\n {\n loader: \'image-webpack-loader\',\n options: {\n bypassOnDebug: true,\n }\n }\n ]\n}</code></pre></div><p><b>(5). 尽可能利用 CSS3 效果代替图片</b><br><br> 有很多图片使用 CSS 效果(渐变、阴影等)就能画出来,这种情况选择 CSS3 效果更好。因为代码大小通常是图片大小的几分之一甚至几十分之一。</p><p><b> (6). 使用 webp 格式的图片</b><br><br> WebP 优势有更优图像数据压缩算法,带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。</p><p><b>10. 通过 webpack 按需加载代码,提取第三库代码,减少 ES6 转为 ES5 的冗余代码</b></p><blockquote>懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。</blockquote><p><b>根据文件内容生成文件名,结合 import 动态引入组件实现按需加载</b></p><p> 通过配置 output 的 filename 属性可以实现这个需求。filename 属性的值选项中有一个 [contenthash],它将根据文件内容创建出唯一 hash。当文件内容发生变化时,[contenthash] 也会发生变化。</p><div><pre><code>output: {\n filename: \'[name].[contenthash].js\',\n chunkFilename: \'[name].[contenthash].js\',\n path: path.resolve(__dirname, \'../dist\'),\n},</code></pre></div><h3>提取第三方库</h3><p>由于引入的第三方库一般都比较稳定,不会经常改变。所以将它们单独提取出来,作为长期缓存是一个更好的选择。 这里需要使用 webpack4 的 splitChunk 插件 cacheGroups 选项。</p><div><pre><code>optimization: {\n runtimeChunk: {\n name: \'manifest\' // 将 webpack 的 runtime 代码拆分为一个单独的 chunk。\n },\n splitChunks: {\n cacheGroups: {\n vendor: {\n name: \'chunk-vendors\',\n test: /[\\\\/]node_modules[\\\\/]/,\n priority: -10,\n chunks: \'initial\'\n },\n common: {\n name: \'chunk-common\',\n minChunks: 2,\n priority: -20,\n chunks: \'initial\',\n reuseExistingChunk: true\n }\n },\n }\n},</code></pre></div><ul><li>test: 用于控制哪些模块被这个缓存组匹配到。原封不动传递出去的话,它默认会选择所有的模块。可以传递的值类型:RegExp、String和Function;</li><li>priority:表示抽取权重,数字越大表示优先级越高。因为一个 module 可能会满足多个 cacheGroups 的条件,那么抽取到哪个就由权重最高的说了算;</li><li>reuseExistingChunk:表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。</li><li>minChunks(默认是1):在分割之前,这个代码块最小应该被引用的次数(译注:保证代码块复用性,默认配置的策略是不需要多次引用也可以被分割)</li><li>chunks (默认是async) :initial、async和all</li><li>name(打包的chunks的名字):字符串或者函数(函数可以根据条件自定义名字)</li></ul><p><b> 减少 ES6 转为 ES5 的冗余代码</b><br> <br>Babel 转化后的代码想要实现和原来代码一样的功能需要借助一些帮助函数,比如:</p><div><pre><code>class Person {}</code></pre></div><p>会被转换为:</p><div><pre><code>\"use strict\";\n\nfunction _classCallCheck(instance, Constructor) {\n if (!(instance instanceof Constructor)) {\n throw new TypeError(\"Cannot call a class as a function\");\n }\n}\n\nvar Person = function Person() {\n _classCallCheck(this, Person);\n};</code></pre></div><p>这里<code>_classCallCheck</code>是<code>helper</code>函数,如果很多文件里都声明了类,就会产生很多个这样的 <code>helper</code> 函数。<br><br>这里的<code>@babel/runtime</code> 包声明了所有需要用到的帮助函数,而<code>@babel/plugin-transform-runtime</code> 的作用是将所有需要<code>helper</code>函数的文件,从 <code>@babel/runtime包</code> 引进来:</p><div><pre><code>\"use strict\";\n\nvar _classCallCheck2 = require(\"@babel/runtime/helpers/classCallCheck\");\n\nvar _classCallCheck3 = _interopRequireDefault(_classCallCheck2);\n\nfunction _interopRequireDefault(obj) {\n return obj && obj.__esModule ? obj : { default: obj };\n}\n\nvar Person = function Person() {\n (0, _classCallCheck3.default)(this, Person);\n};</code></pre></div><p>这里就没有再编译出 <code>helper</code> 函数 <code>classCallCheck</code> 了,而是直接引用了 <code>@babel/runtime</code> 中的 <code>helpers/classCallCheck</code>。</p><p><b>安装</b></p><div><pre><code>npm i -D @babel/plugin-transform-runtime @babel/runtime</code></pre></div><p><b>使用</b>在<code>.babelrc</code>文件中</p><div><pre><code>\"plugins\": [\n \"@babel/plugin-transform-runtime\"\n]</code></pre></div><p><b>11. 减少重绘重排</b><br> <br><b>浏览器渲染过程</b></p><ol><li>解析HTML生成DOM树。</li><li>解析CSS生成CSSOM规则树。</li><li>解析JS,操作 DOM 树和 CSSOM 规则树。</li><li>将DOM树与CSSOM规则树合并在一起生成渲染树。</li><li>遍历渲染树开始布局,计算每个节点的位置大小信息。</li><li>浏览器将所有图层的数据发送给GPU,GPU将图层合成并显示在屏幕上。</li></ol><figure><img src=\"d45b59801c2df1518a10e5b86b3679b7.webp\"> </figure><p><b>重排</b><br><br> 当改变 DOM 元素位置或大小时,会导致浏览器重新生成渲染树,这个过程叫重排。<br> <br><b>重绘</b><br> <br>当重新生成渲染树后,就要将渲染树每个节点绘制到屏幕,这个过程叫重绘。不是所有的动作都会导致重排,例如改变字体颜色,只会导致重绘。记住,重排会导致重绘,重绘不会导致重排 。<br> <br>重排和重绘这两个操作都是非常昂贵的,因为 JavaScript 引擎线程与 GUI 渲染线程是互斥,它们同时只能一个在工作。<br> <br>什么操作会导致重排?</p><ul><li>添加或删除可见的 DOM 元素</li><li>元素位置改变</li><li>元素尺寸改变</li><li>内容改变</li><li>浏览器窗口尺寸改变</li></ul><p> 如何减少重排重绘?</p><ul><li>用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。</li><li>如果要对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement),都能很好的实现这个方案。</li></ul><p><b>12. 使用事件委托</b><br><br> 事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托可以节省内存。</p><div><pre><code><ul>\n <li>苹果</li>\n <li>香蕉</li>\n <li>凤梨</li>\n</ul>\n\n// good\ndocument.querySelector(\'ul\').onclick = (event) => {\n const target = event.target\n if (target.nodeName === \'LI\') {\n console.log(target.innerHTML)\n }\n}\n\n// bad\ndocument.querySelectorAll(\'li\').forEach((e) => {\n e.onclick = function() {\n console.log(this.innerHTML)\n }\n}) </code></pre></div><p><b>13. 注意程序的局部性</b><br><br> 一个编写良好的计算机程序常常具有良好的局部性,它们倾向于引用最近引用过的数据项附近的数据项,或者最近引用过的数据项本身,这种倾向性被称为局部性原理。有良好局部性的程序比局部性差的程序运行得更快。<br> <br><b>局部性通常有两种不同的形式:</b></p><ul><li>时间局部性:在一个具有良好时间局部性的程序中,被引用过一次的内存位置很可能在不远的将来被多次引用。</li><li>空间局部性 :在一个具有良好空间局部性的程序中,如果一个内存位置被引用了一次,那么程序很可能在不远的将来引用附近的一个内存位置。</li></ul><p> 时间局部性示例</p><div><pre><code>function sum(arry) {\n let i, sum = 0\n let len = arry.length\n\n for (i = 0; i < len; i++) {\n sum += arry[i]\n }\n\n return sum\n}</code></pre></div><p>例子中,变量sum在每次循环迭代中被引用一次,因此,对于sum来说,具有良好的时间局部性 空间局部性示例</p><p><b>具有良好空间局部性的程序</b></p><div><pre><code>// 二维数组 \nfunction sum1(arry, rows, cols) {\n let i, j, sum = 0\n\n for (i = 0; i < rows; i++) {\n for (j = 0; j < cols; j++) {\n sum += arry[i][j]\n }\n }\n return sum\n}</code></pre></div><p><b>空间局部性差的程序</b></p><div><pre><code>// 二维数组 \nfunction sum2(arry, rows, cols) {\n let i, j, sum = 0\n\n for (j = 0; j < cols; j++) {\n for (i = 0; i < rows; i++) {\n sum += arry[i][j]\n }\n }\n return sum\n}</code></pre></div><p>上面两个空间局部性示例,像示例中从每行开始按顺序访问数组每个元素方式,称具有步长为1的引用模式。 如果在数组中,每隔k个元素进行访问,就称为步长为k的引用模式。 一般而言,随着步长的增加,空间局部性下降。<br><br> 两个例子中区别?区别在于第一个示例是按行扫描数组,每扫描完一行再去扫下一行;第二个示例是按列来扫描数组,扫完一行中的一个元素,马上就去扫下一行中的同一列元素。<br> <br>数组在内存中是按照行顺序来存放的,结果就是逐行扫描数组的示例得到了步长为 1 引用模式,具有良好的空间局部性;而另一个示例步长为 rows,空间局部性极差。<br> <br><b>性能测试</b></p><p>运行环境:</p><ul><li>cpu: i5-7400</li><li>浏览器: chrome 70.0.3538.110</li></ul><p>对长度为9000二维数组(子数组长度也为9000)进行10次空间局部性测试,时间(毫秒)取平均值,结果如下:<br><br> 所用示例为上述两个空间局部性示例</p><table><tbody><tr><th>步长为 1</th><th>步长为 9000</th></tr><tr><td>124</td><td>2316</td></tr></tbody></table><p> 以上测试结果来看,步长为 1 的数组执行时间比步长为 9000 的数组快了一个数量级。</p><p>总结:</p><ul><li>重复引用相同变量的程序具有良好的时间局部性</li><li>对于具有步长为 k 的引用模式的程序,步长越小,空间局部性越好;而在内存中以大步长跳来跳去的程序空间局部性会很差</li></ul><p> 参考资料:</p><ul><li><a href=\"https://link.zhihu.com/?target=https%3A//link.juejin.cn/%3Ftarget%3Dhttps%253A%252F%252Fbook.douban.com%252Fsubject%252F26912767%252F\" target=\"_blank\">深入理解计算机系统</a></li></ul><p><b> 14. if-else 对比 switch</b><br><br> 当判断条件数量越来越多时,越倾向于使用 switch 而不是 if-else。</p><div><pre><code>if (color == \'blue\') {\n\n} else if (color == \'yellow\') {\n\n} else if (color == \'white\') {\n\n} else if (color == \'black\') {\n\n} else if (color == \'green\') {\n\n} else if (color == \'orange\') {\n\n} else if (color == \'pink\') {\n\n}\n\nswitch (color) {\n case \'blue\':\n\n break\n case \'yellow\':\n\n break\n case \'white\':\n\n break\n case \'black\':\n\n break\n case \'green\':\n\n break\n case \'orange\':\n\n break\n case \'pink\':\n\n break\n}</code></pre></div><p>像上面的这种情况,从可读性来说,使用 switch 是比较好的(js 的 switch 语句不是基于哈希实现,而是循环判断,所以说 if-else、switch 从性能上来说是一样的)。</p><p><b>15. 查找表</b><br><br> 当条件语句特别多时,使用 switch 和 if-else 不是最佳的选择,这时不妨试一下查找表。查找表可以使用数组和对象来构建。</p><div><pre><code>switch (index) {\n case \'0\':\n return result0\n case \'1\':\n return result1\n case \'2\':\n return result2\n case \'3\':\n return result3\n case \'4\':\n return result4\n case \'5\':\n return result5\n case \'6\':\n return result6\n case \'7\':\n return result7\n case \'8\':\n return result8\n case \'9\':\n return result9\n case \'10\':\n return result10\n case \'11\':\n return result11\n}</code></pre></div><p>可以将这个 switch 语句转换为查找表</p><div><pre><code>const results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,result11]\n\nreturn results[index]</code></pre></div><p>如果条件语句不是数值而是字符串,可以用对象来建立查找表</p><div><pre><code>const map = {\n red: result0,\n green: result1,\n}\n\nreturn map[color]</code></pre></div><p><b>16. 避免页面卡顿</b></p><p><b>60fps 与设备刷新率</b></p><blockquote>目前大多数设备的屏幕刷新率为 60 次/秒。因此,如果在页面中有一个动画或渐变效果,或者用户正在滚动页面,那么浏览器渲染动画或页面的每一帧的速率也需要跟设备屏幕的刷新率保持一致。</blockquote><p>其中每个帧的预算时间仅比16毫秒多一点 (1 秒/ 60 = 16.66 毫秒)。但实际上,浏览器有整理工作要做,因此所有工作需在10毫秒内完成。如果无法符合此预算,帧率将下降,并且内容会在屏幕上抖动。 此现象通常称为卡顿,会对用户体验产生负面影响。</p><figure><img src=\"042ceb98d6d3766777cd4b0a19ba31e2.webp\"> </figure><p>假如你用JavaScript修改了DOM并触发样式修改,经历重排重绘最后画到屏幕上。如果这其中任意一项的执行时间过长,都会导致渲染这一帧的时间过长,平均帧率就会下降。假设这一帧花了 50 ms,此时的帧率为 1s / 50ms = 20fps,页面看起来就像卡顿了一样。<br> <br>对于一些长时间运行的 JavaScript,我们可以使用定时器进行切分,延迟执行。</p><div><pre><code>for (let i = 0, len = arry.length; i < len; i++) {\n process(arry[i])\n}</code></pre></div><p>假设上面的循环结构由于 process() 复杂度过高或数组元素太多,甚至两者都有,可以尝试一下切分。</p><div><pre><code>const todo = arry.concat()\nsetTimeout(function() {\n process(todo.shift())\n if (todo.length) {\n setTimeout(arguments.callee, 25)\n } else {\n callback(arry)\n }\n}, 25)</code></pre></div><p><b>17. 使用 requestAnimationFrame 来实现视觉变化</b><br> <br>从第16点可知道,大多数设备屏幕刷新率为 60 次/秒,每一帧的平均时间为 16.66 毫秒。使用 JavaScript 实现动画效果的时候,最好的情况就是每次代码都是在帧的开头开始执行。而保证 JavaScript 在帧开始时运行的唯一方式是使用 <code>requestAnimationFrame</code>。</p><div><pre><code>/**\n * If run as a requestAnimationFrame callback, this\n * will be run at the start of the frame.\n */\nfunction updateScreen(time) {\n // Make visual updates here.\n}\n\nrequestAnimationFrame(updateScreen);</code></pre></div><p>如果采取<code>setTimeout</code>或<code>setInterval</code>实现动画的话,回调函数将在帧中的某个时点运行,可能刚好在末尾,而这可能经常会使我们丢失帧,导致卡顿。</p><figure><img src=\"7c6ddea478f68ac02d3d78366df850e5.webp\"> </figure><p><b>18. 使用 Web Workers</b><br> <br>Web Worker 使用其他工作线程从而独立于主线程之外,它可以执行任务而不干扰用户界面。一个 worker 可以将消息发送到创建它的 JavaScript 代码, 通过将消息发送到该代码指定的事件处理程序(反之亦然)。<br> Web Worker 适用于那些处理纯数据,或者与浏览器 UI 无关的长时间运行脚本。<br> <br>创建一个新的 worker 很简单,指定一个脚本的 URI 来执行 worker 线程(main.js):</p><div><pre><code>var myWorker = new Worker(\'worker.js\');\n// 你可以通过postMessage() 方法和onmessage事件向worker发送消息。\nfirst.onchange = function() {\n myWorker.postMessage([first.value,second.value]);\n console.log(\'Message posted to worker\');\n}\n\nsecond.onchange = function() {\n myWorker.postMessage([first.value,second.value]);\n console.log(\'Message posted to worker\');\n}</code></pre></div><p>在 worker 中接收到消息后,我们可以写一个事件处理函数代码作为响应(worker.js):</p><div><pre><code>onmessage = function(e) {\n console.log(\'Message received from main script\');\n var workerResult = \'Result: \' + (e.data[0] * e.data[1]);\n console.log(\'Posting message back to main script\');\n postMessage(workerResult);\n}</code></pre></div><p>onmessage处理函数在接收到消息后马上执行,代码中消息本身作为事件的data属性进行使用。这里我们简单的对这2个数字作乘法处理并再次使用postMessage()方法,将结果回传给主线程。<br> <br>回到主线程,再次使用onmessage以响应worker回传的消息:</p><div><pre><code>myWorker.onmessage = function(e) {\n result.textContent = e.data;\n console.log(\'Message received from worker\');\n}</code></pre></div><p>在这里我们获取消息事件的data,并且将它设置为result的textContent,所以用户可以直接看到运算的结果。<br> <br>在worker内,不能直接操作DOM节点,也不能使用window对象的默认方法和属性。可使用大量window对象之下的,包括WebSockets,IndexedDB以及FireFox OS专用的Data Store API等数据存储机制。</p><p><b>19. 使用位操作</b><br><br> JavaScript 中的数字都使用 IEEE-754 标准以 64 位格式存储。但是在位操作中,数字被转换为有符号的 32 位格式。即使需要转换,位操作也比其他数学运算和布尔操作快得多。<br><br><b>取模</b><br> <br>由于偶数的最低位为 0,奇数为 1,所以取模运算可以用位操作来代替。</p><div><pre><code>if (value % 2) {\n // 奇数\n} else {\n // 偶数 \n}\n// 位操作\nif (value & 1) {\n // 奇数\n} else {\n // 偶数\n}</code></pre></div><p><b>取整</b></p><div><pre><code>~~10.12 // 10\n~~10 // 10\n~~\'1.5\' // 1\n~~undefined // 0\n~~null // 0</code></pre></div><p><b>拉掩码</b></p><div><pre><code>const a = 1\nconst b = 2\nconst c = 4\nconst options = a | b | c</code></pre></div><p>通过定义这些选项,可以用按位与操作来判断 a/b/c 是否在 options 中。</p><div><pre><code>// 选项 b 是否在选项中\nif (b & options) {\n ...\n}</code></pre></div><p><b>20. 不要覆盖原生方法</b><br> <br>无论你的 JavaScript 代码如何优化,都比不上原生方法。因为原生方法是用低级语言写的(C/C++),并且被编译成机器码,成为浏览器的一部分。当原生方法可用时,尽量使用它们,特别是数学运算和 DOM 操作。<br> <br><b>21. 降低 CSS 选择器的复杂性</b><br> <br>(1). 浏览器读取选择器,遵循的原则是从选择器的右边到左边读取。看个示例</p><div><pre><code>#block .text p {\n color: red;\n}</code></pre></div><ol><li>查找所有 P 元素。</li><li>查找结果 1 中的元素是否有类名为 text 的父元素</li><li>查找结果 2 中的元素是否有 id 为 block 的父元素</li></ol><h3>(2). CSS 选择器优先级</h3><div><pre><code>内联 > ID选择器 > 类选择器 > 标签选择器</code></pre></div><p>根据以上两个信息可以得出结论。</p><ol><li>选择器越短越好。</li><li>尽量使用高优先级的选择器,例如 ID 和类选择器。</li><li>避免使用通配符 *。</li></ol><p>最后要说一句,据我查找的资料所得,CSS 选择器没有优化的必要,因为最慢和慢快的选择器性能差别非常小。</p><p><b>22. 使用 flexbox 而不是较早的布局模型</b><br> <br>在早期的 CSS 布局方式中我们能对元素实行绝对定位、相对定位或浮动定位。而现在,我们有了新的布局方式 <a href=\"https://link.zhihu.com/?target=https%3A//link.juejin.cn/%3Ftarget%3Dhttps%253A%252F%252Fdeveloper.mozilla.org%252Fzh-CN%252Fdocs%252FWeb%252FCSS%252FCSS_Flexible_Box_Layout%252FBasic_Concepts_of_Flexbox\" target=\"_blank\">flexbox</a>,它比起早期的布局方式来说有个优势,那就是性能比较好。<br> <br>下面的截图显示了在 1300 个框上使用浮动的布局开销:</p><figure> <img src=\"b78140ef46ad998e4cdd069c0846db0e.webp\"><br> </figure><p>然后用 flexbox 来重现这个例子:</p><figure> <img src=\"684cbaf2f15b744b3ef1b5594bf2e420.webp\"><br> </figure><p>现在,对于相同数量的元素和相同的视觉外观,布局的时间要少得多(本例中为分别 3.5 毫秒和 14 毫秒)。<br> flexbox 兼容性还是有点问题,不是所有浏览器都支持它,所以要谨慎使用。<br> <br>各浏览器兼容性:</p><ul><li>Chrome 29+</li><li>Firefox 28+</li><li>Internet Explorer 11</li><li>Opera 17+</li><li>Safari 6.1+ (prefixed with -webkit-)</li><li>Android 4.4+</li><li>iOS 7.1+ (prefixed with -webkit-)</li></ul><p><b>23. 使用 transform 和 opacity 属性更改来实现动画</b><br> <br>在CSS 中transforms 和 opacity 两个属性更改不会触发重排与重绘,可由合成器(composite)单独处理属性。</p><figure> <img src=\"11f77d329d1f668e698da02ec6ef387c.webp\"><br> </figure><p><b>24. 合理使用规则,避免过度优化</b><br> <br>性能优化主要分为两类:</p><ol><li>加载时优化</li><li>运行时优化</li></ol><p>上述 23 条建议中,属于加载时优化的是前面 10 条建议,属于运行时优化的是后面 13 条建议。通常来说,没有必要 23 条性能优化规则都用上,根据网站用户群体来做针对性的调整是最好的,节省精力,节省时间。<br> <br>解决问题前,先找出问题,否则无从下手。所以在做性能优化之前,最好先调查一下网站的加载性能和运行性能。<br> <br><b>检查加载性能</b><br> <br>一个网站加载性能如何主要看白屏时间和首屏时间。</p><ul><li>白屏时间:指从输入网址,到页面开始显示内容的时间。</li><li>首屏时间:指从输入网址,到页面完全渲染的时间。</li></ul><p> 将以下脚本放在 <code></head></code> 前面就能获取白屏时间。</p><div><pre><code><script>\n new Date() - performance.timing.navigationStart\n // 通过 domLoading 和 navigationStart 也可以\n performance.timing.domLoading - performance.timing.navigationStart\n</script></code></pre></div><p>在<code>window.onload</code>事件里执行<code>new Date() - performance.timing.navigationStart</code> 即可获取首屏时间。<br> <br><b>检查运行性能</b><br><br> 配合 chrome 的开发者工具,可查看网站在运行时的性能。<br> <br>打开网站,按 F12 选择 performance,点击左上角的灰色圆点,变成红色就代表开始记录了。这时可以模仿用户使用网站,在使用完毕后,点击 stop,然后你就能看到网站运行期间的性能报告。如果有红色的块,代表有掉帧的情况;如果是绿色,则代表 FPS 很好。performance 的具体使用方法请用搜索引擎搜索一下,毕竟篇幅有限。<br><br></p>', '2022-01-13 17:08:53', '前端性能优化 24 条建议', 141),
(47, 'nextjsvsremixjs', 'admin', 'React,前端,资讯', '<h2><img src=\"22373098f5ef41e74511cc8deafc9a2d.webp\"></h2><h2>横向对比</h2><p>先上一行对比图:</p><table><thead><tr><th> </th><th>Next.js</th><th>Remix</th></tr></thead><tbody><tr><td>SSG静态站点生成</td><td>✅内置</td><td>?不支持</td></tr><tr><td>SSR服务器端渲染</td><td>✅内置</td><td>✅通过 <code>loader</code></td></tr><tr><td>API 路由</td><td>✅<code>pages/api/</code> 目录下</td><td>?Remix 就是路由,你可以更加灵活去进行自定义路由</td></tr><tr><td>Forms表单</td><td>? 非内置</td><td>✅ 内置,且功能强大</td></tr><tr><td>基于文件系统的路由管理</td><td>✅ 页面级</td><td>✅ 组件级</td></tr><tr><td>会话管理</td><td>? 非内置</td><td>✅ 内置 Cookie、Sessions</td></tr><tr><td>禁用 JS</td><td>? 未提供充分支持</td><td>✅ 静态页面路由</td></tr><tr><td>样式</td><td>✅ 提供了全局及组件级样式支持 TailwindCSS 等</td><td>? 非内置</td></tr><tr><td>嵌套布局</td><td>? 不支持</td><td>✅内置</td></tr><tr><td>i18n国际化</td><td>✅内置</td><td>? 非内置</td></tr><tr><td>图片优化</td><td>✅通过 <code>next/image</code> 组件</td><td>✅通过简单转换、备选质量等方式</td></tr><tr><td>谷歌 AMP</td><td>✅内置</td><td>? 非内置</td></tr><tr><td>适配器</td><td>Node.js Request 和 Response 接口</td><td>Fetch API Request 和 Response 接口</td></tr><tr><td>Preload</td><td>链接自动</td><td>非自动</td></tr><tr><td>异常处理</td><td>创建 404,500 等页面</td><td>使用 <code>ErrorBoundary</code> 组件局部抛错</td></tr><tr><td>Polyfill</td><td><code>fetch</code>、<code>Object.assign</code> 和 <code>URL</code></td><td><code>fetch</code></td></tr></tbody></table><h2>适用场景</h2><h3>Next.js</h3><p>静态网站。这是其最大优势。在使用 TailwindCSS 等,可以更加灵活的制作出样式优美的页面及组件。拥有着较为完善的生态圈。</p><p>适合快速上手做项目。</p><h3>Remix</h3><p>管理后台,对于数据的加载、嵌套数据或者组件的路由、并发加载优化做得很好,并且异常的处理已经可以精确到局部级别。</p><p>或许是下一代的 Web 开发框架,需要折腾。</p><h3>小结</h3><ul><li>数据复杂,内容较多(如可视化大屏): Remix</li><li>包含表单和会话的管理系统: Remix</li><li>SEO 友好的网站: Next.js</li><li>纯静态部署: Next.js</li><li>国际化支持: Next.js</li></ul>', '2022-01-18 23:35:41', 'Next.js对比Remix.js', 80),
(49, 'eeeae2f9d42160ee3e583c2b1d63af87', 'admin', '前端,Vue', '<p><span><b><font size=\"6\">前言</font></b></span></p>\n<p>最近鼓捣了一下 Vue3 + Vite2,遇到了不少问题,整理了5个可以提高开发效率的小知识,让你在 Vue3 的项目开发中更加丝滑、顺畅。</p>\n<h1>一、setup name 增强</h1>\n<p><code>Vue3</code>的<code>setup</code>语法糖是个好东西,但使用<code>setup</code>语法带来的第一个问题就是无法自定义<code>name</code>,而我们使用<code>keep-alive</code>往往是需要<code>name</code>的,解决这个问题通常是通过写两个<code>script</code>标签来解决,一个使用<code>setup</code>,一个不使用,但这样必然是不够优雅的。</p>\n<pre><code><script lang=<span>\"ts\"</span>>\n<span>import</span> { defineComponent, onMounted } <span>from</span> <span>\'vue\'</span>\n\n<span>export</span> <span>default</span> defineComponent({\n <span>name</span>: <span>\'OrderList\'</span>\n})\n</script>\n\n<span><span><<span>script</span> <span>lang</span>=<span>\"ts\"</span> <span>setup</span>></span><span>\nonMounted(<span>() =></span> {\n <span>console</span>.log(<span>\'mounted===\'</span>)\n})\n</span><span></<span>script</span>></span></span>\n</code></pre>\n<p>这时候借助插件<code>vite-plugin-vue-setup-extend</code>可以让我们更优雅的解决这个问题,不用写两个<code>script</code>标签,可以直接在<code>script</code>标签上定义<code>name</code>。</p>\n<h2>安装</h2>\n<pre><code>npm i vite-plugin-vue-setup-extend -D\n</code></pre>\n<h2>配置</h2>\n<pre><code><span>// vite.config.ts</span>\n<span>import</span> { defineConfig } <span>from</span> <span>\'vite\'</span>\n<span>import</span> VueSetupExtend <span>from</span> <span>\'vite-plugin-vue-setup-extend\'</span>\n\n<span>export</span> <span>default</span> defineConfig({\n <span>plugins</span>: [\n VueSetupExtend()\n ]\n})\n</code></pre>\n<h2>使用</h2>\n<pre><code><script lang=<span>\"ts\"</span> setup name=<span>\"OrderList\"</span>>\n<span>import</span> { onMounted } <span>from</span> <span>\'vue\'</span>\n\nonMounted(<span>() =></span> {\n <span>console</span>.log(<span>\'mounted===\'</span>)\n})\n</script>\n</code></pre>\n<h1>二、API 自动导入</h1>\n<p><code>setup</code>语法让我们不用再一个一个的把变量和方法都<code>return</code>出去就能在模板上使用,大大的解放了我们的双手。然而对于一些常用的<code>Vue</code>API,比如<code>ref</code>、<code>computed</code>、<code>watch</code>等,还是每次都需要我们在页面上手动进行<code>import</code>。</p>\n<p>我们可以通过<code>unplugin-auto-import</code>实现自动导入,无需<code>import</code>即可在文件里使用<code>Vue</code>的API。</p>\n<h2>安装</h2>\n<pre><code>npm i unplugin-auto-<span>import</span> -D\n</code></pre>\n<h2>配置</h2>\n<pre><code><span>// vite.config.ts</span>\n<span>import</span> { defineConfig } <span>from</span> <span>\'vite\'</span>\n<span>import</span> AutoImport <span>from</span> <span>\'unplugin-auto-import/vite\'</span>\n\n<span>export</span> <span>default</span> defineConfig({\n <span>plugins</span>: [\n AutoImport({\n <span>// dts: \'src/auto-imports.d.ts\', // 可以自定义文件生成的位置,默认是根目录下</span>\n <span>imports</span>: [<span>\'vue\'</span>]\n })\n ]\n})\n</code></pre>\n<p>安装配置完会自动生成<code>auto-imports.d.ts</code>文件。</p>\n<pre><code><span>// auto-imports.d.ts</span>\n<span>// Generated by \'unplugin-auto-import\'</span>\n<span>// We suggest you to commit this file into source control</span>\n<span>declare</span> <span>global</span> {\n <span>const</span> computed: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'computed\'</span>]\n <span>const</span> createApp: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'createApp\'</span>]\n <span>const</span> customRef: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'customRef\'</span>]\n <span>const</span> defineAsyncComponent: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'defineAsyncComponent\'</span>]\n <span>const</span> defineComponent: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'defineComponent\'</span>]\n <span>const</span> effectScope: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'effectScope\'</span>]\n <span>const</span> EffectScope: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'EffectScope\'</span>]\n <span>const</span> getCurrentInstance: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'getCurrentInstance\'</span>]\n <span>const</span> getCurrentScope: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'getCurrentScope\'</span>]\n <span>const</span> h: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'h\'</span>]\n <span>const</span> inject: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'inject\'</span>]\n <span>const</span> isReadonly: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'isReadonly\'</span>]\n <span>const</span> isRef: <span>typeof</span> <span>import</span>(<span>\'vue\'</span>)[<span>\'isRef\'</span>]\n <span>// ...</span>\n}\n<span>export</span> {}\n</code></pre>\n<h2>使用</h2>\n<pre><code><script lang=<span>\"ts\"</span> setup name=<span>\"OrderList\"</span>>\n<span>// 不用import,直接使用ref</span>\n<span>const</span> count = ref(<span>0</span>)\n\nonMounted(<span>() =></span> {\n <span>console</span>.log(<span>\'mounted===\'</span>)\n})\n</script>\n</code></pre>\n<p>上面我们在<code>vite.config.ts</code>的配置里只导入了<code>vue</code>,<code>imports: [\'vue\']</code>,除了<code>vue</code>的你也可以根据文档导入其他的如<code>vue-router</code>、<code>vue-use</code>等。</p>\n<p>个人建议只对一些比较熟悉的API做自动导入,如<code>vue</code>的API我们在开发时都比较熟悉,闭着眼都能写出来,对于一些不大熟悉的像<code>VueUse</code>这种库,还是使用<code>import</code>更好一些,毕竟编辑器都有提示,不易写错。</p>\n<h2>解决<code>eslint</code>报错问题</h2>\n<p>在没有<code>import</code>的情况下使用会导致<code>eslint</code>提示报错,可以通过在<code>eslintrc.js</code>安装插件<code>**vue-global-api**</code>解决。</p>\n<pre><code>// 安装依赖\nnpm i vue-global-api -D\n\n// eslintrc.js\nmodule.exports = {\n extends: [\n \'vue-global-api\'\n ]\n}\n</code></pre>\n<h1>三、告别.value</h1>\n<p>众所周知,<code>ref</code>要求我们访问变量时需要加上<code>.value</code>,这让很多开发者觉得难受.</p>\n<pre><code><span>let</span> count = ref(<span>1</span>)\n\n<span>const</span> addCount = <span>() =></span> {\n count.value += <span>1</span>\n}\n</code></pre>\n<p>后来尤大大也提交了一份新的<code>ref</code>语法糖提案。</p>\n<pre><code>ref: count = <span>1</span>\n\n<span>const</span> addCount = <span>() =></span> {\n count += <span>1</span>\n}\n</code></pre>\n<p>该提案一出便引起了社区的一片讨论,时间已经过去很久了,这里就不再废话这个话题了。</p>\n<p>这里我介绍的是另外一种写法,也是官方后来出的一种方案,在<code>ref</code>前加上<code>$</code>,该功能默认关闭,需要手动开启。</p>\n<pre><code><span>// vite.config.ts</span>\n<span>import</span> { defineConfig } <span>from</span> <span>\'vite\'</span>\n<span>import</span> vue <span>from</span> <span>\'@vitejs/plugin-vue\'</span>\n\n<span>export</span> <span>default</span> defineConfig({\n <span>plugins</span>: [\n vue({\n <span>refTransform</span>: <span>true</span> <span>// 开启ref转换</span>\n })\n ]\n})\n</code></pre>\n<p>开启之后可以这样写:</p>\n<pre><code><span>let</span> count = $ref(<span>1</span>)\n\n<span>const</span> addCount = <span>() =></span> {\n count++\n}\n</code></pre>\n<p>该语法糖根据不同的版本配置也略有不同,下面贴一下我自己所用相关插件的版本:</p>\n<pre><code>\"vue\": \"^3.2.2\",\n\"@vitejs/plugin-vue\": \"^1.9.0\",\n\"@vue/compiler-sfc\": \"^3.2.5\",\n\"vite\": \"^2.6.13\"\n</code></pre>\n<h1>四、自动导入图片</h1>\n<p>在<code>Vue2</code>时我们经常会这样引用图片:</p>\n<pre><code><span><<span>img</span> <span>:src</span>=<span>\"require(\'@/assets/image/logo.png\')\"</span> /></span>\n</code></pre>\n<p>但在<code>Vite</code>中不支持<code>require</code>了,引用图片变成了下面这样:</p>\n<pre><code><template>\n <span><span><<span>img</span> <span>:src</span>=<span>\"Logo\"</span> /></span></span>\n</template>\n\n<span><span><<span>script</span> <span>lang</span>=<span>\"ts\"</span> <span>setup</span>></span><span>\n<span>import</span> Logo <span>from</span> <span>\'@/assets/image/logo.png\'</span>\n</span><span></<span>script</span>></span></span>\n</code></pre>\n<p>每次使用图片都得<code>import</code>,显然耽误了大家摸鱼的时间,这时我们可以借助<code>vite-plugin-vue-images</code>来实现自动导入图片。</p>\n<h2>安装</h2>\n<pre><code>npm i vite-plugin-vue-images -D\n</code></pre>\n<h2>配置</h2>\n<pre><code><span>// vite.config.ts</span>\n<span>import</span> { defineConfig } <span>from</span> <span>\'vite\'</span>\n<span>import</span> ViteImages <span>from</span> <span>\'vite-plugin-vue-images\'</span>\n\n<span>export</span> <span>default</span> defineConfig({\n <span>plugins</span>: [\n ViteImages({\n <span>dirs</span>: [<span>\'src/assets/image\'</span>] <span>// 指明图片存放目录</span>\n })\n ]\n})\n</code></pre>\n<h2>使用</h2>\n<pre><code><template>\n <!-- 直接使用 -->\n <span><span><<span>img</span> <span>:src</span>=<span>\"Logo\"</span> /></span></span>\n</template>\n\n<span><span><<span>script</span> <span>lang</span>=<span>\"ts\"</span> <span>setup</span>></span><span>\n<span>// import Logo from \'@/assets/image/logo.png\'</span>\n</span><span></<span>script</span>></span></span>\n</code></pre>\n<h1>五、忽略.vue后缀</h1>\n<p>相信很多人在<code>Vue2</code>开发时,导入文件都是忽略.vue后缀的。但在<code>Vite</code>里,忽略.vue后缀会引起报错。</p>\n<pre><code><span>import</span> Home <span>from</span> <span>\'@/views/home\'</span> <span>// error</span>\n<span>import</span> Home <span>from</span> <span>\'@/views/home.vue\'</span> <span>// ok</span>\n</code></pre>\n<p>根据尤大大的回答,必须写后缀其实是故意这么设计的,也就是提倡大家这么去写。</p>\n<p>但如果你真的不想写,官方也是提供了支持的。</p>\n<pre><code><span>// vite.config.ts</span>\n<span>import</span> { defineConfig } <span>from</span> <span>\'vite\'</span>\n\n<span>export</span> <span>default</span> defineConfig({\n <span>resolve</span>: {\n <span>extensions</span>: [<span>\'.js\'</span>, <span>\'.ts\'</span>, <span>\'.jsx\'</span>, <span>\'.tsx\'</span>, <span>\'.json\'</span>, <span>\'.vue\'</span>]\n }\n})\n</code></pre>\n<p>这里要注意,手动配置<code>extensions</code>要记得把其他类型的文件后缀也加上,因为其他类型如js等文件默认是可以忽略后缀导入的,不写上的话其他类型文件的导入就变成需要加后缀了。</p>\n<blockquote>\n<p>虽然可以这么做,不过毕竟官方文档说了不建议忽略.vue后缀,所以建议大家在实际开发中还是老老实实写上.vue。</p>\n</blockquote>\n<h1>感谢</h1>\n<p>本次分享到这里就结束了,<strong>感谢您的阅读</strong>!予人玫瑰,手有余香,别忘了动动手指<strong>点个赞</strong>❤️。</p>\n<p>本文如果有什么错误或不足,也欢迎评论区指正!</p>', '2022-01-18 23:35:41', '5个知识点,让 Vue3 开发更加丝滑', 253),
(55, 'javascriptoptimization', 'admin', 'JavaScript', '<h1>写好JavaScript的三个原则:各司其责 组件封装 过程抽象</h1><h2>各司其责</h2><ul><li><p>HTML/CSS/JavaScript各司其责</p><ul><li>HTML -> Structural ; CSS -> Presentational ; JavaScript -> Behavioral</li></ul></li><li>应当避免不必要的由JS直接操作样式</li><li>可以用class来表示状态</li><li>纯展示类交互应寻求零JS方案</li></ul><h2>组件封装</h2><p>组件是指Web页面上抽出来的一个个包含模块(HTML)、样式(CSS)和功能(JS)的单元。好的组件具备封装性、正确性、扩展性、复用性。实现组件的步骤:结构设计、展现效果、行为设计,三次重构:插件化重构、模板化重构、抽象化重构。</p><ul><li>结构设计:HTML</li><li>展现效果:CSS</li><li><p>行为设计:JS</p><ul><li>API(功能),API 设计应保证原子操作,职责单一,满足灵活性。</li><li>Event(控制流),使用自定义事件来解耦。</li></ul></li><li><p>插件化重构,即解耦</p><ul><li>将控制元素抽取成插件</li><li>插件与组件之间通过依赖注入方式建立联系</li></ul></li><li><p>模板化重构</p><ul><li>将HTML模板化,更易于扩展</li></ul></li><li><p>抽象化重构(组件框架)</p><ul><li>将通用的组件模型抽象出来</li></ul></li></ul><h2>过程抽象</h2><p>过程抽象是指用来处理局部细节控制的一些方法,是函数式编程思想的基础应用。</p><ul><li><p>高阶函数</p><ul><li>以函数作为参数</li><li>以函数作为返回值</li><li>常用于函数装饰器</li></ul></li></ul><pre><code>//零阶高阶函数,等价于直接调用函数\nfunction HOF_0 (fn){\n return function(...args){\n return fn.apply(this.args);\n }\n}</code></pre><ul><li>构造 once 高阶函数,为了能够让“只执行一次”的需求(例如一些异步操作、一次性的HTTP请求)覆盖不同的事件处理,我们可以将这个需求利用闭包剥离出来。这个过程称为过程抽象。</li></ul><pre><code>function once ( fn ) {\n return function (...args){\n const res = fn.allpy (this.args);\n fn = null;\n return res;\n }\n}</code></pre><ul><li><p>防抖函数,在第一次触发事件时,不立即执行函数,而是给出一个期限值,如果在期限值内没有再次触发滚动事件,那么就执行函数,如果在期限值内再次触发滚动事件,那么当前的计时取消,重新开始计时</p><pre><code>const debounce = (fn, delay) => {\n let timer = null; \n //借助闭包\n return function () {\n clearTimeout(timer);\n timer = setTimeout(fn, delay);\n }\n}</code></pre></li><li><p>节流函数,类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活。效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。</p><pre><code>const myThrottle = (fn, delay) => {\n let flag = true;\n return function () {\n if (!flag) {\n return false;\n } else {\n flag = false;\n setTimeout(() => {\n fn();\n flag = true;\n }, delay);\n }\n }\n}</code></pre></li><li><p>为什么要使用高阶函数?</p><ul><li>减少系统内非纯函数的数量,提高系统的可测试性和稳定性。</li></ul></li></ul><h1>写好 JavaScript</h1><h2>写代码应该关注</h2><ul><li>效率</li><li>风格</li><li>使用场景</li><li>约定</li><li>设计<br>具体的代码实现要因场景而定,不同的场景注重点不一样,例如在某些比较底层的场景,可能更注重效率,而在多人协作的时候可能更关注约定。</li></ul><h2>判断是否是4的幂</h2><ul><li><p>常规操作</p><pre><code>const isPowerOfFour = (num) => { \n num = parseInt(num); \n while(num > 1){ \n if(num % 4) return false; \n num /= 4; \n } \n return true;\n}</code></pre></li><li><p>优化版本1,利用 4 的幂的二进制数最高位为1,低位为偶数个0</p><pre><code>const isPowerOfFour = (num) => {\n num = parseInt(num);\n return num > 0 &&\n (num & (num-1)) === 0 &&\n (num & 0XAAAAAAAA) === 0;\n}</code></pre></li><li>优化版本2,利用正则表达式</li></ul><pre><code>const isPowerOfFour = (num) => {\n num = parseInt(num).toString(2);\n return /^1(?:00)*$/.test(num);\n}</code></pre><h2>实现交通灯切换效果</h2><ul><li><p>版本一,利用setTimeout,可能出现回调地狱</p><pre><code>const traffic = document.getElementById(\'traffic\'); \n(function reset(){ \n traffic.className = \'s1\'; \n setTimeout(function(){ \n traffic.className = \'s2\'; \n setTimeout(function(){ \n traffic.className = \'s3\'; \n setTimeout(function(){ \n traffic.className = \'s4\'; \n setTimeout(function (){ \n traffic.className = \'s5\'; \n setTimeout(reset,1000) \n },1000) \n },1000) \n },1000) \n },1000) \n})();</code></pre></li><li><p>优化版本,利用async/await</p><pre><code>const traffic = document.getElementById(\'traffic\'); \nfunction wait(time){ \n return new Promise(resolve => setTimeout(resolve,time)) \n} \n\nfunction setState(state){ \n traffic.className = state;\n} \n\nasync function start(){ \n while(1){ \n setState(\'wait\'); \n await wait(1000); \n setState(\'stop\'); \n await wait(3000); \n setState(\'pass\'); \n await wait(3000);\n } \n} \n\nstart();</code></pre></li></ul><h2>洗牌算法</h2><ul><li><p>错误示例<br>看似可以正确洗牌,但实际上较小的牌放到前面的概率更大,较大的的牌放到后面的概率更大</p><pre><code>const cards = [0,1,2,3,4,5,6,7,8,9];\nconst shuffle = (cards) => {\n return [...cards].sort(() => { Math.random() > 0.5 ? -1 : 1 });\n}</code></pre></li><li>正确示例</li></ul><pre><code>const cards = [0,1,2,3,4,5,6,7,8,9]; \nfunction *draw(cards){ \n const c = [...cards]; \n for(let i = c.length ; i > 0 ; i--){ \n const pIdx = Math.floor(Math.random() * i);\n [c[pIdx],c[i - 1]] = [c[i - 1],c[pIdx]]; \n yield c[i - 1];\n } \n return c; \n}</code></pre><h2>分红包问题</h2><ul><li><p>版本一,类似于切蛋糕,取最大的继续切分,得出的结果会比较平均</p><pre><code>function generate(amount,count){ \n let ret = [amount]; \n while(count > 1){ \n // 挑出最大的进行切分 \n let cake = Math.max(...ret), \n idx = ret.indexOf(cake), \n part = 1 + Math.floor((cake / 2) * Math.random()), \n rest = cake - part; \n ret.splice(idx,1,part,rest); \n count --; \n } \n return ret; \n}</code></pre></li><li>版本二,随机性较大,可能会有较大的红包出现</li></ul><pre><code>function *draw(cards){ \n const c = [...cards]; \n for(let i = c.length ; i > 0 ; i --){ \n const pIdx = Math.floor(Math.random()*i); \n [c[pIdx],c[i-1]] = [c[i - 1],c[pIdx]]; \n yield c[i-1] \n } \n return c;\n} \n\nfunction generate(amount,count){ \n if(count <= 1) return [amount]; \n const cards = Array(amount - 1).fill(null).map((_,i) => i+1); \n const pick = draw(cards); \n const result = []; \n for(let i = 0 ; i < count; i++){ \n result.push(pick.next().value);\n } \n result.sort((a,b)=>a-b); \n for(let i = count - 1 ; i > 0 ; i--){ \n result[i] = result[i] - result[i - 1];\n } \n return result; \n}</code></pre>', '2022-01-25 11:00:34', '如何写好JavaScript', 95),
(56, 'puppeteerusebrowser', 'admin', 'JavaScript,前端', '<p><img src=\"94df169ff6234c88a1945a45d9fafdb1.webp\"><br></p><p><font size=\"5\"><b>前言 <i> </i></b></font></p><p>Puppeteer是一个非常火爆的无头浏览器并在最近得到非常多的star。主要功能是使用安装的Chromium启动一个浏览器来模拟用户操作,但是这个浏览器可以说是一次性的无法缓存信息,很多时候我们希望Puppeteer可以复用本地已启动的浏览器。在查找多篇文章后总结出正确的使用方法:</p><p><b><font size=\"5\">使用 </font></b></p><p>1.为了保证顺利链接我们需要设置Chrome浏览器的启动端口<br></p><p> 右键快捷方式设置目标中的内容:在最后空格后添加 --remote-debugging-port=9222</p><p><img src=\"7eed074f2ed1f0a5ceae29481db2eb8f.webp\"><br><br>2.编写程序配置puppeteer配置</p><p><img src=\"6a7ae9043b22bf169b855b1994939fd6.webp\"><br><br></p><p><b><font size=\"5\">讲解</font></b></p><p>使用axios获取对应链接(http://localhost:9222/json/version)的通信配置传入puppeteer配置(链接中的端口号需要和Chrome目标路径中的端口号对应)</p><p>后面就是基本的创建页面页面滚动以及页面跳转的配置了</p><p><b><font size=\"5\">总结</font></b><br></p><ol><li>修改Chrome固定端口号</li><li>在配置browser配置前请求对应的连接获取webSocket配置</li><li>将webSocket的key放入对应的配置中</li></ol>', '2022-02-01 18:14:30', 'Puppeteer连接已有Chrome浏览器', 110);
INSERT INTO `article` (`id`, `router`, `author`, `type`, `article`, `time`, `title`, `view_count`) VALUES
(57, 'npmfailedtoinstallsharp', 'admin', 'Leetcode,Node,后端,填坑', '<h3><a id=\"sharpnodejs_0\"></a>sharp包是基于node.js的高性能图片处理器</h3> \n<h3><a id=\"npmsharp_1\"></a>在使用npm安装sharp的时候,需要注意以下问题</h3> \n<ul><li>使用镜像地址:</li></ul> \n<pre><code class=\"prism language-bash\"><span class=\"token function\">npm</span> config <span class=\"token keyword\">set</span> sharp_binary_host <span class=\"token string\">\"https://npm.taobao.org/mirrors/sharp\"</span>\n<span class=\"token function\">npm</span> config <span class=\"token keyword\">set</span> sharp_libvips_binary_host <span class=\"token string\">\"https://npm.taobao.org/mirrors/sharp-libvips\"</span></code></pre><p>cnpm对域名进行了修改(如果上面的代码无效请使用这行)</p><pre><code class=\"prism language-bash\">npm config set sharp_binary_host \"https://npmmirror.com/mirrors/sharp\"\nnpm config set sharp_libvips_binary_host \"https://npmmirror.com/mirrors/sharp-libvips\"\n</code></pre><ul><li>提前下载好sharp某版本号对应的libvips依赖包,放在npm的缓存路径中,通常这个路径是<code>/Users/<username>/.npm/_libvips</code>(在使用了nvm管理node版本的情况下,正常情况下执行1操作后就没有问题了)</li></ul> \n<p>如果没有提前下载好并放在缓存中,可能会出现如下的报错信息</p> \n<pre><code class=\"prism language-bash\">info sharp Using cached /Users/<span class=\"token operator\"><</span>usernmae<span class=\"token operator\">></span>/.npm/_libvips/libvips-8.10.0-darwin-x64.tar.br\nERR<span class=\"token operator\">!</span> sharp Decompression failed\ninfo sharp Attempting to build from <span class=\"token function\">source</span> via node-gyp but this may fail due to the above error\ninfo sharp Please see https://sharp.pixelplumbing.com/install <span class=\"token keyword\">for</span> required dependencies\n TOUCH Release/obj.target/libvips-cpp.stamp\n CC<span class=\"token punctuation\">(</span>target<span class=\"token punctuation\">)</span> Release/obj.target/nothing/node_modules/node-addon-api/nothing.o\n LIBTOOL-STATIC Release/nothing.a\n CXX<span class=\"token punctuation\">(</span>target<span class=\"token punctuation\">)</span> Release/obj.target/sharp/src/common.o\n<span class=\"token punctuation\">..</span>/src/common.cc:24:10: fatal error: <span class=\"token string\">\'vips/vips8\'</span> <span class=\"token function\">file</span> not found\n<span class=\"token comment\">#include <vips/vips8></span>\n ^~~~~~~~~~~~\n1 error generated.\nmake: *** <span class=\"token punctuation\">[</span>Release/obj.target/sharp/src/common.o<span class=\"token punctuation\">]</span> Error 1\ngyp ERR<span class=\"token operator\">!</span> build error \ngyp ERR<span class=\"token operator\">!</span> stack Error: <span class=\"token variable\"><span class=\"token variable\">`</span><span class=\"token function\">make</span><span class=\"token variable\">`</span></span> failed with <span class=\"token keyword\">exit</span> code: 2\ngyp ERR<span class=\"token operator\">!</span> stack at ChildProcess.onExit <span class=\"token punctuation\">(</span>/Users/maxingyuan/.nvm/versions/node/v12.16.1/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:194:23<span class=\"token punctuation\">)</span>\ngyp ERR<span class=\"token operator\">!</span> stack at ChildProcess.emit <span class=\"token punctuation\">(</span>events.js:311:20<span class=\"token punctuation\">)</span>\ngyp ERR<span class=\"token operator\">!</span> stack at Process.ChildProcess._handle.onexit <span class=\"token punctuation\">(</span>internal/child_process.js:275:12<span class=\"token punctuation\">)</span>\ngyp ERR<span class=\"token operator\">!</span> System Darwin 19.6.0\ngyp ERR<span class=\"token operator\">!</span> <span class=\"token function\">command</span> <span class=\"token string\">\"/Users/<usernmae>/.nvm/versions/node/v12.16.1/bin/node\"</span> <span class=\"token string\">\"/Users/<usernmae>/.nvm/versions/node/v12.16.1/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js\"</span> <span class=\"token string\">\"rebuild\"</span>\ngyp ERR<span class=\"token operator\">!</span> cwd /Users/maxingyuan/.nvm/versions/node/v12.16.1/lib/node_modules/sharp\ngyp ERR<span class=\"token operator\">!</span> node -v v12.16.1\ngyp ERR<span class=\"token operator\">!</span> node-gyp -v v5.0.5\ngyp ERR<span class=\"token operator\">!</span> not ok \n<span class=\"token function\">npm</span> ERR<span class=\"token operator\">!</span> code ELIFECYCLE\n<span class=\"token function\">npm</span> ERR<span class=\"token operator\">!</span> errno 1\n<span class=\"token function\">npm</span> ERR<span class=\"token operator\">!</span> [email protected] install: <span class=\"token variable\"><span class=\"token variable\">`</span><span class=\"token punctuation\">(</span>node install/libvips <span class=\"token operator\">&&</span> node install/dll-copy <span class=\"token operator\">&&</span> prebuild-install<span class=\"token punctuation\">)</span> <span class=\"token operator\">||</span> <span class=\"token punctuation\">(</span>node-gyp rebuild <span class=\"token operator\">&&</span> node install/dll-copy<span class=\"token punctuation\">)</span><span class=\"token variable\">`</span></span>\n<span class=\"token function\">npm</span> ERR<span class=\"token operator\">!</span> Exit status 1\n<span class=\"token function\">npm</span> ERR<span class=\"token operator\">!</span> \n<span class=\"token function\">npm</span> ERR<span class=\"token operator\">!</span> Failed at the [email protected] <span class=\"token function\">install</span> script.\n<span class=\"token function\">npm</span> ERR<span class=\"token operator\">!</span> This is probably not a problem with npm. There is likely additional logging output above.\n\n<span class=\"token function\">npm</span> ERR<span class=\"token operator\">!</span> A complete log of this run can be found in:\n<span class=\"token function\">npm</span> ERR<span class=\"token operator\">!</span> /Users/<span class=\"token operator\"><</span>usernmae<span class=\"token operator\">></span>/.npm/_logs/2020-11-11T07_51_13_893Z-debug.log</code></pre>', '2022-02-06 20:05:48', 'npm无法安装sharp', 100),
(58, 'unabletouseftp', 'admin', '填坑', '<p>宝塔安装并且配置FTP后默认是外网无法链接的,使用FTP工具会卡在读取目录不动。需要进行两点检查:</p><h3 id=\"rnuvi\">1.将FTP的内网地址修改为外网地址</h3><p><img src=\"ac4df566a4fd8cb12c655ee7245f8b39.webp\"><br>将#注释去掉后面的内网地址切换为外网地址(关键词:ForcePassiveIP 通常在188行)</p><h3 id=\"ucu1g\">2.检查云服务器的安全组端口放行配置</h3><p><img src=\"311ad846df6143953e7d6b698786e359.webp\"></p><p>将21/20/39000-40000都放行就可以了</p><p><b>FTP</b>工具推荐使用<b>FileZilla</b></p><p><br></p>', '2022-02-06 20:47:56', '宝塔Linux无法链接FTP', 80),
(60, 'bf7bd604a4fdbb72f3bd42a4d40acba3', 'admin', 'Node,资讯', '<p><img src=\"98689ca74373eeb830fe97762c324aa1.webp\"></p><p>目前前端技术非常火热,并且技术持续更新,目前很多前端技术是基于Node.js构建。Node.js也成为前端工程师必会的技术栈,这里为前端开发工程师推荐几个好用的Node.js框架。</p><p><b><br></b></p><p><b>1. Express.JS</b><br></p><p>Express是一种流行的模型视图控制器(MVC)Node.js框架,具有快速、极简和灵活的优点,为Web和移动应用程序开发提供了强大的功能集合。它或多或少是在Node.js上编写Web应用程序的事实上的API。</p><p>它是一组路由库,提供了一层薄薄的基本Web应用程序功能,添加到讨巧的现有Node.js功能中。它侧重于高性能,支持强大的路由和HTTP帮助程序(重定向和缓存等)。它随带支持逾14个模板引擎的视图系统、内容协商以及用于快速生成应用程序的可执行文件。</p><p>此外,Express还随带大量易于使用的HTTP实用程序方法、函数和中间件,从而使开发人员能够轻松快速地编写可靠的API。几个流行的Node.js框架基于Express构建。</p><p><b>2. Socket.io</b></p><p>Socket.io是一种快速可靠的全堆栈框架,用于构建实时应用程序。它为基于事件的实时双向通信而设计。</p><p>它支持自动重新连接、断开检测、二进制、多路复用和房间。它有一个简单方便的API,适用于所有平台、浏览器或设备(同样专注于可靠性和速度)。</p><p><b>3. Meteor.JS</b></p><p>Meteor.js是一种超简单的全堆栈Node.js框架,用于构建现代Web和移动应用程序。它与Web、iOS、Android或桌面系统兼容。</p><p>它集成了用于构建连接客户端响应应用程序的关键技术集合、构建工具以及来自Node.js和整个JavaScript社区的一套精选的软件包。</p><p><b>4. Koa.JS</b></p><p>Koa.js是Express的开发人员构建的一种新的Web框架,使用ES2017异步功能。它旨在成为一种更小巧、更具表达力和更可靠的基础框架,用于开发Web应用程序和API。它使用promises和async函数,消除应用程序的回调地狱(callback hell),并简化错误处理。</p><p><b>5. Sails.js</b></p><p>Sailsjs是一种基于Express的实时MVC Web开发框架,面向Node.js。它的MVC架构类似Ruby on Rails等框架的架构。但不同之处在于,它支持更现代的、数据驱动的Web应用程序和API开发。</p><p>它支持自动生成的REST API、与WebSocket轻松集成,并与任何前端兼容:Angular、React、iOS、Android、Windows Phone以及自定义硬件。</p><p>它拥有支持现代应用程序需求的功能。Sails特别适合开发聊天之类的实时功能。</p><p><b>6. MEAN.io</b></p><p>MEAN的全称是Mongo、Express、Angular(6)和Node,结合了一套开源技术,这些技术共同提供了一种从头开始构建动态Web应用程序的端到端框架。</p><p>它旨在提供一个简单而有趣的起点,用于编写云原生全堆栈JavaScript应用程序。它是另一种基于Express构建的Node.js框架。</p><p><b>7. Nest.JS</b></p><p>Nest.js是一种灵活的、通用的、渐进式的Node.js REST API框架,用于构建高效、可靠、可扩展的服务器端应用程序。它使用现代JavaScript,使用TypeScript构建。它结合了OOP(面向对象编程)、FP(函数式编程)和FRP(函数式反应编程)的元素。</p><p>它是一种开箱即用的应用程序架构,打包成一个完整的开发工具包,用于编写企业级应用程序。在内部它使用Express,并与另外众多库兼容。</p><p><b>8. Loopback.io</b></p><p>LoopBack是一种高度可扩展的Node.js框架,让你在几乎不用编程的情况下就能创建动态的端到端REST API。它旨在使开发人员能够在几分钟内轻松构建模型并创建REST API。</p><p>它支持轻松的身份验证和授权设置。它还随带模型关系支持、各种后端数据存储、即席查询和附加组件(第三方登录和存储服务)。</p><p><b>9. Keystone.JS</b></p><p>KeystoneJS是一种开源、轻量级、灵活且可扩展的Nodejs全堆栈框架,基于Express和MongoDB构建。它用来构建数据库驱动的网站、应用程序和API。</p><p>它支持动态路由、表单处理、数据库构建块(ID/字符串/布尔值/日期/数字)以及会话管理。它随带一个漂亮的、可定制的管理UI,可以轻松管理你的数据。</p><p>有了Keystone,一切都很简单;你选择并使用适合自身要求的功能,替换不适合要求的功能。</p><p><b>10. Feathers.JS</b></p><p>Feathers.js是一种实时极简的微服务REST API框架,用于编写现代应用程序。它结合了各种工具和架构,旨在从头开始轻松编写可扩展的REST API和实时Web应用程序。它也基于Express构建。</p><p>它可以在几分钟内快速构建应用程序原型,在几天内构建生产就绪的实时后端。它可以轻松与任何客户端框架集成,无论是Angular、React还是VueJS。此外,它还支持灵活的可选插件,以便在你的应用程序中实现身份验证和授权权限。最重要的是,诸多功能使你能够编写出简洁而灵活的代码。</p><p><b>11. Hapi.JS</b></p><p>Hapi.js是一种简单、丰富、稳定、可靠的MVC框架,用于构建应用程序和服务。它用于编写可重用的应用程序逻辑,而不是构建基础架构。它以配置为中心,并提供诸多功能,比如输入验证、缓存、身份验证及其他必要功能等。</p><p><b>12. Strapi.io</b></p><p>Strapi是一种快速、可靠且功能丰富的MVC Node.js框架,用于为网站/应用程序或移动应用程序开发高效安全的API。Strapi默认是安全的,它面向插件(每个新项目都提供一组默认插件),并与前端无关。</p><p>它随带一个嵌入式优雅、完全可定制、完全可扩展的管理面板,拥有控制数据的headless CMS功能。</p><p><b>13. Restify.JS</b></p><p>Restify是一种利用连接样式中间件的Nodejs REST API框架。究其底层,它大量借鉴了Express。它经过了优化(尤其是针对自省和性能),用于构建语义正确的、充分利用REST的Web服务,这种Web服务可大规模用于生产环境。</p><p>重要的是,Restify用于支持外面众多庞大的Web服务,比如像Netflix这样的公司。</p><p><b>14. Adonis.JS</b></p><p>Adonis.js是另一种流行的Node.js Web框架,简单而稳定,拥有优雅的语法。这种MVC框架提供了一个稳定的生态系统,以便从头开始编写稳定、可扩展的服务器端Web应用程序。Adonisjs采用模块化设计,它由多个服务提供者(service provider)组成,服务提供者是AdonisJs应用程序的构建模块。</p><p>总结一下,个人觉得Express框架使用量最大,因为其简单容易上手,尤其对新手来讲Express绝对算得上入门级框架。同时本站服务端也是Express编写,并且有向Nest.js更新的打算。</p>', '2022-02-13 22:50:36', '主流Node.js 框架推荐', 63),
(68, 'typescriptgetparamstype', 'admin', 'TypeScript,讲解', '<p>现在有一个函数update,我们想要获取他的参数类型,你应该怎么做呢?这个时候我们需要就要用到<strong>Parameters</strong></p>\n <pre class=\"language-typescript\"><code>function updata(state) {\n return {\n router: state.router\n }\n}\n</code></pre>\n <p>获取参数类型:</p>\n <pre class=\"language-typescript\"><code>type ArrType = Parameters<typeof updata>\n// ArrType => [state: any]\n</code></pre>\n <p>如果想获取state的类型呢?这个时候需要用到<strong>infer</strong></p>\n <pre class=\"language-typescript\"><code>type GetType<T> = T extends (arg: infer P) => void ? P : string;\ntype StateType = GetType<typeof update>\n// StateType => any\n// 因为state没有设置类型,所以ts推断state的类型为any\n</code></pre>\n <p>把这段代码翻译一下:<br>\n (arg: infer P):arg的类型待推断为P<br>\n 整段代码的意思:如果T能赋值给(arg: infer P) => void,则返回P,否则返回string</p>\n <p>如果想要获取函数的<strong>返回值</strong>类型,需要使用typescript提供的内置方法<strong>ReturnType</strong></p>\n <pre class=\"language-typescript\"><code>type Return = ReturnType<typeof update>\n// ReturnType => \n// {\n// router: any;\n//}\n</code></pre>', '2022-02-28 09:33:00', 'TypeScript获取函数参数类型', 76),
(71, 'vue3-declare-emits', 'admin', '填坑,Vue', '<p>Vue3已将父子组件传值修改为defineEmits函数,但是在日常使用时有时会报错:<i>[Vue warn]: Extraneous non-emits event listeners (update) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the \"emits\" option.</i></p><p><img style=\"max-width:100%;\" contenteditable=\"false\" src=\"d942fe9a5296f7ea010a06813f9a2e7c.webp\"></p><p>通过警告信息可以得出Vue需要我们将emits中的传参事件标明:</p><pre><code class=\"language-javascript\"><xmp><script setup>\r\nimport { ref, watchEffect, watch, computed } from \"vue\";\r\nlet emit = defineEmits([\"update:mode\", \"update:data\", \"update\"]);\r\nlet props = defineProps({\r\n mode: {\r\n type: [String, Boolean],\r\n required: true,\r\n },\r\n data: {\r\n type: Object,\r\n required: true,\r\n },\r\n});\r\n</script></xmp></code></pre><p>这样修改,将需要用到的事件在定义时传在函数就可以了</p>', '2022-03-05 20:45:31', 'Vue3 declare it using the \"emits\" option警告', 57),
(72, 'monitor-qiniu-cdn', 'admin', '云服务器,填坑', '<p>七牛云每月赠送10GB的OSS以及10GB的CDN流量(HTTPS付费),很多浏览器不支持在https网站中使用http资源,所以一般自建站需要对https流量进行付费,但是CDN不同于云服务器他使用七牛云的服务器并且没有带宽限制在收到CC攻击时会在短时间内发出大量的流量。自己使用闲置的学生版低配云服务器测试了一下,几秒钟时间消耗了1GB多的流量。</p><p><img style=\"max-width:100%;\" contenteditable=\"false\" src=\"3490eee80d892b0076a3efb9d37e4acd.webp\"><br><img style=\"max-width:100%;\" contenteditable=\"false\" src=\"5897d0db111134875599a7d59fd38c9b.webp\"><br>为了防止一晚上起来房子归七牛云的情况发生,我写了一个Node.js程序,主要用于检测CDN流量在日流量超出所设置的警戒线后向七牛云发送请求下线域名。在使用时只需要填写配置即可。</p><p><img style=\"max-width:100%;\" contenteditable=\"false\" src=\"47d1e82f4c1e6e02c0cdb16d68f0a394.webp\"><br><b>使用方法:</b></p><ol><li>申请开发者Key</li><li>qiniu.config.js填写域名信息</li><li>启动</li></ol><p>这样,放在云服务器上监测就好了</p><p><img style=\"max-width:100%;\" contenteditable=\"false\" src=\"351007278e46eaa5843539cac9f12e34.webp\"></p><p>程序提供邮箱提示,日志打印等功能(邮箱使用小号的QQ邮箱,珍惜开源程序请勿破坏,也可以将邮箱Key换成自己的)</p><p><a href=\"https://github.com/Lrunlin/monitor_qiniu_cdn\" target=\"_blank\">项目地址</a><br></p><p><a href=\"https://github.com/Lrunlin/monitor_qiniu_cdn.git\" target=\"_blank\">克隆地址</a></p>', '2022-03-26 23:42:41', '七牛云CDN防止CC攻击', 32),
(74, '2612a5751ab42d158cf9791b0d10af39', '[email protected]', 'MySQL,资讯', '<p>阿斯顿撒旦</p>', '2022-04-02 18:21:03', '阿阿斯顿撒', 43);
-- --------------------------------------------------------
--
-- 表的结构 `article_type`
--
DROP TABLE IF EXISTS `article_type`;
CREATE TABLE IF NOT EXISTS `article_type` (
`id` varchar(100) NOT NULL,
`type` varchar(60) NOT NULL,
`time` datetime DEFAULT NULL,
`isShow` tinyint(1) NOT NULL COMMENT '是否显示在侧边栏',
PRIMARY KEY (`id`),
UNIQUE KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- 转存表中的数据 `article_type`
--
INSERT INTO `article_type` (`id`, `type`, `time`, `isShow`) VALUES
('0047678c81ea4b97950288824ff31950', 'MySQL', '2022-01-18 23:35:40', 0),
('20fd5a3effb14a6f861c386f5d1b98ef', 'Vue', '2021-11-12 01:32:20', 1),
('3368046505c9432ab7ffa4457b268232', '前端', '2022-03-05 12:35:53', 1),
('53db556bf6bb416d932793ec1990408a', '资讯', '2022-03-05 09:35:53', 1),
('582fe1e2844b41b2a56b325870b4a85f', 'JavaScript', '2022-01-25 11:00:33', 0),
('6361bed6d59a4181b009278444e4619b', 'Leetcode', '2022-01-01 22:35:16', 0),
('8cec1e332a3143399a8a10bf9fc65bcd', 'CSS', '2022-01-01 22:35:16', 0),
('95d08c7ce44546e3bad583ba80db630e', 'TypeScript', '2022-01-01 22:35:16', 0),
('97e6a5ec901541d6a1f2f56f9f7141fd', 'Node', '2022-01-01 22:35:16', 1),
('c036380af9ff4c54b0e175d970a0a711', '讲解', '2022-01-01 22:35:16', 1),
('c8702e9e46514d4cb00221b419311aae', '填坑', '2022-01-01 22:35:16', 1),
('e5174cc881184ef4980dacbe2370482d', 'React', '2021-11-12 01:32:34', 1),
('ee9a1f8befb5493c80334bd6d0b9c5fa', '后端', '2022-03-05 12:35:51', 1);
-- --------------------------------------------------------
--
-- 表的结构 `comment`
--
DROP TABLE IF EXISTS `comment`;
CREATE TABLE IF NOT EXISTS `comment` (
`id` varchar(60) NOT NULL,
`commentator` varchar(20) NOT NULL COMMENT '评论者',
`superior` varchar(60) DEFAULT NULL COMMENT '评论的上级',
`articleId` varchar(30) DEFAULT NULL COMMENT '评论在什么地方(文章/公共)',
`content` text NOT NULL COMMENT '评论内容',
`time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- 表的结构 `github`
--
DROP TABLE IF EXISTS `github`;
CREATE TABLE IF NOT EXISTS `github` (
`id` varchar(60) NOT NULL,
`name` varchar(200) NOT NULL COMMENT '项目名称',
`description` text NOT NULL COMMENT '项目介绍',
`url` varchar(200) NOT NULL COMMENT '项目地址',
`time` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- 转存表中的数据 `github`
--
INSERT INTO `github` (`id`, `name`, `description`, `url`, `time`) VALUES
('771d8a932cb1ed1c13f614443006875e', '情书屋管理系统', '用户端使用JQuery,管理系统使用Vue,服务端使用Node,数据库使用MySQL,管理员可以添加用户查看情书内容和使用到的资源,用户可以发布情书设置口令和上传录音、图片。', 'https://github.com/Lrunlin/article', '2022-02-09 10:29:28'),
('e8d6adff880649d713a492e6f1e4cf9b', '学生成绩管理系统', '使用Vue、Node、MySQL搭建的学生成绩管理系统,分为学生、教师、管理员三个身份。可以对学生成绩进行管理、打印以及部分系统信息进行配置', 'https://github.com/Lrunlin/student-achievement', '2022-02-09 10:29:28');
-- --------------------------------------------------------
--
-- 表的结构 `links`
--
DROP TABLE IF EXISTS `links`;
CREATE TABLE IF NOT EXISTS `links` (
`id` varchar(100) NOT NULL,
`url` varchar(100) NOT NULL,
`logo` varchar(100) NOT NULL,
`name` varchar(100) NOT NULL,
`description` varchar(200) NOT NULL,
`drainage` tinyint(1) NOT NULL,
`time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
--
-- 转存表中的数据 `links`
--
INSERT INTO `links` (`id`, `url`, `logo`, `name`, `description`, `drainage`, `time`) VALUES
('91a2bf6398a34608b19b37f7f0ec63ad', 'https://dzblog.cn', 'https://assets-open.dzblog.cn/images/logo.png', 'Wintermelon\'s blog', 'Web前端 & 边玩边学', 1, '2022-04-03 15:06:05');
-- --------------------------------------------------------
--
-- 表的结构 `user`
--
DROP TABLE IF EXISTS `user`;
CREATE TABLE IF NOT EXISTS `user` (
`email` varchar(60) CHARACTER SET latin1 NOT NULL COMMENT '用户邮箱',
`password` varchar(40) CHARACTER SET latin1 NOT NULL COMMENT '用户密码',
`GitHub` varchar(60) CHARACTER SET latin1 DEFAULT NULL COMMENT 'GitHub地址',
PRIMARY KEY (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- 转存表中的数据 `user`
--
INSERT INTO `user` (`email`, `password`, `GitHub`) VALUES
('[email protected]', '96e79218965eb72c92a549dd5a330112', 'https://github.com/LRunLin'),
('[email protected]', 'f379eaf3c831b04de153469d1bec345e', NULL);
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;