Appearance
JavaScript模块化、异常处理和正则
内容概述
这一讲主要讲解JavaScript模块化,然后补充两个前面没有讲到的知识:异常处理和正则
教学目标
- 理解JavaScript模块化是什么以及为什么需要模块化
- 掌握ES6模块化规划语法
- 理解CommonJS模块化语法
- 理解异常处理,掌握try/catch/finally语句使用
- 掌握正则表达式的基本使用,能编写简单的正则
具体内容
- JavaScript模块化:CommonJS和ES6模块
- 异常处理
- 正则表达式:创建正则表达式,特殊字符,正则相关方法
JavaScript模块化
JavaScript 模块化是一种将代码分割成独立、可复用的单元(称为“模块”)的技术。这种技术有助于组织代码、提高代码的可维护性、促进代码重用,并有助于管理依赖关系。JavaScript 模块化经历了几个发展阶段,从最初的简单函数封装,到使用立即执行函数表达式(IIFE),再到现代的 ES6 模块(ECMAScript 2015 引入)和 CommonJS、AMD 等规范。
没有模块化的问题
比如,我们网页中引入JavaScript代码,经常是这样的。
javascript
<script src="./utils.js"></script>
<script src="./main.js"></script>javascript
// utils.js
let a = 1
const sum = function (a, b) {
return a + b
}
// main.js
let a = 1
console.log(sum(1, 2))问题
- 变量容易冲突
- script引入顺序要根据依赖关系
所以需要模块化解决
- 需要实现每个模块都要有自己单独的作用域,每个模块之间的内部变量不会产生冲突;
- 需要实现每个模块需要能导入其它模块的功能,也能导出自己的功能供其它模块调用。
CommonJS
CommonJS 是一种主要用于 Node.js 的模块规范。它使用 require() 函数来同步加载模块,并使用 module.exports 或 exports 对象来导出模块。
导出模块:
javascript
// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
// 或者使用 exports
// exports.add = add;
// exports.subtract = subtract;导入模块:
javascript
// app.js
const { add, subtract } = require('./math.js');
console.log(add(2, 3)); // 5
console.log(subtract(5, 2)); // 3ES6 模块
ES6 引入了原生的模块系统,使得在浏览器和 Node.js 环境中都可以使用 import 和 export 语句来导入和导出模块。
浏览器如果需要加载ES6模块,需要在<script>标签上加上type="module"属性。
javascript
<script type="module" src="./main.js"></script>export命令
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。
javascript
// user.js
export const name = 'js';
export const age = 18;export的写法,除了像上面这样,还有另外一种。
javascript
// user.js
const name = 'js';
const age = 18;
export { name, age };export命令除了输出变量,还可以输出函数或类(class)。
javascript
export function multiply(x, y) {
return x * y;
};import命令
- 使用
export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
javascript
// main.js
import { name, age } from './user.js';
console.log(name)
console.log(age)- 如果想为输入的变量重新取一个名字,
import命令要使用as关键字,将输入的变量重命名。
js
import { name as userName } from './user.js';
console.log(userName)- 如果多次重复执行同一句
import语句,那么只会执行一次,而不会执行多次。
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
下面是一个circle.js文件,它输出两个方法area和circumference。
javascript
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}现在,加载这个模块。
javascript
// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));上面写法是逐一指定要加载的方法,整体加载的写法如下。
javascript
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));默认导出
javascript
// export-default.js
export default function () {
console.log('foo');
}其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
javascript
// main.js
import customName from './export-default';
customName(); // 'foo'export default命令用在非匿名函数前,也是可以的。
js
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者写成
function foo() {
console.log('foo');
}
export default foo;异常处理
在JavaScript中,异常处理是一种重要的编程实践,它可以帮助你捕获和处理在运行时可能发生的错误,从而避免程序因未处理的错误而完全崩溃。JavaScript通过try...catch...finally语句来实现异常处理。
基本语法
javascript
try {
// 尝试执行的代码块
// 如果这里面的代码抛出了异常,则控制权会转移到catch块
} catch (error) {
// 捕获异常并处理
// error对象包含了异常的信息
} finally {
// 无论是否捕获到异常,finally块中的代码都会执行
// 通常用于执行清理工作,如关闭文件、释放资源等
}示例
javascript
try {
// 尝试执行一个可能会抛出异常的操作
const result = 10 / 0; // 这将抛出一个Infinity值,但JavaScript不会因此抛出异常
// 如果这里尝试访问一个未定义的属性,比如null.someProperty,则会抛出TypeError
// 故意制造一个错误以演示try...catch
let obj = null
console.log(obj.a);
throw new Error('这是一个自定义的错误');
} catch (error) {
// 捕获到异常,并执行这里的代码
console.error('捕获到错误:', error.name);
} finally {
// 无论是否捕获到异常,finally块都会执行
console.log('finally块执行了');
}正则表达式
JavaScript 中的正则表达式(Regular Expressions)是一种强大的文本处理工具,它允许你定义搜索文本的模式,并根据这些模式执行匹配、搜索、替换等操作。
创建正则表达式
方式一:通过调用RegExp对象的构造函数创建
javascript
const regexp = new RegExp(/123/);方式二:利用字面量创建 正则表达式
javascript
const reg = /123/;测试正则表达式
test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。
javascript
const reg = /123/;
console.log(reg.test(123));//匹配字符中是否出现123 出现结果为true
console.log(reg.test('abc'));//匹配字符中是否出现123 未出现结果为false正则表达式中的特殊字符
修饰符
| 修饰符 | 描述 |
|---|---|
| i | 执行对大小写不敏感的匹配。 |
| g | 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。 |
| m | 执行多行匹配。 |
方括号
方括号用于查找某个范围内的字符:
| 表达式 | 描述 |
|---|---|
| [abc] | 查找方括号之间的任何字符。 |
| [^abc] | 查找任何不在方括号之间的字符。 |
| [0-9] | 查找任何从 0 至 9 的数字。 |
| [a-z] | 查找任何从小写 a 到小写 z 的字符。 |
| [A-Z] | 查找任何从大写 A 到大写 Z 的字符。 |
| [A-z] | 查找任何从大写 A 到小写 z 的字符。 |
| [adgk] | 查找给定集合内的任何字符。 |
| [^adgk] | 查找给定集合外的任何字符。 |
| (red|blue|green) | 查找任何指定的选项。 |
javascript
const reg1 = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
const reg2 = /^[abc]$/; // 三选一 只有是a 或者是 b 或者是c 这三个字母才返回 true
// - 短横线 表示一个范围
const reg3 = /^[a-z]$/ //26个英文字母任何一个字母返回 true - 表示的是a 到z 的范围
// 字符组合
const reg4 = /^[a-zA-Z0-9]$/; // 26个英文字母(大写和小写都可以)任何一个字母返回 true
//取反 方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false 。
const reg5 = /^[^a-zA-Z0-9]$/;元字符
元字符(Metacharacter)是拥有特殊含义的字符:
| 元字符 | 描述 |
|---|---|
| . | 查找单个字符,除了换行和行结束符。 |
| \w | 查找数字、字母及下划线。 |
| \W | 查找非单词字符。 |
| \d | 查找数字。 |
| \D | 查找非数字字符。 |
| \s | 查找空白字符。 |
| \S | 查找非空白字符。 |
| \b | 匹配单词边界。 |
| \B | 匹配非单词边界。 |
| \0 | 查找 NULL 字符。 |
| \n | 查找换行符。 |
| \f | 查找换页符。 |
| \r | 查找回车符。 |
| \t | 查找制表符。 |
| \v | 查找垂直制表符。 |
| \xxx | 查找以八进制数 xxx 规定的字符。 |
| \xdd | 查找以十六进制数 dd 规定的字符。 |
| \uxxxx | 查找以十六进制数 xxxx 规定的 Unicode 字符。 |
量词
| 量词 | 描述 |
|---|---|
| n+ | 匹配任何包含至少一个 n 的字符串。例如,/a+/ 匹配 "candy" 中的 "a","caaaaaaandy" 中所有的 "a"。 |
| n* | 匹配任何包含零个或多个 n 的字符串。例如,/bo*/ 匹配 "A ghost booooed" 中的 "boooo","A bird warbled" 中的 "b",但是不匹配 "A goat grunted"。 |
| n? | 匹配任何包含零个或一个 n 的字符串。例如,/e?le?/ 匹配 "angel" 中的 "el","angle" 中的 "le"。 |
| n{X} | 匹配包含 X 个 n 的序列的字符串。例如,/a{2}/ 不匹配 "candy," 中的 "a",但是匹配 "caandy," 中的两个 "a",且匹配 "caaandy." 中的前两个 "a"。 |
| n{X,} | X 是一个正整数。前面的模式 n 连续出现至少 X 次时匹配。例如,/a{2,}/ 不匹配 "candy" 中的 "a",但是匹配 "caandy" 和 "caaaaaaandy." 中所有的 "a"。 |
| n{X,Y} | X 和 Y 为正整数。前面的模式 n 连续出现至少 X 次,至多 Y 次时匹配。例如,/a{1,3}/ 不匹配 "cndy",匹配 "candy," 中的 "a","caandy," 中的两个 "a",匹配 "caaaaaaandy" 中的前面三个 "a"。注意,当匹配 "caaaaaaandy" 时,即使原始字符串拥有更多的 "a",匹配项也是 "aaa"。 |
| n$ | 匹配任何结尾为 n 的字符串。 |
| ^n | 匹配任何开头为 n 的字符串。 |
| ?=n | 匹配任何其后紧接指定字符串 n 的字符串。 |
| ?!n | 匹配任何其后没有紧接指定字符串 n 的字符串。 |
javascript
// 量词符:用来设定某个模式出现的次数
//简单理解:就是让下面的a这个字符重复多少次
//* 相当于 >=0 可以出现0次或者很多次
const reg1 = /^a*$/;
//+相当于 >=1 可以出现1次或者很多次
const reg2 = /^a+$/;
//?相当于 1 || 0 重复1次或者0次
const reg3 = /^a?$/;
//{3}就是重复3次
const reg4 = /^a{3}$/;
//{3,}大于等于3 >=3
const reg5 = /^a{3,}$/;
//{3,16} 等于等于3 并且 小于等于16
const reg6 = /^a{3,16}$/;边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
| 边界符 | 说明 |
|---|---|
| ^ | 表示匹配行首的文本(以谁开始) |
| $ | 表示匹配行尾的文本(以谁结束) |
如果 ^和 $ 在一起,表示必须是精确匹配。
javascript
const reg1 = /abc/; // 只要包含有abc这个字符串返回的都是true
const reg2 = /^abc/; // 必须以abc开头
const reg3 = /^abc$/; // 精确匹配 要求必须是 abc字符串才符合规范正则相关方法
String 对象的方法
match()- 用法:
str.match(regexp) - 描述:执行一个搜索匹配,返回一个数组,其中存放匹配的结果。如果没有找到任何匹配项,则返回
null。如果使用了全局匹配(即正则表达式中包含g标志),则返回的数组包含字符串中的所有匹配项,但不包括捕获组。
- 用法:
search()- 用法:
str.search(regexp) - 描述:执行一个搜索匹配,如果找到匹配项,则返回第一个匹配项的索引;否则返回
-1。search()方法不返回匹配的字符串,也不使用捕获组。
- 用法:
replace()- 用法:
str.replace(regexp|substr, newSubStr|function) - 描述:在字符串中搜索匹配项,然后替换它们。
replace()可以接受一个字符串或一个正则表达式作为第一个参数,第二个参数可以是一个字符串或一个函数,用于指定替换内容。
- 用法:
split()- 用法:
str.split([separator[, limit]]) - 描述:使用指定的分隔符(可以是一个字符串或正则表达式)将字符串分割成子字符串数组,并返回该数组。可选的
limit参数可以限制返回的数组中的元素数量。
- 用法:
RegExp 对象的方法
虽然RegExp对象本身不直接提供与字符串操作相关的方法(除了它的exec()和test()方法),但它们与字符串方法紧密相关,因为字符串方法可以接受正则表达式作为参数。不过,这里还是列出RegExp对象的两个与匹配相关的方法:
exec()- 用法:
regexp.exec(str) - 描述:在字符串中执行一个搜索匹配。返回一个数组,其中存放匹配的结果。如果没有找到任何匹配项,则返回
null。返回的数组包含匹配的文本和任何捕获组的匹配项,以及索引和输入字符串。与String.prototype.match()类似,但在没有全局标志(g)时,exec()在每次调用时都会从字符串的开头开始搜索,而不是从上次匹配的末尾开始。
- 用法:
test()- 用法:
regexp.test(str) - 描述:执行一个正则表达式匹配测试。如果字符串
str中含有匹配正则表达式的文本,则返回true;否则返回false。
- 用法:
案例
作业
- 使用正则识别手机号,手机号规则
- 11个数字
- 1开头
- 第2位只能是3、4、5、7、8的其中一个
javascript
function isPhone (str) {
}- 使用正则判断时间格式 yyyy-mm-dd
javascript
function isDate (str) {
}- 使用正则去除前后空格
javascript
function trim (str) {
}
let str = " Hello, World! ";
let trimmedStr = trim(str);
console.log(trimmedStr); // 输出: "Hello, World!"作业讲解
- 实现函数,函数接收一个参数arg,类型未知,如果是数字返回该数字的平方,如果是字符串将该字符串重复5次并返回,如果是其他类型直接返回该参数
javascript
function test1(arg) {
// 补充代码
if (typeof arg === 'number' && !Number.isNaN(arg)) {
return arg * arg
} else if (typeof arg === 'string') {
return arg.repeat(5)
}
}
console.log(test1(10)); // 100
console.log(test1('abc')); // 'abcabcabcabcabc'- 实现函数,函数接收一个数组且每个元素都是数字,分别计算数组的最小值、最大值、平均值按顺序放入一个长度为3的数组中并返回
javascript
function test2(arr) {
// 补充代码
let min = arr[0]
let max = arr[0]
let total = 0;
for (let i = 0; i < arr.length; i++) {
if (min > arr[i]) {
min = arr[i]
}
if (max < arr[i]) {
max = arr[i]
}
total = total + arr[i]
}
return [min, max, total / arr.length]
}
console.log(test2([1, 7, 10])); // [1, 10, 6]- 将下列函数转换为箭头函数
javascript
function getSum (a, b) {
return a + b;
}
function square (a) {
return a * a;
}
function say (name, message) {
console.log(name);
console.log(message);
console.log(name + ':' + message)
}4.实现函数test1,参数接收一个数组表示所有学生考试分数,数组每个元素是个对象,对象有两个属性id和score分数,筛选出分数大于等于60的学生然后求平均分(取整数部分)
javascript
function test1 (arr) {
// 实现代码
const filterArr = students.filter(item => item.score >= 60)
const total = filterArr.reduce((pre, current) => {
return pre + current.score
}, 0)
return Math.floor(total / filterArr.length)
}
// 参数示例
const students = [
{
id: 1,
score: 90
},
{
id: 2,
score: 45
},
{
id: 3,
score: 70
},
{
id: 4,
score: 65
},
{
id: 5,
score: 55
}
]
console.log(test1(students)); // 75- 实现函数test2,参数接收一个数组表示所有学生考试分数,数组每个元素是个对象,对象有两个属性id和score分数,去掉一个最低分,去掉一个最高分,然后求平均分(取整数部分)
javascript
function test2 (arr) {
// 实现代码
const sortArr = arr.sort((a, b) => a.score - b.score)
const filterArr = sortArr.slice(1, sortArr.length - 1)
console.log(filterArr);
const total = filterArr.reduce((pre, current) => {
return pre + current.score
}, 0)
return Math.floor(total / filterArr.length)
}
// 参数示例
const students = [
{
id: 1,
score: 90
},
{
id: 2,
score: 45
},
{
id: 3,
score: 70
},
{
id: 4,
score: 65
},
{
id: 5,
score: 55
}
]
console.log(test2(students)); // 63- 写一个函数,入参是一个日期对象,实现格式化日期对象,返回yyyy-MM-dd HH:mm:ss的形式
javascript
function addZero (str) {
if (String(str).length < 2) {
return '0' + str;
} else {
return str
}
}
function date2str (date) {
// yyyy-MM-dd HH:mm:ss
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
return `${year}-${addZero(month)}-${addZero(day)} ${addZero(hour)}:${addZero(minute)}:${addZero(second)}`;
}
console.log(date2str(new Date()));7.写一个函数实现单词逆转,如,输入Welcome to Shenzhen 返回Shenzhen to Welcome
javascript
function reverseWords (str) {
const words = str.split(' ');
return words.reverse().join(' ')
}
console.log(reverseWords('Welcome to Shenzhen'));8.写一个函数,入参是3个字符串,函数内部实现1s之后打印第1个字符串,再过2s之后打印第2个字符串,再过3s之后打印第3个字符串
javascript
function test (str1, str2, str3) {
setTimeout(() => {
console.log(str1);
setTimeout(() => {
console.log(str2);
setTimeout(() => {
console.log(str3);
}, 3000)
}, 2000)
}, 1000)
}
test('a', 'b', 'c')9.编写一个异步函数sleep实现定时效果,接收一个参数表示需要定时的时间,单位ms
javascript
function sleep (time) {
return new Promise(reslove => {
setTimeout(() => {
reslove()
}, time)
})
}
sleep(3000).then(() => {
console.log('3s后执行该行代码');
})- 在题目1实现sleep函数的基础上,写一个函数,入参是3个字符串,函数内部实现1s之后打印第1个字符串,再过2s之后打印第2个字符串,再过3s之后打印第3个字符串,写出两种方式(Promise链式编程和async/await语法形式)
javascript
function sleep (time) {
return new Promise(reslove => {
setTimeout(() => {
reslove()
}, time)
})
}
function test1 (str1, str2, str3) {
sleep(1000).then(() => {
console.log(str1);
return sleep(2000)
}).then(() => {
console.log(str2);
return sleep(3000)
}).then(() => {
console.log(str3);
})
}- 使用正则识别手机号,手机号规则 11个数字 1开头 第2位只能是3、4、5、7、8的其中一个
javascript
function isPhone (str) {
return /^1[34578]\d{9}$/.test(str)
}
console.log(isPhone('18674584978'));- 使用正则判断时间格式 yyyy-mm-dd
javascript
function isDate (str) {
return /^\d{4}-\d{2}-\d{2}$/.test(str);
}- 使用正则去除前后空格
javascript
function trim (str) {
return str.replace(/^\s+|\s+$/g, '');
}html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>文章列表页面</title>
<style>
/* styles.css */
body,
html {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.articles {
display: flex;
flex-direction: column;
gap: 20px;
}
.article {
display: flex; /* 启用Flex布局 */
align-items: flex-start; /* 垂直对齐方式:项目在交叉轴上的起点对齐 */
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px; /* 根据需要调整内边距 */
margin-bottom: 20px; /* 文章项之间的间距 */
position: relative;
}
.cover-image {
width: 150px; /* 或者使用百分比,根据需要调整 */
height: auto;
border-radius: 5px 0 0 5px; /* 仅在左侧添加圆角 */
margin-right: 15px; /* 封面和简介之间的间距 */
}
.content {
position: relative;
flex-grow: 1; /* 使得内容区域能够填充剩余空间 */
}
/* 其他CSS样式保持不变或根据需要调整 */
.article h2 {
margin: 0 0 10px 0;
font-size: 1.5em;
}
.article img {
width: 150px;
height: 80px;
}
.article p {
margin: 0;
color: #666;
}
.date {
position: absolute; /* 绝对定位 */
right: 15px;
top: 15px;
font-size: 0.8em;
color: #aaa;
}
/* 响应式设计,当屏幕较小时,可以调整Flex方向或文章样式 */
@media (min-width: 600px) {
.articles {
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.article {
flex: 1 1 calc(50% - 10px); /* 宽度自适应,但保留间隙 */
position: relative;
}
}
#addForm {
text-align: right;
}
.form-row {
display: flex;
margin-bottom: 20px;
}
.form-row > label {
margin-right: 5px;
}
.article-title,.article-content {
flex: 1;
padding: 10px;
border-radius: 5px;
margin-right: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.submit-button {
padding: 10px 20px;
border: none;
border-radius: 5px;
background-color: #8e9bf0;
color: #fff;
cursor: pointer;
margin-bottom: 10px;
}
.article-delete {
padding: 5px 10px;
border: none;
border-radius: 5px;
background-color: #ff6b6b;
color: #fff;
cursor: pointer;
transition: background-color 0.3s;
position: absolute;
right: 5px;
bottom: 5px;
}
.article-delete:hover {
background-color: #ff4f4f;
}
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1>文章列表</h1>
</header>
<form class="form" id="addForm">
<div class="form-row">
<label for="articleTitle">文章标题</label>
<input
type="text"
class="article-title"
placeholder="请输入文章标题"
id="articleTitle"
/>
</div>
<div class="form-row">
<label for="articleContent">文章内容</label>
<textarea
rows="3"
cols="30"
class="article-content"
placeholder="请输入文章内容"
id="articleContent"></textarea>
</div>
<button type="submit" class="submit-button">提交</button>
</form>
<div class="articles">
<div class="article">
<img
src="https://api.likepoems.com/img/nature"
alt="文章封面 1"
class="cover-image"
/>
<div class="content">
<h2>文章标题 1</h2>
<p>这是一篇关于HTML和CSS的入门级文章,适合初学者。</p>
<span class="date">发布日期:2023-08-06 12:00:00</span>
</div>
<button class="article-delete">删除</button>
</div>
<div class="article">
<img
src="https://api.likepoems.com/img/nature"
alt="文章封面 2"
class="cover-image"
/>
<div class="content">
<h2>文章标题 2</h2>
<p>这是一篇关于HTML和CSS的入门级文章,适合初学者。</p>
<span class="date">发布日期:2023-08-06 13:00:00</span>
</div>
<button class="article-delete">删除</button>
</div>
<div class="article">
<img
src="https://api.likepoems.com/img/nature"
alt="文章封面 3"
class="cover-image"
/>
<div class="content">
<h2>文章标题 3</h2>
<p>这是一篇关于HTML和CSS的入门级文章,适合初学者。</p>
<span class="date">发布日期:2023-08-06 14:00:00</span>
</div>
<button class="article-delete">删除</button>
</div>
<!-- 更多文章 -->
</div>
</div>
<script>
</script>
</body>
</html>