Javascript/ES6+/Typescript重点内容篇——手撕(待总结)

发布于:2025-08-07 ⋅ 阅读:(23) ⋅ 点赞:(0)

在这里插入图片描述

前端核心知识点梳理与面试题详解

1. Promise

核心知识点

  • Promise 是异步编程的解决方案,用于处理异步操作
  • 三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
  • 状态一旦改变就不会再变,从 pending 到 fulfilled 或 rejected
  • 常用方法:then()、catch()、finally()、all()、race()、allSettled() 等

面试题:实现 Promise.all

Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    // 判断传入的是否是可迭代对象
    if (!Array.isArray(promises)) {
      return reject(new TypeError('The input must be an array'));
    }
    
    const results = [];
    let completedCount = 0;
    const total = promises.length;
    
    if (total === 0) {
      return resolve(results);
    }
    
    promises.forEach((promise, index) => {
      // 确保每个元素都是 Promise 对象
      Promise.resolve(promise).then(
        (value) => {
          results[index] = value;
          completedCount++;
          
          // 所有 Promise 都成功时才 resolve
          if (completedCount === total) {
            resolve(results);
          }
        },
        (reason) => {
          // 有一个 Promise 失败就立即 reject
          reject(reason);
        }
      );
    });
  });
};

面试题:实现 Promise 串行执行

// 实现一个函数,让多个Promise按顺序执行
function promiseSerial(tasks) {
  // 初始返回一个已 resolved 的 Promise
  return tasks.reduce((prev, current) => {
    return prev.then((results) => {
      // 等待当前任务执行完成
      return current().then((result) => {
        // 将结果添加到数组中
        return [...results, result];
      });
    });
  }, Promise.resolve([]));
}

// 使用示例
const createTask = (time, value) => {
  return () => new Promise(resolve => {
    setTimeout(() => {
      console.log(value);
      resolve(value);
    }, time);
  });
};

const tasks = [
  createTask(1000, '任务1'),
  createTask(500, '任务2'),
  createTask(800, '任务3')
];

promiseSerial(tasks).then(results => {
  console.log('所有任务完成:', results);
});

2. 原型链

核心知识点

  • 每个对象都有 __proto__ 属性,指向其构造函数的 prototype
  • 构造函数有 prototype 属性,是其实例的原型
  • 原型链是由 __proto__ 连接而成的链式结构
  • 当访问对象的属性或方法时,会先在自身查找,找不到则沿原型链向上查找
  • Object.prototype 是原型链的终点,其 __proto__ 为 null

面试题:原型链相关输出题

function Foo() {
  getName = function() { console.log(1); };
  return this;
}

Foo.getName = function() { console.log(2); };

Foo.prototype.getName = function() { console.log(3); };

var getName = function() { console.log(4); };

function getName() { console.log(5); }

// 以下输出结果是什么?
Foo.getName();          // 2 - 访问Foo的静态方法
getName();              // 4 - 函数声明提升后被变量声明覆盖
Foo().getName();        // 1 - Foo()执行后修改了全局getName
getName();              // 1 - 已经被Foo()修改
new Foo.getName();      // 2 - 优先级问题,相当于new (Foo.getName)()
new Foo().getName();    // 3 - 实例化后访问原型上的方法
new new Foo().getName();// 3 - 先实例化Foo,再调用其原型上的getName并实例化

面试题:实现 instanceof

function myInstanceof(left, right) {
  // 基本类型直接返回false
  if (typeof left !== 'object' || left === null) return false;
  
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left);
  
  // 遍历原型链
  while (true) {
    // 找到尽头仍未匹配,返回false
    if (proto === null) return false;
    
    // 找到匹配的原型,返回true
    if (proto === right.prototype) return true;
    
    // 继续向上查找
    proto = Object.getPrototypeOf(proto);
  }
}

// 测试
function Person() {}
const p = new Person();
console.log(myInstanceof(p, Person)); // true
console.log(myInstanceof(p, Object)); // true
console.log(myInstanceof(p, Array));  // false
console.log(myInstanceof([], Array)); // true

3. 生成器(Generator)

核心知识点

  • 生成器函数使用 function* 声明,内部使用 yield 关键字
  • 调用生成器函数返回迭代器对象,而非直接执行函数体
  • 通过迭代器的 next() 方法控制函数执行,每次遇到 yield 暂停
  • next() 方法返回包含 valuedone 属性的对象
  • 可用于实现异步操作的同步化表达、自定义迭代器等

面试题:使用 Generator 实现异步操作

// 模拟异步操作
function fetchData(url) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`数据: ${url}`);
    }, 1000);
  });
}

// 使用Generator处理异步
function* getData() {
  console.log('开始请求数据1');
  const data1 = yield fetchData('url1');
  console.log('获取到数据1:', data1);
  
  console.log('开始请求数据2');
  const data2 = yield fetchData('url2');
  console.log('获取到数据2:', data2);
  
  return '所有数据获取完成';
}

// 执行生成器
function runGenerator(gen) {
  const iterator = gen();
  
  function handleResult(result) {
    if (result.done) {
      console.log('最终结果:', result.value);
      return;
    }
    
    // 处理Promise
    result.value.then(data => {
      handleResult(iterator.next(data));
    });
  }
  
  handleResult(iterator.next());
}

// 运行
runGenerator(getData);

4. 闭包

核心知识点

  • 闭包是函数及其捆绑的周边环境状态的引用的组合
  • 形成条件:函数嵌套、内部函数引用外部函数的变量、内部函数被外部引用
  • 作用:实现私有变量、模块化、柯里化、保存状态等
  • 注意:不当使用可能导致内存泄漏

面试题:实现防抖函数

function debounce(fn, delay, immediate = false) {
  let timer = null;
  let isInvoked = false;
  
  // 返回闭包函数
  return function(...args) {
    const context = this;
    
    // 如果存在定时器,清除它
    if (timer) {
      clearTimeout(timer);
    }
    
    // 立即执行的情况
    if (immediate && !isInvoked) {
      fn.apply(context, args);
      isInvoked = true;
    } else {
      // 延迟执行
      timer = setTimeout(() => {
        fn.apply(context, args);
        isInvoked = false;
        timer = null;
      }, delay);
    }
  };
}

// 使用示例
const handleSearch = (keyword) => {
  console.log('搜索:', keyword);
};

// 防抖处理,延迟500ms执行,不立即执行
const debouncedSearch = debounce(handleSearch, 500);

// 模拟频繁调用
debouncedSearch('a');
debouncedSearch('ab');
debouncedSearch('abc'); // 只有最后一次会在500ms后执行

面试题:实现节流函数

function throttle(fn, interval) {
  let lastTime = 0;
  let timer = null;
  
  return function(...args) {
    const context = this;
    const now = Date.now();
    const remainingTime = interval - (now - lastTime);
    
    // 如果时间间隔已到,直接执行
    if (remainingTime <= 0) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(context, args);
      lastTime = now;
    } else if (!timer) {
      // 否则设置定时器,确保最后一次一定会执行
      timer = setTimeout(() => {
        fn.apply(context, args);
        lastTime = Date.now();
        timer = null;
      }, remainingTime);
    }
  };
}

// 使用示例
const handleScroll = () => {
  console.log('滚动事件触发');
};

// 节流处理,每100ms最多执行一次
const throttledScroll = throttle(handleScroll, 100);

// 监听滚动事件
window.addEventListener('scroll', throttledScroll);

5. 异步与事件循环

核心知识点

  • JavaScript 是单线程语言,通过事件循环实现异步
  • 事件循环:调用栈 -> 微任务队列 -> 宏任务队列 -> UI渲染
  • 微任务优先级高于宏任务,常见微任务:Promise.then/catch/finally、process.nextTick、MutationObserver
  • 常见宏任务:setTimeout、setInterval、DOM事件、I/O操作、setImmediate

面试题:事件循环输出题

console.log('1');

setTimeout(function() {
  console.log('2');
  new Promise(function(resolve) {
    console.log('3');
    resolve();
  }).then(function() {
    console.log('4');
  });
}, 0);

new Promise(function(resolve) {
  console.log('5');
  resolve();
}).then(function() {
  console.log('6');
});

setTimeout(function() {
  console.log('7');
  new Promise(function(resolve) {
    console.log('8');
    resolve();
  }).then(function() {
    console.log('9');
  });
}, 0);

console.log('10');

// 输出顺序:1 5 10 6 2 3 4 7 8 9

6. Map

核心知识点

  • Map 是 ES6 新增的键值对集合
  • 与对象相比,Map 的键可以是任意类型,而对象的键只能是字符串或 Symbol
  • 常用方法:set()、get()、has()、delete()、clear()、size 属性
  • 可以通过 for…of 直接迭代,迭代顺序是插入顺序
  • 适合用于频繁添加/删除键值对的场景

面试题:实现 Map 的基本功能

class MyMap {
  constructor() {
    // 存储键值对的数组
    this.items = [];
  }
  
  // 向Map中添加元素
  set(key, value) {
    // 检查键是否已存在
    const index = this.items.findIndex(item => this.isEqual(item.key, key));
    
    if (index !== -1) {
      // 键存在则更新值
      this.items[index].value = value;
    } else {
      // 键不存在则添加新键值对
      this.items.push({ key, value });
    }
    
    return this;
  }
  
  // 获取指定键的值
  get(key) {
    const item = this.items.find(item => this.isEqual(item.key, key));
    return item ? item.value : undefined;
  }
  
  // 检查是否包含指定键
  has(key) {
    return this.items.some(item => this.isEqual(item.key, key));
  }
  
  // 删除指定键
  delete(key) {
    const index = this.items.findIndex(item => this.isEqual(item.key, key));
    
    if (index !== -1) {
      this.items.splice(index, 1);
      return true;
    }
    
    return false;
  }
  
  // 清空Map
  clear() {
    this.items = [];
  }
  
  // 获取Map的大小
  get size() {
    return this.items.length;
  }
  
  // 判断两个键是否相等
  isEqual(a, b) {
    // 处理NaN的情况,NaN !== NaN但在Map中视为相等
    if (a !== a && b !== b) return true;
    // 处理+0和-0的情况,在Map中视为相等
    if (a === 0 && b === 0) return 1 / a === 1 / b;
    return a === b;
  }
  
  // 迭代器,用于for...of循环
  *[Symbol.iterator]() {
    for (const item of this.items) {
      yield [item.key, item.value];
    }
  }
}

// 使用示例
const map = new MyMap();
map.set('name', '张三');
map.set(1, '数字1');
map.set(NaN, 'NaN值');
console.log(map.get('name')); // 张三
console.log(map.size); // 3
console.log(map.has(NaN)); // true

for (const [key, value] of map) {
  console.log(key, value);
}

7. 数组

核心知识点

  • 数组是有序的元素集合,具有动态长度
  • 常用方法:push()、pop()、shift()、unshift()、slice()、splice() 等
  • 高阶函数:map()、filter()、reduce()、forEach()、find()、some()、every() 等
  • 数组去重、扁平化、排序是常见操作

面试题:数组扁平化

// 方法1:使用递归
function flatten(arr, depth = Infinity) {
  if (depth <= 0) return arr.slice();
  
  return arr.reduce((acc, item) => {
    if (Array.isArray(item)) {
      // 递归扁平化,并减少深度
      return acc.concat(flatten(item, depth - 1));
    } else {
      return acc.concat(item);
    }
  }, []);
}

// 方法2:使用ES6的flat方法
// const flattened = arr.flat(depth);

// 测试
const nestedArray = [1, [2, [3, [4]], 5]];
console.log(flatten(nestedArray)); // [1, 2, 3, 4, 5]
console.log(flatten(nestedArray, 1)); // [1, 2, [3, [4]], 5]

面试题:数组去重

// 方法1:使用Set
function unique1(arr) {
  return [...new Set(arr)];
}

// 方法2:使用filter和indexOf
function unique2(arr) {
  return arr.filter((item, index) => {
    return arr.indexOf(item) === index;
  });
}

// 方法3:使用对象键值对
function unique3(arr) {
  const obj = {};
  return arr.filter(item => {
    // 处理不同类型但值相同的情况,如1和'1'
    const key = typeof item + item;
    if (!obj[key]) {
      obj[key] = true;
      return true;
    }
    return false;
  });
}

// 方法4:使用Map
function unique4(arr) {
  const map = new Map();
  return arr.filter(item => {
    if (!map.has(item)) {
      map.set(item, true);
      return true;
    }
    return false;
  });
}

// 测试
const testArray = [1, 2, 2, '3', '3', true, true, false, false, null, null, undefined, undefined, NaN, NaN];
console.log(unique1(testArray)); // Set方法能正确去重NaN
console.log(unique2(testArray)); // indexOf无法识别NaN,会保留重复的NaN
console.log(unique3(testArray)); // 能区分不同类型的值
console.log(unique4(testArray)); // Map方法也能正确去重NaN

以上梳理了前端核心知识点及常见面试题,涵盖了Promise、原型链、生成器、闭包、异步、事件循环、Map和数组等内容。这些知识点不仅是面试高频考点,也是日常开发中经常用到的核心概念,掌握这些内容对于前端工程师至关重要。

前端对象与字符串知识点梳理及面试题详解

一、对象(Object)

核心知识点

  • 对象是键值对的集合,键可以是字符串或Symbol,值可以是任意类型
  • 对象的创建方式:对象字面量{}new Object()、构造函数、Object.create()
  • 对象属性的访问方式:点语法(obj.key)和方括号语法(obj['key']
  • 可枚举属性与不可枚举属性:可枚举属性会被for...in遍历到
  • 对象的特性:writable(可写)、enumerable(可枚举)、configurable(可配置)
  • 常见方法:Object.keys()Object.values()Object.entries()Object.assign()

面试题:实现对象的深拷贝

function deepClone(obj, hash = new WeakMap()) {
  // 处理null和基本类型
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 处理日期
  if (obj instanceof Date) {
    return new Date(obj);
  }
  
  // 处理正则
  if (obj instanceof RegExp) {
    return new RegExp(obj.source, obj.flags);
  }
  
  // 处理循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  
  // 区分数组和对象
  const cloneObj = Array.isArray(obj) ? [] : {};
  hash.set(obj, cloneObj);
  
  // 遍历属性(包括Symbol键)
  Reflect.ownKeys(obj).forEach(key => {
    cloneObj[key] = deepClone(obj[key], hash);
  });
  
  return cloneObj;
}

// 测试
const obj = {
  a: 1,
  b: { c: 2 },
  d: [3, 4],
  e: new Date(),
  f: /test/,
  [Symbol('g')]: 5
};
obj.self = obj; // 循环引用

const cloned = deepClone(obj);
console.log(cloned);

面试题:实现对象的扁平化解构

function flattenObject(obj, prefix = '', result = {}) {
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key];
      const newKey = prefix ? `${prefix}.${key}` : key;
      
      if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
        // 递归处理嵌套对象
        flattenObject(value, newKey, result);
      } else {
        result[newKey] = value;
      }
    }
  }
  return result;
}

// 测试
const nestedObj = {
  a: 1,
  b: {
    c: 2,
    d: {
      e: 3,
      f: 4
    }
  },
  g: 5
};

console.log(flattenObject(nestedObj));
// 输出: { a: 1, 'b.c': 2, 'b.d.e': 3, 'b.d.f': 4, g: 5 }

面试题:实现对象的属性拦截(类似Vue2的响应式原理)

function observe(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return;
  }
  
  // 遍历对象属性
  Object.keys(obj).forEach(key => {
    let value = obj[key];
    
    // 递归处理嵌套对象
    observe(value);
    
    // 重定义属性
    Object.defineProperty(obj, key, {
      get() {
        console.log(`获取属性${key}的值: ${value}`);
        return value;
      },
      set(newValue) {
        console.log(`设置属性${key}的值: ${newValue}`);
        // 处理新值为对象的情况
        observe(newValue);
        value = newValue;
      }
    });
  });
}

// 测试
const data = {
  name: '张三',
  age: 20,
  address: {
    city: '北京'
  }
};

observe(data);
data.name; // 触发get
data.age = 21; // 触发set
data.address.city = '上海'; // 触发get和set

二、字符串(String)

核心知识点

  • 字符串是字符的有序序列,在JavaScript中是不可变的
  • 常见创建方式:字符串字面量(''"")、模板字符串(` `)、new String()
  • 常用属性:length(长度)
  • 常用方法:
    • 查找:indexOf()lastIndexOf()includes()startsWith()endsWith()
    • 截取:slice()substring()substr()
    • 转换:toUpperCase()toLowerCase()trim()
    • 其他:split()replace()charAt()concat()
  • 模板字符串支持多行文本和变量插值(${}

面试题:实现字符串反转

// 方法1:使用数组方法
function reverseString1(str) {
  return str.split('').reverse().join('');
}

// 方法2:使用for循环
function reverseString2(str) {
  let result = '';
  for (let i = str.length - 1; i >= 0; i--) {
    result += str[i];
  }
  return result;
}

// 方法3:使用递归
function reverseString3(str) {
  if (str === '') {
    return '';
  } else {
    return reverseString3(str.substr(1)) + str.charAt(0);
  }
}

// 测试
console.log(reverseString1('hello')); // 'olleh'
console.log(reverseString2('world')); // 'dlrow'
console.log(reverseString3('test'));  // 'tset'

面试题:实现字符串中的单词反转(不改变单词顺序)

function reverseWords(str) {
  // 分割单词(处理多个空格的情况)
  const words = str.split(/\s+/);
  
  // 反转每个单词
  const reversedWords = words.map(word => {
    return word.split('').reverse().join('');
  });
  
  // 拼接回字符串
  return reversedWords.join(' ');
}

// 测试
console.log(reverseWords('Hello World')); // 'olleH dlroW'
console.log(reverseWords('I love JavaScript')); // 'I evol tpircSavaJ'
console.log(reverseWords('   Hello   there   ')); // '   olleH   ereht   '

面试题:实现千位分隔符格式化数字

function formatNumber(num) {
  // 处理非数字情况
  if (typeof num !== 'number' || isNaN(num)) {
    return '0';
  }
  
  // 转换为字符串并分割整数和小数部分
  const parts = num.toString().split('.');
  let integerPart = parts[0];
  const decimalPart = parts[1] || '';
  
  // 处理负数
  let sign = '';
  if (integerPart[0] === '-') {
    sign = '-';
    integerPart = integerPart.slice(1);
  }
  
  // 反转字符串便于处理
  let reversed = integerPart.split('').reverse().join('');
  let formatted = '';
  
  // 每三位添加一个逗号
  for (let i = 0; i < reversed.length; i++) {
    if (i !== 0 && i % 3 === 0) {
      formatted += ',';
    }
    formatted += reversed[i];
  }
  
  // 反转回来并拼接符号和小数部分
  formatted = sign + formatted.split('').reverse().join('');
  
  return decimalPart ? `${formatted}.${decimalPart}` : formatted;
}

// 测试
console.log(formatNumber(1234567)); // '1,234,567'
console.log(formatNumber(1234567.89)); // '1,234,567.89'
console.log(formatNumber(-123456)); // '-123,456'
console.log(formatNumber(0)); // '0'

面试题:实现字符串匹配算法(KMP算法)

// 构建部分匹配表
function buildLPS(pattern) {
  const lps = new Array(pattern.length).fill(0);
  let len = 0; // 最长前缀后缀的长度
  
  for (let i = 1; i < pattern.length; ) {
    if (pattern[i] === pattern[len]) {
      len++;
      lps[i] = len;
      i++;
    } else {
      if (len !== 0) {
        len = lps[len - 1];
      } else {
        lps[i] = 0;
        i++;
      }
    }
  }
  
  return lps;
}

// KMP匹配算法
function kmpSearch(text, pattern) {
  const n = text.length;
  const m = pattern.length;
  
  if (m === 0) return 0; // 空模式匹配开头
  
  const lps = buildLPS(pattern);
  let i = 0; // text的索引
  let j = 0; // pattern的索引
  
  while (i < n) {
    if (pattern[j] === text[i]) {
      i++;
      j++;
      
      if (j === m) {
        return i - j; // 找到匹配,返回起始索引
      }
    } else {
      if (j !== 0) {
        j = lps[j - 1];
      } else {
        i++;
      }
    }
  }
  
  return -1; // 未找到匹配
}

// 测试
console.log(kmpSearch('ABC ABCDAB ABCDABCDABDE', 'ABCDABD')); // 15
console.log(kmpSearch('hello world', 'world')); // 6
console.log(kmpSearch('test string', 'xyz')); // -1

对象和字符串是JavaScript中最基础也是最常用的数据类型,掌握它们的特性和操作方法对于前端开发至关重要。上述知识点和面试题覆盖了常见的考点和实际开发需求,深入理解这些内容有助于提升代码质量和解决问题的能力。


以下是对前端核心知识点(Promise、原型链、闭包、异步、事件循环、Map、数组)的系统梳理及高频手写题目详解,结合面试考点和实现细节,助你高效备战。


一、核心知识梳理与面试详解

⚡ 1. Promise与异步编程
  • 核心机制
    • 三种状态Pending(等待)、Fulfilled(成功)、Rejected(失败),状态不可逆。
    • 链式调用.then()处理成功状态,.catch()捕获失败,.finally()无论成败均执行。
  • 手写Promise.all
    function myPromiseAll(promises) {
      return new Promise((resolve, reject) => {
        // 校验输入是否为可迭代对象
        if (typeof promises?.[Symbol.iterator] !== 'function') {
          reject(new TypeError('Argument is not iterable'));
          return;
        }
        const promiseArray = Array.from(promises);
        const results = new Array(promiseArray.length);
        let completedCount = 0;
        // 空数组直接返回
        if (promiseArray.length === 0) resolve(results);
        promiseArray.forEach((p, i) => {
          Promise.resolve(p)  // 包装非Promise值
            .then(res => {
              results[i] = res;  // 按索引存储结果
              if (++completedCount === promiseArray.length) resolve(results);
            })
            .catch(err => reject(err));  // 任一失败立即终止
        });
      });
    }
    
    得分点
    • 校验可迭代对象、处理空数组、非Promise包装。
    • 结果顺序与输入一致(避免push导致错乱)。

🔗 2. 原型链与继承
  • 核心概念
    • 原型(Prototype):构造函数(如Array)的prototype属性,存放共享方法(如Array.prototype.map)。
    • 原型链:对象通过__proto__向上查找属性,终点为Object.prototype.__proto__ === null
  • 继承实现
    // 寄生组合继承(最优解)
    function Parent(name) { this.name = name; }
    Parent.prototype.say = function() { console.log(this.name); };
    function Child(name, age) {
      Parent.call(this, name);  // 继承实例属性
      this.age = age;
    }
    Child.prototype = Object.create(Parent.prototype); // 继承方法
    Child.prototype.constructor = Child;  // 修复构造函数指向
    
    面试重点
    • 避免组合继承的两次调用父类构造函数问题。
    • ES6的class本质是语法糖(如super()调用父类构造)。

🔒 3. 闭包与生成器
  • 闭包(Closure)
    • 原理:函数嵌套,内层函数访问外层作用域的变量(即使外层已销毁)。
    • 应用
      // 自增ID生成器
      function createIdGenerator(init = 0) {
        let id = init;
        return () => ++id;  // 闭包保存id状态
      }
      const gen = createIdGenerator();
      gen(); // 1, gen(); // 2
      
      优势:状态隔离(多个生成器互不干扰)。
  • 生成器(Generator)
    • 特性function*定义,yield暂停执行,next()恢复执行。
    • 异步应用:配合co库实现类似async/await的异步控制(已逐渐被替代)。

🔁 4. 事件循环(Event Loop)
  • 执行顺序
    1. 同步代码 → 微任务(Promise.thenMutationObserver) → 宏任务(setTimeoutDOM事件)。
  • 经典面试题
    console.log('1');
    setTimeout(() => console.log('2'), 0);
    Promise.resolve().then(() => console.log('3'));
    console.log('4');
    // 输出:1 → 4 → 3 → 2
    
    原理:微任务优先级高于宏任务,同步代码执行完毕后清空微任务队列。

🗺️ 5. Map与数组高频操作
  • Map vs. Object

    • Map优势:键可为任意类型(对象、函数)、有序迭代、性能更优(频繁增删场景)。
    • APIset(), get(), has(), delete()
  • 数组重点方法

    方法 用途 是否改变原数组
    map() 映射新数组
    filter() 过滤满足条件的元素
    reduce() 累计计算(如求和、扁平化)
    splice() 删除/插入元素
    slice() 截取子数组

    手写数组扁平化

    function flatten(arr) {
      return arr.reduce((acc, cur) => 
        acc.concat(Array.isArray(cur) ? flatten(cur) : cur), []);
    }
    flatten([1, [2, [3]]]); // [1, 2, 3]
    

二、高频手撕题目详解

1. 防抖(Debounce)与节流(Throttle)
  • 防抖:连续触发时只执行最后一次:
    function debounce(fn, delay) {
      let timer;
      return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
      };
    }
    // 场景:搜索框输入联想(停止输入300ms后请求)
    
  • 节流:固定间隔执行一次:
    function throttle(fn, interval) {
      let lastTime = 0;
      return (...args) => {
        const now = Date.now();
        if (now - lastTime >= interval) {
          fn.apply(this, args);
          lastTime = now;
        }
      };
    }
    // 场景:滚动事件(每200ms计算位置)
    
2. 手写深拷贝
function deepClone(obj, map = new Map()) {
  if (typeof obj !== 'object' || obj === null) return obj;
  if (map.has(obj)) return map.get(obj); // 解决循环引用
  const clone = Array.isArray(obj) ? [] : {};
  map.set(obj, clone);
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], map);
    }
  }
  return clone;
}

边界处理:循环引用(Map记录已拷贝对象)、函数和正则等特殊类型。


三、面试策略与复习建议

  1. 手写题要点
    • 先口述思路(如防抖的“清除定时器+延迟执行”),再写代码。
    • 处理边界:空输入、非预期类型(如Promise.all校验可迭代对象)。
  2. 框架结合
    • React:闭包在useCallback中的应用(依赖项变化才更新函数)。
    • Vue:响应式原理依赖Object.definePropertygetter/setter(类闭包状态保存)。
  3. 性能优化
    • 减少闭包滥用(避免内存泄漏),如事件监听后及时移除。
    • 数组操作优先map/filter(返回新数组,避免forEach副作用)。

本文完整题目及扩展解析参考:https://blog.csdn.net/weixin_48377020/article/details/132241816 | https://blog.csdn.net/T_iAn_T/article/details/145597660。


在 JavaScript 中,对象(引用类型)和字符串(基本类型)的拷贝机制存在本质差异,这直接影响开发中的数据处理逻辑。以下是关键知识点梳理和对比:


📦 一、对象的拷贝

1. 浅拷贝(Shallow Copy)
  • 特点:仅复制对象的第一层属性。嵌套对象仍共享内存地址。
  • 实现方式
    • Object.assign({}, obj)
    • 展开运算符 { ...obj }
    • 数组方法如 slice()concat()
  • 示例
    const obj = { a: 1, b: { c: 2 } };
    const shallowCopy = { ...obj };
    shallowCopy.b.c = 3; // 修改嵌套属性
    console.log(obj.b.c); // 3(原对象被影响)
    
2. 深拷贝(Deep Copy)
  • 特点:递归复制所有层级,新旧对象完全独立。
  • 实现方式
    • JSON.parse(JSON.stringify(obj))
      • ✅ 简单快捷
      • ❌ 无法处理函数、undefinedSymbolDate(转为字符串)、循环引用
    • 递归实现
      function deepClone(obj, hash = new WeakMap()) {
        if (typeof obj !== 'object' || obj === null) return obj;
        if (hash.has(obj)) return hash.get(obj); // 解决循环引用
        const clone = Array.isArray(obj) ? [] : {};
        hash.set(obj, clone);
        for (const key in obj) {
          if (obj.hasOwnProperty(key)) {
            clone[key] = deepClone(obj[key], hash);
          }
        }
        return clone;
      }
      
    • 特殊类型处理
      • Datenew Date(obj.getTime())
      • RegExpnew RegExp(obj)
      • Map/Set → 递归复制元素
    • 现代 APIstructuredClone()
      • ✅ 支持循环引用、DateMapSet
      • ❌ 不支持函数、DOM 节点
    • 第三方库_.cloneDeep(obj)(Lodash)

🔤 二、字符串的拷贝

  • 字符串是基本类型(Primitive Type),赋值时直接复制值,而非引用地址。
  • 示例
    const str1 = "hello";
    const str2 = str1; // 复制值
    str2 = "world";    // 修改不影响 str1
    console.log(str1); // "hello"
    
  • 特点
    • 无需深拷贝/浅拷贝概念,每次赋值均创建独立副本。
    • 操作(如拼接、切片)均返回新字符串,原字符串不变。

⚠️ 三、关键差异总结

特性 对象(引用类型) 字符串(基本类型)
拷贝机制 赋值传递引用地址 赋值直接复制值
修改影响 浅拷贝时嵌套属性互相影响 修改后原数据不变
深拷贝需求 必需(需递归处理嵌套引用) 无需
内存占用 浅拷贝节省内存,深拷贝开销大 每次修改均创建新副本

💡 四、实际应用建议

  1. 对象拷贝场景
    • 简单数据且无特殊类型 → JSON.parse(JSON.stringify())
    • 复杂对象(含循环引用、Map等)→ structuredClone() 或 Lodash 的 _.cloneDeep()
  2. 字符串操作
    • 直接赋值或使用 slice()substring() 等返回新字符串的方法。
  3. 性能优化
    • 避免对大对象频繁深拷贝,改用 不可变数据(Immutable.js)按需拷贝

完整实现代码及边界案例可参考:https://blog.csdn.net/qq_53353440/article/details/148548048、https://developer.mozilla.org/en-US/docs/Web/API/structuredClone。


网站公告

今日签到

点亮在社区的每一天
去签到