隐藏内容

· ⏰ 2 分钟阅读
losgif

前情提要

Gitlab CI 环境一般有:docker, shell。本文讨论在docker运行环境中如何再次构建Docker镜像。
有点先有鸡还是先有蛋的意思,所以docker的解决方案是dind

dind 指的是 Docker in Docker,一种在docker容器内使用docker的方法

构建

首先看下文件结构

$ tree     
.
├── .gitlab-ci.yml
└── Dockerfile

0 directories, 3 files

Dockerfile

不必多说,docker构建镜像必备文件

.gitlab-ci.yml

gitlab ci 配置文件

直接看下配置文件

# This file is a template, and might need editing before it works on your project.
build:
tags:
- docker-ci-runner
# Official docker image.
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- docker build --tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
only:
# 仅运行在添加标签时
- tags

前置知识需要了解 .gitlab-ci.yml 配置
文档可以查看这里
内容很简单,声明image、tags、script、only、services
主要关注services内声明使用了docker:dind,借此可以完成docker容器 内构建docker 镜像。

docker build --tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG" .

此命令完成了构建镜像任务,且使用git标签名作为镜像标签。

推送

docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"

Gitlab CE版本也是支持docker镜像库的,上面的配置可以完成推送至镜像库。
至此完成了镜像构建和推送。

· ⏰ 5 分钟阅读
losgif

使用场景

在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。
此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。

名词解释

防抖函数

防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,
事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。

debounce.png

节流函数

节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。
如下图,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。 throttle.png

代码实现

防抖:

// 防抖
function debounce (fn: Function, wait) {
var timeout = null;
return function () {
if (timeout !== null) {
clearTimeout(timeout);
}

timeout = setTimeout(fn, wait);
}
}

// 处理函数
function handle () {
console.log(Math.random());
}

// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));

当持续触发scroll事件时,事件处理函数handle只在停止滚动1000毫秒之后才会调用一次。
也就是说在持续触发scroll事件的过程中,事件处理函数handle一直没有执行。

节流

var throttle = function (func, delay) {
var timer = null;
return function () {
var context = this;
var args = arguments;

if (!timer) {
timer = setTimeout(function () {
func.call(context, args);
timer = null;
}, delay);
}
}
}

function handle () {
console.log(Math.random());
}

window.addEventListener('scroll', throttle(handle, 1000));

当触发事件的时候,我们设置一个定时器,再次触发事件的时候,如果定时器存在,就不执行,直到delay时间后,定时器执行执行函数,并且清空定时器,这样就可以设置下个定时器。当第一次触发事件时,不会立即执行函数,而是在delay秒后才执行。而后再怎么频繁触发事件,也都是每delay时间才执行一次。当最后一次停止触发后,由于定时器的delay延迟,可能还会执行一次函数。

总结

防抖:

将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

节流:

使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。

区别:

函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

参考

https://www.lodashjs.com/docs/lodash.debounce https://www.huaweicloud.com/articles/d3842d8455d26db6868c19c7daced54b.html https://dev.to/heymarkkop/debounce-x-throttle-23k5

· ⏰ 5 分钟阅读
losgif

为什么?

引言: Vue 3 都拥抱 TypeScript 了,你说呢?[狗头]

随着应用的增长,静态类型系统可以帮助防止许多潜在的运行时错误,这就是为什么 Vue 3 是用 TypeScript 编写的。这意味着在 Vue 中使用 TypeScript 不需要任何其他工具——它具有一流的公民支持。

如何操作

Vue 3 使用 TypeScript 很简单,CLI 创建项目时选取下即可
已有Vue2项目不配吗?
当然不是,Vue 官方通过 ts 的 d.ts 文件申明了Vue 2的静态类型
也实现了Vue 2使用 TypeScript 的愿望

添加 TypeScript 支持

安装 NPM 包

npm install -D typescript

配置 typconfig.json 文件

// tsconfig.json
{
"compilerOptions": {
// 与 Vue 的浏览器支持保持一致
"target": "es5",
// 这可以对 `this` 上的数据 property 进行更严格的推断
"strict": true,
// 如果使用 webpack 2+ 或 rollup,可以利用 tree-shake:
"module": "es2015",
"moduleResolution": "node"
}
}

安装工作到此已经完成

基本用法

之前直接使用对象定义组件的方式行不通了
要让 TypeScript 正确推断 Vue 组件选项中的类型
您需要使用 Vue.component 或 Vue.extend 定义组件

import Vue from 'vue'
const RightComponent = Vue.extend({
// 类型推断已启用
})

const ErrorComponent = {
// 这里不会有类型推断,
// 因为 TypeScript 不能确认这是 Vue 组件的选项
}

高级用法

请参见 VueJS 官方文档

基于类的 Vue 组件

如果你说我不喜欢 Vue.extendVue.component 定义出来的组件,想使用 Class 定义
也是可以的

可以使用官方维护的 vue-class-component 装饰器:

import Vue from 'vue'
import Component from 'vue-class-component'

// @Component 修饰符注明了此类为一个 Vue 组件
@Component({
// 所有的组件选项都可以放在这里
template: '<button @click="onClick">Click!</button>'
})
export default class MyComponent extends Vue {
// 初始数据可以直接声明为实例的 property
message: string = 'Hello!'

// 组件方法也可以直接声明为实例的方法
onClick (): void {
window.alert(this.message)
}
}

与 TSX 搭配使用

既然都用上了 Class 组件,干脆再 "React" 点,来人,上 TSX

import AnchoredHeading from './AnchoredHeading.vue'

new Vue({
el: '#demo',
render: function (h) {
return (
<AnchoredHeading level={1}>
<span>Hello</span> world!
</AnchoredHeading>
)
}
})

组件定义方式

Vue2 + JS

方法一:使用对象定义

export default {
// type inference disabled
}

方法二:使用 Composition API 方式定义

此方式可以大部分兼容 Vue3 写法,升级Vue3时只需要修改导入即可

import { defineComponent } from '@vue/composition-api'

export default defineComponent({
// type inference disabled
})

Vue3 + JS

与 Vue2 + JS 方法二 基本一致,导入差异罢了

import { defineComponent } from 'vue'

export default defineComponent({
// type inference disabled
})

Vue2 + TS

方法一: 使用 Vue.extend 定义

基本兼容Vue2对象定义写法

danger

注意 $ref 不兼容

import { Vue } from 'vue-property-decorator'

export default Vue.extend({
// type inference enabled
})

方法二:使用 Vue.component 定义

同上

import { Vue } from 'vue-property-decorator'

export default Vue.component({
// type inference enabled
})

方法三

出现了,React "邪教" 成员

<template>
<div>
...
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'

@Component
export default class App extends Vue ({
// type inference enabled
})
</script>

方法四

浓厚味道的 React 写法

import { Vue, Component } from 'vue-property-decorator'

@Component
export default class App extends Vue ({
@Prop({
type: String,
required: true
}) readonly msg!: string

render () {
return (
<div>
<div>
{ this.msg }
</div>
<input type="text" v-model={this.msg}/>
</div>
)
}
})

参考文献

Vue.js 官方文档
Vue Class Component 官方文档
Vue Composition Api 官方文档

· ⏰ 3 分钟阅读
losgif

优势

可以在一个仓库或者多个仓库里保存源码及构建后的静态文件产物 全程自动化部署,无需人工干预 GitHub 代码托管,保证代码安全无虞

GitHub Actions 是什么?

GitHub Actions 是由 GitHub 推出的包含 CI、CD、构建、测试及部署的自动化工作流

官方介绍为:

GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub. Make code reviews, branch management, and issue triaging work the way you want.

GitHub Pages 是什么?

GitHub Pages 是由 GitHub 推出的一项静态页面托管服务,可用以展示项目、个人、组织等相关页面。

具体 Github Pages 此处不做过多赘述,请自行查阅资料

简单使用

借用 Actions 的强大可自定义性质,我们可以使用 GitHub Actions 实现对 GitHub Pages 的自动化部署。

创建Git仓库并安装博客程序

首先创建一个用来保存静态页面的仓库,这里我们使用的静态页面生成器是 Hexo 博客框架

⚠️注意:代码仓库里保存的是博客程序的源码,无需执行 hexo g 上传 public 文件夹

创建 GitHub Actions 工作流文件

在目录 ./github/workflows 下创建 YAML 文件,文件名自取,如 deploy.yml

内容填充为

name: Deploy to GitHub Pages
on:
push:
branches:
- master # 推送此分支触发部署流程

jobs:
deploy:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
steps:
# 检出代码
- uses: actions/checkout@master

# 设置 Nodejs
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm i -g hexo-cli
- run: npm install
- run: cd themes/obsidian && npm install
- run: hexo g

# 部署静态页面
- name: Deploy
uses: s0/git-publish-subdir-action@develop
env:
REPO: self
BRANCH: gh-pages # Github Pages 部署分支
FOLDER: public/
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

提交代码至 GitHub 仓库

GitHub 仓库会自动识别工作流文件,并且在你每次提交新的代码到 master 分支时开始执行一个 action 至此,GitHub Actions 与 GitHub Pages 简单搭配使用已结束。

· ⏰ 6 分钟阅读
losgif

js 中的 async / await 有何作用?

有一种特殊的语法可以更舒适地与Promise一起使用,称为“async/await”。它非常容易理解和使用。

写过JQuery的同学对于其中的回调肯定印象深刻,在多层嵌套回调中,代码结构简直乱如麻。众多饱受其荼毒的受害者对此起了一个形象的名称:回调地狱。

为了解决此类问题,JS提供了两个新语法:Promiseasync / await,其中 async / await 一般配套使用。

async / await 语法

async function name([param[, param[, ...param]]]) {
statements
}

参数

  • name 函数名
  • param 形参
  • statements 函数主体语句,其中 await 为可选使用

返回值

返回一个Promise,它将由async函数返回的值来解决,或者被async函数抛出或未捕获到的异常拒绝。

示例

function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}

async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
// expected output: "resolved"
}

asyncCall();

使用注意

async 函数总是返回 Promise

即使返回值只是一个primitive值,async函数也会通过return自动将返回值包装成一个Promise对象返回。 因此,下面两组函数是等价的。

正常 (Fulfill)

// async函数
async function foo () {
return 'a'
}

// Promise
function foo () {
return Promise.resolve('a')
}

异常 (Reject)

// async函数
async function foo () {
throw new Error('error')
}

// Promise
function foo () {
return Promise.reject(new Error('error'))
}

await总是按顺序执行

使用async函数之前,我们还得搞清楚它的运行机制。尤其是在执行顺序上,完全用同步的思维也许并不适用于async函数。

function asyncGet (x) {
return new Promise(resolve => setTimeout(() => {
console.log('a')
resolve(x)
}, 500))
}

async function test () {
console.log('b')
const x = 3 + 5
console.log(x)

const a = await asyncGet(1)
console.log(a)

const b = await asyncGet(2)
console.log(b)

console.log('c')
return a + b
}

const now = Date.now()
console.log('d')
test().then(x => {
console.log(x)
console.log(`elapsed: ${Date.now() - now}`)
})
console.log('f')
  1. async函数和普通函数一样按顺序执行,同时,在执行到await语句时,返回一个Promise对象
  2. await可以理解为将async函数挂起,直到等待的Promisefulfill或者reject,再继续执行之后的代码
  3. async函数的返回值和普通Promise没有区别

因此,上面代码输出应该是

d
b
8
f
a
1
a
2
c
3
elapsed: 1010

注意 d 和 f 中间的输出

让我们再来看一个混合了Promise的版本。

function asyncGet (x) {
return new Promise(resolve => setTimeout(() => {
console.log('a')
resolve(x)
}, 500))
}

async function test () {
console.log('b')
const x = 3 + 5
console.log(x)

const [a, b] = await Promise.all([
asyncGet(1),
asyncGet(2)
])

console.log('c')
return a + b
}

const now = Date.now()
console.log('d')
test().then(x => {
console.log(x)
console.log(`elapsed: ${Date.now() - now}`)
})
console.log('f')

输出结果

d
b
8
f
a
a
c
3
elapsed: 509

注意到elapsed的差别了吗?这就是为什么我们说await总是顺序执行的。不同的await之间无法并行执行,想要真正的完全异步还得借助类似Promise.all这样的方法。

async 函数和 callback

await 只能能影响直接包裹它的 async 函数。因此在 callback 函数中的 await 并不会挂起整个 async 函数的执行。

一种常见的错误

async function getAll (vals) {
return vals.map(v => await asyncGet(v))
}

这段代码有语法错误,await 并不在 async 函数内部。如果给 mapcallback 加上 async 呢?

async function getAll (vals) {
return vals.map(async v => await asyncGet(v))
}

这段代码虽然能执行,但还有两个问题。

  1. 返回一个Promise对象的数组,并不是我们期待的value数组
  2. await只会暂停mapcallback,因此map完成时,不能保证asyncGet也全部完成

正确的写法还得借助 Promise.all

async function getAll (vals) {
return Promise.all(vals.map(v => asyncGet(v)))
}

总结

从上文我们可以看出,Promiseasync 函数的基础,想要愉快的使用 async 函数,必须对 Promise 有比较深入的理解。甚至一些常见的任务,仅仅依靠 async 函数无法实现。 希望大家看完本文后能对 async 函数有加更全面的认识,这样使用起来才会更加顺手。