GPT训练我的第三天,明白了应该咋说满分回答!

GPT训练我的第三天,明白了应该咋说满分回答!

文章目录

    • 回顾
    • 今日训练
  • 第一题:执行上下文到底经历了什么?
      • 第一步:进入全局执行上下文(GO)
      • 第二步:执行到 `fn()` 时
      • 第三步:执行:
  • 第二题:作用域链
      • 问题1
      • 问题2
      • 问题3
    • 小任务(非常重要)
    • 菜鸟回答 —— 这里菜鸟还是没有掌握满分回答的精髓
    • GPT 批改
  • 第一题批改
    • GO
      • 创建阶段
      • 执行阶段
    • AO
      • 创建阶段
      • 执行阶段
    • 查找 `a`
  • 第二题批改
    • console.log(c)
    • console.log(b)
    • console.log(a)
    • 今日加练(闭包预热)
      • 问题1
      • 问题2
    • 菜鸟回答 —— 这里已经大致知道GPT想要什么答案了
    • GPT 解析
  • 问题1:`fn` 的 AO 会不会销毁?
  • 问题2:你的 GO 写错了
    • 创建阶段
    • 执行阶段
  • AO(fn)
  • AO(test)
  • 查找 b
  • 查找 a
  • 但是最关键的是这一句
  • 真正的作用域链
    • 验收题(非常重要)
    • 菜鸟回答 —— 接近满分(所以就不给GPT批改了)

接前两天的文章:

  1. 直接让GPT每日训练我!!!😕😕😕
  2. GPT训练我的第二天,我表示不过如此!!!😕😕😕

回顾

昨天你已经理解了:

  • 函数提升优先于var变量提升
  • GO/AO(全局对象/执行上下文对象)
  • 创建阶段和执行阶段

今天继续推进两个核心知识点:

  1. 执行上下文(Execution Context)完整流程
  2. 作用域链(Scope Chain)到底是怎么找变量的

今日训练

第一题:执行上下文到底经历了什么?

看代码:

vara=1functionfn(){varb=2console.log(a)console.log(b)}fn()

请不要运行,按照下面顺序回答:

第一步:进入全局执行上下文(GO)

GO 里面有什么?

写出:

GO={}

第二步:执行到fn()

创建函数执行上下文(AO)。

写出:

AO={}

第三步:执行:

console.log(a)

JS 引擎是:

  1. 去 AO 找?
  2. 去 GO 找?
  3. 还是直接全局找?

请写出完整查找过程。


第二题:作用域链

再看:

vara=1functionfn(){varb=2functiontest(){varc=3console.log(a)console.log(b)console.log(c)}test()}fn()

不要运行,请回答:

问题1

执行:

console.log(c)

变量在哪里找到?

问题2

执行:

console.log(b)

查找路径是什么?

例如:

AO(test)AO(fn)GO

问题3

执行:

console.log(a)

查找路径是什么?

小任务(非常重要)

从现在开始,每看一个函数,都先在脑子里画:

GOAO(fn)AO(test)

以后学:

  • 闭包
  • this
  • call/apply/bind
  • Vue 响应式源码
  • Pinia
  • React Hooks

全部建立在这套执行上下文和作用域链模型之上。

菜鸟回答 —— 这里菜鸟还是没有掌握满分回答的精髓

第一题:GO{a:1,fn:functionfn(){}}AO:{a:1,b:2}a 会现在AO找,发现找不到所以去GO找到了a:1,所以console.log(a)会输出1第二题: console.log(c)直接在test的AO中找,并找到了,输出3console.log(b)先在test的AO中找,找不到,就去fn的AO找,找到了,输出2console.log(a)先在test的AO中找,找不到,就去fn的AO找,也找不到,去GO找,找到了,输出1

GPT 批改

第一题批改

代码:

vara=1functionfn(){varb=2console.log(a)console.log(b)}fn()

GO

你写的是:

GO={a:1,fn:functionfn(){}}

✅ 最终执行完成后是对的。

但如果按照执行上下文的完整流程:

创建阶段

GO={a:undefined,fn:functionfn(){}}

执行阶段

执行:

a=1

变成:

GO={a:1,fn:functionfn(){}}

AO

你写的是:

AO={a:1,b:2}

这里错了一点。

创建阶段

进入fn

AO={b:undefined}

因为:

functionfn(){varb=2}

函数里面只有:

varb

并没有:

vara

所以a不会出现在 AO 中。

执行阶段

执行:

b=2

变成:

AO={b:2}

查找a

你写的:

AO 找不到 → GO 找到

完全正确。

实际上 JS 引擎干的事情类似:

AO(fn)↓ 没有 aGO↓ 找到 a=1返回1

第二题批改

代码:

vara=1functionfn(){varb=2functiontest(){varc=3console.log(a)console.log(b)console.log(c)}test()}fn()

console.log©

你:

直接在 test 的 AO 找到,输出 3

✅ 正确。

AO(test)↓ 找到 c=3

console.log(b)

你:

test 的 AO 没有 → fn 的 AO 找到

✅ 正确。

AO(test)AO(fn)↓ 找到 b=2

console.log(a)

你:

test 的 AO 没有 → fn 的 AO 没有 → GO 找到

✅ 完全正确。

AO(test)AO(fn)GO↓ 找到 a=1

今日加练(闭包预热)

不要运行:

vara=1functionfn(){varb=2functiontest(){console.log(a)console.log(b)}returntest}varf=fn()f()

问题1

执行完:

varf=fn()

以后:

fn 的AO

会不会销毁?


问题2

执行:

f()

时:

console.log(b)

是怎么找到b的?

菜鸟回答 —— 这里已经大致知道GPT想要什么答案了

问题1:不会 问题2: 解析阶段:GO{a:undefined,f:functionfn(){}}执行:GO{a:1,f:functionfn(){}}解析阶段:AO(fn){b:undefined,test:functiontest(){}}执行阶段:AO(fn){b:2,test:functiontest(){}}解析阶段:AO(test){}执行阶段: b找不到,去AO(fn)中找,找到了,所以是2a找不到,去AO(fn)中找,还是找不到,去GO中找,所以是1

GPT 解析

问题1:fn的 AO 会不会销毁?

你回答:

不会

✅ 正确。

但是原因你还没有说出来。

因为:

varf=fn()

实际上:

f===test

也就是:

varf=test

test函数里面:

console.log(b)

需要访问:

AO(fn)

JS 引擎发现:

虽然 fn 执行结束了,但是还有一个函数(test)在引用它里面的变量。

所以:

AO(fn)↑ test 引用了它

因此:

AO(fn)不会被垃圾回收(GC)

这就是闭包产生的根本原因。


问题2:你的 GO 写错了

代码:

vara=1functionfn(){varb=2functiontest(){console.log(a)console.log(b)}returntest}varf=fn()f()

你写:

GO={a:undefined,f:functionfn(){}}

这里错了。

创建阶段

GO={a:undefined,fn:functionfn(){},f:undefined}

因为:

varafunctionfn(){}varf

都会提升。

执行阶段

执行:

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

执行:

f=fn()

此时:

fn()

返回:

test

所以:

GO={a:1,fn:functionfn(){},f:functiontest(){}}

AO(fn)

创建阶段:

AO(fn)={b:undefined,test:functiontest(){}}

执行阶段:

AO(fn)={b:2,test:functiontest(){}}

AO(test)

创建阶段:

AO(test)={}

执行阶段:

AO(test)={}

因为:

functiontest(){console.log(a)console.log(b)}

里面没有:

  • var
  • function
  • 参数

所以 AO 为空。


查找 b

执行:

console.log(b)

查找过程:

AO(test)↓ 没有 bAO(fn)↓ 找到 b=2返回2

查找 a

执行:

console.log(a)

查找过程:

AO(test)↓ 没有 aAO(fn)↓ 没有 aGO↓ 找到 a=1返回1

但是最关键的是这一句

很多人会以为:

f()

执行时:

AO(test)GO

因为:

f()

是在全局调用的,这是错误的!


真正的作用域链

作用域链由:

函数定义的位置决定(词法作用域)

不是:

函数调用的位置决定

所以:

functionfn(){varb=2functiontest(){console.log(b)}returntest}

test出生的时候:

GOAO(fn)AO(test)

这个链条已经固定了。

即使:

varf=fn()f()

甚至:

setTimeout(f)obj.xxx=f window.abc=f

调用位置变了无数次。

但是:

AO(test)AO(fn)GO

永远不会变。

验收题(非常重要)

不要运行:

vara=100functionfn(){vara=200functiontest(){console.log(a)}returntest}varf=fn()a=300f()

请你推导:

  1. 输出多少?
  2. 查找路径是什么?

菜鸟回答 —— 接近满分(所以就不给GPT批改了)

解析阶段:GO:{a:undefined,fn:functionfn(){},f:undefined}执行阶段: a=100GO:{a:100,fn:functionfn(){},f:undefined}f=fn()GO:{a:100,fn:functionfn(){},f:functiontest(){}}a=300GO:{a:300,fn:functionfn(){},f:functiontest(){}}解析阶段AO(fn){a:undefined,test:functiontest(){}}执行阶段: a=200AO(fn){a:200,test:functiontest(){}}解析阶段:AO(test){}执行阶段:AO(test)找不到a,去AO(fn)找到a,所以是200