接第一天:直接让GPT每日训练我!!!😕😕😕
今天一上来,GPT先给我科普了一波!
练手准备
知识点1:var 为什么没有块级作用域?
题目1
ini
if (true) {
var a = 1;
}
console.log(a);
先思考 ------ 自己想 30 秒
输出什么?
A:1
B:undefined
C:报错
解析
因为:var
只有:
- 全局作用域
- 函数作用域
没有:
- if 作用域
- for 作用域
- while 作用域
- switch 作用域
所以:
ini
if (true) {
var a = 1;
}
实际上等价于:
ini
var a;
if (true) {
a = 1;
}
console.log(a); // 1
答案:1
再看一个
css
for (var i = 0; i < 3; i++) {}
console.log(i);
输出:?
很多人刚工作时会觉得i,应该只存在于 for 里面。
但实际上var不会被for创建的作用域限制,等价于:
css
var i;
for (i = 0; i < 3; i++) {}
console.log(i);
答案:3
为什么 ES6 要出 let
因为var太容易产生污染:
ini
if (true) {
var user = {};
}
for (var i = 0; i < 100; i++) {}
console.log(user);
console.log(i);
所有变量全部泄露到外面。
所以let,拥有块级作用域:
ini
if (true) {
let a = 1;
}
console.log(a);
直接:
javascript
ReferenceError
知识点2:作用域链是怎么找变量的?
题目2
ini
var a = 1;
function fn() {
console.log(a);
}
fn();
输出什么?
A:1
B:undefined
C:报错
解析
JS 查找变量遵循:
当前作用域找不到 → 去父作用域 → 一直向上找到全局作用域。
这个链条叫:
sql
作用域链(Scope Chain)
图:
php
Global
└── fn
执行:
javascript
function fn() {
console.log(a);
}
步骤:
第一步
先找fn,内部有没有a。
第二步
沿作用域链向上:
sql
Global
发现:
css
var a = 1
所以答案:1
再看这个
ini
var a = 1;
function fn() {
var a = 2;
console.log(a);
}
fn();
作用域:
ini
Global
└── fn
└── a = 2
查找:
arduino
console.log(a)
先找当前作用域:
css
var a = 2
已经找到,不会继续向上。
答案:2
再看经典面试题
ini
var a = 1;
function fn() {
console.log(a);
var a = 2;
}
fn();
很多人会答 1,实际上输出:undefined!
原因
创建阶段
css
fn AO = {
a: undefined
}
作用域:
ini
Global
└── fn(a = undefined)
执行:
arduino
console.log(a);
先找当前作用域:
ini
a = undefined
已经找到,不会再去全局找:
ini
a = 1
所以 undefined
今日核心口诀
var
只有函数作用域
没有块级作用域
作用域链
当前作用域找
找到立即停止
找不到向父级找
一直找到全局
变量提升的本质
先创建作用域(这个昨天有深入的理解)
再创建变量
最后执行代码
今日习题
js
var a = 1;
function fn() {
console.log(a);
if (false) {
var a = 2;
}
}
fn();
菜鸟回答 ------ 这里VO、AO菜鸟没搞清楚,后面就清楚了
css
输出:undefined
原因:var没有if的作用域,会被提升出去,所以a就是undefined,因为函数里面有,所以也不会往外找
执行过程:
第一步:
外部
AO:
a = 1
VO:
fn
第二步:
AO:
a = undefined
后续也不会赋值!
GPT 批改
非常好,这题你已经抓住核心了,属于理解到了变量提升和作用域链的交汇点。但有两个小细节需要纠正。
你的答案
javascript
undefined
✅ 正确。
原因
你说:
var没有if的作用域,会被提升出去,所以a就是undefined,因为函数里面有,所以也不会往外找
✅ 思路正确。
可以再专业一点:
var a虽然写在if(false)里面,但var没有块级作用域,因此会提升到fn的函数作用域顶部,并初始化为undefined。执行console.log(a)时,当前作用域已经存在变量a,值为undefined,因此不会继续沿作用域链向上查找全局的a。
你写的执行过程
你写:
第一步
外部:
makefile
AO:
a = 1
VO:
fn
这里概念混了一点。
实际上:
全局执行上下文(GEC)
创建阶段:
css
GO = {
a: undefined,
fn: function fn(){}
}
执行阶段:
css
GO = {
a: 1,
fn: function fn(){}
}
进入 fn
创建阶段:
ini
if (false) {
var a = 2;
}
虽然不会执行:
ini
a = 2
但是:
css
var a
仍然会提升。
所以:
css
AO = {
a: undefined
}
执行阶段
执行:
arduino
console.log(a);
JS 查找:
先看:
css
AO = {
a: undefined
}
已经找到a。
于是:
javascript
console.log(undefined);
结束。
因为:
arduino
if (false)
条件为假:
ini
a = 2
永远不会执行。
最终:
css
AO = {
a: undefined
}
整个过程画出来
创建全局
javascript
GO
├── a: undefined
└── fn: function
执行:
yaml
GO
├── a: 1
└── fn: function
调用 fn
创建 AO:
scss
AO(fn)
└── a: undefined
作用域链:
scss
GO
└── AO(fn)
执行:
arduino
console.log(a)
查找过程:
scss
AO(fn)
└── a: undefined ← 找到了,停止查找
输出:
javascript
undefined
不会再去:
css
GO
└── a: 1
加练题目
第一题
ini
var a = 1;
function fn() {
if (true) {
var a = 2;
}
console.log(a);
}
fn();
按照格式回答:
输出:
AO:
执行过程:
第二题(难度+1)
ini
var a = 1;
function fn() {
console.log(a);
if (true) {
var a = 2;
}
console.log(a);
}
fn();
按照格式回答:
输出:
AO:
第一次查找:
第二次查找:
菜鸟回答 ------ 这里AO、GO也没有很准确
ini
第一题:
输出:2
AO:
fn
a = undefined
但是后面if(true)了,所以a=2
执行过程:
第一步:
GO:
a= 1
fn
第二步:
AO:
a = undefined
但是后面if(true)了,所以a=2
第二题:
输出:undefined 2
AO:
fn
a = undefined
但是后面if(true)了,所以a=2
第一次查找: a = undefined
第二次查找:a=2
GPT 批改
第一题
ini
var a = 1;
function fn() {
if (true) {
var a = 2;
}
console.log(a);
}
fn();
你的答案:
输出:2
✅ 完全正确。
真正执行过程
全局创建阶段
css
GO = {
a: undefined,
fn: function fn(){}
}
全局执行阶段
css
GO = {
a: 1,
fn: function fn(){}
}
调用 fn
创建 AO:
css
AO = {
a: undefined
}
作用域链:
scss
GO
└── AO(fn)
执行
执行:
ini
if (true) {
a = 2;
}
此时:
css
AO = {
a: 2
}
继续:
arduino
console.log(a);
查找:
scss
AO(fn)
└── a: 2
输出 2
第二题
ini
var a = 1;
function fn() {
console.log(a);
if (true) {
var a = 2;
}
console.log(a);
}
fn();
你的答案:
javascript
输出:undefined 2
✅ 完全正确。
执行过程
全局创建阶段
css
GO = {
a: undefined,
fn: function fn(){}
}
全局执行阶段
css
GO = {
a: 1,
fn: function fn(){}
}
调用 fn
创建 AO:
css
AO = {
a: undefined
}
第一次输出
执行:
arduino
console.log(a)
查找:
scss
AO(fn)
└── a: undefined
已经找到,停止向上查找。
输出:
javascript
undefined
执行 if
ini
if (true) {
a = 2;
}
此时:
css
AO = {
a: 2
}
第二次输出
执行:
arduino
console.log(a)
查找:
scss
AO(fn)
└── a: 2
输出 2
但是这里有个细节,你的描述有一点问题
你写:
ini
AO:
fn
a = undefined
但是后面if(true)了,所以a=2
严格来说应该拆成两个阶段。
创建阶段
css
AO = {
a: undefined
}
执行 if 后
css
AO = {
a: 2
}
因为:
AO 是在进入函数的时候一次性创建好的,不会因为执行 if 再重新创建。
这是以后阅读 Vue 源码非常重要的思维。
很多人脑子里的模型是:
进入 if
↓
创建变量
这是错的,真正模型:
javascript
进入函数
↓
创建 AO
↓
收集所有 var
↓
初始化 undefined
↓
开始执行代码
↓
给变量赋值
最终一题
ini
var a = 1;
function fn() {
console.log(a);
var a = 2;
function a() {}
console.log(a);
}
fn();
请按格式回答:
GO:
AO:
第一次输出:
第二次输出:
原因:
菜鸟回答 ------ 这里AO、GO菜鸟就比较明确了
css
GO:{
a : 1
fn : function fn() {}
}
AO:{
a: function a() {}
a:2
}
第一次输出:function a() {}
第二次输出:2
原因:昨天的函数整体提升,AO中一开始a被赋值为function a() {} ,后面的var声明的变量a,因为已经有a了,所以不会在创建a,所以第一次是function a() {},后面执行var a=2,将a赋值为了2,所以第二次是2,function a提升了,所以这里不会再运行
GPT 批改
非常好,这次已经接近满分答案了。你已经开始形成执行上下文的运行模型了。
我只纠正最后一个容易混淆的点。
你的答案
输出
csharp
function a() {}
2
✅ 正确
原因
css
function a 提升
↓
AO.a = function a(){}
↓
var a 发现已经存在
↓
不会重新创建
↓
第一次输出函数
↓
执行 a = 2
↓
第二次输出 2
✅ 完全正确。
唯一的小问题
你写:
css
AO = {
a: function a() {}
a: 2
}
严格来说,这是执行完成后的 AO,不是创建阶段的 AO。
应该拆开写:
创建阶段
css
AO = {
a: function a() {}
}
执行
执行:
ini
var a = 2;
实际上:
ini
a = 2;
此时:
css
AO = {
a: 2
}
真正的执行过程
第一步:创建 GO
css
GO = {
a: undefined,
fn: function fn() {}
}
第二步:执行 GO
css
GO = {
a: 1,
fn: function fn() {}
}
第三步:进入 fn,创建 AO
扫描函数体:
先处理函数声明
csharp
function a() {}
得到:
css
AO = {
a: function a() {}
}
再处理 var
css
var a;
发现 a 已经存在,什么都不做:
css
AO = {
a: function a() {}
}
第四步:执行代码
执行:
arduino
console.log(a);
输出:
csharp
function a() {}
然后:
ini
var a = 2;
等价于:
ini
a = 2;
AO:
css
AO = {
a: 2
}
最后:
arduino
console.log(a);
输出:2
这道题最重要的结论
在同一个作用域:
csharp
function a() {}
var a;
等价于:
css
AO = {
a: function a() {}
}
不是:
css
AO = {
a: undefined
}
也不是:
css
AO = {
a: function a() {},
a: undefined
}
因为对象属性不可能同时存在两个同名 key。