积累:03-ES6

76 阅读12分钟

1. 说说var、let、const之间的区别

  • 一句话:
    • var 是函数作用域,可以变量提升
    • let 和 const 是块级作用域,有暂时性死区,不能重复声明
    • const 声明的变量不能修改,但是const 对象的属性可以修改
特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升✅ 是❌ 否❌ 否
暂时性死区❌ 无✅ 有✅ 有
重复声明✅ 可以❌ 报错❌ 报错
是否可修改✅ 可修改✅ 可修改❌ 不可重新赋值(但对象属性可变)
  • 作用域差异
function test() {
  if (true) {
    var a = 1;
    let b = 2;
    const c = 3;
  }
  console.log(a); // ✅ 1
  console.log(b); // ❌ 报错:b is not defined
  console.log(c); // ❌ 报错:c is not defined
}

  • 变量提升
console.log(x); // undefined(var 提升)
var x = 5;

console.log(y); // ❌ 报错(let 不提升)
let y = 5;

  • 暂时性死区(TDZ)
console.log(a); // 报错 ReferenceError
let a = 10;
//  在声明a之前,a不能访问,这段事件就叫“暂时性死区”

-重复声明

var a = 1;
var a = 2; // ✅ 合法

let b = 1;
let b = 2; // ❌ 报错:Identifier 'b' has already been declared

  • const 不可修改(引用不可变)
const obj = { name: 'Tom' };
obj.name = 'Jerry'; // ✅ 可以改属性

obj = {}; // ❌ 报错:Assignment to constant variable

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);   // [1, 2, 3]
Array.of("a");       // ["a"]
Array.of();          // []
Array.of(undefined); // [undefined]

// 对比一下 Array():
Array(3);    // [empty × 3]  → 长度为3的空数组
Array.of(3); // [3]          → 就是包含数字3的数组

2. Array.from

👉 作用:把类数组可迭代对象转换成真正的数组。

  • 常见输入:argumentsNodeListSetMap
  • 可以带第二个参数(类似 map),对元素做处理。
// 类数组转换
function test() {
  return Array.from(arguments);
}
test(1, 2, 3); // [1, 2, 3]

// 可迭代对象(Set)
Array.from(new Set([1, 2, 3])); // [1, 2, 3]

// 带map函数
Array.from([1, 2, 3], x => x * 2); // [2, 4, 6]

3. 核心区别总结

特性Array.ofArray.from
输入一组参数类数组对象或可迭代对象
输出包含这些参数的数组转换后的新数组
是否展开不展开,按原样当元素会展开类数组/可迭代对象
是否支持回调✅ 第二个参数可做 map 处理
常见用途避免 Array(3) 的歧义argumentsNodeListSet 等转成数组

👉 简单记忆:

  • Array.of = 参数列表 → 数组
  • Array.from = 类数组/可迭代对象 → 数组

3. 函数新增了哪些扩展?

  • 一句话:默认值、箭头函数、省参数、...收集、name提升、this自动绑!
特性说明示例
✅ 默认参数函数参数可设置默认值function fn(a = 1) {}
✅ 剩余参数 ...args获取不定数量参数(替代 argumentsfunction sum(...nums) {}
✅ 箭头函数 =>更简洁的函数写法,自动绑定 thisconst 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); // 6

  • 箭头函数 ()=>{}
    • 箭头函数没有自己的this,arguments,super,自动继承外层上下文
    • 关于this指向:谁定义就指向谁
function Timer() {
  this.count = 0;
  setInterval(() => {
    this.count++; // this 指向 Timer 实例
  }, 1000);
}


4. 函数名属性 function.name


function foo() {}
console.log(foo.name); // "foo"

const bar = function () {};
console.log(bar.name); // "bar"(现代浏览器自动推断)
  • 函数生成器 function * (高级)
function* gen(){
  yield 1;
  yield 2;
  return 3;
}

const it = gen();
it.next(); // { value: 1, done: false }

补充: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); // ✅ true
Object.is(+0, -0);   // ❌ false
✅ 2. Object.assign(target, ...sources)
  • 对象浅拷贝(从后往前合并属性)

const a = { x: 1 };
const b = { y: 2 };
Object.assign(a, b); // a => { x: 1, y: 2 }
✅ 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); // [['a', 1], ['b', 2]]
✅ 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(); // Hi

🎯 面试答题模板:

ES6 对象扩展主要包括两部分:
一是对象字面量增强语法,如属性简写、方法简写、动态属性名;
二是新增的 Object 方法,如 Object.isassignentriescreategetPrototypeOf 等。
这些扩展让对象操作更灵活、更表达式化,也更易与 Map/Set 等数据结构联动使用。

5.你是怎么理解ES6中 Promise的?使⽤场景?

✅ Promise 是一个 构造函数,用来创建“表示未来才会结束的任务”的对象。

基本语法:

const promise = new Promise((resolve, reject) => {
  // 异步操作成功时调用 resolve(value)
  // 异步操作失败时调用 reject(reason)
});
状态说明
pending初始状态,等待中
fulfilled成功,调用了 resolve()
rejected失败,调用了 reject()
  • async/await 的本质和语法糖实现
function run(generatorFunc) {
  const gen = generatorFunc(); // 拿到生成器对象

  function step(nextF) {
    let next;
    try {
      next = nextF(); // 执行 generator 的 next()
    } 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());
}

  • 手写Promise
// 三种状态:只会单向变化,且只能变化一次
const PENDING   = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED  = 'rejected';

class MyPromise {
  constructor(executor) {
    this.state = PENDING;      // 当前状态
    this.value = undefined;    // 成功结果
    this.reason = undefined;   // 失败原因
    this.onFulfilledCallbacks = []; // 成功回调队列(then 注册的)
    this.onRejectedCallbacks  = []; // 失败回调队列(then 注册的)

    // 成功时调用
    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());
      }
    };

    // 立即执行 executor(用户传入的函数),并捕获异常
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  /**
   * then:注册回调,并返回一个新的 Promise(链式调用关键)
   */
  then(onFulfilled, onRejected) {
    // 如果用户没传函数,做值穿透 / 错误透传
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    onRejected  = typeof onRejected === 'function' ? onRejected : e => { throw e };

    // 返回一个新的 Promise,支持链式调用
    return new MyPromise((resolve, reject) => {
      // 如果已经是成功状态,异步执行成功回调
      if (this.state === FULFILLED) {
        setTimeout(() => { // 异步执行(Promises/A+ 规范要求)
          try {
            const x = onFulfilled(this.value); // 拿到回调返回值
            resolve(x); // 直接 resolve 给下一个 then
          } catch (e) {
            reject(e);  // 出错就 reject
          }
        });
      }

      // 如果已经是失败状态,异步执行失败回调
      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-shakingWebpack/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* 声明一个生成器函数
// yield 语句表示“暂停”,下一次调用.next()继续执行
// 返回的是一个 **迭代器对象**
  • 基本用法
function* gen() {
  console.log('开始');
  yield 'A';
  console.log('中间');
  yield 'B';
  return '结束';
}

const g = gen();
console.log(g.next()); // { value: 'A', done: false }
console.log(g.next()); // { value: 'B', done: false }
console.log(g.next()); // { value: '结束', done: true }

🎯 实际使用场景(超实用):

场景说明
✅ 异步流程控制比如连续的请求处理(早期 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); // value 是 Promise
  }

  step();
}

8. 你是怎么理解ES6中 Decorator(装饰器) 的?使⽤场景?

  • 装饰器是一种函数,用于“增强”类,方法,属性,参数的功能,不改源代码就能添加逻辑,就像给对象贴标签
@log
class MyClass{}
function log(target : Function){
    console.log('被装饰的类',target.name) // 输出:MyClass
}
// 意思就是把log函数应用到MyClass类上

  • 本质就是一个函数,用来“拦截并增强”类,方法,属性,参数等
  • 它是一个高级的元编程工具,让你可以:
    • 在类定义时“动态注入”行为
    • 类似于 AOP(面向切面编程)
    • 编译阶段就可以插入逻辑

🧪 装饰器类型总结(TypeScript 中常见)

类型作用对象示例
类装饰器装饰整个类@Controller()
属性装饰器装饰类的某个属性@Inject()
方法装饰器装饰类的方法@Get()
参数装饰器装饰方法的参数@Body()
1. 类装饰器(用于增强类本身)
// 应用装饰器,给UseController添加一个Controller方法
@Controller
class UseController{}

function Controller(target : Function){
    target.property.isController = true
}

// 测试
const uc = new UserController();
console.log(uc.isController); // true ✅
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. 参数装饰器
// 👇 参数装饰器:记录第几个参数是 body
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...offorEach

✨ 常用 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 }, '对象键'); // 对象作为 key
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 对比:

特性ObjectMapSet
字符串/符号任意类型无键,只存值
是否有序无序有序有序
是否可迭代❌(需额外处理)✅(直接 for...of)
常用操作obj[key].get()/.set().add()/.has()
重复值支持支持 key 唯一自动去重

🧠 面试答题模板:

ES6 新增的 SetMap 是对传统数组和对象的增强数据结构。
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 对比:

对比点ProxydefineProperty
监听属性✅ 可监听新增/删除❌ 只能监听已有属性
监听数组✅ 完整支持❌ 不支持索引变化
API 简洁性✅ 一个代理搞定所有❌ 每个属性都要手动定义
支持能力✅ 支持 13 种拦截操作❌ 支持少
性能✅ 更优(现代浏览器优化)