前言

这次简单聊聊JS中的三个比较相关的概念:

  • 作用域
  • 闭包
  • 变量提升

 

 

1、作用域

什么是作用域

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。 简单来讲,作用域就是一个变量/函数/对象的的作用范围;作用域决定了代码区块中变量和其他资源的可见性。

 

目的:为了提高程序的可靠性,同时减少命名冲突!

 

举例:

    function Fun() {
    var aWord = "内层变量2";
    }

    Fun(); //要先执行这个函数,否则根本不知道里面是啥
    console.log(aWord); // Uncaught ReferenceError: aWord is not defined

上面这个例子就可以清晰了解到作用域的概念:变量aWord并没有在全局作用域下声明,因此,在全局作用域下取值会报错。

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

 

作用域分类

在ES6之前,JS 没有块级作用域,只有:

  • 全局作用域 --> 在代码中任何地方都能访问到
  • 函数作用域(局部作用域) --> 只作用于函数内的代码环境

 

而ES6 的到来,为我们提供了:

  • 块级作用域 --> 在函数内部 / 在代码块内部{}

可通过新增命令 let 和 const 来体现。

 

作用域的访问关系

在内部作用域中可以访问到外部作用域的变量,在外部作用域中无法访问到内部作用域的变量。

举例:

    var a = 'aaa';
    function foo() {
        var b = 'bbb';
        console.log(a); // 打印结果:aaa。说明 内层作用域 可以访问 外层作用域 里的变量
    }

    foo();
    console.log(b); // 报错:Uncaught ReferenceError: b is not defined。说明 外层作用域 无法访问 内层作用域 里的变量

 

变量的作用域

根据作用域的不同,变量可以分为两类:

  • 全局变量
  • 局部变量

 

全局变量

  • 在全局作用域下声明的变量,叫「全局变量」。在全局作用域的任何一地方,都可以访问这个变量。
  • 在全局作用域下,使用 var 声明的变量是全局变量
  • 特殊情况:在函数内 不使用 var 声明的变量也是全局变量(不推荐)==> 因为没有任何声明的变量,实际上是window的属性,所有作用于全局!

 

局部变量

  • 定义在函数作用域的变量,叫「局部变量」
  • 在函数内部,使用 var 声明的变量是局部变量
  • 函数的形参也是属于局部变量

举例:

    var a = 100; // 全局作用域的变量a, 也是window的属性(即 == window.a)

    function fun1(b, c){
        //形参的 b,c 也是局部变量;等同于如下表达:
        // var b;
        // var c;
        var d = 200;   //局部变量
        e = 300;       //没有用 var 声明,为全局变量

        console.log(a) //打印: 100;
    }

    console.log(d); //错误! (as d是局部变量!)
    console.log(e); //打印: 300

 

Remarks: 值得注意的是,局部作用域一定是在函数内部的! 块语句({}中间的语句),如 if 和 switch条件语句或 for 和 while 循环语句,不像函数,它们不会创建一个新的作用域

举例:

    if (true) {
    // 'if' 条件语句块不会创建一个新的作用域
    var name = "Hammad"; // name 依然在全局作用域中
    }
    console.log(name); // logs 'Hammad'

 

执行效率:

  • 全局变量:只有浏览器关闭时才会被销毁,比较占内存
  • 局部变量:当其所在的代码块运行结束后,就会被销毁,比较节约内存空间。

 

作用域链

访问顺序:

  1. 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用就近原则)。
  2. 如果没有则向上一级作用域中寻找,直到找到全局作用域
  3. 如果全局作用域中依然没有找到,则会报错 ReferenceError。

在函数中要访问全局变量可以使用window对象。 (比如说,全局作用域和函数作用域都定义了变量a,如果想访问全局变量,可以使用window.a

 

 

2.1、变量声明提前(变量提升)

  • 使用var关键字声明的变量( 比如 var a = 1)

它会在所有的代码执行之前被声明(但是不会赋值) 但是如果声明变量时不用var关键字(比如直接写a = 1),则变量不会被声明提前

举例1:

    console.log(a); // undefined
    var a = 100;

变量 a 在被创建前调用,因为用了var关键字来声明,所以它会先被声明(但不会赋值),所以可见,代码不会报错,只是undefined!

 

举例2:

    console.log(b); //报错!
    b = 100;

这里由于变量 b 是直接赋值的,没用 var 进行声明,因此它不会变量提升,从而报错!!

 

举例3:

    foo();

    function foo() {
        if (false) {
            var i = 123;
        }
        console.log(i);
    }

打印结果:undefined。注意,打印结果并没有报错,而是 undefined。这个例子,再次说明了:变量 i 在函数执行前,就被提前声明了,只是尚未被赋值。

 

例3中, if(false)里面的代码虽然不会被执行,但是整个代码有解析的环节,解析的时候就已经把 变量 i 给提前声明了

结论: 由于JS中存在变量提升的现象,那么,在实战开发中,为了避免出错,建议:先声明变量,然后再使用

 

 

2.2、函数提升

  • 函数声明:function foo(){ }

使用函数声明的形式创建的函数function foo(){},会被声明提前。

也就是说,整个函数会在所有的代码执行之前就被创建完成。所以,在代码顺序里,我们可以先调用函数,再定义函数。

举例:

    fn1(); //正常执行

    function fn1() {
        console.log('我是函数 fn1');
    }

fn1() 依旧可以正常调用执行! 即使其创建代码在其后,但整个代码执行顺序实际上是先执行function的(先创建了fn1),之后才执行的fn1()!

 

除了函数声明,其他方式都不会提前声明! 如:函数表达式 创建的函数 var foo = function(){},不会被声明提前,所以不能在声明前调用。

很好理解,因为此时foo被声明了(这里只是变量声明),且为undefined,并没有把 function(){} 赋值给 foo。

举例:

    fn2(); //报错

    var fn2 = function(){
        alert('hi');
    };

 

 

3、闭包

现在我们知道,变量分为全局和局部变量(函数内部),而由于局部变量只作用域函数内部,外部无法访问。 但是,如果现在有一个需求,就是想要在函数外部访问函数内的局部变量,那要怎么办呢?这就需要引入闭包的概念。

 

概念

  • 闭包:指有权访问另一个函数作用域中变量的函数

简单而言:如果这个作用域可以访问另外一个函数内部局部变量,那就产生了闭包。

举例:

    function fn1() {
        let a = 10;

        function fn2() {
            console.log(a);
        }
        fn2();
    }

    fn1(); // 打印结果:10

在函数 fn2 的作用域 访问了 fn1 中的局部变量,那么,此时在 fn1 中就产生了闭包,fn1 称之为闭包函数

 

一般来说,在 fn1 函数执行完毕后,它里面的变量 a 会立即销毁。但此时由于产生了闭包,所以 fn1 函数中的变量 a 不会立即销毁,因为 fn2 函数还要继续调用变量 a。只有等所有函数把变量 a 调用完了,变量 a 才会销毁

延伸阅读
  1. 上一篇: JS笔记 | 5. 正则表达式 RegExp
  2. 下一篇: JS笔记 | 3. 对象Object (this、原型对象、Class语法糖)

发表评论

您尚未登录,登录后方可评论~~
登陆 or 注册

评论列表

暂无评论哦~~