JavaScript中的上下文环境

949 阅读7分钟

1、什么是上下文

上下文(Context)是程序运行的环境,在上下文中存储了一段程序运行时所需要的全部数据。在面向对象的编程语言中,上下文通常是一个对象,所以也被称为上下文对象。

在之前的课程中我们讲到过,程序中的变量存储在栈区,准确的说变量存储在了上下文对象中,而上下文对象保存在了栈中。开始执行一段程序时,它的上下文对象就会被创建,并被推入栈中(入栈);程序执行完成时,它的上下文对象就会被销毁,并从栈顶被推出(出栈)。

img

栈结构是一种先进先出的数据存储结构,通过栈这种特殊的数据结构可以确保程序中的变量在被使用时满足就近原则,避免数据混乱的问题,接下来我们就详细的了解,JS是如何利用上下文对象和栈来达到这个目的的。

img

小结:

上下文对象在一段程序执行之前创建

创建好上下文对象之后,会将该对象压入栈中

在程序执行的过程中,js总会从栈顶查找所需的数据

当程序执行结束,销毁上下文并出栈

2、上下文的分类

首先上下文对象分为两类,一种是全局上下文对象,一种是函数上下文对象

全局上下文对象是在开始执行一段javascript代码时所创建的上下文对象,在html环境中,该上下文对象就是window对象。在node环境中为global对象。创建完上下文对象之后,该对象会入栈。全局上下文对象有且只有一个,只有当浏览器关闭时,全局上下文对象才会出栈。

函数上下文对象是在一个函数开始执行时所创建的上下文对象,创建完该对象以后,该对象同样的会入栈,当函数执行完毕,函数上下文对象出栈。每一次函数的调用都会创建新的函数上下文对象并入栈,哪怕是同一个函数的多次调用依然如此。

在秩序执行过程中所需要的数据,都会从栈顶的上下文对象中获取。

请看下面一段代码:

<Script>
//1.全局上下文对象入栈
var v=10;
console.log(v);//2.从栈顶的上下文中获取数据v
function f1(){
    var v1=1;
    console.log(v1);//4.从栈顶的上下文中获取数据v1
    f2();//5.f2函数上下文入栈
    //8.f1函数上下文出战
}
function f2(){
    var v2=2;
    console.log(v2);//6.从栈顶的上下文中获取数据v2
    //7.f2函数上下文出栈
}
f1();//3.f1函数上下文入栈
</script>

现场图示:

img

3、上下文对象的创建过程

思考:为什么在函数内可以使用全局变量呢?var变量提升是怎么造成的?函数定义的不同方式有何异同?这些问题的答案都在这个上下文对象创建过程中。 上下文对象在创建时,会在内部创建两个对象:词法环境对象和变量环境对象。 上下文对象结构:

Context={
    词法环境对象:{
        
    },
    变量环境对象:{
    
	}
}

在词法环境对象中存储所有以let、const声明的变量以及所有的函数。而在变量环境对象中只存储以var声明的所有变量。值得注意的是由于函数具备参数,所以在函数的上下文对象的词法环境对象中还存储了一个arguments对象用于存储参数数据。 例如:如下代码

var v1=10;
let v2=20;
function f(num){
    var v3=30;
    let v4=40;
    function f1(){

    }
}
f(10);

上下文结构如下: 全局上下文对象结构:

GlobalContext={
    词法环境对象:{
        v2:值,
        f:值
    },
    变量环境对象:{
    	v1:值
	}
}

函数上下文对象:

FunctionContext={
    词法环境对象:{
        v4:值,
        f1:值,
        arguments:[参数]
    },
    变量环境对象:{
    	v3:值
	}
}

现场图示:

img

上下文对象在创建的过程中将变量和函数数据存储在了自己内部,那么此时各种不同的变量和函数的值是什么呢?JS针对不同的变量和函数采用了不同的方式来处理。

let、const在上下文对象的创建阶段不会被初始化,在代码执行阶段才会被赋值var在上下文对象的创建阶段会被初始化为undefined。 表达式函数如果用let声明则不会被初始化,表达式函数如果用var生命则被初始化为undefined。 声明式函数在对象的创建阶段会被赋值为函数本身 函数的参数在创建阶段已经被赋值为实参 也就是说上面的代码对应的上下文对象,在上下文对象的创建阶段结构如下: 全局上下文对象结构:

GlobalContext={
    词法环境对象:{
        v2:未初始化,
        f:function f(){}
    },
    变量环境对象:{
    	v1:undefined
	}
}

函数上下文对象:

FunctionContext={
    词法环境对象:{
        v4:未初始化,
        f1:function f1(){},
        arguments:[10]
    },
    变量环境对象:{
    	v3:undefined
	}
}

判断如下代码执行结果:

console.log(a);
var a=10;

输出结果:undefined

原因:创建上下文对象的同时,已经对变量a进行了初始化并赋值为undefined。执行代码时从栈顶的上下文对象中找a,自然值为undefined。

console.log(a);
let a=10;

输出结果:img

原因:创建上下文对象的同时,let定义的变量不会被初始化。执行代码时从栈顶的上下文对象中找a,未初始化报错。 正因为这个原因,该程序一直执行到let a之前的部分,都是无法使用变量a的,这种情况就是暂时性死区。

f();
function f(){
    console.log(1);
}

输出结果:1

原因:创建上下文对象的同时,针对声明式函数已经将其初始化并赋值为函数本身。所以在执行f函数时可以正常调用。

c();
var c=function(){
    console.log(1);
}

输出结果:img

原因:创建上下文对象的同时,针对表达式函数的处理方式取决于前面变量的关键字,如果变量关键字为var,则会初始化并赋值为undefined,但是undefined并不是一个函数,所以报错。

f();
let f=function(){
    console.log(1);
}

输出结果:img

原因:创建上下文对象的同时,针对表达式函数的处理方式取决于前面变量的关键字,如果变量关键字为let, 则不会进行初始化,报错。

4、作用域与作用域链

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。从另一个角度讲就是指当前程序在执行时处于栈顶的上下文对象中是否能查找到该数据,那么如果在当前上下文对象中没有找到该数据怎么办呢?在上下文对象的创建过程中,会在词法环境对象和变量环境对象中定义一个属性(例如:father),该属性的值为函数定义时所在的上下文对象,这个上下文一定是它的上级上下文对象。然后在查找数据时,如果在当前上下文对象中没有找到该数据,则会通过father找到它的上级上下文对象,以此类推一直查找到全局上下文对象为止。这些上下文对象一起构成了一个作用域链条,它被称为作用域链。

var a=100;
function f(){
    var a=10;
    console.log(a);
    function f1(){
        console.log(a);
    }
    f1();
};
f();

现场图示: img

无论你在学习上有任何问题,重庆蜗牛学院欢迎你前来咨询,联系QQ:296799112