执行上下文和作用域
什么是执行上下文。
JavaScript在执行语句前,是需要经过了一些列的“准备”的, 为代码执行创造一个执行上下文,就好像老师上课前要有,教室一样
本节课重点放在文本环境
文本环境相当于我们教室的点名册,为上课做准备。文本环境为代码执行做准备。
代码在执行之前,变量名,函数名,类名,登记在文本环境里,在js执行的时候就可以在文本环境中查找变量,函数,类
那么js在哪里去找执行上下文呢?(只有找到了执行上下文才能找到文本环境里的变量,函数,类,)
答案就是在执行栈里找到执行上下文
4种情况会创建新的执行上下文
1.进入全局代码
2.进入function函数体代码
3.进入eval函数参数指定的代码
4.进入module代码
本节课重点介绍
- 进入全局代码
- 进入function函数体代码
执行上下文做了那些准备?
图A-5
名词解释
:
顶级函数声明
- 就是不包含在大括号内的函数声明,
- 更不是函数表达式
名字重复处理 -
- let const class 声明的名字之间不能重复(重复报错)
-
- let const class 和var function的名字不能重复重复报错)
-
- var和function名 字重复的,function声明的函数名优先
//验证 var和function名 字重复的,function声明的函数名优先
console.log(foo) //执行结果:ƒ foo() { console.log(这是函数) }
var foo = '这是var声明'
function foo() {
console.log(这是函数)
}
以下文字及代码参考图A-5
先看下面的代码,是不是很懵
//1. var和function声 明创建在全局对象中,而1et const class 声明的变量创建在全局scope中
//2.先到全局scope中找变量,查找不到再到全局对象查找
//代码编号:s1
let aLet ='aLet' ;
console . log(aLet); //执行结果:aLet
console . log(window. aLet); //执行结果:undefined
//代码编号:s2
var aVar =' aVar' ;
console.log( aVar);//执行结果:aVer
console.1og(window.aVar );//执行结果:aVer
//代码编号:s3
console.log(Function); //执行结果:f Function() { [native code] }
console.log(window.Function); //执行结果:f Function() { [native code] }
//代码编号:s4
let Function = 'let定义覆盖了Function';
console.log(Function); //执行结果:let定义覆盖了Function
console.log(window.Function);//执行结果:f Function() { [native code] }
现在,我们拿着图A-5来验证s1这段代码
//代码编号:s1
let aLet ='aLet' ;
console . log(aLet); //执行结果:aLet
console . log(window. aLet); //执行结果:undefined
Step1:创建全局执行上下文,并加入栈顶
Step2:
- 找到所有的非函数中的var声明(s1代码中
无
) - 找到所有的顶级函数声明(s1代码中
无
) - 找到顶级let const class声明(参考图A-5,将被let声明的aLet变量加入到全局scope中)
Step3:
- 名字重复处理(
无
)
Step4:创建绑定
- 登记并初始化var为undefined(
无
) - 顶级函数声明:登记function名字, 并初始化为新创建函数对象(
无
) - 块级中函数声明:登记名字,初始化为undefined(
无
) - 登记let const class ,但未初始化(登记aLet 变量名)
Step5:执行语句
前面Step1-Step4已经为语句执行做好了准备工作
执行顺序:
1、第一行代码,let aLet =‘aLet’ ;
(1)先去全局scope
中找aLte,可以找到,在Step4中已经准备好了由let声明的aLet变量只是没有初始化
(2)再为aLet 初始化为’aLet’
2、第二行代码,console . log(aLet);
先去全局scope
中找有没有aLet这个变量名,再去全局对象
中找
此次我们再全局scope
中就可以找到aLet这个变量名,不用在去全局对象
了
先去全局scope
中找到,let aLet =‘aLet’,所以打印aLet
2、第三行代码,console . log(window. aLet);
在浏览器中,window对象就是全局对象
,
所以我们到直接到全局对象
,中找aLet这个变量名,是没有的,所以打印undefined
可以在浏览器中看到如下:
现在,我们拿着图A-5来验证s2这段代码
//代码编号:s2
var aVar =' aVar' ;
console.log(aVar);//执行结果:aVer
console.1og(window.aVar);//执行结果:aVer
Step1:创建全局执行上下文,并加入栈顶
Step2:
- 找到所有的非函数中的var声明(参考图A-5,将被var声明的aVer变量加入到
全局对
象中) - 找到所有的顶级函数声明(s2代码中
无
) - 找到顶级let const class声明(s2代码中
无
)
Step3:
- 名字重复处理(
无
)
Step4:创建绑定
- 登记并初始化var为undefined(var aVer = undefined )
- 顶级函数声明:登记function名字, 并初始化为新创建函数对象(
无
) - 块级中函数声明:登记名字,初始化为undefined(
无
) - 登记let const class ,但未初始化(
无
)
Step5:执行语句
前面Step1-Step4已经为语句执行做好了准备工作
执行顺序:
1、第一行代码,var aVar =‘aVar’ ;
(1)先去全局scope
中找aVar,找不到
(2)再去全局对象
中找,可以找到赋值,var aVar =‘aVar’
2、第二行代码,console.log( aVar);
先去全局scope
中找有没有aVar这个变量名,再去全局对象
中找
此次我们再全局scope
中没有找到aVar这个变量名
再去全局对象
找aVar这个变量名,找到了,var aVar =‘aVar’
所以打印aVar
2、第三行代码,console.1og(window.aVar);
在浏览器中,window对象就是全局对象
,
所以我们到直接到全局对象
,中找aVar这个变量名,可以找到,所以打印aVar
s3这段代码和s2原理一样都是在全局对象
中登记
现在,我们拿着图A-5来验证s4这段代码
//代码编号:s4
let Function = 'let定义覆盖了Function';
console.log(Function); //执行结果:let定义覆盖了Function
console.log(window.Function);//执行结果:f Function() { [native code] }
Step1:创建全局执行上下文,并加入栈顶
Step2:
- 找到所有的非函数中的var声明(s4代码中
无
) - 找到所有的顶级函数声明(s4代码中
无
) - 找到顶级let const class声明(参考图A-5,将被let声明的Function 变量加入到全局scope中)
Step3:
- 名字重复处理(
无
)
Step4:创建绑定
- 登记并初始化var为undefined(
无
) - 顶级函数声明:登记function名字, 并初始化为新创建函数对象(找到一个函数声明)
- 块级中函数声明:登记名字,初始化为undefined(
无
) - 登记let const class ,但未初始化(登记Function 变量名)
Step5:执行语句
前面Step1-Step4已经为语句执行做好了准备工作
执行顺序:
1、第一行代码,let Function = ‘let定义覆盖了Function’;
(1)先去全局scope
中找Function ,可以找到,在Step4中已经准备好了由let声明的Function 变量只是没有初始化
(2)再为Function 初始化为 ‘let定义覆盖了Function’
2、第二行代码,console.log(Function);
先去全局scope
中找有没有Function 这个变量名,再去全局对象
中找
此次我们再全局scope
中就可以找到Function 这个变量名,不用在去全局对象
了
先去全局scope
中找到,let Function = ‘let定义覆盖了Function’,所以打印:let定义覆盖了Function
2、第三行代码,console.log(window.Function);
在浏览器中,window对象就是全局对象
,
所以我们到直接到全局对象
,中找Function这个变量名,找到是一个对象打印:f Function() { [native code] }
//1. let const class 声明的名字之间不能重复
//2. let const class 和var function的名字不能重复
//3. var和function名 字重复的,function声 明的函数名优先
我现在回过头看看图A-5的执行
console. log(foo); //执行结果:foo
if (false) {
var foo = 'foo';
}
图A-6
函数执行上下文
图A-7
函数的调用会创建一个函数的执行上下文如:图A-8
图A-8
Step1:创建全局执行上下文,并加入栈顶
Step2:
- 找到所有的非函数中的var声明(图A-7代码中找到一个)
- 找到所有的顶级函数声明(图A-7代码中找到一个)
- 找到顶级let const class声明(
无
,注意图A-7代码中let声明是在大括号里的,不是顶级)
Step3:
- 名字重复处理(
无
)
Step4:创建绑定
- 登记并初始化var为undefined
- 顶级函数声明:登记function名字, 并初始化为新创建函数对象(图A-7初始化一个函数对象,注意:函数对象’体内’会保存,函数创建时的执行上下文的文本环境)
- 块级中函数声明:登记名字,初始化为undefined(
无
) - 登记let const class ,但未初始化(
无
)
Step5:执行语句
前面Step1-Step4已经为语句执行做好了准备工作
执行顺序:
1、第一行代码,var a = 10;
(1)先去全局scope
中找a,没找到
(2)再去全局队象
找a变量,找到,赋值10
2、第二行代码,foo();
(1)如:图A-8创建函数foo执行上下文,加入栈顶
函数foo执行上下文(图A-8中,‘函数foo执行上下文’这几个字上面那个空白黄色方框,就是文本环境) 的 文本环境
会以它自己体内保存的文本环境为父亲
(2)函数foo执行上下文,按照Step1 - Step5
打印结果:如下图A-9
图A-9
我们把创建但是无法使用的变量称为临时死区
作用域
作用域是解析(查找)变量名的- -个集合,就是当前运行上下文(也可以是当前上下文的词法环境(Lexical Environment ))
- 全局作用域就是全局运行.上下文
- 函数作用域就是函数运行上下文
函数调用时的执行上下文看“身世"一-函数在 哪里创建,就保存哪里的运行上下文 函数的作用域是在函数创建的时候决定的而不是调用的时候决定
(就是因为函数执行上下文的文本环境
会把自己体内的那个执行上下文作为自己的父亲,这个父亲是在函数创建的时候生成的)
函数的作用域是在函数创建的时候决定的而不是调用的时候决定,如:图A-10,执行结果:2
图A-10
函数bar foo do都是在全局作用域里创建的,所以调用的时候指向全局如:图图A-11
图A-11
块级作用域
文本环境初始化
文本环境赋值
文本环境销毁
块级作用域声明函数
图A-12
1、执行第一行语句打印undefined
2、执行if块
图A-13
2.1、if块执行完毕
1、退出块执行
2、链接重新指向原来的文本环境
3、检查全局对象
中是否有一个叫foo的全局对象,如果有就把if块中的foo赋值,给全局对象里的foo如图A-14
图A-14
3、执行foo()
函数foo执行上下文的文本环境
链接到它体内的文本环境
例子:
// 例子1
console. log(foo); // 执行结果undefined
if (false) {
function foo(){
console.log(';;');
}
}
例子1中,虽然if永远不会执行,但是它里面的函数是有提升的,就是没有赋值
// 例子2
console. log(foo); // 执行结果:报错
// if (false) {
// function foo(){
// console.log(';;');
// }
// }
例子2中,此时不存在foo的变量
// 例子3
console. log(foo); // 执行结果undefined
if (false) {
function foo(){
console.log(';;');
}
}
foo(); //报错不能使用
例子3中,因为if块的重来没执行过,就不存在,if块的作用域(图A-14),根本就链接不到全局对象
里面
// 例子4
let foo;
if (true) {
function foo() {
console.log(';;');
}
}
foo(); //报错不能使用
在例子4中,执行失败原因
(1)let foo和if块中的函数名重复
(2)虽然if作用域存在(图A-14),但是在if块执行完毕
,检查全局对象
中是否有一个叫foo的全局对象,没有找到(没有找到的原因是foo存在全局scpe
中)
// 例子5
var foo = '';
if (true) {
function foo() {
console.log(';;');
}
}
foo(); //执行结果:;;
在例子5中,
(1)var foo和if块中的函数名重复(if函数名不做任何处理)
(2)if作用域存在(图A-14),但是在if块执行完毕
,检查全局对象
中是否有一个叫foo的全局对象,此时是有的,我们就把foo的值进行覆盖
var liList = [];
for (var i = 0; i<5; i++) {
liList[i] = function () {
console.log(i)
}
}
liList[0]() //执行结果:5
liList[1]() //执行结果:5
liList[2]() //执行结果:5
liList[3]() //执行结果:5
liList[4]() //执行结果:5
打印5个5
分析
注意:
liList[i] = function ()是函数表达式
经过step1-step5此时是这样的如图A-15
图A-15
执行i<5?判断成立,执行函数表达式liList[i] = function (),创建了list[0]的作用域,(也可以称之为文本环境)
执行i<5?判断完毕
链接重新链接到全局上下文的文本环境如图A-16
图A-16
i++,此时i是1
执行i<5?判断成立,执行函数表达式liList[i] = function (),创建了list[1]的作用域,(也可以称之为文本环境)
我们可以发现他们的list对象是不同的,到那时链接到的都在一个文本环境上。
list()执行如图A-17
A-17
var liList = [];
for (let i = 0; i<5; i++) {
liList[i] = function () {
console.log(i)
}
}
liList[0]() //执行结果:0
liList[1]() //执行结果:1
liList[2]() //执行结果:2
liList[3]() //执行结果:3
liList[4]() //执行结果:4
打印 01234
let块之间是相互隔离的,会重新复制一个let i=0
重新指向新的let i = 0
创建function函数文本环境
执行i++之前,再次复制i的值
执行i++,然后判断,和前面一样创建文本化境
创建function函数文本环境
全部循环执行完是这样子
//例子1
for(leti=0;i<5;i++){
leti=29;
console. log(i);
}
// 打印5个29