前端汇总 --004

259 阅读13分钟

一、 状态模式和观察者模式的黄金组合

1.1 观察者模式

观察者模式是一种行为型设计模式,它定义了对象之间一对多的依赖关系,使得当一个对象状态发生改变时,所有依赖它的对象都会得到通知并自动更新。在审查流程中,我们可以将审查对象作为主题,将所有需要接收审查结果通知的参与方作为观察者,当审查结果发生变化时,通知所有观察者进行相应的操作。

class Subject {
  constructor() {
    this.observers = []; // 存储所有观察者的数组
  }

  addObserver(observer) { // 添加观察者的方法
    this.observers.push(observer);
  }

  removeObserver(observer) { // 移除观察者的方法
    this.observers = this.observers.filter(o => o !== observer);
  }

  notify() { // 通知所有观察者更新的方法
    this.observers.forEach(observer => observer.update());
  }
}

class Observer {
  update() { // 观察者更新方法
    console.log('Observer updated');
  }
}

const subject = new Subject(); // 创建主题对象
const observer1 = new Observer(); // 创建观察者1对象
const observer2 = new Observer(); // 创建观察者2对象

subject.addObserver(observer1); // 添加观察者1
subject.addObserver(observer2); // 添加观察者2

subject.notify(); // 通知所有观察者更新

1.2 状态模式

状态模式是一种行为型设计模式,它允许对象在内部状态改变时改变它的行为,从而使对象看起来像是改变了它的类。在审查流程中,我们可以将审查状态定义为一个状态接口,并为每个状态实现对应的行为。这样,当审查状态发生改变时,审查对象只需要更新其状态,就可以自动更新其行为。

class State {
  doAction(context) {} // 状态执行方法
}

class StartState extends State {
  doAction(context) { // 开始状态执行方法
    console.log('Start state');
    context.setState(this); // 设置当前状态为开始状态
  }
}

class StopState extends State {
  doAction(context) { // 停止状态执行方法
    console.log('Stop state');
    context.setState(this); // 设置当前状态为停止状态
  }
}

class Context {
  constructor() {
    this.state = null; // 当前状态
  }

  setState(state) { // 设置状态的方法
    this.state = state;
  }

  getState() { // 获取当前状态的方法
    return this.state;
  }
}

const context = new Context(); // 创建上下文对象

const startState = new StartState(); // 创建开始状态对象
startState.doAction(context); // 执行开始状态
console.log(context.getState()); // 输出当前状态

const stopState = new StopState(); // 创建停止状态对象
stopState.doAction(context); // 执行停止状态
console.log(context.getState()); // 输出当前状态

1.3 结合观察者模式和状态模式实现审查流程

我们已经了解了观察者模式和状态模式的实现方式。现在,我们将这两种模式结合起来,实现一个简单的审查流程。在这个流程中,我们有三个角色:审查对象、审查员和管理员。审查对象的状态包括“未审查”、“已通过”和“未通过”,而审查员和管理员都是观察者,当审查对象状态发生改变时,会自动收到通知。

// 状态接口,定义了状态对应的行为
class State {
  doAction(context) {}
}

// 未审查状态
class PendingState extends State {
  doAction(context) {
    console.log('Pending state'); // 输出当前状态
    context.setState(this); // 更新状态为当前状态
  }
}

// 审查通过状态
class ApprovedState extends State {
  doAction(context) {
    console.log('Approved state'); // 输出当前状态
    context.setState(this); // 更新状态为当前状态
  }
}

// 审查拒绝状态
class RejectedState extends State {
  doAction(context) {
    console.log('Rejected state'); // 输出当前状态
    context.setState(this); // 更新状态为当前状态
  }
}

// 审查对象
class Context {
  constructor() {
    this.state = null; // 当前状态为空
    this.observers = []; // 观察者列表为空
  }

  // 更新状态
  setState(state) {
    this.state = state;
    this.notifyObservers(); // 通知所有观察者
  }

  // 获取当前状态
  getState() {
    return this.state;
  }

  // 添加观察者
  addObserver(observer) {
    this.observers.push(observer);
  }

  // 移除观察者
  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index !== -1) {
      this.observers.splice(index, 1);
    }
  }

  // 通知所有观察者
  notifyObservers() {
    this.observers.forEach(observer => {
      observer.update();
    });
  }
}

// 观察者接口
class Observer {
  update() {}
}

// 审查员
class Reviewer extends Observer {
  constructor(name) {
    super();
    this.name = name;
  }

  // 更新
  update() {
    console.log(`Reviewer ${this.name} received notification`);
  }
}

// 管理员
class Admin extends Observer {
  constructor(name) {
    super();
    this.name = name;
  }

  // 更新
  update() {
    console.log(`Admin ${this.name} received notification`);
  }
}

// 创建审查对象
const context = new Context();

// 创建观察者
const reviewer1 = new Reviewer('Alice');
const reviewer2 = new Reviewer('Bob');
const admin = new Admin('Charlie');

// 将观察者添加到审查对象的观察者列表中
context.addObserver(reviewer1);
context.addObserver(reviewer2);
context.addObserver(admin);

// 创建状态对象
const pendingState = new PendingState();
const approvedState = new ApprovedState();
const rejectedState = new RejectedState();

// 将状态对象设置为审查对象的状态
context.setState(pendingState);
console.log(context.getState()); // 输出当前状态

// 更新状态为“审查通过”
context.setState(approvedState);
console.log(context.getState()); // 输出当前状态

// 更新状态为“审查拒绝”
context.setState(rejectedState);
console.log(context.getState()); // 输出当前状态

上述代码实现了一个简单的审查流程,其中包括了观察者模式和状态模式。代码中包含了如下的状态模式和观察者模式的设计思路:

  • 状态模式:将对象的行为和状态分离开来,使对象在不同的状态下表现出不同的行为。这里通过定义了状态接口,然后定义了具体的状态类,并在状态类中实现了对应状态的行为。
  • 观察者模式:定义了一种一对多的关系,当一个对象状态改变时,所有依赖于它的对象都会得到通知并自动更新。这里通过定义了观察者接口和具体的观察者类,并在审查对象的状态改变时,通知所有的观察者。

同时,代码中也体现了设计模式的优点:

  • 状态模式:让代码更加清晰,易于维护和扩展。如果要新增一种状态,只需要实现对应状态的类即可,不会对现有代码产生影响。
  • 观察者模式:让对象之间的耦合度降低,不需要让一个对象硬编码去知道其他对象的存在,使得对象之间更加松散耦合,更易于维护和扩展。

状态模式和观察者模式的结合,为实现复杂的审查流程提供了一种优雅、灵活的方式。

二、Promise + 队列 :有序执行方法

class asyncQueue {
  constructor() {
    this.asyncList = [];
    this.inProgress = false;
  }

  add(asyncFunc) {
    return new Promise((resolve, reject) => {
      this.asyncList.push({asyncFunc, resolve, reject});
      if (!this.inProgress) {
        this.execute();
      }
    });
  }

  execute() {
    if (this.asyncList.length > 0) {
      const currentAsyncTask = this.asyncList.shift();

      currentAsyncTask.asyncFunc()
        .then(result => {
          currentAsyncTask.resolve(result);
          this.execute();
        })
        .catch(error => {
          currentAsyncTask.reject(error);
          this.execute();
        });

      this.inProgress = true;
    } else {
      this.inProgress = false;
    }
  }
}

export default asyncQueue

调用方法如下:

const queue = new asyncQueue()

watch(() => props.dataList, async (newVal, oldVal) => {
  queue.add(() => dataRender(newVal))
}, {
  immediate: true,
  deep: true
})

通过上述代码我们就可以,让我们的每一个异步任务有顺序的执行,并且让每一个异步任务执行完成以后自动执行下一个

两种其他的场景:

  1. 比如某个按钮用户点击了多次,但是我们要让这些请求有序的执行并且依次拿到这些请求返回的数据
  2. 某些高频的通信操作,我们不能丢弃用户的每次通信,而是需要用这种队列的方式,自动、有序的执行

三、 WEB常见安全漏洞

DVWA中包含的web漏洞:

暴力破解:HTTP表单暴力破解登录页面;用来测试密码暴力破解工具,展示弱口令的不安全性。

命令执行:在底层操作系统上执行命令。

伪造跨站请求:启动一个“攻击者”更改应用的管理员密码。

文件包含:允许“攻击者”将远程/本地文件包含到Web应用程序中。

SQL注入:启动一个“攻击者”将SQL语句注入到HTTP表单的输入框中,DVWA包括基于(盲注)和错误的SQL注入。

不安全文件上传:允许“攻击者”将恶意文件上传到web服务器。

跨站脚本(XSS):“攻击者”可以将自己编写的脚本注入到web应用或数据库中,DVWA包含反射型和存储型XSS。

作为新手靶场,它的部署也非常简单,比如我们要部署到本地,只需DVWA靶场文件与PHPSTUDY。

首先再官网下载源文件:

dvwa.co.uk/

我们打开PHPSTUDY配置环境,在PHPSTUDY目录文件里布置DVWA源码,访问本地(127.0.0.1)即可。

3.1 SQL注入漏洞

这应该是最常见的WEB漏洞了,其原理大致是Web应用程序对用户的输入没有做严格的判断,导致用户可用将非法的SQL语句拼接到正常的语句中,被当作SQL语句的一部分执行。基本的注入类型:

UNION query SQL injection(联合查询注入)

Error-based SQL injection(错型注入)

Boolean-based blind SQL injection(基于布尔的盲注)

Time-based blind SQL injection(基于时间的盲注)

Stacked queries SQL injection(可多语句查询注入)

3.2 暴力破解

暴力破解可以理解为穷举所有可能的条件,一般利用于用户密码的爆破,将密码进行逐个推算直到找出真正的密码为止。我们将在常用的密码字典中穷举出正确的密码。

3.3 文件上传

攻击者可以上传一个与网站脚本语言相对应的恶意代码动态脚本到服务器上,然后访问这些恶意脚本中包含的恶意代码,从而获得了执行服务器端命令的能力,进一步影响服务器安全。如果Web 服务器所保存上传文件的可写目录具有执行权限,那么就可以直接上传后门文件,导致网站沦陷。

3.4 XSS漏洞

跨站脚本攻击。它指的是恶意攻击者往web页面插入恶意的html代码。当用户浏览该页面时,嵌入到web里面的html代码会被运行,从而达到恶意攻击用户的特殊目的(比如影响网站运行,窃取管理员COOKIE)。

可以简单分为如下几类:

反射型xss:不会永久存储用户的数据,仅发生在用户的一次访问之后。

存储型xss:攻击代码被持久化保存在服务器上,不会因为一次访问就消失,持久攻击。

DOM型不需要与服务器端交互的,它只发生在客户端处理数据阶段。

3.5 命令执行

命令执行就是在需要输入数据的地方输入了恶意代码,而且系统并没有对其进行过滤或者其他处理导致恶意代码也被执行,最终导致数据泄露或者正常数据被破坏。常用的命令执行漏洞也是我们常见的系统命令(比如exec,cat等),通常不会让我们直接执行命令,而是需要一定的过滤来绕过检测,举一些拼接例子:

A&B(简单的拼接,AB之间无制约关系)

A&&B(A执行成功才会执行B)

A|B(A的输出作为B的输入)

A||B(A执行失败,然后才会执行B)

四、《JavaScript 语言精粹修订版》,作者是:道格拉斯·克罗克福德

4.1 核心

整个精华部分被分为 6 块,分别是:

  1. 语法
  2. 对象
  3. 函数
  4. 继承
  5. 数组
  6. 方法

4.11 语法:

整个语法部分涉及到的是整个 JavaScript 的基础语法。

4.111 字符串

而对于 JavaScript 中的字符串类型而言,书中主要从两个方面进行了描述:

  1. 首先是字面量:所谓字面量指的就是 快速创建某种类型的方式。对于字符串来说,它有两种字面量形式: 单引号和双引号。在日常开发中,我们更推荐使用 单引号 的字面量形式。

  2. 第二个叫做特性:这里的特性主要有三个:

    1. 首先第一个叫做 可拼接:多个字符串是可以通过 + 的形式进行拼接的。
    2. 第二个叫做 可 length:咱们可以通过 字符串.length 的形式,获取字符串的长度
    3. 第三个叫做 有方法:JavaScript 中的字符串提供了非常多的方法,这个咱们把它放到 方法 这一大块中再去说就可以。

4.112 语句

JavaScript 被称作是一个图灵完备的编程语言。所以它必然要具备 变量声明、逻辑判断 等语句。

JavaScript 中的语句可以写在 <script> 标签 中,每一对花括号中间就可以被叫做一组语句。

书中把 JavaScript 的语句分为 4 大块:

  1. 首先是声明语句:JavaScript 想要声明变量主要有三种形式

    1. 第一种是 var :它是在 ES6 之前唯一的声明变量的方式,现在已经不推荐使用了。
    2. 第二种是 let:在 ES6 之后可以通过它来定义变量
    3. 最后是 const:在 ES6 之后可以通过它来定义常量
  2. 其次是条件语句:JavaScript 中的条件语句主要有两个

    1. 第一个是 if
    2. 第二个是 switch

    这两种条件判断形式,在很多的编程语言中都是存在的。但是 JavaScript 中判断条件有些不太一样的地方。

    JavaScript任何表达式 都可以被作为判断条件,并且 有且仅有 6 个值 在条件判断中会被作为 ,它们分别是: false、null、undefined、空字符串、数字 0 、NaN。除此之外,其他的所有值都会被判断为

  3. 再往后就是循环语句:JavaScript 中循环语句主要有三种

    1. 第一个是 for 循环
    2. 第二个是 while 循环
    3. 第三种是 do...while 循环

    同时,针对于 for 循环来说,又可以被分为: 普通 for 循环 和 forIn 循环

  4. 最后就是强制跳转语句,所谓强制跳转语句指的是: 会强制更改程序执行顺序的语句,这些语句在日常开发中也是被经常使用的:

    1. 比如 break:它会让程序 退出循环 || 退出 switch 语句
    2. continue:会让程序 终止当前循环
    3. 然后是 renturn:它会终止函数的执行
    4. throw:会通过 抛出异常的形式,终止程序执行
    5. try...catch:会通过 捕获异常 的形式,让程序强制跳转到其他的逻辑中

4.12 对象

对象在 JavaScript 中是一个非常特殊的概念。

4.121 定义

JavaScript 中数据类型主要被分为两种:

  1. 简单数据类型:有 number、string、boolean、null、undefined、symbol、bigint
  2. 除了简单数据类型之外,其他的都被叫做 复杂数据类型,有: 数组、函数、正则表达式、对象。 而 JavaScript 中,所有的复杂数据类型,都被统称为 对象

4.122 检索与更新

同时对于对象来说,它可以 获取值(检索) ,也可以 修改值(更新)

JavaScript 中提供了两种检索与更新的形式:

  1. 第一种是通过 . 语法:这种方式最为简单易懂,是大多数情况下推荐使用的方式
  2. 第二种是通过 [] 语法[] 内部可以放一个变量,适用于 根据变量取值的情况

4.123 反射

其实在 JavaScript 里面,没有反射的明确定义。大家就把它作为是 作者的一个称呼即可

反射这一块里面,主要包含了两部分:

  1. 利用 typeof 获取变量的类型
  2. 利用 hasOwnProperty 判断 对象自身属性中是否具有指定的属性,具有则返回 true,否则则是 false

4.124 枚举

hasOwnProperty 这个方法在 “枚举” 的时候会非常有用。

和反射一样,JavaScript 中同样没有枚举的明确概念。它一样是作者的一个称呼。

书中的枚举指的主要是: 遍历一个对象并且获取该对象自身的所有属性

遍历可以直接通过 forIn 循环执行,而判断是否为对象自身属性,则需要借助 hasOwnProperty 方法

4.125 删除

最后,针对于对象模块,作者还提到了 删除 的概念。

我们可以利用 delete 关键字,直接删除对象中的指定属性。这也是很方便的。

4.13 函数

在对象说完之后,下面咱们就进入到了一个 大块 了,那就是函数!

函数 这一章里面,存在很多的 落后性描述的偏差,同时也有很多的 精华

4.131 精华

在函数这一章的开篇,作者有一句话我非常喜欢,它描述了整个编程的本质,咱们一起来读一下:所谓编程,就是将一组需求分解成一组函数与数据结构的技能

4.132 调用

在函数调用时,书中一共分为两部分去说:

  1. 首先第一部分是 函数可以调用其他函数:这个只要大家使用过编程语言,那么应该都很好理解。这种方式会 暂停当前函数执行,传递控制权和参数给新的函数

  2. 第二种是 附加参数:在 JavaScript 的函数中,存在两个附加参数:

    1. 第一个是 this:它的值取决于调用的模式。书中把调用模式分成了 4 大类:

      1. 前三大类 方法调用、函数调用、构造器调用this 指向调用方
      2. 最后一大类 apply 调用 中, this 指向 指定值
    2. 第二个 arguments:它表示函数得到的所有实际参数

4.133 构造器模式

函数除了可以被直接 调用 之外,还可以被当做 构造器使用,也就是所谓的 构造器模式。

关于 构造器模式,书中主要明确了四个方面,分别是:

  1. JavaScript 是一门基于原型继承的语言,这就意味着 对象可以直接从其他对象中继承属性
  2. 第二个是: 构造器本身就是函数
  3. 第三就是: 构造器不通过小括号的形式直接调用,而是要 配合 new 关键字 使用
  4. 最后就是:根据 约定,构造器它的 首字母要大写

这四块是非常重要的,哪怕是在当下时间段依然是行之有效的。

4.134 apply 调用模式

在前面,咱们提到了 apply 调用模式,咱们知道所谓的 apply 调用模式 实际上是分为三个方法的,分别是:

  1. apply
  2. call
  3. bind

这三个方法的核心作用,就是 改变 this 指向

4.135 返回值

再往后,书中对 函数的返回值 进行了一些说明。

大家需要知道的,主要是有三点:

  1. 首先第一点: 函数必然存在返回值。如果你什么都不返回,那么函数默认返回 undefined
  2. 然后: 可以通过 renturn 让函数提前返回指定的值,并且中止执行
  3. 第三:函数也可以配合 new 使用,这样会返回 this

4.137 递归

递归是函数调用非常常见并且复杂的场景。

书中也对 递归 进行了详细说明,说明的方式是通过两部分:

  1. 第一个就是什么叫递归,所谓递归指的就是: 直接或间接调用自身的函数
  2. 接下来是递归的场景: 递归把一个复杂问题分解为一组相似的子问题,每一个都用一个寻常解去解决,这是典型的美式语言。

4.138 闭包

接下来是闭包的逻辑。

这里作者对闭包进行了定义: 内部函数可以访问定义他们的外部函数的参数和变量 ,这样的函数就是闭包。

我针对于这句话加上我个人的理解,也总结了一句: 可以访问其它函数作用域中变量的函数 ,就是闭包。

大家可以看看哪个好理解,就理解哪一句就可以。

这里我列举了一段代码:

function a () {
  const name = '张三'
  return function b () {
    return name
  }
}
​
console.log(a()()) // 张三

在这段代码中, b函数 就是闭包函数

4.139 柯里化

作者专门讲解了 柯里化,他对柯里化是这样定义的,他说: 把函数与传递给它的参数相结合,产生一个新的函数。 这叫做柯里化。

这个定义和咱们的平常认知不同,在咱们的认知中,柯里化指的应该是: 柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)

4.1310 总结

那么到这里,关于函数这一章咱们就说完了。

从函数这一章中,其实咱们就可以明确的感受出来: 书中的内容具备一定的落后性(比如:块级作用域)  和 译文导致的一些描述的偏差(比如:柯里化)  ,但是 其中也有很多的精华(比如:返回值、递归、闭包)

4.14 继承

4.141 对象说明符

这里作者又提到了一个新的词汇:对象说明符。

并且列举出来了一个场景: 当一个函数接收多个参数时,记住参数的顺序非常困难

比如:

maker(f, l, m, c, s)

所以,与其这么写,倒不如这样写:

maker({
  first: f,
  middle: m,
  last: l,
  state, s,
  city: c
})

简单来说,也就是: 把多个参数,合并成一个用对象表示的参数

作者把这样的一种方式叫做: 对象说明符

4.15 数组

4.51 定义

这里作者对于数组的定义比较有意思,他拿传统数组和 JavaScript 数组 进行了对比:

  1. 针对于传统数组来说:数组是一段线性分配的内存,它通过整数计算偏移并访问其中的元素

  2. 但是 JavaScript 中的数组不同:

    1. 首先: JavaScript 中并不存在数组一样的数据结构
    2. JavaScript 只不过是提供了 类数组特性的对象

    咱们之前说过 js 中除了基本数据类型,其他的都是对象,这里就能够呼应上了。

4.152 长度

想要获取数组长度,通过 .length 属性获取

4.153 删除

想要删除数组的元素,书中提到了两种方式:

  1. 因为数组本质上是对象,所以依然可以使用 delete 关键字删除。但是这种删除方式,会导致原来的位置留下 “空洞”
  2. 而第二种方式,是咱们比较常用的,那就是通过 splice 进行删除

4.154 枚举

所谓的枚举,其实指的就是咱们常说的 遍历

而数组的遍历常用的方式主要是两种:

  1. forIn 循环,但是这样无法保证顺序
  2. 第二种是 普通的 for 循环,这也是咱们常用的方式

其实除了这两种方式之外,大家应该还能想到别的,比如: forEach 方法。

4.155 指定初始值

书中关于数组的最后一个小节讲解了 数组初始值 相关的一些东西,主要是有两点:

  1. 第一点就是: 数组没有预设值。通过 [] 定义数组后,会是一个空数组(本来难道不应该是这样吗?)
  2. 第二点就是:JavaScript 中本质上没有多维数组的概念,所谓的多维数组不过是 以数组为元素 产生的

4.16 毒瘤

作者把 JavaScript 中非常差劲的特性叫做 毒瘤。意思是: 不除不快

这里面涉及到的语法非常的多,咱们简单过一下即可:

  1. 全局变量:中大型项目中,全局变量可以被任意修改,会使得程序的行为变得极度复杂
  2. 作用域:无块级作用域
  3. 自动插入分号:不合时宜的自动插入
  4. 保留字:大量的保留字不可以被用作变量名
  5. unicodeunicode 把一对字符视为一个单一的字符,而JavaScript认为一对字符是两个不同的字符
  6. typeoftypeof 的返回总是很 “奇怪”
  7. parseInt:遇到非数字时会停止解析,而不是抛出一个错误
  8. +:既可以让数字相加,也可以链接字符串
  9. 浮点数:二进制的浮点数不能正确的处理十进制的小数
  10. NaNNaN 表示不是一个数字,同时它的一些运算也会让人感到奇怪
  11. 伪数组:JavaScript 中没有真正的数组,却又存在伪数组的概念
  12. 假值:0、NaN、''、false、null、undefined 在逻辑判断中都会被认为假
  13. hasOwnPropertyhasOwnProperty 是一个方法而不是运算符,所以在任何对象中,他可以被替换掉
  14. 对象:JavaScript 中的对象永远不会是真的空对象,因为它们可以从原型链中取得成员属性

4.17 糟粕

作者把一些不符合规范,但是可以使用的代码,叫做 糟粕

主要有:

  1. ==:不判断类型,所以尽量不要使用它
  2. with:改变一段语句的作用域链
  3. eval:传递字符串给 JavaScript 编译器,会使得代码更加难以阅读
  4. continue:作者发现移除 continue 之后,性能会得到改善
  5. switch 穿越:必须明确中断 case,否则会穿越到下一个
  6. 缺少块的语句
  7. ++ --:这两个运算符鼓励了一种不够谨慎的编程风格
  8. 位运算符:JavaScript 执行环境一般不接触硬件,所以执行非常慢
  9. function 语句对比 function 表达式:多种定义方式让人困惑
  10. 类型的包装对象:你应该从来没有使用过 new Boolean(false)。所以作者认为:这是完全没有必要,并且令人困惑的语法

五、正则

5.1 正则表达式定义:

正则表达式,又称正规表达式、常规表达式,使用字符串来描述、匹配一系列符合某个规则的字符串。

5.2 正则表达式组成

5.21 普通字符:

大小写字母、数字、标点符号及一些其他符号

5.22 元字符:

在正则表达式中具有特殊意义的专用字符

元字符 \:转义字符,!、\n等

^:匹配字符串开始的位置

例: ^a、^the、^#

$:匹配字符串结束的位置

例: word$

.:匹配除\n之外的任意的一个字符

例: go.d、g..d

*:匹配前面子表达式0次或者多次

例:goo*d、go.*d

[list]:匹配list列表中的一个字符

例: go[ola]d,[abc]、[a-z]、[a-z0-9]

[^list]:匹配任意不在list列表中的一个字符

例: [^a-z]、[^0-9]、[^A-Z0-9]

{n,m}:匹配前面的子表达式n到m次,有{n}、{n,}、{n,m}三种格式

例:go{2}d、go{2,3}d、go{2,}d

5.23 扩展元字符

   +:匹配前面子表达式1次以上

例: go+d,将匹配至少一个o

?:匹配前面子表达式0次或者1次

例: go?d,将匹配gd或god

():将括号中的字符串作为一个整体

例:(xyz)+,将匹配 xyz 整体1次以上,如xyzxyz

|:以或的方式匹配字条串

例1: good|food,将匹配good或者food

例2: g(oo|la)d,将匹配good或者glad

5.3 使用grep匹配正则

   Grep 【选项】 查找条件 目标文件

-w:表示精确匹配

-E :开启扩展(Extend)的正则表达式

-c : 计算找到'搜寻字符串'的次数

-i :忽略大小写的不同,所以大小写视为相同

-o :只显示被模式匹配到的宁符串

-v:反向选择,亦即显示出没有'搜寻字符串′内容的那一行! (反向查找,输出与查找条件不相符的行)--color=auto : 可以将找到的关键词部分加上颜色的显示喔!

-n :顺便输出行号

基础正则表达式常见元字符:

:转义符,将特殊字符进行转义,忽略其特殊意义a.b匹配a.b,但不能匹配ajb,.被转义为特殊意义\\

^:匹配行首,^则是匹配字符串的开始^tux匹配以tux开头的行^^^^

:匹配行尾,:匹配行尾,则是匹配字符串的结尾tux$匹配以tux结尾的行 .:匹配除了换行符 \r\n之外的任意单个字符

[list]:匹配list列表中的一个字符

例: go[ola]d,[abc]、[a-z]、[a-z0-9]

[^list]:匹配任意不在list列表中的一个字符

例: [^a-z]、[^0-9]、[^A-Z0-9]

{n,m}:匹配前面的子表达式n到m次,有{n}、{n,}、{n,m}三种格式

例:go{2}d、go{2,3}d、go{2,}d

六、 正则表达式语法大全

6.1、作用(正则表达式是一种字符串匹配的模式)

数据验证:比如电话号码、邮箱等

替换文本:快速找到特定文本,用于替换

快速提取字符串:基于匹配原则,快速从文档中查找或提取子字符串

6.2、语法(普通字符+特殊字符)

6.21 普通字符

[abc] 匹配[...]的所有字符

[^abc] 取反,除了[...]的其他字符

[A-Z] 区间字母A到Z

. 匹配除(\n换行符 \r 回车符)的任何单个字符

\s \S 匹配所有,\s所有空白符,包括换行 \S非空白符,不包括换行

\w 匹配字母、数字、下划线

6.22 特殊字符

结尾位置(匹配 结尾位置 (匹配字符----$)

^ 开始位置(匹配$字符----^)

() 子表达式开始和结束(匹配字符----( 和 \))

  • 匹配零次或多次(匹配字符----*)
  • 匹配一次或多次(匹配字符----+)

? 匹配零次或一次(匹配字符----?)

| 两项间的一个(匹配字符----|)

6.23 限定符

{n} n为非负整数,匹配n次

{n,} n为非负整数,至少n次

{n,m} n为非负整数,n<=m,最少n次,最多m次

6.24 修饰符

i 不区分大小写

g 全局匹配

m 多行匹配

s 特殊字符远点包含换行符

6.3、常用场景

  • 16进制颜色 /^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/ 以#开始 ,6或3个字符(A-F、a-f、0-9)结尾
  • 电话号码 /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/ 以1开头,3可跟任一数字(\d),4可跟5-9,5可跟0-3或5-9 ,6后2567其中一个,7后是0-8,8后任一数字,9后是0-3或3-5,其余8位是任意数字(\d{8})
  • 身份证号 /^[1-9]\d{5}(19|20|21)\d{2}(0[1-9]|10|11|12)(0[1-9]|[1-2]\d|30|31)\d{3}[\dX]$/ 第一位在0-9区间,后面是5位任意数字,4位年份(19、20、21开头,后两位任意),两位代表月份(0开头的1-9或者是10、11、12),两位日期(01-31),三位顺序码,最后一位是校验码,可数字可X
  • 网址 /^((https?):)?\/\/([^?:/]+)(:(\d+))?(\/[^?]*)?(\?(.*))?/
  • 邮箱 ^[A-Za-z0-9-_\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$ 名称有汉字([\ue00-\u9fa5])、字母([a-zA-z])、数字、下划线、中划线,域名有数字、字母、下划线、中划线

6.4、使用方法

字符串.match(正则):返回符合的字符串,若不满足返回null

字符串.search(正则):返回搜索到的位置,若非一个字符,则返回第一个字母的下标,若不匹配则返回-1

字符串.replace(正则,新的字符串):找到符合正则的内容并替换

正则.test(字符串):在字符串中查找符合正则的内容,满足则返回true,反之为false

6.5 补充

6.51 正则表达式验证Ip地址

iPv4的ip地址都是(1255).(0255).(0255).(0255)的格式

这里的写法没有考虑两种情况,一是172.00.01.3这种,前面以零开头,或者多个零的这种情况,二是本网络地址,如果想支持,可根据下面的解释进行扩展:

"^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\."

+"(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\."

+"(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\."

+"(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)$"

上面的一个不漏就是正确的验证ip的正则表达式,简单的讲解一下

\d表示0~9的任何一个数字

{2}表示正好出现两次

[0-4]表示0~4的任何一个数字

| 的意思是或者

( )上面的括号不能少,是为了提取匹配的字符串,表达式中有几个()就表示有几个相应的匹配字符串

1\d{2}的意思就是100~199之间的任意一个数字

2[0-4]\d的意思是200~249之间的任意一个数字

25[0-5]的意思是250~255之间的任意一个数字

[1-9]\d的意思是10~99之间的任意一个数字

[1-9])的意思是1~9之间的任意一个数字

\.的意思是.点要转义(特殊字符类似,@都要加\转义)

七、 安装依赖时总是碰到 If you are behind a proxy, please make sure that thenpm ERR! network ‘proxy‘ config is set

解决方案:1

解决办法:
 
1、执行:
 
npm config get proxy
npm config get https-proxy
如果返回值不为null,继续执行:
(这一步很重要,一定要保证两个命令的返回值都为null,话说回来,应该出现这个错误这两个返回值有不为null的)
npm config set proxy null
npm config set https-proxy null
2、执行:
npm config set registry http://registry.cnpmjs.org/
可以使用淘宝镜像安装
npm install -g cnpm --registry=https://registry.npm.taobao.org 

解决方案2

npm config set proxy false
 
npm cache verify

八、 export ‘Switch‘ (imported as ‘Switch‘) was not found in ‘react-router-dom‘

一跟着网上做react项目时,代码中以下代码:

import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'

出现了以下几个报错:

export 'Switch' (imported as 'Switch') was not found in 'react-router-dom'

export 'Redirect' (imported as 'Redirect') was not found in 'react-router-dom'

export 'withRouter' (imported as 'withRouter') was not found in 'react-router-dom'

原因:这些报错原因均为'Switch' 和'Redirect' 是react-router 5版本的接口,而最新版本是

"react-router-dom": "^6.2.1",并且已经将Switch改为Routes。

解决办法有二:

1.将所有 Switch 改为 RoutesRedirect 改为 NavigatewithRouter改为 useNavigate

(vscode中,按快捷键:CTRL+F及可查找替换)

2.卸载新版本,再安装5的版本

npm uninstall react-router-dom
npm install react-router-dom@5

九、 Matched leaf route at location “/“ does not have an element. This means it will render an <Outlet />

使用react-router-dom配置路由的时候,开发者工具中会警告

Matched leaf route at location "/register" does not have an element. This means it will render an <Outlet /> with a null value by default resulting in an "empty" page. 

代码:

import {BrowserRouter as Router, Route, Routes} from 'react-router-dom';
import routers from './../routers';

function Main() {
  return (
    <Router>
      <Routes>
        {routers.map((item, index) => {
          return (
            <Route
              key={index}
              exact
              path={item.path}
              component={<item.component/>}
            />
          );
        })}
      </Routes>
    </Router>
  );
}

export default Main;

错误原因: react-router-dom6.x版本中,不再通过component指定组件,通过element进行配置。因此将<Route>中的component改为element即可:

import {BrowserRouter as Router, Route, Routes} from 'react-router-dom';
import routers from './../routers';

function Main() {
  return (
    <Router>
      <Routes>
        {routers.map((item, index) => {
          return (
            <Route
              key={index}
              exact
              path={item.path}
              element={<item.component/>}
            />
          );
        })}
      </Routes>
    </Router>
  );
}

export default Main;

十、 Vue 3.3 主要新特性详解

10.1 Vue 3.3 的这次发布主要是为了改进 DX (开发者体验),新增了一些语法糖和宏,以及 TypeScript 上的改善。

  • 泛型组件
  • 在 SFC(单文件组件)中导入外部 TS 类型
  • defineSlots 来定义插槽的类型
  • defineEmits 更便捷的语法
  • defineOptions 定义组件选项
  • (试验性) 响应式的 props 解构
  • (试验性) defineModel 语法糖
  • 废弃 Reactivity Transform

10.2 defineOptions 宏

10.21 现状

所以我们在 Vue 3.3 中新引入了 defineOptions 宏。顾名思义,主要是用来定义 Options API 的选项。我们可以用 defineOptions 定义任意的选项, props, emits, expose, slots 除外(因为这些可以使用 defineXXX 来做到)。我们甚至可以不用 <template> 标签,而直接在 defineOptions 中用 h 函数或 JSX 写渲染函数 render(当然并不推荐这样用)。

10.22 🌰 例子

<script setup>
defineOptions({
  name: 'Foo',
  inheritAttrs: false,
  // ... 更多自定义属性
})
</script>

10.3 提升静态常量

这个功能是一个 SFC (单文件组件)编译器的优化。在 script 部分新增了 hoistStatic 选项。

10.31 模板中的 hoistStatic

template 下的 hoistStatic 与它基本类似。Vue 编译器有一个优化——它能够让静态的元素节点提升到顶级作用域。这样仅在代码被加载的时候执行一次,而不是每次执行 render 函数的时候被重复执行(在极端情况下可能也有其弊端)。

例子 🌰:

<template>
  <div id="title">Hello World</div>
</template>

Vue SFC Playground

以上这段代码会被编译成以下这段 JavaScript (非关键代码已省略)

const _hoisted_1 = { id: 'title' }
function render(_ctx, _cache) {
  return _openBlock(), _createElementBlock('div', _hoisted_1, 'Hello World')
}

我们可以看到 _hoisted_1 变量就是被编译器故意提升到顶层的代码。如果关闭此功能,则它会在 render 函数内。

10.32 script 标签的 hoistStatic

在 Vue 3.3 之前,只有上述一个功能。我们在 Vue 3.3 也做了类似的优化——如果一个常量的值是 primitive value(基本类型,即为 string, number, boolean, bigint, symbol, null, undefined),那么该常量声明会被提升到顶层。(注:每次定义的 symbol 都不相等,而 undefined 可能会被覆盖,所以都不会被提升)

因为这些类型的常量是无法被改变的,所以它不论在哪被声明,效果都是一样的。

10.33 作用

这个特性除了性能优化之外,还有一个更有用的地方。在 Vue 3.3 之前我们在使用宏时,是没有办法传递定义在 <script setup> 块的变量的。看看下面的例子。

<script setup>
const name = 'Foo'
defineOptions({
  name,
})
</script>

Vue SFC Playground

我们会得到一个报错。

[@vue/compiler-sfc] `defineOptions()` in <script setup> cannot reference locally declared variables because it will be hoisted outside of the setup() function. If your component options require initialization in the module scope, use a separate normal <script> to export the options instead.

这是因为之前提到的原因,defineProps 会添加一个与 setup 平级的 props 属性,而 name 常量又是声明在 setup 函数内的。我们无法在函数外部引用一个在其内部的变量,这是因为该变量压根还没有被初始化。以下代码毫无疑问是错误的。

const __sfc__ = {
  props: [propName],
  setup(__props) {
    const propName = 'foo'
  },
}

在 Vue 3.3 之后,第 4 行的 propName 就会提升到第一行,那么代码就完全说得通了。本特性默认是开启的。在一般情况下,开发者也无需感知该特性是存在的。

10.4 defineModel 宏

10.41 动机

这个宏是一个纯粹的语法糖。在 Vue 3.3 之前,如果我们要定义一个双向绑定的 prop,是一件挺麻烦的事情。

<script setup lang="ts">
const props = defineProps<{
  modelValue: number
}>()

const emit = defineEmits<{
  (evt: 'update:modelValue', value: number): void
}>()

// 更新值
emit('update:modelValue', props.modelValue + 1)
</script>

我们需要先定义 props,再定义 emits 。其中有许多重复的代码。如果需要修改此值,还需要手动调用 emit 函数。

我在想:我们为什么不封装一个函数(宏)来简化这个步骤呢?因此就有了 defineModel 宏。

10.42 🌰 例子

<script setup>
const modelValue = defineModel()
modelValue.value++
</script>

以上繁琐的 7 行代码,在 Vue 3.3 中可以简写为两行!

10.43 与 useVModel 的区别

在 VueUse 中也有 useVModel 函数可以实现类似的效果。为什么我们还要引入这个宏呢?

这是因为 VueUse 只是把 propsemit 函数结合后,封装为了一个 Ref,它无法为组件定义 propsemits。这意味着开发者仍需手动分别调用 definePropsdefineEmits

10.5 defineSlots 宏

10.51 背景

Vue 3.3 新增了 defineSlots 宏。我们可以使用 defineSlots 自己定义插槽的类型。这个宏在简单的组件中几乎用不到,但对于一些复杂的组件非常有用,尤其是这个特性与泛型组件一起使用。或是在 Volar 无法正确地推测出类型时,我们可以手动指定。

10.52 🌰 例子

<script setup lang="ts">
const slots = defineSlots<{
  default(props: { foo: string; bar: number }): any
}>()
</script>

我们手动定义了 default 组件的插槽作用域的类型。

10.53 🌰 示例

比如我们有一个分页器组件,我们可以通过插槽来控制具体的 item 要如何渲染。

<script setup lang="ts" generic="T">
// 子组件 Paginator
defineProps<{
  data: T[]
}>()

defineSlots<{
  default(props: { item: T }): any
}>()
</script>
<template>
  <!-- 父组件 -->
  <Paginator :data="[1, 2, 3]">
    <template #default="{ item }">{{ item }}</template>
  </Paginator>
</template>

我们在传递了 data 参数,它是 number[],所以 item 也会被推断为 numberitem 的类型会根据传给 data 参数的类型而改变。

10.6 defineEmits 更便捷的语法

这个特性也是一个纯粹的语法糖。

10.61 例子

<script setup lang="ts">
const emits = defineEmits<{
  (evt: 'update:modelValue', value: string): void
  (evt: 'change'): void
}>()

// ⬇️ Vue 3.3 之后
const emits = defineEmits<{
  'update:modelValue': [value: string],
  'change': []
}>()
</script>

在 Vue 3.3 以前我们需要多写几个字符,现在能省则省。