-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 241 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 241 KB
1
[{"title":"手写cli","date":"2020-11-09T02:09:42.000Z","path":"2020/11/09/NodeJS/手写cli/","text":"  通过命令行工具,初始化团队项目,并生成团队规范代码,一键创建项目,一键生成代码,一键生成功能模块···  解放双手,从 cli 开始, JSer 永不为奴! 创建项目目录两种方法: 鼠标右键创建文件夹,文件夹名称即为项目名称; 打开系统命令面板(powershell、cmd···),输入命令: 1mkdir 项目名称 初始化 node 项目在项目文件目录运行命令: 1npm init 运行命令后会出现如下提示(按照提示完成即可): 根据提示完成操作后,会在项目根目录生成项目描述文件package.json,可以回车跳过以下步骤,后续在package.json文件中完善项目信息。 1234567891011121314151617181920212223242526272829303132333435This utility will walk you through creating a package.json file.It only covers the most common items, and tries to guess sensible defaults.See `npm help init` for definitive documentation on these fieldsand exactly what they do.Use `npm install <pkg>` afterwards to install a package andsave it as a dependency in the package.json file.Press ^C at any time to quit.package name: 项目名称version: 版本号description: 项目描述entry point: (index.js)入口test command: 测试相关git repository: git仓库地址keywords: 关键词author: 作者license: 证书About to write to E:\\***\\package.json:{ \"name\": \"***\", \"version\": \"***\", \"description\": \"***\", \"main\": \"index.js\", \"scripts\": { \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\" }, \"author\": \"***\", \"license\": \"ISC\"}Is this OK? (yes) yes 安装相关依赖项目后续会使用以下依赖包。 运行命令: 1npm i commander download-git-repo ora handlebars figlet clear chalk open 依赖包介绍: commander(完整的 node.js 命令行解决方案) download-git-repo(从节点下载并提取仓库(GitHub, GitLab, Bitbucket)) ora(终端进度条) handlebars(轻量的语义化模板) figlet(终端实现图形字体) clear(终端清屏) chalk(终端字符串样式) open(跨平台级的,可打开 url、文件、可执行脚本) 创建 cli 入口文件项目根目录创建文件夹bin,在bin文件夹中创建mycli.js(自定义)文件。 文件内容如下: 12#!/usr/bin/env node// 第一行为约定脚本解释器以 node 进行解析,必须放在文件第一行!行首不能有任何字符! 定义执行命令打开package.json文件,添加相关代码(句首有+号的代码为新增代码): 123456789101112131415161718192021222324{ \"name\": \"***\", \"version\": \"***\", \"description\": \"***\", \"main\": \"index.js\", \"scripts\": { \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\" }, + \"bin\": { + \"mycli\": \"./bin/mycli.js\" + }, \"author\": \"***\", \"license\": \"ISC\", \"dependencies\": { \"chalk\": \"^4.1.0\", \"clear\": \"^0.1.0\", \"commander\": \"^6.2.0\", \"download-git-repo\": \"^3.0.2\", \"figlet\": \"^1.5.0\", \"handlebars\": \"^4.7.6\", \"open\": \"^7.3.0\", \"ora\": \"^5.1.0\" }} 映射命令至系统将package.json文件中刚添加的bin命令映射至系统中。 运行命令: 1npm link 此时我们可以全局使用bin中的mycli命令了。 npm link机理:由于已经安装好 node 环境,故此我们可以直接全局使用node与npm命令,而npm link则是将package.json中bin下的命令通过npm与系统环境变量桥接,所以我们可以全局使用bin中的命令。 至此我们实现了 cli 的第一步:可以全局使用我们自定义的命令了~~ 莫要问我为什么在这里用了彩色的字。。。 因为我比较———— 骚! 定制命令行界面在入口文件bin => mycli.js中进行命令行界面定制。 12345678910111213#!/usr/bin/env nodeconst program = require('commander') // 引入 commanderprogram.version(require('../package.json').version) // 声明 cli 当前版本,如:输入mycli -V 命令,即可显示当前 cli 版本program .command('init <name>') // 定义使用命令,如:命令 mycli init <name> .description('init project') // 命令描述 .action(name => { // 该命令对应的动作 console.log('init--------'+name); })program.parse(process.argv) // 将命令参数暴露出去,供使用者使用 定制初始化功能在根目录中创建lib文件夹,在lib文件夹中创建init.js文件,用于编写初始化功能代码。 基础结构搭建初始化功能基础代码结构。 lib => init.js文件代码如下: 1234567891011const { promisify } = require('util') // node 核心模块 util,其中的 promisify 方法可以将异步回调方法改造成返回 Promise 实例的方法const figlet = promisify(require('figlet')) // 以 promise 形式引入 figlet 模块,figlet 可以式代码以图形出现在终端const clear = require('clear') // 终端清屏const chalk = require('chalk'); // 终端字符串样式定制const colorLog = (content, color) => console.log(chalk[color||'green'](content));// 封装日志输出,可定制颜色module.exports = async name => { clear(); const data = await figlet('Gisuni Welcome'); colorLog(data,'red')} 连接功能与命令bin => mycli.js对init方法进行引入并使用,代码如下: 1234567891011121314#!/usr/bin/env nodeconst program = require('commander') // 引入 commanderprogram.version(require('../package.json').version) // 声明 cli 当前版本,如:输入mycli -V 命令,即可显示当前 cli 版本program .command('init <name>') // 定义使用命令,如:命令 mycli init <name> .description('init project') // 命令描述- .action(name => { // 该命令对应的动作- console.log('init--------'+name);- })+ .action(require('../lib/init'))program.parse(process.argv) // 将命令参数暴露出去,供使用者使用 下拉仓库功能将仓库中的代码下拉至本地。 在lib文件夹中创建download.js文件,其代码如下: 1234567891011const { promisify } = require('util') // node 核心模块 util,其中的 promisify 方法可以将异步回调方法改造成返回 Promise 实例的方法module.exports.clone = async (repo, desc) => { const download = promisify(require('download-git-repo')) // 以 promise 形式引入 download-git-repo 模块,download-git-repo 可以下拉仓库代码 const ora = require('ora') // 引入终端进度条代码 const process = ora(`下载中··· ${repo}`) // 进度条提示 process.start() // 进度条开始 await download(repo, desc) // 下拉仓库代码 process.succeed() // 进度条结束} 引入下拉功能将下拉功能引入至初始化模块中。 lib => init.js文件代码如下: 1234567891011121314const { promisify } = require('util') // node 核心模块 util,其中的 promisify 方法可以将异步回调方法改造成返回 Promise 实例的方法const figlet = promisify(require('figlet')) // 以 promise 形式引入 figlet 模块,figlet 可以式代码以图形出现在终端const clear = require('clear') // 终端清屏const chalk = require('chalk') // 终端字符串样式定制const colorLog = (content, color) => console.log(chalk[color||'green'](content)) // 封装日志输出,可定制颜色+const { clone } = require('./download') // 引入下拉功能模块module.exports = async name => { clear(); const data = await figlet('Gisuni Welcome'); colorLog(data,'red');+ colorLog(`创建项目:${name}`);+ await clone('github:用户名/项目名称', name); // 如果项目克隆地址为:https://github.com/ShanYi-Hui/react-template.git,则此处应改造为:github:ShanYi-Hui/react-template} 自动安装依赖下拉的项目将自动安装依赖包。 lib => init.js文件代码如下: 12345678910111213141516171819202122232425262728293031323334353637const { promisify } = require('util') // node 核心模块 util,其中的 promisify 方法可以将异步回调方法改造成返回 Promise 实例的方法const figlet = promisify(require('figlet')) // 以 promise 形式引入 figlet 模块,figlet 可以式代码以图形出现在终端const clear = require('clear') // 终端清屏const chalk = require('chalk') // 终端字符串样式定制const colorLog = (content, color) => console.log(chalk[color||'green'](content)) // 封装日志输出,可定制颜色const { clone } = require('./download') // 引入下拉功能模块+const spawn = async (...args) => {+ const { spawn } = require('child_process') // node 核心模块 child_process,spawn 方法会异步地衍生子进程,且不阻塞 Node.js 事件循环+ return new Promise(resolve => {+ const proc = spawn(...args);+ proc.stdout.pipe(process.stdout);+ proc.stderr.pipe(process.stderr);+ proc.on('close', () => {+ resolve();+ })+ })+}module.exports = async name => { clear(); const data = await figlet('Gisuni Welcome'); colorLog(data,'red'); colorLog(`创建项目:${name}`); await clone('github:用户名/项目名称', name); // 如果项目克隆地址为:https://github.com/ShanYi-Hui/react-template.git,则此处应改造为:github:ShanYi-Hui/react-template+ colorLog('安装依赖中···');+ await spawn(process.platform === 'win32'?'npm.cmd':'npm', ['install'], { cwd:`./${name}` }); // 安装依赖+ colorLog(`+安装完成!++项目将自动启动,若未启动请执行如下命令:+==============================+ cd ${name}+ npm run serve+==============================+ `);+} 项目自启动依赖安装完成后将自动打开浏览器,并在指定端口(默认 8080 端口)运行服务。 lib => init.js文件代码如下: 12345678910111213141516171819202122232425262728293031323334353637383940const { promisify } = require('util') // node 核心模块 util,其中的 promisify 方法可以将异步回调方法改造成返回 Promise 实例的方法const figlet = promisify(require('figlet')) // 以 promise 形式引入 figlet 模块,figlet 可以式代码以图形出现在终端const clear = require('clear') // 终端清屏const chalk = require('chalk') // 终端字符串样式定制const colorLog = (content, color) => console.log(chalk[color||'green'](content)) // 封装日志输出,可定制颜色const { clone } = require('./download') // 引入下拉功能模块+const open = require('open') // 打开链接const spawn = async (...args) => { const { spawn } = require('child_process') // node 核心模块 child_process,spawn 方法会异步地衍生子进程,且不阻塞 Node.js 事件循环 return new Promise(resolve => { const proc = spawn(...args); proc.stdout.pipe(process.stdout); // 子进程中的日志通过管道传输至主进程 proc.stderr.pipe(process.stderr); // 子进程中的错误捕获通过管道输出至主进程 proc.on('close', () => { // 关闭子进程 resolve(); // promise 状态切换至执行完成状态 }) })}module.exports = async name => { clear(); const data = await figlet('Gisuni Welcome'); colorLog(data,'red'); colorLog(`创建项目:${name}`); await clone('github:用户名/项目名称', name); // 如果项目克隆地址为:https://github.com/ShanYi-Hui/react-template.git,则此处应改造为:github:ShanYi-Hui/react-template colorLog('安装依赖中···'); await spawn(process.platform === 'win32'?'npm.cmd':'npm', ['install'], { cwd:`./${name}` }); // 安装依赖 colorLog(`安装完成!项目将自动启动,若未启动请执行如下命令:============================== cd ${name} npm run serve============================== `);+ open('http://localhost:8080'); // 打开指定端口+ await spawn(process.platform === 'win32'?'npm.cmd':'npm', ['run','serve'], { cwd:`./${name}` }); // 启动项目} 其实到这里,一个最基本的 cli 已经制作完成了~~~ 那么接下来将开始丰富它的功能! 更新中···","updated":"2020-11-15T12:20:32.150Z","tags":[{"name":"NodeJS","slug":"NodeJS","permalink":"http://huishanyi.club/tags/NodeJS/"},{"name":"CLI","slug":"CLI","permalink":"http://huishanyi.club/tags/CLI/"}]},{"title":"浏览器渲染页面过程","date":"2020-09-02T22:10:15.000Z","path":"2020/09/03/Web性能优化/浏览器渲染页面过程/","text":"  用户在浏览器地址栏输入网址 到 页面加载完成,发生了什么? 客户端地址栏输入网址; 举个小白的例子:小白(域名)在一家大公司(云厂商)上班,有天小红(客户端)来看小白。 域名解析; 也叫DNS解析,云厂商将域名转化为 IP 地址,IP 才是网站真正的网络地址; 接着奏乐接着舞:可他到了小白的公司,发现找不到小白,于是去找前台小姐姐(DNS解析),小姐姐查了查小白的工位(IP),查到后告诉了小红。 TCP “三次握手”; 通过“三次握手”建立 TCP 连接,这里的三次是建立连接成功最少的通讯次数; 接着奏乐接着舞: 第一次握手:小红到了办公室门口,有门禁,敲门后说:“小白你在里面吗?我是小红。”; 第二次握手:小白说:“我在,你是那个小红啊?”; 第三次握手:小红说:“我是和你在东北玩泥巴的小红啊!”,假设此时小白已认可小红。 客户端发送请求; 请求页面数据(一般会存在多次请求,html,css,js,图片······) 接着奏乐接着舞:小红将目光(接口)转向大方脸,大眼睛,大红唇(请求参数)的小白。 服务器端响应; 服务器端接口收到请求,给予响应; 接着奏乐接着舞:小白发现自己被看着,站了起来(小白现在的样子(响应数据)已经通过目光(接口)展现在小红的眼里、心里和深深脑海里(响应数据传递到客户端))。 页面渲染; 主要分为 DOM => CSSDOM => RenderTree => Layout => Painting => Display; 接着奏乐接着舞:小红和小白曾经在一起的经历一一回想(逐步渲染),完整的小白呈现(页面渲染完成。老子说过:只有躯壳的人是不完整的。)。 更新中(将会详细剖析2到5阶段)······","updated":"2020-09-02T23:40:30.477Z","tags":[{"name":"Web性能优化","slug":"Web性能优化","permalink":"http://huishanyi.club/tags/Web性能优化/"}]},{"title":"CSRF攻击","date":"2020-07-15T09:43:30.000Z","path":"2020/07/15/Web安全/CSRF攻击/","text":"  CSRF(Cross Site Request Forgery),跨站请求伪造, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。 原理一个简易的假设如下,B 站伪造 A 站请求。 用户使用浏览器访问 A 网站并登录; 登录成功后,网站 A 产生 Cookie 信息并返回给浏览器; 此时(A 站产生的 Cookie 存在时)访问 B 网站; B 网站存在恶意代码(<img src="A 站支付请求" />); CSRF 成功(通过 B 站发送 A 站请求达到某些目的)。 条件 未进行请求来源校验; Token 未进行动态化处理。 防御 Referer 检测:请求头中 Referer(用来记录请求来源地址) 值检测。 Token 校验(每次请求 Token 都在变,黑客无法获取可用 Token) 请求参数中添加随机数(随机数可以存取在 session 中) 请求头中添加自定义属性 jwt(Json Web Token)","updated":"2020-07-18T12:32:13.349Z","tags":[{"name":"Web安全","slug":"Web安全","permalink":"http://huishanyi.club/tags/Web安全/"}]},{"title":"XSS攻击","date":"2020-07-15T00:12:25.000Z","path":"2020/07/15/Web安全/XSS攻击/","text":"  XSS(Cross Site Scripting) 攻击,跨站脚本攻击,因为缩写 CSS 和层叠样式表(Cascading Style Script)冲突,所以改为 XSS。  XSS 攻击者在 Web 页面插入恶意的脚本,当用户访问该页面时,嵌入到页面中的恶意脚本执行,从而攻击用户。 非持久型XSS(反射型XSS)通过特定手法(如电子邮件、网站论坛发布包装链接等),诱使用户去访问一个包含恶意代码的 URL,当受害者点击该链接的时候,恶意代码就会直接在受害者主机上的浏览器执行。 原理一个简易的假设如下,输入一个脚本得到一个脚本结果。 客户端访问指定页面(漏洞页面); 在表单(搜索框)中输入 XSS 代码(<script>alert(1)</script>),提交,即访问(http://test.com/?=<script>alert(1)</script>); 服务器收到请求,解析,并返回响应内容(含有 XSS 代码); 浏览器解析响应(解析过程中会执行 XSS 代码); alert(1) 被执行。 条件 页面对特殊字符未转义 特点 即时性,不进行服务器存储; 需要诱使用户点击; 盗取用户信息。 防御 页面渲染的所有内容应该来自服务端; 对特殊字符做转义处理(传向服务器的数据和服务器传来的数据); 对重要的 cookie 设置 httpOnly,避免 cookie 被客户端恶意的 JS 窃取。 持久型XSS(存储型XSS)通过特定手法(留言板),将XSS代码提交存储在服务器端(数据库,内存,文件系统等),下次请求目标页面时不用再提交 XSS 代码,而是从服务器解析之后加载出来。 原理一个简易的假设如下,输入一个脚本存储到服务器,每次在请求指定页面时,都会直接读取解析到之前在该页面存储的 XSS 代码。 客户端访问指定页面(漏洞页面); 在表单(留言板)中输入 XSS 代码(<script>alert(1)</script>),提交; 服务器收到请求,解析,并返回响应内容(含有 XSS 代码); 浏览器解析响应(解析过程中会执行 XSS 代码); alert(1) 被执行; 再次访问该页面,浏览器自动解析留言内容; alert(1) 被执行。 条件须同时满足以下条件。 后端对前端的 POST 请求未做转义就直接入库; 后端从数据库中取出的数据未做转义就直接输出给前端; 前端拿到后端的数据未做转义就直接渲染成 DOM。 特点 植入在数据库中; 危害面广; 盗取用户信息。 防御对需要达成 XSS 的条件进行阻断。 后端对前端的 POST 请求须做转义再入库; 后端从数据库中取出的数据须做转义再输出给前端; 前端拿到后端的数据须做转义再渲染成 DOM。 DOM-based XSS通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击。 原理 攻击者构造出特殊的URL、在其中可能包含恶意代码; 用户打开带有恶意代码的URL; 用户浏览器收到响应后解析执行。前端使用js取出url中的恶意代码并执行; 执行时,恶意代码窃取用户数据并发送到攻击者的网站中,那么攻击者网站拿到这些数据去冒充用户的行为操作。调用目标网站接口执行攻击者一些操作。 条件存在下列任何一个就有可能触发。 使用document.write直接输出数据。 使用innerHTML直接输出数据。 使用location、location.href、location.replace、iframe.src、document.referer、window.name等。 特点 仅发生在客户端,服务器不参与。 防御 前端页面对特殊字符做转义; 少用满足条件的代码,尽可能做代码替换(纯文本时 innerText、textContent 比 innerHTML 更贴切)。 常见特殊字符转义表 特殊字符 转义字符 & &amp; < &lt; > &gt; 空格 &nbsp; ‘ &#39; “ &quot; / &#x2F;","updated":"2020-07-18T08:46:29.866Z","tags":[{"name":"Web安全","slug":"Web安全","permalink":"http://huishanyi.club/tags/Web安全/"}]},{"title":"强缓存和协商缓存","date":"2020-07-14T06:57:42.000Z","path":"2020/07/14/Web性能优化/强缓存和协商缓存/","text":"  缓存是什么?为什么要做缓存?常见的缓存机制有哪些?缓存是如何工作的? 定义这里讨论的缓存为浏览器缓存。通过复用以前获取的资源来提高网站性能。 缓存优点 减少对服务器的访问次数,减轻服务器压力; 节省带宽; 再次访问时提高了访问速度,提升了用户体验。 强缓存(本地缓存)强制使用缓存方案,直接使用本地缓存,不与服务器通讯。成功状态码为 200。 控制由 header 中的两个字段控制,分别为 expires 和 cache-control。 expires(截止日期,http1.0规范):客户端时间在截止日期之前的时间,都直接使用本地缓存(本地已存在缓存,客户端时间与服务器时间有偏差时,可能导致缓存失效); cache-control(缓存控制,优先级高于expires,http1.1规范) max-age=value:value 表示资源的最大有效时间的“秒数”,如果最新一次请求时间还小于资源第一次请求时间加上这个 value,则使用本地缓存,不会因为客户端时间与服务器时间有偏差而导致缓存失效; public:表示客户端和代理服务器(如CDN)都可以缓存; private(默认值):表示只有客户端可以缓存; no-cache:客户端缓存内容,但是是否使用缓存需要经过协商缓存来验证决定; no-store:所有内容都不会被缓存,即不使用强缓存,也不使用协商缓存; must-revalidate:如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证。 弱缓存(协商缓存)强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。成功状态码为 304。 控制由请求头中的 If-Modified-Since 和 If-None-Match 与响应头对应的 Last-Modified 和 ETag 来控制(必须成对使用才有效果)。 Last-Modify/If-Modified-Since过程 第一次请求时,在 response header 中返回 Last-Modify,表示最后一次修改时间; 下次请求是,request header 中包含 If-Modify-Since,去询问服务器是不是还是上次那个最后修改时间; 如果还是上次的一样的时间,那么说明数据没有更新,服务端返回 304,浏览器直接从缓存中获取就行了; 如果不是上次不是上次的时间了,那么就返回数据,同时返回 Last-Modify。 缺点 Last-Modified 保存的是绝对时间,并且是精确到秒,所以如果资源在1秒内修改了多次的话,那就无法识别; 对于文件只改变了修改时间,内容不变,这时候也会使缓存失效,其实这个时候我们是不希望客户端重新请求的; 某些服务器不能精确的得到文件的最后修改时间。 ETag/If-None-Match这两个值是由服务器生成的资源唯一表示字符串,只要资源有变化这个值就会变化。判断过程同上,不同的是当服务器返回 304 时,由于 ETag 重新生成过,response header 中还是会将 ETag 返回,即使和原来的是一样的。 优点除缓存的优点外,还解决了 Last-Modify/If-Modified-Since 的缺点。 缺点ETag虽然能解决问题,但也并非完美,ETag 每次服务端生成都需要进行读写操作(因为要生成 hash),而 Last-Modified 只需要读取操作,ETag 消耗更大一些。 同时使用Last-Modified 与 ETag 一起使用时,服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。","updated":"2020-07-18T08:46:29.867Z","tags":[{"name":"Web性能优化","slug":"Web性能优化","permalink":"http://huishanyi.club/tags/Web性能优化/"}]},{"title":"JS操作字符串","date":"2020-07-12T21:27:58.000Z","path":"2020/07/13/JS/JS操作字符串/","text":"  字符串用于存储和处理文本。 创建字符串用单引号 或 双引号包起来的字符(单引号双引号不可嵌套或交叉使用)。 12var str = \"Hello String!\";var str1 = 'Hello String!'; 注意:特殊字符需要使用‘\\’转义。 ' 单引号 " 双引号 \\ 反斜杠 \\n 换行 \\r 回车 \\t tab(制表符) \\b 退格符 \\f 换页符 字符串属性和方法字符串长度123var str = \"Hello String!\";console.log(str.length); // 13 字符串指定位置字符12345678910var str = \"Hello String!\";// 返回第 1 个字符console.log(str.charAt(0)); // H// 返回第 1 个字符的 Unicode 编码console.log(str.charCodeAt(0)); // 72// 将 Unicode 编码转换为字符console.log(String.fromCharCode(72)); // H 字符串指定字符位置123456789101112var str = \"Hello String!\";// l 首次出现的位置var address1 = str.indexOf(\"l\");// l 最后一次出现的位置var address2 = str.lastIndexOf(\"l\");// 查找不存在的字符会返回 -1var address3 = str.lastIndexOf(\"a\");console.log(address1, address2, address3); // 2 3 -1 判断字符串开头下列方法不会改变原字符串。 1234var str = \"Hello World!\";// 判断 str 是否以 h 开头console.log(str.startsWith(\"h\")); // false 字符串拼接下列方法不会改变原字符串。 123456789var str1 = \"Hello\", str2 = \" \", str3 = \"World\", str4 = \"!\";// 相当于 str1 + str2 + str3var str = str1.concat(str2, str3, str4);console.log(str); // Hello World! 字符串重复下列方法不会改变原字符串。 1234var str = \"ab\";// str 重复两遍console.log(str.repeat(2)); // abab 字符串与正则下列方法不会改变原字符串。 123456789101112131415var str = \"abcdabcd\";// 整个字符串中查找,出现两次console.log(str.match(/a/g)); // [\"a\", \"a\"]// 整个字符串中替换 a 为 evar str1 = str.replace(/a.*/g, \"e\");console.log(str, str1); // abcdabcd ebcdebcd// 查找指定字符所在位置,search 支持正则var address1 = str.search(\"a\"), address2 = str.search(\"e\");console.log(address1, address2); // 0 -1 字符串分割成数组下列方法不会改变原字符串。 123456var str = \"Hello World!\";// 以空格来划分数组var strArr = str.split(\" \");console.log(str, strArr); // Hello World! [\"Hello\", \"World!\"] 字符串截取下列方法不会改变原字符串。 123456789101112var str = \"Hello World!\";// slice(start,end) 截取字符串,start 为起始位置,必填,最小为0;end 为结束位置,不填即代表至末尾;start <= 截取值 < endvar str1 = str.slice(0,2);// substr(start,length) 截取字符串,start 为起始位置,必填,最小为0;length 为截取长度,不填即代表至末尾;start <= 截取值 < start+lengthvar str2 = str.substr(1,2);// substring(start,end) 截取字符串,start 为起始位置,必填,最小为0;end 为结束位置,不填即代表至末尾;start <= 截取值 < endvar str3 = str.substring(2,8);console.log(str, str1, str2, str3); // Hello World! He el ll0 w0 去字符串首尾空白123456var str = \" Hello World! \";// trim() 去字符串首尾空白var str1 = str.trim();console.log('-',str,'--',str1,'-'); // - Hello World! -- Hello World! - 字符串大小写转换下列方法不会改变原字符串。 1234567891011121314151617var str = \"Hello World!\";// 使用 toLowerCase() 将 str 转换为小写var str1 = str.toLowerCase();// 使用 toUpperCase() 将 str 转换为大写var str2 = str.toUpperCase();// 使用 toLocaleLowerCase() 将 str 转换为小写// 可以识别本地主机语言(部分地域语言存在特有的大小写转换)来得出对应的小写var str3 = str.toLocaleLowerCase();// 使用 toLocaleUpperCase() 将 str 转换为大写// 可以识别本地主机语言(部分地域语言存在特有的大小写转换)来得出对应的小写var str4 = str.toLocaleUpperCase();console.log(str,str1,str2,str3,str4); // Hello World! hello world! HELLO WORLD!","updated":"2020-07-18T08:46:29.863Z","tags":[{"name":"JS","slug":"JS","permalink":"http://huishanyi.club/tags/JS/"}]},{"title":"JS操作数组","date":"2020-07-12T21:26:58.000Z","path":"2020/07/13/JS/JS操作数组/","text":"  数组对象的作用是使用单独的变量名来存储一系列不限类型的值。 创建数组常规方式1234var myCars=new Array(); myCars[0] = \"Saab\"; myCars[1] = \"Volvo\";myCars[2] = \"BMW\"; 简洁方式1var myCars = new Array(\"Saab\",\"Volvo\",\"BMW\"); 字面1var myCars = [\"Saab\",\"Volvo\",\"BMW\"]; 访问数组通过指定数组名以及索引号码,你可以访问或修改某个特定的元素。 12345var myCars = [\"Saab\",\"Volvo\",\"BMW\"];console.log(myCars[0]); // 输出:SaabmyCars[1] = \"Benz\";console.log(myCars); // 输出:[\"Saab\",\"Benz\",\"BMW\"] 数组属性和方法数组长度(数组中元素数量)12var myCars = [\"Saab\",\"Volvo\",\"BMW\"];console.log(myCars.length); // 3 指定元素在数组中的位置12var myCars = [\"Saab\",\"Volvo\",\"BMW\"];console.log(myCars.indexOf(\"Volvo\")); // 1 判断是否为数组1234var arr = [];var json = {};console.log(Array.isArray(arr), Array.isArray(json)); // true false 数组转换为字符串下列方法不会改变原数组。 12345678var num = [1, 2, 3];var str1 = num.toString();var str2 = num.join();// Object.prototype.toString.call() 可检测任意数据类型console.log(Object.prototype.toString.call(str1), Object.prototype.toString.call(str1)); // [object String] [object String]console.log(str1===str2); // true 数组排序下列方法会改变原数组。 1234567891011var num = [2, 1, 5, 4, 6, 3];// 反序原数组var num1 = num.reverse();console.log(num, num1) // [3, 6, 4, 5, 1, 2] [3, 6, 4, 5, 1, 2]// 正向排序数组var num2 = num.sort();console.log(num, num1, num2); // [1, 2, 3, 4, 5, 6] [1, 2, 3, 4, 5, 6] [1, 2, 3, 4, 5, 6],都指向 num 累加器下列方法不会改变原数组。 12345678910111213141516171819202122232425262728var num = [1, 2, 3, 4, 5, 6];// 从左至右累加var result = num.reduce((total, item) => { console.log(`total:${total} -- item:${item} -- result:${total+item}`); return total + item})/*** total:1 -- item:2 -- result:3* total:3 -- item:3 -- result:6* total:6 -- item:4 -- result:10* total:10 -- item:5 -- result:15* total:15 -- item:6 -- result:21*/// 从右至左累加var result1 = num.reduceRight((total, item) => { console.log(`total:${total} -- item:${item} -- result:${total+item}`); return total + item})/** total:6 -- item:5 -- result:11* total:11 -- item:4 -- result:15* total:15 -- item:3 -- result:18* total:18 -- item:2 -- result:20* total:20 -- item:1 -- result:21*/console.log(result, result1) // 21 21 数组过滤下列方法不会改变原数组。 123456789101112131415161718192021222324252627var num = [1, 2, 3, 4, 5, 6];// 从 num 中获取一个大于 3 的数组var num1 = num.filter(item => item>3);// 从 num 中获取小于 4 的第一个元素var num2 = num.find(item => item<4);// 从 num 中获取小于 4 的第一个元素的索引var num3 = num.findIndex(item => item<4);// 从 num 中获取 2 元素的索引var num4 = num.indexOf(2);// 从 num 中获取 2 元素最后一次出现的索引var num5 = num.lastIndexOf(2);// 判断 num 数组中是否所有元素都大于 4var judgement = num.every(item => item>4); // false// 判断 num 数组中是否含有元素大于 4var judgement1 = num.some(item => item>4); // true// 判断 num 中是否有 1 元素var judgement2 = num.includes(1); // trueconsole.log(num, num1, num2, num3, num4, num5, judgement, judgement1, judgement2); // [1, 2, 3, 4, 5, 6] [4, 5, 6] 1 0 1 1 false true true 连接多个数组下列方法不会改变原数组。 123456var num1 = [1, 2];var num2 = [3];var num3 = [4, 5, 6];var num4 = num1.concat(num2,num3);console.log(num1, num2, num3, num4) // [1, 2] [3] [4, 5, 6] [1, 2, 3, 4, 5, 6] 添加元素至数组下列方法会改变原数组。可添加任意数量。 123456789var num = [3, 4, 5];// 推送至首位var length1 = num.unshift(1, 2);// 推送至末位var length2 = num.push(6);console.log(num, length1, length2); // [1, 2, 3, 4, 5, 6] 5 6 删除数组中的元素下列方法会改变原数组。每次仅可删除一个。 123456789var letter = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"];// 删除首位元素var element1 = letter.shift();// 删除末位元素var element2 = letter.pop();console.log(letter, element1, element2); // [\"b\", \"c\", \"d\", \"e\"] \"a\" \"f\" 数组截取替换123456789101112131415161718192021222324252627282930313233var letter = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"];// slice(start, end);start <= 取值 < end,不改变原数组var letter1 = letter.slice(0, 2);// 负数代表倒数,-1 为 e,-3 为 cvar letter2 = letter.slice(-3, -1);// slice(start);start <= 取值 < 末尾var letter3 = letter.slice(letter.length-1);console.log(letter,letter1,letter2,letter3); // [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"] [\"a\", \"b\"] [\"d\", \"e\"] [\"f\"]// ******************************************************var letter = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"];// 万能数组操作方法,会改变原数组// splice(start, deleteNum, addElements),从 start 位置删除 deleteNum 个,在 start 位置前添加 addElements// 给 a 之前增加元素var letter1 = letter.slice(0);letter1.splice(0, 0, \"g\", \"g\");// 删除 a 和 bvar letter2= letter.slice(0);letter2.splice(0, 2);// 修改 b 为 bbvar letter3 = letter.slice(0);letter3.splice(1, 1, \"bb\");console.log(letter,letter1,letter2,letter3); // [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"] [\"g\", \"g\", \"a\", \"b\", \"c\", \"d\", \"e\", \"f\"] [\"c\", \"d\", \"e\", \"f\"] [\"a\", \"bb\", \"c\", \"d\", \"e\", \"f\"] 数组遍历123456789101112131415161718var letter = [\"a\", \"b\", \"c\", \"\"];var feArr = [], mpArr = [];// forEach 遍历数组var forEachArr = letter.forEach((item, index) => { feArr.push(index+item); return index+item;});// map 遍历数组var mapArr = letter.map((item, index) => { mpArr.push(index+item); return index+item;});console.log(letter, feArr, forEachArr, mpArr, mapArr); // [\"a\", \"b\", \"c\", \"\"] [\"0a\", \"1b\", \"2c\", \"3\"] undefined [\"0a\", \"1b\", \"2c\", \"3\"] [\"0a\", \"1b\", \"2c\", \"3\"] 数组深拷贝数组为指向型数据。 123456789101112var num = [1, 2, 3];//声明的 num1 指向 num,即修改 num 或 num1 任何一个,都会两个一起改变var num1 = num;num[0] = 0;console.log(num,num1); // [0, 2, 3] [0, 2, 3]num1[0] = 4;console.log(num, num1); // [4, 2, 3] [4, 2, 3] slice(0)123456789101112var num = [1, 2, 3];//截取一个数组,从初始位置到结束var num1 = num.slice(0);num[0] = 0;console.log(num,num1); // [0, 2, 3] [1, 2, 3]num1[0] = 4;console.log(num, num1); // [0, 2, 3] [4, 2, 3] concat()123456789101112var num = [1, 2, 3];//声明的 num1 指向 num,即修改 num 或 num1 任何一个,都会两个一起改变var num1 = num.concat();num[0] = 0;console.log(num,num1); // [0, 2, 3] [0, 2, 3]num1[0] = 4;console.log(num, num1); // [0, 2, 3] [4, 2, 3] […[]]123456789101112var num = [1, 2, 3];//声明的 num1 指向 num,即修改 num 或 num1 任何一个,都会两个一起改变var num1 = [...num];num[0] = 0;console.log(num,num1); // [0, 2, 3] [0, 2, 3]num1[0] = 4;console.log(num, num1); // [0, 2, 3] [4, 2, 3]","updated":"2020-07-18T08:46:29.864Z","tags":[{"name":"JS","slug":"JS","permalink":"http://huishanyi.club/tags/JS/"}]},{"title":"Promise","date":"2020-07-07T21:49:44.000Z","path":"2020/07/08/JS/Promise/","text":"  Promise 对象用于表示一个异步操作的最终完成 (或失败), 及其结果值。推荐文档 简单使用1234567891011121314151617181920const promise1 = new Promise((resolve, reject) => { // resolve,异步操作完成结果,指引本函数执行 then 中的代码 // reject,异步操作失败结果,指引本函数执行 catch 中的代码 // *** resolve 或 reject 任何一个执行,即代表本函数逻辑执行完成,不再执行本函数的其它代码 resolve('It is value.') setTimeout(() => { reject('It is err.') }, 3000);});promise1.then((value) => { console.log(value); // 输出(resolve 执行,reject 不执行): \"It is value.\"}).catch((err) => { console.log(err); // 输出(reject 执行,resolve 不执行): \"It is err.\"});console.log(promise1);// 输出: [object Promise] 参数executor [ɪɡˈzekjətər],执行器,是 Promise 的唯一参数。 executor 是一个函数,这个函数包含两个参数,分别为 resolve 和 reject。resolve 代表 Promise 执行完成,Promise 状态变为 fulfilled,并执行 then 回调返回执行结果;reject 代表 Promise 执行失败,Promise 状态变为 rejected,并执行 catch 回调抛出错误信息; 状态Promise 有 3 种状态,分别为 pending、fulfilled 和 rejected。 pending初始状态,既不是成功,也不是失败状态。 fulfilled意味着操作成功完成; rejected意味着操作失败。 注意:如果一个 promise 对象处在 fulfilled 或 rejected 状态而不是 pending 状态,那么它也可以被称为 settled 状态。你可能也会听到一个术语 resolved ,它表示 promise 对象处于 settled 状态。 链式调用因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回promise 对象, 所以它们可以被链式调用。 属性 Promise.length:其值总是为 1 (构造器参数的数目); Promise.prototype:表示 Promise 构造器的原型。 方法Promise.all(iterable)Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable(一个可迭代对象,Array 或 String) 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。 12345678910const promise1 = Promise.resolve(3);const promise2 = 42;const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'foo');});Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values);});// 2s 后输出: Array [3, 42, \"foo\"] 返回值 resolved 参数为空的可迭代对象,返回一个已完成(already resolved)状态的 Promise; 参数不包含任何 promise,返回一个异步完成(asynchronously resolved) Promise。 rejected 无参数,返回一个 rejected 状态的 Promise,并报错Uncaught (in promise) TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined; 参数中存在 rejected,返回一个 rejected 状态的 Promise,并报错指出在参数中 rejected 的位置。 其他情况返回一个处理中(pending)的Promise。 Promise.race(iterable)Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个 promise 解决 或 拒绝,返回的 promise 就会 解决 或 拒绝。 123456789101112const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, 'one');});const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'two');});Promise.race([promise1, promise2]).then((value) => { console.log(value);});// 1s 后输出: \"two\" Promise.reject(reason)Promise.reject(reason) 方法返回一个带有拒绝原因的Promise对象。 12345678910function resolved(result) { console.log('Resolved');}function rejected(result) { console.error(result);}Promise.reject(new Error('fail')).then(resolved, rejected);// 输出: Error: fail Promise.resolve(value)Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是thenable(即带有”then” 方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。 123456789101112131415161718192021222324252627282930313233343536373839// Resolve一个thenable对象var p1 = Promise.resolve({ then: function(onFulfill, onReject) { onFulfill(\"fulfilled!\"); }});console.log(p1 instanceof Promise) // true, 这是一个Promise对象p1.then(function(v) { console.log(v); // 输出\"fulfilled!\" }, function(e) { // 不会被调用});// Thenable在callback之前抛出异常// Promise rejectsvar thenable = { then: function(resolve) { throw new TypeError(\"Throwing\"); resolve(\"Resolving\");}};var p2 = Promise.resolve(thenable);p2.then(function(v) { // 不会被调用}, function(e) { console.log(e); // TypeError: Throwing});// Thenable在callback之后抛出异常// Promise resolvesvar thenable = { then: function(resolve) { resolve(\"Resolving\"); throw new TypeError(\"Throwing\");}};var p3 = Promise.resolve(thenable);p3.then(function(v) { console.log(v); // 输出\"Resolving\"}, function(e) { // 不会被调用}); 原型Promise.prototype.constructor(),返回被创建的实例函数. 默认为 Promise 函数。 Promise.prototype.catch(onRejected)catch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用 Promise.prototype.then(undefined, onRejected) 相同。 无法捕获错误示例 12345678910111213141516171819202122232425262728293031323334353637//创建一个新的 Promise ,且已决议var p1 = Promise.resolve(\"calling next\");var p2 = p1.catch(function (reason) { //这个方法永远不会调用 console.log(\"catch p1!\"); console.log(reason);});p2.then(function (value) { console.log(\"next promise's onFulfilled\"); /* next promise's onFulfilled */ console.log(value); /* calling next */}, function (reason) { console.log(\"next promise's onRejected\"); console.log(reason);});// 在异步函数中抛出的错误不会被catch捕获到var p3 = new Promise(function(resolve, reject) { setTimeout(function() { throw 'Uncaught Exception!'; }, 1000);});p3.catch(function(e) { console.log(e); // 不会执行});// 在resolve()后面抛出的错误会被忽略var p4 = new Promise(function(resolve, reject) { resolve(); throw 'Silenced Exception!';});p4.catch(function(e) { console.log(e); // 不会执行}); Promise.prototype.then(onFulfilled, onRejected)then() 方法返回一个 Promise。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。 123456789101112131415161718192021const promise1 = new Promise((resolve, reject) => { //resolve('Success!'); reject('Filled!')});promise1.then((value) => { console.log(value); // resolve 先执行则输出: \"Success!\"},(err)=>{ console.log(err) // reject 先执行则输出: \"Filled!\"})// *********** 与上方等同 ************promise1.then((value) => { console.log(value); // resolve 先执行则输出: \"Success!\"}).catch((err)=>{ console.log(err) // reject 先执行则输出: \"Filled!\"}) Promise.prototype.finally(onFinally)finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这避免了同样的语句需要在then()和catch()中各写一次的情况(例如,发一次请求,无论请求成功与否,都需要记录一次请求总次数)。 示例 12345Promise.resolve(2).then(() => {}, () => {}) // resolve 的结果为 undefinedPromise.resolve(2).finally(() => {}) // resolve 的结果为 2Promise.reject(3).then(() => {}, () => {}) // resolve 的结果为 undefinedPromise.reject(3).finally(() => {}) // rejected 的结果为 3 实例该实例为使用 XHR 加载图像。源码及步骤详解演示地址 手写 Promise通过手写 Promise 来深刻理解 Promise。 参数及状态设计 参数:executor,详细请看 参数; 状态:pending、fulfilled(resolved)和 rejected,详细请看 状态。 设计代码12345678910111213141516171819202122232425262728293031323334class MyPromise { constructor(executor) { // 默认为 pending (等待)状态 this.PromiseStatus = 'pending'; // 默认为值为 undefined this.PromiseValue = undefined; // executor 中的 resolve 方法 let resolve = value => { if(this.PromiseStatus === 'pending'){ // 将 MyPromise 状态更改为 resolved this.PromiseStatus = 'resolved'; } // 更新 MyPromise 的 value this.PromiseValue = value; } // executor 中的 reject 方法 let reject = reason => { if(this.PromiseStatus === 'pending'){ // 将 MyPromise 状态更改为 rejected this.PromiseStatus = 'rejected' } // 更新 MyPromise 的 value this.PromiseValue = reason; // 抛出错误信息 console.error(reason); } // 捕获,若 executor 报错,直接执行 reject try{ executor(resolve, reject) }catch(err) { reject(err) } }} 测试代码1234567891011var p1 = new Promise(() => {})var p2 = new Promise((resolve, reject) => {resolve(123)})var p3 = new Promise((resolve, reject) => {reject(123)})var mp1 = new MyPromise(() => {})var mp2 = new MyPromise((resolve, reject) => {resolve(123)})var mp3 = new MyPromise((resolve, reject) => {reject(123)})console.log(p1,mp1)console.log(p2,mp2)console.log(p3,mp3) 测试截图复制代码到控制台试试吧~ then 方法设计then() 方法有两个参数,onFulfilled 和 onRejected,详细请看 Promise.prototype.then。 设计代码123456789101112131415161718192021222324252627282930313233343536373839404142class MyPromise { constructor(executor) { // 默认为 pending (等待)状态 this.PromiseStatus = 'pending'; // 默认为值为 undefined this.PromiseValue = undefined; // executor 中的 resolve 方法 let resolve = value => { if(this.PromiseStatus === 'pending'){ // 将 MyPromise 状态更改为 resolved this.PromiseStatus = 'resolved'; } // 更新 MyPromise 的 value this.PromiseValue = value; } // executor 中的 reject 方法 let reject = reason => { if(this.PromiseStatus === 'pending'){ // 将 MyPromise 状态更改为 rejected this.PromiseStatus = 'rejected'; } // 更新 MyPromise 的 value this.PromiseValue = reason; // 抛出错误信息 console.error(reason); } // 捕获,若 executor 报错,直接执行 reject try{ executor(resolve, reject) }catch(err) { reject(err) } } // then 方法接受两个回调,onFulfilled 为成功时的回调,onRejected 为失败时的回调 then(onFulfilled, onRejected) { if(this.PromiseStatus=='resolved'){ onFulfilled(this.PromiseValue); } eslse if(this.PromiseStatus=='rejected') { onRejected(this.PromiseValue); } }} 测试代码123456789101112131415161718192021222324function test(arg) { var p = new Promise((resolve, reject) => { if(arg=='1') { resolve('成功!') }else{ reject('失败!') } }) console.log('test:', p); p.then(value => { console.log('then:', value) }).catch(reason => { console.log('catch:', reason) })}var mp1 = new MyPromise(() => {})var mp2 = new MyPromise((resolve, reject) => {resolve(123)})var mp3 = new MyPromise((resolve, reject) => {reject(123)})console.log(p1,mp1)console.log(p2,mp2)console.log(p3,mp3) 测试截图复制代码到控制台试试吧~ 更新中······","updated":"2020-07-18T08:46:29.865Z","tags":[{"name":"ES6","slug":"ES6","permalink":"http://huishanyi.club/tags/ES6/"}]},{"title":"NodeJS面试题","date":"2020-05-28T23:52:56.000Z","path":"2020/05/29/前端面试题/NodeJS面试题/","text":"  本片文章将会每周不定期更新不定数量面试题,毕竟质量才是关键。  实践是检验真理的唯一标准!看题不记题,看题要做题!  面试是一个学习交流的过程,抱着虚心求教的心,人无完人,祝你变得更优秀!","updated":"2020-07-07T21:45:28.630Z","tags":[]},{"title":"Vue面试题","date":"2020-05-28T23:52:28.000Z","path":"2020/05/29/前端面试题/Vue面试题/","text":"  本片文章将会每周不定期更新不定数量面试题,毕竟质量才是关键。  实践是检验真理的唯一标准!看题不记题,看题要做题!  面试是一个学习交流的过程,抱着虚心求教的心,人无完人,祝你变得更优秀! 1. Vue 中 Key 的理解作用 key 是虚拟 DOM 中的唯一标识 使 Diff 算法更快的找到对应的节点,从而提高 Diff 的运行效率 Vue 存在就地更新策略,默认是高效的,当该策略不产生副作用时,建议不要使用 key,这样可以取得更好的性能【只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。】 有相同父元素的子元素必须有独特的 key 保证相同标签名元素的过度切换(使 Vue 可以区分,否则只会替换其内部属性而不会触发过渡效果) 场景 完整地触发组件的生命周期钩子 1234<!-- text 变化就会重新渲染,diff 依靠 key 识别是否为新元素 --><transition> <span :key=\"text\">{{ text }}</span></transition> 触发过渡效果 原理 采用 Diff 算法 通过 patchVnode 方法对比新旧 vnode(深度优先,同层比较) 通过 updateChildren 方法更新(首尾交叉对比) 分析方法 下载源码 写个小例子打断点找到源码位置(比如写个列表) 源码位置:src/core/vdompatch.js patchVnode 函数(Diff 发生的地方) updateChildren 函数 2. Vue 中 Diff 算法的理解作用 Diff 算法是为虚拟 DOM 的框架而生的,可以精确定位所改变的元素使组件更新更加准确与快速 源码 mountComponent 函数中,可以看出每挂载一个组件就会产生一个新 Watcher,为了保证 Vue 组件更新的准确性,采用 Diff 也就可以理解了 Diff 算法比较方式:深度优先,同层比较 触发时机:数据响应式触发 setter => setter 会触发通知 => 通知的方式就是将 watcher 添加到异步更新队列 => 事件循环结束则清空队列(watcher 执行他们的更新函数) => 更新函数执行时调用组件渲染函数和组件的更新函数 => 重新渲染最新的 VDom => 组件实例执行更新函数 打补丁:新旧 VDom 对比就叫做 patch 分析方法 源码位置:src/instance/lifecycle.js mountComponent 函数 每个组件都有一个 watcher,为了精确的更新,Diff 算法就很有必要了 源码位置:src/core/vdompatch.js patchVnode 函数(深度优先,同层比较,Diff 发生的地方,源码见1) updateChildren 函数(首位交叉对比,算法核心,源码见1) 3. MVC、MVP、MVVM 的理解共同点:解决代码杂糅问题。区别:MVP 是 MVC2.0,MVVM 是 MVC3.0。 Web 1.0 时代 早期开发,前后端未分离。虽然开发快,但代码较混乱(jsp,php,.net)。业务逻辑越复杂,单文件代码量越大,项目越难维护; MVC 的诞生就是解决这一些问题,将项目拆分成 Model、View 和 Controller 三大部分。 Model:保存应用数据,与后端数据进行同步; View:将 Model 中的数据可视化,用户可在此进行操作; Controler:业务逻辑,根据用户操作修改 Model 中的数据。 MVC 刚诞生时仅用于后端,比如 spring、Structs 等框架,前端只是 View 部分(前端输出静态页面交给后端,后端人员使用模板语法做整合)。 优点 低耦合 可复用 易维护 缺点 增加系统结构,实现较复杂,需要考虑代码功能划分,最好用于大点的项目,否则得不偿失; View 与 Controller 联系过于紧密。拆分不够完全,很难独立复用; View 对 Model 的低效率访问。Model 中接口不同,View 可能需要多次调用才能拿全数据; Web 2.0 时代 Google 的 Gmail 出现,伴生的 ajax 可使前后台进行交互; 前后端分离,前端专注 HTML、CSS、JS,后端专注数据服务,协同开发,效率提高; 前端通过 ajax 拥有了使页面局部刷新的能力,减轻了后端的负载和流量消耗,用户体验更佳; 专职前端出现,页面内容越来越多,前端代码量变大,项目难维护。 MVP 模式 Model:提供数据; View:负责显示; Presenter:负责 Model 与 View 之间的通信; MVC 衍变而来。 优点 View 与 Model 完全隔离; Presenter 与 View 具体实现技术无关。 缺点 增加了代码复杂度 Presenter 中除了有 Controller 代码,还有大量 Model 与 View 的手动同步逻辑代码,难维护; Presenter 与 View 的耦合度较高。 Web 3.0 时代 前端 MVC 模式出现(将后端 MVC 中的 V 拆为前端的 MVC); 前端 MVVM 模式出现(Angular、React、Vue)。 Model-View-ViewModel 优点 低耦合 可重用 独立开发 可测试(测试 viewModel) 4. Vue 设计原则(理念) 渐进式 JavaScript 框架; 自底向上逐层应用(有着足够庞大的生态去应对多种需求,无论是简单的静态页,还是需要路由跳转,更或者全局状态管理都可以满足); 核心库(DeclarativeRendering、ComponsentSystem)只关注视图层。 易用:没有学习过 Vue,只要你是前端,看着开发文档就可以上手,学习曲线很平滑,模板语法太友善了; 灵活:渐进式,可以是构建用户界面的 JavaScript 库,也可以是一套完整的框架; 高效:Vue2 引入了虚拟 DOM 和 Diff 算法,Vue3 引入 Proxy 对数据响应式改进。 5. Vue 组件化的理解定义 将复杂系统解耦,拆分成多个功能模块,并根据业务进行重组复用; 组件化其实就是一个做积木搭积木的过程; Vue 的核心特性之一。 使用场景页面元素多样,业务逻辑复杂,功能模块可拆分。 优点 功能复用,协同开发,提高开发效率; 单元测试; 可针对指定组件进行更新,提高性能(lifecycle.js - mountComponent(),一个组件一个 watcher); 扩展性高。 注意事项 组件应该是高内聚、低耦合的(任性复用); 合理的拆分组件才能将组件化的优点最大化; 数据传递时尽量遵循单向数据流原则,避免关系混乱。 Vue 组件化特点 声明组件:Vue.component(); 单文件组件(vue-loader 将 template 编译成 render 函数); 组件配置 => VDOM => DOM;(通过 patch.js - createElm() 实例化及挂载) 属性 prop; 自定义事件; 插槽; 双向数据绑定。 6. Vue 组件之间的通讯方式父子组件 父组件向子组件传值 props ref(直接访问 DOM 元素) $children(做封装后使用,避免依赖 DOM) 子组件向父组件传值 $emit / $on $parent(做封装后使用,避免依赖 DOM) 兄弟组件 $parent $root eventbus vuex 跨层级关系 eventbus $attrs / $listeners(在子组件中处理非属性值的传递) provide/inject(单向,祖辈向后代传值) vuex 7. Vue 组件模板只能有一个根元素 实例化 Vue 创建组件时,el 选项只能定一个根元素(入口); 单文件组件为一个 Vue 实例,一个 Vue 实例只能定一个根元素; Diff 算法比较时需要有相同的根。 8. Vue 组件中 data 选项必须为函数而根实例无此限制 Vue 组件 官网有说明当在组件中使用 data property 的时候 (除了 new Vue 外的任何地方),它的值必须是返回一个对象的函数; Vue 组件中的 data 不是函数时无法通过 Vue 检测,提示需要将 data 改为函数; Vue 组件可能存在多个实例; 以对象形式定义 data 会导致它们公用一个 data 从而产生数据污染; 以函数形式定义 data,由于函数的闭包特性,不会产生数据污染; Vue 根实例 根实例只有一个,不会产生数据污染,所以没限制,也不需要做这种限制。 9. v-if 和 v-for 优先级 官网有说明v-for比v-if具有更高的优先级; 写个v-for和v-if同级的情况,打印下app.$options.render,看下渲染函数; 源码src/compiler/codegen/index.js genElement() 函数中判断条件for是高于if的; 如果两个条件需要同时作用,可以先将v-if提到v-for的上层,减少每次循环都做判断导致的性能消耗; 如果v-for和v-if必须作用在一个标签上,那就应该考虑列表数组中数据的筛选了,v-for作用的应该是渲染数组(这么理解的话,就不会产生v-for和v-if必须作用在一个标签上的情况)。 10. vue-router 中保护指定路由的安全通过导航守卫(钩子函数)在路由跳转时先判断条件(是否登录,是否有权限)再决定是否跳转 全局守卫 触发时机:全局有导航发生时 无法获取组件实例 全局前置守卫(beforeEach(to, from, next)) to:目标路由 from:当前路由 next:高阶函数,处理传过来的参数,返回一个设置的 hook 来决定导航后续该怎么做 不传参:正常放行 传参 next(false):未登录、无权限时阻止导航 next(path):传递 path 字符串可以重定向到一个新地址 全局解析守卫(beforeResolve(to, from, next)) 全局后置钩子(afterEach(to, from)) 路由独享守卫(beforeEnter(to, from, next)) 触发时机:发生与当前路由相关的导航时 无法获取组件实例 组件内的守卫 触发时机:用到当前组件时 可以获取组件实例 beforeRouteEnter(to, from, next) 导航确认前调用 不能获取组件实例 this 组件实例还没被创建 beforeRouteUpdate(to, from, next) 导航更新时调用(例如:复用组件时,路径由test/01跳转至test/02时,就会调用此函数) beforeRouteLeave(to, from, next) 导航离开前调用(例如:填完表单还未保存就离开,执行此函数让用户进行确认是否离开) 导航解析流程: 导航被触发。 在失活的组件里调用 beforeRouteLeave 守卫。 调用全局的 beforeEach 守卫。 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。 在路由配置里调用 beforeEnter。 解析异步路由组件。 在被激活的组件里调用 beforeRouteEnter。 调用全局的 beforeResolve 守卫 (2.5+)。 导航被确认。 调用全局的 afterEach 钩子。 触发 DOM 更新。 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。 11. nextTick 的理解定义nextTick 是 Vue 的一个全局 API,由于 Vue 的异步更新策略导致我们对数据的直接修改不会立刻体现在 DOM 中,在这种情况下使用 nextTick 方法即可解决。 作用由于 Vue 在更新 DOM 时是异步执行。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。nextTick 方法会在队列中加入一个回调函数,确保函数在前面的 DOM 操作完成后调用。 使用方法 123456789101112131415161718192021222324252627282930313233343536373839404142<div id=\"example\">{{message}}</div>var vm = new Vue({ el: '#example', data: { message: '123' }})vm.message = 'new message' // 更改数据vm.$el.textContent === 'new message' // DOM 未更新Vue.nextTick(function () { vm.$el.textContent === 'new message' // DOM 更新})/*********************************************************/// 组件内使用Vue.component('example', { template: '<span>{{ message }}</span>', data: function () { return { message: '未更新' } }, methods: { updateMessage: function () { this.message = '已更新' console.log(this.$el.textContent) // 未更新 this.$nextTick(function () { console.log(this.$el.textContent) // 已更新 }) } }})/********************************************************/// 作为一个 Promise 使用 (2.1.0 起新增)Vue.nextTick() .then(function () { // DOM 更新了 }) 原理将我们传入 nextTick 中的函数加入到 callbacks 里,然后使用 timerFunc 函数异步调用,Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。 12. Vue 的响应式定义监听数据变化(更改数据) 并 作出响应(更新 DOM)。 作用MVVM 框架中解决的一个核心问题就是连接数据层和视图层,通过数据驱动应用,数据变化,视图更新,数据响应式处理就是为了解决这个问题。 原理 优缺点 优点 只操作数据,不用频繁的操作 DOM,既降低了开发难度,又提升了开发效率(Diff 算法) 缺点 初始化时递归遍历会造成性能损失 Vue3 中响应式的新变化 13. Vue 性能优化路由懒加载 12345const router = new VueRouter({ routes: [ {path:'/foo', component: () => import('.Foo.vue')} ]}) keep-alive缓存页面 1234567<template> <div id=\"app\"> <keep-alive> <router-view /> </keep-live> </div></template> 合理的利用v-show替换v-ifv-show 的原理是使用 display 属性实现的隐藏显示,v-if 的原理是删除创建,所以你懂得~ v-for与v-if同级时官方文档建议在同时需要使用 v-for 和 v-if 时,把 v-if 提到外层,避免每次循环都进行条件判断,但是有时业务逻辑就需要你在循环项做判断,我们可以使用 computed 对需遍历数组先过滤一遍。 长列表做虚拟滚动使用 vue-virtual-scroller 对长列表进行区域渲染(DOM 复用,只对内容进行更新)。 123456789101112<recycle-scroller class=\"items\" :items=\"items\" :item-size=\"24\"> <template v-slot=\"{items}\"> <FetchItemView :item=\"item\" @vote=\"voteItem(item)\" /> </template></recycle-scroller> 订阅后要在组件销毁时取消订阅Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 1234567created() { this.timer = setInterval(this.refresh, 2000)},beforeDestroy() { // 取消订阅防止内存泄漏 clearInterval(this.timer)} 图片懒加载图片过多时,使用 vue-lazyload 仅加载当前可视区域的图片。 1<img v-lazy=\"/static/img/1.png\" /> 第三方库做按需引入策略项目开发中仅依赖第三方库中的部分组件或方法,按需引入避免体积过大。 1import { Button, Select } from 'element-ui' 无状态组件标记为函数时组件 123456789101112<template functional> <div class=\"cell\"> <div v-if=\"props.value\" class=\"on\"></div> <section v-else class=\"off\"></section> </div></template><script> export default { props: ['value'] }</script> 子组件分割对组件内部渲染频繁的部分进行拆分,使其单独管理自身渲染,减少不必要的渲染。 变量本地化频繁调用的数据存储为变量,减少引用次数。 12345678const a = this.a;let b;for (let i=0; i<1000; i++) { b = b + a}return b; 14. Vue3 新特性 更快 虚拟 DOM 重写 组件快速路径(Component fast path) 单个调用(Monomorphic calls) 子节点类型检测(Children type detection) 优化 slots 生成 静态树提升 静态属性提升 基于 Proxy 的响应式系统 更小 通过摇树优化核心库体积 更易维护 TypeScript + 模块化 更加友好 跨平台:编译器核心和运行时核心与平台无关,使 Vue 更容易在多平台使用 更易使用 改进对 TypeScript 的支持,编辑器可以提供更明确地类型检查和错误警告 更好的调试支持 独立的响应化模块 Composition API 理解才能更好的使用!","updated":"2020-07-19T00:24:23.248Z","tags":[{"name":"前端面试题","slug":"前端面试题","permalink":"http://huishanyi.club/tags/前端面试题/"}]},{"title":"React面试题","date":"2020-05-28T23:52:13.000Z","path":"2020/05/29/前端面试题/React面试题/","text":"  本片文章将会每周不定期更新不定数量面试题,毕竟质量才是关键。  实践是检验真理的唯一标准!看题不记题,看题要做题!  面试是一个学习交流的过程,抱着虚心求教的心,人无完人,祝你变得更优秀! React 中对 key 的理解作用 key 是虚拟 DOM 中的唯一标识 Diff 算法可以更快的找到对应的新旧元素节点,从而提高 Diff 运算效率; key 值保证了元素渲染后的更新效果; 遍历的元素没有 key 属性会报警告。 场景 遍历的元素就要用 key 原理 采用 Diff 算法 注意 key 是唯一标识,切记不要使用遍历的 index 值 使用 index 作为 key 会导致额外的性能消耗; 使用 index 作为 key 会导致渲染出错。 React 要求遍历元素必须使用 key,但不使用没有报 error 而是 warning React 默认(子组件无状态变化,为定值,不存在动态值如 input 输入框)为无 key 的遍历元素加 key,值为 index。 key 的唯一性是存在与兄弟(React 不存在表兄弟,堂兄弟)元素间 举个例子:老王有 3 个娃(小明,小红,小黑),老李也有 3 个娃(小明,小红,小黑),但是他俩的 3 个娃不需要标记(姓就不同)。 key 不能是随机数(key 应唯一且稳定,无论遍历几次 key 都不会变) 举个例子:老王带着 3 个娃(小明,小红,小黑)报班,报成功了,第二天送娃(娃改名字了,小亮、小赤、小暗)去上课,补课班(React)报班人太多,记不清孩子样子,只认登记的名字,很尴尬老王又报了一次名,多浪费了一次钱(额外的性能消耗)。 React 中 Diff 算法的理解详细请看 React虚拟DOM。 Diff 算法为虚拟 DOM 而生,可以更快的找到对应的新旧元素节点,从而提高 Diff 运算效率; Diff 算法比较方式:深度优先,同层比较; React 中的 Diff 算法基于两个假设 两个不同类型的元素会产生出不同的树; 开发者可以通过 key 属性来暗示哪些子元素在不同的渲染下能保持稳定; 分析方法 _updateChildren() 方法 React 生命周期的理解详细请看 React生命周期。 React 15 constructor():初始化 props 和 state super(props) 获取来自父组件传来的参数 this.state 初始状态 componentWillMount():组件将进行渲染钩子 render():组件渲染钩子 componentDidMount():组件渲染完成钩子,此处进行数据请求 componentWillReceiveProps():组件将接收参数钩子,props 改变引起的组件更新 shouldComponentUpdate(nextProps,nextState):组件是否应该更新钩子,可以在此处对比前后的 state 与 props,来判断是否更新组件,做性能优化 componentDidUpdate(prevProps, prevState):组件更新完成钩子 componentWillUnmount():组件即将卸载钩子,在此处进行取消订阅,避免内存泄漏 React 16.4 constructor():初始化 props 和 state super(props) 获取来自父组件传来的参数 this.state 初始状态 render():组件渲染钩子 componentDidMount():组件渲染完成钩子,此处进行数据请求 static getDerivedStateFromProps():从 props 中获取 state,贯穿 constructor 与 shouldComponentUpdate 之间,必须用 static 生命,否则会被忽略 shouldComponentUpdate(nextProps,nextState):组件是否应该更新钩子,可以在此处对比前后的 state 与 props,来判断是否更新组件,做性能优化 getSnapshotBeforeUpdate(prevProps, prevState):更新前获取快照,在最近一次渲染输出(提交到 DOM 节点)之前调用,并返回 null 或者 snapshort(snapshort 会传递给 componentDidUpdate()) componentDidUpdate(prevProps, prevState, snapshot):组件更新完成钩子,snapshot 为 getSnapshotBeforeUpdate() 传递而来 componentWillUnmount():组件即将卸载钩子,在此处进行取消订阅,避免内存泄漏 React 16.8+(Hook) useState():初始化 state,代替 constructor 中的 this.state render():组件渲染钩子 useEffect():代替 componentDidMount, componentDidUpdate, componentWillUnmount,由此可见该方法时发生在渲染后 useMemo():代替 shouldComponentUpdate(),使用备忘录,记忆组件之前的状态,当更新时该组件状态不变,则跳过本次渲染 useLayoutEffect():useEffect 出错时使用,发生于渲染前,毕竟布局是要在渲染前开始的 React refs 的理解React 事件系统的理解setState 的理解React 的组件通信方式谈谈对函数组件与 class 组件的理解React 性能优化方案","updated":"2020-08-24T00:15:43.722Z","tags":[{"name":"前端面试题","slug":"前端面试题","permalink":"http://huishanyi.club/tags/前端面试题/"}]},{"title":"CSS面试题","date":"2020-05-28T23:51:59.000Z","path":"2020/05/29/前端面试题/CSS面试题/","text":"  本片文章将会每周不定期更新不定数量面试题,毕竟质量才是关键。  实践是检验真理的唯一标准!看题不记题,看题要做题!  面试是一个学习交流的过程,抱着虚心求教的心,人无完人,祝你变得更优秀! 谈谈标准盒模型和怪异盒模型 标准盒模型 width = contentWidth 怪异盒模型(IE 盒模型) width = contentWidth + padding + border 两栏布局 float + margin html12345678910111213141516<style> .left { width: 200px; height: 100vh; float: left; } .right { width: 100%; height: 100vh; margin-left: 200px; }</style><body> <div class=\"left\"> <div class=\"right\"></body> position flex(不推荐,兼容性较差) inline-block + calc(不推荐,兼容性较差) 三栏布局 float + margin(中间元素移动要放到最后,否则右元素会被挤到下一行) html12345678910111213141516171819<style> .left { float: left; width: 200px; } .center { margin: 0 100px 0 200px; } .right { float: right; width: 100px; }</style><body> <div class=\"left\"> <div class=\"right\"> <!-- 中间元素移动要放到最后,否则 right 元素会被挤到下一行 --> <div class=\"center\"></body> position flex(不推荐,兼容性较差) inline-block + calc(不推荐,兼容性较差) 水平居中方案 行内元素(inline) 给父级加text-align: center; 给一个左右相等的 margin(宽度为内容撑开,无背景色情况下 padding 也可以做到) 定位(根据应用场景选取 absolute 或者 fixed,left 与 right 置 0,margin 水平值给 auto) 块元素(block) 给一个左右相等的 margin(宽度为内容撑开,无背景色情况下 padding 也可以做到) 定宽(定位,可根据应用场景选取 relative 、 absolute 或者 fixed) left 与 right 置 0,margin 水平值给 auto left 给 50%,margin-left 给宽度的一半 left 设置为 calc(50% - 宽度的一半) 宽度自适应 width 设置为 fit-content,margin 水平值给 auto 定位,left 给 50%,transform: translateX(-50%) 行内块元素(inline-block) 给父级加text-align: center; 给一个左右相等的 margin(宽度为内容撑开,无背景色情况下 padding 也可以做到) 定宽(定位,可根据应用场景选取 relative 、 absolute 或者 fixed) left 与 right 置 0,margin 水平值给 auto left 给 50%,margin-left 给宽度的一半 left 设置为 calc(50% - 宽度的一半) 宽度自适应 width 设置为 fit-content,margin 水平值给 auto 定位,left 给 50%,transform: translateX(-50%) 弹性盒子(flex) 父级设置justify-content: center; 垂直居中方案 行内元素(inline) 给父级加 line-height 并且值等于 height 块元素(block) 给一个上下相等的 margin(高度为内容撑开,无背景色情况下 padding 也可以做到) 定位(根据应用场景选取 relative 、 absolute 或者 fixed) 定高 top 与 bottom 置 0,margin 纵向值给 auto top 设置为 calc(50% - 高度的一半) 高度自适应 top 给 50%,transform: translateY(-50%) 行内块元素(inline-block) 给父级加 line-height 并且值等于 height 给一个上下相等的 margin(高度为内容撑开,无背景色情况下 padding 也可以做到) 定位(根据应用场景选取 relative 、 absolute 或者 fixed) 定高 top 与 bottom 置 0,margin 纵向值给 auto top 设置为 calc(50% - 高度的一半) 高度自适应 top 给 50%,transform: translateY(-50%) 弹性盒子(flex) 父级设置align-items: center; 清除浮动副作用的方法 父级元素定义高度 父级元素overflow: hidden; 原理:BFC(Block formatting context),块级格式化上下文,它是一个独立的布局环境,其中的元素不受外界影响,外界的元素也不会被其中的元素影响 父级定义伪类 原理:parent:after 添加clear: both;样式,其兄弟元素(child)浮动对它无影响,既然浮动对它无影响,则会正常布局,到浮动元素下方(parent:after 有display: block;,独占一行),由于 parent:after 在文档流中,所以父元素会被撑开 12345<!-- DOM 结构 --><div class=\"parent\"> <div class=\"child\"></div> <!-- 此处为 parrent:after 元素位置 --></div> 1234567891011121314/* 样式 */.child { width: 100px; height: 100px; background-color: red; float: left;}.parent:after { content: \"\"; display: block; height: 0; visibility: hidden; clear: both;} BFC定义BFC(Block formatting context),块级格式化上下文,它是一个独立的布局环境,其中的元素不受外界影响,外界的元素也不会被其中的元素影响。BFC 就好比是快递盒子,无论盒子里的东西如何排列,都不会影响快递车里快递怎么排列,反之亦然。 创建 BFC float 值不为 none position 值不为 static 或者 relative display 值为 inline-block、flex、inline-flex、table-caption、table-cell overflow 值不为 visible 应用 相邻元素 margin 重叠 给其中一个元素做 BFC 浮动导致父级高度丢失 给父级元素做 BFC 行内块元素并排时的间隙解决方法 原因:行内元素排列,空白、回车、换行会被浏览器处理成一个空白符; 解决方法:父元素设置font-size: 0;,子元素设置正确的字体大小。 更新中······","updated":"2020-07-07T21:45:28.629Z","tags":[{"name":"前端面试题","slug":"前端面试题","permalink":"http://huishanyi.club/tags/前端面试题/"}]},{"title":"HTML面试题","date":"2020-05-28T23:51:41.000Z","path":"2020/05/29/前端面试题/HTML面试题/","text":"  本片文章将会每周不定期更新不定数量面试题,毕竟质量才是关键。  实践是检验真理的唯一标准!看题不记题,看题要做题!  面试是一个学习交流的过程,抱着虚心求教的心,人无完人,祝你变得更优秀! HTML5 和 HTML4 区别 HTML5 使用<!DOCTYPE html>对文档类型进行申明 HTML5 文档解析标准,不再基于 SGML(Standard Generalized Markup Language)标准 HTML5 增加了语义标签(<header>、<footer>、<section>、<article>、<nav>、<hgroup>、<aside>、<figure>) HTML5 废除了部分样式标签(<big>、<u>、<font>、<basefont>、<center>、<s>、<tt>) HTML5 实现了多媒体中的音频和视频功能(<audio>、<video>) HTML5 属性 增强了<input>标签的 type 属性 1234567891011121314151617<!-- 此类型要求输入格式正确的email地址 --><input type=email ><!-- 要求输入格式正确的URL地址 --><input type=url ><!-- 要求输入格式数字,默认会有上下两个按钮 --><input type=number ><!-- 时间系列,但目前只有 Opera和Chrome支持 --><input type=date ><input type=time ><input type=datetime ><input type=datetime-local ><input type=month ><input type=week ><!-- 默认占位文字 --><input type=text placeholder=\"your message\" ><!-- 默认聚焦属性 --><input type=text autofacus=\"true\" > 增加了<meta>标签的 charset 属性 和<script>的 async 属性 1234<!-- meta标签增加charset属性 --><meta charset=\"utf-8\"><!-- script标签增加async属性 --><script async></script> 部分属性名默认具有 boolean 属性 1234<!-- 只写属性名默认为true --><input type=\"checkbox\" checked/><!-- 属性名=\"属性名\"也为true --><input type=\"checkbox\" checked=\"checked\"/> HTML5 新增 WebStorage(localStorage 和 sessionStorage) HTML5 引入了应用程序缓存器(application cache),可对web进行缓存,在没有网络的情况下使用,通过创建cache manifest文件,创建应用缓存,为PWA(Progressive Web App)提供了底层的技术支持。 meta 标签属性 charset 属性 12<!-- 页面文档编码格式 --><meta charset=\"utf-8\"> name + content 属性 1234567891011121314151617181920212223<!-- 网页作者 --><meta name=\"author\" content=\"\"/><!-- 网页地址 --><meta name=\"website\" content=\"\"/><!-- 网页版权信息 --><meta name=\"copyright\" content=\"\"/><!-- 网页关键字(多个关键字以英文逗号‘,’隔开), 用于SEO --><meta name=\"keywords\" content=\"html,css,js\"/><!-- 网页描述 --><meta name=\"description\" content=\"good good study\"/><!-- 搜索引擎索引方式,一般为all,不用深究 --><meta name=\"robots\" content=\"all\" /><!-- 移动端常用视口设置 --><meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0,maximum-scale=1.0, user-scalable=no\"/><!-- viewport参数详解: width:宽度(数值 / device-width)(默认为980 像素) height:高度(数值 / device-height) initial-scale:初始的缩放比例 (0<范围<=10) minimum-scale:允许用户缩放到的最小比例 maximum-scale:允许用户缩放到的最大比例 user-scalable:用户是否可以手动缩 (no,yes)--> http-equiv 属性 123456789101112<!-- expires指定网页的过期时间。一旦网页过期,必须从服务器上下载。 --><meta http-equiv=\"expires\" content=\"Fri, 12 Jan 2020 18:18:18 GMT\"/><!-- 等待一定的时间刷新或跳转到其他url。下面1表示1秒 --><meta http-equiv=\"refresh\" content=\"1; url=https://www.baidu.com\"/><!-- 禁止浏览器从本地缓存中读取网页,即浏览器一旦离开网页在无法连接网络的情况下就无法访问到页面。 --><meta http-equiv=\"pragma\" content=\"no-cache\"/><!-- 设置cookie的一种方式,并且可以指定过期时间 --><meta http-equiv=\"set-cookie\" content=\"name=value expires=Fri, 12 Jan 2001 18:18:18 GMT,path=/\"/><!-- 使用浏览器版本 --><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" /><!-- 针对WebApp全屏模式,隐藏状态栏/设置状态栏颜色,content的值为default | black | black-translucent --><meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" /> script 标签中的 defer 和 async属性 defer:页面加载完成再进行脚本解析; async:HTML5 新特性,异步执行脚本。 href 与 src 的区别 href Hypertext Reference,超文本链接 触发该功能,地址栏会产生变化,可以做地址跳转,也可以做锚点功能 浏览器解析时不会停止对当前文档的处理 src source,资源 对资源进行下载引入页面作为内容 浏览器解析时会暂停对文档的处理 更新中······","updated":"2020-10-05T13:07:12.286Z","tags":[{"name":"前端面试题","slug":"前端面试题","permalink":"http://huishanyi.club/tags/前端面试题/"}]},{"title":"原生JS面试题","date":"2020-05-28T23:46:33.000Z","path":"2020/05/29/前端面试题/原生JS面试题/","text":"  本片文章将会每周不定期更新不定数量面试题,毕竟质量才是关键。  实践是检验真理的唯一标准!看题不记题,看题要做题!  面试是一个学习交流的过程,抱着虚心求教的心,人无完人,祝你变得更优秀! 什么是防抖和节流? 防抖:事件连续多次触发,本次执行未完成时再一次(或多次)触发事件,以最后一次执行为准。例如:轮播图在自动播放时以3秒间隔轮播一次,第一次轮播开始,但在第2秒内用户手动触发轮播事件,那么第三次轮播将在1秒后执行,每3秒轮播一次中间却出现了一次等待时间为1秒的轮播(就好像抖了一下),而我们消除这一尴尬现象(轮播事件最前方插一行清除定时器的代码)的过程就是防抖。 节流:事件连续多次触发,但在指定时间内只触发一次。例如:还是轮播图,自动播放以3秒间隔轮播一次,无论在这3秒内触发多少次,他都是3秒执行一次轮播。 JS操作数组的方法参考 JS操作数组。 JS操作字符串的方法参考 JS操作字符串。 谈谈对JS中变量提升的理解JS 中函数声明和变量声明总会被提升到作用域顶部(仅是声明提升到顶部,赋值并不会提升)。 先来看一段代码 1234567891011121314151617181920212223242526// test_1:控制台报错“Uncaught ReferenceError: x is not defined”,并不会输出 1console.log(x);console.log(1);// test_2:编码过程中往往需要在未声明前使用部分代码,且不希望报错中断整体代码// 该示例将会分别输出 undefined 与 1,此时不报错就是因为 var 对 x 的生命提升到了作用于顶部console.log(x);console.log(1);var x = 2; // 不过变量提升并不推荐,因此 ES6 中的 let 与 const 声明并不存在变量提升// test_3:函数式声明也存在变量提升,不过建议使用 const 声明函数,避免变量与函数冲突// 此时会输出 function x(){return 'It is a function !' },你可能说覆盖声明,好理解,那么看 test_4console.log(x);var x = 2;function x(){ return 'It is a function !' }// test_4:函数式声明会赋值,函数声明的权重总是高一些,所以最终结果往往是指向函数声明的引用。// 覆盖可以直接 x=1 ,则会输出 1,仅做值的覆盖时不存在声明的权重冲突// 因此下方输出同 test_3,而不是 undefinedconsole.log(x);function x(){ return 'It is a function !' }var x = 2; 谈谈对JS中use strict的理解use strict 是 ES5 的概念,是一种运行时自动执行更严格的 JavaScript 代码解析和错误处理的方法。如果代码错误被忽略或失败,将会产生错误或抛出异常,ES6 模块默认使用 use strict,开发底层框架时保证兼容性基于 ES5 会声明(源码中会出现)。 优秀的报错机制大大的减少了排错的时间,并且消除了隐藏的风险问题。 严格模式限制 变量必须声明后再使用 函数的参数不能有同名属性,否则报错 不能使用 with 语句 不能对只读属性赋值,否则报错 不能使用前缀 0 表示八进制数,否则报错 不能删除不可删除的属性,否则报错 不能删除变量 delete prop ,会报错,只能删除属性 selete global[prop] eval 不会在它的外层作用域引入变量 eval 和 arguments 不能被重新赋值 arguments 不会自动反映函数参数的变化 不能使用 arguments.callee 不能使用 arguments.caller 禁止 this 指向全局对象 不能使用 fn.caller 和 fn.arguments 获取函数调用的堆栈 增加了保留字(比如 protected、static 和 interface) 手写一个compose函数1const compose = (...arg) => data => arg.reduce((total,curentValue) => curentValue(total), data);","updated":"2020-08-24T00:13:22.902Z","tags":[{"name":"前端面试题","slug":"前端面试题","permalink":"http://huishanyi.club/tags/前端面试题/"}]},{"title":"博客搭建——后台管理系统搭建","date":"2020-04-07T03:13:17.000Z","path":"2020/04/07/项目实例/博客搭建/博客搭建——后台管理系统搭建/","text":"  动态网站(存在数据交互的网站)通常分为前台 UI 界面与后台管理系统,在这前、后台分离开发的时代,开发并没有先后顺序,有图就尽管画,只要接口对的上。 项目名称sumblog_manager 技术栈Ts + Node + React全家桶 + AntD 功能概述 登录(无注册功能,仅由超管添加) 用户管理(低权限用户禁止查看高权限用户) 文章管理(仅做删除、警告,编辑功能归前台) 日志管理(记录何时何地何设备进行了何操作) 安全设置(特定级别用户可在前台选择相应的代码保护) 支持移动化(时时管理) 自定义主题(主题色、字体调整、背景图) 新功能持续添加中 配置 Node 环境参考 NodeJS安装教程。 建议版本:node v12.16.1+、npm v6.13.4+ 了解 NPM 常用命令参考 NPM介绍。 创建 React 应用参考 create-react-app(我创建的应用名称为 sumblog_manager)。 创建 React Ts 应用命令:npx create-react-app ts_app_name --template typescript。 扩展webpack配置参考 react-app-rewired。 目录结构由create-react-app生成目录改造而来(暂时仅改造src目录)。 123456789101112src├── components(公共组件)├── pages│ ├── article(文章管理)│ ├── log(日志管理)│ ├── login(登录)│ ├── safety(安全管理)│ ├── theme(主题管理)│ └── user(用户管理)│ ├── PersonalInfo(个人信息)│ └── UserList(用户列表)└── router(路由) 添加路由参考 react-router与react-router-dom。 添加状态管理工具参考 Redux与react-redux。","updated":"2020-07-07T21:45:28.637Z","tags":[{"name":"博客搭建","slug":"博客搭建","permalink":"http://huishanyi.club/tags/博客搭建/"}]},{"title":"RESTfulAPI","date":"2020-03-16T02:39:47.000Z","path":"2020/03/16/接口/RESTfulAPI/","text":"  RESTful 是一个符合架构原理、功能强、性能好、适宜通信的架构。适用于移动互联网厂商作为业务使能接口的场景。 RESTREST(Representational State Transfer,表征性状态转移),万维网软件架构风格,它首次出现在 2000 年 Roy Fielding 的博士论文中,Roy Fielding是 HTTP 规范的主要编写者之一。 六大原则 客户-服务器(Client-Server,C/S):两端分离(服务端专注数据存储;前端专注用户界面); 无状态:所有用户会话信息都保存在客户端,简化了服务端代码,利于服务端故障恢复;每次请求必须包括所有信息,接口透明; 缓存:所有服务端响应都要被标明是否可缓存,减少前后端交互,提升了性能; 接口统一:不同接口拥有相同的规范,易用,实现了前后端分离; 一个 URI 代表一种资源 通过 JSON 或 XML 表述操作资源 超媒体作为应用状态引擎(接口嵌套可通过超链接跳转) 分层系统:仅相邻层透明,更安全; 按需代码:客户端可下载运行服务器传来的代码,简化了客户端。 RESTful满足 REST 风格的架构即为 RESTful。 RESTful API满足 REST 风格的 API(Application Program Interface,允许两个程序相互通信的代码)。 设计规范设计规范转自阮一峰的网络日志 ———— RESTful API 设计指南 协议API与用户的通信协议,总是使用HTTPS协议(防止页面篡改)。 域名应该尽量将API部署在专用域名之下。 1https://api.example.com 如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。 1https://example.org/api/ 版本应该将API的版本号放入URL。 1https://api.example.com/v1/ 路径路径又称”终点”(endpoint),表示API的具体网址。 在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。 12https://api.example.com/v1/usershttps://api.example.com/v1/articles HTTP 动词对于资源的具体操作类型,由HTTP动词表示。 常用的HTTP动词有下面五个(括号里是对应的SQL命令)。 GET(SELECT):从服务器取出资源(一项或多项)。 POST(CREATE):在服务器新建一个资源。 PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。 PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。 DELETE(DELETE):从服务器删除资源。 HEAD:获取资源的元数据。 OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。 过滤信息如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。 12345?limit=10:指定返回记录的数量?offset=10:指定返回记录的开始位置。?page=2&per_page=100:指定第几页,以及每页的记录数。?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。?users_id=1:指定筛选条件 参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。 状态码服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。 123456789101112200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)204 NO CONTENT - [DELETE]:用户删除数据成功。400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。 查看所有状态码信息 错误处理如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。 123{ error: "Invalid API key"} 返回结果针对不同操作,服务器向用户返回的结果应该符合以下规范。 123456GET /collection:返回资源对象的列表(数组)GET /collection/resource:返回单个资源对象POST /collection:返回新生成的资源对象PUT /collection/resource:返回完整的资源对象PATCH /collection/resource:返回完整的资源对象DELETE /collection/resource:返回一个空文档 Hypermedia APIRESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。 比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。 123456{"link": { "rel": "collection https://www.example.com/users", "href": "https://api.example.com/users", "title": "List of users", "type": "application/vnd.yourformat+json"}} 上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。 API 身份认证API 的身份认证可使用 OAuth 2.0 或 JWT(保证用户数据安全)。 限流防止网站攻击,频繁请求导致服务器挂掉。 经典实例最全最可靠的实例 ———— GitHub REST API V3 接口。","updated":"2020-07-07T21:45:28.631Z","tags":[{"name":"接口","slug":"接口","permalink":"http://huishanyi.club/tags/接口/"}]},{"title":"MySQL常见问题","date":"2020-03-09T05:56:04.000Z","path":"2020/03/09/数据库/MySQL常见问题/","text":"  MySQL 常见问题汇总。碰到问题先看是什么原因,英文底子差就翻译下,只有找准问题才可以更快的找到解决方案。 Client does not support authentication protocol问题描述客户端不支持身份验证协议。 产生原因v8.0.4 开始 mysql 引入一个 caching_sha2_password 模块作为默认身份验证插件,以前版本通过 mysql_native_password 模块作为默认身份验证插件,当存在版本冲突时就会发生该问题。 版本冲突举例: 旧版本创建的数据库迁移到新版本使用,会自动切换身份验证方式,不会出现该错误; npm 托管平台 mysql 包并未对此出代码进行更新,连接新版本数据库时会出现该错误。 解决方案 进入 mysql:输入命令mysql -u root -p(默认已进行 mysql 全局变量配置); 提示输入密码,输入密码(无密码直接回车),回车,即可进入 mysql; 输入命令ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';设置 mysql 验证方式为 mysql_native_password,并重置密码为 123456; 重启你的项目,你发现世界还是曾经的样子~","updated":"2020-07-07T21:45:28.632Z","tags":[{"name":"数据库","slug":"数据库","permalink":"http://huishanyi.club/tags/数据库/"},{"name":"MySQL","slug":"MySQL","permalink":"http://huishanyi.club/tags/MySQL/"}]},{"title":"创建Node项目并上传至NPM","date":"2020-03-05T04:05:00.000Z","path":"2020/03/05/NodeJS/创建Node项目并上传至NPM/","text":"  通过命令创建 NodeJS 项目,复用性较强的项目可上传至 NPM 包管理平台,下次项目使用可直接通过 npm install 命令拉取至新项目使用。 创建 Node 项目打开命令面板,运行npm init生成 Node 项目目录。 package name: 项目名称; version: 版本; description: 项目描述; entry point: 默认项目入口; test command: ; git repository: 若需上传项目至 git,填写 git 仓库地址即可; keywords: 项目关键字,多个关键字用逗号隔开,若上传至 NPM ,可通过关键字查询; author: 项目作者; license: 授权,直接回车进行公用。 配置快速启动命令初始化完成后,项目根目录生成的package.json中添加如下命令: 123\"scripts\":{ \"start\":\"node 入口文件相对目录\",// 配置后可以直接运行 npm start 运行项目} 配置自启动node 项目启动后,每次更新代码,都需要手动重启项目,建议为项目配置自启动功能,节省开发时间。 运行npm install --save-dev nodemon或npm i -D nodemon安装开发环境依赖; package.json中修改运行命令; 123\"scripts\": { \"start\": \"nodemon 入口文件相对目录\"} 代码编写根据功能需求编写代码。 上传至 NPM功能代码编写完成后,即可上传至 NPM 包管理平台,便于下次项目拉取。 注册 NPM 账号进入 NPM 官网,进行账号注册。 登录 NPM在项目根目录下打开命令面板,运行npm login填写相关信息进行登录。 发布命令面板运行npm publish,完成发布。 拉取项目项目上传至 NPM 成功后,若要拉取,运行命令npm install 包名或npm i 包名进行拉取使用。","updated":"2020-07-07T21:45:28.624Z","tags":[{"name":"NodeJS","slug":"NodeJS","permalink":"http://huishanyi.club/tags/NodeJS/"}]},{"title":"Node+Koa搭建服务端","date":"2020-03-05T03:05:24.000Z","path":"2020/03/05/项目实例/Node+Koa搭建服务端/","text":"  将 Node 项目中的数据库服务解耦,上传至 NPM 平台,方便之后项目复用。 技能要求 了解 NodeJS(v7.6.0+) 了解 KoaJS(koa2) 了解 RESTful API 了解 MySQL 或者 MongoDB 了解 postman 环境要求 Node v7.6.0+ 环境(参考 NodeJS 安装教程) 数据库管理工具(二选一) MySQL(参考 MySQL安装建库建表) MongoDB 网页请求调试工具(postman) 创建 Node 项目参考 创建 Node 项目并上传至 NPM。 服务端目录结构123456789db-service├── app│ ├── controller(控制器目录)│ ├── model(模型目录)│ ├── public(公共文件目录)│ ├── routers(路由目录)│ ├── config.js(配置文件)│ └── index.js(应用入口)└── package.json(项目依赖包目录) 服务端功能实现(MongoDB)意义为 WEB 应用提供可对数据库进行操作的接口服务。 依赖包(插件)将服务端功能拆解,仅维护核心代码,其余功能由插件提供(保证了开发效率,及功能的更新维护)。 生产依赖 koa:基于 Node 开发的轻量级 WEB 应用框架,API 开发的利器; koa-router:koa 的路由中间件,提供 Restful 资源路由; koa-body:koa 请求体解析中间件,支持文件上传; koa-json-error:koa2 错误处理中间件(json 形式返回错误),显示堆栈跟踪; koa-parameter:koa 参数验证中间件; koa-static:koa 静态文件服务中间件; koa-jwt:koa jwt 认证中间件; mongoose:MongoDB 对象建模工具,连接操作数据库; 开发依赖 cross-env:设置环境变量(可跨平台); 入口文件db-service > app > index.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748const Koa = require('koa'); // 服务端采用的框架const koaBody = require('koa-body'); // 请求体解析中间件,支持文件上传const koaStatic = require('koa-static'); // 静态文件服务中间件const error = require('koa-json-error'); // 错误处理中间件const parameter = require('koa-parameter'); // 参数验证中间件const mongoose = require('mongoose'); // 连接操作 MongoDBconst path = require('path'); // Node 核心模块,提供路径服务const routers = require('./routers'); // 路由文件const { connectionStr } = require('./config');// 连接参数文件const app = new Koa; // 实例化:将抽象的类具体化,进而实现调用类中的功能/* MongoDB 连接服务 */mongoose.connect(connectionStr, { useNewUrlParser: true, useUnifiedTopology: true }, () => { console.log('MongoDB connection succeeded !');});mongoose.connection.on('error', console.error);mongoose.set('useFindAndModify', false);/* 静态文件服务 */app.use(koaStatic(path.join(__dirname, 'public')));/* 错误处理(生产环境不返回堆栈信息) */app.use(error({ postFormat: (err, {stack, ...rest}) => { if(process.env.NODE_ENV === 'production'){ return rest; } else { return {stack, ...rest}; } }}));/* 请求体解析,文件上传 */app.use(koaBody({ multipart: true, formidable: { uploadDir: path.join(__dirname, '/public/uploads'), keepExtensions: true }}));/* 添加 verifyParams 方法(控制器中会有体现),不添加 catch 中间件 */parameter(app);/* 注册路由 */routers(app);/* 在 3000 端口启动服务 */app.listen(3000, () => { console.log('Starting with 3000 ···')}) 数据库连接配置文件db-service > app > config.js 1234module.exports = { secret: \"\", // jwt 认证需要的参数(后期处理) connectionStr: 'mongodb+srv://admin:<password>@dbservice-ndhcg.mongodb.net/test?retryWrites=true&w=majority' // MongoDB 连接信息码(后期处理)} 创建用户表组织结构db-service > app > model > user.js 123456789101112131415const mongoose = require('mongoose');const {Schema, model} = mongoose;const userSchema = new Schema({ name: { type: String }, password: { type: String, select: false }})module.exports = model('User', userSchema); 控制器db-service > app > controller > home.js 123456789101112131415const path = require('path');class HomeCtrl { index(ctx) { ctx.body = \"<h1>Home</h1>\" } // 上传接口 upload(ctx) { const file = ctx.request.files.file; const basename = path.basename(file.path); ctx.body = {url: `${ctx.origin}/uploads/${basename}`}; }}module.exports = new HomeCtrl(); 用户表控制器db-service > app > controller > user.js 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980const jwt = require('koa-jwt'); // json web token 认证中间件const User = require('../model/user'); // 用户表结构const {secret} = require('../config'); // jwt 签名所需要的参数class UserCtrl { // 增 async create(ctx) { ctx.verifyParams({ name: { type: 'string' }, password: { type: 'string' } }) const {name} = ctx.request.body; const repeatedUser = await User.findOne({name}); if(repeatedUser) {ctx.throw(409, '用户已存在!')}; const user = await new User(ctx.request.body).save(); ctx.body = user; } // 删 async del(ctx) { const user = await User.findByIdAndRemove(ctx.params.id); if(!user) {ctx.throw(404, '用户不存在!')}; ctx.status = 204; } // 改 async update(ctx) { ctx.verifyParams({ name: { type: 'string', required: false }, password: { type: 'string', required: false } }) const user = await User.findByIdAndUpdate(ctx.params.id, ctx.request.body); if(!user) {ctx.throw(404, '用户不存在!')} ctx.body = user; } // 查询所有 async find(ctx) { ctx.body = await User.find(); } // 查询特定用户 async findById(ctx) { const user = await User.findById(ctx.params.id); if(!user) {ctx.throw(404, '用户不存在!')} ctx.body = user; } // 登录 async login(ctx) { ctx.verifyParams({ name: { type: 'string' }, password: { type: 'string' } }) const user = await User.findOne(ctx.request.body); if(!user) {ctx.throw(401, '用户名或密码不正确!')} const {_id, name} = user; const token = jwt.sign({_id, name}, secret, {expiresIn: '1d'}); ctx.body = {token}; } // 认证 auth = jwt({secret}); // 授权 async checkOwner(ctx, next) { if(ctx.params.id !== ctx.state.user._id) {ctx.throw(403, '没有权限!')} await next(); }}module.exports = new UserCtrl(); 路由入口db-service > app > routers > index.js 1234567891011const fs = require('fs');module.exports = (app) => { // 利用 fs 模块遍历注册路由 fs.readdirSync(__dirname).forEach((file) => { if(file !== 'index.js') { const router = require(`./${file}`); app.use(router.routes()).use(router.allowedMethods()); } })} 主路由db-service > app > routers > home.js 12345678910const Router = require('koa-router');const {index, upload} = require('../controller/home');const router = new Router();router.get('/', index);router.post('/upload', upload);module.exports = router; 用户路由db-service > app > routers > user.js 12345678910111213141516const Router = require('koa-router');const {create, del, update, find, findById, login, auth, checkOwner} = require('../controller/user');const usersRouter = new Router({prefix: '/users'}); // ‘/’代表‘/users’usersRouter.post('/', create);usersRouter.delete('/:id', auth, checkOwner, del);usersRouter.patch('/:id', auth, checkOwner, update);usersRouter.get('/', find);usersRouter.get('/:id', findById);usersRouter.post('/login', login);module.exports = usersRouter; 更新中······","updated":"2020-07-07T21:45:28.637Z","tags":[{"name":"NodeJS","slug":"NodeJS","permalink":"http://huishanyi.club/tags/NodeJS/"},{"name":"数据库","slug":"数据库","permalink":"http://huishanyi.club/tags/数据库/"},{"name":"服务端搭建","slug":"服务端搭建","permalink":"http://huishanyi.club/tags/服务端搭建/"}]},{"title":"ESLint","date":"2020-03-04T00:46:58.000Z","path":"2020/03/04/代码规范/ESLint/","text":"  ESLint 是一个开源的、提供插件化的 javascript 代码检测工具,初衷是为了让程序员可以创建自己的检测规则。它内置了一些流行代码规则,也可以自定义代码规则,让你的代码更加规范,使团队的代码更加统一。  ESLint 由 Nicholas C. Zakas 于2013年6月创建。若需详细了解 ESLint,请浏览 ESLint 官方文档,ESLint 汉化文档。 为什么要使用 ESLint ? 统一团队代码,减少沟通成本; 插拔式代码规则,自由选取; 配置简单,快速实用。 环境 支持 ESLint 插件的编辑器(VSCode、Sublime、Atom、WebStorm···) Node v5.2.0+(主版本号已经是两位数了,老旧版本没必要重提,此类版本保证了 npx 的正常使用) React 项目配置 ESLint 进入项目根目录; 运行npx eslint --init初始化; How would you like to use ESLint? To check syntax, find problems, and enforce code style What type of modules does your project use? JavaScript modules (import/export) Which framework does your project use? React Does your project use TypeScript? (y/N) N(取决于你是否使用TypeScript) Where does your code run?(多选,空格选取,回车确定,取决于你的项目运行环境) (*)Browser(空格后括号中会出现 *) (*)Node How would you like to define a style for your project? Use a popular style guide(使用主流代码风格,==推荐==) Answer questions about your style(问答形式生成代码风格) Inspect your JavaScript file(s)(检查已有 JS 文件生成代码风格) Which style guide do you want to follow?(上一步选择的是主流代码风格) Airbnb: https://github.com/airbnb/javascript (推荐,97.6k star) Standard: https://github.com/standard/standard (24k star) Google: https://github.com/google/eslint-config-google (1.3k star) What format do you want your config file to be in? (生成的 ESLint 配置文件格式) JavaScript(个人习惯) YAML JSON Would you like to install them now with npm? Y(安装依赖) ESLint 初始化完成,项目根目录将生成.eslintrc.js文件(配置代码规则)。 进行编辑器配置,以 VSCode 为例,安装 ESLint 插件即可。 完成上述操作,打开项目文件,从此代码变美丽! VSCode 自动格式化代码 安装 EsLint 插件 选择File(文件) => Preference(首选项) => Settings(设置) 搜索 eslint 点击 Edit in setting.json(在 setting.json 中编辑) 添加如下配置(2019 提交的版本) 12345678910111213141516171819\"eslint.autoFixOnSave\": true,\"eslint.validate\": [ { \"language\": \"vue\", \"autoFix\": true }, { \"language\": \"html\", \"autoFix\": true }, { \"language\": \"javascript\", \"autoFix\": true }, { \"language\": \"typescript\", \"autoFix\": true },], 添加如下配置(2020 提交的版本) 123\"editor.codeActionsOnSave\": { \"source.fixAll.eslint\": true,}, ESLint 问题汇总ESLint 在 VSCode 中不生效问题描述VSCode 中 ESLint 插件已正常安装,项目初始化完成,打开项目文件发现 ESLint 不生效。 产生原因VSCode 中 ESLint 插件默认读取 WORKSPACE 下项目根目录下的.eslintrc.js,请勿将自己创建的项目汇总目录添加至 VSCode 中。 解决方案项目目录在 VSCode 中外层不要包裹文件夹。正确目录格式: 错误目录格式: Expected linebreaks to be ‘LF’ but found ‘CRLF’问题描述文件中报错Expected linebreaks to be 'LF' but found 'CRLF'。 产生原因系统换行符异常。 解决方案.eslintrc.js文件中添加下述代码: 1234rules: { // 解决 Expected linebreaks to be 'LF' but found 'CRLF'. 报错 \"linebreak-style\": [0 ,\"error\", \"windows\"], },","updated":"2020-07-07T21:45:28.628Z","tags":[{"name":"代码规范","slug":"代码规范","permalink":"http://huishanyi.club/tags/代码规范/"}]},{"title":"计算机组成与结构","date":"2020-02-04T22:38:40.000Z","path":"2020/02/05/系统分析师/计算机组成与结构/","text":"  介绍计算机硬件组成及功能、计算机系统结构及系统可靠性分析。 计算机硬件组成 运算器 控制器 存储器 内部存储器:容量小,速度快,临时存放数据; 外部存储器:容量大,速度慢,长期保存数据。 输入设备(鼠标键盘) 输出设备(显示器) 中央处理单元CPU,Central Processing unit。组成:运算器,控制器,寄存器组,内部总线。功能:程序控制,操作控制,时间控制,数据处理。 运算器组成 算术逻辑单元(ALU,Arithmetic and Logic Unit):数据的算数和逻辑运算; 累加器:(AC,Accumulator ):运算结果或源操作数的存放区; 数据缓冲寄存器(DR,Data Register):暂时存放内存的指令或数据; 状态条件寄存器(PSW,Program Status Word):保存指令运行结果的条件吗内容,如进位标志、溢出标志等。 功能 算术运算(加减乘除等); 逻辑运算并逻辑测试(与、或、非、比较等)。 控制器组成 指令寄存器(IR,Instruction Register):暂存 CPU 执行指令; 程序计数器(PC,Program Counter):存放指令执行地址; 地址寄存器(AR,Address Register):保存当前 CPU 访问的内存地址; 指令译码器(ID,Instruction Decoder):分析指令操作码。 功能控制整个 CPU 的工作,最为重要。 程序控制 时序控制 数据表示校验码指令系统存储系统总线结构系统可靠性分析","updated":"2020-07-07T21:45:28.634Z","tags":[{"name":"系统分析师","slug":"系统分析师","permalink":"http://huishanyi.club/tags/系统分析师/"}]},{"title":"“一万年”未碰博客有感","date":"2020-02-02T02:38:25.000Z","path":"2020/02/02/随笔/“一万年”未碰博客有感/","text":"  “一万年”没有更新博客了,年后爆发的冠状病毒使假期不断延长(原本为1月30号,后来变成了2月2号,又后来变为了2月9号),可我却未更新一篇博客,真的是没有时间吗?直到这最后一天假(2月3号开始远成办公,感谢我们的总经理和人事在这么危险的时候跑到公司为我们开机进行远程调试),看到工作群里老板发的一篇PPT,里面有博客链接,突然刺痛了我,我的博客呢?!。   我承认有太多的担心,每天醒来第一件事就是看病毒传染实时动态,可并不代表我没有时间,过年的轻松已经让我忘了还有更新博客的习惯(或许还并没有成为真正的习惯),更新博客还需要外界去提醒,可笑。   3分钟热度,我是存在的,可讨厌它也喜欢它,讨厌它是因为做一件事很难坚持,喜欢它是因为在高中发现它后我就开始不断地定小目标,有点像代码拆分,哈哈哈。但是有时候定的小目标并不小,时间也挺长,那么我就得找个开始的点(2月2号创建,2月3号发布),有了开始的点就得有对应的结束点(下一次过年),这样我就在一个时间区段中工作,类似于上学、上班的时候你不能迟到、不能早退等规矩,那么我就成功的将不太小的目标拆分成一个个可实施性更强的小目标。   之前总认为博客中的随笔是浪费时间,有的人随笔总是在记录生活中的琐碎,有的人则是被刺破后突发的感想,显然我是后者,有点像“盗梦空间”给自己做的自我暗示。   “一万年”没有更新博客了,这不来了嘛!我要加油!武汉加油!中国加油!","updated":"2020-07-07T21:45:28.636Z","tags":[{"name":"随笔","slug":"随笔","permalink":"http://huishanyi.club/tags/随笔/"}]},{"title":"系统分析师介绍","date":"2020-01-08T22:11:27.000Z","path":"2020/01/09/系统分析师/系统分析师介绍/","text":"  系统分析师是指具有从事计算机应用系统的分析和设计工作能力及业务水平,能指导系统设计师和高级程序员的工作的人员。  在软件开发流程中主要从事需求分析、信息系统项目架构设计(包括概要设计和详细设计)、开发阶段的主要模块的规划、设计和测试,同时也涉及可行性分析的工作。  富有经验的系统分析师往往是优秀技术专家和项目管理者的结合体,他们精通系统论和控制论,擅长将杂乱无章的复杂性问题整理调顺,并将其模块化,从而使项目的实施走向成功。 岗位职责 系统分析师通常在本单位担任独当一面的技术骨干,同时也应当承担起更大的工作责任,充分发挥自己的特长,更好地为单位作出应有的贡献。 系统分析师是计算机行业的高级人才,是一个大型软件项目的核心领导者,他的主要职责是对软件项目进行整体规划、需求分析、设计软件的核心架构、指导和领导项目开发小组进行软件开发和软件实现,并对整个项目进行全面的管理工作。 系统分析师所具备的职业能力和素质主要有:精通计算机行业的前沿理论,精通代表主流开发思想的程序开发语言,精通建设信息系统所要求的各种具体技术,熟悉应用领域的业务,能分析用户的需求和约束条件,写出信息系统需求规格说明书,制定项目开发计划,协调信息系统开发与运行所涉及的各类人员,能指导制定企业的战略数据规划,组织开发信息系统,能评估和选用适宜的开发方法和工具,能按照标准规范写系统分析、设计文档,能对开发过程进行质量控制与进度控制,能具体指导项目开发,具有高级工程师的实际工作能力和业务水平。 系统分析师的基本职责是从事管理信息系统的定制、企业资源管理系统的设计开发及市场评估策划,能独立翻译、阅读国外技术资料,理解商务逻辑和客户需求,有管理信息系统的设计、项目设计能力、开发进度的估计能力、控制力,具有良好的理解力和逻辑分析能力以及表达能力、足够的沟通能力,具备基本文档写作能力。 在日常工作当中,系统分析师通常都是本单位的技术骨干,主要担任项目的主导者和领导者的工作。在政府机关,系统分析师通常负责数字化城市、电子政务、公共政务网等电子政府统一规划的规划与建设工作;在高校、研究所等科研机构,系统分析师通常担任计算机前沿理论的研究、计算机专业、信息化管理专业、电子商务及电子政务等专业的教学、数字化校园的规划与建设、大型集中式教务数据库的建设、教务系统的开发与建设等工作;在非IT企业,系统分析师通常主要负责本企业的电子商务系统的规划与建设、大型信息化系统(如MIS、ERP等)的规划、建设与开发等工作;在IT企业,系统分析师通常担任首席分析师和项目经理的工作,主要负责中大型软件项目的规划、建设、软件架构的整体设计与详细设计、开发模式的设计、项目开发工作的指导和监督、系统的整体测试工作、项目的全面管理及进度管理等。 业务范围根据时下IT企业所涉足开发的领域,其业务范围主要包括:仓储管理系统、报关业务系统、销售统计与管理系统、财务管理系统、物流管理指挥系统、楼宇智能化管理系统以及各种数据查询统计与分析、业务流程控制系统、模拟考试系统、人力资源管理分析系统等等,由此而产生各种信息系统分析师人才品种,即系统分析专家,他们不仅承担着为客户设计开发软件新品的业务指导任务,而且向客户提供二次开发的技术支持和培训顾问服务,既是IT企业中的技术骨干和将才,又是IT企业软件新品市场前景的预测者和参与营销的市场策划者,从这个角度看,又是企业的智囊高参和运筹帷幄的帅才,堪称具有国际视野的高级复合型人才。熟悉应用领域的业务,能分析用户的需求和约束条件,写出信息系统需求规格说明书,制定项目开发计划,协调信息系统开发与运行所涉及的各类人员,能指导制定企业的战略数据规划,组织开发信息系统,能评估和选用适宜的开发方法和工具,能按照标准规范写系统分析、设计文档,能对开发过程进行质量控制与进度控制,能具体指导项目开发,具有高级工程师的实际工作能力和业务水平。 考试说明系统分析师考试是中国计算机行业的金字招牌,是全国计算机职业资格与水平考试(简称国家软考)中级别最高、含金量最高、难度最大的一门考试,处于中国计算机行业认证考试的金字塔顶端。系统分析师考试的主要目的在于为国家培养计算机行业的专家级人才,因此系统分析师的考试一直以难度高、通过率低著称,据官方公开资料,系统分析师考试每年通过的人数极少,平均每年通过的人数不超过1千人(部分年份甚至全国通过人数不到200人/年),通过率长期在5~10%附近徘徊。 考试目标通过本考试的合格人员应熟悉应用领域的业务,能分析用户的需求和约束条件,写出信息系统需求规格说明书,制订项目开发计划,协调信息系统开发与运行所涉及的各类人员;能指导制订企业的战略数据规划、组织开发信息系统;能评估和选用适宜的开发方法和工具;能按照标准规范编写系统分析、设计文档;能对开发过程进行质量控制与进度控制;能具体指导项目开发;具有高级工程师的实际工作能力和业务水平。 考试要求 掌握系统工程的基础知识; 掌握开发信息系统所需的综合技术知识(硬件、软件、网络、数据库等); 熟悉企业或政府信息化建设,并掌握组织信息化战略规划的知识; 熟练掌握信息系统开发过程和方法; 熟悉信息系统开发标准; 掌握信息安全的相关知识与技术; 熟悉信息系统项目管理的知识与方法; 掌握应用数学、经济与管理的相关基础知识,熟悉有关的法律法规; 熟练阅读和正确理解相关领域的英文文献。 科目设置 信息系统综合知识,考试时间为150分钟,笔试,选择题; 系统分析设计案例,考试时间为90分钟,笔试,问答题; 系统分析设计论文,考试时间为120分钟,笔试,论文题。 未来展望近年来,我国政府及工商企业使用电子计算器处理作业快速成长,人才需求愈来愈多,惟因学校培育的科班信息人才供不应求。因而造就其它科系更多的出路,其中尤以商业科系为主。展望台后在经济结构改变.各行业规模愈趋扩大,并须建立企业计算机化管理制度的情势下,对系统分析师的需求更将有增无减,因此.凡受过信息专业教育或训练,具有电子计算器专门知识及企业管理观念的青年,其就业前途是非常乐观的。 以上内容均来自于百度百科,若想深入了解,请百度系统分析师。","updated":"2020-07-07T21:45:28.634Z","tags":[{"name":"系统分析师","slug":"系统分析师","permalink":"http://huishanyi.club/tags/系统分析师/"}]},{"title":"MySQL数据类型选取","date":"2020-01-02T02:54:34.000Z","path":"2020/01/02/数据库/MySQL数据类型选取/","text":"  合适的数据类型,可以让你的数据库纵想丝般顺滑。 三大选取原则 越小越好:数据类型越小,占用磁盘、内存和 CPU 就越少,处理速度就越快; 越简单越好:数据类型越简单,需要的 CPU 周期就越少; 避免 NULL:可为NULL的列使得索引、索引统计和值比较都更复杂,同时也会使用更多的存储空间。 数据类型选取步骤选择数据大类根据数据先分好大类,是数值类型、日期和时间类型 还是 字符串类型。 了解数据特性数据大类 OK 之后,再细分具体数据类型。 长度是否固定; 是否区分符号; 判断取值范围; 数据类型详表数值类型 类型 大小 范围(有符号) 范围(无符号) 用途 TINYINT 1 字节 (-128,127) (0,255) 小整数值 SMALLINT 2 字节 (-32 768,32 767) (0,65 535) 大整数值 MEDIUMINT 3 字节 (-8 388 608,8 388 607) (0,16 777 215) 大整数值 INT或INTEGER 4 字节 (-2 147 483 648,2 147 483 647) (0,4 294 967 295) 大整数值 BIGINT 8 字节 (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) (0,18 446 744 073 709 551 615) 极大整数值 FLOAT 4 字节 (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) 0,(1.175 494 351 E-38,3.402 823 466 E+38) 单精度,浮点数值 DOUBLE 8 字节 (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) 双精度,浮点数值 DECIMAL 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 依赖于M和D的值 依赖于M和D的值 小数值 日期和时间类型 类型 大小(字节) 范围 格式 用途 DATE 3 1000-01-01/9999-12-31 YYYY-MM-DD 日期值 TIME 3 ‘-838:59:59’/‘838:59:59’ HH:MM:SS 时间值或持续时间 YEAR 1 1901/2155 YYYY 年份值 DATETIME 8 1000-01-01 00:00:00/9999-12-31 23:59:59 YYYY-MM-DD HH:MM:SS 混合日期和时间值 TIMESTAMP 4 1970-01-01 00:00:00/2038 结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 YYYYMMDD HHMMSS 混合日期和时间值,时间戳 字符串类型 类型 大小 用途 CHAR 0-255字节 定长字符串 VARCHAR 0-65 535 字节 变长字符串 TINYBLOB 0-255字节 不超过 255 个字符的二进制字符串 TINYTEXT 0-255字节 短文本字符串 BLOB 0-65 535字节 二进制形式的长文本数据 TEXT 0-65 535字节 长文本数据 MEDIUMBLOB 0-16 777 215字节 二进制形式的中等长度文本数据 MEDIUMTEXT 0-16 777 215字节 中等长度文本数据 LONGBLOB 0-4 294 967 295字节 二进制形式的极大文本数据 LONGTEXT 0-4 294 967 295字节 极大文本数据 相似数据类型差异TINYINT(1) 与 TINYINT(2)TINYINT(1) 相当于 BOOLEAN,值为 0 时相当于 false,非 0 时为 true,数值类型像TINYINT(1) 与 TINYINT(2) 括号中的数字并不代表存储长度,只有勾选了ZF时才会有作用,即“zero fill”,长度不够时左边用 0 填充。 INT 与 INTEGER没有差异,如果非得要一个差异的话,那它们名字不一样长··· CHAR 与 VARCHARCHAR 是定长字符串,VARCHAR 是变长字符串,当存储“abcd”时,CHAR(8) 所占长度依然是 8,后面补 4 个空格,而 VARCHAR(8) 所占长度会变成 4,字符串类型括号中的数字表示的是最多显示多少位字符,CHAR 比 VARCHAR 的查询速度快。 VARCHAR 与 TEXTMysql 5.0.3 版本之前,其存储大小为 0~255 字节,5.0.3 版本之后已经和 TEXT 的存储大小相同了,VARCHAR在保存的时候,不进行填充,当值保存和检索时尾部的空格仍保留。TEXT列不能有默认值,存储或检索过程中,不存在大小写转换。可以用 VARCHAR 就用 VARCHAR,不知道长度时用 TEXT,VARCHAR 比 TEXT 的查询速度快。 常见字段对应数据类型及字段标识仅做参考,业务需求不同,所对应的数据类型与字段类型标识须适当修改。 用户信息表用户表常用字段与数据类型对应关系如下: uid:INT类型,最多可支持 4 294 967 295 位用户的 ID。勾选字段标识:PK、NN、UN、AI; 账号:VARCHAR(10)类型,长度不定的字符串,使用变长字符串,用户 ID 最多为 10 位,因此账号 10 位字符就完全够用了。勾选字段标识:NN; 密码:VARCHAR(16)类型,长度不定的字符串,使用变长字符串,最多可存储 16 位字符的密码。勾选字段标识:NN; 邮箱:VARCHAR(128)类型,长度不定的字符串,使用变长字符串,邮箱超过 128 位字符的请绕道走开,为什么不用 π 去做邮箱呢?勾选字段标识:NN; 手机号:CHAR(11)类型,长度固定,使用定长字符串,如果存在区号,再单独建一个表,如果你存在国际用户,VARCHAR(11)也是不错的,国际手机号长度没有 11 位,毕竟中国人口这么多 11 位也就搞定了。还有为什么 BIGINT 容量与速度上最优却不用,防止碰坑(like 索引会失效,你懂得的php手机号变4294967295,手机号格式验证较字符串复杂等其他意外,大家都不喜欢在赶项目的时候查 bug 吧···)。勾选字段标识:NN; 创建时间:TIMESTAMP类型,值以 UTC 格式保存,时区转化 ,存储时对当前的时区进行转换,检索时再转换回当前的时区,值范围不能早于1970或晚于2038,大小是 DATETIME 的一半,如果仅需存取日期、时间、或年份,那么 DATE、TIME 和 YEAR 才是你的菜。勾选字段标识:不用勾,添加默认值为 CURRENT_TIMESTAMP; 权限级别:TINYINT(1)类型,不同级别用不同的 1 位数字去表示,范围0 ~ 255 种权限够吗?勾选字段标识:NN、UN,默认值为 5(0,1,2,3,4,5对应6个级别); 昵称:VARCHAR(16)类型,长度不定的字符串,使用变长字符串,16 位字符的昵称已经够骚了气了···勾选字段标识:NN,用户未填写可默认使用账号字段下的值; 性别:TINYINT(1)类型,不写括号时,默认为TINYINT(4),0 代表女性,1 代表男性,2 代表双性,3 代表未填写(数字用的还是很合理的),勾选字段标识:NN,默认值为 3; 出生日期:DATETIME类型,以’YYYY-MM-DD HH:MM:SS’格式检索和显示DATETIME值,应该没有 999 年出生的或者 10000 年出生的吧,TIMESTAMP 的日期范围不能早与 1970 年,不符合业务需求,毕竟 1970 年前的用户也是有的(不是大佬就是领导···)。勾选字段标识:不用勾; 爱好:VARCHAR(64)类型,长度不定的字符串,使用变长字符串,64 位字符,稍微表达下自己喜欢的就行了。勾选字段标识:不用勾; 个性签名:VARCHAR(256)类型,长度不定的字符串,使用变长字符串,256 位字符就够个性了,比 TEXT 查询速度快。勾选字段标识:不用勾。 SQL 语句参考expension 字段为预留字段。 123456789101112131415ALTER TABLE `sumblog`.`user` ADD COLUMN `uid` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,ADD COLUMN `phone` CHAR(11) NOT NULL AFTER `email`,ADD COLUMN `class` TINYINT(1) UNSIGNED NOT NULL DEFAULT 5 AFTER `create_time`,ADD COLUMN `nickname` VARCHAR(16) NOT NULL AFTER `class`,ADD COLUMN `sex` TINYINT(1) UNSIGNED NOT NULL DEFAULT 3 AFTER `nickname`,ADD COLUMN `birthday` DATETIME NULL AFTER `sex`,ADD COLUMN `expansion1` VARCHAR(45) NULL AFTER `birthday`,ADD COLUMN `expansion2` VARCHAR(45) NULL AFTER `expansion1`,ADD COLUMN `expansion3` VARCHAR(45) NULL AFTER `expansion2`,CHANGE COLUMN `password` `password` VARCHAR(16) NOT NULL AFTER `username`,CHANGE COLUMN `username` `username` VARCHAR(10) NOT NULL ,CHANGE COLUMN `email` `email` VARCHAR(128) NOT NULL ,ADD PRIMARY KEY (`uid`);;","updated":"2020-07-07T21:45:28.633Z","tags":[{"name":"数据库","slug":"数据库","permalink":"http://huishanyi.club/tags/数据库/"},{"name":"MySQL","slug":"MySQL","permalink":"http://huishanyi.club/tags/MySQL/"}]},{"title":"MySQL安装建库建表","date":"2019-12-24T01:32:49.000Z","path":"2019/12/24/数据库/MySQL安装建库建表/","text":"  MySQL 该怎样创建数据库与数据表呢?这篇文章将带你简单的了解如何利用 MySQL Workbench 去建库建表。 MySQL 简介MySQL 是一款开源的关系型数据库管理系统。在 WEB 应用方面,MySQL 是最好的 RDBMS 之一。它将 SQL 语言作为自己的数据库语言,采用双授权政策(社区和商业),其体积小、速度快、成本低,得到了广大开发者的认可。 MySQL Workbench 简介MySQL Workbench 是为 MySQL 设计的 ER/数据库建模工具,是一款可视化的数据库设计软件,它为数据库管理员、程序开发者和系统规划师提供可视化设计、模型建立、以及数据库管理功能。它包含了用于创建复杂的数据建模ER模型,正向和逆向数据库工程,也可以用于执行通常需要花费大量时间和需要的难以变更和管理的文档任务。MySQL工作台可在Windows,Linux和Mac上使用。 MySQL(MySQL Workbench) 安装MySQL Workbench 集成到 MySQL 中,其实就是在安装 MySQL。 下载进入 MySQL Workbench 下载页面进行安装包下载。下载页面如下图: 点击红框标记的“Go to Download Page”按钮,mysql-installer-web-community 为联网安装,mysql-installer-community 为离线安装,为了避免网络所造成的的意外影响,建议下载离线安装包进行安装,安装包为 MySQL 高版本安装包(已集成 MySQL Workbench),如下图: 安装双击下载好的安装包进行安装,弹出步骤窗口,如下图:步骤一:同意 MySQL 协议并点击“Next”进行下一步,不用在意内容,要使用就必须同意(安装过,不会出现此步骤); 步骤二:选择安装类型(Choosing a Setup Type),推荐默认开发版本(Developer Default),如下图: 步骤三:自定义安装路径(Path Conflicts),如下图: 步骤四:检查要求(Check Requirements),如下图: 点击“Excute”执行,可能会弹出一些官方需要配置的软件,乖乖根据他的提示安装下,毕竟 MySQL 可能依赖这些软件,如下图: 步骤五:安装 MySQL 相关功能模块(包括 MySQL Workbench),点击“Excute”执行,如下图: 安装完成后的模块前面会有个对勾,我们点击“Next”即可,如下图: 步骤六:产品配置描述(Product Configuration),看看就好,点击“Next”下一步,如下图: 步骤七:高可用(High Availability),选择第一个,如下图: 步骤八:类型与网络(Type and Networking),首先我们需要选择当前运行设备类型,如下图: 连接设置,默认配置就可以了,我们再勾选下“Show Advanced and Logging Options”提前配置下高级功能 步骤九:身份验证(Authentication Method),8.0 版本以上推荐第一个“Strong Password”,低版本建议升级,如下图: 步骤十:用户密码设置(Accounts and Roles),设置完成点击“Next”下一步,如下图: 步骤十一:Windows 服务(Windows service),MySQL 和 Windows 的那些事,不用操心,小手一抖“Next”下一步,如下图: 步骤十二:日志设置(Logging Options),默认设置就好,直接下一步,这块自行翻译,不会有歧义,如下图: 步骤十三:高级设置(Advanced Options),暂时使用默认配置,后期使用过程中若需使用相关功能,再更改配置,直接下一步,如下图: 步骤十四:应用配置(Apply Configuration),点击“Excute”执行,再点击“Finish”完成应用配置,如下图: 步骤十五:产品配置(Product Configuration),点击“Next”下一步,如下图: 步骤十六:路由配置(MySQL Router Configuration),点击“Finish”完成路由配置,如下图: 步骤十七:连接服务(Connect To Server),输入密码,点击“Check”,会提示连接成功,然后点击“Next”下一步,如下图: 步骤十八:应用配置(Apply Configuration),参考步骤十四 步骤十九:安装完成(Installation Complete),点击“Finish”完成安装。 MySQL Workbench 创建新连接步骤一:双击 MySQL Workbench 图标打开,若桌面未生成图标,在开始菜单搜索“MySQL Workbench”,即可看到该应用,点击进入,如下图: 步骤二:点击下图红框处的加号,创建新的连接: 步骤三:填写“Connection Name”连接名,其他为默认值就好,点击红框“Store in Vault…”,如下图: 步骤四:设置密码,如下图: 步骤五:测试新连接是否可用,如下图: 步骤六:弹出连接成功,点击红框中的两个“OK”就完成创建了,如下图: MySQL Workbench 创建数据库步骤一:点击新连接进入,如下图: 步骤二:创建数据库并完成数据库配置,如下图: 步骤三:生成 SQL 语句,点击“Apply”应用,如下图: 步骤四:执行 SQL 语句,点击“Finish”应用,完成创建数据库,如下图: MySQL Workbench 创建数据表步骤一:右键“Tables”,选择创建一个“user”模板数据表,点击“Apply”应用,如下图: 步骤二:生成 SQL 语句,点击“Apply”应用,如下图: 步骤三:执行 SQL 语句,点击“Finish”应用,完成创建数据库,如下图: 此时就完成了数据库及数据表的创建,如下图: 数据表编辑建议数据表预留 3 个字段,应对之后添加字段难的问题。 数据表配置介绍 Table Name:数据表名; Schema:数据库名; Charset/Collation:字符集/排序规则; Engine:存储引擎; Comments:描述; Column Name:字段名,属性; Datatype:数据类型; PK:primary key,主键约束(唯一标识,不为空,一个表一个主键); NN:not null,非空; UQ:unique,唯一索引(是索引而不是约束,可为空,一个表可以有多个); B:binary,二进制数据(比 text 更大的二进制数据); UN:unsigned,整数(非负数); ZF:zero fill,用0填充剩余位置,例如字段内容是1 int(4), 则内容显示为0001 ; AI:auto increment,自增; G:generated column,生成列; Default/Expression:默认值。 字符集/排序字符集charset,用来组织、控制或表示数据项的字母、数字以及计算机能识别的符号的集合,不同的字符集可能字符占位大小不同,常用utf8(库中仅含 BMP即Basic Multilingual Plane 的字符,如常规文字图片),utf8mb4(库中含有不在 BMP 中的字符,比如emoji 表情)。 排序collation,为字符集中字符之间的比较、排序制定的规则,顺序需要对应utf8_general_ci,utf8mb4_general__ci。 以utf8为例分析_ci、_cs、_bin之间的区别: utf8_general_ci 不区分大小写,这个你在注册用户名和邮箱的时候就要使用; utf8_general_cs 区分大小写,用户名和邮箱用这个就对用户不友好; utf8_bin:字符串每个字符用二进制数据编译存储,区分大小写,而且可以存二进制的内容。 以utf8为例分析含 general 和含 unicode: utf8_general_ci校对速度快,但准确度稍差,页面应用足够用。 utf8_unicode_ci准确度高,但校对速度稍慢。 存储引擎engine,MySQL支持数个存储引擎作为对不同表的类型的处理器(页面应用推荐使用 InnoDB)。MySQL 最常用的存储引擎: MyISAM:默认的MySQL插件式存储引擎,它是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一。注意,通过更改STORAGE_ENGINE配置变量,能够方便地更改MySQL服务器的默认存储引擎。 InnoDB:用于事务处理应用程序,具有众多特性,包括ACID事务支持。 BDB:可替代InnoDB的事务引擎,支持COMMIT、ROLLBACK和其他事务特性。- Memory:将所有数据保存在RAM中,在需要快速查找引用和其他类似数据的环境下,可提供极快的访问。 Merge:允许MySQL DBA或开发人员将一系列等同的MyISAM表以逻辑方式组合在一起,并作为1个对象引用它们。对于诸如数据仓储等VLDB环境十分适合。 Archive:为大量很少引用的历史、归档、或安全审计信息的存储和检索提供了完美的解决方案。 Federated:能够将多个分离的MySQL服务器链接起来,从多个物理服务器创建一个逻辑数据库。十分适合于分布式环境或数据集市环境。 Cluster/NDB:MySQL的簇式数据库引擎,尤其适合于具有高性能查找要求的应用程序,这类查找需求还要求具有最高的正常工作时间和可用性。 Other:其他存储引擎包括CSV(引用由逗号隔开的用作数据库表的文件),Blackhole(用于临时禁止对数据库的应用程序输入),以及Example引擎(可为快速创建定制的插件式存储引擎提供帮助)。 数据类型datatype,根据三大数据选取原则合理选用数据类型可以极大地提高 MySQL 效率,MySQL 支持的数据类型大致分为三类:数值、日期/时间 和 字符串类型。 具体请看:数据类型选取。","updated":"2020-07-07T21:45:28.632Z","tags":[{"name":"数据库","slug":"数据库","permalink":"http://huishanyi.club/tags/数据库/"},{"name":"MySQL","slug":"MySQL","permalink":"http://huishanyi.club/tags/MySQL/"}]},{"title":"数据库简介","date":"2019-12-18T02:14:21.000Z","path":"2019/12/18/数据库/数据库简介/","text":"  数据库(database)是“按照数据结构来组织、存储和管理数据的仓库”。  数据库供应用程序使用,但却彼此独立。数据库最基本的操作便是增、删、改、查。 定义 数据库是存放数据的仓库,仓库中的数据是按照一定的规则进行存放的,否则查询数据时效率很低。数据库既是一个实体(仓库),又是一种方法(高效管理数据)。 数据库管理系统数据库管理系统(Database Management System,简称:DBMS)是为管理数据库而设计的电脑软件系统,一般具有数据库的存储、截取、安全保障、备份等基础功能,并且可对数据库中的数据进行增、删、改、查等操作。顺便提下关系数据库管理系统(Relational Database Management System,简称:RDBMS),就是管理关系数据库的数据库管理系统。 数据库设计步骤 需求分析:了解应用程序将涉及的所有实体及实体所拥有的属性和特点(时效性、核心数据、增长情况等),尽可能避免修改或重构数据结构; 概念结构设计(方法如下):整理需求,得到一个独立于具体 DBMS 的概念模型(用 E-R 图表示); 自上而下:定义全局概念结构框架,然后不断细化; 自下而上:定义局部概念结构,然后将局部概念结构连起来,得到全局概念结构; 逐步扩张:定义核心概念结构,然后往外扩张,得到全局概念结构; 混合策略:结合自上而下与自下而上得到全局概念结构。 逻辑结构设计:选择适用于应用程序的 DBMS,并将概念结构转换为该 DBMS 所支持的数据模型(例如:关系模型); 物理结构设计:利用已选用 DBMS 所提供的技术和方法,对逻辑结构进行分析,得到一个高效、可实现的物理数据结构(存储结构、存取路径、存储位置及存储分配俱佳); 数据库验证:将数据入库,并运行一些典型的应用任务来验证数据库设计的正确性和合理性; 数据库运行与维护:验证通过后,即可投入正式运行,并在数据库运行过程中不断调整、修改与完善。 数据库设计原则 一对一原则:遵循一对一关系设计原则,避免出现干扰数据影响设计; 独特命名原则:减少数据库设计过程中重复命名和规范命名现象出现而导致的命名冲突,利于规范化后台代码工作的开展; 双向使用原则:包括事务使用原则和索引功能原则,保证数据可以及时更新及灵活排序。 数据库类型数据库分为两大类:关系型数据库 与 非关系型数据库。 关系型数据库以关系模型(二维表的形式表示实体和实体间联系的数据模型)来组织数据的数据库。 常见的关系型数据库Oracle、DB2、MySQL、Microsoft SQL Server、Microsoft Access 优点 成熟:专业的团队负责,且有维护工具和充足的资料; 标准化:基本都遵循 SQL(结构化查询语言,Structured Query Language),统一的标准,学一个库,会多个库; 安全:存储在磁盘中,不会因断电等原因导致数据丢失; 稳定:先定义数据结构,再根据结构进行数据存储,且遵循 ACDI(原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability))规则; 缺点 付费:有专业的团队负责,隶属于公司,那么想使用或全功能使用,就避免不了付费(不是有破解版吗?你猜库会出问题吗·^_^·?); 响应较慢:数据存储在磁盘中,非关系型数据库存储在内存中,所以响应速度较非关系型数据库会慢; 拓展性差:拓展往往会带来新的表,而关系型数据库的瓶颈就是多表操作; 浪费空间:由于以二维表进行存储,则存在空字段时也须分配空间。 非关系型数据库NoSQL(Not Only SQL),不是关系型数据库的数据库。 常见非关系型数据库 分类 代表库 应用场景 数据模型 优点 缺点 键值(key-value) Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 Key 指向 Value 的键值对,通常用 hash table来实现 查找速度快 数据无结构化,通常只被当作字符串或者二进制数据 列存储数据库 Cassandra, HBase, Riak 分布式的文件系统 以列簇式存储,将同一列数据存在一起 查找速度快,可扩展性强,更容易进行分布式扩展 功能相对局限 文档型数据库 CouchDB, MongoDb Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) Key-Value对应的键值对,Value为结构化数据 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 查询性能不高,而且缺乏统一的查询语法。 图形(Graph)数据库 Neo4J, InfoGrid, Infinite Graph 社交网络,推荐系统等。专注于构建关系图谱 图结构 利用图结构相关算法。比如最短路径寻址,N度关系查找等 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群方案。 优点 免费:通常采用开源的方式,比如 HBase,MongoDb等; 响应快:数据存储在内存中; 拓展性好:采用分布式存储; 缺点 不成熟:开源产品,维护的工具和资料有限; 无统一标准:不提供 SQL 语句支持; 不安全: 数据存储在内存中,断电就很尴尬啊;","updated":"2020-07-07T21:45:28.633Z","tags":[{"name":"数据库","slug":"数据库","permalink":"http://huishanyi.club/tags/数据库/"}]},{"title":"启动Node项目报错","date":"2019-12-09T02:35:26.000Z","path":"2019/12/09/NodeJS/启动Node项目报错/","text":"  本章节主要提供以 npm start 启动时出现报错的相关解决方案。 报错 Exit status 1报错提示如下,可是发现自己并没有主动使用require(),也许是电脑使用过程中误操作导致依赖包出现了问题,也有可能最近写的代码存在冲突。解决方案:可以尝试将 node_modules 删除,重新执行 npm install,再启动下项目,若还没有好,可以回退代码版本,具体定位问题由哪部分代码导致。 12345678910111213141516E:\\git\\sumblog_manager>npm start> sumblog_manager@0.1.0 start E:\\git\\sumblog_manager> react-app-rewired startrequire(...) is not a functionnpm ERR! code ELIFECYCLEnpm ERR! errno 1npm ERR! sumblog_manager@0.1.0 start: `react-app-rewired start`npm ERR! Exit status 1npm ERR!npm ERR! Failed at the sumblog_manager@0.1.0 start script.npm ERR! This is probably not a problem with npm. There is likely additional logging output above.npm ERR! A complete log of this run can be found in:npm ERR! C:\\Users\\Administrator\\AppData\\Roaming\\npm-cache\\_logs\\xxxx-xx-xxT02_27_47_249Z-debug.log","updated":"2020-07-07T21:45:28.624Z","tags":[{"name":"NodeJS","slug":"NodeJS","permalink":"http://huishanyi.club/tags/NodeJS/"}]},{"title":"React生命周期","date":"2019-11-18T05:34:51.000Z","path":"2019/11/18/React/React生命周期/","text":"  React 组件是有生命的,而它生命不同阶段的集合就叫做生命周期。在其生命的不同阶段进行精确控制,可提高 React 项目的性能。  React 生命周期函数属于钩子函数的一种(钩子函数:即系统在运行到某一阶段时自动触发的函数)。  本篇文章将以 ES6 的角度对 React15 版本 与 React16 版本的生命周期进行讨论。 React15React15 版本生命周期分为四个阶段:初始化、挂载、更新及卸载。以下进行详细说明(推荐使用 React16)。 生命周期的四个阶段初始化在 constructor 函数中初始化 props 与 state,分别通过 super(props)来获取父组件的 props 和 this.state 来设置默认数据 进行初始化。 挂载挂载过程将触发三个钩子函数: componentWillMount(); render(); componentDidMount()。 更新更新前提为 (props change || states change) 结果为真。props 改变触发的钩子函数有: componentWillReceiveProps(); shouldComponentUpdate(); componentWillUpdate(); render(); componentDidUpdate()。 states 改变触发的钩子函数有: shouldComponentUpdate(); componentWillUpdate(); render(); componentDidUpdate()。 卸载卸载过程仅有一个钩子函数被触发: componentWillUnmount()。 生命周期函数组件由“生”到“死”所经历的一些“事”。 componentWillMount() 字面意义组件将被挂载,即在组件挂载到 DOM 前自动触发并且只触发一次。 触发时间:组件挂载阶段第一个触发的生命周期函数。 说明:这里修改 state 不会使组件重新渲染(因为此时组件还未被渲染,官网推荐使用 constructor() 进行 state 初始化),项目中基本用不到。 注意:React16 中提示为 UNSAFE_componentWillMount(),React17 中官宣将被淘汰,建议在开发过程中不要使用。 render() 字面意义渲染,即组件渲染时自动触发,是纯函数(传入相同的 props 与 state,返回的结果一定相同)。 触发时间: 组件挂载阶段: componentWillMount() 之后自动触发; 组件更新阶段: componentWillUpdate() 之后自动触发。 说明:React 组件中必须存在的函数,并且必须 return 一个值。创建虚拟dom,进行diff算法,更新dom树都在此进行。当其被调用时,它会检查 this.props 与 this.state 并返回一个值,这个值可以是 React 元素、数组、fragments、Portals、字符串、数值、布尔值或者 null。根据返回值渲染 DOM 结构。返回值与渲染结果映射情况如下: React 元素(虚拟 DOM) —————— 自定义组件 数组或 fragments —————— 返回多个元素 Portals —————— 根据 ReactDOM.createPortal(child, domNode),将返回值 child 渲染到 domNode(父组件以外的 DOM 节点) 中 字符串或数值 —————— 文本节点 布尔值或 null —————— 不渲染(主要用于判断是否渲染,如: test && ) componentDidMount() 字面意义组件完成挂载,即组件挂载到 DOM 之后自动触发并且只触发一次。 触发时间:组件挂载阶段最后一个函数。 说明:依赖于 DOM 节点的初始化应该放在这里,如获取数据时在此处实例化请求。 componentWillReceiveProps(nextProps) 字面意义组件将接受属性,即 props 改变所自动触发的第一个函数。 触发时间:因 props 改变导致的组件更新阶段的第一个函数。 说明:由于 props 的改变组件将会发生更新,此时进行 state 的更新不会触发二次渲染(如:tab 切换,内容改变),同时可以在更新组件时比较 this.props 与 nextProps 来进行性能优化。 注意:React16 中提示为 UNSAFE_componentWillReceiveProps(),React17 中官宣将被淘汰,建议在开发过程中不要使用。 shouldComponentUpdate(nextProps, nextState) 字面意义组件应该更新吗,即在组件更新前 判断组件是否更新时自动触发的函数。 触发时间: props 改变导致的更新阶段:componentWillReceiveProps() 之后自动触发; state 改变导致的更新阶段:第一个自动触发的函数。 说明:此方法作为性能优化的方式而存在,通过比较当前属性、状态值与下一属性、状态值 控制组件是否更新(返回 true 继续更新流程,返回 false 停止更新流程) 来减少渲染次数 进而达到性能优化,不过尽量不要在该方法中进行深层比较或使用 JSON.stringify(),这样非常影响效率,且会损害性能。推荐使用 PureComponent 来替代 shouldComponentUpdate() ,不仅实现了 props 与 state 的浅比较,还减少了跳过必要更新的可能性。 componentWillUpdate(nextProps, nextState) 字面意义组件将更新,即组件开始更新时自动触发。 触发时间:组件更新阶段 shouldComponentUpdate() 返回 true 之后。 说明:在此方法中可以读取 DOM 信息(如:保存滚动位置),不能在此方法中直接(setState)或间接(dispatch)操作 state。 注意:React16 中提示为 UNSAFE_componentWillUpdate(),React17 中官宣将被淘汰,建议在开发过程中不要使用。 componentDidUpdate(prevProps, prevState, snapshot) 字面意义组件更新完成,即组件更新完成自动触发。 触发时间:组件更新阶段更新完成时自动触发。 说明:可以在此处进行网络请求(如:当属性、状态值发生变化后开始请求);也可以在此处中直接调用 setState(),但请注意它必须被包裹在一个条件语句里,否则会产生死循环,或者产生额外的渲染。 componentWillUNmount() 字面意义组件将被卸载,即组件将要被卸载及销毁时自动触发。 触发时间:组件卸载阶段。 说明:此处为执行必要的清理操作,例如,清除定时器,取消网络请求或清除在 componentDidMount() 中创建的订阅等。 React16更新中……","updated":"2020-07-07T21:45:28.625Z","tags":[{"name":"React","slug":"React","permalink":"http://huishanyi.club/tags/React/"}]},{"title":"Redux与react-redux","date":"2019-11-04T02:18:03.000Z","path":"2019/11/04/React/Redux与react-redux/","text":"  Redux 是 JavaScript 应用程序的可预测状态容器。其他背景、原理、接口我就不多说了,因为看官方文档就够了,至于英文不太 OK 的“爱国人士”可以看汉化文档。想要充分的使用一定要详读文档,理解它才可让它更高效(即优化)。  虽然总是见到 Redux 和 React 一起使用,并且都是字母 r 开头的单词,但是它们两个其实没有半毛钱的关系,真正与 React 有关的叫 react-redux (Redux官方文档与汉化文档也都有提到)。 Redux 默认你对 Redux 有些了解了,本篇文章只会告诉你它是如何集中管理项目中的 state,项目中该如何使用(React 项目中使用 react-redux 更方便)。 安装运行命令npm install --save redux。 创建状态管理文件目录结构如下(默认是以 create-react-app 创建的项目): 1234567项目根目录└── src └── store(状态管理目录) ├── index.js (创建 store,Redux 应用只有一个单一的 store, 需要拆分数据处理逻辑时,使用 reducer 组合) ├── actionTypes.js(定义 action 类型) ├── actionCreators.js(创建 action 对象,包括必须要的 action 类型与非必须的数据) └── reducer.js(state 处理库,纯函数,接收旧的 state 和 action,返回新的 state) js 文件源码及说明(toDoList 为例)123456789/****** index.js ******/import {createStore} from 'redux' import reducer from './reducer'// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 是配置调试工具 Redux DevTools,可在浏览器开发工具 Redux 专栏进行应用调试const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) // 创建 storeexport default store 12345678910111213141516171819202122232425262728293031323334353637/****** reducer.js ******/// 引入常量,避免项目过大,“手抽筋”敲错导致功能异常且浏览器不报错(字符串敲错浏览器不报错,常量敲错浏览器会报错)import {CHANGE_INPUT_VALUE, ADD_LIST_ITEM, DELETE_LIST_ITEM, INIT_STATE} from './actionTypes'const defaultState = { // state 初始化 inputValue: '', list: []}export default (state=defaultState, action) => { // 禁止修改 state,所以这里做一次深拷贝(喜欢新语法也可以试试 ES7 的对象展开运算符) const newState = JSON.parse(JSON.stringify(state)) // 匹配值为具体数值时请用 switch case 语句(发现很多人对 if else 爱的不能自拔啊) switch(action.type) { case CHANGE_INPUT_VALUE: // 根据不同事件类型,更新 state 中指定数据 newState.inputValue = action.value return newState case ADD_LIST_ITEM: newState.list = [...newState.list, state.inputValue] newState.inputValue = '' return newState case DELETE_LIST_ITEM: newState.list.splice(action.index, 1) return newState case INIT_STATE: newState.list = action.list return newState default: return newState // 此处 newState 即 state,为统一写为 newState }} 1234567/****** actionTypes.js(也可以叫 constants.js) ******/// 声明并导出常量export const CHANGE_INPUT_VALUE = 'change_input_value'export const ADD_LIST_ITEM = 'add_list_item'export const DELETE_LIST_ITEM = 'delete_list_item'export const INIT_STATE = 'init_state' 123456789101112131415161718192021222324/****** actionCreators.js ******/// 引入常量,避免项目过大,“手抽筋”敲错导致功能异常且浏览器不报错(字符串敲错浏览器不报错,常量敲错浏览器会报错)import {CHANGE_INPUT_VALUE, ADD_LIST_ITEM, DELETE_LIST_ITEM, INIT_STATE} from './actionTypes'// 导出可供组件使用的方法(组件中调用此方法创建 action 对象)export const getInputChangeAction = (value) => ({ type: CHANGE_INPUT_VALUE, value // value: value,键名与键值相同时可简写,建议使用相同的键名与键值来精简代码})export const getAddListItemAction = () => ({ type: ADD_LIST_ITEM})export const getDeleteListItemAction = (index) => ({ type: DELETE_LIST_ITEM, index})export const initState = (list) => ({ type: INIT_STATE, list}) 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263/****** 逻辑组件 ******/import React, { Component } from 'react'import axios from 'axios'import TodoUI from './TodoUi'// 引入状态管理模块import store from './store'import {getInputChangeAction, getAddListItemAction, getDeleteListItemAction, initState} from './store/actionCreators'class Todo extends Component { constructor(props) { super(props) this.state = store.getState() store.subscribe(this.storeChange) } render() { return ( <TodoUI inputValue = {this.state.inputValue} list = {this.state.list} inputChange = {this.inputChange} addListItem = {this.addListItem} deleteListItem = {this.deleteListItem} /> ) } componentDidMount() { axios.get('/list.json') // 可在项目根目录下的 public 文件中创建 list.json 进行数据模拟 .then((res) => { console.log(res) const action = initState(res.data) store.dispatch(action) }) .catch((err) => { console.log(err) }) } inputChange = (e) => { const action = getInputChangeAction(e.target.value) // 声明 action ,调用 actionCreators.js 中的方法创建 action 对象 store.dispatch(action) } addListItem = () => { const action = getAddListItemAction() store.dispatch(action) } deleteListItem = (e) => { const action = getDeleteListItemAction(e.target.getAttribute('index')) store.dispatch(action) } storeChange = () => { this.setState(store.getState) }}export default Todo toDoList 实例下载todoList Redux实例下载 react-redux react-redux 是 Redux 的官方 React 绑定库,在 React 项目中若使用 Redux,建议配合 react-redux,它可以使你的 React 组件与 Redux store数据传递更方便快捷。 安装运行命令npm install --save react-redux(默认你已安装完成 redux)。 创建状态管理文件部分目录结构如下(默认是以 create-react-app 创建的项目): 12345678910111213141516项目根目录└── src ├── index.js (create-react-app 生成的 index.js) ├── App.js (项目所有组件的插槽) ├── store (状态管理目录) │ ├── index.js (创建 store,Redux 应用只有一个单一的 store, 需要拆分数据处理逻辑时,使用 reducer 组合) │ └── reducer.js (汇总所有子组件的 reducer,state 区块化) └── common (公共组件目录) └── header (公共头部组件) ├── index.js (header UI 组件) ├── style.js (创建带有样式的组件,须配合 styled-components 包使用) └── store ├── index.js (仅做 header 组件的 reducer 导出) ├── constants.js (定义 action 类型) ├── actionCreators.js (创建 action 对象,包括必须要的 action 类型与非必须的数据) └── reducer.js(state 处理库,纯函数,接收旧的 state 和 action,返回新的 state) js 文件示例及说明src/index.js12345678/****** src/index.js ******/// 页面所有可见内容来自于 App 组件import React from 'react';import ReactDOM from 'react-dom';import App from './App';ReactDOM.render(<App />, document.getElementById('root')); // 挂载到页面 src/App.js1234567891011121314151617181920212223242526272829303132/****** src/App.js ******/// 页面所有可见内容的插槽(页面最大的容器组件)import React from 'react';// 被 <Provider> 包裹的所有组件都可以访问 storeimport { Provider } from 'react-redux' // React 应用路由,相关博文以阐述import { BrowserRouter, Switch, Route } from 'react-router-dom' import store from './store' // 引入状态管理库import Header from './common/header' // 引入 header 组件import Home from './pages/home' // 引入 Home 页面import Detail from './pages/detail' // 引入 Detail 页面function App() { return ( <div className=\"App\"> <Provider store={store}> <Header /> <BrowserRouter> <Switch> <Route exact path='/' component={Home}></Route> <Route path='/detail' component={Detail}></Route> </Switch> </BrowserRouter> </Provider> </div> );}export default App; // 导出供 src/index.js 使用 src/store/index123456789101112131415/****** src/store/index ******/// 创建状态管理库import { createStore, compose, applyMiddleware } from 'redux'import thunk from 'redux-thunk' // 引入 redux-thunk 中间件进行异步 action,提高性能import reducer from './reducer'// 使用 composeEnhancers 进行对 Redux 的中间件拓展const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // 创建 store,使用中间件增强 Reduxconst store = createStore(reducer, composeEnhancers( applyMiddleware(thunk)))export default store src/store/reducer.js123456789101112/****** src/store/reducer.js ******/// 汇总所有 Reducer,营造良好的 Redux 结构,方便数据管理// 使用 redux-immutable 对 Redux 项目 reducer 进行分类汇总管理import { combineReducers } from 'redux-immutable' import { reducer as headerReducer } from '../common/header/store'const reducer = combineReducers({ header: headerReducer})export default reducer header/index.js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157/****** src/common/header/index.js ******/// header UI组件import React from 'react'// 使组件与 store 建立连接import { connect } from 'react-redux' import { CSSTransition } from 'react-transition-group' // CSS 动画库import { actionCreators } from './store' // 引入创建 action 对象方法库import { HeaderWrapper, Container, Logo, Nav, NavItem, SearchWrapper, SearchInfo, SearchInfoTitle, SearchInfoSwitch, SearchInfoList, SearchInfoListItem, NavSearch, Addition, Button} from './style' // 样式组件库中引入 UI 组件const Header = (props) => { const ref = React.createRef(); const { focused, list, page, totalPage, mouseIn, searchFocus, searchBlur, enterSearchInfo, leaveSearchInfo, changeSearchInfoPage } = props; const jsList = list.toJS(); const pageList = []; if(jsList.length){ for (let i = (page-1)*10; i < page*10; i++) { pageList.push( <SearchInfoListItem key={jsList[i]}>{jsList[i]}</SearchInfoListItem> ) } } return ( <HeaderWrapper> <Container> <Logo href='/'/> <Nav> <NavItem className='left active'>首页</NavItem> <NavItem className='left'>下载</NavItem> <NavItem className='right'>登录</NavItem> <NavItem className='right'> <span className=\"iconfont\">&#xe636;</span> </NavItem> <SearchWrapper> <CSSTransition timeout={200} in={focused} classNames='slide' > <NavSearch className={focused ? 'focused' : ''} onFocus={()=>{searchFocus(list)}} onBlur={searchBlur} ></NavSearch> </CSSTransition> <span className={focused ? 'iconfont focused zoom' : 'iconfont zoom'}>&#xe614;</span> <SearchInfo className={focused||mouseIn ? '' : 'noShow'} onMouseEnter={enterSearchInfo} onMouseLeave={leaveSearchInfo} > <SearchInfoTitle> 热门搜索 <SearchInfoSwitch onClick={()=>{changeSearchInfoPage(page, totalPage, ref.current)}} > <span ref = {ref} className=\"iconfont spin\">&#xe6ac;</span> 换一批 </SearchInfoSwitch> </SearchInfoTitle> <SearchInfoList> {pageList} </SearchInfoList> </SearchInfo> </SearchWrapper> </Nav> <Addition> <Button className='writting'> <span className=\"iconfont\">&#xe60e;</span> 写文章 </Button> <Button className='reg'>注册</Button> </Addition> </Container> </HeaderWrapper> )}// 使用 connect() 前,需要先定义 mapStateToProps,该方法将当前 store 中的 state 映射到展示组件的 props 中const mapStateToProps = (state) => { return { /* 获取 header 中的 reducer 中的 focused 值(src/common/header/index.js => src/store/reducer => src/common/header/store/reducer.js, src/common/header/store/reducer.js 中将 state 变为 immutable 对象,所以用其语法 getIn 获取数据) */ focused: state.getIn(['header', 'focused']), mouseIn: state.getIn(['header', 'mouseIn']), list: state.getIn(['header', 'list']), page: state.getIn(['header', 'page']), totalPage: state.getIn(['header', 'totalPage']) }}/* mapDispatchToProps 接收 dispatch() 方法并返回期望注入到展示组件 props 中的回调方法中,引入 actionCreators 中已创建好的 action 对象进行派发(派发至 src/common/header/store/reducer.js 中,根据 type 类型进行 state 操作) */const mapDispatchToProps = (dispatch) => { return { searchFocus(list) { list.size===0 && dispatch(actionCreators.getList()) dispatch(actionCreators.searchFocuse()) }, searchBlur() { dispatch(actionCreators.searchBlur()) }, enterSearchInfo() { dispatch(actionCreators.enterSearchInfo()) }, leaveSearchInfo() { dispatch(actionCreators.leaveSearchInfo()) }, changeSearchInfoPage(page, totalPage, spin) { let originAngle = spin.style.transform.replace(/\\D/g, ''); if(originAngle||originAngle===0){ originAngle = parseInt(originAngle) }else{ originAngle = 0; } spin.style.transform = `rotate(${originAngle+360}deg)` if(page < totalPage){ dispatch(actionCreators.changeSearchInfoPage(page+1)) }else{ dispatch(actionCreators.changeSearchInfoPage(1)) } } }}export default connect(mapStateToProps, mapDispatchToProps)(Header) header/style.js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180/****** src/common/header/style.js ******/// 带有样式的组件,重点是对 styled-components 中的用法做简单了解import styled from 'styled-components'import logoPic from '../../statics/images/logo.png'export const HeaderWrapper = styled.div` border: 1px solid #f0f0f0;`export const Container = styled.div` position: relative; width: 1440px; height: 56px; margin: 0 auto;`export const Logo = styled.a` position: absolute; top: 0; left: 0; display: block; width: 100px; height: 56px; background: url(${logoPic}) no-repeat center/contain;`export const Nav = styled.div` width: 960px; height: 100%; padding-right: 70px; box-sizing: border-box; margin: 0 auto;`export const NavItem = styled.div` line-height: 56px; padding: 0 15px; font-size:17px; &.left { float: left; color: #333; } &.right { float: right; color: #969696; } &.active { color: #ea6f5a; }`export const SearchWrapper = styled.div` position: relative; float: left; .zoom { position: absolute; right: 5px; bottom: 4px; display: block; width: 30px; line-height: 30px; border-radius: 20px; text-align: center; &.focused { background: #969696; color: #fefefe; } }`export const NavSearch = styled.input.attrs({ placeholder: '搜索'})` width: 160px; height: 38px; padding: 0 30px 0 20px; border: none; border-radius: 19px; margin-top: 9px; margin-left: 20px; box-sizing: border-box; outline: none; background: #eee; font-size: 15px; color: #666; &::placeholder { color: #999; } &.focused { width: 240px; } &.slide-enter { transition: all .2s ease-out; } &.slider-enter-active { width: 240px; } &.slide-exit { transition: all .2s ease-out } &.slide-exit-active { width: 160px; }`export const SearchInfo = styled.div` position: absolute; left: 0; top: 56px; width: 240px; padding: 0 20px; box-shadow: 0 0 8px rgba(0, 0, 0, .2); &.noShow{ display: none; }`export const SearchInfoTitle = styled.div` margin-top: 20px; margin-bottom: 15px; line-height: 20px; color: #969696;`export const SearchInfoSwitch = styled.div` float: right; font-size: 14px; cursor: pointer; user-select:none; .spin{ display: block; float: left; margin-right: 2px; font-size: 12px; transition: all .2s ease-in; transform-origin: center; }`export const SearchInfoList = styled.div` overflow: hidden;`export const SearchInfoListItem = styled.a` display: block; float: left; line-height: 20px; padding: 0 5px; border: 1px solid #ddd; border-radius: 3px; margin-right: 10px; margin-bottom: 15px; font-size: 12px; color: #787878;`export const Addition = styled.div` position: absolute; top: 0; right: 0; height: 56px;`export const Button = styled.div` float: right; padding: 0 20px; margin-top: 9px; margin-right: 20px; line-height: 38px; border-radius: 19px; border: 1px solid #ec6149; font-size: 14px; &.reg { color: #ec6149; } &.writting { color: #fff; background: #ec6149; }` header/store/index.js12345678/****** src/common/header/store/index.js ******/// 对 header/store 中的文件做统一导出,方便其他文件引用import reducer from './reducer'import * as actionCreators from './actionCreators'import * as constants from './constants'export { reducer, actionCreators, constants } header/store/constants.js123456789/****** src/common/header/store/constants.js ******/// 也可以叫做 actionTypes.js,仅是将字符串变为常量,大型项目防止名称敲错但浏览器不报错,不能及时定位问题export const SEARCH_FOCUSE = 'header/SEARCH_FOCUSE'export const SEARCH_BLUR = 'header/SEARCH_BLUR'export const CHANGE_LIST = 'header/CHANGE_LIST'export const ENTER_SEARCH_INFO = 'header/ENTER_SEARCH_INFO'export const LEAVE_SEARCH_INFO = 'header/LEAVE_SEARCH_INFO'export const CHANGE_SEARCH_INFO_PAGE = 'header/CHANGE_SEARCH_INFO_PAGE' header/store/actionCreators.js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354/****** src/common/header/store/actionCreators.js ******/// 统一创建 action 对象import { SEARCH_FOCUSE, SEARCH_BLUR, CHANGE_LIST, ENTER_SEARCH_INFO, LEAVE_SEARCH_INFO, CHANGE_SEARCH_INFO_PAGE} from './constants'// 此处推荐做 axios,当然你得在 src/store/index.js 中做中间件设置,这样才可以将 action 对象变为函数导出import axios from 'axios' // 将纯 JS 对象和数组深层转换为不可变映射和列表(immutable 类型数据),immutable 类型数据仅能通过同类型数据进行更改import { fromJS } from 'immutable' // *************************************const changeList = (data) => ({ type: CHANGE_LIST, data: fromJS(data), // 将替换 src/common/header/store/reducer.js 中 state(immutable 类型数据)中的 list 值 totalPage: Math.ceil(data.length / 10)})// **************************************export const searchFocuse = () => ({ type: SEARCH_FOCUSE})export const searchBlur = () => ({ type: SEARCH_BLUR})export const enterSearchInfo = () => ({ type: ENTER_SEARCH_INFO})export const leaveSearchInfo = () => ({ type: LEAVE_SEARCH_INFO})export const changeSearchInfoPage = (page) => ({ type: CHANGE_SEARCH_INFO_PAGE, page})export const getList = () => { return (dispatch) => { axios.get('/api/headerList.json') .then((res) => { dispatch(changeList(res.data.data)) }) .catch((err) => { console.log(err) }) }} header/store/reducer.js1234567891011121314151617181920212223242526272829303132333435363738394041424344454647/****** src/common/header/store/reducer.js ******/// 根据 src/common/header/store/actionCreators.js 中派发过来的 type 类型对 state 进行操作,并返回新的 state 供其它组件使用import { SEARCH_FOCUSE, SEARCH_BLUR, CHANGE_LIST, ENTER_SEARCH_INFO, LEAVE_SEARCH_INFO, CHANGE_SEARCH_INFO_PAGE} from './constants'// 将纯 JS 对象和数组深层转换为不可变映射和列表(immutable 类型数据),这样 state 将不会被改变import { fromJS } from 'immutable' // immutable 对象只能用 immutable 语法进行 state 中的属性值获取(getIn)和更改(set 和 merge 方法)const defaultState = fromJS({ focused: false, mouseIn: false, list: [], page: 1, totalPage: 1})export default (state=defaultState, action) => { switch(action.type) { case SEARCH_FOCUSE: // 对 state 的单个属性进行更改(set 为 immutable 的语法) return state.set('focused', true) case SEARCH_BLUR: return state.set('focused', false) case CHANGE_LIST: // 对 state 的多个属性进行更改(merge 为 immutable 的语法) return state.merge({ list: action.data, totalPage: action.totalPage }) case ENTER_SEARCH_INFO: return state.set('mouseIn', true) case LEAVE_SEARCH_INFO: return state.set('mouseIn', false) case CHANGE_SEARCH_INFO_PAGE: return state.set('page', action.page) default: break } return state}","updated":"2020-07-07T21:45:28.627Z","tags":[{"name":"React","slug":"React","permalink":"http://huishanyi.club/tags/React/"}]},{"title":"React虚拟DOM","date":"2019-09-23T03:22:40.000Z","path":"2019/09/23/React/React虚拟DOM/","text":"  React 为什么要做一个虚拟 DOM 呢?一个框架的好坏取决于其功能与性能,而这个虚拟 DOM 就是为了提升 React 性能。  虚拟 DOM 其实就是一个 JS 对象,然后通过 render 方法将其渲染成真实的 DOM。  了解虚拟 DOM 有利于在项目开发过程中持续输出优质代码。 虚拟 DOM 是怎样炼成的  由一下三个阶段可以分析出虚拟 DOM 极大的提升了 React 性能。 无虚拟 DOM state(数据) JSX(模板) state + JSX 通过 JS 生成真实的 DOM state 改变 state + JSX 通过 JS 生成真实的 DOM 新生成的 DOM 节点替换旧 DOM 节点 性能分析:JS 操作 DOM 非常耗性能,而3、5、6都进行了 DOM 的操作。 无虚拟 DOM 优化 state(数据) JSX(模板) state + JSX 通过 JS 生成真实的 DOM state 改变 state + JSX 通过 JS 生成真实的 DOM 新生成的 DOM 与旧 DOM 做比较 对有差异的 DOM 节点进行替换 性能分析:将之前的6分为现在的6、7,流程上多了一步,但在项目中有大量的 DOM 节点,而只有少部分节点存在使用 state 的情况,差异替换减少了操作 DOM 节点的数量,有一定程度的性能提高。 有虚拟 DOM state(数据) JSX(模板) state + JSX 通过 react 内部机制生成虚拟 DOM(虚拟 DOM 就是一个 JS 对象) state + JSX 通过 JS 生成真实的 DOM state 改变 state + JSX 通过 react 内部机制生成虚拟 DOM 新生成的虚拟 DOM 与旧虚拟 DOM 做比较(JS 对象之间的比较) 对有差异的 DOM 节点进行操作 性能分析:虚拟 DOM 并不是 DOM,生成虚拟 DOM 所消耗的性能远小于生成真实 DOM,虚拟 DOM 的比较远小于真实 DOM 的比较,对指定 DOM 的内容进行替换做消耗的行能小于对 DOM 进行替换。 12345678910111213// React 组件class Test extends Component { state = { data: 1 } render() { return ( <div>{this.state.data}</div> ); }} export default Test; React Diff 算法(差异算法)  虚拟 DOM 比较时,使用了 Diff 算法,为什么要用 Diff 算法?它做了些什么?  在开发过程中我们必须要清楚 Diff 算法是如何做优化的,通过它的优化方式写出适合其快速执行的代码,就像在做 SEO 时,我们的代码要贴合搜索引擎的爬取习惯。 同层比较  在两个树结构进行比较时,Diff 会从树的根部开始比较,且只进行同层比较,若某层元素类型不同,将不进行之后层级的比较,若元素类型相同,属性不同,则只进行元素属性的增删。 123456789101112131415161718192021222324252627282930313233<!-- DOM树 --><div> <ul> <li style=\"color: red, background: blue\">List1</li> <li class=\"listItem\">List2</li> </ul> <p> <a href=\"###\">ALink</a> <h1> <span>Span1</span> <span>Span2</span> </h1> </p></div><!-- **************************************************** --><!-- 改变后的DOM树 --><div> <ul> <li style=\"color: red, background: green\">List1</li> <li class=\"listItem\" title=\"second\">List2</li> </ul> <p> <a href=\"###\">ALink</a> <h2> <span>Span1</span> <span>Span2</span> </h2> </p></div> DOM 树变化过程 比较根节点(div 标签),无变化,继续下一层比较; 比较第二层(ul、p 标签),无变化,继续下一层比较; 比较第三层(ul 标签的子标签),第一个 li 标签将 background 属性进行更改,第二个 li 标签添加 title 属性,继续下一层比较; 比较第四层(p 标签的子标签),a 标签无变化,h1 标签发生改变,直接以新 DOM 中与 h1 相对应的元素及其子元素进行替换(h1 标签的子标签不再进行对比)。 遍历元素通过 key 值调整位置  为了解决遍历得到的同层元素 位置发生改变 或 出现新元素 所导致元素删除与重建而造成的性能消耗,对每个遍历的元素赋予一个唯一属性值 key,当位置发生变化,检测到新的结构树与旧的结构树同层有相同的 key 值时,不进行删除与重建过程,仅进行位置调整。 1234567891011121314151617<!-- DOM 树 --><ul> <li key=\"a\">a</li> <li key=\"b\">b</li> <li key=\"c\">c</li> <li key=\"d\">d</li></ul><!-- 改变后的 DOM 树 --><ul> <li key=\"b\">b</li> <li key=\"a\">a</li> <li key=\"d\">d</li> <li key=\"e\">e</li> <li key=\"c\">c</li></ul> DOM 树的变化过程 对比新旧 DOM 中的元素,通过 key 值判断是否存在相同元素,存在则进行位置调整,不存在则创建元素; 新 DOM 中第一个 li 标签 key=”b”,将旧 DOM 中 key=”b” 之前的标签都移到它之后; 现在新旧 DOM 第二个标签都为 key=”a”; 新 DOm 中第三个标签 key=”d”,将旧 DOM 中 key=”d” 之前的未重新排序的标签都移到它之后; 新 DOM 中第四个标签 key=”e”,对比旧 DOM,不存在,在第三个标签之后插入 key=”e” 标签; 第五个标签为 key=”c”,整个操作完成 注意  由于使用传统的 Diff 算法生成将一棵树转换成另一棵树的最小操作数复杂程度为 O(n³),n 是树中元素的数量,也就是说假如要展示1000个元素的树时,计算量在十亿的量级范围,显然无法高效的应对各种各样的项目,React 在以下两个假设下将算法简化到O(n): 两个不同类型的元素会产生出不同的树; 开发者可以通过 key 属性来暗示哪些子元素在不同的渲染下能保持稳定;  在实践中,几乎都可以满足这两个假设,所以采用了这个 React 版的 Diff 算法。 建议 该算法不会尝试匹配不同组件类型的子树。如果你发现你在两种不同类型的组件中切换,但输出非常相似的内容,建议把它们改成同一类型; Key 应该具有稳定,可预测,以及列表内唯一的特质。不稳定的 key(比如通过 Math.random() 生成,使用索引值 index)会导致许多组件实例和 DOM 节点被不必要地重新创建,这可能导致性能下降和子组件中的状态丢失。st=>start: 开始比较 e=>end: 更新完成 op1=>operation: 比较根节点 op2=>operation: 比较下一层 op3=>operation: 对应元素及其子元素进行替换 op4=>operation: 对应属性进行增、删 c1=>condition: 元素类型是否相同? c2=>condition: 元素属性是否相同? c3=>condition: 是否存在下一层元素? st->op1(bottom)->c1(yes,bottom)->c2(yes,right)->c3(no,bottom)->e c1(no)->op3->c3 c2(no)->op4->c3 c3(yes)->op2(right)->c1{\"scale\":1,\"line-width\":2,\"line-length\":50,\"text-margin\":10,\"font-size\":12} var code = document.getElementById(\"flowchart-0-code\").value; var options = JSON.parse(decodeURIComponent(document.getElementById(\"flowchart-0-options\").value)); var diagram = flowchart.parse(code); diagram.drawSVG(\"flowchart-0\", options);","updated":"2020-07-07T21:45:28.626Z","tags":[{"name":"React","slug":"React","permalink":"http://huishanyi.club/tags/React/"}]},{"title":"React项目性能优化","date":"2019-09-20T03:05:50.000Z","path":"2019/09/20/React/React项目性能优化/","text":"  无论以什么技术去开发项目,都存在性能优化,项目越大,性能缺陷越明显,那么该如何避免呢或者解决呢?  良好的开发习惯及优质的代码格式可以避免一些性能问题,但在工期紧,项目网络复杂的情况下难免会忽视一些问题或者存在未曾见过的情景引入的新问题,而这些必须通过积累才可以在今后的项目中从容应对。 性能优化意识(浮躁的人请看=>优化项<=)  拥有性能优化意识可以更快的发现存在过或将出现的性能问题,同一问题发现的越早,该问题当然就会解决的越早。还有持之以恒的做以下几点,优质的代码自然会出现!!! 关注官方文档  开发框架的工程师往往比应用框架的工程师更了解其结构,而一些优秀的建议总是体现在官方文档中。 好东西都是值得拆的  常用的框架或者包就是好东西,常用说明它带来的功能很有价值,知其然不如知其所以然!当你了解它的时候没准可以改造它,去除一些不用的功能消耗或者增加一些你需要的功能,让它更适合你。 搬砖好(hǎo),好(hào)搬砖,搬好(hǎo)砖  无论是自己的项目还是别人的项目,碰到好的问题没见过的问题就去记录,防止自己下次项目就会转角遇见“爱”。曾经那么多问题出现在我面前,我却视而不见,直到我忘了是什么问题的时候才追悔莫及,人世间最大的痛就是太相信自己的记忆力,如果上天能够给我一个再来一次的机会,我希望······来一碗黑芝麻糊不要糊多放核桃! 优化项  以下是详细的性能优化项积累,持续更新中······ 减少不必要的渲染  手动挡:React 项目中随着状态值(state)发上变化,render 函数将会执行渲染。当状态值(state)发生变化,使用生命周期函数 shouldComponentUpdate 函数控制非相关组件不进行二次渲染(即禁止非相关组件更新)。  自动挡:如果项目中存在 Redux,可以配合 immutable 并把 React.Component 替换为 React.PureComponent。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192// 以toDoList为例import React, {Component, Fragment} from 'react'// 入口组件(父组件)class ToDoList extends Component{ state={ inputValue: '', listArr: ['list1','list2'] } render(){ return ( <Fragment> <div> <input value={this.state.inputValue} onChange={this.inputValueChange} /> <button onClick={this.addClick} > 添加 </button> </div> <ul> {this.state.listArr.map((item, index) => { return ( <ListItem key={index+item} // 遍历后的元素别忘了 key index={index} item={item} delClick={this.delClick} /> ) })} </ul> </Fragment> ) } // 双向数据绑定(将输入框中的 value 值传递给状态中,状态改变触发二次渲染,更新输入框中显示的 value 值) inputValueChange = (e) => { const inputValue = e.target.value this.setState(() => ({ inputValue // ES6,相当于'inputValue: inputValue' })) } // 点击添加按钮,将输入框中的内容添加到列表 addClick = () => { if(!this.state.inputValue) return this.setState((prevState) => ({ // prevState 相当于 this.state inputValue: '', listArr: [...prevState.listArr, prevState.inputValue] })) } // 点击指定列表项进行删除 delClick = (index) => { this.setState((prevState) => { const listArr = prevState.listArr listArr.splice(index, 1) return { listArr } }) }}// 列表项组件(子组件)class ListItem extends Component { shouldComponentUpdate(nextProps, nextState){// 可以通过当前的属性或状态值与之后的属性或状态值作对比,返回 true 更新组件,false 不更新组件 if(this.props.item !== nextProps.item)// 指定列表项中的内容不改变则该列表项组件不更新 return true return false } render() { return ( <li onClick={this.delClick} > {this.props.item} </li> ) } delClick = () => { this.props.delClick(this.props.index) }}export default ToDoList","updated":"2020-07-07T21:45:28.626Z","tags":[{"name":"React","slug":"React","permalink":"http://huishanyi.club/tags/React/"}]},{"title":"react-router与react-router-dom","date":"2019-09-16T09:36:07.000Z","path":"2019/09/16/React/react-router与react-router-dom/","text":"  为了使 React 应用更快的拥有进行页面跳转的能力,react-router 应运而生,它是基于 React 的路由核心库,让你的 React 应用在进行跳转时纵想丝般顺滑;而在 Web 技术栈高速发展的的环境下,react-router 衍生出了 react-router-dom,是基于 react-router 专为 Web 端开发的。 react-router React 路由核心库,为 react-router-dom 和 react-router-native 做支持。本篇仅对 react-router API 进行简单罗列,若想详细了解,请看官方文档(英文不太 OK 的爱国人士请看 汉化文档,建议仅做参考,部分翻译后不好理解)。 注意: 项目开发过程中,<Route> 与 <Link> 一定要在共同的 <Router> 域下,否则 <Route> 与 <Link> 无法配对! APIRouter<Router> 是所有路由组件共用的底层接口。通常,我们的应用程序将使用其中一个高级路由器代替: <BrowserRouter> <HashRouter> <MemoryRouter> <NativeRouter> <StaticRouter> MemoryRouter<MemoryRouter> 能在内存中保存你的 “URL” 的历史记录(并不会对地址栏进行读写)。很适合在测试环境和非浏览器环境中使用,例如 React Native。 StaticRouter<StaticRouter> 一个永不改变地址的 。当用户没有真正点击时,它对于服务器端渲染脚本是有帮助的,因为该 location 实际上并未发生变化。由此命名:静态。当你只需要插入一个位置并且在渲染输出上作出断言时,它在简单测试中也很有用。 Route<Route> 当前页面 location 与 Route 组件的 path 匹配时,React应用进行组件渲染。 Redirect<Redirect> 进行页面重定向,新的 location 会覆盖当前 history 堆栈中的 location。 Switch<Switch> 渲染与当前页面 location 匹配的第一个子节点<Route>或者<Redirect>。 Prompt<Prompt> 用于在用户离开页面之前及时提示用户。当你的应用程序进入应阻止用户离开的状态时(比如一个表格被填满了一半),渲染一个<Prompt>。 react-router-dom react-router 专为 Web 应用程序提供的工具包,额外提供了<BrowserRouter>和<HashRouter>路由。深入探究请看官方文档(英文不太 OK 的爱国人士请看 汉化文档,建议仅做参考,部分翻译后不好理解)。 注意: 项目开发过程中,<Route> 与 <Link> 一定要在共同的 <Router> 域下,否则 <Route> 与 <Link> 无法配对! 安装运行命令npm install --save react-router-dom。 创建配置文件创建router目录文件夹,并在该目录创建router.js文件,写入代码如下: 12345678910111213141516171819202122import React, { Component } from 'react';import {BrowserRouter, Route, Switch} from 'react-router-dom'//项目中存在的需要跳转的页面,根据自己的目录结构定import Login from '../pages/login'import UserDetail from '../pages/user/UserDetail'import UserList from '../pages/user/UserList'class SystemRouter extends Component {// SystemRouter 不是固定的,语义化就 OK 了 render() { return ( <BrowserRouter>{/*使 Web应用程序中的 UI 与 URL 相对应*/} <Switch>{/*渲染与当前 location 匹配的第一个子节点(<Route> 或者 <Redirect>),若路由地址为根目录须加 exact(因为\"/Login\"与\"/userList\" 都会被\"/\"匹配), */} <Route exact path=\"/\" component={UserDetail} />{/*<Route> 组件:使当前 location 与 path 中的值进行模糊匹配,若匹配则渲染 component 中的组件,不匹配则继续向下寻找匹配项。若多个 <Route> 组件中的 path 值都与某一页面location相匹配,则在第一个匹配的 <Route> 组件中加 exact(完全匹配) 属性*/} <Route path=\"/Login\" component={Login} /> <Route path=\"/userList\" component={UserList} /> </Switch> </BrowserRouter> ); }} export default SystemRouter; 将路由文件添加到入口文件以create-react-app为例,其入口文件为根目录 => src => index.js,该文件代码更改后如下: 12345678910111213import React from 'react';import ReactDOM from 'react-dom';- import SystemRouter from './App';+ import SystemRouter from './router/router';import * as serviceWorker from './serviceWorker';- ReactDOM.render(<App />, document.getElementById('root'));+ ReactDOM.render(<SystemRouter />, document.getElementById('root'));// If you want your app to work offline and load faster, you can change// unregister() to register() below. Note this comes with some pitfalls.// Learn more about service workers: https://bit.ly/CRA-PWAserviceWorker.unregister(); 需要进行跳转的页面这里仅对跳转相对应的代码进行呈现,如下: 123456789101112131415// UserDetail.jsimport React, { Component } from 'react';import {Link} from 'react-router-dom' // 引入 Linkclass UserDetail extends Component { state = { } render() { return ( <Link to=\"/userList\">to userList</Link> {/*相对于 a 链接*/} ); }} export default UserDetail; 1234567891011121314// UserList.jsimport React, { Component } from 'react';import {Link} from 'react-router-dom'class UserList extends Component { render() { return ( <Link to=\"/\">to userDetail</Link> ); }} export default UserList; 尽情的旋转跳跃吧!API下方为除 react-router 中已做说明的 API BrowserRouter使用 HTML5 历史 API 记录( pushState,replaceState 和 popstate 事件)的 使您的UI与URL保持同步。 HashRouter使用 URL 的 hash 部分(即 window.location.hash )的 <Router> 使您的 UI 与 URL 保持同步。重要提示:Hash 历史记录不支持 location.key 或 location.state。在以前的版本中,我们试图填补行为,但存在我们无法解决的边缘案例。 任何需要此行为的代码或插件都将无法使用。由于此技术仅用于支持传统浏览器,因此我们鼓励您配置服务器以便与 <BrowserHistory> 配合使用。 Link在应用程序中提供已声明且可访问的导航,与 a 链接比较相似。 NavLink一个特殊版本的 Link,当它的 path 与当前 URL 匹配时,为其渲染的组件添加样式属性。","updated":"2020-07-07T21:45:28.628Z","tags":[{"name":"React","slug":"React","permalink":"http://huishanyi.club/tags/React/"}]},{"title":"react-app-rewired","date":"2019-09-09T09:05:28.000Z","path":"2019/09/09/React/react-app-rewired/","text":"  react-app-rewired是用来对 webpack 进行自定义配置的。使用create-react-app创建 React 应用之后,webpack配置会被隐藏,在更改或扩展时须npm eject去暴露配置再去操作,而该过程并不可逆。But I don’t wanna Eject.  在阅读本篇文章前默认您已阅读create-react-app。 安装运行命令npm install --save-dev react-app-rewired。 创建配置文件在根目录(即与package.json同级)下创建配置文件config-overrides.js。 123456/* config-overrides.js */ module.exports = function override(config, env) { //do stuff with the webpack config... return config;} 更改 package.json 中的文件‘+’为增加代码,‘-’为删除代码。 1234567891011 /* package.json */ \"scripts\": {- \"start\": \"react-scripts start\",+ \"start\": \"react-app-rewired start\",- \"build\": \"react-scripts build\",+ \"build\": \"react-app-rewired build\",- \"test\": \"react-scripts test --env=jsdom\",+ \"test\": \"react-app-rewired test --env=jsdom\", \"eject\": \"react-scripts eject\"} 配置less更便捷的样式编写。 安装运行命令npm install --save react-app-rewire-less。 编辑config-overrides.js123456789const rewireLess = require('react-app-rewire-less'); /* config-overrides.js */module.exports = function override(config, env) { config = rewireLess(config, env); // with loaderOptions // config = rewireLess.withLoaderOptions(someLoaderOptions)(config, env); return config;} 配置按需加载按需加载UI组件库(以antd为例),优化应用网络性能。 安装运行命令npm install --save antd。 运行命令npm install --save-dev customize-cra(react-app-rewired @2.x进行按需加载推荐额外安装的依赖包)。 运行命令npm install --save-dev babel-plugin-import。 编辑config-overrides.js123456789// 引入 customize-cra 进行对指定UI组件库按需加载的配置const { override, fixBabelImports } = require('customize-cra');module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css', }), ); 若想对antd主题进行自定义,请看如下代码(自定义主题是通过less-loader的modifyVars来进行配置的,less与less-loader我们之前已经安装了react-app-rewire-less进行替代)。 123456789101112131415- const { override, fixBabelImports } = require('customize-cra');+ const { override, fixBabelImports, addLessLoader } = require('customize-cra');module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es',- style: 'css',+ style: true, }),+ addLessLoader({+ javascriptEnabled: true,+ modifyVars: { '@primary-color': '#1DA57A' },+ }),);","updated":"2020-07-07T21:45:28.627Z","tags":[{"name":"React","slug":"React","permalink":"http://huishanyi.club/tags/React/"}]},{"title":"create-react-app","date":"2019-09-05T02:48:02.000Z","path":"2019/09/05/React/create-react-app/","text":"  create-react-app是用来创建React应用的脚手架,它在内部使用了 Babel 和 webpack,但你无需了解它们的任何细节。  官方文档(出来不久,所以是纯英文,估计以后会支持中文吧!),感觉看起来困难的可以看看本片文章。 环境须配置好node环境(使用最新稳定版node),参考NodeJS安装教程。 创建React应用在cmd中运行命令npx create-react-app 应用名称(不能使用大写字母)(图片看不清了另存为到本地看,图片在线预览优化中) 目录结构12345678910111213141516myapp├── node_modules(依赖包)├── public(公共文件,不会被webpack解析)│ ├── favicon.ico(站点图标,页签左上角图标)│ ├── index.html(应用首页,作为组件容器)│ └── manifest.json(配合PWA[Progressive Web App]使用的文件,与src目录中的serviceWorker.js有关)├── src(应用源码,会被webpack解析)│ ├── index.js(入口文件,将布局组件添加到应用首页中,可选择是否启用PWA)│ ├── App.js(布局组件,可作为其他组件的插槽)│ ├── App.test.js(自动化测试文件)│ ├── serviceWorker.js(离线缓存文件,优化移动端体验,暂不支持ios)│ └── .css文件(样式文件)├── .gitignore(上传git时需要忽略的文件)├── package.json(Node.js项目描述文件)├── README.md(项目介绍文件)└── yarn.lock(依赖包版本锁定,不要改动) 运行React应用在创建成功React应用后,在cmd中输入命令cd myapp & npm start来运行该应用。运行成功后会自动在浏览器中打开http://localhost:3000/","updated":"2020-07-07T21:45:28.627Z","tags":[{"name":"React","slug":"React","permalink":"http://huishanyi.club/tags/React/"}]},{"title":"React介绍","date":"2019-09-05T01:43:38.000Z","path":"2019/09/05/React/React介绍/","text":"  React 是用于构建用户界面的JavaScript库。  由于剧情(项目实例)需要才产生的此篇博文,随着React的高速发展,现在我看到的所有React教程中,自认为没有比官网更细致、更清楚、更易上手的了。里面没其他内容了,所以你懂得! ==> 传送门。","updated":"2020-07-07T21:45:28.625Z","tags":[{"name":"React","slug":"React","permalink":"http://huishanyi.club/tags/React/"}]},{"title":"博客搭建(二)———— 后台管理系统","date":"2019-09-04T07:59:59.000Z","path":"2019/09/04/项目实例/博客搭建/博客搭建(二)/","text":"页面布局确定后台管理系统整体样式布局。 目录增删***后文字为目录更改注释。 1234567891011121314151617181920212223242526272829303132333435根目录 ├── public(公共文件,不会被webpack解析,create-react-app 创建项目时自带文件) │ └── api(本地模拟接口数据,数据较多可使用 easyMock) │ └── menu.json(菜单数据模拟) └── src ├── commons(公共组件) *** 原 comments 目录,语义不够明确,故修改为 commons │ └── SysLayout(系统样式布局组件) *** 添加 │ ├── components(布局子组件) │ │ └── MenuList.json(菜单组件) │ ├── actionCreators.js(创建 action 对象) │ ├── constants.js(定义 action 类型) │ ├── reducer.js(SysLayout 组件的 state 处理库) │ └── index.jsx(SysLayout UI 组件) ├── pages(项目页面目录) │ ├── article(文章管理) │ ├── log(日志管理) │ ├── login(登录) │ ├── result(页面获取情况) *** 添加 │ │ ├── 403.jsx(无权访问) │ │ ├── 404.jsx(页面不存在) │ │ └── 500.jsx(服务器发生错误) │ ├── safety(安全管理) │ ├── theme(主题管理) │ └── user(用户管理) │ ├── PersonalInfo(个人信息) │ │ └── index.js(页面组件) │ └── UserList(用户列表) │ └── index.js(页面组件) ├── router(路由) │ └── index.js(系统路由文件) *** 修改 ├── store(状态管理目录) │ ├── index.js(创建系统数据管理仓库) │ └── reducer.js (汇总所有子组件的 reducer,state 区块化) *** 修改 ├── App.js(系统组件插槽,可作为系统布局组件,这里为了后期主题一键修改,将布局组件放到公共组件目录中) *** 修改 └── index.js(项目入口文件,可选择是否启用PWA) *** 修改 相关源码及说明所操作代码进行展示及说明。 menu.json12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061/****** public/api/menu.json ******/ // 导航菜单模拟数据,menu(一级菜单数组),text(文本),icon(图标),subMenu(二级菜单数组),link(路由){ \"status\": \"sucess\", \"menu\": [ { \"text\": \"用户管理\", \"icon\": \"user\", \"subMenu\": [ { \"text\": \"个人信息\", \"icon\": \"smile\", \"link\": \"\" }, { \"text\": \"用户列表\", \"icon\": \"smile\", \"link\": \"userList\" } ] }, { \"text\": \"菜单管理\", \"icon\": \"desktop\", \"subMenu\": [ { \"text\": \"菜单列表\", \"icon\": \"smile\", \"link\": \"menuList\" } ] }, { \"text\": \"安全管家\", \"icon\": \"alert\", \"subMenu\": [ { \"text\": \"数据分析\", \"icon\": \"smile\", \"link\": \"dataAnlysis\" }, { \"text\": \"安全限制\", \"icon\": \"smile\", \"link\": \"safetyControl\" }, { \"text\": \"日志管理\", \"icon\": \"smile\", \"link\": \"logs\" } ] }, { \"text\": \"系统信息\", \"icon\": \"setting\", \"link\": \"systemInfo\" } ]} src/commons/SysLayout123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293/****** SysLayout/index.jsx ******/// SysLayout UI 组件// PureComponent 与 Component 功能相似,自带 prop 与 state的浅比较来实现 shouldComponentUpdateimport React, { PureComponent } from 'react'; // 连接 storeimport { connect } from 'react-redux'import * as actionCreators from './actionCreators'import MenuList from './components/MenuList'import { Layout, Icon } from 'antd';const { Header, Sider, Content } = Layout;class SysLyout extends PureComponent{ componentDidMount(){ // 为获取数据而生 this.props.getMenu();// 获取菜单数据 } render() { const { collapsed, menuList, changeCollapsed } = this.props return ( <Layout> <Sider trigger={null} collapsible collapsed={collapsed} style={{ overflow: 'auto', height: '100vh', position: 'fixed', left: 0, }} > <div className=\"logo\" style={{height:'32px',margin:'16px',background:'#666'}} /> <MenuList menuList={menuList.toJS()} /> </Sider> <Layout style={collapsed ? {marginLeft:'80px'} : {marginLeft:'200px'}}> <Header style={{padding: '0 20px',position:'fixed',top:'0',width:'100%'}}> <Icon className=\"trigger\" style={{color:'#fff', fontSize: '20px'}} type={collapsed ? 'menu-unfold' : 'menu-fold'} onClick={changeCollapsed} /> </Header> <Content style={{ margin: '88px 16px 24px', padding: '24px', background: '#fff', minHeight: '666px', height: 'calc(100vh - 112px)' }} > { this.props.children } </Content> </Layout> </Layout> ) } }const mapStateToProps = state => { return { collapsed: state.getIn(['sysLayoutReducer', 'collapsed']), menuList: state.getIn(['sysLayoutReducer', 'menuList']) }}const mapDispatchToProps = dispatch => { return { changeCollapsed(){ dispatch(actionCreators.changeCollapsed()) }, getMenu(){ dispatch(actionCreators.getMenu()) } }}export default connect( mapStateToProps, mapDispatchToProps )(SysLyout) 1234/****** SysLayout/constants.js ******/// 定义 action 类型,将 type 由 string 转化为 contantant,输错时控制台会报错,易定位问题export const CHANGE_COLLAPSED = 'SysLayout/CHANGE_COLLAPSED'export const CHANGE_MENU = 'SysLayout/CHANGE_MENU' 12345678910111213141516171819202122232425262728293031323334353637/****** SysLayout/actionCreators.js ******/// 创建 action 对象// 易用、简洁且高效的基于 Promise 的 http 库,这里用来请求数据import axios from 'axios'// 文件中存在需要替换 immutable 类型数据的数据,因此需要将其转化为同类型的 immutable 类型数据import { fromJS } from 'immutable'import { CHANGE_COLLAPSED, CHANGE_MENU} from './constants'// 若仅在本文件使用,那么就不要输出,避免不必要的性能消耗const changeMenu = (menuList) => ({ type: CHANGE_MENU, menuList: fromJS(menuList)})// 菜单折叠 actionexport const changeCollapsed = () => ({ type: CHANGE_COLLAPSED})// 菜单获取 actionexport const getMenu = () => { return (dispatch) => { axios.get('/api/menu.json') .then((res) => {// 请求成功执行的方法 dispatch(changeMenu(res.data.menu)) }) .catch((err) => {// 请求失败执行的方法 console.log(err) }) } } 123456789101112131415161718192021222324252627/****** SysLayout/reducer.js ******/// state 处理库,根据 action 类型执行特定方法,输出新的 stateimport { fromJS } from 'immutable'import { CHANGE_COLLAPSED, CHANGE_MENU} from './constants'// 初始化菜单列表与菜单折叠状态const defaultState = fromJS({ menuList: [], collapsed: false})export default ( state=defaultState, action ) => { switch(action.type) { case CHANGE_COLLAPSED: let newCollapsed = !state.get('collapsed') return state.set('collapsed', newCollapsed) case CHANGE_MENU: return state.set('menuList', action.menuList) default: return state }} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253/****** SysLayout/components/MenuList.jsx ******/import React, { PureComponent } from \"react\";import { Link } from \"react-router-dom\";import { Menu, Icon } from \"antd\";const { SubMenu } = Menu;export default class MenuList extends PureComponent { render() { return ( <Menu defaultOpenKeys={['用户管理']} defaultSelectedKeys={['个人信息']} theme=\"dark\" mode=\"inline\" > {this.props.menuList.map((item, index) => { return item.subMenu ? ( <SubMenu key={item.text} title={ <span> <Icon type={item.icon} /> <span>{item.text}</span> </span> } > {item.subMenu.map((item, index) => { return ( <Menu.Item key={item.text}> <Link to={\"/\" + item.link} key={item.link}> <Icon type={item.icon} /> <span>{item.text}</span> </Link> </Menu.Item> ); })} </SubMenu> ) : ( <Menu.Item key={item.text}> <Link to={\"/\" + item.link} key={item.link}> <Icon type={item.icon} /> <span>{item.text}</span> </Link> </Menu.Item> ); })} </Menu> ); }} src/pages/result12345678910111213141516171819202122/****** result/403.jsx ******/import React from 'react';import { Link } from 'react-router-dom';import { Result, Button } from 'antd';const NotAccess = () =>{ return ( <Result status=\"403\" title=\"403\" subTitle=\"抱歉,您没有访问此页面的权限。\" extra={ <Button type=\"primary\"> <Link to=\"/\">返回首页</Link> </Button> } /> )}export default NotAccess 12345678910111213141516171819202122/****** result/404.jsx ******/import React from 'react';import { Link } from 'react-router-dom';import { Result, Button } from 'antd';const NotFoundPage = () =>{ return ( <Result status=\"404\" title=\"404\" subTitle=\"抱歉,您访问的页面不存在。\" extra={ <Button type=\"primary\"> <Link to=\"/\">返回首页</Link> </Button> } /> )}export default NotFoundPage 12345678910111213141516171819202122/****** result/500.jsx ******/import React from 'react';import { Link } from 'react-router-dom';import { Result, Button } from 'antd';const NotFoundPage = () =>{ return ( <Result status=\"500\" title=\"500\" subTitle=\"抱歉,服务期发生错误。\" extra={ <Button type=\"primary\"> <Link to=\"/\">返回首页</Link> </Button> } /> )}export default NotFoundPage src/pages/user12345678910111213/****** user/PersonalInfo/index.js ******/import React, { Component } from 'react';class PersonalInfo extends Component { render() { return ( <div>PersonalInfo</div> ); }} export default PersonalInfo; 12345678910111213/****** user/UserList/index.js ******/import React, { Component } from 'react';class UserList extends Component { render() { return ( <div>UserList</div> ); }} export default UserList; src/router1234567891011121314151617181920212223242526/****** router/index.js ******//** router 文件在路由部分已做讲解,文件此处所做修改为去掉了<Switch>外层的<BrowserRouter>包裹* 由于页面布局基本相同,故将 <BrowserRouter> 放在了插槽出并将 <SysLayout> 组件包裹,即 App.js 中* 共在同一 <BrowserRouter> 下的 <Link> 与 <Route> 才能进行匹配*/import React from 'react';import { Route, Switch } from 'react-router-dom'import Login from '../pages/login'import PersonalInfo from '../pages/user/PersonalInfo'import UserList from '../pages/user/UserList'import NotFound from '../pages/result/404.jsx'const SystemRouter = () => { return ( <Switch> <Route exact path=\"/\" component={PersonalInfo} /> <Route path=\"/Login\" component={Login} /> <Route path=\"/userList\" component={UserList} /> <Route path=\"\" component={NotFound}/> </Switch> )} export default SystemRouter; src/store123456789101112/****** store/reducer.js ******/// 引入 combineReducers 进行 state 区块化管理import { combineReducers } from 'redux-immutable'// 引入 sysLayout 下的 state 数据import sysLayoutReducer from '../commons/SysLayout/reducer'const reducer = combineReducers({ sysLayoutReducer})export default reducer; src/App.js123456789101112131415161718192021222324/****** src/App.js ******/import React from 'react'// 打通组件与 store 的连接import { Provider } from 'react-redux' import store from './store'import { BrowserRouter } from 'react-router-dom'import SysLayout from './commons/SysLayout'import SysRouter from './router'const App = () => { return ( <Provider store={store}> <BrowserRouter> <SysLayout> <SysRouter /> </SysLayout> </BrowserRouter> </Provider> )}export default App src/index.js1234567891011121314/****** src/index.js ******/import React from 'react';import ReactDOM from 'react-dom';import App from './App';import * as serviceWorker from './serviceWorker';// 将组件挂载至页面ReactDOM.render(<App />, document.getElementById('root'));// If you want your app to work offline and load faster, you can change// unregister() to register() below. Note this comes with some pitfalls.// Learn more about service workers: https://bit.ly/CRA-PWAserviceWorker.unregister(); 个人信息用户登录后显示的个人信息页面,主要包括基本信息、密码修改、设备信息。项目路径:src/pages/user/PersonalInfo 目录增删***后文字为目录更改注释。 1234567891011121314src├── utils(util 是与通用业务无关的工具,如日期工具;tool 是与通用的部分业务相关的工具,如身份校验工具) *** 添加│ └── UAParser(用户代理解析程序) └── pages(项目页面目录) └── user(用户管理) └── PersonalInfo(个人信息) ├── index.js(页面组件) *** 修改 ├── actionCreators.js(创建 PersonalInfo 下的 action 对象) *** 添加 ├── constants.js(定义 action 的类型) *** 添加 ├── reducer.js(PersonalInfo 组件的 state 处理库) *** 添加 └── components(PersonalInfo 下的子组件) *** 添加 ├── BasicInfo.jsx(基本信息) ├── PasswordSecurity.jsx(密码安全) └── EquipmentInfo.jsx(设备信息) 相关源码及说明所操作代码进行展示及说明。 PersonalInfo/index.js首先确定该页面最大结构,这里采用 Tab 选项卡进行内容分类展示。 1234567891011121314151617181920212223242526272829/****** PersonalInfo/index.js ******/import React from 'react';import BasicInfo from './components/BasicInfo'import PasswordSecurity from './components/PasswordSecurity'import EquipmentInfo from './components/EquipmentInfo'import { Tabs } from 'antd';const { TabPane } = Tabs;const UserDetail = () => { return ( <div> <Tabs defaultActiveKey=\"1\" tabPosition={'top'}> <TabPane tab={`基本信息`} key={1}> <BasicInfo/> </TabPane> <TabPane tab={`密码安全`} key={2}> <PasswordSecurity/> </TabPane> <TabPane tab={`设备信息`} key={3}> <EquipmentInfo /> </TabPane> </Tabs> </div> );}export default UserDetail PersonalInfo/componentsPersonal 下的子组件目录,子组件中包括 Redux 的相关知识(之前的 Redux 博文中提到,这里不做过多解释)。 12/****** Personal/components/BasicInfo.jsx ******/更新中··· 12/****** Personal/components/PasswordSecure.jsx ******/更新中··· 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273/****** Personal/components/EquipmentInfo.jsx ******/import React, { PureComponent } from \"react\";import { connect } from 'react-redux'import * as actionCreators from '../actionCreators'class EquipmentInfo extends PureComponent { componentDidMount() { this.props.getIP(); // 组件结构渲染完毕,发请求,填充真实数据 } render() { const { browser, cpu, engine, IP, system } = this.props; let JSIP = IP.toJS(); // 将 immutable 对象转换为 JS 对象 return ( <ul className=\"equipmentInfo\"> <li> <label>IP地址:</label> <span>{JSIP.query}</span> </li> <li> <label>运营商:</label> <span>{JSIP.isp}</span> </li> <li> <label>当前位置:</label> <span>{`${JSIP.country} ${JSIP.regionName} ${JSIP.city}`}</span> </li> <li> <label>浏览器:</label> <span>{browser}</span> </li> <li> <label>渲染引擎:</label> <span>{engine}</span> </li> <li> <label>系统:</label> <span>{system}</span> </li> <li> <label>处理器:</label> <span>{cpu}</span> </li> </ul> ); }}const mapStateToProps = (state) => { return { IP: state.getIn(['PersonalInfoReducer', 'IP']), browser: state.getIn(['PersonalInfoReducer', 'browser']), cpu: state.getIn(['PersonalInfoReducer', 'cpu']), engine: state.getIn(['PersonalInfoReducer', 'engine']), system: state.getIn(['PersonalInfoReducer', 'system']) }}const mapDispatchToProps = (dispatch) => { return { getIP() { dispatch(actionCreators.getIP()) } }}export default connect(mapStateToProps, mapDispatchToProps)(EquipmentInfo) PersonalInfo/actionCretors.js创建供组件使用的 action 对象,并在组件使用 action 对象后,将相关数据传给 reducer 来进行 state 的加工。此处将会进行 ajax 请求的发送,故本地安装 axios 模块,同时须保证已经对 Redux 进行增强(之前的 Redux 博文中提到,这里不做过多解释)。 12345678910111213141516171819202122232425/****** Personal/actionCretaors.js ******/import axios from 'axios'import { fromJS } from 'immutable'import { GET_IP } from './constants'// ******************************************************const getEquipmentInfo = (data) => ({ type: GET_IP, data: fromJS(data)})// *******************************************************export const getIP = () => { return (dispatch) => { axios.post('http://ip-api.com/json/?lang=zh-CN') // IP 信息接口,可先访问下这个地址,看看数据结构 .then((res)=>{ dispatch(getEquipmentInfo(res.data)) }) .catch((err)=>{ console.log(err) }) }} PersonalInfo/constants.js定义 action 的类型。 12/****** PersonalInfo/constants.js ******/export const GET_IP = 'UserDetail/GET_IP' PersonalInfo/reducer.jsPersonalInfo 组件的 state 处理库。 123456789101112131415161718192021222324252627282930313233343536373839/****** PersonalInfo/reducer.js ******/import { fromJS} from 'immutable'import { GET_IP } from './constants'import equipmentInfo from '../../../utils/UAParser'const defaultState = fromJS({ IP: { address: \"获取中···\", city: \"\", country: \"获取中···\", query: \"获取中···\", regionName: \"\", isp: \"获取中···\" }, browser: \"获取中···\", cpu: \"获取中···\", engine: \"获取中···\", system: \"获取中···\"})export default (state=defaultState, action) => { switch(action.type) { case GET_IP: return state.merge({ IP: action.data, browser: equipmentInfo.browser.name, cpu: equipmentInfo.cpu.architecture, engine: equipmentInfo.engine.name, system: equipmentInfo.os.name + equipmentInfo.os.version }) default: return state }} src/utils/UAParser.js用户代理解析程序。 123456789/****** UAParser.js ******/// 使用 npm 引入 ua-parser-js 包来快速进行 UA 解析(并且日后通过升级包来更新 UA 解析模块也更加便捷)import UAParser from 'ua-parser-js'const newUAParser = new UAParser()const UAJson = newUAParser.getResult()export default UAJson ## 更新中(最新更新2019年12月17日)……","updated":"2020-07-07T21:45:28.638Z","tags":[{"name":"博客搭建","slug":"博客搭建","permalink":"http://huishanyi.club/tags/博客搭建/"}]},{"title":"JS 6种刷新方案测试及分析","date":"2019-08-21T02:18:15.000Z","path":"2019/08/21/JS/JS刷新及测试/","text":"  现在市面上的主流浏览器主要分为三类:谷歌浏览器、IE浏览器、各类双内核浏览器。截至今日,测试情况如本文所示。 浏览器市场份额分析访问 百度统计流量研究院 进行浏览器市场份额分析,然后测试主流浏览器对各类刷新方案的支持程度。 刷新方案 方案一location.reload(true); // 刷新当前文档,若 reload 参数默认为 false :它就会用 HTTP 头 If-Modified-Since 来检测服务器上的文档是否已改变。如果文档已改变,reload() 会再次下载该文档。如果文档未改变,则该方法将从缓存中装载文档。这与用户单击浏览器的刷新按钮的效果是完全一样的。参数为 true 时:无论何时刷新都会绕过本地缓存,从服务器下拉最新文档。 方案二history.go(0); // 加载访问历史中的当前文档(一定会使用本地缓存)。 方案三location=location; // 相当于点击 a链接。 方案四location.assign(location); // location.assign()载入新文档,相当于方案3,会产生新的历史记录项。 方案五location.replace(location); // location.replace()替换当前文档地址,不会产生新的历史记录项。 方案六window.navigate(location); // 仅IE支持的事件,可在限定用户使用IE浏览器时使用(特殊场景:前端控制用户必须使用IE以达到某些特定功能(安全、执行逻辑)。例如:window.open(),IE浏览器可以控制地址栏的显示与隐藏,而谷歌必会显示地址栏)。 Demo下载JS 刷新Demo下载","updated":"2020-07-07T21:45:28.622Z","tags":[{"name":"JS","slug":"JS","permalink":"http://huishanyi.club/tags/JS/"}]},{"title":"博客搭建(一)———— 构思","date":"2019-07-30T06:05:00.000Z","path":"2019/07/30/项目实例/博客搭建/博客搭建(一)/","text":"  博客搭建将会制作成一套系列性教程,以此作为知识总结的载体。  此段都是废话!为什么要做这套系列性教程呢?在玩自己喜欢的技术时,常常被工作打断,等忙完工作上的事后,再次接着去做,发现半头雾水,为了自己以后头上没有雾水,于是就出现了这些内容。。。  项目将会涉及到HTML、CSS、Less、JS、Node、React全家桶、Vue、MySQL(所用技术在项目完成前都是假的!) 项目名称SumBlog(聚博客) 项目需求 前台 可供用户注册、登录、发帖及评论; 用户分类为六类:超级管理员(拥有最高权限,可执行一切博客可行操作)、管理员(与大当家功能类似,不过禁止删除用户及更改用户权限)、一级用户(暂定为普通用户)、二级用户(暂定为普通用户)、三级用户(暂定为普通用户)、四级用户(暂定为普通用户); 个人中心:头像、修改密码、个人信息; 导航功能:主页、教程、软件、心语圈; 所有一级分类下须存在搜索功能、热门标签、Top10; 支持移动化。 后台 登录 用户管理 文章管理 日志管理 支持移动化 数据库 MySQL","updated":"2020-07-07T21:45:28.638Z","tags":[{"name":"博客搭建","slug":"博客搭建","permalink":"http://huishanyi.club/tags/博客搭建/"}]},{"title":"NodeJS模块","date":"2019-07-18T09:23:00.000Z","path":"2019/07/18/NodeJS/NodeJS模块/","text":"  模块式开发贯穿着整个NodeJS项目,那么,模块是什么?模块相当于一个最小的功能块,在NodeJS项目中,一个JS文件就为一个模块。  模块开发有何好处? 1.维护快稳准:随着项目的进行,代码量增大,导致了后期维护极其困难,而使用模块试开发,在后期出现问题时只需要找到相应的模块进行代码修改即可。2.可复用性高:同一项目不同页面同一功能可以复用模块;不同项目相同功能可以复用模块。 文件模块 由开发者自己编写的模块(如.js文件、NPM安装到本地的包,包就是拥有一个或多个小模块组成的大模块)。   创建一个node-test文件夹作为模块测试目录,并在node-test文件夹下创建hello.js与greet.js,内容如下。 hello模块(hello.js)12345function hello(name){ console.log(`Hello ${name} !`) // ES6 & ES5字符串拼接。console.log('Hello '+name+' !')}module.exports = hello // 模块.暴露 = hello,module.exports将本模块内容暴露出去,使其他模块可以发现它并且调用它。exports.hello = hello也可以达到同样的效果,只不过它不可以像module.exports一样暴露对象,例如module.exports = {k1: v1, k2: v2}。 greet模块(greet.js)123var hello = require('./hello'); // 使用Node提供的require方法,以相对路径将hello模块引入,引入时.js后缀可以省略hello('World'); // 控制台输出Hello World ! 核心模块 NodeJS 核心模块是指在安装完成Node环境之后便产生的模块。 Buffer模块 Buffer 类用让 Node.js 处理二进制数据。 console模块 console模块用于提供控制台的标准输出。 process模块 process模块是一个全局变量,提供了一些与操作系统相关的简单接口,用来操作或查看相关进程信息。 fs模块 fs模块用于操作Node项目目录及文件。 stream模块 stream模块是一种抽象的数据结构,使文件具有流动性。 http模块 http模块用于搭建 HTTP 服务端和客户端。 crypto模块 crypto模块提供了加密、解密、签名、验证等功能。 util模块 util模块提供常用函数的集合,用于弥补核心JavaScript 的功能 过于精简的不足。 OS模块 os 模块提供了一些基本的系统操作函数。 path模块 path模块提供了一些用于处理文件路径的小工具。 url模块 url模块提供了URL解析和分析工具。 zlib模块 zlib模块可以对文件、HTTP请求和响应进行压缩与解压处理。 events模块 events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装。 Net模块 Net 模块提供了一些用于底层的网络通信的小工具,包含了创建服务器/客户端的方法. DNS模块 DNS 模块用于解析域名。 Domain模块 Domain(域) 简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的异常。","updated":"2020-07-07T21:45:28.624Z","tags":[{"name":"NodeJS","slug":"NodeJS","permalink":"http://huishanyi.club/tags/NodeJS/"}]},{"title":"NodeJS安装教程","date":"2019-07-16T10:47:00.000Z","path":"2019/07/16/NodeJS/NodeJS安装教程/","text":"  本篇文章将介绍如何安装NodeJS,以及根据本机系统环境与对node功能的需求进行NodeJS版本的选择。同时为经常开发NodeJS的工程师提供了node包管理工具和基础用法。 配置开发环境 进入Node.js官网下载指定平台安装包(nodejs.org),Windows系统下载安装红框的安装包,macOS 下载安装蓝框的安装包,看清楚自己电脑是32位还是64位。一直下一步直到完成就好。 Node.js的开发非常活跃,更新时以两个版本更新: Current版本:可以体验Node的新特性; LTS版本:没有激进的新特性更新,更加适应于生产环境; Current版是不稳定,LTS版稳定。 检查是否安装成功 配置开发环境完毕后,在cmd控制面板输入node(空格)–version或者node(空格)-v,回车后会立即得到一个结果,而这个结果就是Node.js的版本号,即安装成功。 交互型运行 在cmd控制面板中输入node,回车后即进入NodeJS编译环境,在此环境中可以输入任何符合NodeJS语法规范的代码,回车立刻得到结果。 编译型运行 通过cmd控制面板进入你想运行的js文件的所在目录文件夹,假如你的sum.js文件在桌面,进入(cd为进入命令)桌面目录(Desktop),输入node(空格)sum.js,回车即可得到结果。 node版本管理工具 若感觉会长期进行NodeJS开发,建议使用node包管理工具。 nvmw(windows环境) npm install -g nvmw安装好后使用cmd命令行工具(window10 自带的 powershell 使用 nvmw 会报错,建议使用 cmd 或 git 命令行工具)。 常用命令 帮助:nvmw -h; 安装指定版本node:nvmw install (node版本号); 卸载指定版本node:输入nvmw uninstall (node版本号); 查看所有已安装的node版本:nvmw ls; 当前shell切换node版本:nvmw use (node版本号),在中使用给定版本的Node; 当前系统切换node版本:nvmw switch (node版本号)。 n(Mac环境) Mac环境下的node包管理工具,需要的自行百度,对Mac不感兴趣。","updated":"2020-07-07T21:45:28.623Z","tags":[{"name":"NodeJS","slug":"NodeJS","permalink":"http://huishanyi.club/tags/NodeJS/"}]},{"title":"NodeJS介绍","date":"2019-07-16T10:34:00.000Z","path":"2019/07/16/NodeJS/NodeJS介绍/","text":"  对于Node.js,我认为它只是一个运行环境,或者说是一个开发平台,要说详细,我想加点修饰就可以了,它是可以使JavaScript运行在浏览器端外的一个运行环境,可以让JavaScript运行在服务器端的一个开发平台。实际上,它是对Google V8引擎进行了封装。   Node.js从名字上就可以看出肯定与JS有着密切的联系(都采用了ECMAscript语法),当然,不难看出也存在着一定的差异(Node.js:是一个可以快速构建网络服务及应用的平台,运行在服务器的ECMAScript中,操作服务器的文件、数据库、http系统底层的一些东西,主要用于后端的开发。JavaScript:是一种Web前端语言,浏览器的ECMAscript在前台解析,操作历史记录、BOM与DOM,主要用于Web前端开发)。   接下来,详细说一下Node.js。简单来说,Node.js就是运行在服务器端的JavaScript。Node.js是一个基于Chrome V8引擎的JavaScript运行环境。Node.js使用了一个事件驱动、非阻塞I/O的模型,使其轻量(体积小、无插件)又高效(运行速度快)。Node.js的包管理器npm,是全球最大的开源系统。Node.js特性是单线程、异步I/O。 Node.js优势: 基于事件驱动; 单进程、单线程(降低线程创建与切换的消耗); 异步I/O(非阻塞I/O); 依赖于Chorm V8引擎进行代码解释,运行速度快; 轻量、可伸缩,适用于数据的实时交互。 Node.js劣势: 不适用CPU密集型应用,当其中如果存在大循环(耗时长的运算),就会使得后续的I/o无法发起; 仅支持单核CPU,所以不能充分利用CPU; 可靠性低,若中间代码某个环节崩溃,整个系统都将崩溃; 开元组件库组件上传条件低,使得资源质量参差不齐,更新快,向下不兼容。   当然,出了问题自然就要解决问题,尤其是这么好用的东西,只要学了JS,这个就不会存在什么大问题,在不用再费很大功夫的同时掌握一门语言,并且涉及广还这么好用,何乐而不为? 解决方案: 劣势1方案:将大型运算任务分解为多个小任务,使运算可以适时释放,不阻塞后续I/O的发起; 劣势3方案:Nginx反向代理,负载均衡,开多个进程,绑定多个代理;开多个进程监听同一端口,使用cluster模块。   以上就是我对Node.js的简单介绍,若对个别概念性名词存在问题,可以百度普及一下,若存在其他问题,欢迎讨论。","updated":"2020-07-07T21:45:28.623Z","tags":[{"name":"NodeJS","slug":"NodeJS","permalink":"http://huishanyi.club/tags/NodeJS/"}]},{"title":"NPM介绍","date":"2019-07-16T09:46:00.000Z","path":"2019/07/16/NodeJS/NPM介绍/","text":"  NPM(Node Package Manger),Node包管理工具。在安装完Node之后,NPM便已经同时安装完成,用户可以通过NPM将自己写的包或者命令上传到NPM服务器上(当然上传必须要先注册账号),用户也可以通过命令行工具将NPM服务器上已经存在的包下载至本地为自己所用。 NPM常用命令 安装模块: 本地安装:npm install packageName ; 全局安装:npm install -g packageName; 安装包信息加入生产阶段依赖(项目package.json的dependencies对象中):–save或-S; 安装包信息加入到开发阶段依赖(项目package.json的devDependencies对象中):–save-dev或-D; 简写:npm i packageName; 安装指定版本:npm install packageName@1.0.0; 卸载模块:npm uninstall packageName 删除模块:npm remove packageName 查看已安装模块:npm ls; 查看指定已安装包:npm ls packageName; 查看全局安装的包:npm ls -g; 查看全局安装的指定包:npm ls -g packageName 更新模块:npm update packageName 查看指定包注册信息:npm view packageName 查看已安装包版本:npm version 查看包安装的路径:npm root;npm root -g 查看npm帮助:npm help NPM换源工具 由于NPM服务器在国外,在下载包时会比较慢,所以可以使用国内镜像cnpm或者taobao,前提是需要一个包(nrm),即命令行输入npm i -g nrm,安装完成之后即可控制源。 查看可选源列表:nrm ls 查看所有源响应时间:nrm test 切换源:nrm use taobao(cnpm…) 添加源:nrm add name 删除源:nrm del name。","updated":"2020-07-07T21:45:28.622Z","tags":[{"name":"NodeJS","slug":"NodeJS","permalink":"http://huishanyi.club/tags/NodeJS/"}]},{"title":"VScode配置","date":"2019-07-09T06:39:00.000Z","path":"2019/07/09/编辑器/VScode-config/","text":"这是我个人的VScode插件配置及说明,根据个人喜好进行插件的增删。 vsCode Config | └- Package | ├- Chinese(汉化包) | ├- Setting Sync(本地扩展备份,支持上传与拉取) | ├- HTML Snippets(H5代码片段以及提示) | ├- HTML CSS Support(html 标签上写class 智能提示当前项目所支持的样式) | ├- ESlint(保存时格式化js代码,格式较乱时需要多次保存ctrl+s) | ├- jQuery Code Snippets(输入jq时出现jq提示) | ├- Path Intellisense(自动补全路径) | ├- Bracket Pair Colorizer(不同级的括号使用不同的颜色) | ├- Live Server(浏览器页面实时刷新) | ├- Debugger for Chrome(映射 chrome 的 debug功能) | ├- GitLens(Git日志) | ├- filesize(左下角显示当前文件大小) | ├- Import Cost(提示引入包的大小) | ├- Code Runner(运行各种代码) | ├- vscode-fileheader(ctrl+alt+i,创建文件头部注释) | ├- HTML Boilerplate(输入 html,并按 Tab 键,即可生成干净的文档结构) | └- Simple React Snippets(更快的引用react,例如输入imrc,会出现import React, { Component } from ‘react’)","updated":"2020-07-07T21:45:28.635Z","tags":[{"name":"编辑器","slug":"编辑器","permalink":"http://huishanyi.club/tags/编辑器/"}]},{"title":"Atom配置","date":"2019-07-08T09:45:00.000Z","path":"2019/07/08/编辑器/atom-config/","text":"这是我个人的Atom插件配置及说明,根据个人喜好进行插件的增删。 Atom Config └- Package ├- simplified-chinese-menu(汉化) ├- amWiki(文库系统) ├- atom-ternjs(自动补全,包括对ES5、ES6、Node.js、jQuery、Angular的支持) ├- javascript-snippets(js代码片段补全) ├- jquery-snippets(jq代码片段补全) ├- autocomplete-paths(补全路径) ├- emmet(HTML+CSS速写) ├- html-to-string(为多行HTML添加字符串分隔符) ├- pretty-json(Atom包自动格式化json文件) ├- atom-html-preview(HTML实时预览) ├- atom-react-preview(atom中预览react组件) ├- react-component-preview(react组件预览)未配置 ├- jshint(js验证插件) ├- linter(代码风格,错误检查框架) ├- atom-ctags(快速监听方法,快速跳转方法) ├- atom-beauty(Atom中美化) ├- language-babel(自动缩进) ├- color-picker(颜色选择器) └- atom-runner (编译运行脚本Alt+R)","updated":"2020-07-07T21:45:28.635Z","tags":[{"name":"编辑器","slug":"编辑器","permalink":"http://huishanyi.club/tags/编辑器/"}]},{"title":"为什么我要写博客呢?","date":"2019-07-07T17:30:00.000Z","path":"2019/07/08/随笔/preface/","text":"  做有结果的事情总是要有些目的的,要不事情总是很难达到预期的结果,毕竟没有预期,所以我在努力做一个有目的的人。   为什么我要写博客呢?自认为是一个比较懒的人。那为什么不用博客平台注册个账号直接开始整文章?因为我太懒了,懒到没有仪式感、没有导火索、外围环境······各式各样的借口很难让我长期去做一件事,总是三分钟的热度,我讨厌这种总是懊悔没有坚持下去的自己。当看到一篇优秀的博客文章时,便会忍不住点开博主的所有文章,希望可以得到更多好的想法来滋润自己,确实可以发现还有雨露,只不过现在往往是沙漠。而阮一峰、廖雪峰、刘末鹏等大牛的文章总是持续的更新着。   SumBlog 是此博客名称,是 Summary Blog 的简写(取首字母可能略显尴尬,,,)。知识是需要沉淀的,当时的收获对我来说并非是收获,我需要的是“你存在,我深深地脑海里”这样的收获,而博客恰恰是知识沉淀的好方法之一。它好在哪里?   复用性。项目中碰到一件棘手的问题,解决它存在着两种不同的流程,第一种:灵感突现,具体时间不定;第二种:经过各种渠道搜集解决方案==>理解他人的解决思路==>取最优解决方案==>转化为自己的思想==>实际运用到项目中,具体时间因问题出现频率而定。当然存在着两种流程混合解决实际问题的情况。在花费巨大的精力与时间解决问题后,没有及时的总结,或者是简单的进行记录,但在很久之后的某天发现了同样的问题,或许淡忘了曾经碰到过这个问题,或许在自己的笔记库中找到了记录却发现步骤简陋而须重新思考某些点,相同的过程再次投入了几乎相同的时间,这是在浪费生命。博客中的文章不仅是写给自己,也是写给读者,在写解决方案时就考虑了如何让“小白”也可以快速读懂并且快速运用的情况,那么经过时间冲刷后的自己再读本篇文章时是否就节省了时间去做更有意义的事,例如:项目中需要一个功能点,当时写了一个功能函数,之后的项目中又碰到同样的功能点,我们就可以在自己的函数库中调用此方法进行快速实现。这其实就是解决方案的复用,又好比如在持续写有价值的博客过程中,吸引了越来越多的读者,而每次阅读都相当于对此篇文章的调用,而这又是话语的复用······   长期的产物。 blog 本意是网络日志,详细介绍过程、经历或者是事件的记录,也就是说需要长期的投入才能成为一名合格的 blogger ,走在这条路上的我将会把这3分钟的热度转化为100%的热情,从而激励我不断地高效学习。说些题外话:从2010年开始真正接触“浮躁”这个词,来源于我初中的第二位班主任————恩师肖凌云,正值叛逆期的我,总是被说着太浮躁了,了解到意思的我就是不认。做事顾头不顾尾的我没有午睡习惯,充分的玩耍着,坐不住,有几次路过办公室,总看到老师在写些什么,后来才知道在为我们中考找经典题型,一年多的时间,没有断过,很是触动,期间我也开始了我的第一次学习计划制定,周内每天凌晨3点到4点起床,晚上9点之前睡觉,持续了5年,渐渐的心沉稳起来,才真正明白了浮躁是什么样的。   教(jiào)学。教(jiāo)是学的最好方法,教(jiāo)学的过程也是对问题深层次剖析的过程,当可以处理这个问题衍生的所有问题时便是彻底的学会了,这便是教(jiào)学。博客可以使问题开源,让更多的人可以通过平台抒发自己对事物的提问与见解,在提问与见解的过程中收获个人所得不到的东西。经常碰到有些人说懂了,但你要问他的时候,又给你说不清楚,写也写不清楚,淡然的一笑,想起了小时候的自己,何不尝试着讲一讲,看看哪里讲不清楚,将这个点拿出来研究与讨论,清楚了便清楚了。在你为什么应该(从现在开始就)写博客中看到这么一个诡异的故事:TopLanguage上的一位朋友sagasw曾经讲了这样一个小故事:据说在某个著名软件公司里,开发组的桌上会放着一只小熊,大家互相问问题之前,先对着小熊把问题说一遍,看能不能把问题描述的清晰,基本上说的比较有条理以后,答案也就随之而来了。为什么说它诡异呢?因为我从很多地方看到这个故事,却从未发现这家著名软件公司是什么名字。   博客的优点暂时就发现了这么多,接下来谈谈暗时间————没有产生结果的时间。西方统计学家指出,假如一个人的寿命为60岁,那么他总共有21900天。一生时间的用途分别为:睡眠20年(7300天),吃饭6年(2190天),穿衣和梳洗5年(1825天)上下班和旅行5年(1825天),娱乐8年(2920天),生病3年(1095天),等待3年(1095天),打电话1年(365天),照镜子70天,揩鼻涕10天。最后只剩下8年285天用来做事情。当然每个人都不尽相同。接下来的日子,我会将暗时间利用起来,去丰富我的眼界,继而丰富我的博客,不知道是否会在未来的某一天,我的博客也会对其他人产生感触,就像末鹏哥的博客一样,打动着一批又一批的读者。   首先定个小目标,每周至少一更,每三个月一次总结,对不足的地方进行修正, SumBlog 就这样开始了!!!","updated":"2020-07-07T21:45:28.636Z","tags":[{"name":"前言","slug":"前言","permalink":"http://huishanyi.club/tags/前言/"},{"name":"随笔","slug":"随笔","permalink":"http://huishanyi.club/tags/随笔/"}]}]