394 lines
122 KiB
JavaScript
394 lines
122 KiB
JavaScript
|
export default [
|
|||
|
{
|
|||
|
url: '/api/posts',
|
|||
|
method: 'get',
|
|||
|
response: () => {
|
|||
|
return {
|
|||
|
code: 0,
|
|||
|
message: 'ok',
|
|||
|
data: [
|
|||
|
{
|
|||
|
id: 38,
|
|||
|
title: '关于Vite配置preprocessorOptions.scss.additionalData全局引入scss文件无效问题',
|
|||
|
author: 'Ronnie',
|
|||
|
category: 'Vite',
|
|||
|
description: '在vite项目中,有时候我们需要全局引入css变量、scss变量...',
|
|||
|
content:
|
|||
|
'在vite项目中,有时候我们需要全局引入css变量、scss变量,或者引入全局scss样式文件,vite提供了以下这种配置方式\n\n```js\n//vite.config.js\ncss: {\n preprocessorOptions: {\n //define global scss variable\n scss: {\n additionalData: `@import \'@/styles/variables.scss\';`,\n },\n },\n}\n```\n\n这种写法没有任何问题,并且我已经在一些项目中实践过了,可有一次我创建新项目的时候却无效了,在浏览器上也没有看到任何相关的样式,但是在main.js中引入又是正常的\n\n我先是排查写法和路径是否有问题,然后排查sass或者vite的版本是否有问题,排查几个小时下来发现都没有问题,纳闷不已,唯一能确定的是vite的问题\n\n于是我就想,也许别人也碰到过这种问题,当我找遍各大博客网站都没答案后(一大堆妥协说直接在main.js引入就好的),我准备去Vite仓库提各Issue\n\n当我尝试查一下有没有类似的Issue时,发现竟然有好几个类似的Issue,还是关闭状态,难道这个问题已经解决了?我一个一个点开看,终于在其中一个Issue中找到了答案\n\n[#issue5682](https://github.com/vitejs/vite/issues/5682#issuecomment-968713998)\n\n![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/876fcf83012f49d6b768e2f919e33031~tplv-k3u1fbpfcp-watermark.image?)\n原来这不是一个bug,只有在main.js引入一个其他scss文件或者在.vue文件中使用<style lang="scss"><style>,并且里面有内容,则 scss.additionalData 配置的全局scss文件就可以正确引入了,还建议我们在 scss.additionalData 引入的文件最好只写scss变量,别写css变量,因为css变量是运行时属性\n\n至此,这个问题算是圆满解决了',
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2022-02-18T04:07:47.000Z',
|
|||
|
updateDate: '2022-03-06T05:26:12.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 37,
|
|||
|
title: 'Vite+Vue3+NaiveUI+Pinia搭建一套优雅的后台管理模板,真香',
|
|||
|
author: 'Ronnie',
|
|||
|
category: 'Vite,Vue3',
|
|||
|
description:
|
|||
|
'趁着春节放假,完善了一下春节前自己搭建的后台管理模板的文档,在完善文档的同时又删改了部分不是bug...',
|
|||
|
content:
|
|||
|
'趁着春节放假,完善了一下春节前自己搭建的后台管理模板的文档,在完善文档的同时又删改了部分不是bug的代码,讲真,有强迫症和代码洁癖的我搭建这套模板的进度属实是有点慢了,但是终究还是完成了,尽管这是第一个版本\n\n\n*首先申明,这个模板参考了多个流行的Vue3后台管理模板,主要是[*Vben Admin*](https://github.com/anncwb/vue-vben-admin),毫无疑问这是一个非常优秀且流行的后台管理模板,但是对新手并不友好,学习成本较高,甚至对一个中级前端来说要直接上手二次开发也是有一定难度的*\n\n## 简介\n\nVue Naive Admin,一个基于 Vue3.0、Vite、Naive UI 的后台管理模板,相较于其他比较流行的后台管理模板,此项目相对简洁、轻量,没有集成 TypeScript,没有集成国际化,没有集成复杂的主题配置,学习成本非常低,对新手极其友好。不过麻雀虽小五脏俱全,权限、Mock、菜单、axios 封装、pinia、项目配置、样式配置、环境配置,以及一些经常用的基础组件封装等等这些该有的都有,经过参考多个 vue3 后台管理模板后以最简洁优雅的方式实现,非常适用于中小型项目或者个人项目,当然,以此模板进行二次封装改造用于大型项目也未尝不可。\n\n\n## 为什么要开发这个模板\n\n1. Vue3 和 Vite 已经趋于成熟,学习 vite 和 vue3 非常有必要,通过开发模板进行学习是一个很好的方式,事实也证明我确实从中获益良多\n2. 目前主流的 Vue3+Vite 后台管理模板都相对复杂,甚至感觉有点花里胡哨(没有贬低的意思,大部分的架构设计都很优秀,只是觉得集成了太多不实用的东西)\n3. 自己搭的模板开发起来才最顺手。本人很反感拿别人的模板直接上手开发,如果非要拿别人的模板开发也会尽量先吃透再用,不吃透就没有代码的掌控感和安全感\n\n## 功能\n\n- ',
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2022-02-10T04:05:35.000Z',
|
|||
|
updateDate: '2022-02-10T04:05:35.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 36,
|
|||
|
title: '使用纯css优雅配置移动端rem布局',
|
|||
|
author: 'Ronnie',
|
|||
|
category: '移动端,Css',
|
|||
|
description: '通常配置rem布局会使用js进行处理,比如750的设计稿会这样...',
|
|||
|
content:
|
|||
|
'通常配置rem布局会使用js进行处理,比如750的设计稿会这样配置\r\n\r\n```javascript\r\nfunction responseAdjust(width = 750) {\r\n var htmlDom = document.documentElement\r\n htmlDom.style.fontSize = htmlDom.clientWidth / width * 100 + "px"\r\n}\r\nresponseAdjust()\r\nwindow.onresize = function() {\r\n return responseAdjust()\r\n}\r\n```\r\n\r\n如果需要配置响应式断点,如屏幕宽度大于600px或者小于300px时不必再响应式了,可以这样配置(相信我,这样的场景还是很多的,比如移动端的H5网站需要在PC端也能正常显示)\r\n\r\n```javascript\r\nfunction responseAdjust(width = 750, minWidth = 300, maxWidth = 450) {\r\n var htmlDom = document.documentElement\r\n var clientWidth = htmlDom.clientWidth\r\n if(clientWidth > maxWidth) {\r\n clientWidth = maxWidth\r\n }\r\n if(clientWidth < minWidth) {\r\n clientWidth = minWidth\r\n }\r\n htmlDom.style.fontSize = clientWidth / width * 100 + "px"\r\n}\r\nresponseAdjust()\r\nwindow.onresize = function() {\r\n return responseAdjust()\r\n}\r\n```\r\n\r\n虽然代码不多,但总觉得不是很优雅,这些事能否交给css去处理呢。其实是可以的,而且还很优雅\r\n\r\n> 先上个最简单的\r\n\r\n```css\r\nhtml {\r\n font-size: calc(100vw / 7.5);\r\n}\r\n```\r\n\r\n关键在于calc和vw属性,calc代表计算,vw代表窗口的宽度的比例,即100vw表示100%的窗口宽度\r\n\r\n> 再结合媒体查询做断点处理\r\n\r\n```css\r\nhtml {\r\n font-size: calc(100vw / 7.5);\r\n}\r\n\r\n@media screen and (min-width: 600px) {\r\n html {\r\n font-size: calc(600px / 7.5);\r\n }\r\n}\r\n\r\n@media screen and (max-width: 300px) {\r\n html {\r\n font-size: calc(300px / 7.5);\r\n }\r\n}\r\n```\r\n\r\n*是不是瞬间觉得优雅了呢,效果跟js处理方式是一样的*',
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-11-04T04:03:36.000Z',
|
|||
|
updateDate: '2021-11-04T04:03:36.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 35,
|
|||
|
title: 'Vue2&Vue3项目风格指南',
|
|||
|
author: 'Ronnie',
|
|||
|
category: 'Vue',
|
|||
|
description: '总结的Vue2和Vue3的项目风格',
|
|||
|
content:
|
|||
|
'### 1. 命名风格\n\n> 文件夹如果是由多个单词组成,应该始终是横线连接 (kebab-case)。\n\n防止有些文件系统对大小写不敏感出现问题。\n\n\n> 组件名应该始终由多个单词组成,除了根组件 App,以及 <transition>、<component> 之类的 Vue 内置组件。\n\n这样做可以避免与现有以及未来的 HTML 元素产生冲突,因为所有的 HTML 元素名称都是单个单词的。\n\n> 单文件组件的文件名始终是横线连接 (kebab-case)。\n```\nmy-component.vue\n```\n\n> 和父组件紧密耦合的子组件应该以父组件名作为前缀命名。\n```\ncomponents/\n|- TodoList.vue\n|- TodoListItem.vue\n└─ TodoListItemButton.vue\n```\n\n> 应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V。\n```\ncomponents/\n|- AppButton.vue\n|- AppTable.vue\n|- AppIcon.vue\n```\n\n> 组件名称应该以高阶的 (通常是一般化描述的) 单词开头,并以描述性的修饰词结尾。\n```\ncomponents/\n|- SearchButtonClear.vue\n|- SearchButtonRun.vue\n|- SearchInputQuery.vue\n|- SearchInputExcludeGlob.vue\n|- SettingsCheckboxTerms.vue\n|- SettingsCheckboxLaunchOnStartup.vue\n```\n\n\n### 2. 代码风格\n> 在单文件组件中没有内容的组件应该是自闭合的。\n```html\n<my-component />\n```\n\n> 始终以 key 配合 v-for。\n\n> 永远不要在一个元素上同时使用 v-if 和 v-for。\n\n> 在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case。\n```js\nprops: {\n greetingText: String\n}\n\n<my-component greeting-text="hi" />\n```\n> prop 的定义应该尽量详细,至少指定其类型。\n\n> 组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。\n\n> 应该把复杂计算属性尽可能多地分割为更简单的计算属性。\n\n\n> 模板中使用双引号,js中使用单引号\n```html\n<div class="wrapper"></div>\n```\n```js\nlet str = \'hello!\'\n```\n\n\n> 元素 (包括组件) 的 attribute 应该有统一的顺序。 \n- 定义:is\n- 列表渲染:v-for\n- 条件:v-if、v-else-if、v-else、v-show、v-cloak\n- ref、key\n- v-model\n- 其他 Attribute\n- v-on:@\n- 内容:v-html、v-text\n\n```html\n<my-component\n is\n v-for="item in list"\n v-if="true"\n v-show="false"\n v-cloak="false"\n ref="myCom"\n :key="item.id"\n v-model="value"\n style\n class\n @click="handleItemClick"\n v-html="vHtml"\n v-text="text"\n/>\n<!-- 注意,有些属性是不允许同时出现的,此处只是为了更方便展示 -->\n```\n\n> 组件选项应该有统一的顺序。 \n- name\n- components\n- provide/inject\n- inheritAttrs、 props、 emits、 expose\n- setup\n- data\n- computed\n- watch\n- 生命周期事件:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、activated、deactivated、beforeUnmount、unmounted、errorCaptured、renderTracked、renderTriggered\n- methods\n- template/render\n\n\n[参考链接:Vue3风格指南](https://v3.cn.vuejs.org/style-guide) \n[参考链接:Vant V3风格指南](https://vant-contrib.gitee.io/vant/v3/#/zh-CN/style-guide)',
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-10-25T08:57:47.000Z',
|
|||
|
updateDate: '2022-02-28T04:02:39.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 34,
|
|||
|
title: '关于无感刷新token,我也有话说',
|
|||
|
author: 'Ronnie',
|
|||
|
category: 'JavaScript',
|
|||
|
description:
|
|||
|
'最近在掘金看到一篇讲无感刷新token的文章,正好最近我的个人网站奇思Admin也处理了一个刷新token的问题,而我的解决方案跟这篇文章讲到的所有方案都不一样',
|
|||
|
content:
|
|||
|
"最近在掘金看到一篇讲无感刷新token的文章,正好最近我的个人网站奇思Admin也处理了一个刷新token的问题,而我的解决方案跟这篇文章讲到的所有方案都不一样\n\n所以,我也想借这个话题来写(shui)一写(shui),于是,就有了这篇文章\n\n其实jwt早就不是一个新鲜的词了,你可能没用过,但多少肯定了解过,所以这里就不赘述jwt的概念了,想了解的可以自行google或者百度\n\n首先,为什么需要刷新token呢\n\n> 测试:前端,快帮我看一下我这页面怎么突然就跳到登录页了呢,我还没测嗨呢 \n> 前端:我看看...哦,是token过期了 \n> 测试:??这不合理啊,明明我前一秒还在操作呢 \n> 前端:我想想... \n> ... \n> 前端:后端,你能不能把给我的token过期时间设置的长一点 \n> 后端:不行啊,这样做不安全,但我可以给你一个刷新token的接口,这样你就可以拿到一个重新计时的新token \n> 前端:好吧,我想想... \n\n然后,要怎么刷新token呢,并且做到让用户无感知的刷新,很明显这个难题最终还是甩给了前端\n\n来看一下这篇文章的作者讲到了哪几种方案\n\n> ![方法一](https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/20211015144103.png)\n\n> ![方法二](https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/20211015145428.png)\n\n个人觉得方法二其实还是比较稳妥的,所谓的资源浪费和性能消耗指的就是定时器,但这点性能消耗其实并不多,完全没必要看这么重\n\n> ![方法三](https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/20211015150200.png) \n这个方法是这篇文章的作者自己提供并使用的方案,并给出了详细的实现代码,感兴趣的可以看下[原文](https://juejin.cn/post/6983582201690456071)\n\n个人觉得这个方案是上面几种方案中最不可取的,因为这个方案方式为了防止token过期后同时发起两个或者两个以上的请求,将请求存进了队列中,并通过Promise的Pending状态阻塞多个请求,这不仅增加代码复杂度,还很不优雅,最重要的是token过期后会让接口响应时间延长,容易造成请求超时,用户其实是很容易感知到的,体验并不好\n\n> 方法四(本人使用的方案)\n\n通过函数节流的方式刷新token,在请求拦截器中去尝试调用刷新token的方法,如果和上次刷新token的时间间隔不够则不刷新token,这样的话可以保证每次F5刷新或者进入页面的时候刷新一次token,在页面持续操作也能保证每隔一段时间刷新一次token,离开页面后也不会继续刷新token,基本不会有太大的开销,话不多说,上代码\n\n> /utils/index.js\n```js\n/**\n * 节流函数\n * @param {Function} fn \n * @param {Number} wait \n * @returns {Function}\n */\nexport function throttle(fn, wait) {\n var context, args\n var previous = 0\n\n return function () {\n var now = +new Date()\n context = this\n args = arguments\n if (now - previous > wait) {\n fn.apply(context, args)\n previous = now\n }\n }\n}\n```\n\n> /utils/cookie.js\n```js\nimport Cookies from 'js-cookie'\nimport { refreshToken } from '@/api/auth'\nimport { throttle } from '@/utils'\n\nconst TOKEN_CODE = 'access_token'\n\nexport function setToken(token) {\n return Cookies.set(TOKEN_CODE, token, { expires: new Date(new Date().getTime() + 2 * 60 * 60 * 1000) })\n}\n\n// 防止频繁刷新token,对此方法节流处理,30分钟间隔\nexport const refreshAccessToken = throttle(async function () {\n try {\n // 请求刷新token接口\n const res = await refreshToken()\n if (res.code === 0) {\n setToken(res.data.token)\n }\n } catch (error) {\n console.error(error)\n }\n}, 1000 * 60 * 30)\n```\n\n> /utils/request.js\n```js\nimport axios from 'axios'\n\nconst service = axios.create({\n timeout: 120000,\n baseURL: '/',\n})\n\nservice.interce
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-10-14T10:02:40.000Z',
|
|||
|
updateDate: '2021-10-26T07:10:26.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 29,
|
|||
|
title: '使用css优雅配置移动端rem布局',
|
|||
|
author: '张传龙',
|
|||
|
category: 'Css',
|
|||
|
description: '使用css优雅配置移动端rem布局',
|
|||
|
content:
|
|||
|
'配置rem布局通常会使用js进行处理,比如750的设计稿会这样配置\n\n```javascript\nfunction responseAdjust(width = 750) {\n var htmlDom = document.documentElement\n htmlDom.style.fontSize = htmlDom.clientWidth / width * 100 + "px"\n}\nresponseAdjust()\nwindow.onresize = function() {\n return responseAdjust()\n}\n```\n\n如果需要配置响应式断点,如屏幕宽度大于600px或者小于300px时不必再响应式了,可以这样配置(相信我,这样的场景还是很多的,比如移动端的H5网站需要在PC端也能正常显示)\n\n```javascript\nfunction responseAdjust(width = 750, minWidth = 300, maxWidth = 450) {\n var htmlDom = document.documentElement\n var clientWidth = htmlDom.clientWidth\n if(clientWidth > maxWidth) {\n clientWidth = maxWidth\n }\n if(clientWidth < minWidth) {\n clientWidth = minWidth\n }\n htmlDom.style.fontSize = clientWidth / width * 100 + "px"\n}\nresponseAdjust()\nwindow.onresize = function() {\n return responseAdjust()\n}\n```\n\n虽然代码不多,但总觉得不是很优雅,这些事能否交给css去处理呢。其实是可以的,而且还很优雅\n\n> 先上个最简单的\n\n```css\nhtml {\n font-size: calc(100vw / 7.5);\n}\n```\n\n关键在于calc和vw属性,calc代表计算,vw代表窗口的宽度的比例,即100vw表示100%的窗口宽度\n\n> 再结合媒体查询做断点处理\n\n```css\nhtml {\n font-size: calc(100vw / 7.5);\n}\n\n@media screen and (min-width: 600px) {\n html {\n font-size: calc(600px / 7.5);\n }\n}\n\n@media screen and (max-width: 300px) {\n html {\n font-size: calc(300px / 7.5);\n }\n}\n```\n\n是不是瞬间觉得很优雅了呢,效果跟js处理方式是一样的\n',
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-07-08T11:06:19.000Z',
|
|||
|
updateDate: '2021-09-29T02:08:01.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 28,
|
|||
|
title: '如何优雅的给图片添加水印',
|
|||
|
author: '张传龙',
|
|||
|
category: 'JavaScript',
|
|||
|
description: '优雅的给图片添加水印',
|
|||
|
content:
|
|||
|
"我之前写过一篇文章记录了一次上传图片的优化史,[计一次vant组件图片上传优化史](http://www.qszone.com/blog/list/23) \n还是上次的项目,现在有了一个新需求,就是要给上传的图片添加水印,水印的内容是图片的时间+上传人+系统Logo+系统名称 \n这个需求还是比较难处理的,在这之前添加水印一直都是后端处理的,现在希望可以尝试一下在前端处理 \n而且现在项目里使用的是OSS直接上传的方式,为了减轻服务器的压力,上传的操作是不经过自己的服务器中转的 \n既然没办法甩给后端处理,那就开干吧,想一想尝试做没做过的事还是挺兴奋的 \n\n> 先来第一版,使用canvas填充文字的方式\n```js\n/**\n * 添加水印\n * @param {file} 上传的图片文件\n */\nasync function addWaterMarker(file) {\n // 先将文件转成img标签\n let img = await blobToImg(file)\n return new Promise((resolve, reject) => {\n // 创建canvas画布\n let canvas = document.createElement('canvas')\n canvas.width = img.width\n canvas.height = img.height\n let ctx = canvas.getContext('2d')\n ctx.drawImage(img, 0, 0)\n\n // 设置填充字号和字体,样式,这里设置字体大小根据canvas的宽度等比缩放,防止大图片生成的水印很小的问题\n ctx.font = `${canvas.width * 0.05}px 宋体`\n ctx.fillStyle = \"red\"\n // 设置右对齐\n ctx.textAlign = 'right'\n // 在指定位置绘制文字\n ctx.fillText('我是水印1', canvas.width - 100, canvas.height - 100)\n ctx.fillText('我是水印2', canvas.width - 100, canvas.height - 50)\n\n // 将canvas转成blob文件返回\n canvas.toBlob(blob => resolve(blob))\n })\n}\n\n/**\n* blob转img标签\n*/\nfunction blobToImg(blob) {\n return new Promise((resolve, reject) => {\n let reader = new FileReader()\n reader.addEventListener('load', () => {\n let img = new Image()\n img.src = reader.result\n img.addEventListener('load', () => resolve(img))\n })\n reader.readAsDataURL(blob)\n })\n}\n```\n\n水印确实加上去了,如果水印只是简单布局的文字,这也可以用 \n但思前想后总觉得不优雅,加上水印内容比较复杂,还有图片,靠这种方式要实现还是够呛的 \n\n> 经过苦苦折腾,第二版搞出来了,水印内容通过html转成图片,然后把水印图片合成到上传的图片中\n\n```js\n/**\n * 添加水印\n * @param {file} 上传的图片文件\n * @param {el} 水印内容html\n */\nasync function addWaterMarker(file, el = '#markImg') {\n // 先将文件转成img标签\n let img = await blobToImg(file)\n return new Promise(async (resolve, reject) => {\n try {\n // 创建canvas画布\n let canvas = document.createElement('canvas')\n canvas.width = img.width\n canvas.height = img.height\n let ctx = canvas.getContext('2d')\n ctx.drawImage(img, 0, 0)\n\n // 创建水印图片canvas画布\n let markCanvas = document.createElement('canvas')\n // 创建水印图片canvas\n const markImg = await createMarkImg(document.querySelector(el))\n //让水印根据图片尺寸等比缩放,默认宽度设成1000\n let zoom = canvas.width / 1000\n let markCtx = markCanvas.getContext('2d')\n // 缩放水印图片canvas\n markCtx.scale(zoom, zoom)\n\n // 将水印画布的宽高设置成缩放后的水印图片canvas的宽高\n markCanvas.width = markImg.width\n markCanvas.height = markImg.height\n // 将水印图片canvas填充到水印画布中\n markCtx.drawImage(markImg, 0, 0)\n\n // 将水印画布canvas填充到canvas画布\n ctx.drawImage(markCanvas, canvas.width - markCanvas.width, canvas.height - markCanvas.height, markCanvas.width, markCanvas.height)\n canvas.toBlob(blob => resolve(blob))\n } catch (error) {\n reject(error)\n }\n })\n}\n\n/**\n* blob转img标签\n*/\nfunction blobToImg(blob) {\n return new Promise((re
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-06-24T18:46:19.000Z',
|
|||
|
updateDate: '2021-09-23T07:51:22.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 27,
|
|||
|
title: '前端&Vue开发vscode配置指南',
|
|||
|
author: '张传龙',
|
|||
|
category: '其他',
|
|||
|
description: '自我总结配置极尽详细的前端&Vue开发vscode配置',
|
|||
|
content:
|
|||
|
'```json\n{\n "workbench.startupEditor": "none", // 不显示vscode启动欢迎页\n "workbench.editor.enablePreview": false, // 打开文件不覆盖\n "workbench.colorTheme": "One Monokai", // 主题\n "workbench.iconTheme": "vscode-great-icons", // 图标\n "security.workspace.trust.untrustedFiles": "open", //始终允许不受信任的文件引入受信任的工作区,而不显示提示\n "terminal.integrated.tabs.enabled": true, // 控制终端选项卡显示为侧边列表\n "liveServer.settings.donotShowInfoMsg": true, // 底部显示live serve操作信息,需安装live serve才生效\n "editor.tabSize": 2,\n "editor.fontFamily": "Fira Code", // 字体。需安装Fira Code字体才生效\n "editor.fontLigatures": true, // 使用变体连体字\n "editor.fontSize": 16,\n "editor.fontWeight": 400,\n "editor.formatOnSave": true, // 保存的时候是否格式化\n "editor.formatOnType": false, // 在键入一行后是否格式化\n "editor.formatOnPaste": false, // 在粘贴时是否格式化\n "javascript.format.semicolons": "remove", // 去除js的分号\n "javascript.format.insertSpaceBeforeFunctionParenthesis": false, // 让函数(名)和后面的括号之间加个空格\n "typescript.format.semicolons": "remove", // 处理ts的分号\n "typescript.format.insertSpaceBeforeFunctionParenthesis": false, // 让函数(名)和后面的括号之间加个空格\n /*\n * 在不受支持的语言与Emmet支持的语言之间添加映射,表示按映射的语言处理不受支持的语言\n */\n "emmet.includeLanguages": {\n "vue": "html",\n "vue-html": "html",\n "wxml": "html"\n },\n /*\n * vetur格式化\n */\n "vetur.format.defaultFormatter.html": "prettier",\n "vetur.format.defaultFormatter.css": "prettier",\n "vetur.format.defaultFormatter.postcss": "prettier",\n "vetur.format.defaultFormatter.scss": "prettier",\n "vetur.format.defaultFormatter.less": "prettier",\n "vetur.format.defaultFormatter.stylus": "stylus-supremacy",\n "vetur.format.defaultFormatter.js": "prettier",\n "vetur.format.defaultFormatter.ts": "prettier",\n "vetur.format.defaultFormatter.sass": "sass-formatter",\n "vetur.format.defaultFormatterOptions": {\n "prettier": {\n "singleQuote": true, // 单引号\n "semi": false, // 句尾不加;\n "arrowParens": "avoid", // allow paren-less arrow functions 箭头函数的参数使用圆括号\n "htmlWhitespaceSensitivity": "ignore", //忽略自闭合标签\n "trailingComma": "none", // 对象最后一个属性是否加 (,) 逗号\n // "eslintIntegration": true,\n // "end_with_newline": false, //是否允许以换行符结尾\n }\n }\n}\n/*\n* eslint的配置,当eslint格式化与vscode和vetur格式化冲突时启用\n* 需在项目根路径建立.vscode/settings.json,并添加如下配置\n\n{\n "editor.formatOnSave": false, // 取消保存的时候自动格式化\n "eslint.validate": [\n "javascript",\n "javascriptreact",\n "html",\n "vue",\n "typescript",\n "typescriptreact"\n ],\n //每次保存的时候按照eslint格式进行修复\n "editor.codeActionsOnSave": {\n "source.fixAll.eslint": true\n }\n}\n*/\n\n```',
|
|||
|
isRecommend: false,
|
|||
|
isPublish: false,
|
|||
|
createDate: '2021-06-17T11:18:19.000Z',
|
|||
|
updateDate: '2022-03-02T09:14:49.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 26,
|
|||
|
title: '前端缓存的理解',
|
|||
|
author: '张传龙',
|
|||
|
category: 'Http',
|
|||
|
description: '谈谈前端缓存的理解',
|
|||
|
content:
|
|||
|
'> 背景\n\n公司有个vue-cli3移动端web项目发版更新后发现部分用户手机在钉钉内置浏览器打开出现了缓存,就是页面依然显示更新前的内容\n\n了解vue-cli项目的都知道,项目打包使用的是webpack,通常webpack打包的时候是会给文件加上一串hash值的,为的就是防止更新的时候出现浏览器缓存问题\n\n我检查了一下webpack的配置,使用的是chunkhash,它会分析入口文件的依赖,构建对应的chunk,并生成对应的hash值,如果入口文件及其依赖没有发生改变,则生成的hash值也不会变,换言之,如果发生改变则hash值必然会随之改变\n\n接着我又检查了buid生成的dist文件夹下的文件,对比历史版本的文件发现确实有部分文件的哈希值并没有发生改变,而正好修改过的文件的哈希值是变化了的\n\nso,排除项目构建的问题\n\n接下来我猜测会不会是入口文件index.html的问题,经过检查我发现三个跟缓存有关系的meta标签\n\n```html\n<meta http-equiv="Expires" content="0">\n<meta http-equiv="Pragma" content="no-cache">\n<meta http-equiv="Cache-control" content="no-cache">\n```\n三个标签都是用于控制浏览器的强制缓存,起到的作用都是一样的-禁用浏览器强制缓存\n\n### 强缓存\n\nExpires:是http 1.0的写法,值是一个GMT格式或者数字格式的时间,代表缓存到期的时间,如果值是数字则代表缓存剩余时间,单位是秒,0和-1代表的是本地缓存0秒就过期了\n\nPragma:也是http 1.0,且只有IE浏览器才能识别,有以下几个值\n- no-cache:表示禁止浏览器读取本地缓存\n- public:浏览器和缓存服务器都可以缓存页面信息\n- no-store:请求和响应的信息都不应该被存储在对方的磁盘系统中;\n- must-revalidate:对于客户机的每次请求,代理服务器必须向服务器验证缓存是否过时\n\nCache-control:这是http 1.1的实现,它区别于Expires的是没有采用*具体的过期时间点*这个方式,而是采用过期时长来控制缓存,对应的字段是max-age,单位也是秒\n```html\n<meta http-equiv="Cache-control" content="max-age=3600">\n```\n它还有别的属性:\n- private: 表示只有浏览器能缓存,中间的代理服务器不能缓存。\n- no-cache:禁用缓存(这几个标签的禁用缓存都是指跳过当前的强缓存,直接进入协商缓存)\n- no-store:不进行任何形式的缓存。\n- s-maxage:跟max-age很像,但是区别在于s-maxage是针对代理服务器的缓存时间。\n- must-revalidate: 当缓存过期的时候,必须回到源服务器验证。\n \n注:当Expires和Cache-Control同时存在的时候,Cache-Control会优先考虑。\n\n当强缓存失效或被禁用时就会进入协商缓存了\n\n### 协商缓存\n\n强缓存失效之后,浏览器在请求头中携带相应的缓存tag来向服务器发请求,由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存\n\n缓存tag分两种:Last-Modified 和 ETag\n\n#### Last-Modified\n\n最后修改时间,浏览器第一次向服务器请求某个资源时,服务器会在响应头中加上这个字段\n\n浏览器接收后会在下一次请求的时候携带If-Modified-Since字段,值就是上一次请求接收到的文件的最后修改时间\n\n服务器再根据请求头中的If-Modified-Since的值和服务器该资源的最后修改时间对比,如果该资源的最后修改时间更大则表示修改更新了,返回新的资源,反之则返回304告诉浏览器资源没有更新,直接使用缓存就行了\n\n#### ETag\n\nETag 是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变,服务器通过响应头把这个值给浏览器\n\n浏览器接收到ETag的值,会在下次请求时,将这个值作为If-None-Match这个字段的内容,并放到请求头中,然后发给服务器\n\n服务器接收
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-06-10T18:51:19.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 24,
|
|||
|
title: '使用jQuery的load方法帮女朋友实现套娃Html',
|
|||
|
author: '张传龙',
|
|||
|
category: 'JavaScript',
|
|||
|
description: '最近女朋友刚入职新公司,接到的第一个任务就是将一个网站所有的页面合并成一个页面',
|
|||
|
content:
|
|||
|
'最近女朋友刚入职新公司,接到的第一个任务就是将一个网站所有的页面合并成一个页面\n\n女朋友:我接到一个jQuery项目,有大概七八个页面,好在是静态页面,全部合一起渲染展示没有多大问题\n\n我:挺简单的嘛\n\n女朋友:去,虽然不难,但是每一个页面的html代码都挺多的,全部合一起太痛苦了,而且也不方便维护\n\n我:那倒是,苦力活啊\n\n女朋友:如果能像Vue那样组件化引入就太好了\n\n我:这可难了,而且现在改用Vue写也来不及了啊...\n\n说完我灵机一动,想到jQuery有个load方法好像可以实现,立马改口\n\n我:不过用jQuery应该也可以,你看...\n\n> test1.html\n\n```html\n<div>Html</div>\n```\n\n> index.html\n\n```html\n<div class="include" file="test1.html"></div>\n\n<script>\n var forEach = Array.prototype.forEach\n\n function loadHtml() {\n var _includeEles = $(".include")\n forEach.call(_includeEles,function(ele,index) {\n var url = $(ele).attr("file")\n if(url){\n $(ele).load(url,function(html){\n // 将加载的html内容放入当前dom的后面,然后删除当前dom,相当于将加载的内容替换原来的标签\n $(ele).after(html).remove()\n })\n }\n })\n }\n loadHtml()\n<script>\n```\n\n女朋友:太棒了,我试一下...\n\n...\n\n女朋友:你这方法用是好用,内容已经加载进来了,但不知道为什么我的轮播图不起作用了\n\n我:是不是你轮播图初始化的顺序不对\n\n女朋友:初始化代码明明是在加载代码之后执行的呀\n\n我一看还真是,原来jQuery的load方法是异步执行的,初始化轮播图的时候load方法还没结束呢,轮播图自然就不起作用了\n\n我:等等,我给你加个回调函数\n\n```js\nfunction loadHtml(cb) {\n var _includeEles = $(".include")\n var len = _includeEles\n forEach.call(_includeEles,function(ele,index){\n var url = $(ele).attr("file")\n if(url){\n $(ele).load(url,function(html){\n // 将加载的html内容放入当前dom的后面,然后删除当前dom,相当于将加载的内容替换原来的标签\n $(ele).after(html).remove()\n // 循环load结束后再执行回调方法cb\n if(index === len - 1) {\n cb()\n }\n })\n }else if(index === len - 1){\n cb()\n }\n })\n}\n\nloadHtml(function() {\n // 在此处执行轮播图初始化\n})\n```\n\n问题解决\n\n...\n\n第二天女朋友下班回来\n\n女朋友:如果我想在引入的html中再引入其他html,可以吗\n\n我:啊,这不是套娃吗\n\n女朋友:对啊,就是套娃啊,怎么,不喜欢了?\n\n我:a piece of cake,递归走起\n\n说做就做\n\n```js\n/**\n* @cb 回调函数\n* @invalidCount 无效地址数\n*/\nfunction loadHtml(cb, invalidCount = 0) {\n var _includeEles = $(".include")\n var len = _includeEles.length\n // 直到没有.include标签或者所有的.include标签都没有url时进行回调\n if (len === 0 || len === invalidCount) {\n cb()\n return\n }\n\n var forEach = Array.prototype.forEach\n forEach.call(_includeEles, function (ele, index) {\n var url = $(ele).attr("file")\n !url && invalidCount++\n if (url) {\n $(ele).load(url, function (html) {\n // 将加载的html内容放入当前dom的后面,然后删除当前dom,相当于将加载的内容替换原来的标签\n $(ele).after(html).remove()\n // 遍历结束再递归加载文件内容\n if (index === len - 1) {\n loadHtml(cb, invalidCount)\n }\n })\n } else if (index === len - 1) {\n loadHtml(cb, invalidCount)\n }\n })\n}\nloadHtml(function() {\n // do something\n})\n```\n\n女朋友微微一笑,完美\n',
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-05-26T15:26:06.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 23,
|
|||
|
title: '计一次vant组件图片上传优化史',
|
|||
|
author: '张传龙',
|
|||
|
category: 'JavaScript',
|
|||
|
description: '图片批量上传、图片压缩、前端上传至OSS',
|
|||
|
content:
|
|||
|
"> 背景\n\n项目使用的是vue-cli+vant,其中有个图片上传功能,一开始使用的是vant的 Uploader 组件\n\n组件用着很方便,既可以上传,又封装好了图片预览功能\n\n但有一个问题,就是部分安卓机限制不死单张上传,批量上传支持又不好,好在批量上传特殊处理一下也可以了\n\n首先在组件中添加属性 multiple=\"true\",然后处理after-read回调函数,在回调函数中判断上传的文件是是否是数组\n\n```js\n// 处理after-read函数\nhandleAfterRead(file, detail){\n // 判断file参数是否是数组\n if(Array.isArray(file)){\n //循环调用接口上传\n }else {\n //调用接口上传\n }\n}\n```\n\n后面发现限制不死文件类型、数量、大小,所以需要在after-read回调函数再次判断\n\n```js\n// 处理after-read函数\nhandleAfterRead(file, detail){\n // 判断file参数是否是数组\n if(Array.isArray(file)){\n //限制一次只能上传9张\n if (file.length > 9) {\n console.log('一次最多上传9张,请分批次上传!')\n return\n }\n\n //循环调用接口上传\n }else {\n // 限制只能上传图片类型文件\n if(!file.file.type.includes('image')){\n console.log('上传失败,只能上传照片!')\n return\n }\n // 限制上传图片大小不能超过10M\n if(!file.file.size >= 1024 * 1024 * 10){\n console.log('文件太大,不能超过10M!')\n return\n }\n\n //调用接口上传\n }\n}\n```\n\n加限制很顺利,但紧接着另一个问题又来了,那就是虽然加了上传单张图片的大小不能超过10M,但通过拍照上传的图片普遍都有3~5M\n\n所以上传的速度很慢,而且很容易就上传超时导致接口报错上传失败\n\n后面决定调用接口上传之前先对图片进行压缩,压缩使用vant推荐的compressorjs插件\n\n```shell\nnpm i compressorjs -S\n```\n\n```js\n// 处理after-read函数\nasync handleAfterRead(file, detail){\n // 判断file参数是否是数组\n if(Array.isArray(file)){\n //限制判断\n \n //循环压缩处理再调用接口上传\n\n //循环调用接口上传\n }else {\n // 限制判断\n \n //压缩处理\n const img = await compressor(file.file,0.2)\n\n //调用接口上传\n }\n}\n\n// 压缩方法\n// 先引入 compressorjs\nimport Compressor from 'compressorjs'\n/**\n* @ file 文件\n* @ quality 图片压缩质量\n*/\nfunction compressor(file,quality) {\n return new Promise(resolve => {\n new Compressor(file, {\n quality,\n success: resolve,\n error(err) {\n console.log(err.message)\n },\n })\n })\n}\n```\n\n至此,图片上传算是没有太大问题了,在前端上面也算是差不多优化到极致了,剩下的就是接口的问题了,如接口不稳定,接口处理缓慢等问题...\n\n最后再介绍一种前端直接上传至阿里云OSS的方法\n\n首先安装OSS的npm包\n\n```shell\nnpm i ali-oss -S\n```\n\n封装OSS(src/utils/oss.js)\n\n```js\nconst OSS = require('ali-oss')\nconst OSSConfig = {\n uploadHost: 'https://XXXXXX.oss-cn-shenzhen.aliyuncs.com',\n folder: 'test/',\n region: 'oss-cn-shenzhen',\n accessKeyId: 'XXXXXXXXXXXXXXXXX',\n accessKeySecret: 'XXXXXXXXXXXXXXXXXXXXXXXX',\n bucket: 'XXXXX',\n}\nfunction uploadOSS(file){\n return new Promise(async (resolve, reject) => {\n const fileName = `${OSSConfig.folder}${file.name}`\n const client = new OSS({\n region: OSSConfig.region,\n accessKeyId: OSSConfig.accessKeyId,\n accessKeySecret: OSSConfig.accessKeySecret,\n bucket: OSSConfig.bucket,\n })\n const res = await client.multipartUpload(fileName, file)\n if (res.name) {\n resolve({\n // 上传的文件名\n fileName: file.name,\n url: `${OSSConfig.uploadHost}/${fileName}`\n })\n } else {\n reject('OSS上传失败')\n }\n })\n}\nexport { uploadOSS }\n```\n\n使用\n\n```js\nimport { uploadOSS } from '@/utils/oss'\n\nconst {
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-05-24T11:28:41.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 21,
|
|||
|
title: 'git实用指令,持续更新...',
|
|||
|
author: '张传龙',
|
|||
|
category: 'Git',
|
|||
|
description: 'git实用指令,持续更新...',
|
|||
|
content:
|
|||
|
'## 分支\n\n```bash\n\n# 新建分支\ngit branch [branch-name]\n\n# 切换分支\ngit checkout [branch-name]\n\n# 新建并切换到新建分支\ngit checkout -b [branch-name]\n\n# 查看本地分支:\ngit branch\n\n# 查看所有分支(含远程分支)\ngit branch -a\n\n# 查看远程分支\ngit branch -r\n\n# 删除本地分支\ngit branch -D [branch-name]\n\n# 删除本地的远程分支\ngit branch -r -D [remote-name]/[branch-name]\n\n# 删除远程分支\ngit push [remote-name] -d [branch-name]\n\n# 取消合并\ngit merge --abort\n\n```\n',
|
|||
|
isRecommend: false,
|
|||
|
isPublish: false,
|
|||
|
createDate: '2021-05-13T12:19:31.000Z',
|
|||
|
updateDate: '2022-03-02T09:19:10.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 20,
|
|||
|
title: 'web常用原理知识点',
|
|||
|
author: '张传龙',
|
|||
|
category: 'Web',
|
|||
|
description: 'web常用原理知识点',
|
|||
|
content:
|
|||
|
'# 1. 浏览器的渲染过程\n> 渲染引擎首先通过网络获得所请求文档的内容\n1. 解析html为DOM树,解析Css构建Css树,并将标签转化为内容树种的DOM节点。\n2. 把DOM树和Css树整合起来生成渲染树。\n3. 渲染树构建好之后会执行布局过程,它将确定每个节点在屏幕上的确切坐标。\n4. 将渲染树绘制到屏幕上。遍历渲染树,使用UI后端层绘制每一个节点。\n\n\n# 2. HTTP报文组成部分\n1. 请求报文: 请求行、请求头、空行、请求体\n2. 响应报文: 状态行、响应头、空行、响应体\n\n> 请求报文\n1. 请求行: Http方法、请求地址、Http协议和版本\n2. 请求头:key-value值,告诉服务端需要的内容\n3. 空行:告诉服务端以下内容为请求体\n4. 请求体:数据部分\n\n> 响应报文\n1. 状态行:Http版本、响应状态码、状态码的文本描述\n2. 响应头(Response Header)\n3. 空行:告诉服务端以下内容为响应内容\n4. 响应体:响应的数据\n\n# 3. 什么是构造函数\n> 构造函数的本质是一个普通函数,它的特点是通过new关键字来调用,用来创建对象的实例\n\n# 4. 什么是原型和原型链\n原型模式是JS实现继承的一种方式。所有的函数都有一个prototype属性,通过new生成一个对象时,prototype会实例化为对象的属性。所有的引用类型都有一个__proto__指向其构造函数的prototype。\n原型链的话,指的就是当访问一个引用类型时,如果本身没有这个属性或方法,就会通过__proto__属性在父级的原型中找,一级一级往上找,直到最顶层为止。\n> 注:原型链最顶层是Object,它的prototype的__proto__指向为nulll\n\n# 5. 如何理解constructor属性\n所有函数的原型对象都有一个constructor属性指向函数本身\n```js\n[].__proto__.constructor // Array() { [native code] }\n\n```\n\n# 6. new操作符的执行过程\n1. 创建一个对象\n2. 将这个空对象的__proto__指向构造函数的prototype\n3. 将构造函数的this指向这个对象\n4. 执行这个构造函数的代码\n\n# 7. 如何判断一个变量是不是数组类型\n最完美的方法:toString Object.prototype.toString.call([]) \n或者也可以使用instanceof方法或者constructor属性\n```js\nObject.prototype.toString.call([]) // [object Object]\n[] instanceof Array //true\n[].constructor === Array //true\n```',
|
|||
|
isRecommend: false,
|
|||
|
isPublish: false,
|
|||
|
createDate: '2021-03-19T16:17:52.000Z',
|
|||
|
updateDate: '2022-03-02T09:19:08.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 19,
|
|||
|
title: 'Vue原理知识点',
|
|||
|
author: '张传龙',
|
|||
|
category: 'Vue',
|
|||
|
description: 'Vue原理知识点',
|
|||
|
content:
|
|||
|
'### 1. Vue的双向绑定原理,请手动写一个数据绑定\n核心是利用ES5的Object.defineProperty实现数据劫持,在getter中收集依赖,在setter中派发更新\n\n手写一个数据绑定\n```html\n<h3 id="text"></h3>\n<input id="input" type="text" />\n\n<script>\n let text = document.getElementById("text");\n let input = document.getElementById("input");\n let data = { value: "" };\n Object.defineProperty(data, "value", {\n set: function(val) {\n text.innerHTML = val;\n input.value = val;\n },\n get: function() {\n return input.value;\n }\n });\n input.onkeyup = function(e) {\n data.value = e.target.value;\n }\n</script>\n```\n\n### 2. Vue 项目时为什么要在列表组件中写 key,其作用是什么?\nVue项目中使用的是虚拟Dom,利用diff算法比对虚拟节点的变化,列表组件中使用key是为了提升diff(同级比较)的效率,相当于给每一项增加一个唯一索引,这样就可以很清楚的知道每一个列表的变化,如果不加key的话就只能一个个对比了,而且容易导致列表状态更新错乱问题。\n\n### 3. 为什么 Vuex 的 mutation 中不能做异步操作?\n因为修改state的函数必须是纯函数,即是统一输入就会统一输出,没有任何副作用,如果是异步的话则会导致state的修改不可预测\n\n### 4. 在Vue中,子组件为什么不可以修改父组件传递的Prop?\n为了保证数据的单向流动,防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解,这样做也便于对数据进行追踪,避免数据混乱。\n\n### 5. Vue的响应式原理中Object.defineProperty有什么缺陷?\nObject.defineProperty无法监控到数组下标的变化,导致直接通过下标修改数组值不能实时响应\n在Vue3.0中弃用了Object.defineProperty,采用Proxy劫持整个对象,有13种劫持操作,解决了这个问题\n\n### 6. Vue的父组件和子组件生命周期钩子执行顺序是什么?\n\n加载渲染过程:\n1. 父 beforeCreate\n2. 父 created\n3. 父 beforeMount\n4. 子 beforeCreate\n5. 子 cerated\n6. 子 beforeMount\n7. 子 mounted\n8. 父 mounted\n\n子组件更新过程:\n1. 父 beforeUpdate\n2. 子 beforeUpdate\n3. 子 updated\n4. 父 updated\n\n父组件更新过程:\n1. 父 beforeUpdate\n2. 父 updated\n\n销毁过程:\n1. 父 beforeDestroy\n2. 子 beforeDestroy\n3. 子 destroyed\n3. 父 destroyed\n\n### 7. Vue是如何对数组方法进行变异的?\n\n1. 为什么要对数组进行单独处理\n\n在Vue2.x中,通过Object.defineProperty劫持数据处理响应式,但这个方法并不能监听到数组内的变化,所以需要对这些数组操作进行hack,让vue能监听到其中的变化。\n\n2. 怎么处理的\n\n简单来讲就是重写了数组的那些方法,首先获取到这个数组的_ob_,也就是它的Observe对象,如果有新的值就调用observeArray继续对新的值观察变化,然后手动调用notify,通知watcher执行update\n\n### 8. Vue nextTick的原理\n\nnextTick使用场景:\n\n1. 在Vue的created钩子函数中进行DOM操作一定要放到Vue.nextTick()的回调函数中\n2. 在数据变化后要执行某个操作,而这个操作需要用到随数据变化而改变的DOM结构时,这个操作需要放到Vue.nextTick()的回调函数中\n\n使用原因:\n\n> Vue执行DOM更新是异步的。只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,如果同一个watcher被多次触发,则只会被加入队列中一次,以避免不必要的计算和 DOM 操作。然后在下一个事件循环“tick”中,Vue刷新队列并执行实际(已去重)工作。\n\n### 9. Vue 中的 computed 是如何实现的\n\n本质就是一个惰性的watcher,在取值的时候根据自身标记dirty属性返回上一次计算结果或者重新计算值,创建时会执行一次取值操作,并收集依赖变动的对象和属性
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-02-24T16:55:27.000Z',
|
|||
|
updateDate: '2022-03-02T09:20:44.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 18,
|
|||
|
title: 'Promise的五个静态方法',
|
|||
|
author: '张传龙',
|
|||
|
category: 'JavaScript',
|
|||
|
description: '简单介绍下在 Promise 类中,有5 种静态方法及它们的使用场景',
|
|||
|
content:
|
|||
|
'## 1. Promise.all\n\n并行执行多个 promise,并等待所有 promise 都准备就绪。再对它们进行处理。这就是 `Promise.all` 的用途。\n\n语法:\n\n```js\nlet promise = Promise.all([...promises...])\n```\n\n`Promise.all`接受一个promise数组作为参数并返回一个新的promise。只有当所有给定的promise都被settled时,新的promise才会resolve,并且其结果数组将成为新的promise的结果。\n\n> 注意:结果数组中元素的顺序与其在源 promise 中的顺序相同,即使第一个 promise 花费了最长的时间才 resolve,但它仍是结果数组中的第一个。\n>\n> **如果任意一个 promise 被 reject,由 `Promise.all` 返回的 promise 就会立即 reject,并且结果就是这个 error,其他 promise 将被忽略**\n\n## 2. Promise.allSettled\n\n 等待所有 promise 都 settle 时,并以包含以下内容的对象数组的形式返回它们的结果:\n\n- `status`: `"fulfilled"` 或 `"rejected"`\n- `value`(如果 fulfilled)或 `reason`(如果 rejected)。\n\n![image-20210222221959318](https://gitee.com/zclzone/res/raw/master/images/image-20210222221959318.png)\n\n## 3. Promise.race\n\n等待第一个 settle 的 promise,并将其 result/error 作为结果。\n\n```js\nPromise.race([\n new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),\n new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),\n new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))\n]).then(alert)\t//1\n```\n\n## 4. Promise.resolve\n\n`Promise.resolve(value)` 用结果 `value` 创建一个 resolved 的 promise。\n\n等价于\n\n```js\nlet promise = new Promise(resolve => resolve(value))\n```\n\n## 5. Promise.reject\n\n`Promise.reject(error)` 用 `error` 创建一个 rejected 的 promise。\n\n等价于\n\n```js\nlet promise = new Promise((resolve, reject) => reject(error))\n```\n\n`这五个方法中,Promise.all是实际中使用最多的。`\n\n\n\n\n\n\n',
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-02-22T22:37:06.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 17,
|
|||
|
title: '前端常见面试题收集整理',
|
|||
|
author: '张传龙',
|
|||
|
category: '其他',
|
|||
|
description: '前端常见面试题收集整理',
|
|||
|
content:
|
|||
|
'## position的值, relative和absolute分别是相对于谁进行定位的\n- absolute: 生成绝对定位的元素,相对于最近一级的定位不是static的父元素来进行定位\n- fixed: 生成绝对定位的元素,通常相对于浏览器窗口或者frame进行定位\n- relative: 生成相对定位的元素,相对于其在普通流中的位置进行定位\n- static: 默认值,没有定位,元素出现在正常的流中\n- sticky: 生成粘性定位的元素,容器的位置根据正常文档流计算得出\n\n\n## 如何解决跨域问题\n1. JSONP: 动态插入script标签\n2. CORS: 后端设置Access-Control-Allow-Origin\n3. window.name: window.name持久存在于一个窗口载入过的所有页面中的\n4. 反向代理: 本地调试使用webpack的devserve,生产环境使用nginx配置反向代理实现同源\n\n\n## XML和JSON的区别\n1. json体积更小,传递的速度更快\n2. json更便于与js交互,更容易解析\n3. json对数据的描述性较差\n\n## 谈谈你对webpack的看法\nWebPack 是一个模块打包工具,你可以使用WebPack管理你的模块依赖,并编绎输出模块们所需的静态文件。它能够很好地管理、打包Web开发中所用到的HTML、Javascript、CSS以及各种静态文件(图片、字体等),让开发过程更加高效。对于不同类型的资源,webpack有对应的模块加载器。webpack模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源。\n\n',
|
|||
|
isRecommend: false,
|
|||
|
isPublish: false,
|
|||
|
createDate: '2021-01-27T16:32:45.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 16,
|
|||
|
title: 'Linux安装并配置Nginx',
|
|||
|
author: '张传龙',
|
|||
|
category: 'Linux',
|
|||
|
description: 'Linux安装配置Nginx',
|
|||
|
content:
|
|||
|
'## 1. 安装nginx依赖\n\n1. gcc && g++\n```cmd\nyum install gcc-c++\n```\n2. pcre\n```\nyum install -y pcre pcre-devel\n```\n3. zlib\n```\nyum install -y zlib zlib-devel\n```\n4. openssl\n```\nyum install -y openssl openssl-devel\n```\n\n## 2. 安装nginx\n1. 下载nginx安装包\n```cmd\nwget http://nginx.org/download/nginx-1.19.6.tar.gz\n```\n2. 解压安装包\n```cmd\ntar -zxvf nginx-1.19.6.tar.gz\n```\n3. 安装\n```\ncd nginx-1.19.6\nmake && make install\n# 默认会安装在/usr/local/nginx下\n```\n4. 删除安装包和解压包\n```cmd\nrm -rf nginx-1.19.6.tar.gz\nrm -rf nginx-1.19.6\n```\n\n## 3. 配置nginx\n> 配置文件默认是 /usr/local/nginx/conf 文件夹下的 *nginx.conf*\n> 启动文件是安装目录下的sbin的*nginx*\n1. 启动、重启、停止nginx\n```cmd\ncd /usr/local/nginx\n# 启动\n./sbin/nginx\n# 重启\n./sbin/nginx -s reload\n# 停止\n./sbin/nginx -s stop\n```\n2. 修改配置文件\n```cmd\ncd /usr/local/nginx\nvim ./conf/nginx.conf\n```\n3. 配置文件参考\n```bash\n#user nobody;\nworker_processes 1;\n\n#error_log logs/error.log;\n#error_log logs/error.log notice;\n#error_log logs/error.log info;\n\n#pid logs/nginx.pid;\n\n\nevents {\n worker_connections 1024;\n}\n\nhttp {\n include mime.types;\n default_type application/octet-stream;\n\n sendfile on;\n\n keepalive_timeout 65;\n\n gzip on;\n\n upstream api_server {\n server blog.qszone.com:3000;\n }\n\n upstream sally_api_server {\n server sally.qszone.com:3001;\n }\n\n upstream blog_server {\n server www.qszone.com:8080;\n }\n\n server {\n listen 80;\n server_name qszone.com;\n rewrite ^/(.*)$ http://www.qszone.com/$1 permanent;\n }\n\n server {\n listen\t 80;\n server_name fangfang.qszone.com;\n rewrite ^/(.*)$ http://sally.qszone.com/$1 permanent;\n }\n\n server {\n listen 80;\n server_name blog.qszone.com;\n\n location / {\n root /usr/local/projects/blog/dist;\n index index.html;\n try_files $uri $uri/ /index.html;\n }\n\n location /api/ {\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_pass http://api_server/;\n }\n\n location = /50x.html {\n root html;\n }\n }\n\n server {\n listen 80;\n server_name www.qszone.com;\n\n location / {\n root /usr/local/projects/qs-zone/;\n index index.html;\n }\n\n location /api/ {\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_pass http://api_server/;\n }\n\n location = /50x.html {\n root html;\n }\n }\n\n server {\n listen 80;\n server_name me.qszone.com;\n\n location / {\n root /usr/local/projects/resume/;\n index index.html;\n }\n\n location = /50x.html {\n root html;\n }\n }\n\n server {\n listen 80;\n server_name sally.qszone.com;\n\n location / {\n root /usr/local/projects/sally-blog/dist;\n index index.html;\n }\n\n location /api/ {\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_pass http://sally_api_server/;\n }\n\n location = /50x.html {\n root html;\n }\n }\n}\n\n```\n\n\n\n\n\n',
|
|||
|
isRecommend: false,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-01-03T20:07:06.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 15,
|
|||
|
title: '十大经典排序算法—计数排序',
|
|||
|
author: '张传龙',
|
|||
|
category: '算法',
|
|||
|
description: '计数排序,5种js实现方式,一步一步优化,最终解决方案结合hash解决了传统计数排序的弊端',
|
|||
|
content:
|
|||
|
"```js\n/*计数排序1 */\nfunction count_sort1 (arr, maxVal) {\n maxVal = maxVal || Math.max(...arr)\n var bucket = new Array(maxVal + 1)\n var sortIndex = 0\n var arrLen = arr.length\n var bucketLen = maxVal + 1\n for (var i = 0; i < arrLen; i++) {\n if (!bucket[arr[i]]) {\n bucket[arr[i]] = 0\n }\n bucket[arr[i]]++\n }\n for (var j = 0; j < bucketLen; j++) {\n while (bucket[j] > 0) {\n arr[sortIndex++] = j\n bucket[j]--\n }\n }\n return arr\n}\n\n/*计数排序2 */\nfunction count_sort2 (arr) {\n var bucket = []\n var sorted_arr = []\n var arrLen = arr.length\n for (var i = 0; i < arrLen; i++) {\n if (!bucket[arr[i]]) {\n bucket[arr[i]] = 0\n }\n bucket[arr[i]]++\n }\n var bucketLen = bucket.length\n for (var j = 1; j < bucketLen; j++) {\n bucket[j] = bucket[j] || 0\n bucket[j - 1] = bucket[j - 1] || 0\n bucket[j] += bucket[j - 1] || 0\n }\n for (var k = 0; k < arrLen; k++) {\n sorted_arr[--bucket[arr[k]]] = arr[k]\n }\n return sorted_arr\n}\n\n/*计数排序3 */\nfunction count_sort3 (arr) {\n var bucket = []\n var sorted_arr = []\n for (var i = 0; i < arr.length; i++) {\n if (bucket[arr[i]]) {\n bucket[arr[i]].push(i)\n } else {\n bucket[arr[i]] = [i]\n }\n }\n bucket.forEach(item => {\n if (item && item.length > 0) {\n item.forEach(num => {\n sorted_arr.push(arr[num])\n })\n }\n })\n return sorted_arr\n}\n\n/*计数排序4 */\nfunction count_sort4 (arr) {\n var bucket = {}\n var sorted_arr = []\n var double = arr.length\n for (var i = 0; i < arr.length; i++) {\n insertCount(bucket, i, arr[i] * double)\n }\n for (const key in bucket) {\n if (bucket.hasOwnProperty(key)) {\n sorted_arr.push(arr[bucket[key]])\n }\n }\n return sorted_arr\n}\n\nfunction insertCount (bucket, index, val) {\n if (bucket[val]) {\n insertCount(bucket, index, val + 1)\n } else {\n bucket[val] = index\n }\n}\n\n/*计数排序5,速度最优 */\nfunction count_sort5 (arr) {\n console.time('count_sort5')\n var bucket = {}\n var sorted_arr = []\n arr.forEach(function (item) {\n if (bucket[item]) {\n bucket[item].push(item)\n } else {\n bucket[item] = [item]\n }\n })\n for (var key in bucket) {\n if (bucket.hasOwnProperty(key)) {\n bucket[key].forEach(function (item) {\n sorted_arr.push(item)\n })\n }\n }\n console.timeEnd('count_sort5')\n return sorted_arr\n}\n\n/*计数排序6 占空间最小*/\nfunction count_sort6 (arr) {\n console.time('count_sort6')\n var bucket = {}\n var sorted_arr = []\n arr.forEach(function (item) {\n if (bucket[item]) {\n bucket[item]++\n } else {\n bucket[item] = 1\n }\n })\n for (var key in bucket) {\n if (bucket.hasOwnProperty(key)) {\n while (bucket[key]-- > 0) {\n sorted_arr.push(key)\n }\n }\n }\n console.timeEnd('count_sort6')\n return sorted_arr\n}\n```\n",
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-01-02T11:08:19.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 14,
|
|||
|
title: 'git创建gh-pages分支',
|
|||
|
author: '张传龙',
|
|||
|
category: 'Git',
|
|||
|
description: 'git创建gh-pages分支',
|
|||
|
content:
|
|||
|
"```\nnpm run build\n\ncd dist\n\ngit init\n\ngit checkout --orphan gh-pages\n\ngit add .\n\ngit commit -m 'deploy gh-pages'\n\ngit remote add origin https://github.com/zclzone/blog.git\n\ngit push -f origin gh-pages\n\n```",
|
|||
|
isRecommend: false,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-01-02T11:07:53.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 13,
|
|||
|
title: 'JavaScript高级程序设计学习笔记',
|
|||
|
author: '张传龙',
|
|||
|
category: 'JavaScript',
|
|||
|
description: 'ECMAScript 的语法大量借鉴了C 及其他类C 语言(如Java 和Perl)的语法...',
|
|||
|
content:
|
|||
|
'# 1 基本概念\n\n## 1.1 语法\n\n ECMAScript 的语法大量借鉴了C 及其他类C 语言(如Java 和Perl)的语法。\n 熟悉这些语言的开发人员在接受ECMAScript 更加宽松的语法时,一定会有一种轻松自在的感觉。\n 即使没有其他编程语言基础,JavaScript仍然易上手。\n\n### 1.1.1 区分大小写\n\n ECMAScript 中的一切(变量、函数名和操作符)都区分大小写。\n 变量名test 和变量名Test 分别表示两个不同的变量。\n\n### 1.1.2 标识符\n\n 标识符,就是指变量、函数、属性的名字,或者函数的参数,规则:\n 1. 第一个字符必须是一个字母、下划线(_)或一个美元符号($)\n 2. 其他字符可以是字母、下划线、美元符号或数字\n 3. 不允许是关键字、保留字、true、false和null\n\nECMAScript 标识符采用驼峰大小写格式:\n\n```javascript\nvar myBlog\nvar doSomethingImportant\n```\n\n### 1.1.3 注释\n\n```javascript\n//单行注释\n\n/*\n * 这是一个多行\n * (块级)注释\n */\n```\n\n 虽然上面多行注释中的第二和第三行都以一个星号开头,但这不是必需的。之所以添加那两个星号,纯粹是为了提高注释的可读性\n\n### 1.1.4 语句\n\nECMAScript 中的语句以一个分号结尾;如果省略分号,则由解析器确定语句的结尾,如下例所示:\n\n```javascript\nvar sum = a + b // 即使没有分号也是有效的语句——不推荐\nvar diff = a - b // 有效的语句——推荐\n```\n\n 加上分号也会在某些情况下增进代码的性能,因为这样解析器就不必再花时间推测应该在哪里插入分号了\n\n尽量始终在控制语句中使用代码块——即使代码块中只有一条语句\n\n```javascript\n// 有效但容易出错,不要使用\nif (test) alert(test)\n\n// 推荐使用\nif (test) {\n alert(test)\n}\n```\n\n 在控制语句中使用代码块可以让编码意图更加清晰,而且也能降低修改代码时出错的几率\n\n## 1.2 关键字和保留字\n\nECMAScript 的全部关键字\n\n> break do instanceof typeof case else new var\n> catch finally return void continue for switch while\n> debugger function this with default if throw delete in try\n\n保留字:\n\n> abstract enum int short boolean export interface static\n> byte extends long super char final native synchronized\n> class float package throws const goto private transient\n> debugger implements protected volatile double import public\n\n## 1.3 变量\n\nECMAScript 的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。\n\n 可以使用一条语句定义多个变量,只要像下面这样把每个变量(初始化或不初始化均可)用逗号分隔开即可:\n\n```javascript\nvar message = \'hi\',\n found = false,\n age = 29\n```\n\n## 1.4 数据类型\n\n5 种简单数据类型(也称为基本数据类型):\n\n- Undefined\n- Null\n- Boolean\n- Number\n- String\n\n1 种复杂数据类型: Object\n\n### 1.4.1 typeof 操作符\n\ntypeof——用来检测给定变量的数据类型的操作符\n\n- "undefined"——如果这个值未定义\n- "boolean"——如果这个值是布尔值\n- "string"——如果这个值是字符串\n- "number"——如果这个值是数值\n- "object"——如果这个值是对象或 null\n- "function"——如果这个值是函数\n\n例:\n\n```javascript\nvar message = \'some string\'\nconsole.log(typeof message) // "string"\nconsole.log(typeof message) // "string"\nconsole.log(typeof 95) // "number"\n```\n\n 注意:typeof 是一个操作符而不是函数,因此例子中的圆括号尽管可以使用,但不是必需的\n\n### 1.4.2 Undefined 类型\n\nUndefined 类型只有一个值,即特殊的 undefined。在使用 var 声明变量但未对其加以初始化时,\n这个变量的值就是 undefined,例如:\n\n```javascript\nvar message\nconsole.log(message == undefined) //true\n```\n\n### 1.4.3 Null 类型\n\nNull
|
|||
|
isRecommend: false,
|
|||
|
isPublish: false,
|
|||
|
createDate: '2021-01-02T11:07:20.000Z',
|
|||
|
updateDate: '2022-03-02T09:15:26.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 12,
|
|||
|
title: 'ES6常用知识点总结归纳',
|
|||
|
author: '张传龙',
|
|||
|
category: 'JavaScript',
|
|||
|
description: 'ES6常用知识点总结归纳',
|
|||
|
content:
|
|||
|
"\n## 一、新的声明方式\n\n1. let:声明的变量只在代码块内有效\n2. const: 声明常量,声明时必须赋值且不可改变\n\n```javascript\nlet a = 'aaa'\nconst b = 'bbb'\nconsole.log(a, b)\n```\n\n## 二、变量的解构赋值\n\n> 数组解构赋值\n\n```javascript\nlet [aa, bb, cc] = [0, 1, 2]\n```\n\n> 对象解构赋值\n\n```javascript\nlet { cnName, enName } = {\n id: '151521574',\n cnName: '张生',\n enName: 'Ronnie'\n}\nconsole.log(cnName, enName) //'张生','Ronnie'\n```\n\n## 三、扩展运算符和 rest 运算符\n\n> 对象扩展运算符\n\n```javascript\nfunction test01(...arg) {\n console.log(arg[0]) //1\n console.log(arg[1]) //2\n console.log(arg[2]) //3\n console.log(arg[3]) //undefined\n}\ntest01(1, 2, 3)\n\nlet arr1 = [1, 2, 3]\nlet arr2 = [...arr1]\narr2.push(4) //此时不改变arr1\nconsole.log(arr1) //[1,2,3]\nconsole.log(arr2) //[1,2,3,4]\n```\n\n> rest 运算符(表示剩余参数)\n\n```javascript\nfunction test02(first, ...arg) {\n for (let item of arg) {\n console.log(item) //依次输出1,2,3,4,5,6\n }\n console.log(first) //0\n}\ntest02(0, 1, 2, 3, 4, 5, 6)\n```\n\n## 四、字符串模版\n\n> 字符串模版\n\n```javascript\nlet name = 'Ronnie'\nlet str = `大家好,我是<b>${name}</b><br/>很高兴认识大家`\nconsole.log(str)\n```\n\n> 字符串查找\n\n```javascript\nlet str = `大家好,我是<b>${name}</b><br/>很高兴认识大家`\nconsole.log(str.includes('不高兴')) //false\nconsole.log(str.startsWith('大家好')) //判断开头是否存在,true\nconsole.log(str.endsWith('大家')) //判断结尾是否存在,true\n```\n\n## 五、数字操作\n\n> 二进制声明 Binary\n\n```javascript\nlet binary = 0b010101\nconsole.log(binary) //21\n```\n\n> 八进制声明 Octal\n\n```javascript\nlet octal = 0o666\nconsole.log(octal) //438\n```\n\n> 判断是否为数字\n\n```javascript\nNumber.isFinite(888) //true\nNumber.isFinite('888') //false,不会先将字符串尝试转换成数字再判断\nNumber.isFinite('asd') //false\nNumber.isFinite(undefined) //false\nNumber.isFinite(NaN) //false\n```\n\n> 判断是否是整数\n\n```javascript\nNumber.isInteger(1.1) //false\nNumber.isInteger(1) //true\n```\n\n> 最大和最小安全整数\n\n```javascript\nNumber.MAX_SAFE_INTEGER\nNumber.MIN_SAFE_INTEGER\n```\n\n> 判断是否为安全整数\n\n```javascript\nNumber.isSafeInteger(Math.pow(2, 53) - 1) //true\nNumber.isSafeInteger(Math.pow(2, 53) + 1) //false\n```\n\n## 六、新增的数组知识\n\n> 数组格式转换: Array.from、 Array.of\n\n```javascript\nlet json = {\n '0': 'Ronnie',\n '1': 'Rose',\n '2': 'zhangsheng',\n '3': 'Json',\n length: 4\n}\n\nlet jsonArr = Array.from(json)\nconsole.log(jsonArr) //['Ronnie','Rose','zhangsheng','Json']\n\nlet strArr = Array.of('a', 'b', 'c', 'd')\nconsole.log(strArr) //['a', 'b', 'c', 'd']\n```\n\n> find()方法:返回满足条件的第一个元素,三个参数\n\n- value:当前查找的值\n- index: 当前查找的值的索引\n- strArr: 查找的原数组\n\n```javascript\nlet findRst = strArr.find((value, index, strArr) => {\n return value === 'd' || value === 'a'\n})\nconsole.log(findRst) //a\n```\n\n> fill() 方法\n\n```javascript\nlet fillArr = ['Ronnie', 'Rose', 'Zhangsheng']\nfillArr.fill('es6', 1, 3) //将索引为[1,3),即索引为1和2的值替换为'es6'\nconsole.log(fillArr) //[\"Ronnie\", \"es6\", \"es6\"]\n```\n\n> for...of\n\n```javascript\nfor (let item of fillArr) {\n console.log(item)\n}\n//带索引的方式\nfor (let [index, value] of fillArr.entries()) {\n console.log(index + ':' + value)\n}\n```\n\n> entries() 生成的是 Iterator 形式的数组,这种形式的好处就是可以让我们在需要时用 next()手动跳转到下一个值\n\n```javascript\nlet list = fillArr.entries()\nconsole.log(list.next().value) //[0, \"Ronnie\"]\nconsole.log(list.next().value) //[1, \"es6\"]\nconsole.log(list.next().value) //[2, \"es6\"]\nconsole.log(list.next().value) //undefined\n```\n\n> some 方法: 返回一个 Boolean,判断是否有元<E69C89><E58583>
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-01-02T11:06:42.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 11,
|
|||
|
title: 'Css Flex布局',
|
|||
|
author: '张传龙',
|
|||
|
category: 'Css',
|
|||
|
description: 'CSS Flex布局',
|
|||
|
content:
|
|||
|
"## 属性总览\n\n| 作用在 flex 容器上 | 作用在 flex 子项上 |\n| ------------------ | ------------------ |\n| flex-direction | order |\n| flex-wrap | flex-grow |\n| flex-flow | flex-shrink |\n| justify-content | flex-basis |\n| align-items | flex |\n| align-content | align-self |\n\n> 无论作用在 flex 容器上,还是作用在 flex 子项,都是控制的 flex 子项的呈现,只是前者控制的是整体,后者控制的是个体。\n\n## 属性详解\n\n### 1. flex-direction\n\n`flex-direction`用来控制子项整体布局方向,是从左往右还是从右往左,是从上往下还是从下往上。\n\n属性如下:\n\n`row`: 默认值,显示为行,从左到右\n\n`row-reverse`: 显示为行,方向从右到左,与 row 相反\n\n`column`: 显示为列,从上至下\n\n`column-reverse`: 显示为列,从下往上\n\n语法如下:\n\n```css\nflex-direction: row | row-reverse | column | column-reverse;\n```\n\n### 2. flex-wrap\n\n`flex-wrap`用来控制子项是单行显示还是换行显示\n\n属性如下:\n\n`nowrap`: 默认值,不换行,所有的子项单行显示,如果宽度溢出会压缩宽度显示\n\n`wrap`: 宽度溢出则换行显示\n\n`wrap-reverse`: 宽度溢出换行显示,从下往上显示,即原本换行在下面的子项显示在上面\n\n语法如下:\n\n```css\nflex-wrap: nowrap | wrap | wrap-reverse;\n```\n\n### 3. flex-flow\n\n`flex-flow`是 **_flex-direction_** 和 **_flex-wrap_** 的缩写\n\n语法如下:\n\n```css\nflex-flow: row wrap;\n```\n\n> 当多属性同时使用的时候,使用空格分隔\n\n### 4. justify-content\n\n`justify-content`属性决定了水平方向子项的对齐和分布方式\n\n属性如下:\n\n`flex-start`: 默认值,左对齐\n\n`flex-end`: 右对齐\n\n`center`: 居中对齐\n\n`space-between`: 两端对齐,多余的空白只在元素中间分配\n\n`space-around`: 环绕,每个子项两侧都环绕互不干扰的等宽间距,最终表现为两端空白是中间空白的一半\n\n`space-evenly`: 均匀分布,每个子项两侧空白完全相等\n\n语法如下:\n\n```css\njustify-content: flex-start | flex-end | center | space-between | space-arount |\n space-evenly;\n```\n\n### 5. align-items\n\n`align-items`中的 items 指的是 flex 中的子项,因此`align-items`指的就是 flex 子项们相对于 flex 容器在垂直方向上的对齐方式\n\n属性如下:\n\n`stretch`: 默认值,子项拉伸显示。如果子项设置了高度,则按设置的高度渲染,而不是拉伸\n\n`flex-start`: 顶部对齐\n\n`flex-end`: 底部对齐\n\n`center`: 垂直居中对齐\n\n`baseline`: 相对于 flex 容器的基线对齐\n\n### 6. align-content\n\n`align-content`可以看成是和 justify-content 是相似且对立的属性,`justify-content`指的是水平方向 flex 子项的对齐和分布方式,而 align-content 则是指垂直方向每一行 flex 子项的对齐和分布方式\n\n> 如果所有 flex 子项只有一行,则 align-content 属性是没有任何效果的\n\n属性如下:\n\n`stretch`: 默认值。每一行 flex 子元素都等比例拉伸。例如,如果共两行 flex 子元素,则每一行拉伸高度是 50%。\n\n`flex-start`: 逻辑 CSS 属性值,与文档流方向相关。默认表现为顶部堆砌。\n\n`flex-end`: 逻辑 CSS 属性值,与文档流方向相关。默认表现为底部堆放。\n\n`center`: 表现为整体垂直居中对齐。\n\n`space-between`: 表现为上下两行两端对齐。剩下每一行元素等分剩余空间。\n\n`space-around`: 每一行元素上下都享有独立不重叠的空白空间。\n\n`space-evenly`: 每一行元素都完全上下等分。\n\n### 7. order (==作用在 flex 子项==)\n\n`order`可以改变一个 flex 子项的排序位置\n\n> 所有 flex 子项默认 order 属性值为 0,如果想让某一子项在最前面显示,设置比 0 小的整数值就行了,如:-1\n\n语法如下:\n\n```css\norder: -1;
|
|||
|
isRecommend: false,
|
|||
|
isPublish: false,
|
|||
|
createDate: '2021-01-02T11:06:17.000Z',
|
|||
|
updateDate: '2022-03-02T09:15:41.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 10,
|
|||
|
title: '专属单车',
|
|||
|
author: '张传龙',
|
|||
|
category: '情感',
|
|||
|
description: '我想要一辆单车,一辆专属我的单车',
|
|||
|
content:
|
|||
|
'我想要一辆单车\n一辆专属我的单车\n它不是摩拜\n也不像ofo\n它,只由我掌控\n它,只供我驱使\n\n我希望\n它是没有刹车的\n骑上它\n便什么都不用管\n不管是上坡还是下坡\n红灯还是绿灯\n也不用顾左盼右\n迟疑前后\n它,是勇往直前的\n它,是永不停歇的\n它,是无畏规条的\n\n如果真有这么一辆车\n我希望\n在我骑上它的时候\n能够头戴耳麦\n放一首我最爱的音乐\n单曲循环\n音量开到最大\n穿山越海\n越陌度阡\n不瞩目\n不回望\n狂放驰骋\n执著前行\n\n骑上这样的单车\n注定是孤独的\n这份孤独\n我希望可以独享\n如果足够幸运\n碰到同行者\n我也很乐意与其分享这一路的精彩\n哪怕只有孤独\n也一样精彩',
|
|||
|
isRecommend: false,
|
|||
|
isPublish: false,
|
|||
|
createDate: '2021-01-02T11:05:56.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 9,
|
|||
|
title: '如何在github上找开源项目',
|
|||
|
author: '张传龙',
|
|||
|
category: '其他',
|
|||
|
description: '很多人找开源项目会直接在在搜索框中输入关键字直接查询...',
|
|||
|
content:
|
|||
|
'很多人找开源项目会直接在在搜索框中输入关键字直接查询,比如要找Vue开源项目,直接在搜索框直接搜索Vue,这样不是不行,只是这样会搜索出大量相关的结果,难以筛选结果,那么要如何筛选呢\n\n> 筛选项目名称中带有Vue的项目\n```\nin:name Vue\n```\n\n> 筛选README中中带有Vue的项目\n```\nin:readme Vue\n```\n\n> 筛选项目名称中带有Vue且Star>2000的项目\n```\nin:name Vue stars:>2000\n```\n\n> 筛选项目名称中带有Vue且更新时间>2020-09-01的项目\n```\nin:name Vue pushed:>2020-09-01\n```\n\n> 筛选项目名称中带有Vue且编程语言为Vue的项目\n```\nin:name Vue language:vue\n```\n\n> 筛选项目名称中带有Vue且forks>1000的项目\n```\nin:name Vue forks:>10000\n```',
|
|||
|
isRecommend: false,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-01-02T11:05:12.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 8,
|
|||
|
title: 'js判断字符串中是否包含某个字符串',
|
|||
|
author: 'Ronnie',
|
|||
|
category: 'JavaScript',
|
|||
|
description: '几种常用的判断字符串中是否包含某个字符串的方法,包括ES5和ES6方法',
|
|||
|
content:
|
|||
|
"### ES6:\n- includes():是否包含了参数字符串,返回布尔值\n- startsWith():参数字符串是否在原字符串的头部,返回布尔值\n- endsWith():参数字符串是否在原字符串的尾部,返回布尔值\n```js\nconst homeUrl = 'https://qszone.com'\nhomeUrl.includes('qszone') //true\nhomeUrl.startsWith('https') //true\nhomeUrl.endsWith('cn') //false\n```\n\n注:这三个方法都支持第二个参数,表示开始匹配的位置\n\n### ES5:\n1. indexOf():返回某个指定的字符串值在字符串中首次出现的位置,如果检索的字符串没有出现,则返回-1,此方法同样适用于数组\n```js\nvar homeUrl = 'https://qszone.com'\nhomeUrl.indexOf('qszone') !== -1 //true,表示包含\n\nvar arr = ['https','qszone','com']\narr.indexOf('http') //-1\narr.indexOf('qszone') //1\n```\n2. search():跟indexOf方法类似,参数可以是正则表达式,不过不适用数组,不推荐使用\n3. 正则方法:match()、test()、exec()\n```js\nvar homeUrl = 'https://qszone.com'\nvar reg = RegExp(/1024/)\nhomeUrl.match(reg)) //null,返回匹配的对象,不匹配则返回null\n\nreg.test(homeUrl) //false,返回bool值\n\nreg = RegExp(/qszone/)\nreg.exec(homeUrl) //[\"qszone\", index: 8, input: \"https://qszone.com\", groups: undefined],不匹配则返回null\n```\n",
|
|||
|
isRecommend: false,
|
|||
|
isPublish: false,
|
|||
|
createDate: '2021-01-02T11:04:47.000Z',
|
|||
|
updateDate: '2022-03-02T09:15:51.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 7,
|
|||
|
title: 'CSS常用知识点',
|
|||
|
author: '张传龙',
|
|||
|
category: 'Css',
|
|||
|
description: 'CSS盒子模型、CSS选择器...',
|
|||
|
content:
|
|||
|
'## 1. CSS盒子模型有哪些,区别是什么?\n\n> 盒子模型\n\n有两种盒子模型,IE和模型(border-box)、W3C标准和模型(content-box)\n盒模型分为内容(content)、填充(padding)、边界(margin)、边框(border)四个部分\n\n> 区别:\n\n1. W3C标准盒模型:属性width,height只包含border和padding\n2. IE盒模型:属性width,height包含content、border和padding\n\n浏览中由box-sizing控制使用哪个盒模型,默认值为content-box标准盒模型\n\n\n## 2. CSS选择器有哪些?\n\n1. id选择器(#idName)\n2. 类选择器(.className)\n3. 标签选择器(div、p、ul)\n4. 后代选择器(ul li)\n5. 子选择器(ul>li)\n6. 兄弟选择器(li~a)\n7. 相邻兄弟选择器(li+a)\n8. 属性选择器(h1[title="标题"]、input[type="checkbox"])\n9. 伪类选择器(button:hover、li:nth-child(1))\n10. 伪元素选择器(::before、::after)\n11. 通配符选择器(*)\n\n\n## 3. ::before和:after中双冒号和单冒号有什么区别\n\n单冒号用于CSS3伪类,双冒号用于CSS3为元素\n双冒号是在CSS3规范中引入的,用于区分伪类和伪元素。不过浏览器需要同时支持旧的已经存在的伪元素写法,比如:first-line、:first-letter、:before、:after等\n而在新的CSS3中引入的为元素则不允许再支持旧的单冒号写法\n\n\n## 4. 伪类和伪元素的区别\n\n伪类和伪元素是用于修饰不在文档树中的部分,比如p标签中的第一个字母,或者ul列表中的最后一个元素li\n\n>区别\n\n1. 伪类用于描述已有元素的某个状态,比如鼠标悬停在某个元素时,可以使用:hover来描述这个元素的状态\n2. 伪元素用于创建一些不在文档树中的元素并添加样式,比如可以使用:after在一个元素后面增加一个元素并指定样式,虽然浏览器可以看到这个元素,单实际上这个元素不存在文档树上,也就意味着用js很难直接控制伪元素\n\n\n## 5. CSS中哪些属性可以继承\n\n一般具有继承性的属性有,字体相关的属性,font-size和font-weight等;文本相关的属性color和text-align等\n表格的一些布局属性、列表属性如list-style等;光标属性cursor、元素可见性属性visibility。\n\n注:当一个属性不是继承属性时,可通过将属性值设置成inherit来使它从父元素那获取同名的属性值来继承。\n\n\n## 6. CSS优先级算法\n\n判断优先级,首先判断属性声明后面是否加了!important,如果夹了则它的优先级是最高的,除非它后面出现了同样加了!important的声明。\n\n!important > 行内样式 > id选择器 > 类选择器、伪类选择器和属性选择器 > 元素选择器和伪元素选择器\n\n注:同等规则,后出现的优先级更高\n\n## 7. 伪类LVHA\n\na标签的四种状态,链接访问前、访问后、鼠标滑过、激活,分别对应:link、:visited、:hover、:active\n\n1. 当鼠标滑过a链接时,满足:link和:hover两种状态,要改变a标签的颜色,就必须将:hover在:link后面声明\n2. 当鼠标点击激活a链接时,同时满足:hover、:link、:active三种状态,要显示a标签:active,必须将:active声明放到:link和:hover之后。因此得出LVHA顺序\n\n### 8. CSS3新增伪类\n\n1. ele:nth-child(n) 选中父元素下的第n个子元素,并且这个子元素是ele标签\n2. ele:nth-last-child(n) 作用同上,逆序开始计算\n3. ele:first-child 第一个子元素\n4. ele:last-child 最后一个子元素\n5. ele:only-child 如果ele是父元素下唯一的子元素,则选中\n6. ele:nth-of-type(n) 选中父元素下第n个ele类型的元素 \n7. ele:first-of-type 父元素下第一个ele类型元素\n8. ele:last-of-type 父元素下最后一个ele类型元素\n9. ele:only-of-type 如果ele是父元素下唯一一个此类型的元素,则选中\n10. ele:empty 选中不包含子元素和内容的ele类型元素\n11. ele:target 选中当前活动的ele元素\n12. :not(e
|
|||
|
isRecommend: false,
|
|||
|
isPublish: false,
|
|||
|
createDate: '2021-01-02T11:02:56.000Z',
|
|||
|
updateDate: '2022-03-02T09:15:53.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 6,
|
|||
|
title: 'Linux安装和卸载Docker',
|
|||
|
author: '张传龙',
|
|||
|
category: 'Linux',
|
|||
|
description: 'Linux安装和卸载Docker',
|
|||
|
content:
|
|||
|
'## 1. 卸载旧版本\n```shell\nyum remove docker \\\ndocker-client \\\ndocker-client-latest \\\ndocker-common \\\ndocker-latest \\\ndocker-latest-logrotate \\\ndocker-logrotate \\\ndocker-engine\n```\n\n## 2. 安装依赖包\n```shell\nyum install -y yum-utils\n```\n\n## 3. 设置镜像的仓库\n```shell\nyum-config-manager \\\n--add-repo \\\nhttps://download.docker.com/linux/centos/docker-ce.repo\n```\n\n## 4.更新yum软件包索引\n```shell\nyum makecache\n```\n\n## 5. 安装Docker docker-ce 社区版\n```shell\nyum install docker-ce docker-ce-cli containerd.io\n```\n\n## 6. 启动Docker\n```shell\nsystemctl start docker\n```\n\n## 7. Hello-world\n```shell\ndocker run hello-world\t# 检测没有会自动下载\n```\n\n## 8. 查看下载的镜像\n```shell\ndocker images\n```\n\n## 9. 配置阿里云镜像加速器\n```\nsudo mkdir -p /etc/docker\nsudo tee /etc/docker/daemon.json <<-\'EOF\'\n{\n "registry-mirrors": ["https://tpnvxk11.mirror.aliyuncs.com"]\n}\nEOF\nsudo systemctl daemon-reload\nsudo systemctl restart docker\n```\n\n## 10. 卸载Docker\n```shell\n# 卸载依赖\nyum remove docker-ce docker-ce-cli containerd.io\n# 删除资源(docker的默认工作路径:/var/lib/docker)\nrm -rf /var/lib/docker\n```\n',
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-01-02T11:02:29.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 5,
|
|||
|
title: '彻底弄懂JavaScript闭包',
|
|||
|
author: '张传龙',
|
|||
|
category: 'JavaScript',
|
|||
|
description: '彻底弄懂JavaScript闭包',
|
|||
|
content:
|
|||
|
'\n## 1. 闭包的定义\n关于JavaScript闭包的定义有很多种,很难准确的给其下定义,闭包的本质是函数,是声明在一个函数内部的函数。\n\n\n**闭包的产生:当内层作用域访问它外层函数作用域里的变量、参数或者函数时,就产生闭包了**\n\n举例:\n```js\nfunction fn1() {\n let a = 123\n function fn2() {\n return a\n }\n return fn1\n}\nconsole.log(fn1()()) //123\n```\n例子中fn1函数外部本来是访问不到fn1函数内部的a变量的,但是fn1函数通过返回一个内部函数的方式让外部访问内部变量成为可能.\n\n## 2. 闭包与函数作用域\n函数的作用域实际上是个动态概念,如果定义一个函数但没有调用,函数的作用域是不存在的,只有函数调用时才会在内存中创建一个独立的作用域,调用结束后这个作用域又关闭了,函数运行中在内存创建的数据也会被清除。\n\n```js\nfunction fn1() {\n let a = 0\n function fn2() {\n console.log(++a)\n }\n fn2()\n fn2()\n fn2()\n}\nfn1() //1 2 3\n```\n这个例子中,fn1被调用,首先会在内存中开辟作用域,执行过程中定义并调用了三次fn2函数,fn2每次调用也都会开辟自己的作用域,在调用fn2的过程中访问了还未关闭的fn1函数作用域中的变量a,且三次调用访问的都是同一个变量a。但fn1总有执行完的时刻,也即有关闭作用域的时候。\n```js\nfunction fn1() {\n let a = 0\n function fn2() {\n console.log(++a)\n }\n return fn2\n}\nlet fn = fn1()\nfn() //1\nfn() //2\nfn() //3\n```\n这个例子中,运行fn1,将fn1运行过程中创建的fn2函数赋给全局变量fn,导致fn1函数运行完时不敢关闭自己的作用域,此时让内部函数fn2成了闭包函数,而全局变量fn则是这个闭包函数的引用,由于全局作用域是永恒的,只要浏览器窗口不关闭,全局变量fn所引用闭包函数的父作用域也不会被关闭,会一直在内存中存在。当闭包函数被调用时,始终能访问其父作用域,只有当闭包函数的引用被释放时,它的父作用域才会被关闭。\n\n## 3. 使用闭包的好处\n- 使用闭包可以避免使用全局变量,防止污染全局变量\n- 使用闭包让外部作用域访问函数内部变量变成可能,此点用于实现软件设计上的封装,可以设计出类库、框架,比如jQuery、Vue.js\n\n## 4. 闭包的缺点\n使用闭包会使得局部作用域的变量常驻在内存中(有一块内存空间被长期占用,而不被释放),过度使用闭包,会导致内存占用过多,容易造成内存泄漏。\n避免闭包导致内存泄漏的解决方法是,将不再使用的闭包函数的引用全部删除或者赋值为null。\n\n\n\n',
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2021-01-02T11:02:01.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
{
|
|||
|
id: 4,
|
|||
|
title: 'Linux安装并配置MySql',
|
|||
|
author: 'Ronnie',
|
|||
|
category: 'Linux',
|
|||
|
description: 'Linux安装并配置MySql',
|
|||
|
content:
|
|||
|
"## 一、安装前准备\n1. 检查是否已经安装过mysql\n```cmd\nrpm -qa | grep mysql\n```\n\n2. 查询所有Mysql对应的文件夹\n```cmd\nwhereis mysql\nfind / -name mysql\n```\n\n3. 删除相关目录或文件\n```cmd\nrm -rf ...\n```\n\n4. 检查mysql用户组和用户是否存在,如果没有,则创建\n```cmd\ncat /etc/group | grep mysql\ncat /etc/passwd |grep mysql\ngroupadd mysql\nuseradd -r -g mysql mysql\n```\n5. 从[官网](https://downloads.mysql.com/archives/community/)下载是用于Linux的Mysql安装包\n```cmd\nwget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.31-linux-glibc2.12-x86_64.tar.gz\n```\n\n## 二、安装Mysql\n1、在执行wget命令的目录下或你的上传目录下找到Mysql安装包,执行解压命令:\n```cmd\n#解压\ntar xzvf mysql-5.7.31-linux-glibc2.12-x86_64.tar.gz\n#删除压缩包\nrm -rf xzvf mysql-5.7.31-linux-glibc2.12-x86_64.tar.gz\n#移动解压文件夹\nmv xzvf mysql-5.7.31-linux-glibc2.12-x86_64.tar.gz /usr/local/mysql\n```\n2. 在/usr/local/mysql目录下创建data目录\n```cmd\nmkdir /usr/local/mysql/data\n```\n3. 更改mysql目录下所有的目录及文件夹所属的用户组和用户,以及权限\n```cmd\nchown -R mysql:mysql /usr/local/mysql\nchmod -R 755 /usr/local/mysql\n```\n4. 编译安装并初始化mysql,务必记住初始化输出日志末尾的密码(数据库管理员临时密码)\n```cmd\ncd /usr/local/mysql/bin\n./mysqld --initialize --user=mysql --datadir=/usr/local/mysql/data --basedir=/usr/local/mysql\n```\n5. 编辑配置文件my.cnf,添加配置如下\n```cmd\nvi /etc/my.cnf\n\n[mysqld]\ndatadir=/usr/local/mysql/data\nport=3306\nsql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES\nsymbolic-links=0\nmax_connections=600\n#是否将每个表的数据单独存储,1表示单独存储;0表示关闭独立表空间,可以通过查看数据目录,查看文件结构的区别;\ninnodb_file_per_table=1\n#是否区分大小写,1表示存储时表名为小写,操作时不区分大小写;0表示区分大小写;不能动态设置,修改后,必须重启才能生效:\nlower_case_table_names=1\n#设置数据库默认字符集,如果不设置默认为latin1\ncharacter_set_server=utf8\n\n```\n6. 测试启动mysql服务器\n```cmd\n/usr/local/mysql/support-files/mysql.server start\n```\n7. 添加软连接,并重启mysql服务\n```cmd\nln -s /usr/local/mysql/support-files/mysql.server /etc/init.d/mysql\nln -s /usr/local/mysql/bin/mysql /usr/bin/mysql\nervice mysql restart\n\n```\n8. 登录mysql,修改密码(密码为步骤4生成的临时密码)\n```cmd\nmysql -u root -p\nEnter password:\nset password for root@localhost = password('123456');\n```\n9. 开放远程连接\n```cmd\nuse mysql;\nupdate user set user.Host='%' where user.User='root';\nlush privileges;\n```\n10. 设置开机自动启动\n```cmd\n#1. 将服务文件拷贝到init.d下,并重命名为mysql\ncp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld\n#2. 赋予可执行权限\nchmod +x /etc/init.d/mysqld\n#3. 添加服务\nchkconfig --add mysqld\n#4. 显示服务列表\nchkconfig --list\n```\n至此,mysql5.7.31版本的数据库安装,已经完成。\n具体参考文章[https://www.jianshu.com/p/276d59cbc529](https://www.jianshu.com/p/276d59cbc529)\n\n\n\n\n\n\n\n",
|
|||
|
isRecommend: true,
|
|||
|
isPublish: true,
|
|||
|
createDate: '2020-12-28T22:24:51.000Z',
|
|||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
|||
|
},
|
|||
|
],
|
|||
|
}
|
|||
|
},
|
|||
|
},
|
|||
|
]
|