说说js的数据类型
js的数据类型可以分为两类,基本数据类型和引用数据类型
基本数据类型
基本数据类型主要有6种:Number,String,Boolean,Symbol,Null,Undefined,后来又添加了一种叫做BigInt,所以说基本数据类型就有7种。
Number
最常见的整数类型格式则为十进制,还可以设置八进制(零开头)、十六进制(0x开头)
1 | let intNum = 55 // 10进制的55 |
浮点类型则在数值中必须包含小数点,还可通过科学计数法表示。
1 | let floatNum1 = 1.1; |
格式化
关于浮点数有一个重要的知识点就是格式化
使用
toFixed()方法表示保留几位小数,要注意的是不是
format方法,在js中不是使用这种方法格式化浮点数1
2let num = 123.456;
console.log(num.toFixed(2)); // 输出 "123.46" - 四舍五入到两位小数toFixed()返回的是一个字符串,而不是数字。如果需要进行进一步的数值计算,你可能需要将其转换回数字类型。使用
toPrecision()方法这个方法表示保留几位有效数字,并且会根据需要,自动调整数字的表示形式(科学记数法或固定点表示法)
1
2
3let num = 123.456;
console.log(num.toPrecision(3)); // 输出 "123"
console.log(num.toPrecision(5)); // 输出 "123.46"Number.prototype.toLocaleString()toLocaleString()可以用于获取特定地区的数字格式,包括货币、百分比和日期格式等。对于浮点数格式化,它可以用来设置小数位数和使用逗号作为千位分隔符等。1
2let num = 1123.456;
console.log(num.toLocaleString())//输出1,123.456
NaN
在数值类型中,存在一个特殊数值NaN,意为“不是数值”,用于表示数值运算操作失败了,而不是抛出错误
1 | console.log(0/0); // NaN |
存储空间
在 JavaScript 中,变量的声明方式(var、let 或 const)不会影响其占用的内存大小。内存占用主要取决于变量存储的数据类型,而不是声明关键字本身。
Number8 字节(64 位),因为所有数字都以双精度浮点数形式存储。
1
2let num = 123; // 占用 8 字节
const pi = 3.14; // 占用 8 字节BigInt:内存占用随整数大小动态变化。1
const bigIntValue = 1234567890123456789012345678901234567890n; // 内存占用随值增大而增加
String:内存占用与字符串长度成正比,每个字符通常占用2 字节1
const str = "Hello"; // 长度为 5 的字符串,占用约 10 字节
Boolean通常占用4 字节或更少(具体实现因引擎而异)。1
const flag = true; // 占用少量固定内存
undefined和null:通常占用4 字节或更少。1
2let x; // undefined,占用少量固定内存
const y = null; // 占用少量固定内存
存储一个ip地址,如何实现存储空间最小?在c语言中,一个字符char,占用一个字节,用字符串存储一个ip地址,最多占用3*4+3(3个分隔符)=15个字节,但是因为ip地址每位的范围是0-255,用一个字节就能存储,所以用一个数组存储ip数组的各个部分,最多大概只需要占用4字节。
String
字符串使用双引号(”)、单引号(’)或反引号(`)表示都可以,反引号表示的是模板字符串,模板字符串和普通字符串有什么区别呢?
在模板字符串中可以嵌入变量,这是模板字符串最常见的用法
1 | let name = 'tom' |
在模板字符串中,会保留字符串中的所有空白字符,包括空格、制表符(\t)和换行符(\n)。
这种特性,使得模板字符串非常适合用于生成多行文本或格式化的字符串内容。这与普通字符串(使用单引号 ' 或双引号 ")不同,普通字符串不会自动保留换行和缩进,必须手动添加换行符(\n)。
1 | // 普通字符串 |
在js中,字符串是不可变的,意思是一旦创建,它们的值就不能变了。因为虽然字符串是基本数据类型,但实际存储在堆中,栈中保存的是引用。
1 | let lang = "Java";//这行代码,会在内存中创建一个包含 "Java" 的字符串对象,并将引用赋值给变量 lang。 |
字符串比较
| 场景 | == 结果 | === 结果 | 原因 |
|---|---|---|---|
| 2个原始字符串:let a = ‘123’, b=’123’ | true | true | a,b都是基本数据类型中的字符串,又因为引擎会将相同的字符串字面量(如 '123')存储为同一个堆内存地址,而非创建多个实例。这样可以节省内存并提高性能。因此,a 和 b 实际上指向了同一个堆内存地址,所以a,b的值也是相同的,因此 a === b 返回 true |
| 原始字符串 vs String 对象:let a = ‘123’, b=new String(‘123’) | true | false | ===比较的结果为false,因为a,b不是同一数据类型,a的类型是string,b的类型是object;==比较的结果是true,是因为b.valueof的值就是,字面量字符串123的引用。 |
| 两个 String 对象:let a = new String(‘123’), b=new String(‘123’) | false | false | a,b的数据类型虽然相同,值也相同,但是由于对象之间的比较,比较的是引用,无论是严格比较还是非严格比较,a,b是2个不同的对象,所以a,b存储的引用并不相同,严格比较和非严格比较的值都是false |
Boolean
Boolean(布尔值)类型有两个字面值: true和false
通过Boolean可以将其他类型的数据,显式转化成布尔值
| 数据类型 | 转换为 true 的值 | 转换为 false 的值 |
|---|---|---|
| String | 非空字符串 | “” |
| Number | 非零数值(包括负数) | 0 、 NaN |
| Object | 任意对象 | null |
| Undefined | N/A (不存在) | undefined |
Symbol
Symbol关键字的主要用途,是用来创造一个唯一的标识符,用作对象属性,确保不会产生属性冲突。
1 | let genericSymbol = Symbol(); |
传入符号主要为了标识,符号相同并不代表值也相同
1 | let fooSymbol = Symbol('foo'); |
可枚举性
Symbol类型的键默认是可枚举的,通过对象字面量或常规赋值添加的属性,默认都是可枚举的
1 | const sym = Symbol(); |
通过Object.defineProperty定义的属性,其可枚举性才默认为false,无论是字符串键还是 Symbol 键,均可通过 Object.defineProperty() 显式设置 enumerable: true/false,顾名思义,这个方法就是用来定义,修改属性的,而且每次只能修改一个属性。
1 | //第一个参数指明要修改哪个对象,第二个参数指明要修改这个对象的哪个属性,第三个参数指明如何修改这个属性 |
在某些方法中不被考虑
Symbol 类型的键,并且不会出现在for...in 循环中,也不会被Object.keys()方法返回,因为这两种方法只考虑字符串类型的键,并不是Symbo类型的属性就是不可枚举的。
1 | // 创建一个 Symbol |
Symbol 类型的键和值,都不会包含在序列化的结果中,因为无法转化成字符串,而且JSON规范明确要求了键必须是字符串。
1 | // 创建一个 Symbol |
Object.assign
Object.assign会把Symbol 类型的键也拷贝进,因为Symbol类型的键默认也是可枚举的
1 | // 创建一个 Symbol |
Null
Null类型同样只有一个值,即特殊值 null
null明明是基本数据类型,typeof null返回的结果却是"object",逻辑上讲, null 值表示一个空对象,这也是给typeof传一个 null 会返回 "object" 的原因,这也是js的历史遗留问题。
1 | let car = null; |
Undefined
Undefined 类型只有一个值,就是特殊值 undefined,如果一个变量声明了但是未被赋值,那么这个变量的值就是undefined。
1 | let message; // 这个变量被声明了,只是值为 undefined |
引用数据类型
引用数据类型有多种,引用数据类型统称为Object,所以一般不会问有几种 ,一般只问基本类型有几种。
引用数据类型主要包括以下三种:
Array
js数组是一组有序的数据,但跟其他语言不同的是,数组中每个槽位可以存储任意类型的数据。并且,数组也是动态大小的,会随着数据添加而自动增长。
通常通过字面量表示法创建数组
1 | let colors = ["red", 2, {age: 20 }] |
或者通过Array来创建数组,给数组分配大小固定,连续的空间,内部默认没有元素;可以调用数组的fill方法填充数组,比如arr.fill(0);
1 | const arr = new Array(4) |
虽然说数组大小好像是固定的,比如这里初始化长度为4,但是还是可以往数组中加入元素,改变数组的大小,不过新加入的元素放在已分配空间之后。
创建二维数组
1 | const arr = new Array(4)//创建一个长度为4的数组,虽然创建的时候指定了长度,但是长度还是可以变化的 |
上述代码可简写为:
1 | const arr = new Array(4).fill(0).map( ele => new Array(4).fill(0)) |
可以看出在js中创建二维数组还是挺麻烦的。
Function
函数实际上是对象,每个函数都是 Function类型的实例,而 Function也有属性和方法,跟其他引用类型一样,其中最常见的属性比如prototype。但是函数和其他对象不同的是,对一个函数使用typeof返回的是function,对一个数组使用typeof返回的都是object
其他类型
除了上述说的2种之外,还包括Date、RegExp、Map、Set等,他们都是Object类型的子类。
区别

对于基本类型变量,变量和值都直接存储在栈内存中;对于引用类型变量,变量名和引用都存储在栈内存中,值存储在堆内存中。
当基本数据类型的值,被作为参数,传递给函数或者变量时,实际上是将该值的一个副本传给了它们。
1
2
3
4
5
6
7
8
9
10
11
12
13//函数内部对参数所做的任何修改都不会影响到原始变量。
function changeValue(x) {
x = 10;
}
let a = 5;
changeValue(a);
console.log(a); //输出5
let a = 10;
let b = a; // 复制 a 的值给 b,b 是独立的新值
b = 20;
console.log(a); // 10(a 未受影响)当引用类型的值,被作为参数传递给函数或者变量时,实际上是将该值的一个引用传给了它们。
1
2
3
4
5
6
7
8
9
10
11
12
13
14let obj1 = { value: 10 };
let obj2 = obj1; // 复制引用地址,obj2 与 obj1 指向同一个对象
obj2.value = 20;
console.log(obj1.value); // 20(原始对象被修改)
console.log(obj2.value); // 20
function modifyObject(obj) {
obj.value = 100; // 修改共享对象的属性
}
let myObj = { value: 10 };
modifyObject(myObj);
console.log(myObj.value); // 100(原始对象被修改)
数组的常用方法
我们可以从增删查改,是否会修改原数组这几个角度,来给数组的常用方法归类
增
push():可以传入任意个数的元素,这些元素会被添加到数组的末尾,返回新数组的长度,会修改原数组。1
2
3let colors = []; // 创建一个数组
let count = colors.push("red", "green"); // 推入两项
console.log(count) // 2unshift():也是可以传入任意个数的元素,这些元素会被添加到数组的首部,返回新数组的长度,会修改原数组。1
2
3
4let colors = new Array(); // 创建一个数组
let count = colors.unshift("red", "green"); // 从数组开头推入两项
console.log(count); // 2
console.log(colors)//['red', 'green'],说明不是先推入red,后推入green,而是视为一个整体,放到了数组的头部这个方法很容易和数组另一个方法
shift混用,后者用来删除数组首部元素。splice():第一个参数传入开始位置,第二个参数(表示删除元素的个数)传入0,表示不删除元素,后续参数传入插入的元素。1
2
3
4let colors = ["red", "green", "blue"];
let removed = colors.splice(1, 0, "yellow", "orange")
console.log(colors) // red,yellow,orange,green,blue(插入的元素从开始下标开始排序)
console.log(removed) // [],返回包含被删除元素的数组,因为没有元素被删除所以是空数组concat():首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组。1
2
3
4let colors = ["red", "green", "blue"];
let colors2 = colors.concat("yellow", ["black", "brown"]);
console.log(colors); // ["red", "green","blue"],可以看到原数组并没有改变
console.log(colors2); // ["red", "green", "blue", "yellow", "black", "brown"]
删
pop():方法用于删除数组的最后一项,同时减少数组的length值,返回被删除的项1
2
3
4let colors = ["red", "green"]
let item = colors.pop(); // 取得最后一项
console.log(item) // green
console.log(colors.length) // 1shift():法用于删除数组的第一项,同时减少数组的length值,返回被删除的项1
2
3
4let colors = ["red", "green"]
let item = colors.shift(); // 取得第一项
console.log(item) // red
console.log(colors.length) // 1splice():第一个参数传入开始位置,第二个参数传入要删除元素的个数,返回包含被删除元素的数组,如果,没有任何元素被删除,则返回空数组。1
2
3
4let colors = ["red", "green", "blue"];
let removed = colors.splice(0,1); // 删除第一项
console.log(colors); // green,blue
console.log(removed); // ["red"],只有一个元素的数组slice():本质是返回一个数组切片,并不会修改原数组,截取区间遵循左闭右开原则。1
2
3
4
5
6let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(1, 4);
console.log(colors) // red,green,blue,yellow,purple
console.log(colors2); // green,blue,yellow,purple
console.log(colors3); // green,blue,yellow
改
一般通过下标修改数组元素的值,也可以使用splice先删除元素再添加元素。
1 | let colors = ["red", "green", "blue"]; |
查
一般也是通过下标来查找数组元素。
indexOf():传入一个元素,返回数组中第一个与该元素相等的元素的下标,使用的是严格比较,如果数组中没有该元素,则返回-1,因为NaN不与任何数相等,所以indexOf(NaN)返回值必定为-1。其实这个方法特别语义化,indexOf(元素)意思不就是某个元素的下标吗。1
2
3let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1,NaN];
numbers.indexOf(4) // 3
console.log(numbers.indexOf(NaN)) // -1includes():判断某个元素是否在数组中存在,也是严格比较,存在返回true,否则返回false。对NaN做了特殊处理,能判断是它否存在于数组中,就这一点而言,是比indexOf要强大的。1
2
3let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1,NaN];
numbers.includes(4) //true
numbers.includes(NaN) //返回truefind():传入一个返回值是布尔类型的回调函数,用于判断满足某个条件的元素是否存在,存在则返回第一个符合条件的元素,不存在则返回undefined,通常用于判断对象数组中是否存在某个对象。1
2
3
4
5
6
7
8
9
10
11const people = [
{
name: "Matt",
age: 27
},
{
name: "Nicholas",
age: 29
}
];
people.find((element, index, array) => element.age < 28)// {name: "Matt", age: 27}
findIndex():语法和用途和find相同,不过返回的是元素的下标,未找到返回-1。
排序方法
reverse():反转数组,会修改原数组1
2
3let values = [1, 2, 3, 4, 5];
values.reverse();
alert(values); // 5,4,3,2,1sort():给数组排序,sort()方法接受一个比较函数,用于判断哪个值应该排在前面,用的是非常多,特别在算法题里1
2
3
4
5
6
7
8function compare(value1, value2) {
//return value1-value2 升序排序
//return value2-value1 降序排序
//value1[key]-value2[key] 根据某个属性升序排序,反之降序排序
}
let values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values);
转换方法
join():把数组中的元素拼接成一个字符串,用传入的符号连接,如果传入的符号是'',那么就是一个类似将字符数组转化成字符串的过程。显然这个方法也不会修改原数组。
1 | let colors = ["red", "green", "blue"]; |
迭代方法
some():传入一个返回值为布尔值的回调函数,作为判断条件,如果数组中存在满足条件的元素,则该方法返回true,否则返回false。要注意千万不要把这个方法写成any,数组并没有any方法,这是Promise的静态方法。every():传入一个返回值为布尔值的回调函数,作为判断条件,如果数组中每个元素都满足条件,则该方法返回true,否则返回false。注意千万不要把这个方法写成all,数组中并没有all方法,这是Promise的静态方法。forEach():遍历数组中的每个元素,并执行一定操作,可以修改原数组。1
2
3
4let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.forEach((item, index, array) => {
// 执行某些操作
});filter():传入一个返回值为布尔值的回调函数,作为判断条件,返回一个数组,这个数组包含所有满足这个判断条件的元素。无论原数组是否包含满足条件的元素,filter总是会返回一个新的数组。如果没有找到任何满足条件的元素,则返回的是一个空数组[]。1
2
3let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // 3,4,5,4,3map():根据传入的回调函数和数组中的每一个元素,并返回一个新的数组。要注意的是,传入的回调函数虽然也是需要有返回值的,就如同filter,some,every,但是不同的是,传入map方法的回调函数的返回值并不是一个布尔值,而是通过每个数组元素计算得到的新的值。1
2
3let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
console.log(mapResult) // 2,4,6,8,10,8,6,4,2
字符串常用方法
操作方法
concat
用于将一个或多个字符串拼接成一个新字符串,返回一个新的字符串,不会修改原来的字符串,js中的字符串是不可变的。
在数组中也有这个方法哦,效果也非常相似,其实数组和字符串有很多同名的方法。
1 | let stringValue = "hello "; |
slice() substr() substring()
作用是返回字符串的切片
1 | let stringValue = "hello world"; |
数组中也有
slice()方法可以看出
slice()和substring()的用法是一致的,当传入两个参数的时候,分别表示的是截取的左右区间(左闭右开,目前就没见到过左闭右闭的情况,除了正则表达式中)而
substr()传入两个参数时,第一个表示参数起始位置,第二个参数表示的是要截取的元素的个数。当只传入一个参数,三者的效果是相同的。
indexOf() startWith() includes()
indexOf:从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 ),数组中也有这个方法,也许因为字符串本来就可以看成字符数组。
1 | let stringValue = "hello world"; |
startWith():判断字符串是否以某个字符串开头,返回值为布尔类型。
includes():判断字符串中是否包含某个字符串,返回值是布尔类型,数组中也有这个方法。
1 | let message = "foobarbaz"; |
由此可见,无论是数组还是字符串中,都有indexOf,includes,slice,concat方法
字符串拆分
把字符串按照指定的分割符,拆分成字符数组,特别是当传入'',即空字符的时候,是真正意义上的把字符串拆分成字符数组,不会包含空字符。
1 | let str = "12+23+34" |
模板匹配
提及字符串,就不得不提到模板匹配,提起模板匹配就不得不提起正则表达式,会在后面介绍。
match()
接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象(正则表达式对象),如果你传递一个非正则表达式对象,它会被隐式转换为正则表达式;返回值是数组。
非全局匹配(传入的正则表达未加修饰符g):只会匹配第一个符合条件的字符串片段,下面给出一个例子
1 | let text = "cat, bat, sat, fat"; |
匹配成功的返回结果,是一个数组,但是这个数组并不是传统意义上的数组,因为它的键不全是数字,包含第一个匹配的字符串片段和更多信息。不得不说,在js中,有的数组是真像对象,但它就是数组,有的对象也是真的像数组(伪数组),但就是对象。因为在js中,数组本质就是一个对象。

index属性:匹配结果在字符串中的开始位置input属性:原始字符串
如果匹配失败则返回null
全局匹配
返回所有符合条件的字符串片段,并以数组的形式给出,例子如下:
1 | let text = "cat, bat, sat, fat"; |
匹配成功的返回结果,只包含符合条件的字符串片段。

如果匹配失败则返回null
search()
1 | str.search(regexp) |
str是要进行搜索操作的字符串。regexp是一个正则表达式对象。如果你传递一个非正则表达式对象(例如,一个字符串),它将被隐式转换为一个正则表达式对象。- 如果找到匹配项,
search()方法返回第一个匹配项的首字符的下标。 - 如果没有找到匹配项,
search()方法返回-1。 - 是否给传入的正则表达式添加修饰符
g,对结果没有影响。 - 简单的来说
search返回的就是第一个被匹配的字符串片段的下标
下面举个例子:
1 | let text = "cas, bat, sat, fat"; |
replace()
replace() 方法用于在字符串中查找匹配的子字符串,并用新的子字符串替换它,这个方法不会改变原始字符串,因为JavaScript中的字符串是不可变的,它会返回一个新的字符串作为结果。
1 | str.replace(regexp|substr, newSubstr|function) |
regexp(正则表达式):一个RegExp对象或者其字面量,标识要查找的子字符串。全局搜索需要使用g标志。substr(字符串):将被替换的子字符串。newSubstr(字符串):新子字符串,用于替换匹配项的字符串。function(函数):用于创建新子字符串的函数,所以要有返回值,该函数将被每一个匹配项调用。
1 | let str = "Hello world!"; |
1 | let str = "Hello world! Welcome to the world of programming."; |
1 | let str = "Hello World! Welcome to the world of Programming."; |
1 | let str = "20 apples, 15 bananas, and 3 cherries"; |
区别
match方法返回的是一个数组(无论是否是全局匹配),search方法返回的是下标,replace方法返回的是修改后的字符串。
说说js中的日期对象Date
JavaScript中的Date对象用于处理日期和时间。它提供了一系列方法来获取和设置日期的各个方面,如年、月、日、小时、分钟、秒和毫秒等
创建日期对象
我们都知道,通过new Date()就可以创建一个代表当前日期时间的对象,但是你有没有想过,可以给Date构造函数传入不同的参数呢?
不带参数:创建一个代表
当前日期和时间的对象。1
2const now = new Date();
console.log(now); // 输出类似 "2025-03-07T03:48:32.123Z" 的字符串(具体时间取决于执行时刻)带日期字符串参数:根据提供的日期字符串,创建对应的日期对象,但通常情况下,要我们手动传入一个格式规范的日期字符串,是比较难的吧。
1
2const dateStr = new Date('2025-03-07T00:00:00');
console.log(dateStr); // 输出 "2025-03-07T00:00:00.000Z"带时间戳参数:根据传入的时间戳,返回对应的时间日期对象
1
2
3
4const timestamp = new Date(1709756400000);
console.log(timestamp); // 输出 "2025-03-07T00:00:00.000Z"
//我们只要再调用toLocaleString方法,时间格式就变得熟悉了
console.log(timestamp.toLocaleString()) // 2024/3/7 04:20:00通过多个数值参数创建:指定年、月(从0开始计数)、日、时、分、秒和毫秒,感觉是比传入一个日期字符串好用?
1
2const customDate = new Date(2025, 2, 7, 0, 0, 0, 0); // 注意月份是从0开始计数的,所以2表示3月
console.log(customDate); // 输出 "2025-03-07T00:00:00.000Z"
获取日期信息
Date对象提供了多种方法来获取日期的不同部分:
getFullYear():获取四位数的年份。getMonth():获取月份(0-11)。getDate():获取一个月中的某一天(1-31)。getDay():获取星期几(0-6,0表示星期天)。getHours():获取小时(0-23)。getMinutes():获取分钟(0-59)。getSeconds():获取秒(0-59)。getMilliseconds():获取毫秒(0-999)。getTime():获取自1970年1月1日以来的毫秒数,也就是时间戳,获取时间戳的方法还有Date.now()
其他常用方法
toDateString():返回日期部分的字符串表示形式,不常用toTimeString():返回时间部分的字符串表示形式,不常用toISOString():返回ISO格式的日期字符串(UTC时间),不常用toLocaleString():基于本地时间格式化日期和时间,常用,要注意的是不要把locale(现场)写成local(本地)toLocaleDateString():仅格式化日期部分为本地格式。toLocaleTimeString():仅格式化时间部分为本地格式。
1 | const date = new Date() |
说说js中的正则表达式
创建正则表达式
字面量语法
1 | const regex = /pattern/; |
例如:/ab+c/i 匹配 “abc”, “ABBC” 等。
构造函数语法
1 | const regex = new RegExp('pattern', '修饰符'); |
例如:new RegExp('ab+c', 'i'),等价于/ab+c/i,感觉还是字面量语法方便啊
正则表达式语法
元字符
\d:数字(0-9),因为digit的意思就是数字的意思;\D:非数字\w:单词字符(字母、数字、下划线,在js中的标识符,就是由这三者构成的),word就是单词的意思;\W:非单词字符\s:空白符(空格、制表符、换行),space就是空格的意思,然而\s匹配的是所有类型的空白字符,而不仅仅是空格。\S:非空白符.:匹配除换行外的任意字符(若需包含换行,使用修饰符s)^:字符串开头;$:字符串结尾
字符组
[abc]:匹配 a、b、c 中的任意一个[a-z]:匹配 a 到 z 的任意小写字母[^abc]:匹配任何一个不在a、b、c范围内的字符,也就是说**^在字符组中也有取反的意思**
要注意的是,无论字符组中有多少个符号,匹配的都只是一个字符。
分组/捕获组
小括号可以将多个字符或子表达式组合在一起,形成一个逻辑单元
1 | const pattern = /(ab)+/; |
(ab)将ab视为一个整体,而不是一个单独的字符。+表示匹配这个整体一次或多次。- 匹配结果是整个字符串
"ababab",而第一个捕获组的结果是"ab"。
小括号会创建一个捕获组,用于提取匹配的部分内容。每个捕获组的内容可以通过 match() 方法的返回值中的数组访问。
1 | const pattern = /(\d{4})-(\d{2})-(\d{2})/; |
其实上述正则表达式不使用捕获组,也能匹配到str,但如果不使用捕获组,结果数组中也就不会有捕获组。
量词
*:0 次或多次+:1 次或多次?:0 次或 1 次{n}:精确匹配 n 次{n,}:>=n次{n,m}:n 到 m 次,左闭右闭
总结:在js中的正则表达式中,中括号表示字符组,只匹配一个字符;大括号表示量词,表示匹配多少次,而小括号则表示一个分组或者匹配组。
修饰符
i:不区分大小写g:全局匹配(查找所有匹配项)m:多行模式(^和$匹配每行的开头和结尾)s:dotAll 模式(.匹配换行符)u:Unicode 模式y:粘性匹配(从lastIndex开始匹配)
常用方法
test()
1 | /hello/.test('hello world'); // true |
回布尔值,判断是否匹配成功
exec()
1 | /(\d+).(\d+)/.exec('3.14'); // ['3.14', '3', '14', index: 0, ...] |
不使用全局标志的时候
调用一个正则表达式的exec方法,并传入一个字符串,效果完全等同于:调用一个字符串的match方法并传入一个正则表达式
使用全局标志
每次调用
exec()都会从上一次匹配结束的位置,开始寻找下一个匹配项,这一点字符串的match方法就不同了,真难记啊。
Object的常见静态方法
Object.keys()
Object.keys() 方法,用于返回一个对象自身,可枚举属性,且不包括Symbol类型的属性,组成的数组。
1 | const obj = { a: 1, b: 2, c: 3, [Symbol()]: 4 }; |
1 | const obj = {}; |
我们还经常使用for in来获取一个对象所有的可枚举属性,但是它与Object.keys() 方法不同的是,还能获取原型链上的可枚举属性,所以在某些情况还需要借助hasOwnProperty来判断是不是自身的属性。
Object.values()
Object.values() 方法,用于返回一个对象自身可枚举属性(且不包括Symbol类型的属性)的值组成的数组。它只返回对象自身的属性(不包括原型链上的属性),并且这些属性必须是可枚举的。
1 | const obj = { a: 1, b: 2, c: 3, [Symbol()]: 4 }; |
Object.entries()
Object.entries() 方法,用于返回一个对象自身可枚举属性(且不包括Symbol类型的属性)的键值对数组。每个键值对是一个包含两个元素的数组:第一个元素是属性名(键),第二个元素是对应的属性值。
1 | const obj = { a: 1, b: 2, c: 3, [Symbol()]: 4 }; |
Object.assign()
传入2个对象作为参数,会将第二个对象中可枚举的自有属性(包括Symbol类型的属性),拷贝到第一个对象。返回值就是传入的第一个对象,传入的第一个对象会被修改。
1 | var obj = { |
Object.defineProperty
Object.defineProperty 是 JavaScript 中用于定义或修改对象属性的底层方法
1 | Object.defineProperty(obj, prop, descriptor); |
obj: 要定义属性的目标对象。prop: 要定义或修改的属性名称(字符串或 Symbol)。descriptor: 属性描述符对象,用于定义属性的行为。descriptor是一个对象,可以包含以下键值对:
数据描述符
| 属性名 | 描述 |
|---|---|
value | 属性的值,默认为 undefined。 |
writable | 是否可以修改属性的值,默认为 false(即不可写)。 |
enumerable | 是否可以通过 for...in 或 Object.keys() 枚举该属性,默认为 false。 |
configurable | 是否可以删除该属性或修改其描述符,默认为 false。 |
存取器描述符
| 属性名 | 描述 |
|---|---|
get | 定义获取属性值时调用的函数,默认为 undefined。 |
set | 定义设置属性值时调用的函数,默认为 undefined。 |
1 | const obj = {}; |
typeof和instanceof
typeof
typeof 操作符返回一个字符串,表示值的数据类型。
1 | typeof operand //这种方式用的多 |
这两种使用方法都是可以的。下面是一些例子。
1 | typeof 1 // 'number' |
值得注意的是,对所有引用数据类型(除了function,包括数组,普通对象),使用typeof返回的都是object
instanceof
主要用来判断某个构造函数,是否在某个实例对象的原型链上。
1 | object instanceof constructor |
区别
typeof返回的是字符串,instanceof返回的是布尔值
typeof只能能准确判断基本数据的类型,不能
准确判断引用数据的类型。intanceof只能准确判断
引用数据的类型,不能判断基本数据的类型
可以看到,上述两种方法都有弊端,并不能满足所有场景的需求。
谈谈 JavaScript 中的类型转换机制
前面我们讲到,JS中有六种简单数据类型:undefined、null、boolean、string、number、symbol,以及引用类型:object
常见的类型转换有:强制转换(显示转换),自动转换(隐式转换)
显式转换
显式转换,即我们很清楚可以看到这里发生了类型的转变,常见的方法有:Number(),parseInt(),String(),Boolean()
Number()
将任意类型的值转化为数值
1 | Number(324) // 324 |
- 从上面可以看到,
Number转换的时候是很严格的,只要有一个字符无法转成数值,整个字符串就会被转为NaN null转化成数字类型是0,而undefined转化成数字类型是NaN,这是二者最大的区别之一- 总结一下,哪些数据转化成数字类型后的值是0:
- 空字符串
- false
- null
- 空数组
parseInt()
parseInt相比Number,就没那么严格了,parseInt函数逐个解析字符,遇到不能转换的字符就停下来。这个方法是可以直接被调用的,不许要显式借助其他对象。
1 | parseInt('32a3') //32 |
要注意的是,如果传入parseInt的值不是以数字开头的字符串,那么parseInt的返回值将是NaN ,这一点其实是和Number()相同的。
1 | console.log(parseInt(true))//输出NaN,因为传入的不是字符串 |
和parseInt方法类似的还有parseFloat方法,后者和前者不同的是,是从字符串中提取出浮点数。
String()
可以将任意类型的值转化成字符串
1 | // 数值:转为相应的字符串 |
可以看到,对于基本数据类型,强制转化成字符串,就是加个双引号就好了,而引用数据类型就不一样了,需要调用toString方法
Boolean()
可以将任意类型的值转为布尔值,转换规则如下:
1 | Boolean(undefined) // false |
隐式转换
隐式转换本质就是偷偷帮我们调用了显式转换的函数,在隐式转换中,我们可能最大的疑惑是 :何时发生隐式转换?
我们这里可以归纳为两种情况发生隐式转换的场景:
- 比较运算(
==、!=、>、<) - 算术运算(
+、-、*、/、%) if、while需要布尔值地方
除了上面的场景,还要求运算符两边的操作数不是同一类型
自动转化成布尔值
在需要
布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用Boolean函数自动转换成字符串
遇到预期为
字符串的地方,就会将非字符串的值自动转为字符串常发生在
+运算中,一旦存在字符串,则会进行字符串拼接操作1
2
3
4
5
6
7
8'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]" ,因为{}转化成字符串是[object Object]
'5' + [] // "5" 因为[]转换成字符串是空串
'5' + function (){} // "5function (){}",因为函数调用toString方法返回值是function (){}
'5' + undefined // "5undefined"
'5' + null // "5null"对于基本数据类型和函数,字符串拼接的时候直接参与拼接,对于其他引用数据类型,需要先调用
toString方法。哈哈,原来字符串凭借不是所有情况都是直接拼接啊。自动转换成数值
除了左右两边包含字符串的
+号,其他运算符都会把参与运算的数据自动转成数值1
2
3
4
5
6
7
8
9
10'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 等价于5*0
false / '5' // 等价于0/5
'abc' - 1 // 等价于NaN-1
null + 1 // 等价于0+1
undefined + 1 // 等价于NaN+1
说说你对BOM的理解
BOM (Browser Object Model),浏览器对象模型,提供了独立于内容,与浏览器窗口进行交互的对象
其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退,前进,刷新,浏览器的窗口发生变化,滚动条的滚动,以及获取客户的一些信息如:浏览器品牌版本,屏幕分辨率

window
Bom的核心对象是window,它表示浏览器的一个实例,location,navigator等后续介绍的对象都是window的属性。
在浏览器中,window对象有双重角色,即是浏览器窗口的一个接口,又是全局对象,因此所有在全局作用域中声明的变量、函数都会变成window对象的属性和方法
window.scrollTo
如果有滚动条,将横向滚动条移动到相对于窗体宽度为x个像素的位置,将纵向滚动条移动到相对于窗体高度为y个像素的位置
1 | window.scrollTo(0, 500);//将页面垂直滚动到距离页面顶部500像素的位置,而水平滚动条不会发生变化。 |
不需要添加单位
window.scrollBy
如果有滚动条,将横向滚动条向左移动x个像素,将纵向滚动条向下移动y个像素,也不需要添加单位
window.open
window.open()既可以导航到一个特定的url,也可以打开一个新的浏览器窗口。
window.open() 会返回新窗口的引用,也就是新窗口的 window 对象,当使用 window.open() 方法打开新窗口时,如果返回值是 null,这通常意味着浏览器阻止了该弹窗的创建。现代浏览器为了防止恶意网站滥用弹窗,通常会限制非用户交互触发的弹窗。如果你在页面加载时或没有明确的用户动作(如点击事件)的情况下调用 window.open(),浏览器可能会认为这是未经请求的弹窗,并阻止它。
比如直接在script标签中书写:
1 | window.open('sanye.blog')//被浏览器阻止 |
1 | document.querySelector('button').addEventListener('click', (e) => { |
1 | var newWindow = window.open(url, target, features[, replace]); |
参数分析:
url :类型为String,新窗口要加载的 URL 地址。如果省略或设置为 null,则会打开一个空白窗口。
target :类型为 String,指定新窗口的目标位置。它可以是以下常用的预定义值之一:
_self: 在当前页面中加载新页面(默认行为)。_blank: 在新的窗口或标签页中加载页面。
features:类型为String,是一系列用逗号分隔的字符串,用于指定新窗口的各种属性和行为。每个特征可以带有或不带参数
width = 600: 设置窗口宽度为 600 像素。height = 400: 设置窗口高度为 400 像素
replace:类型为Boolean, 如果设置为 true,则新加载的页面将替换历史记录中的当前条目;如果为 false 或未提供,则会在历史记录中添加一个新条目。这对于防止用户多次点击后退按钮返回到同一个页面非常有用。
window.close
仅用于关闭通过 window.open() 打开的窗口,如果尝试关闭一个不同域名下的窗口,可能会遇到跨域限制。在这种情况下,window.close() 可能不会工作,因为浏览器的安全模型会阻止你操作不属于同一源的窗口。
1 | myWin.close()//关闭myWin窗口,它是使用 `window.open()` 打开的新窗口 |
新创建的 window 对象有一个 opener 属性,该属性指向打开他的原始窗口对象。
location
是一个对象,包含了许多属性,一个url地址例子如下:
1 | http://www.wrox.com:80/WileyCDA/?q=javascript#contents |
可以将location理解为当前页面URL的JS抽象
属性
location属性描述如下:
| 属性名 | 例子 | 说明 |
|---|---|---|
| hash | “#contents” | url中,#后面的字符,没有则返回空串 |
| host | www.wrox.com:80 | 服务器名称和端口号 |
| hostname | www.wrox.com | 域名,不带端口号 |
| href | http://www.wrox.com:80/WileyCDA/?q=javascript#contents | 完整url |
| pathname | “/WileyCDA/“ | 服务器下面的文件路径 |
| port | 80 | url的端口号,没有则为空 |
| protocol | http: | 使用的协议 |
| search | ?q=javascript | url的查询字符串,通常为?后面的内容 |
除了 hash之外,只要修改location的一个属性,就会导致页面重新加载新URL,因为hash值不会发送给服务器,所以修改哈希值后刷新也面也没意义。
location.reload
此方法可以重新刷新当前页面。这个方法会根据最有效的方式刷新页面,如果页面自上一次请求以来没有改变过,页面就会从浏览器缓存中重新加载,这一点和浏览器的缓存策略相关。如果要强制从服务器中重新加载,传递一个参数true即可。
location.replace
1 | // 跳转到新页面,但在历史记录中不保留当前页面 |
效果:浏览器立即跳转到指定 URL,当前页面不会出现在“后退”历史中。可以防止用户点击“后退”回到登录页(避免重复提交或自动填充密码),提升用户体验。
history
history是window对象的一个属性,它本身也是个对象,提供了许多api,主要用来操作浏览器URL的历史记录,允许我们编程式控制页面在历史记录之间跳转,也允许我们修改历史记录。检查一个页面并在控制台输入history,即可查看当前页面的state,scrollRestoraion等信息。

| API | 作用 |
|---|---|
| history.back() | 跳转到前一个页面,如果没有前一个页面,则不做响应,不会改变history.length |
| history.forward() | 跳转到后一个页面,如果当前就最新页面,则不做响应,不会改变history.length |
| history.go() | 传入数字,正数表示前进几个页面,负数表示后退几个页面,0表示刷新页面,不会改变history.length |
| history.length | 历史记录栈的长度,它是一个只读属性,无法直接修改。 |
| history.pushState() | 往历史记录栈顶添加一条记录,历史记录条数加1,但是不会跳转页面。在当前页面调用这个api,你能明显的看到url改变了,但是页面没有跳转。接收三个参数:历史记录对象(state),页面标题,URL路径。 |
| history.replaceState() | 不会增加历史记录数目,会修改当前历史记录 |
| history.state | 访问当前页面的状态对象。 |
| history.scrollRestoraion | 如果值为auto,则在前进或者后退的时候,滚动条会回到原来的位置。如果值为manual(手动的),则不会恢复。默认值是auto,即后退到历史页面的时候,滚动条会回到原来的位置。可以通过在页面(html文件)内部的js代码中使用history.scrollRestoraion来修改这个页面滚动条的恢复方式,要注意的是,这么做只会影响当前页面的滚动行为,不会影响整个页面栈中所有页面的滚动行为 |
历史记录用一个栈来维护,每添加一历史记录的操作可以叫做push(入栈),当前页面就是历史记录栈顶的页面 ;假设当前历史记录栈的大小是3,当执行history.back(),弹出(pop)一条历史记录,页面也随之发生变化,因为栈顶元素改变了,但是这条历史记录并不会丢失,当我们执行history.forward(),它又会重新成为历史记录栈的栈顶元素。
navigator
navigator 对象主要用来获取浏览器的属性,区分浏览器类型。属性较多,且兼容性比较复杂。
screen
保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度。
DOM常见的操作有哪些
DOM是什么
浏览器根据html标签生成的js对象,所有的标签属性都可以在上面找到(所以说node中没有dom),修改这个对象属性会自动映射到标签上。关键词:浏览器,html标签,js对象,属性映射。
nodeType
nodeType 是一个只读属性,用于标识 DOM 节点的类型。常见的nodeType值包括:
1:元素节点(Element),因为元素结点是最常见的结点,所以nodeType的值是13:文本节点(Text)8:注释节点(Comment)9:文档节点(Document)
DOM常见的操作
创建节点
createElement
创建元素结点
1 | const divEl = document.createElement("div"); |
createTextNode
创建文本结点
1 | const textEl = document.createTextNode("content"); |
获取节点
可以通过捕获的方式获取dom结点,也可以通过一个dom结点的属性来获取另一个dom结点
querySelector
传入任何有效的css 选择器,即获得首个符合条件的Dom元素:
1 | document.querySelector('.element') |
如果页面上没有指定的元素时,返回 null
querySelectorAll
传入任何有效的css 选择器,返回一个伪数组,包含全部符合匹配条件的DOM元素。
1 | const notLive = document.querySelectorAll("p"); |
其他方法
1 | document.getElementById('id属性值');//返回拥有指定id的对象的引用 |
我们仅通过观察是...Element...还是,...Elements...就能判断出返回的结果是集合还是单独的元素
除此之外,每个DOM元素还有parentNode、childNodes、firstChild、lastChild、nextSibling、previousSibling属性,关系图如下图所示。

parentNode和parentElement
parentNode 返回指定节点的父节点,这个父节点可以是任何类型的节点,包括文档类型节点、元素节点、文本节点等。但是,在实际应用中,除了元素节点外,其他类型的节点很少作为父节点存在。
parentElement 仅返回指定节点的父元素节点(即类型为HTMLElement的节点)。如果指定节点的父节点不是一个元素节点(例如,它可能是一个文本节点),则 parentElement 返回 null,即先捕获再判断类型。
简单的来说,就是一个对父节点的类型有要求,一个没有。
childNodes和children
childNodes 返回一个实时的NodeList对象,包含了指定节点的所有直接子节点(一级子节点),包括元素节点、文本节点、注释节点等所有类型的节点。
children 返回一个实时的 HTMLCollection 对象,只包含指定节点的直接子元素节点(一级元素结点,即标签)。不包括文本节点、注释节点等其他类型的节点。
previousElementSibling和previousSibling
前者用来获取上一个元素节点(即 HTML 标签),后者用来获取上一个任意类型的结点
更新结点
innerHTML
不但可以修改一个DOM节点的文本内容,如果传入的是html片段,还会被解析成dom结点。
1 | // 获取<p id="p">...</p > |
innerText、textContent
自动对字符串进行HTML编码,就是把小于号转化成<大于号转化成>保证无法设置任何HTML标签
1 | // 获取<p id="p-id">...</p > |
两者的区别在于读取属性时,innerText不返回隐藏元素的文本(即 display: none 或者 visibility: hidden),而textContent返回所有文本
1 | <div id="example"> |
添加结点
appendChild
把一个节点添加到父节点的最后一个子节点之后,如果这个添加的结点已经在页面中存在,那么这个结点会先从原位置删除。
1 | <p id="js">JavaScript</p > |
添加一个p元素
1 | const js = document.getElementById('js') |
在HTML结构变成了下面
1 | <div id="list"> |
insertBefore
1 | parentElement.insertBefore(newElement, referenceElement) |
子节点会插入到referenceElement之前
parentElement: 这是要操作的目标元素,新的子节点将被添加到这个元素的子节点列表中。newElement: 这是你想要插入的新元素节点。referenceElement: 这是在新元素插入之前,所依据的参考元素。新元素会被放置在这个参考元素之前。如果这个参数为null,则新元素会被插入到父元素的最后,就像使用appendChild()一样。
删除结点
removeChild
删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild方法,把自己删掉,也就是说,一个结点是不能删除自身的,而是需要借助父节点。
1 | // 拿到待删除节点: |
删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。
说说js中的布局属性
要明白如何实现功能,我们首先要搞清楚dom元素的一些布局属性
clientWidth,scrollWidth,offsetWidth
client系列
clientWidth/clientHeight:可视区域的宽/高+内边距(padding),不包含border
offset系列
offsetWidth/offsetHeight:可视区域的宽/高+内边距+border+滚动条,这两个属性通常被拿来与clientWidth/clientHeight属性比较。这2类属性的范畴都包含可视区域的宽高和padding,都不包含margin(明明和padding一样都是边距,为什么就这么不受待见呢),区别在于offset系列还包含border,后者不包含,范围更小。
scroll系列
scrollWidth/scrollHeight:有滚动条的元素的整体的宽高。一个没有滚动条的元素,它的scrollWidth/scrollHeight属性的值等于它的clientWidth/clientHeight属性的值。
举个例子,我们在一个高度为600px的盒子box里放两个背景颜色不同,高度都是400px的盒子box1,box2,并给box添加css属性
1 | overflow:auto |
1 | 补充一下overflow属性的值 |
这样box盒子就出现了滚条,可以实现内容的滚动,内部盒子也不会影响外部盒子的布局(开启了BFC,可以观察添加该条属性前后,body高度的变化,从800px变为600px)。然后我们访问box盒子(有滚动条的盒子)的clientHeight属性和scrollHeight属性
1 | box.clientHeight //600px |
这样是不是就很容易理解client和scroll之间的区别呢。对于没有滚动条的元素,clientWidth/clientHeight与scrollWidth/scrollHeight的值是一一相等的。
当我们不断地给body添加元素,**body的高度总有超过浏览器窗口高度的时候,此时body标签的父元素,html标签会自动开启滚动条,html.clientHeight就是浏览器窗口**的高度。
scrollLeft和scrollTop
scroll开头的属性中,还有两个重要的属性,scrollLeft/scrollTop。
表示具有滚动条的元素,顶部滚动出可视区域的高度,或者左部滚动出可视区域的宽度,对于不具有滚动条的元素,这两个属性的值都是0。这两个属性是可读写的,将元素的 scrollLeft 和 scrollTop 设置为 0,可以重置元素的滚动位置,通常用来实现一键到底,或者返回顶部等功能。
offsetLeft和offsetTop
- 元素左部/顶部,距离最近的定位元素的距离,相对的不是视口,通常是固定的,不会随页面滚动而改变。
- offsetLeft/offsetTop这2个属性是只读的,不能手动修改
- 要注意的是,没有
offsetRight和offsetBottom属性。

offsetX和offsetY
offsetX和offsetY是与鼠标事件相关的属性,通常在处理用户交互时使用,注意要和offsetLeft/offsetTop区分开来。- 这两个属性提供了鼠标指针相对于触发事件的元素(即事件目标元素,可以通过
event.target获得)的 X 和 Y 坐标。它们是 MouseEvent 对象的一部分 - 我曾经尝试使用这2个属性来做放大镜的效果,发现一直实现不了,后来发现了问题所在,触发鼠标事件的目标元素始终是蒙层,而不是商品图片,可以通过给蒙层添加
point-events:none忽略鼠标事件来解决问题。
说说js资源加载事件
DOMContentLoaded
DOMContentLoaded事件是在HTML文档被完全加载和解析之后触发的,也就是说,当浏览器已经解析完整个HTML文档,DOM树构建完毕,这时候才会触发这个事件。不过,可能需要注意的是,虽然DOM树已经构建完成,但像图片和样式表,这些外部资源可能还没有加载完毕。
1 | document.addEventListener('DOMContentLoaded', function() { |
load
则是在所有资源(包括图片、样式表等)都加载完毕后才触发,只等待资源加载,不等待资源解析,也不等待所有的异步请求完成。
比如通过 JavaScript 发起的 AJAX 请求或 Fetch API 请求,甚至不等待动态加载的内容(如通过 JavaScript 动态插入的图片或其他资源),为什么要等待呢?,load事件怎么知道你请求什么时候响应,怎么知道你什么时候插入图片或者其他资源,如果你始终不这么操作,load难不成还一直等待你?
1 | window.addEventListener('load', function() { |
需要值得注意的是,首屏渲染,只需要等待html标签解析完毕,构建好dom树,等待css文件加载并解析完,生成cssom树之后,就可以进行。换句话说,首屏渲染不等待图片资源,所以有时候我们能看到页面渲染出来了,但是图片没加载出来的情况。
unload和beforeunload
当用户导航至其他页面,且新页面在本窗口打开、或者关闭当前标签页或窗口、或者刷新页面时,都会触发 unload 事件,但是我们常用的其实是beforeunload,即在页面卸载前做些什么,因为unload事件触发的时候页面已经被卸载了,我们做任何操作都没用了。
1 | window.addEventListener('beforeunload', function (e) { |
当用户刷新页面的时候,浏览器会提示是否刷新站点;当用户跳转到其他页面的时候,提示是否进行页面跳转。
