搞定JS作用域和执行上下文

1.作用域

举个简单的函数例子

function getNum(num){

  num =100;

  console.log(num);

}

getNum();

console.log(num);

 

结果是:100和Uncaught ReferenceError: num is not defined

 

虽然在函数中声明了一个隐式全局变量,可是函数的形参跟隐式全局变量的名字是相同的,而函数形参是个局部变量,而且是在隐式全局变量之前声明的,相当于

function getNum(   ){

        var  num;

  num =100;

  console.log(num);

}

所以在外面不能获取到内部的值。

 

光知道“javascript没有块级作用域是完全不够的,你需要知道的是——javascript除了全局作用域之外,只有函数可以创建的作用域作用域在函数定义时就已经确定。

所以,我们在声明变量时,全局代码要在代码前端声明,函数中要在函数体一开始就声明好。除了这两个地方,其他地方都不要出现变量声明。而且建议用var”形式

下面继续说作用域。作用域是一个很抽象的概念,类似于一个地盘

 

如上图,全局代码和fnbar两个函数都会形成一个作用域。而且,作用域有上下级的关系,上下级关系的确定就看函数是在哪个作用域下创建的。例如,fn作用域下创建了bar函数,那么“fn作用域就是“bar作用域的上级。

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。例如以上代码中,三个作用域下都声明了“a”这个变量,但是他们不会有冲突。各自的作用域下,用各自的“a”

 

 

2.上下文context

你在JS中找不到context。是因为上下文这个东西不是一个具体的东西,上下文在不同的地方表示不同的含义,要感性理解。

context其实说白了,和文章的上下文是一个意思,在通俗一点,我觉得叫环境更好。

 

在JS中,当浏览器打开时,首先会开辟形成一个顶层的栈内存,就是全局作用域,而对象是存储在中。

例如:

var obj = { foo:  5 };

上面的代码将一个对象赋值给变量obj。JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj。也就是说,变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。

对象的属性在全局作用域中需要地址索引间接读取,就是这个间接性导致对象和全局上下文相对独立,形成“对象上下文”。

JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

 

var f = function () {};

var obj = { f: f };

 

// 单独执行

f()

 

// obj 环境执行

obj.f()

JavaScript 允许在函数体内部,引用当前环境的其他变量。

 

var f = function () {

  console.log(x);

};

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。

 

现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

 

var f = function () {

  console.log(this.x);

}

上面代码中,函数体里面的this.x就是指当前运行环境的x。

 

​ ​​

3.执行上下文Execution Context

执行上下文其实就是“动态意义的”上下文。

3.1全局执行上下文

在执行全局代码前将window确定为全局执行上下文

 

在一段js代码拿过来真正一句一句运行之前,浏览器已经做了一些准备工作,其中就包括对变量的声明以及变量提升(此处重点不是变量提升,感兴趣的童鞋可以自行搜索学习),而不是赋值。变量赋值是在赋值语句执行的时候进行的。

 

准备工作中,需要注意函数表达式函数声明。虽然两者都很常用,但是这两者在准备工作”时,却是两种待遇。

对待函数表达式就像对待“ var a = 10 ”这样的变量一样,只是声明。而对待函数声明时,却把函数整个赋值了所以可以可直按调用

 

准备工作中完成了哪些工作:

  • 变量、函数表达式——变量声明,默认赋值为undefined
  • this——赋值(指向window);
  • 函数声明——赋值;

这三种数据的准备情况我们称之为执行上下文或者执行上下文环境

变量对象(variable object)

作用域链(scope chain)

this指针(this value)

它们影响着变量的解析变量作用域

 

3.2作用域链

前文第2节说到作用域在函数定义时就已经确定。由于在全局上下文中声明了许多函数,这些函数之间就产生了作用域链式关系。

在说作用域链之前, 先解释一下什么是自由变量

A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。如下图

函数有个倔强的特点:在函数被调用时要到创建这个函数的那个作用域中取变量值

如果找了,还没找到呢?

接着向上一层作用域找!

一直找到全局作用域为止。要是在全局作用域中都没有找到,那就是真的没有了。

这个一步一步的路线,我们称之为——作用域链。

 

3.3函数执行上下文

在调用函数,准备执行函数体之前,也会创建对应的函数执行上下文对象

函数执行上下文准备工作保存着函数执行所需的重要信息,其中有三个属性:

变量对象(variable object)

this指针(this value)

作用域链(scope chain)(函数内部嵌套函数)

它们影响着变量的解析变量作用域函数this的指向

​ 首先明确最重要的一点:当函数被调用的时候调用函数的那个对象会被传递到执行上下文中,成为this的值。

 

4.作用域和执行上下文关系

全局作用域和函数作用域是在全局执行上下文在做“准备工作”时产生的,是静态的不变的。

 

全局执行上下文只产生一次,函数执行上下文是在函数调用时候才会产生是动态的,多次调用产生多次,同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。

 

第一步,在加载程序时,已经确定了全局上下文环境,并随着程序的执行而对变量就行赋值。

第二步,程序执行到第27行,调用fn(10),此时生成此次调用fn函数时的上下文环境,压栈,并将此上下文环境设置为活动状态。

第三步,执行到第23行时,调用bar(100),生成此次调用的上下文环境,压栈,并设置为活动状态。

第四步,执行完第23行,bar(100)调用完成。则bar(100)上下文环境被销毁。接着执行第24行,调用bar(200),则又生成bar(200)的上下文环境,压栈,设置为活动状态。

第五步,执行完第24行,则bar(200)调用结束,其上下文环境被销毁。此时会回到fn(10)上下文环境,变为活动状态。

第六步,执行完第27行代码,fn(10)执行完成之后,fn(10)上下文环境被销毁,全局上下文环境又回到活动状态。

结束

由上述过程可以注意到,在bar作用域中出现了两个函数执行上下文环境,即同一个函数作用域下,不同的调用会产生不同的执行上下文环境。

 

5.执行上下文栈

由上述过程其实已经可以发现,执行上下文有类似栈数据结构的特点。没错!执行上下文确实有的概念。

执行上下文栈的创建过程如下

1.在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中(压栈)
4.在当前函数执行完后将栈顶的对象移除(出栈)
5.当所有的代码执行完后,中只剩下window

 

以上内容为本人参考以下文章,结合自己思考做的整理和修改,有错误之处欢迎在评论区指出!

 

深入理解javascript原型和闭包(完结) - 王福朋 - 博客园
https://www.cnblogs.com/wangfupeng1988/p/3977924.html

彻底理解js的执行上下文,以及变量对象 - 简书
https://www.jianshu.com/p/f8e628b5c312

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lvshuai666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值