本文是我学习JavaScript作用域整理的笔记,如有不对,请多指出。

作用域

一个变量的作用域是程序源代码中定义这个变量的区域。

而在ES5中只分为全局作用域和函数作用域,也就是说for,if,while等语句是不会创建作用域的。ES6(let,const)除外。

    //全局作用域
   var a = 123;
   function aa () {
       //局部作用域
       var b = 456;                            
   }

声明提前

JavaScript函数里声明的所有变量(但不涉及赋值)都被“提升”至函数体的顶部,在代码开始运行之前。这个特性被称为声明提前。

    var a = "g";
    function f() {
      console.log(a); //输出undefined
      var a = "l";
      console.log(a); //输出"l"
    } 

由于函数作用域的特性,局部变量在整个函数体始终是有定义的,也就是说,函数体的局部变量覆盖了同名全局变量。在函数体内,变量a被“提前”了,提前至函数体的顶部,所以第一次输出的是undefined,那时候还没赋值,但代码执行到var语句时候,局部变量才会被赋值。因此第二次输出则是“l”。此代码过程如下:

    var a = "g";
    function f() {
        var a; 
        console.log(a); //输出undefined
        a = "l";
        console.log(a); //输出"l"
   } 

因此一些程序员特意将变量声明放在函数体的顶部,而不是将声明靠近放在使用变量之处。

作用域链

先看一段简单代码,代码如下:

var name = "wythe";
function one () {
    console.log(name); //wythe
    var firend = "zero";
}
one();
console.log(firend); //报错

看到代码可知,name是在全局作用域中声明的全局变量,而firend则是在函数作用域中声明的局部变量。在执行时候你会发现函数作用域能够访问到在全局作用域中name这个变量,而全局作用域却不能访问到函数作用域的friend的变量,原因是作用域链!
作用域链的规则:
外部不能访问内部变量,内部可以访问外部变量
为什么会有这样规则?因为是执行环境所规定的。

执行环境定义了变量或函数有权访问其他数据,决定了它们的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的一个执行环境。在Web浏览器中,全局执行环境被认为是window对象。某个执行环境中所有所有代码执行完毕后,该环境被销毁,保存在其中的所有的变量和函数定义也随之销毁。

补充说明:需要了解一些概念,变量对象(Variable Object)、活动对象(Activation Object)、函数的属性[[scope]].

变量对象指的是变量对象(缩写为VO)是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的内容有:变量 (var, 变量声明)、函数声明和函数的形参。

执行上下文(执行环境):每次当控制器转到ECMAScript可执行代码的时候,即会进入到一个执行上下文。执行上下文(简称-EC)是ECMA-262标准里的一个抽象概念,用于同可执行代码(executable code)概念进行区分。

活动对象指的是由函数的运行期上下文(代码执行前)创建,在运行时可变,初始时只有 arguments 属性,通过变量的初始化,包含了局部变量、命名参数、 this 等

汤姆大叔深入理解JavaScript变量对象

函数属性[[scope]]指的是函数对象都有一个内部属性 [[scope]],函数被创建后,函数 [[scope]] 属性会被创建此函数的作用域中可访问的数据对象填充,是所有父变量对象的层级链。[[scope]] 在函数被创建时静态存储,永远不会改变,直至销毁。

作用域与作用域链

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将环境弹出,把控制权返回之前的执行环境。当代码在一个环境执行时候,会创建变量对象的一个作用域链(scope chain)。作用域的前端,始终都是当前执行的代码所在环境的变量对象。如何这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始只包含一个变量,即arguments对象(这个对象在全局环境是不存在)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含对象。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域中的最后一个对象。
根据这个概念图解上面代码:

图片描述

在函数one创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,当执行流执行到one()语句时,会创建函数one执行环境。将函数one执行环境。如果这个环境是函数,则创建一个活动对象,然后此对象会被推入作用域链的前端,当函数执行完毕后,活动对象也随之销毁。新的作用域链如下图所示:

图片描述

标识符解析是沿着作用域一级一级地搜素标识符的过程。搜素过程始终从作用域的前端开始,然后逐级地向后回溯,直到找到标识符为止,找不到,会导致错误发生。内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境中任何变量和函数。这些环境之间的联系是线性,有次序的。

这是初步了解作用域,如想更深入了解作用域,请看下面链接:
JavaScript作用域原理
JavaScript作用域链
由一道题图解JavaScript的作用域
或者看《JavaScript权威指南》和《JavaScript高级程序设计》


Wythehuang
117 声望6 粉丝