前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于js作用域问题详解

关于js作用域问题详解

作者头像
IT人一直在路上
发布2019-09-18 10:57:09
1.8K0
发布2019-09-18 10:57:09
举报
文章被收录于专栏:前端重点笔记前端重点笔记

执行上下文

函数表达式和函数声明

代码语言:javascript
复制
1.
console.log(a); // ReferenceError: a is not defined
// ReferenceError(引用错误)对象表明一个不存在的变量被引用。
2.
console.log(a); // undefined
var a;
3.
console.log(a); // undefined
var a = 10;
4.
var a = 10;
console.log(a); // 10

在一段js代码拿过来真正一句一句运行之前,浏览器已经做了一些“准备工作”,其中就包括对变量的声明,而不是赋值。变量赋值是在赋值语句执行的时候进行的。可用下图模拟:第一句报错,a未定义,很正常。第二句、第三句输出都是undefined,说明浏览器在执行console.log(a)时,已经知道了a是undefined,但却不知道a是10(第三句中)。

context
context

接下来的这段代码需要注意代码注释中的两个名词——“函数表达式”和“函数声明”。虽然两者都很常用,但是这两者在“准备工作”时,却是两种待遇。

“准备工作”

代码语言:javascript
复制
1.
console.log(a); // ReferenceError: a is not defined
// ReferenceError(引用错误)对象表明一个不存在的变量被引用。
2.
console.log(a); // undefined
var a;
3.
console.log(a); // undefined
var a = 10;
4.
var a = 10;
console.log(a); // 10

在“准备工作”中,对待函数表达式就像对待“ var a = 10 ”这样的变量一样,只是声明。看以上代码。“函数声明”时我们看到了第二种情况,而“函数表达式”时我们看到了第一种情况。

而对待函数声明时,却把函数整个赋值了。

总结一下,在“准备工作”中完成了哪些工作:

  1. 变量、函数表达式——变量声明,默认赋值为undefined;
  2. this——赋值;
  3. 函数声明——把函数整个赋值;

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

代码语言:javascript
复制
function fn(x) {
console.log(arguments); //Arguments { 0: 10, 等 2 项… }
console.log(x); //10
}
fn(10);

以上代码展示了在函数体的语句执行之前,arguments变量和函数的参数都已经被赋值。从这里可以看出,函数每被调用一次,都会产生一个新的执行上下文环境。因为不同的调用可能就会有不同的参数。

另外一点不同在于,函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域。用一个例子说明一下:

代码语言:javascript
复制
var a = 10;
 
function fn() {
console.log(a); //a是自由变量
//函数创建时,就确定了a要取值的作用域
}
 
function bar() {
var a = 20;
fn(); //打印10不是20
}
bar(fn); //10

执行上下文栈给执行上下文环境下一个通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。

执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境。当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。

其实这是一个压栈出栈的过程——执行上下文栈。

js-context
js-context
代码语言:javascript
复制
1 var a = 10, //1.进入全局上下文环境
2 fn,
3 bar = function(x) {
4 var b = 5;
5 fn(x + b); //3.进入fn函数上下文环境
6 };
7
8 fn = function(y) {
9 var c = 5;
10 console.log(y + c);
11 }
12
13 bar(10); //2.进入bar函数上下文环境

在执行代码之前,首先将创建全局上下文环境。

全局

上下文环境

a

undefined

fn

undefined

bar

undefined

this

window

然后是代码执行。代码执行到第12行之前,上下文环境中的变量都在执行过程中被赋值。

全局

上下文环境

a

10

fn

function

bar

function

this

window

执行到第13行,调用bar函数。

跳转到bar函数内部,执行函数体语句之前,会创建一个新的执行上下文环境。 全局 | 上下文环境 —|— b | undefined x | 10 arguments | [10] this | window

并将这个执行上下文环境压栈,设置为活动状态。

js-context
js-context

执行到第5行,又调用了fn函数。进入fn函数,在执行函数体语句之前,会创建fn函数的执行上下文环境,并压栈,设置为活动状态。

js-context
js-context

待第5行执行完毕,即fn函数执行完毕后,此次调用fn所生成的上下文环境出栈,并且被销毁(已经用完了,就要及时销毁,释放内存)。

js-context
js-context

同理,待第13行执行完毕,即bar函数执行完毕后,调用bar函数所生成的上下文环境出栈,并且被销毁(已经用完了,就要及时销毁,释放内存)。

js-context
js-context

好了,给大家介绍了一段简短代码的执行上下文环境的变化过程,一个完整的闭环。其中上下文环境的变量赋值过程我省略了许多,因为那些并不难,一看就知道。

作用域

基础认识

“javascript没有块级作用域”。所谓“块”,就是大括号“{}”中间的语句。

比如一个if语句

代码语言:javascript
复制
var i = 10;
if (i > 1) {
var name = "yzh";
}
console.log(name); //yzh
代码语言:javascript
复制
for (var i = 0; i < 10; i++) {
 
}
console.log(i); //10

for语句

我们在编写代码的时候,不要在“块”里面声明变量,要在代码的一开始就声明好了。以避免发生歧义

代码语言:javascript
复制
var i;
for (i = 0; i < 10; i++) {
 
}
console.log(i);

我们在声明变量时,全局代码要在代码前端声明,函数中要在函数体一开始就声明好。除了这两个地方,其他地方都不要出现变量声明。而且建议用“单var”形式你光知道“javascript没有块级作用域”是完全不够的,你需要知道的是——javascript除了全局作用域之外,只有函数可以创建的作用域。

概念

js-context
js-context

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

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突

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

作用域和上下文环境

js-context
js-context

如上图,我们在上文中已经介绍了,除了全局作用域之外

每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时确定。

下面我们将按照程序执行的顺序,一步一步把各个上下文环境加上

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

js-context
js-context

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

js-context
js-context

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

js-context
js-context

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

js-context
js-context

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

js-context
js-context

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

js-context
js-context

最后我们可以把以上这几个图片连接起来看看。

js-context
js-context

作用域只是一个“地盘”,一个抽象的概念,其中没有变量。要通过作用域对应的执行上下文环境来获取变量的值。

同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。所以,作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。

如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。

自由变量

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

代码语言:javascript
复制
var x = 50;
 
function fn() {
var b = 20;
console.log(x + b);
}
fn(); //70
代码语言:javascript
复制
var x = 50;
 
function fn() {
console.log(x);
}
 
function show(f) {
var x = 20;
(function() {
f(); //50 不是20
})();
}
show(fn); //50 不是20

在调用fn()函数时,函数体中第6行。取b的值就直接可以在fn作用域中取,因为b就是在这里定义的。而取x的值时,就需要到另一个作用域中取。到哪个作用域中取呢? 有人说过要到父作用域中取,其实有时候这种解释会产生歧义 例如:

不要在用以上说法了。相比而言,用这句话描述会更加贴切——要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,切记切记——其实这就是所谓的“静态作用域”。

对于本文第一段代码,在fn函数中,取自由变量x的值时,要到哪个作用域中取?——要到创建fn函数的那个作用域中取——无论fn函数将在哪里调用

上面描述的只是跨一步作用域去寻找。

如果跨了一步,还没找到呢?——接着跨!——一直跨到全局作用域为止。要是在全局作用域中都没有找到,那就是真的没有了。

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

全局环境

changeColor()的局部环境

swapColors()的局部环境

变量color

变量anotherColor

变量tempColor

函数changeColor()

函数swapColors()

以上代码共涉及3个执行环境:全局环境、changeColor()的局部环境和swapColors()的局部环境。

内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。

每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境

最后这个例子是从书上找到的,比较经典和简单。

代码语言:javascript
复制
var color = "blue";
 
function changeColor() {
var anotherColor = "red";
 
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
 
//这里可以访问color,anotherColor和tempColor
}
 
//这里可以访问color和anotherColor,但不能访问tempColor 
swapColors();
}
 
changeColor(); //注释后alert显示为blue
 
//这里只能访问color
alert("Color is now " + color); //red
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-12-24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 执行上下文
    • 函数表达式和函数声明
      • “准备工作”
      • 作用域
        • 基础认识
          • 概念
          • 作用域和上下文环境
            • 自由变量
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档