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.interceptors.request.use(\n async config => {\n // 排除登录、注册、刷新token等请求\n if (!config.url.startsWith('/auth')) {\n refreshAccessToken()\n }\n return config\n },\n err => Promise.reject(err)\n)\n```\n\n这就是我在我的个人网站中的实现方式了,个人感觉还是挺优雅简单的\n\n*注意:这种实现方式有个隐藏的问题,就是如果用户是需要进行长时间操作但又不涉及请求接口,则有可能会无法及时刷新token造成token过期,当然要规避这种情况也是可以的,最简单的规避方式就是将token过期时间适当的延长一点,比如延长至12小时或者更久(不建议超过24小时)*\n\n\n\n\n\n\n\n\n\n\n",
|
||
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((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* 创建水印canvas,需要安装html2canvas.js插件\n*/\nfunction createMarkImg(el) {\n return new Promise(async (resolve, reject) => {\n try {\n const markImg = await html2canvas(el, {\n allowTaint: false, //允许污染\n useCORS: true,\n backgroundColor: null//'transparent' //背景色\n })\n resolve(markImg)\n } catch (error) {\n reject(error)\n }\n })\n}\n```\n\n写得虽然复杂了点,但是很好用,水印内容使用html+css先画好,然后直接合成到图片的指定位置就行了,省心很多 \n但人有时候就是爱折腾,头天写完的代码第二天就是觉得很别扭,还是觉得不够优雅,自我总结至少有以下两个问题 \n1. 水印部分为了实现缩放使用了两层canvas,对性能有一定的耗损,不妥\n2. 上传的图片加了水印后体积大了有两三倍,这还是在上传图片之前先进行压缩的前提下,这无疑会大大增加上传图片的速度\n\n> 再次折腾,暂定的终极版终于出炉,代码如下\n\n```js\n/**\n * 添加水印\n */\nexport async function addWaterMarker(file, el = '#markImg') {\n // 将文件blob转换成图片\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宽高,以缩小图片体积\n let imgRatio = img.naturalWidth / img.naturalHeight //图片比例\n canvas.width = 750 //默认设置成750\n canvas.height = canvas.width / imgRatio\n\n let ctx = canvas.getContext('2d')\n\n // 填充上传的图片\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\n\n // 生成水印图片\n const markWidth = document.querySelector(el).clientWidth\n let zoom = canvas.width * 0.3 / markWidth\n let markEle = document.querySelector(el)\n // 先缩放水印html再转成图片\n markEle.style.transform = `scale(${zoom})`\n const markImg = await htmlToCanvas(markEle)\n\n // 填充水印\n ctx.drawImage(markImg, canvas.width - markImg.width - 15 * zoom, canvas.height - markImg.height - 15 * zoom, markImg.width, markImg.height)\n\n // 将canvas转换成blob\n canvas.toBlob(blob => resolve(blob))\n } catch (error) {\n reject(error)\n }\n\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* html转成canvas,需要安装html2canvas.js插件\n*/\nexport function htmlToCanvas(el, backgroundColor = 'rgba(0,0,0,.1)') {\n return new Promise(async (resolve, reject) => {\n try {\n const markImg = await html2canvas(el, {\n allowTaint: false, //允许污染\n useCORS: true,\n backgroundColor //'transparent' //背景色\n })\n resolve(markImg)\n } catch (error) {\n reject(error)\n }\n })\n}\n```\n\n至此,上面两个问题也算是完美解决,如果图片体积还是过大,适当调整canvas的宽度即可,下面看一下图片加上水印后的效果\n\n![加水印后](https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/E4jwsf_Aoraki_ZH-CN7776353328_1920x1080.png)\n\n",
|
||
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服务器接收到If-None-Match后,会跟服务器上该资源的ETag进行比对,如果两者不一样,说明要更新了,返回新资源,反之则返回304告诉浏览器资源没有更新,直接使用缓存就行了\n\n两者对比\n\n在精准度上,ETag优于Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:\n\n1. 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效\n2. Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了\n3. 在性能上,Last-Modified优于ETag,也很简单理解,Last-Modified仅仅只是记录一个时间点,而 Etag需要根据文件的具体内容生成哈希值\n\n另外,如果两种方式都支持的话,服务器会优先考虑ETag\n\n这些都是我了解到的一些缓存知识,所以程序应该是直接跳过强缓存,走协商缓存了\n\n但就协商缓存来说,服务器资源不管是Last-Modified还是Etag都发生了改变,当浏览器请求的时候必然会返回最新的资源,而不应该出现缓存的情况\n\n那会不会是Nginx配置的问题呢,我参考了资料,Nginx是有禁用浏览器一切缓存的配置,即不管什么情况都不让浏览器缓存资源,每次请求都是从服务器返回最新的资源,但是直觉告诉我这并不能解决问题,反而会因为没有使用到缓存策略加重服务器的负担\n\n```shell\nlocation / {\n ...\n add_header Cache-Control "no-cache, no-store"; # 禁用浏览器一切缓存\n}\n```\n\n配置修改完就生效了,可以看到每次刷新页面都是请求服务器获取资源,不再从缓存里取数据了\n\n但是正如我预料的那样,这并没有解决那部分用户缓存的问题,所以还是将配置还原吧\n\n由此断定,那部分有缓存的用户,打开页面时加载静态资源并没有请求服务器,很有可能是手机或者钉钉预加载了页面,所有静态资源并没有在内置浏览器中发起请求,我更倾向于部分手机的钉钉会预加载网页\n\n后面经过确认,确实是华为手机的钉钉才有此情况,并且刷新后就可以显示更新的内容了,有部分用户通过手机管家清理手机缓存也能解决问题\n\n第二天开始所有用户都没有缓存的问题了,应该是预加载的缓存也有过期时间吧,过期后会自动清理,清理后就会重新请求服务器了\n\n最后吐槽一点:现在很多安卓手机都会对一些主流App进行优化,优化无可厚非,优化用户体验很好,但是请不要负优化,最起码一点不要破坏规范,不然找bug和兼容的时间可能都是几倍于开发时间了,这无疑会给开发造成巨大的负担,尤其是华为系列的手机,一个月内碰到两次华为安卓机的兼容问题了,有些兼容问题经过特殊处理还能解决,像这次的缓存问题基本就是无解了\n\n文章参考: [三元博客·谈谈前端缓存](http://47.98.159.95/my_blog/blogs/perform/001.html)\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 { fileName, url } = await uploadOSS(file)\n```",
|
||
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属性返回上一次计算结果或者重新计算值,创建时会执行一次取值操作,并收集依赖变动的对象和属性,在依赖的对象或属性变动时,仅将自身标记dirty设置成true\n\n### 10. Vue 中的 computed 和 watch 的区别在哪里\ncomputed: 计算属性\n\n计算属性是由data中已知的值得到一个新的值,这个新值会根据计算属性依赖的已知值的变化而变化,其他不相关数据的变化不会影响该新值。\n\nwatch:数据监听\n\n监听data中数据的变化,根据监听的变化执行一些操作\n\nwatch擅长处理的场景:一个数据影响多个别的数据\n\ncomputed擅长处理的场景:多个数据影响一个数据\n\n### 11. v-if、v-show、v-html的原理\nv-if会调用addIfCondition方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染。\nv-show会生成vnode,render的时候也会渲染成真实的节点,只是在render的过程中会在节点的属性中修改display属性值。\nv-html会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值。\n',
|
||
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 类型是第二个只有一个值的数据类型,这个特殊的值是 null。从逻辑角度来看,null 值表示一个空对象指针,而这也正是使用 typeof 操作符检测 null 值时会返回"object"的原因,如下面的例子所示:\n\n```javascript\nvar car = null\nconsole.log(typeof car) // "object"\n```\n\n实际上,undefined 值是派生自 null 值的,因此 ECMA-262 规定对它们的相等性测试要返回 true:\n\n```javascript\nconsole.log(null == undefined) //true\n```\n\n### 1.4.4 Boolean 类型\n\n该类型只有两个字面值:true 和 false。\n虽然 Boolean 类型的字面值只有两个,但 ECMAScript 中所有类型的值都有与这两个 Boolean 值 等价的值。要将一个值转换为其对应的 Boolean 值,可以调用转型函数 Boolean(),如下例所示:\n\n```javascript\nvar message = \'Hello world!\'\nvar messageAsBoolean = Boolean(message)\nconsole.log(messageAsBoolean) //true\n```\n\n| 数据类型 | 转换为 true 的值 | 转换为 false 的值 |\n| :-------: | :--------------------------: | :---------------: |\n| Boolean | true | false |\n| String | 任何非空字符串 | \'\'(空字符串) |\n| Number | 任何非零数字值(包括无穷大) | 0 和 NaN |\n| Undefined | 不适合这种规则 | undefined |\n\n### 1.4.5 Number 类型\n\nNumber 类型使用 IEEE754 格式来表示 整数和浮点数值(浮点数值在某些语言中也被称为双精度数值)。\n\n#### 1. 浮点数值\n\n所谓浮点数值,就是该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字\n由于保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会不失时机地将浮点数值 转换为整数值。显然,如果小数点后面没有跟任何数字,那么这个数值就可以作为整数值来保存。同样 地,如果浮点数值本身表示的就是一个整数(如 1.0),那么该值也会被转换为整数,如下面的例子所示:\n\n```javascript\nvar floatNum1 = 1 // 小数点后面没有数字——解析为 1\nvar floatNum2 = 10.0 // 整数——解析为 10\n```\n\n浮点数值的高精度是 17 位小数,但在进行算术计算时其精确度远远不如整数。例如,0.1 加 0.2 的结果不是 0.3,而是 0.30000000000000004。这个小小的舍入误差会导致无法测试特定的浮点数值。 例如:\n\n```javascript\nif (a + b == 0.3) {\n console.log(\'You got 0.3.\') // 不要做这样的测试!\n}\n```\n\n 在这个例子中,我们测试的是两个数的和是不是等于 0.3。如果这两个数\n 是 0.05和 0.25,或者是 0.15 和 0.15都不会有问题。而如前所述,如\n 果这两个数是 0.1和 0.2,那么测试将无法通过。因此,永远不 要测试某\n 个特定的浮点数值。\n\n#### 2. 数值范围\n\n由于内存的限制,ECMAScript 并不能保存世界上所有的数值。ECMAScript 能够表示的小数值保 存在 Number.MIN_VALUE 中——在大多数浏览器中,这个值是 5e-324;能够表示的大数值保存在 Number.MAX_VALUE 中——在大多数浏览器中,这个值是 1.7976931348623157e+308。如果某次计算的 结果得到了一个超出 JavaScript 数值范围的值,那么这个数值将被自动转换成特殊的 Infinity 值。具 体来说,如果这个数值是负数,则会被转换成-Infinity(负无穷),如果这个数值是正数,则会被转 换成 Infinity(正无穷)。\n\n 如果某次计算返回了正或负的 Infinity 值,那么该值将无法继续参与下一次的计算,\n 因为 Infinity 不是能够参与计算的数值\n\n#### 3. NaN\n\nNaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数 未返回数值的情况(这样就不会抛出错误了),例如,任何数值除以 0 都会导致错误,从而停止代码执行。但在 ECMAScript 中,任何数值除以 0 会返回 NaN,因此不会影响其他代码的执行。\n\n#### 4. 数值转换\n\n有 3 个函数可以把非数值转换为数值:Number()、parseInt()和 parseFloat()\n\nNumber()函数的转换规则:\n\n 1. 如果是Boolean 值,true 和false 将分别被转换为1 和0。\n 2. 如果是数字值,只是简单的传入和返回。\n 3. 如果是null 值,返回0。\n 4. 如果是undefined,返回NaN。\n 5. 如果是字符串,遵循下列规则:\n - 如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即"1"会变成1,"123"会变成123,而"011"会变成11(注意:前导的零被忽略了);\n - 如果字符串中包含有效的浮点格式,如"1.1",则将其转换为对应的浮点数值(同样,也会忽略前导零);\n - 如果字符串中包含有效的十六进制格式,例如"0xf",则将其转换为相同大小的十进制整数值;\n - 如果字符串是空的(不包含任何字符),则将其转换为0;\n - 如果字符串中包含除上述格式之外的字符,则将其转换为NaN。\n 6. 如果是对象,则调用对象的valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是NaN,则调用对象的toString()方法,然后再次依照前面的规则转换返回的字符串值。\n\n```javascript\nvar num1 = Number(\'Hello world!\') //NaN\nvar num2 = Number(\'\') //0\nvar num3 = Number(\'000011\') //11\nvar num4 = Number(true) //1\n```\n\nparseInt()函数的转换规则\n\n```javascript\nvar num1 = parseInt(\'1234blue\') // 1234\nvar num2 = parseInt(\'\') // NaN\nvar num3 = parseInt(\'0xA\') // 10(十六进制数)\nvar num4 = parseInt(22.5) // 22\nvar num5 = parseInt(\'070\') // 56(八进制数)\nvar num6 = parseInt(\'70\') // 70(十进制数)\nvar num7 = parseInt(\'0xf\') // 15(十六进制数)\n```\n\nparseFloat()转换数值的规则\n\n```javascript\nvar num1 = parseFloat(\'1234blue\') //1234 (整数)\nvar num2 = parseFloat(\'0xA\') //0\nvar num3 = parseFloat(\'22.5\') //22.5\nvar num4 = parseFloat(\'22.34.5\') //22.34\nvar num5 = parseFloat(\'0908.5\') //908.5\nvar num6 = parseFloat(\'3.125e7\') //31250000\n```\n\n### 1.4.6 String 类型\n\n1. 字符字面量\n String 数据类型包含一些特殊的字符字面量,也叫转义序列,用于表示非打印字符,或者具有其他用途的字符\n\n| 字面量 | 含义 |\n| :----: | :------------------------------------------------------------------------------------------: |\n| \\n | 换行 |\n| \\t | 制表 |\n| \\b | 空格 |\n| \\r | 回车 |\n| \\f | 进纸 |\n| \\\\\\\\ | 斜杠 |\n| \\\\\\\' | 单引号(\'),在用单引号表示的字符串中使用。例如:\'He said, \\\'hey.\\\'\' |\n| \\\\\\" | 双引号("),在用双引号表示的字符串中使用。例如:"He said, \\"hey.\\"" |\n| \\xnn | 以十六进制代码 nn 表示的一个字符(其中 n 为 0 ~ F)。例如,\\x41 表示"A" |\n| \\unnnn | 以十六进制代码 nnnn 表示的一个 Unicode 字符(其中 n 为 0 ~ F)。例如,\\u03a3 表示希腊字符 Σ |\n\n2. 字符串的特点\n ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量\n\n3. 转换为字符串\n 要把一个值转换为一个字符串有两种方式:\n\n- toString()方法\n\n```javascript\nvar age = 11\nvar ageAsString = age.toString() // 字符串"11"\nvar found = true\nvar foundAsString = found.toString() // 字符串"true"\n```\n\n- String()方法\n\n```javascript\nvar value1 = 10\nvar value2 = true\nvar value3 = null\nvar value4\nalert(String(value1)) // "10"\nalert(String(value2)) // "true"\nalert(String(value3)) // "null"\nalert(String(value4)) // "undefined"\n```\n\n未完待续。。。',
|
||
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,判断是否有元素符合 func 条件\n\n```javascript\nlet someArr = [1, 2, 3, 4]\nsomeArr.some((item) => item > 1) //true\n```\n\n> every 方法: 返回一个 Boolean,判断每一个元素是否符合 func 条件\n\n```javascript\nlet everyArr = [1, 2, 3, 4]\neveryArr.every((item) => item > 3) //false\neveryArr.every((item) => item >= 1) //true\n```\n\n> filter 方法: 返回一个符合 func 条件的元素数组,不改变原来数组\n\n```javascript\nlet ages = [23, 28, 25, 32]\nages.filter((item) => item > 25) //[28,32]\nconsole.log(ages) //[23, 28, 25, 32]\n```\n\n> map 方法: 返回一个新的 array,数组元素由每一次调用函数产生结果组成\n\n```javascript\nlet mapArr = [1, 2, 3, 4, 5, 6]\nmapArr.map((item) => item + 1) //[2,3,4,5,6,7]\n```\n\n> in 方法:用来判断对象或者数组中是否存在某个 key 或索引\n\n```javascript\nlet inObj = {\n cnName: '张生',\n enName: 'Ronnie'\n}\nconsole.log('enName' in inObj) //true\nconsole.log(4 in ages) //false\n```\n\n## 七、ES6 中的函数\n\n> 函数解构 json 对象\n\n```javascript\nlet jsonObj = {\n cnName: '张生',\n enName: 'Rose'\n}\n\nfunction fun({ cnName, enName = 'Ronnie' }) {\n console.log(cnName, enName)\n}\nfun(jsonObj) //张生 Rose\n```\n\n## 八、ES6 中的对象\n\n> 对象赋值:ES6 允许把声明的变量直接赋值给对象\n\n```javascript\nlet nameObj = { cnName, enName }\nconsole.log(nameObj) //{cnName: \"张生\",enName: \"Ronnie\"}\n\n//对象Key值构建\nlet key = 'skill'\nlet keyObj = {\n [key]: 'web'\n}\nconsole.log(keyObj) //skill: \"web\"\n```\n\n> Object.is( ) 对象比较,===为同值相等,is()为严格相等\n\n```javascript\nconsole.log(+0 === -0) //true\nconsole.log(NaN === NaN) //false\nconsole.log(Object.is(+0, -0)) //false\nconsole.log(Object.is(NaN, NaN)) //true\n```\n\n> Object.assign()对象合并\n\n```javascript\nlet obj1 = { cnName: '张生' }\nlet obj2 = { enName: 'Ronnie', age: 26 }\nlet obj3 = Object.assign(obj1, obj2)\nconsole.log(obj3) //{cnName: \"张生\", enName: \"Ronnie\", age: 26}\n```\n\n## 九、Set、WeakSet 以及 map 数据结构\n\nSet:Set 和 Array 的区别是 Set 不允许内部有重复的值,如果有只显示一个,相当于去重\n\n> Set 的声明\n\n```javascript\nlet setArr = new Set(['ronnie', 'zhangsheng', 'web'])\nconsole.log(setArr) //Set(3) {'ronnie', 'zhangsheng', 'web'}\n```\n\n> Set 值的增删查\n\n```javascript\nsetArr.add('前端') //增\nsetArr.delete('web') //删\nsetArr.has('前端') //查: true\nsetArr.clear() //清空\n```\n\n> Set 的遍历\n\n```javascript\nfor (let item of setArr) {\n console.log(item)\n}\n```\n\n> size 属性\n\n```javascript\nconsole.log(setArr.size) //3\n```\n\nWeakSet:用于存储对象的 set\n\n> WeakSet 声明,声明是不允许赋值,否则报错,同时 WeakSet 里边的值也是不允许重复的\n\n```javascript\nlet weakObj = new WeakSet()\nweakObj.add({ cnName: '张生', age: 26 })\nconsole.log(weakObj)\n```\n\nmap:map 是一种灵活,简单的适合一对一查找的数据结构,它跟 json 对象很像,但反应速度更高,而且 Map 的灵活性要更好,你可以把它看成一种特殊的键值对,但 key 可以设置成数组,值也可以设置成字符串\n\n> map 的声明\n\n```javascript\nlet map = new Map()\n```\n\n> map 的增删查\n\n```javascript\nlet obj4 = { cnName: '张生', age: 26 }\nmap.set('ronnie', obj4) //增\nmap.set('ronnie', '永远十八岁') //改:相同key则修改\nmap.set(obj4, 'Ronnie') //增:key也可以为对象\nconsole.log(map)\nconsole.log(map.get(obj4)) //取值: Ronnie\nmap.delete('ronnie') //删除:根据key值删除\nconsole.log(map.size) //size属性\nconsole.log(map.has(obj4)) //has:查找是否存在\nmap.clear() //clear清除所有元素\nconsole.log(map)\n```\n\n## 十、Proxy 预处理\n\nProxy: 类似于钩子函数,当我们在操作一个对象或者方法时会有几种前置动作\n\n- get 属性:get 属性是在你得到某对象属性值时预处理的方法,它接收三个参数\n 1. target:得到的目标值\n 2. key:目标的 key 值,相当于对象的属性\n 3. property:可选参数,这个不常用\n- set 属性:set 属性是指你要改变 Proxy 属性值时,进行的预先处理。它接收四个参数。\n 1. target:目标值\n 2. key:目标的 Key 值\n 3. value:要改变的值\n 4. receiver:改变前的原始值\n\n```javascript\nlet pro = new Proxy(\n {\n add: function (val) {\n return val + 10\n },\n name: 'I am Ronnie'\n },\n {\n get: function (target, key) {\n console.log('come in Get')\n return target[key]\n },\n set: function (target, key, value, receiver) {\n console.log(`setting ${key} = ${value}`)\n return (target[key] = value)\n }\n }\n)\nconsole.log(pro.name) //先输出 come in Get 再输出 I am Ronnie\npro.name = '张生' //setting name = 张生\n```\n\n## 十一、promise 对象的使用\n\npromise 的出现是为了解决回调地狱的问题\n\n> promise 的基本用法(举例说明),比如把大象放入冰箱有三个步骤\n\n1. 打开冰箱门\n2. 把大象放进去\n3. 关上冰箱门\n\n```javascript\nlet isSuccess = true\nfunction step1(resolve, reject) {\n console.log('First step')\n if (isSuccess) {\n resolve('成功打开冰箱门')\n } else {\n reject('打开冰箱门出错')\n }\n}\nfunction step2(resolve, reject) {\n console.log('Second step')\n isSuccess = false\n if (isSuccess) {\n resolve('成功把大象放进去')\n } else {\n reject('把大象放进去出错')\n }\n}\nfunction step3(resolve, reject) {\n console.log('Third step')\n if (isSuccess) {\n resolve('成功关上冰箱门')\n } else {\n reject('关上冰箱门出错')\n }\n}\n\nnew Promise(step1)\n .then(function (rst) {\n console.log(rst)\n return new Promise(step2)\n })\n .then(function (rst) {\n console.log(rst)\n return new Promise(step3)\n })\n .then(function (rst) {\n // console.log(rst);\n return rst\n })\n .catch((e) => {\n console.log(e) //捕获Promise reject返回的错误信息\n })\n```\n\n## 十二、class 类的使用\n\n> 类的声明与使用\n\n```javascript\nclass Coder {\n name(val) {\n console.log(val)\n return val\n }\n skill(val) {\n console.log(this.name('Ronnie') + ':' + 'Skill-' + val)\n }\n constructor(cnName, age) {\n this.cnName = cnName\n this.age = age\n }\n introduce() {\n return this.cnName + ':' + this.age\n }\n}\n\nlet Ronnie = new Coder('张生', 26)\nRonnie.name('Ronnie') //Ronnie\nRonnie.skill('web') //Ronnie: Skill-web\nconsole.log(Ronnie.introduce()) //Ronnie:26\n```\n\n> class 的继承\n\n```javascript\nclass htmler extends Coder {}\n\nlet zhangsheng = new htmler()\nzhangsheng.name('zhangsheng') //zhangsheng\n```\n\n## 十三、模块化操作\n\n> 模块化操作主要包括两个方面\n\n1. export :负责进行模块化,也是模块的输出\n2. import : 负责把模块引,也是模块的引入操作\n\n> export 的用法:export 可以让我们把变量,函数,对象进行模块化,提供外部调用接口,让外部进行引用\n\n```javascript\nexport let name = 'Ronnie' //export temp.js\n\nimport { a } from './temp.js' //在index.js中以import的形式引入 此时的a对应的是temp.js中输出的name\n```\n\n> 多变量的输出以及函数的输出\n\n```javascript\nlet var1 = 'Ronnie'\nvar var2 = '张生'\nvar var3 = 'zhangsheng'\nfunction add(a, b) {\n return a + b\n}\nexport { var1, var2, var3, add }\nimport { var1, add } from './temp' //对应的引入方式\n```\n\n> export defalut: 只能输出一个\n\n```javascript\nlet str = 'ronnie is so handsome'\nexport default str\nimport aaa from './temp.js' //对应的引入方式,引入的名称可以任意\n```\n\nOK,以上就是关于 ES6 的常用知识点了,关于 Proxy 预处理的介绍可能不是很详细,具体讲解的话将会是长篇大论,所以这里就不深入介绍了,感兴趣的朋友可以自行搜索别人的文章,相信很多朋友都解释得比我好。\n",
|
||
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;\n```\n\n### 8. flex-grow (==作用在 flex 子项==)\n\n`flex-grow`指扩展 flex 子项所占据的宽度,扩展的空间就是除去元素外剩余的空白间隙\n\n> flex-grow 不支持负值,默认值是 0,表示不占用剩余的空白间隙扩展自己的宽度。如果 flex-grow 大于 0,则 flex 容器剩余空间的分配就会发生,具体规则如下:\n\n- 所有剩余空间总量是 1。\n- 如果只有一个 flex 子项设置了 flex-grow 属性值:\n\n - 如果 flex-grow 值小于 1,则扩展的空间就总剩余空间和这个比例的计算值。\n - 如果 flex-grow 值大于 1,则独享所有剩余空间。\n\n- 如果有多个 flex 设置了 flex-grow 属性值:\n - 如果 flex-grow 值总和小于 1,则每个子项扩展的空间就总剩余空间和当前元素设置的 flex-grow 比例的计算值。\n - 如果 flex-grow 值总和大于 1,则所有剩余空间被利用,分配比例就是 flex-grow 属性值的比例。例如所有的 flex 子项都设置 flex-grow:1,则表示剩余空白间隙大家等分,如果设置的 flex-grow 比例是 1:2:1,则中间的 flex 子项占据一半的空白间隙,剩下的前后两个元素等分。\n\n语法如下:\n\n```css\nflex-grow: 0.5; /* 数值,可以是小数,默认值是 0 */\n```\n\n### 9. flex-shrink(==作用在 flex 子项==)\n\nshrink: 收缩,`flex-shrink`指的是当 flex 容器空间不足时候,单个元素的收缩比例。\n\n> flex-shrink 不支持负值,默认值是 1,也就是默认所有的 flex 子项都会收缩。如果设置为 0,则表示不收缩,保持原始的宽度。\n\n已知 flex 子项不换行,且容器空间不足,不足的空间就是“完全收缩的尺寸”:\n\n- 如果只有一个 flex 子项设置了 flex-shrink:\n - flex-shrink 值小于 1,则收缩的尺寸不完全,会有一部分内容溢出 flex 容器。\n - flex-shrink 值大于等于 1,则收缩完全,正好填满 flex 容器。\n- 如果多个 flex 子项设置了 flex-shrink:\n - flex-shrink 值的总和小于 1,则收缩的尺寸不完全,每个元素收缩尺寸占“完全收缩的尺寸”的比例就是设置的 flex-shrink 的值。\n - flex-shrink 值的总和大于 1,则收缩完全,每个元素收缩尺寸的比例和 flex-shrink 值的比例一样。下面案例演示的就是此场景。\n\n语法如下:\n\n```css\nflex-shrink: 0; /* 数值,可以是小数,默认值是 1 */\n```\n\n### 10. flex-basis(==作用在 flex 子项==)\n\n`flex-basis`定义了在分配剩余空间之前元素的默认大小。\n\n默认值是 auto,就是自动。有设置 width 则占据空间就是 width,没有设置就按内容宽度来。如果同时设置 width 和 flex-basis,就渲染表现来看,会忽略 width。flex 顾名思义就是弹性的意思,因此,实际上不建议对 flex 子项使用 width 属性,因为不够弹性。\n\n> 当剩余空间不足的时候,flex 子项的实际宽度通常并不是设置的 flex-basis 尺寸,因为 flex 布局剩余空间不足的时候默认会收缩。\n\n语法如下:\n\n```css\nflex-basis: <length> | auto; /* 默认值是 auto */\n```\n\n### 11. flex(==flex 子项中==)\n\n`flex`属性是 flex-grow,flex-shrink 和 flex-basis 的缩写。\n\n语法如下:\n\n```css\nflex: none | auto | [ < 'flex-grow' > < 'flex-shrink' >? || < 'flex-basis' > ];\n```\n\n> 第 2 和第 3 个参数(flex-shrink 和 flex-basis)是可选的。默认值为 0 1 auto。\n\n### 12. align-self(==作用在 flex 子项==)\n\n`align-self`指控制单独某一个 flex 子项的垂直对齐方式,写在 flex 容器上的这个 align-items 属性,后面是 items,有个 s,表示子项们,是全体;这里是 self,单独一个个体。语法几乎一样:\n\n```css\nalign-self: auto | flex-start | flex-end | center | baseline | stretch;\n```\n\n唯一区别就是 align-self 多了个 auto(默认值),表示继承自 flex 容器的 align-items 属性值。\n",
|
||
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(ele) 选中非ele元素的每个元素\n13. :enabled 控制表单控件的禁用状态\n14. :disabled 控制表单控件的禁用状态\n15. :checked 单选框或复选框被选中\n\n### 9. div居中\n- 水平居中\n\n> 给div设置一个宽度,然后添加margin: 0 auto属性\n```css\n div {\n width: 500px;\n margin: 0 auto;\n }\n```\n> 使用text-align:center\n```css\n .container{\n text-align: center;\n font-size: 0;\n }\n .container .box {\n display: inline-block;\n width: 500px;\n }\n```\n> 使用绝对定位\n```css\n div {\n position: absolute;\n width: 200px;\n height: 200px;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n margin: auto;\n }\n```\n- 水平垂直居中\n\n> 使用定位 + margin或transform\n```css\n div{\n position: absolute;\n width: 500px;\n height: 300px;\n top: 50%;\n left: 50%;\n /* margin方式 */\n margin: -150px 0 0 -250px;\n /* transform方式 */\n /* transform: translate(-50%,-50%); */\n }\n```\n> 使用flex\n```css\n .container {\n display: flex;\n align-items: center; /* 垂直居中 */\n justify-content: center; /* 水平居中 */\n }\n .container div {\n width: 200px;\n height: 150px;\n }\n```\n> 使用fixed\n```css\n .container {\n position: fixed;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n text-align: center;\n font-size: 0;\n white-space: nowrap;\n overflow: auto;\n }\n .container::after {\n content: \'\',\n display: inline-block;\n height: 100%;\n vertical-align: middle;\n }\n .container .box {\n display: inline-block;\n width: 300px;\n height: 300px;\n white-space: normal;\n vertical-align: middle;\n }\n```\n\n### 10. display有哪些值\n\n- block 块类型,可设置宽高和换行显示,默认宽度为父元素的宽度\n- none 不显示\n- inline 行内类型,不可设置宽高,同行显示,宽度为内容宽度\n- inline-block 行内块类型,可设置宽高,同行显示,默认宽度为内容宽度\n- list-item 像块元素一样显示,并添加样式列表标记\n- table 此元素块级表格来显示\n- inherit 从父元素继承display属性\n\n### 11. relative和absolute定位的原点\nrelative定位的元素,是相对于元素本身的正常位置来进行定位的\nabsolute定位的元素,是相对于它的第一个position值不为static的祖先元素的padding进行定位的\n\n### 12. CSS3的Flex box布局是什么,适用于什么场景\nFlex布局时CSS3新增的一种布局方式,我们可以通过将一个元素的display属性值设置成flex让它成为一个flex容器,它的所有子元素都会成为它的项目。\n\n一个flex容器默认有两条轴,一个时水平的主轴,一个是与主轴垂直的交叉轴。我们可以使用flex-direction来指定主轴的方向\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n',
|
||
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',
|
||
},
|
||
],
|
||
}
|
||
},
|
||
},
|
||
]
|