• 读《你不知道的JavaScript(上卷)》后感-作用域闭包(二)


    github原文

    一、 序言

    最近我在读一本书:《你不知道的JavaScript》,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们,也入手看看这本书,受益匪浅。

    《你不知道的JavaScript上卷》

    image

    现在我读完这本书的一些心得与总结:

    一、作用域闭包

    先来一段代码:

    function foo(){
    
      var a = 10;
      function bar(){
        console.log(a); // 10
      }
    
      bar()
    }		  
    
    foo(); 
    
    console.log(a); // ReferenceError: a is not defined
    

    这段代码看起来和嵌套作用域中的示例代码很相似。基于词法作用域的查找规则,函数 bar() 可以访问外部作用域中的变量 a(这个例子中的是一个 RHS 引用查询)。

    当调用foo()时候,进入了foo函数的作用域里面,一直读,读到输出a的时候,foo里面的生命周期就结束了,当然我们清晰知道,外面作用域是访问不了内部的作用域的变量的,则最外层的输出a ReferenceError: a is not defined

    我们还记得return的语法么?

    小复习:

    1.
    
    (function foo(){
        return function(){
            console.log(2);
        };
    }())
    
    

    这里我定义了一个自调用的函数,第一步是什么都没有输出的,这里是执行了foo函数,那也是重要的一部,当执行完了foo函数,里面的return到底发生了什么的作用?

    我简单概括rerun有2个作用是:

    1. return;  等于 return false;在这个作用域的后面的任何代码都会停止运行,一定记得加逗号;结束。
    2. retrun ... ; 后面跟着一个函数就返回一个函数,后面跟着一个值就会返回什么的值。
    

    所以:上面执行了foo()函数,return 返回了 bar函数

    在执行了bar()函数,则输出了2

    有了上面的基础,我们来优化第一次的代码:

    function foo(){
    
      var a = 10;
      return function bar(){
        console.log(a); 
      }
    }		  
    
    foo()(); // 10
    
    

    以上代码,我的理解是:

    当执行foo()函数时候,return 返回 bar函数,

    在次执行foo()() => 实际是执行了bar()函数,

    所以输出了 2

    在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃 圾回收器用来释放不再使用的内存空间。由于看上去 foo() 的内容不会再被使用,所以很 自然地会考虑对其进行回收。

    而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此 没有被回收。谁在使用这个内部作用域?原来是 bar() 本身在使用。

    闭包的形式非常多,例如:

    1.
    function foo() { 
      var a = 2;
      function baz() { 
        console.log( a ); // 2
      }
      bar( baz ); 
    }
    function bar(fn) {
      fn(); // 妈妈快看呀,这就是闭包!
    }
    
    

    把内部函数 baz 传递给 bar,当调用这个内部函数时(现在叫作 fn),它涵盖的 foo() 内部
    作用域的闭包就可以观察到了,因为它能够访问 a

    传递函数当然也可以是间接的
    
    var fn;
    function foo() {
      var a = 2;
      function baz() { 
        console.log( a );
      }
      fn = baz; // 将 baz 分配给全局变量 
    }
      function bar() {
        fn(); // 妈妈快看呀,这就是闭包!
      }
    foo();
    bar(); // 2
    
    

    看了上面的代码,我按照第一章的作用域的读取顺序能搞明白,但这里的原理是什么??

    我继续读下去发现一段代码:

    function wait(message) {
      setTimeout( function timer() {
        console.log( message );
      }, 1000 ); 
        
    }
    wait( "Hello, closure!" );
    
    

    书里的解释是:

    将一个内部函数(名为 timer)传递给 setTimeout(..)。timer 具有涵盖 wait(..) 作用域
    的闭包,因此还保有对变量 message 的引用。

    wait(..) 执行 1000 毫秒后,它的内部作用域并不会消失,timer 函数依然保有 wait(..)
    作用域的闭包。

    深入到引擎的内部原理中,内置的工具函数 setTimeout(..) 持有对一个参数的引用,这个 参数也许叫作 fn 或者 func,或者其他类似的名字。引擎会调用这个函数,在例子中就是 内部的 timer 函数,而词法作用域在这个过程中保持完整。

    这就是闭包。

    这里代码我看了几次,终于发现之前理解以为这里只是简单的函数调用:

    我的理解是:

    function wait(message) {
      setTimeout( params , 1000 ); 
        
    }
    wait( "Hello, closure!" );
    
    传进去的params返回来的是params,或许也叫fn比较适合
    
     function timer() {
        console.log( message );
      }
      
      引擎会调用这个函数,就有了所谓的定时器。
    
    

    书中更是引入了一段jq的源代码:

    function setupBot(name, selector) {
    $( selector ).click( function activator() {
        console.log( "Activating: " + name );
      } );
    }
      setupBot( "Closure Bot 1", "#bot_1" );
      setupBot( "Closure Bot 2", "#bot_2" );
    
    

    原来这里也是个闭包啊!

    本质上无论何时何地,如果将函数(访问它们各自的词法作用域)当作第一 级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使 用了回调函数,实际上就是在使用闭包!

    我记得之前老师教过我三个经典的闭包函数:

    for (var i=1; i<=5; i++) { 
      setTimeout( function timer() {
        console.log( i ); // 6,6,6,6,6
      }, i*1000 );
    }
    
    这段代码,根据作用域的关系,输出:5个6
    
    为啥呢?
    
    我们来加一行代码:
    
    
    for (var i=1; i<=5; i++) { 
      console.log(i) // 1,2,3,4,5
      setTimeout( function timer() {
        console.log( i );  // 6,6,6,6,6
      }, i*1000 );
    }
    
    由以上可知道,
     setTimeout( function timer() {
        console.log( i );  // 6,6,6,6,6
      }, i*1000 );
     
    
    这段代码已经暴露出外面的作用域了,
    我们再来修改一下代码
    
    
    我想,如果他已经暴露出了外面的作用域,那么的每次来自调用它看下可不可以呢?
    
    for (var i=1; i<=5; i++) { 
      (function(){
        setTimeout( function timer() {
          console.log( i );  // 6,6,6,6,6
        }, i*1000 );
      })();
    }
    
    其实这里给多了一个作用域,实质是,和第一次没什么分别
    
    实际是这样:
    
    
    var i;
    for(i = 0;i < 5; i++){
    		
    }
    
    (function (){
      setTimeout(function(){
        console.log(i)
     },i * 1000)
    })()
    
    i始终是5
    
    

    那么既然setTimeout还是在for循环这个作用域里,i还是能在的,我想尝试一下一下代码

    for (var i=1; i<=5; i++) { 
      (function(i){
        setTimeout( function timer() {
          console.log( i );  
        }, i*1000 );
      })(i);
    }
    
    我把for循环里的i一个一个的穿进去,这不是通了么
    
    执行一下代码:
    
    1
    2
    3
    4
    5
    
    太棒了!可以了。
    

    这里要不得不提一下es6里面的let

    for(let i = 0;i < 5; i++){
      setTimeout(function(){
        console.log(i)
      },i * 1000)
    }
    一样能到达同样的效果,分别输出1,2,3...5
    因为let的作用域起了作用
    
    实际是这样:
    
    
    
    1.第一种. 
    
    for(let i = 0;i < 3; i++){
    }
    (function (){
    	setTimeout(function(){
       	console.log(i)
     },i * 1000)
    })()
    // 输出 ReferenceError: i is not defined
    
    2.第二种. 
    
    for(var i = 0;i < 5; i++){
    }
    (function (){
      setTimeout(function(){
        console.log(i)
     },i * 1000)
    })()
    // 输出 5
    
    3.第三种. 
    
    let i
    for(i = 0;i < 5; i++){
    }
    (function (){
    	setTimeout(function(){
       	console.log(i)
     },i * 1000)
    })()
    // 输出 5
    
    这里我的理解是: 块级作用域的问题:
    
    let 在for循环括号里有效
    
    刚才:
    
    for (var i=1; i<=5; i++) { 
      (function(i){
        setTimeout( function timer() {
          console.log( i ); 
        }, i*1000 );
      })(i);
    }
    这里把i穿进去,其实是闭包的作用,传进去返回出来
    很酷是吧?块作用域和闭包联手便可天下无敌
    

    原文

  • 相关阅读:
    C++11常用特性的使用经验总结
    Websocket协议的学习、调研和实现
    高性能后台服务器架构设计
    Linux下ping命令、traceroute命令、tracert命令的使用
    docker-4-Dockerfile配置文件详解
    Docker 国内仓库和镜像
    centos7.0查看IP
    tomcat报错:This is very likely to create a memory leak问题解决
    windows 环境下搭建docker私有仓库
    Docker 修改镜像源地址
  • 原文地址:https://www.cnblogs.com/liangfengbo/p/7780432.html
Copyright © 2020-2023  润新知