JavaScript——作用域与作用域链

75 阅读4分钟

作用域

什么是作用域

作用域:规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。换句话说,作用域决定了代码区块中变量和其他资源的可见性,如下面这个例子:

function checkscope(){
    var scope = "local scope";
}
console.log(scope);//Uncaught ReferenceError: scope is not defined

我们在checkscope函数作用域中定义了scope变量,没有在全局作用域中定义它,函数作用域规定了scope变量只能在函数checkscope()中使用,当在全局作用域中去调用它,就会报错。

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

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    console.log(scope);//local scope
}
checkscope();
console.log(scope);//global scope

我们在全局作用域和函数作用域checkscope中都定义了scope变量,但在不同作用域下的打印是不同的;

作用域分类

作用域分为三类:全局作用域、函数作用域(局部作用域)、块级作用域

全局作用域

直接编写在 script 标签之中的JS代码,都是全局作用域;

或者是一个单独的 JS 文件中的。

全局作用域在页面打开时创建,页面关闭时销毁

在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:

  • 1.window对象上属性或方法
console.log(window);

image.png

  • 2.最外层函数或最外层函数外定义的变量
var  aa = '最外层函数外定义的变量';
function fun() {
    var aa1 = '最外层函数内定义的变量';
}
console.log(aa);//最外层函数外定义的变量
console.log(fun);
console.log(aa1);

image.png

  • 3.未声明直接赋值的变量
console.log(window);
function fun() {
    aa = '未定义直接赋值的变量';
    var aa1 = '已定义且赋值的变量'
}
fun();
console.log(aa);//未定义直接赋值的变量
console.log(aa1);//Uncaught ReferenceError: aa1 is not defined

全局作用域有个弊端:如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样就会 污染全局命名空间, 容易引起命名冲突

函数作用域(局部作用域)

javascript除了全局作用域之外,只有函数可以创建的作用域

在函数内部就是局部作用域,这个代码的名字只在函数的内部起作用

调用函数时创建函数作用域,函数执行完毕之后,函数作用域销毁;

每调用一次函数就会创建一个新的函数作用域,它们之间是相互独立的。

作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行,如下:

function foo(a) {
    var b = a * 2;
    function bar(c) {
        console.log( a, b, c );
    }
    bar( b * 3 );
}
foo( 2 ); // 2, 4, 12

在这个例子中有三个逐级嵌套的作用域。为了帮助理解,可以将它们想象成几个逐级包含的气泡。

image.png

  • 第一个泡泡包含着整个全局作用域,其中只有一个标识符:foo。

  • 第二个泡泡包含着 foo 所创建的作用域,其中有三个标识符:a、bar 和 b。

  • 第三个泡泡包含着 bar 所创建的作用域,其中只有一个标识符:c。

作用域气泡由其对应的作用域块代码写在哪里决定,它们是逐级包含的

块级作用域

所谓“块”,就是大括号“{}”中间的语句,例如if语句和for语句,和函数不同,它们不会创建一个新的作用域

if (true){
    //do something
}
for (var i=0;i<3;i++){
    //do something
}

ES6 之前 JavaScript 是没有块级作用域的,ES6之后,提供了‘块级作用域’,可通过新增命令letconst来体现,所声明的变量在指定块的作用域外无法被访问,

{
    let aa = '我是块级作用域中的变量';
    console.log(aa);//我是块级作用域中的变量
}
console.log(aa);//Uncaught ReferenceError: aa is not defined

块级作用域有如下特点:

  1. 声明的变量不会提升至顶部
console.log(aa);//undefind
console.log(aa1);//Uncaught ReferenceError: aa1 is not defined
var aa = '我是使用var声明的变量';
let aa1 = '我是使用let声明的变量';
  1. 禁止重复声明
var aa = '我是使用var声明的变量,第一次声明';
var aa = '我是使用var声明的变量,第二次声明'
let aa1 = '我是使用let声明的变量,第一次声明';
let aa1 = '我是使用let声明的变量,第二次声明';
//Uncaught SyntaxError: Identifier 'aa1' has already been declared 

作用域链

从当前作用域开始一层层往上查找某个变量,如果找到全局作用域还没找到,就放弃寻找 。这种层级关系就是作用域链。(在词法层面上,作用域链是由多个执行上下文的变量对象构成的链表 【即:当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。所以,作用域链其实是一个包含指针的列表,每个指针分别指向一个变量对象】)

在沿着作用域链查找变量的过程中要特别注意一下自由变量,那什么是自由变量呢?

自由变量: 在函数中使用的既不是函数参数也不是函数局部变量的变量称作自由变量

注:在JavaScript中,根据作用域的不同,变量可以分为两种:全局变量 和 局部变量,在全局作用域中的变量称为全局变量,而在函数作用域中的变量作为局部变量

var x = 10
function fn() {
    console.log(x);//x是一个自由变量
}
function show(f) {
    var x = 20;
    (function() {
        f() //10,而不是20
    })();//(function)()为立即执行语法
}
show(fn);

如上程序中,在调用fn()函数时,x变量既不是fn函数的参数也不是fn函数的局部变量,则x是fn的自由变量,那自由变量的值到哪个作用域中去取呢?x的取值值20,还是10呢?

函数的作用域在函数定义的时候就决定了,fn是在全局作用域中定义的,而不是在函数作用域show中定义的,所以,x的取值是全局作用域中的值,即为10,

参考文章