1. 说说var、let、const之间的区别
- 一句话:
- var 是函数作用域,可以变量提升
- let 和 const 是块级作用域,有暂时性死区,不能重复声明
- const 声明的变量不能修改,但是const 对象的属性可以修改
| 特性 | var | let | const |
|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | ✅ 是 | ❌ 否 | ❌ 否 |
| 暂时性死区 | ❌ 无 | ✅ 有 | ✅ 有 |
| 重复声明 | ✅ 可以 | ❌ 报错 | ❌ 报错 |
| 是否可修改 | ✅ 可修改 | ✅ 可修改 | ❌ 不可重新赋值(但对象属性可变) |
function test() {
if (true) {
var a = 1;
let b = 2;
const c = 3;
}
console.log(a);
console.log(b);
console.log(c);
}
console.log(x);
var x = 5;
console.log(y);
let y = 5;
console.log(a);
let a = 10;
-重复声明
var a = 1;
var a = 2;
let b = 1;
let b = 2;
const obj = { name: 'Tom' };
obj.name = 'Jerry';
obj = {};
2. ES6中数组新增了哪些扩展?
- 一句话:
- from、of 新建数组
- find、includes 查元素
- fill、copyWithin 改数组
- entries/keys/values 遍历数组
| 方法名 | 分类 | 用途简介 | 示例 |
|---|
Array.from() | 创建 | 把类数组/可迭代对象转成真正数组 | Array.from('abc') → ['a','b','c'] |
Array.of() | 创建 | 用参数生成数组(区别于 Array 构造器) | Array.of(1, 2) → [1, 2] |
find() | 查找 | 找出符合条件的第一个元素 | [1,2,3].find(x => x > 1) → 2 |
findIndex() | 查索引 | 找出符合条件的第一个索引 | [1,2,3].findIndex(x => x > 1) → 1 |
includes() | 判断 | 是否包含某个值(类似字符串) | [1, 2, 3].includes(2) → true |
fill() | 替换 | 用某个值填满整个或部分数组 | [1,2,3].fill(0) → [0,0,0] |
copyWithin() | 拷贝 | 把数组一部分复制到另一部分 | [1,2,3,4].copyWithin(1, 2) → [1,3,4,4] |
entries() | 遍历器 | 返回键值对迭代器 | for-of 可用:[...arr.entries()] |
keys() | 遍历器 | 返回键名迭代器 | [...arr.keys()] → [0,1,2] |
values() | 遍历器 | 返回值迭代器 | [...arr.values()] → [a,b,c](某些浏览器需兼容) |
扩展:Array.of和Array.from 区别
1. Array.of
👉 作用:把一组参数转成数组。
- 无论参数是什么,都会放到数组里,不会有额外的“展开”。
- 常用来避免
Array()构造器的坑。
Array.of(1, 2, 3);
Array.of("a");
Array.of();
Array.of(undefined);
Array(3);
Array.of(3);
2. Array.from
👉 作用:把类数组或可迭代对象转换成真正的数组。
- 常见输入:
arguments、NodeList、Set、Map。
- 可以带第二个参数(类似
map),对元素做处理。
function test() {
return Array.from(arguments);
}
test(1, 2, 3);
Array.from(new Set([1, 2, 3]));
Array.from([1, 2, 3], x => x * 2);
3. 核心区别总结
| 特性 | Array.of | Array.from |
|---|
| 输入 | 一组参数 | 类数组对象或可迭代对象 |
| 输出 | 包含这些参数的数组 | 转换后的新数组 |
| 是否展开 | 不展开,按原样当元素 | 会展开类数组/可迭代对象 |
| 是否支持回调 | ❌ | ✅ 第二个参数可做 map 处理 |
| 常见用途 | 避免 Array(3) 的歧义 | 把 arguments、NodeList、Set 等转成数组 |
👉 简单记忆:
Array.of = 参数列表 → 数组
Array.from = 类数组/可迭代对象 → 数组
3. 函数新增了哪些扩展?
- 一句话:默认值、箭头函数、省参数、...收集、name提升、this自动绑!
| 特性 | 说明 | 示例 |
|---|
| ✅ 默认参数 | 函数参数可设置默认值 | function fn(a = 1) {} |
✅ 剩余参数 ...args | 获取不定数量参数(替代 arguments) | function sum(...nums) {} |
✅ 箭头函数 => | 更简洁的函数写法,自动绑定 this | const add = (a, b) => a + b |
✅ 函数名 name属性 | 函数自动拥有 name 属性 | (function hello() {}).name // "hello" |
✅ function* 生成器函数 | 暂停执行、生成迭代器 | function* gen() { yield 1; } |
function greet(name = '匿名') {
return `你好,${name}`;
}
greet();
- 剩余参数...rest (代替arguments)
function sum(...nums) {
return nums.reduce((a, b) => a + b);
}
sum(1, 2, 3);
- 箭头函数 ()=>{}
- 箭头函数没有自己的this,arguments,super,自动继承外层上下文
- 关于this指向:
谁定义就指向谁
function Timer() {
this.count = 0;
setInterval(() => {
this.count++;
}, 1000);
}
4. 函数名属性 function.name
function foo() {}
console.log(foo.name);
const bar = function () {};
console.log(bar.name);
function* gen(){
yield 1;
yield 2;
return 3;
}
const it = gen();
it.next();
补充:ES6+ 的 async/await(ES8)
虽然是 ES8 引入的,但属于函数的重要演进:
js
复制编辑
async function fetchData() {
const res = await fetch('/api');
const data = await res.json();
return data;
}
4. 对象新增了哪些扩展?
✅ 一句话速记口诀:
简洁写法、动态属性、Object 新方法、原型操作全掌握!
🧠 ES6 对象的主要扩展(分为两类):
🔹 一、对象字面量增强语法(简洁写法)
| 特性 | 说明 | 示例 |
|---|
| 属性简写 | 变量名与属性名相同,可省略 | { name } 相当于 { name: name } |
| 方法简写 | 可省略 function 关键字 | say() {} |
| 动态属性名 | 属性名可用变量计算 | [propName]: value |
✅ 示例:
const name = 'Tom';
const age = 18;
const prop = 'score';
const person = {
name,
age,
say() {
console.log(`I am ${this.name}`);
},
[prop]: 100
};
🔹 二、Object 新增的 API 方法
✅ 1. Object.is(a, b)
- 类似于
===,但可以区分 +0 和 -0,也能判断 NaN === NaN
Object.is(NaN, NaN);
Object.is(+0, -0);
✅ 2. Object.assign(target, ...sources)
const a = { x: 1 };
const b = { y: 2 };
Object.assign(a, b);
✅ 3. Object.keys() / Object.values() / Object.entries()
| 方法 | 返回值 |
|---|
Object.keys(obj) | 所有可枚举键数组 |
Object.values(obj) | 所有可枚举值数组 |
Object.entries(obj) | 键值对数组:[key, value] |
const obj = { a: 1, b: 2 };
Object.entries(obj);
✅ 4. Object.getOwnPropertyDescriptors()
- 获取对象所有属性(包括 getter/setter)的完整描述符
const obj = {
get name() {
return 'Tom';
}
};
Object.getOwnPropertyDescriptors(obj);
输出结果:
{
name: {
get: [Function: get name],
set: undefined,
enumerable: true,
configurable: true
}
}
✅ 5. 原型操作方法
| 方法 | 作用 |
|---|
Object.create(proto) | 以某对象为原型创建新对象 |
Object.setPrototypeOf(obj, proto) | 设置对象原型 |
Object.getPrototypeOf(obj) | 获取对象原型 |
const proto = {
greet() {
console.log('Hi');
}
};
const user = Object.create(proto);
user.greet();
🎯 面试答题模板:
ES6 对象扩展主要包括两部分:
一是对象字面量增强语法,如属性简写、方法简写、动态属性名;
二是新增的 Object 方法,如 Object.is、assign、entries、create、getPrototypeOf 等。
这些扩展让对象操作更灵活、更表达式化,也更易与 Map/Set 等数据结构联动使用。
5.你是怎么理解ES6中 Promise的?使⽤场景?
✅ Promise 是一个 构造函数,用来创建“表示未来才会结束的任务”的对象。
基本语法:
const promise = new Promise((resolve, reject) => {
});
| 状态 | 说明 |
|---|
pending | 初始状态,等待中 |
fulfilled | 成功,调用了 resolve() |
rejected | 失败,调用了 reject() |
function run(generatorFunc) {
const gen = generatorFunc();
function step(nextF) {
let next;
try {
next = nextF();
} catch (e) {
return Promise.reject(e);
}
if (next.done) return Promise.resolve(next.value);
return Promise.resolve(next.value).then(
v => step(() => gen.next(v)),
e => step(() => gen.throw(e))
);
}
return step(() => gen.next());
}
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e };
return new MyPromise((resolve, reject) => {
if (this.state === FULFILLED) {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (e) {
reject(e);
}
});
}
if (this.state === REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (e) {
reject(e);
}
});
}
if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (e) {
reject(e);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (e) {
reject(e);
}
});
});
}
});
}
}
Promise.all 等全部成功,
Promise.race 谁快听谁的,
Promise.allSettled 全部等结果,失败也不怕!
| 方法 | 成功条件 | 失败条件 | 返回值 | 常用场景 |
|---|
Promise.all | 所有都成功才成功 | 只要有一个失败就立即失败 | 成功返回数组结果 | 并发请求并全部依赖 |
Promise.race | 任意一个先结束就返回(成功或失败) | 第一个失败即失败 | 返回第一个结果 | 超时控制、竞速请求 |
Promise.allSettled | 都执行完才返回 | 不会中断失败 | 每个结果都有状态 | 展示每个结果是否成功 |
6. 你是怎么理解ES6中Module的?使⽤场景?
| 特性 | 说明 |
|---|
| 静态加载 | 编译阶段就确定模块依赖,提高编译效率 |
| 按需引入 | 只加载用到的部分,支持 tree-shaking |
| 默认严格模式 | 模块默认是 use strict |
| 模块作用域隔离 | 不会污染全局变量 |
| 支持异步动态加载 | 可通过 import() 动态导入 |
🧩 常用语法:
✅ 1. 导出模块
export const name = 'Tom';
export function greet() {}
export default function () {
console.log('我是默认导出的函数');
}
✅ 2. 导入模块
import { name, greet } from './module.js';
import myFn from './module.js';
import myFn, { name } from './module.js';
✅ 3. 重命名(防止变量名冲突)
import { name as userName } from './module.js';
✅ 4. 导出所有(重新导出)
export * from './user.js';
✅ 5. 动态导入(异步)
import('./module.js').then(mod => {
mod.doSomething();
});
📌 可用于懒加载、路由按需加载
🎯 使用场景:
| 场景 | 示例 |
|---|
| 分模块组织代码 | utils.js / api.js / config.js |
| 前端构建工具 Tree-shaking | Webpack/Vite 自动去除没用的代码 |
| SSR 服务端渲染项目 | Next.js 使用 ES Module |
| 动态导入组件 | Vue/React 的异步组件加载 |
| 浏览器原生使用 Module | <script type="module"> 支持 |
7. 你是怎么理解ES6中 Generator的?使⽤场景?
- Generator 是可以暂停执行的函数,用于控制流程,异步处理和状态管理。非常适合处理复杂逻辑逐步执行
function* generatorFn() {
yield 1;
yield 2;
return 3;
}
function* gen() {
console.log('开始');
yield 'A';
console.log('中间');
yield 'B';
return '结束';
}
const g = gen();
console.log(g.next());
console.log(g.next());
console.log(g.next());
🎯 实际使用场景(超实用):
| 场景 | 说明 |
|---|
| ✅ 异步流程控制 | 比如连续的请求处理(早期 async/await 替代前) |
| ✅ 按需生成数据 | 无限序列生成器、分页等 |
| ✅ 状态机实现 | 多状态流程控制(如表单流程) |
| ✅ 自定义遍历器 | 对象、树结构自定义迭代逻辑 |
| ✅ 中间件机制 | Koa2 源码中的核心机制就是 Generator |
🔁 示例:异步流程控制(模拟 async/await)
function* asyncFlow() {
const res1 = yield fetch('/api/user');
const res2 = yield fetch('/api/order');
console.log(res1, res2);
}
function run(generator) {
const it = generator();
function step(data) {
const { value, done } = it.next(data);
if (done) return;
value.then(step);
}
step();
}
8. 你是怎么理解ES6中 Decorator(装饰器) 的?使⽤场景?
- 装饰器是一种函数,用于“增强”类,方法,属性,参数的功能,不改源代码就能添加逻辑,就像给对象贴标签
@log
class MyClass{}
function log(target : Function){
console.log('被装饰的类',target.name)
}
- 本质就是一个函数,用来“拦截并增强”类,方法,属性,参数等
- 它是一个高级的元编程工具,让你可以:
- 在类定义时“动态注入”行为
- 类似于 AOP(面向切面编程)
- 编译阶段就可以插入逻辑
🧪 装饰器类型总结(TypeScript 中常见)
| 类型 | 作用对象 | 示例 |
|---|
| 类装饰器 | 装饰整个类 | @Controller() |
| 属性装饰器 | 装饰类的某个属性 | @Inject() |
| 方法装饰器 | 装饰类的方法 | @Get() |
| 参数装饰器 | 装饰方法的参数 | @Body() |
1. 类装饰器(用于增强类本身)
@Controller
class UseController{}
function Controller(target : Function){
target.property.isController = true
}
const uc = new UserController();
console.log(uc.isController);
2. 属性装饰器(通常用于依赖注入)
function Inject(target: any, key: string) {
console.log(`💉 正在注入属性: ${key} 到类 ${target.constructor.name}`);
}
class OrderService {
@Inject
dbService: any;
}
3. 方法饰器(拦截函数调用,添加日志等)
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`🔍 调用了方法: ${key},参数:`, args);
return original.apply(this, args);
};
return descriptor;
}
class UserService {
@Log
login(username: string, password: string) {
console.log('🔐 正在执行登录逻辑...');
return true;
}
}
new UserService().login('admin', '123456');
4. 参数装饰器
function Body(target: any, methodName: string, paramIndex: number) {
console.log(`📦 方法 ${methodName} 的第 ${paramIndex} 个参数标记为 @Body`);
}
class ApiController {
create(@Body data: any) {
console.log('📝 创建数据:', data);
}
}
new ApiController().create({ name: 'Tom' });
9. 你是怎么理解ES6新增Set、Map两种数据结构的?
- Set 是 “无重复数组“,Map 是 “键值对的加强版对象”,都支持更强的 API 和迭代能力。
-
📦 一、Set:值唯一的集合
✅ 特点:
| 特性 | 说明 |
|---|
| ✅ 值唯一 | 自动去重,不能重复 |
| ✅ 类型支持 | 可以存对象、NaN、undefined |
| ✅ 可遍历 | 支持 for...of、forEach |
✨ 常用 API:
const s = new Set([1, 2, 2, 3]);
s.add(4);
s.has(2);
s.delete(3);
s.size;
s.clear();
for (let item of s) console.log(item);
🎯 使用场景:
| 场景 | 说明 |
|---|
| ✅ 数组去重 | Array.from(new Set(arr)) |
| ✅ 判断是否访问过 | 比如 visited URL |
| ✅ 多值唯一集合操作 | 处理标签、关键字 |
📦 二、Map:更强大的键值对容器
✅ 特点:
| 特性 | 说明 |
|---|
| ✅ 任意类型作 key | 可以是对象、函数等 |
| ✅ 有序 | 遍历顺序就是插入顺序 |
| ✅ 不存在原型链干扰 | 不会和 hasOwnProperty 冲突 |
✨ 常用 API:
const map = new Map();
map.set('name', 'Tom');
map.set({ id: 1 }, '对象键');
map.get('name');
map.has('name');
map.delete('name');
map.clear();
map.size;
for (let [key, val] of map) {
console.log(key, val);
}
🎯 使用场景:
| 场景 | 说明 |
|---|
| ✅ 复杂键值缓存 | 如对象缓存、LRU |
| ✅ 数据结构优化 | 避免对象键名只能是字符串 |
| ✅ 高性能查找表 | 比如映射状态码、UI 控件缓存 |
📊 Set vs Map vs Object 对比:
| 特性 | Object | Map | Set |
|---|
| 键 | 字符串/符号 | 任意类型 | 无键,只存值 |
| 是否有序 | 无序 | 有序 | 有序 |
| 是否可迭代 | ❌(需额外处理) | ✅(直接 for...of) | ✅ |
| 常用操作 | obj[key] | .get()/.set() | .add()/.has() |
| 重复值 | 支持 | 支持 key 唯一 | 自动去重 |
🧠 面试答题模板:
ES6 新增的 Set 和 Map 是对传统数组和对象的增强数据结构。
Set 用于存储唯一值,可用于数组去重、状态缓存等;Map 是更灵活的键值对集合,支持任意类型作为键,常用于结构化数据存储、缓存、对象映射等。
相比传统的 Object/Array,它们提供了更强的 API、更高的性能和更准确的数据语义。
10. 你是怎么理解ES6中Proxy的?使⽤场景?
- Proxy 是”代理“对象的工具,可以拦截和自定义对象的基本操作行为(读写、函数调用、属性判断等) ,像是在对象前面加了一层“中间人”。
- 例子:拦截属性读写
class User {
name:'Ton',
age:25
}
const proxyUser = new Proxy(user, {
get(target, key) {
console.log(`读取属性:${key}`);
return target[key];
},
set(target, key, value) {
console.log(`设置属性:${key} = ${value}`);
target[key] = value;
return true;
}
});
console.log(proxyUser.name);
proxyUser.age = 30;
🧠 Proxy 能拦截哪些操作?(常用 traps)
| 操作行为 | trap 名称 | 说明 |
|---|
| 读取属性 | get | 拦截 obj.prop |
| 设置属性 | set | 拦截 obj.prop = val |
| 是否存在 | has | 拦截 'key' in obj |
| 删除属性 | deleteProperty | 拦截 delete obj.key |
| 枚举属性 | ownKeys | 拦截 Object.keys()、for...in |
| 获取原型 | getPrototypeOf | 拦截 Object.getPrototypeOf() |
| 调用函数 | apply | 拦截函数调用(用于函数代理) |
🎯 使用场景总结:
| 场景 | 应用说明 |
|---|
| ✅ Vue3 响应式 | reactive() 内部用 Proxy 监听对象变化 |
| ✅ 表单校验 | 拦截非法属性设置 |
| ✅ 数据保护 | 防止访问或修改敏感属性 |
| ✅ 自动默认值 | get 时自动返回默认内容 |
| ✅ Mock 数据 | 拦截访问,返回模拟结果 |
| ✅ API 代理 | 调用远程方法,如 RPC 封装 |
🛠 示例场景代码
✅ 1. Vue3 响应式核心原理(简化版 reactive)
js
复制编辑
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
console.log('读取:', key);
return target[key];
},
set(target, key, val) {
console.log('设置:', key, '=', val);
target[key] = val;
return true;
}
});
}
const state = reactive({ count: 0 });
state.count++;
✅ 2. 设置限制器(防止非法属性被添加)
js
复制编辑
const safeUser = new Proxy({}, {
set(target, key, value) {
if (['name', 'age'].includes(key)) {
target[key] = value;
return true;
} else {
throw new Error(`❌ 不允许添加属性:${key}`);
}
}
});
safeUser.name = 'Alice';
safeUser.role = 'admin';
✅ 3. 函数代理(统计调用次数)
function sayHi(name) {
console.log(`Hi, ${name}`);
}
const trackedSayHi = new Proxy(sayHi, {
apply(target, thisArg, args) {
console.log('📞 被调用了');
return target.apply(thisArg, args);
}
});
trackedSayHi('Tom');
🧠 面试答题模板:
Proxy 是 ES6 引入的元编程工具,它可以拦截对对象的各种操作(如读写、函数调用、in 判断等),并自定义行为。
它被广泛用于响应式系统(Vue3)、安全控制、Mock 接口、日志追踪等。相比 Object.defineProperty,Proxy 支持更广泛的操作拦截,能完美代理整个对象。
✅ Proxy vs Object.defineProperty 对比:
| 对比点 | Proxy | defineProperty |
|---|
| 监听属性 | ✅ 可监听新增/删除 | ❌ 只能监听已有属性 |
| 监听数组 | ✅ 完整支持 | ❌ 不支持索引变化 |
| API 简洁性 | ✅ 一个代理搞定所有 | ❌ 每个属性都要手动定义 |
| 支持能力 | ✅ 支持 13 种拦截操作 | ❌ 支持少 |
| 性能 | ✅ 更优(现代浏览器优化) | ❌ |