CI-CD

GitLab CI/CD 快速开始

前言

最近新需求 需要搭建新项目 新项目需要配置流水线 涉及到了CI/CD 本文主要是基本使用方法,更加细致的说明可以查看官方文档 极狐GitLab CI/CD | 极狐GitLab

GitLab CI/CD

GitLab CI/CD 是一种使用持续方法进行软件开发的工

在开始之前确保拥有

  • gitlab 中想使用ci/cd的项目

  • 项目的维护者或者所有者角色

CI/CD 流程概念

1.确保有可食用的runner。如果没有runner。则需要为项目或者群组安装gitlab runner并注册runner

2.在仓库的根目录下创建一个.gitlab-ci.yml文件。这是定义CI/CD作业的地方

确保有可用的runner

跳转到设置>CI/CD 并展开Runners

只要您至少有一个有效的 runner,旁边有一个绿色圆圈,您就有一个 runner 可以处理您的作业

如果没有则需要注册添加gitlab runner,将在另一篇中说明如何注册gitlab runner

创建.gitlab-ci.yml 文件

在此文件中,定义:

  • runner应执行的作业的顺序和结构

  • runner 在遇到特定条件时应作出的决定

1.在想要提交的分支中,根目录下出创建一.gitlab-ci.yml

.gitlab-ci.yml 文件中没有定义 stages,那么默认的流水线阶段是:

  1. build 中的所有作业并行执行。
  2. 如果 build 中的所有作业都成功,test 作业将并行执行。
  3. 如果 test 中的所有作业都成功,deploy 作业将并行执行。
  4. 如果 deploy 中的所有作业都成功,则流水线被标记为 passed
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
stages: # 流水线阶段的名称和顺序
- build-job
- test
- deploy-prod
build-job:
stage: build-job
only:
refs:
- /^dong-test$/ # 分支dong-test的时候执行
script: # 允许脚本
- echo "Hello, $GITLAB_USER_LOGIN!" #
tags: # runner tag
- web-uat2
test-job1:
stage: test
only:
refs:
- /^dong-test$/
script:
- echo "This job tests something"
tags:
- web-uat2

test-job2:
stage: test
only:
refs:
- /^dong-test$/
script:
- echo "This job tests something, but takes more time than test-job1."
- echo "After the echo commands complete, it runs the sleep command for 20 seconds"
- echo "which simulates a test that runs 20 seconds longer than test-job1"
- sleep 20
tags:
- web-uat2

deploy-prod:
stage: deploy-prod
script:
- echo "This job deploys something from the $CI_COMMIT_BRANCH branch."
environment: production
tags:
- web-uat2

$GITLAB_USER_LOGIN 和 $CI_COMMIT_BRANCH 是在作业运行时填充的预定义变量。

查看流水线和作业状态

当提交之后,流水线启动

  • 跳转到 CI/CD > pipelines

显示对应有三个阶段的流水线

  • 查看流水线可视化表,点击流水线ID

  • 查看作业详情信息,点击作业名称,例如build-job

微前端

微前端

写在前面

中台部分业务模块多,单个仓库存放代码量多,阅读困难,难以维护,所以按照业务区分来拆分微前端,方便维护。本文介绍微应用的基本应用,并在最后介绍基本api。

什么是微前端

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

核心价值

  • 技术栈无关

    • 主框架不限制应用技术栈接入,微应用完全具备自主权
  • 独立开发、独立部署

    • 微应用仓库独立,前后端可以独立开发,部署完成后主框架自动完成同步更新
  • 增量升级

    • 在面对各种复杂场景时候,很难对一个已经存在的系统进行全量的升级或者重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  • 独立运行时

    • 每个微应用之间状态隔离、运行时状态不共享

主应用

在对应的微应用仓库运行项目,在主应用注册相关微应用配置即可。

主应用不限制技术展 只需提供一个容器DOM,然后start即可

安装qiankun

$ yarn add qiankun # 或者 npm i qiankun -S

1、主应用 配置文件添加微应用注册配置

注册微应用并start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/micro/config.ts
import { registerMicroApps, start } from 'qiankun'
const apps = [
{
name: 'subapp',// 注意跟微应用pageage.json name一致
entry: '//localhost:3000/',// 微应用地址入口
container: '#subapp-container',// 微应用加载容器
activeRule: '/subapp-admin',// 微应用路由入口,注意子应用的所有路由都须加上相应的路由前缀规则
},
{
name: 'subapp_admin',
entry: '//localhost:4000/',
container: '#subapp-container',
activeRule: '#/subapp-admin-account',
},
]
registerMicroApps(apps)
// 启动qiankun
start()

这里我们是拆分中台应用,所以使用的语言都为vue,所以向微应用传递store、router、BaseRequest(axios请求封装),并且使用prefetchApps手动预加载指定的微应用静态资源

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
import { registerMicroApps, prefetchApps } from 'qiankun'
import store from '@/store'
import router from '@/router'
import BaseRequest from '@/utils/http'
const apps = [
{
name: 'subapp',// 注意跟微应用pageage.json name一致
entry: '//localhost:3000/',// 微应用地址入口
container: '#subapp-container',// 微应用加载容器
activeRule: '/subapp-admin',// 微应用路由入口,注意子应用的所有路由都须加上相应的路由前缀规则
props: {
store,
router,
BaseRequest,
},
},
{
name: 'subapp_admin',
entry: '//localhost:4000/',
container: '#subapp-container',
activeRule: '#/subapp-admin-account',
props: {
store,
router,
BaseRequest,
},
},
]
registerMicroApps(apps)
// 手动预加载指定的微应用静态资源
prefetchApps(apps)

2、微应用 项目配置

微应用分为有 webpack 构建和无 webpack 构建项目,有 webpack 的微应用(主要是指 Vue、React、Angular)

这里只介绍有webpack构建且用vue框架时如何应用

1.新增 public-path.js 文件,用于修改运行时的 publicPath

注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。

2.微应用建议使用history模式的路由,需要设置路由base,值和它的activeRule是一样

3.在入口文件最顶部引入public-path.js,修改并导出三个生命周期函数

4.修改webpack打包,允许开发环境跨域和umd打包

vue微应用

1.在src目录新增 public-path.js 文件

1
2
3
4
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

2.这里在vue中使用hash路由

3.在入口文件最顶部引入public-path.js,修改并导出三个生命周期函数

  • 在微应用的入口文件中导出相应的生命周期钩子

以Vue3.x的入口文件为例:src/main.ts

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import Vue, { createApp } from 'vue'
import { createRouter, createWebHashHistory, Router } from 'vue-router'
import App from './App.vue'
import store from './store/index'
import routes from './router/index'
import './public-path.ts'

let instance: Vue.App<Element>
let router: Router
const routerBase = '/subapp-account'
const __qiankun__ = window.__POWERED_BY_QIANKUN__
interface IRenderProps {
container: Element | string
store?: any
[propname: string]: any
}
/**
* @name 渲染函数
* @param props 主应用下发的props
* @description 分为微前端应用和独立应用渲染
*/
function render(props: IRenderProps) {
const { container, store: mainStore } = props
window.$mainStore = mainStore
// 查看主应用router
router = createRouter({
history: createWebHashHistory(__qiankun__ ? `${routerBase}/` : '/'),
routes
})
instance = createApp(App)
instance
.use(router)
.use(store)
.mount(
container instanceof Element
? (container.querySelector('#app') as Element)
: (container as string)
)
}

function setupStore(props: IRenderProps) {
props.onGlobalStateChange(
(value: any, prev: any) =>
console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
true
)
props.setGlobalState &&
props.setGlobalState({
ignore: props.name,
user: {
name: props.name
},
routes
})
}
// 独立运行时
if (!__qiankun__) {
render({ container: '#app' })
}
// 暴露主应用生命周期钩子
export async function bootstrap() {
console.log('subapp bootstraped')
}

/**
* @name 创建微应用
* @param props 主应用下发的props
*/
export async function mount(props: any) {
console.log('mount subapp')
setupStore(props)
render(props)
}

/**
* @name 卸载微应用
*/
export async function unmount() {
console.log('unmount college app')
instance.unmount()
}

4.修改webpack打包,允许开发环境跨域和umd打包

除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// webpack

const packageName = require('./package.json').name;


module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
output: {

library: `${packageName}-[name]`,

libraryTarget: 'umd',// 把微应用打包成 umd 库格式

jsonpFunction: `webpackJsonp_${packageName}`,
// webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
},
};

配置gitlab CI

// 参考subapp-admin gitlab-ci.yml

微应用单独编译打包

这里只做简单解释 具体需要先了解 gitlab ci/cd 极狐GitLab CI/CD 入门 | 极狐GitLab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
stages:
- build-211-dev
- build-211-sit
- build-211-uat
- build-211-pro
variables: # 变量 根据脚本执行添加 在script中应用
project: admin
targetpage: admin-sg
proRep: admin-pro

build-211-dev:
stage: build-211-dev
only: # 触发条件
refs:
- /^dev$/ # dev分支
variables:
- $CI_COMMIT_MESSAGE =~ /\-\-build/ # commit 中添加 --build 触发
script: # 触发后执行脚本
- sh /home/gitlab/front-shell/projects/youxin-admin/admin.sh -b dev -d subapp-account -r admin-sg/subapp-account-sg
tags: # runner 名称 tag
- web-dev

常见api

registerMicroApps(apps, lifeCycles?)

  • 参数

    • apps - Array<RegistrableApp> - 必选,微应用的一些注册信息
    • lifeCycles - LifeCycles - 可选,全局的微应用生命周期钩子
  • 类型

    • RegistrableApp

      • name - string - 必选,微应用的名称,微应用之间必须确保唯一。

      • entry - string | { scripts?: string[]; styles?: string[]; html?: string } - 必选,微应用的入口。

        • 配置为字符串时,表示微应用的访问地址,例如 https://qiankun.umijs.org/guide/
        • 配置为对象时,html 的值是微应用的 html 内容字符串,而不是微应用的访问地址。微应用的 publicPath 将会被设置为 /
      • container - string | HTMLElement - 必选,微应用的容器节点的选择器或者 Element 实例。如container: '#root' 或 container: document.querySelector('#root')

      • activeRule - string | (location: Location) => boolean | Array<string | (location: Location) => boolean> - 必选,微应用的激活规则。

        • 支持直接配置字符串或字符串数组,如 activeRule: '/app1' 或 activeRule: ['/app1', '/app2'],当配置为字符串时会直接跟 url 中的路径部分做前缀匹配,匹配成功表明当前应用会被激活。
        • 支持配置一个 active function 函数或一组 active function。函数会传入当前 location 作为参数,函数返回 true 时表明当前微应用会被激活。如 location => location.pathname.startsWith('/app1')
  • LifeCycles

    type Lifecycle = (app: RegistrableApp) => Promise;

    • beforeLoad - Lifecycle | Array<Lifecycle> - 可选
    • beforeMount - Lifecycle | Array<Lifecycle> - 可选
    • afterMount - Lifecycle | Array<Lifecycle> - 可选
    • beforeUnmount - Lifecycle | Array<Lifecycle> - 可选
    • afterUnmount - Lifecycle | Array<Lifecycle> - 可选
  • 用法

    注册微应用的基础配置信息。当浏览器 url 发生变化时,会自动检查每一个微应用注册的 activeRule 规则,符合规则的应用将会被自动激活。

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import { registerMicroApps } from 'qiankun';

    registerMicroApps(
    [
    {
    name: 'app1',
    entry: '//localhost:8080',
    container: '#container',
    activeRule: '/react',
    props: {
    name: 'kuitos',
    },
    },
    ],
    {
    beforeLoad: (app) => console.log('before load', app.name),
    beforeMount: [(app) => console.log('before mount', app.name)],
    },
    );

prefetchApps(apps, importEntryOpts?)

  • 参数

    • apps - AppMetadata[] - 必选 - 预加载的应用列表
    • importEntryOpts - 可选 - 加载配置
  • 类型

    • AppMetadata
      • name - string - 必选 - 应用名
      • entry - string | { scripts?: string[]; styles?: string[]; html?: string } - 必选,微应用的 entry 地址
  • 用法

    手动预加载指定的微应用静态资源。仅手动加载微应用场景需要,基于路由自动激活场景直接配置 prefetch 属性即可。

  • 示例

    1
      

常见问题

1、Sass变量全局配置问题

场景:新项目安装node-sass、和sass-loder后,自动导入样式文件还是无法读取全局变量。

解决方案:还需安装sass-resources-loader 包

疑问:其他项目同样版本并无安装以上依赖照样可以运行。估计是Webpack版本兼容问题。

1
2
3
4
5
6
7
8
// vue.config.js
css: {
loaderOptions: {
sass: {
prependData: `@import "@/assets/styles/theme.scss";`,
},
},
},

2、Vue路由切换卡顿问题

解决方案:watch route.name 而不是整个route响应式对象

[Deprecation] ‘window.webkitStorageInfo’ is deprecated. Please use ‘navigator.webkitTemporaryStorage’ or ‘navigator.webkitPersistentStorage’ instead. · Issue #3112 · vuejs/vue-next

3、样式隔离问题

解决方案:添加命名空间

子服务使用element-ui: Failed to execute ‘getComputedStyle’ on ‘Window’: parameter 1 is not of type ‘Element’. · Issue #634 · umijs/qiankun

4、打包缓存问题

解决方案:强制刷新,打包添加md5命名

Uncaught TypeError: Cannot read property ‘call’ of undefined · Issue #959 · webpack/webpack

5、KeepAlive方案

解决方案:子应用使用KeepAlive,主应用控制需要缓存的组件列表

其他方案参考

[Feature Request] 主应用多页签切换不同子应用的页面状态保持 · Issue #361 · umijs/qiankun

6、Hash路由模式不触发unmont问题

解决方案:不同子项目才会触发,同子项目路由变更不触发

[Bug]似乎不支持hash路由 · Issue #118 · umijs/qiankun

7、子路由前缀相同时,路由切换报错

报错提示: https://single-spa.js.org/error/?code=31&arg=mount&arg=application&arg=subapp-market&arg=3000

解决方案:精确匹配路由规则即可

子应用路由前缀相同时,路由切换错误 #607

vercel + hexo 搭建个人博客并绑定域名

vercel + hexo 搭建个人博客并绑定域名

vercel + hexo 搭建个人博客并绑定域名

前言

一直想搭建一个个人博客,最近了解到使用vercel + hexo即可快速搭建个人博客,最后搭配域名即可做到国内访问自己的博客,本篇即是记录个人搭建博客的过程。如果你舍不得去购买服务器,又想使用markdown格式快速创建自己的博客,那么本篇很适合你。

vercel

vercel:是一家提供网站托管的云平台,可以将你的项目部署在平台上,并提供网站访问。可以将github授权给vercel之后,就可以轻松且优雅的部署你的项目,只需想代码一推送,项目立马就自动更新部署。这里我们使用vercel就是为了使用vercel提供的服务器,已经方便的项目管理已经自动部署功能。

hexo

Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。

搭建准备

在搭建个人博客之前需要一些准备工作,安装node,拥有github账号,并安装了git,本篇只讲述我跟人使用的,其他方案可自行选择。

登录vercel账号

首先需要进入的vercel官网

这里点击GitHub继续,使用GitHub账号登录,只需绑定手机号之后这时候你就注册并登录了vercel

安装hexo-cli

在安装hexo之前需要安装node,具体访问node官网下载

在安装好node之后只需在命令行输入下面的指定就能简单的创建一个hexo项目

1
2
3
4
5
npm install hexo-cli -g //安装hexo-cli
hexo init blog //创建hexo初始博客
cd blog
npm install //安装依赖
hexo server //运行

输入以上指令之后你就创建好了hexo项目,并且在输入hexo server指令之后就能够在本地运行hexo为你创建的一个简单的demo

但是这时候只能够在本地运行,我们需要借助vercel发布服务器上供远端访问

将hexo项目上传的GitHub仓库

首先需要将该项目上传到GitHub仓库,由于我们前面通过GitHub登录vercel,所以只需将项目上传到GitHub仓库之后就能在vercel中使用该项目

进入GitHub主页,点击your repositories

image.png

点击如图的绿色new按钮,新建仓库

image.png

出现如下图界面,输入新建仓库名称,输入一个不与你已有仓库重名的名称,点击最下面的创建仓库

image.png

等待一会儿后,仓库就创建好了,上面的地址就是GitHub远端仓库的地址,GitHub在下方代码区域也告诉你了如何将你的代码上传到GitHub仓库

这里使用ssh连接到GitHub,如何配置ssh请自行搜索

image.png

这时候进入刚刚创建的hexo blog目录下面,在git bash中输入一下命令,

1
2
3
4
5
cd blog //紧接上面的命令,在blog目录下
git init
git add .
git remote add origin git@github.com:colin-d-h/hexo-test2.git
git push -u origin master

这时候就将代码上传到GitHub仓库了,这时候我们回到刚刚登录的vercel界面

image.png

点击创建新项目

image.png

这时候就看到刚刚在GitHub创建的hexo blog项目了,点击import,这里我点击的是我先前创建好的项目

image.png

再点击deploy,大概等待一分钟不到,您就搭建好了hexo项目了

image.png

点击右上角按钮之后

image.png

可以看到vercel为我们刚刚的项目部署到了服务器上,并且域名就是红色框框中的,这时候可以点击右上角的visit来在服务器上查看我们的项目。因为vercel的服务器在外国,如果此时你使用了魔法,你可以马上看到项目搭建的结果,如果是身处国内节点,则无法访问,那么改如何解决这个问题的?

配置域名达到国内访问

点击右上角的view domains,可以看到当前项目的域名

image.png

可以看到现在只有vercel为我们默认创建的

image.png

可以添加自己的域名达到国内访问

购买域名

这里使用万网购买域名

登录后在右上角输入自己想要的域名

搜索后选择自己喜欢的域名,选择.top.xyz等这样的会比较便宜

跟着指示操作即可,最后在域名控制台查看购买的域名

域名备案

由于我们使用vercel网站托管,使用的是vercel的外国服务器,国内域名加上国外服务器是不需要域名备案的
如果使用国内服务器,参考这篇博客一篇域名从购买到备案到解析的详细教程 - 掘金 (juejin.cn)

域名解析

将域名解析到服务器IP

进入域名控制台查看我们的域名,点击右下角的解析

点击添加记录

  • 记录类型:选择 A 。

  • 主机记录:一般是指子域名的前缀(如需创建子域名为www.dns-example.com, 主机记录输入 www;如需实现dns-example.com,主机记录输入 **@**)。

  • 解析线路:选择 默认 (默认为必选项,如未设置会导致部分用户无法访问 )。

    • 记录值:记录值为 IP 地址,填写 IPv4 地址。(服务器地址,这里我们使用的是vercel的服务器,所以要填vercel服务器的ip地址,A记录地址:76.223.126.88 CNAME 记录地址:cname-china.vercel-dns.com
  • TTL:为缓存时间,数值越小,修改记录各地生效时间越快,默认为10分钟。

    这里只说明了A方式,其他方式参考官方文档添加解析记录 (aliyun.com)

解析好之后,回到vercel

image.png

输入我们的域名,例如我刚刚填写的www 则我的域名是www.colin-dong.com , 如果刚刚填写的是@则是colin-dong.com 建议两种都添加

至此就能够在国能使用vercel构建我们的项目,通过域名访问博客了

正则表达式相关应用

正则表达式相关应用

正则表达式相关应用

JavaScript实现千分位分隔符

1.

使用js自带函数toLocaleString
Array.prototype.toLocaleString()
toLocaleString() 返回一个字符串表示数组中的元素。数组中的元素将各自使用toLocaleString()方法转换成字符串,这些字符串将使用一个特定环境的字符串(例如一个逗号 “,”)隔开。

1
2
3
let a = 1234546435
a.toLocaleString()
//'1,234,546,435'

2.

1
2
3
4
const toMoney = (num) => {
const str = num + ''
return str.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}

  • str.replace(/\B(?=(\d{3})+(?!\d))/g, ','); 使用正则表达式替换数字字符串中的位置,以在每三位数字之间插入逗号。这个正则表达式使用了零宽断言(lookahead assertion),确保只有在数字的后面插入逗号,而不是在小数点后面。

    • \B: 非单词边界,用于确保在数字的内部而不是开头或结尾插入逗号。
    • (?=(\d{3})+(?!\d)): 正向预测先行断言,匹配三个数字的倍数位置,但不能在数字的末尾。
  • /g 表示全局匹配,会替换所有匹配的位置。

3.

1
2
3
4
5
6
7
8
9
function numFormat(num){
var res=num.toString().replace(/\d+/, function(n){ // 先提取整数部分
return n.replace(/(\d)(?=(\d{3})+$)/g,function($1){
return $1+",";
});
})
return res;
}

  • replace(/\d+/, function(n) {...}) 用于替换整数部分。

    • \d+ 匹配一个或多个数字。
    • function(n) {...} 是一个回调函数,用于处理匹配的整数部分。
  • n.replace(/(\d)(?=(\d{3})+$)/g, function($1) {...}) 用于在整数部分的每三位数字之间插入逗号。

    • (\d) 匹配一个数字,并将其捕获为第一个捕获组。
    • (?=(\d{3})+$) 是一个正向预测先行断言,匹配整数部分的每三位数字的位置。
    • function($1) {...} 是一个回调函数,用于在匹配的数字前面插入逗号。

百分数

1
2
3
4
5
const formatter = (value) =>
value.replace(
/^(-|(?!([1-9]\d?(\.\d{1,2})?|100(\.00?)?)$)\d+(\.\d{1,2})?)$/,
value.substr(0, value.length - 1) || ''
)

正则表达式的解释:

  • ^ 表示匹配字符串的开始。
  • (-|(?!...)) 是一个组,匹配负数或者不满足后面条件的数字。
  • (?!...) 是负向预测先行断言,用于排除某些情况。
  • ([1-9]\d?(\.\d{1,2})?|100(\.00?)?) 匹配一个合法的数字,可以是整数或带有两位小数的小数,最大值为 100。
  • \d+(\.\d{1,2})? 匹配正整数或带有两位小数的小数。
  • $ 表示匹配字符串的结束。
正则表达式

正则表达式

正则表达式

正则表达式是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象,这些模式被用于RegExpexectest 方法,以及 StringmatchmatchAllreplacesearchsplit 方法。本章介绍 JavaScript 正则表达式。

创建一个正则表达式

可以用一下两种方法创建一个正则表达式

使用一个正则表达式字面量,其中包含斜杠的表达模式组成

1
var re = /ab+c/

x function numFormat(num){  var res=num.toString().replace(/\d+/, function(n){ // 先提取整数部分       return n.replace(/(\d)(?=(\d{3})+$)/g,function($1){          return $1+”,”;       }); })  return res;}const toMoney = (num) => {    const str = num + ‘’    return str.replace(/\B(?=(\d{3})+(?!\d))/g, ‘,’)}​ const formatter = (value) =>    value.replace(        /^(-|(?!([1-9]\d?(.\d{1,2})?|100(.00?)?)$)\d+(.\d{1,2})?)$/,        value.substr(0, value.length - 1) || ‘’   )js

或者调用RegExp对象的构造函数

1
var re = new RegExp("ab+c")

在脚本运行中,用构造函数创建的正则表达式会被编译。如果正则表达式将会被改变,或者它会将用户输入等来源中动态地产生,就需要使用构造函数来创建正则表达式。

编写一个正则表达式的模式

一个正则表达式模式是由简单的字符所构成的

比如/abc/;或者是简单和特殊字符的组合,比如/ab*c/或 /Chapter (\d+)\.\d*/。最后的例子中用到了括号,它在正则表达式中常用作记忆设备。即这部分所匹配的字符将会被记住以备后续使用,例如使用括号的子字符串匹配

使用简单模式

简单模式是由你直接想找到的字符构成。比如,/abc/ 这个模式就能且仅能匹配”abc”字符按照顺序同时出现的情况。

例如能匹配

"Hi, do you know your abc's?"
"The latest airplane designs evolved from slabcraft."

但是不能匹配”Grab crab” ,只能一一对应

使用特殊字符

当你需要匹配一个不确定字符串的时候

可以使用/ab*c/ 去匹配一个单独的”a”后面跟了零个或者多个”b”,同时后面跟着”c”的字符串

* 的意思是前一项零次或者多次。在字符串”cbbabbbbcdebc”中,这模式匹配了字符串”abbbbc”

元字符

正则表达式主要依赖于元字符。 元字符不代表他们本身的字面意思,他们都有特殊的含义。一些元字符写在方括号中的时候有一些特殊的意思。以下是一些元字符的介绍:

元字符 描述
. 句号匹配任意单个字符除了换行符。
[ ] 字符种类。匹配方括号内的任意字符。
[^ ] 否定的字符种类。匹配除了方括号里的任意字符
* 匹配>=0个重复的在*号之前的字符。
+ 匹配>=1个重复的+号前的字符。
? 标记?之前的字符为可选.
{n,m} 匹配num个大括号之前的字符或字符集 (n <= num <= m).
(xyz) 字符集,匹配与 xyz 完全相等的字符串.
| 或运算符,匹配符号前或后的字符.
\ 转义字符,用于匹配一些保留的字符 `[ ] ( ) { } . * + ? ^ $ \
^ 从开始行开始匹配.
$ 从末端开始匹配.

简写字符集

正则表达式提供一些常用的字符集简写。如下:

简写 描述
. 除换行符外的所有字符
\w 匹配所有字母数字,等同于 [a-zA-Z0-9_]
\W 匹配所有非字母数字,即符号,等同于: [^\w]
\d 匹配数字: [0-9]
\D 匹配非数字: [^\d]
\s 匹配所有空格字符,等同于: [\t\n\f\r\p{Z}]
\S 匹配所有非空格字符: [^\s]
\f 匹配一个换页符
\n 匹配一个换行符
\r 匹配一个回车符
\t 匹配一个制表符
\v 匹配一个垂直制表符
\p 匹配 CR/LF(等同于 \r\n),用来匹配 DOS 行终止符
字符 含义
\ 依照下列规则匹配:在非特殊字符之前的反斜杠表示下一个字符是特殊字符,不能按照字面理解。例如,前面没有 “" 的 “b” 通常匹配小写字母 “b”,即字符会被作为字面理解,无论它出现在哪里。但如果前面加了 “",它将不再匹配任何字符,而是表示一个字符边界。在特殊字符之前的反斜杠表示下一个字符不是特殊字符,应该按照字面理解。详情请参阅下文中的 “转义(Escaping)” 部分。如果你想将字符串传递给 RegExp 构造函数,不要忘记在字符串字面量中反斜杠是转义字符R。所以为了在模式中添加一个反斜杠,你需要在字符串字面量中转义它。/[a-z]\s/inew RegExp("[a-z]\\s", "i") 创建了相同的正则表达式:一个用于搜索后面紧跟着空白字符(\s 可看后文)并且在 a-z 范围内的任意字符的表达式。为了通过字符串字面量给 RegExp 构造函数创建包含反斜杠的表达式,你需要在字符串级别和正则表达式级别都对它进行转义。例如 /[a-z]:\\/inew RegExp("[a-z]:\\\\","i") 会创建相同的表达式,即匹配类似 “C:" 字符串。
^ 匹配输入的开始。如果多行标志被设置为 true,那么也匹配换行符后紧跟的位置。例如,/^A/ 并不会匹配 “an A” 中的 ‘A’,但是会匹配 “An E” 中的 ‘A’。当 ‘^‘ 作为第一个字符出现在一个字符集合模式时,它将会有不同的含义。反向字符集合 一节有详细介绍和示例。
$ 匹配输入的结束。如果多行标志被设置为 true,那么也匹配换行符前的位置。例如,/t$/ 并不会匹配 “eater” 中的 ‘t’,但是会匹配 “eat” 中的 ‘t’。
* 匹配前一个表达式 0 次或多次。等价于 {0,}。例如,/bo*/ 会匹配 “A ghost boooooed” 中的 ‘booooo’ 和 “A bird warbled” 中的 ‘b’,但是在 “A goat grunted” 中不会匹配任何内容。
+ 匹配前面一个表达式 1 次或者多次。等价于 {1,}。例如,/a+/ 会匹配 “candy” 中的 ‘a’ 和 “caaaaaaandy” 中所有的 ‘a’,但是在 “cndy” 中不会匹配任何内容。
? 匹配前面一个表达式 0 次或者 1 次。等价于 {0,1}。例如,/e?le?/ 匹配 “angel” 中的 ‘el’、”angle” 中的 ‘le’ 以及 “oslo’ 中的 ‘l’。如果紧跟在任何量词 *、 +、? 或 {} 的后面,将会使量词变为非贪婪(匹配尽量少的字符),和缺省使用的贪婪模式(匹配尽可能多的字符)正好相反。例如,对 “123abc” 使用 /\d+/ 将会匹配 “123”,而使用 /\d+?/ 则只会匹配到 “1”。还用于先行断言中,如本表的 x(?=y)x(?!y) 条目所述。
. (小数点)默认匹配除换行符之外的任何单个字符。例如,/.n/ 将会匹配 “nay, an apple is on the tree” 中的 ‘an’ 和 ‘on’,但是不会匹配 ‘nay’。如果 s (“dotAll”) 标志位被设为 true,它也会匹配换行符。
(x) 像下面的例子展示的那样,它会匹配 ‘x’ 并且记住匹配项。其中括号被称为捕获括号。模式 /(foo) (bar) \1 \2/ 中的 ‘(foo)‘ 和 ‘(bar)‘ 匹配并记住字符串 “foo bar foo bar” 中前两个单词。模式中的 \1\2 表示第一个和第二个被捕获括号匹配的子字符串,即 foobar,匹配了原字符串中的后两个单词。注意 \1\2、…、\n 是用在正则表达式的匹配环节,详情可以参阅后文的 \n 条目。而在正则表达式的替换环节,则要使用像 $1$2、…、$n 这样的语法,例如,'bar foo'.replace(/(...) (...)/, '$2 $1')$& 表示整个用于匹配的原字符串。
(?:x) 匹配 ‘x’ 但是不记住匹配项。这种括号叫作非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。看看这个例子 /(?:foo){1,2}/。如果表达式是 /foo{1,2}/{1,2} 将只应用于 ‘foo’ 的最后一个字符 ‘o’。如果使用非捕获括号,则 {1,2} 会应用于整个 ‘foo’ 单词。更多信息,可以参阅下文的 Using parentheses 条目。
x(?=y) 匹配’x’仅仅当’x’后面跟着’y’.这种叫做先行断言。例如,/Jack(?=Sprat)/会匹配到’Jack’仅当它后面跟着’Sprat’。/Jack(?=Sprat|Frost)/匹配‘Jack’仅当它后面跟着’Sprat’或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分。
(?<=y)x 匹配’x’仅当’x’前面是’y’.这种叫做后行断言。例如,/(?<=Jack)Sprat/会匹配到’ Sprat ‘仅仅当它前面是’ Jack ‘。/(?<=Jack|Tom)Sprat/匹配‘Sprat ’仅仅当它前面是’Jack’或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。
x(?!y) 仅仅当’x’后面不跟着’y’时匹配’x’,这被称为正向否定查找。例如,仅仅当这个数字后面没有跟小数点的时候,/\d+(?!.)/ 匹配一个数字。正则表达式/\d+(?!.)/.exec(“3.141”) 匹配‘141’而不是‘3.141’
(?<!*y*)*x* 仅仅当’x’前面不是’y’时匹配’x’,这被称为反向否定查找。例如,仅仅当这个数字前面没有负号的时候,/(?<!-)\d+/ 匹配一个数字。 /(?<!-)\d+/.exec('3') 匹配到 “3”. /(?<!-)\d+/.exec('-3') 因为这个数字前有负号,所以没有匹配到。
`x y`
{n} n 是一个正整数,匹配了前面一个字符刚好出现了 n 次。 比如, /a{2}/ 不会匹配“candy”中的’a’,但是会匹配“caandy”中所有的 a,以及“caaandy”中的前两个’a’。
{n,} n 是一个正整数,匹配前一个字符至少出现了 n 次。例如,/a{2,}/ 匹配 “aa”, “aaaa” 和 “aaaaa” 但是不匹配 “a”。
{n,m} n 和 m 都是整数。匹配前面的字符至少 n 次,最多 m 次。如果 n 或者 m 的值是 0,这个值被忽略。例如,/a{1, 3}/ 并不匹配“cndy”中的任意字符,匹配“candy”中的 a,匹配“caandy”中的前两个 a,也匹配“caaaaaaandy”中的前三个 a。注意,当匹配”caaaaaaandy“时,匹配的值是“aaa”,即使原始的字符串中有更多的 a。
[xyz\] 一个字符集合。匹配方括号中的任意字符,包括转义序列。你可以使用破折号(-)来指定一个字符范围。对于点(.)和星号(*)这样的特殊符号在一个字符集中没有特殊的意义。他们不必进行转义,不过转义也是起作用的。 例如,[abcd] 和 [a-d] 是一样的。他们都匹配”brisket”中的‘b’,也都匹配“city”中的‘c’。/[a-z.]+/ 和/[\w.]+/与字符串“test.i.ng”匹配。
[^xyz\] 一个反向字符集。也就是说, 它匹配任何没有包含在方括号中的字符。你可以使用破折号(-)来指定一个字符范围。任何普通字符在这里都是起作用的。例如,[^abc] 和 [^a-c] 是一样的。他们匹配”brisket”中的‘r’,也匹配“chop”中的‘h’。
[\b\] 匹配一个退格 (U+0008)。(不要和\b混淆了。)
\b 匹配一个词的边界。一个词的边界就是一个词不被另外一个“字”字符跟随的位置或者前面跟其他“字”字符的位置,例如在字母和空格之间。注意,匹配中不包括匹配的字边界。换句话说,一个匹配的词的边界的内容的长度是 0。(不要和 [\b] 混淆了)使用”moon”举例: /\bm/匹配“moon”中的‘m’; /oo\b/并不匹配”moon”中的’oo’,因为’oo’被一个“字”字符’n’紧跟着。 /oon\b/匹配”moon”中的’oon’,因为’oon’是这个字符串的结束部分。这样他没有被一个“字”字符紧跟着。 /\w\b\w/将不能匹配任何字符串,因为在一个单词中间的字符永远也不可能同时满足没有“字”字符跟随和有“字”字符跟随两种情况。备注: JavaScript 的正则表达式引擎将特定的字符集定义为“字”字符。不在该集合中的任何字符都被认为是一个断词。这组字符相当有限:它只包括大写和小写的罗马字母,十进制数字和下划线字符。不幸的是,重要的字符,例如“é”或“ü”,被视为断词。
\B 匹配一个非单词边界。匹配如下几种情况:字符串第一个字符为非“字”字符字符串最后一个字符为非“字”字符两个单词字符之间两个非单词字符之间空字符串例如,/\B../匹配”noonday”中的’oo’, 而/y\B../匹配”possibly yesterday”中的’yes‘
\c*X* 当 X 是处于 A 到 Z 之间的字符的时候,匹配字符串中的一个控制符。例如,/\cM/ 匹配字符串中的 control-M (U+000D)。
\d 匹配一个数字。``等价于 [0-9]。例如, /\d/ 或者 /[0-9]/ 匹配”B2 is the suite number.”中的’2’。
\D 匹配一个非数字字符。``等价于 [^0-9]。例如, /\D/ 或者 /[^0-9]/ 匹配”B2 is the suite number.”中的’B’ 。
\f 匹配一个换页符 (U+000C)。
\n 匹配一个换行符 (U+000A)。
\r 匹配一个回车符 (U+000D)。
\s 匹配一个空白字符,包括空格、制表符、换页符和换行符。等价于 [\f\n\r\t\v\u0020\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。例如,/\s\w*/ 匹配”foo bar.”中的’ bar’。经测试,\s不匹配”\u180e“,在当前版本 Chrome(v80.0.3987.122) 和 Firefox(76.0.1) 控制台输入/\s/.test(“\u180e”) 均返回 false。
\S 匹配一个非空白字符。等价于 [^\f\n\r\t\v\u0020\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。例如,/\S\w*/ 匹配”foo bar.”中的’foo’。
\t 匹配一个水平制表符 (U+0009)。
\v 匹配一个垂直制表符 (U+000B)。
\w 匹配一个单字字符(字母、数字或者下划线)。等价于 [A-Za-z0-9_]。例如,/\w/ 匹配 “apple,” 中的 ‘a’,”$5.28,”中的 ‘5’ 和 “3D.” 中的 ‘3’。
\W 匹配一个非单字字符。等价于 [^A-Za-z0-9_]。例如,/\W/ 或者 /[^A-Za-z0-9_]/ 匹配 “50%.” 中的 ‘%’。
\*n* 在正则表达式中,它返回最后的第 n 个子捕获匹配的子字符串 (捕获的数目以左括号计数)。比如 /apple(,)\sorange\1/ 匹配”apple, orange, cherry, peach.”中的’apple, orange,’ 。
\0 匹配 NULL(U+0000)字符,不要在这后面跟其他小数,因为 \0<digits> 是一个八进制转义序列。
\xhh 匹配一个两位十六进制数(\x00-\xFF)表示的字符。
\uhhhh 匹配一个四位十六进制数表示的 UTF-16 代码单元。
\u{hhhh}或\u{hhhhh} (仅当设置了 u 标志时)匹配一个十六进制数表示的 Unicode 字符。