正则的三个应用场景-Node.js基础入门

正则的三个应用场景

正则表达式的创建和使用

正则表达式的创建方式

  1. 使用正则表达式字面量
const reg = /[a-z]\d+[a-z]/i;

优点

  • 简单方便
  • 不需要考虑二次转义

缺点

  • 子内容无法重复使用
  • 过长的正则导致可读性差
  1. 使用RegExp构造函数
const alphabet = '[a-z]';
const reg = new RegExp(`${alphabet}\\d+${alphabet}`, 'i');

优点:

  • 子内容可以重复使用
  • 可以通过控制子内容的粒度提高可读性

缺点:

  • 二次转义的问题非常容易导致 bug

正则表达式的常见用法

  • RegExp.proptotype.test():用于检测一个字符串是否匹配某个模式.
    • 输入:
      • 要求输入字符串,如果输入的不是字符串类型,会尝试进行类型转换,转换失败会抛出 TypeError
    • 输出:
      • true 或者 false,表示匹配成功或失败
        例子:
const reg = /[a-z]\d+[a-z]/i;

reg.test('a1a'); // true
reg.test('1a1'); // false
reg.test(Symbol('a1a')); // TypeError
  • RegExp.prototype.sourceRegExp.prototype.flags
    RegExp.prototype.source :返回当前正则表达式的模式文本的字符串
    RegExp.prototype.flags:es2015新增,返回当前正则表达式的修饰符的字符串,会对修饰符按照字母升序进行排序(gimsuy)

  • RegExp.prototype.exec()String.prototype.match()解析

    • 输入:
      • RegExp.prototype.exec要求输入字符串,遇到非字符串类型会尝试转换
      • String.prototype.match 要求输入正则表达式,遇到其它类型会先尝试转成字符串,再以字符串为 source 创建正则表达式
    • 输出:
      • 匹配成功,返回匹配结果
      • 匹配失败,返回 null
    const reg = /(a)/g;
    reg.exec('a1a'); // ["a", "a", index: 0, input: "a1a", groups: undefined]
    'a1a'.match(reg); // ["a", "a"]
    

    当正则表达式含有 g 修饰符时,RegExp.prototype.exec 每次只返回一个匹配结果,数据格式和不含 g 修饰符相同。
    String.prototype.match 会返回所有的匹配结果,数据格式会变为字符串数组。
    由于 String.prototype.match 返回的数据格式不固定,因此大多数情况都建议使用 RegExp.prototype.exec

  • RegExp.prototype.lastIndex:当前正则表达式最后一次匹配成功的结束位置

const reg = /(a)/g;
const str = 'a1a';

reg.lastIndex; // 0
reg.exec('a1a'); // ["a", "a", index: 0, input: "a1a", groups: undefined]
reg.lastIndex; // 1
reg.exec('a1a'); // ["a", "a", index: 2, input: "a1a", groups: undefined]
reg.lastIndex; // 3
reg.exec('a1a'); // null
reg.lastIndex; // 0

lastIndex 不会自己重置,只有当上一次匹配失败才会重置为 0 ,因此,当你需要反复使用同一个正则表达式的时候,请在每次匹配新的字符串之前重置 lastIndex!

  • String.prototype.replace()替换、String.prototype.search()String.prototype.split()
'a1a'.replace(/a/, 'b'); // 'b1a'
'a1a'.replace(/a/g, 'b'); // 'b1b'

'a1a'.search(/a/); // 0
'a1a'.search(/a/g); // 0

'a1a'.split(/a/); // ["", "1", ""]
'a1a'.split(/a/g); // ["", "1", ""]

场景一:正则与数值

数值判断不简单

例一:/[0-9]+/ 目标:匹配数字
说明:

  • []:
    • 字符集,使用连字符 - 表示指定的字符范围,如果想要匹配连字符,需要挨着方括号放置,或进行转义
    • 0-9 表示匹配从 0 到 9 的数字字符,常用的还有 a-z 匹配小写字母,\u4e00-\u9fa5 匹配汉字等
    • 如果只是匹配数字,还可以使用字符集缩写 \d
  • +:限定符,匹配一个或多个​​​​​​​

该正则表达式的缺点:
不是全字符匹配,存在误判,如 /[0-9]+/.test('a1') === true

例二:/^\d+$/ 目标:匹配数字
说明:
^:匹配字符串开始位置,当结合 m 修饰符时,匹配某一行开始位置
$:匹配字符串结束位置,当结合 m 修饰符时,匹配某一行结束位置
该正则的缺点:

  1. 不能匹配带符号的数值,如 +1,-2
  2. 不能匹配小数,如 3.14159

例三:/^[+-]?\d+(\.\d+)?$/:该正则表示一个小数
说明:
():圆括号内是一个子表达式,当圆括号不带任何修饰符时,表示同时创建一个捕获组
?:? 在正则中有多种含义,作为限定符时,表示匹配零到一个
\.:. 可以匹配除换行符之外的任意字符,当结合 s 修饰符时,可以匹配包括换行符在内的任意字符
当匹配小数点字符时需要转义
该正则缺点:

  1. 不能匹配无整数部分的小数,如 .123
  2. 捕获组会带来额外的开销

例四:/^[+-]?(?:\d*\.)?\d+$/
说明:
(?:):创建一个非捕获组
*:限定符,匹配零个或多个
该正则缺点:

  1. 不能匹配无小数部分的数值,如 2.
  2. 不能匹配科学计数法,如 1e2、3e-1、-2.e+4

完整的数值正则怎么写

完整数值的表示:
在这里插入图片描述
因此完整的数值正则如下:
/^[+-]?(?:\d+\.?|\d*\.\d+)(?: e[+-]?\d+)?$/i
说明:
|:用来创建分支,当位于圆括号内时,表示子表达式的分支条件,当位于圆括号外时,表示整个正则表达式的分支条件
i:表示匹配时忽略大小写,在这个例子中用于匹配科学计数法的 e,去掉 i 修饰符需要把 e 改为 [eE]

用正则处理数值

需求一:数值解析
解析一个CSS字符串,将里面的数值以数组的形式返回。

function execNumberList(str) {
   // ……
}

console.log(execNumberList('1.0px .2px -3px +4e1px')); // [1, 0.2, -3, 40]
console.log(execNumberList('+1.0px -0.2px 3e-1px')); // [1, -0.2, 0.3]
console.log(execNumberList('1px 0')); // [1, 0]
console.log(execNumberList('-1e+1px')); // [-10]

解答:

const reg = /[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?=px|\s|$)/gi;

function execNumberList(str) {
    reg.lastIndex = 0;
    let exec = reg.exec(str);
    const result = [];
    while (exec) {
        result.push(parseFloat(exec[0]));
        exec = reg.exec(str);
    }
    return result;
}

解析:/[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?=px|\s|$)/gi
(?=expression): 叫做正向肯定环视 / 顺序肯定环视 / 先行断言,用于匹配符合条件的位置。
表示当前位置匹配到了这几种情况px、\s、$,正则表达式匹配到这里位置

(?!expression)正向否定环视 / 顺序否定环视 / 先行否定断言
(?<=expression) 反向肯定环视 / 逆序肯定环视 / 后行断言,es2018 新增
(?<!expression)反向否定环视 / 逆序否定环视 / 后行否定断言,es2018 新增

g:修饰符,表示全局匹配,用于取出目标字符串中所有符合条件的结果

需求二:数值转货币格式

function formatCurrency(str) {
   // ……
}

console.log(formatCurrency('1')); // 1
console.log(formatCurrency('123')); // 123
console.log(formatCurrency('12345678')); // 12,345,678

答案:

const reg = /(\d)(?=(\d{3})+(,|$))/g;
function formatCurrency(str) {
   return str.replace(reg, '$1,');
}

解析:
{n}:限定符,表示重复 n 次,n 必须是非负整数
{n, m} 表示重复 n 到 m 次,n 和 m 都必须是非负整数,且 n <= m
{n,}表示重复 n 次以上

场景二:正则与颜色

颜色的表示方式

  1. 16进制表示法:
color: #rrggbb;
color: #rgb;
color: #rrggbbaa;
color: #rgba;

对应的正则写法:

const hex = '[0-9a-fA-F]';
const reg = new RegExp(`^(?:#${hex}{6}|#${hex}{8}|#${hex}{3,4})$`);
  1. rgb/rgba 表示法
color: rgb(r, g, b);
color: rgb(r%, g%, b%);
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 个捕获组
由于 r/g/b 必须同时为数值或百分比,所以 %? 只需要捕获一次,用 \1 来引用
\s:字符集缩写,用于匹配空白

使用正则处理颜色

需求一:16进制颜色的优化

function shortenColor(str) {
   // ……
}

console.log(shortenColor('#336600')); // '#360'
console.log(shortenColor('#19b955')); // '#19b955'
console.log(shortenColor('#33660000')); // '#3600'

答案:

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>');
}

解析:
(?<key>)

  1. es2018 新增,具名捕获组
  2. 反向引用时的语法为 \k
  3. 在 replace 中,使用 $ 来访问具名捕获组
  4. 当应用 exec 时,具名捕获组可以通过 execResult.groups[key] 访问

场景三:正则与url

用正则解析URL

完整的URL规范
在这里插入图片描述

function execURL(url) {
    // ……
}

console.log(execURL('http://localhost:8080/?#'));
{
  protocol: 'http:',
  host: 'localhost:8080',
  hostname: 'localhost',
  port: '8080',
  pathname: '/',
  search: '?',
  hash: '#'
}

答案:

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 || '';
        return result.groups;
    }
    return {
        protocol: '', host: '', hostname: '', port: '',
        pathname: '', search: '', hash: '',
    };
}

用正则解析search和hash

function execUrlParams(str) {
    // ……
}

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: '' }

答案:

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;
}

解析:const reg = /(?:^|&)([^&=]*)=?([^&]*?)(?=&|$)/y;
*g:? 可以跟在任何限定符之后,表示非贪婪模式
y:es6 新增,粘连修饰符,和 g 修饰符类似,也是全局匹配。
区别在于:

  1. y 修饰符每次匹配的结果必须是连续的
  2. y 修饰符在 match 时只会返回第一个匹配结果

Node.js基础入门

什么是Node.js

Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
Node.js是一个基于Chrome的V8 JavaScript引擎构建的JavaScript运行池。

与 JavaScript 的区别

  • 基于异步 I/O 相关接口
  • 基于 node_modules 和 require 的模块依赖
  • 提供 C++ addon API 与系统交互

Node.js 可以干什么?

  • Web 服务端:Web Server、爬虫
  • CLI 命令行脚本:webpack
  • GUI 客户端软件:VSCode、网易云音乐
  • IoT, 图像处理, 实时通讯,加密货币…

Node.js基础

首先去官网下载node.js

demo1: hello world

// index.js
console.log('hello world');

-> ~node index.js
hello world

demo2: 读写文件

const fs = require('fs');
fs.readFile('test.txt', (err, data) => {
    console.log(data);
});
console.log('read file content');

注意:fs.readFile()是一个异步操作,提供了一个callback方法,当读取完成之后,会执行这个callback。
所以对于以上的代码,会先打印read file content,再打印hello world

模块

  • 内置模块:编译进 Node 中,例如 http fs net process path 等
const fs = require('fs');
fs.readFile('a.text', (err, buffer) => {
  console.log(buffer);
})
const {readFile} = require('fs');
readFile('a.txt', (err, buffer) => {
  console.log(buffer);
})
  • 文件模块:原生模块之外的模块,和文件(夹)一一对应
// app.js
var circle = require('./circle.js');
console.log('半径为4的圆面积是:' + circle.area(4));

定义模块:

// circle.js
const pi = Math.PI;
exports.area = function (r) {
    return pi * r * r;
};
exports.circumference = function (r) {
    return 2 * pi * r;
};

模块加载

// 加载绝对路径文件
require('/foo/bar/a.js');

// 加载相对路径文件
require('../a.js');

// 加载无后缀的文件
require('../a');

// 加载外部模块
require('pkg-name');

模块类型

  • .js
  • .json
  • .node
  • .mjs

模块路径查找

  • 绝对路径
  • 相对路径
    • 和当前路径处理为绝对路径
  • 模块/文件夹
    • 原生模块,直接读取缓存
    • [$NODE_PATH, ~/.node_modules,
    • ./node_modules, …/node_modules, …]
    • 解析 package.json,查找 main 属性,没有则使用 index.js
    • 如果未找到,则报错

js 模块解析

  • 通过 fs.readFileSync 同步拿到文件内容
  • 对内容进行包装
(function (exports, require, module, __filename, __dirname) {
    var circle = require('./circle.js');
    console.log('The area is ' + circle.area(4));
});
  • 通过 vm.runInThisContext 执行
  • 获取 module 对象的值作为模块的返回值

模块缓存

  • 模块加载后会将返回值缓存起来
  • 下次加载时直接读取缓存结果,避免文件 I/O 和解析时间
  • 导出对象缓存在 Module._cache 对象上

NPM

npm(node package manager)是包管理器

包管理

  • 一个package.json文件应该存在于包顶级目录下
  • 二进制文件应该包含在bin目录下
  • JavaScript代码应该包含在lib目录下
  • 文档应该在doc目录下
  • 单元测试应该在test目录下

package.json

star-plan npm init -y
使用该命令快速创建一个package.json文件

{
  "name": "star-plan",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

基于Node.js的Web开发

TODO List 项目实战

Node.js的调试

  • 日志调试
  • 断点调试
    • node --inspect
    • vscode
    • ndb
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值