实际场景
约 1998 字大约 7 分钟
2026-05-03
如何判断 JS 变量是数组
1. Object.prototype.toString.call() = [object Array]
2. obj.__proto__ === Array.prototype;
3. Array.isArray
4. obj instanceof Array
5. Array.prototype.isPrototypeOf(obj)Object.prototype.toString.call() 核心原理解析
Object.prototype.toString 是 Object 原型上的一个原生方法,它的内部逻辑是:
- 读取调用对象的内部类型标签(ES5 中叫 [[Class]] 内部属性,ES6+ 中优先读取 Symbol.toStringTag)。
- 返回固定格式字符串:"[object 类型标签]"。
JS 的类数组对象
JavaScript 的类数组对象(Array-like Object)是指具有类似数组特性,但并不是数组的对象。它们通常具备 length 属性和按索引存储的元素(例如 arguments 对象、DOM 方法返回的集合如 NodeList)。要将类数组对象转换为真正的数组,可以使用以下几种方法:
- 使用
Array.prototype.slice.call()方法
- 使用
const arrayLike = {0: 'a', 1: 'b', length: 2};
const realArray = Array.prototype.slice.call(arrayLike);- 使用
Array.from方法
- 使用
const arrayLike = {0: 'a', 1: 'b', length: 2};
const realArray = Array.from(arrayLike);- 使用扩展运算符
扩展:为什么 JS 函数的arguments参数是类数组而不是数组?
关于为什么 arguments 是类数组而不是数组:
1)历史原因:arguments 对象是在 JavaScript 语言早期就被引入的。那时候,JavaScript 还没有真正的数组对象,所以arguments被设计成了一个类数组对象。
2)性能考虑:将 arguments 实现为真正的数组可能会带来一些性能开销。类数组对象可以更高效地实现某些操作。arguments 是一个对象,它的属性是从0开始依次递增的数字,还有 callee 和length 等属性,与数组相似;但是它却没有数组常见的方法属性,如 forEach,reduce 等,所以叫它们类数组。
JS 的map和forEach 循环能否 break?能否await
JS 数组遍历处理一步和break可以用 for..of
在 JavaScript 中,map 和 forEach 方法中是不能直接使用break 或 continue 语句来结束循环的。这是因为 map 和 forEach 是高阶函数,它们的设计初衷就是要遍历整个数组。
但是,我们可以在 forEach 中抛出一个自定义异常,然后在异常捕获块中终止循环
try {
[1, 2, 3, 4, 5].forEach(item => {
if (item > 3) throw new Error("Break");
console.log(item);
});
} catch (e) {
if (e.message !== "Break") throw e;
}这种方法虽然可以达到目的,但我个人不太推荐,因为使用异常来控制程序流程不是一个好习惯,下面看一下好的实现方式
[1, 2, 3, 4, 5].some(item => {
if (item > 3) return true;
console.log(item);
return false;
});every 方法也可以,这两个方法允许你在满足某个条件时提前结束循环,这种方法更加优雅,也更符合函数式编程的思想
使用普通的for循环
for (let item of [1, 2, 3, 4, 5]) {
if (item > 3) break;
console.log(item);
}这种方法虽然不够简介,但是在需要更细粒度控制循环的情况下很有用
使用 Array.prototype.reduce()
[1, 2, 3, 4, 5].reduce((acc, item) => {
if (acc.break) return acc;
if (item > 3) return { break: true };
console.log(item);
return acc;
}, {});这种方法比较灵活,可以根据需要存储和传递更多信息
高阶函数?
JavaScript 中,满足以下任意一个条件的函数,就是高阶函数,它的存在前提是 JS 中「函数是一等公民」—— 函数可以被赋值给变量、作为参数传递、作为返回值返回。
- 接收一个或多个函数作为入参;
- 执行后返回一个新的函数。
高阶函数是「接收函数入参」或「返回新函数」的函数,forEach/map 等数组方法都是高阶函数,适合声明式的遍历 / 转换,但不支持 break,也不适合处理串行异步。
ES6的类
核心结论:ES6 的 class 本质是 ES5 构造函数 + 原型链的语法糖,没有引入新的面向对象机制,只是写法更清晰、更符合传统面向对象语言的习惯。
一、基础对应:类声明、构造函数、实例方法
class Person {
// 1. 构造函数:对应 ES5 构造函数本体
constructor(name, age) {
this.name = name; // 实例属性
this.age = age;
}
// 2. 实例方法:对应 ES5 原型上的方法
sayHi() {
console.log(`Hi, I'm ${this.name}`);
}
}
// 使用
const p = new Person("张三", 18);
p.sayHi(); // 输出:Hi, I'm 张三
// 1. 构造函数:直接用 function 声明
function Person(name, age) {
this.name = name; // 实例属性
this.age = age;
}
// 2. 实例方法:挂载到构造函数的 prototype 上
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
// 使用方式完全一致
const p = new Person("张三", 18);
p.sayHi(); // 输出:Hi, I'm 张三关键细节补充
- typeof Person 在 ES6 中仍然是 "function",证明 class 本质就是函数;
- ES6 class 内部默认开启严格模式,ES5 构造函数需要手动加 "use strict";
- ES6 class 的实例方法是不可枚举的,而 ES5 直接赋值到 prototype 的方法默认可枚举(需用 Object.defineProperty 手动设置不可枚举)。
二、静态属性 / 静态方法的对应
class Person {
// 1. 静态属性(ES2022+ 支持直接在 class 内声明)
static species = "Human";
// 2. 静态方法:用 static 关键字
static getSpecies() {
return this.species; // 静态方法中的 this 指向类本身,而非实例
}
}
// 使用
console.log(Person.species); // 输出:Human
console.log(Person.getSpecies()); // 输出:Humanfunction Person() {}
// 1. 静态属性:直接挂载到构造函数本身
Person.species = "Human";
// 2. 静态方法:直接挂载到构造函数本身
Person.getSpecies = function() {
return this.species;
};
// 使用方式完全一致
console.log(Person.species); // 输出:Human
console.log(Person.getSpecies()); // 输出:Human三、继承的对应(核心难点)
ES6 的 extends + super 对应 ES5 的寄生组合继承(最完美的继承方式)。
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`Hi, I'm ${this.name}`);
}
}
// 1. 用 extends 继承父类
class Student extends Person {
constructor(name, grade) {
// 2. 用 super() 调用父类构造函数(必须在使用 this 之前调用)
super(name);
this.grade = grade;
}
study() {
console.log(`${this.name} is studying in grade ${this.grade}`);
}
// 重写父类方法
sayHi() {
// 3. 用 super.方法名() 调用父类的实例方法
super.sayHi();
console.log("I'm a student");
}
}
// 使用
const s = new Student("李四", 3);
s.sayHi(); // 输出:Hi, I'm 李四 → I'm a student
s.study(); // 输出:李四 is studying in grade 3// 父类构造函数
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
// 子类构造函数
function Student(name, grade) {
// 1. 借用父类构造函数:对应 ES6 的 super(name)
Person.call(this, name);
this.grade = grade;
}
// 2. 原型链继承:对应 ES6 的 extends
// 步骤1:创建一个中间对象,其原型指向父类的 prototype
Student.prototype = Object.create(Person.prototype);
// 步骤2:修正子类 prototype 的 constructor 指向(否则会指向 Person)
Student.prototype.constructor = Student;
// 子类实例方法
Student.prototype.study = function() {
console.log(`${this.name} is studying in grade ${this.grade}`);
};
// 重写父类方法
Student.prototype.sayHi = function() {
// 3. 调用父类原型方法:对应 ES6 的 super.sayHi()
Person.prototype.sayHi.call(this);
console.log("I'm a student");
};
// 使用方式完全一致
const s = new Student("李四", 3);
s.sayHi(); // 输出:Hi, I'm 李四 → I'm a student
s.study(); // 输出:李四 is studying in grade 3数组拆分成 10 个一组
const chunk = (arr, size = 10) => arr.reduce((r, e, i) => (r[Math.floor(i / size)] ||= []).push(e) && r, []);
// 测试数组
const arr = Array.from({ length: 25 }, (_, i) => i + 1);
// 拆分(默认10个一组)
const result = chunk(arr);
console.log(result);
/* 输出:
[
[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]
]
*/
// 自定义每组数量(比如5个一组)
const result5 = chunk(arr, 5);「如果组不存在,就初始化一个空数组」:r[Math.floor(i / size)] ||= []
- r:reduce 的累加器,最终会是我们要的「二维数组」(比如 [[1,2,...], [11,12,...]]);
||=:逻辑或赋值运算符(简写,等价于 a = a || b),意思是: 如果左边 r[组索引] 是「假值」(undefined、null、0 等),就给它赋值右边的 [](空数组); 如果左边已经是数组(真值),就什么都不做,保持原样。 作用:确保每个组在第一次使用时,都是一个空数组,避免 push 时报错。