RegExp
1 正则的简单介绍
1.1 匹配
匹配模式,要不匹配字符,要不匹配位置。而正则表达式的强大在于实现模糊匹配,横向模糊和纵向模糊。
1.1.1 横向模糊匹配
一个正则可匹配的字符串长度不是固定的,而是多种情况的。量词[+ ? *] 表示1个或者多个;0个或者1次,任意次;{m,n}表示连续出现最少m次,最多n次。
1.1.2 纵向模糊匹配
纵向模糊指的是一个正则匹配的字符串,具体到某一位时,可以是不确定的字符串,有多种可能。[abc] 表示一个字符可以是a,b或者c。
1.1.3 字符组
上面提到的[abc]就是一个字符组,字符组可以有很多功能
- 范围表示法:[1-6], 若想匹配
-,顺序必须调换,[-16] - 排除字符组:就是反向纵向模糊匹配,反正不可以是这些字符串,
[^abc], 也可以用在范围表示法
字符组简写
\d是[0-9],\D是[^0-9]\w就是[0-9a-zA-Z_]\W是[^0-9a-zA-Z_]w就是word\s是[ \t\v\n\r\f],\S是[^ \t\v\n\r\f].就是[^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外- 匹配任意字符串,可以使用
[\d\D]、[\w\W]、[\s\S]和[^]中任何的一个
1.1.4 量词
- {m,} 表示至少出现m次。
- {m} 等价于{m,m},表示出现m次。
- ? 等价于{0,1},表示出现或者不出现。记忆方式:问号的意思表示,有吗?
- + 等价于{1,},表示出现至少一次。记忆方式:加号是追加的意思,得先有一个,然后才考虑追加。
- * 等价于{0,},表示出现任意次,有可能不出现。记忆方式:看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来。
1.1.5 惰性匹配
默认下匹配是惰性匹配,如果加了全局标识符就是贪婪匹配,尽可能找出最多的匹配模式,而惰性匹配就是找到第一个就停手,加一个问号就可以。如果只是希望匹配数量的话,大可不必使用问号,更改全局标识符即可。
var regex = /\d{2,5}?/g;
惰性匹配在对数量有要求的较复杂环境下有特别的作用,比如上面的例子,我们是希望找到2个就停手,还是尽可能多的匹配到再停手?这个时候问号标识符就排上用场了。加个问好可以保证匹配到两个就停手。
1.2 多选分支
使用管道符号 | 可以表示多选,和或者or 的表达符很像。多选分支反而是默认惰性的,即使一个完整的字符串,匹配上第一个就立即结束并返回匹配上的字符串。
处理分支还可以使用捕获组配合横向模糊匹配。
1.3 位置匹配攻略
1.3.1 什么是位置
位置指的是相邻字符之间的位置。
1.3.2 如何匹配位置
ES5中有六个锚字符
^ $ \b \B (?=p) (?!p)
下面是例子
匹配开头和结尾
^ 匹配开头,多行匹配时匹配每行的开头
$ 匹配结尾,多行匹配时匹配每行的结尾
var result = "I\nlove\njavascript".replace(/^|$/gm, '#');
console.log(result);
/*
#I#
#love#
#javascript#
*/
单词边界
\b是单词边界,\w 与 \W的边界,单词与非单词的边界,也包括了字符与开头,结尾的边界。
var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
console.log(result); // => "[#JS#] #Lesson_01#.#mp4#"
\B则是上面的反面,
你瞅啥
(?=p) (?!p) (?<=p) (?<!p)
这四者表示四个位置,分别是positive lookahead, negative lookahead, positive lookbehind, negative lookbehind.
这几个可以匹配相关的位置,具体就是某个字符的前面,后面,除了某个字符前/后的所有地方,包括开头结尾。
1.4 位置的特性
位置可以理解成空字符串"",且开头和结尾可以有无数个空字符串。
因此,把/^hello$/写成/^^hello?$/,是没有任何问题的:
1.4.1 案例
- 不匹配任何东西的正则
/.^/ - 数字的千位分割表示法
( /(?=(\d{3})+$)/g ,'#' ) - 千位分割的高级形式,上面的方法在正好是3的倍数的情况下会在开头有一个逗号。因此不能是开头,要加上(?!^)
( /(?!^)(?=(\d{3})+$)/g ,'#' )
1.4.2 密码判断,6-12位,数字,小写字符,大写字符,至少2种
1.4.3 判断是否包含某一种字符
假设,要求的必须包含数字,怎么办?此时我们可以使用(?=.*[0-9])来做
var reg = /((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9A-Za-z]{6,12}$/;
console.log( reg.test("1234567") ); // false 全是数字
console.log( reg.test("abcdef") ); // false 全是小写字母
console.log( reg.test("ABCDEFGH") ); // false 全是大写字母
console.log( reg.test("ab23C") ); // false 不足6位
console.log( reg.test("ABCDEF234") ); // true 大写字母和数字
console.log( reg.test("abcdEF234") ); // true 三者都有
1.5 括号的使用
1.5.1 分组和分支结构
括号主要提供分组功能,而括号加或者符,可以进行分支结构的选择。
1.5.2 引用分组
可以使用RegExp.$1 或者在string.replace(regex, "$2$3$1")
指代最近定义一个regex。
var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; var result = string.replace(regex, "$2/$3/$1");
console.log(result);
// => "06/12/2017"
var result = string.replace(regex, function() { return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1; });
1.5.3 反向引用
只能引用已经出现了的,使用\num的格式引用之前出现过的第几个分组,从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
比如上面的例子,保持日期匹配的分隔符一致
1.5.4 括号嵌套
括号嵌套下,以左括号出现的次序判断,
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.5.5 \10?
能正确表达10的意思
1.5.6 引用不存在的分组
会进行匹配,匹配最原本的意思。
1.6 非捕获分组
(?:ab)这种分组不会被api捕获
相关案例
-
去开头结尾空白符,
str.replace(/^\s+|\s+$/g, '') -
第二种,提取方法
str.replace(/^\s*(.*?)\s*$/g, '$1' )
第二种方法使用了惰性匹配,不然会一致匹配到最后一个空格之前。 -
将每个单词首字母大写,我们要匹配的是首字母,有两种可能,前面是开头或者空格
/()/g
2 创建正则
2.1 正则字面量 const reg = /[a-z]/i
优点:
- 简单方便
- 不需要考虑二次转义 缺点:
- 子内容无法重复使用
- 过长可能导致可读性差
2.2 使用RegExp
const s1 = '[a-z]'
const reg = new RegExp(`${s1}\\d+${s1}`,i)
优点:
- 子内容可以重复使用
- 动态创建
- 控制子内容的粒度提高可读性 缺点:
- 二次转义的问题容易导致bug
3 用法
RegExp.prototype.test()
- 输入要求字符串,如果类型住转换失败,会抛出TypeError
- 输出true/false 表示匹配结果
RegExp.prototype.source & RegExp.prototype.flags
- 前者获得当前正则对象的模式文本字符串
- 后者获得当前正则对象的修饰字符串,比如
i,g,返回按照字母升序
RegExp.prototype.exec & String.prototype.match()
- 前者输入字符串,后者输出正则表达式
- 匹配成功返回匹配结果,否则null
- 当全局匹配(
g)时,前者每次只返回一个匹配结果,多次调用才能返回所有匹配结果;后者会返回所有匹配结果,是一个字符串数组。 - match返回格式不固定,推荐使用前者
RegExp.prototype.lastIndex
- 主要是配合exec使用,返回最后一次匹配成功的位置(下一次匹配开始的位置),匹配失败归0。
const reg = /(a)/g;
const str = 'a1a';
reg.lastIndex; //0
reg.exec(str); //
reg.lastIndex; //1
reg.exec(str); //
reg.lastIndex; //3
reg.exec(str); //
reg.lastIndex; //0 失败了
reg.exec(str); //
String.prototype.replace()/search()/split()
- replace 替换所有匹配到的文本换成自定义文本
- search 返回第一次匹配到的文本开头在字符串中的index
- split 将匹配到的文本移除,返回一个字符串数组,注意开头结尾空字符串
4 场景
4.1 数值判断&解析
字符集纵向匹配
- [0-9] 匹配数字 等于
\d - [a-zA-Z] 匹配字母
- [\u4e00-\u9fa5] 匹配汉字
- [xyz123] 自定义
如何匹配数字?
- 要求全匹配 使用
^$匹配开头 - 要求正负号的处理
[+-]?允许有无符号 - 要求小数的处理
/^[+-]?\d+(\.\d+)?$/(.匹配除了换行符之外的所有字符,\s匹配换行符,这里要转义) - 要求处理无整数部分的小数
/^[+-]?(\d+)?(\.\d+)?$/ - 捕获组的额外开销,
(?:)创建一个非捕获组/^[+-]?(?:\d*\.)?\d+?$/ - 要求匹配无小数部分的数字 比如
2.。/^[+-]?(?:\d*\.)?\d+?(?:\.)?$/ - 要求处理科学计数法
/^[+-]?(?:\d+\.?|\d*\.\d+)(?:e[+-]?\d+)?$/i
解析CSS数值
没有2.0这种写法,其余情况都要考虑,还要考虑后缀单位
reg = `/[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?=px|\s|$)/ig`
function execNumberList(str){
reg.lastIndex = 0 //手动归零
let exec = reg.exec(str);
cosnt result = []
while(exec){
result.push(parseFloat(exec[0]));
exec = reg.exec(str);
}
return result;
}
console.log(execNumberList('1.0px .2px -3px') //[1,0.2,-3]
console.log(execNumberList('-1e+1px') //[10]
这里使用了(?=expression) 先行断言。 用于匹配位置,表示该位置应该有这些字符,但是并不会获取到这些字符。
与之类似,还有先行否定断言,后行断言,后行否定断言。
数值转货币
reg = `/(\d)(?=(\d{3})+(,|$))/g`
function execNumberList(str){
return str.replace(reg,'$1,')
}
console.log(execNumberList('1') //1
console.log(execNumberList('12345678') // 12,345,678
这里使用捕获组加先行断言的方式确定要加逗号的位置,然后全局匹配并替换。$1表示第一个捕获组。$&表示完整匹配。注意非捕获组断言里面的小括号也会生成捕获组。
4.2 正则与颜色
有以下几种情况
color: #rrggbb;
color: #rgb; //16进制缩写
color: #rrggbbaa; //有alpha透明度
color: #rgba //有alpha的缩写
正则可以写成如下格式
const hex = [0-9a-f];
const reg = new RegExp(`^(?:#${hex}{6}|#${hex}{8}|#${hex}{3,4})$`,'i');
直接匹配数字字母出现3,4,6,8次即可。
还有rgb表示法:
color: rgb(r,g,b)
color: rgb(r%,g%,b%,a%)
color: rgba(r,g,b,a)
color: rgba(r%,g%,b%,a)
color: rgba(r,g,b,a%)
color: rgba(r%,g%,b%,a%)
对应的正则
const num = '[+-]?(?:\\d*\\.)?\\d+(?:e[+-]?\\d+)?';
const comma = '\\s*,\\s*';
const reg = new RegExp(`rgba?\\(\\s*${num}(%?)(?:${comma}${num}\\1)[2](?:${comma}${num}%?)?\\s*\\)`)
- \n 反向引用,表示引用第n个捕获组
- \s 字符集缩写用于匹配空白
- 捕获组内容可选,问号在捕获组内,可以使用嵌套捕获组如果圆括号不可省略
缩写的转化:
//我的答案
const hex = '[0-9a-z]'
const reg = new RegExp( `/^#(${hex})\1(${hex})\2(${hex})\3(?:(${hex})\4)?/i` )
function shortenColor(str){
let result = reg.exec(str);
let res ='#'
if(!result[0]){
return str;
}else{
result.shift();
res = res + result.join("");
}
return res;
}
const hex = '[0-9a-z]'
const hexreg = new RegExp( `^#(?<r>${hex})\\k<r>(?<g>${hex})\\k<g>(?<b>${hex})\\k<b>(?<a>${hex}?)\\k<a>$`,'i');
function shortenColor(str){
return str.replace(hexreg,'#$<r>$<g>$<b>$<a>')
}
console.log(shortenColor('#336600') //#360
console.log(shortenColor('#19b955') // #19b955
console.log(shortenColor('#33660000') // #3600
教程答案使用了具名捕获组,(?<key>),反向引用使用\k<key>,而在replace中,使用$<key>来访问具名捕获组。
当应用exec时,具名捕获组可以通过execResult.groups[key]访问。
4.3 正则与URL
const protocol = '(?<protocol>https?:)';
const host = '(?<host>(?<hostname>[^/#?:]+)(?::(?<port>\\d+))?)'
const path = '(?<pathname>(?:\\/[^/#?]+)*\\/?)';
const search = '(?<search>(?:\\?[^#]*)?)';
const hash = '(?<hash>(?:#.*)?)';
const reg = new RegExp(`^${protocol}\/\/${host}${path}${search}${hash}$`);
function execURL(url){
const result = reg.exec(url);
if(result){
result.groups.port = result.groups.port || ""; //考虑undefined的情况
return result.groups;
}
return {
protocol:'',host:'',hostname:'',port:'',
pathname:'',search:'',hash:'',
};
}
解析search和hash(腾讯面试原题)
function execUrlParams(str){
str = str.replace(/^[#?&]/,"");
const result = {}
if(!str){ // 可能匹配全空字符串,要特殊处理
return result;
}
const reg = /(?:^|&)([^&=]*)=?([^&]*?)(?=&|$)/y;
let exec = reg.exec(str);
while(exec){
result[exec[1]] = exec[2];
exec = reg.exec(str);
}
return result;
}
console.log(execUrlParams('#')); // {}
console.log(execUrlParams('##')); // {'#':''}
console.log(execUrlParams('?q=360&src=srp')); // {q:'360',src:'srp'}
console.log(execUrlParams('test=a=b=c&&==&a=')); // {test:'a-b-c','':'=',a:''}
reg可能是以&开头,这是第一个非捕获组,然后是获取key,key里面不能有^=&并且任意长度,之后的等号也可能是没有的,考虑到两个&&之间是个空键值对。
[^&]*?表示任意长度匹配但是非贪婪,这里要和后面的先行断言结合,意思是第一个断言匹配到的地方就是我们想要的。但是这里似乎不影响,因为后面有y修饰符
y修饰符是ES6新增的粘连修饰符,是全局匹配但是:
- 每次匹配结果是连续的
- match时只会返回第一个匹配结果
练习
function getUrlParams(str,key){
str = str.replace(/^[#?&]/,"");
if(!str){ // 可能匹配全空字符串,要特殊处理
return "";
}
const reg = new RegExp(`(?:^|&)${key}=([^&]*?)(?=&|$)`,'g');
let exec = reg.exec(str);
let result;
while(exec){
result = exec?exec[1]:""
exec = reg.exec(str);
}
return result;
}
NODEJS
介绍
与JS的区别
- 基于异步的IO接口
- 基于node_modules 和require的模块依赖
- 提供C++ addon API与系统交互
可以干什么?
- WEB服务端
- CLI命令行
- GUI客户端
- IOT
内置模块
- http
- fs
- net
- process
- path
const fs = require('fs');
const {readFile} = require('fs');
fs.readFile(path,callback)//是异步的哦
文件模块
//import
const circle = require('./circle.js');
console.log(circle.area())
//define
exports.area = function(){}
exports.circumference = function(){}
//package name
require('pkg-name')
//从当前目录依次往上查找目标文件
模块类型
- .js
- .json 引入是一个对象
- .node C++编译生成的文件
- .mjs 新格式,基于ES6 module
- ...
模块路径查找
- 绝对路径
- 相对路径 和当前路径处理过后变成绝对路径
- 模块/文件夹
-
- 原始模块直接读取缓存
-
- 从当前路径以此往上查找
-
- 解析package.json 查找main属性,没有则使用index.js
- 未找到 报错
js模块解析
const circle = require('./circle.js');
- require 并不是全局变量,是怎么引入的?
- circle变量会污染其他文件吗?
解析过程
- 通过
fs.readFileSync同步拿到文件内容 - 对内容进行包装,在闭包内部有单独的作用域,不会污染全局
(function(exports, require, module, __filename, __dirname){
var circle = require('./circle.js');
console.log('The area is ' + circle.area(4));
})
- 通过vm.runInThisContext执行
- 获取module对象的值作为模块的返回值
模块缓存
- 模块加载后会将返回值缓存起来
- 下次加载时直接读取缓存内容,避免文件IO和解析时间
- 导出对象缓存在Module._cache对象上
Node Package Manager
- 一个package.json文件应该存在于包顶级目录之下
- 二进制文件应该在bin目录下
- javascript代码应该在lib目录下
- 文档应该在doc目录下
- 单元测试在test目录下
可以使用npm init -y的命令创建一个package.json
依赖
- dependencies 一个包需要其他包才能运行,放在dependencies下面
- devDependencies 仅仅是开发的时候使用,放在devDependencies,比如测试工具
- peerDependencies 装一个包必须安装放在peerDependencies下面的包,比如一个依附于特定框架的插件
- bundledDependencies 捆绑安装
- optionalDependencies 安装一个包的时候可以选择另一个包
samver
1.0.0三位分别代表大版本,中版本,小版本。小版本一般是bug的修复,中版本一般是特性的增加,大版本是整体的重构,可能向下不兼容。
registry
- ^1.2.2 允许中版本和小版本的更新
- ~0.5.0 允许小版本的更新
*接受任何更新- 0.0.2 只接受这一个版本
-
, >= 的意思就是字面意思
开发应用小栗子
WEB服务器
const http = require('http');
const server = http.createServer((req,res)=>{
res.end('hello world');
});
server.listen(3000);
ToDoList 实战
数据表
RESTful 接口规范
- 每个API都对应一种资源或资源集合
- 使用HTTP Method表示对资源的动作
- 使用HTTP Status Code表示资源操作结果
RESTful API 设计
- GET /ticket 获取ticket列表
- GET /ticket/:id 查看具体ticket
- POST /ticket 新建一个ticket
- PUT /ticket/:id 更新id为12的ticket
- DELETE /ticket/:id 删除id为12的ticket
脚手架创建
以基类的getAction为例,就是简单的返回数据,如果有id就根据id查询
async getAction(){
let data;
if(this.id){
const pk = this.modelInstance.pk;
data = await this.modelInstance.where({[pk]:this.id}).find();
return this.success(data);
}
data = await this.modelInstance.select();
return this.success(data);
}
数据库配置
exports.model = {
type: 'mysql',
common: {
logConnect: isDev,
logSql: isDev,
logger: msg => think.logger.info(msg)
},
mysql: {
handle: mysql,
database: 'todo',
prefix: '',
encoding: 'utf8',
host: '127.0.0.1',
port: '',
user: 'root',
password: 'root',
dateStrings: true
}
};
数据校验
- 使用logic层专门支持数据校验,非业务功能
- 文件和Action与Controller一一对应
- 使用配置代替逻辑
// src/logic/ticket.js
module.exports = class extends think.Logic {
getAction() {
this.rules = {
id: {
int: true
}
};
}
deleteAction() {
this.rules = {
id: {
int: true,
required: true,
method: 'get'
}
};
}
putAction() {
this.rules = {
id: {
int: true,
required: true,
method: 'get'
},
status: {
int: true,
required: true
},
desc: {
```