# 2020年01月技术日常

# 2020/01/30 周四

# 百度统计网站测速

登录到百度统计,在优化分析 - 网站速度诊断位置,输入链接,测速速度。现在静态化后的博客比之前的jsp打开速度有了明显的提高。可以达到99分

# seo链接提交到搜索引擎

最近对zuo11.com进行了改版,完成了博客的静态化并完成了上线,nginx + 静态文件代替了原来的 tomcat + jsp + mysql的模式。针对百度收录与索引,google收录,做了一些处理。

# 登录到百度站长平台

由于url除了zuo11.com其他原来文章的url全部失效,需要让百度重新收录,添加索引。入口:百度站长平台,现在叫资源搜索平台 (opens new window)

  1. 提交网站改版的规则URL对,百度搜索 site:zuo11.com,将收录的网页链接,以及改版后的url以规定的格式提交
# 旧URL,对应的改版后的url,以空格隔开,一行是一条数据
http://zuo11.com/Notes.woe?action=detail&note_id=24 http://www.zuo11.com/blog/2016/10/c_vim.html
http://zuo11.com/Notes.woe?action=APUE http://www.zuo11.com/blog/category.html
  1. 提交死链,对于改版后404的页面,可以提交死链,防止搜索引擎认为网站不稳定或服务异常,导致权重评分降级
# 死链规则
http://zuo11.com/Notes.woe?
  1. 新链接的提交,这里推荐使用sitemap的方式提交链接,怎么生成sitemap呢?使用 https://www.xml-sitemaps.com/ 输入你的站点,就可以自动生成sitemap.xml信息了,默认只有sitemap.xml,可以找更多文件的下载入口,可以下到一个sitemaps.zip的一个文件,里面还包含了txt、html等非xml格式的数据。将生成后的sitemap.xml文件放到域名根目录下,提交对应的链接到百度站长平台

# 登录到Google search console

入口:Google search console (opens new window),登录后左侧菜单index - Sitemaps提交sitemap链接

# 开源协议MIT等具体含义

一般新建一个开源仓库时,需要确定开源协议。之前习惯是MIT,就是别人拿去干什么都可以。对于一些需要控制他人使用的就需要其他协议了。阮一峰博客里有一张图来解释很清晰明了

开源协议.jpg

  • 他人修改源码后是否可以闭源?
    • 可以闭源,没一个修改过的文件是否都必须放置版权说明?
      • 需要放置版权说明 Apache许可证
      • 不需要放版权说明,衍生软件的广告是否可以使用你的名字促销?
        • 可以用你的名字促销 MIT许可证
        • 不可以用你的名字促销 BSD许可证
    • 只能开源,那新增代码后是否采用同样的许可证(不能闭源)?
      • 新增代码后也只能开源 GPL许可证
      • 新增代码后可以闭源,需要对源码的修改之处提供说明文档吗?
        • 需要对源码的修改之处提供说明文档 LGPL许可证
        • 不需要提供说明文档 Mozilla许可证

结合实际情况,zuo11.com个人站点blog部分的开源可以使用 Apache许可证,如果是后面开源生成静态页面的程序,可以使用MIT协议

# 2020/01/29 周三

# 将markdown文件转html

其实早在18年12月,我就已经写好了最简的demo,使用的是marked这个工具。本来准备将博客静态化的,但后来就没继续了,这里来说下方法

// github: https://github.com/markedjs/marked
// marked.js 是下载好的
let marked = require('./lib/marked') // import marked.js
let fs = require('fs')
// 读取md文件
fs.readFile('./notes/2016/10/iOS程序启动过程,从main函数开始UIApplication与AppDelegate.md', (err, data) => {
  if (err) {
    console.log(err);
    return;
  }

  // 这里加入了基本的html框架,加入了代码高亮prismjs
  let htmlStr = `<!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
      <link href="../lib/prismjs/prism_default.css" rel="stylesheet" />
    </head>
    <body>
      ${marked(data.toString())}
      <script src="../lib/prismjs/prism_default.js"></script>
    </body>
  </html>
  `
  // 生成新的文件
  fs.writeFile('./dist/test.html', htmlStr, (err) => {
    console.log(err)
    console.log('写入文件成功');
  })
})

注意:marked将md文件转html时,如果在ol或ul后面加了代码块,必须换行,如果不换行就会准换异常

// - 这是一个ul
// ul后面这里不能直接用```写代码,需要换行,如果不换行在Typora可以正常渲染,但marked转换时会出问题

# nginx开启gzip

首先复习下windows下,nginx怎么使用:

  1. nginx官网 (opens new window)下载nginx,稳定版,现在是nginx/Windows-1.16.1,下载后是一个zip文件
  2. 解压后放到桌面,进入解压后的目录 nginx-1.16.1,先修改nginx的root文件夹,也就是80端口指向的目录。修改 conf 目录下的 nginx.conf文件,如下图,将静态项目路径设置到root后

nginx配置.png

  1. 运行nginx
# 进入到nginx目录,shift + 鼠标右键,在此处打开命令窗口,将nginx.exe拖到窗口,再打一个空格 -c 配置文件,类似下面的命令
nginx.exe -c conf/nginx.conf
# 关闭nginx服务,注意 nginx.exe 是将nginx.exe文件拖到terminal时产生的
nginx.exe -s stop

在上面的图中,已经有开启gzip的代码了。默认情况下 gzip on 是注释掉的,我们打开这个注释再添加几个属性即可。对于额外增加的几个属性这里说明下:

  • gzip_types是指定需要开启gzip压缩的文件类型
  • gzip_comp_level 指定压缩等级
  • gzip_min_length 当超过多少字节时就压缩,我上面设置的是1K
  • gzip_vary 增加响应头”Vary: Accept-Encoding”

# 怎么判断nginx是否成功开启gzip

打开chrome访问对应的站点,F12,点击network. 在Name,Priority 那一栏的最右侧空白位置,右键,勾选 Content-Encoding,如下图,设置好后刷新页面就可以看到Content-Encoding那一列了,如果有gzip就说明开启了gzip,需要结合Size这个属性看,如果没有超过设定大小的文件,是不会开启gzip压缩的。

chrome_gzip.png

# 2020/01/21 周二

# 使用set去重时的问题

如果add的值是数组,那么是无法自动去重的。下面来看一个例子:

var mySet = new Set()
mySet.add([-1,0,1])
mySet.add([-1,0,1])
mySet.size // 2
console.log(Array.from(mySet)) // [[-1, 0, 1], [-1, 0, 1]]

// 这种情况想去重,可以将值[-1, 0, 1].join('|') 处理下,添加进去,到时统一再split出来

# 数组排序sort值有负数时排序异常

正常情况使用 sort 是好用的,但如果有负数时,会有问题

// 
var arr = [5, -11, -10] 
arr.sort() // [-10, -11, 5]

// 明显上面的结果是有问题的,默认的排序遇到负数就不正确了
// 这就需要自定义排序了
arr.sort((a, b) => a - b) // [-11, -10, 5]

再来个复杂的例子 [[-15, -10, 3], [-14, -9, 5]] 对于这种数组,怎么写自定义排序?

arr.sort((a, b) => {
  if (a[0] === b[0] && a[1] === b[1]) {
    return a[2] - b[2]
  }
  if (a[0] === b[0]) {
    return a[1] - b[1]
  }
  return a[0] - b[0] 
}) 

# 2020/01/19 周日

# 零编码或少编码生成通用封装的axios函数

现在项目中,每个模块都会单独弄一个对应service.js,把所有接口请求放到里面,其实就是将axios请求封装为一个个函数。每个函数的函数名、url、请求方法会有所差别。重复代码比较多。最近在看mongodb教程时,了解到零编码编程的思想,于是想把这里优化下,最好以后写新模块时,只要写简单的配置文件就可以自动生成函数,不用再单独手写函数。先来看看原来的方式:

// someService.js
// 这里的service是对axios的封装,增加了一些请求拦截,用响应拦截等
import { service, downloadService } from '../service.js'
let someService = {
  urls: {
    funcA: '/api/url1',
    funcB: '/api/url2',
    funcC: '/api/url3',
  },
  funcA() {
    return service.post(someService.urls.funcA, payload)
  },
  funcB() {
    return service.get(someService.urls.funcB, {params: payload})
  },
  funcC() {
    return downloadService.post(someService.urls.funcC, payload)
  }
}
export default someService

// 在vue组件里,使用方法
import someService from 'someService.js'
someService.funcA(payload).then(() => {
  // 接收结果
})

当接口比较多时,比如20+,那就需要写20个类似的函数,冗余性太高,这里使用零编码编程的思想来优化一下,先来看看优化后的代码

// 优化后的 someService.js 和旧的写法实现的功能一样,且更加强大
import generateCommonApi from './utils/generateCommonApi'
import { downloadService } from '../service.js'

// 之前使用对象结构,发现还是有大量重复的属性字段,不是很方便,用数组的方式,写法更精简,更高效
// 但同时牺牲了扩展性,类似于大的框架总会遇到的问题:各种实现都各有优缺点,关键是要去找一种平衡,做一些取舍。
const someApiList = [
  ['funcA', '/api/url1', 'post'],
  ['funcB', '/api/url2'],
  ['funcC', '/api/url3', 'post', downloadService]
]
export default generateCommonApi(someApiList)

这里通过写一个generateCommonApi.js来实现自动生成通用api对象,以后就不用再写大量重复的代码了。来看看具体实现:

// generateCommonApi.js
// 参考文档:https://github.com/axios/axios
import { services } from './service.js'

function generateCommonApi(apiList, isAddTimestamp2Url) {
  let obj = {}
  let methodsList = ['request', 'get', 'delete', 'head', 'options', 'post', 'put' 'patch']

  // 遍历JSON配置,生成对应的请求函数并挂载到obj对象
  apiList.forEach(item => {
    let [apiName, url, method = 'get', servicesFunc = services] = item
    let isMethodOk = typeof method === 'string' && methodsList.includes(method.toLowerCase())
    method = isMethodOk ? method : 'get'

    // 如果需要加时间戳
    url += isAddTimestamp2Url ? '' : `${url.includes('?') ? '&' : '?'}t=${+new Date()}`

    obj[apiName] = async (payload = {}, config = {}) => {
      let paramsMap = {
        '1': [payload],
        '2': [url, {params: payload, ...config}],
        '3': [url, payload, config]
      }
      let is3Args = ['post', 'put', 'patch'].includes(method)
      let methodType = method === 'request' ? '1' : is3Args ? '3' : '2'

      return servicesFunc[method](...paramsMap[methodType])
    }
  })
  return obj
}

export default generateCommonApi 

# 测试loading时写的等待函数最简代码

一般想模拟延时,测试loading效果时,会写一个等待函数,怎么最简单方便呢?

// 一般写法
const delay = function (msec){
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, msec)
  }) 
}
await delay(2000)
console.log('test')

// 使用箭头函数简写
const delay = (msec) => new Promise(resolve => setTimeout(() => resolve(), msec))
await delay(2000)
console.log('test')

// 舍弃函数封装与自定义时长,最精简写法
(async () => {
  await new Promise(r => setTimeout(() => r(), 2000)) // 一行代码
  console.log('test')
})()

在vue中的实际应用

vue.prototype.$mydelay = (t) => new Promise(r => setTimeout(() => r(), t))
// 在vue中间中调用
await this.$mydelay(2000)

# 2020/01/16 周四

# node遍历文件夹下的文件名再require对应的文件出错

在使用koa mock接口时,一个模块有很多接口,就写了十几个js,每个js对应一个接口数据。如果每增加一个接口,再添加一个require就很麻烦,于是写了个index.js来遍历文件夹,进行动态引入。以后写好js,就不用再手动require了。之前都运行正常的,最近再运行时发现一直报错,require异常。后来打印遍历的fileName,发现居然有 .DS_Store 文件,require这个文件时错误。这个文件属于mac系统自动生成的文件,之前都没有的。在程序中过滤调这个文件即可。

mock目录结构如下:

# 目录结构
mock
├── pm
│   ├── index.js # 入口
│   ├── 接口1.js
│   ├── 接口2.js
│   └── 接口n.js
├── user 
├── product
└── sever.js # 入口文件,require('./pm/index.js')(router) 将接口添加到路由

pm/index.js 代码如下:

const fs = require('fs')

module.exports = router => {
  fs.readdirSync(__dirname).forEach(fileName => {
    if (fileName === 'index.js' || fileName.startsWith('.')) return
    require('./' + item)(router)
  })
}

# 2020/01/14 周二

# export PATH 后关闭termial就失效了

mac 修改环境变量,以便能在任何地方使用 mongod 命令

# 运行该命令后,当前terminal生效了,但关闭后,打开其他terminal就无效
export PATH=/usr/local/mongodb/bin:$PATH

这种情况需要在 .bash_profile 文件里,加入 export PATH=/usr/local/mongodb/bin:$PATH 才行

vi ~/.bash_profile

修改后的 .bash_profile 文件,之前还修改过mysql的环境变量

export PATH=${PATH}:/usr/local/mysql/bin
export PATH="$HOME/.rbenv/bin:$PATH"
export PATH=/usr/local/mongodb/bin:$PATH
if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi

修改好后,关闭该终端,再打开,运行 mongod 命令即可

# npm简写与项目名称注意事项

最近在安装 npm install 时喜欢简写 npm i mysql2 -s 但发现执行后,并没有安装成功,在package.json里也没有任何记录。还没有报任何错误。

# 把简写的命令换成 非简写 再执行
npm install mysql --save

# kevindeMacBook-Air:mysql2 kevin$ npm i mysql2 -s    # 简写执行后,没有任何信息
# kevindeMacBook-Air:mysql2 kevin$ npm install mysql2 -save # 这次就报错了,提示项目名称与包名称相同
# npm ERR! code ENOSELF
# npm ERR! Refusing to install package with name "mysql2" under a package
# npm ERR! also called "mysql2". Did you name your project the same
# npm ERR! as the dependency you're installing?
# npm ERR! 
# npm ERR! For more information, see:
# npm ERR!     <https://docs.npmjs.com/cli/install#limitations-of-npms-install-algorithm>

# 由于在初始化生成package.json时为了方便,直接使用了下面的命令
npm init -y  # 所有默认yes,生成的package.json里面,项目名称字段为当前文件夹名。

# 由于文件夹名就是 mysql2,与安装的包名重复了。将package.json里的name属性改一个名字即可

总结: 项目名称不要与依赖的包名相同,当npm安装简写执行异常时,使用非简写方法再试试。

# 2020/01/11 周六

# markdown里怎么为文字添加颜色,怎么画复杂表格

之前看markdown语法时,并没有添加颜色和复杂表格的方法,但最近了解到markdown里面可以直接使用html,那就方便了。复杂表格直接使用table标签画,如果想给文字加颜色

<span style="color: red">这是一段有颜色的字体</span>

<!-- 下划线 -->
<u>这是一段有下划线的文字</u>

注意:markdown解析器很多。在Typora嵌入html的复杂表格没什么问题,但在github上显示时,table被放到了文档的最下面。语雀里导入时,也会有一点问题。所以为了保证最大程度的兼容,写md时,尽量避免使用内嵌html,对于复杂表格可以使用图片代替

# 2020/01/10 周五

# css var() 与变量 --

<style>
  /* 最大高度为三行,将line-height定义为变量lh */
  .module {
    --lh: 1.2rem;
    line-height: var(--lh);
    max-height: calc(var(--lh) * 3);
    overflow: hidden;
  } 
</style>

# 多行文本截断

参考:Line Clampin' (Truncating Multiple Line Text) | CSS-Tricks (opens new window)

line_clamp.png

/* 方法1:使用 ::after 伪元素选择器,覆盖*/
 .method-a {
  line-height: 1.2rem;
  max-height: 3.6rem;
  overflow: hidden;
  position: relative;
}
.method-a::after {
  position: absolute;
  bottom: 0;
  right: 0;
  width: 30%;
  max-width: 100px;
  content: '';
  height: 1.2rem;
  background: linear-gradient(to right, rgba(255,255,255,0), rgba(255,255,255,1) 90%);
}

/* 方法2:使用 -webkit-line-clamp 不过不支持IE */
.method-b {
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}

# position:relative

之前想把内容区域的title,有一半放到顶部背景,当时只想着使用position:absolute来做,现在发现将title的position设置为relative,top为负数就可以了,且不影响标题下面元素显示的布局。position默认为static,不能使用top,right,left,bottom属性。

.title {
  position: relative;
  top: -10px;
} 

# node path.resolve()

koa静态文件服务中间件的实现里,需要将当前路径 __dirname 与用户传入的路径合并为一个绝对路径,就可以使用path.resolve函数

The path.resolve() method resolves a sequence of paths or path segments into an absolute path.

const path = require('path')

path.resolve('/foo/bar', './baz');
// Returns: '/foo/bar/baz'

path.resolve('/foo/bar', '/tmp/file/');
// Returns: '/tmp/file'

let dirPath = './public'
path.resolve(__dirname, dirPath)

参考:node path.resolve (opens new window)

# 2020/01/09 周四

# asycn/await 执行顺序问题

注意:await 后面的内容如果值为promise,则等待promise执行完再向下执行,如果非promise,await不会等待(await下面的代码和await等待的函数会同步执行)

(async () => {
  await test() // await fn()
  console.log('异步执行完成')
})()

async function test() {
  fn() // return fn() 或 await fn()
}

async function fn(next) {
  console.log('start fn')
  await delay()
  console.log('end fn')
}

function delay() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, 2000)
  })
}

// return fn()  或 await fn() 结果
// start fn
// end fn
// 异步执行完成

// fn() 结果
// start fn
// 异步执行完成
// end fn

参考:async/await函数的执行顺序的理解 - csdn (opens new window)

# forEach的缺点

// 真实场景: 匹配路由数组里的路径,匹配到就结束遍历,发现无法结束遍历
// 遍历路由进行匹配,如果匹配到了则执行,停止往下执行下一个中间件,否则向下执行
stock.forEach((item) => {
  if (ctx.url === item.path && item.methods.includes(ctx.method)) {
    return
  }
  await next()
})

总结:forEach遍历开始后,无法停止后面的遍历,无法终止执行函数。for里面可以通过break来结束循环,return结束函数。对于需要遍历到匹配的数据就退出的情况,就需要使用for了

很清奇的操作:使用抛异常的方式,来结束forEach循环

// 参考:https://www.cnblogs.com/Marydon20170307/p/8920775.html
try {
  var array = ["first","second","third","fourth"];

  // 执行到第3次,结束循环
  array.forEach(function(item,index){
      if (item == "third") {
          throw new Error("EndIterative");
      }
      alert(item);// first,sencond
  });
} catch(e) {
    if(e.message!="EndIterative") throw e;
};

# 2020/01/08 周三

# IE下dialog弹窗全屏后列表横向滚动卡顿的问题

全屏后添加了一个名为 is-fullscreen 的class,发现把对应的overflow:auto去掉后,就不会卡顿。去掉其实就是将overflow设置为默认值visible,如果子组件高度超出范围,将is-fullscreen设置的height 100% 改为 auto。另一种方法是将table的z-index改为3000(相对dialog比较高的一个层级),这样IE下就不会卡顿了。

overflow相关值描述

描述
visible 默认值。内容不会被修剪,会呈现在元素框之外。
hidden 内容会被修剪,并且其余内容是不可见的。
scroll 内容会被修剪,但是浏览器会显示滚动条以便查看其余的内容。
auto 如果内容被修剪,则浏览器会显示滚动条以便查看其余的内容。
inherit 规定应该从父元素继承 overflow 属性的值。

参考:CSS overflow 属性 (opens new window)

# if else较多时可使用策略模式

// if else 
if () {
  a
} else if () {
  b
} else if () {
  c
}

// 更优雅的写法,策略模式
// 更多策略模式,策略模式表单验证可参考:JS设计模式与开发实战 第五章p82
let rules = [
  'a': () => { a },
  'b': () => { b },
  'c': () => { c },
]
rules[name]()

# 2020/01/07 周二

# 判断当前鼠标是否在某个div内部

let eventType = document.mozHidden ? 'DOMMouseScroll' : 'mousewheel'
let ele = '某个dom'
ele.addEventListener(eventType, (e) => {
  if ('容器范围内dom'.contains(e.target)) {
    // 当前鼠标在容器内
  }
})

参考: Node.contains - Web API 接口参考 | MDN (opens new window)

# IE下dialog弹窗的滚动条滚动到底部时,触发了浏览器滚动条

同样都是有遮罩层,chrome都是OK的,但IE下会有问题。经过定位后发现,对于dialog使用了append-to-body属性的,都没问题。发现dialog显示时body上添加了一个 el-popup-parent--hidden的class, 设置了overflow为hidden,关了滚动条。对于没有append-to-body属性的dialog如果想修复有两种办法:

  1. 添加append-to-body,将dialog插入到body上
  2. 不插入到body,根据el-popup-parent--hidden将有滚动条的子div设置overflow:hidden。

对于不是dialog,普通的弹窗,可以使用另一种思路: 监听鼠标滚动事件,使用Node.contains函数,判断鼠标是否在dialog范围内滚动,如果是,且到了底部,禁止其默认行为

append-to-body属性: Dialog 自身是否插入至 body 元素上。嵌套的 Dialog 必须指定该属性并赋值为 true

参考:element dialog (opens new window)

# Element表格IE下由于滚动条原因导致错位问题

Element 表头固定,表内容可滑动,在IE下滚动条会显示,有一定的宽度占位,导致表头与表内容由点错位,解决方法是:由于表单内容是从接口加载的,从接口加载完数据后,对el-talbe进行从新布局dolayout

// Table Methods: doLayout
// 对 Table 进行重新布局。当 Table 或其祖先元素由隐藏切换为显示时,可能需要调用此方法
// <el-table ref="table"></el-table>

// 从接口获取数据成功后
this.$nextTick(() => {
  this.$refs['table'].doLayout()
})

参考:Table 表格组件 | Element (opens new window)

# 如果组件的隐藏显示切换导致布局异常,可以将透明度设置为0来隐藏

注意:将元素的透明度设置为0,如果元素内部可点击或者遮挡了下面层级的点击按钮,可以根据布局进行调整,比如调整z-index或使用margin等将元素移动到其他位置。

element {
  opacity: 0; /* 设置透明度为0 */
}

# 2020/01/03 周五

# 初始化一个ts项目

# 初始化一个yarn
yarn init

# 新增 ts-node 与 typescript模块
yarn add ts-node --dev
yarn add typescript --dev

# 运行index.ts
yarn ts-node index.ts  # 控制台打印 hello ts-weather

# index.ts 内容 console.log('hello ts-weather')

# 2020/01/02 周四

# TypeScript基础

最近把TypeScript基础初略的过了一遍,笔记:https://www.yuque.com/guoqzuo/tepur0/zb0x9b

上次更新: 2020/10/29 22:59:19