《你不知道的javascript(上)》作用域与闭包(三)

在翻阅《你不知道的javascript》这一套书的中上卷目录之后,发现书中针对闭包、对象、原型、语法、异步、回调等等既基础又重要的
javascript知识有着针对性的阐述,于是决定对这套书的中上卷进行学习。上卷和中卷各讲述了两大部分知识,分别是:作用域与闭包、
this和对象原型、类型和语法、异步和性能。本文是对作用域与闭包的学习总结。

闭包,一个非常神秘的词语,其实不光js中存在闭包的概念,在其他编程语言中也有。在《你不知道的javascript(上)》一书中,通过一些易懂的实例,很容易的就解释清楚闭包,以及它的主要作用。学习之后,我认为对闭包主要掌握三个要点:概念,实际用途,利用闭包来建立模块。

1.闭包概念

  正如书中所说,闭包在js中无处不在,其定义也直截了当:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。注意重点,第一句话阐述闭包的定义,根据js引擎按照作用域链向上查找的机制,函数内部嵌套的函数肯定会形成闭包,但是使一个闭包显得有意义的却是第二句话(当然闭包不只这一种作用)。

  如下代码:

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

在foo()函数中,bar()已经形成了一个闭包,通过return bar将bar返回给了全局作用域,而通过var声明将bar传递给了baz,于是,此时baz作为在全局作用域中的标识符,却能够访问foo作用域内的变量a,这就是闭包所起的作用。

  再来看一个复杂的传参例子:

function foo(){
var a=2;
function baz(){
  console.log(a);
}
  bar(baz);
}
function bar(fn){
  fn();
}

在foo()函数中通过bar(baz)调用了外部函数bar(fn),也就是将baz传给了外部函数bar(fn),既fn=baz,因此在执行的时候,执行fn()也就是执行了一次baz(),于是,作为另一个函数中的fn(),就能够访问到foo()中的变量a,而这是baz()闭包所起的作用。

2.闭包的作用

  闭包所起的作用不只是能够在外部作用域中访问内部变量,在回调函数及循环语句中,主要也是闭包在起作用。同样,看一个书中提供的使用延时器的例子:

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

在调用wait("Hello,closure!")之后,延时器中的函数要在1000毫秒后执行,此时,按照正常的js垃圾收回机制,wait()的作用域及其中储存的变量值"Hello,closure!"应该在wait("Hello,closure!")执行几微秒后就被收回了,然后正是由于延时器中的函数timer()处于wait(message)函数内部,形成了闭包,使得timer()可以在1000毫秒后能够访问到变量值"Hello,closure!"。可见,闭包不仅能使外部访问函数作用域内的变量,还能使得函数作用域在一定时间内保持完整。

  有时候(或者说经常),会在for循环内执行延时器,如果延时器要使用for循环的i变量(j或者其他也可以,总之就是循环按照其变化执行的那个变量),经常会出现所有延时器使用的都是最后一个i值的情况出现。对此,需要使用闭包和IIFE才能够很好的处理这个问题(IIFE指立即执行函数表达式)。
如下代码:

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

预想的情况是,每隔1000秒执行一次timer(),并且timer()中使用的i值依次为1到5。然而实际情况却是在6000毫秒之后同时执行了timer(),并且timer()中使用到的i值全都是6。究其原因,需要解释两部分内容:

  首先是出现i值为6的原因,由于当i值为5时,依然满足for循环的条件,所以循环语句会再执行一次,而i++就使得最后的i值为6;其次,为何不是使用的1到5,因为for循环执行完毕只是几微秒的时间,而按照预想最早执行的timer()也会在1000毫秒之后执行,此时i全都变成了6,所以最后延时器中的函数全都使用的是6。

  要解决这个问题,主要方法就是在延时器外边包裹一层IIFE,在每次for循环执行的时候,立即执行一次IIFE并将此时的i值保存在IIFE中,以给IIFE中的延时器使用。如下代码:

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

利用j适时保存了i值,而同时延时器是异步的(也就是说有5个延时器在执行),每个延时器内部的timer()都拥有一个作用域,此时体现出了闭包的作用,闭包使得包裹延时器的IIFE能够保持完整,j值得以保存,因此最后timer()使用的j变量分别是1到5。此外,利用IIFE的进阶用法,还可以省掉var j=i语句,如下:

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

直接进行了传参。

3.模块化

  前端发展到现今,模块化已经是一个主流的概念,而如今的模块主要都是定义一个模块封装函数,使用户可以自定义模块。书中介绍了模块封装函数的核心概念,如下代码:(非常重要的一段代码,完全可以使用到自己以后的代码中,自行建模

var MyModules = (function Manager(){
  var modules = {};
  function define(name,deps,impl){
    for(var i=0;i<deps.length;i++){
      dep[i] = modules[deps[i]];
    }
    modules[name] = impl.apply(impl,deps);
  }
  function get(name){
    return modules[name];
  }
  return{
    define: define,
    get:get
  };
})();

简单介绍一下我理解的各段代码的作用:return()返回了MyModules的对象字面量(此处使用了闭包机制),以便外部可以使用MyModules的define和get两个函数。define(name,deps,impl)用于自定义模块(就是在该模块内自定义自己需要定义的函数),name是函数名(使用define()后成为MyModules对象的属性),deps是自定义函数需要使用的参数组,而impl是定义的函数的具体代码。
get(name)用于从模块中取得自己想要使用的函数或方法。

  以上,关于模块的具体使用,在书中有实例讲解了具体如何运用,建议看书进行了解。

Small Star's Blog|小星的博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容