你不知道的javascript--上卷--读书笔记2

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你不知道的javascript--上卷--读书笔记2相关的知识,希望对你有一定的参考价值。

  • 闭包是什么?

  答:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。通俗地来说:函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量,这就是javascript的闭包。

  • 闭包有哪些应用?

  答:函数作为返回值:

function foo() {
var a = 2;
function bar() {   //bar拥有涵盖foo作用域的闭包,并对它保持了引用
console.log( a );        
}
return bar;
}
var baz = foo();
baz(); // 2

  函数作为参数进行传递:

function foo() {
var a = 2;
function baz() { //baz拥有涵盖foo作用域的闭包,并对它保持了引用
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn();
}
foo();

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

  • 闭包有哪些作用?

  答:闭包可以访问函数内部的变量;可以让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在;可以封装对象的私有方法和私有属性,实现模块化。

  循环和闭包

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

结果:输出5个6;

上述代码中,我们的预期是分别输出数字 1~5,每秒一次,每次一个,但实质上却输出了5个6;为什么会这样?

6是循环结束时 i 的最终值,这个不难理解,但为什么会是5个6呢?根据作用域的工作原理,首先声明一个i变量,然后进行迭代,每次迭代对i进行LHS操作,接着定义一个延迟1S输出函数,对i进行RHS操作,看起来好像没什么问题,实质上呢?各个迭代的函数共享同一个i的引用,在执行过程中,循环快还是延迟输出快?由结果不难推知,循环快。由此,私以为执行过程可以与下述代码等效:

var i= 6;
setTimeout( function timer() {
console.log( i );   //6
}, i*1000 );
setTimeout( function timer() {
console.log( i );    //6
}, i*1000 );
setTimeout( function timer() {
console.log( i );   //6
}, i*1000 );
setTimeout( function timer() {
console.log( i );    //6
}, i*1000 );
setTimeout( function timer() {
console.log( i );    //6
}, i*1000 );

 

如此看来问题就很简单了,在循环的过程中每个迭代我们都需要一个闭包作用域。

在这之前,我们需要了解一些概念:

函数声明: function aaa(){}  

函数声明虽然可以实现函数作用域的创建,但由此也带来了一个问题,就是全局变量的污染(aaa 被绑定在所在作用域中)和必须显示的调用这个函数才能执行其中的代码。那么怎么才能同时解决这两种问题呢?

立即调用函数表达式: (function aaa(){})() ;   或者  (function aaa (){}());

  由于函数被包含在一对 ( ) 括号内部,因此成为了一个表达式,通过在末尾加上另外一个 ( ) 可以立即执行这个函数,

  区分函数声明和表达式最简单的方法是看 function 关键字出现在声明中的位 置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中 的第一个词,那么就是一个函数声明,否则就是一个函数表达式。

ok,言归正传,上面说到,在循环的过程中每个迭代我们都需要一个闭包作用域。而立即调用函数表达式会通过声明并立即执行一个函数来创建作用域。所以我们可以将上述循环改写成这样:

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

这样可以吗?不可以,虽然每次迭代我们都创建了一个新的作用域,但这个作用域是空的,没有任何变量来存储迭代中i的值。所以我们还需要声明一个变量:

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

还有其它方法吗?有,在块作用域那里说过ES6中的:let;let可以将声明的变量绑定到所在的任意的作用域内,也可以说将一个块转换成一个可以被关闭的作用域。比如这样:

for (var i=1; i<=5; i++) {
let j = i; // 是的,闭包的块作用域!
setTimeout( function timer() {
console.log( j );
}, j*1000 );
}
或者
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
请记住:for循环中let声明,会在每一次迭代中都声明变量,且每次迭代都会使用上一次迭代的结束值来初始化变量。
  • 模块:

  模块的一般形式:创建对象的私有属性和私有方法,然后通过闭包创建一个能够访问对象私有属性和方法的特权方法,最后返回这个函数或者把它保存到能够访问到的地方。

  模块模式具有两个必要条件:

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。

  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并 且可以访问或者修改私有的状态。

  

var foo = (function CoolModule() {
  var something = "cool";
  var another = [1, 2, 3];
  function doSomething() {
    console.log( something );
  }
  function doAnother() {
    console.log( another.join( " ! " ) );
  }
  return {
    doSomething: doSomething,
    doAnother: doAnother
  };
})();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

 

以上是关于你不知道的javascript--上卷--读书笔记2的主要内容,如果未能解决你的问题,请参考以下文章

你不知道的javascript--上卷--读书笔记1

你不知道的javascript--上卷--读书笔记2

你不知道的JavaScript上卷 - 读书笔记 - 第2章词法作用域-2.2 欺骗词法

你不知道的JavaScript上卷 - 读书笔记 - 第2章词法作用域-2.2 欺骗词法

JavaScript中的this—你不知道的JavaScript上卷读书笔记

你不知道的Javascript(上卷)读书笔记之二 ---- 词法作用域