JavaScript中的执行环境和作用域链

前言

JS 中的执行环境和作用域链是非常重要的概念,它们是 JS 引擎在处理 JS 代码的时候对变量和函数的处理方式,这两个概念的正确理解能够帮助我们更好地理解和预测代码的行为。

执行环境

执行环境定义了变量或者函数有权访问的数据集合,每一个执行环境都有一个与之关联的变量对象,该执行环境中定义的所有变量和函数都保存在这个对象中。我们无法直接访问这个对象,这个对象只是在解析器处理数据的时候使用。

我们平时说的全局变量就是在最外围的一个执行环境中定义的变量,全局执行环境根据 ECMAScript 的不同实现而有不同的表示,在 Web 浏览器中,全局执行环境就是 window 对象,所有的全局变量和函数就是作为 window 对象的属性和方法创建的。在 nodejs 的实现中,全局执行环境就是global 对象。
除了全局执行环境,每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。也就是说某个执行环境中的代码全部执行完毕之后,该环境就被销毁,保存在其中的所有变量和函数定义也随之销毁,全局执行环境直到应用程序额推出——例如网页或浏览器被关闭时才被销毁。

作用域链

前面说到每个执行环境都有一个变量对象来保存环境中定义的变量和函数,环境是层层嵌套的,所以当代码进入到一个新的环境开始执行时,会创建变量对象的一个作用域链,把嵌套的执行环境之间的变量对象做一个有序的联系。作用域链最主要的作用是确保当前执行环境有权访问的变量和函数,并且有序地查找。在作用域链的最前端始终是当前正在执行的代码所处的执行环境的变量对象,如果这个环境是一个函数,就把函数的活动对象作为其变量对象,在函数中没有定义新的变量时,这个活动独享就是函数的 arguments 对象。作用域链的下一个变量都西昂来自于当前执行环境的包含环境,依次类推,逐层嵌套,知道全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个都对象。

当我们的代码在执行的时候,遇到的每一个标识符解析都会沿着作用域链一级一级地进行搜索,从作用域链的前端(当前执行环境的变量对象)逐级向后回溯,知道找到标识符为止,如果在作用域链上没有找到这个标识符,通常会导致错误。我们经常遇到的 Uncaught ReferenceError: x is not defined 就是这个错误在浏览器中的表现。

JS解释器在执行时会将变量和函数进行声明提前,在声明函数的时候,会给函数一个 [[scope]] 属性,这个属性中包含了当前函数所有包含环境的变量对象,也就是我们的函数在声明提前的时候就已经生成了他的包含环境的作用域链了,然后当函数执行的时候会把自己的 arguments 和内部定义的函数和变量打包成一个变量对象加到 scope chain 的最后。

函数参数也被当做变量来对待,因此起访问规则与执行环境中的其他变量相同。

作用域链的这种特性理解起来其实也是比较直观的,但是在实际的代码中由于情况非常多,有时候有些行为还是比较反直觉或者说容易产生误解的。比如下面的情况:

作用域链看的是函数定义的位置而不是执行的位置

var x = 10
bar()
function foo() {
 console.log(x)
}
function bar(){
 var x = 30
 foo()
}

在这个例子里面,可能会有人误以为 bar() 会输出 30,我们只要理解函数其实是保存在堆中,我们给函数命名只是一个指向函数堆中地址的一个引用,当我们执行函数的时候根据这个引用去堆中找对应的函数执行。所以无论我们在哪里执行函数,函数的位置都是不变的,我们看作用域链也是,我们确定作用域链不是看函数是在哪里执行,而是要看函数是在哪里定义,作用域链可以认为是函数声明时就已经生成了。

个人认为 ECMAScript 这样处理作用域链是为了作用域链能够保持不变而不用一直维护,并且根据环境的嵌套保持一致性。

闭包

除了全局执行环境的变量对象是始终存在的,其他局部函数的变量对象都只在函数的执行过程中存在,一般来讲,函数执行完毕之后,局部活动对象就被销毁了,内存中仅仅保存全局执行环境的变量对象,但是闭包的情况是不同的。

闭包指的是有权访问另一个函数作用域中的变量的函数,比如下面这样:

 function outer(){
   var scope = "outer";
   return function (){
    return scope;
   }
 }
var fn = outer();
fn();

在一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到他的作用域链中,因此在 outer 函数内部定义的匿名函数(我们下面把这个匿名函数称为 inner 函数)的作用域链中,实际上会包含外部函数 outer() 的活动对象,下图可以看书当代码执行时,outer inner 函数的作用域链。

当匿名函数从 outer() 中被返回后,inner() 函数仍然可以访问在 outer() 中定义的所有变量,也就是说,当 outer() 函数执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链依然在引用这个活动对象。换句话说,当 outer() 函数执行完毕返回后,其执行环境和作用域链都被销毁,但它的活动对象依然保存在内存中,如果匿名函数不销毁,则这个活动对象会一直存在于内存中。

js中的对象都是保存在堆中,我们在代码中写的都是对对象的引用,作用域链中也是,所以上面说的 outer() 函数执行完毕后作用域链被销毁但是对象还存在,其实销毁的只是引用, js 中的垃圾处理机制的一种策略是引用计数,当某个变量或对象的引用次数为 0 的时候内存会被收回。outer 函数的变量对象的引用有两个一个是 outer 的作用域链和匿名函数的作用域链,所以只要匿名函数不被销毁,这个引用就一直存在,outer() 的活动对象也会一直存在。

轮子哥在知乎给过一个比较容易理解的说法:“闭”的意思不是封闭内部状态,而是封闭外部状态,一个函数如何能够封闭外部状态呢,当外部状态的 scope 失效的时候,它自己还保留了一份。

由于闭包会携带包含它的函数的作用域,因此回避其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,只在必要的时候使用闭包。

总结

任何一种编程语言都有作用域的概念,我们的程序是围绕着变量操作的,那么在设计语言的时候,变量如何储存,储存到哪里,我们的程序如何找到对应的变量就是一个首先要解决的问题。而作用域就是语言设计者针对这个问题编写的一套设计良好的规则来存储并搜索对象,这就是作用域的概念。而 JS 中的这个规则就是作用域链,我们在编写程序的时候也需要知道我们的变量(以及函数)是如何储存,以及 JS 引擎在遇到标识符解析的时候是按照什么规则来搜索变量或者函数的,只有这样我们才能写出更可靠的代码。

以上就是JavaScript中的执行环境和作用域链的详细内容,更多关于JavaScript执行环境和作用域链的资料请关注我们其它相关文章!

(0)

相关推荐

  • js 函数的执行环境和作用域链的深入解析

    第一步. 定义后:每个已定义函数,都有一个内在属性[scope],其对应一个对象的列表,列表中的对象仅能内部访问. 例如:建立一个全局函数A,那么A的[Scope]内部属性中只包含一个全局对象(Global Object),而如果我们在A中创建一个新的函数B,那么B的[Scope]属性中就包含两个对象,函数A的Activation Object对象在前面,全局对象(Global Object)排在后面. 简而言之,一个函数的[Scope]属性中对象列表的顺序是上一层函数的Activation O

  • JavaScript执行环境及作用域链实例分析

    本文实例讲述了JavaScript执行环境及作用域链.分享给大家供大家参考,具体如下: 1.执行环境: 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中. 执行环境包括全局执行环境和函数执行环境. 全局执行环境是最外围的一个执行环境,在浏览器中,全局执行环境被认为是是window对象,所有全局变量和属性都是作为window对象的属性和方法创建的. 函数执行环境是指函数的执行环境,当执行流进入一个函数时,函数的环境会被推入一个环境栈中,在函数执行之后,栈将其环境

  • javascript基础进阶_深入剖析执行环境及作用域链

    执行环境 执行环境定义了变量或函数有权访问的其他函数,决定了他们各自的行为.每个执行环境都有一个与之关联的变量对象. 变量对象 环境中定义的所有变量和函数都保存在这个对象中. 全局执行环境 全局执行环境是最外围的一个执行环境.在web浏览器中,全局执行环境被认为是Window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的. 每个函数都有自己的执行环境. 作用域链 当代码在一个环境中执行,会创建变量对象的一个作用域链. 用途:保证对执行环境有权访问的所有变量和函数有序访问.

  • javascript作用域链与执行环境详解

    前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! 作用域.作用域链.执行环境.执行环境栈以及this的概念在javascript中非常重要,本人经常弄混淆,这里梳理一下: 局部作用域函数内部的区域,全局作用域就是window: 作用域链取决于函数被声明时的位置,解析标识符的时候就先找当前作用域,再向外查找,直到全局,这样一个顺序:和函数在哪里调用无关: 执行环境就是函数可访问的数据和变量的集合,也就是函数的作用域链上的所有数据和变量: 执行环境栈就是根

  • 深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解

    函数表达式 1.JavaScript中定义函数有2钟方法: 1-1.函数声明: 复制代码 代码如下: function funcName(arg1,arg2,arg3){  //函数体} ①name属性:可读取函数名.非标准,浏览器支持:FF.Chrome.safari.Opera. ②函数声明提升:指执行代码之前会先读取函数声明.即函数调用可置于函数声明之前. 1-2.函数表达式: 复制代码 代码如下: var funcName = function(arg1,arg2,arg3){  //函

  • 浅谈javascript中执行环境(作用域)与作用域链

    相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行环境分为全局执行环境和局部执行环境,其中局部执行环境我们又可以称之为函数执行环境.那么究竟什么使执行环境呢?通俗的说,执行环境即为代码执行时所处的环境.我们下来看一看如下代码,再进一步分析之. <script><br>var name="zhuzhenwei"; function changeName(){ if (name

  • JavaScript中的执行环境和作用域链

    前言 JS 中的执行环境和作用域链是非常重要的概念,它们是 JS 引擎在处理 JS 代码的时候对变量和函数的处理方式,这两个概念的正确理解能够帮助我们更好地理解和预测代码的行为. 执行环境 执行环境定义了变量或者函数有权访问的数据集合,每一个执行环境都有一个与之关联的变量对象,该执行环境中定义的所有变量和函数都保存在这个对象中.我们无法直接访问这个对象,这个对象只是在解析器处理数据的时候使用. 我们平时说的全局变量就是在最外围的一个执行环境中定义的变量,全局执行环境根据 ECMAScript 的

  • 谈一谈js中的执行环境及作用域

    最近在面试时被问到了对作用域链的理解,感觉当时回答的不是很好,今天就来说说js中的作用域链吧. 首先来说说js中的执行环境,所谓执行环境(有时也称环境)它是JavaScript中最为重要的一个概念.执行环境定义了变量或函数有权访问的其他数据 ,决定了它们各自的行为.而每个执行环境都有一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象中. 理解了执行环境,现在就看看什么是作用域链吧.每个函数都有自己的执行环境,当代码在执行环境中执行时,就会创建变量对象的作用域链.作用域链保证了对执

  • javascript中关于执行环境的杂谈

    --这就要从JAVASCRIPT的解释器开始说起了:每当JAVASCRIPT解释器开始执行一个函数的时候,都会创建一个执行环境,并且还会产生一个和这个函数息息相关的变量对象,在这个执行环境中定义的一切变量或者函数都会被他保存起来.但是他就像有关部门,可能和你息息相关,但是你永远找不到他的身影,你是无法调用这个对象的,但是JAVASCRIPT解析器处理数据时便会调用到它. 点题就到此为止了,让我们真刀真枪来说说执行环境吧.先从全局执行环境开始,他处于整个执行环境的最外面,并且是作为window对象

  • 浅谈JavaScript 执行环境、作用域及垃圾回收

    执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为.每个执行环境都有一个与之关联的变量对象. 全局执行环境是最外围的一个执行环境.根据JavaScript实现所在的宿主环境不同,表示执行环境的对象也不一样.在Web浏览器中,全局执行环境被认为是window对象.因此,所有的全局变量和函数都是作为window对象的属性和方法创建的. 变量对象:环境中定义的所有变量和函数都保存在这个对象中. 作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链.作用域链的用途是保证对执行环

  • javascript执行环境及作用域详解

    最近在重读<javascript高级程序设计3>,觉得应该写一些博客记录一下学习的一些知识,不然都忘光啦.今天要总结的是js执行环境和作用域. 首先来说一下执行环境 一.执行环境         书上概念,执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为.每个执行环境都有一个与之关联的变量对象.环境中定义的所有变量和函数都保存在这个对象中.虽然我们在编写代码的时候无法访问这个对象,但解析器在处理数据时会在后台用到它. 执行环境是一个概念,一种机制,它定义了变量或函数是否有权访

  • 老生常谈原生JS执行环境与作用域

    首先,我们要知道执行环境和作用域是两个完全不同的概念. 函数的每次调用都有与之紧密相关的作用域和执行环境.从根本上来说,作用域是基于函数的,而执行环境是基于对象的(例如:全局执行环境即window对象). 换句话说,作用域涉及到所被调用函数中的变量访问,并且不同的调用场景是不一样的.执行环境始终是this关键字的值,它是拥有当前所执行代码的对象的引用.每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在

随机推荐