老李

~路漫漫其修远兮~


  • 首页

  • 关于

  • 标签

  • 归档

为什么要做技术分享

发表于 2020-12-04 | 评论数:

1. 提升学习效率

我们来看一张图就明白了:

教学法

想必大家应该都深有感触,我们看书、看视频等被动学习,当时印象深刻,没过几天基本就忘记了。但是如果我们边看书边做脑图笔记,甚至边实战(比如敲代码做 demo)(被动学习+主动学习)那效果不言而喻,比前者(纯被动学习)要效果好很多。如果我们学完之后,在总结、整理教会别人、或者立即在项目中应用,那又是一个层次了,这个就是上图中主动学习吸收率最高的学习方法了。

2. 提升影响力,提升自信心

对于分享者,每分享一次,都会加深听众对你的认识,“原来这些我平常不知道的,这个大哥都知道”、“原来我常碰到的这个错误是这个原因啊”。无形中自己的影响力就慢慢加大了。自己优越感提升了,自然也更自信了。慢慢也会发现分享对自己带来的好处,从而对自己形成一个良性循环。

3. 总结经验教训,让自己以及他人少走弯路

通过技术分享可以总结过去,展望未来,比如近期分享的站在前端开发工程师的角度《聊聊前后端之登录鉴权》,就是对自己过去做登录鉴权的一个总结,不单单站在前端角度思考,更多的是站在整个登录鉴权流程去思考,应该使用session-cookie方案还是token方案,或者是使用OAuth方案,对着三种方案做了原理分析、优缺点对比、突出问题讲解(比如session-cookie方案时跨域要怎么处理,会出现哪些问题等等),分享前还各种参考资料的搜集整理等等,最后通过一个 demo(从前端到后端打通)的实战来分享出来。

通过该总结,自己对鉴权也更加熟悉,大大提升了对鉴权的理解。对于他人(听众)来说,有些人可能不熟悉的,也知道怎么回事了,对于熟悉的,同样也是系统的加深了一遍印象,后面跟后端对接业务的时候也能够很好的指定方案,出现问题也能很好的排错。

同时,还有另外一方面,通过技术分享,可以查漏补缺、取长补短。分享者不见得面面俱到,平常觉得应该是这样处理的问题,可能在分享会上碰到比你研究更深的人提出质疑甚至是修改之后,也能让分享者发现自己的错误:“原来我之前一直这样思考(一直这样做)是有问题的(考虑不全的),感谢大佬的提醒”。

以上是我到目前为止做技术分享的一点理解和感悟。

记一次群晖NAS+电信光猫+华硕RT-AC86U路由+自定义域名+SSL证书的群晖NAS外网访问配置过程

发表于 2020-10-25 | 更新于 2021-09-23 | 评论数:

自己买了一台群晖 NAS,一直想用自己购买的域名访问,虽然群晖官方提供了quickconnect,但我经常出现访问不了的情况,所以最终还是决定自己折腾,折腾了很久,终于算是大功告成了。

我的设备:

  1. 群晖 DS218+
  2. 电信光猫
  3. 华硕 RT-AC86U 路由器

大致思路:

  1. 将光猫改成桥接模式,申请公网 IP(打 10000 修改、申请)
  2. 路由器拨号联网(打 10000 获取账号密码)
  3. 路由器配置端口转发
  4. 用自己申请的域名绑定到公网 IP
  5. 申请免费 SSL 证书,导入到群晖

光猫改桥接+申请公网 IP

  • 方法一(推荐):打 10000,将电信光猫改成桥接模式,并申请公网 IP。
  • 方法二:也可以看这篇文章或这篇文章的教程获取到超级管理员账号,然后登陆进去改路由器为桥接模式。教程大概就是:
  1. 使用光猫背面的(普通用户)账号密码登录进光猫,然后访问链接http://192.168.1.1/backupsettings.conf下载光猫的配置文件;
  2. backupsettings.conf配置文件中就包含了超级管理员telecomadmin的密码(可以搜索 password 查看到),宽带账号密码也在里面(搜 username)。

telecomadmin
username

有了这些信息,光猫就可以自己修改了,不过我也折腾了很久,才将桥接改成功,并且路由器拨号上网也没问题了,当时遇到一个问题就是路由器 WAN IP 和百度搜索到的外网 IP 不一致,我以为自己没有公网 IP,所以后面又还原回去了,就打 10000 让电信师傅来帮忙弄了,电信师傅来弄的时候,最后也是一样的结果,路由器上 WAN IP 和百度上显示的外网 IP 不一致 😓(因为这是验证是否有公网 IP 的方法):

  • 路由器 IP:路由器IP
  • IP138 获取的 IP:IP138获取的IP

最终电信师傅回去之后微信告诉我,重启路由器就行了。重启路由后,果然成功了 😂。

路由器拨号上网+端口转发

桥接改好了,公网 IP 也有了,接下来就是路由器拨号上网、配置端口转发,登录路由器管理页面,然后找到外部网络 WAN:

  • 路由器拨号上网:路由器拨号上网
  • 端口转发配置:端口转发配置

域名解析 + 免费 SSL 证书申请 + SSL 证书导入到群晖

我的域名是在阿里云上购买的,最初用的是阿里云万网的 DNS,后面改成了腾讯云上 dnspod 的 DNS,所以我在 dnspod 进行了域名解析之后,又在腾讯云后台申请了免费 SSL 证书。

  • 域名解析:域名解析
  • 免费证书:免费证书

证书导入到群晖:

证书导入

使用域名成功访问群晖:

访问群晖

至此,配置基本完成,后续慢慢摸索优化。

vue2和vue3的v-if与v-for优先级对比学习

发表于 2020-10-09 | 更新于 2021-09-23 | 评论数:

Vue.js 中使用最多的两个指令就是 v-if 和 v-for,因此我们可能会想要同时使用它们。虽然官方不建议这样做,但有时确实是必须的,我们来了解下他们的工作方式:

  • 在 vue 2.x 中,在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用。
  • 在 vue 3.x 中,v-if 总是优先于 v-for 生效。

对比学习

接下来我们通过一个简单的示例来感知下,假设我们想要实现一个极简的 todoList 效果:

todolist

我们有一个 todoList:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const todoList = [
{
id: 0,
task: '吃饭',
done: true,
},
{
id: 1,
task: '睡觉',
done: false,
},
{
id: 2,
task: '洗澡',
done: true,
},
// ...,
];

在 vue2 中,v-for 优先级高于 v-if,我们可以这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
<ul>
<!-- vue2中,v-for优先级高于v-if -->
<li v-for="item in todoList" v-if="!item.done" :class="{todo: !item.done}" :key="item.id">
<span>{{item.task}}</span>
</li>
</ul>

<ul>
<li v-for="item in todoList" v-if="item.done" :class="{finished: item.done}" :key="item.id">
<span>{{item.task}}</span>
</li>
</ul>

在 vue3 中,由于 v-if 优先级要高于 v-for,所以不能像 vue2 那样将 v-for 和 v-if 放在同一个元素上,我们在 li 外面套一层用来执行 for 循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<ul>
<template v-for="item in list" :key="item.id">
<li v-if="!item.done" :class="{todo: !item.done}">
<span>{{item.task}}</span>
</li>
</template>
</ul>
<ul>
<template v-for="item in list" :key="item.id">
<li v-if="item.done" :class="{finished: item.done}">
<span>{{item.task}}</span>
</li>
</template>
</ul>

可以看出,如果在 vue2.x 中 v-if 和 v-for 在同一个元素上使用是无法直接在 vue3.x 中兼容的。

最佳实践

针对 v-if 和 v-for 的使用,其实官方是建议我们使用计算属性来处理的,这样既提高了性能,又可以兼容到 vue3.x,接下来我们看看计算属性实现方式:

模板部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="app">
<!-- 最佳实践 -->
<ul class="todo-list">
<li v-for="item in todos" class="todo" :key="item.id">
<span>{{item.task}}</span>
</li>
</ul>

<ul v-if="showFinished">
<li v-for="item in finished" :key="item.id" class="finished">
<span>{{item.task}}</span>
</li>
</ul>

<p>
show finished?
<input type="checkbox" v-model="showFinished" />
{{showFinished ? 'yes' : 'no'}}
</p>
</div>

js 部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// vue3.x
Vue.createApp({
data() {
return {
msg: 'Todo List',
showFinished: true,
list: todoList,
};
},
computed: {
finished() {
return todoList.filter((t) => t.done);
},
todos() {
return todoList.filter((t) => !t.done);
},
},
}).mount('#app');

// vue2.x
new Vue({
el: '#app',
data() {
return {
msg: 'Todo List',
showFinished: true,
list: todoList,
};
},
computed: {
finished() {
return todoList.filter((t) => t.done);
},
todos() {
return todoList.filter((t) => !t.done);
},
},
});

示例效果:

总结

  1. vue2.x 中v-for优先级高于v-if,vue3.x 相反;
  2. 尽量避免在同一个元素上面同时使用v-if和v-for,建议使用计算属性替代。

深入理解v-model之修饰符(vue3和vue2对比分析)

发表于 2020-09-27 | 更新于 2020-09-29 | 评论数:

vue2 中

我们知道在 vue2 中有 3 个硬编码的修饰符:lazy、number和trim,他们的作用分别如下:

  • lazy:将触发input事件转为触发change事件,在某些场景下来降低数据同步频率提升性能。
1
2
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" />
  • number:自动将用户的输入值转为数值类型。
1
<input v-model.number="age" type="number" />
  • trim:自动过滤用户输入的首尾空白字符
1
<input v-model.trim="msg" />

vue3 中

不同于 vue2 的硬编码,在 vue3 中支持自定义修饰符:

1
<my-component v-model.capitalize="bar"></my-component>

组件接收自定义修饰符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.component('my-component', {
props: {
modelValue: String,
// 组件接收属性modelModifiers对象,对象里面包含多个修饰符,这里默认给个空对象:{}
modelModifiers: {
default: () => ({}),
},
},
template: `
<input type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)">
`,
created() {
console.log(this.modelModifiers); // { capitalize: true }
},
});

我们看到组件会接收一个属性为modelModifiers的对象,上面示例给出了默认值{},然后在生命周期函数created中拿到了修饰符capitalize的值为true(因为在上面组件使用时绑定了v-model.capitalize="bar")。

接下来,我们实现修饰符capitalize,即将 v-model 绑定的值首字母大写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
app.component('my-component', {
props: {
modelValue: String,
modelModifiers: {
default: () => ({}),
},
},
template: `
<input type="text"
:value="modelValue"
@input="emitValue">
`,
methods: {
emitValue(e) {
let v = e.target.value;
if (this.modelModifiers.capitalize) {
v = v.charAt(0).toUpperCase() + v.slice(1);
}
this.emit('update:modelValue', v);
},
},
});

我们来看看效果:

由于 vue3 支持组件同时绑定多个带参数的 v-model(不带参数的话只有第一个 v-model 是有效的),所以在带参数的情况下,修饰符属性命名就变成了参数名 + "Modifiers",我们看下面代码:

1
2
3
4
<my-component
v-model:first-name.capitalize="firstName"
v-model:last-name.upper="lastName"
></my-component>

组件实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.component('my-component', {
props: ['firstName', 'firstNameModifiers', 'lastName', 'lastNameModifiers'],
template: `
<input type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"> -
<input type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)">
`,
created() {
console.log(this.firstNameModifiers); // { capitalize: true }
console.log(this.lastNameModifiers); // { upper: true }
},
});

上面代码中,我们的参数名是firstName和lastName,所以自定义组件接收到的修饰符属性由原来的modelModifiers修改为firstNameModifiers和lastNameModifiers。

下面我们来看完整的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 创建APP
const app = Vue.createApp({
data() {
return {
firstName: '',
lastName: '',
};
},
});
// 自定义组件
app.component('my-component', {
props: ['firstName', 'firstNameModifiers', 'lastName', 'lastNameModifiers'],
template: `
<p>firstName: <input type="text" :value="firstName" @input="emitCapitalize"></p>
<p>lastName: <input type="text" :value="lastName" @input="emitReverse"></p>
`,
methods: {
emitCapitalize(e) {
let v = e.target.value;
if (this.firstNameModifiers.capitalize) {
v = v.charAt(0).toUpperCase() + v.slice(1);
}
this.$emit('update:firstName', v);
},
emitReverse(e) {
let v = e.target.value;
if (this.lastNameModifiers.upper) {
v = v.toUpperCase();
}
this.$emit('update:lastName', v);
},
},
});
// 挂载
app.mount('#app');

至此,v-model 在 vue2 和 vue3 中的应用就基本讲完了,欢迎大家留言或加微信(cleam_lee)讨论!

汇总:

  1. 深入理解 v-model 之表单用法(vue3 和 vue2 对比分析)
  2. 深入理解 v-model 之自定义组件用法(vue3 和 vue2 对比分析)
  3. 深入理解 v-model 之修饰符(vue3 和 vue2 对比分析)

深入理解v-model之自定义组件用法

发表于 2020-09-27 | 更新于 2021-10-09 | 评论数:

根据上一篇《深入理解 v-model 之表单用法》基本对 v-model 有了比较深的理解,接下来我们看看它如何在自定义组件中使用。

首先,我们知道下面两个用法等价的:

1
2
3
<input v-model="msg" />
<!-- 等价于 -->
<input :value="msg" @input="msg = $event.target.value" />

在 vue3 中

当在自定义组件中使用v-model时,组件接收一个属性modelValue的值,然后通过触发update:modelValue事件来更新该值:

1
2
3
4
<custom-comp v-model="msg"></custom-comp>
<!-- 等价于 -->
<custom-comp :model-value="msg" @update:model-value="msg = $event"></custom-comp>
<!-- 建议命名按照kebab-cased规范,如:model-value,而不是modelValue -->

v-model 实现

根据上面的定义规则,我们可以这样实现一个自定义 input 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 示例1:自定义input组件
// 实现1:
app.component('custom-input', {
props: ['modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`,
});
// 实现2:使用input的v-model + computed(计算属性)
app.component('custom-input', {
props: ['modelValue'],
computed: {
value: {
get() {
return this.modelValue;
},
set(v) {
this.$emit('update:modelValue', v);
},
},
},
template: `
<input v-model="value">
`,
});

使用:

1
<custom-input v-model="msg"></custom-input>;

上面示例只是对 input 做了一层包装,如果自定义组件里面不包含 input 又该如何实现呢?为了加深理解,我们看下面一个自定义 count 组件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 示例2:自定义count组件
app.component('custom-count', {
props: {
modelValue: Number,
},
methods: {
increment() {
this.$emit('update:modelValue', ++this.modelValue);
},
decrement() {
this.$emit('update:modelValue', --this.modelValue);
},
},
template: `
<button @click="increment">+1</button> ~
<button @click="decrement">-1</button>
<p>{{modelValue}}</p>
`,
});

使用:

1
<custom-count v-model="num"></custom-count>;

我们来看看实现效果:

v-model 参数

通过示例我们发现 v-model 是接收属性modelValue的值,然后触发事件update:modelValue来更新该值,那么我们可不可以修改这个属性名modelValue呢?该如何操作?其实我们只需要给v-model添加参数即可,比如:v-model:mv,这样就将modelValue换成了mv。

我们来将上面的自定义组件改造一下:

1
2
3
4
5
6
7
8
9
app.component('custom-input', {
props: ['mv'],
template: `
<input
:value="mv"
@input="$emit('update:mv', $event.target.value)"
>
`,
});

使用方式就变成了:

1
<custom-count v-model:mv="num"></custom-count>;

多个 v-model 绑定

正是由于 vue3 中新增了 v-model 的参数传递,所以自定义组件可以同时支持多个v-model的绑定:

1
<user-name v-model:first-name="firstName" v-model:last-name="lastName"></user-name>

组件实现就变成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.component('user-name', {
props: {
firstName: String,
lastName: String,
},
template: `
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)">

<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)">
`,
});

在 vue2 中

当在自定义组件中使用v-model时,组件接收一个属性value的值,然后通过触发input事件来更新该值,这样便实现了v-model:

1
2
3
<custom-comp v-model="msg"></custom-comp>
<!-- 等价于 -->
<custom-comp :value="msg" @input="msg = $event"></custom-comp>

v-model 实现

实现方式类似,我们看下 vue2 中实现一个自定义 input 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 示例1:自定义input组件
Vue.component('comp-input', {
props: {
value: String,
},
template: `
<input
type="text"
:value="value"
@input="$emit('input', $event.target.value)"
>
`,
});

自定义 v-model 属性

同样在 vue2 中也支持修改接收的属性名,只是和 vue3 不同,vue2 是通过在组件中指定属性 model 的 prop 和 event 来修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 示例2:自定义count组件
Vue.component('custom-count', {
model: {
prop: 'v', // default: value
event: 'i', // default: input
},
props: {
v: Number,
},
data() {
return {
count: this.v,
};
},
template: `<button @click="$emit('i', ++count)">+1</button>`,
});

我们看到在这个示例里面多了一个model属性,并指定了两个属性:prop和event,没错,这正是 v-model 需要的属性和事件名,只是他们的默认值为value和input,我们通过修改 model 属性的 prop 和 event 就实现了自定义。

在线效果:

关于为什么要出来一个 model 属性,官方文档也有说明,就是为了避免和 value 值有其他用途时和 v-model 产生冲突,比如单选框、复选框,具体可以查看官方示例。

总结

自定义组件的 v-model 我们通过在 vue3 和 vue2 中的实现都讲解了一遍,而且也能发现了其中的差异:

  1. vue3 默认属性名、事件名为:modelValue和update:modelValue;而 vue2 中则是:value和input;
  2. vue3 中直接通过 v-model 后面参数v-model:foo来指定属性名,而且修改体现在父组件中,并且支持绑定多个 v-model;而 vue2 中通过子组件的model 属性中的prop值和event值来指定属性名和事件名,修改体现在子组件中。

接下来我们来看下一篇:深入理解v-model之修饰符(vue3和vue2对比分析)》。

深入理解v-model之表单用法

发表于 2020-09-25 | 更新于 2021-10-09 | 评论数:

我们知道v-model主要用于进行表单项(input、textarea、select)的输入绑定,本质上只是一个语法糖,它负责监听用户的输入事件以更新数据。

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

如何理解,我们看下面代码,对比直接使用官方 v-model 以及自定义实现 v-model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<input type="text" v-model="msg" value="msg" />
<!-- 等价于 -->
<input type="text" :value="msg" @input="msg = $event.target.value" />

<input type="text" v-model="ck" />
<!-- 等价于 -->
<input type="checkbox" :checked="ck" @change="ck=$event.target.checked" />

<select v-model="selected">
<option value="" disable>--请选择--</option>
<option value="dog">小狗</option>
<option value="cat">小猫</option>
<option value="hamster">小仓鼠</option>
</select>
<span>Selected: {{ selected }}</span>
<!-- 等价于 -->
<select :value="selected" @change="selected=$event.target.value">
<option value="" disable>--请选择--</option>
<option value="dog">小狗</option>
<option value="cat">小猫</option>
<option value="hamster">小仓鼠</option>
<span>Selected: {{ selected }}</span>
</select>

然后我们来看一个完整示例:

可以看出,实现出来效果是一样的。(可以通过修改注释代码查看效果)

但是在使用 radio 和 checkbox 的时候,我们一般是以组的形式使用,我们一般不需要 checked 的值,而是需要 value 的值,这个时候我们可以这么使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<label> <input type="checkbox" name="fruit" value="apple" v-model="cv" />苹果 </label>
<label> <input type="checkbox" name="fruit" value="banana" v-model="cv" />香蕉 </label>
<!-- 等价于 -->
<label>
<input
type="checkbox"
name="fruit"
value="apple"
:checked="cv.includes('apple')"
@change="
$event.target.checked ? cv.push($event.target.value) : cv.splice($event.target.value, 1)
"
/>苹果
</label>
<label>
<input
type="checkbox"
name="fruit"
value="banana"
:checked="cv.includes('banana')"
@change="
$event.target.checked ? cv.push($event.target.value) : cv.splice($event.target.value, 1)
"
/>香蕉
</label>

<p>性别: {{ rv }}</p>
<label> <input type="radio" name="gender" value="male" v-model="rv" />男 </label>
<label> <input type="radio" name="gender" value="female" v-model="rv" />女 </label>
<!-- 等价于 -->
<label>
<input
type="radio"
name="gender"
value="male"
:checked="rv === 'male'"
@change="$event.target.checked ? (rv = $event.target.value) : ''"
/>男
</label>
<label>
<input
type="radio"
name="gender"
value="female"
:checked="rv === 'female'"
@change="$event.target.checked ? (rv = $event.target.value) : ''"
/>女
</label>

注意:checkbox组得到的值是一个数组,而 radio组得到的是单个值。

可以看出,针对radio组和chackbox组,我们一样可以通过自定义实现 v-model 的效果。

接下来,我们看看v-model在自定义组件中的使用,以及对比其在 vue3 和 vue2 中的区别。请看下一篇文章:《深入理解v-model之自定义组件用法》

Hexo使用命令

发表于 2020-09-23 | 更新于 2021-09-23 | 评论数:

new:新建一篇文章

1
2
3
4
# new
hexo new [layout] <title>
# hexo new "post title with whitespace"
# hexo new page --path about/me
选项 描述
-p, --path 自定义新文章的路径
-r, --replace 如果存在同名文章,将其替换
-s, --slug 文章的 Slug,作为新文章的文件名和发布后的 URL

server:启动服务

1
hexo server
选项 描述
-p, --port 重设端口
-l, --log 启动日记记录,使用覆盖记录格式

generate:生成静态文件

1
2
hexo generate
# 简写:hexo g
选项 描述
-d, --deploy 文件生成后立即部署网站
-w, --watch 监视文件变动
-b, --bail 生成过程中如果发生任何未处理的异常则抛出异常
-f, --force 强制重新生成文件
Hexo 引入了差分机制,如果 public 目录存在,
那么 hexo g 只会重新生成改动的文件。
使用该参数的效果接近 hexo clean && hexo generate
-c, --concurrency 最大同时生成文件的数量,默认无限制

publish:发表草稿

1
hexo publish [layout] <filename>

deploy:部署网站

1
2
hexo deploy
# 简写:hexo d
选项 描述
-g, --generate 部署之前预先生成静态文件

render:渲染文件

1
hexo render <file1> [file2] ...
选项 描述
-o, --output 设置输出路径

migrate:博客迁移

1
hexo migrate <type>

更多:https://hexo.io/zh-cn/docs/migration

clean:清除缓存

清除缓存文件 (db.json) 和已生成的静态文件 (public)

1
hexo clean

list:列出网站资料

1
hexo list <type>

version:显示 hexo 版本

1
hexo version

Javascript内建对象Math学习

发表于 2020-03-02 | 更新于 2020-09-23 | 评论数:

Math 既不能当做一般函数来调用,也不能用于 new 操作符来创建对象。

Math 的属性都是不可修改的,因此他们都以名字大写的方式来表示自己与一般属性变量的不同。

数字 Π:

1
2
Math.PI;
3.141592653589793;

2 的平方根:

1
2
Math.SQRT2;
1.4142135623730951;

欧拉常数 e:

1
2
Math.E;
2.718281828459045;

2 的自然对数:

1
2
Math.LN2;
0.6931471805599453;

10 的自然对数:

1
2
Math.LN10;
2.302585092994046;

random()所返回的是 0 到 1 之间的某个数,下面给出一些常用示例:

1、获取 0 到 100 之间的某个数:

1
100 * Math.random();

2、获取 max 和 min 之间的值,可以通过一个公式((max - min) * Math.random() + min)来获取。如获取 2 到 10 之间的某个数:

1
8 * Math.random() + 2;

如果只需要整数的话,使用Math.floor()用于舍弃和Math.ceil()用于取入,也可以直接调用Math.round()进行四舍五入:

1
2
3
4
5
6
Math.floor(1.23);
Math.floor(1.63);
Math.ceil(1.23);
Math.ceil(1.63);
Math.round(1.23);
Math.round(1.63);

获取随机数 0 或 1:

1
Math.round(Math.random());

Math.max()获取最大值、Math.min()获取最小值:

1
2
3
4
Math.max(2, 5);
Math.min(2, 5);
Math.max(2, 5, 3, 9, 10, 11);
Math.min(12, 5, 3, 9, 10, 11);

示例:对表单中输入的月份进行验证时,可以使用下面方式来确保数据正常工作:

1
Math.min(Math.max(1, input), 12);

Math 的其他数学计算方法:

  • 指数运算(2 的 3 次方):
1
Math.pow(2, 3); // 8
  • 平方根:
1
Math.sqrt(9); // 3
  • 正弦、余弦函数:
1
2
Math.sin(number);
Math.cos(number);

其中 number 的为弧度值,返回参数 number 的正弦值,返回值介于 [-1, 1] 之间。

角度 = 360 * 弧度 / (2 * Math.PI)
化简为:
角度 = 180 * 弧度 / Math.PI

学会 Array.reduce

发表于 2018-11-22 | 更新于 2020-10-09 | 评论数:

方法解释

arr.reduce(callback[, initialValue])对数组中的每一个元素执行你提供的一个回调方法(callback)并返回一个结果值。下面我们通过示例来详细了解下。

示例(累加操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义一个数组
const arr = [1, 2, 3, 4, 5];
// 定义一个callback函数,它接收4个参数
const callback = (acc, cur, currentIndex, sourceArr) => {
console.log(`arr[${currentIndex}]: ${cur}`); // 对比查看提供与不提供初始值initialValue时的执行情况
return acc + cur;
};
// 执行reduce操作,接收返回值
const returnValue1 = arr.reduce(callback); // 不提供initialValue
// 打印返回值结果
console.log(returnValue1); // 打印结果:15

// 执行reduce操作,接收返回值
const returnValue2 = arr.reduce(callback, 5); // 提供initialValue为5
// 打印返回值结果
console.log(returnValue2); // 打印结果:20

callback 函数接收的 4 个参数说明

  • 累加值(acc)(理解:上一次callback的返回值,当做下一次callback的第一个参数。)
  • 当前值(cur)
  • 当前索引(currentIndex)
  • 原数组(sourceArr)

理解reduce()

  • 如果没有提供初始值(initialValue),reduce()将从数组索引 1(也就是第 2 个元素)开始执行callback(跳过数组的第 1 个元素),此时,acc等于数组的第一个元素值(即arr[0]),cur等于数组的第 2 个元素值(即arr[1])
  • 如果提供了初始值(initialValue),reduce()将从数组索引 0(也就是第 1 个元素)开始执行callback,此时,acc等于initialValue的值,cur等于数组的第 1 个元素值(即arr[0])

边缘情况

  • 如果数组arr为空且没有提供初始值initialValue,代码将报TypeError错误
  • 如果数组只有一个值(arr.length === 1)且没有提供初始值initialValue 或 提供了初始值initialValue但数组arr为空,则直接返回那个唯一的值且回调方法callback不会被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const arr = [1];
const callback = (acc, cur, currentIndex, sourceArr) => {
console.log(`arr[${currentIndex}]: ${cur}`); // 未执行
return acc + cur;
};
const returnValue1 = arr.reduce(callback);
console.log(returnValue1); // 打印结果:1

const arr2 = [];
const callback2 = (acc, cur, currentIndex, sourceArr) => {
console.log(`arr2[${currentIndex}]: ${cur}`); // 未执行
return acc + cur;
};
const returnValue2 = arr2.reduce(callback2, 5);
console.log(returnValue2); // 打印结果:5

const arr3 = [];
const callback3 = (acc, cur, currentIndex, sourceArr) => {
return acc + cur;
};
arr3.reduce(callback3); // Uncaught TypeError: Reduce of empty array with no initial value

如何使用vultr搭建自己的vps实现科学上网

发表于 2018-11-19 | 更新于 2021-09-23 | 评论数:
  • https://www.vultr.com/?ref=7246043
  • 科学上网:Vultr VPS 搭建 Shadowsocks(ss)教程(新手向)
  • 科学上网的终极姿势:在 Vultr VPS 上搭建 Shadowsocks
123
Cleam Lee

Cleam Lee

前端生涯记录总结

30 日志
18 标签
RSS
GitHub Twitter 微博
© 2021 Cleam Lee