一、闭包(前端最核心:作用域 + 执行上下文 + 垃圾回收 三合一)
**闭包是 JS 词法作用域 + 函数一等公民 + 垃圾回收机制 共同作用产生的自然现象,而非人为语法。**满足两点即形成闭包:
- 内层函数引用了外层函数作用域的变量 / 函数
- 内层函数在外层函数执行上下文销毁后,仍被外部作用域持有
本质:外层函数的 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)
防抖函数的「实战核心步骤」:
- 定义要防抖的原函数(比如发请求、改样式、提交表单);
- 用
debounce()包装原函数,得到防抖后的新函数; - 把新函数绑定到「频繁触发的场景」(输入框、按钮点击、窗口 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. 高频泄漏场景
- 闭包滥用:全局变量长期持有闭包引用,AO 对象无法回收
- 意外全局变量:未声明变量自动挂载 window
- DOM 引用未清理:删除 DOM 后,JS 仍持有节点引用
- 定时器 / 回调未清除:setInterval、事件监听未移除
- 控制台打印:console.log 会阻止对象回收
3. 闭包与泄漏的正确关系
闭包本身不会泄漏。 闭包导致泄漏的唯一原因:闭包引用被全局长期持有,且不再使用却未释放。解决方案:手动断开引用 fn = null。
4. 延伸:如何排查
Chrome DevTools → Memory 面板:
- 快照对比:找异常增长对象
- allocation instrumentation:定位泄漏代码位置
四、this 指向(JS最难:4 种绑定规则 + 优先级 + 箭头函数)
本质:this是执行上下文的属性,在函数调用时动态绑定,而非定义时确定(箭头函数例外:继承外层作用域 this)。
四大绑定规则(优先级从高→低)
- new 绑定 → 指向实例对象
- 显式绑定 → call /apply/bind 强行指定
- 隐式绑定 → 对象。方法 (),指向调用对象
- 默认绑定 → 独立函数调用,指向 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 面向对象底层基石)
底层本质:
- 函数才有 prototype:是函数的原型对象,用于共享方法
- 对象才有 proto:指向构造函数的 prototype,构成原型链
- 原型链尽头是 null:
Object.prototype.__proto__ === null
原型链查找机制
访问对象属性 / 方法时:
- 先找自身
- 找不到 → 沿
__proto__找原型对象 - 一直找到
Object.prototype - 仍无 → 返回 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 是单线程,靠事件循环实现异步:
- 执行同步代码 → 调用栈
- 遇到异步 → 放入 Web API
- 异步完成 → 推入任务队列
- 调用栈空 → 执行任务队列
宏任务 > 微任务 执行规则:
- 每执行一个宏任务 ,清空所有微任务
- 微任务:Promise.then、MutationObserver、queueMicrotask
- 宏任务:script、setTimeout、setInterval、IO、UI 渲染
2. 异步进化史
- 回调函数:嵌套地狱,异常捕获难,信任问题
- Promise:解决回调地狱,状态不可逆(pending/fulfilled/rejected)
- Generator + co:协程,同步写法,使用复杂
- async/await:Promise 语法糖,同步写法,异步流程,终极方案
3. 核心要点
- Promise 链式调用、catch 穿透、all/race/any/allSettled
- async/await 错误处理:try/catch
- 异步 + 闭包:保存异步现场(循环异步核心)
- 异步 + this:Promise 回调 this 指向 window
Promise 链式调用原理及示例:
- 每执行一次
.then(),都会返回一个全新的 Promise- then 内部的返回值:
- 普通值 → 作为下一个 then 的参数
- Promise → 等待该 Promise 执行完再传递
- 抛出错误 → 进入 catch
- 链式调用本质:串行执行异步任务,按顺序流转数据
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 穿透原理及示例:
- Promise 链中任意位置抛出错误 / 返回 reject
- 会跳过中间所有 then,直接找到离它最近的 catch
- 这就是 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原理:
- async/await 是 Promise 语法糖,基于 Promise 实现
- await 后面的 Promise 变为 rejected 时,会抛出异常
- 必须用 try/catch 捕获,否则程序崩溃
- try 放正常逻辑,catch 捕获错误
javascript
async function fn() {
try {
const res = await Promise.resolve('成功')
console.log(res)
} catch (err) {
// 捕获 await 错误 / 手动抛错
console.log('捕获异常:', err)
}
}
异步 + 闭包:保存异步现场(循环异步核心)原理:
- 异步任务(定时器、Promise)会延迟执行
- var 无块级作用域,循环共用一个变量,异步执行时变量已被覆盖
- 闭包创建独立作用域,保存每一轮的变量现场
- 让异步回调拿到当前轮的正确值
javascript
// 闭包解决循环异步问题
for(var i=0; i<5; i++){
(function(j){
setTimeout(()=>console.log(j),0)
})(i)
}
异步+闭包最常用的经典案例是防抖节流,详述在上文。
异步 + this:Promise 回调 this 指向 window,原理:
- Promise 的 then/catch 回调是异步微任务
- 执行时处于全局执行上下文
- 普通函数的 this 遵循默认绑定 → 指向 window(严格模式 undefined)
- 不是指向调用时的对象
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. 类型判断
- typeof
- 适用:原始类型(除 null)
- 缺陷**:**
typeof null === 'object',数组 / 对象都返回 object- instanceof
- 适用:引用类型,基于原型链
- 缺陷:不能判断原始类型,跨 iframe 失效
- Object.prototype.toString.call()
- 最准确 :
[object Array]/[object Object]- 通用所有类型,面试首选
- 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:状态更新需深拷贝避免视图异常
- 深浅拷贝 + 性能:浅拷贝性能远高于深拷贝
九、全链路串联(全局视野)
- 闭包是防抖节流、模块化、循环异步的基础,处理不当引发内存泄漏
- this 在闭包、回调、原型、异步中会动态变化,靠绑定 / 箭头函数修复
- 原型链是 JS 面向对象核心,支撑 instanceof、继承、方法共享
- 异步编程靠 Event Loop 运行,常结合闭包保存现场、this 修正指向
- 数据类型 分栈堆存储,决定了引用类型必须用深浅拷贝区分
- 所有知识点底层都围绕:执行上下文、作用域、垃圾回收、内存模型
好啦!吃饭!!!下课煲仔们!!!