八股文(二)
[TOC]
JS
JavaScript有哪些数据类型?它们的区别是什么
JavaScript 是一种动态类型的编程语言,支持多种数据类型。这些数据类型可以分为两大类:原始类型(Primitive Types) 和 引用类型(Reference Types)。
1. 原始类型(Primitive Types)
原始类型是直接存储在栈内存中的简单数据,具有不可变性(即不能直接修改,只能重新赋值)。JavaScript 有以下 7 种原始类型:
(1) number
- 表示数字,包括整数和浮点数。
- 例如:
42
,3.14
,NaN
(非数字),Infinity
(无穷大)。
(2) string
- 表示文本数据,用单引号、双引号或反引号包裹。
- 例如:
"Hello"
,'World'
,`Hello ${name}`
。
(3) boolean
- 表示逻辑值,只有两个值:
true
和false
。 - 例如:
true
,false
。
(4) undefined
- 表示未定义的值。变量声明但未赋值时,默认值为
undefined
。 - 例如:
let x; console.log(x); // undefined
。
(5) null
- 表示空值或对象不存在。通常用于显式地清空一个变量。
- 例如:
let x = null;
。
(6) symbol
(ES6 新增)
- 表示唯一的标识符,通常用于对象的属性名。
- 例如:
const id = Symbol("id");
。
(7) bigint
(ES11 新增)
- 表示大整数,用于表示超过
Number
类型范围的整数。 - 例如:
const bigNum = 123456789012345678901234567890n;
。
2. 引用类型(Reference Types)
引用类型是存储在堆内存中的复杂数据,变量存储的是其内存地址。JavaScript 有以下常见的引用类型:
(1) object
- 表示复杂数据结构,可以是普通对象、数组、函数等。
- 例如:
1
2const obj = { name: "Alice", age: 25 };
const arr = [1, 2, 3];
(2) function
- 函数是一种特殊的对象,可以被调用。
- 例如:
1
2
3function add(a, b) {
return a + b;
}
(3) array
- 数组是一种特殊的对象,用于存储有序的数据集合。
- 例如:
1
const arr = [1, 2, 3];
(4) date
- 表示日期和时间。
- 例如:
1
const now = new Date();
(5) regexp
- 表示正则表达式,用于匹配字符串。
- 例如:
1
const regex = /abc/;
3. 原始类型与引用类型的区别
特性 | 原始类型 | 引用类型 |
---|---|---|
存储位置 | 栈内存 | 堆内存 |
存储内容 | 直接存储值 | 存储内存地址 |
赋值行为 | 赋值时复制值 | 赋值时复制内存地址 |
比较方式 | 比较值是否相等 | 比较内存地址是否相等 |
可变性 | 不可变(只能重新赋值) | 可变(可以修改属性或元素) |
示例 | let a = 10; let b = a; |
let obj1 = {}; let obj2 = obj1; |
4. 类型检测
JavaScript 提供了多种检测数据类型的方法:
(1) typeof
- 检测变量的原始类型,返回一个字符串。
- 例如:
1
2
3
4
5
6
7
8typeof 42; // "number"
typeof "Hello"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object"(历史遗留问题)
typeof Symbol(); // "symbol"
typeof function() {}; // "function"
typeof {}; // "object"
(2) instanceof
- 检测引用类型,判断对象是否是某个构造函数的实例。
- 例如:
1
2[] instanceof Array; // true
{} instanceof Object; // true
(3) Object.prototype.toString
- 精确检测数据类型,返回
[object 类型]
。 - 例如:
1
2Object.prototype.toString.call(42); // "[object Number]"
Object.prototype.toString.call([]); // "[object Array]"
5. 类型转换
JavaScript 是弱类型语言,支持隐式和显式类型转换。
(1) 隐式转换
- 例如:
1
2"5" + 1; // "51"(字符串拼接)
"5" - 1; // 4(数字运算)
(2) 显式转换
- 例如:
1
2
3Number("42"); // 42
String(42); // "42"
Boolean(0); // false
总结
JavaScript 的数据类型分为原始类型和引用类型:
- 原始类型:
number
,string
,boolean
,undefined
,null
,symbol
,bigint
。 - 引用类型:
object
,function
,array
,date
,regexp
等。
它们的区别主要体现在存储位置、赋值行为、比较方式和可变性上。通过 typeof
、instanceof
和 Object.prototype.toString
可以检测数据类型。
如何判断JavaScript变量是数组?
在 JavaScript 中,判断一个变量是否为数组有多种方法。以下是常见的几种方式:
1. 使用 Array.isArray()
这是最推荐的方式,专门用于判断变量是否为数组。
语法:
1 | Array.isArray(value); |
示例:
1 | const arr = [1, 2, 3]; |
优点:
- 专门用于判断数组,准确且可靠。
- 兼容性良好,支持所有现代浏览器。
2. 使用 instanceof
通过检查变量是否是 Array
的实例来判断。
语法:
1 | value instanceof Array; |
示例:
1 | const arr = [1, 2, 3]; |
注意:
- 在跨窗口或跨框架的环境中(如 iframe),
instanceof
可能失效,因为不同窗口的Array
构造函数不同。
3. 使用 Object.prototype.toString
通过调用 Object.prototype.toString
方法,返回 [object Array]
来判断。
语法:
1 | Object.prototype.toString.call(value) === "[object Array]"; |
示例:
1 | const arr = [1, 2, 3]; |
优点:
- 适用于所有场景,包括跨窗口或跨框架的环境。
4. 使用 constructor
通过检查变量的 constructor
属性是否为 Array
来判断。
语法:
1 | value.constructor === Array; |
示例:
1 | const arr = [1, 2, 3]; |
注意:
- 如果变量的
constructor
属性被修改,可能会导致错误判断。
5. 使用 typeof
结合 instanceof
typeof
无法直接判断数组(因为数组是对象),但可以结合 instanceof
使用。
示例:
1 | const arr = [1, 2, 3]; |
注意:
- 这种方法较为繁琐,不推荐使用。
6. 使用 Array.prototype.isPrototypeOf
通过检查 Array.prototype
是否是变量的原型链来判断。
语法:
1 | Array.prototype.isPrototypeOf(value); |
示例:
1 | const arr = [1, 2, 3]; |
注意:
- 这种方法较为少见,不如
Array.isArray()
直观。
总结
以下是判断 JavaScript 变量是否为数组的推荐方法:
方法 | 推荐度 | 优点 | 缺点 |
---|---|---|---|
Array.isArray() |
⭐⭐⭐⭐⭐ | 专门用于判断数组,准确且可靠 | 无 |
instanceof |
⭐⭐⭐⭐ | 简单直观 | 跨窗口或跨框架时可能失效 |
Object.prototype.toString.call |
⭐⭐⭐⭐ | 适用于所有场景,包括跨窗口或跨框架的环境 | 语法较为复杂 |
constructor |
⭐⭐⭐ | 简单直观 | 如果 constructor 被修改,可能导致错误 |
typeof 结合 instanceof |
⭐⭐ | 可以判断对象是否为数组 | 方法较为繁琐 |
Array.prototype.isPrototypeOf |
⭐⭐ | 可以判断原型链 | 方法较为少见 |
**推荐使用 Array.isArray()
**,因为它是最简单、最可靠的方式。
JavaScript中null和undefined的区别是什么?
在 JavaScript 中,null
和 undefined
都表示“无”或“空”的概念,但它们在语义和用法上有明显的区别。以下是它们的详细对比:
1. 定义
类型 | 定义 |
---|---|
undefined |
表示变量未定义或未赋值。 |
null |
表示变量被显式地赋值为空值,通常用于表示“无”或“对象不存在”。 |
2. 类型
类型 | 类型 |
---|---|
undefined |
是 undefined 类型的唯一值。 |
null |
是 object 类型的唯一值(这是 JavaScript 的历史遗留问题)。 |
3. 默认值
类型 | 默认值 |
---|---|
undefined |
变量声明但未赋值时,默认值为 undefined 。 |
null |
不是默认值,需要显式赋值。 |
4. 使用场景
类型 | 使用场景 |
---|---|
undefined |
- 变量声明但未赋值。 - 函数参数未传递。 - 访问对象不存在的属性。 |
null |
- 显式地表示“无”或“空值”。 - 清空变量或对象。 |
5. 示例
undefined
示例
1 | let x; |
null
示例
1 | let y = null; |
6. 类型检测
类型 | typeof |
=== 比较 |
---|---|---|
undefined |
typeof undefined 返回 "undefined" |
undefined === undefined 返回 true |
null |
typeof null 返回 "object" |
null === null 返回 true |
7. 比较
比较 | 结果 |
---|---|
undefined == null |
true |
undefined === null |
false |
undefined === undefined |
true |
null === null |
true |
8. 转换
类型 | 转换为数字 | 转换为布尔值 |
---|---|---|
undefined |
NaN |
false |
null |
0 |
false |
9. 总结
特性 | undefined |
null |
---|---|---|
定义 | 表示变量未定义或未赋值。 | 表示变量被显式地赋值为空值。 |
类型 | undefined |
object (历史遗留问题) |
默认值 | 变量声明但未赋值时,默认值为 undefined 。 |
不是默认值,需要显式赋值。 |
使用场景 | 变量未赋值、函数参数未传递、访问不存在的属性。 | 显式地表示“无”或“空值”。 |
类型检测 | typeof undefined 返回 "undefined" |
typeof null 返回 "object" |
比较 | undefined == null 返回 true |
undefined === null 返回 false |
转换 | 转换为数字是 NaN ,转换为布尔值是 false 。 |
转换为数字是 0 ,转换为布尔值是 false 。 |
10. 使用建议
- **
undefined
**:- 用于表示变量未定义或未赋值。
- 不要显式地将变量赋值为
undefined
,因为它是默认值。
- **
null
**:- 用于显式地表示“无”或“空值”。
- 在需要清空变量或对象时,使用
null
。
示例代码
1 | let a; // 变量声明但未赋值 |
通过理解 null
和 undefined
的区别,可以更好地编写清晰、健壮的 JavaScript 代码。
typeof null 的结果是什么?为什么?
在 JavaScript 中,typeof null
的结果是 "object"
。这是一个历史遗留问题,源于 JavaScript 早期的实现细节。
1. typeof null
的结果
1 | console.log(typeof null); // "object" |
2. 为什么 typeof null
返回 "object"
?
typeof null
返回 "object"
的原因可以追溯到 JavaScript 的早期设计。
历史背景:
- 在 JavaScript 最初的实现中,值以 类型标签 和 实际值 的形式存储在内存中。
- 对象的类型标签是
0
,而null
的值在内存中被表示为全零(0x00
)。 - 因此,当
typeof
检查null
的类型标签时,发现它的值是0
,误认为它是一个对象。
技术细节:
- JavaScript 使用 32 位 存储值,其中最低的 3 位用于表示类型标签:
000
:对象1
:整数010
:浮点数100
:字符串110
:布尔值
null
的值在内存中被表示为全零(0x00
),其类型标签为000
,因此被误认为是对象。
3. 为什么没有修复这个问题?
尽管这是一个明显的错误,但 JavaScript 的设计者 Brendan Eich 和 ECMAScript 标准委员会决定不修复这个问题,原因如下:
- 兼容性:修复这个问题会导致大量现有代码无法正常运行。
- 成本:修改底层实现会影响 JavaScript 的性能和稳定性。
- 约定俗成:开发者已经习惯了这种行为,且可以通过其他方式(如
===
)准确判断null
。
4. 如何准确判断 null
?
由于 typeof null
返回 "object"
,可以使用以下方法准确判断一个值是否为 null
:
使用 ===
严格相等运算符
1 | const value = null; |
使用 Object.prototype.toString
1 | const value = null; |
5. 总结
typeof null
返回"object"
是 JavaScript 的一个历史遗留问题。- 这个问题源于 JavaScript 早期的内存表示方式,
null
的类型标签被误认为是对象。 - 尽管这是一个错误,但由于兼容性和成本的考虑,未被修复。
- 可以使用
===
或Object.prototype.toString
准确判断null
。
示例代码
1 | console.log(typeof null); // "object" |
通过理解 typeof null
的行为,可以避免在实际开发中误用或误解这一特性。
typeof和instanceof有什么区别?
typeof
和 instanceof
是 JavaScript 中用于检测数据类型的两个操作符,但它们的功能和使用场景有所不同。以下是它们的详细对比:
1. typeof
- 功能:用于检测变量的原始类型。
- 返回值:返回一个字符串,表示变量的类型。
- 适用场景:适用于检测原始类型(如
number
、string
、boolean
等)以及undefined
和function
。 - 局限性:无法准确区分对象的具体类型(如
Array
、Date
等),且typeof null
返回"object"
。
示例:
1 | console.log(typeof 42); // "number" |
2. instanceof
- 功能:用于检测对象是否是某个构造函数的实例。
- 返回值:返回一个布尔值,表示对象是否是构造函数的实例。
- 适用场景:适用于检测引用类型(如
Array
、Date
、Object
等)。 - 局限性:无法检测原始类型,且在跨窗口或跨框架的环境中可能失效。
示例:
1 | console.log([] instanceof Array); // true |
3. 区别对比
特性 | typeof |
instanceof |
---|---|---|
功能 | 检测原始类型 | 检测对象是否是某个构造函数的实例 |
返回值 | 返回一个字符串 | 返回一个布尔值 |
适用场景 | 适用于原始类型和 undefined 、function |
适用于引用类型 |
局限性 | 无法区分对象的具体类型,typeof null 返回 "object" |
无法检测原始类型,跨窗口或跨框架时可能失效 |
4. 使用场景
typeof
的使用场景
- 检测变量是否为原始类型(如
number
、string
、boolean
等)。 - 检测变量是否为
undefined
或function
。
instanceof
的使用场景
- 检测对象是否是某个构造函数的实例(如
Array
、Date
等)。 - 判断对象的继承关系。
5. 示例代码
1 | // typeof 示例 |
6. 总结
- **
typeof
**:- 用于检测原始类型,返回一个字符串。
- 无法区分对象的具体类型,
typeof null
返回"object"
。
- **
instanceof
**:- 用于检测对象是否是某个构造函数的实例,返回一个布尔值。
- 无法检测原始类型,跨窗口或跨框架时可能失效。
根据具体需求选择合适的操作符,可以更准确地判断变量的类型。
为什么Javascript中0.1+ 0.2 !==0.3,如何让其相等?
在 JavaScript 中,0.1 + 0.2 !== 0.3
的原因是浮点数的精度问题。这是由于 JavaScript 使用 IEEE 754 双精度浮点数 标准来表示数字,而这种表示方式无法精确表示某些小数。
1. 为什么 0.1 + 0.2 !== 0.3
?
IEEE 754 浮点数表示
JavaScript 使用 64 位双精度浮点数表示数字,其中:
- 1 位表示符号(正负)。
- 11 位表示指数。
- 52 位表示尾数(有效数字)。
由于这种表示方式,某些十进制小数(如 0.1
和 0.2
)无法被精确表示,而是以近似值存储。
具体原因
0.1
在二进制中是无限循环小数,存储时会丢失精度。0.2
在二进制中也是无限循环小数,存储时会丢失精度。- 当
0.1
和0.2
相加时,它们的近似值相加,结果是一个接近0.3
但不完全等于0.3
的值。
示例
1 | console.log(0.1 + 0.2); // 0.30000000000000004 |
2. 如何让 0.1 + 0.2 === 0.3
?
方法 1:使用 Number.EPSILON
进行比较
Number.EPSILON
是 JavaScript 中表示最小精度误差的值。可以通过比较两个数的差值是否小于 Number.EPSILON
来判断它们是否相等。
1 | function isEqual(a, b) { |
方法 2:使用 toFixed
方法
通过 toFixed
方法将结果四舍五入到指定的小数位数。
1 | const sum = (0.1 + 0.2).toFixed(1); |
注意:toFixed
返回的是字符串,如果需要数字类型,可以转换为数字:
1 | const sum = parseFloat((0.1 + 0.2).toFixed(1)); |
方法 3:使用第三方库
可以使用第三方库(如 decimal.js
、big.js
等)来处理高精度计算。
1 | // 使用 decimal.js |
方法 4:放大为整数计算
将小数放大为整数,计算后再缩小为小数。
1 | function add(a, b) { |
3. 总结
方法 | 描述 | 优点 | 缺点 |
---|---|---|---|
Number.EPSILON |
比较两个数的差值是否小于最小精度误差。 | 简单直观 | 需要手动实现比较逻辑 |
toFixed |
将结果四舍五入到指定的小数位数。 | 简单易用 | 返回字符串,需要转换 |
第三方库 | 使用高精度计算库(如 decimal.js 、big.js )。 |
高精度,功能强大 | 需要引入第三方库 |
放大为整数计算 | 将小数放大为整数,计算后再缩小为小数。 | 无需引入外部库 | 实现较为复杂 |
4. 推荐方法
- 如果只需要简单的比较,推荐使用
Number.EPSILON
。 - 如果需要高精度计算,推荐使用第三方库(如
decimal.js
)。
通过理解浮点数精度问题,可以更好地处理 JavaScript 中的数值计算。
如何获取安全的unfined值?
在 JavaScript 中,undefined
是一个全局变量,表示未定义的值。通常情况下,直接使用 undefined
是安全的,但在某些情况下,undefined
可能被意外覆盖,导致不可预测的行为。为了获取安全的 undefined
值,可以采用以下几种方法:
1. 直接使用 undefined
在大多数情况下,直接使用 undefined
是安全的,因为它是 JavaScript 的全局变量。
示例:
1 | let x; |
注意:
- 如果在局部作用域中定义了
undefined
变量,可能会导致意外行为。
2. 使用 void 0
void
是一个运算符,用于执行表达式并返回 undefined
。void 0
是获取 undefined
的一种安全方式,因为它总是返回 undefined
,即使 undefined
被覆盖。
示例:
1 | let undefined = 42; // 覆盖 undefined |
优点:
- 即使
undefined
被覆盖,void 0
仍然返回undefined
。
缺点:
- 语法较为晦涩,可读性较差。
3. 使用函数参数默认值
在函数参数中,如果没有传递值,参数默认值为 undefined
。可以通过这种方式获取 undefined
。
示例:
1 | function getUndefined(value = undefined) { |
优点:
- 语法清晰,易于理解。
缺点:
- 需要定义一个函数。
4. 使用解构赋值
通过解构赋值,可以获取 undefined
。
示例:
1 | const { undefined: safeUndefined } = {}; |
优点:
- 语法简洁。
缺点:
- 可读性较差。
5. 使用全局对象
在浏览器环境中,可以通过 window
对象获取 undefined
。在 Node.js 环境中,可以通过 global
对象获取 undefined
。
示例:
1 | // 浏览器环境 |
注意:
- 在严格模式(
"use strict"
)下,window
或global
可能无法直接访问。
6. 使用 Object.prototype
通过 Object.prototype
获取 undefined
。
示例:
1 | const safeUndefined = Object.prototype.valueOf.call(undefined); |
优点:
- 即使
undefined
被覆盖,仍然可以获取undefined
。
缺点:
- 语法较为复杂。
7. 使用 Symbol
通过 Symbol
创建一个唯一的值,模拟 undefined
。
示例:
1 | const safeUndefined = Symbol("undefined"); |
注意:
- 这种方式实际上是创建了一个新的符号值,而不是真正的
undefined
。
总结
以下是获取安全的 undefined
值的推荐方法:
方法 | 优点 | 缺点 |
---|---|---|
直接使用 undefined |
简单直观 | 可能被覆盖 |
void 0 |
即使 undefined 被覆盖,仍然有效 |
语法晦涩,可读性较差 |
函数参数默认值 | 语法清晰,易于理解 | 需要定义一个函数 |
解构赋值 | 语法简洁 | 可读性较差 |
全局对象 | 简单直接 | 在严格模式下可能无法直接访问 |
Object.prototype |
即使 undefined 被覆盖,仍然有效 |
语法复杂 |
Symbol |
创建唯一值 | 不是真正的 undefined |
**推荐使用 void 0
**,因为它是最简单、最安全的方式,且兼容性好。
typeof NaN的结果是什么?
在 JavaScript 中,typeof NaN
的结果是 "number"
。这是因为 NaN
(Not-a-Number)是 JavaScript 中表示“非数字”的特殊值,但它本身属于 number
类型。
1. NaN
的定义
NaN
是 JavaScript 中表示“非数字”的特殊值。- 它是一个全局属性,通常出现在数学运算中,例如:
1
2
3console.log(0 / 0); // NaN
console.log(Math.sqrt(-1)); // NaN
console.log(Number("abc")); // NaN
2. typeof NaN
的结果
1 | console.log(typeof NaN); // "number" |
3. 为什么 typeof NaN
返回 "number"
?
NaN
属于 number
类型,因为它表示的是一个无效的数值。在 JavaScript 中,NaN
是 number
类型的一个特殊值,类似于 Infinity
和 -Infinity
。
JavaScript 中的 number
类型包括:
- 普通数字(如
42
、3.14
)。 - 特殊值:
NaN
、Infinity
、-Infinity
。
4. 如何判断一个值是否是 NaN
?
由于 NaN
是 JavaScript 中唯一一个不等于自身的值,因此不能直接使用 ===
或 ==
来判断一个值是否是 NaN
。
方法 1:使用 isNaN
函数
isNaN
函数用于判断一个值是否是 NaN
。如果值是 NaN
或无法转换为数字,则返回 true
。
1 | console.log(isNaN(NaN)); // true |
注意:isNaN
会将值隐式转换为数字,因此非数字值(如字符串)也会返回 true
。
方法 2:使用 Number.isNaN
方法
Number.isNaN
是 ES6 引入的方法,用于严格判断一个值是否是 NaN
。只有值是 NaN
时才会返回 true
。
1 | console.log(Number.isNaN(NaN)); // true |
方法 3:利用 NaN
不等于自身的特性
1 | function isNaNValue(value) { |
5. 总结
typeof NaN
返回"number"
,因为NaN
是number
类型的一个特殊值。- 使用
Number.isNaN
或value !== value
可以准确判断一个值是否是NaN
。
示例代码
1 | console.log(typeof NaN); // "number" |
通过理解 NaN
的行为和特性,可以更好地处理 JavaScript 中的数值计算和异常情况。
isNaN和Number.isNaN函数有什么区别?
isNaN
和 Number.isNaN
是 JavaScript 中用于判断一个值是否为 NaN
的两个函数,但它们的行为和用途有显著区别。以下是它们的详细对比:
1. isNaN
- 功能:判断一个值是否是
NaN
或是否可以转换为NaN
。 - 行为:
- 如果传入的值是
NaN
,则返回true
。 - 如果传入的值可以转换为
NaN
(如非数字字符串),则返回true
。 - 其他情况返回
false
。
- 如果传入的值是
- 实现原理:
isNaN
会先将传入的值隐式转换为数字,然后判断是否为NaN
。
示例:
1 | console.log(isNaN(NaN)); // true |
2. Number.isNaN
- 功能:严格判断一个值是否是
NaN
。 - 行为:
- 如果传入的值是
NaN
,则返回true
。 - 其他情况返回
false
。
- 如果传入的值是
- 实现原理:
Number.isNaN
不会对传入的值进行类型转换,直接判断是否为NaN
。
示例:
1 | console.log(Number.isNaN(NaN)); // true |
3. 区别对比
特性 | isNaN |
Number.isNaN |
---|---|---|
功能 | 判断值是否是 NaN 或是否可以转换为 NaN 。 |
严格判断值是否是 NaN 。 |
类型转换 | 会先将值隐式转换为数字。 | 不会进行类型转换。 |
返回值 | 值或转换后的值是 NaN 时返回 true 。 |
只有值是 NaN 时返回 true 。 |
示例 | isNaN("abc") 返回 true 。 |
Number.isNaN("abc") 返回 false 。 |
4. 使用场景
isNaN
的使用场景
- 需要判断一个值是否是
NaN
或是否可以转换为NaN
。 - 例如:处理用户输入时,判断输入是否为非数字。
Number.isNaN
的使用场景
- 需要严格判断一个值是否是
NaN
。 - 例如:在数值计算中,确保值不是
NaN
。
5. 示例代码
1 | // isNaN 示例 |
6. 总结
- **
isNaN
**:会先将值隐式转换为数字,然后判断是否为NaN
。适用于需要判断值是否为NaN
或是否可以转换为NaN
的场景。 - **
Number.isNaN
**:不会进行类型转换,严格判断值是否为NaN
。适用于需要严格判断值是否为NaN
的场景。
根据具体需求选择合适的函数,可以更准确地判断 NaN
。
==操作符的强制类型转换规则是什么?
==
是 JavaScript 中的宽松相等运算符,它在比较两个值时,会进行隐式的类型转换(强制类型转换)。以下是 ==
操作符的强制类型转换规则:
1. 基本规则
- 如果两个值的类型相同,直接比较它们的值。
- 如果两个值的类型不同,会尝试将它们转换为相同类型后再比较。
2. 类型转换规则
以下是 ==
操作符在不同类型之间的转换规则:
(1) null
和 undefined
null
和undefined
在==
比较时相等。1
console.log(null == undefined); // true
(2) number
和 string
- 如果一个是
number
,另一个是string
,会将string
转换为number
再比较。1
2console.log(42 == "42"); // true("42" 转换为 42)
console.log(0 == ""); // true("" 转换为 0)
(3) boolean
和其他类型
- 如果一个是
boolean
,会将boolean
转换为number
再比较(true
转换为1
,false
转换为0
)。1
2
3
4console.log(true == 1); // true(true 转换为 1)
console.log(false == 0); // true(false 转换为 0)
console.log(true == "1"); // true(true 转换为 1,"1" 转换为 1)
console.log(false == ""); // true(false 转换为 0,"" 转换为 0)
(4) object
和原始类型
- 如果一个是
object
,另一个是原始类型,会将object
转换为原始类型再比较。object
会先调用valueOf()
方法,如果返回的不是原始类型,再调用toString()
方法。1
2
3console.log([42] == 42); // true([42] 转换为 "42",再转换为 42)
console.log([] == 0); // true([] 转换为 "",再转换为 0)
console.log({} == "[object Object]"); // true({} 转换为 "[object Object]")
(5) symbol
symbol
与其他类型比较时,不会进行类型转换,直接返回false
。1
console.log(Symbol("foo") == "foo"); // false
(6) NaN
NaN
与任何值(包括自身)比较时,都返回false
。1
console.log(NaN == NaN); // false
3. 特殊情况
以下是一些常见的特殊情况:
(1) false
、0
和 ""
false
、0
和""
在==
比较时相等。1
2
3console.log(false == 0); // true
console.log(false == ""); // true
console.log(0 == ""); // true
(2) true
、1
和 "1"
true
、1
和"1"
在==
比较时相等。1
2
3console.log(true == 1); // true
console.log(true == "1"); // true
console.log(1 == "1"); // true
(3) null
和 0
null
和0
在==
比较时不相等。1
console.log(null == 0); // false
(4) undefined
和 0
undefined
和0
在==
比较时不相等。1
console.log(undefined == 0); // false
4. 总结
==
操作符的强制类型转换规则如下:
null
和undefined
相等。number
和string
比较时,将string
转换为number
。boolean
与其他类型比较时,将boolean
转换为number
。object
与原始类型比较时,将object
转换为原始类型。symbol
与其他类型比较时,不进行转换。NaN
与任何值比较时,返回false
。
5. 推荐使用 ===
由于 ==
的隐式类型转换规则较为复杂,容易导致意外行为,推荐使用 ===
(严格相等运算符)进行比较。===
不会进行类型转换,直接比较值和类型。
示例:
1 | console.log(42 == "42"); // true(宽松相等) |
通过理解 ==
的强制类型转换规则,可以更好地避免 JavaScript 中的隐式类型转换问题。
JavaScript其他值到字符串的转换规则是什么?
在 JavaScript 中,将其他类型的值转换为字符串的规则取决于具体的类型。以下是详细的转换规则:
1. 原始类型到字符串的转换
类型 | 转换规则 |
---|---|
string |
保持不变。 |
number |
直接转换为对应的字符串形式。 |
boolean |
true 转换为 "true" ,false 转换为 "false" 。 |
null |
转换为 "null" 。 |
undefined |
转换为 "undefined" 。 |
symbol |
转换为 "Symbol(description)" ,其中 description 是 Symbol 的描述。 |
bigint |
直接转换为对应的字符串形式。 |
示例:
1 | console.log(String("Hello")); // "Hello" |
2. 对象到字符串的转换
对象转换为字符串时,会调用对象的 toString()
方法。如果 toString()
方法返回的不是原始类型,则会调用 valueOf()
方法。
默认行为
- 大多数对象(如
{}
、[]
、function
等)的toString()
方法返回"[object Type]"
,其中Type
是对象的类型。 - 数组的
toString()
方法会将数组元素转换为字符串,并用逗号连接。 - 函数的
toString()
方法返回函数的源代码字符串。
示例:
1 | console.log(String({})); // "[object Object]" |
自定义 toString()
方法
可以为对象自定义 toString()
方法,控制其转换为字符串的行为。
1 | const obj = { |
3. 特殊对象的转换
对象 | 转换规则 |
---|---|
Date |
返回日期的字符串表示(如 "Wed Oct 18 2023 12:00:00 GMT+0800 (中国标准时间)" )。 |
RegExp |
返回正则表达式的字符串表示(如 "/abc/g" )。 |
Error |
返回错误的字符串表示(如 "Error: 错误信息" )。 |
示例:
1 | console.log(String(new Date())); // "Wed Oct 18 2023 12:00:00 GMT+0800 (中国标准时间)" |
4. 隐式转换
在 JavaScript 中,某些操作会隐式地将值转换为字符串。例如:
- 字符串拼接(
+
操作符,其中一个操作数是字符串)。 - 模板字符串(
`Hello ${value}`
)。
示例:
1 | console.log("Value: " + 42); // "Value: 42" |
5. 总结
类型 | 转换结果 |
---|---|
string |
保持不变。 |
number |
对应的字符串形式。 |
boolean |
"true" 或 "false" 。 |
null |
"null" 。 |
undefined |
"undefined" 。 |
symbol |
"Symbol(description)" 。 |
bigint |
对应的字符串形式。 |
object |
调用 toString() 方法,默认返回 "[object Type]" 。 |
特殊对象 | 返回特定的字符串表示(如日期、正则表达式、错误等)。 |
通过理解 JavaScript 中的字符串转换规则,可以更好地处理不同类型的数据并避免意外行为。
JavaScript其他值到数字值的转换规则是什么?
在 JavaScript 中,将其他类型的值转换为数字的规则取决于具体的类型。以下是详细的转换规则:
1. 原始类型到数字的转换
类型 | 转换规则 |
---|---|
string |
- 如果字符串是有效的数字形式(如 "42" ),则转换为对应的数字。- 如果字符串是空字符串( "" ),则转换为 0 。- 如果字符串不是有效的数字形式(如 "abc" ),则转换为 NaN 。 |
number |
保持不变。 |
boolean |
true 转换为 1 ,false 转换为 0 。 |
null |
转换为 0 。 |
undefined |
转换为 NaN 。 |
symbol |
抛出 TypeError 异常。 |
bigint |
如果数字在 Number 的范围内,则转换为对应的数字;否则抛出 TypeError 异常。 |
示例:
1 | console.log(Number("42")); // 42 |
2. 对象到数字的转换
对象转换为数字时,会调用对象的 valueOf()
方法。如果 valueOf()
方法返回的不是原始类型,则会调用 toString()
方法,然后将结果转换为数字。
默认行为
- 大多数对象(如
{}
、[]
、function
等)的valueOf()
方法返回对象本身,因此会调用toString()
方法。 - 数组的
toString()
方法会将数组元素转换为字符串,并用逗号连接。 - 函数的
toString()
方法返回函数的源代码字符串。
示例:
1 | console.log(Number({})); // NaN({} 转换为 "[object Object]",再转换为 NaN) |
自定义 valueOf()
方法
可以为对象自定义 valueOf()
方法,控制其转换为数字的行为。
1 | const obj = { |
3. 特殊对象的转换
对象 | 转换规则 |
---|---|
Date |
返回时间戳(从 1970 年 1 月 1 日 00:00:00 UTC 到当前时间的毫秒数)。 |
RegExp |
转换为 NaN 。 |
Error |
转换为 NaN 。 |
示例:
1 | console.log(Number(new Date())); // 1697606400000(当前时间的时间戳) |
4. 隐式转换
在 JavaScript 中,某些操作会隐式地将值转换为数字。例如:
- 数学运算(如
+
、-
、*
、/
)。 - 比较运算(如
>
、<
、>=
、<=
)。 - 一元加号运算符(
+
)。
示例:
1 | console.log("42" - 0); // 42 |
5. 总结
类型 | 转换结果 |
---|---|
string |
- 有效数字形式:对应的数字。 - 空字符串: 0 。- 无效数字形式: NaN 。 |
number |
保持不变。 |
boolean |
true :1 ,false :0 。 |
null |
0 。 |
undefined |
NaN 。 |
symbol |
抛出 TypeError 异常。 |
bigint |
如果数字在 Number 的范围内,则转换为对应的数字;否则抛出 TypeError 异常。 |
object |
调用 valueOf() 方法,默认返回 NaN 。 |
特殊对象 | 返回特定的数字值(如日期的时间戳)或 NaN 。 |
通过理解 JavaScript 中的数字转换规则,可以更好地处理不同类型的数据并避免意外行为。
JavaScript其他值到布尔值的转换规则是什么?
在 JavaScript 中,将其他类型的值转换为布尔值的规则遵循 “假值(falsy)” 和 “真值(truthy)” 的概念。以下是详细的转换规则:
1. 假值(Falsy Values)
以下值在转换为布尔值时会被转换为 false
:
false
0
(包括-0
和0.0
)""
(空字符串)null
undefined
NaN
示例:
1 | console.log(Boolean(false)); // false |
2. 真值(Truthy Values)
除了上述假值之外,其他所有值在转换为布尔值时都会被转换为 true
。例如:
true
- 非零数字(包括正数、负数、浮点数)
- 非空字符串
- 对象(包括
{}
、[]
、function
等) Symbol
示例:
1 | console.log(Boolean(true)); // true |
3. 隐式转换
在 JavaScript 中,某些操作会隐式地将值转换为布尔值。例如:
if
语句的条件判断。- 逻辑运算符(如
&&
、||
、!
)。 - 三元运算符(
? :
)。
示例:
1 | if ("Hello") { |
4. 总结
类型 | 转换为布尔值的结果 |
---|---|
false |
false |
0 、-0 、0.0 |
false |
"" (空字符串) |
false |
null |
false |
undefined |
false |
NaN |
false |
其他所有值 | true |
5. 注意事项
- 对象:即使是空对象(
{}
)或空数组([]
),也会被转换为true
。 - 字符串:只有空字符串(
""
)会被转换为false
,其他字符串(包括"0"
、"false"
)都会被转换为true
。 - 数字:只有
0
和NaN
会被转换为false
,其他数字(包括Infinity
)都会被转换为true
。
6. 示例代码
1 | console.log(Boolean(false)); // false |
通过理解 JavaScript 中的布尔值转换规则,可以更好地处理条件判断和逻辑运算。
JavaScript中||和&&操作符的返回值是什么?
在 JavaScript 中,||
(逻辑或)和 &&
(逻辑与)操作符的返回值遵循 短路求值(Short-Circuit Evaluation) 的规则。它们的返回值不一定是布尔值,而是根据操作数的值返回其中一个操作数。
1. ||
(逻辑或)操作符
- 规则:
- 从左到右依次检查操作数。
- 如果第一个操作数为 真值(truthy),则返回第一个操作数。
- 如果第一个操作数为 假值(falsy),则返回第二个操作数。
- 返回值:返回第一个为真值的操作数,或者最后一个操作数。
示例:
1 | console.log("Hello" || "World"); // "Hello"(第一个操作数为真值) |
2. &&
(逻辑与)操作符
- 规则:
- 从左到右依次检查操作数。
- 如果第一个操作数为 假值(falsy),则返回第一个操作数。
- 如果第一个操作数为 真值(truthy),则返回第二个操作数。
- 返回值:返回第一个为假值的操作数,或者最后一个操作数。
示例:
1 | console.log("Hello" && "World"); // "World"(第一个操作数为真值) |
3. 短路求值(Short-Circuit Evaluation)
||
操作符:- 如果第一个操作数为真值,不会计算第二个操作数。
- 如果第一个操作数为假值,才会计算第二个操作数。
&&
操作符:- 如果第一个操作数为假值,不会计算第二个操作数。
- 如果第一个操作数为真值,才会计算第二个操作数。
示例:
1 | function foo() { |
4. 使用场景
||
操作符
- 默认值设置:当变量为假值时,使用默认值。
1
2
3const name = "";
const displayName = name || "Guest";
console.log(displayName); // "Guest"
&&
操作符
- 条件执行:当变量为真值时,执行某个操作。
1
2const user = { name: "Alice" };
user && console.log(user.name); // "Alice"
5. 总结
操作符 | 规则 | 返回值 |
---|---|---|
` | ` | |
&& |
返回第一个为假值的操作数,或者最后一个操作数。 | 假值或最后一个操作数 |
通过理解 ||
和 &&
操作符的行为,可以更好地利用它们进行条件判断和默认值设置。
Object.is()与比较操作符==和===的区别是什么?
Object.is()
和比较操作符 ==
和 ===
是 JavaScript 中用于比较值的工具,但它们的行为和用途有所不同。以下是它们的详细对比:
1. ==
(宽松相等)
- 功能:比较两个值是否相等,允许类型转换。
- 行为:
- 如果两个值的类型不同,会尝试将它们转换为相同类型后再比较。
- 转换规则较为复杂,可能导致意外行为。
示例:
1 | console.log(42 == "42"); // true(字符串 "42" 转换为数字 42) |
2. ===
(严格相等)
- 功能:比较两个值是否相等,不允许类型转换。
- 行为:
- 如果两个值的类型不同,直接返回
false
。 - 如果类型相同,再比较它们的值。
- 如果两个值的类型不同,直接返回
示例:
1 | console.log(42 === "42"); // false(类型不同) |
3. Object.is()
- 功能:比较两个值是否相等,行为与
===
类似,但有以下不同:NaN
与NaN
相等。+0
与-0
不相等。
- 行为:
- 如果两个值的类型和值都相同,则返回
true
。 - 不进行类型转换。
- 如果两个值的类型和值都相同,则返回
示例:
1 | console.log(Object.is(42, "42")); // false(类型不同) |
4. 区别对比
特性 | == |
=== |
Object.is() |
---|---|---|---|
类型转换 | 允许类型转换 | 不允许类型转换 | 不允许类型转换 |
NaN 比较 |
NaN == NaN 返回 false |
NaN === NaN 返回 false |
Object.is(NaN, NaN) 返回 true |
+0 和 -0 比较 |
+0 == -0 返回 true |
+0 === -0 返回 true |
Object.is(+0, -0) 返回 false |
null 和 undefined 比较 |
null == undefined 返回 true |
null === undefined 返回 false |
Object.is(null, undefined) 返回 false |
5. 使用场景
==
的使用场景
- 需要宽松比较,允许类型转换。
- 例如:处理用户输入时,判断输入是否为特定值。
===
的使用场景
- 需要严格比较,不允许类型转换。
- 例如:在条件判断中,确保类型和值都匹配。
Object.is()
的使用场景
- 需要精确比较,尤其是处理
NaN
和+0
/-0
时。 - 例如:在算法或库中,确保比较结果精确无误。
6. 示例代码
1 | // == 示例 |
7. 总结
特性 | == |
=== |
Object.is() |
---|---|---|---|
类型转换 | 允许类型转换 | 不允许类型转换 | 不允许类型转换 |
NaN 比较 |
NaN == NaN 返回 false |
NaN === NaN 返回 false |
Object.is(NaN, NaN) 返回 true |
+0 和 -0 比较 |
+0 == -0 返回 true |
+0 === -0 返回 true |
Object.is(+0, -0) 返回 false |
null 和 undefined 比较 |
null == undefined 返回 true |
null === undefined 返回 false |
Object.is(null, undefined) 返回 false |
根据具体需求选择合适的比较方式,可以更好地处理 JavaScript 中的值比较。
什么是Javascript中的包装类型?
在 JavaScript 中,包装类型(Wrapper Types) 是指与原始类型(如 string
、number
、boolean
)对应的对象类型。它们分别是 String
、Number
和 Boolean
。包装类型的存在是为了让原始类型可以像对象一样调用方法和访问属性。
1. 包装类型的作用
JavaScript 中的原始类型(如 string
、number
、boolean
)本身不是对象,因此不能直接调用方法或访问属性。包装类型的作用是将原始类型包装成对象,使其可以调用方法和访问属性。
示例:
1 | const str = "Hello"; |
在上面的代码中,str
是一个原始类型的字符串,但它可以调用 length
属性和 toUpperCase()
方法。这是因为 JavaScript 在背后自动将原始类型包装成 String
对象。
2. 包装类型的自动转换
当对原始类型调用方法或访问属性时,JavaScript 会自动将其转换为对应的包装类型对象。这个过程称为 自动装箱(Autoboxing)。
示例:
1 | const num = 42; |
在上面的代码中,num
是一个原始类型的数字,但调用 toFixed()
方法时,JavaScript 会将其自动转换为 Number
对象。
3. 包装类型的手动创建
除了自动装箱,也可以手动创建包装类型的对象。
示例:
1 | const strObj = new String("Hello"); |
注意:手动创建的包装类型对象是对象类型,而不是原始类型。
4. 包装类型与原始类型的区别
特性 | 原始类型 | 包装类型 |
---|---|---|
类型 | string 、number 、boolean |
String 、Number 、Boolean |
存储方式 | 存储在栈内存中 | 存储在堆内存中 |
可变性 | 不可变 | 可变 |
比较方式 | 比较值是否相等 | 比较引用是否相等 |
示例 | const str = "Hello"; |
const strObj = new String("Hello"); |
示例:
1 | const str1 = "Hello"; |
5. 包装类型的注意事项
- 性能问题:频繁使用包装类型会创建大量临时对象,影响性能。建议优先使用原始类型。
- 类型检查:包装类型是对象,
typeof
返回"object"
,而原始类型返回"string"
、"number"
或"boolean"
。 - 隐式转换:在需要时,JavaScript 会自动将原始类型转换为包装类型,无需手动创建。
6. 总结
包装类型 | 对应的原始类型 | 示例 |
---|---|---|
String |
string |
new String("Hello") |
Number |
number |
new Number(42) |
Boolean |
boolean |
new Boolean(true) |
包装类型的存在是为了让原始类型可以像对象一样调用方法和访问属性。在大多数情况下,JavaScript 会自动完成原始类型到包装类型的转换,无需手动创建包装类型对象。
Javascript中如何进行隐式类型转换?
在 JavaScript 中,隐式类型转换是指在运行时自动将一种类型的值转换为另一种类型,而无需显式调用转换函数。隐式类型转换通常发生在以下场景中:
- 使用
==
进行比较时。 - 使用
+
、-
、*
、/
等运算符时。 - 在条件判断中(如
if
、while
等)。 - 在模板字符串中嵌入变量时。
以下是 JavaScript 中隐式类型转换的详细规则和示例:
1. ==
操作符的隐式类型转换
==
操作符在比较两个值时,会尝试将它们转换为相同类型后再比较。
转换规则:
- 如果一个是
number
,另一个是string
,会将string
转换为number
。 - 如果一个是
boolean
,会将boolean
转换为number
。 - 如果一个是
object
,会将object
转换为原始类型。
示例:
1 | console.log(42 == "42"); // true("42" 转换为 42) |
2. +
操作符的隐式类型转换
+
操作符在以下场景中会进行隐式类型转换:
- 如果其中一个操作数是
string
,会将另一个操作数转换为string
并拼接。 - 如果操作数不是
string
,会尝试将它们转换为number
并相加。
示例:
1 | console.log("Hello" + 42); // "Hello42"(42 转换为 "42") |
3. -
、*
、/
操作符的隐式类型转换
-
、*
、/
操作符会尝试将操作数转换为 number
后再运算。
示例:
1 | console.log("42" - 2); // 40("42" 转换为 42) |
4. 条件判断中的隐式类型转换
在条件判断中(如 if
、while
等),会将值隐式转换为布尔值。
转换规则:
- 假值(
false
、0
、""
、null
、undefined
、NaN
)会被转换为false
。 - 其他值会被转换为
true
。
示例:
1 | if ("Hello") { |
5. 模板字符串中的隐式类型转换
在模板字符串中嵌入变量时,会将变量隐式转换为字符串。
示例:
1 | const num = 42; |
6. 对象到原始类型的隐式转换
当对象参与运算或比较时,会尝试将对象转换为原始类型。
转换规则:
- 默认调用
valueOf()
方法,如果返回的不是原始类型,再调用toString()
方法。 - 如果对象是
Date
类型,优先调用toString()
方法。
示例:
1 | const obj = { |
7. 总结
场景 | 转换规则 |
---|---|
== 比较 |
尝试将两个值转换为相同类型后再比较。 |
+ 操作符 |
如果一个是 string ,转换为字符串拼接;否则转换为 number 相加。 |
- 、* 、/ 操作符 |
将操作数转换为 number 后再运算。 |
条件判断 | 将值转换为布尔值。 |
模板字符串 | 将变量转换为字符串。 |
对象到原始类型 | 调用 valueOf() 方法,如果返回的不是原始类型,再调用 toString() 方法。 |
8. 避免隐式类型转换
为了避免隐式类型转换带来的意外行为,可以:
- 使用
===
代替==
进行比较。 - 显式调用转换函数(如
Number()
、String()
、Boolean()
)。 - 在需要时使用
typeof
或instanceof
进行类型检查。
示例:
1 | console.log(Number("42") === 42); // true |
通过理解 JavaScript 中的隐式类型转换规则,可以更好地处理不同类型的数据并避免意外行为。
Javascript中+操作符什么时候用于字符串的拼接?
在 JavaScript 中,+
操作符用于字符串拼接的规则如下:
1. +
操作符的两种用途
- 数值加法:如果两个操作数都是
number
类型,+
操作符会执行加法运算。 - 字符串拼接:如果至少有一个操作数是
string
类型,+
操作符会执行字符串拼接。
2. +
操作符的隐式类型转换
当使用 +
操作符时,JavaScript 会按照以下规则进行隐式类型转换:
- 如果其中一个操作数是
string
类型,则将另一个操作数转换为string
类型,然后进行字符串拼接。 - 如果操作数不是
string
类型,则尝试将它们转换为number
类型,然后执行加法运算。
3. +
操作符用于字符串拼接的场景
以下是 +
操作符用于字符串拼接的具体场景:
(1) 显式的字符串拼接
当至少有一个操作数是 string
类型时,+
操作符会执行字符串拼接。
1 | console.log("Hello" + "World"); // "HelloWorld" |
(2) 隐式的字符串拼接
当操作数中包含 string
类型时,即使另一个操作数不是 string
类型,也会被隐式转换为 string
类型。
1 | console.log("Value: " + 42); // "Value: 42" |
(3) 混合类型的拼接
当操作数中包含 string
类型和其他类型时,+
操作符会优先执行字符串拼接。
1 | console.log(1 + 2 + "3"); // "33"(1 + 2 = 3,然后 "3" + "3" = "33") |
(4) 对象与字符串的拼接
当操作数中包含 object
类型时,会先将对象转换为 string
类型,然后进行拼接。
1 | console.log("Value: " + {}); // "Value: [object Object]" |
4. +
操作符用于数值加法的场景
当两个操作数都是 number
类型时,+
操作符会执行加法运算。
1 | console.log(1 + 2); // 3 |
5. 总结
场景 | 操作数类型 | 行为 |
---|---|---|
字符串拼接 | 至少有一个操作数是 string |
将另一个操作数转换为 string 并拼接。 |
数值加法 | 两个操作数都是 number |
执行加法运算。 |
混合类型 | 包含 string 和其他类型 |
优先执行字符串拼接。 |
6. 注意事项
- 隐式转换的优先级:如果操作数中包含
string
类型,+
操作符会优先执行字符串拼接。 - 避免意外行为:如果希望执行加法运算,确保操作数都是
number
类型;如果希望执行字符串拼接,确保至少有一个操作数是string
类型。
示例:
1 | // 加法运算 |
通过理解 +
操作符的行为,可以更好地处理字符串拼接和数值加法。
JavaScript中为什么会有BigInt的提案?JavaScript 引入 BigInt
的提案是为了解决 Number
类型的局限性,尤其是在处理大整数时。以下是 BigInt
提案的背景和原因:
1. Number
类型的局限性
JavaScript 中的 Number
类型使用 IEEE 754 双精度浮点数 表示,其有效整数范围是 -2^53 到 2^53(即 Number.MIN_SAFE_INTEGER
到 Number.MAX_SAFE_INTEGER
)。
问题:
- 精度丢失:超出
Number
范围的整数会丢失精度。1
console.log(9007199254740992 === 9007199254740993); // true(精度丢失)
- 无法表示大整数:
Number
类型无法准确表示超过Number.MAX_SAFE_INTEGER
的整数。
2. BigInt
的引入
BigInt
是一种新的原始类型,用于表示任意精度的整数。它的引入解决了 Number
类型的局限性。
特点:
- 无精度限制:
BigInt
可以表示任意大小的整数,没有精度丢失的问题。 - 独立类型:
BigInt
是 JavaScript 中的一种独立类型,与Number
类型不同。 - 字面量语法:
BigInt
字面量以n
结尾,例如123n
。
示例:
1 | const bigInt = 9007199254740993n; |
3. BigInt
的使用场景
(1) 大整数计算
BigInt
可以用于处理超出 Number
范围的整数计算。
1 | const a = 9007199254740993n; |
(2) 加密和哈希算法
加密和哈希算法通常需要处理非常大的整数,BigInt
提供了更好的支持。
1 | const hash = 1234567890123456789012345678901234567890n; |
(3) 金融计算
金融计算中需要高精度的整数运算,BigInt
可以避免浮点数精度问题。
1 | const amount = 1000000000000000000000000n; // 1 万亿亿 |
4. BigInt
的注意事项
不能与
Number
直接混合运算:1
console.log(1n + 2); // TypeError: Cannot mix BigInt and other types
需要使用显式类型转换:
1
console.log(1n + BigInt(2)); // 3n
不支持小数:
BigInt
只能表示整数,不能表示小数。1
console.log(1.5n); // SyntaxError
**JSON 序列化不支持
BigInt
**:JSON.stringify
无法序列化BigInt
,需要手动处理。1
2const data = { value: 123n };
console.log(JSON.stringify(data)); // TypeError: Do not know how to serialize a BigInt
5. BigInt
的提案背景
BigInt
的提案(TC39 Proposal)由 Daniel Ehrenberg 等人提出,旨在解决 JavaScript 在处理大整数时的局限性。提案经过多个阶段的讨论和测试,最终被纳入 ECMAScript 标准(ES2020)。
6. 总结
特性 | Number |
BigInt |
---|---|---|
整数范围 | -2^53 到 2^53 | 任意大小整数 |
精度 | 双精度浮点数,可能丢失精度 | 无精度限制 |
字面量语法 | 123 |
123n |
使用场景 | 普通数值计算 | 大整数计算、加密算法、金融计算等 |
BigInt
的引入填补了 JavaScript 在处理大整数方面的空白,为开发者提供了更强大的工具。
Object.assign和对象拓展运算符有什么区别?是深拷贝还是浅拷贝?
Object.assign
和对象拓展运算符(...
)都是 JavaScript 中用于对象合并的工具,但它们的行为和用途有一些区别。以下是它们的详细对比:
1. Object.assign
功能:
将源对象的属性复制到目标对象中。
语法:
1 | Object.assign(target, ...sources); |
特点:
- 浅拷贝:只会复制对象的可枚举属性,不会复制嵌套对象。
- 修改目标对象:会直接修改目标对象。
- 覆盖属性:如果目标对象和源对象有同名属性,源对象的属性会覆盖目标对象的属性。
示例:
1 | const target = { a: 1 }; |
2. 对象拓展运算符(...
)
功能:
将源对象的属性复制到新对象中。
语法:
1 | const result = { ...source1, ...source2 }; |
特点:
- 浅拷贝:只会复制对象的可枚举属性,不会复制嵌套对象。
- 创建新对象:不会修改现有对象,而是返回一个新对象。
- 覆盖属性:如果多个源对象有同名属性,后面的源对象会覆盖前面的属性。
示例:
1 | const source1 = { a: 1, c: { d: 3 } }; |
3. 区别对比
特性 | Object.assign |
对象拓展运算符(... ) |
---|---|---|
目标对象 | 修改目标对象 | 创建新对象 |
返回值 | 返回目标对象 | 返回新对象 |
浅拷贝 | 是 | 是 |
覆盖属性 | 后面的源对象覆盖前面的属性 | 后面的源对象覆盖前面的属性 |
语法 | Object.assign(target, ...sources) |
{ ...source1, ...source2 } |
4. 深拷贝 vs 浅拷贝
无论是 Object.assign
还是对象拓展运算符,它们都是 浅拷贝,即:
- 只会复制对象的可枚举属性。
- 如果属性是对象或数组,复制的是引用,而不是创建新的对象或数组。
示例:
1 | const source = { a: 1, b: { c: 2 } }; |
5. 如何实现深拷贝?
如果需要深拷贝对象,可以使用以下方法:
(1) JSON.parse(JSON.stringify(obj))
- 将对象转换为 JSON 字符串,再解析为新的对象。
- 缺点:无法复制函数、
undefined
、Symbol
等特殊值。
1 | const source = { a: 1, b: { c: 2 } }; |
(2) 递归实现深拷贝
- 手动实现递归函数,复制对象的每个属性。
1 | function deepClone(obj) { |
(3) 使用第三方库
- 使用
lodash
等第三方库实现深拷贝。
1 | const _ = require("lodash"); |
6. 总结
特性 | Object.assign |
对象拓展运算符(... ) |
---|---|---|
目标对象 | 修改目标对象 | 创建新对象 |
浅拷贝 | 是 | 是 |
适用场景 | 需要修改目标对象时 | 需要创建新对象时 |
无论是 Object.assign
还是对象拓展运算符,都是 浅拷贝。如果需要深拷贝,可以使用 JSON.parse(JSON.stringify(obj))
、递归实现或第三方库。
Javascript中Map和Object的区别是什么?
在 JavaScript 中,Map
和 Object
都可以用于存储键值对,但它们在设计和使用上有一些重要的区别。以下是 Map
和 Object
的主要区别:
1. 键的类型
- Map:键可以是任意类型的值(包括对象、函数、基本类型等)。
1
2
3
4const map = new Map();
const keyObj = {};
map.set(keyObj, "value");
console.log(map.get(keyObj)); // 输出: value - Object:键只能是字符串或 Symbol。如果使用其他类型作为键,它会被自动转换为字符串。
1
2
3
4const obj = {};
const keyObj = {};
obj[keyObj] = "value";
console.log(obj["[object Object]"]); // 输出: value
2. 键的顺序
- Map:键的顺序是确定的,即插入顺序。遍历
Map
时,键值对会按照插入的顺序返回。1
2
3
4const map = new Map();
map.set("a", 1);
map.set("b", 2);
console.log([...map]); // 输出: [['a', 1], ['b', 2]] - Object:在 ES6 之前,键的顺序是不确定的。从 ES6 开始,
Object
的键顺序遵循以下规则:- 数字键(如
"1"
)按升序排列。 - 字符串键按插入顺序排列。
- Symbol 键按插入顺序排列。
1
2const obj = { b: 2, a: 1, 1: "one" };
console.log(Object.keys(obj)); // 输出: ['1', 'b', 'a']
- 数字键(如
3. 大小
- Map:可以通过
size
属性直接获取键值对的数量。1
2
3
4const map = new Map();
map.set("a", 1);
map.set("b", 2);
console.log(map.size); // 输出: 2 - Object:需要手动计算键的数量。
1
2const obj = { a: 1, b: 2 };
console.log(Object.keys(obj).length); // 输出: 2
4. 性能
- Map:在频繁增删键值对的场景下,性能优于
Object
。 - Object:在静态键值对(键不常变化)的场景下,性能与
Map
相当或更好。
5. 默认行为
- Map:是一个纯粹的键值对集合,没有默认的键或方法。
- Object:继承了
Object.prototype
,因此默认包含一些方法和属性(如toString
、hasOwnProperty
等),这可能导致意外的行为。1
2const obj = {};
console.log(obj.toString); // 输出: [Function: toString]
6. 序列化
- Map:不能直接使用
JSON.stringify
序列化。需要先转换为数组或对象。1
2
3const map = new Map();
map.set("a", 1);
console.log(JSON.stringify([...map])); // 输出: [["a",1]] - Object:可以直接使用
JSON.stringify
序列化。1
2const obj = { a: 1 };
console.log(JSON.stringify(obj)); // 输出: {"a":1}
7. 迭代
- Map:可以直接使用
for...of
或forEach
进行迭代。1
2
3
4
5
6const map = new Map();
map.set("a", 1);
map.set("b", 2);
for (const [key, value] of map) {
console.log(key, value);
} - Object:需要使用
Object.keys
、Object.values
或Object.entries
进行迭代。1
2
3
4const obj = { a: 1, b: 2 };
for (const key of Object.keys(obj)) {
console.log(key, obj[key]);
}
8. 继承
- Map:是一个独立的类,不继承
Object.prototype
。 - Object:是 JavaScript 中所有对象的基类,继承了
Object.prototype
。
总结
- 使用 Map 的场景:
- 需要键为任意类型。
- 需要保持键的插入顺序。
- 需要频繁增删键值对。
- 使用 Object 的场景:
- 键是字符串或 Symbol。
- 需要直接序列化为 JSON。
- 需要与现有代码或 API 兼容。
根据具体需求选择合适的数据结构。
JavaScript中判断数据类型的方式有哪些?
在 JavaScript 中,判断数据类型的方式有多种,每种方式都有其特定的使用场景和局限性。以下是常见的判断数据类型的方法:
1. typeof
操作符
- 作用:返回一个字符串,表示未经计算的操作数的类型。
- 特点:
- 可以区分基本类型(如
number
、string
、boolean
、undefined
)。 - 对于
null
和引用类型(如object
、array
、function
),typeof
的返回值不够精确。
- 可以区分基本类型(如
- 示例:
1
2
3
4
5
6
7
8console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (历史遗留问题)
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof function () {}); // "function"
2. instanceof
操作符
- 作用:检查一个对象是否是某个构造函数的实例。
- 特点:
- 适用于判断引用类型(如
Array
、Object
、Function
)。 - 不能用于判断基本类型。
- 适用于判断引用类型(如
- 示例:
1
2
3
4console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function () {} instanceof Function); // true
console.log("hello" instanceof String); // false
3. Object.prototype.toString
方法
- 作用:返回一个表示对象的字符串,格式为
[object Type]
。 - 特点:
- 可以精确判断所有数据类型,包括基本类型和引用类型。
- 是最常用的类型判断方法之一。
- 示例:
1
2
3
4
5
6
7
8console.log(Object.prototype.toString.call(42)); // "[object Number]"
console.log(Object.prototype.toString.call("hello")); // "[object String]"
console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call({})); // "[object Object]"
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call(function () {})); // "[object Function]"
4. constructor
属性
- 作用:返回创建对象的构造函数的引用。
- 特点:
- 可以判断引用类型。
- 不适用于
null
和undefined
。 - 如果对象的原型链被修改,
constructor
可能不准确。
- 示例:
1
2
3
4
5
6console.log((42).constructor === Number); // true
console.log("hello".constructor === String); // true
console.log(true.constructor === Boolean); // true
console.log({}.constructor === Object); // true
console.log([].constructor === Array); // true
console.log(function () {}.constructor === Function); // true
5. Array.isArray
方法
- 作用:判断一个值是否是数组。
- 特点:
- 专门用于判断数组类型。
- 示例:
1
2console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
6. ===
严格相等操作符
- 作用:判断一个值是否是
null
或undefined
。 - 特点:
- 只能用于判断
null
和undefined
。
- 只能用于判断
- 示例:
1
2console.log(null === null); // true
console.log(undefined === undefined); // true
7. 自定义类型判断函数
- 作用:结合多种方法,实现更灵活的类型判断。
- 示例:
1
2
3
4
5
6
7
8
9function getType(value) {
if (value === null) return "null";
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}
console.log(getType(42)); // "number"
console.log(getType("hello")); // "string"
console.log(getType([])); // "array"
console.log(getType({})); // "object"
console.log(getType(null)); // "null"
总结
方法 | 适用场景 | 局限性 |
---|---|---|
typeof |
基本类型(除 null ) |
null 和引用类型无法精确区分 |
instanceof |
引用类型(如 Array 、Object ) |
不适用于基本类型 |
Object.prototype.toString |
所有类型(最常用) | 无 |
constructor |
引用类型 | 不适用于 null 和 undefined ,原型链修改后不准确 |
Array.isArray |
判断数组 | 仅适用于数组 |
=== |
判断 null 或 undefined |
仅适用于 null 和 undefined |
自定义函数 | 灵活组合多种方法 | 需要手动实现 |
根据具体需求选择合适的方法。对于大多数场景,Object.prototype.toString
是最推荐的方式。
JavaScript有哪些内置对象?
JavaScript 提供了许多内置对象,这些对象为开发者提供了丰富的功能和方法。以下是 JavaScript 中常见的内置对象及其分类:
1. 值类型包装对象
- **
Boolean
**:布尔值的包装对象。1
const bool = new Boolean(true);
- **
Number
**:数字的包装对象。1
const num = new Number(42);
- **
String
**:字符串的包装对象。1
const str = new String("hello");
2. 基础对象
- **
Object
**:所有对象的基类。1
const obj = new Object();
- **
Function
**:函数的构造函数。1
const func = new Function("a", "b", "return a + b");
- **
Array
**:数组的构造函数。1
const arr = new Array(1, 2, 3);
3. 错误对象
- **
Error
**:通用错误对象。1
const err = new Error("Something went wrong");
- **
SyntaxError
**:语法错误。1
const syntaxErr = new SyntaxError("Invalid syntax");
- **
TypeError
**:类型错误。1
const typeErr = new TypeError("Invalid type");
- **
ReferenceError
**:引用错误。1
const refErr = new ReferenceError("Undefined variable");
4. 数学和日期对象
- **
Math
**:提供数学常量和函数的对象。1
console.log(Math.PI); // 3.141592653589793
- **
Date
**:用于处理日期和时间的对象。1
const date = new Date();
5. 集合对象
- **
Set
**:存储唯一值的集合。1
const set = new Set([1, 2, 3]);
- **
Map
**:存储键值对的集合。1
2const map = new Map();
map.set("key", "value"); - **
WeakSet
**:存储弱引用对象的集合。1
const weakSet = new WeakSet();
- **
WeakMap
**:存储键为弱引用对象的键值对集合。1
const weakMap = new WeakMap();
6. 正则表达式对象
- **
RegExp
**:用于处理正则表达式的对象。1
const regex = new RegExp("abc");
7. JSON 对象
- **
JSON
**:用于解析和序列化 JSON 数据的对象。1
const json = JSON.stringify({ key: "value" });
8. Promise 对象
- **
Promise
**:用于处理异步操作的对象。1
2
3const promise = new Promise((resolve, reject) => {
resolve("Success!");
});
9. 代理和反射对象
- **
Proxy
**:用于定义自定义行为的对象。1
2
3
4
5const proxy = new Proxy({}, {
get(target, prop) {
return `Property ${prop} accessed`;
},
}); - **
Reflect
**:提供操作对象的方法。1
Reflect.set({}, "key", "value");
10. 其他内置对象
- **
Symbol
**:表示唯一值的基本类型。1
const sym = Symbol("unique");
- **
BigInt
**:用于表示大整数的基本类型。1
const bigInt = BigInt(9007199254740991);
11. 全局对象
- **
globalThis
**:指向全局对象(在浏览器中是window
,在 Node.js 中是global
)。1
console.log(globalThis);
总结
JavaScript 的内置对象涵盖了从基本类型到复杂数据结构的各个方面,为开发者提供了强大的功能支持。以下是一些常用的内置对象及其用途:
对象类别 | 常用对象 | 用途 |
---|---|---|
值类型包装对象 | Boolean 、Number 、String |
包装基本类型,提供额外方法 |
基础对象 | Object 、Function 、Array |
创建对象、函数和数组 |
错误对象 | Error 、TypeError |
处理运行时错误 |
数学和日期对象 | Math 、Date |
处理数学运算和日期时间 |
集合对象 | Set 、Map |
存储唯一值和键值对 |
正则表达式对象 | RegExp |
处理字符串匹配 |
JSON 对象 | JSON |
解析和序列化 JSON 数据 |
Promise 对象 | Promise |
处理异步操作 |
代理和反射对象 | Proxy 、Reflect |
自定义对象行为和操作对象 |
其他内置对象 | Symbol 、BigInt |
表示唯一值和大整数 |
全局对象 | globalThis |
指向全局对象 |
根据具体需求选择合适的内置对象,可以大大提高开发效率。
JavaScript中常用的正则表达式有哪些?
在 JavaScript 中,正则表达式(RegExp)是处理字符串匹配和替换的强大工具。以下是一些常用的正则表达式及其应用场景:
1. 匹配数字
- 匹配整数:
1
2
3const regex = /^\d+$/;
console.log(regex.test("123")); // true
console.log(regex.test("12a")); // false - 匹配浮点数:
1
2
3const regex = /^\d+\.\d+$/;
console.log(regex.test("12.34")); // true
console.log(regex.test("12")); // false
2. 匹配字母
- 匹配小写字母:
1
2
3const regex = /^[a-z]+$/;
console.log(regex.test("abc")); // true
console.log(regex.test("123")); // false - 匹配大写字母:
1
2
3const regex = /^[A-Z]+$/;
console.log(regex.test("ABC")); // true
console.log(regex.test("abc")); // false
3. 匹配字母和数字
- 匹配字母和数字的组合:
1
2
3const regex = /^[a-zA-Z0-9]+$/;
console.log(regex.test("abc123")); // true
console.log(regex.test("abc@123")); // false
4. 匹配邮箱地址
- 简单邮箱验证:
1
2
3const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
console.log(regex.test("test@example.com")); // true
console.log(regex.test("test@.com")); // false
5. 匹配 URL
- 简单 URL 验证:
1
2
3const regex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
console.log(regex.test("https://www.example.com")); // true
console.log(regex.test("example.com")); // true
6. 匹配手机号码
- 中国手机号码:
1
2
3const regex = /^1[3-9]\d{9}$/;
console.log(regex.test("13800138000")); // true
console.log(regex.test("12345678901")); // false
7. 匹配日期
- 匹配
YYYY-MM-DD
格式的日期:1
2
3const regex = /^\d{4}-\d{2}-\d{2}$/;
console.log(regex.test("2023-10-01")); // true
console.log(regex.test("2023/10/01")); // false
8. 匹配密码强度
- 密码至少包含一个大写字母、一个小写字母和一个数字,长度至少为 8:
1
2
3const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
console.log(regex.test("Password123")); // true
console.log(regex.test("password")); // false
9. 匹配 IP 地址
- 匹配 IPv4 地址:
1
2
3const regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
console.log(regex.test("192.168.1.1")); // true
console.log(regex.test("256.256.256.256")); // false
10. 匹配 HTML 标签
- 匹配简单的 HTML 标签:
1
2
3const regex = /<("[^"]*"|'[^']*'|[^'">])*>/;
console.log(regex.test("<div>Hello</div>")); // true
console.log(regex.test("Hello")); // false
11. 匹配空白行
- 匹配空白行:
1
2
3const regex = /^\s*$/;
console.log(regex.test(" ")); // true
console.log(regex.test("Hello")); // false
12. 匹配中文
- 匹配中文字符:
1
2
3const regex = /^[\u4e00-\u9fa5]+$/;
console.log(regex.test("中文")); // true
console.log(regex.test("English")); // false
13. 匹配文件扩展名
- 匹配
.jpg
或.png
文件:1
2
3const regex = /\.(jpg|png)$/i;
console.log(regex.test("image.jpg")); // true
console.log(regex.test("image.gif")); // false
14. 匹配身份证号码
- 匹配中国身份证号码:
1
2
3const regex = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
console.log(regex.test("110105199003071234")); // true
console.log(regex.test("123456789012345678")); // false
15. 匹配特殊字符
- 匹配特殊字符(如
@
、#
、$
等):1
2
3const regex = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/;
console.log(regex.test("hello@")); // true
console.log(regex.test("hello")); // false
总结
应用场景 | 正则表达式示例 | 说明 |
---|---|---|
匹配数字 | /^\d+$/ |
匹配整数 |
匹配字母 | /^[a-z]+$/ |
匹配小写字母 |
匹配邮箱地址 | /^[^\s@]+@[^\s@]+\.[^\s@]+$/ |
简单邮箱验证 |
匹配 URL | /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/ |
简单 URL 验证 |
匹配手机号码 | /^1[3-9]\d{9}$/ |
中国手机号码 |
匹配日期 | /^\d{4}-\d{2}-\d{2}$/ |
匹配 YYYY-MM-DD 格式的日期 |
匹配密码强度 | /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/ |
密码至少包含大小写字母和数字,长度至少为 8 |
匹配 IP 地址 | `/^(?:(?:25[0-5] | 2[0-4][0-9] |
匹配中文 | /^[\u4e00-\u9fa5]+$/ |
匹配中文字符 |
匹配文件扩展名 | `/.(jpg | png)$/i` |
根据具体需求选择合适的正则表达式,可以提高字符串处理的效率和准确性。
说说你对JSON的理解
JSON
(JavaScript Object Notation)是一种轻量级的数据交换格式,广泛用于前后端数据传输、配置文件、API 通信等场景。以下是对 JSON 的详细理解:
1. JSON 的基本概念
- 定义:JSON 是一种基于文本的格式,用于表示结构化数据。它源于 JavaScript 的对象字面量语法,但独立于编程语言。
- 特点:
- 轻量级:相比于 XML,JSON 的格式更简洁,数据量更小。
- 易读:采用键值对的形式,结构清晰,易于人类阅读和编写。
- 跨平台:几乎所有编程语言都支持 JSON 的解析和生成。
2. JSON 的语法规则
- 数据格式:
- 数据以键值对的形式表示,键和值之间用冒号
:
分隔。 - 键必须是字符串,用双引号
"
包裹。 - 值可以是字符串、数字、布尔值、数组、对象或
null
。 - 键值对之间用逗号
,
分隔。 - 对象用花括号
{}
包裹。 - 数组用方括号
[]
包裹。
- 数据以键值对的形式表示,键和值之间用冒号
- 示例:
1
2
3
4
5
6
7
8
9
10{
"name": "John",
"age": 30,
"isStudent": false,
"hobbies": ["reading", "coding"],
"address": {
"city": "New York",
"zip": "10001"
}
}
3. JSON 的数据类型
- 字符串(String):用双引号
"
包裹,例如"hello"
。 - 数字(Number):整数或浮点数,例如
42
或3.14
。 - 布尔值(Boolean):
true
或false
。 - 数组(Array):有序的值列表,用方括号
[]
包裹,例如[1, 2, 3]
。 - 对象(Object):无序的键值对集合,用花括号
{}
包裹,例如{"key": "value"}
。 - 空值(null):表示空值或缺失值。
4. JSON 的用途
- 前后端数据传输:JSON 是前后端通信的标准格式,常用于 API 的请求和响应。
1
2
3
4
5
6
7
8
9
10
11
12// 请求示例
{
"userId": 1,
"action": "getUserInfo"
}
// 响应示例
{
"userId": 1,
"name": "John",
"email": "john@example.com"
} - 配置文件:JSON 常用于存储配置信息,例如
package.json
文件。1
2
3
4
5
6
7{
"name": "my-project",
"version": "1.0.0",
"dependencies": {
"express": "^4.17.1"
}
} - 数据存储:JSON 可以作为轻量级数据库的存储格式,例如
JSON
文件或NoSQL
数据库(如 MongoDB)。
5. JSON 的解析与生成
- 在 JavaScript 中:
- 解析:使用
JSON.parse()
将 JSON 字符串转换为 JavaScript 对象。1
2
3const jsonStr = '{"name": "John", "age": 30}';
const obj = JSON.parse(jsonStr);
console.log(obj.name); // 输出: John - 生成:使用
JSON.stringify()
将 JavaScript 对象转换为 JSON 字符串。1
2
3const obj = { name: "John", age: 30 };
const jsonStr = JSON.stringify(obj);
console.log(jsonStr); // 输出: {"name":"John","age":30}
- 解析:使用
- 在其他语言中:
- Python:使用
json.loads()
和json.dumps()
。 - Java:使用
Jackson
或Gson
库。 - PHP:使用
json_decode()
和json_encode()
。
- Python:使用
6. JSON 的优点
- 易读性:结构清晰,便于人类阅读和编写。
- 高效性:数据量小,解析速度快。
- 通用性:几乎所有编程语言都支持 JSON。
- 灵活性:支持嵌套结构,可以表示复杂的数据。
7. JSON 的局限性
- 不支持注释:JSON 标准不支持注释,无法在数据中添加说明。
- 数据类型有限:JSON 仅支持字符串、数字、布尔值、数组、对象和
null
,不支持函数、日期等复杂类型。 - 严格语法:键必须用双引号
"
包裹,不能省略。
8. JSON 的扩展
- JSON5:JSON 的扩展格式,支持注释、单引号、尾随逗号等特性。
1
2
3
4
5{
// 注释
name: 'John',
age: 30,
} - JSON Schema:用于描述 JSON 数据结构的规范,常用于数据验证。
总结
JSON 是一种简单、高效、通用的数据交换格式,广泛应用于前后端通信、配置文件和数据存储等场景。它的易读性和跨平台特性使其成为现代开发中不可或缺的工具。尽管 JSON 有一些局限性,但其优点远大于缺点,是处理结构化数据的首选格式。
JavaScript脚本延迟加载的方式有哪些?
在 JavaScript 中,延迟加载脚本可以提高页面加载性能,尤其是在页面内容较多或脚本较大的情况下。以下是几种常见的延迟加载脚本的方式:
1. defer
属性
- 作用:使脚本在 HTML 文档解析完成后执行,但保持脚本的执行顺序。
- 特点:
- 脚本会在
DOMContentLoaded
事件之前执行。 - 多个带有
defer
属性的脚本会按照它们在文档中的顺序执行。
- 脚本会在
- 示例:
1
<script src="script.js" defer></script>
2. async
属性
- 作用:使脚本异步加载,加载完成后立即执行。
- 特点:
- 脚本的执行顺序不确定,取决于加载完成的时间。
- 适用于不依赖其他脚本的独立脚本。
- 示例:
1
<script src="script.js" async></script>
3. 动态加载脚本
- 作用:通过 JavaScript 动态创建
<script>
标签并插入到文档中。 - 特点:
- 可以灵活控制脚本加载的时机。
- 适用于需要条件加载的场景。
- 示例:
1
2
3const script = document.createElement("script");
script.src = "script.js";
document.body.appendChild(script);
4. setTimeout
或 setInterval
- 作用:通过定时器延迟加载脚本。
- 特点:
- 可以精确控制脚本加载的延迟时间。
- 适用于需要延迟执行的场景。
- 示例:
1
2
3
4
5setTimeout(() => {
const script = document.createElement("script");
script.src = "script.js";
document.body.appendChild(script);
}, 3000); // 延迟 3 秒加载
5. IntersectionObserver
- 作用:当脚本进入视口时加载。
- 特点:
- 适用于懒加载场景,只有当用户滚动到特定位置时才加载脚本。
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const script = document.createElement("script");
script.src = "script.js";
document.body.appendChild(script);
observer.disconnect(); // 停止观察
}
});
});
observer.observe(document.querySelector("#targetElement"));
6. load
事件
- 作用:在页面完全加载后(包括图片、样式等资源)加载脚本。
- 特点:
- 适用于需要在页面所有资源加载完成后执行的脚本。
- 示例:
1
2
3
4
5window.addEventListener("load", () => {
const script = document.createElement("script");
script.src = "script.js";
document.body.appendChild(script);
});
7. 模块化加载(ES Modules)
- 作用:使用
import()
动态加载 ES 模块。 - 特点:
- 适用于现代浏览器,支持按需加载模块。
- 示例:
1
2
3import("module.js").then((module) => {
module.default(); // 调用模块的默认导出
});
8. requestIdleCallback
- 作用:在浏览器空闲时加载脚本。
- 特点:
- 适用于低优先级的脚本,避免影响页面的关键渲染。
- 示例:
1
2
3
4
5requestIdleCallback(() => {
const script = document.createElement("script");
script.src = "script.js";
document.body.appendChild(script);
});
9. 使用 data-*
属性
- 作用:通过自定义属性标记需要延迟加载的脚本。
- 特点:
- 可以结合其他方法(如动态加载)实现延迟加载。
- 示例:
1
2
3
4
5
6
7
8
9<script data-src="script.js"></script>
<script>
window.addEventListener("load", () => {
const scripts = document.querySelectorAll("script[data-src]");
scripts.forEach((script) => {
script.src = script.getAttribute("data-src");
});
});
</script>
10. 第三方库
- 作用:使用第三方库(如
LazyLoad
)实现延迟加载。 - 特点:
- 提供更高级的功能和更好的兼容性。
- 示例:
1
2
3const lazyLoadInstance = new LazyLoad({
elements_selector: ".lazy",
});
总结
方法 | 适用场景 | 特点 |
---|---|---|
defer 属性 |
需要保持脚本执行顺序 | 脚本在 HTML 解析完成后执行 |
async 属性 |
独立脚本,不依赖其他脚本 | 脚本加载完成后立即执行,顺序不确定 |
动态加载脚本 | 需要灵活控制加载时机 | 可编程,适用于条件加载 |
setTimeout |
需要延迟指定时间加载 | 精确控制延迟时间 |
IntersectionObserver |
懒加载,当脚本进入视口时加载 | 提高性能,减少初始加载时间 |
load 事件 |
需要在页面所有资源加载完成后执行 | 确保脚本在所有资源加载后执行 |
模块化加载(ES Modules) | 现代浏览器,按需加载模块 | 支持动态导入模块 |
requestIdleCallback |
低优先级脚本,避免影响关键渲染 | 在浏览器空闲时加载 |
data-* 属性 |
结合动态加载实现延迟加载 | 灵活标记需要延迟加载的脚本 |
第三方库 | 需要高级功能和更好的兼容性 | 提供更丰富的功能和更好的浏览器支持 |
根据具体需求选择合适的延迟加载方式,可以有效优化页面性能,提升用户体验。
JavaScrpit脚本异步加载如何实现?各有什么区别?
在 JavaScript 中,异步加载脚本可以显著提高页面加载性能,尤其是在脚本较大或需要延迟加载的情况下。以下是常见的异步加载脚本的方式及其区别:
1. async
属性
- 作用:使脚本异步加载,加载完成后立即执行。
- 特点:
- 脚本的加载不会阻塞 HTML 解析。
- 脚本的执行顺序不确定,取决于加载完成的时间。
- 适用于不依赖其他脚本的独立脚本。
- 示例:
1
<script src="script.js" async></script>
2. defer
属性
- 作用:使脚本异步加载,但在 HTML 文档解析完成后按顺序执行。
- 特点:
- 脚本的加载不会阻塞 HTML 解析。
- 多个带有
defer
属性的脚本会按照它们在文档中的顺序执行。 - 适用于需要保持执行顺序的脚本。
- 示例:
1
<script src="script.js" defer></script>
3. 动态加载脚本
- 作用:通过 JavaScript 动态创建
<script>
标签并插入到文档中。 - 特点:
- 可以灵活控制脚本加载的时机。
- 适用于需要条件加载的场景。
- 示例:
1
2
3const script = document.createElement("script");
script.src = "script.js";
document.body.appendChild(script);
4. import()
动态导入(ES Modules)
- 作用:动态加载 ES 模块。
- 特点:
- 支持按需加载模块。
- 适用于现代浏览器。
- 示例:
1
2
3import("module.js").then((module) => {
module.default(); // 调用模块的默认导出
});
5. load
事件
- 作用:在页面完全加载后(包括图片、样式等资源)加载脚本。
- 特点:
- 适用于需要在页面所有资源加载完成后执行的脚本。
- 示例:
1
2
3
4
5window.addEventListener("load", () => {
const script = document.createElement("script");
script.src = "script.js";
document.body.appendChild(script);
});
6. IntersectionObserver
- 作用:当脚本进入视口时加载。
- 特点:
- 适用于懒加载场景,只有当用户滚动到特定位置时才加载脚本。
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const script = document.createElement("script");
script.src = "script.js";
document.body.appendChild(script);
observer.disconnect(); // 停止观察
}
});
});
observer.observe(document.querySelector("#targetElement"));
7. requestIdleCallback
- 作用:在浏览器空闲时加载脚本。
- 特点:
- 适用于低优先级的脚本,避免影响页面的关键渲染。
- 示例:
1
2
3
4
5requestIdleCallback(() => {
const script = document.createElement("script");
script.src = "script.js";
document.body.appendChild(script);
});
8. setTimeout
或 setInterval
- 作用:通过定时器延迟加载脚本。
- 特点:
- 可以精确控制脚本加载的延迟时间。
- 适用于需要延迟执行的场景。
- 示例:
1
2
3
4
5setTimeout(() => {
const script = document.createElement("script");
script.src = "script.js";
document.body.appendChild(script);
}, 3000); // 延迟 3 秒加载
9. 第三方库
- 作用:使用第三方库(如
LazyLoad
)实现异步加载。 - 特点:
- 提供更高级的功能和更好的兼容性。
- 示例:
1
2
3const lazyLoadInstance = new LazyLoad({
elements_selector: ".lazy",
});
各方式的区别
方法 | 加载时机 | 执行顺序 | 适用场景 |
---|---|---|---|
async 属性 |
脚本加载完成后立即执行 | 不确定 | 独立脚本,不依赖其他脚本 |
defer 属性 |
HTML 解析完成后执行 | 按文档顺序 | 需要保持执行顺序的脚本 |
动态加载脚本 | 可编程控制 | 不确定 | 需要灵活控制加载时机的脚本 |
import() 动态导入 |
按需加载模块 | 按模块依赖顺序 | 现代浏览器,按需加载模块 |
load 事件 |
页面所有资源加载完成后执行 | 不确定 | 需要在页面所有资源加载完成后执行的脚本 |
IntersectionObserver |
脚本进入视口时加载 | 不确定 | 懒加载,当脚本进入视口时加载 |
requestIdleCallback |
浏览器空闲时加载 | 不确定 | 低优先级脚本,避免影响关键渲染 |
setTimeout |
延迟指定时间后加载 | 不确定 | 需要延迟指定时间加载的脚本 |
第三方库 | 根据库的实现 | 根据库的实现 | 需要高级功能和更好的兼容性 |
总结
- 如果需要保持脚本的执行顺序,使用
defer
属性。 - 如果脚本是独立的,不依赖其他脚本,使用
async
属性。 - 如果需要灵活控制加载时机,使用动态加载脚本或
import()
动态导入。 - 如果需要懒加载,使用
IntersectionObserver
。 - 如果需要延迟指定时间加载,使用
setTimeout
。
根据具体需求选择合适的异步加载方式,可以有效优化页面性能,提升用户体验。
什么是JavaScript的类数组对象?如何转化为数组?
在 JavaScript 中,类数组对象(Array-like Object)是一种具有数组特性的对象,但它们并不是真正的数组。类数组对象通常具有以下特征:
- 具有
length
属性:表示对象的元素数量。 - 通过索引访问元素:可以通过数字索引(如
obj[0]
)访问元素。 - 不是数组:没有数组的方法(如
push
、pop
、slice
等)。
常见的类数组对象包括:
arguments
对象(在函数中使用的参数列表)。- DOM 元素集合(如
document.getElementsByTagName
返回的结果)。 - 字符串(可以通过索引访问字符)。
1. 类数组对象的示例
1 | // arguments 对象 |
2. 将类数组对象转化为数组
类数组对象没有数组的方法(如 map
、filter
、reduce
等),因此在实际开发中,通常需要将它们转化为真正的数组。以下是常见的转化方法:
方法 1:Array.from()
- 作用:将类数组对象或可迭代对象转化为数组。
- 示例:
1
2
3const argumentsObj = { 0: "a", 1: "b", 2: "c", length: 3 };
const arr = Array.from(argumentsObj);
console.log(arr); // ["a", "b", "c"]
方法 2:Array.prototype.slice.call()
- 作用:通过
slice
方法将类数组对象转化为数组。 - 示例:
1
2
3const argumentsObj = { 0: "a", 1: "b", 2: "c", length: 3 };
const arr = Array.prototype.slice.call(argumentsObj);
console.log(arr); // ["a", "b", "c"]
方法 3:扩展运算符(...
)
- 作用:将可迭代的类数组对象转化为数组。
- 示例:
1
2
3const argumentsObj = { 0: "a", 1: "b", 2: "c", length: 3 };
const arr = [...argumentsObj];
console.log(arr); // ["a", "b", "c"]
方法 4:手动遍历
- 作用:通过遍历类数组对象,将元素逐个添加到数组中。
- 示例:
1
2
3
4
5
6const argumentsObj = { 0: "a", 1: "b", 2: "c", length: 3 };
const arr = [];
for (let i = 0; i < argumentsObj.length; i++) {
arr.push(argumentsObj[i]);
}
console.log(arr); // ["a", "b", "c"]
3. 常见类数组对象的转化示例
arguments
对象
1 | function example() { |
DOM 元素集合
1 | const divs = document.getElementsByTagName("div"); |
字符串
1 | const str = "hello"; |
4. 类数组对象与数组的区别
特性 | 类数组对象 | 数组 |
---|---|---|
length 属性 |
有 | 有 |
索引访问 | 支持 | 支持 |
数组方法(如 push ) |
不支持 | 支持 |
可迭代性 | 部分支持(如 arguments ) |
支持 |
转化为数组 | 需要手动转化 | 无需转化 |
5. 总结
- 类数组对象:具有
length
属性和索引访问特性,但没有数组方法。 - 转化为数组:可以使用
Array.from()
、Array.prototype.slice.call()
、扩展运算符或手动遍历。 - 应用场景:在处理
arguments
、DOM 元素集合或字符串时,通常需要将类数组对象转化为数组,以便使用数组的方法。
通过将类数组对象转化为数组,可以更方便地操作数据,提升代码的可读性和效率。
JavaScript的数组有哪些原生方法?
JavaScript 提供了丰富的数组原生方法,用于操作和处理数组数据。以下是常见的数组方法及其分类:
1. 修改数组的方法
- **
push()
**:在数组末尾添加一个或多个元素,返回新数组的长度。1
2const arr = [1, 2];
arr.push(3); // arr: [1, 2, 3] - **
pop()
**:删除数组的最后一个元素,并返回该元素。1
2const arr = [1, 2, 3];
arr.pop(); // arr: [1, 2] - **
unshift()
**:在数组开头添加一个或多个元素,返回新数组的长度。1
2const arr = [1, 2];
arr.unshift(0); // arr: [0, 1, 2] - **
shift()
**:删除数组的第一个元素,并返回该元素。1
2const arr = [0, 1, 2];
arr.shift(); // arr: [1, 2] - **
splice()
**:在指定位置添加或删除元素,返回被删除的元素。1
2const arr = [1, 2, 3];
arr.splice(1, 1, 4); // arr: [1, 4, 3] - **
reverse()
**:反转数组的元素顺序。1
2const arr = [1, 2, 3];
arr.reverse(); // arr: [3, 2, 1] - **
sort()
**:对数组元素进行排序。1
2const arr = [3, 1, 2];
arr.sort(); // arr: [1, 2, 3]
2. 访问数组的方法
- **
concat()
**:合并两个或多个数组,返回新数组。1
2
3const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = arr1.concat(arr2); // arr3: [1, 2, 3, 4] - **
slice()
**:从数组中提取指定范围的元素,返回新数组。1
2const arr = [1, 2, 3, 4];
const newArr = arr.slice(1, 3); // newArr: [2, 3] - **
indexOf()
**:返回指定元素在数组中首次出现的索引,若不存在则返回-1
。1
2const arr = [1, 2, 3];
const index = arr.indexOf(2); // index: 1 - **
lastIndexOf()
**:返回指定元素在数组中最后一次出现的索引,若不存在则返回-1
。1
2const arr = [1, 2, 3, 2];
const index = arr.lastIndexOf(2); // index: 3 - **
includes()
**:判断数组是否包含指定元素,返回布尔值。1
2const arr = [1, 2, 3];
const result = arr.includes(2); // result: true
3. 遍历数组的方法
- **
forEach()
**:对数组中的每个元素执行指定操作。1
2const arr = [1, 2, 3];
arr.forEach((item) => console.log(item)); // 输出: 1, 2, 3 - **
map()
**:对数组中的每个元素执行指定操作,返回新数组。1
2const arr = [1, 2, 3];
const newArr = arr.map((item) => item * 2); // newArr: [2, 4, 6] - **
filter()
**:过滤数组中的元素,返回满足条件的新数组。1
2const arr = [1, 2, 3];
const newArr = arr.filter((item) => item > 1); // newArr: [2, 3] - **
reduce()
**:对数组中的元素进行累加操作,返回最终结果。1
2const arr = [1, 2, 3];
const result = arr.reduce((acc, item) => acc + item, 0); // result: 6 - **
reduceRight()
**:从右到左对数组中的元素进行累加操作,返回最终结果。1
2const arr = [1, 2, 3];
const result = arr.reduceRight((acc, item) => acc + item, 0); // result: 6 - **
every()
**:判断数组中的每个元素是否都满足条件,返回布尔值。1
2const arr = [1, 2, 3];
const result = arr.every((item) => item > 0); // result: true - **
some()
**:判断数组中是否有元素满足条件,返回布尔值。1
2const arr = [1, 2, 3];
const result = arr.some((item) => item > 2); // result: true - **
find()
**:返回数组中第一个满足条件的元素。1
2const arr = [1, 2, 3];
const result = arr.find((item) => item > 1); // result: 2 - **
findIndex()
**:返回数组中第一个满足条件的元素的索引。1
2const arr = [1, 2, 3];
const result = arr.findIndex((item) => item > 1); // result: 1
4. 其他方法
- **
join()
**:将数组中的所有元素拼接成一个字符串。1
2const arr = [1, 2, 3];
const str = arr.join("-"); // str: "1-2-3" - **
toString()
**:将数组转换为字符串。1
2const arr = [1, 2, 3];
const str = arr.toString(); // str: "1,2,3" - **
flat()
**:将多维数组扁平化。1
2const arr = [1, [2, [3]]];
const newArr = arr.flat(2); // newArr: [1, 2, 3] - **
flatMap()
**:先对数组中的每个元素执行操作,再将结果扁平化。1
2const arr = [1, 2, 3];
const newArr = arr.flatMap((item) => [item * 2]); // newArr: [2, 4, 6] - **
fill()
**:用指定值填充数组。1
2const arr = [1, 2, 3];
arr.fill(0); // arr: [0, 0, 0]
5. 静态方法
- **
Array.isArray()
**:判断一个值是否为数组。1
2const arr = [1, 2, 3];
const result = Array.isArray(arr); // result: true - **
Array.from()
**:将类数组对象或可迭代对象转化为数组。1
const arr = Array.from("hello"); // arr: ["h", "e", "l", "l", "o"]
- **
Array.of()
**:根据参数创建一个新数组。1
const arr = Array.of(1, 2, 3); // arr: [1, 2, 3]
总结
JavaScript 的数组方法可以分为以下几类:
- 修改数组:
push
、pop
、unshift
、shift
、splice
、reverse
、sort
。 - 访问数组:
concat
、slice
、indexOf
、lastIndexOf
、includes
。 - 遍历数组:
forEach
、map
、filter
、reduce
、reduceRight
、every
、some
、find
、findIndex
。 - 其他方法:
join
、toString
、flat
、flatMap
、fill
。 - 静态方法:
Array.isArray
、Array.from
、Array.of
。
这些方法为数组操作提供了强大的支持,可以满足各种开发需求。
为什么JavaScript函数的arguments参数是类数组而不是数组?如何遍历类数组?
JavaScript 中的 arguments
对象是类数组而不是真正的数组,这是由 JavaScript 的历史设计和语言特性决定的。以下是详细解释以及如何遍历类数组的方法:
1. 为什么 arguments
是类数组而不是数组?
历史原因
- 在 JavaScript 的早期版本中,数组的功能并不像现在这样强大,因此
arguments
被设计为类数组对象,而不是真正的数组。 arguments
的设计初衷是提供一个简单的方式来访问函数的所有参数,而不是作为一个功能齐全的数组。
性能考虑
- 类数组对象比真正的数组更轻量,因为它不需要实现数组的所有方法(如
push
、pop
、map
等)。 - 这种设计可以提高性能,尤其是在函数调用频繁的场景中。
灵活性
arguments
是动态的,它会随着函数参数的变化而变化。如果它是一个真正的数组,这种动态性可能会带来额外的复杂性。
语言一致性
- JavaScript 中有许多类数组对象(如 DOM 元素集合、字符串等),
arguments
的设计与这些对象保持一致。
2. arguments
的类数组特性
length
属性:表示参数的个数。- 索引访问:可以通过数字索引(如
arguments[0]
)访问参数。 - 不是数组:没有数组的方法(如
push
、map
、forEach
等)。
示例:
1 | function example(a, b, c) { |
3. 如何遍历类数组
方法 1:for
循环
- 使用传统的
for
循环遍历类数组对象。1
2
3
4
5
6function example() {
for (let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
example(1, 2, 3);
方法 2:for...of
循环
- 使用
for...of
循环遍历类数组对象(需要类数组对象是可迭代的)。1
2
3
4
5
6function example() {
for (const arg of arguments) {
console.log(arg);
}
}
example(1, 2, 3);
方法 3:Array.from()
- 将类数组对象转化为数组,然后使用数组的方法遍历。
1
2
3
4
5function example() {
const arr = Array.from(arguments);
arr.forEach((arg) => console.log(arg));
}
example(1, 2, 3);
方法 4:扩展运算符(...
)
- 使用扩展运算符将类数组对象转化为数组,然后遍历。
1
2
3
4
5function example() {
const arr = [...arguments];
arr.forEach((arg) => console.log(arg));
}
example(1, 2, 3);
方法 5:Array.prototype.forEach.call()
- 使用
Array.prototype.forEach
方法遍历类数组对象。1
2
3
4function example() {
Array.prototype.forEach.call(arguments, (arg) => console.log(arg));
}
example(1, 2, 3);
4. arguments
的替代方案
在现代 JavaScript 中,可以使用 剩余参数(rest parameters) 来替代 arguments
。剩余参数是一个真正的数组,支持所有数组方法。
示例:
1 | function example(...args) { |
5. 总结
arguments
是类数组:这是由 JavaScript 的历史设计和性能考虑决定的。- 遍历类数组:可以使用
for
循环、for...of
循环、Array.from()
、扩展运算符或Array.prototype.forEach.call()
。 - 替代方案:在现代开发中,推荐使用剩余参数(
...args
)替代arguments
,因为它是一个真正的数组,功能更强大。
通过理解 arguments
的设计原理和遍历方法,可以更好地处理函数参数,并编写更高效的代码。
什么是DOM和BOM?
在 JavaScript 中,DOM(Document Object Model)和 BOM(Browser Object Model)是两个重要的概念,它们分别用于操作网页内容和浏览器窗口。以下是它们的详细解释和区别:
1. DOM(Document Object Model)
- 定义:DOM 是 HTML 和 XML 文档的编程接口,它将文档解析为一个树形结构,每个节点都是一个对象,开发者可以通过 JavaScript 操作这些对象来动态修改网页内容。
- 作用:DOM 提供了对网页内容的访问和操作能力,例如修改元素、添加或删除节点、改变样式等。
- 核心对象:
document
:表示整个文档,是 DOM 树的根节点。Element
:表示 HTML 元素(如<div>
、<p>
等)。Node
:表示 DOM 树中的节点(包括元素、文本、注释等)。
- 常见操作:
- 获取元素:
1
2
3const element = document.getElementById("myId");
const elements = document.getElementsByClassName("myClass");
const elements = document.querySelectorAll("div.myClass"); - 修改内容:
1
2element.innerHTML = "Hello, World!";
element.textContent = "Hello, World!"; - 修改样式:
1
2element.style.color = "red";
element.classList.add("active"); - 添加或删除节点:
1
2
3const newElement = document.createElement("div");
element.appendChild(newElement);
element.removeChild(newElement);
- 获取元素:
2. BOM(Browser Object Model)
- 定义:BOM 是浏览器提供的编程接口,用于操作浏览器窗口和与浏览器交互。BOM 不是标准化的,不同浏览器的实现可能有所不同。
- 作用:BOM 提供了对浏览器窗口的控制能力,例如打开新窗口、获取屏幕尺寸、操作历史记录等。
- 核心对象:
window
:表示浏览器窗口,是 BOM 的顶层对象。navigator
:提供浏览器和操作系统的信息。location
:提供当前 URL 的信息,并可以操作 URL。history
:提供对浏览器历史记录的访问和操作。screen
:提供屏幕的尺寸和颜色深度等信息。
- 常见操作:
- 打开新窗口:
1
window.open("https://www.example.com");
- 获取屏幕尺寸:
1
2const width = screen.width;
const height = screen.height; - 操作 URL:
1
2console.log(location.href); // 获取当前 URL
location.href = "https://www.example.com"; // 跳转到新 URL - 操作历史记录:
1
2history.back(); // 返回上一页
history.forward(); // 前进到下一页
- 打开新窗口:
3. DOM 和 BOM 的区别
特性 | DOM | BOM |
---|---|---|
定义 | 操作网页内容的接口 | 操作浏览器窗口的接口 |
核心对象 | document 、Element 、Node |
window 、navigator 、location 、history 、screen |
标准化 | 是 W3C 标准 | 非标准化,浏览器实现可能不同 |
主要功能 | 动态修改网页内容 | 控制浏览器窗口和交互 |
示例 | 修改元素内容、样式、结构 | 打开新窗口、获取屏幕尺寸、操作 URL |
4. DOM 和 BOM 的关系
- 依赖关系:BOM 包含 DOM,
window
对象是 BOM 的顶层对象,而document
对象是window
的一个属性。 - 使用场景:
- DOM 用于操作网页内容,例如动态更新页面元素。
- BOM 用于与浏览器交互,例如控制窗口、获取浏览器信息。
5. 总结
- DOM:用于操作网页内容,核心对象是
document
,是标准化的接口。 - BOM:用于操作浏览器窗口,核心对象是
window
,是非标准化的接口。 - 关系:BOM 包含 DOM,
window
是 BOM 的顶层对象,document
是window
的属性。
通过理解 DOM 和 BOM 的区别和关系,可以更好地掌握 JavaScript 在网页开发和浏览器交互中的应用。
escape、encodeURI、endcodeURIComponent的区别是什么?
在 JavaScript 中,escape
、encodeURI
和 encodeURIComponent
都是用于编码字符串的函数,但它们的作用和使用场景有所不同。以下是它们的区别和具体用法:
1. escape
- 作用:对字符串进行编码,使其可以在 URL 中使用。注意:
escape
已被弃用,不推荐使用。 - 编码规则:
- 对 ASCII 字母、数字、
@
、*
、_
、+
、-
、.
、/
不编码。 - 对其他字符进行 Unicode 编码(如
%20
表示空格)。
- 对 ASCII 字母、数字、
- 示例:
1
2console.log(escape("Hello World!")); // "Hello%20World%21"
console.log(escape("你好")); // "%u4F60%u597D"
2. encodeURI
- 作用:对整个 URI 进行编码,使其可以在 URL 中使用。
- 编码规则:
- 对 URI 中的保留字符(如
:
、/
、?
、#
、@
等)不编码。 - 对其他字符进行 UTF-8 编码(如
%20
表示空格)。
- 对 URI 中的保留字符(如
- 使用场景:用于编码完整的 URI。
- 示例:
1
2console.log(encodeURI("https://www.example.com/你好")); // "https://www.example.com/%E4%BD%A0%E5%A5%BD"
console.log(encodeURI(":/?#[]@!$&'()*+,;=")); // ":/?#[]@!$&'()*+,;="
3. encodeURIComponent
- 作用:对 URI 的组件(如查询参数)进行编码,使其可以在 URL 中使用。
- 编码规则:
- 对 URI 中的保留字符(如
:
、/
、?
、#
、@
等)进行编码。 - 对其他字符进行 UTF-8 编码(如
%20
表示空格)。
- 对 URI 中的保留字符(如
- 使用场景:用于编码 URI 的组成部分(如查询参数、路径段等)。
- 示例:
1
2console.log(encodeURIComponent(":/?#[]@!$&'()*+,;=")); // "%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D"
console.log(encodeURIComponent("你好")); // "%E4%BD%A0%E5%A5%BD"
4. 区别对比
特性 | escape |
encodeURI |
encodeURIComponent |
---|---|---|---|
作用 | 编码字符串(已弃用) | 编码完整 URI | 编码 URI 组件 |
保留字符 | @ 、* 、_ 、+ 、- 、. 、/ |
: 、/ 、? 、# 、@ 、! 、$ 、& 、' 、( 、) 、* 、+ 、, 、; 、= |
无(对所有保留字符编码) |
编码规则 | Unicode 编码 | UTF-8 编码 | UTF-8 编码 |
使用场景 | 不推荐使用 | 编码完整 URI(如网址) | 编码 URI 组件(如查询参数) |
示例 | escape("Hello World!") → "Hello%20World%21" |
encodeURI("https://www.example.com/你好") → "https://www.example.com/%E4%BD%A0%E5%A5%BD" |
encodeURIComponent(":/?#[]@!$&'()*+,;=") → "%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D" |
5. 如何选择?
- 使用 **
encodeURI
**:当需要编码完整的 URI 时(如https://www.example.com/你好
)。 - 使用 **
encodeURIComponent
**:当需要编码 URI 的组成部分时(如查询参数name=你好
)。 - 避免使用 **
escape
**:因为它是非标准方法,已被弃用,且对 Unicode 字符的编码方式不一致。
6. 示例对比
1 | const url = "https://www.example.com/你好?name=张三&age=20"; |
7. 总结
- **
encodeURI
**:用于编码完整 URI,保留 URI 的保留字符。 - **
encodeURIComponent
**:用于编码 URI 的组成部分,对所有保留字符编码。 - **
escape
**:已弃用,不推荐使用。
根据具体需求选择合适的编码方法,可以确保 URL 的正确性和安全性。
什么是AJAX?如何实现一个AJAX请求
什么是 AJAX?
AJAX(Asynchronous JavaScript and XML)是一种用于创建异步 Web 应用的技术。它允许在不重新加载整个页面的情况下,与服务器进行数据交换并更新部分网页内容。AJAX 的核心是通过 JavaScript 发送 HTTP 请求,并在后台处理服务器返回的数据。
AJAX 的特点:
- 异步:请求在后台发送,不会阻塞页面加载。
- 无刷新更新:可以局部更新页面内容,提升用户体验。
- 数据格式灵活:支持多种数据格式,如 XML、JSON、HTML、纯文本等。
如何实现一个 AJAX 请求?
在 JavaScript 中,可以通过原生 XMLHttpRequest
对象或现代的 fetch
API 来实现 AJAX 请求。以下是两种方法的实现示例:
方法 1:使用 XMLHttpRequest
XMLHttpRequest
是传统的 AJAX 实现方式。
1 | // 1. 创建 XMLHttpRequest 对象 |
方法 2:使用 fetch
API
fetch
是现代的 AJAX 实现方式,基于 Promise,语法更简洁。
1 | // 1. 发送请求 |
AJAX 请求的步骤
无论是使用 XMLHttpRequest
还是 fetch
,AJAX 请求的基本步骤如下:
创建请求对象:
XMLHttpRequest
:new XMLHttpRequest()
fetch
:直接调用fetch()
函数。
设置请求方法和 URL:
- 指定请求方法(如
GET
、POST
)和目标 URL。
- 指定请求方法(如
设置请求头(可选):
- 例如,设置
Content-Type
为application/json
。
- 例如,设置
发送请求:
XMLHttpRequest
:调用send()
方法。fetch
:直接返回 Promise。
处理响应:
- 解析响应数据(如 JSON、XML)。
- 更新页面内容或执行其他操作。
处理错误:
- 捕获请求失败或网络错误。
AJAX 的常见应用场景
- 表单提交:异步提交表单数据,避免页面刷新。
- 动态加载内容:从服务器加载数据并更新页面部分内容。
- 实时搜索:根据用户输入实时搜索并显示结果。
- 分页加载:加载更多数据时无需刷新页面。
- 用户验证:检查用户名或邮箱是否已被注册。
AJAX 的优缺点
优点:
- 提升用户体验:无需刷新页面即可更新内容。
- 减少带宽消耗:只传输必要的数据,减少服务器负载。
- 异步处理:不会阻塞页面加载。
缺点:
- SEO 不友好:动态加载的内容可能无法被搜索引擎抓取。
- 复杂性增加:需要处理异步逻辑和错误处理。
- 安全性问题:可能引发跨站脚本攻击(XSS)或跨站请求伪造(CSRF)。
总结
- AJAX 是一种通过 JavaScript 发送异步 HTTP 请求的技术,用于实现无刷新更新页面内容。
- 可以通过
XMLHttpRequest
或fetch
API 实现 AJAX 请求。 fetch
是现代推荐的方式,语法简洁且基于 Promise。- AJAX 广泛应用于动态加载内容、表单提交、实时搜索等场景。
掌握 AJAX 技术可以显著提升 Web 应用的交互性和用户体验。
常见的DOM操作有哪些?
在 JavaScript 中,DOM 操作是指通过 JavaScript 动态访问和修改 HTML 文档的内容、结构和样式。以下是常见的 DOM 操作及其示例:
1. 获取元素
- **
getElementById
**:通过元素的id
获取元素。1
const element = document.getElementById("myId");
- **
getElementsByClassName
**:通过元素的class
获取元素集合。1
const elements = document.getElementsByClassName("myClass");
- **
getElementsByTagName
**:通过标签名获取元素集合。1
const elements = document.getElementsByTagName("div");
- **
querySelector
**:通过 CSS 选择器获取第一个匹配的元素。1
const element = document.querySelector(".myClass");
- **
querySelectorAll
**:通过 CSS 选择器获取所有匹配的元素集合。1
const elements = document.querySelectorAll("div.myClass");
2. 修改元素内容
- **
innerHTML
**:获取或设置元素的 HTML 内容。1
element.innerHTML = "<strong>Hello</strong> World!";
- **
textContent
**:获取或设置元素的文本内容。1
element.textContent = "Hello, World!";
- **
innerText
**:获取或设置元素的可见文本内容。1
element.innerText = "Hello, World!";
3. 修改元素属性
- **
getAttribute
**:获取元素的属性值。1
const value = element.getAttribute("href");
- **
setAttribute
**:设置元素的属性值。1
element.setAttribute("href", "https://www.example.com");
- **
removeAttribute
**:移除元素的属性。1
element.removeAttribute("href");
- **
classList
**:操作元素的类名。1
2
3element.classList.add("active"); // 添加类名
element.classList.remove("active"); // 移除类名
element.classList.toggle("active"); // 切换类名
4. 修改元素样式
- **
style
**:直接修改元素的样式。1
2element.style.color = "red";
element.style.backgroundColor = "yellow"; - **
className
**:设置元素的类名。1
element.className = "myClass";
5. 创建和插入元素
- **
createElement
**:创建新元素。1
const newElement = document.createElement("div");
- **
appendChild
**:将元素插入到父元素的末尾。1
parentElement.appendChild(newElement);
- **
insertBefore
**:将元素插入到指定元素之前。1
parentElement.insertBefore(newElement, referenceElement);
- **
replaceChild
**:替换子元素。1
parentElement.replaceChild(newElement, oldElement);
6. 删除元素
- **
removeChild
**:删除子元素。1
parentElement.removeChild(childElement);
- **
remove
**:直接删除元素。1
element.remove();
7. 遍历元素
- **
parentNode
**:获取父元素。1
const parent = element.parentNode;
- **
childNodes
**:获取所有子节点(包括文本节点和注释节点)。1
const children = element.childNodes;
- **
children
**:获取所有子元素(不包括文本节点和注释节点)。1
const children = element.children;
- **
firstChild
**:获取第一个子节点。1
const firstChild = element.firstChild;
- **
lastChild
**:获取最后一个子节点。1
const lastChild = element.lastChild;
8. 事件处理
- **
addEventListener
**:为元素添加事件监听器。1
2
3element.addEventListener("click", function () {
console.log("Element clicked!");
}); - **
removeEventListener
**:移除事件监听器。1
element.removeEventListener("click", handleClick);
9. 操作表单
- **
value
**:获取或设置表单元素的值。1
2const inputValue = inputElement.value;
inputElement.value = "New Value"; - **
checked
**:获取或设置复选框或单选按钮的选中状态。1
2const isChecked = checkboxElement.checked;
checkboxElement.checked = true;
10. 操作表格
- **
insertRow
**:在表格中插入新行。1
const newRow = tableElement.insertRow();
- **
insertCell
**:在行中插入新单元格。1
const newCell = newRow.insertCell();
总结
常见的 DOM 操作包括:
- 获取元素:
getElementById
、querySelector
等。 - 修改内容:
innerHTML
、textContent
等。 - 修改属性:
getAttribute
、setAttribute
等。 - 修改样式:
style
、classList
等。 - 创建和插入元素:
createElement
、appendChild
等。 - 删除元素:
removeChild
、remove
等。 - 遍历元素:
parentNode
、children
等。 - 事件处理:
addEventListener
、removeEventListener
等。 - 操作表单:
value
、checked
等。 - 操作表格:
insertRow
、insertCell
等。
掌握这些 DOM 操作可以让你更灵活地控制网页内容和行为,提升用户体验。
use strict是什么意思?使用它有什么区别?
"use strict"
是 JavaScript 中的一种指令,用于启用严格模式(Strict Mode)。严格模式是 ECMAScript 5(ES5)引入的一种特殊的 JavaScript 执行模式,旨在改进代码的安全性、可读性和性能。
1. 什么是严格模式?
严格模式对 JavaScript 的语法和行为进行了一些限制,避免了一些潜在的错误和不规范的写法。它可以帮助开发者编写更安全、更高效的代码。
2. 如何启用严格模式?
在脚本或函数的开头添加 "use strict";
即可启用严格模式。
全局启用
1 | ; |
局部启用(函数内部)
1 | function myFunction() { |
3. 严格模式的主要区别和限制
以下是严格模式与普通模式的主要区别:
1. 禁止隐式声明全局变量
在普通模式下,未使用 var
、let
或 const
声明的变量会自动成为全局变量。严格模式下会抛出错误。
1 | ; |
2. 禁止删除变量、函数或函数参数
在普通模式下,删除变量、函数或函数参数不会报错,但严格模式下会抛出错误。
1 | ; |
3. 禁止重复的属性名或参数名
在普通模式下,对象字面量或函数参数中允许重复的属性名或参数名,但严格模式下会抛出错误。
1 | ; |
4. 禁止使用 with
语句
with
语句在严格模式下被禁用,因为它会导致代码难以理解和优化。
1 | ; |
5. 禁止使用 eval
创建变量
在普通模式下,eval
可以创建变量,但在严格模式下,eval
创建的变量只能在 eval
内部使用。
1 | ; |
6. 禁止对只读属性赋值
在严格模式下,对只读属性(如 undefined
、NaN
等)赋值会抛出错误。
1 | ; |
7. 禁止使用 arguments.callee
和 arguments.caller
在严格模式下,arguments.callee
和 arguments.caller
被禁用,因为它们会降低代码的可读性和性能。
1 | ; |
8. this
的值为 undefined
在普通模式下,全局函数中的 this
指向全局对象(如 window
),但在严格模式下,this
的值为 undefined
。
1 | ; |
9. 禁止八进制字面量
在普通模式下,以 0
开头的数字会被解释为八进制,但在严格模式下会抛出错误。
1 | ; |
4. 严格模式的优点
- 更安全的代码:严格模式禁止了一些潜在的错误行为,如隐式声明全局变量。
- 更好的性能:严格模式禁用了某些难以优化的语法(如
with
语句),提高了代码的执行效率。 - 更清晰的代码:严格模式强制使用更规范的语法,提高了代码的可读性和可维护性。
5. 严格模式的缺点
- 兼容性问题:严格模式是 ES5 引入的特性,旧版浏览器可能不支持。
- 迁移成本:将现有代码迁移到严格模式可能需要修改部分代码。
6. 总结
"use strict"
用于启用严格模式,改进代码的安全性、可读性和性能。- 主要区别:严格模式禁止隐式声明全局变量、删除变量、重复属性名、
with
语句等。 - 优点:更安全、更高效、更清晰的代码。
- 缺点:兼容性问题和迁移成本。
在现代 JavaScript 开发中,建议始终使用严格模式,以避免潜在的错误并提高代码质量。
JavaScript如何判断一个对象是否属于某个类?
在 JavaScript 中,判断一个对象是否属于某个类可以通过以下几种方式实现:
1. instanceof
操作符
- 作用:检查对象是否是某个类的实例。
- 特点:
- 适用于构造函数创建的实例。
- 会检查对象的原型链。
- 示例:
1
2
3class MyClass {}
const obj = new MyClass();
console.log(obj instanceof MyClass); // true
2. constructor
属性
- 作用:通过对象的
constructor
属性检查其构造函数。 - 特点:
- 适用于构造函数创建的实例。
- 如果对象的原型链被修改,
constructor
可能不准确。
- 示例:
1
2
3class MyClass {}
const obj = new MyClass();
console.log(obj.constructor === MyClass); // true
3. Object.prototype.isPrototypeOf()
- 作用:检查对象的原型链中是否包含某个类的原型。
- 特点:
- 适用于任何对象。
- 会检查对象的原型链。
- 示例:
1
2
3class MyClass {}
const obj = new MyClass();
console.log(MyClass.prototype.isPrototypeOf(obj)); // true
4. Object.getPrototypeOf()
- 作用:获取对象的原型,并与类的原型进行比较。
- 特点:
- 适用于任何对象。
- 不会检查对象的原型链。
- 示例:
1
2
3class MyClass {}
const obj = new MyClass();
console.log(Object.getPrototypeOf(obj) === MyClass.prototype); // true
5. Symbol.hasInstance
- 作用:自定义
instanceof
的行为。 - 特点:
- 适用于需要自定义判断逻辑的场景。
- 示例:
1
2
3
4
5
6
7class MyClass {
static [Symbol.hasInstance](obj) {
return obj && obj.isMyClass;
}
}
const obj = { isMyClass: true };
console.log(obj instanceof MyClass); // true
6. typeof
和 Object.prototype.toString
- 作用:结合
typeof
和Object.prototype.toString
判断对象的类型。 - 特点:
- 适用于基本类型和内置对象。
- 示例:
1
2
3class MyClass {}
const obj = new MyClass();
console.log(Object.prototype.toString.call(obj) === "[object Object]"); // true
7. 自定义方法
- 作用:在类中定义方法来判断对象是否属于该类。
- 特点:
- 灵活,可以根据需要自定义逻辑。
- 示例:
1
2
3
4
5
6
7class MyClass {
static isMyClass(obj) {
return obj && obj.__proto__ === MyClass.prototype;
}
}
const obj = new MyClass();
console.log(MyClass.isMyClass(obj)); // true
总结
方法 | 适用场景 | 特点 |
---|---|---|
instanceof |
构造函数创建的实例 | 检查原型链 |
constructor |
构造函数创建的实例 | 直接比较构造函数 |
Object.prototype.isPrototypeOf() |
任何对象 | 检查原型链 |
Object.getPrototypeOf() |
任何对象 | 直接比较原型 |
Symbol.hasInstance |
自定义 instanceof 行为 |
灵活,可自定义判断逻辑 |
typeof 和 Object.prototype.toString |
基本类型和内置对象 | 适用于基本类型和内置对象 |
自定义方法 | 需要自定义逻辑的场景 | 灵活,可自定义逻辑 |
根据具体需求选择合适的方法,可以准确判断对象是否属于某个类。
ajax、axios、fetchd的区别是什么?
AJAX
、Axios
和 Fetch
都是用于在 JavaScript 中发送 HTTP 请求的技术,但它们在工作原理、使用方式和功能上有一些区别。以下是它们的详细对比:
1. AJAX(Asynchronous JavaScript and XML)
- 定义:AJAX 是一种技术概念,用于在不重新加载整个页面的情况下,通过 JavaScript 异步发送 HTTP 请求并更新部分网页内容。
- 实现方式:通常通过
XMLHttpRequest
对象实现。 - 特点:
- 原生支持,无需额外库。
- 语法较为复杂,需要手动处理请求和响应。
- 支持老版本浏览器。
- 示例:
1
2
3
4
5
6
7
8const xhr = new XMLHttpRequest();
xhr.open("GET", "https://jsonplaceholder.typicode.com/posts/1");
xhr.onload = function () {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.send();
2. Axios
- 定义:Axios 是一个基于 Promise 的 HTTP 客户端库,用于浏览器和 Node.js。
- 特点:
- 语法简洁,易于使用。
- 支持请求和响应拦截器。
- 自动转换 JSON 数据。
- 提供取消请求的功能。
- 支持浏览器和 Node.js。
- 示例:
1
2
3
4
5
6
7axios.get("https://jsonplaceholder.typicode.com/posts/1")
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.error(error);
});
3. Fetch
- 定义:Fetch 是原生的 JavaScript API,用于发送 HTTP 请求,基于 Promise 设计。
- 特点:
- 原生支持,无需额外库。
- 语法简洁,易于使用。
- 不支持老版本浏览器(如 IE)。
- 需要手动处理 JSON 数据。
- 不支持请求拦截器和取消请求(需结合
AbortController
实现取消功能)。
- 示例:
1
2
3
4
5
6
7
8fetch("https://jsonplaceholder.typicode.com/posts/1")
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error(error);
});
三者的对比
特性 | AJAX(XMLHttpRequest) | Axios | Fetch |
---|---|---|---|
语法 | 复杂 | 简洁 | 简洁 |
基于 Promise | 不支持 | 支持 | 支持 |
JSON 自动转换 | 不支持 | 支持 | 需要手动处理 |
拦截器 | 不支持 | 支持 | 不支持 |
取消请求 | 不支持 | 支持 | 需要结合 AbortController |
浏览器支持 | 支持所有浏览器 | 支持所有浏览器 | 不支持 IE |
Node.js 支持 | 不支持 | 支持 | 不支持 |
社区支持 | 原生,无社区支持 | 社区支持广泛 | 原生,社区支持较少 |
4. 如何选择?
- 使用 AJAX:
- 如果需要兼容老版本浏览器(如 IE)。
- 如果不想引入额外的库。
- 使用 Axios:
- 如果需要简洁的语法和强大的功能(如拦截器、取消请求)。
- 如果需要在浏览器和 Node.js 中使用。
- 使用 Fetch:
- 如果只需要现代浏览器的支持。
- 如果不想引入额外的库,并且可以接受手动处理 JSON 数据。
5. 总结
- AJAX 是一种技术概念,通常通过
XMLHttpRequest
实现,兼容性好但语法复杂。 - Axios 是一个功能强大的 HTTP 客户端库,语法简洁,支持拦截器和取消请求。
- Fetch 是原生的 HTTP 请求 API,语法简洁,但不支持老版本浏览器,且功能相对较少。
根据具体需求选择合适的技术,可以更高效地实现 HTTP 请求功能。
JavaScript数组的遍历方法有哪些?
JavaScript 提供了多种遍历数组的方法,每种方法都有其特定的使用场景和功能。以下是常见的数组遍历方法及其示例:
1. for
循环
- 特点:最基础的遍历方式,灵活且性能高。
- 示例:
1
2
3
4const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
2. for...of
循环
- 特点:用于遍历可迭代对象(如数组),语法简洁。
- 示例:
1
2
3
4const arr = [1, 2, 3];
for (const item of arr) {
console.log(item);
}
3. forEach
方法
- 特点:对数组中的每个元素执行回调函数,无返回值。
- 示例:
1
2
3
4const arr = [1, 2, 3];
arr.forEach((item) => {
console.log(item);
});
4. map
方法
- 特点:对数组中的每个元素执行回调函数,返回一个新数组。
- 示例:
1
2
3const arr = [1, 2, 3];
const newArr = arr.map((item) => item * 2);
console.log(newArr); // [2, 4, 6]
5. filter
方法
- 特点:对数组中的每个元素执行回调函数,返回满足条件的新数组。
- 示例:
1
2
3const arr = [1, 2, 3];
const newArr = arr.filter((item) => item > 1);
console.log(newArr); // [2, 3]
6. reduce
方法
- 特点:对数组中的每个元素执行回调函数,返回一个累积值。
- 示例:
1
2
3const arr = [1, 2, 3];
const sum = arr.reduce((acc, item) => acc + item, 0);
console.log(sum); // 6
7. reduceRight
方法
- 特点:从右到左对数组中的每个元素执行回调函数,返回一个累积值。
- 示例:
1
2
3const arr = [1, 2, 3];
const result = arr.reduceRight((acc, item) => acc + item, 0);
console.log(result); // 6
8. every
方法
- 特点:检查数组中的每个元素是否都满足条件,返回布尔值。
- 示例:
1
2
3const arr = [1, 2, 3];
const isAllPositive = arr.every((item) => item > 0);
console.log(isAllPositive); // true
9. some
方法
- 特点:检查数组中是否有元素满足条件,返回布尔值。
- 示例:
1
2
3const arr = [1, 2, 3];
const hasEvenNumber = arr.some((item) => item % 2 === 0);
console.log(hasEvenNumber); // true
10. find
方法
- 特点:返回数组中第一个满足条件的元素,若无则返回
undefined
。 - 示例:
1
2
3const arr = [1, 2, 3];
const result = arr.find((item) => item > 1);
console.log(result); // 2
11. findIndex
方法
- 特点:返回数组中第一个满足条件的元素的索引,若无则返回
-1
。 - 示例:
1
2
3const arr = [1, 2, 3];
const index = arr.findIndex((item) => item > 1);
console.log(index); // 1
12. keys
方法
- 特点:返回数组索引的迭代器。
- 示例:
1
2
3
4const arr = [1, 2, 3];
for (const index of arr.keys()) {
console.log(index); // 0, 1, 2
}
13. values
方法
- 特点:返回数组元素的迭代器。
- 示例:
1
2
3
4const arr = [1, 2, 3];
for (const item of arr.values()) {
console.log(item); // 1, 2, 3
}
14. entries
方法
- 特点:返回数组索引和元素的迭代器。
- 示例:
1
2
3
4const arr = [1, 2, 3];
for (const [index, item] of arr.entries()) {
console.log(index, item); // 0 1, 1 2, 2 3
}
15. for...in
循环
- 特点:遍历数组的索引(不推荐用于数组,更适合对象)。
- 示例:
1
2
3
4const arr = [1, 2, 3];
for (const index in arr) {
console.log(index); // 0, 1, 2
}
总结
方法 | 特点 | 返回值 |
---|---|---|
for 循环 |
灵活,性能高 | 无 |
for...of 循环 |
简洁,用于可迭代对象 | 无 |
forEach |
对每个元素执行回调函数 | 无 |
map |
对每个元素执行回调函数,返回新数组 | 新数组 |
filter |
返回满足条件的新数组 | 新数组 |
reduce |
返回累积值 | 累积值 |
reduceRight |
从右到左返回累积值 | 累积值 |
every |
检查是否所有元素都满足条件 | 布尔值 |
some |
检查是否有元素满足条件 | 布尔值 |
find |
返回第一个满足条件的元素 | 元素或 undefined |
findIndex |
返回第一个满足条件的元素的索引 | 索引或 -1 |
keys |
返回数组索引的迭代器 | 迭代器 |
values |
返回数组元素的迭代器 | 迭代器 |
entries |
返回数组索引和元素的迭代器 | 迭代器 |
for...in |
遍历数组索引(不推荐用于数组) | 无 |
根据具体需求选择合适的遍历方法,可以更高效地处理数组数据。
JavaScript的forEach和map方法有什么区别?
forEach
和 map
是 JavaScript 中用于遍历数组的两个常用方法,它们的主要区别在于返回值和使用场景。以下是它们的详细对比:
1. forEach
方法
- 作用:对数组中的每个元素执行回调函数。
- 返回值:
undefined
(无返回值)。 - 特点:
- 用于遍历数组,执行某些操作(如修改元素、打印日志等)。
- 不会改变原数组。
- 示例:
1
2
3
4const arr = [1, 2, 3];
arr.forEach((item) => {
console.log(item); // 输出: 1, 2, 3
});
2. map
方法
- 作用:对数组中的每个元素执行回调函数,并返回一个新数组。
- 返回值:新数组,包含回调函数的返回值。
- 特点:
- 用于将数组中的元素映射为新的值。
- 不会改变原数组。
- 示例:
1
2
3const arr = [1, 2, 3];
const newArr = arr.map((item) => item * 2);
console.log(newArr); // 输出: [2, 4, 6]
3. 区别对比
特性 | forEach |
map |
---|---|---|
返回值 | undefined |
新数组 |
是否改变原数组 | 否 | 否 |
使用场景 | 遍历数组,执行操作 | 将数组元素映射为新的值 |
性能 | 略高(不生成新数组) | 略低(生成新数组) |
链式调用 | 不支持 | 支持 |
4. 使用场景
**
forEach
**:- 用于遍历数组,执行某些操作(如修改元素、打印日志等)。
- 例如:
1
2
3
4const arr = [1, 2, 3];
arr.forEach((item) => {
console.log(item); // 输出: 1, 2, 3
});
**
map
**:- 用于将数组中的元素映射为新的值,并返回一个新数组。
- 例如:
1
2
3const arr = [1, 2, 3];
const newArr = arr.map((item) => item * 2);
console.log(newArr); // 输出: [2, 4, 6]
5. 性能对比
forEach
的性能略高于map
,因为它不需要生成新数组。- 如果只需要遍历数组而不需要返回值,建议使用
forEach
。
6. 链式调用
map
支持链式调用,可以与其他数组方法(如filter
、reduce
)结合使用。1
2
3
4
5const arr = [1, 2, 3];
const result = arr
.map((item) => item * 2)
.filter((item) => item > 2);
console.log(result); // 输出: [4, 6]forEach
不支持链式调用,因为它返回undefined
。
7. 总结
- **
forEach
**:用于遍历数组,执行某些操作,无返回值。 - **
map
**:用于将数组元素映射为新的值,返回新数组。
根据具体需求选择合适的方法:
- 如果需要执行操作而不需要返回值,使用
forEach
。 - 如果需要将数组元素映射为新的值,使用
map
。
mouseover和mouseenter事件的区别是什么?
mouseover
和 mouseenter
是 JavaScript 中用于处理鼠标移入事件的两种事件类型,它们的主要区别在于事件冒泡和触发时机。以下是它们的详细对比:
1. mouseover
事件
- 触发时机:当鼠标指针移动到元素或其子元素上时触发。
- 事件冒泡:会冒泡(bubble),即从子元素向父元素传递。
- 特点:
- 如果鼠标从父元素移动到子元素,会再次触发父元素的
mouseover
事件。 - 适用于需要监控元素及其子元素鼠标移入的场景。
- 如果鼠标从父元素移动到子元素,会再次触发父元素的
- 示例:
1
2
3
4
5
6
7
8
9<div id="parent">
<div id="child"></div>
</div>
<script>
const parent = document.getElementById("parent");
parent.addEventListener("mouseover", () => {
console.log("mouseover on parent");
});
</script>- 当鼠标从外部移动到
#parent
时,触发mouseover
。 - 当鼠标从
#parent
移动到#child
时,再次触发mouseover
。
- 当鼠标从外部移动到
2. mouseenter
事件
- 触发时机:当鼠标指针首次移动到元素上时触发。
- 事件冒泡:不会冒泡。
- 特点:
- 如果鼠标从父元素移动到子元素,不会再次触发父元素的
mouseenter
事件。 - 适用于只需要监控元素本身鼠标移入的场景。
- 如果鼠标从父元素移动到子元素,不会再次触发父元素的
- 示例:
1
2
3
4
5
6
7
8
9<div id="parent">
<div id="child"></div>
</div>
<script>
const parent = document.getElementById("parent");
parent.addEventListener("mouseenter", () => {
console.log("mouseenter on parent");
});
</script>- 当鼠标从外部移动到
#parent
时,触发mouseenter
。 - 当鼠标从
#parent
移动到#child
时,不会再次触发mouseenter
。
- 当鼠标从外部移动到
3. mouseout
和 mouseleave
事件
- **
mouseout
**:与mouseover
对应,当鼠标移出元素或其子元素时触发,会冒泡。 - **
mouseleave
**:与mouseenter
对应,当鼠标移出元素时触发,不会冒泡。
4. 对比总结
特性 | mouseover |
mouseenter |
---|---|---|
触发时机 | 鼠标移动到元素或其子元素时触发 | 鼠标首次移动到元素时触发 |
事件冒泡 | 会冒泡 | 不会冒泡 |
从父元素移动到子元素 | 会再次触发父元素的事件 | 不会再次触发父元素的事件 |
适用场景 | 需要监控元素及其子元素的鼠标移入 | 只需要监控元素本身的鼠标移入 |
5. 如何选择?
- 使用 **
mouseover
**:- 当需要监控元素及其子元素的鼠标移入事件时。
- 例如,实现一个包含子元素的复杂交互效果。
- 使用 **
mouseenter
**:- 当只需要监控元素本身的鼠标移入事件时。
- 例如,实现简单的悬停效果。
6. 示例对比
1 | <div id="parent" style="width: 200px; height: 200px; background: lightblue;"> |
结果分析
- 鼠标从外部移动到
#parent
时:- 触发
mouseover
和mouseenter
。
- 触发
- 鼠标从
#parent
移动到#child
时:- 触发
mouseover
,但不会触发mouseenter
。
- 触发
7. 总结
mouseover
和mouseenter
的主要区别在于事件冒泡和触发时机。mouseover
会监控元素及其子元素的鼠标移入,且会冒泡。mouseenter
只监控元素本身的鼠标移入,且不会冒泡。
根据具体需求选择合适的事件类型,可以更精确地处理鼠标移入事件。
JavaScript的==和===有什么区别?
在 JavaScript 中,==
和 ===
是用于比较两个值的操作符,它们的主要区别在于类型转换。以下是它们的详细对比:
1. ==
(宽松相等)
- 特点:
- 比较两个值时会进行隐式类型转换。
- 如果类型不同,会尝试将值转换为相同类型后再比较。
- 示例:
1
2
3
4console.log(1 == "1"); // true(字符串 "1" 转换为数字 1)
console.log(true == 1); // true(布尔值 true 转换为数字 1)
console.log(null == undefined); // true(特殊规则,null 和 undefined 相等)
console.log("" == 0); // true(空字符串转换为数字 0)
2. ===
(严格相等)
- 特点:
- 比较两个值时不会进行类型转换。
- 只有类型和值都相同时才返回
true
。
- 示例:
1
2
3
4console.log(1 === "1"); // false(类型不同)
console.log(true === 1); // false(类型不同)
console.log(null === undefined); // false(类型不同)
console.log("" === 0); // false(类型不同)
3. 比较规则
==
的隐式类型转换规则
- 如果类型相同,直接比较值。
- 如果类型不同:
- 数字与字符串:将字符串转换为数字。
- 布尔值与非布尔值:将布尔值转换为数字(
true
→1
,false
→0
)。 - 对象与基本类型:将对象转换为原始值(调用
valueOf
或toString
方法)。 null
和undefined
:相等。NaN
:与任何值(包括自身)都不相等。
===
的比较规则
- 如果类型不同,直接返回
false
。 - 如果类型相同,比较值:
- 数字:值相同则
true
。 - 字符串:字符序列相同则
true
。 - 布尔值:值相同则
true
。 - 对象:引用相同则
true
。 null
和undefined
:只有与自身相等。
- 数字:值相同则
4. 示例对比
表达式 | == 结果 |
=== 结果 |
原因 |
---|---|---|---|
1 == "1" |
true |
false |
"1" 转换为 1 |
true == 1 |
true |
false |
true 转换为 1 |
null == undefined |
true |
false |
特殊规则 |
"" == 0 |
true |
false |
"" 转换为 0 |
NaN == NaN |
false |
false |
NaN 与任何值都不相等 |
{} == {} |
false |
false |
对象比较引用,不同对象引用不同 |
"hello" == "hello" |
true |
true |
字符串值相同 |
5. 使用建议
**使用
===
**:- 在大多数情况下,推荐使用
===
,因为它更严格,可以避免因类型转换导致的意外行为。 - 例如:
1
2
3if (x === 10) {
// 只有当 x 是数字 10 时才执行
}
- 在大多数情况下,推荐使用
**使用
==
**:- 在需要类型转换的场景下使用,但要确保理解其隐式转换规则。
- 例如:
1
2
3if (x == null) {
// 当 x 是 null 或 undefined 时执行
}
6. 总结
特性 | == (宽松相等) |
=== (严格相等) |
---|---|---|
类型转换 | 是 | 否 |
比较规则 | 隐式转换后比较值 | 直接比较类型和值 |
使用场景 | 需要类型转换的场景 | 大多数场景,推荐使用 |
根据具体需求选择合适的操作符,可以避免因类型转换导致的意外行为,提高代码的可靠性和可读性。
JavaScript中substring和substr函数的区别是什么?
在 JavaScript 中,substring
和 substr
是用于提取字符串的两个函数,但它们的行为和参数有所不同。以下是它们的详细对比:
1. substring
函数
- 语法:
str.substring(startIndex, endIndex)
- 作用:提取字符串中从
startIndex
到endIndex
(不包括endIndex
)的子字符串。 - 参数:
startIndex
:起始索引(包含)。endIndex
:结束索引(不包含,可选)。
- 特点:
- 如果
startIndex
大于endIndex
,会自动交换两个参数。 - 如果参数为负数,会被视为
0
。
- 如果
- 示例:
1
2
3
4
5const str = "Hello, World!";
console.log(str.substring(0, 5)); // "Hello"
console.log(str.substring(7)); // "World!"
console.log(str.substring(7, 2)); // "llo"(自动交换参数)
console.log(str.substring(-3, 5)); // "Hello"(负数被视为 0)
2. substr
函数
- 语法:
str.substr(startIndex, length)
- 作用:提取字符串中从
startIndex
开始的指定长度的子字符串。 - 参数:
startIndex
:起始索引(包含)。length
:要提取的字符长度(可选)。
- 特点:
- 如果
startIndex
为负数,表示从字符串末尾开始计算。 - 如果
length
为负数或省略,则提取到字符串末尾。
- 如果
- 示例:
1
2
3
4
5const str = "Hello, World!";
console.log(str.substr(0, 5)); // "Hello"
console.log(str.substr(7)); // "World!"
console.log(str.substr(-6, 5)); // "World"(从末尾开始计算)
console.log(str.substr(7, -3)); // ""(长度为负数,返回空字符串)
3. 区别对比
特性 | substring |
substr |
---|---|---|
参数含义 | startIndex 和 endIndex |
startIndex 和 length |
参数处理 | 自动交换 startIndex 和 endIndex 如果 startIndex > endIndex |
支持负数 startIndex ,表示从末尾开始计算 |
负数处理 | 负数被视为 0 |
负数 startIndex 从末尾计算,负数 length 返回空字符串 |
返回值 | 从 startIndex 到 endIndex 的子字符串 |
从 startIndex 开始的指定长度的子字符串 |
4. 使用场景
**
substring
**:- 当你需要提取字符串的某一部分,并且知道起始和结束索引时。
- 例如:
1
2const str = "Hello, World!";
const result = str.substring(0, 5); // "Hello"
**
substr
**:- 当你需要从某个位置开始提取指定长度的子字符串时。
- 例如:
1
2const str = "Hello, World!";
const result = str.substr(7, 5); // "World"
5. 注意事项
substr
已弃用:substr
已被标记为弃用(deprecated),在未来的 JavaScript 版本中可能会被移除。建议使用substring
或slice
替代。- **推荐使用
slice
**:slice
函数的行为与substring
类似,但支持负数索引,且不会自动交换参数。
6. slice
函数
- 语法:
str.slice(startIndex, endIndex)
- 作用:提取字符串中从
startIndex
到endIndex
(不包括endIndex
)的子字符串。 - 参数:
startIndex
:起始索引(包含)。endIndex
:结束索引(不包含,可选)。
- 特点:
- 支持负数索引,表示从字符串末尾开始计算。
- 如果
startIndex
大于endIndex
,返回空字符串。
- 示例:
1
2
3
4
5const str = "Hello, World!";
console.log(str.slice(0, 5)); // "Hello"
console.log(str.slice(7)); // "World!"
console.log(str.slice(-6, -1)); // "World"
console.log(str.slice(7, 2)); // ""(startIndex > endIndex,返回空字符串)
7. 总结
特性 | substring |
substr |
slice |
---|---|---|---|
参数含义 | startIndex 和 endIndex |
startIndex 和 length |
startIndex 和 endIndex |
负数处理 | 负数被视为 0 |
支持负数 startIndex |
支持负数 startIndex 和 endIndex |
自动交换参数 | 是 | 否 | 否 |
推荐使用 | 是 | 否(已弃用) | 是 |
- **
substring
**:适合提取字符串的某一部分,且不需要负数索引。 - **
substr
**:已弃用,不推荐使用。 - **
slice
**:功能最强大,支持负数索引,推荐使用。
根据具体需求选择合适的函数,可以更高效地处理字符串操作。
JavaScript数组的map和forEach函数中能否通过break等语法结束循环?
在 JavaScript 中,map
和 forEach
函数是用于遍历数组的高阶函数,但它们不支持通过 break
、continue
或 return
等语法提前结束循环。以下是详细解释和替代方案:
1. map
函数
- 特点:
- 遍历数组中的每个元素,并对每个元素执行回调函数。
- 返回一个新数组,包含回调函数的返回值。
- **无法使用
break
或continue
**:map
函数会遍历整个数组,无法提前结束循环。- 在回调函数中使用
break
或continue
会抛出语法错误。
- 示例:
1
2
3
4
5
6
7
8
9const arr = [1, 2, 3];
const newArr = arr.map((item) => {
if (item === 2) {
// break; // 报错:Uncaught SyntaxError: Illegal break statement
// continue; // 报错:Uncaught SyntaxError: Illegal continue statement
}
return item * 2;
});
console.log(newArr); // [2, 4, 6]
2. forEach
函数
- 特点:
- 遍历数组中的每个元素,并对每个元素执行回调函数。
- 无返回值。
- **无法使用
break
或continue
**:forEach
函数会遍历整个数组,无法提前结束循环。- 在回调函数中使用
break
或continue
会抛出语法错误。
- 示例:
1
2
3
4
5
6
7
8const arr = [1, 2, 3];
arr.forEach((item) => {
if (item === 2) {
// break; // 报错:Uncaught SyntaxError: Illegal break statement
// continue; // 报错:Uncaught SyntaxError: Illegal continue statement
}
console.log(item); // 输出: 1, 2, 3
});
3. 替代方案
如果需要在遍历数组时提前结束循环,可以使用以下方法:
方法 1:for
循环
- 使用
for
循环,支持break
和continue
。 - 示例:
1
2
3
4
5
6
7const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
if (arr[i] === 2) {
break; // 提前结束循环
}
console.log(arr[i]); // 输出: 1
}
方法 2:for...of
循环
- 使用
for...of
循环,支持break
和continue
。 - 示例:
1
2
3
4
5
6
7const arr = [1, 2, 3];
for (const item of arr) {
if (item === 2) {
break; // 提前结束循环
}
console.log(item); // 输出: 1
}
方法 3:some
或 every
函数
some
:如果回调函数返回true
,则提前结束循环。every
:如果回调函数返回false
,则提前结束循环。- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const arr = [1, 2, 3];
// some
arr.some((item) => {
if (item === 2) {
return true; // 提前结束循环
}
console.log(item); // 输出: 1
return false;
});
// every
arr.every((item) => {
if (item === 2) {
return false; // 提前结束循环
}
console.log(item); // 输出: 1
return true;
});
4. 总结
方法 | 支持 break 和 continue |
特点 |
---|---|---|
map |
否 | 遍历整个数组,返回新数组 |
forEach |
否 | 遍历整个数组,无返回值 |
for 循环 |
是 | 支持 break 和 continue |
for...of 循环 |
是 | 支持 break 和 continue |
some 或 every |
是(通过返回值控制) | 提前结束循环,返回布尔值 |
- **
map
和forEach
**:无法使用break
或continue
,会遍历整个数组。 - 替代方案:使用
for
循环、for...of
循环或some
/every
函数,可以提前结束循环。
根据具体需求选择合适的方法,可以更灵活地控制循环行为。
JavaScript中如何合并对象?
在 JavaScript 中,合并对象是常见的操作,可以通过多种方式实现。以下是几种常见的合并对象的方法及其区别:
1. Object.assign
方法
- 作用:将一个或多个对象的属性复制到目标对象中。
- 特点:
- 浅拷贝(只复制对象的引用,不复制嵌套对象)。
- 会修改目标对象。
- 示例:
1
2
3
4const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = Object.assign({}, obj1, obj2);
console.log(merged); // { a: 1, b: 3, c: 4 }
2. 扩展运算符(...
)
- 作用:将一个或多个对象的属性展开并合并到新对象中。
- 特点:
- 浅拷贝。
- 不会修改原对象。
- 示例:
1
2
3
4const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 3, c: 4 }
3. JSON.parse
和 JSON.stringify
- 作用:通过序列化和反序列化实现深拷贝合并。
- 特点:
- 深拷贝(复制嵌套对象)。
- 不支持函数、
undefined
、Symbol
等特殊类型。
- 示例:
1
2
3
4const obj1 = { a: 1, b: { x: 1 } };
const obj2 = { b: { y: 2 }, c: 3 };
const merged = JSON.parse(JSON.stringify({ ...obj1, ...obj2 }));
console.log(merged); // { a: 1, b: { y: 2 }, c: 3 }
4. 递归合并
- 作用:递归合并对象的嵌套属性。
- 特点:
- 深拷贝。
- 需要手动实现递归逻辑。
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function deepMerge(target, source) {
for (const key in source) {
if (source[key] instanceof Object && key in target) {
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
const obj1 = { a: 1, b: { x: 1 } };
const obj2 = { b: { y: 2 }, c: 3 };
const merged = deepMerge({}, { ...obj1, ...obj2 });
console.log(merged); // { a: 1, b: { x: 1, y: 2 }, c: 3 }
5. 使用第三方库(如 Lodash)
- 作用:通过第三方库实现对象合并。
- 特点:
- 功能强大,支持浅拷贝和深拷贝。
- 需要引入第三方库。
- 示例:
1
2
3
4
5
6const _ = require("lodash");
const obj1 = { a: 1, b: { x: 1 } };
const obj2 = { b: { y: 2 }, c: 3 };
const merged = _.merge({}, obj1, obj2);
console.log(merged); // { a: 1, b: { x: 1, y: 2 }, c: 3 }
6. 对比总结
方法 | 浅拷贝/深拷贝 | 是否修改原对象 | 特点 |
---|---|---|---|
Object.assign |
浅拷贝 | 是(目标对象) | 简单易用,会修改目标对象 |
扩展运算符(... ) |
浅拷贝 | 否 | 语法简洁,不会修改原对象 |
JSON.parse 和 JSON.stringify |
深拷贝 | 否 | 深拷贝,不支持特殊类型 |
递归合并 | 深拷贝 | 否 | 灵活,需手动实现递归逻辑 |
第三方库(如 Lodash) | 深拷贝 | 否 | 功能强大,需引入第三方库 |
7. 使用建议
- 浅拷贝:使用
Object.assign
或扩展运算符(...
)。 - 深拷贝:使用递归合并或第三方库(如 Lodash)。
- 简单场景:优先使用扩展运算符(
...
),语法简洁。 - 复杂场景:使用递归合并或第三方库(如 Lodash)。
根据具体需求选择合适的方法,可以更高效地合并对象。
JavaScript如何判断一个对象是不是对象?
在 JavaScript 中,判断一个值是否为对象可以通过多种方式实现。以下是常见的方法及其区别:
1. typeof
操作符
- 作用:返回值的类型。
- 特点:
- 对于对象,
typeof
返回"object"
。 - 但
null
也会返回"object"
,这是一个历史遗留问题。
- 对于对象,
- 示例:
1
2
3const obj = {};
console.log(typeof obj); // "object"
console.log(typeof null); // "object"(注意:null 也会返回 "object")
2. Object.prototype.toString
方法
- 作用:返回对象的字符串表示。
- 特点:
- 对于对象,返回
"[object Object]"
。 - 可以区分
null
和undefined
。
- 对于对象,返回
- 示例:
1
2
3const obj = {};
console.log(Object.prototype.toString.call(obj)); // "[object Object]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
3. instanceof
操作符
- 作用:检查对象是否是某个构造函数的实例。
- 特点:
- 对于对象,
obj instanceof Object
返回true
。 - 但
null
和undefined
会返回false
。
- 对于对象,
- 示例:
1
2
3const obj = {};
console.log(obj instanceof Object); // true
console.log(null instanceof Object); // false
4. Object.prototype.constructor
属性
- 作用:检查对象的构造函数。
- 特点:
- 对于对象,
obj.constructor === Object
返回true
。 - 但
null
和undefined
会抛出错误。
- 对于对象,
- 示例:
1
2
3const obj = {};
console.log(obj.constructor === Object); // true
console.log(null.constructor === Object); // 报错:Uncaught TypeError: Cannot read property 'constructor' of null
5. Object
构造函数
- 作用:检查值是否为对象。
- 特点:
- 对于对象,
Object(obj) === obj
返回true
。 - 但
null
和undefined
会返回false
。
- 对于对象,
- 示例:
1
2
3const obj = {};
console.log(Object(obj) === obj); // true
console.log(Object(null) === null); // false
6. 自定义函数
- 作用:结合多种方法判断值是否为对象。
- 特点:
- 灵活,可以根据需要自定义逻辑。
- 示例:
1
2
3
4
5
6
7function isObject(value) {
return value !== null && typeof value === "object";
}
console.log(isObject({})); // true
console.log(isObject(null)); // false
console.log(isObject(undefined)); // false
7. 总结
方法 | 特点 | 适用场景 |
---|---|---|
typeof |
返回 "object" ,但 null 也返回 "object" |
简单判断,需排除 null |
Object.prototype.toString |
返回 "[object Object]" ,可区分 null |
精确判断对象类型 |
instanceof |
检查对象是否是 Object 的实例 |
判断对象是否为某个构造函数的实例 |
Object.prototype.constructor |
检查对象的构造函数 | 判断对象的构造函数 |
Object 构造函数 |
检查值是否为对象 | 判断值是否为对象 |
自定义函数 | 灵活,可自定义逻辑 | 需要精确判断的场景 |
8. 推荐方法
- 简单判断:
1
2
3function isObject(value) {
return value !== null && typeof value === "object";
} - 精确判断:
1
2
3function isObject(value) {
return Object.prototype.toString.call(value) === "[object Object]";
}
根据具体需求选择合适的方法,可以更准确地判断值是否为对象。
JavaScript的spice和slice函数回改变原数组吗?
在 JavaScript 中,splice
和 slice
是用于操作数组的两个方法,但它们的行为和效果不同。以下是它们的详细对比:
1. splice
方法
- 作用:从数组中添加或删除元素。
- 语法:
array.splice(startIndex, deleteCount, item1, item2, ...)
- 特点:
- 会改变原数组。
- 可以添加、删除或替换元素。
- 返回被删除的元素组成的数组。
- 示例:
1
2
3
4const arr = [1, 2, 3, 4, 5];
const deleted = arr.splice(1, 2, 6, 7); // 从索引 1 开始,删除 2 个元素,并插入 6 和 7
console.log(arr); // [1, 6, 7, 4, 5](原数组被修改)
console.log(deleted); // [2, 3](返回被删除的元素)
2. slice
方法
- 作用:从数组中提取指定范围的元素。
- 语法:
array.slice(startIndex, endIndex)
- 特点:
- 不会改变原数组。
- 提取的元素包括
startIndex
,不包括endIndex
。 - 返回一个新数组。
- 示例:
1
2
3
4const arr = [1, 2, 3, 4, 5];
const newArr = arr.slice(1, 3); // 提取索引 1 到 3 的元素(不包括 3)
console.log(arr); // [1, 2, 3, 4, 5](原数组未被修改)
console.log(newArr); // [2, 3](返回新数组)
3. 对比总结
方法 | 是否改变原数组 | 作用 | 返回值 |
---|---|---|---|
splice |
是 | 添加、删除或替换元素 | 被删除的元素组成的数组 |
slice |
否 | 提取指定范围的元素 | 新数组 |
4. 使用场景
**
splice
**:- 当你需要修改原数组时(如删除、插入或替换元素)。
- 例如:
1
2
3const arr = [1, 2, 3, 4, 5];
arr.splice(2, 1); // 删除索引 2 的元素
console.log(arr); // [1, 2, 4, 5]
**
slice
**:- 当你需要提取数组的一部分而不修改原数组时。
- 例如:
1
2
3const arr = [1, 2, 3, 4, 5];
const newArr = arr.slice(1, 4); // 提取索引 1 到 4 的元素
console.log(newArr); // [2, 3, 4]
5. 总结
- **
splice
**:会改变原数组,用于添加、删除或替换元素。 - **
slice
**:不会改变原数组,用于提取数组的一部分。
根据具体需求选择合适的方法,可以更高效地操作数组。
JavaScript中怎么删除数组最后一个元素?
在 JavaScript 中,删除数组的最后一个元素有多种方法。以下是常见的几种方式及其示例:
1. pop
方法
- 作用:删除数组的最后一个元素,并返回该元素。
- 特点:
- 会修改原数组。
- 返回被删除的元素。
- 示例:
1
2
3
4const arr = [1, 2, 3];
const lastElement = arr.pop();
console.log(arr); // [1, 2]
console.log(lastElement); // 3
2. 修改 length
属性
- 作用:通过减少数组的
length
属性来删除最后一个元素。 - 特点:
- 会修改原数组。
- 不会返回被删除的元素。
- 示例:
1
2
3const arr = [1, 2, 3];
arr.length = arr.length - 1;
console.log(arr); // [1, 2]
3. slice
方法
- 作用:创建一个新数组,不包含最后一个元素。
- 特点:
- 不会修改原数组。
- 返回新数组。
- 示例:
1
2
3
4const arr = [1, 2, 3];
const newArr = arr.slice(0, -1);
console.log(arr); // [1, 2, 3](原数组未被修改)
console.log(newArr); // [1, 2]
4. splice
方法
- 作用:从数组中删除最后一个元素。
- 特点:
- 会修改原数组。
- 返回被删除的元素组成的数组。
- 示例:
1
2
3
4const arr = [1, 2, 3];
const deleted = arr.splice(-1, 1);
console.log(arr); // [1, 2]
console.log(deleted); // [3]
5. 扩展运算符(...
)
- 作用:创建一个新数组,不包含最后一个元素。
- 特点:
- 不会修改原数组。
- 返回新数组。
- 示例:
1
2
3const arr = [1, 2, 3];
const [lastElement, ...newArr] = arr.reverse();
console.log(newArr.reverse()); // [1, 2]
6. filter
方法
- 作用:创建一个新数组,不包含最后一个元素。
- 特点:
- 不会修改原数组。
- 返回新数组。
- 示例:
1
2
3
4const arr = [1, 2, 3];
const newArr = arr.filter((_, index) => index !== arr.length - 1);
console.log(arr); // [1, 2, 3](原数组未被修改)
console.log(newArr); // [1, 2]
7. 总结
方法 | 是否修改原数组 | 返回值 | 特点 |
---|---|---|---|
pop |
是 | 被删除的元素 | 直接删除最后一个元素 |
修改 length 属性 |
是 | 无 | 通过减少 length 删除最后一个元素 |
slice |
否 | 新数组 | 创建新数组,不修改原数组 |
splice |
是 | 被删除的元素组成的数组 | 从指定位置删除元素 |
扩展运算符(... ) |
否 | 新数组 | 创建新数组,不修改原数组 |
filter |
否 | 新数组 | 创建新数组,不修改原数组 |
8. 推荐方法
- 如果需要修改原数组:使用
pop
或splice
。 - 如果不希望修改原数组:使用
slice
或filter
。
根据具体需求选择合适的方法,可以更高效地删除数组的最后一个元素。
在 JavaScript 中,删除数组的最后一个元素有多种方法。以下是常见的几种方式及其示例:
1. pop
方法
- 作用:删除数组的最后一个元素,并返回该元素。
- 特点:
- 会修改原数组。
- 返回被删除的元素。
- 示例:
1
2
3
4const arr = [1, 2, 3];
const lastElement = arr.pop();
console.log(arr); // [1, 2]
console.log(lastElement); // 3
2. 修改 length
属性
- 作用:通过减少数组的
length
属性来删除最后一个元素。 - 特点:
- 会修改原数组。
- 不会返回被删除的元素。
- 示例:
1
2
3const arr = [1, 2, 3];
arr.length = arr.length - 1;
console.log(arr); // [1, 2]
3. slice
方法
- 作用:创建一个新数组,不包含最后一个元素。
- 特点:
- 不会修改原数组。
- 返回新数组。
- 示例:
1
2
3
4const arr = [1, 2, 3];
const newArr = arr.slice(0, -1);
console.log(arr); // [1, 2, 3](原数组未被修改)
console.log(newArr); // [1, 2]
4. splice
方法
- 作用:从数组中删除最后一个元素。
- 特点:
- 会修改原数组。
- 返回被删除的元素组成的数组。
- 示例:
1
2
3
4const arr = [1, 2, 3];
const deleted = arr.splice(-1, 1);
console.log(arr); // [1, 2]
console.log(deleted); // [3]
5. 扩展运算符(...
)
- 作用:创建一个新数组,不包含最后一个元素。
- 特点:
- 不会修改原数组。
- 返回新数组。
- 示例:
1
2
3const arr = [1, 2, 3];
const [lastElement, ...newArr] = arr.reverse();
console.log(newArr.reverse()); // [1, 2]
6. filter
方法
- 作用:创建一个新数组,不包含最后一个元素。
- 特点:
- 不会修改原数组。
- 返回新数组。
- 示例:
1
2
3
4const arr = [1, 2, 3];
const newArr = arr.filter((_, index) => index !== arr.length - 1);
console.log(arr); // [1, 2, 3](原数组未被修改)
console.log(newArr); // [1, 2]
7. 总结
方法 | 是否修改原数组 | 返回值 | 特点 |
---|---|---|---|
pop |
是 | 被删除的元素 | 直接删除最后一个元素 |
修改 length 属性 |
是 | 无 | 通过减少 length 删除最后一个元素 |
slice |
否 | 新数组 | 创建新数组,不修改原数组 |
splice |
是 | 被删除的元素组成的数组 | 从指定位置删除元素 |
扩展运算符(... ) |
否 | 新数组 | 创建新数组,不修改原数组 |
filter |
否 | 新数组 | 创建新数组,不修改原数组 |
8. 推荐方法
- 如果需要修改原数组:使用
pop
或splice
。 - 如果不希望修改原数组:使用
slice
或filter
。
根据具体需求选择合适的方法,可以更高效地删除数组的最后一个元素。
JavaScript操作数组元素的方法有哪些?
在 JavaScript 中,操作数组元素的方法非常丰富,涵盖了添加、删除、修改、查找、遍历等多种操作。以下是常见的数组操作方法及其分类:
1. 添加元素
- **
push
**:在数组末尾添加一个或多个元素。1
2const arr = [1, 2];
arr.push(3); // [1, 2, 3] - **
unshift
**:在数组开头添加一个或多个元素。1
2const arr = [1, 2];
arr.unshift(0); // [0, 1, 2] - **
splice
**:在指定位置添加或删除元素。1
2const arr = [1, 2, 3];
arr.splice(1, 0, 4); // [1, 4, 2, 3]
2. 删除元素
- **
pop
**:删除数组的最后一个元素。1
2const arr = [1, 2, 3];
arr.pop(); // [1, 2] - **
shift
**:删除数组的第一个元素。1
2const arr = [1, 2, 3];
arr.shift(); // [2, 3] - **
splice
**:从指定位置删除元素。1
2const arr = [1, 2, 3];
arr.splice(1, 1); // [1, 3]
3. 修改元素
- 直接赋值:通过索引修改元素。
1
2const arr = [1, 2, 3];
arr[1] = 4; // [1, 4, 3] - **
splice
**:替换指定位置的元素。1
2const arr = [1, 2, 3];
arr.splice(1, 1, 4); // [1, 4, 3]
4. 查找元素
- **
indexOf
**:返回元素首次出现的索引,若不存在则返回-1
。1
2const arr = [1, 2, 3];
console.log(arr.indexOf(2)); // 1 - **
lastIndexOf
**:返回元素最后一次出现的索引,若不存在则返回-1
。1
2const arr = [1, 2, 3, 2];
console.log(arr.lastIndexOf(2)); // 3 - **
includes
**:检查数组是否包含某个元素,返回布尔值。1
2const arr = [1, 2, 3];
console.log(arr.includes(2)); // true - **
find
**:返回第一个满足条件的元素。1
2const arr = [1, 2, 3];
console.log(arr.find((item) => item > 1)); // 2 - **
findIndex
**:返回第一个满足条件的元素的索引。1
2const arr = [1, 2, 3];
console.log(arr.findIndex((item) => item > 1)); // 1
5. 遍历数组
- **
forEach
**:对数组中的每个元素执行回调函数。1
2const arr = [1, 2, 3];
arr.forEach((item) => console.log(item)); // 1, 2, 3 - **
map
**:对数组中的每个元素执行回调函数,返回新数组。1
2const arr = [1, 2, 3];
const newArr = arr.map((item) => item * 2); // [2, 4, 6] - **
filter
**:返回满足条件的新数组。1
2const arr = [1, 2, 3];
const newArr = arr.filter((item) => item > 1); // [2, 3] - **
reduce
**:对数组中的每个元素执行回调函数,返回累积值。1
2const arr = [1, 2, 3];
const sum = arr.reduce((acc, item) => acc + item, 0); // 6 - **
reduceRight
**:从右到左对数组中的每个元素执行回调函数,返回累积值。1
2const arr = [1, 2, 3];
const result = arr.reduceRight((acc, item) => acc + item, 0); // 6
6. 排序和反转
- **
sort
**:对数组进行排序。1
2const arr = [3, 1, 2];
arr.sort(); // [1, 2, 3] - **
reverse
**:反转数组的顺序。1
2const arr = [1, 2, 3];
arr.reverse(); // [3, 2, 1]
7. 合并和拆分
- **
concat
**:合并两个或多个数组,返回新数组。1
2
3const arr1 = [1, 2];
const arr2 = [3, 4];
const newArr = arr1.concat(arr2); // [1, 2, 3, 4] - **
slice
**:提取数组的一部分,返回新数组。1
2const arr = [1, 2, 3];
const newArr = arr.slice(1, 3); // [2, 3] - **
join
**:将数组元素拼接成字符串。1
2const arr = [1, 2, 3];
const str = arr.join("-"); // "1-2-3"
8. 其他操作
- **
fill
**:用指定值填充数组。1
2const arr = [1, 2, 3];
arr.fill(0); // [0, 0, 0] - **
flat
**:将多维数组扁平化。1
2const arr = [1, [2, [3]]];
const newArr = arr.flat(2); // [1, 2, 3] - **
flatMap
**:先对数组中的每个元素执行回调函数,再将结果扁平化。1
2const arr = [1, 2, 3];
const newArr = arr.flatMap((item) => [item * 2]); // [2, 4, 6]
9. 判断和转换
- **
Array.isArray
**:判断一个值是否为数组。1
console.log(Array.isArray([1, 2, 3])); // true
- **
Array.from
**:将类数组对象或可迭代对象转换为数组。1
const arr = Array.from("hello"); // ["h", "e", "l", "l", "o"]
- **
Array.of
**:根据参数创建一个新数组。1
const arr = Array.of(1, 2, 3); // [1, 2, 3]
10. 总结
操作类型 | 方法示例 | 说明 |
---|---|---|
添加元素 | push 、unshift 、splice |
在数组开头、末尾或指定位置添加元素 |
删除元素 | pop 、shift 、splice |
删除数组开头、末尾或指定位置的元素 |
修改元素 | 直接赋值、splice |
通过索引或 splice 修改元素 |
查找元素 | indexOf 、find 、includes |
查找元素的索引或值 |
遍历数组 | forEach 、map 、filter 、reduce |
对数组中的每个元素执行操作 |
排序和反转 | sort 、reverse |
对数组进行排序或反转 |
合并和拆分 | concat 、slice 、join |
合并数组、提取部分数组或拼接字符串 |
其他操作 | fill 、flat 、flatMap |
填充数组、扁平化数组或映射并扁平化 |
判断和转换 | Array.isArray 、Array.from 、Array.of |
判断数组类型或创建新数组 |
掌握这些数组操作方法,可以更高效地处理数组数据。
JavaScript中for…in和for…of的区别是什么?
在 JavaScript 中,for...in
和 for...of
是两种用于遍历的循环语句,但它们的作用对象和行为有所不同。以下是它们的详细对比:
1. for...in
循环
- 作用:遍历对象的可枚举属性(包括原型链上的属性)。
- 适用对象:对象(
Object
)、数组(Array
)、字符串(String
)等。 - 特点:
- 遍历的是对象的键(key)。
- 会遍历对象的所有可枚举属性,包括继承的属性。
- 示例:
1
2
3
4
5
6
7
8
9const obj = { a: 1, b: 2 };
for (const key in obj) {
console.log(key); // 输出: "a", "b"
}
const arr = [1, 2, 3];
for (const index in arr) {
console.log(index); // 输出: "0", "1", "2"
}
2. for...of
循环
- 作用:遍历可迭代对象的值。
- 适用对象:数组(
Array
)、字符串(String
)、Map
、Set
、arguments
等。 - 特点:
- 遍历的是对象的值(value)。
- 只能遍历具有
Symbol.iterator
方法的对象(即实现了迭代器协议的对象)。
- 示例:
1
2
3
4
5
6
7
8
9const arr = [1, 2, 3];
for (const value of arr) {
console.log(value); // 输出: 1, 2, 3
}
const str = "hello";
for (const char of str) {
console.log(char); // 输出: "h", "e", "l", "l", "o"
}
3. 区别对比
特性 | for...in |
for...of |
---|---|---|
遍历对象 | 对象的可枚举属性(键) | 可迭代对象的值 |
返回值 | 键(key) | 值(value) |
适用对象 | 对象、数组、字符串等 | 数组、字符串、Map 、Set 等可迭代对象 |
原型链属性 | 会遍历原型链上的可枚举属性 | 不会遍历原型链上的属性 |
迭代器协议 | 不需要 | 需要实现 Symbol.iterator 方法 |
4. 使用场景
**
for...in
**:- 遍历对象的属性。
- 例如:
1
2
3
4const obj = { a: 1, b: 2 };
for (const key in obj) {
console.log(key, obj[key]); // 输出: "a 1", "b 2"
}
**
for...of
**:- 遍历数组、字符串、
Map
、Set
等可迭代对象的值。 - 例如:
1
2
3
4const arr = [1, 2, 3];
for (const value of arr) {
console.log(value); // 输出: 1, 2, 3
}
- 遍历数组、字符串、
5. 注意事项
**
for...in
**:- 遍历对象时,可能会遍历到原型链上的属性。可以使用
hasOwnProperty
方法过滤。1
2
3
4
5
6const obj = { a: 1, b: 2 };
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key); // 输出: "a", "b"
}
}
- 遍历对象时,可能会遍历到原型链上的属性。可以使用
**
for...of
**:- 不能直接遍历普通对象,因为普通对象没有实现
Symbol.iterator
方法。1
2
3
4const obj = { a: 1, b: 2 };
for (const value of obj) {
console.log(value); // 报错:Uncaught TypeError: obj is not iterable
}
- 不能直接遍历普通对象,因为普通对象没有实现
6. 总结
特性 | for...in |
for...of |
---|---|---|
遍历对象 | 对象的可枚举属性(键) | 可迭代对象的值 |
返回值 | 键(key) | 值(value) |
适用对象 | 对象、数组、字符串等 | 数组、字符串、Map 、Set 等可迭代对象 |
原型链属性 | 会遍历原型链上的可枚举属性 | 不会遍历原型链上的属性 |
迭代器协议 | 不需要 | 需要实现 Symbol.iterator 方法 |
根据具体需求选择合适的遍历方式,可以更高效地处理数据。
JavaScript如何使用for…of遍历对象?
在 JavaScript 中,for...of
循环用于遍历可迭代对象(如数组、字符串、Map
、Set
等)。然而,普通对象(Object
)默认是不可迭代的,因此不能直接使用 for...of
遍历对象。不过,可以通过以下方法使对象可迭代,从而实现 for...of
遍历。
1. 使用 Object.keys
、Object.values
或 Object.entries
- **
Object.keys
**:返回对象的键组成的数组。 - **
Object.values
**:返回对象的值组成的数组。 - **
Object.entries
**:返回对象的键值对组成的数组。 - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const obj = { a: 1, b: 2, c: 3 };
// 遍历对象的键
for (const key of Object.keys(obj)) {
console.log(key); // 输出: "a", "b", "c"
}
// 遍历对象的值
for (const value of Object.values(obj)) {
console.log(value); // 输出: 1, 2, 3
}
// 遍历对象的键值对
for (const [key, value] of Object.entries(obj)) {
console.log(key, value); // 输出: "a 1", "b 2", "c 3"
}
2. 自定义迭代器
- 原理:通过实现对象的
Symbol.iterator
方法,使对象可迭代。 - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24const obj = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator]() {
const keys = Object.keys(this);
let index = 0;
return {
next: () => {
if (index < keys.length) {
const key = keys[index++];
return { value: { key, value: this[key] }, done: false };
} else {
return { done: true };
}
},
};
},
};
// 使用 for...of 遍历对象
for (const { key, value } of obj) {
console.log(key, value); // 输出: "a 1", "b 2", "c 3"
}
3. 使用 Map
代替对象
- 原理:
Map
是可迭代对象,可以直接使用for...of
遍历。 - 示例:
1
2
3
4
5
6
7
8
9
10const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
// 使用 for...of 遍历 Map
for (const [key, value] of map) {
console.log(key, value); // 输出: "a 1", "b 2", "c 3"
}
4. 使用生成器函数
- 原理:通过生成器函数返回一个可迭代对象。
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12function* objectEntries(obj) {
for (const key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
const obj = { a: 1, b: 2, c: 3 };
// 使用 for...of 遍历对象
for (const [key, value] of objectEntries(obj)) {
console.log(key, value); // 输出: "a 1", "b 2", "c 3"
}
5. 总结
方法 | 特点 | 适用场景 |
---|---|---|
Object.keys |
返回对象的键组成的数组 | 遍历对象的键 |
Object.values |
返回对象的值组成的数组 | 遍历对象的值 |
Object.entries |
返回对象的键值对组成的数组 | 遍历对象的键值对 |
自定义迭代器 | 实现 Symbol.iterator 方法 |
需要将对象转换为可迭代对象 |
使用 Map |
Map 是可迭代对象 |
适合需要键值对结构的场景 |
生成器函数 | 通过生成器函数返回可迭代对象 | 需要动态生成可迭代对象的场景 |
根据具体需求选择合适的方法,可以更高效地使用 for...of
遍历对象。
const对象的属性可以修改吗?
在 JavaScript 中,const
关键字用于声明一个常量,但它的行为取决于所声明的数据类型。以下是关于 const
对象属性的详细解释:
1. const
的基本特性
对于基本类型(如
number
、string
、boolean
):const
声明的变量不能被重新赋值。- 示例:
1
2const num = 10;
num = 20; // 报错:Uncaught TypeError: Assignment to constant variable.
对于引用类型(如
object
、array
):const
声明的变量不能被重新赋值,但可以修改其属性或元素。- 示例:
1
2
3
4
5const obj = { a: 1, b: 2 };
obj.a = 3; // 可以修改属性
console.log(obj); // { a: 3, b: 2 }
obj = { c: 4 }; // 报错:Uncaught TypeError: Assignment to constant variable.
2. const
对象的属性是否可以修改?
可以修改:
const
声明的对象,其属性可以被修改、添加或删除。- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13const obj = { a: 1, b: 2 };
// 修改属性
obj.a = 3;
console.log(obj); // { a: 3, b: 2 }
// 添加属性
obj.c = 4;
console.log(obj); // { a: 3, b: 2, c: 4 }
// 删除属性
delete obj.b;
console.log(obj); // { a: 3, c: 4 }
不能重新赋值:
const
声明的对象不能被重新赋值。- 示例:
1
2const obj = { a: 1, b: 2 };
obj = { c: 3 }; // 报错:Uncaught TypeError: Assignment to constant variable.
3. 如何防止对象属性被修改?
如果需要防止 const
对象的属性被修改,可以使用以下方法:
方法 1:Object.freeze
- 作用:冻结对象,使其属性不可修改、添加或删除。
- 特点:
- 浅冻结(只冻结对象本身,不冻结嵌套对象)。
- 示例:
1
2
3
4
5
6
7
8
9
10
11const obj = { a: 1, b: 2 };
Object.freeze(obj);
obj.a = 3; // 修改无效
console.log(obj); // { a: 1, b: 2 }
obj.c = 4; // 添加无效
console.log(obj); // { a: 1, b: 2 }
delete obj.b; // 删除无效
console.log(obj); // { a: 1, b: 2 }
方法 2:Object.seal
- 作用:密封对象,使其属性不可添加或删除,但可以修改。
- 特点:
- 浅密封(只密封对象本身,不密封嵌套对象)。
- 示例:
1
2
3
4
5
6
7
8
9
10
11const obj = { a: 1, b: 2 };
Object.seal(obj);
obj.a = 3; // 可以修改
console.log(obj); // { a: 3, b: 2 }
obj.c = 4; // 添加无效
console.log(obj); // { a: 3, b: 2 }
delete obj.b; // 删除无效
console.log(obj); // { a: 3, b: 2 }
方法 3:Proxy
- 作用:通过代理对象,拦截对属性的操作。
- 特点:
- 可以深度冻结或密封对象。
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13const obj = { a: 1, b: 2 };
const handler = {
set(target, prop, value) {
throw new Error("Cannot modify object properties");
},
deleteProperty(target, prop) {
throw new Error("Cannot delete object properties");
},
};
const proxy = new Proxy(obj, handler);
proxy.a = 3; // 报错:Cannot modify object properties
delete proxy.b; // 报错:Cannot delete object properties
4. 总结
const
对象:- 属性可以修改、添加或删除。
- 对象本身不能被重新赋值。
- 防止修改:
- 使用
Object.freeze
冻结对象。 - 使用
Object.seal
密封对象。 - 使用
Proxy
拦截对属性的操作。
- 使用
根据具体需求选择合适的方法,可以更好地控制对象的可变性。
ES6
ES6箭头函数和普通函数有什么区别?
ES6 引入的箭头函数(Arrow Function)与普通函数(Function)在语法和行为上有一些重要的区别。以下是它们之间的主要区别:
1. 语法简洁
- 箭头函数:语法更简洁,尤其是当函数体只有一条语句时,可以省略
{}
和return
。1
const add = (a, b) => a + b;
- 普通函数:语法相对冗长。
1
2
3function add(a, b) {
return a + b;
}
2. this
的绑定
- 箭头函数:没有自己的
this
,它会捕获其所在上下文的this
值。也就是说,箭头函数中的this
是词法作用域的,继承自外层函数的this
。1
2
3
4
5
6
7
8const obj = {
value: 42,
method: function() {
setTimeout(() => {
console.log(this.value); // 42,箭头函数继承了外层函数的 this
}, 100);
}
}; - 普通函数:有自己的
this
,this
的值取决于函数的调用方式(如通过对象调用、直接调用、使用call
/apply
等)。1
2
3
4
5
6
7
8const obj = {
value: 42,
method: function() {
setTimeout(function() {
console.log(this.value); // undefined,this 指向全局对象或 undefined(严格模式)
}, 100);
}
};
3. arguments
对象
- 箭头函数:没有自己的
arguments
对象,它会继承外层函数的arguments
。1
2
3const func = () => {
console.log(arguments); // 报错,箭头函数没有 arguments
}; - 普通函数:有自己的
arguments
对象,包含所有传入的参数。1
2
3function func() {
console.log(arguments); // 正常输出 arguments 对象
}
4. new
和 prototype
- 箭头函数:不能使用
new
关键字调用,也没有prototype
属性,因此不能作为构造函数。1
2const Foo = () => {};
const obj = new Foo(); // 报错:Foo is not a constructor - 普通函数:可以使用
new
关键字调用,可以作为构造函数,并且有prototype
属性。1
2function Foo() {}
const obj = new Foo(); // 正常创建实例
5. 方法定义
- 箭头函数:不适合作为对象的方法,因为
this
不会指向对象本身。1
2
3
4
5
6const obj = {
value: 42,
method: () => {
console.log(this.value); // undefined,this 指向外层作用域
}
}; - 普通函数:适合作为对象的方法,
this
会指向调用该方法的对象。1
2
3
4
5
6const obj = {
value: 42,
method: function() {
console.log(this.value); // 42,this 指向 obj
}
};
6. 函数提升(Hoisting)
- 箭头函数:不会被提升,必须在定义之后才能使用。
1
2console.log(func()); // 报错:func is not defined
const func = () => 42; - 普通函数:会被提升,可以在定义之前调用。
1
2
3
4console.log(func()); // 42
function func() {
return 42;
}
7. 默认参数
- 箭头函数:支持默认参数。
1
const func = (a = 1, b = 2) => a + b;
- 普通函数:也支持默认参数。
1
2
3function func(a = 1, b = 2) {
return a + b;
}
总结
- 箭头函数更适合用于简单的回调函数或需要继承外层
this
的场景。 - 普通函数更适合需要动态
this
、作为构造函数或方法定义的场景。
根据具体需求选择合适的函数类型可以避免一些常见的陷阱。
ES6箭头函数能当构造函数吗?
ES6 箭头函数不能作为构造函数使用。这是箭头函数与普通函数的一个重要区别。以下是具体原因和细节:
1. 箭头函数没有 prototype
属性
- 普通函数有一个
prototype
属性,用于实现基于原型的继承和构造函数的功能。 - 箭头函数没有
prototype
属性,因此无法作为构造函数使用。1
2const Foo = () => {};
console.log(Foo.prototype); // undefined
2. 箭头函数不能使用 new
关键字
- 使用
new
调用箭头函数会直接报错,因为箭头函数没有内部的[[Construct]]
方法,无法实例化对象。1
2const Foo = () => {};
const obj = new Foo(); // 报错:Foo is not a constructor
3. 箭头函数没有自己的 this
- 普通函数在被
new
调用时,会创建一个新的对象,并将this
绑定到该对象。 - 箭头函数没有自己的
this
,它的this
是词法作用域的,继承自外层函数的this
。因此,箭头函数无法实现构造函数的功能。1
2
3const Foo = () => {
this.value = 42; // 报错:this 不是指向新创建的对象
};
4. 箭头函数的设计初衷
- 箭头函数的设计目的是为了简化函数语法,并解决普通函数中
this
绑定混乱的问题。它更适合用于回调函数、匿名函数或需要继承外层this
的场景,而不是构造函数。
总结
箭头函数不能作为构造函数使用。如果需要创建实例对象或实现基于原型的继承,应该使用普通函数或 ES6 的 class
语法。例如:
1 | // 使用普通函数作为构造函数 |
箭头函数更适合用于以下场景:
- 简单的回调函数。
- 需要继承外层
this
的函数。 - 不需要动态
this
或构造函数的场景。
ES6有哪些新特性?
ES6(ECMAScript 2015)是 JavaScript 语言的一次重大更新,引入了许多新特性,极大地提升了开发效率和代码可读性。以下是 ES6 的主要新特性:
1. 块级作用域变量(let
和 const
)
let
和const
引入了块级作用域,解决了var
的变量提升和全局污染问题。const
用于声明常量,值不可重新赋值。1
2
3
4
5
6
7let x = 10;
const y = 20;
if (true) {
let x = 30;
console.log(x); // 30
}
console.log(x); // 10
2. 箭头函数(Arrow Functions)
- 语法更简洁,
this
继承自外层作用域。1
const add = (a, b) => a + b;
3. 模板字符串(Template Literals)
- 支持多行字符串和嵌入表达式。
1
2
3const name = "Alice";
const message = `Hello, ${name}!
Welcome to ES6.`;
4. 解构赋值(Destructuring Assignment)
- 从数组或对象中提取值并赋值给变量。
1
2const [a, b] = [1, 2];
const { name, age } = { name: "Bob", age: 30 };
5. 默认参数(Default Parameters)
- 函数参数可以设置默认值。
1
2
3function greet(name = "Guest") {
console.log(`Hello, ${name}!`);
}
6. 剩余参数(Rest Parameters)
- 将多余的参数收集到一个数组中。
1
2
3function sum(...numbers) {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
7. 扩展运算符(Spread Operator)
- 将数组或对象展开。
1
2const arr = [1, 2, 3];
const newArr = [...arr, 4, 5];
8. 对象属性简写(Object Property Shorthand)
- 简化对象字面量的写法。
1
2
3const name = "Alice";
const age = 25;
const person = { name, age };
9. 对象方法简写(Method Shorthand)
- 对象中的方法可以省略
function
关键字。1
2
3
4
5const obj = {
greet() {
console.log("Hello!");
}
};
10. 类(Classes)
- 引入了基于原型的面向对象编程的语法糖。
1
2
3
4
5
6
7
8
9
10class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
const alice = new Person("Alice");
alice.greet();
11. 模块化(Modules)
- 支持
import
和export
语法,实现模块化开发。1
2
3
4
5
6// math.js
export const add = (a, b) => a + b;
// main.js
import { add } from './math.js';
console.log(add(1, 2));
12. Promise
- 提供了更优雅的异步编程方式,解决了回调地狱问题。
1
2
3
4const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("Success!"), 1000);
});
promise.then(result => console.log(result));
13. 迭代器和生成器(Iterators and Generators)
- 迭代器:提供统一的遍历接口。
- 生成器:通过
function*
定义,使用yield
暂停和恢复执行。1
2
3
4
5
6function* gen() {
yield 1;
yield 2;
}
const iterator = gen();
console.log(iterator.next().value); // 1
14. Symbol
- 引入了一种新的原始数据类型,用于创建唯一的标识符。
1
2const id = Symbol("id");
const obj = { [id]: 123 };
15. Map 和 Set
Map
:键值对集合,键可以是任意类型。Set
:值唯一的集合。1
2
3
4const map = new Map();
map.set("name", "Alice");
const set = new Set([1, 2, 3]);
16. Proxy 和 Reflect
Proxy
:用于拦截对象操作。Reflect
:提供操作对象的方法。1
2
3
4
5
6
7const target = {};
const proxy = new Proxy(target, {
get(obj, prop) {
return obj[prop] || "Not Found";
}
});
console.log(proxy.name); // "Not Found"
17. 二进制和八进制字面量
- 支持
0b
表示二进制,0o
表示八进制。1
2const binary = 0b1010; // 10
const octal = 0o12; // 10
18. 新的字符串方法
includes()
、startsWith()
、endsWith()
、repeat()
等。1
2const str = "Hello, ES6!";
console.log(str.includes("ES6")); // true
19. 新的数组方法
Array.from()
、Array.of()
、find()
、findIndex()
、fill()
等。1
2const arr = [1, 2, 3];
console.log(arr.find(x => x > 1)); // 2
20. 尾调用优化(Tail Call Optimization)
- 在严格模式下,支持尾调用优化,避免栈溢出。
1
2
3
4function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
总结
ES6 引入了大量新特性,极大地提升了 JavaScript 的开发体验和代码质量。这些特性包括块级作用域、箭头函数、模板字符串、解构赋值、Promise、类、模块化等,使得 JavaScript 更加现代化和强大。
ES6新增Symbol基础数据类型有什么作用?
ES6 引入的 Symbol
是一种新的原始数据类型,用于创建唯一且不可变的值。它的主要作用是为对象属性提供唯一的标识符,避免属性名冲突,同时支持实现一些高级功能。以下是 Symbol
的主要作用和应用场景:
1. 创建唯一的属性键
Symbol
值可以作为对象的属性键,确保属性名不会与其他属性冲突。1
2
3
4
5
6const id = Symbol("id");
const obj = {
[id]: 123,
name: "Alice"
};
console.log(obj[id]); // 123
2. 避免属性名冲突
- 在大型项目或库中,使用
Symbol
可以避免属性名被意外覆盖或冲突。1
2
3
4
5
6
7
8
9
10const myLib = (function() {
const privateKey = Symbol("privateKey");
return {
[privateKey]: "This is private",
publicMethod() {
console.log(this[privateKey]);
}
};
})();
myLib.publicMethod(); // "This is private"
3. 定义对象的私有属性
Symbol
可以用来模拟私有属性,因为Symbol
属性不会被for...in
、Object.keys()
或JSON.stringify()
枚举。1
2
3
4
5
6
7const age = Symbol("age");
const person = {
name: "Alice",
[age]: 25
};
console.log(Object.keys(person)); // ["name"]
console.log(JSON.stringify(person)); // {"name":"Alice"}
4. 实现内置对象的行为
Symbol
定义了一些内置的符号(如Symbol.iterator
、Symbol.toStringTag
等),用于实现对象的默认行为。1
2
3
4const obj = {
[Symbol.toStringTag]: "MyObject"
};
console.log(obj.toString()); // "[object MyObject]"
5. 定义迭代器
- 使用
Symbol.iterator
可以为对象定义自定义的迭代行为。1
2
3
4
5
6
7
8
9
10const myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
};
for (const value of myIterable) {
console.log(value); // 1, 2, 3
}
6. 定义对象的默认行为
- 通过
Symbol
可以修改对象的默认行为,例如Symbol.toPrimitive
用于定义对象如何转换为原始值。1
2
3
4
5
6
7
8
9const obj = {
[Symbol.toPrimitive](hint) {
if (hint === "number") return 42;
if (hint === "string") return "Hello";
return "default";
}
};
console.log(+obj); // 42
console.log(`${obj}`); // "Hello"
7. 作为常量值
Symbol
可以作为常量值,确保值的唯一性。1
2
3
4
5
6
7
8
9
10
11const LOG_LEVEL = {
DEBUG: Symbol("debug"),
INFO: Symbol("info"),
ERROR: Symbol("error")
};
function log(message, level) {
if (level === LOG_LEVEL.DEBUG) {
console.log(`DEBUG: ${message}`);
}
}
log("This is a debug message", LOG_LEVEL.DEBUG);
8. 注册全局 Symbol
- 使用
Symbol.for()
可以在全局 Symbol 注册表中创建或获取 Symbol,确保相同描述符的 Symbol 是同一个值。1
2
3const id1 = Symbol.for("id");
const id2 = Symbol.for("id");
console.log(id1 === id2); // true
9. 避免魔法字符串
- 使用
Symbol
可以避免在代码中直接使用字符串(魔法字符串),提高代码的可维护性。1
2
3
4
5
6
7
8
9
10
11
12const ACTION_TYPE = {
ADD: Symbol("ADD"),
DELETE: Symbol("DELETE")
};
function reducer(action) {
switch (action.type) {
case ACTION_TYPE.ADD:
return "Add action";
case ACTION_TYPE.DELETE:
return "Delete action";
}
}
总结
Symbol
的主要作用包括:
- 创建唯一的属性键,避免属性名冲突。
- 定义对象的私有属性。
- 实现内置对象的行为(如迭代器、默认行为等)。
- 作为常量值,确保值的唯一性。
- 提高代码的可维护性,避免魔法字符串。
Symbol
是 ES6 中一个非常强大的特性,特别适合用于需要唯一性、隐私性或自定义行为的场景。
ES Module与CommonJS模块方案有什么异同?
ES Module(ESM)和 CommonJS(CJS)是 JavaScript 中两种主要的模块化方案。它们在语法、加载方式、运行环境等方面有一些显著的异同。以下是它们的对比:
1. 语法
- ES Module:
- 使用
import
和export
语法。 - 支持静态导入和动态导入。
1
2
3
4
5
6
7
8
9// 导出
export const name = "Alice";
export function greet() {
console.log("Hello!");
}
// 导入
import { name, greet } from './module.js';
greet();
- 使用
- CommonJS:
- 使用
require
和module.exports
语法。 - 动态加载模块。
1
2
3
4
5
6
7
8
9
10// 导出
const name = "Alice";
function greet() {
console.log("Hello!");
}
module.exports = { name, greet };
// 导入
const { name, greet } = require('./module.js');
greet();
- 使用
2. 加载方式
- ES Module:
- 静态加载:
import
语句必须在模块的顶层,不能在条件语句或函数中使用。 - 支持异步加载(
import()
)。1
2
3
4
5
6
7// 静态导入
import { name } from './module.js';
// 动态导入
import('./module.js').then(module => {
console.log(module.name);
});
- 静态加载:
- CommonJS:
- 动态加载:
require
可以在代码的任何位置使用。1
2
3if (condition) {
const module = require('./module.js');
}
- 动态加载:
3. 运行环境
- ES Module:
- 是 ECMAScript 标准的一部分,现代浏览器和 Node.js(12+)原生支持。
- 文件扩展名通常为
.mjs
或在package.json
中设置"type": "module"
。
- CommonJS:
- 是 Node.js 的默认模块系统,广泛用于服务器端开发。
- 文件扩展名通常为
.js
或.cjs
。
4. 模块解析
- ES Module:
- 支持静态分析,便于工具进行优化(如 Tree Shaking)。
- 默认使用严格模式(
"use strict"
)。
- CommonJS:
- 动态解析,无法在编译时确定依赖关系。
- 默认不使用严格模式。
5. 循环依赖
- ES Module:
- 支持循环依赖,但行为更可预测。
- 导入的是模块的引用,而不是值的拷贝。
- CommonJS:
- 支持循环依赖,但行为可能不一致。
- 导入的是模块的值的拷贝。
6. 性能
- ES Module:
- 静态加载和解析使得性能更优,尤其是在浏览器中。
- CommonJS:
- 动态加载可能导致性能开销,尤其是在大型项目中。
7. 兼容性
- ES Module:
- 现代浏览器和 Node.js 12+ 原生支持。
- 需要使用 Babel 等工具才能在旧环境中使用。
- CommonJS:
- 广泛支持,尤其是 Node.js 环境。
- 浏览器中需要使用打包工具(如 Webpack、Browserify)支持。
8. Tree Shaking
- ES Module:
- 支持 Tree Shaking,工具可以静态分析并移除未使用的代码。
- CommonJS:
- 不支持 Tree Shaking,因为依赖关系是动态解析的。
9. 默认导出
- ES Module:
- 支持默认导出和命名导出。
1
2
3
4
5
6
7// 默认导出
export default function() {
console.log("Hello!");
}
// 导入默认导出
import greet from './module.js';
- 支持默认导出和命名导出。
- CommonJS:
- 支持默认导出(
module.exports
)和命名导出(exports
)。1
2
3
4
5
6
7// 默认导出
module.exports = function() {
console.log("Hello!");
};
// 导入默认导出
const greet = require('./module.js');
- 支持默认导出(
总结
特性 | ES Module (ESM) | CommonJS (CJS) |
---|---|---|
语法 | import / export |
require / module.exports |
加载方式 | 静态加载,支持动态导入 | 动态加载 |
运行环境 | 现代浏览器、Node.js 12+ | Node.js |
模块解析 | 静态分析,支持 Tree Shaking | 动态解析 |
循环依赖 | 行为可预测 | 行为可能不一致 |
性能 | 更优 | 可能存在性能开销 |
兼容性 | 需要工具支持旧环境 | 广泛支持 |
默认导出 | 支持默认导出和命名导出 | 支持默认导出和命名导出 |
- ES Module 是未来的标准,适合现代 JavaScript 开发,尤其在浏览器中。
- CommonJS 是 Node.js 的默认模块系统,适合服务器端开发。
在实际项目中,可以根据环境和需求选择合适的模块化方案,或者使用工具(如 Babel、Webpack)实现兼容。
如果new一个箭头函数会怎么样?
如果尝试使用 new
调用一个箭头函数,会直接抛出错误。这是因为箭头函数没有 [[Construct]]
内部方法,无法作为构造函数使用。具体表现如下:
1. 报错信息
- 使用
new
调用箭头函数会抛出TypeError
,提示箭头函数不是构造函数。1
2const Foo = () => {};
const obj = new Foo(); // TypeError: Foo is not a constructor
2. 为什么箭头函数不能作为构造函数?
- 没有
prototype
属性:- 构造函数需要有一个
prototype
属性,用于实现基于原型的继承。 - 箭头函数没有
prototype
属性。1
2const Foo = () => {};
console.log(Foo.prototype); // undefined
- 构造函数需要有一个
- **没有自己的
this
**:- 箭头函数的
this
是词法作用域的,继承自外层函数的this
。 - 构造函数需要通过
new
创建一个新对象,并将this
绑定到该对象。1
2
3const Foo = () => {
this.value = 42; // 报错:this 不是指向新创建的对象
};
- 箭头函数的
3. 箭头函数的设计初衷
- 箭头函数的设计目的是为了简化函数语法,并解决普通函数中
this
绑定混乱的问题。 - 它更适合用于回调函数、匿名函数或需要继承外层
this
的场景,而不是构造函数。
4. 如何替代箭头函数作为构造函数?
如果需要创建实例对象或实现基于原型的继承,可以使用普通函数或 ES6 的
class
语法。普通函数:
1
2
3
4
5function Foo(value) {
this.value = value;
}
const obj = new Foo(42);
console.log(obj.value); // 42ES6 class:
1
2
3
4
5
6
7class Bar {
constructor(value) {
this.value = value;
}
}
const obj2 = new Bar(42);
console.log(obj2.value); // 42
5. 总结
- 箭头函数不能使用
new
调用,因为它没有prototype
属性,也没有自己的this
。 - 如果需要构造函数的功能,请使用普通函数或
class
语法。 - 箭头函数更适合用于以下场景:
- 简单的回调函数。
- 需要继承外层
this
的函数。 - 不需要动态
this
或构造函数的场景。
示例代码
1 | // 错误示例:尝试用 new 调用箭头函数 |
ES6箭头函数的**this**指向哪里?
ES6 箭头函数的 this
指向是 词法作用域 的,也就是说,箭头函数的 this
继承自定义箭头函数时所在上下文的 this
,而不是根据调用方式动态绑定。这是箭头函数与普通函数的一个重要区别。
1. 箭头函数的 this
规则
- 箭头函数没有自己的
this
,它的this
是继承自外层函数或全局作用域的this
。 - 箭头函数的
this
在定义时就已经确定,且不会因为调用方式而改变。
2. 与普通函数的对比
- 普通函数:
this
的值取决于函数的调用方式(如通过对象调用、直接调用、使用call
/apply
等)。1
2
3
4
5
6const obj = {
method: function() {
console.log(this); // this 指向 obj
}
};
obj.method();
- 箭头函数:
this
继承自定义箭头函数时所在上下文的this
。1
2
3
4
5
6const obj = {
method: () => {
console.log(this); // this 指向外层作用域的 this(通常是全局对象或 undefined)
}
};
obj.method();
3. 常见场景分析
场景 1:全局作用域中的箭头函数
- 在全局作用域中,箭头函数的
this
指向全局对象(浏览器中是window
,Node.js 中是global
)。1
2
3
4const foo = () => {
console.log(this); // 浏览器中:window;Node.js 中:global
};
foo();
场景 2:对象方法中的箭头函数
- 箭头函数作为对象的方法时,
this
不会指向对象本身,而是继承自外层作用域的this
。1
2
3
4
5
6
7const obj = {
value: 42,
method: () => {
console.log(this.value); // undefined,this 指向外层作用域的 this
}
};
obj.method();
场景 3:嵌套函数中的箭头函数
- 箭头函数在嵌套函数中时,
this
继承自外层函数的this
。1
2
3
4
5
6
7
8
9
10const obj = {
value: 42,
method: function() {
const innerFunc = () => {
console.log(this.value); // 42,this 继承自 method 的 this
};
innerFunc();
}
};
obj.method();
场景 4:事件处理函数中的箭头函数
- 箭头函数作为事件处理函数时,
this
不会指向触发事件的元素,而是继承自外层作用域的this
。1
2
3
4const button = document.querySelector("button");
button.addEventListener("click", () => {
console.log(this); // this 指向外层作用域的 this(通常是全局对象或 undefined)
});
4. 箭头函数 this
的不可变性
- 箭头函数的
this
在定义时就已经确定,无法通过call
、apply
或bind
改变。1
2
3
4
5
6
7
8
9
10
11const obj1 = { value: 42 };
const obj2 = { value: 100 };
const foo = () => {
console.log(this.value); // this 继承自外层作用域的 this
};
foo.call(obj1); // 无法改变 this,输出 undefined 或全局对象的值
foo.apply(obj2); // 同上
const boundFoo = foo.bind(obj1); // 无法改变 this
boundFoo(); // 同上
5. 箭头函数 this
的使用场景
- 回调函数:
- 箭头函数适合用于需要继承外层
this
的回调函数,例如setTimeout
、Promise
等。1
2
3
4
5
6
7
8
9const obj = {
value: 42,
method: function() {
setTimeout(() => {
console.log(this.value); // 42,this 继承自 method 的 this
}, 100);
}
};
obj.method();
- 箭头函数适合用于需要继承外层
- 避免
this
绑定问题:- 箭头函数可以避免普通函数中
this
绑定混乱的问题,尤其是在嵌套函数或回调函数中。
- 箭头函数可以避免普通函数中
总结
- 箭头函数的
this
继承自定义时所在上下文的this
,而不是根据调用方式动态绑定。 - 箭头函数的
this
在定义时就已经确定,且无法通过call
、apply
或bind
改变。 - 箭头函数适合用于需要继承外层
this
的场景,例如回调函数或嵌套函数。
如果需要动态绑定 this
,请使用普通函数。
说说ES6拓展运算符的作用及其使用场景?
ES6 的扩展运算符(Spread Operator)使用 ...
语法,可以将数组、对象或字符串“展开”为独立的元素。它在许多场景中都非常有用,能够简化代码并提高可读性。以下是扩展运算符的主要作用及其使用场景:
1. 展开数组
- 作用:将数组展开为独立的元素。
- 使用场景:
- 合并数组。
- 将数组作为函数的参数传递。
1
2
3
4
5
6
7
8
9
10const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2]; // 合并数组
console.log(merged); // [1, 2, 3, 4, 5, 6]
function sum(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6,将数组展开为参数
2. 展开对象
- 作用:将对象展开为独立的键值对。
- 使用场景:
- 合并对象。
- 创建对象的浅拷贝。
1
2
3
4
5
6
7const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 }; // 合并对象
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }
const copy = { ...obj1 }; // 创建对象的浅拷贝
console.log(copy); // { a: 1, b: 2 }
3. 展开字符串
- 作用:将字符串展开为单个字符的数组。
- 使用场景:
- 将字符串转换为字符数组。
1
2
3const str = "hello";
const chars = [...str]; // 将字符串展开为字符数组
console.log(chars); // ["h", "e", "l", "l", "o"]
- 将字符串转换为字符数组。
4. 函数参数中的剩余参数
- 作用:将多余的参数收集到一个数组中。
- 使用场景:
- 处理不定数量的函数参数。
1
2
3
4function sum(...numbers) {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3)); // 6
- 处理不定数量的函数参数。
5. 复制数组或对象
- 作用:创建数组或对象的浅拷贝。
- 使用场景:
- 复制数组或对象,避免直接修改原数据。
1
2
3
4
5
6
7const arr = [1, 2, 3];
const copyArr = [...arr]; // 复制数组
console.log(copyArr); // [1, 2, 3]
const obj = { a: 1, b: 2 };
const copyObj = { ...obj }; // 复制对象
console.log(copyObj); // { a: 1, b: 2 }
- 复制数组或对象,避免直接修改原数据。
6. 替换 apply
方法
- 作用:将数组展开为函数的参数,替代
Function.prototype.apply
。 - 使用场景:
- 简化函数调用。
1
2const numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // 3,替代 Math.max.apply(null, numbers)
- 简化函数调用。
7. 解构赋值中的剩余元素
- 作用:将剩余的元素收集到一个数组或对象中。
- 使用场景:
- 解构数组或对象时,获取剩余部分。
1
2
3
4
5
6
7const [first, ...rest] = [1, 2, 3, 4]; // 解构数组
console.log(first); // 1
console.log(rest); // [2, 3, 4]
const { a, ...restObj } = { a: 1, b: 2, c: 3 }; // 解构对象
console.log(a); // 1
console.log(restObj); // { b: 2, c: 3 }
- 解构数组或对象时,获取剩余部分。
8. 动态添加属性
- 作用:在对象中动态添加属性。
- 使用场景:
- 根据条件扩展对象。
1
2
3
4
5
6const condition = true;
const obj = {
a: 1,
...(condition && { b: 2 }), // 根据条件动态添加属性
};
console.log(obj); // { a: 1, b: 2 }
- 根据条件扩展对象。
9. 将类数组对象转换为数组
- 作用:将类数组对象(如
arguments
、NodeList
)转换为真正的数组。 - 使用场景:
- 使用数组方法操作类数组对象。
1
2
3
4
5function func() {
const args = [...arguments]; // 将 arguments 转换为数组
console.log(args);
}
func(1, 2, 3); // [1, 2, 3]
- 使用数组方法操作类数组对象。
总结
扩展运算符的主要作用包括:
- 展开数组、对象或字符串。
- 合并数组或对象。
- 创建数组或对象的浅拷贝。
- 处理不定数量的函数参数。
- 解构赋值时获取剩余部分。
- 动态添加属性。
它的使用场景非常广泛,能够简化代码并提高开发效率。无论是处理数组、对象还是函数参数,扩展运算符都是一个非常强大的工具。
ES6的Proxy可以实现什么功能?
ES6 引入的 Proxy
是一个强大的特性,用于创建一个对象的代理,从而拦截并自定义对象的基本操作(如属性访问、赋值、枚举等)。通过 Proxy
,开发者可以实现高级功能,如数据验证、日志记录、缓存、权限控制等。以下是 Proxy
的主要功能和使用场景:
1. 基本语法
Proxy
的构造函数接受两个参数:target
:要代理的目标对象。handler
:一个对象,定义了拦截操作的“陷阱”(trap)方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14const target = {};
const handler = {
get(target, prop, receiver) {
console.log(`Getting property: ${prop}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Setting property: ${prop} to ${value}`);
return Reflect.set(target, prop, value, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.name = "Alice"; // 设置属性
console.log(proxy.name); // 获取属性
2. 主要功能
1. 拦截属性访问(get
)
- 拦截对对象属性的读取操作。
1
2
3
4
5
6
7
8
9
10const handler = {
get(target, prop) {
if (prop === "age") {
return `Age is ${target[prop]}`;
}
return target[prop];
}
};
const proxy = new Proxy({ age: 25 }, handler);
console.log(proxy.age); // "Age is 25"
2. 拦截属性赋值(set
)
- 拦截对对象属性的赋值操作。
1
2
3
4
5
6
7
8
9
10
11
12const handler = {
set(target, prop, value) {
if (prop === "age" && typeof value !== "number") {
throw new TypeError("Age must be a number");
}
target[prop] = value;
return true;
}
};
const proxy = new Proxy({}, handler);
proxy.age = 25; // 正常赋值
proxy.age = "25"; // 抛出错误:Age must be a number
3. 拦截函数调用(apply
)
- 拦截对函数的调用操作。
1
2
3
4
5
6
7
8const handler = {
apply(target, thisArg, args) {
console.log(`Calling function with arguments: ${args}`);
return target(...args);
}
};
const proxy = new Proxy(function(a, b) { return a + b; }, handler);
console.log(proxy(1, 2)); // "Calling function with arguments: 1,2" 和 3
4. 拦截 new
操作(construct
)
- 拦截对构造函数的
new
操作。1
2
3
4
5
6
7
8
9
10
11
12
13
14const handler = {
construct(target, args, newTarget) {
console.log(`Creating instance with arguments: ${args}`);
return new target(...args);
}
};
class Person {
constructor(name) {
this.name = name;
}
}
const ProxyPerson = new Proxy(Person, handler);
const alice = new ProxyPerson("Alice"); // "Creating instance with arguments: Alice"
console.log(alice.name); // "Alice"
5. 拦截属性删除(deleteProperty
)
- 拦截对对象属性的删除操作。
1
2
3
4
5
6
7
8
9
10
11
12const handler = {
deleteProperty(target, prop) {
if (prop === "id") {
throw new Error("Cannot delete id property");
}
delete target[prop];
return true;
}
};
const proxy = new Proxy({ id: 1, name: "Alice" }, handler);
delete proxy.name; // 正常删除
delete proxy.id; // 抛出错误:Cannot delete id property
6. 拦截属性枚举(ownKeys
)
- 拦截对对象属性的枚举操作(如
Object.keys()
)。1
2
3
4
5
6
7const handler = {
ownKeys(target) {
return Object.keys(target).filter(key => key !== "password");
}
};
const proxy = new Proxy({ name: "Alice", password: "123" }, handler);
console.log(Object.keys(proxy)); // ["name"]
7. 拦截 in
操作(has
)
- 拦截
in
操作符的检查。1
2
3
4
5
6
7
8
9
10const handler = {
has(target, prop) {
if (prop === "secret") {
return false;
}
return prop in target;
}
};
const proxy = new Proxy({ name: "Alice", secret: "123" }, handler);
console.log("secret" in proxy); // false
3. 使用场景
1. 数据验证
- 在设置属性时进行数据验证。
1
2
3
4
5
6
7
8
9
10
11
12const validator = {
set(target, prop, value) {
if (prop === "age" && (typeof value !== "number" || value < 0)) {
throw new TypeError("Age must be a positive number");
}
target[prop] = value;
return true;
}
};
const person = new Proxy({}, validator);
person.age = 25; // 正常
person.age = -5; // 抛出错误
2. 日志记录
- 记录对象的操作日志。
1
2
3
4
5
6
7
8
9
10
11
12
13
14const logger = {
get(target, prop) {
console.log(`Getting property: ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`Setting property: ${prop} to ${value}`);
target[prop] = value;
return true;
}
};
const proxy = new Proxy({}, logger);
proxy.name = "Alice"; // 记录日志
console.log(proxy.name); // 记录日志
3. 缓存
- 缓存函数调用的结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const cache = new Map();
const handler = {
apply(target, thisArg, args) {
const key = args.toString();
if (cache.has(key)) {
return cache.get(key);
}
const result = target(...args);
cache.set(key, result);
return result;
}
};
const proxy = new Proxy(function(a, b) { return a + b; }, handler);
console.log(proxy(1, 2)); // 3
console.log(proxy(1, 2)); // 3(从缓存中获取)
4. 权限控制
- 控制对对象属性的访问权限。
1
2
3
4
5
6
7
8
9
10
11const handler = {
get(target, prop) {
if (prop === "secret") {
throw new Error("Access denied");
}
return target[prop];
}
};
const proxy = new Proxy({ name: "Alice", secret: "123" }, handler);
console.log(proxy.name); // "Alice"
console.log(proxy.secret); // 抛出错误:Access denied
5. 虚拟属性
- 动态计算属性值。
1
2
3
4
5
6
7
8
9
10const handler = {
get(target, prop) {
if (prop === "fullName") {
return `${target.firstName} ${target.lastName}`;
}
return target[prop];
}
};
const proxy = new Proxy({ firstName: "Alice", lastName: "Smith" }, handler);
console.log(proxy.fullName); // "Alice Smith"
4. 总结
Proxy
的主要功能包括:
- 拦截对象的基本操作(如属性访问、赋值、枚举等)。
- 实现高级功能,如数据验证、日志记录、缓存、权限控制等。
- 创建虚拟属性或动态计算属性值。
Proxy
是一个非常灵活和强大的工具,适合用于需要自定义对象行为的场景。通过合理使用 Proxy
,可以显著提升代码的可维护性和扩展性。
什么是ES6的数组结构和对象结构?
ES6 引入了 解构赋值(Destructuring Assignment) 语法,允许从数组或对象中提取值并赋值给变量。解构赋值分为 数组解构 和 对象解构,它们可以大大简化代码,提高可读性。
1. 数组解构
- 作用:从数组中提取值并赋值给变量。
- 语法:使用方括号
[]
,左侧是变量列表,右侧是数组。 - 特点:
- 按照数组元素的顺序解构。
- 可以跳过不需要的元素。
- 支持默认值。
- 支持剩余元素。
示例
1 | // 基本用法 |
2. 对象解构
- 作用:从对象中提取值并赋值给变量。
- 语法:使用花括号
{}
,左侧是变量列表,右侧是对象。 - 特点:
- 按照属性名解构。
- 支持别名。
- 支持默认值。
- 支持嵌套解构。
示例
1 | // 基本用法 |
3. 使用场景
1. 提取函数返回值
- 从函数返回的数组或对象中提取值。
1
2
3
4
5function getData() {
return [1, 2, 3];
}
const [a, b, c] = getData();
console.log(a, b, c); // 1, 2, 3
2. 交换变量值
- 无需临时变量即可交换两个变量的值。
1
2
3let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2, 1
3. 处理函数参数
- 解构对象参数,使函数参数更清晰。
1
2
3
4function greet({ name, age }) {
console.log(`Hello, ${name}! You are ${age} years old.`);
}
greet({ name: "Alice", age: 25 }); // "Hello, Alice! You are 25 years old."
4. 处理 API 响应
- 从 API 返回的复杂对象中提取所需数据。
1
2
3
4
5
6
7
8const response = {
status: 200,
data: {
user: { name: "Alice", age: 25 }
}
};
const { data: { user: { name, age } } } = response;
console.log(name, age); // "Alice", 25
5. 设置默认值
- 为解构的变量设置默认值,避免
undefined
。1
2const { name = "Guest", age = 18 } = { name: "Alice" };
console.log(name, age); // "Alice", 18
6. 处理嵌套结构
- 解构嵌套的对象或数组。
1
2
3
4
5
6
7
8
9
10
11
12const user = {
id: 1,
info: {
name: "Alice",
address: {
city: "New York",
zip: "10001"
}
}
};
const { info: { address: { city, zip } } } = user;
console.log(city, zip); // "New York", "10001"
4. 注意事项
- 数组解构:
- 顺序很重要,解构的变量与数组元素一一对应。
- 如果数组元素不足,未解构的变量会是
undefined
。
- 对象解构:
- 属性名必须匹配,否则变量会是
undefined
。 - 可以使用别名来避免变量名冲突。
- 属性名必须匹配,否则变量会是
- 默认值:
- 如果解构的值是
undefined
,会使用默认值。
- 如果解构的值是
5. 总结
特性 | 数组解构 | 对象解构 |
---|---|---|
语法 | [a, b] = [1, 2] |
{ name, age } = { name: "Alice", age: 25 } |
解构依据 | 数组顺序 | 属性名 |
默认值 | 支持 | 支持 |
别名 | 不支持 | 支持 |
嵌套解构 | 支持 | 支持 |
剩余元素 | 支持 | 支持 |
解构赋值是 ES6 中非常实用的特性,能够简化代码并提高可读性,适合用于提取数据、处理函数参数、交换变量值等场景。
ES6中,如何提取深度嵌套的对象指定属性?
在 ES6 中,可以通过 对象解构赋值(Destructuring Assignment) 提取深度嵌套的对象指定属性。解构赋值支持嵌套结构,可以直接从嵌套对象中提取所需属性。以下是具体方法:
1. 基本语法
- 使用花括号
{}
进行对象解构。 - 对于嵌套对象,可以在解构模式中继续嵌套解构。
示例
1 | const user = { |
2. 提取多个嵌套属性
- 可以在一次解构中提取多个嵌套属性。
示例
1 | const user = { |
3. 为提取的属性设置别名
- 如果需要为提取的属性设置别名,可以使用
:
语法。
示例
1 | const user = { |
4. 设置默认值
- 如果提取的属性可能不存在,可以为其设置默认值,避免
undefined
。
示例
1 | const user = { |
5. 提取多层嵌套的属性
- 对于更复杂的嵌套结构,可以逐层解构。
示例
1 | const company = { |
6. 结合函数参数使用
- 在函数参数中直接解构嵌套对象,使代码更简洁。
示例
1 | const user = { |
7. 注意事项
- 如果嵌套的属性不存在,解构会抛出错误。可以通过设置默认值或空对象
{}
避免错误。 - 解构赋值不会修改原对象,只是提取值并赋值给变量。
8. 总结
场景 | 示例 |
---|---|
提取嵌套属性 | const { info: { address: { city } } } = user; |
提取多个嵌套属性 | const { info: { name, address: { city, zip } } } = user; |
设置别名 | const { info: { name: userName } } = user; |
设置默认值 | const { info: { address: { city = "Unknown" } = {} } } = user; |
提取多层嵌套属性 | const { departments: { engineering: { manager: { name } } } } = company; |
函数参数解构 | function printUserInfo({ info: { name } }) { ... } |
通过解构赋值,可以轻松提取深度嵌套的对象指定属性,使代码更简洁、易读。
说说你对ES6中rest参数的理解?
ES6 引入了 rest 参数(Rest Parameters),它允许函数接受不定数量的参数,并将这些参数收集到一个数组中。rest 参数使用 ...
语法,是 ES6 中处理可变参数的一种更优雅的方式。以下是 rest 参数的详细理解及其使用场景:
1. 基本语法
- rest 参数必须放在函数参数的最后一个位置。
- 使用
...
语法将多余的参数收集到一个数组中。
示例
1 | function sum(...numbers) { |
2. 与 arguments
对象的对比
arguments
对象:- 是一个类数组对象,包含所有传入的参数。
- 不支持数组方法,需要转换为数组才能使用。
1
2
3
4
5function sum() {
const args = Array.prototype.slice.call(arguments);
return args.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3)); // 6
- rest 参数:
- 是一个真正的数组,可以直接使用数组方法。
- 语法更简洁,更符合现代 JavaScript 的风格。
1
2
3
4function sum(...numbers) {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3)); // 6
3. 使用场景
1. 处理不定数量的参数
- rest 参数非常适合处理函数需要接受不定数量参数的场景。
1
2
3
4function logNames(...names) {
names.forEach(name => console.log(name));
}
logNames("Alice", "Bob", "Charlie"); // "Alice", "Bob", "Charlie"
2. 与普通参数结合使用
- rest 参数可以与普通参数结合使用,但必须放在最后。
1
2
3
4function greet(greeting, ...names) {
return `${greeting}, ${names.join(", ")}!`;
}
console.log(greet("Hello", "Alice", "Bob")); // "Hello, Alice, Bob!"
3. 解构赋值中的剩余参数
- rest 参数可以与解构赋值结合,用于收集剩余的元素。
1
2
3const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [2, 3, 4]
4. 处理函数参数默认值
- rest 参数可以与默认值结合,处理不传参的情况。
1
2
3
4
5
6
7function sum(...numbers) {
if (numbers.length === 0) {
return 0;
}
return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum()); // 0
4. 注意事项
- rest 参数必须放在函数参数的最后一个位置,否则会报错。
1
2
3function invalidSyntax(a, ...b, c) {
// 报错:Rest parameter must be last formal parameter
} - rest 参数是一个真正的数组,而
arguments
是一个类数组对象。 - rest 参数不会影响函数的
length
属性,length
只计算普通参数。
5. 与扩展运算符的区别
- rest 参数:
- 用于函数参数,将多余的参数收集到一个数组中。
1
2
3function sum(...numbers) {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
- 用于函数参数,将多余的参数收集到一个数组中。
- 扩展运算符:
- 用于展开数组或对象,将数组或对象展开为独立的元素。
1
2const numbers = [1, 2, 3];
console.log(...numbers); // 1, 2, 3
- 用于展开数组或对象,将数组或对象展开为独立的元素。
6. 总结
特性 | rest 参数 | arguments 对象 |
---|---|---|
语法 | function sum(...numbers) { ... } |
function sum() { arguments; ... } |
数据类型 | 真正的数组 | 类数组对象 |
使用数组方法 | 直接使用 | 需要转换为数组 |
与普通参数结合 | 可以,但必须放在最后 | 无法区分普通参数和剩余参数 |
默认值支持 | 支持 | 不支持 |
rest 参数是 ES6 中处理可变参数的一种更现代、更简洁的方式,适合用于需要接受不定数量参数的场景。它比 arguments
对象更灵活、更易用,推荐在开发中优先使用 rest 参数。
什么是ES6新增的模板语法?
ES6 引入了 模板字符串(Template Literals),它是一种新的字符串语法,使用反引号(`
)包裹字符串,并支持嵌入表达式、多行字符串和标签函数等功能。模板字符串极大地提升了字符串处理的灵活性和可读性。以下是模板字符串的主要特性及其使用场景:
1. 基本语法
- 使用反引号(
`
)包裹字符串。 - 支持在字符串中嵌入变量或表达式,使用
${}
语法。
示例
1 | const name = "Alice"; |
2. 多行字符串
- 模板字符串支持直接编写多行字符串,无需使用
\n
或字符串拼接。
示例
1 | const message = ` |
3. 嵌入表达式
- 在
${}
中可以嵌入任意有效的 JavaScript 表达式。
示例
1 | const a = 10; |
4. 标签函数(Tagged Templates)
- 标签函数是一种特殊的函数,可以对模板字符串进行自定义处理。
- 标签函数的第一个参数是一个字符串数组,其余参数是嵌入的表达式。
示例
1 | function highlight(strings, ...values) { |
5. 使用场景
1. 动态生成字符串
- 模板字符串非常适合动态生成包含变量或表达式的字符串。
1
2
3const user = { name: "Alice", age: 25 };
const bio = `${user.name} is ${user.age} years old.`;
console.log(bio); // "Alice is 25 years old."
2. 多行文本
- 模板字符串可以直接编写多行文本,无需手动拼接。
1
2
3
4
5
6
7const html = `
<div>
<h1>Hello, World!</h1>
<p>Welcome to ES6.</p>
</div>
`;
console.log(html);
3. 国际化(i18n)
- 结合标签函数,可以实现国际化功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function i18n(strings, ...values) {
const translations = {
"Hello": "Hola",
"Welcome": "Bienvenido"
};
let result = "";
strings.forEach((str, i) => {
result += translations[str] || str;
if (values[i]) {
result += values[i];
}
});
return result;
}
const message = i18n`Hello, Welcome to ES6!`;
console.log(message); // "Hola, Bienvenido to ES6!"
4. SQL 查询
- 结合标签函数,可以安全地构建 SQL 查询。
1
2
3
4
5
6
7
8
9
10
11
12
13
14function sql(strings, ...values) {
let result = "";
strings.forEach((str, i) => {
result += str;
if (values[i]) {
result += escapeSQL(values[i]);
}
});
return result;
}
const table = "users";
const id = 1;
const query = sql`SELECT * FROM ${table} WHERE id = ${id};`;
console.log(query); // "SELECT * FROM users WHERE id = 1;"
6. 注意事项
- 模板字符串中的空格和换行会保留,需注意格式化。
- 在
${}
中嵌入的表达式可以是任意有效的 JavaScript 表达式,包括函数调用、算术运算等。 - 标签函数可以用于自定义模板字符串的处理逻辑,如国际化、安全处理等。
7. 总结
特性 | 模板字符串 | 普通字符串 |
---|---|---|
语法 | 使用反引号(` ) |
使用单引号(' )或双引号(" ) |
多行字符串 | 直接支持 | 需要手动拼接或使用 \n |
嵌入表达式 | 支持 ${} 语法 |
不支持 |
标签函数 | 支持 | 不支持 |
可读性 | 更高 | 较低 |
模板字符串是 ES6 中非常实用的特性,适合用于动态生成字符串、编写多行文本、国际化、安全处理等场景。它使字符串处理更加简洁、灵活和可读。
ES6新增了哪些字符串处理函数?
ES6 引入了许多新的字符串处理函数,使得字符串操作更加方便和强大。以下是 ES6 新增的字符串处理函数及其功能:
1. String.prototype.includes()
- 功能:判断字符串是否包含指定的子字符串。
- 返回值:布尔值(
true
或false
)。 - 示例:
1
2
3const str = "Hello, World!";
console.log(str.includes("World")); // true
console.log(str.includes("world")); // false(区分大小写)
2. String.prototype.startsWith()
- 功能:判断字符串是否以指定的子字符串开头。
- 返回值:布尔值(
true
或false
)。 - 示例:
1
2
3const str = "Hello, World!";
console.log(str.startsWith("Hello")); // true
console.log(str.startsWith("hello")); // false(区分大小写)
3. String.prototype.endsWith()
- 功能:判断字符串是否以指定的子字符串结尾。
- 返回值:布尔值(
true
或false
)。 - 示例:
1
2
3const str = "Hello, World!";
console.log(str.endsWith("World!")); // true
console.log(str.endsWith("world!")); // false(区分大小写)
4. String.prototype.repeat()
- 功能:将字符串重复指定次数。
- 返回值:新的字符串。
- 示例:
1
2const str = "Hello";
console.log(str.repeat(3)); // "HelloHelloHello"
5. String.prototype.padStart()
- 功能:在字符串的开头填充指定的字符,直到字符串达到指定的长度。
- 返回值:新的字符串。
- 示例:
1
2const str = "5";
console.log(str.padStart(3, "0")); // "005"
6. String.prototype.padEnd()
- 功能:在字符串的结尾填充指定的字符,直到字符串达到指定的长度。
- 返回值:新的字符串。
- 示例:
1
2const str = "5";
console.log(str.padEnd(3, "0")); // "500"
7. String.prototype.trimStart()
/ String.prototype.trimLeft()
- 功能:去除字符串开头的空白字符。
- 返回值:新的字符串。
- 示例:
1
2const str = " Hello, World! ";
console.log(str.trimStart()); // "Hello, World! "
8. String.prototype.trimEnd()
/ String.prototype.trimRight()
- 功能:去除字符串结尾的空白字符。
- 返回值:新的字符串。
- 示例:
1
2const str = " Hello, World! ";
console.log(str.trimEnd()); // " Hello, World!"
9. String.prototype[Symbol.iterator]()
- 功能:使字符串可迭代,可以通过
for...of
循环遍历字符串的每个字符。 - 示例:
1
2
3
4const str = "Hello";
for (const char of str) {
console.log(char); // "H", "e", "l", "l", "o"
}
10. String.raw()
- 功能:返回模板字符串的原始形式,忽略转义字符。
- 示例:
1
2const path = String.raw`C:\Users\Alice\Documents`;
console.log(path); // "C:\Users\Alice\Documents"
11. String.prototype.codePointAt()
- 功能:返回字符串中指定位置的字符的 Unicode 码点。
- 返回值:码点值(数字)。
- 示例:
1
2const str = "𠮷";
console.log(str.codePointAt(0)); // 134071
12. String.prototype.normalize()
- 功能:将字符串规范化(Unicode 标准化形式)。
- 返回值:新的字符串。
- 示例:
1
2const str = "é";
console.log(str.normalize("NFD")); // "é" 分解为 "e" 和 "´"
13. String.prototype.matchAll()
- 功能:返回一个包含所有匹配正则表达式的结果的迭代器。
- 返回值:迭代器。
- 示例:
1
2
3const str = "Hello, World!";
const matches = [...str.matchAll(/[A-Z]/g)];
console.log(matches); // [["H"], ["W"]]
14. String.prototype.replaceAll()
- 功能:替换字符串中所有匹配的子字符串。
- 返回值:新的字符串。
- 示例:
1
2const str = "Hello, World!";
console.log(str.replaceAll("o", "0")); // "Hell0, W0rld!"
总结
函数 | 功能描述 |
---|---|
includes() |
判断字符串是否包含指定子字符串 |
startsWith() |
判断字符串是否以指定子字符串开头 |
endsWith() |
判断字符串是否以指定子字符串结尾 |
repeat() |
将字符串重复指定次数 |
padStart() |
在字符串开头填充字符,直到达到指定长度 |
padEnd() |
在字符串结尾填充字符,直到达到指定长度 |
trimStart() / trimLeft() |
去除字符串开头的空白字符 |
trimEnd() / trimRight() |
去除字符串结尾的空白字符 |
[Symbol.iterator]() |
使字符串可迭代 |
String.raw() |
返回模板字符串的原始形式 |
codePointAt() |
返回指定位置字符的 Unicode 码点 |
normalize() |
将字符串规范化(Unicode 标准化形式) |
matchAll() |
返回所有匹配正则表达式的结果的迭代器 |
replaceAll() |
替换字符串中所有匹配的子字符串 |
这些新增的字符串处理函数极大地提升了字符串操作的便捷性和功能,适合用于各种字符串处理场景。