GPT训练我的第四天,被打惨了!!!😭😭😭

接前几天的文章:

  1. 直接让GPT每日训练我!!!😕😕😕
  2. GPT训练我的第二天,我表示不过如此!!!😕😕😕
  3. GPT训练我的第三天,明白了应该咋说满分回答!😕😕😕

第一题 执行上下文(Execution Context)

先不要急着看答案。

js 复制代码
var a = 1

function fn() {
  var b = 2

  function inner() {
    var c = 3
    console.log(a)
    console.log(b)
    console.log(c)
  }

  inner()
}

fn()

问题1

代码执行过程中,一共创建了几个执行上下文?

A:1个

B:2个

C:3个

D:4个

问题2

执行顺序是什么?

按照进入和退出的顺序写出来。

问题3(思考题)

执行 console.log(a) 时: JS 引擎是如何找到 a 的?

为什么 inner() 里面能直接访问:

js 复制代码
a
b
c

而没有报:

js 复制代码
a is not defined

第二题:作用域链

还是先思考。

js 复制代码
var name = 'global'

function a() {
  var name = 'a'

  function b() {
    var name = 'b'
    console.log(name)
  }

  b()
}

a()

输出什么?为什么?

再改一下:

javascript 复制代码
var name = 'global'

function a() {
  var name = 'a'

  function b() {
    console.log(name)
  }

  b()
}

a()

输出什么?为什么?

再改:

javascript 复制代码
var name = 'global'

function a() {
  function b() {
    console.log(name)
  }

  b()
}

a()

输出什么?为什么?

菜鸟回答 ------ 感觉和上一天重复,但是其实有部分新知识

这里菜鸟感觉不是和第二天一样吗?简简单单,没有一点难度,所以很快就完成了!

但是写的过程中发现,我好像完全不清楚执行上下文怎么算个数

所以回答如下:

js 复制代码
第一题:
问题1:不知道,我不清楚执行上下文怎么算个数
问题2:
解析阶段
GO: {
  a: undefined,
  fn:  function fn() {}

}

执行阶段
a = 1

GO: {
  a: 1,
  fn:  function fn() {}

}

解析阶段
AO(fn): {
  b:undefined,
  inner:function inner() {}
}

执行阶段:
b = 2

AO(fn): {
  b:2,
  inner:function inner() {}
}

解析阶段
AO(inner): {
  c:undefined,
}

执行阶段
c = 3

AO(inner): {
  c:3
}

问题3:
AO(inner) --> AO(fn)-->GO  找到的a


第二题:
输出:b
因为:AO(b) --> 可以找到name --> 直接输出

后面也是一样的道理,找不到就往外找
输出:a
输出:global

GPT 批改

很好,这次已经能看出来你昨天的变量提升知识点已经基本掌握了。我给你逐个点评。

第一题

问题1:执行上下文有几个?

代码:

js 复制代码
var a = 1

function fn() {
  var b = 2

  function inner() {
    var c = 3
    console.log(a)
    console.log(b)
    console.log(c)
  }

  inner()
}

fn()

你说:

不知道,我不清楚执行上下文怎么算个数

这是今天最大的知识点。

判断规则(记住这一条)

规则1

全局代码 -> 创建一个全局执行上下文

js 复制代码
Global Execution Context

规则2

每调用一次函数,就创建一个新的执行上下文。

注意:

js 复制代码
function a() {}

这里只有定义:

js 复制代码
全局执行上下文

而:

js 复制代码
function a() {}

a()

就是:

js 复制代码
全局执行上下文
a执行上下文

回到题目

第一步

进入文件:

php 复制代码
var a = 1
function fn() {}
fn()

创建:

sql 复制代码
Global EC

第二步

执行:

js 复制代码
fn()

创建:

js 复制代码
fn EC

第三步

执行:

js 复制代码
inner()

创建:

js 复制代码
inner EC

所以一共:

js 复制代码
Global EC
fn EC
inner EC

答案:3个执行上下文。


问题2

你写的是:

js 复制代码
解析阶段
GO
执行阶段
a = 1

AO(fn)
执行阶段
b = 2

AO(inner)
执行阶段
c = 3

这个思路已经对了 80%

但是今天需要在脑子里再增加一个东西:

调用栈(Call Stack)

真正执行过程:

js 复制代码
① 创建 Global EC
Call Stack:
┌──────────┐
│ GlobalEC │
└──────────┘

执行:

js 复制代码
fn()

压栈:

js 复制代码
┌──────────┐
│   fnEC   │
├──────────┤
│ GlobalEC │
└──────────┘

执行:

js 复制代码
inner()

再次压栈:

js 复制代码
┌──────────┐
│ innerEC  │
├──────────┤
│   fnEC   │
├──────────┤
│ GlobalEC │
└──────────┘

执行:

js 复制代码
console.log(c)

输出:3

inner 执行完:

js 复制代码
┌──────────┐
│   fnEC   │
├──────────┤
│ GlobalEC │
└──────────┘

fn 执行完:

js 复制代码
┌──────────┐
│ GlobalEC │
└──────────┘

全局执行完:

js 复制代码

你现在脑子里应该有这个模型

js 复制代码
执行上下文 = 工作空间
调用栈 = 工作空间叠起来的栈

问题3

你回答:

js 复制代码
AO(inner)
→ AO(fn)
→ GO

完全正确。

但是我想再往深一层。

执行:

js 复制代码
console.log(a)

查找过程:

js 复制代码
AO(inner)
{
  c: 3
}

有没有:

js 复制代码
a

没有,就向外找:

css 复制代码
AO(fn)
{
  b:2,
  inner: fn
}

有没有:

js 复制代码
a

没有,继续向外:

js 复制代码
GO
{
  a:1,
  fn: fn
}

有:

js 复制代码
a = 1

输出:1

再看:

js 复制代码
console.log(b)

AO(inner) ------ 没有
↓
AO(fn)
↓
找到 b = 2

输出:2

再看:

js 复制代码
console.log(c)
js 复制代码
AO(inner)
↓
直接找到 c = 3

输出:3


第二题

第一段

js 复制代码
var name = 'global'

function a() {
  var name = 'a'

  function b() {
    var name = 'b'
    console.log(name)
  }

  b()
}

a()

你答:b

✅ 完全正确。

查找:

js 复制代码
AO(b)
{
  name:'b'
}

当前作用域直接找到。

输出:b

第二段

js 复制代码
function a() {
  var name = 'a'

  function b() {
    console.log(name)
  }

  b()
}

你答:a

✅ 完全正确。

查找:

js 复制代码
AO(b) ------ 没有
↓
AO(a)
{
  name:'a'
}
↓
找到,输出:a

第三段

js 复制代码
function a() {
  function b() {
    console.log(name)
  }

  b()
}

你答:global

✅ 完全正确。

查找:

js 复制代码
AO(b) ------ 没有
↓
AO(a) ------ 没有
↓
GO
{
  name:'global'
}
↓
找到,输出:global

今天最重要的一句话

你现在不要再把它记成:

js 复制代码
AO(inner)
→ AO(fn)
→ GO

而应该升级成:

js 复制代码
作用域链:

inner.[[Scope]]
↓
AO(fn)
↓
GO

因为后面学闭包时:

js 复制代码
function outer() {
  let count = 0

  return function () {
    count++
    console.log(count)
  }
}

之所以:

js 复制代码
1
2
3
4

能够一直打印下去,就是因为:

js 复制代码
返回的函数
↓
[[Scope]]
↓
AO(outer)
↓
count

虽然 outer() 已经执行完,但这个作用域还被引用着,没有被销毁。

第二题 今日验收

js 复制代码
var a = 1

function fn() {
  var a = 2

  function inner() {
    console.log(a)
  }

  return inner
}

const f = fn()

f()

回答三个问题:

  1. 一共创建了几个执行上下文?
  2. 输出什么?
  3. inner 为什么还能访问到 a?

菜鸟回答 ------ 知道新知识后,感觉不难,沾沾自喜

js 复制代码
1、
Global EC
fn EC
inner EC

2、
输出:2

3、
解析阶段
GO: {
  a: undefined,
  fn: function fn() {}
  f: undefined
}

执行阶段
a = 1

GO: {
  a: 1,
  fn: function fn() {}
  f: undefined
}

f = fn()

GO: {
  a: 1,
  fn: function fn() {}
  f: function inner() {}
}

因为inner里面访问了a,而a在AO(inner)中找不到,会去找AO(fn),所以不能销毁AO(fn),也就能继续访问a了

GPT 批改

非常好,这次我给你打 90 分

因为你已经自己推导出了闭包最核心的本质

因为 inner 还要访问 AO(fn) 里的变量,所以 AO(fn) 不能被销毁。

很多工作 3~5 年的前端,其实只能背:

闭包就是函数引用外部变量。

但说不出来:

为什么外部变量不会被销毁?

而你已经开始能解释原因了。


但是有两个细节要纠正

纠正1:GO 里面没有 f

你写:

js 复制代码
GO: {
  a: undefined,
  fn: function fn() {},
  f: undefined
}

这个不对。

因为:

js 复制代码
const f = fn()

里面:

js 复制代码
const f

不是:

js 复制代码
var f

var

会挂到:

js 复制代码
GO

例如:

js 复制代码
var a = 1

解析:

js 复制代码
GO = {
  a: undefined
}

let/const

不会成为 GO 的属性

例如:

ini 复制代码
const f = 123
let b = 456

更准确的理解是:

js 复制代码
Global Lexical Environment
├── a
├── fn
├── f
└── b

所以:

js 复制代码
window.f

结果:

js 复制代码
undefined

这个知识先有个印象即可。

后面讲:

  • 词法环境(Lexical Environment)
  • TDZ(暂时性死区)
  • let/const 底层实现

会再展开。


纠正2:不是「找不到就不能销毁」

你写:

因为 inner 里面访问了 a,而 a 在 AO(inner)中找不到,会去找 AO(fn),所以不能销毁 AO(fn)

这个描述已经很接近了。

但更准确的说法应该是:

inner 的 \[Scope] 保存了对 fn 词法环境的引用,所以 fn 的词法环境不能被垃圾回收。


JS 引擎真正做的事情

执行:

js 复制代码
function fn() {
  var a = 2

  function inner() {
    console.log(a)
  }

  return inner
}

创建:

js 复制代码
GlobalEC

调用:

js 复制代码
fn()

创建:

js 复制代码
fnEC
AO(fn) {
  a: 2
  inner: function inner(){}
}

此时:

js 复制代码
inner.[[Scope]]
      ↓
AO(fn)

注意:

这里已经产生引用关系了。

然后:

js 复制代码
return inner

执行:

js 复制代码
const f = fn()

实际上:

js 复制代码
f === inner

所以:

js 复制代码
f
 ↓
inner函数对象
 ↓
[[Scope]]
 ↓
AO(fn)

形成:

js 复制代码
f
 ↓
inner
 ↓
[[Scope]]
 ↓
AO(fn)

这时候:

js 复制代码
fn()

虽然执行结束。

调用栈:

js 复制代码
GlobalEC

fnEC 出栈:

js 复制代码
GlobalEC

但是:

fnEC 出栈 ≠ AO(fn) 被销毁

因为:

js 复制代码
f
 ↓
inner
 ↓
[[Scope]]
 ↓
AO(fn)

还有人引用它,所以:

js 复制代码
AO(fn)

继续活着。


再执行

js 复制代码
f()

创建:

js 复制代码
innerEC

执行:

js 复制代码
console.log(a)

查找:

js 复制代码
AO(inner)
    ↓
[[Scope]]
    ↓
AO(fn)

找到:

js 复制代码
a = 2

输出:2


你现在应该建立这个模型

js 复制代码
调用栈(Call Stack)
决定:
谁正在执行

作用域链(Scope Chain)
决定:
变量去哪里找

闭包(Closure)
决定:
作用域是否继续活着

三者是串起来的。

最后给你一道真正的闭包题(Day3 加练)

js 复制代码
function outer() {
  let count = 0

  return function () {
    count++
    console.log(count)
  }
}

const fn1 = outer()
const fn2 = outer()

fn1()
fn1()
fn2()
fn1()
fn2()

回答:

  1. 输出什么?
  2. 一共创建了几个执行上下文?
  3. 内存里面会存在几个:
js 复制代码
AO(outer)

为什么?

菜鸟回答 ------ 直接全军覆没

js 复制代码
输出:
1
2
3
4
5

创建了3个:
Global EC、outer EC、function EC

内存有1个AO(outer),因为有两个变量赋值都是AO(function),并不是AO(outer),但是AO(outer)因为被AO(function)引用了变量,所以不会注销,所以一直存在一个

GPT 批改

问题1:输出

你答:

复制代码
1
2
3
4
5

❌ 错了。

代码:

js 复制代码
function outer() {
  let count = 0

  return function () {
    count++
    console.log(count)
  }
}

const fn1 = outer()
const fn2 = outer()

fn1()
fn1()
fn2()
fn1()
fn2()

第一步

执行:

js 复制代码
const fn1 = outer()

创建:

js 复制代码
AO(outer#1)
{
  count: 0
}

返回:

js 复制代码
fn1
 ↓
匿名函数
 ↓
[[Scope]]
 ↓
AO(outer#1)

第二步

执行:

js 复制代码
const fn2 = outer()

创建:

js 复制代码
AO(outer#2)
{
  count: 0
}

返回:

js 复制代码
fn2
 ↓
匿名函数
 ↓
[[Scope]]
 ↓
AO(outer#2)

注意:

js 复制代码
AO(outer#1)

js 复制代码
AO(outer#2)

是两个完全独立的作用域

执行 fn1()

js 复制代码
count: 0 → 1

输出:1

执行 fn1()

js 复制代码
count: 1 → 2

输出:2

执行 fn2()

这是另一份:

js 复制代码
count: 0 → 1

输出:1

执行 fn1()

还是第一份:

js 复制代码
count: 2 → 3

输出:3

执行 fn2()

第二份:

js 复制代码
count: 1 → 2

输出:2

正确答案

复制代码
1
2
1
3
2

问题2:创建几个执行上下文?

你答:

js 复制代码
Global EC
outer EC
function EC

❌ 不对。

Global EC

js 复制代码
1个

outer()

调用了几次?

js 复制代码
const fn1 = outer()
const fn2 = outer()

两次。

所以:

js 复制代码
outer EC #1
outer EC #2

共:

js 复制代码
2个

返回的匿名函数执行了几次?

js 复制代码
fn1()
fn1()
fn2()
fn1()
fn2()

一共:

js 复制代码
5次

每调用一次函数:

js 复制代码
创建一个新的执行上下文

所以:

js 复制代码
匿名函数 EC × 5

总数

js 复制代码
Global EC × 1
outer EC × 2
匿名函数 EC × 5
=================
8个执行上下文

问题3:几个 AO(outer)?

你答:

复制代码
1个

❌ 这是今天最关键的坑。

你说:

因为两个变量赋值都是 AO(function)

这里有个概念混淆。

fn1

执行:

js 复制代码
const fn1 = outer()

创建:

js 复制代码
AO(outer#1)
{
  count: 0
}

返回:

js 复制代码
fn1
 ↓
匿名函数
 ↓
[[Scope]]
 ↓
AO(outer#1)

fn2

执行:

js 复制代码
const fn2 = outer()

又创建:

js 复制代码
AO(outer#2)
{
  count: 0
}

返回:

js 复制代码
fn2
 ↓
匿名函数
 ↓
[[Scope]]
 ↓
AO(outer#2)

所以内存其实是:

js 复制代码
fn1
 ↓
匿名函数
 ↓
AO(outer#1)
{
  count: 0
}


fn2
 ↓
匿名函数
 ↓
AO(outer#2)
{
  count: 0
}

最终

存在:

js 复制代码
AO(outer#1)
AO(outer#2)

一共:

js 复制代码
2个

这就是为什么很多面试题会这样写

js 复制代码
function createCounter() {
  let count = 0

  return () => ++count
}

const c1 = createCounter()
const c2 = createCounter()

很多人以为:

js 复制代码
count只有一个

其实:

js 复制代码
c1
 ↓
AO(createCounter#1)
{
  count: 0
}


c2
 ↓
AO(createCounter#2)
{
  count: 0
}

是两份独立状态。


看到这里,你应该突然能理解 Vue3 了

js 复制代码
function useCounter() {
  const count = ref(0)

  const inc = () => {
    count.value++
  }

  return {
    count,
    inc
  }
}

第一次:

js 复制代码
const a = useCounter()

产生:

js 复制代码
AO(useCounter#1)
{
  count: Ref(0)
}

第二次:

js 复制代码
const b = useCounter()

产生:

js 复制代码
AO(useCounter#2)
{
  count: Ref(0)
}

所以:

js 复制代码
a.inc()

不会影响:

js 复制代码
b.count.value

因为:每次调用 Composition Function,本质上都是创建了一套新的闭包环境。

菜鸟感觉这样完全没有理解,所以要了几个加练的题目,这里就不写答案了,各位自己做吧

欢迎评论区留下你的结果!

第1题

js 复制代码
function fn() {
  let a = 1

  return function () {
    console.log(a)
  }
}

const f = fn()

f()
f()

问题

  1. 输出什么?
  2. 一共创建几个执行上下文?
  3. 内存里面有几个 AO(fn)?

第2题

js 复制代码
function fn() {
  let a = 1

  return function () {
    a++
    console.log(a)
  }
}

const f = fn()

f()
f()
f()

问题

  1. 输出什么?

  2. 有几个 AO(fn)?

  3. 为什么不是:

    2
    2
    2


第3题

js 复制代码
function fn() {
  let a = 1

  return function () {
    a++
    console.log(a)
  }
}

const f1 = fn()
const f2 = fn()

f1()
f1()
f2()
f1()

问题

  1. 输出什么?
  2. 有几个 AO(fn)?
  3. f1f2a 是不是同一个?

第4题(提高)

js 复制代码
function fn() {
  let a = 1

  return {
    add() {
      a++
    },
    get() {
      console.log(a)
    }
  }
}

const obj = fn()

obj.get()
obj.add()
obj.get()
obj.add()
obj.get()

问题

  1. 输出什么?
  2. addget 为什么都能访问 a
  3. 内存里面几个 AO(fn)?

第5题(Vue源码味道)

js 复制代码
function useCounter() {
  let count = 0

  const inc = () => count++
  const get = () => count

  return {
    inc,
    get
  }
}

const c1 = useCounter()
const c2 = useCounter()

c1.inc()
c1.inc()

console.log(c1.get())
console.log(c2.get())

问题

  1. 输出什么?
  2. 有几个 AO(useCounter)?
  3. 为什么 c1 不影响 c2
相关推荐
神奇小汤圆8 小时前
深入解析 Flink Kafka Connector:原理、配置与最佳实践
前端·后端
科技之门8 小时前
百公里管网漏损分级定位实战方案2026
前端·人工智能·算法
xyphf_和派孔明9 小时前
企业级微前端项目完整创建步骤,第三步:配置 Vue 子应用
前端·javascript
自强的小白9 小时前
条件查询或查询全部时时前端请求数据等时间格式
前端
阳光是sunny9 小时前
LangGraph实战教程:一文搞懂图的状态(State)管理
前端·人工智能·后端
whyfail9 小时前
前端学 Spring Boot(5):从“你是谁”到“服务真的上线了”
前端·spring boot·后端
程序员黑豆10 小时前
鸿蒙开发:RelativeContainer相对布局组件实战——实现5个文本组件对齐
前端·华为·harmonyos
环境栈笔记10 小时前
高性价比指纹浏览器推荐与选型:如何对照价格和实际可用功能筛选候选
前端·人工智能·后端·自动化
hyf32663310 小时前
泛程序哪有想象中难!小白跟着走一遍就全懂
前端·爬虫·搜索引擎·seo·蜘蛛池
码农学院10 小时前
React公众号开发:母婴用品会员积分体系技术方案
前端·react.js·前端框架