[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

  • 表示逻辑值,只有两个值:truefalse
  • 例如: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
    2
    const obj = { name: "Alice", age: 25 };
    const arr = [1, 2, 3];

(2) function

  • 函数是一种特殊的对象,可以被调用。
  • 例如:
    1
    2
    3
    function 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
    8
    typeof 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
    2
    Object.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
    3
    Number("42"); // 42
    String(42); // "42"
    Boolean(0); // false

总结

JavaScript 的数据类型分为原始类型和引用类型:

  • 原始类型number, string, boolean, undefined, null, symbol, bigint
  • 引用类型object, function, array, date, regexp 等。

它们的区别主要体现在存储位置、赋值行为、比较方式和可变性上。通过 typeofinstanceofObject.prototype.toString 可以检测数据类型。

如何判断JavaScript变量是数组?

在 JavaScript 中,判断一个变量是否为数组有多种方法。以下是常见的几种方式:


1. 使用 Array.isArray()

这是最推荐的方式,专门用于判断变量是否为数组。

语法:

1
Array.isArray(value);

示例:

1
2
3
4
5
const arr = [1, 2, 3];
console.log(Array.isArray(arr)); // true

const obj = { name: "Alice" };
console.log(Array.isArray(obj)); // false

优点:

  • 专门用于判断数组,准确且可靠。
  • 兼容性良好,支持所有现代浏览器。

2. 使用 instanceof

通过检查变量是否是 Array 的实例来判断。

语法:

1
value instanceof Array;

示例:

1
2
3
4
5
const arr = [1, 2, 3];
console.log(arr instanceof Array); // true

const obj = { name: "Alice" };
console.log(obj instanceof Array); // false

注意:

  • 在跨窗口或跨框架的环境中(如 iframe),instanceof 可能失效,因为不同窗口的 Array 构造函数不同。

3. 使用 Object.prototype.toString

通过调用 Object.prototype.toString 方法,返回 [object Array] 来判断。

语法:

1
Object.prototype.toString.call(value) === "[object Array]";

示例:

1
2
3
4
5
const arr = [1, 2, 3];
console.log(Object.prototype.toString.call(arr) === "[object Array]"); // true

const obj = { name: "Alice" };
console.log(Object.prototype.toString.call(obj) === "[object Array]"); // false

优点:

  • 适用于所有场景,包括跨窗口或跨框架的环境。

4. 使用 constructor

通过检查变量的 constructor 属性是否为 Array 来判断。

语法:

1
value.constructor === Array;

示例:

1
2
3
4
5
const arr = [1, 2, 3];
console.log(arr.constructor === Array); // true

const obj = { name: "Alice" };
console.log(obj.constructor === Array); // false

注意:

  • 如果变量的 constructor 属性被修改,可能会导致错误判断。

5. 使用 typeof 结合 instanceof

typeof 无法直接判断数组(因为数组是对象),但可以结合 instanceof 使用。

示例:

1
2
3
4
5
const arr = [1, 2, 3];
console.log(typeof arr === "object" && arr instanceof Array); // true

const obj = { name: "Alice" };
console.log(typeof obj === "object" && obj instanceof Array); // false

注意:

  • 这种方法较为繁琐,不推荐使用。

6. 使用 Array.prototype.isPrototypeOf

通过检查 Array.prototype 是否是变量的原型链来判断。

语法:

1
Array.prototype.isPrototypeOf(value);

示例:

1
2
3
4
5
const arr = [1, 2, 3];
console.log(Array.prototype.isPrototypeOf(arr)); // true

const obj = { name: "Alice" };
console.log(Array.prototype.isPrototypeOf(obj)); // false

注意:

  • 这种方法较为少见,不如 Array.isArray() 直观。

总结

以下是判断 JavaScript 变量是否为数组的推荐方法:

方法 推荐度 优点 缺点
Array.isArray() ⭐⭐⭐⭐⭐ 专门用于判断数组,准确且可靠
instanceof ⭐⭐⭐⭐ 简单直观 跨窗口或跨框架时可能失效
Object.prototype.toString.call ⭐⭐⭐⭐ 适用于所有场景,包括跨窗口或跨框架的环境 语法较为复杂
constructor ⭐⭐⭐ 简单直观 如果 constructor 被修改,可能导致错误
typeof 结合 instanceof ⭐⭐ 可以判断对象是否为数组 方法较为繁琐
Array.prototype.isPrototypeOf ⭐⭐ 可以判断原型链 方法较为少见

**推荐使用 Array.isArray()**,因为它是最简单、最可靠的方式。

JavaScript中null和undefined的区别是什么?

在 JavaScript 中,nullundefined 都表示“无”或“空”的概念,但它们在语义和用法上有明显的区别。以下是它们的详细对比:


1. 定义

类型 定义
undefined 表示变量未定义或未赋值。
null 表示变量被显式地赋值为空值,通常用于表示“无”或“对象不存在”。

2. 类型

类型 类型
undefined undefined 类型的唯一值。
null object 类型的唯一值(这是 JavaScript 的历史遗留问题)。

3. 默认值

类型 默认值
undefined 变量声明但未赋值时,默认值为 undefined
null 不是默认值,需要显式赋值。

4. 使用场景

类型 使用场景
undefined - 变量声明但未赋值。
- 函数参数未传递。
- 访问对象不存在的属性。
null - 显式地表示“无”或“空值”。
- 清空变量或对象。

5. 示例

undefined 示例

1
2
3
4
5
6
7
8
9
10
let x;
console.log(x); // undefined

function foo(a) {
console.log(a); // undefined
}
foo();

const obj = {};
console.log(obj.name); // undefined

null 示例

1
2
3
4
5
let y = null;
console.log(y); // null

const obj = { name: null };
console.log(obj.name); // 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
2
3
4
5
6
7
8
9
10
11
let a; // 变量声明但未赋值
console.log(a); // undefined

let b = null; // 显式地赋值为空值
console.log(b); // null

console.log(typeof a); // "undefined"
console.log(typeof b); // "object"

console.log(a == b); // true
console.log(a === b); // false

通过理解 nullundefined 的区别,可以更好地编写清晰、健壮的 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
2
const value = null;
console.log(value === null); // true

使用 Object.prototype.toString

1
2
const value = null;
console.log(Object.prototype.toString.call(value) === "[object Null]"); // true

5. 总结

  • typeof null 返回 "object" 是 JavaScript 的一个历史遗留问题。
  • 这个问题源于 JavaScript 早期的内存表示方式,null 的类型标签被误认为是对象。
  • 尽管这是一个错误,但由于兼容性和成本的考虑,未被修复。
  • 可以使用 ===Object.prototype.toString 准确判断 null

示例代码

1
2
3
4
5
console.log(typeof null); // "object"

const value = null;
console.log(value === null); // true
console.log(Object.prototype.toString.call(value) === "[object Null]"); // true

通过理解 typeof null 的行为,可以避免在实际开发中误用或误解这一特性。

typeof和instanceof有什么区别?

typeofinstanceof 是 JavaScript 中用于检测数据类型的两个操作符,但它们的功能和使用场景有所不同。以下是它们的详细对比:


1. typeof

  • 功能:用于检测变量的原始类型。
  • 返回值:返回一个字符串,表示变量的类型。
  • 适用场景:适用于检测原始类型(如 numberstringboolean 等)以及 undefinedfunction
  • 局限性:无法准确区分对象的具体类型(如 ArrayDate 等),且 typeof null 返回 "object"

示例:

1
2
3
4
5
6
7
8
console.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 function() {}); // "function"
console.log(typeof {}); // "object"
console.log(typeof []); // "object"

2. instanceof

  • 功能:用于检测对象是否是某个构造函数的实例。
  • 返回值:返回一个布尔值,表示对象是否是构造函数的实例。
  • 适用场景:适用于检测引用类型(如 ArrayDateObject 等)。
  • 局限性:无法检测原始类型,且在跨窗口或跨框架的环境中可能失效。

示例:

1
2
3
4
5
6
7
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(new Date() instanceof Date); // true
console.log(function() {} instanceof Function); // true

console.log(42 instanceof Number); // false
console.log("Hello" instanceof String); // false

3. 区别对比

特性 typeof instanceof
功能 检测原始类型 检测对象是否是某个构造函数的实例
返回值 返回一个字符串 返回一个布尔值
适用场景 适用于原始类型和 undefinedfunction 适用于引用类型
局限性 无法区分对象的具体类型,typeof null 返回 "object" 无法检测原始类型,跨窗口或跨框架时可能失效

4. 使用场景

typeof 的使用场景

  • 检测变量是否为原始类型(如 numberstringboolean 等)。
  • 检测变量是否为 undefinedfunction

instanceof 的使用场景

  • 检测对象是否是某个构造函数的实例(如 ArrayDate 等)。
  • 判断对象的继承关系。

5. 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// typeof 示例
console.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 function() {}); // "function"
console.log(typeof {}); // "object"
console.log(typeof []); // "object"

// instanceof 示例
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(new Date() instanceof Date); // true
console.log(function() {} instanceof Function); // true

console.log(42 instanceof Number); // false
console.log("Hello" instanceof String); // false

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.10.2)无法被精确表示,而是以近似值存储。

具体原因

  • 0.1 在二进制中是无限循环小数,存储时会丢失精度。
  • 0.2 在二进制中也是无限循环小数,存储时会丢失精度。
  • 0.10.2 相加时,它们的近似值相加,结果是一个接近 0.3 但不完全等于 0.3 的值。

示例

1
2
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

2. 如何让 0.1 + 0.2 === 0.3

方法 1:使用 Number.EPSILON 进行比较

Number.EPSILON 是 JavaScript 中表示最小精度误差的值。可以通过比较两个数的差值是否小于 Number.EPSILON 来判断它们是否相等。

1
2
3
4
5
function isEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}

console.log(isEqual(0.1 + 0.2, 0.3)); // true

方法 2:使用 toFixed 方法

通过 toFixed 方法将结果四舍五入到指定的小数位数。

1
2
const sum = (0.1 + 0.2).toFixed(1);
console.log(sum === "0.3"); // true

注意:toFixed 返回的是字符串,如果需要数字类型,可以转换为数字:

1
2
const sum = parseFloat((0.1 + 0.2).toFixed(1));
console.log(sum === 0.3); // true

方法 3:使用第三方库

可以使用第三方库(如 decimal.jsbig.js 等)来处理高精度计算。

1
2
3
4
// 使用 decimal.js
import Decimal from "decimal.js";
const sum = new Decimal(0.1).plus(new Decimal(0.2));
console.log(sum.equals(0.3)); // true

方法 4:放大为整数计算

将小数放大为整数,计算后再缩小为小数。

1
2
3
4
5
6
7
8
9
10
function add(a, b) {
const factor = Math.pow(10, Math.max(getDecimalLength(a), getDecimalLength(b)));
return (a * factor + b * factor) / factor;
}

function getDecimalLength(num) {
return (String(num).split(".")[1] || "").length;
}

console.log(add(0.1, 0.2) === 0.3); // true

3. 总结

方法 描述 优点 缺点
Number.EPSILON 比较两个数的差值是否小于最小精度误差。 简单直观 需要手动实现比较逻辑
toFixed 将结果四舍五入到指定的小数位数。 简单易用 返回字符串,需要转换
第三方库 使用高精度计算库(如 decimal.jsbig.js)。 高精度,功能强大 需要引入第三方库
放大为整数计算 将小数放大为整数,计算后再缩小为小数。 无需引入外部库 实现较为复杂

4. 推荐方法

  • 如果只需要简单的比较,推荐使用 Number.EPSILON
  • 如果需要高精度计算,推荐使用第三方库(如 decimal.js)。

通过理解浮点数精度问题,可以更好地处理 JavaScript 中的数值计算。

如何获取安全的unfined值?

在 JavaScript 中,undefined 是一个全局变量,表示未定义的值。通常情况下,直接使用 undefined 是安全的,但在某些情况下,undefined 可能被意外覆盖,导致不可预测的行为。为了获取安全的 undefined 值,可以采用以下几种方法:


1. 直接使用 undefined

在大多数情况下,直接使用 undefined 是安全的,因为它是 JavaScript 的全局变量。

示例:

1
2
let x;
console.log(x === undefined); // true

注意:

  • 如果在局部作用域中定义了 undefined 变量,可能会导致意外行为。

2. 使用 void 0

void 是一个运算符,用于执行表达式并返回 undefinedvoid 0 是获取 undefined 的一种安全方式,因为它总是返回 undefined,即使 undefined 被覆盖。

示例:

1
2
3
let undefined = 42; // 覆盖 undefined
console.log(undefined); // 42
console.log(void 0); // undefined

优点:

  • 即使 undefined 被覆盖,void 0 仍然返回 undefined

缺点:

  • 语法较为晦涩,可读性较差。

3. 使用函数参数默认值

在函数参数中,如果没有传递值,参数默认值为 undefined。可以通过这种方式获取 undefined

示例:

1
2
3
4
5
function getUndefined(value = undefined) {
return value;
}

console.log(getUndefined()); // undefined

优点:

  • 语法清晰,易于理解。

缺点:

  • 需要定义一个函数。

4. 使用解构赋值

通过解构赋值,可以获取 undefined

示例:

1
2
const { undefined: safeUndefined } = {};
console.log(safeUndefined); // undefined

优点:

  • 语法简洁。

缺点:

  • 可读性较差。

5. 使用全局对象

在浏览器环境中,可以通过 window 对象获取 undefined。在 Node.js 环境中,可以通过 global 对象获取 undefined

示例:

1
2
3
4
5
// 浏览器环境
console.log(window.undefined); // undefined

// Node.js 环境
console.log(global.undefined); // undefined

注意:

  • 在严格模式("use strict")下,windowglobal 可能无法直接访问。

6. 使用 Object.prototype

通过 Object.prototype 获取 undefined

示例:

1
2
const safeUndefined = Object.prototype.valueOf.call(undefined);
console.log(safeUndefined); // undefined

优点:

  • 即使 undefined 被覆盖,仍然可以获取 undefined

缺点:

  • 语法较为复杂。

7. 使用 Symbol

通过 Symbol 创建一个唯一的值,模拟 undefined

示例:

1
2
const safeUndefined = Symbol("undefined");
console.log(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
    3
    console.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 中,NaNnumber 类型的一个特殊值,类似于 Infinity-Infinity

JavaScript 中的 number 类型包括:

  • 普通数字(如 423.14)。
  • 特殊值:NaNInfinity-Infinity

4. 如何判断一个值是否是 NaN

由于 NaN 是 JavaScript 中唯一一个不等于自身的值,因此不能直接使用 ===== 来判断一个值是否是 NaN

方法 1:使用 isNaN 函数

isNaN 函数用于判断一个值是否是 NaN。如果值是 NaN 或无法转换为数字,则返回 true

1
2
3
console.log(isNaN(NaN)); // true
console.log(isNaN("abc")); // true
console.log(isNaN(42)); // false

注意:isNaN 会将值隐式转换为数字,因此非数字值(如字符串)也会返回 true

方法 2:使用 Number.isNaN 方法

Number.isNaN 是 ES6 引入的方法,用于严格判断一个值是否是 NaN。只有值是 NaN 时才会返回 true

1
2
3
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("abc")); // false
console.log(Number.isNaN(42)); // false

方法 3:利用 NaN 不等于自身的特性

1
2
3
4
5
6
7
function isNaNValue(value) {
return value !== value;
}

console.log(isNaNValue(NaN)); // true
console.log(isNaNValue("abc")); // false
console.log(isNaNValue(42)); // false

5. 总结

  • typeof NaN 返回 "number",因为 NaNnumber 类型的一个特殊值。
  • 使用 Number.isNaNvalue !== value 可以准确判断一个值是否是 NaN

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(typeof NaN); // "number"

console.log(isNaN(NaN)); // true
console.log(isNaN("abc")); // true
console.log(isNaN(42)); // false

console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("abc")); // false
console.log(Number.isNaN(42)); // false

function isNaNValue(value) {
return value !== value;
}

console.log(isNaNValue(NaN)); // true
console.log(isNaNValue("abc")); // false
console.log(isNaNValue(42)); // false

通过理解 NaN 的行为和特性,可以更好地处理 JavaScript 中的数值计算和异常情况。

isNaN和Number.isNaN函数有什么区别?

isNaNNumber.isNaN 是 JavaScript 中用于判断一个值是否为 NaN 的两个函数,但它们的行为和用途有显著区别。以下是它们的详细对比:


1. isNaN

  • 功能:判断一个值是否是 NaN 或是否可以转换为 NaN
  • 行为
    • 如果传入的值是 NaN,则返回 true
    • 如果传入的值可以转换为 NaN(如非数字字符串),则返回 true
    • 其他情况返回 false
  • 实现原理isNaN 会先将传入的值隐式转换为数字,然后判断是否为 NaN

示例:

1
2
3
4
5
6
console.log(isNaN(NaN)); // true
console.log(isNaN("abc")); // true("abc" 转换为数字是 NaN)
console.log(isNaN("123")); // false("123" 转换为数字是 123)
console.log(isNaN(42)); // false
console.log(isNaN(null)); // false(null 转换为数字是 0)
console.log(isNaN(undefined)); // true(undefined 转换为数字是 NaN)

2. Number.isNaN

  • 功能:严格判断一个值是否是 NaN
  • 行为
    • 如果传入的值是 NaN,则返回 true
    • 其他情况返回 false
  • 实现原理Number.isNaN 不会对传入的值进行类型转换,直接判断是否为 NaN

示例:

1
2
3
4
5
6
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("abc")); // false("abc" 不是 NaN)
console.log(Number.isNaN("123")); // false("123" 不是 NaN)
console.log(Number.isNaN(42)); // false
console.log(Number.isNaN(null)); // false(null 不是 NaN)
console.log(Number.isNaN(undefined)); // false(undefined 不是 NaN)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// isNaN 示例
console.log(isNaN(NaN)); // true
console.log(isNaN("abc")); // true
console.log(isNaN("123")); // false
console.log(isNaN(42)); // false
console.log(isNaN(null)); // false
console.log(isNaN(undefined)); // true

// Number.isNaN 示例
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("abc")); // false
console.log(Number.isNaN("123")); // false
console.log(Number.isNaN(42)); // false
console.log(Number.isNaN(null)); // false
console.log(Number.isNaN(undefined)); // false

6. 总结

  • **isNaN**:会先将值隐式转换为数字,然后判断是否为 NaN。适用于需要判断值是否为 NaN 或是否可以转换为 NaN 的场景。
  • **Number.isNaN**:不会进行类型转换,严格判断值是否为 NaN。适用于需要严格判断值是否为 NaN 的场景。

根据具体需求选择合适的函数,可以更准确地判断 NaN

==操作符的强制类型转换规则是什么?

== 是 JavaScript 中的宽松相等运算符,它在比较两个值时,会进行隐式的类型转换(强制类型转换)。以下是 == 操作符的强制类型转换规则:


1. 基本规则

  • 如果两个值的类型相同,直接比较它们的值。
  • 如果两个值的类型不同,会尝试将它们转换为相同类型后再比较。

2. 类型转换规则

以下是 == 操作符在不同类型之间的转换规则:

(1) nullundefined

  • nullundefined== 比较时相等。
    1
    console.log(null == undefined); // true

(2) numberstring

  • 如果一个是 number,另一个是 string,会将 string 转换为 number 再比较。
    1
    2
    console.log(42 == "42"); // true("42" 转换为 42)
    console.log(0 == ""); // true("" 转换为 0)

(3) boolean 和其他类型

  • 如果一个是 boolean,会将 boolean 转换为 number 再比较(true 转换为 1false 转换为 0)。
    1
    2
    3
    4
    console.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
      3
      console.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) false0""

  • false0""== 比较时相等。
    1
    2
    3
    console.log(false == 0); // true
    console.log(false == ""); // true
    console.log(0 == ""); // true

(2) true1"1"

  • true1"1"== 比较时相等。
    1
    2
    3
    console.log(true == 1); // true
    console.log(true == "1"); // true
    console.log(1 == "1"); // true

(3) null0

  • null0== 比较时不相等。
    1
    console.log(null == 0); // false

(4) undefined0

  • undefined0== 比较时不相等。
    1
    console.log(undefined == 0); // false

4. 总结

== 操作符的强制类型转换规则如下:

  1. nullundefined 相等。
  2. numberstring 比较时,将 string 转换为 number
  3. boolean 与其他类型比较时,将 boolean 转换为 number
  4. object 与原始类型比较时,将 object 转换为原始类型。
  5. symbol 与其他类型比较时,不进行转换。
  6. NaN 与任何值比较时,返回 false

5. 推荐使用 ===

由于 == 的隐式类型转换规则较为复杂,容易导致意外行为,推荐使用 ===(严格相等运算符)进行比较。=== 不会进行类型转换,直接比较值和类型。

示例:

1
2
console.log(42 == "42"); // true(宽松相等)
console.log(42 === "42"); // false(严格相等)

通过理解 == 的强制类型转换规则,可以更好地避免 JavaScript 中的隐式类型转换问题。

JavaScript其他值到字符串的转换规则是什么?

在 JavaScript 中,将其他类型的值转换为字符串的规则取决于具体的类型。以下是详细的转换规则:


1. 原始类型到字符串的转换

类型 转换规则
string 保持不变。
number 直接转换为对应的字符串形式。
boolean true 转换为 "true"false 转换为 "false"
null 转换为 "null"
undefined 转换为 "undefined"
symbol 转换为 "Symbol(description)",其中 descriptionSymbol 的描述。
bigint 直接转换为对应的字符串形式。

示例:

1
2
3
4
5
6
7
console.log(String("Hello")); // "Hello"
console.log(String(42)); // "42"
console.log(String(true)); // "true"
console.log(String(null)); // "null"
console.log(String(undefined)); // "undefined"
console.log(String(Symbol("foo"))); // "Symbol(foo)"
console.log(String(123n)); // "123"

2. 对象到字符串的转换

对象转换为字符串时,会调用对象的 toString() 方法。如果 toString() 方法返回的不是原始类型,则会调用 valueOf() 方法。

默认行为

  • 大多数对象(如 {}[]function 等)的 toString() 方法返回 "[object Type]",其中 Type 是对象的类型。
  • 数组的 toString() 方法会将数组元素转换为字符串,并用逗号连接。
  • 函数的 toString() 方法返回函数的源代码字符串。

示例:

1
2
3
console.log(String({})); // "[object Object]"
console.log(String([1, 2, 3])); // "1,2,3"
console.log(String(function() {})); // "function() {}"

自定义 toString() 方法

可以为对象自定义 toString() 方法,控制其转换为字符串的行为。

1
2
3
4
5
6
7
const obj = {
toString() {
return "Custom Object";
}
};

console.log(String(obj)); // "Custom Object"

3. 特殊对象的转换

对象 转换规则
Date 返回日期的字符串表示(如 "Wed Oct 18 2023 12:00:00 GMT+0800 (中国标准时间)")。
RegExp 返回正则表达式的字符串表示(如 "/abc/g")。
Error 返回错误的字符串表示(如 "Error: 错误信息")。

示例:

1
2
3
console.log(String(new Date())); // "Wed Oct 18 2023 12:00:00 GMT+0800 (中国标准时间)"
console.log(String(/abc/g)); // "/abc/g"
console.log(String(new Error("错误信息"))); // "Error: 错误信息"

4. 隐式转换

在 JavaScript 中,某些操作会隐式地将值转换为字符串。例如:

  • 字符串拼接(+ 操作符,其中一个操作数是字符串)。
  • 模板字符串(`Hello ${value}`)。

示例:

1
2
console.log("Value: " + 42); // "Value: 42"
console.log(`Value: ${true}`); // "Value: true"

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 转换为 1false 转换为 0
null 转换为 0
undefined 转换为 NaN
symbol 抛出 TypeError 异常。
bigint 如果数字在 Number 的范围内,则转换为对应的数字;否则抛出 TypeError 异常。

示例:

1
2
3
4
5
6
7
8
9
console.log(Number("42")); // 42
console.log(Number("")); // 0
console.log(Number("abc")); // NaN
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(Number(Symbol("foo"))); // TypeError
console.log(Number(123n)); // 123

2. 对象到数字的转换

对象转换为数字时,会调用对象的 valueOf() 方法。如果 valueOf() 方法返回的不是原始类型,则会调用 toString() 方法,然后将结果转换为数字。

默认行为

  • 大多数对象(如 {}[]function 等)的 valueOf() 方法返回对象本身,因此会调用 toString() 方法。
  • 数组的 toString() 方法会将数组元素转换为字符串,并用逗号连接。
  • 函数的 toString() 方法返回函数的源代码字符串。

示例:

1
2
3
4
console.log(Number({})); // NaN({} 转换为 "[object Object]",再转换为 NaN)
console.log(Number([42])); // 42([42] 转换为 "42",再转换为 42)
console.log(Number([1, 2, 3])); // NaN([1, 2, 3] 转换为 "1,2,3",再转换为 NaN)
console.log(Number(function() {})); // NaN(函数转换为 "function() {}",再转换为 NaN)

自定义 valueOf() 方法

可以为对象自定义 valueOf() 方法,控制其转换为数字的行为。

1
2
3
4
5
6
7
const obj = {
valueOf() {
return 42;
}
};

console.log(Number(obj)); // 42

3. 特殊对象的转换

对象 转换规则
Date 返回时间戳(从 1970 年 1 月 1 日 00:00:00 UTC 到当前时间的毫秒数)。
RegExp 转换为 NaN
Error 转换为 NaN

示例:

1
2
3
console.log(Number(new Date())); // 1697606400000(当前时间的时间戳)
console.log(Number(/abc/g)); // NaN
console.log(Number(new Error("错误信息"))); // NaN

4. 隐式转换

在 JavaScript 中,某些操作会隐式地将值转换为数字。例如:

  • 数学运算(如 +-*/)。
  • 比较运算(如 ><>=<=)。
  • 一元加号运算符(+)。

示例:

1
2
3
console.log("42" - 0); // 42
console.log(true + 1); // 2
console.log(+""); // 0

5. 总结

类型 转换结果
string - 有效数字形式:对应的数字。
- 空字符串:0
- 无效数字形式:NaN
number 保持不变。
boolean true1false0
null 0
undefined NaN
symbol 抛出 TypeError 异常。
bigint 如果数字在 Number 的范围内,则转换为对应的数字;否则抛出 TypeError 异常。
object 调用 valueOf() 方法,默认返回 NaN
特殊对象 返回特定的数字值(如日期的时间戳)或 NaN

通过理解 JavaScript 中的数字转换规则,可以更好地处理不同类型的数据并避免意外行为。

JavaScript其他值到布尔值的转换规则是什么?

在 JavaScript 中,将其他类型的值转换为布尔值的规则遵循 “假值(falsy)”“真值(truthy)” 的概念。以下是详细的转换规则:


1. 假值(Falsy Values)

以下值在转换为布尔值时会被转换为 false

  • false
  • 0(包括 -00.0
  • ""(空字符串)
  • null
  • undefined
  • NaN

示例:

1
2
3
4
5
6
console.log(Boolean(false)); // false
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false

2. 真值(Truthy Values)

除了上述假值之外,其他所有值在转换为布尔值时都会被转换为 true。例如:

  • true
  • 非零数字(包括正数、负数、浮点数)
  • 非空字符串
  • 对象(包括 {}[]function 等)
  • Symbol

示例:

1
2
3
4
5
6
7
8
console.log(Boolean(true)); // true
console.log(Boolean(42)); // true
console.log(Boolean(-1)); // true
console.log(Boolean("Hello")); // true
console.log(Boolean({})); // true
console.log(Boolean([])); // true
console.log(Boolean(function() {})); // true
console.log(Boolean(Symbol("foo"))); // true

3. 隐式转换

在 JavaScript 中,某些操作会隐式地将值转换为布尔值。例如:

  • if 语句的条件判断。
  • 逻辑运算符(如 &&||!)。
  • 三元运算符(? :)。

示例:

1
2
3
4
5
6
7
if ("Hello") {
console.log("Truthy"); // 输出 "Truthy"
}

console.log(!!0); // false
console.log(!!"Hello"); // true
console.log(0 || "Default"); // "Default"

4. 总结

类型 转换为布尔值的结果
false false
0-00.0 false
""(空字符串) false
null false
undefined false
NaN false
其他所有值 true

5. 注意事项

  • 对象:即使是空对象({})或空数组([]),也会被转换为 true
  • 字符串:只有空字符串("")会被转换为 false,其他字符串(包括 "0""false")都会被转换为 true
  • 数字:只有 0NaN 会被转换为 false,其他数字(包括 Infinity)都会被转换为 true

6. 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log(Boolean(false)); // false
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false

console.log(Boolean(true)); // true
console.log(Boolean(42)); // true
console.log(Boolean(-1)); // true
console.log(Boolean("Hello")); // true
console.log(Boolean({})); // true
console.log(Boolean([])); // true
console.log(Boolean(function() {})); // true
console.log(Boolean(Symbol("foo"))); // true

通过理解 JavaScript 中的布尔值转换规则,可以更好地处理条件判断和逻辑运算。

JavaScript中||和&&操作符的返回值是什么?

在 JavaScript 中,||(逻辑或)和 &&(逻辑与)操作符的返回值遵循 短路求值(Short-Circuit Evaluation) 的规则。它们的返回值不一定是布尔值,而是根据操作数的值返回其中一个操作数。


1. ||(逻辑或)操作符

  • 规则
    • 从左到右依次检查操作数。
    • 如果第一个操作数为 真值(truthy),则返回第一个操作数。
    • 如果第一个操作数为 假值(falsy),则返回第二个操作数。
  • 返回值:返回第一个为真值的操作数,或者最后一个操作数。

示例:

1
2
3
4
console.log("Hello" || "World"); // "Hello"(第一个操作数为真值)
console.log("" || "World"); // "World"(第一个操作数为假值)
console.log(null || undefined); // undefined(两个操作数都为假值,返回最后一个)
console.log(0 || 42); // 42(第一个操作数为假值)

2. &&(逻辑与)操作符

  • 规则
    • 从左到右依次检查操作数。
    • 如果第一个操作数为 假值(falsy),则返回第一个操作数。
    • 如果第一个操作数为 真值(truthy),则返回第二个操作数。
  • 返回值:返回第一个为假值的操作数,或者最后一个操作数。

示例:

1
2
3
4
console.log("Hello" && "World"); // "World"(第一个操作数为真值)
console.log("" && "World"); // ""(第一个操作数为假值)
console.log(null && undefined); // null(第一个操作数为假值)
console.log(0 && 42); // 0(第一个操作数为假值)

3. 短路求值(Short-Circuit Evaluation)

  • || 操作符
    • 如果第一个操作数为真值,不会计算第二个操作数。
    • 如果第一个操作数为假值,才会计算第二个操作数。
  • && 操作符
    • 如果第一个操作数为假值,不会计算第二个操作数。
    • 如果第一个操作数为真值,才会计算第二个操作数。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
console.log("foo called");
return true;
}

function bar() {
console.log("bar called");
return false;
}

console.log(foo() || bar()); // 输出 "foo called",返回 true(不会调用 bar)
console.log(bar() && foo()); // 输出 "bar called",返回 false(不会调用 foo)

4. 使用场景

|| 操作符

  • 默认值设置:当变量为假值时,使用默认值。
    1
    2
    3
    const name = "";
    const displayName = name || "Guest";
    console.log(displayName); // "Guest"

&& 操作符

  • 条件执行:当变量为真值时,执行某个操作。
    1
    2
    const user = { name: "Alice" };
    user && console.log(user.name); // "Alice"

5. 总结

操作符 规则 返回值
` `
&& 返回第一个为假值的操作数,或者最后一个操作数。 假值或最后一个操作数

通过理解 ||&& 操作符的行为,可以更好地利用它们进行条件判断和默认值设置。

Object.is()与比较操作符==和===的区别是什么?

Object.is() 和比较操作符 ===== 是 JavaScript 中用于比较值的工具,但它们的行为和用途有所不同。以下是它们的详细对比:


1. ==(宽松相等)

  • 功能:比较两个值是否相等,允许类型转换。
  • 行为
    • 如果两个值的类型不同,会尝试将它们转换为相同类型后再比较。
    • 转换规则较为复杂,可能导致意外行为。

示例:

1
2
3
console.log(42 == "42"); // true(字符串 "42" 转换为数字 42)
console.log(null == undefined); // true(null 和 undefined 相等)
console.log(false == 0); // true(false 转换为 0)

2. ===(严格相等)

  • 功能:比较两个值是否相等,不允许类型转换。
  • 行为
    • 如果两个值的类型不同,直接返回 false
    • 如果类型相同,再比较它们的值。

示例:

1
2
3
console.log(42 === "42"); // false(类型不同)
console.log(null === undefined); // false(类型不同)
console.log(false === 0); // false(类型不同)

3. Object.is()

  • 功能:比较两个值是否相等,行为与 === 类似,但有以下不同:
    • NaNNaN 相等。
    • +0-0 不相等。
  • 行为
    • 如果两个值的类型和值都相同,则返回 true
    • 不进行类型转换。

示例:

1
2
3
4
5
console.log(Object.is(42, "42")); // false(类型不同)
console.log(Object.is(null, undefined)); // false(类型不同)
console.log(Object.is(false, 0)); // false(类型不同)
console.log(Object.is(NaN, NaN)); // true(NaN 与 NaN 相等)
console.log(Object.is(+0, -0)); // false(+0 与 -0 不相等)

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
nullundefined 比较 null == undefined 返回 true null === undefined 返回 false Object.is(null, undefined) 返回 false

5. 使用场景

== 的使用场景

  • 需要宽松比较,允许类型转换。
  • 例如:处理用户输入时,判断输入是否为特定值。

=== 的使用场景

  • 需要严格比较,不允许类型转换。
  • 例如:在条件判断中,确保类型和值都匹配。

Object.is() 的使用场景

  • 需要精确比较,尤其是处理 NaN+0/-0 时。
  • 例如:在算法或库中,确保比较结果精确无误。

6. 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// == 示例
console.log(42 == "42"); // true
console.log(null == undefined); // true
console.log(false == 0); // true

// === 示例
console.log(42 === "42"); // false
console.log(null === undefined); // false
console.log(false === 0); // false

// Object.is() 示例
console.log(Object.is(42, "42")); // false
console.log(Object.is(null, undefined)); // false
console.log(Object.is(false, 0)); // false
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, -0)); // false

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
nullundefined 比较 null == undefined 返回 true null === undefined 返回 false Object.is(null, undefined) 返回 false

根据具体需求选择合适的比较方式,可以更好地处理 JavaScript 中的值比较。

什么是Javascript中的包装类型?

在 JavaScript 中,包装类型(Wrapper Types) 是指与原始类型(如 stringnumberboolean)对应的对象类型。它们分别是 StringNumberBoolean。包装类型的存在是为了让原始类型可以像对象一样调用方法和访问属性。


1. 包装类型的作用

JavaScript 中的原始类型(如 stringnumberboolean)本身不是对象,因此不能直接调用方法或访问属性。包装类型的作用是将原始类型包装成对象,使其可以调用方法和访问属性。

示例:

1
2
3
const str = "Hello";
console.log(str.length); // 5
console.log(str.toUpperCase()); // "HELLO"

在上面的代码中,str 是一个原始类型的字符串,但它可以调用 length 属性和 toUpperCase() 方法。这是因为 JavaScript 在背后自动将原始类型包装成 String 对象。


2. 包装类型的自动转换

当对原始类型调用方法或访问属性时,JavaScript 会自动将其转换为对应的包装类型对象。这个过程称为 自动装箱(Autoboxing)

示例:

1
2
const num = 42;
console.log(num.toFixed(2)); // "42.00"

在上面的代码中,num 是一个原始类型的数字,但调用 toFixed() 方法时,JavaScript 会将其自动转换为 Number 对象。


3. 包装类型的手动创建

除了自动装箱,也可以手动创建包装类型的对象。

示例:

1
2
3
4
5
6
7
const strObj = new String("Hello");
const numObj = new Number(42);
const boolObj = new Boolean(true);

console.log(typeof strObj); // "object"
console.log(typeof numObj); // "object"
console.log(typeof boolObj); // "object"

注意:手动创建的包装类型对象是对象类型,而不是原始类型。


4. 包装类型与原始类型的区别

特性 原始类型 包装类型
类型 stringnumberboolean StringNumberBoolean
存储方式 存储在栈内存中 存储在堆内存中
可变性 不可变 可变
比较方式 比较值是否相等 比较引用是否相等
示例 const str = "Hello"; const strObj = new String("Hello");

示例:

1
2
3
4
5
const str1 = "Hello";
const str2 = new String("Hello");

console.log(str1 === str2); // false(原始类型与包装类型不相等)
console.log(str1 == str2); // true(值相等,但类型不同)

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 中,隐式类型转换是指在运行时自动将一种类型的值转换为另一种类型,而无需显式调用转换函数。隐式类型转换通常发生在以下场景中:

  • 使用 == 进行比较时。
  • 使用 +-*/ 等运算符时。
  • 在条件判断中(如 ifwhile 等)。
  • 在模板字符串中嵌入变量时。

以下是 JavaScript 中隐式类型转换的详细规则和示例:


1. == 操作符的隐式类型转换

== 操作符在比较两个值时,会尝试将它们转换为相同类型后再比较。

转换规则:

  • 如果一个是 number,另一个是 string,会将 string 转换为 number
  • 如果一个是 boolean,会将 boolean 转换为 number
  • 如果一个是 object,会将 object 转换为原始类型。

示例:

1
2
3
console.log(42 == "42"); // true("42" 转换为 42)
console.log(true == 1); // true(true 转换为 1)
console.log([] == 0); // true([] 转换为 "",再转换为 0)

2. + 操作符的隐式类型转换

+ 操作符在以下场景中会进行隐式类型转换:

  • 如果其中一个操作数是 string,会将另一个操作数转换为 string 并拼接。
  • 如果操作数不是 string,会尝试将它们转换为 number 并相加。

示例:

1
2
3
console.log("Hello" + 42); // "Hello42"(42 转换为 "42")
console.log(1 + true); // 2(true 转换为 1)
console.log(1 + "2"); // "12"(1 转换为 "1")

3. -*/ 操作符的隐式类型转换

-*/ 操作符会尝试将操作数转换为 number 后再运算。

示例:

1
2
3
console.log("42" - 2); // 40("42" 转换为 42)
console.log("10" * "2"); // 20("10" 和 "2" 转换为 10 和 2)
console.log("10" / "2"); // 5("10" 和 "2" 转换为 10 和 2)

4. 条件判断中的隐式类型转换

在条件判断中(如 ifwhile 等),会将值隐式转换为布尔值。

转换规则:

  • 假值(false0""nullundefinedNaN)会被转换为 false
  • 其他值会被转换为 true

示例:

1
2
3
4
5
6
7
if ("Hello") {
console.log("Truthy"); // 输出 "Truthy"
}

if (0) {
console.log("Falsy"); // 不会输出
}

5. 模板字符串中的隐式类型转换

在模板字符串中嵌入变量时,会将变量隐式转换为字符串。

示例:

1
2
const num = 42;
console.log(`The number is ${num}`); // "The number is 42"

6. 对象到原始类型的隐式转换

当对象参与运算或比较时,会尝试将对象转换为原始类型。

转换规则:

  • 默认调用 valueOf() 方法,如果返回的不是原始类型,再调用 toString() 方法。
  • 如果对象是 Date 类型,优先调用 toString() 方法。

示例:

1
2
3
4
5
6
7
8
9
10
const obj = {
valueOf() {
return 42;
}
};

console.log(obj == 42); // true(obj 转换为 42)

const arr = [1, 2, 3];
console.log(arr + ""); // "1,2,3"(arr 转换为 "1,2,3")

7. 总结

场景 转换规则
== 比较 尝试将两个值转换为相同类型后再比较。
+ 操作符 如果一个是 string,转换为字符串拼接;否则转换为 number 相加。
-*/ 操作符 将操作数转换为 number 后再运算。
条件判断 将值转换为布尔值。
模板字符串 将变量转换为字符串。
对象到原始类型 调用 valueOf() 方法,如果返回的不是原始类型,再调用 toString() 方法。

8. 避免隐式类型转换

为了避免隐式类型转换带来的意外行为,可以:

  • 使用 === 代替 == 进行比较。
  • 显式调用转换函数(如 Number()String()Boolean())。
  • 在需要时使用 typeofinstanceof 进行类型检查。

示例:

1
2
3
console.log(Number("42") === 42); // true
console.log(String(42) === "42"); // true
console.log(Boolean(0) === false); // true

通过理解 JavaScript 中的隐式类型转换规则,可以更好地处理不同类型的数据并避免意外行为。

Javascript中+操作符什么时候用于字符串的拼接?

在 JavaScript 中,+ 操作符用于字符串拼接的规则如下:


1. + 操作符的两种用途

  • 数值加法:如果两个操作数都是 number 类型,+ 操作符会执行加法运算。
  • 字符串拼接:如果至少有一个操作数是 string 类型,+ 操作符会执行字符串拼接。

2. + 操作符的隐式类型转换

当使用 + 操作符时,JavaScript 会按照以下规则进行隐式类型转换:

  1. 如果其中一个操作数是 string 类型,则将另一个操作数转换为 string 类型,然后进行字符串拼接。
  2. 如果操作数不是 string 类型,则尝试将它们转换为 number 类型,然后执行加法运算。

3. + 操作符用于字符串拼接的场景

以下是 + 操作符用于字符串拼接的具体场景:

(1) 显式的字符串拼接

当至少有一个操作数是 string 类型时,+ 操作符会执行字符串拼接。

1
2
3
console.log("Hello" + "World"); // "HelloWorld"
console.log("Hello" + 42); // "Hello42"
console.log(42 + "Hello"); // "42Hello"

(2) 隐式的字符串拼接

当操作数中包含 string 类型时,即使另一个操作数不是 string 类型,也会被隐式转换为 string 类型。

1
2
3
4
console.log("Value: " + 42); // "Value: 42"
console.log("Value: " + true); // "Value: true"
console.log("Value: " + null); // "Value: null"
console.log("Value: " + undefined); // "Value: undefined"

(3) 混合类型的拼接

当操作数中包含 string 类型和其他类型时,+ 操作符会优先执行字符串拼接。

1
2
console.log(1 + 2 + "3"); // "33"(1 + 2 = 3,然后 "3" + "3" = "33")
console.log("1" + 2 + 3); // "123"("1" + 2 = "12",然后 "12" + 3 = "123")

(4) 对象与字符串的拼接

当操作数中包含 object 类型时,会先将对象转换为 string 类型,然后进行拼接。

1
2
console.log("Value: " + {}); // "Value: [object Object]"
console.log("Value: " + [1, 2, 3]); // "Value: 1,2,3"

4. + 操作符用于数值加法的场景

当两个操作数都是 number 类型时,+ 操作符会执行加法运算。

1
2
console.log(1 + 2); // 3
console.log(3.14 + 2.86); // 6

5. 总结

场景 操作数类型 行为
字符串拼接 至少有一个操作数是 string 将另一个操作数转换为 string 并拼接。
数值加法 两个操作数都是 number 执行加法运算。
混合类型 包含 string 和其他类型 优先执行字符串拼接。

6. 注意事项

  • 隐式转换的优先级:如果操作数中包含 string 类型,+ 操作符会优先执行字符串拼接。
  • 避免意外行为:如果希望执行加法运算,确保操作数都是 number 类型;如果希望执行字符串拼接,确保至少有一个操作数是 string 类型。

示例:

1
2
3
4
5
6
7
8
9
// 加法运算
console.log(1 + 2); // 3

// 字符串拼接
console.log("1" + 2); // "12"

// 混合类型
console.log(1 + 2 + "3"); // "33"
console.log("1" + 2 + 3); // "123"

通过理解 + 操作符的行为,可以更好地处理字符串拼接和数值加法。

JavaScript中为什么会有BigInt的提案?JavaScript 引入 BigInt 的提案是为了解决 Number 类型的局限性,尤其是在处理大整数时。以下是 BigInt 提案的背景和原因:


1. Number 类型的局限性

JavaScript 中的 Number 类型使用 IEEE 754 双精度浮点数 表示,其有效整数范围是 -2^53 到 2^53(即 Number.MIN_SAFE_INTEGERNumber.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
2
3
const bigInt = 9007199254740993n;
console.log(bigInt); // 9007199254740993n
console.log(typeof bigInt); // "bigint"

3. BigInt 的使用场景

(1) 大整数计算

BigInt 可以用于处理超出 Number 范围的整数计算。

1
2
3
const a = 9007199254740993n;
const b = 9007199254740994n;
console.log(a + b); // 18014398509481987n

(2) 加密和哈希算法

加密和哈希算法通常需要处理非常大的整数,BigInt 提供了更好的支持。

1
2
const hash = 1234567890123456789012345678901234567890n;
console.log(hash); // 1234567890123456789012345678901234567890n

(3) 金融计算

金融计算中需要高精度的整数运算,BigInt 可以避免浮点数精度问题。

1
2
const amount = 1000000000000000000000000n; // 1 万亿亿
console.log(amount); // 1000000000000000000000000n

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
    2
    const 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
2
3
4
5
6
7
8
9
const target = { a: 1 };
const source = { b: 2, c: { d: 3 } };

const result = Object.assign(target, source);
console.log(result); // { a: 1, b: 2, c: { d: 3 } }
console.log(target); // { a: 1, b: 2, c: { d: 3 } }

source.c.d = 4;
console.log(result.c.d); // 4(浅拷贝,嵌套对象被修改)

2. 对象拓展运算符(...

功能:

将源对象的属性复制到新对象中。

语法:

1
const result = { ...source1, ...source2 };

特点:

  • 浅拷贝:只会复制对象的可枚举属性,不会复制嵌套对象。
  • 创建新对象:不会修改现有对象,而是返回一个新对象。
  • 覆盖属性:如果多个源对象有同名属性,后面的源对象会覆盖前面的属性。

示例:

1
2
3
4
5
6
7
8
const source1 = { a: 1, c: { d: 3 } };
const source2 = { b: 2 };

const result = { ...source1, ...source2 };
console.log(result); // { a: 1, b: 2, c: { d: 3 } }

source1.c.d = 4;
console.log(result.c.d); // 4(浅拷贝,嵌套对象被修改)

3. 区别对比

特性 Object.assign 对象拓展运算符(...
目标对象 修改目标对象 创建新对象
返回值 返回目标对象 返回新对象
浅拷贝
覆盖属性 后面的源对象覆盖前面的属性 后面的源对象覆盖前面的属性
语法 Object.assign(target, ...sources) { ...source1, ...source2 }

4. 深拷贝 vs 浅拷贝

无论是 Object.assign 还是对象拓展运算符,它们都是 浅拷贝,即:

  • 只会复制对象的可枚举属性。
  • 如果属性是对象或数组,复制的是引用,而不是创建新的对象或数组。

示例:

1
2
3
4
5
6
7
8
9
10
11
const source = { a: 1, b: { c: 2 } };

// 使用 Object.assign
const result1 = Object.assign({}, source);
source.b.c = 3;
console.log(result1.b.c); // 3(浅拷贝,嵌套对象被修改)

// 使用对象拓展运算符
const result2 = { ...source };
source.b.c = 4;
console.log(result2.b.c); // 4(浅拷贝,嵌套对象被修改)

5. 如何实现深拷贝?

如果需要深拷贝对象,可以使用以下方法:

(1) JSON.parse(JSON.stringify(obj))

  • 将对象转换为 JSON 字符串,再解析为新的对象。
  • 缺点:无法复制函数、undefinedSymbol 等特殊值。
1
2
3
4
const source = { a: 1, b: { c: 2 } };
const result = JSON.parse(JSON.stringify(source));
source.b.c = 3;
console.log(result.b.c); // 2(深拷贝,嵌套对象未被修改)

(2) 递归实现深拷贝

  • 手动实现递归函数,复制对象的每个属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function deepClone(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
const result = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}

const source = { a: 1, b: { c: 2 } };
const result = deepClone(source);
source.b.c = 3;
console.log(result.b.c); // 2(深拷贝,嵌套对象未被修改)

(3) 使用第三方库

  • 使用 lodash 等第三方库实现深拷贝。
1
2
3
4
5
const _ = require("lodash");
const source = { a: 1, b: { c: 2 } };
const result = _.cloneDeep(source);
source.b.c = 3;
console.log(result.b.c); // 2(深拷贝,嵌套对象未被修改)

6. 总结

特性 Object.assign 对象拓展运算符(...
目标对象 修改目标对象 创建新对象
浅拷贝
适用场景 需要修改目标对象时 需要创建新对象时

无论是 Object.assign 还是对象拓展运算符,都是 浅拷贝。如果需要深拷贝,可以使用 JSON.parse(JSON.stringify(obj))、递归实现或第三方库。

Javascript中Map和Object的区别是什么?

在 JavaScript 中,MapObject 都可以用于存储键值对,但它们在设计和使用上有一些重要的区别。以下是 MapObject 的主要区别:


1. 键的类型

  • Map:键可以是任意类型的值(包括对象、函数、基本类型等)。
    1
    2
    3
    4
    const map = new Map();
    const keyObj = {};
    map.set(keyObj, "value");
    console.log(map.get(keyObj)); // 输出: value
  • Object:键只能是字符串或 Symbol。如果使用其他类型作为键,它会被自动转换为字符串。
    1
    2
    3
    4
    const obj = {};
    const keyObj = {};
    obj[keyObj] = "value";
    console.log(obj["[object Object]"]); // 输出: value

2. 键的顺序

  • Map:键的顺序是确定的,即插入顺序。遍历 Map 时,键值对会按照插入的顺序返回。
    1
    2
    3
    4
    const map = new Map();
    map.set("a", 1);
    map.set("b", 2);
    console.log([...map]); // 输出: [['a', 1], ['b', 2]]
  • Object:在 ES6 之前,键的顺序是不确定的。从 ES6 开始,Object 的键顺序遵循以下规则:
    1. 数字键(如 "1")按升序排列。
    2. 字符串键按插入顺序排列。
    3. Symbol 键按插入顺序排列。
      1
      2
      const obj = { b: 2, a: 1, 1: "one" };
      console.log(Object.keys(obj)); // 输出: ['1', 'b', 'a']

3. 大小

  • Map:可以通过 size 属性直接获取键值对的数量。
    1
    2
    3
    4
    const map = new Map();
    map.set("a", 1);
    map.set("b", 2);
    console.log(map.size); // 输出: 2
  • Object:需要手动计算键的数量。
    1
    2
    const obj = { a: 1, b: 2 };
    console.log(Object.keys(obj).length); // 输出: 2

4. 性能

  • Map:在频繁增删键值对的场景下,性能优于 Object
  • Object:在静态键值对(键不常变化)的场景下,性能与 Map 相当或更好。

5. 默认行为

  • Map:是一个纯粹的键值对集合,没有默认的键或方法。
  • Object:继承了 Object.prototype,因此默认包含一些方法和属性(如 toStringhasOwnProperty 等),这可能导致意外的行为。
    1
    2
    const obj = {};
    console.log(obj.toString); // 输出: [Function: toString]

6. 序列化

  • Map:不能直接使用 JSON.stringify 序列化。需要先转换为数组或对象。
    1
    2
    3
    const map = new Map();
    map.set("a", 1);
    console.log(JSON.stringify([...map])); // 输出: [["a",1]]
  • Object:可以直接使用 JSON.stringify 序列化。
    1
    2
    const obj = { a: 1 };
    console.log(JSON.stringify(obj)); // 输出: {"a":1}

7. 迭代

  • Map:可以直接使用 for...offorEach 进行迭代。
    1
    2
    3
    4
    5
    6
    const map = new Map();
    map.set("a", 1);
    map.set("b", 2);
    for (const [key, value] of map) {
    console.log(key, value);
    }
  • Object:需要使用 Object.keysObject.valuesObject.entries 进行迭代。
    1
    2
    3
    4
    const 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 操作符

  • 作用:返回一个字符串,表示未经计算的操作数的类型。
  • 特点
    • 可以区分基本类型(如 numberstringbooleanundefined)。
    • 对于 null 和引用类型(如 objectarrayfunction),typeof 的返回值不够精确。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    console.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 操作符

  • 作用:检查一个对象是否是某个构造函数的实例。
  • 特点
    • 适用于判断引用类型(如 ArrayObjectFunction)。
    • 不能用于判断基本类型。
  • 示例
    1
    2
    3
    4
    console.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
    8
    console.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 属性

  • 作用:返回创建对象的构造函数的引用。
  • 特点
    • 可以判断引用类型。
    • 不适用于 nullundefined
    • 如果对象的原型链被修改,constructor 可能不准确。
  • 示例
    1
    2
    3
    4
    5
    6
    console.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
    2
    console.log(Array.isArray([])); // true
    console.log(Array.isArray({})); // false

6. === 严格相等操作符

  • 作用:判断一个值是否是 nullundefined
  • 特点
    • 只能用于判断 nullundefined
  • 示例
    1
    2
    console.log(null === null); // true
    console.log(undefined === undefined); // true

7. 自定义类型判断函数

  • 作用:结合多种方法,实现更灵活的类型判断。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function 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 引用类型(如 ArrayObject 不适用于基本类型
Object.prototype.toString 所有类型(最常用)
constructor 引用类型 不适用于 nullundefined,原型链修改后不准确
Array.isArray 判断数组 仅适用于数组
=== 判断 nullundefined 仅适用于 nullundefined
自定义函数 灵活组合多种方法 需要手动实现

根据具体需求选择合适的方法。对于大多数场景,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
    2
    const 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
    3
    const promise = new Promise((resolve, reject) => {
    resolve("Success!");
    });

9. 代理和反射对象

  • **Proxy**:用于定义自定义行为的对象。
    1
    2
    3
    4
    5
    const 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 的内置对象涵盖了从基本类型到复杂数据结构的各个方面,为开发者提供了强大的功能支持。以下是一些常用的内置对象及其用途:

对象类别 常用对象 用途
值类型包装对象 BooleanNumberString 包装基本类型,提供额外方法
基础对象 ObjectFunctionArray 创建对象、函数和数组
错误对象 ErrorTypeError 处理运行时错误
数学和日期对象 MathDate 处理数学运算和日期时间
集合对象 SetMap 存储唯一值和键值对
正则表达式对象 RegExp 处理字符串匹配
JSON 对象 JSON 解析和序列化 JSON 数据
Promise 对象 Promise 处理异步操作
代理和反射对象 ProxyReflect 自定义对象行为和操作对象
其他内置对象 SymbolBigInt 表示唯一值和大整数
全局对象 globalThis 指向全局对象

根据具体需求选择合适的内置对象,可以大大提高开发效率。

JavaScript中常用的正则表达式有哪些?

在 JavaScript 中,正则表达式(RegExp)是处理字符串匹配和替换的强大工具。以下是一些常用的正则表达式及其应用场景:


1. 匹配数字

  • 匹配整数
    1
    2
    3
    const regex = /^\d+$/;
    console.log(regex.test("123")); // true
    console.log(regex.test("12a")); // false
  • 匹配浮点数
    1
    2
    3
    const regex = /^\d+\.\d+$/;
    console.log(regex.test("12.34")); // true
    console.log(regex.test("12")); // false

2. 匹配字母

  • 匹配小写字母
    1
    2
    3
    const regex = /^[a-z]+$/;
    console.log(regex.test("abc")); // true
    console.log(regex.test("123")); // false
  • 匹配大写字母
    1
    2
    3
    const regex = /^[A-Z]+$/;
    console.log(regex.test("ABC")); // true
    console.log(regex.test("abc")); // false

3. 匹配字母和数字

  • 匹配字母和数字的组合
    1
    2
    3
    const regex = /^[a-zA-Z0-9]+$/;
    console.log(regex.test("abc123")); // true
    console.log(regex.test("abc@123")); // false

4. 匹配邮箱地址

  • 简单邮箱验证
    1
    2
    3
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    console.log(regex.test("test@example.com")); // true
    console.log(regex.test("test@.com")); // false

5. 匹配 URL

  • 简单 URL 验证
    1
    2
    3
    const 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
    3
    const regex = /^1[3-9]\d{9}$/;
    console.log(regex.test("13800138000")); // true
    console.log(regex.test("12345678901")); // false

7. 匹配日期

  • 匹配 YYYY-MM-DD 格式的日期
    1
    2
    3
    const 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
    3
    const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
    console.log(regex.test("Password123")); // true
    console.log(regex.test("password")); // false

9. 匹配 IP 地址

  • 匹配 IPv4 地址
    1
    2
    3
    const 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
    3
    const regex = /<("[^"]*"|'[^']*'|[^'">])*>/;
    console.log(regex.test("<div>Hello</div>")); // true
    console.log(regex.test("Hello")); // false

11. 匹配空白行

  • 匹配空白行
    1
    2
    3
    const regex = /^\s*$/;
    console.log(regex.test(" ")); // true
    console.log(regex.test("Hello")); // false

12. 匹配中文

  • 匹配中文字符
    1
    2
    3
    const regex = /^[\u4e00-\u9fa5]+$/;
    console.log(regex.test("中文")); // true
    console.log(regex.test("English")); // false

13. 匹配文件扩展名

  • 匹配 .jpg.png 文件
    1
    2
    3
    const regex = /\.(jpg|png)$/i;
    console.log(regex.test("image.jpg")); // true
    console.log(regex.test("image.gif")); // false

14. 匹配身份证号码

  • 匹配中国身份证号码
    1
    2
    3
    const 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
    3
    const 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):整数或浮点数,例如 423.14
  • 布尔值(Boolean)truefalse
  • 数组(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
      3
      const jsonStr = '{"name": "John", "age": 30}';
      const obj = JSON.parse(jsonStr);
      console.log(obj.name); // 输出: John
    • 生成:使用 JSON.stringify() 将 JavaScript 对象转换为 JSON 字符串。
      1
      2
      3
      const obj = { name: "John", age: 30 };
      const jsonStr = JSON.stringify(obj);
      console.log(jsonStr); // 输出: {"name":"John","age":30}
  • 在其他语言中
    • Python:使用 json.loads()json.dumps()
    • Java:使用 JacksonGson 库。
    • PHP:使用 json_decode()json_encode()

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
    3
    const script = document.createElement("script");
    script.src = "script.js";
    document.body.appendChild(script);

4. setTimeoutsetInterval

  • 作用:通过定时器延迟加载脚本。
  • 特点
    • 可以精确控制脚本加载的延迟时间。
    • 适用于需要延迟执行的场景。
  • 示例
    1
    2
    3
    4
    5
    setTimeout(() => {
    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
    12
    const 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
    5
    window.addEventListener("load", () => {
    const script = document.createElement("script");
    script.src = "script.js";
    document.body.appendChild(script);
    });

7. 模块化加载(ES Modules)

  • 作用:使用 import() 动态加载 ES 模块。
  • 特点
    • 适用于现代浏览器,支持按需加载模块。
  • 示例
    1
    2
    3
    import("module.js").then((module) => {
    module.default(); // 调用模块的默认导出
    });

8. requestIdleCallback

  • 作用:在浏览器空闲时加载脚本。
  • 特点
    • 适用于低优先级的脚本,避免影响页面的关键渲染。
  • 示例
    1
    2
    3
    4
    5
    requestIdleCallback(() => {
    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
    3
    const 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
    3
    const script = document.createElement("script");
    script.src = "script.js";
    document.body.appendChild(script);

4. import() 动态导入(ES Modules)

  • 作用:动态加载 ES 模块。
  • 特点
    • 支持按需加载模块。
    • 适用于现代浏览器。
  • 示例
    1
    2
    3
    import("module.js").then((module) => {
    module.default(); // 调用模块的默认导出
    });

5. load 事件

  • 作用:在页面完全加载后(包括图片、样式等资源)加载脚本。
  • 特点
    • 适用于需要在页面所有资源加载完成后执行的脚本。
  • 示例
    1
    2
    3
    4
    5
    window.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
    12
    const 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
    5
    requestIdleCallback(() => {
    const script = document.createElement("script");
    script.src = "script.js";
    document.body.appendChild(script);
    });

8. setTimeoutsetInterval

  • 作用:通过定时器延迟加载脚本。
  • 特点
    • 可以精确控制脚本加载的延迟时间。
    • 适用于需要延迟执行的场景。
  • 示例
    1
    2
    3
    4
    5
    setTimeout(() => {
    const script = document.createElement("script");
    script.src = "script.js";
    document.body.appendChild(script);
    }, 3000); // 延迟 3 秒加载

9. 第三方库

  • 作用:使用第三方库(如 LazyLoad)实现异步加载。
  • 特点
    • 提供更高级的功能和更好的兼容性。
  • 示例
    1
    2
    3
    const lazyLoadInstance = new LazyLoad({
    elements_selector: ".lazy",
    });

各方式的区别

方法 加载时机 执行顺序 适用场景
async 属性 脚本加载完成后立即执行 不确定 独立脚本,不依赖其他脚本
defer 属性 HTML 解析完成后执行 按文档顺序 需要保持执行顺序的脚本
动态加载脚本 可编程控制 不确定 需要灵活控制加载时机的脚本
import() 动态导入 按需加载模块 按模块依赖顺序 现代浏览器,按需加载模块
load 事件 页面所有资源加载完成后执行 不确定 需要在页面所有资源加载完成后执行的脚本
IntersectionObserver 脚本进入视口时加载 不确定 懒加载,当脚本进入视口时加载
requestIdleCallback 浏览器空闲时加载 不确定 低优先级脚本,避免影响关键渲染
setTimeout 延迟指定时间后加载 不确定 需要延迟指定时间加载的脚本
第三方库 根据库的实现 根据库的实现 需要高级功能和更好的兼容性

总结

  • 如果需要保持脚本的执行顺序,使用 defer 属性。
  • 如果脚本是独立的,不依赖其他脚本,使用 async 属性。
  • 如果需要灵活控制加载时机,使用动态加载脚本或 import() 动态导入。
  • 如果需要懒加载,使用 IntersectionObserver
  • 如果需要延迟指定时间加载,使用 setTimeout

根据具体需求选择合适的异步加载方式,可以有效优化页面性能,提升用户体验。

什么是JavaScript的类数组对象?如何转化为数组?

在 JavaScript 中,类数组对象(Array-like Object)是一种具有数组特性的对象,但它们并不是真正的数组。类数组对象通常具有以下特征:

  1. 具有 length 属性:表示对象的元素数量。
  2. 通过索引访问元素:可以通过数字索引(如 obj[0])访问元素。
  3. 不是数组:没有数组的方法(如 pushpopslice 等)。

常见的类数组对象包括:

  • arguments 对象(在函数中使用的参数列表)。
  • DOM 元素集合(如 document.getElementsByTagName 返回的结果)。
  • 字符串(可以通过索引访问字符)。

1. 类数组对象的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// arguments 对象
function example() {
console.log(arguments); // 类数组对象
console.log(arguments.length); // 3
console.log(arguments[0]); // 1
}
example(1, 2, 3);

// DOM 元素集合
const divs = document.getElementsByTagName("div");
console.log(divs); // 类数组对象
console.log(divs.length); // 元素数量
console.log(divs[0]); // 第一个 div 元素

// 字符串
const str = "hello";
console.log(str.length); // 5
console.log(str[0]); // "h"

2. 将类数组对象转化为数组

类数组对象没有数组的方法(如 mapfilterreduce 等),因此在实际开发中,通常需要将它们转化为真正的数组。以下是常见的转化方法:

方法 1:Array.from()

  • 作用:将类数组对象或可迭代对象转化为数组。
  • 示例
    1
    2
    3
    const 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
    3
    const 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
    3
    const argumentsObj = { 0: "a", 1: "b", 2: "c", length: 3 };
    const arr = [...argumentsObj];
    console.log(arr); // ["a", "b", "c"]

方法 4:手动遍历

  • 作用:通过遍历类数组对象,将元素逐个添加到数组中。
  • 示例
    1
    2
    3
    4
    5
    6
    const 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
2
3
4
5
function example() {
const arr = Array.from(arguments);
console.log(arr); // [1, 2, 3]
}
example(1, 2, 3);

DOM 元素集合

1
2
3
const divs = document.getElementsByTagName("div");
const arr = Array.from(divs);
console.log(arr); // [div, div, div]

字符串

1
2
3
const str = "hello";
const arr = Array.from(str);
console.log(arr); // ["h", "e", "l", "l", "o"]

4. 类数组对象与数组的区别

特性 类数组对象 数组
length 属性
索引访问 支持 支持
数组方法(如 push 不支持 支持
可迭代性 部分支持(如 arguments 支持
转化为数组 需要手动转化 无需转化

5. 总结

  • 类数组对象:具有 length 属性和索引访问特性,但没有数组方法。
  • 转化为数组:可以使用 Array.from()Array.prototype.slice.call()、扩展运算符或手动遍历。
  • 应用场景:在处理 arguments、DOM 元素集合或字符串时,通常需要将类数组对象转化为数组,以便使用数组的方法。

通过将类数组对象转化为数组,可以更方便地操作数据,提升代码的可读性和效率。

JavaScript的数组有哪些原生方法?

JavaScript 提供了丰富的数组原生方法,用于操作和处理数组数据。以下是常见的数组方法及其分类:


1. 修改数组的方法

  • **push()**:在数组末尾添加一个或多个元素,返回新数组的长度。
    1
    2
    const arr = [1, 2];
    arr.push(3); // arr: [1, 2, 3]
  • **pop()**:删除数组的最后一个元素,并返回该元素。
    1
    2
    const arr = [1, 2, 3];
    arr.pop(); // arr: [1, 2]
  • **unshift()**:在数组开头添加一个或多个元素,返回新数组的长度。
    1
    2
    const arr = [1, 2];
    arr.unshift(0); // arr: [0, 1, 2]
  • **shift()**:删除数组的第一个元素,并返回该元素。
    1
    2
    const arr = [0, 1, 2];
    arr.shift(); // arr: [1, 2]
  • **splice()**:在指定位置添加或删除元素,返回被删除的元素。
    1
    2
    const arr = [1, 2, 3];
    arr.splice(1, 1, 4); // arr: [1, 4, 3]
  • **reverse()**:反转数组的元素顺序。
    1
    2
    const arr = [1, 2, 3];
    arr.reverse(); // arr: [3, 2, 1]
  • **sort()**:对数组元素进行排序。
    1
    2
    const arr = [3, 1, 2];
    arr.sort(); // arr: [1, 2, 3]

2. 访问数组的方法

  • **concat()**:合并两个或多个数组,返回新数组。
    1
    2
    3
    const arr1 = [1, 2];
    const arr2 = [3, 4];
    const arr3 = arr1.concat(arr2); // arr3: [1, 2, 3, 4]
  • **slice()**:从数组中提取指定范围的元素,返回新数组。
    1
    2
    const arr = [1, 2, 3, 4];
    const newArr = arr.slice(1, 3); // newArr: [2, 3]
  • **indexOf()**:返回指定元素在数组中首次出现的索引,若不存在则返回 -1
    1
    2
    const arr = [1, 2, 3];
    const index = arr.indexOf(2); // index: 1
  • **lastIndexOf()**:返回指定元素在数组中最后一次出现的索引,若不存在则返回 -1
    1
    2
    const arr = [1, 2, 3, 2];
    const index = arr.lastIndexOf(2); // index: 3
  • **includes()**:判断数组是否包含指定元素,返回布尔值。
    1
    2
    const arr = [1, 2, 3];
    const result = arr.includes(2); // result: true

3. 遍历数组的方法

  • **forEach()**:对数组中的每个元素执行指定操作。
    1
    2
    const arr = [1, 2, 3];
    arr.forEach((item) => console.log(item)); // 输出: 1, 2, 3
  • **map()**:对数组中的每个元素执行指定操作,返回新数组。
    1
    2
    const arr = [1, 2, 3];
    const newArr = arr.map((item) => item * 2); // newArr: [2, 4, 6]
  • **filter()**:过滤数组中的元素,返回满足条件的新数组。
    1
    2
    const arr = [1, 2, 3];
    const newArr = arr.filter((item) => item > 1); // newArr: [2, 3]
  • **reduce()**:对数组中的元素进行累加操作,返回最终结果。
    1
    2
    const arr = [1, 2, 3];
    const result = arr.reduce((acc, item) => acc + item, 0); // result: 6
  • **reduceRight()**:从右到左对数组中的元素进行累加操作,返回最终结果。
    1
    2
    const arr = [1, 2, 3];
    const result = arr.reduceRight((acc, item) => acc + item, 0); // result: 6
  • **every()**:判断数组中的每个元素是否都满足条件,返回布尔值。
    1
    2
    const arr = [1, 2, 3];
    const result = arr.every((item) => item > 0); // result: true
  • **some()**:判断数组中是否有元素满足条件,返回布尔值。
    1
    2
    const arr = [1, 2, 3];
    const result = arr.some((item) => item > 2); // result: true
  • **find()**:返回数组中第一个满足条件的元素。
    1
    2
    const arr = [1, 2, 3];
    const result = arr.find((item) => item > 1); // result: 2
  • **findIndex()**:返回数组中第一个满足条件的元素的索引。
    1
    2
    const arr = [1, 2, 3];
    const result = arr.findIndex((item) => item > 1); // result: 1

4. 其他方法

  • **join()**:将数组中的所有元素拼接成一个字符串。
    1
    2
    const arr = [1, 2, 3];
    const str = arr.join("-"); // str: "1-2-3"
  • **toString()**:将数组转换为字符串。
    1
    2
    const arr = [1, 2, 3];
    const str = arr.toString(); // str: "1,2,3"
  • **flat()**:将多维数组扁平化。
    1
    2
    const arr = [1, [2, [3]]];
    const newArr = arr.flat(2); // newArr: [1, 2, 3]
  • **flatMap()**:先对数组中的每个元素执行操作,再将结果扁平化。
    1
    2
    const arr = [1, 2, 3];
    const newArr = arr.flatMap((item) => [item * 2]); // newArr: [2, 4, 6]
  • **fill()**:用指定值填充数组。
    1
    2
    const arr = [1, 2, 3];
    arr.fill(0); // arr: [0, 0, 0]

5. 静态方法

  • **Array.isArray()**:判断一个值是否为数组。
    1
    2
    const 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 的数组方法可以分为以下几类:

  1. 修改数组pushpopunshiftshiftsplicereversesort
  2. 访问数组concatsliceindexOflastIndexOfincludes
  3. 遍历数组forEachmapfilterreducereduceRighteverysomefindfindIndex
  4. 其他方法jointoStringflatflatMapfill
  5. 静态方法Array.isArrayArray.fromArray.of

这些方法为数组操作提供了强大的支持,可以满足各种开发需求。

为什么JavaScript函数的arguments参数是类数组而不是数组?如何遍历类数组?

JavaScript 中的 arguments 对象是类数组而不是真正的数组,这是由 JavaScript 的历史设计和语言特性决定的。以下是详细解释以及如何遍历类数组的方法:


1. 为什么 arguments 是类数组而不是数组?

历史原因

  • 在 JavaScript 的早期版本中,数组的功能并不像现在这样强大,因此 arguments 被设计为类数组对象,而不是真正的数组。
  • arguments 的设计初衷是提供一个简单的方式来访问函数的所有参数,而不是作为一个功能齐全的数组。

性能考虑

  • 类数组对象比真正的数组更轻量,因为它不需要实现数组的所有方法(如 pushpopmap 等)。
  • 这种设计可以提高性能,尤其是在函数调用频繁的场景中。

灵活性

  • arguments 是动态的,它会随着函数参数的变化而变化。如果它是一个真正的数组,这种动态性可能会带来额外的复杂性。

语言一致性

  • JavaScript 中有许多类数组对象(如 DOM 元素集合、字符串等),arguments 的设计与这些对象保持一致。

2. arguments 的类数组特性

  • length 属性:表示参数的个数。
  • 索引访问:可以通过数字索引(如 arguments[0])访问参数。
  • 不是数组:没有数组的方法(如 pushmapforEach 等)。

示例:

1
2
3
4
5
6
function example(a, b, c) {
console.log(arguments.length); // 3
console.log(arguments[0]); // 1
console.log(Array.isArray(arguments)); // false
}
example(1, 2, 3);

3. 如何遍历类数组

方法 1:for 循环

  • 使用传统的 for 循环遍历类数组对象。
    1
    2
    3
    4
    5
    6
    function 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
    6
    function example() {
    for (const arg of arguments) {
    console.log(arg);
    }
    }
    example(1, 2, 3);

方法 3:Array.from()

  • 将类数组对象转化为数组,然后使用数组的方法遍历。
    1
    2
    3
    4
    5
    function example() {
    const arr = Array.from(arguments);
    arr.forEach((arg) => console.log(arg));
    }
    example(1, 2, 3);

方法 4:扩展运算符(...

  • 使用扩展运算符将类数组对象转化为数组,然后遍历。
    1
    2
    3
    4
    5
    function 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
    4
    function example() {
    Array.prototype.forEach.call(arguments, (arg) => console.log(arg));
    }
    example(1, 2, 3);

4. arguments 的替代方案

在现代 JavaScript 中,可以使用 剩余参数(rest parameters) 来替代 arguments。剩余参数是一个真正的数组,支持所有数组方法。

示例:

1
2
3
4
5
function example(...args) {
console.log(Array.isArray(args)); // true
args.forEach((arg) => console.log(arg));
}
example(1, 2, 3);

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
      3
      const element = document.getElementById("myId");
      const elements = document.getElementsByClassName("myClass");
      const elements = document.querySelectorAll("div.myClass");
    • 修改内容:
      1
      2
      element.innerHTML = "Hello, World!";
      element.textContent = "Hello, World!";
    • 修改样式:
      1
      2
      element.style.color = "red";
      element.classList.add("active");
    • 添加或删除节点:
      1
      2
      3
      const 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
      2
      const width = screen.width;
      const height = screen.height;
    • 操作 URL:
      1
      2
      console.log(location.href); // 获取当前 URL
      location.href = "https://www.example.com"; // 跳转到新 URL
    • 操作历史记录:
      1
      2
      history.back(); // 返回上一页
      history.forward(); // 前进到下一页

3. DOM 和 BOM 的区别

特性 DOM BOM
定义 操作网页内容的接口 操作浏览器窗口的接口
核心对象 documentElementNode windownavigatorlocationhistoryscreen
标准化 是 W3C 标准 非标准化,浏览器实现可能不同
主要功能 动态修改网页内容 控制浏览器窗口和交互
示例 修改元素内容、样式、结构 打开新窗口、获取屏幕尺寸、操作 URL

4. DOM 和 BOM 的关系

  • 依赖关系:BOM 包含 DOM,window 对象是 BOM 的顶层对象,而 document 对象是 window 的一个属性。
  • 使用场景
    • DOM 用于操作网页内容,例如动态更新页面元素。
    • BOM 用于与浏览器交互,例如控制窗口、获取浏览器信息。

5. 总结

  • DOM:用于操作网页内容,核心对象是 document,是标准化的接口。
  • BOM:用于操作浏览器窗口,核心对象是 window,是非标准化的接口。
  • 关系:BOM 包含 DOM,window 是 BOM 的顶层对象,documentwindow 的属性。

通过理解 DOM 和 BOM 的区别和关系,可以更好地掌握 JavaScript 在网页开发和浏览器交互中的应用。

escape、encodeURI、endcodeURIComponent的区别是什么?

在 JavaScript 中,escapeencodeURIencodeURIComponent 都是用于编码字符串的函数,但它们的作用和使用场景有所不同。以下是它们的区别和具体用法:


1. escape

  • 作用:对字符串进行编码,使其可以在 URL 中使用。注意:escape 已被弃用,不推荐使用。
  • 编码规则
    • 对 ASCII 字母、数字、@*_+-./ 不编码。
    • 对其他字符进行 Unicode 编码(如 %20 表示空格)。
  • 示例
    1
    2
    console.log(escape("Hello World!")); // "Hello%20World%21"
    console.log(escape("你好")); // "%u4F60%u597D"

2. encodeURI

  • 作用:对整个 URI 进行编码,使其可以在 URL 中使用。
  • 编码规则
    • 对 URI 中的保留字符(如 :/?#@ 等)不编码。
    • 对其他字符进行 UTF-8 编码(如 %20 表示空格)。
  • 使用场景:用于编码完整的 URI。
  • 示例
    1
    2
    console.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 的组成部分(如查询参数、路径段等)。
  • 示例
    1
    2
    console.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
2
3
4
5
6
7
8
9
10
11
const url = "https://www.example.com/你好?name=张三&age=20";

// encodeURI
console.log(encodeURI(url)); // "https://www.example.com/%E4%BD%A0%E5%A5%BD?name=%E5%BC%A0%E4%B8%89&age=20"

// encodeURIComponent
const query = "name=张三&age=20";
console.log(encodeURIComponent(query)); // "name%3D%E5%BC%A0%E4%B8%89%26age%3D20"

// escape(不推荐)
console.log(escape(url)); // "https%3A//www.example.com/%u4F60%u597D%3Fname%3D%u5F20%u4E09%26age%3D20"

7. 总结

  • **encodeURI**:用于编码完整 URI,保留 URI 的保留字符。
  • **encodeURIComponent**:用于编码 URI 的组成部分,对所有保留字符编码。
  • **escape**:已弃用,不推荐使用。

根据具体需求选择合适的编码方法,可以确保 URL 的正确性和安全性。

什么是AJAX?如何实现一个AJAX请求

什么是 AJAX?

AJAX(Asynchronous JavaScript and XML)是一种用于创建异步 Web 应用的技术。它允许在不重新加载整个页面的情况下,与服务器进行数据交换并更新部分网页内容。AJAX 的核心是通过 JavaScript 发送 HTTP 请求,并在后台处理服务器返回的数据。

AJAX 的特点:

  1. 异步:请求在后台发送,不会阻塞页面加载。
  2. 无刷新更新:可以局部更新页面内容,提升用户体验。
  3. 数据格式灵活:支持多种数据格式,如 XML、JSON、HTML、纯文本等。

如何实现一个 AJAX 请求?

在 JavaScript 中,可以通过原生 XMLHttpRequest 对象或现代的 fetch API 来实现 AJAX 请求。以下是两种方法的实现示例:


方法 1:使用 XMLHttpRequest

XMLHttpRequest 是传统的 AJAX 实现方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1. 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();

// 2. 设置请求方法和 URL
xhr.open("GET", "https://jsonplaceholder.typicode.com/posts/1", true);

// 3. 设置请求头(可选)
xhr.setRequestHeader("Content-Type", "application/json");

// 4. 定义回调函数,处理响应
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
// 请求成功,解析响应数据
const response = JSON.parse(xhr.responseText);
console.log(response);
} else {
// 请求失败
console.error("Request failed with status:", xhr.status);
}
};

// 5. 定义错误处理函数
xhr.onerror = function () {
console.error("Request failed");
};

// 6. 发送请求
xhr.send();

方法 2:使用 fetch API

fetch 是现代的 AJAX 实现方式,基于 Promise,语法更简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 发送请求
fetch("https://jsonplaceholder.typicode.com/posts/1", {
method: "GET", // 请求方法
headers: {
"Content-Type": "application/json", // 请求头
},
})
.then((response) => {
if (response.ok) {
// 解析响应数据
return response.json();
} else {
// 请求失败
throw new Error("Request failed with status: " + response.status);
}
})
.then((data) => {
// 处理响应数据
console.log(data);
})
.catch((error) => {
// 处理错误
console.error(error);
});

AJAX 请求的步骤

无论是使用 XMLHttpRequest 还是 fetch,AJAX 请求的基本步骤如下:

  1. 创建请求对象

    • XMLHttpRequestnew XMLHttpRequest()
    • fetch:直接调用 fetch() 函数。
  2. 设置请求方法和 URL

    • 指定请求方法(如 GETPOST)和目标 URL。
  3. 设置请求头(可选)

    • 例如,设置 Content-Typeapplication/json
  4. 发送请求

    • XMLHttpRequest:调用 send() 方法。
    • fetch:直接返回 Promise。
  5. 处理响应

    • 解析响应数据(如 JSON、XML)。
    • 更新页面内容或执行其他操作。
  6. 处理错误

    • 捕获请求失败或网络错误。

AJAX 的常见应用场景

  1. 表单提交:异步提交表单数据,避免页面刷新。
  2. 动态加载内容:从服务器加载数据并更新页面部分内容。
  3. 实时搜索:根据用户输入实时搜索并显示结果。
  4. 分页加载:加载更多数据时无需刷新页面。
  5. 用户验证:检查用户名或邮箱是否已被注册。

AJAX 的优缺点

优点:

  1. 提升用户体验:无需刷新页面即可更新内容。
  2. 减少带宽消耗:只传输必要的数据,减少服务器负载。
  3. 异步处理:不会阻塞页面加载。

缺点:

  1. SEO 不友好:动态加载的内容可能无法被搜索引擎抓取。
  2. 复杂性增加:需要处理异步逻辑和错误处理。
  3. 安全性问题:可能引发跨站脚本攻击(XSS)或跨站请求伪造(CSRF)。

总结

  • AJAX 是一种通过 JavaScript 发送异步 HTTP 请求的技术,用于实现无刷新更新页面内容。
  • 可以通过 XMLHttpRequestfetch 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
    3
    element.classList.add("active"); // 添加类名
    element.classList.remove("active"); // 移除类名
    element.classList.toggle("active"); // 切换类名

4. 修改元素样式

  • **style**:直接修改元素的样式。
    1
    2
    element.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
    3
    element.addEventListener("click", function () {
    console.log("Element clicked!");
    });
  • **removeEventListener**:移除事件监听器。
    1
    element.removeEventListener("click", handleClick);

9. 操作表单

  • **value**:获取或设置表单元素的值。
    1
    2
    const inputValue = inputElement.value;
    inputElement.value = "New Value";
  • **checked**:获取或设置复选框或单选按钮的选中状态。
    1
    2
    const isChecked = checkboxElement.checked;
    checkboxElement.checked = true;

10. 操作表格

  • **insertRow**:在表格中插入新行。
    1
    const newRow = tableElement.insertRow();
  • **insertCell**:在行中插入新单元格。
    1
    const newCell = newRow.insertCell();

总结

常见的 DOM 操作包括:

  1. 获取元素getElementByIdquerySelector 等。
  2. 修改内容innerHTMLtextContent 等。
  3. 修改属性getAttributesetAttribute 等。
  4. 修改样式styleclassList 等。
  5. 创建和插入元素createElementappendChild 等。
  6. 删除元素removeChildremove 等。
  7. 遍历元素parentNodechildren 等。
  8. 事件处理addEventListenerremoveEventListener 等。
  9. 操作表单valuechecked 等。
  10. 操作表格insertRowinsertCell 等。

掌握这些 DOM 操作可以让你更灵活地控制网页内容和行为,提升用户体验。

use strict是什么意思?使用它有什么区别?

"use strict" 是 JavaScript 中的一种指令,用于启用严格模式(Strict Mode)。严格模式是 ECMAScript 5(ES5)引入的一种特殊的 JavaScript 执行模式,旨在改进代码的安全性、可读性和性能。


1. 什么是严格模式?

严格模式对 JavaScript 的语法和行为进行了一些限制,避免了一些潜在的错误和不规范的写法。它可以帮助开发者编写更安全、更高效的代码。


2. 如何启用严格模式?

在脚本或函数的开头添加 "use strict"; 即可启用严格模式。

全局启用

1
2
3
4
"use strict";

// 严格模式下的代码
let x = 10;

局部启用(函数内部)

1
2
3
4
5
6
function myFunction() {
"use strict";

// 严格模式下的代码
let y = 20;
}

3. 严格模式的主要区别和限制

以下是严格模式与普通模式的主要区别:

1. 禁止隐式声明全局变量

在普通模式下,未使用 varletconst 声明的变量会自动成为全局变量。严格模式下会抛出错误。

1
2
"use strict";
x = 10; // 抛出错误:Uncaught ReferenceError: x is not defined

2. 禁止删除变量、函数或函数参数

在普通模式下,删除变量、函数或函数参数不会报错,但严格模式下会抛出错误。

1
2
3
"use strict";
let y = 20;
delete y; // 抛出错误:Uncaught SyntaxError: Delete of an unqualified identifier in strict mode

3. 禁止重复的属性名或参数名

在普通模式下,对象字面量或函数参数中允许重复的属性名或参数名,但严格模式下会抛出错误。

1
2
3
4
5
"use strict";
const obj = {
x: 10,
x: 20, // 抛出错误:Uncaught SyntaxError: Duplicate data property in object literal not allowed in strict mode
};

4. 禁止使用 with 语句

with 语句在严格模式下被禁用,因为它会导致代码难以理解和优化。

1
2
3
4
"use strict";
with (Math) {
console.log(PI); // 抛出错误:Uncaught SyntaxError: Strict mode code may not include a with statement
}

5. 禁止使用 eval 创建变量

在普通模式下,eval 可以创建变量,但在严格模式下,eval 创建的变量只能在 eval 内部使用。

1
2
3
"use strict";
eval("var x = 10;");
console.log(x); // 抛出错误:Uncaught ReferenceError: x is not defined

6. 禁止对只读属性赋值

在严格模式下,对只读属性(如 undefinedNaN 等)赋值会抛出错误。

1
2
"use strict";
undefined = 10; // 抛出错误:Uncaught TypeError: Cannot assign to read only property 'undefined' of object '#<Window>'

7. 禁止使用 arguments.calleearguments.caller

在严格模式下,arguments.calleearguments.caller 被禁用,因为它们会降低代码的可读性和性能。

1
2
3
4
"use strict";
function myFunction() {
console.log(arguments.callee); // 抛出错误:Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
}

8. this 的值为 undefined

在普通模式下,全局函数中的 this 指向全局对象(如 window),但在严格模式下,this 的值为 undefined

1
2
3
4
5
"use strict";
function myFunction() {
console.log(this); // 输出:undefined
}
myFunction();

9. 禁止八进制字面量

在普通模式下,以 0 开头的数字会被解释为八进制,但在严格模式下会抛出错误。

1
2
"use strict";
let x = 0123; // 抛出错误:Uncaught SyntaxError: Octal literals are not allowed in strict mode

4. 严格模式的优点

  1. 更安全的代码:严格模式禁止了一些潜在的错误行为,如隐式声明全局变量。
  2. 更好的性能:严格模式禁用了某些难以优化的语法(如 with 语句),提高了代码的执行效率。
  3. 更清晰的代码:严格模式强制使用更规范的语法,提高了代码的可读性和可维护性。

5. 严格模式的缺点

  1. 兼容性问题:严格模式是 ES5 引入的特性,旧版浏览器可能不支持。
  2. 迁移成本:将现有代码迁移到严格模式可能需要修改部分代码。

6. 总结

  • "use strict" 用于启用严格模式,改进代码的安全性、可读性和性能。
  • 主要区别:严格模式禁止隐式声明全局变量、删除变量、重复属性名、with 语句等。
  • 优点:更安全、更高效、更清晰的代码。
  • 缺点:兼容性问题和迁移成本。

在现代 JavaScript 开发中,建议始终使用严格模式,以避免潜在的错误并提高代码质量。

JavaScript如何判断一个对象是否属于某个类?

在 JavaScript 中,判断一个对象是否属于某个类可以通过以下几种方式实现:


1. instanceof 操作符

  • 作用:检查对象是否是某个类的实例。
  • 特点
    • 适用于构造函数创建的实例。
    • 会检查对象的原型链。
  • 示例
    1
    2
    3
    class MyClass {}
    const obj = new MyClass();
    console.log(obj instanceof MyClass); // true

2. constructor 属性

  • 作用:通过对象的 constructor 属性检查其构造函数。
  • 特点
    • 适用于构造函数创建的实例。
    • 如果对象的原型链被修改,constructor 可能不准确。
  • 示例
    1
    2
    3
    class MyClass {}
    const obj = new MyClass();
    console.log(obj.constructor === MyClass); // true

3. Object.prototype.isPrototypeOf()

  • 作用:检查对象的原型链中是否包含某个类的原型。
  • 特点
    • 适用于任何对象。
    • 会检查对象的原型链。
  • 示例
    1
    2
    3
    class MyClass {}
    const obj = new MyClass();
    console.log(MyClass.prototype.isPrototypeOf(obj)); // true

4. Object.getPrototypeOf()

  • 作用:获取对象的原型,并与类的原型进行比较。
  • 特点
    • 适用于任何对象。
    • 不会检查对象的原型链。
  • 示例
    1
    2
    3
    class MyClass {}
    const obj = new MyClass();
    console.log(Object.getPrototypeOf(obj) === MyClass.prototype); // true

5. Symbol.hasInstance

  • 作用:自定义 instanceof 的行为。
  • 特点
    • 适用于需要自定义判断逻辑的场景。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    class MyClass {
    static [Symbol.hasInstance](obj) {
    return obj && obj.isMyClass;
    }
    }
    const obj = { isMyClass: true };
    console.log(obj instanceof MyClass); // true

6. typeofObject.prototype.toString

  • 作用:结合 typeofObject.prototype.toString 判断对象的类型。
  • 特点
    • 适用于基本类型和内置对象。
  • 示例
    1
    2
    3
    class MyClass {}
    const obj = new MyClass();
    console.log(Object.prototype.toString.call(obj) === "[object Object]"); // true

7. 自定义方法

  • 作用:在类中定义方法来判断对象是否属于该类。
  • 特点
    • 灵活,可以根据需要自定义逻辑。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    class 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 行为 灵活,可自定义判断逻辑
typeofObject.prototype.toString 基本类型和内置对象 适用于基本类型和内置对象
自定义方法 需要自定义逻辑的场景 灵活,可自定义逻辑

根据具体需求选择合适的方法,可以准确判断对象是否属于某个类。

ajax、axios、fetchd的区别是什么?

AJAXAxiosFetch 都是用于在 JavaScript 中发送 HTTP 请求的技术,但它们在工作原理、使用方式和功能上有一些区别。以下是它们的详细对比:


1. AJAX(Asynchronous JavaScript and XML)

  • 定义:AJAX 是一种技术概念,用于在不重新加载整个页面的情况下,通过 JavaScript 异步发送 HTTP 请求并更新部分网页内容。
  • 实现方式:通常通过 XMLHttpRequest 对象实现。
  • 特点
    • 原生支持,无需额外库。
    • 语法较为复杂,需要手动处理请求和响应。
    • 支持老版本浏览器。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    const 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
    7
    axios.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
    8
    fetch("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
    4
    const arr = [1, 2, 3];
    for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
    }

2. for...of 循环

  • 特点:用于遍历可迭代对象(如数组),语法简洁。
  • 示例
    1
    2
    3
    4
    const arr = [1, 2, 3];
    for (const item of arr) {
    console.log(item);
    }

3. forEach 方法

  • 特点:对数组中的每个元素执行回调函数,无返回值。
  • 示例
    1
    2
    3
    4
    const arr = [1, 2, 3];
    arr.forEach((item) => {
    console.log(item);
    });

4. map 方法

  • 特点:对数组中的每个元素执行回调函数,返回一个新数组。
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    const newArr = arr.map((item) => item * 2);
    console.log(newArr); // [2, 4, 6]

5. filter 方法

  • 特点:对数组中的每个元素执行回调函数,返回满足条件的新数组。
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    const newArr = arr.filter((item) => item > 1);
    console.log(newArr); // [2, 3]

6. reduce 方法

  • 特点:对数组中的每个元素执行回调函数,返回一个累积值。
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    const sum = arr.reduce((acc, item) => acc + item, 0);
    console.log(sum); // 6

7. reduceRight 方法

  • 特点:从右到左对数组中的每个元素执行回调函数,返回一个累积值。
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    const result = arr.reduceRight((acc, item) => acc + item, 0);
    console.log(result); // 6

8. every 方法

  • 特点:检查数组中的每个元素是否都满足条件,返回布尔值。
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    const isAllPositive = arr.every((item) => item > 0);
    console.log(isAllPositive); // true

9. some 方法

  • 特点:检查数组中是否有元素满足条件,返回布尔值。
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    const hasEvenNumber = arr.some((item) => item % 2 === 0);
    console.log(hasEvenNumber); // true

10. find 方法

  • 特点:返回数组中第一个满足条件的元素,若无则返回 undefined
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    const result = arr.find((item) => item > 1);
    console.log(result); // 2

11. findIndex 方法

  • 特点:返回数组中第一个满足条件的元素的索引,若无则返回 -1
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    const index = arr.findIndex((item) => item > 1);
    console.log(index); // 1

12. keys 方法

  • 特点:返回数组索引的迭代器。
  • 示例
    1
    2
    3
    4
    const arr = [1, 2, 3];
    for (const index of arr.keys()) {
    console.log(index); // 0, 1, 2
    }

13. values 方法

  • 特点:返回数组元素的迭代器。
  • 示例
    1
    2
    3
    4
    const arr = [1, 2, 3];
    for (const item of arr.values()) {
    console.log(item); // 1, 2, 3
    }

14. entries 方法

  • 特点:返回数组索引和元素的迭代器。
  • 示例
    1
    2
    3
    4
    const 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
    4
    const 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方法有什么区别?

forEachmap 是 JavaScript 中用于遍历数组的两个常用方法,它们的主要区别在于返回值使用场景。以下是它们的详细对比:


1. forEach 方法

  • 作用:对数组中的每个元素执行回调函数。
  • 返回值undefined(无返回值)。
  • 特点
    • 用于遍历数组,执行某些操作(如修改元素、打印日志等)。
    • 不会改变原数组。
  • 示例
    1
    2
    3
    4
    const arr = [1, 2, 3];
    arr.forEach((item) => {
    console.log(item); // 输出: 1, 2, 3
    });

2. map 方法

  • 作用:对数组中的每个元素执行回调函数,并返回一个新数组。
  • 返回值:新数组,包含回调函数的返回值。
  • 特点
    • 用于将数组中的元素映射为新的值。
    • 不会改变原数组。
  • 示例
    1
    2
    3
    const 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
      4
      const arr = [1, 2, 3];
      arr.forEach((item) => {
      console.log(item); // 输出: 1, 2, 3
      });
  • **map**:

    • 用于将数组中的元素映射为新的值,并返回一个新数组。
    • 例如:
      1
      2
      3
      const arr = [1, 2, 3];
      const newArr = arr.map((item) => item * 2);
      console.log(newArr); // 输出: [2, 4, 6]

5. 性能对比

  • forEach 的性能略高于 map,因为它不需要生成新数组。
  • 如果只需要遍历数组而不需要返回值,建议使用 forEach

6. 链式调用

  • map 支持链式调用,可以与其他数组方法(如 filterreduce)结合使用。
    1
    2
    3
    4
    5
    const 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事件的区别是什么?

mouseovermouseenter 是 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. mouseoutmouseleave 事件

  • **mouseout**:与 mouseover 对应,当鼠标移出元素或其子元素时触发,会冒泡。
  • **mouseleave**:与 mouseenter 对应,当鼠标移出元素时触发,不会冒泡。

4. 对比总结

特性 mouseover mouseenter
触发时机 鼠标移动到元素或其子元素时触发 鼠标首次移动到元素时触发
事件冒泡 会冒泡 不会冒泡
从父元素移动到子元素 会再次触发父元素的事件 不会再次触发父元素的事件
适用场景 需要监控元素及其子元素的鼠标移入 只需要监控元素本身的鼠标移入

5. 如何选择?

  • 使用 **mouseover**:
    • 当需要监控元素及其子元素的鼠标移入事件时。
    • 例如,实现一个包含子元素的复杂交互效果。
  • 使用 **mouseenter**:
    • 当只需要监控元素本身的鼠标移入事件时。
    • 例如,实现简单的悬停效果。

6. 示例对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="parent" style="width: 200px; height: 200px; background: lightblue;">
<div id="child" style="width: 100px; height: 100px; background: lightcoral;"></div>
</div>
<script>
const parent = document.getElementById("parent");

parent.addEventListener("mouseover", () => {
console.log("mouseover on parent");
});

parent.addEventListener("mouseenter", () => {
console.log("mouseenter on parent");
});
</script>

结果分析

  1. 鼠标从外部移动到 #parent 时:
    • 触发 mouseovermouseenter
  2. 鼠标从 #parent 移动到 #child 时:
    • 触发 mouseover,但不会触发 mouseenter

7. 总结

  • mouseovermouseenter 的主要区别在于事件冒泡触发时机
  • mouseover 会监控元素及其子元素的鼠标移入,且会冒泡。
  • mouseenter 只监控元素本身的鼠标移入,且不会冒泡。

根据具体需求选择合适的事件类型,可以更精确地处理鼠标移入事件。

JavaScript的==和===有什么区别?

在 JavaScript 中,===== 是用于比较两个值的操作符,它们的主要区别在于类型转换。以下是它们的详细对比:


1. ==(宽松相等)

  • 特点
    • 比较两个值时会进行隐式类型转换
    • 如果类型不同,会尝试将值转换为相同类型后再比较。
  • 示例
    1
    2
    3
    4
    console.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
    4
    console.log(1 === "1"); // false(类型不同)
    console.log(true === 1); // false(类型不同)
    console.log(null === undefined); // false(类型不同)
    console.log("" === 0); // false(类型不同)

3. 比较规则

== 的隐式类型转换规则

  1. 如果类型相同,直接比较值。
  2. 如果类型不同:
    • 数字与字符串:将字符串转换为数字。
    • 布尔值与非布尔值:将布尔值转换为数字(true1false0)。
    • 对象与基本类型:将对象转换为原始值(调用 valueOftoString 方法)。
    • nullundefined:相等。
    • NaN:与任何值(包括自身)都不相等。

=== 的比较规则

  1. 如果类型不同,直接返回 false
  2. 如果类型相同,比较值:
    • 数字:值相同则 true
    • 字符串:字符序列相同则 true
    • 布尔值:值相同则 true
    • 对象:引用相同则 true
    • nullundefined:只有与自身相等。

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
      3
      if (x === 10) {
      // 只有当 x 是数字 10 时才执行
      }
  • **使用 ==**:

    • 在需要类型转换的场景下使用,但要确保理解其隐式转换规则。
    • 例如:
      1
      2
      3
      if (x == null) {
      // 当 x 是 null 或 undefined 时执行
      }

6. 总结

特性 ==(宽松相等) ===(严格相等)
类型转换
比较规则 隐式转换后比较值 直接比较类型和值
使用场景 需要类型转换的场景 大多数场景,推荐使用

根据具体需求选择合适的操作符,可以避免因类型转换导致的意外行为,提高代码的可靠性和可读性。

JavaScript中substring和substr函数的区别是什么?

在 JavaScript 中,substringsubstr 是用于提取字符串的两个函数,但它们的行为和参数有所不同。以下是它们的详细对比:


1. substring 函数

  • 语法str.substring(startIndex, endIndex)
  • 作用:提取字符串中从 startIndexendIndex(不包括 endIndex)的子字符串。
  • 参数
    • startIndex:起始索引(包含)。
    • endIndex:结束索引(不包含,可选)。
  • 特点
    • 如果 startIndex 大于 endIndex,会自动交换两个参数。
    • 如果参数为负数,会被视为 0
  • 示例
    1
    2
    3
    4
    5
    const 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
    5
    const 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
参数含义 startIndexendIndex startIndexlength
参数处理 自动交换 startIndexendIndex 如果 startIndex > endIndex 支持负数 startIndex,表示从末尾开始计算
负数处理 负数被视为 0 负数 startIndex 从末尾计算,负数 length 返回空字符串
返回值 startIndexendIndex 的子字符串 startIndex 开始的指定长度的子字符串

4. 使用场景

  • **substring**:

    • 当你需要提取字符串的某一部分,并且知道起始和结束索引时。
    • 例如:
      1
      2
      const str = "Hello, World!";
      const result = str.substring(0, 5); // "Hello"
  • **substr**:

    • 当你需要从某个位置开始提取指定长度的子字符串时。
    • 例如:
      1
      2
      const str = "Hello, World!";
      const result = str.substr(7, 5); // "World"

5. 注意事项

  • substr 已弃用substr 已被标记为弃用(deprecated),在未来的 JavaScript 版本中可能会被移除。建议使用 substringslice 替代。
  • **推荐使用 slice**:slice 函数的行为与 substring 类似,但支持负数索引,且不会自动交换参数。

6. slice 函数

  • 语法str.slice(startIndex, endIndex)
  • 作用:提取字符串中从 startIndexendIndex(不包括 endIndex)的子字符串。
  • 参数
    • startIndex:起始索引(包含)。
    • endIndex:结束索引(不包含,可选)。
  • 特点
    • 支持负数索引,表示从字符串末尾开始计算。
    • 如果 startIndex 大于 endIndex,返回空字符串。
  • 示例
    1
    2
    3
    4
    5
    const 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
参数含义 startIndexendIndex startIndexlength startIndexendIndex
负数处理 负数被视为 0 支持负数 startIndex 支持负数 startIndexendIndex
自动交换参数
推荐使用 否(已弃用)
  • **substring**:适合提取字符串的某一部分,且不需要负数索引。
  • **substr**:已弃用,不推荐使用。
  • **slice**:功能最强大,支持负数索引,推荐使用。

根据具体需求选择合适的函数,可以更高效地处理字符串操作。

JavaScript数组的map和forEach函数中能否通过break等语法结束循环?

在 JavaScript 中,mapforEach 函数是用于遍历数组的高阶函数,但它们不支持通过 breakcontinuereturn 等语法提前结束循环。以下是详细解释和替代方案:


1. map 函数

  • 特点
    • 遍历数组中的每个元素,并对每个元素执行回调函数。
    • 返回一个新数组,包含回调函数的返回值。
  • **无法使用 breakcontinue**:
    • map 函数会遍历整个数组,无法提前结束循环。
    • 在回调函数中使用 breakcontinue 会抛出语法错误。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const 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 函数

  • 特点
    • 遍历数组中的每个元素,并对每个元素执行回调函数。
    • 无返回值。
  • **无法使用 breakcontinue**:
    • forEach 函数会遍历整个数组,无法提前结束循环。
    • 在回调函数中使用 breakcontinue 会抛出语法错误。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    const 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 循环,支持 breakcontinue
  • 示例
    1
    2
    3
    4
    5
    6
    7
    const 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 循环,支持 breakcontinue
  • 示例
    1
    2
    3
    4
    5
    6
    7
    const arr = [1, 2, 3];
    for (const item of arr) {
    if (item === 2) {
    break; // 提前结束循环
    }
    console.log(item); // 输出: 1
    }

方法 3:someevery 函数

  • some:如果回调函数返回 true,则提前结束循环。
  • every:如果回调函数返回 false,则提前结束循环。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const 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. 总结

方法 支持 breakcontinue 特点
map 遍历整个数组,返回新数组
forEach 遍历整个数组,无返回值
for 循环 支持 breakcontinue
for...of 循环 支持 breakcontinue
someevery 是(通过返回值控制) 提前结束循环,返回布尔值
  • **mapforEach**:无法使用 breakcontinue,会遍历整个数组。
  • 替代方案:使用 for 循环、for...of 循环或 some/every 函数,可以提前结束循环。

根据具体需求选择合适的方法,可以更灵活地控制循环行为。

JavaScript中如何合并对象?

在 JavaScript 中,合并对象是常见的操作,可以通过多种方式实现。以下是几种常见的合并对象的方法及其区别:


1. Object.assign 方法

  • 作用:将一个或多个对象的属性复制到目标对象中。
  • 特点
    • 浅拷贝(只复制对象的引用,不复制嵌套对象)。
    • 会修改目标对象。
  • 示例
    1
    2
    3
    4
    const 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
    4
    const 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.parseJSON.stringify

  • 作用:通过序列化和反序列化实现深拷贝合并。
  • 特点
    • 深拷贝(复制嵌套对象)。
    • 不支持函数、undefinedSymbol 等特殊类型。
  • 示例
    1
    2
    3
    4
    const 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
    15
    function 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
    6
    const _ = 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.parseJSON.stringify 深拷贝 深拷贝,不支持特殊类型
递归合并 深拷贝 灵活,需手动实现递归逻辑
第三方库(如 Lodash) 深拷贝 功能强大,需引入第三方库

7. 使用建议

  • 浅拷贝:使用 Object.assign 或扩展运算符(...)。
  • 深拷贝:使用递归合并或第三方库(如 Lodash)。
  • 简单场景:优先使用扩展运算符(...),语法简洁。
  • 复杂场景:使用递归合并或第三方库(如 Lodash)。

根据具体需求选择合适的方法,可以更高效地合并对象。

JavaScript如何判断一个对象是不是对象?

在 JavaScript 中,判断一个值是否为对象可以通过多种方式实现。以下是常见的方法及其区别:


1. typeof 操作符

  • 作用:返回值的类型。
  • 特点
    • 对于对象,typeof 返回 "object"
    • null 也会返回 "object",这是一个历史遗留问题。
  • 示例
    1
    2
    3
    const obj = {};
    console.log(typeof obj); // "object"
    console.log(typeof null); // "object"(注意:null 也会返回 "object")

2. Object.prototype.toString 方法

  • 作用:返回对象的字符串表示。
  • 特点
    • 对于对象,返回 "[object Object]"
    • 可以区分 nullundefined
  • 示例
    1
    2
    3
    const 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
    • nullundefined 会返回 false
  • 示例
    1
    2
    3
    const obj = {};
    console.log(obj instanceof Object); // true
    console.log(null instanceof Object); // false

4. Object.prototype.constructor 属性

  • 作用:检查对象的构造函数。
  • 特点
    • 对于对象,obj.constructor === Object 返回 true
    • nullundefined 会抛出错误。
  • 示例
    1
    2
    3
    const 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
    • nullundefined 会返回 false
  • 示例
    1
    2
    3
    const obj = {};
    console.log(Object(obj) === obj); // true
    console.log(Object(null) === null); // false

6. 自定义函数

  • 作用:结合多种方法判断值是否为对象。
  • 特点
    • 灵活,可以根据需要自定义逻辑。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    function 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
    3
    function isObject(value) {
    return value !== null && typeof value === "object";
    }
  • 精确判断
    1
    2
    3
    function isObject(value) {
    return Object.prototype.toString.call(value) === "[object Object]";
    }

根据具体需求选择合适的方法,可以更准确地判断值是否为对象。

JavaScript的spice和slice函数回改变原数组吗?

在 JavaScript 中,spliceslice 是用于操作数组的两个方法,但它们的行为和效果不同。以下是它们的详细对比:


1. splice 方法

  • 作用:从数组中添加或删除元素。
  • 语法array.splice(startIndex, deleteCount, item1, item2, ...)
  • 特点
    • 会改变原数组
    • 可以添加、删除或替换元素。
    • 返回被删除的元素组成的数组。
  • 示例
    1
    2
    3
    4
    const 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
    4
    const 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
      3
      const arr = [1, 2, 3, 4, 5];
      arr.splice(2, 1); // 删除索引 2 的元素
      console.log(arr); // [1, 2, 4, 5]
  • **slice**:

    • 当你需要提取数组的一部分而不修改原数组时。
    • 例如:
      1
      2
      3
      const 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
    4
    const arr = [1, 2, 3];
    const lastElement = arr.pop();
    console.log(arr); // [1, 2]
    console.log(lastElement); // 3

2. 修改 length 属性

  • 作用:通过减少数组的 length 属性来删除最后一个元素。
  • 特点
    • 会修改原数组。
    • 不会返回被删除的元素。
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    arr.length = arr.length - 1;
    console.log(arr); // [1, 2]

3. slice 方法

  • 作用:创建一个新数组,不包含最后一个元素。
  • 特点
    • 不会修改原数组。
    • 返回新数组。
  • 示例
    1
    2
    3
    4
    const 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
    4
    const arr = [1, 2, 3];
    const deleted = arr.splice(-1, 1);
    console.log(arr); // [1, 2]
    console.log(deleted); // [3]

5. 扩展运算符(...

  • 作用:创建一个新数组,不包含最后一个元素。
  • 特点
    • 不会修改原数组。
    • 返回新数组。
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    const [lastElement, ...newArr] = arr.reverse();
    console.log(newArr.reverse()); // [1, 2]

6. filter 方法

  • 作用:创建一个新数组,不包含最后一个元素。
  • 特点
    • 不会修改原数组。
    • 返回新数组。
  • 示例
    1
    2
    3
    4
    const 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. 推荐方法

  • 如果需要修改原数组:使用 popsplice
  • 如果不希望修改原数组:使用 slicefilter

根据具体需求选择合适的方法,可以更高效地删除数组的最后一个元素。

在 JavaScript 中,删除数组的最后一个元素有多种方法。以下是常见的几种方式及其示例:


1. pop 方法

  • 作用:删除数组的最后一个元素,并返回该元素。
  • 特点
    • 会修改原数组。
    • 返回被删除的元素。
  • 示例
    1
    2
    3
    4
    const arr = [1, 2, 3];
    const lastElement = arr.pop();
    console.log(arr); // [1, 2]
    console.log(lastElement); // 3

2. 修改 length 属性

  • 作用:通过减少数组的 length 属性来删除最后一个元素。
  • 特点
    • 会修改原数组。
    • 不会返回被删除的元素。
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    arr.length = arr.length - 1;
    console.log(arr); // [1, 2]

3. slice 方法

  • 作用:创建一个新数组,不包含最后一个元素。
  • 特点
    • 不会修改原数组。
    • 返回新数组。
  • 示例
    1
    2
    3
    4
    const 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
    4
    const arr = [1, 2, 3];
    const deleted = arr.splice(-1, 1);
    console.log(arr); // [1, 2]
    console.log(deleted); // [3]

5. 扩展运算符(...

  • 作用:创建一个新数组,不包含最后一个元素。
  • 特点
    • 不会修改原数组。
    • 返回新数组。
  • 示例
    1
    2
    3
    const arr = [1, 2, 3];
    const [lastElement, ...newArr] = arr.reverse();
    console.log(newArr.reverse()); // [1, 2]

6. filter 方法

  • 作用:创建一个新数组,不包含最后一个元素。
  • 特点
    • 不会修改原数组。
    • 返回新数组。
  • 示例
    1
    2
    3
    4
    const 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. 推荐方法

  • 如果需要修改原数组:使用 popsplice
  • 如果不希望修改原数组:使用 slicefilter

根据具体需求选择合适的方法,可以更高效地删除数组的最后一个元素。

JavaScript操作数组元素的方法有哪些?

在 JavaScript 中,操作数组元素的方法非常丰富,涵盖了添加、删除、修改、查找、遍历等多种操作。以下是常见的数组操作方法及其分类:


1. 添加元素

  • **push**:在数组末尾添加一个或多个元素。
    1
    2
    const arr = [1, 2];
    arr.push(3); // [1, 2, 3]
  • **unshift**:在数组开头添加一个或多个元素。
    1
    2
    const arr = [1, 2];
    arr.unshift(0); // [0, 1, 2]
  • **splice**:在指定位置添加或删除元素。
    1
    2
    const arr = [1, 2, 3];
    arr.splice(1, 0, 4); // [1, 4, 2, 3]

2. 删除元素

  • **pop**:删除数组的最后一个元素。
    1
    2
    const arr = [1, 2, 3];
    arr.pop(); // [1, 2]
  • **shift**:删除数组的第一个元素。
    1
    2
    const arr = [1, 2, 3];
    arr.shift(); // [2, 3]
  • **splice**:从指定位置删除元素。
    1
    2
    const arr = [1, 2, 3];
    arr.splice(1, 1); // [1, 3]

3. 修改元素

  • 直接赋值:通过索引修改元素。
    1
    2
    const arr = [1, 2, 3];
    arr[1] = 4; // [1, 4, 3]
  • **splice**:替换指定位置的元素。
    1
    2
    const arr = [1, 2, 3];
    arr.splice(1, 1, 4); // [1, 4, 3]

4. 查找元素

  • **indexOf**:返回元素首次出现的索引,若不存在则返回 -1
    1
    2
    const arr = [1, 2, 3];
    console.log(arr.indexOf(2)); // 1
  • **lastIndexOf**:返回元素最后一次出现的索引,若不存在则返回 -1
    1
    2
    const arr = [1, 2, 3, 2];
    console.log(arr.lastIndexOf(2)); // 3
  • **includes**:检查数组是否包含某个元素,返回布尔值。
    1
    2
    const arr = [1, 2, 3];
    console.log(arr.includes(2)); // true
  • **find**:返回第一个满足条件的元素。
    1
    2
    const arr = [1, 2, 3];
    console.log(arr.find((item) => item > 1)); // 2
  • **findIndex**:返回第一个满足条件的元素的索引。
    1
    2
    const arr = [1, 2, 3];
    console.log(arr.findIndex((item) => item > 1)); // 1

5. 遍历数组

  • **forEach**:对数组中的每个元素执行回调函数。
    1
    2
    const arr = [1, 2, 3];
    arr.forEach((item) => console.log(item)); // 1, 2, 3
  • **map**:对数组中的每个元素执行回调函数,返回新数组。
    1
    2
    const arr = [1, 2, 3];
    const newArr = arr.map((item) => item * 2); // [2, 4, 6]
  • **filter**:返回满足条件的新数组。
    1
    2
    const arr = [1, 2, 3];
    const newArr = arr.filter((item) => item > 1); // [2, 3]
  • **reduce**:对数组中的每个元素执行回调函数,返回累积值。
    1
    2
    const arr = [1, 2, 3];
    const sum = arr.reduce((acc, item) => acc + item, 0); // 6
  • **reduceRight**:从右到左对数组中的每个元素执行回调函数,返回累积值。
    1
    2
    const arr = [1, 2, 3];
    const result = arr.reduceRight((acc, item) => acc + item, 0); // 6

6. 排序和反转

  • **sort**:对数组进行排序。
    1
    2
    const arr = [3, 1, 2];
    arr.sort(); // [1, 2, 3]
  • **reverse**:反转数组的顺序。
    1
    2
    const arr = [1, 2, 3];
    arr.reverse(); // [3, 2, 1]

7. 合并和拆分

  • **concat**:合并两个或多个数组,返回新数组。
    1
    2
    3
    const arr1 = [1, 2];
    const arr2 = [3, 4];
    const newArr = arr1.concat(arr2); // [1, 2, 3, 4]
  • **slice**:提取数组的一部分,返回新数组。
    1
    2
    const arr = [1, 2, 3];
    const newArr = arr.slice(1, 3); // [2, 3]
  • **join**:将数组元素拼接成字符串。
    1
    2
    const arr = [1, 2, 3];
    const str = arr.join("-"); // "1-2-3"

8. 其他操作

  • **fill**:用指定值填充数组。
    1
    2
    const arr = [1, 2, 3];
    arr.fill(0); // [0, 0, 0]
  • **flat**:将多维数组扁平化。
    1
    2
    const arr = [1, [2, [3]]];
    const newArr = arr.flat(2); // [1, 2, 3]
  • **flatMap**:先对数组中的每个元素执行回调函数,再将结果扁平化。
    1
    2
    const 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. 总结

操作类型 方法示例 说明
添加元素 pushunshiftsplice 在数组开头、末尾或指定位置添加元素
删除元素 popshiftsplice 删除数组开头、末尾或指定位置的元素
修改元素 直接赋值、splice 通过索引或 splice 修改元素
查找元素 indexOffindincludes 查找元素的索引或值
遍历数组 forEachmapfilterreduce 对数组中的每个元素执行操作
排序和反转 sortreverse 对数组进行排序或反转
合并和拆分 concatslicejoin 合并数组、提取部分数组或拼接字符串
其他操作 fillflatflatMap 填充数组、扁平化数组或映射并扁平化
判断和转换 Array.isArrayArray.fromArray.of 判断数组类型或创建新数组

掌握这些数组操作方法,可以更高效地处理数组数据。

JavaScript中for…in和for…of的区别是什么?

在 JavaScript 中,for...infor...of 是两种用于遍历的循环语句,但它们的作用对象和行为有所不同。以下是它们的详细对比:


1. for...in 循环

  • 作用:遍历对象的可枚举属性(包括原型链上的属性)。
  • 适用对象:对象(Object)、数组(Array)、字符串(String)等。
  • 特点
    • 遍历的是对象的(key)。
    • 会遍历对象的所有可枚举属性,包括继承的属性。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const 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)、MapSetarguments 等。
  • 特点
    • 遍历的是对象的(value)。
    • 只能遍历具有 Symbol.iterator 方法的对象(即实现了迭代器协议的对象)。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const 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)
适用对象 对象、数组、字符串等 数组、字符串、MapSet 等可迭代对象
原型链属性 会遍历原型链上的可枚举属性 不会遍历原型链上的属性
迭代器协议 不需要 需要实现 Symbol.iterator 方法

4. 使用场景

  • **for...in**:

    • 遍历对象的属性。
    • 例如:
      1
      2
      3
      4
      const obj = { a: 1, b: 2 };
      for (const key in obj) {
      console.log(key, obj[key]); // 输出: "a 1", "b 2"
      }
  • **for...of**:

    • 遍历数组、字符串、MapSet 等可迭代对象的值。
    • 例如:
      1
      2
      3
      4
      const arr = [1, 2, 3];
      for (const value of arr) {
      console.log(value); // 输出: 1, 2, 3
      }

5. 注意事项

  • **for...in**:

    • 遍历对象时,可能会遍历到原型链上的属性。可以使用 hasOwnProperty 方法过滤。
      1
      2
      3
      4
      5
      6
      const 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
      4
      const 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)
适用对象 对象、数组、字符串等 数组、字符串、MapSet 等可迭代对象
原型链属性 会遍历原型链上的可枚举属性 不会遍历原型链上的属性
迭代器协议 不需要 需要实现 Symbol.iterator 方法

根据具体需求选择合适的遍历方式,可以更高效地处理数据。

JavaScript如何使用for…of遍历对象?

在 JavaScript 中,for...of 循环用于遍历可迭代对象(如数组、字符串、MapSet 等)。然而,普通对象(Object)默认是不可迭代的,因此不能直接使用 for...of 遍历对象。不过,可以通过以下方法使对象可迭代,从而实现 for...of 遍历。


1. 使用 Object.keysObject.valuesObject.entries

  • **Object.keys**:返回对象的键组成的数组。
  • **Object.values**:返回对象的值组成的数组。
  • **Object.entries**:返回对象的键值对组成的数组。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const 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
    24
    const 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
    10
    const 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
    12
    function* 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 的基本特性

  • 对于基本类型(如 numberstringboolean

    • const 声明的变量不能被重新赋值。
    • 示例:
      1
      2
      const num = 10;
      num = 20; // 报错:Uncaught TypeError: Assignment to constant variable.
  • 对于引用类型(如 objectarray

    • const 声明的变量不能被重新赋值,但可以修改其属性或元素。
    • 示例:
      1
      2
      3
      4
      5
      const 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
      13
      const 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
      2
      const 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
    11
    const 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
    11
    const 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
    13
    const 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
    3
    function add(a, b) {
    return a + b;
    }

2. this 的绑定

  • 箭头函数:没有自己的 this,它会捕获其所在上下文的 this 值。也就是说,箭头函数中的 this 是词法作用域的,继承自外层函数的 this
    1
    2
    3
    4
    5
    6
    7
    8
    const obj = {
    value: 42,
    method: function() {
    setTimeout(() => {
    console.log(this.value); // 42,箭头函数继承了外层函数的 this
    }, 100);
    }
    };
  • 普通函数:有自己的 thisthis 的值取决于函数的调用方式(如通过对象调用、直接调用、使用 call/apply 等)。
    1
    2
    3
    4
    5
    6
    7
    8
    const obj = {
    value: 42,
    method: function() {
    setTimeout(function() {
    console.log(this.value); // undefined,this 指向全局对象或 undefined(严格模式)
    }, 100);
    }
    };

3. arguments 对象

  • 箭头函数:没有自己的 arguments 对象,它会继承外层函数的 arguments
    1
    2
    3
    const func = () => {
    console.log(arguments); // 报错,箭头函数没有 arguments
    };
  • 普通函数:有自己的 arguments 对象,包含所有传入的参数。
    1
    2
    3
    function func() {
    console.log(arguments); // 正常输出 arguments 对象
    }

4. newprototype

  • 箭头函数:不能使用 new 关键字调用,也没有 prototype 属性,因此不能作为构造函数。
    1
    2
    const Foo = () => {};
    const obj = new Foo(); // 报错:Foo is not a constructor
  • 普通函数:可以使用 new 关键字调用,可以作为构造函数,并且有 prototype 属性。
    1
    2
    function Foo() {}
    const obj = new Foo(); // 正常创建实例

5. 方法定义

  • 箭头函数:不适合作为对象的方法,因为 this 不会指向对象本身。
    1
    2
    3
    4
    5
    6
    const obj = {
    value: 42,
    method: () => {
    console.log(this.value); // undefined,this 指向外层作用域
    }
    };
  • 普通函数:适合作为对象的方法,this 会指向调用该方法的对象。
    1
    2
    3
    4
    5
    6
    const obj = {
    value: 42,
    method: function() {
    console.log(this.value); // 42,this 指向 obj
    }
    };

6. 函数提升(Hoisting)

  • 箭头函数:不会被提升,必须在定义之后才能使用。
    1
    2
    console.log(func()); // 报错:func is not defined
    const func = () => 42;
  • 普通函数:会被提升,可以在定义之前调用。
    1
    2
    3
    4
    console.log(func()); // 42
    function func() {
    return 42;
    }

7. 默认参数

  • 箭头函数:支持默认参数。
    1
    const func = (a = 1, b = 2) => a + b;
  • 普通函数:也支持默认参数。
    1
    2
    3
    function func(a = 1, b = 2) {
    return a + b;
    }

总结

  • 箭头函数更适合用于简单的回调函数或需要继承外层 this 的场景。
  • 普通函数更适合需要动态 this、作为构造函数或方法定义的场景。

根据具体需求选择合适的函数类型可以避免一些常见的陷阱。

ES6箭头函数能当构造函数吗?

ES6 箭头函数不能作为构造函数使用。这是箭头函数与普通函数的一个重要区别。以下是具体原因和细节:


1. 箭头函数没有 prototype 属性

  • 普通函数有一个 prototype 属性,用于实现基于原型的继承和构造函数的功能。
  • 箭头函数没有 prototype 属性,因此无法作为构造函数使用。
    1
    2
    const Foo = () => {};
    console.log(Foo.prototype); // undefined

2. 箭头函数不能使用 new 关键字

  • 使用 new 调用箭头函数会直接报错,因为箭头函数没有内部的 [[Construct]] 方法,无法实例化对象。
    1
    2
    const Foo = () => {};
    const obj = new Foo(); // 报错:Foo is not a constructor

3. 箭头函数没有自己的 this

  • 普通函数在被 new 调用时,会创建一个新的对象,并将 this 绑定到该对象。
  • 箭头函数没有自己的 this,它的 this 是词法作用域的,继承自外层函数的 this。因此,箭头函数无法实现构造函数的功能。
    1
    2
    3
    const Foo = () => {
    this.value = 42; // 报错:this 不是指向新创建的对象
    };

4. 箭头函数的设计初衷

  • 箭头函数的设计目的是为了简化函数语法,并解决普通函数中 this 绑定混乱的问题。它更适合用于回调函数、匿名函数或需要继承外层 this 的场景,而不是构造函数。

总结

箭头函数不能作为构造函数使用。如果需要创建实例对象或实现基于原型的继承,应该使用普通函数或 ES6 的 class 语法。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用普通函数作为构造函数
function Foo(value) {
this.value = value;
}
const obj = new Foo(42);
console.log(obj.value); // 42

// 使用 ES6 class 语法
class Bar {
constructor(value) {
this.value = value;
}
}
const obj2 = new Bar(42);
console.log(obj2.value); // 42

箭头函数更适合用于以下场景:

  • 简单的回调函数。
  • 需要继承外层 this 的函数。
  • 不需要动态 this 或构造函数的场景。

ES6有哪些新特性?

ES6(ECMAScript 2015)是 JavaScript 语言的一次重大更新,引入了许多新特性,极大地提升了开发效率和代码可读性。以下是 ES6 的主要新特性:


1. 块级作用域变量(letconst

  • letconst 引入了块级作用域,解决了 var 的变量提升和全局污染问题。
  • const 用于声明常量,值不可重新赋值。
    1
    2
    3
    4
    5
    6
    7
    let 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
    3
    const name = "Alice";
    const message = `Hello, ${name}!
    Welcome to ES6.`;

4. 解构赋值(Destructuring Assignment)

  • 从数组或对象中提取值并赋值给变量。
    1
    2
    const [a, b] = [1, 2];
    const { name, age } = { name: "Bob", age: 30 };

5. 默认参数(Default Parameters)

  • 函数参数可以设置默认值。
    1
    2
    3
    function greet(name = "Guest") {
    console.log(`Hello, ${name}!`);
    }

6. 剩余参数(Rest Parameters)

  • 将多余的参数收集到一个数组中。
    1
    2
    3
    function sum(...numbers) {
    return numbers.reduce((acc, curr) => acc + curr, 0);
    }

7. 扩展运算符(Spread Operator)

  • 将数组或对象展开。
    1
    2
    const arr = [1, 2, 3];
    const newArr = [...arr, 4, 5];

8. 对象属性简写(Object Property Shorthand)

  • 简化对象字面量的写法。
    1
    2
    3
    const name = "Alice";
    const age = 25;
    const person = { name, age };

9. 对象方法简写(Method Shorthand)

  • 对象中的方法可以省略 function 关键字。
    1
    2
    3
    4
    5
    const obj = {
    greet() {
    console.log("Hello!");
    }
    };

10. 类(Classes)

  • 引入了基于原型的面向对象编程的语法糖。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Person {
    constructor(name) {
    this.name = name;
    }
    greet() {
    console.log(`Hello, ${this.name}!`);
    }
    }
    const alice = new Person("Alice");
    alice.greet();

11. 模块化(Modules)

  • 支持 importexport 语法,实现模块化开发。
    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
    4
    const 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
    6
    function* gen() {
    yield 1;
    yield 2;
    }
    const iterator = gen();
    console.log(iterator.next().value); // 1

14. Symbol

  • 引入了一种新的原始数据类型,用于创建唯一的标识符。
    1
    2
    const id = Symbol("id");
    const obj = { [id]: 123 };

15. Map 和 Set

  • Map:键值对集合,键可以是任意类型。
  • Set:值唯一的集合。
    1
    2
    3
    4
    const 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
    7
    const target = {};
    const proxy = new Proxy(target, {
    get(obj, prop) {
    return obj[prop] || "Not Found";
    }
    });
    console.log(proxy.name); // "Not Found"

17. 二进制和八进制字面量

  • 支持 0b 表示二进制,0o 表示八进制。
    1
    2
    const binary = 0b1010; // 10
    const octal = 0o12; // 10

18. 新的字符串方法

  • includes()startsWith()endsWith()repeat() 等。
    1
    2
    const str = "Hello, ES6!";
    console.log(str.includes("ES6")); // true

19. 新的数组方法

  • Array.from()Array.of()find()findIndex()fill() 等。
    1
    2
    const arr = [1, 2, 3];
    console.log(arr.find(x => x > 1)); // 2

20. 尾调用优化(Tail Call Optimization)

  • 在严格模式下,支持尾调用优化,避免栈溢出。
    1
    2
    3
    4
    function 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
    6
    const 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
    10
    const 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...inObject.keys()JSON.stringify() 枚举。
    1
    2
    3
    4
    5
    6
    7
    const 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.iteratorSymbol.toStringTag 等),用于实现对象的默认行为。
    1
    2
    3
    4
    const obj = {
    [Symbol.toStringTag]: "MyObject"
    };
    console.log(obj.toString()); // "[object MyObject]"

5. 定义迭代器

  • 使用 Symbol.iterator 可以为对象定义自定义的迭代行为。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const 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
    9
    const 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
    11
    const 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
    3
    const 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
    12
    const 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 的主要作用包括:

  1. 创建唯一的属性键,避免属性名冲突。
  2. 定义对象的私有属性。
  3. 实现内置对象的行为(如迭代器、默认行为等)。
  4. 作为常量值,确保值的唯一性。
  5. 提高代码的可维护性,避免魔法字符串。

Symbol 是 ES6 中一个非常强大的特性,特别适合用于需要唯一性、隐私性或自定义行为的场景。

ES Module与CommonJS模块方案有什么异同?

ES Module(ESM)和 CommonJS(CJS)是 JavaScript 中两种主要的模块化方案。它们在语法、加载方式、运行环境等方面有一些显著的异同。以下是它们的对比:


1. 语法

  • ES Module
    • 使用 importexport 语法。
    • 支持静态导入和动态导入。
      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
    • 使用 requiremodule.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
      3
      if (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
    2
    const Foo = () => {};
    const obj = new Foo(); // TypeError: Foo is not a constructor

2. 为什么箭头函数不能作为构造函数?

  • 没有 prototype 属性
    • 构造函数需要有一个 prototype 属性,用于实现基于原型的继承。
    • 箭头函数没有 prototype 属性。
      1
      2
      const Foo = () => {};
      console.log(Foo.prototype); // undefined
  • **没有自己的 this**:
    • 箭头函数的 this 是词法作用域的,继承自外层函数的 this
    • 构造函数需要通过 new 创建一个新对象,并将 this 绑定到该对象。
      1
      2
      3
      const Foo = () => {
      this.value = 42; // 报错:this 不是指向新创建的对象
      };

3. 箭头函数的设计初衷

  • 箭头函数的设计目的是为了简化函数语法,并解决普通函数中 this 绑定混乱的问题。
  • 它更适合用于回调函数、匿名函数或需要继承外层 this 的场景,而不是构造函数。

4. 如何替代箭头函数作为构造函数?

  • 如果需要创建实例对象或实现基于原型的继承,可以使用普通函数或 ES6 的 class 语法。

  • 普通函数

    1
    2
    3
    4
    5
    function Foo(value) {
    this.value = value;
    }
    const obj = new Foo(42);
    console.log(obj.value); // 42
  • ES6 class

    1
    2
    3
    4
    5
    6
    7
    class Bar {
    constructor(value) {
    this.value = value;
    }
    }
    const obj2 = new Bar(42);
    console.log(obj2.value); // 42

5. 总结

  • 箭头函数不能使用 new 调用,因为它没有 prototype 属性,也没有自己的 this
  • 如果需要构造函数的功能,请使用普通函数或 class 语法。
  • 箭头函数更适合用于以下场景:
    • 简单的回调函数。
    • 需要继承外层 this 的函数。
    • 不需要动态 this 或构造函数的场景。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 错误示例:尝试用 new 调用箭头函数
const ArrowFunc = () => {
this.value = 42; // 报错
};
const obj = new ArrowFunc(); // TypeError: ArrowFunc is not a constructor

// 正确示例:使用普通函数作为构造函数
function NormalFunc(value) {
this.value = value;
}
const obj2 = new NormalFunc(42);
console.log(obj2.value); // 42

// 正确示例:使用 ES6 class
class MyClass {
constructor(value) {
this.value = value;
}
}
const obj3 = new MyClass(42);
console.log(obj3.value); // 42

ES6箭头函数的**this**指向哪里?

ES6 箭头函数的 this 指向是 词法作用域 的,也就是说,箭头函数的 this 继承自定义箭头函数时所在上下文的 this,而不是根据调用方式动态绑定。这是箭头函数与普通函数的一个重要区别。


1. 箭头函数的 this 规则

  • 箭头函数没有自己的 this,它的 this 是继承自外层函数或全局作用域的 this
  • 箭头函数的 this 在定义时就已经确定,且不会因为调用方式而改变。

2. 与普通函数的对比

  • 普通函数
    • this 的值取决于函数的调用方式(如通过对象调用、直接调用、使用 call/apply 等)。
      1
      2
      3
      4
      5
      6
      const obj = {
      method: function() {
      console.log(this); // this 指向 obj
      }
      };
      obj.method();
  • 箭头函数
    • this 继承自定义箭头函数时所在上下文的 this
      1
      2
      3
      4
      5
      6
      const obj = {
      method: () => {
      console.log(this); // this 指向外层作用域的 this(通常是全局对象或 undefined)
      }
      };
      obj.method();

3. 常见场景分析

场景 1:全局作用域中的箭头函数

  • 在全局作用域中,箭头函数的 this 指向全局对象(浏览器中是 window,Node.js 中是 global)。
    1
    2
    3
    4
    const foo = () => {
    console.log(this); // 浏览器中:window;Node.js 中:global
    };
    foo();

场景 2:对象方法中的箭头函数

  • 箭头函数作为对象的方法时,this 不会指向对象本身,而是继承自外层作用域的 this
    1
    2
    3
    4
    5
    6
    7
    const 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
    10
    const obj = {
    value: 42,
    method: function() {
    const innerFunc = () => {
    console.log(this.value); // 42,this 继承自 method 的 this
    };
    innerFunc();
    }
    };
    obj.method();

场景 4:事件处理函数中的箭头函数

  • 箭头函数作为事件处理函数时,this 不会指向触发事件的元素,而是继承自外层作用域的 this
    1
    2
    3
    4
    const button = document.querySelector("button");
    button.addEventListener("click", () => {
    console.log(this); // this 指向外层作用域的 this(通常是全局对象或 undefined)
    });

4. 箭头函数 this 的不可变性

  • 箭头函数的 this 在定义时就已经确定,无法通过 callapplybind 改变。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const 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 的回调函数,例如 setTimeoutPromise 等。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      const obj = {
      value: 42,
      method: function() {
      setTimeout(() => {
      console.log(this.value); // 42,this 继承自 method 的 this
      }, 100);
      }
      };
      obj.method();
  • 避免 this 绑定问题
    • 箭头函数可以避免普通函数中 this 绑定混乱的问题,尤其是在嵌套函数或回调函数中。

总结

  • 箭头函数的 this 继承自定义时所在上下文的 this,而不是根据调用方式动态绑定。
  • 箭头函数的 this 在定义时就已经确定,且无法通过 callapplybind 改变。
  • 箭头函数适合用于需要继承外层 this 的场景,例如回调函数或嵌套函数。

如果需要动态绑定 this,请使用普通函数。

说说ES6拓展运算符的作用及其使用场景?

ES6 的扩展运算符(Spread Operator)使用 ... 语法,可以将数组、对象或字符串“展开”为独立的元素。它在许多场景中都非常有用,能够简化代码并提高可读性。以下是扩展运算符的主要作用及其使用场景:


1. 展开数组

  • 作用:将数组展开为独立的元素。
  • 使用场景
    • 合并数组。
    • 将数组作为函数的参数传递。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const 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
      7
      const 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
      3
      const str = "hello";
      const chars = [...str]; // 将字符串展开为字符数组
      console.log(chars); // ["h", "e", "l", "l", "o"]

4. 函数参数中的剩余参数

  • 作用:将多余的参数收集到一个数组中。
  • 使用场景
    • 处理不定数量的函数参数。
      1
      2
      3
      4
      function sum(...numbers) {
      return numbers.reduce((acc, curr) => acc + curr, 0);
      }
      console.log(sum(1, 2, 3)); // 6

5. 复制数组或对象

  • 作用:创建数组或对象的浅拷贝。
  • 使用场景
    • 复制数组或对象,避免直接修改原数据。
      1
      2
      3
      4
      5
      6
      7
      const 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
      2
      const numbers = [1, 2, 3];
      console.log(Math.max(...numbers)); // 3,替代 Math.max.apply(null, numbers)

7. 解构赋值中的剩余元素

  • 作用:将剩余的元素收集到一个数组或对象中。
  • 使用场景
    • 解构数组或对象时,获取剩余部分。
      1
      2
      3
      4
      5
      6
      7
      const [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
      6
      const condition = true;
      const obj = {
      a: 1,
      ...(condition && { b: 2 }), // 根据条件动态添加属性
      };
      console.log(obj); // { a: 1, b: 2 }

9. 将类数组对象转换为数组

  • 作用:将类数组对象(如 argumentsNodeList)转换为真正的数组。
  • 使用场景
    • 使用数组方法操作类数组对象。
      1
      2
      3
      4
      5
      function func() {
      const args = [...arguments]; // 将 arguments 转换为数组
      console.log(args);
      }
      func(1, 2, 3); // [1, 2, 3]

总结

扩展运算符的主要作用包括:

  1. 展开数组、对象或字符串。
  2. 合并数组或对象。
  3. 创建数组或对象的浅拷贝。
  4. 处理不定数量的函数参数。
  5. 解构赋值时获取剩余部分。
  6. 动态添加属性。

它的使用场景非常广泛,能够简化代码并提高开发效率。无论是处理数组、对象还是函数参数,扩展运算符都是一个非常强大的工具。

ES6的Proxy可以实现什么功能?

ES6 引入的 Proxy 是一个强大的特性,用于创建一个对象的代理,从而拦截并自定义对象的基本操作(如属性访问、赋值、枚举等)。通过 Proxy,开发者可以实现高级功能,如数据验证、日志记录、缓存、权限控制等。以下是 Proxy 的主要功能和使用场景:


1. 基本语法

  • Proxy 的构造函数接受两个参数:
    • target:要代理的目标对象。
    • handler:一个对象,定义了拦截操作的“陷阱”(trap)方法。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const 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
    10
    const 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
    12
    const 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
    8
    const 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
    14
    const 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
    12
    const 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
    7
    const 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
    10
    const 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
    12
    const 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
    14
    const 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
    15
    const 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
    11
    const 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
    10
    const 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 的主要功能包括:

  1. 拦截对象的基本操作(如属性访问、赋值、枚举等)。
  2. 实现高级功能,如数据验证、日志记录、缓存、权限控制等。
  3. 创建虚拟属性或动态计算属性值。

Proxy 是一个非常灵活和强大的工具,适合用于需要自定义对象行为的场景。通过合理使用 Proxy,可以显著提升代码的可维护性和扩展性。

什么是ES6的数组结构和对象结构?

ES6 引入了 解构赋值(Destructuring Assignment) 语法,允许从数组或对象中提取值并赋值给变量。解构赋值分为 数组解构对象解构,它们可以大大简化代码,提高可读性。


1. 数组解构

  • 作用:从数组中提取值并赋值给变量。
  • 语法:使用方括号 [],左侧是变量列表,右侧是数组。
  • 特点
    • 按照数组元素的顺序解构。
    • 可以跳过不需要的元素。
    • 支持默认值。
    • 支持剩余元素。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 基本用法
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1, 2, 3

// 跳过元素
const [x, , z] = [1, 2, 3];
console.log(x, z); // 1, 3

// 默认值
const [p, q = 10] = [1];
console.log(p, q); // 1, 10

// 剩余元素
const [m, ...rest] = [1, 2, 3, 4];
console.log(m, rest); // 1, [2, 3, 4]

2. 对象解构

  • 作用:从对象中提取值并赋值给变量。
  • 语法:使用花括号 {},左侧是变量列表,右侧是对象。
  • 特点
    • 按照属性名解构。
    • 支持别名。
    • 支持默认值。
    • 支持嵌套解构。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 基本用法
const { name, age } = { name: "Alice", age: 25 };
console.log(name, age); // "Alice", 25

// 别名
const { name: userName, age: userAge } = { name: "Alice", age: 25 };
console.log(userName, userAge); // "Alice", 25

// 默认值
const { name = "Guest", age = 18 } = { name: "Alice" };
console.log(name, age); // "Alice", 18

// 嵌套解构
const { info: { address } } = { info: { address: "123 Main St" } };
console.log(address); // "123 Main St"

3. 使用场景

1. 提取函数返回值

  • 从函数返回的数组或对象中提取值。
    1
    2
    3
    4
    5
    function getData() {
    return [1, 2, 3];
    }
    const [a, b, c] = getData();
    console.log(a, b, c); // 1, 2, 3

2. 交换变量值

  • 无需临时变量即可交换两个变量的值。
    1
    2
    3
    let x = 1, y = 2;
    [x, y] = [y, x];
    console.log(x, y); // 2, 1

3. 处理函数参数

  • 解构对象参数,使函数参数更清晰。
    1
    2
    3
    4
    function 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
    8
    const response = {
    status: 200,
    data: {
    user: { name: "Alice", age: 25 }
    }
    };
    const { data: { user: { name, age } } } = response;
    console.log(name, age); // "Alice", 25

5. 设置默认值

  • 为解构的变量设置默认值,避免 undefined
    1
    2
    const { name = "Guest", age = 18 } = { name: "Alice" };
    console.log(name, age); // "Alice", 18

6. 处理嵌套结构

  • 解构嵌套的对象或数组。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const 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
2
3
4
5
6
7
8
9
10
11
12
13
14
const 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"

2. 提取多个嵌套属性

  • 可以在一次解构中提取多个嵌套属性。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const user = {
id: 1,
info: {
name: "Alice",
address: {
city: "New York",
zip: "10001"
}
}
};

// 提取多个嵌套属性
const { info: { name, address: { city, zip } } } = user;
console.log(name, city, zip); // "Alice", "New York", "10001"

3. 为提取的属性设置别名

  • 如果需要为提取的属性设置别名,可以使用 : 语法。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const user = {
id: 1,
info: {
name: "Alice",
address: {
city: "New York",
zip: "10001"
}
}
};

// 设置别名
const { info: { name: userName, address: { city: userCity, zip: userZip } } } = user;
console.log(userName, userCity, userZip); // "Alice", "New York", "10001"

4. 设置默认值

  • 如果提取的属性可能不存在,可以为其设置默认值,避免 undefined

示例

1
2
3
4
5
6
7
8
9
10
11
const user = {
id: 1,
info: {
name: "Alice"
// address 属性不存在
}
};

// 设置默认值
const { info: { address: { city = "Unknown", zip = "00000" } = {} } } = user;
console.log(city, zip); // "Unknown", "00000"

5. 提取多层嵌套的属性

  • 对于更复杂的嵌套结构,可以逐层解构。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const company = {
name: "Tech Corp",
departments: {
engineering: {
manager: {
name: "Bob",
contact: {
email: "bob@tech.com",
phone: "123-456-7890"
}
}
}
}
};

// 提取多层嵌套属性
const { departments: { engineering: { manager: { name, contact: { email, phone } } } } } = company;
console.log(name, email, phone); // "Bob", "bob@tech.com", "123-456-7890"

6. 结合函数参数使用

  • 在函数参数中直接解构嵌套对象,使代码更简洁。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const user = {
id: 1,
info: {
name: "Alice",
address: {
city: "New York",
zip: "10001"
}
}
};

// 函数参数解构
function printUserInfo({ info: { name, address: { city, zip } } }) {
console.log(`Name: ${name}, City: ${city}, ZIP: ${zip}`);
}

printUserInfo(user); // "Name: Alice, City: New York, ZIP: 10001"

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
2
3
4
function sum(...numbers) {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3)); // 6

2. arguments 对象的对比

  • arguments 对象
    • 是一个类数组对象,包含所有传入的参数。
    • 不支持数组方法,需要转换为数组才能使用。
      1
      2
      3
      4
      5
      function 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
      4
      function sum(...numbers) {
      return numbers.reduce((acc, curr) => acc + curr, 0);
      }
      console.log(sum(1, 2, 3)); // 6

3. 使用场景

1. 处理不定数量的参数

  • rest 参数非常适合处理函数需要接受不定数量参数的场景。
    1
    2
    3
    4
    function logNames(...names) {
    names.forEach(name => console.log(name));
    }
    logNames("Alice", "Bob", "Charlie"); // "Alice", "Bob", "Charlie"

2. 与普通参数结合使用

  • rest 参数可以与普通参数结合使用,但必须放在最后。
    1
    2
    3
    4
    function greet(greeting, ...names) {
    return `${greeting}, ${names.join(", ")}!`;
    }
    console.log(greet("Hello", "Alice", "Bob")); // "Hello, Alice, Bob!"

3. 解构赋值中的剩余参数

  • rest 参数可以与解构赋值结合,用于收集剩余的元素。
    1
    2
    3
    const [first, ...rest] = [1, 2, 3, 4];
    console.log(first); // 1
    console.log(rest); // [2, 3, 4]

4. 处理函数参数默认值

  • rest 参数可以与默认值结合,处理不传参的情况。
    1
    2
    3
    4
    5
    6
    7
    function sum(...numbers) {
    if (numbers.length === 0) {
    return 0;
    }
    return numbers.reduce((acc, curr) => acc + curr, 0);
    }
    console.log(sum()); // 0

4. 注意事项

  • rest 参数必须放在函数参数的最后一个位置,否则会报错。
    1
    2
    3
    function invalidSyntax(a, ...b, c) {
    // 报错:Rest parameter must be last formal parameter
    }
  • rest 参数是一个真正的数组,而 arguments 是一个类数组对象。
  • rest 参数不会影响函数的 length 属性,length 只计算普通参数。

5. 与扩展运算符的区别

  • rest 参数
    • 用于函数参数,将多余的参数收集到一个数组中。
      1
      2
      3
      function sum(...numbers) {
      return numbers.reduce((acc, curr) => acc + curr, 0);
      }
  • 扩展运算符
    • 用于展开数组或对象,将数组或对象展开为独立的元素。
      1
      2
      const 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
2
3
const name = "Alice";
const greeting = `Hello, ${name}!`;
console.log(greeting); // "Hello, Alice!"

2. 多行字符串

  • 模板字符串支持直接编写多行字符串,无需使用 \n 或字符串拼接。

示例

1
2
3
4
5
6
7
8
const message = `
Hello,
Welcome to ES6!
`;
console.log(message);
// 输出:
// Hello,
// Welcome to ES6!

3. 嵌入表达式

  • ${} 中可以嵌入任意有效的 JavaScript 表达式。

示例

1
2
3
4
const a = 10;
const b = 20;
const result = `The sum of ${a} and ${b} is ${a + b}.`;
console.log(result); // "The sum of 10 and 20 is 30."

4. 标签函数(Tagged Templates)

  • 标签函数是一种特殊的函数,可以对模板字符串进行自定义处理。
  • 标签函数的第一个参数是一个字符串数组,其余参数是嵌入的表达式。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function highlight(strings, ...values) {
let result = "";
strings.forEach((str, i) => {
result += str;
if (values[i]) {
result += `<strong>${values[i]}</strong>`;
}
});
return result;
}

const name = "Alice";
const age = 25;
const message = highlight`Hello, my name is ${name} and I am ${age} years old.`;
console.log(message); // "Hello, my name is <strong>Alice</strong> and I am <strong>25</strong> years old."

5. 使用场景

1. 动态生成字符串

  • 模板字符串非常适合动态生成包含变量或表达式的字符串。
    1
    2
    3
    const 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
    7
    const 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
    16
    function 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
    14
    function 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()

  • 功能:判断字符串是否包含指定的子字符串。
  • 返回值:布尔值(truefalse)。
  • 示例
    1
    2
    3
    const str = "Hello, World!";
    console.log(str.includes("World")); // true
    console.log(str.includes("world")); // false(区分大小写)

2. String.prototype.startsWith()

  • 功能:判断字符串是否以指定的子字符串开头。
  • 返回值:布尔值(truefalse)。
  • 示例
    1
    2
    3
    const str = "Hello, World!";
    console.log(str.startsWith("Hello")); // true
    console.log(str.startsWith("hello")); // false(区分大小写)

3. String.prototype.endsWith()

  • 功能:判断字符串是否以指定的子字符串结尾。
  • 返回值:布尔值(truefalse)。
  • 示例
    1
    2
    3
    const str = "Hello, World!";
    console.log(str.endsWith("World!")); // true
    console.log(str.endsWith("world!")); // false(区分大小写)

4. String.prototype.repeat()

  • 功能:将字符串重复指定次数。
  • 返回值:新的字符串。
  • 示例
    1
    2
    const str = "Hello";
    console.log(str.repeat(3)); // "HelloHelloHello"

5. String.prototype.padStart()

  • 功能:在字符串的开头填充指定的字符,直到字符串达到指定的长度。
  • 返回值:新的字符串。
  • 示例
    1
    2
    const str = "5";
    console.log(str.padStart(3, "0")); // "005"

6. String.prototype.padEnd()

  • 功能:在字符串的结尾填充指定的字符,直到字符串达到指定的长度。
  • 返回值:新的字符串。
  • 示例
    1
    2
    const str = "5";
    console.log(str.padEnd(3, "0")); // "500"

7. String.prototype.trimStart() / String.prototype.trimLeft()

  • 功能:去除字符串开头的空白字符。
  • 返回值:新的字符串。
  • 示例
    1
    2
    const str = "   Hello, World!   ";
    console.log(str.trimStart()); // "Hello, World! "

8. String.prototype.trimEnd() / String.prototype.trimRight()

  • 功能:去除字符串结尾的空白字符。
  • 返回值:新的字符串。
  • 示例
    1
    2
    const str = "   Hello, World!   ";
    console.log(str.trimEnd()); // " Hello, World!"

9. String.prototype[Symbol.iterator]()

  • 功能:使字符串可迭代,可以通过 for...of 循环遍历字符串的每个字符。
  • 示例
    1
    2
    3
    4
    const str = "Hello";
    for (const char of str) {
    console.log(char); // "H", "e", "l", "l", "o"
    }

10. String.raw()

  • 功能:返回模板字符串的原始形式,忽略转义字符。
  • 示例
    1
    2
    const path = String.raw`C:\Users\Alice\Documents`;
    console.log(path); // "C:\Users\Alice\Documents"

11. String.prototype.codePointAt()

  • 功能:返回字符串中指定位置的字符的 Unicode 码点。
  • 返回值:码点值(数字)。
  • 示例
    1
    2
    const str = "𠮷";
    console.log(str.codePointAt(0)); // 134071

12. String.prototype.normalize()

  • 功能:将字符串规范化(Unicode 标准化形式)。
  • 返回值:新的字符串。
  • 示例
    1
    2
    const str = "é";
    console.log(str.normalize("NFD")); // "é" 分解为 "e" 和 "´"

13. String.prototype.matchAll()

  • 功能:返回一个包含所有匹配正则表达式的结果的迭代器。
  • 返回值:迭代器。
  • 示例
    1
    2
    3
    const str = "Hello, World!";
    const matches = [...str.matchAll(/[A-Z]/g)];
    console.log(matches); // [["H"], ["W"]]

14. String.prototype.replaceAll()

  • 功能:替换字符串中所有匹配的子字符串。
  • 返回值:新的字符串。
  • 示例
    1
    2
    const 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() 替换字符串中所有匹配的子字符串

这些新增的字符串处理函数极大地提升了字符串操作的便捷性和功能,适合用于各种字符串处理场景。