【JS之闭包防抖节流,this指向,原型&原型链,数据类型,深浅拷贝】简单梳理啦!

一、闭包(前端最核心:作用域 + 执行上下文 + 垃圾回收 三合一)

**闭包是 JS 词法作用域 + 函数一等公民 + 垃圾回收机制 共同作用产生的自然现象,而非人为语法。**满足两点即形成闭包:

  1. 内层函数引用了外层函数作用域的变量 / 函数
  2. 内层函数在外层函数执行上下文销毁后,仍被外部作用域持有

本质:外层函数的 AO (活动对象) 无法被 GC 回收,被内层函数持久持有

作用:

  • 数据私有化:外部无法直接篡改,只能通过暴露方法操作(模块化基础)
  • 变量持久化:局部变量不随函数执行完销毁(防抖节流、计数器核心)
  • 保留执行现场:异步回调、柯里化、循环绑定的核心解决方案

经典延伸:循环绑定问题(闭包必考题)

javascript 复制代码
// 错误:var 无块级作用域,i 被共享
for(var i=0; i<5; i++){
  setTimeout(()=>console.log(i), 0) // 输出 5 个 5
}

// 正确:闭包制造独立作用域,保存每轮 i
for(var i=0; i<5; i++){
  (function(j){
    setTimeout(()=>console.log(j),0)
  })(i)
}

// ES6 替代:let 自带块级作用域,本质也是闭包
for(let i=0; i<5; i++){
  setTimeout(()=>console.log(i),0)
}

第二段代码利用 IIFE 立即执行函数 ,在每轮循环都创建一个独立的函数作用域 ,把当前的 i 以参数 j 的形式传进去并保存。由于 setTimeout 回调形成闭包 ,每个作用域里的 j 都会被持久保留,不会被后续循环覆盖,因此能正确输出每一轮的循环变量。

Q:为什么 IIFE 能解决 for 循环 var 异步问题?

A :因为 var 没有块级作用域,循环共用一个变量。使用立即执行函数(IIFE)可以每轮创建独立函数作用域 ,通过闭包保存当前轮的变量副本,让每个异步回调都能拿到正确的值,而不是最终值。

关联拓展

  • 闭包 → 防抖节流(保存定时器 / 时间戳)
  • 闭包 → 内存泄漏(长期持有引用)
  • 闭包 → this 丢失(普通函数闭包内 this 指向变更)
  • 闭包 → 模块化(早期 IIFE 模块化 = 闭包)

二、防抖 & 节流(闭包最经典工程化应用)

区别:

  • 防抖 (debounce) :频繁触发 → 只执行最后一次(等待安静期)场景:输入搜索、窗口 resize、表单验证
  • 节流 (throttle) :频繁触发 → 固定周期只执行一次 (控制频率)场景:滚动加载、鼠标移动、高频点击。节流=冷却机制=闭包+高频事件性能优化

原理:用闭包持久化私有变量(timer /lastTime),不污染全局,实现状态保留。

防抖:

javascript 复制代码
function debounce(fn, delay, immediate = false) {
  let timer = null // 闭包私有变量
  return function(...args) {
    timer && clearTimeout(timer)
    // 立即执行
    if(immediate && !timer) fn.apply(this, args)
    timer = setTimeout(() => {
      // 修复 this 指向 & 参数透传
      !immediate && fn.apply(this, args)
      timer = null
    }, delay)
  }
}

解析版:

javascript 复制代码
// 防抖函数:频繁触发,只执行最后一次(支持立即执行)
// fn:要防抖的目标函数  delay:延迟时间  immediate:是否立即执行(默认false)
function debounce(fn, delay, immediate = false) {
  // 1. 闭包私有变量:保存定时器ID,不会被销毁,也不污染全局
  let timer = null 

  // 2. 返回一个新函数(高阶函数 + 闭包核心)
  // ...args 接收调用时的所有参数(透传给原函数fn)
  return function(...args) {
    // 3. 核心:每次触发,先清除上一次的定时器
    // 只要在delay内再次触发,就重新计时,保证只执行最后一次
    timer && clearTimeout(timer)

    // 4. 立即执行逻辑:immediate为true 且 当前没有定时器(第一次触发)
    if (immediate && !timer) {
      // 用 apply 绑定 this修复this指向、透传参数(...args 接收事件对象 / 自定义参数,通过 apply 传给原函数,保证参数不丢失),执行原函数
      fn.apply(this, args)
    }

    // 5. 设置新定时器,延迟delay毫秒后执行
    timer = setTimeout(() => {
      // 6. 非立即执行模式:延迟后执行原函数
      if (!immediate) {
        fn.apply(this, args)
      }
      // 7. 执行完重置timer为null,释放引用(方便垃圾回收+立即执行下次判断)
      timer = null
    }, delay)
  }
}
javascript 复制代码
// 1)不传第三个参数 → 默认 false → 普通防抖(停了才执行)
debounce(fn, 500)

// 2)第三个参数写 true → 立即执行模式
debounce(fn, 500, true)
防抖函数的「实战核心步骤」:
  1. 定义要防抖的原函数(比如发请求、改样式、提交表单);
  2. debounce() 包装原函数,得到防抖后的新函数
  3. 把新函数绑定到「频繁触发的场景」(输入框、按钮点击、窗口 resize)。

例如:需求:用户在输入框打字时,频繁触发搜索逻辑 → 防抖后停止输入 500ms 才执行搜索(避免频繁发请求)。

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>输入框防抖示例</title>
</head>
<body>
  <input type="text" id="searchInput" placeholder="输入关键词搜索...">
  <p id="result"></p>

  <script>
    // 1. 定义要防抖的原函数(比如发请求、渲染结果)
    function search(keyword) {
      console.log('发送搜索请求:', keyword);
      document.getElementById('result').innerText = `搜索结果:${keyword}`;
    }

    // 2. 防抖函数
    function debounce(fn, delay, immediate = false) {
      let timer = null;
      return function(...args) {
        timer && clearTimeout(timer);
        if (immediate && !timer) {
          fn.apply(this, args);
        }
        timer = setTimeout(() => {
          if (!immediate) {
            fn.apply(this, args);
          }
          timer = null;
        }, delay);
      }
    }

    // 3. 用防抖包装原函数,得到防抖后的函数(延迟 500ms)
    const debouncedSearch = debounce(search, 500);

    // 4. 绑定到输入框的 input 事件
    const input = document.getElementById('searchInput');
    input.addEventListener('input', function(e) {
      // 把输入框的值传给防抖后的函数(参数透传)
      debouncedSearch(e.target.value);
    });
  </script>
</body>
</html>

运行效果(核心验证防抖)

  • 不防抖 :每打一个字,立刻执行 search()(比如打 "前端" 会执行 2 次);
  • 防抖后 :快速打字时完全不执行,停手 500ms 后只执行 1 次(不管打多少字,只搜最后一次输入的内容)。

节流(时间戳版)

javascript 复制代码
function throttle(fn, interval) {
  let lastTime = 0
  return function(...args) {
    const now = Date.now()
    if(now - lastTime >= interval) {
      fn.apply(this, args)
      lastTime = now
    }
  }
}

详解:

javascript 复制代码
// 节流:控制函数在一定时间内【最多执行一次】
// fn:要节流的函数
// interval:间隔时间(多少毫秒执行一次)
function throttle(fn, interval) {
  // 1. 闭包私有变量:保存【上一次执行的时间戳】
  // 作用:记录上次什么时候执行过,不会被重置
  let lastTime = 0

  // 2. 返回一个新函数(闭包 + 高阶函数)
  return function(...args) {
    // 3. 获取【当前时间戳】
    const now = Date.now()

    // 4. 核心判断(闭包:保存上次执行时间,实现冷却机制):
    // 当前时间 - 上次执行时间 >= 规定的间隔时间
    // → 说明冷却时间到了,可以执行了
    if (now - lastTime >= interval) {
      // 5. 执行原函数,修正 this,透传参数
      fn.apply(this, args)

      // 6. 更新【上次执行时间】为当前时间
      lastTime = now
    }
  }
}
案例 1:页面滚动加载更多(最经典)

场景:用户下拉页面滚动到底部,加载下一页数据scroll 事件触发超级频繁,不节流会疯狂发请求,导致接口报错、页面卡顿。

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>节流:滚动加载更多</title>
  <style>
    body { height: 2000px; }
    .tips { position: fixed; top: 10px; left: 10px; }
  </style>
</head>
<body>
  <div class="tips">滚动页面,查看加载逻辑</div>

  <script>
    // 1. 节流函数
    function throttle(fn, interval) {
      let lastTime = 0
      return function(...args) {
        const now = Date.now()
        if (now - lastTime >= interval) {
          fn.apply(this, args)
          lastTime = now
        }
      }
    }

    // 2. 真正要执行的:加载数据逻辑
    function loadMoreData() {
      // 滚动到底部时加载
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
      const windowHeight = window.innerHeight
      const documentHeight = document.documentElement.scrollHeight

      if (scrollTop + windowHeight >= documentHeight - 50) {
        console.log('✅ 触发加载:请求第2页数据...')
      }
    }

    // 3. 包装成节流函数:500ms 内最多执行一次
    const throttleLoad = throttle(loadMoreData, 500)

    // 4. 绑定到 scroll 事件
    window.addEventListener('scroll', throttleLoad)
  </script>
</body>
</html>

效果:

  • 不节流:滚动时每秒执行几十次,控制台疯狂打印
  • 加节流:500ms 只执行一次,平稳不卡顿
案例 2:鼠标移动(mousemove)实时显示位置

场景:鼠标在页面移动时,显示当前坐标。mousemove最高频事件之一,必须节流。

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>节流:鼠标移动</title>
</head>
<body>
  <h3>鼠标坐标:<span id="pos"></span></h3>

  <script>
    // 节流函数
    function throttle(fn, interval) {
      let lastTime = 0
      return function(...args) {
        const now = Date.now()
        if (now - lastTime >= interval) {
          fn.apply(this, args)
          lastTime = now
        }
      }
    }

    // 更新坐标
    function showMousePos(e) {
      document.getElementById('pos').innerText = `x:${e.clientX} y:${e.clientY}`
    }

    // 节流:200ms 更新一次
    const throttleShow = throttle(showMousePos, 200)

    window.addEventListener('mousemove', throttleShow)
  </script>
</body>
</html>

效果:

  • 不节流:坐标疯狂闪烁
  • 节流后:平滑更新,性能极高
案例 3:窗口大小改变 resize 节流

场景:拖动浏览器窗口改变大小时,重新计算页面布局。不节流会疯狂执行布局计算,导致页面卡顿。

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>节流:窗口resize</title>
</head>
<body>
  <h3>窗口宽度:<span id="width"></span>px</h3>

  <script>
    // 节流函数
    function throttle(fn, interval) {
      let lastTime = 0
      return function(...args) {
        const now = Date.now()
        if (now - lastTime >= interval) {
          fn.apply(this, args)
          lastTime = now
        }
      }
    }

    // 更新窗口宽度
    function updateWindowWidth() {
      document.getElementById('width').innerText = window.innerWidth
    }

    // 300ms 执行一次
    const throttleUpdate = throttle(updateWindowWidth, 300)

    window.addEventListener('resize', throttleUpdate)
  </script>
</body>
</html>

节流(定时器版):

javascript 复制代码
// 定时器版节流
function throttle(fn, interval) {
  // 1. 闭包变量:保存定时器 ID
  let timer = null

  // 2. 返回一个新的包装函数
  return function (...args) {
    // 3. 如果定时器还在(冷却中),直接 return,不执行
    if (timer) return

    // 4. 不在冷却 → 设置定时器
    timer = setTimeout(() => {
      // 5. 延迟 interval 后执行函数
      fn.apply(this, args)

      // 6. 执行完 → 清空 timer,解除锁定
      timer = null
    }, interval)
  }
}

延伸考点:

  • 防抖节流必须用 apply 修正 this,否则 this 指向 window
  • 立即执行防抖节流收尾是高频加分点
  • 本质:闭包 + 定时器 / 时间戳 + this 显式绑定

补充:call、apply、bind:

方法 是否立即执行 参数形式 返回值
call 立即执行 参数列表逐个传 函数执行结果
apply 立即执行 参数放在数组 函数执行结果
bind 不执行 参数列表逐个传 绑定 this 后的新函数

三、内存泄漏(从 GC 原理理解,不是 "闭包原罪")

底层原理:JS 采用 可达性 GC 算法 :引擎从根节点(window/global)遍历,不可达对象 被回收;长期可达但无用的对象 → 内存泄漏。

1. 高频泄漏场景

  1. 闭包滥用:全局变量长期持有闭包引用,AO 对象无法回收
  2. 意外全局变量:未声明变量自动挂载 window
  3. DOM 引用未清理:删除 DOM 后,JS 仍持有节点引用
  4. 定时器 / 回调未清除:setInterval、事件监听未移除
  5. 控制台打印:console.log 会阻止对象回收

3. 闭包与泄漏的正确关系

闭包本身不会泄漏。 闭包导致泄漏的唯一原因:闭包引用被全局长期持有,且不再使用却未释放。解决方案:手动断开引用 fn = null。

4. 延伸:如何排查

Chrome DevTools → Memory 面板:

  • 快照对比:找异常增长对象
  • allocation instrumentation:定位泄漏代码位置

四、this 指向(JS最难:4 种绑定规则 + 优先级 + 箭头函数)

本质:this是执行上下文的属性,在函数调用时动态绑定,而非定义时确定(箭头函数例外:继承外层作用域 this)。

四大绑定规则(优先级从高→低)

  1. new 绑定 → 指向实例对象
  2. 显式绑定 → call /apply/bind 强行指定
  3. 隐式绑定 → 对象。方法 (),指向调用对象
  4. 默认绑定 → 独立函数调用,指向 window /undefined(严格模式)

高频坑点(闭包 + 异步 + 原型中 this)

① 闭包 / 回调中的 this 丢失
javascript 复制代码
const obj = {
  name: 'test',
  fn() {
    setTimeout(function(){
      console.log(this.name) // undefined → 默认绑定
    })
  }
}

解决:

  • 缓存 this:const that = this
  • 箭头函数:继承外层 this(无自身 this)
② 箭头函数 this
  • 无 prototype、无 arguments、无 this
  • this 继承外层词法作用域的 this
  • 不能用作构造函数,不能用 call/apply/bind 改 this

关联拓展

  • this + 原型:原型方法 this 指向调用实例
  • this + 异步:回调 / Promise/async 中 this 遵循绑定规则
  • this + 防抖节流:必须用 apply 修正 this

五、原型 & 原型链(JS 面向对象底层基石)

底层本质:

  1. 函数才有 prototype:是函数的原型对象,用于共享方法
  2. 对象才有 proto:指向构造函数的 prototype,构成原型链
  3. 原型链尽头是 nullObject.prototype.__proto__ === null

原型链查找机制

访问对象属性 / 方法时:

  1. 先找自身
  2. 找不到 → 沿 __proto__原型对象
  3. 一直找到 Object.prototype
  4. 仍无 → 返回 undefined

作用:

  • 方法共享:所有实例共用原型方法,不占内存
  • 实现继承 :JS 基于原型链实现继承,而非类继承
  • 属性覆盖:自身属性优先级 > 原型属性

拓展:

① instanceof 原理

检测构造函数的 prototype 是否出现在实例的原型链上。

javascript 复制代码
function myInstanceof(obj, Constructor) {
  let proto = obj.__proto__
  while(proto) {
    if(proto === Constructor.prototype) return true
    proto = proto.__proto__
  }
  return false
}

对于上述循环的详解:

javascript 复制代码
第 1 步:proto = arr.__proto__
→ 拿到 Array.prototype
第 2 步:proto = proto.__proto__
→ 拿到 Array.prototype.__proto__,也就是 Object.prototype
第 3 步:proto = proto.__proto__
→ 拿到 null,循环结束

② 原型中的 this

原型方法里的 this → 指向调用该方法的实例,和普通函数规则一致。

关联拓展

  • 原型 + this:实例方法 this 指向
  • 原型 + 继承:原型链继承、组合继承、寄生组合继承
  • 原型 + 数据类型:Array/Object/Function 都是原型链结构

六、异步编程(从回调地狱到 ES 进化:面试必串讲)

1. 底层基础:Event Loop(JS 非阻塞核心)

JS 是单线程,靠事件循环实现异步:

  1. 执行同步代码 → 调用栈
  2. 遇到异步 → 放入 Web API
  3. 异步完成 → 推入任务队列
  4. 调用栈空 → 执行任务队列

宏任务 > 微任务 执行规则:

  • 每执行一个宏任务 ,清空所有微任务
  • 微任务:Promise.then、MutationObserver、queueMicrotask
  • 宏任务:script、setTimeout、setInterval、IO、UI 渲染

2. 异步进化史

  1. 回调函数:嵌套地狱,异常捕获难,信任问题
  2. Promise:解决回调地狱,状态不可逆(pending/fulfilled/rejected)
  3. Generator + co:协程,同步写法,使用复杂
  4. async/await:Promise 语法糖,同步写法,异步流程,终极方案

3. 核心要点

  • Promise 链式调用、catch 穿透、all/race/any/allSettled
  • async/await 错误处理:try/catch
  • 异步 + 闭包:保存异步现场(循环异步核心)
  • 异步 + this:Promise 回调 this 指向 window
Promise 链式调用原理及示例:
  1. 每执行一次 .then(),都会返回一个全新的 Promise
  2. then 内部的返回值:
    • 普通值 → 作为下一个 then 的参数
    • Promise → 等待该 Promise 执行完再传递
    • 抛出错误 → 进入 catch
  3. 链式调用本质串行执行异步任务,按顺序流转数据
javascript 复制代码
Promise.resolve(1)
  .then(res => {
    console.log(res) // 1
    return 2 // 普通值传递
  })
  .then(res => {
    console.log(res) // 2
    return Promise.resolve(3) // Promise传递
  })
  .then(res => {
    console.log(res) // 3
    throw new Error('出错') // 抛错
  })
  .catch(err => console.log(err)) // 捕获错误

Promise 的 then 方法会返回一个新 Promise,因此支持链式调用。then 内部的返回值会传递给下一个 then,如果抛出错误,就会进入后续的 catch 中,实现异步任务的串行执行。

Promise catch 穿透原理及示例:
  1. Promise 链中任意位置抛出错误 / 返回 reject
  2. 跳过中间所有 then,直接找到离它最近的 catch
  3. 这就是 catch 穿透,保证错误能被统一捕获
javascript 复制代码
Promise.resolve()
  .then(() => { throw new Error('报错') })
  .then(() => {}) // 被跳过
  .then(() => {}) // 被跳过
  .catch(err => {
    console.log('捕获到错误:', err) // 直接走到这
  })

catch 穿透是指 Promise 链式调用中,任何一个环节出现错误,都会跳过后续所有成功的 then 回调,直接被最近的 catch 捕获,实现错误的统一处理。

Promise 静态方法:all /race/any /allSettled:
方法 成功条件 失败条件 返回结果
Promise.all 全部成功 任意一个失败 成功结果数组 / 第一个失败原因
Promise.race 谁先完成就返回谁 谁先完成就返回谁(失败也算) 第一个完成的结果 / 原因
Promise.any 任意一个成功 全部失败 第一个成功结果 / 聚合失败
Promise.allSettled 全部执行完成 永不失败 所有结果(带状态)
javascript 复制代码
// 1. all:全部成功才成功
Promise.all([p1,p2,p3]).then().catch()

// 2. race:谁快返回谁
Promise.race([p1,p2,p3]).then().catch()

// 3. any:谁先成功返回谁
Promise.any([p1,p2,p3]).then().catch()

// 4. allSettled:全部执行完,不管成败
Promise.allSettled([p1,p2,p3]).then()
  • all:等待所有 Promise 成功,一个失败则整体失败;
  • race:返回最先执行完的结果,无论成功失败;
  • any:返回第一个成功的结果,全部失败才失败;
  • allSettled:等待所有执行完毕,返回所有结果的状态和值,永不失败。
async/await 错误处理:try/catch原理:
  1. async/await 是 Promise 语法糖,基于 Promise 实现
  2. await 后面的 Promise 变为 rejected 时,会抛出异常
  3. 必须用 try/catch 捕获,否则程序崩溃
  4. try 放正常逻辑,catch 捕获错误
javascript 复制代码
async function fn() {
  try {
    const res = await Promise.resolve('成功')
    console.log(res)
  } catch (err) {
    // 捕获 await 错误 / 手动抛错
    console.log('捕获异常:', err)
  }
}
异步 + 闭包:保存异步现场(循环异步核心)原理:
  1. 异步任务(定时器、Promise)会延迟执行
  2. var 无块级作用域,循环共用一个变量,异步执行时变量已被覆盖
  3. 闭包创建独立作用域,保存每一轮的变量现场
  4. 让异步回调拿到当前轮的正确值
javascript 复制代码
// 闭包解决循环异步问题
for(var i=0; i<5; i++){
  (function(j){
    setTimeout(()=>console.log(j),0)
  })(i)
}

异步+闭包最常用的经典案例是防抖节流,详述在上文。

异步 + this:Promise 回调 this 指向 window,原理:
  1. Promise 的 then/catch 回调是异步微任务
  2. 执行时处于全局执行上下文
  3. 普通函数的 this 遵循默认绑定 → 指向 window(严格模式 undefined)
  4. 不是指向调用时的对象
javascript 复制代码
const obj = {
  fn() {
    Promise.resolve().then(function() {
      console.log(this) // window
    })
  }
}
obj.fn()

Promise 的 then、catch 回调属于异步微任务,在全局执行上下文中执行,普通函数的 this 会走默认绑定指向 window。解决办法:可以用箭头函数继承外层 this 、**缓存this(**const that = this) 或 bind 绑定来修复 this 指向。

整体串联:

Promise 支持链式调用,因为 then 返回新 Promise,错误可通过 catch 穿透统一捕获;其静态方法 all、race、any、allSettled 分别用于批量异步的不同场景。async/await 是 Promise 语法糖,用 try/catch 处理错误。异步结合闭包可保存执行现场,解决循环异步变量共享问题;而 Promise 回调中的普通函数 this 会指向 window,可通过箭头函数等方式修复。

4.手写延伸:Promise 核心简化

javascript 复制代码
// 简化 Promise 状态流转
class MyPromise {
  constructor(executor) {
    this.state = 'pending'
    this.value = undefined
    const resolve = (val) => {
      if(this.state!=='pending') return
      this.state = 'fulfilled'
      this.value = val
    }
    executor(resolve)
  }
}

七、数据类型 & 类型判断(从堆 / 栈内存理解)

1. 两类数据类型(本质:存储方式不同)

① 原始类型(7 种)

String、Number、Boolean、Undefined、Null、Symbol、BigInt

  • 栈内存,值不可变,按值访问
  • 赋值是值拷贝,互不影响

② 引用类型

Object、Array、Function、Date、RegExp

  • 堆内存,栈存堆地址,按引用访问
  • 赋值是地址拷贝,指向同一对象

2. 类型判断

  1. typeof
    • 适用:原始类型(除 null)
    • 缺陷**:** typeof null === 'object'数组 / 对象都返回 object
  2. instanceof
    • 适用:引用类型,基于原型链
    • 缺陷:不能判断原始类型,跨 iframe 失效
  3. Object.prototype.toString.call()
    • 最准确[object Array]/[object Object]
    • 通用所有类型,面试首选
  4. Array.isArray()
    • 专用判断数组

3. 延伸:包装对象

原始类型没有方法,调用方法时 JS 自动创建临时包装对象,执行完销毁。

javascript 复制代码
'string'.length // 自动创建 String 对象,用完即焚

八、深浅拷贝(基于堆 / 栈内存 + 引用类型)

重点:只有引用类型才分深浅拷贝!!!

浅拷贝:只拷贝第一层,深层引用类型仍共享地址

深拷贝 :递归拷贝所有层级,完全独立,无共享地址

浅拷贝实现:

  • Object.assign()
  • 数组 slice()/concat()
  • 扩展运算符 {...obj}/[...arr]

深拷贝实现:

① JSON 法(简易但有缺陷)

javascript 复制代码
JSON.parse(JSON.stringify(obj))

缺陷:丢失 undefined、Symbol、函数;无法处理循环引用;丢失 Date、RegExp 等特殊对象。

② 手写深拷贝(面试必写,含循环引用 + 特殊类型)

javascript 复制代码
function deepClone(obj, map = new WeakMap()) {
  // 原始类型直接返回
  if(typeof obj !== 'object' || obj === null) return obj
  // 循环引用处理
  if(map.has(obj)) return map.get(obj)
  
  // 特殊对象处理
  let target
  const constructor = obj.constructor
  switch(constructor) {
    case Date: target = new constructor(obj); break
    case RegExp: target = new constructor(obj); break
    default: target = new constructor()
  }
  
  map.set(obj, target)
  // 递归拷贝
  for(let key in obj) {
    if(obj.hasOwnProperty(key)) {
      target[key] = deepClone(obj[key], map)
    }
  }
  return target
}

关联拓展:

  • 深浅拷贝 + 数据类型:原始类型无需拷贝,引用类型必须深拷贝
  • 深浅拷贝 + React/Vue:状态更新需深拷贝避免视图异常
  • 深浅拷贝 + 性能:浅拷贝性能远高于深拷贝

九、全链路串联(全局视野)

  1. 闭包是防抖节流、模块化、循环异步的基础,处理不当引发内存泄漏
  2. this 在闭包、回调、原型、异步中会动态变化,靠绑定 / 箭头函数修复
  3. 原型链是 JS 面向对象核心,支撑 instanceof、继承、方法共享
  4. 异步编程靠 Event Loop 运行,常结合闭包保存现场、this 修正指向
  5. 数据类型 分栈堆存储,决定了引用类型必须用深浅拷贝区分
  6. 所有知识点底层都围绕:执行上下文、作用域、垃圾回收、内存模型

好啦!吃饭!!!下课煲仔们!!!

相关推荐
ok_hahaha2 小时前
java从头开始-苍穹外卖day05-Redis及店铺营业状态设置
java·开发语言·redis
2501_933329552 小时前
舆情监测系统的技术演进:从数据采集到AI中台,Infoseek如何实现“监测+处置”一体化
开发语言·人工智能·自然语言处理·系统架构
kyriewen2 小时前
console.log 骗了我一整个通宵:原来它才是时间旅行者
前端·javascript·chrome
dgvri2 小时前
Windows上安装Go并配置环境变量(图文步骤)
开发语言·windows·golang
忆江南2 小时前
# iOS 动态库与静态库全面解析
前端
冴羽2 小时前
在浏览器控制台调试的 6 个秘密技巧
前端·javascript·chrome
青莲8432 小时前
查找算法详解
android·前端
前端Hardy2 小时前
别再手动调 Prompt 了!这款开源神器让 AI 输出质量提升 300%,支持 Claude、GPT、Gemini,还免费开源!
前端·javascript·面试
yuhaiqiang2 小时前
谈谈什么是多AI交叉论证思维
前端·后端·面试