zoukankan      html  css  js  c++  java
  • 《你不知道的JavaScript》整理(一)——作用域、提升与闭包

    最近在读一本进阶的JavaScript的书《你不知道的JavaScript(上卷)》,里面分析了很多基础性的概念。

    可以更全面深入的理解JavaScript深层面的知识点。

    一、函数作用域

    1)函数作用域

    就是作用域在一个“Function”里,属于这个函数的全部变量都可以在整个函数的范围内使用及复用。

    function foo(a) {
      var b = 2;
      function bar() {
        // ...
      }
      var c = 3;
    }
    
    bar(); // 失败
    console.log( a, b, c ); // 三个全都失败

    上面的“foo”函数内的几个标识符,放到函数外面访问就都会报错,查看源码

    2)立即执行函数表达式

    在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。

    例如上面的bar、a等几个标识符。这样能够保护变量不被污染。

    在写插件的时候经常会用到立即执行函数表达式,为的就是保护里面的变量。

    var a = 2;
    (function foo() {
      var a = 3;
      console.log( a ); // 3
    })();
    console.log( a ); // 2

    “foo”中第一个(  )将函数变成表达式,第二个(  )执行了这个函数。

    有一个专用术语:IIFE,代表立即执行函数表达式(Immediately Invoked Function Expression);

    1. 进阶用法是把它们当作函数调用并传递参数进去

    (function IIFE( global ) {  
      var a = 3;
      console.log( a ); // 3
      console.log( global.a ); // 2
    })( window );

    2. 一种变化的用途是倒置代码的运行顺序,在CMD或AMD项目中被广泛使用。

    (function IIFE(factory) {
        factory( window );
    })(function def( global ) {
      var a = 3;
      console.log( a ); // 3
      console.log( global.a ); // 2
    });

    二、块作用域

    JavaScript不支持块作用域。

    for(var i=0; i<10; i++) {
      console.log( i );
    }

    上面的代码中的“i”相当于下面的

    var i;
    for(i=0; i<10; i++) {
      console.log( i );
    }

    但也有例外,“try/catch”,catch就是一个块作用域。

    try{
      undefined(); // 执行一个非法操作来强制制造一个异常 
    }  
    catch(err) {
      console.log( err ); // 能够正常执行!
    }
    console.log( err ); // ReferenceError: err not found

    ES6改变了现状,引入了新的let关键字,let关键字可以将变量绑定到所在的任意作用域中(通常是{ .. }内部)。换句话说,let为其声明的变量隐式地了所在的块作用域。

    三、提升

    函数作用域和块作用域的行为是一样的,可以总结为:任何声明在某个作用域内的变量,都将附属于这个作用域。

    1)编译与执行

    变量和函数的所有声明都会在任何代码被执行前首先被处理,可以看下面的代码事例。

    a = 2;
    var a;
    console.log(a);//2

    这段代码等价于:

    var a;//定义声明是在编译阶段进行
    a = 2;//赋值声明会被留在原地等待执行阶段
    console.log(a);

    2)函数优先

    函数会首先被提升,然后才是变量。

    foo(); // 1
    var foo;
    function foo() {
      console.log( 1 );
    }
    foo = function() {
      console.log( 2 );
    };

    var foo函数表达式尽管出现在function foo()的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。

    而上面的代码相当于:

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

    四、闭包

    1)定义

    当函数可以记住并访问所在的作用域时,就产生了闭包,即使函数是在当前作用域之外执行

    function foo() {
      var a = 2;
      function bar() { 
        console.log( a );
      }
      return bar;
    }
     
    var baz = foo();
    baz(); // 2 —— 这就是闭包的效果。

    1. 将函数“bar”赋值给“baz”,执行“baz”,当前作用域并不在“bar”的作用域,但是可以执行。

    2. 闭包还会阻止垃圾回收,当“foo”执行完后,内部作用域仍然存在。这样才能让“baz”执行。

    2)将函数作为参数传递

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

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

    如果将函数当作第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。

    定时器事件监听器Ajax请求跨窗口通信Web Workers或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!

    3)循环和闭包

    for (var i = 1; i <= 5; i++) {
      setTimeout(function timer() {
        console.log(i);
      }, i * 1000);
    }

    每次打印出来都将会是6,延迟函数的回调会在循环结束时才执行,查看源码

    根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i

    现在用闭包来实现每次打印不同的i。

    for (var i = 1; i <= 5; i++) {
      (function(j) {
        setTimeout(function timer() {
          console.log(j);
        }, j * 1000);
      })(i);
    }

    IIFE会通过声明并立即执行一个函数来创建作用域。setTimeout中的回调可以记住当前的作用域,每个作用域中的参数“j”都是不同的。

  • 相关阅读:
    bzoj2599
    在Linux下配置jdk的环境变量
    liunx 命令大全
    Liunx下如何使用kettle
    Liunx 解压篇
    Linux下安装MySQL-5.7
    Linux文件权限查看及修改命令chmod,chown
    spring 驱动模式
    Struts2标签之Checkbox
    spring 注解的优点缺点
  • 原文地址:https://www.cnblogs.com/strick/p/5806427.html
Copyright © 2011-2022 走看看