zoukankan      html  css  js  c++  java
  • 作用域和闭包

    作用域和闭包

    作用域

     

    JS中有两种作用域:全局作用域|局部作用域

     

    栗子1

     

    console.log(name);      //undefined
    var name = '波妞';
    var like = '宗介'
    console.log(name);      //波妞
    function fun(){
        console.log(name);  //波妞
        console.log(eat)    //ReferenceError: eat is not defined
        (function(){
            console.log(like)   //宗介
            var eat = '肉'
        })()
    }
    fun();

     

    1. name定义在全局,在全局可以访问到,所以 (2) 打印能够正确打印;
    2. 在函数fun中,如果没有定义name属性,那么会到它的父作用域去找,所以 (3) 也能正确打印。
    3. 内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数。类似单向透明,这就是作用域链,所以 (4) 不行而 (5) 可以。

     

    那么问题来了,为什么第一个打印是"undefined",而不是"ReferenceError: name is not defined"。原理简单的说就是JS的变量提升

     

    变量提升:JS在解析代码时,会将所有的声明提前到所在作用域的最前面

     


     

    栗子2

     

    console.log(name);      //undefined
    var name = '波妞';
    console.log(name);      //波妞
    function fun(){
        console.log(name)   //undefined
        console.log(like)   //undefined
        var name = '大西瓜';
        var like = '宗介'
    }
    fun();

     

    相当于

     

    var name;
    console.log(name);      //undefined
    name = '波妞';
    console.log(name);      //波妞
    function fun(){
        var name;
        var like;
        console.log(name)   //undefined
        console.log(like)   //undefined
        name = '大西瓜';
        like = '宗介'
        console.log(name)   //大西瓜
        console.log(like)   //宗介
    }
    fun();

     

    注意:是提前到当前作用域的最前面

     


     

    栗子3

     

    printName();     //printName is not a function
    var printName = function(){
        console.log('波妞')
    }
    printName();       //波妞

     

    相当于

     

    var printName;
    printName();     //printName is not a function
    printName = function(){
        console.log('波妞')
    }
    printName();       //波妞

     

    这样一来就好理解了,函数表达式在声明的时候还只是个变量

     


     

    栗子4

     

    {
        var name = '波妞';
    }
    console.log(name)   //波妞
    
    (function(){
        var name = '波妞';
    })()
    console.log(name)   //ReferenceError: name is not defined
    
    {
        let name = '波妞';
    }
    console.log(name)   //ReferenceError: name is not defined

     

    从上面的栗子可以看出,不可以草率的认为JS中var声明的变量的作用范围就是大括号的起止范围,ES5并没有块级作用域,实质是函数作用域;ES6中有了let、const定义后,才有了块级作用域。

     


     

    栗子5

     

    function p1() { 
        console.log(1);
    }
    function p2() { 
        console.log(2);
    }
    (function () { 
        if (false) {
            function p1() {
                console.log(3);
            }
        }else{
            function p2(){
                console.log(4)
            }
        }
        p2();
        p1()
    })();       
    //4
    //TypeError: print is not a function

     

    这是一个非常经典的栗子,声明提前了,但是因为判断条件为否,所以没有执行函数体。所以会出现"TypeError: print is not a function"。while,switch,for同理

     

    闭包

     

    函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript中,函数在每次创建时生成闭包。

     

    上面的定义来自MDN,简单讲,闭包就是指有权访问另一个函数作用域中变量的函数。

     


     

    • 闭包的关键在于:外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象.,

     

    //举个例子
    function makeFunc() {
        var name = "波妞";
        function displayName() {
            console.log(name);
        }
        return displayName;
    }
    
    var myFunc = makeFunc();
    myFunc();

     

    JavaScript中的函数会形成闭包。 闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量

     

    在例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用,而 displayName 实例仍可访问其词法作用域中的变量,即可以访问到 name 。由此,当 myFunc 被调用时,name 仍可被访问,其值 '波妞' 就被传递到console.log中。创建闭包最常见方式,就是在一个函数内部创建另一个函数

     


     

    • 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止

     

    //例二
    function makeAdder(x) {
      return function(y) {
        return x + y;
      };
    }
    
    var add5 = makeAdder(5);
    var add10 = makeAdder(10);
    
    console.log(add5(2));  // 7
    console.log(add10(2)); // 12
    
    //释放对闭包的引用
    add5 = null;
    add10 = null;

     

    从本质上讲,makeAdder 是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数。在上面的示例中,我们使用函数工厂创建了两个新函数 — 一个将其参数和 5 求和,另一个和 10 求和。

     

    add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。

     

    闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。

     


     

    • 闭包只能取得包含函数中的任何变量的最后一个值

     

    //栗子1
    function arrFun1(){
        var arr = [];
        for(var i = 0 ; i < 10 ; i++){
            arr[i] = function(){
                return i
            }
        }
        return arr
    }
    console.log(arrFun1()[9]());     //10
    console.log(arrFun1()[1]());     //10
    
    //栗子2
    function arrFun2(){
        var arr = [];
        for(var i = 0 ; i < 10 ; i++){
            arr[i] = function(num){
                return function(){
                    return num
                };
            }(i)
        }
        return arr
    }
    console.log(arrFun2()[9]());     //9
    console.log(arrFun2()[1]());     //1

     

    栗子 1 中,arr数组中包含10个匿名函数,每个函数都可以访问外部的变量 i , arrFun1 执行后,其作用域被销毁,但它的变量依然存在内存中,能被循环中的匿名函数访问,这是的 i 为 10;

     

    栗子 2 中,arr数组中有是个匿名函数,其匿名函数内还有匿名函数,最内层匿名函数访问的 num 被 上一级匿名函数保存在了内存中,所以可以访问到每次的 i 的值。

     


     

    对JavaScript中局部变量、全局变量和闭包的理解

     

    对js中局部变量、全局变量和闭包的理解

    局部变量

    对于局部变量,js给出的定义是这样的:在 JavaScript函数内部声明的变量(使用 var)是局部变量,所以只能在函数内部访问它。(该变量的作用域是局部的)。可以在不同的函数中使用名称相同的局部变量,因为只有声明过该变量的函数才能识别出该变量。只要函数运行完毕,本地变量就会被删除

    我们先来逐步理解:

    • 只能在函数内部访问

      function test() {
          var a = 0;
          return a;
      }
      
      console.log(a);
      //结果:a is not defined

      上面的代码声明了一个test()函数,在函数内部声明了一个局部变量a,当我们尝试在函数外访问局部变量a时,出来的结果是a is not defined

      我们再来看下面这个例子:

      function test() {
          var a = 0;
          return a;
      }
      
      console.log(test());
      //结果:0

      以上两个例子很好的阐述了局部变量只能在函数内部访问,当调用函数时,函数域自动执行其中的代码,局部变量自然也被调用。

    • 只要函数运行完毕,本地变量就会被删除

      function b() {
          var y = 0;
          z = ++y;
          console.log("这是局部变量y:",z)
          return z;
      }
      
      console.log(b(),b(),b());
      //结果:这是局部变量y: 1
      //这是局部变量y: 1
      //这是局部变量y: 1
      //1 1 1

      从上面代码我们可以看出,我们执行了3次函数调用,得到的结果都是1,可能有人会说,这很简单啊,每次出来的结果都是1,那是因为每次执行函数,函数内都会将局部变量y初始化为0。没错,的确是这样,但是如果不初始化变量,则得到的返回值是NaN,所以初始化是必要的。所以,无论用什么办法,在函数内部用一个局部变量去做累加,是不可能实现的。但是,我们可以通过全局变量和闭包来实现累加。

    全局变量

    在js中,这样定义全局变量, 在函数外声明的变量是全局变量,网页上的所有脚本和函数都能访问它。 全局变量会在页面关闭后被删除

    • 我们再来看一个例子

      var a = 0;
      
      function b() {
          ++a;
          console.log("这是全局变量a",a);
          return a;
      }
      console.log("这是未改变的全局变量a:",a,"这是函数b():",b(),b(),b(),"这是改变后的全局变量a:",a);
      //结果:这是全局变量a 1
      //这是全局变量a 2
      //这是全局变量a 3
      //这是未改变的全局变量a: 0 这是函数b(): 1 2 3 这是改变后的全局变量a: 3

      上面代码定义了一个全局变量a,和一个b()函数,通过函数内部对a执行自加加,实现了累加目的,通过三次调用函数,得到的结果a为3。

    闭包

    什么是闭包呢?闭包的定义是这样的,闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。直观的说就是形成一个不销毁的栈环境。

    我对闭包的理解是这样的,闭包就是一个内嵌函数引用顶层函数的变量,而顶层函数是一个立即执行函数(自调用函数),因为它会自动调用,所以局部变量不会被删除,但是这会增加内存消耗。

    • 来看一个例子

      function a() {
          var b = 0;
          return function() {
              return ++b;
          }
      }
      
      var closure = a();
      console.log("这是闭包:",closure(),closure(),closure());
      //结果:这是闭包: 1 2 3

      我们看到,由于闭包的特殊机制,使得局部变量在函数执行完之后不会被销毁,由此得到的最后结果为3 ,而不是1。

     

  • 相关阅读:
    Android Studio 开发
    Jsp编写的页面如何适应手机浏览器页面
    电影
    Oracle 拆分列为多行 Splitting string into multiple rows in Oracle
    sql server 2008 自动备份
    WINGIDE 激活失败
    python安装 错误 “User installations are disabled via policy on the machine”
    ble编程-外设发送数据到中心
    iOS开发-NSString去掉所有换行及空格
    ios9 字符串与UTF-8 互相转换
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/12253047.html
Copyright © 2011-2022 走看看