在这里插入图片描述

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

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 都成功时才 resolveif (completedCount === total) {resolve(results);}},(reason) => {// 有一个 Promise 失败就立即 rejectreject(reason);});});});
};

面试题:实现 Promise 串行执行

// 实现一个函数,让多个Promise按顺序执行
function promiseSerial(tasks) {// 初始返回一个已 resolved 的 Promisereturn 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) {// 基本类型直接返回falseif (typeof left !== 'object' || left === null) return false;// 获取对象的原型let proto = Object.getPrototypeOf(left);// 遍历原型链while (true) {// 找到尽头仍未匹配,返回falseif (proto === null) return false;// 找到匹配的原型,返回trueif (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;}// 处理Promiseresult.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;}// 清空Mapclear() {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)); // truefor (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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/94541.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/94541.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/94541.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[自动化Adapt] 父子事件| 冗余过滤 | SQLite | SQLAlchemy | 会话工厂 | Alembic

第五章&#xff1a;事件处理与融合 欢迎回到OpenAdapt探索之旅~ 在第四章&#xff1a;系统配置中&#xff0c;我们掌握了如何定制化系统参数。更早的第一章&#xff1a;录制引擎则展示了系统如何捕获海量原始操作数据。 假设我们需要训练机器人输入"hello"一词。原…

组合期权:跨式策略

文章目录0.简介1.买入跨式组合&#xff08;Long Straddle&#xff09;1.1 适用场景​1.2 合约选择1.3 损益分析1.4 案例示范2.卖出跨式组合&#xff08;Short Straddle&#xff09;2.1 适用场景​2.2 合约选择2.3 损益分析2.4 案例示范3.小结参考文献0.简介 跨式策略是一种交易…

Vue计算属性详解2

可写计算属性 计算属性默认是只读的,但在特殊场景下,我们可以创建"可写"的计算属性,通过同时提供getter和setter实现: <script setup>import { ref, computed } from vueconst firstName = ref(John)const lastName = ref(Doe)const fullName = computed(…

UniStorm 5.3.0 + Unity2022 + URP配置说明

一、前言 以前我用的是UniStorm3.0&#xff0c;主要用在内置管线里面&#xff0c;最近想在URP管线里面使用UniStorm天气系统&#xff0c;于是弄了UniStorm5.3.0的包&#xff0c;在Unity2022.3的URP模式下配置&#xff0c;直接导入package&#xff0c;两次宣告失败。最后看了官方…

力扣经典算法篇-44-组合总和(回溯问题)

1、题干 给你一个无重复元素的整数数组candidates和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。…

矩阵与高斯消元:数学算法在计算机领域的应用

一、概述和基本概念 矩阵&#xff0c;类似于在 C 中我们看到的二维数组。它有两个维度&#xff0c;行和列。下面是一个典型的矩阵&#xff1a; M[12342345445610111213] M \begin{bmatrix} 1 & 2 & 3 & 4 \\ 2 & 3 & 4 & 5 \\ 4 & 4 & 5 &…

【补题】CodeTON Round 1 (Div. 1 + Div. 2, Rated, Prizes!) D. K-good

题意&#xff1a;给一个n&#xff0c;如果能被k个数整除&#xff0c;要求这k个数%k后不相同&#xff0c;问如果可以&#xff0c;任意k是多少&#xff0c;如果不可以输出-1 思路&#xff1a; D. K-good_牛客博客 从来没见过&#xff0c;太诡异了&#xff0c;做题做少了 1.…

LLM推理框架的“权力的游戏”:vLLM之后的群雄逐鹿

既然我们已经深入探讨了本地与云端的两大代表Ollama和vLLM&#xff0c;是时候将视野拓宽&#xff0c;检视一下在高性能推理这片“高手如云”的竞技场中&#xff0c;还有哪些重量级的玩家。vLLM的出现点燃了战火&#xff0c;但远非终点。 欢迎来到LLM推理框架的“后vLLM时代”—…

TDengine IDMP 背后的技术三问:目录、标准与情景

过去十年&#xff0c;#工业 和#物联网 场景经历了快速的#数字化 建设&#xff1a;传感器接入、系统联网、数据上云……数据平台已能轻松承载每秒千万级别的写入&#xff0c;每天几 TB 的存储量。但今天再回头看&#xff0c;这些看似“完成”的系统&#xff0c;实际上只解决了一…

MyBatis基础操作完整指南

文章目录MyBatis简介环境搭建Maven依赖数据库表结构核心配置MyBatis配置文件数据库配置文件实体类基础CRUD操作Mapper接口Mapper XML映射文件工具类测试类动态SQL常用标签高级特性一对一关联映射一对多关联映射分页查询使用注解方式MyBatis简介 MyBatis是Apache的一个开源项目…

go与grpc

目录下载与安装遇到的问题cmd中protoc找不到命令cmd中--go_out: protoc-gen-go: Plugin failed with status code 1.下载与安装 下载protoc&#xff1a; https://github.com/protocolbuffers/protobuf/releases 点击下载相应电脑版本即可&#xff0c;我是windows系统下载了pro…

2025年AI面试重构招聘新生态

当企业面临业务扩张与人才竞争的双重压力&#xff0c;传统招聘模式已难以满足高效、精准、公平的人才筛选需求。尤其在校招季、蓝领用工潮等关键节点&#xff0c;面试官超负荷运转、跨地域协调困难、评估标准模糊等问题频发。AI技术的深度介入正推动招聘行业从“经验驱动”向“…

Rust进阶-part5-trait

Rust进阶[part5]_trait trait概述 在 Rust 中,trait 是一种定义共享行为的方式。它类似于其他语言中的接口,允许我们定义一组方法签名,然后让不同的类型去实现这些方法。通过 trait,我们可以实现多态性,即不同类型可以以统一的方式处理。 普通实现 使用 trait 关键字来…

【人工智能-18】机器学习:决策树、随机森林

上一期【人工智能-17】机器学习&#xff1a;KNN算法、模型选择和调优、朴素贝叶斯分类 文章目录一、决策树1.使用理由2.技术二、随机森林1.使用理由2.原理核心&#xff1a;Bagging 随机特征子集3.优点和缺点一、决策树 决策树是一种监督学习算法&#xff0c;主要用于分类&…

RFID高频读写器在工业生产线的使用优势

在工业4.0浪潮下&#xff0c;智能制造对生产效率与精准度的要求日益提升。RFID技术凭借其独特的技术优势&#xff0c;成为工业场景中实现数据实时采集与流程优化的关键工具。本文主要从RFID高频读写器出发&#xff0c;系统解析其在工业生产线中的使用优势。RFID高频读写器一、技…

大模型学习笔记

prompt 提示词的构成&#xff1a; 指示&#xff1a;描述让它做什么上下文&#xff1a;给出与任务相关的背景信息输入&#xff1a; 任务的输入信息输出&#xff1a;输出的格式 生成与检索 生成&#xff1a; 优点&#xff1a;内容的多样性、创造性缺点&#xff1a;存在不可控制 检…

龙虎榜——20250806

上证指数继续收阳线&#xff0c;创新高的概率较大&#xff0c;个股上涨多于下跌&#xff0c;但板块轮动较明显&#xff0c;高位板块注意风险。深证指数较昨天放量收阳线&#xff0c;站上5日和10日均线继续上线&#xff0c;大科技方向资金关注更多。2025年8月6日龙虎榜行业方向分…

数据可视化发展历程

数据可视化是数据描述的图形表示&#xff0c;是当今数据分析发展最快速、最引人注目的领域之一。借助于可视化工具的发展&#xff0c;或朴实&#xff0c;或优雅&#xff0c;或绚烂的可视化作品给我们讲述着各种数据故事。在这个领域中&#xff0c;科学、技术和艺术完美地结合在…

深入理解C++中的stack、queue和priority_queue

目录 前言 1. stack&#xff08;栈&#xff09; 1.1 基本概念 1.2 常用接口 1.3 应用示例&#xff1a;最小栈 1.4 模拟实现 2. queue&#xff08;队列&#xff09; 2.1 基本概念 2.2 常用接口 2.3 模拟实现 3. priority_queue&#xff08;优先队列&#xff09; 3.1…

C++ 操作 Redis 客户端

引言 前面几篇文章都在介绍 Redis 原生命令行客户端&#xff0c;在实际应用开发中&#xff0c;开发人员更希望使用针对特定编程语言的专用客户端&#xff0c;通过编程的方式操作 Redis 数据库。因此&#xff0c;Redis 支持多种编程语言。本文将介绍 如何使用 C 语言来操作 Red…