# 2020年01月技术日常
# 2020/01/30 周四
# 百度统计网站测速
登录到百度统计,在优化分析 - 网站速度诊断位置,输入链接,测速速度。现在静态化后的博客比之前的jsp打开速度有了明显的提高。可以达到99分
# seo链接提交到搜索引擎
最近对zuo11.com进行了改版,完成了博客的静态化并完成了上线,nginx + 静态文件代替了原来的 tomcat + jsp + mysql的模式。针对百度收录与索引,google收录,做了一些处理。
# 登录到百度站长平台
由于url除了zuo11.com其他原来文章的url全部失效,需要让百度重新收录,添加索引。入口:百度站长平台,现在叫资源搜索平台 (opens new window)
- 提交网站改版的规则URL对,百度搜索 site:zuo11.com,将收录的网页链接,以及改版后的url以规定的格式提交
# 旧URL,对应的改版后的url,以空格隔开,一行是一条数据
http://zuo11.com/Notes.woe?action=detail¬e_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
- 提交死链,对于改版后404的页面,可以提交死链,防止搜索引擎认为网站不稳定或服务异常,导致权重评分降级
# 死链规则
http://zuo11.com/Notes.woe?
- 新链接的提交,这里推荐使用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,就是别人拿去干什么都可以。对于一些需要控制他人使用的就需要其他协议了。阮一峰博客里有一张图来解释很清晰明了
- 他人修改源码后是否可以闭源?
- 可以闭源,没一个修改过的文件是否都必须放置版权说明?
- 需要放置版权说明 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怎么使用:
- 在nginx官网 (opens new window)下载nginx,稳定版,现在是nginx/Windows-1.16.1,下载后是一个zip文件
- 解压后放到桌面,进入解压后的目录 nginx-1.16.1,先修改nginx的root文件夹,也就是80端口指向的目录。修改 conf 目录下的 nginx.conf文件,如下图,将静态项目路径设置到root后
- 运行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压缩的。
# 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)
/* 方法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如果想修复有两种办法:
- 添加append-to-body,将dialog插入到body上
- 不插入到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