了解JavaScript的执行环境及作用域

一、执行环境

执行环境定义了变量或函数有权访问的其他数据,决定了它们的各自行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们无法访问这个对象,但是解析器在处理数据时会在后台使用它。

全局执行环境是最外围的一个环境,根据JavaScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。 某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出,比如关闭网页和浏览器时才会被销毁)。

每个函数都有自己的执行环节。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。函数执行完之后,栈将其环境弹出,把控制权返回给之前的执行环境。

二、作用域

当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链的下一个变量对象来自于包含(外部)环境,在下一个变量外部环境对象则来自下一个包含环境,这样一直延续到全局执行环境,全局执行环境的对象始终都是作用域链中的最后一个对象。

标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级向后回溯,直至找到标识符为止,比如下面的代码

javascript 复制代码
var color = "blue";   
function changeColor(){
    if (color === "blue"){
        color = "red";
    } else {
        color = "blue";
    }
}      
changeColor();
alert("Color is now " + color);

以上示例中,函数changeColor()的作用域包含两个对象,它自己的变量对象(其中定义着arguments对象)和全局环境的变量对象(color)。可以在函数内部访问变量color,就是因为可以在这个作用域链中找到它。

此外,在局部作用域中定义的变量可以在局部环境中与全局变量互换使用,比如下面的例子

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();    //这里只能访问color

延长作用域链

虽然执行环境的类型总共只有两种------全局和局部(函数),但还是有其他办法来延长作用域链。有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。在两种情况下会发生这种现象。具体来说,就是当执行流进入下列任何一个语句时,作用域链就会得到加长:

try-catch 语句的 catch 块。

with 语句。

这两个语句都会在作用域链的前端添加一个变量对象。对WITH语句来说,将会指定的对象添加到作用域链中。对catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

javascript 复制代码
function builderUrl(){
     var qs="?debug=true";
     with(location){
     var url = href + qs;   
     }
    return url;
}

以上示例中,with语句接受的是location对象,因此其变量对象中就包含了Location对象的所有属性和方法,而这个变量对象被添加到了作用域链的前端。builderUrl()函数中定义了一个变量qs。当在with语句中引用变量href时(实际引用的是location.href),可以在当前执行环境的变量对象中找到。当引用变量qs时,引用的则是在buildUrl()中定义的那个变量,而该变量位于函数环境的变量对象中。至于with语句内部,则定义了一个名为url的变量,因而url就成了函数执行环节的一个部分,所以可以作为函数的值被返回。

没有块级作用域

JavaScript没有块级作用域,而在其他语言中,由大括号封闭的代码块都有自己的作用域,因此支持根据条件定义变量。

javascript 复制代码
if(true){
    var color="blue";
}
alert(color);    //if语句执行完后,变量color依然能被访问
javascript 复制代码
for(var i=0;i<10;i++){
    doSomething(i);
}
alert(i);

对于有块级作用域的语言来说,for语句初始化变量的表达式所定义的变量,只会存在于循环的环境之中。而对于JavaScript来说,由for语句创建的变量i即使在for循环结束后,也依旧会存在于循环外部的执行环境中。

但是在函数内部用var声明的变量,在函数外面是访问不到的,比如

javascript 复制代码
function a(){
  var b=1;
}
console.log(b)    //这一行会导致错误,因为b只能在函数a当中访问

var和let的区别

前面的文章讲过,在JavaScript中,除了可以用var声明变量之外,还可以用let声明变量,与var不同,使用let关键字在块作用域中声明的变量是无法被块作用域之外访问的,详情请参见博文:

JavaScript中的变量、关键字、保留字-CSDN博客

本文部分内容参考《JavaScript高级编程》

相关推荐
getaxiosluo2 分钟前
vue3使用element-plus,树组件el-tree增加引导线
前端·javascript·vue.js·elementui·css3·element-plus
gsgbgxp6 分钟前
C++类中的const成员变量和const成员函数
开发语言·c++·算法
YUJIANYUE10 分钟前
6KBhtm+js实现提交名单随机抽取功能适用活动或课堂随机点名
前端·javascript·css
小码哥呀17 分钟前
RabbitMQ队列详细属性(重要)
javascript·rabbitmq·ruby
皮克斯的进化之路21 分钟前
RabbitMQ的死信队列
java·开发语言
唐僧洗头爱飘柔952743 分钟前
(Go语言)初上手Go?本篇文章帮拿捏Go的数据类型!
开发语言·golang·go语言·go数据类型·go开发·go初上手
Bug从此不上门1 小时前
Nuxt3之使用lighthouse性能测试及性能优化实操
前端·javascript·性能优化·vue·fetch·nuxt3
唐僧洗头爱飘柔95271 小时前
(Go基础)变量与常量?字面量与变量的较量!
开发语言·后端·golang·go·go语言初上手
·云扬·1 小时前
Lambda 表达式详解
java·开发语言·笔记·学习·1024程序员节
linhhanpy1 小时前
自制操作系统(九、操作系统完整实现)
c语言·开发语言·汇编·c++·操作系统·自制操作系统