简易静态服务器

最基本的静态文件服务器

nodejs中Stream是EventEmitter的实现,你可以理解为在程序后台打开了一个文件(不占用主线程), 程序会一点一点的读取(写入)文件,通过事件和回调来完成文件的读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var http = require('http')
var parse = require('url').parse
var fs = require('fs')
var join = require('path').join

var root = __dirname
var server = http.createServer(function(req, res){
var url = parse(req.url)
var path = join(root, url.pathname)
var stream = fs.createStream(path)
stream.on('data',function(chunk){
res.write(chunk)
})
stream.on('end',function(){
res.end()
})
})

server.listen(3000)

处理服务器错误,保证运行不中断

在node中所有继承EventEmitter的类都可能会发出error的事件,fs.ReadStream这样的流是专用的EventEmitter,会有定义好的data和end事件,但是并没有处理error的事件,如果不监听这些错误,就会搞垮服务器。

1
2
3
4
5
// 所以在fs.ReadStream注册一个error事件处理器
stream.on('error',function(err){
res.statusCode = 500
res.end('Internet Server Error')
})

检查文件是否存在,并在响应中提供Content-Length

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
var server = http.createServer((req,res)=>{
var parse = require('url').parse
var join = require('path').join
var path = join(__dirname,parse(req.url).pathname)

fs.stat(path,(err,stat)=>{
if(err){
if('ENOENT' == err.code){
res.status = 404
res.end('file not found')
}else{
res.status = 500
res.end('Internet Server error')
}
}else{
res.setHeader('Content-Length',stat.size)
var stream = fs.createReadStream(stat)
stream.pipe(res)
stream.on('error',(err)=>{
res.statusCode = 500;
res.end('Internet server error')
})
}
})
})

content-length可以让浏览器判断什么时候数据接收已经完成。谷歌浏览器在打开网页的时候,有时候页面的icon一直在转圈,就是因为数据没有接收完毕,一直在等待数据。

数据结构

数据结构主要学习的就是线性结构和树状结构。

按逻辑进行分类

  • 集合: 元素之间没有逻辑关系
  • 线性结构(线性表):一维数组、队列、栈、文件流、字符串
  • 非线性结构:树(组织型)、图(区块链型)、多维数组

按存储结构分类

  • 顺序存储结构
  • 链式存储结构
  • 索引存储结构
  • 散列存储结构

线性结构可以衍生出其他结构,首尾相接的环。
树分二叉树,遍历分为深度优先(先序、中序、后序)和广度优先,

内存

一维结构、地址、指针、引用、存储结构和内存管理

集合

  1. 数据成员是无序的
  2. 数据成员不可重复,仅出现一次

线性表

线性结构中的数据元素之间是一对一的关系,数据是一个个排列的

  1. 用来存放特定类型的元素
  2. 物理结构为顺序表和链表
    c语言里操作原生数组(顺序表),步长一致,类型相同,数据整齐排列,只需要计算偏移量
    js里面的数组(链式表),顺序是杂乱的,每个元素都封装了一层(链表有首节点,尾结点,可以依据这两个节点进行追溯,链表里存放的是引用,指向内存另一个地址)

线性表衍生结构:栈,队列,串

串只能基于连续表,不能基于链表,串的数据一直存在;对串进行修改和删除都是基于之前的串重新复制的
流是按顺序读数据,读取过的数据,流自动丢弃。

时间复杂度

O(n的平方) 冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
function bubleSort(arra) {
var temp;
for (var i = 0; i < arra.length; i++) {
for (var j = 0; j < arra.length - i - 1; j++) {
if (arra[j] > arra[j + 1]) {
temp = arra[j];
arra[j] = arra[j + 1];
arra[j + 1] = temp;
}
}
};
return arra;
}

O(logn) 二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function binarySearch(arr, target) {
let max = arr.length - 1
let min = 0
while (min <= max) {
let mid = Math.floor((max + min) / 2)
if (target < arr[mid]) {
max = mid - 1
} else if (target > arr[mid]) {
min = mid + 1
} else {
return mid
}
}
return -1
}

O(nlogn) 归并排序

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
const mergeSort = array => {
const len = array.length
if (len < 2) {
return len
}
const mid = Math.floor(len / 2)
const first = array.slice(0, mid)
const last = array.slice(mid)
return merge(mergeSort(first), mergeSort(last))
function merge(left, right) {
var result = [];
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
}
}

算法题记录

对输入的字符串进行(){}[]入栈出栈的校验,必须成对出现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const str = (s) => {
var length;
while (s.length) {
if (s.length % 2 == 1 || s.length === length) return false
length = s.length;
s = s.split('()').join('').split('[]').join('').split('{}').join('')
}
return true
}

console.log(str("()[]{}"))
console.log(str('{[()]}'))
console.log(str('[(}'))
console.log(str("[][({})]()(){}{}"))

递归算法

爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

1
2
3
4
5
6
7
8
9
10
var methods = (n) => {
if (n == 0) return 0;
if (n == 1) return 1;
const dp = [];
dp[2] = 2;
for (var i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2]
}
return dp[n]
}

打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额

1
2
3
4
5
6
7
8
9
10
11
12
13
// 动态规划方程:dp[n] = num + Max(dp[n-1])
// 由于不可以在相邻的房屋闯入,所以在当前位置 n 房屋可盗窃的最大值,要么就是 n-1 房屋可盗窃的最大值,要么就是 n-2 房屋可盗窃的最大值加上当前房屋的值,二者之间取最大值

var rob = function(nums) {
if(nums.length === 0) return 0;
if(nums.length === 1) return nums[0];
if(nums.length === 2) return Math.max(nums[0],nums[1]);
let dp = [nums[0],Math.max(nums[0],nums[1])];
for(let i = 2;i < nums.length;i++){
dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
}
return Math.max(dp[nums.length-1],dp[nums.length-2]);
};

最大正方形

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积

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
// 错误思路: 先从2x2矩阵开始验证, 基于此继续验证更大的3x3矩阵, 以此类推
// 正确思路: 仍然使用二维数组保存自己的状态, 如果当前位置的左边/上边/左上的点都为1, 则把当前点保存为2; 如果当前位置的左边/上边/左上的点都为2, 则把当前点保存为3; 以此类推

var maxSquare = (ary) => {
if (ary.length == 0) return 0

let maxLen = 0
let rowLen = ary[0].length, colLen = ary.length;

for (let i = 0; i < rowLen; i++) {
for (let j = 0; j < colLen; j++) {
if (Number(ary[i][j]) === 1) {
ary[i][j] = Number(ary[i][j]);
if (i !== 0 && j !== 0) {
ary[i][j] = Math.min(ary[i - 1][j - 1], ary[i - 1][j], ary[i][j - 1]) + 1
}
maxLen = Math.max(maxLen, ary[i][j])
}
}
}

return maxLen ** 2
}


console.log(maxSquare(
[
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
]
));

单向链表节点两两互换,递归算法

如果当前节点已经是末尾,就返回自己

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function helper(node) {
if (!node) return null
const tempNext = node.next;
const tempNextNext = tempNext.next;
if (tempNext) {
node.next = helper(tempNextNext);
tempNext.next = node;
}
return tempNext || node
}

var d = { v: 'd' }
var c = { v: 'c', next: d }
var b = { v: 'b', next: c }
var a = { v: 'a', next: b }
helper(a)
console.log(a);
console.log(b);
console.log(c);
console.log(d);

扁平数组输出树状结构

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
82
83
84
85
86
87
88
89
let arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 1},
{id: 3, name: '部门3', pid: 1},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
]
// [
// {
// "id": 1,
// "name": "部门1",
// "pid": 0,
// "children": [
// {
// "id": 2,
// "name": "部门2",
// "pid": 1,
// "children": []
// },
// {
// "id": 3,
// "name": "部门3",
// "pid": 1,
// "children": [
// // 结果 ,,,
// ]
// }
// ]
// }
// ]

// 递归
function createTree(ary) {
const resAry = new Set();
const findBy = (target, list) => list.find(item => {
if (item.id === target.pid) {
// 当前对象匹配了id
item.children = item.children || [];
return item.children.push(target)
} else if (!!item.children) {
// 当前对象不匹配,继续遍历他的子节点
return findBy(target, item.children)
} else {
return false
}
})
ary.forEach(item => {
if (item.pid == 0) {
resAry.add(item);
} else {
var res = findBy(item, ary);
if (res && res.pid == 0) {
resAry.add(res);
}
}
})
return Array.from(resAry)
}

console.log(JSON.stringify(Array.from(createTree(arr))))


// 不用递归
function aryToTreeWithMap(ary) {
const result = [];
const maps = {};

ary.forEach(item => {
item.children = maps[item.id]?.children || [];
maps[item.id] = {
...maps[item.id],
...item
};
if (item.pid === 0) {
result.push(item);
}
if (maps[item.pid]) {
maps[item.pid].children.push(item);
} else {
maps[item.pid] = {
children: [item]
};
}
})

return result;
}

console.log(JSON.stringify(Array.from(aryToTreeWithMap(arr))))

输入数组中三个数的和,满足与目标值最接近

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
// 输入:nums = [-1,2,1,-4], target = 1
// 输出:2
// 解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

function targets(nums, target) {
let len = nums.length;
if (len === 3) {
return nums
}
nums = nums.sort((a, b) => a - b);
let min = Infinity
let res

for (var i = 0; i < len - 3; i++) {
let basic = nums[i];
let left = i + 1;
let right = len - 1;
while (left < right) {
res = basic + nums[left] + nums[right];
let diff = Math.abs(res - target);
if (diff < min) {
diff = min
res = sum
}
if (res < target) {
left++
} else if (res > target) {
right--
} else {
return sum
}
}
}
return res
}

单词查询

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
// board = [
// ['A','B','C','E'],
// ['S','F','C','S'],
// ['A','D','E','E']
// ]

// 给定 word = "ABCCED", 返回 true
// 给定 word = "SEE", 返回 true
// 给定 word = "ABCB", 返回 false


function wordExist(board, word) {
var source = word.split('');
if (source.length == 0) return false;

const result = [];
const char = source[0];
// 第一步找到所有的线头,也就是所有可能
for (let i = 0; i < board.length; i++) {
let charstr = board[i].join('');
while (charstr.includes(char)) {
let j = charstr.indexOf(char);
// 找到字符所在位置,替换为*,避免下次重复查找
charstr = charstr.slice(0, j) + '*' + charstr.slice(j + 1, board[i].length)
if (j !== -1) {
result.push(i + '' + j);
}
}
}

if (source.length == 1) return result.length !== 0;

for (let i = 0; i < result.length; i++) {
// 根据所有可能,进行递归
let head = result[i].split('');
if (juan(Number(head[0]), Number(head[1]), 1, [result[i]])) {
return true
}
}

return false

// 这里就卷起来了
function juan(i, j, targetIndex, record) {
const getVal = (row, col) => {
var posi = row + '' + col;
var val = record.includes(posi) ? null : board[row] && board[row][col];
if (val && val === source[targetIndex]) {
// 查找层数与字符长度相等,即通过
if (targetIndex === source.length - 1) {
return true
}
return juan(Number(row), Number(col), targetIndex + 1, [...record, posi]);
}
return false
}
// 查找自己上下左右的所有可能,当然,已经使用过的位置不能重复使用
return getVal(i - 1, + j) || getVal(i + 1, j) || getVal(i, j - 1) || getVal(i, j + 1);
}
}

节点遍历的两种方法

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
var node = {
value: 1,
left: {
value: 2,
left: {
value: 4,
},
right: {
value: 5,
left: {
value: 8,
},
right: {
value: 9,
},
},
},
right: {
value: 3,
left: {
value: 6,
left: {
value: 10,
left: {
value: 14,
},
right: {
value: 15,
},
},
right: {
value: 11,
},
},
right: {
value: 7,
left: {
value: 12,
},
right: {
value: 13,
left: {
value: 16,
},
right: {
value: 17,
},
},
},
},
}

// 深度优先遍历
function deepFirst(node, result) {
result = result ? result + '->' + node.value : node.value;
if (node.left !== undefined) {
deepFirst(node.left, result)
}
if (node.right !== undefined) {
deepFirst(node.right, result)
}
if (node.left === undefined && node.right === undefined) {
console.log(result);
}
}

deepFirst(node, '');
// 1->2->4
// 1->2->5->8
// 1->2->5->9
// 1->3->6->10->14
// 1->3->6->10->15
// 1->3->6->11
// 1->3->7->12
// 1->3->7->13->16
// 1->3->7->13->17

// 广度优先遍历
function wildlyFirst(node) {
var res = []
function wildlyMaxFirst(node, result, index) {
result[index] = result[index] || [];
result[index].push(node.value);
if (node.left !== undefined) {
wildlyMaxFirst(node.left, result, index + 1)
}

if (node.right !== undefined) {
wildlyMaxFirst(node.right, result, index + 1)
}
}
wildlyMaxFirst(node, res, 0)
console.log(res);
}

wildlyFirst(node)
// [
// [ 1 ],
// [ 2, 3 ],
// [ 4, 5, 6, 7 ],
// [ 8, 9, 10, 11, 12, 13 ],
// [ 14, 15, 16, 17 ]
// ]

强大的正则表达式

正则表达式最常用于处理和校验字符串,是非常好用的简便工具。如果用的熟练,能够极大的提高工作效率。并且在阅读前端框架源码的时候经常也会遇到正则,记录在此以备忘。

1
2
3
4
let reg = /^\d{3}$/
console.log(reg.test("123")) // true
console.log(reg.test("a12")) // false
console.log(reg.test("1234")) // false

/^\d{3}$/表示匹配字符串是以三个数字开头和结尾的的字符串。
^表示开头的位置
$表示结束的位置
\d表示[0-9]的数字
{3}表示前面的符号出现的3次

惰性匹配和贪婪匹配

所谓惰性或者贪婪都是在全局搜索符g的修饰下才会有的。
在量词后面追加一个?,就可使贪婪变为懒惰

1
2
3
4
5
6
7
8
9
10
11
12
13
var greedyReg = /\d{2,5}/g; 
var lazyReg = /\d{2,5}?/g;
var str = "12 123 123456789 "
console.log(str.match(greedyReg))
// ["12", "123", "12345", "6789"]
console.log(str.match(lazyReg))
// ["12", "12", "12", "34", "56", "78"]

// 管道符默认是懒惰的,前面的被匹配之后,就不再继续了
var pipChar = /good|goodbye/;
var str = 'goodbye my love';
console.log(str.match(pipChar));
// [good]

正则实用实例

1
2
3
4
5
// 匹配16进制颜色
var colorReg = /[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/

// 匹配时间
var timeReg = /([01][0-9]|2[0-3])-[0-5][0-9]$/

一、字符组和量词

1. 两种模糊匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 横向模糊

var regex = /ab{2,5}c/g;
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log( string.match(regex) );
// => ["abbc", "abbbc", "abbbbc", "abbbbbc"]


// 纵向模糊

var regex = /a[123]b/g;
var string = "a0b a1b a2b a3b a4b";
console.log( string.match(regex) );
// => ["a1b", "a2b", "a3b"]

2. 字符组

1
2
3
// [123] 表示1、2、3中的一个
// - 在[]中表示范围
// ^ 在[]中表示不匹配
字符组 具体含义
\d [0-9]
\D [^0-9]
\w [0-9a-zA-Z_]
\W [^0-9a-zA-Z]
\s 空白符 space缩写
\S 非空白符
. 表示几乎所有字符

3. 量词

惰性量词 贪婪量词
{m, n}? {m, n}
{m, }? {m, } 等价于 {m,m}
?? ? 等价于 {0,1}
+? + 等价于 {1,}
*? * 等价于 {0,}

惰性匹配,就是尽可能少的匹配

4. 多选分支

  • (p1|p2|p3) ,其中 p1、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一。

分支结构是惰性的,即当前面的匹配上了,后面的就不再尝试了。

例子汇总一:

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
// 匹配 16 进制颜色值 #fff #fa541d

var reg = /#([a-zA-Z0-9]{6}|[a-zA-Z0-9]{3})/


// 匹配时间 23:59 7:9

var reg = /^([01][0-9]|2[0-3]):[0-5][0-9]$/
var reg = /^(0?[0-9]|1[0-9]|2[0-3]):(0?[0-9]|[1-5][0-9])$/ // 可以省略第一位0

// 匹配日期 yyyy-mm-dd

var reg = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[0-9]|[12][0-9]|3[01])$/

// windows操作系统路径 可能是文件 也可能是文件夹

// var reg = ^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?;

// 匹配id <div id="container" class="main"></div>

var reg = /.*id="[^"]*"/

// 密码长度 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符。

var reg = /(?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z])^[0-9a-zA-Z]{6,12}$/

var reg = /(?![0-9]{6,12})|(?![a-z]{6,12})|(?![A-Z]{6,12})^[0-9a-z-A-Z]{6,12}$/

二、位置匹配

在 ES5 中,共有 6 个锚:^、$、\b、\B、(?=p)、(?!p)

^ 匹配开头,在多行匹配中匹配行开头

$(美元符号)匹配结尾,在多行匹配中匹配行结尾。

\b 是单词边界,具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。

(?=p) p前面的位置

(?!p) 不是p前面的位置

(?<=p) p后面的位置

(?<!p) 不是p后面的位置

1
2
3
var result = "hello".replace(/(?=l)/g, '#');
console.log(result);
// => "he#l#lo"

例子汇总二:

1
2
3
4
5
6
7
8
9
10
11
// 不匹配任何东西
var reg = /.^/ //要求有个字符,但是字符的后面是开头,这样的字符是不存在的

// 数字的千分位

// var reg = /(?!^)(?=\d{3}+$)/
var reg1 = /\B(?=(\d{3})+\b)/

// 验证密码问题
var reg = /(?=.*[0-9])^[0-9a-zA-Z]{6,12}$/
var reg1 = /(?!^[0-9]{6,12}$)|(?!^[a-z]{6,12}$)|(?!^[A-Z]{6,12}$)^[0-9a-zA-Z]{6,12}$/

三、圆括号的作用

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
// 分组
var reg = /(ab)+/g
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// => ["abab", "ab", "ababab"]

// 分支结构
var regex = /^I love (JavaScript|Regular Expression)$/;
console.log( regex.test("I love JavaScript") );
console.log( regex.test("I love Regular Expression") );

// 提取数据
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( string.match(regex) );
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]
var string = "2017-06-12";
regex.test(string); // 正则操作即可,例如
//regex.exec(string);
//string.match(regex);
console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // "06"
console.log(RegExp.$3); // "12"

// 替换
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1"); //$1、$2、$3 指代相应的分组
// 等价于
var result = string.replace(regex, function () {
 return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
});
// 等价于
var result = string.replace(regex, function (match, year, month, day) {
 return month + "/" + day + "/" + year;
});
console.log(result);
// => "06/12/2017"

反向引用

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
//反向引用 \1应该代表的第一个分组
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false

//括号嵌套
var regex = /^((\d)(\d(\d)))\1\2\3\4$/;
var string = "1231231233";
console.log( regex.test(string) ); // true
console.log( RegExp.$1 ); // 123
console.log( RegExp.$2 ); // 1
console.log( RegExp.$3 ); // 23
console.log( RegExp.$4 ); // 3
// \1,是第一个分组内容,那么看第一个开括号对应的分组是什么,是 "123",
// \2,找到第2个开括号,对应的分组,匹配的内容是 "1",
// \3,找到第3个开括号,对应的分组,匹配的内容是 "23",
// \4,找到第3个开括号,对应的分组,匹配的内容是 "3"。

// \10 表示什么呢
var regex = /(1)(2)(3)(4)(5)(6)(7)(8)(9)(#) \10+/;
var string = "123456789# ########"
console.log( regex.test(string) );
// => true
// 如果真要匹配 \1 和 0 的话,请使用 (?:\1)0 或者 \1(?:0)。

// 引用不存在的分组会怎样?
var regex = /\1\2\3\4\5\6\7\8\9/;
console.log( regex.test("\1\2\3\4\5\6\7\8\9") );
// => true

// 分组后面有量词会怎样?
var regex = /(\d)+/;
var string = "12345";
console.log( string.match(regex) );
// => ["12345", "5", index: 0, input: "12345"]
var regex = /(\d)+ \1/;
console.log( regex.test("12345 1") );
// => false
console.log( regex.test("12345 5") );
// => true

非捕获括号

之前出现的括号,都会捕获它们匹配到的数据,以便后续引用,因此也称它们是捕获型分组和捕获型分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var regex = /(?:ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// => ["abab", "ab", "ababab"]

var pattern=/(ab)\w+(ba)/;
console.log("abcba_".replace(pattern,"$1"));
/*
*结果"ab_";匹配到的字符被第一个分组(ab)
*替换
*/
var pattern2=/(?:ab)\w+(ba)/;
console.log("abcba_".replace(pattern2,"$1"));
// 在分组内使用?:可以产生没有编号的分组。
/*
*结果"ba_";第一次分组内加入了?:,产生的是一个
*没有编号的分组,所以$1匹配的字符是第二个分组,
*也就是第一个编号分组(ba)相匹配的文本内容
*/

例子汇总三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 字符串trim
function trim(str){
return str.replace(/^\s*(.*?)\s*$/g, '$1')
}

// 将每个单词的首字母转换为大写
function titleize(str){
return str.match(/(?:^|\s)\w/g, function(c){
return c.toUpperCase()
})
}
console.log( titleize('my name is epeli') );
// => "My Name Is Epeli"

// 驼峰化

function titleize(str){
return str.match(/[-_\s]+(.)*\w/g, function (c) {
return c.toUpperCase()
})
}

四、回溯

本质上就是深度优先搜索算法。其中退到之前的某一步这一过程,我们称为“回溯”。从上面的描述过程中,可以看出,路走不通时,就会发生“回溯”。即,尝试匹配失败时,接下来的一步通常就是回溯。

个人理解,回溯,就是因为量词有时候匹配了太多内容,导致后面的字符匹配不上,然后匹配过的结果又要一个一个的回退,看是否能够与后面的正则内容匹配上。

1
2
var reg = /".*"/
// 为了减少一些不必要的回溯,可以把正则修改为 /"[^"]*"/。

五、正则表达式的拆分

1. 结构和操作符

操作符描述 操作符 优先级
转义符 \ 1
括号和方括号 (…)、(?:…)、(?=…)、(?!…)、[…] 2
量词限定符 {m}、{m,n}、{m,}、?、*、+ 3
位置和序列 ^、$、\元字符、一般字符 4
管道符(竖杠) | 5

2. 匹配字符串整体问题

因为是要匹配整个字符串,我们经常会在正则前后中加上锚 ^ 和 $。

1
var reg = /^abc|bcd$/

3. 量词连缀问题

1
2
// var reg = /^[abc]{3}+$/   会报错
var reg = /^([abc]{3})+$/

4. 元字符转义问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 所有结构里,用到的元字符总结如下:
// ^ $ . * + ? | \ / ( ) [ ] { } = ! : -
var string = "^$.*+?|\\/[]{}=!:-,";
var regex = /\^\$\.\*\+\?\|\\\/\[\]\{\}\=\!\:\-\,/;
console.log( regex.test(string) );
// => true


// ^、$、.、*、+、?、|、\、/ 等字符,只要不在字符组内,都需要转义的。

// 匹配 "[abc]" 和 "{3,5}"
var string = "[abc]";
var regex = /\[abc]/g;
console.log( string.match(regex)[0] );
// => "[abc]"
var string = "{,3}";
var regex = /{,3}/g;
console.log( string.match(regex)[0] );
// => "{,3}"

不要无所不用正则,正则也有它的局限。

比如一个大正则,使用多个小正则。

注意带有g修饰符的正则在使用match和test时,会有lastIndex这个数值,并不会每次都从同一个位置进行测试

使用具体型字符组来代替通配符,来消除回溯

5. 使用具体型字符组来代替通配符,来消除回溯

匹配字符串 123”abc”456 中的 “abc”。
/“.*”/,会在第 3 阶段产生 4 次回溯
/“.?”/,会产生 2 次回溯
要使用具体化的字符组,来代替通配符.,以便消除不必要的字符,此时使用正则 /“[^”]
“/,即可

6. 使用非捕获型分组

捕获分组需要内存来保存它们。不需要使用分组引用和反向引用时,此时可以使用非捕获分组。

例如,/^[-]?(\d.\d+|\d+|.\d+)$/ 可以修改成:/^[-]?(?:\d.\d+|\d+|.\d+)$/。

7. 独立出确定字符

例如,/a+/ 可以修改成 /aa*/。
因为后者能比前者多确定了字符 “a”。这样会在第四步中,加快判断是否匹配失败,进而加快移位的速度。

8. 提取分支公共部分

比如,/^abc|^def/ 修改成 /^(?:abc|def)/。
又比如, /this|that/修改成 /th(?:is|at)/。
这样做,可以减少匹配过程中可消除的重复。

9. 减少分支的数量,缩小它们的范围

/red|read/ 可以修改成 /rea?d/。
此时分支和量词产生的回溯的成本是不一样的。但这样优化后,可读性会降低的。

六、正则表达式编程

1. 正则的四种操作

验证

1
2
3
4
5
6
7
var regex = /\d/;
var string = "abc123";
console.log( !!~string.search(regex) );
console.log( regex.test(string) );
console.log( !!string.match(regex) );
console.log( !!regex.exec(string) );
// => true

切分

1
2
3
4
5
6
7
8
9
10
var regex = /,/;
var string = "html,css,javascript";
console.log( string.split(regex) );
var regex = /\D/;
console.log( "2017/06/26".split(regex) );
console.log( "2017.06.26".split(regex) );
console.log( "2017-06-26".split(regex) );
// => ["2017", "06", "26"]
// => ["2017", "06", "26"]
// => ["2017", "06", "26"]

提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// match
var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;
var string = "2017-06-26";
console.log( string.match(regex) );
// =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]
console.log( regex.exec(string) );
// =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]

var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;
var string = "2017-06-26";
var date = [];
string.replace(regex, function (match, year, month, day) {
 date.push(year, month, day);
});
console.log(date);
// => ["2017", "06", "26"]

替换

1
2
3
4
var string = "2017-06-26";
var today = new Date( string.replace(/-/g, "/") );
console.log( today );
// => Mon Jun 26 2017 00:00:00 GMT+0800 (中国标准时间)

2. 相关API

String#search
String#split
String#match
String#replace
RegExp#test
RegExp#exec

search 和 match,会把字符串转换为正则的。

1
2
3
4
5
6
7
8
var string = "2017.06.27";
console.log( string.search(".") );
// => 0
//需要修改成下列形式之一
console.log( string.search("\\.") );
console.log( string.search(/\./) );
// => 4
// => 4

3. match 返回结果的格式

与正则对象是否有修饰符 g 有关。

1
2
3
4
5
6
7
var string = "2017.06.27";
var regex1 = /\b(\d+)\b/;
var regex2 = /\b(\d+)\b/g;
console.log( string.match(regex1) );
console.log( string.match(regex2) );
// => ["2017", "2017", index: 0, input: "2017.06.27"]
// => ["2017", "06", "27"]

4. exec 比 match 更强大

正则实例的两个方法 exec、test,当正则是全局匹配时,每一次匹配完成后,都会修改 lastIndex,如果没有 g,自然都是从字符串第 0 个字符处开始尝试匹配。

1
2
3
4
5
6
7
8
9
var string = "2017.06.27";
var regex2 = /\b(\d+)\b/g;
var result;
while ( result = regex2.exec(string) ) {
 console.log( result, regex2.lastIndex );
}
// => ["2017", "2017", index: 0, input: "2017.06.27"] 4
// => ["06", "06", index: 5, input: "2017.06.27"] 7
// => ["27", "27", index: 8, input: "2017.06.27"] 10

5. test 整体匹配时需要使用 ^ 和 $

1
2
3
4
5
6
console.log( /123/.test("a123b") );
// => true
console.log( /^123$/.test("a123b") );
// => false
console.log( /^123$/.test("123") );
// => true

6. split 相关注意事项

1
2
3
4
5
var string = "html,css,javascript";
console.log( string.split(/,/, 2) );
// =>["html", "css"]
console.log( string.split(/(,)/) );
// =>["html", ",", "css", ",", "javascript"]

7. replace 是很强大的

replace 有两种使用形式,这是因为它的第二个参数,可以是字符串,也可以是函数。

当第二个参数是字符串时,如下的字符有特殊的含义:

属性 描述
$1,$2,…,$99 匹配第 1-99 个 分组里捕获的文本
$& 匹配到的子串文本
$` 匹配到的子串的左边文本
$’ 匹配到的子串的右边文本
$$ 美元符号$$

1
2
3
var result = "2,3,5".replace(/(\d+),(\d+),(\d+)/, "$3=$1+$2");
console.log(result);
// => "5=2+3"

8. 修饰符

修饰符 描述
g 全局匹配,即找到所有匹配的,单词是 global。
i 忽略字母大小写,单词是 ingoreCase。
m 多行匹配,只影响 ^ 和 $,二者变成行的概念,即行开头和行结尾。单词是 multiline。

1
2
3
4
5
6
7
var regex = /\w/img;
console.log( regex.global );
console.log( regex.ignoreCase );
console.log( regex.multiline );
// => true
// => true
// => true

正则作用:

  • 正则:用来处理字符串的规则

  • 匹配:判断一个字符串是否符合我们制定的规则

    1
    2
    3
    4
    var reg = /\d/; //包含一个0-9之间的数字
    console.log(reg.test('start')); //false
    console.log(reg.test('1')); // true
    console.log(reg.test('start5435')); //true
  • 捕获:把字符串中符合我们正则规则的内容捕获到 -> exec:reg.exec(str)

    1
    2
    3
    var reg = /\d/; //包含一个0-9之间的数字
    console.log(reg.exec('start')); // null
    console.log(reg.exec('1')); // ["1", index: 0, input: "1", groups: undefined]

如何创建正则:

正则的两种创建方式是有区别的,每一个正则表达式都是由元字符和修饰符组成的

1
2
3
4
//字面量方式:
var reg = /\d/; //包含一个0-9之间的数字
//实例创建方式
var reg = new RegExp('');
  1. 具有特殊意义的元字符
元字符 在//之间具有意义的一些字符
\ 转义字符,转译后面字符所代表的含义
^ 以某个元字符开始(匹配中是不占位置的)
$ 以某个元字符结尾(匹配中是不占位置的)
\n 匹配一个换行符
. 除了\n以外的任意字符
() 分组 把一个大正则本身划分成几个小正则
\d 一个0-9之间的数字 [0-9]
\D 除了0-9之间的数字以外的任何字符
\b 匹配一个边界符
\w 数字、字母、下划线中的任意一个字符 [0-9a-zA-Z_]
\s 匹配一个空白字符,空格、制表符、换页符…
x y
[xyz] x、y、z中的一个
[^xyz] 除了三个以外的任何一个字符
[a-z] a-z之间的任何一个字符
[^a-z] 除了a-z之间的任何一个字符
  1. 代表出现次数的量词元字符
量词元字符 含义
* 出现零到多次
+ 出现一到多次
? 出现或者不出现
{n} 出现n次
{n,} 出现n到多次
{n,m} 出现n到m次
1
2
3
4
5
6
7
8
9
10
11
var reg = /^\d$/; //只能是一个0-9之间的数字
var reg = /^\d+$/;
reg.test('12345'); //true
reg.test('1fdgdf2');//false
// .元字符
var reg = /^\d.+\d$/;
reg.test('1fdgdf2'); //true
// () 分组
var reg = /^(\d+)nihao(\d+)$/;
reg.test('1213fdgdf546');//false
reg.test('1213nihao546');//true

注意

  • []

    1. 在中括号中出现的所有字符都是代表本身意思的字符
    2. 中括号中不识别二位数
      1
      2
      var reg = /^[12-68]$/; //1、2-6或者8中的一个
      var reg = /^[\w-]]$/; //数字、字母、下划线、-中的一个
  • ()

    1. 改变x|y的默认优先级
    2. /a+/ 表示连续出现 a ,而需要连续出现 ab,则需要 /(ab)+/
    3. 在多选分支结构(p1|p2)中,括号的作用是提供了子表达式的所有可能
    4. 一般情况下分组的作用是为了引用或者重复出现分组或者分支结构
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      var reg = /^i love (javascript|regular expression)$/
      console.log(reg.test('i love javascript'));
      console.log(reg.test('i love regular expression'));
      // true
      // true
      reg = /(\d{4})-(\d{2})-(\d{2})/
      var reg = /^18|19$/;
      //18、19、181、1819、119、18
      var reg = /^(18|19)$/;
      //18、19x

正则的应用

  1. 有效数字的正则 正则、负数、零、小数

    1
    2
    3
    4
    //"."可以出现也可以不出现,一旦出现,就必须跟一位或多位数字
    //最开始可以有+/-也可以没有
    //整数部分,一位数可以是0-9之间的一个,多位数不能以0开头
    var reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/
  2. 正则的创建方式

    在字面量中,//之间包起来的所有字符都会是元字符,有的有特殊意义,大部分都代表本身含义的普通元字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var name = "attacki";
    var reg = /^\d+"+name+"\d+$/;
    reg.test('2019attacki2019'); //false
    reg.test('2019"""nameeee"2019'); //true
    //对于这种正则需要用实例创建方式
    var reg = new RegExp("^\\d+"+ name + "\\d+","g");
    reg.test('2019attacki2019'); //true

    // 字面量方式和实例创建的方式在正则中的区别?
    // 字面量方式中出现的一切都是元字符,所以不能进行变量拼接。
    // 而实例创建方式是可以的,字面量方式直接写\d就可以,而在实例中需要把它转义 \\d
  3. 年龄介于18-65 18-19 20-59 60-65

    1
    var reg = /(1[8-9]|[2-5]\d|6[0-5])/;
  4. 验证邮箱

    1
    var reg = /^[\w.-]+@[0-9a-z-A-Z]+(\.[A-Za-z]{2,4}){1,2}$/;
  5. 中国标准真实姓名 2-4位

    1
    var reg = /^[\ue400=\u9fa5]{2,4}$/
  6. 身份证号码

    1
    2
    3
    var reg = /^\d{17}(\d|X)$/
    //正则的捕获
    var reg = /^(\d{2})(\d{4})(\d{4})(\d{2})(\d{2})(\d{2})(\d)(\d|X)$/;
  7. 把数字替换为汉字数字

    1
    2
    3
    4
    5
    var str = "20190328";
    var ary =['零','一','二','三','四','五','六','七','八','九'];
    str = str.replace(/\d/g,function(){
    return ary[arguments[0]];
    })
  8. 获取一个字符串中出现次数最多的字符,并且获取出现次数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var str = "jesus love person, he is the son of god";
    var obj ={};
    str.replace(/\w|,/gi,function(){
    var val = arguments[0];
    obj[val] >= 1 ? obj[val]++ : obj[val] = 1;
    });
    //获取最多的次数
    var maxNum = 0;
    for(var key in obj){
    obj[key] > maxNum ? maxNum = obj[key]:null;
    }
    //把所有符合出现maxNum次数的都获取到
    var ary = [];
    for(var key in obj){
    obj[key] === maxNum ? ary.push(key):null;
    }
    console.log(obj,ary);
  9. 模版引擎实现的初步原理

    1
    2
    3
    4
    5
    var str = 'my name is {0} ,my love is {1}';
    var ary = ["attacki",'miao'];
    str = str.replace(/{(\d+)}/g,function(){
    return ary[arguments[1]];
    });
  10. 把字符串中所有单词首字母变为大写

  11. queryUrlParamter

    1
    2
    3
    4
    5
    6
    7
    var  str = 'http://kbs.sports.qq.com/game?mid=1000&cid=1454&app=1.0';
    var reg =/([^?=&]+)=([^?=&]+)/;
    var obj = {};
    str.replace(reg,function(){
    obj[arguments[1]] = arguments[2];
    })
    console.log(obj);
  12. 时间格式化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var str = "2015-5-16 14:53:00";
    var reg = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2}) +(\d{1,2}):(\d{1,2}):(\d{1,2})$/;
    var ary = [];
    str.replace(reg,function(){
    ary = ([].slice.call(arguments)).slice(1,7);
    })
    //设定好时间格式,把数组中内容替换到指定区域
    var resStr = "{0}年{1}月{2}日 {3}时{4}分{5}秒";
    str = str.replace(/{(\d+)}/g,function(){
    var val = ary[arguments[1]];
    val.length === 1 ? val='0'+val:null;
    return val;
    })
  13. 数据类型检测

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    typeof		用来检测数据类型的运算符
    console.log(typeof 12);
    //typeof返回的都是字符串,其中包含对应数据类型
    // string number boolean undefined function object
    //局限性,不能具体细分是数组、正则还是对象中的其他值。 typeof null === "object"
    //使用逻辑或、逻辑与 替代typeof
    function(num1,num2){
    if(typeof num2==='undefined'){num2 = 0};
    num2 = num2 || 0;
    }
    function(callback){
    if(typeof callback==='function'){callback()};
    callback && callback();
    }

instanceof 检测某一个实例是否属于某个类

1
2
3
4
5
6
var obj = [];
console.log(obj instanceof Array);
console.log(obj instanceof RegExp);
//1、不能用instanceof 检测基本数据类型的值,因为字面量创建和实例创建的结果分别为false和true。
//对于字面量创建的结果是基本的数据类型,不是严谨的实例,但是由于JS松散特点,可以使用Number.prototype上的方法;
//2、只要在当前实例的原型链上,用instanceof检测的结果都是true

constructor 构造函数,先找私有的,私有没有再找原型

1
2
3
4
5
6
7
8
9
10
11
12
//constructor 和 instanceof非常相似
var obj = [];
console.log(obj.constructor === Array); //true
console.log(obj.constructor === RegExp); //false
//constructor 可以处理基本数据类型
var num = 1;
console.log(num.constructor === Number);
//contsructor检测Object和instanceof不一样,一般情况是检测不了的
var reg = /^$/;
console.log(obj.constructor === RegExp); //true
console.log(obj.constructor === Object); //false
//局限性:类的原型可以进行重写,重写的时候很有可能吧constructor覆盖掉,导致检测结果不准确。

Object.prototype.toString.call()

1
2
3
4
5
6
7
8
9
10
11
12
//Object.prototype.toString他的作用是返回当前方法执行主题(方法中this)所属类的信息
var obj = {name:'miao'};
console.log(obj.toString());
//toString中的this是obj,返回的事obj所属类的信息 ->"[object Object]"
//第一个object代表当前实例是对象数据类型的(这个是固定死的),第二个Object代表的事obj所属的类是Object
Math.toString();//"[object Math]"

//检测数据类型
var ary = [];
console.log(Object.prototype.toString.call(ary)==="[object Array]");//true
var reg = /^\[object Array\]$/;
console.log(reg.test(Object.prototype.toString.call(ary)));//true

toString的理解

1
2
3
4
对于NumberStringBooleanArrayRegExpDateFunction原型上的toString方法都是把当前数据类型转化为字符串。
Object上的toString不是用来转换字符串的
console.log((1).toString());//Number.prototype.toString转换为字符串 ,可以接受进制参数
console.log((1).__proto__.__proto__.toString());//Object.prototype.toString,[object Object]

exec 正则的捕获

  • 捕获的内容格式,匹配成功才有内容,匹配失败结果为null

    捕获的内容是一个数组。

    第一项是当前大正则捕获的内容,
    index:捕获内容在字符串中开始的索引位置
    input:捕获的原始字符串

    正则捕获的特点

    懒惰性:每次执行exec只捕获第一个匹配的内容,在不进行任何处理的情况下,再执行多次捕获,捕获的还是第一个匹配内容。
    正则里面有lastIIndex属性:是正则每次捕获字符串开始查找的位置,默认是0

    1
    2
    3
    4
    5
    6
    var reg = /\d+/;
    var str = "AE86AE";
    var res = reg.exec(str); //["86", index: 2, input: "AE86AE", groups: undefined]
    console.log(reg.lastIndex);// 0
    res = reg.exec(str); //["86", index: 2, input: "AE86AE", groups: undefined]
    console.log(reg.lastIndex);// 0 说明第二次捕获还是从0开始查找的
    • 去除正则的懒惰性,可以在正则后面加修饰符

      修饰符 g、i、m
      global(g) 全局匹配
      ignoreCase(i) 忽略大小写
      multiple(m) 多行匹配
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      var reg = /\d+/g;
      var str = "AE8666666AE89AE";
      var res = reg.exec(str);
      console.log(reg.lastIndex);// 9
      res = reg.exec(str);
      console.log(reg.lastIndex);// 13
      //把所有捕获内容放在数组当中
      var str2 = 'AE86AE87AE89';
      var ary = [];
      var res = reg.exec(str2);
      while(res){
      ary.push(res[0]);
      res = reg.exec(str2);
      }
      console.log(ary);//["86", "87", "89"]
    • 正则的每一次捕获都是按照匹配最长的结果捕获的。

      例如:8符合正则,86、87、89也符合,默认捕获的就是86、87、89

    • 取消正则的贪婪性,在量词元字符后面加”?”

      ?在正则中有很多作用:
      放在普通元字符后面代表出现或者不出现
      放在量词元字符后面代表取消捕获的贪婪性

      1
      2
      3
      4
      var reg =/\d+?/g;
      //字符串中的match方法 ->把所有符合正则匹配的字符都捕获到
      var str2 = 'AE86AE87AE89';
      var ary = str2.match(reg); // ["8", "6", "8", "7", "8", "9"]

正则分组捕获

- 改变分组优先级
- 分组引用

\1代表和第一个分组出现一模一样;\2代表和第二个分组出现一模一样;

1
2
var reg = /(\w)\1(\w)\2/;
console.log(reg.test('aaee')); //true

分组捕获 正则捕获的时候,可以把大正则和小分组匹配的内容分别捕获

(?:) 在分组中?:的意思是只匹配不捕获

1
2
3
4
5
6
7
8
9
var str = "411325199301270437";
var reg = /^(\d{2})(\d{4})(\d{4})(\d{2})(\d{2})(\d{2})(\d)(?:\d|X)$/;
var ary = reg.exec(str)
//ary[0] ->大正则匹配的内容
//ary[1] ->第一个分组匹配的内容
console.log(str.match(reg));//和exec捕获的内容是一样的

reg = /AE(\d+)/g;
str = "AE86AE87AE89";//match只能捕获大正则的内容

replace:把原有的字符替换为新字符

在不使用正则的情况下,每当执行一次只能替换一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var str = "lovejesus2018lovejesus2019";
str = str.replace("love","lovejesus").replace("love","lovejesus");
//"lovejesusjesusjesus2018lovejesus2019"
str = str.replace(/love/g,"lovejesus");
//"lovejesusjesus2018lovejesusjesus2019"
//replace方法的第二个参数可以是一个函数
var str = "love2018love2019";
str = str.replace(/love/g,function(){
console.log(arguments);
return "lovejesus";
})
//第二个参数换成一个函数
//1)匿名函数执行多少次,取决于正则捕获的次数
//2)每一次执行匿名函数,里面得到的argumens和exec方法捕获的内容非常相似
//(即使正则有分组,同样可以通过arguments获取到分组捕获的内容)
//3)把大正则捕获的内容替换为return的内容

常用的nginx命令

nginx代理

nginx可以使前端开发变得非常方便。

配置nginx

进入到/usr/local/etc/nginx/nginx.conf

常用命令

1
2
3
4
5
6
7
8
9

# 开启nginx服务
nginx

# 关闭nginx服务
nginx -s stop

# 重新启动nginx
nginx -s reload