执行上下文和作用域

执行上下文和作用域

什么是执行上下文。

JavaScript在执行语句前,是需要经过了一些列的“准备”的, 为代码执行创造一个执行上下文,就好像老师上课前要有,教室一样
在这里插入图片描述
本节课重点放在文本环境
文本环境相当于我们教室的点名册,为上课做准备。文本环境为代码执行做准备。
在这里插入图片描述
代码在执行之前,变量名,函数名,类名,登记在文本环境里,在js执行的时候就可以在文本环境中查找变量,函数,类
那么js在哪里去找执行上下文呢?(只有找到了执行上下文才能找到文本环境里的变量,函数,类,)
答案就是在执行栈里找到执行上下文
在这里插入图片描述

4种情况会创建新的执行上下文

1.进入全局代码
2.进入function函数体代码
3.进入eval函数参数指定的代码
4.进入module代码

本节课重点介绍

  • 进入全局代码
  • 进入function函数体代码

执行上下文做了那些准备?

图A-5 图A-5

名词解释:
顶级函数声明

  • 就是不包含在大括号内的函数声明,
  • 更不是函数表达式
    名字重复处理
    1. let const class 声明的名字之间不能重复(重复报错)
    1. let const class 和var function的名字不能重复重复报错)
    1. 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

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值