第五章 标准库

69 阅读23分钟

Object

静态方法

  • keys(obj)

    返回obj自身的所有可枚举属性的属性名组成的字符串数组

  • values(obj)

    返回obj自身的所有可枚举属性的值组成的数组

  • entries(obj)

    返回obj自身的所有可枚举属性的[key, value]格式的元素组成的数组

实例方法

  • toString()

    返回对象的字符串格式,如:[object Object]

  • valueOf()

    返回this

    当引用值a(可以是普通对象、数组或包装类对象等)与原始值b进行运算时,需要先将引用值转换为原始值,其在背后转换的过程如下:

    • a.valueOf不是一个函数时,直接调用a.toString()方法得到原始值

    • a.valueOf()返回的是引用值时,则调用a.toString()方法得到原始值

    • a.valueOf()返回的是原始值时,则直接将该返回值作为转换后的结果

    • a.valueOf()a.toString()得到的都不是原始值,则直接报错

    • 包装类的prototype中有重写过后的valueOf()方法,这些方法返回的是包装类对象中的原始值内容,而不是this

      new String(123).valueOf()得到的是"123"new Boolean(1).valueOf()得到的是true

Function

实例方法

  • call(ctx)

    调用函数,同时改变函数内部的this为ctx

    函数调用时所需的其他参数在ctx之后依次传入

  • apply(ctx)

    调用函数,同时改变函数内部的this为ctx

    函数调用时所需的其他参数要封装成一个数组(也可以是伪数组),然后作为apply的第二个参数传入

    利用call或apply方法,可以利用伪数组得到真数组

    var 真数组 = Array.prototype.slice.call(伪数组);
    
  • bind(ctx)

    返回一个新函数,新函数内部的this固定为ctx

    该函数除使用new的方式调用外,其它任何方式调用都不会内部的this

    原函数所需的其他参数在ctx后依次传入

    var user = {
        name: "ZSF"
    };
    
    function fn(a, b){
        console.log(this.name);
        console.log(a, b);
    }
    
    var newFn = fn.bind(user, 1);
    
    newFn(2);		// "ZSF"	1 2
    

注意:

  • 如果call、apply或bind中传入的是null或undefined,则函数中的this在非严格模式下指向全局对象,在严格模式下为undefined

    function test(){
        // "use strict";
        console.log(this);
    }
    
    test.call();
    test.apply();
    test.bind()();
    // window {...} × 3 或 undefined × 3
    
  • 如果call、apply或bind中传入的是其他原始值,则函数中的this在非严格模式下指向原始值的包装类对象,在严格模式下为该原始值

    function test(){
        // "use strict"
        console.log(this);			// Number {5}
    }
    
    test.call(5);
    test.apply(5);
    test.bind(5)();
    // Number {5} × 3 或 5
    

手写call

Function.prototype.myCall = function (context = window, ...args) {
    if (typeof(this) !== "function") {
        throw new TypeError("type error");
    }
    var temp_prop = Symbol();
    context[temp_prop] = this;					// 使用符号避免与已有属性重名
    var res = context[temp_prop](...args);
    delete context[temp_prop];
    return res;
};

手写apply

Function.prototype.myApply = function (context = window, args = []) {
    if (typeof(this) !== "function") {
        throw new TypeError("type error");
    }
    var temp_prop = Symbol("temp");
    context[temp_prop] = this;
    var res = context[temp_prop](...args);
    delete context[temp_prop];
    return res;
};

手写bind

Function.prototype.myBind = function (context = window, ...args) {
    if (typeof(this) !== "function") {
        throw new TypeError("type error");
    }
    var that = this;
    return function (...subArgs) {			// 不能返回箭头函数,因为它可能会作为构造函数
        var totalArgs = [...args, ...subArgs];
        if (new.target) {
            // 函数如果是使用new来调用的
            return that.apply(this, totalArgs);
        } else {
            // 其它调用方式
            return that.apply(context, totalArgs);
        }
    }
};

Array

静态成员

  • from(伪数组)

    根据伪数组得到一个真数组

  • isArray(obj)

    判断一个obj是否是真数组

  • of()

    该方法可以传入0 ~ n个参数,每个参数都会作为一个元素加入到数组中,然后返回该数组

实例成员

  • reverse()

    颠倒数组中元素的顺序

  • sort([callback])

    对数组元素进行排序

    内部的默认排序规则是将元素转换为字符串,然后按照字符串的比较规则对数组元素进行升序排序

    打乱数组中元素的排序:

    arr.sort(function(){
        return Math.random() - 0.5;
    });
    

    以上方法会改变原数组

    以下方法不改变原数组

  • includes(item)

    判断数组中是否包含item,内部使用Object.is进行比较

  • forEach(callback)

    遍历数组,返回值为undefined

    每遍历到一个元素,都会调用一次callback,并把元素, 下标, 数组作为实参传入

    不会对empty进行遍历

  • every(callback)

    判断数组中的元素是否都满足条件,返回类型为boolean

    不会对empty进行遍历

  • some(callback)

    判断数组中是否有满足条件的元素,返回类型为boolean

    不会对empty进行遍历

  • filter(callback)

    返回一个新数组,新数组中包含原数组中所有满足条件的元素

    不会对empty进行遍历

  • find(callback)

    返回原数组中第一个满足条件的元素,没有找到则返回undefined

    会对empty进行遍历

  • findIndex(callback)

    返回原数组中第一个满足条件的元素的索引,没有找到则返回-1

    会对empty进行遍历

  • map(callback)

    返回一个新数组,新数组中的每一个元素都是原数组中元素映射后的结果

    不会对empty进行遍历

  • reduce(callback[, initialValue])

    第一次遍历时会把initialValue(如果传入了initialValue)或数组的第一项元素(如果没传入initialValue)传入到回调函数中使用,并把数组中的第一项或下一项元素作为第二个参数传入,之后都会上一次回调的返回值作为下一次回调的第一个参数,第二个参数是数组的下一项元素,直至将数组元素全部遍历完成

    若不传入initValue,并且数组的元素个数为0,该函数会报错

    不会对empty进行遍历

    手写reduce:

    Array.prototype.myReduce = function(callback, initialValue) {
        var length = this.length;			// 获取参数的数量
        var lastRes;
        var startIndex = 0;
        if (arguments.length > 1) {			// 有传入initialValue
            lastRes = initialValue;
        } else {
            if (length === 0) {
                throw new TypeError('Reduce of empty array with no initial value');
            }
            lastRes = this[0];
            startIndex = 1;
        }
        for (let i = startIndex; i < length; i++) {
            lastRes = callback(lastRes, this[i], i, this);
        }
        return lastRes;
    };
    

原始类型包装器

new 包装器(value):返回一个引用值

包装器(value):返回一个原始值

new Number(1);		// 得到Number{1}
Number(1);			// 得到1

Number(value)value转换为数字的原理:

  • value为原始值:按照算术运算中原始值转数字的规则将value转换为数字
  • value为引用值:按照算术运算中引用值转原始值的规则将value转为原始值,再将非数字类型的原始值转换为数字

Boolean(value)value转换为布尔值的原理:布尔判定为假的转为false外,否则转为true

String(value)value转换为字符串的原理:

  • value为原始值:直接给value套上引号

  • value为引用值:调用value.toString()

    value.toString()得到的是字符串,则转换结果就是该字符串

    value.toString()得到的是原始值,但不是字符串,则将该原始值转换为字符串(直接套上引号)

    value.toString()得到的不是原始值,则调用value.valueOf()尝试得到原始值,若value.valueOf()能得到原始值,就将该原始值转换为字符串,否则直接报错

字符串与引用值进行+操作时,并不是直接给引用值外套上String函数让其变为字符串,而是走正常的valueOf() → toString()流程进行转换

var obj = {
    toString() {
        return false;
    },
    valueOf() {
        return true;
    }
};

console.log("1" + obj);				// "1true"

console.log("1" + String(obj));		// "1false"

new Number(value)就相等于把Number(value)的返回值包装成Number对象的结果

new Boolean(value)就相等于把Boolean(value)的返回值包装成Boolean对象的结果

new String(value)就相等于把String(value)的返回值包装成String对象的结果

注意:

  • 使用数字字面量访问属性时,如123.xxx,数字字面量会被解析为以数字开头的标识符,而不会把数字字面量包装为Number对象,所以该语句会报语法错误

    想让数字字面量不被解析为标识符,只需要给其加上括号即可,如(123).xxx

    NaNInfinity不会被解释为标识符,可以直接使用属性访问操作符访问成员

  • boolean以及string字面量可以直接使用属性访问操作符访问属性,这些字面量会自动被包装为对象

Number

静态方法:

  • isNaN()

    判断传入的是不是NaN,全局对象中的isNaN实际上就是Number.isNaN

  • isInfinite()

    判断传入的是不是无穷,全局对象中的isInfinite实际上就是Number.isInfinite

  • isInteger()

    判断一个数是否是整数

  • toFixed(n)

    返回原数字的保留小数点后n位的浮点数的字符串形式,保留时会进行四舍五入

  • parseInt(str, n)

    将字符串看作n进制数,然后转换为十进制整数并返回,n默认为10

    (数字).toString(n):将数字转换为以n进制表示的字符串并返回

  • parseFloat(str)

    将字符串转换为小数

parseInt和parseFloat要求参数是一个字符串,若不是字符串则先转换为字符串,再转换为整数

parseInt和parseFloat将str转为数字的规则与Number(str)不同,它们的转换规则为:

  • 忽略字符串开始和末尾的连续空白字符,并得到忽略后的新字符串

  • 对于parseInt:若新字符串第一位不是数字字符,则直接返回NaN,否则继续查找有效字符

    对于parseFloat:若新字符串第一位不是数字字符或小数点字符,则直接返回NaN,否则继续查找有效字符

  • 向右寻找直到找到第一个不满足格式的字符,停止查找,并返回前面字符串的数字形式

举例:

parseInt("1");			// 1
parseFloat("3.14");		// 3.14

parseInt("-1");			// -1
parseFloat("-3.14");	// -3.14

parseInt(" 1 ");		// 1
parseFloat(" 3.14 ");	// 3.14

parseInt("a1");			// NaN
parseFloat("a3.14");	// NaN

parseInt("");			// NaN
parseFloat("");			// NaN

parseInt(".14");		// NaN
parseFloat(".14");		// 0.14

parseInt("1.14abc");	// 1
parseFloat("3.14abc");	// 3.14

细节:

  1. 若传入的第二个参数是0,则转换时还是会将字符串看作是十进制数

    console.log(parseInt("159", 0));		// 159
    
  2. 若传入的第二个参数与字符串所表示的进制不对应,则会返回NaN

    console.log(parseInt("9", 2));			// NaN
    

String

String对象是一种伪数组,它具有length属性,也可以通过下标的形式访问到对象中的某个字符元素

字符串原始值使用下标获取字符以及使用length获取长度时,其实使用的是将原始值包装后的String对象中属性

String对象比较特殊,其默认包含的属性,除__proto__外都是只读的,无法修改

静态成员:

  • fromCharCode(Unicode, Unicode, ...)

    通过一系列Unicode码创建一个字符串

  • prototype.length

    返回字符串的长度

实例成员:

  • charAt(n)

    返回字符串中下标为n的字符,若n大于等于字符串长度,则返回空字符

    var str = "123";
    str[0];				// "1"
    str.charAt(0);		// "1"
    
    str[5];				// undefined
    str.charAt(5);		// ""
    
  • charCodeAt(index)

    返回字符串中下标为index的字符的Unicode码,若n大于等于字符串长度,则返回NaN;index默认为0

  • concat(str1, str2, ...)

    返回多个字符串拼接后的新字符串

  • includes(str)

    判断字符串中是否包含指定的子串str

  • endsWith(str)

    判断字符串中是否以str为结尾,当参数str为空串时,该函数始终返回true

  • startsWith(str)

    判断字符串中是否以str为开头,当参数str为空串时,该函数始终返回true

  • indexOf(str)

    返回字符串中第一次出现子串str的索引,没找到返回-1

  • lastIndexOf(str)

    返回字符串中最后一次出现子串str的索引,没找到返回-1

  • padStart(len, str)

    在字符串开始处填充str直至字符串长度等于len

  • padEnd(len, str)

    在字符串末尾处填充str直至字符串长度等于len

  • repeat(n)

    将字符串重复n次并返回

  • slice(start, end)

    返回从start下标开始,到end下标结束,但不包含end的子串

    start默认为0,end默认为字符串的长度

    start和end可以为负数,表示倒数第几个位置

  • substr(start, len)

    返回从start下标开始,长度为len的子串

    start可以为负数,表示倒数第几个位置

  • substring(start, end)

    返回从start下标开始,到end下标结束,但不包含end的子串

    start和end不能为负数,如果是负数则用0替代

    如果start大于end,则会将两者的值进行互换后再截取

  • toUpperCase()

    转换为大写

  • toLowerCase()

    转换为小写

  • trim()

    去除首尾空格

  • split(str)

    分割字符串,返回一个数组,数组中包含分割后的结果

Math对象

  • random():产生一个[0, 1)之间的随机数
  • PI:圆周率
  • abs(x):求绝对值
  • floor(x):向下取整
  • ceil(x):向上取整
  • max(...):返回0个到多个数值中的最大值
  • min(...):返回0个到多个数值中的最小值
  • pow(x, y):求x的y次幂
  • round(x):返回四舍五入后的整数

生成随机数

生成[min, max]范围内的随机数

function getRandom(min, max){
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

Date

时间戳

时间戳为0时区的1970年1月1日凌晨0点0分0秒到0时区的某个具体时间所经过的毫秒数

时间戳一定是基于0时区的时间的

创建Date对象

使用new Date()创建Date对象

  • 不传参数:得到当前时间的Date对象

  • 传入一个数字参数:会把参数当做时间戳,基于时间戳得到Date对象

    var date = new Date(1000);
    // date在0时区中为1970年1月1日0时0分1秒,在东8时区中为1970年1月1日8时0分1秒
    
  • 传入两个或多个数字参数:会把这些参数依次作为年、月、日、时、分、秒、毫秒,并以此得到Date对象

    如果参数不足,则日期默认为1,时分秒默认为0

    月份对应的参数应该从0开始,到11为止

    传入的年月日时分秒都会作为本地时间(当前时区的时间)的年月日时分秒

    var date = new Date(2024, 0, 1, 0, 0, 0);
    // 假设用户在东8区调用该函数,则date为2024年1月1日0时0分0秒,转为0时区的时间为2023年12月31日16时0分0秒
    

注意:若new Date()中传入的月日时分秒允许超出正常范围,但如果超出正常返回JS会自动帮你重新计算

new Date(2024, 0, 32);		// 1月没有第32天,因此这里会转变为2月的第1天
new Date(2024, 0, 0);		// 天对应的参数最小为1,传入0则表示在1的基础上后退1天,于是变为了2023年12月31号

静态方法

  • Date.now():得到当前时间的时间戳

实例成员

  • getDate():得到指定日期为一个月中的第几天(1 ~ 31)
  • getDay():得到指定日期为一周中的第几天(0 ~ 6,0表示周日)
  • getFullYear():得到指定日期的年份
  • getMonth():得到指定日期的月份(0 ~ 11)
  • getHours():得到指定日期的小时(0 ~ 23)
  • getMinutes():得到指定日期的分钟(0 ~ 59)
  • getSeconds():得到指定日期的秒(0 ~ 59)
  • getMilliseconds():得到指定日期的毫秒(0 ~ 999)
  • getTime():得到指定日期的时间戳

注意:除getTime()得到的时间戳固定基于0时区外,上面的其它方法得到的都是基于当前时区得到的信息,要想获得当前时区的时间在0时区中的表示,则可以使用getUCTxxx()

var date = new Date(2024, 0, 1, 0, 0, 0);
// 假设用户在东8区调用该函数

date.getFullYear();			// 2024
date.getUTCFullYear();		// 2023

date.getDate();				// 1
date.getUTCDate();			// 31

// ...
  • setDate()

    setUTCDate()

  • setMonth()

    setUTCMonth()

  • setFullYear()

    setUTCFullYear()

  • ...

  • setTime()

日期的运算

日期对象可以进行运算

日期对象重写了valueOf方法,它返回的是日期对象对应的时间戳

日期对象和其他引用值的运算基本相同,唯一的区别是当日期对象进行的是加法运算时,会直接使用toString方法将日期对象转换为字符串,然后进行字符串加法

若将它的toString方法更改为返回引用值,则它会尝试使用valueOf()将date对象转换为原始值,若还转换不了就报错

var date = new Date();

date.valueOf = function(){
    return 2;
}

date.toString = function(){
    return 3;
}

console.log(date + 3);		// 6

正则表达式

正则表达式是一个规则,用于验证字符串

JS中的正则表达式就是一个对象

创建正则表达式

字面量:

/规则/

使用构造函数:

new RegExp("规则字符串");

字面量的形式是语法糖,最终还是需要通过构造函数来创建

直接使用构造函数:

RegExp("规则字符串");

第二种方式和第三种方式都允许传入其它正则表达式

var reg1 = /规则/;

var reg2 = new RegExp(reg1);
var reg3 = RegExp(reg1);

第二种方式与第三种方式的区别在于:当把其他正则表达式作为参数传入时,第二种方式会得到全新的正则表达式,而第三种方式是直接返回参数本身

reg1 === reg2		// false
reg1 === reg3		// true

标记

  • i:忽略大小写

  • m:多行匹配

    是否开启多行匹配会影响起始符号^和结束符号$的匹配规则

    不开启多行匹配时,^$表示匹配字符串的开始和结束

    开启多行匹配时,^$表示匹配字符串中每一行子串的行开始和行结束

  • g:全局匹配

    若不开启全局匹配,则匹配到第一处就不在继续向后匹配

    开启全局匹配后,匹配的起点将会受reg.lastIndex的影响,lastIndex初始为0,可以手动更改reg.lastIndex来控制匹配的起点

    开启全局匹配后,每匹配完一个项目,reg.lastIndex都会移动到该项目的下一个下标位置,并且下次的匹配将会从该位置开始向后匹配(保证已匹配的项目不再参与后续匹配),当lastIndex指向字符串的末尾时,lastIndex就会清零

标记的书写位置:

/规则/img;

new RegExp("规则字符串", "img");

规则

匹配单字符

字面量字符:

  • 直接书写一个字符:匹配该字符

特殊字符:

  • .:匹配除\r\n外的所有字符

  • ^:匹配开始,包括字符串的开始和行的开始

    ^出现在[]内表示非,出现在正则表达式的最外部或()内表示开始

  • $:匹配结束,包括字符串的结束和行的结束

    $出现在正则表达式的最外部或()内表示结束,出现在[]内表示普通的$字符

转义符转义:

  • \n:匹配换行符

  • \r:匹配回车符

  • \t:匹配制表符

  • \d:匹配数字,等价于[0-9]

  • \D:匹配非数字,等价于[^\d]

  • \s:匹配任意空白字符,包括空格、回车、换行、制表等,但不包括空字符""

  • \S:匹配任意非空白字符,等价于[^\s]

  • \w:匹配单词字符,包括数字、字母和下划线,等价于[A-Za-z0-9_]

  • \W:匹配非单词字符,等价于[^\w]

  • \b:匹配单词边界,单词边界是指该侧是字符串开始或行开始或空白字符或字符串结束或行结束

  • \B:匹配非单词边界

  • \u十六进制的Unicode码

    匹配该Unicode码对应的字符

    匹配所有中文字符(不包括生僻字):[\u4e00-\u9fa5]

  • \\:匹配\

  • \^:匹配^

  • \$:匹配$

  • [字符范围]

    匹配该范围内的字符

    []内部,^将解释为【非】,-将解释为【到】

    [A-Za-z]:匹配a到z,或A到Z中的任意字符

    [^A-Za-z]:匹配除a到z,以及A到Z外的任意字符

    匹配所有字符:[\w\W][\s\S]...

    注意:正则表达式中的某些特殊字符在[]中将会失去原本表示的含义,包括起始标记^、结束标记$、单词边界\b、非单词边界\B、或|、捕获组()、特殊字符.

上面的规则仅会匹配一个字符,可以将多个规则重复或组合起来以匹配多个连续的字符,或者使用量词

量词

量词需要紧跟在规则或捕获组之后,表示匹配前面的规则或捕获组的个数

  • *:匹配零个或多个

  • +:匹配一个或多个

  • ?:匹配零个或一个

    注意:正则表达式中?具有很多种含义,具体含义要结合?出现的位置进行判断

  • {n}:匹配n个

  • {n,}:匹配大于等于n个

  • {n,m}:匹配n到m个

注意:正则表达式默认使用的是贪婪匹配模式,即只要能匹配多,就不匹配少,比如

var reg = /\d{1,3}/;

var str = "123";

str.match(reg);
// 匹配时既能匹配【1】,也能匹配【12】,还能匹配【123】,但由于是贪婪匹配,即会往多的进行匹配,因此会匹配【123】

可以在量词后面加上?,表示对量词实施非贪婪匹配,即只要能匹配少,就不匹配多

/\d{1,3}?/

或者

  • |:匹配左侧的组合规则或右侧的组合规则

细节:

  • |的作用效果在其所处的最近的//()

    如:/abc|123/表示匹配abc123,而不是匹配abc23ab123,要想匹配后者,需要使用/ab(c|1)23/

  • |[]表示普通的|字符,没有或者的含义

  • 具体匹配过程:先看|左边是否能够匹配,能匹配成功的话就不看右边的,如果不匹配则才看|右边

    var str = "11212112";
    
    console.log(str.match(/1|12/g));		// ["1", "1", "1", "1", "1"]
    console.log(str.match(/12|1/g));		// ["1", "12", "12", "1", "12"]
    

正则表达式的实例成员

  • global:是否开启了全局匹配

  • multiline:是否开启了多行匹配

  • ignoreCase:是否开启了忽略大小写

  • source:得到规则的字符串形式

  • lastIndex:得到下一次匹配的起始索引

    该属性只有在开启全局匹配后才生效

  • test(str)

    判断str是否满足规则

    当不开启全局匹配时,每次test都会从头开始匹配

    当开启全局匹配时,test会从reg.lastIndex的下标位置开始匹配,当匹配成功时,会将lastIndex修改为匹配成功的结果的下一位下标;当下标到达了字符串的长度时,lastIndex会自动清零并返回false

    var reg = /abc/g;
    
    var str = "abc123abc";
    
    console.log(reg.lastIndex, reg.test(str), reg.lastIndex);		// 0 true 3
    console.log(reg.lastIndex, reg.test(str), reg.lastIndex);		// 3 true 9
    console.log(reg.lastIndex, reg.test(str), reg.lastIndex);		// 9 false 0
    console.log(reg.lastIndex, reg.test(str), reg.lastIndex);		// 0 true 3
    console.log(reg.lastIndex, reg.test(str), reg.lastIndex);		// 3 true 9
    console.log(reg.lastIndex, reg.test(str), reg.lastIndex);		// 9 false 0
    ...
    
  • exec(str)

    返回匹配的结果,若匹配成功将返回一个数组,否则返回null

    当不开启全局匹配时,每次exec都会从头开始匹配

    当开启全局匹配时,exec会从reg.lastIndex的下标位置开始匹配,当匹配成功时,会将lastIndex修改为匹配成功的结果的下一位下标并返回一个包含匹配结果信息的数组;当下标到达了字符串的长度时,会将lastIndex清零并返回null

    var reg = /abc/g;
    
    var str = "abc123abc";
    
    console.log(reg.exec(str));
    // ['abc', index: 0, input: 'abc123abc', groups: undefined]
    console.log(reg.exec(str));
    // ['abc', index: 6, input: 'abc123abc', groups: undefined]
    console.log(reg.exec(str));
    // null
    console.log(reg.exec(str));
    // ['abc', index: 0, input: 'abc123abc', groups: undefined]
    console.log(reg.exec(str));
    // ['abc', index: 6, input: 'abc123abc', groups: undefined]
    ...
    

    exec每次至多匹配出一个项目,开启全局匹配并不代表exec会将所有匹配结果一次性匹配出来,它只是影响exec本次匹配的起始位置

与正则表达式有关的字符串方法

  • match(reg)

    返回匹配的结果,若匹配成功将返回一个数组,否则返回null

    开启全局匹配后,会将所有匹配成功的结果存放到数组中(与reg.exec的区别在于此)

    没有开启全局匹配时,返回结果同reg.exec(str)

  • search(reg)

    返回第一处匹配项的下标

    始终从头开始匹配,不受全局匹配影响

  • split(reg)

    按照指定规则对字符串进行分割

    不管reg是否开启全局匹配,split都会将字符串中的所有匹配项都会做为分割点来分割字符串

  • replace(reg, str|func)

    返回一个新字符串,新字符串中满足匹配条件的项目会被替换为str或func函数执行的结果

    在没有全局匹配的情况下,只会替换第一个匹配的内容

    当第二个参数为函数时,每匹配到一个项目,都会调用一次func,调用时会将匹配的结果作为实参传入

    replace的第一个参数可以是字符串,但如果是字符串,函数内部会将其转换为正则表达式

    str.replace("...", "...");
    // 其实是
    str.replace(new RegExp("..."), "...");
    

    因此,当第一个参数是字符串时,只会对str的第一处匹配项进行替换(因为默认没有开启全局匹配)

捕获组

()包裹的部分叫做捕获组,捕获组让多个规则组合为了一个整体

与exec方法

捕获组的匹配结果会出现在reg.exec()方法的返回结果中

对于该方法,若在本次匹配中匹配成功,则匹配的项目会成为数组中下标为0的元素,若正则表达式中存在捕获组,则第i个捕获组的匹配到的结果会成为数组中下标为i的元素

var reg = /([a-z]+)([0-9]+)/g;

var str = "a123b1234cd12345";

console.log(reg.exec(str));		// [0: "a123", 1: "a", 2: "123"]
console.log(reg.exec(str));		// [0: "b1234", 1: "b", 2: "1234"]
console.log(reg.exec(str));		// [0: "cd12345", 1: "cd", 2: "12345"]
console.log(reg.exec(str));		// null
console.log(reg.exec(str));		// [0: "a123", 1: "a", 2: "123"]

利用捕获组的特点可以很轻松地实现日期格式化:

var reg = /(\d{4})-(\d{1,2})-(\d{1,2})/g;

var str = "2020-1-18, 2023-6-25, 2024-12-1";

var result;
while(result = reg.exec(str)){
    console.log(`${result[1]}${result[2]}${result[3]}日`);
}

捕获组的顺序与捕获组的左括号出现顺序相同,左括号越先出现的捕获组,在匹配结果数组中的位置就越靠前

/(([a-z])-([0-9]))/
// 对于该正则表达式
// 第一个捕获组:(([a-z])-([0-9]))部分
// 第二个捕获组:([a-z])部分
// 第三个捕获组:([0-9])部分
捕获组命名

格式:(?<捕获组名称>规则组)

命名后的捕获组,会成为exec返回的结果中的groups对象的成员(正则表达式中没有命名捕获组时,groups为undefined)

var reg = /(?<year>\d{4})-(?<month>\d{1,2})-(?<day>\d{1,2})/g;

var str = "2020-1-18, 2023-6-25, 2024-12-1";

var result;
while(result = reg.exec(str)){
    /*
    	result = [
    		groups: {
    			year: "...",
    			month: "...",
    			day: "..."
    		}
    	]
    */
    console.log(`${result.groups.year}${result.groups.month}${result.groups.day}日`);
}
捕获组与str.replace方法配合

当replace方法的第二个参数是一个函数时,只要匹配成功,就会调用一次函数

函数的参数除了包含本次匹配的项目外,还包含本次匹配中捕获组的匹配结果

var reg = /(\d{4})-(\d{1,2})-(\d{1,2})/g;

var str = "2020-1-18, 2023-6-25, 2024-12-1";

console.log(str.replace(reg, function(match, group1, group2, group3){
    return `${group1}${group2}${group3}日`;
}));

当replace方法的第二个参数是字符串时,可以在字符串中使用占位符$n来引用第n个捕获组匹配的内容

var reg = /(\d{4})-(\d{1,2})-(\d{1,2})/g;

var str = "2020-1-18, 2023-6-25, 2024-12-1";

console.log(str.replace(reg, "$1年$2月$3日"));

若捕获组后紧跟一个量词,则该捕获组将可能发生多次匹配

当同一个捕获组匹配了多次结果,则方法捕获结果时取捕获组匹配的最后一次结果

var reg = /(?<num>\d){1,3}/;				// 贪婪匹配模式下,如果能够匹配到3个连续的数字,则(?<num>\d)将发生3次匹配

var str = "1234";

console.log(reg.exec(str).groups.num);		// 3
非捕获组

某些方法能够获取到捕获组所匹配的结果,但这种行为会带来一定开销,因此出现了非捕获组

非捕获组本质上还是捕获组,只是捕获的结果不会被方法所获取,格式:(?:规则组)

var reg = /(?:\d{4})-(?:\d{1,2})-(?:\d{1,2})/g;

var str = "2020-1-18, 2023-6-25, 2024-12-1";

console.log(str.replace(reg, "$1年$2月$3日"));		// $n变为普通字符,不具备引用捕获组的能力

反向引用

可以在正则表达式使用\捕获组编号(编号从1开始)的形式来引用某个捕获组所匹配的结果,这称之为反向引用

反向引用相当于是把某个捕获组所匹配的结果加入到正则表达式中成为匹配规则的一部分

var reg = /(\d{2})\1/;		// \1表示引用第一个捕获组,也就是(\d{2})所匹配的内容

console.log(reg.test("1212"));		// true
console.log(reg.test("1234"));		// false

反向引用必须出现在被引用的捕获组之后,否则引用不生效

查找字符串中连续且重复的字符

var reg = /(\w)\1+/g;

var str = "aaabcccddddeeefgggh";

console.log(str.match(reg));

反向引用也可以引用具名捕获组,格式:\k<捕获组名称>

var reg = /(?<char>\w)\k<char>+/;

正向断言

正向断言(正向预查)用于帮助检查某个字符之后的内容是否满足预查的规则,只有满足了,前面的字符才可能被匹配到

预查规则所匹配的内容不会参与形成匹配结果,它仅仅是做一个辅助判断的作用

var reg = /123(?=abc)/;

// 只有当123后面是abc时,123才会被匹配到,abc不会出现在匹配结果中

正向断言后面的规则要避免与断言规则之间产生歧义

var reg = /123(?=456)abc/;

// 表示匹配123,但123后面是456(456不形成匹配结果),且123后面是abc(abc形成匹配结果),因此存在歧义,正则表达式不可能匹配到内容

应用举例:

给数字字符串加上逗号(类似于美元中每三位数字之间有一个逗号的格式):

// 123456789 --> 123,456,789

var str = "123456789";

var reg = /\B(?=(\d{3})+$)/g;

console.log(str.replace(reg, ","));

判断密码是否满足要求:

// 要求密码必须包含数字、小写字母、大写字母、!@$.,长度为6 ~ 12位

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

var pwd = "123abcABC.";

console.log(reg.test(pwd));			// true

负向断言

负向断言(负向预查)用于帮助检查某个字符之后的内容是否不满足预查的规则,只有不满足,前面的字符才可能被匹配到

预查规则所匹配的内容不会参与形成匹配结果,它仅仅是做一个辅助判断的作用

var reg = /123(?!abc)/;

// 只有当123后面不是abc时,123才会被匹配到,abc不会出现在匹配结果中

错误处理

JS中的错误分为:

  1. 语法错误:会导致当前脚本块无法执行,但不会影响到其他脚本块

    语法错误无法被try...catch捕获,因为语法错误是在代码编译时期被检查出来的,这会导致代码无法执行

  2. 运行错误:分为运行时报错或运行结果不符合预期

    运行报错:会导致当前脚本块中的后续代码无法执行

抛出错误

throw new Error("错误信息");

JS对抛出的数据没有做严格的规定,你可以throw错误对象,也可以throw其他类型的数据

JS中的错误对象有很多种,它可以是Error、ReferenceError、SyntaxError、TypeError...

若没有对抛出的错误进行处理,错误将会抛给上一层,尝试让上一层处理该错误

JS之所以能在控制台中将我们抛出的错误显示出来,就是因为我们没有对抛出的错误进行处理(捕获)

以浏览器环境为例:

function C(){
       throw new Error();
       // 执行完上句代码时,由于C没有对抛出的错误进行处理,因此错误会被抛出给B,因为是B调用了C
}

function B(){
       C();
       // 调用C的过程中,C向自己抛出了一个错误,但由于B没有对接收到的错误进行处理,因此错误会被抛给A,因为是A调用了B
}

function A(){
       B();
       // 调用B的过程中,B向自己抛出了一个错误,但由于A没有对接收到的错误进行处理,因此错误会被抛给全局,因为是A是在全局被调用的
}

A();
// 调用A的过程中,A向全局抛出了一个错误,但由于全局中没有对接收到的错误进行处理,因此错误会被抛给浏览器

当抛出的错误没有被及时处理时,错误就会被抛给上一层,一层层向上抛后,最终会抛给浏览器,当浏览器接收到错误时就会将错误打印出来,并终止程序的执行

捕获错误(处理错误)

语法:

try {
    代码块1;
} catch (错误){
    代码块2;
} finally {
    代码块3;
}

如果运行代码块1的过程中抛出了一个错误,会立即停止代码块1的执行,并将抛出的错误传递到catch的错误参数,然后转而执行代码块2

catch是可选的,若try之后没有catch,当try中的代码抛出了一个错误时,错误还是会抛给上层,相等于没有捕获错误

finally是可选的,但如果finally存在,那么只要对应的try被执行,则不管发生什么情况,finally中的代码块都会被执行

finally比较特殊:

只要try被执行到,跟try一起的finally就一定会被执行

try {
    throw new Error();
} finally {
    console.log("finally");
}
// 最终控制台中会输出"finally"

若finally中书写了return语句,则在finally运行之前执行的return,会跟没有执行过一样

function test() {
    try {
        return "try";
        // throw new Error();
    } catch (err) {
        return "catch";
    } finally {
        return "finally";
    }
}

console.log(test());		// "finally"

当finally中没有主动return时,才会使用别人return的结果

function test(){
    try {
        throw new Error();
    } catch (err) {
        return "catch";
    } finally {
        console.log("finally");
    }
}

console.log(test());
/*
	"finally"
	"catch"
*/

某一层将错误进行了捕获后,错误将不再抛给上一层,之后的代码也可以顺利执行