一步一步的理解闭包

一步步的理解闭包:

  1. javascript是函数作用域,按常理来说,一个函数就不能访问另一个函数中的变量。
  2. 而我们实际操作有时候需要用一个函数去操作另一个函数中的变量。
  3. 为了能够访问另一个函数作用域中的变量,javascript提供一种内部机制,给嵌套在其它函数中的函数提供了一种父作用域链保存机制。
  4. 当内部函数创建时,静态的保存了父作用域链,即使父函数执行完毕,其变量对象(这里面保存着我们想要的父变量)一直保存在内部函数的作用域链中。内部函数不再被调用之前,这个活动对象一直在内存中(通俗讲就是这时候父函数的变量对象和内部函数绑定在一起了,同生共死)。这种机制取名为闭包。
  5. 简洁地说:闭包就是有权访问其他函数作用域变量的函数。

下文的叙述以此图为标准,若有疑问请参照

下面我们来一些经典实例来进一步帮你解除闭包的困扰(js中最难的一个问题之一),嵌套函数由于其天然的嵌套形式是闭包最常见的一种表现方式。由嵌套函数开始吧

1  function sayHello2(name){
2      var text=‘hello‘+name;
3      var sayAlert=function(){alert(text);}
4      return sayAlert;
5  }
6  var say2=sayHello2(‘jack‘);
7  say2();//hello jack

上面的代码就是一个嵌套函数,也是我们见过的最常见闭包表现形式,因为匿名函数function(){alert(text);}可以访问到外部函数sayHello2中的text变量,这就形成了闭包。闭包虽然存在了,但它也不能徒有其表,还的做点事情。代码第6行的赋值表达式后,外部函数sayHello2就被销毁了,按常理其内部变量text也随之销毁。可第7行成功的调用说明,外部函数的变量对象始终保留在内存中,直到内部函数销毁,不再被调用为止(总的有个头,不然内存扛不住)。

再看下面的例子

1 function say667(){
3     var num=666;
4     var sayAlert=function(){alert(num);}
5     num++;
6     return sayAlert;
7 }
8 var sayNum=say667();
9 sayNum();//667

为什么最后是667,不是你期望的666?(有的地方是这样解释的,说是对父变量的引用,而不是复制。笔者认为基本类型不存在引用(地址)这一说吧)。保存着外部函数的活动对象,活动对象就是一个盒子。里面的变量,父函数可以随意改变。

这个变量对象本来就是父函数的一部分,所以父函数能操作它是理所当然。而这个父变量对象也静态的保存在内部函数的作用域链中,那内部函数应该也有权利去操作它。

 1 function baz(){
 2     var x=1;
 3     return {
 4         foo:function foo(){return ++x;},
 5         bar:function bar(){return --x;}
 6             };
 7 }
 8 var closures=baz();
 9 alert(closures.foo(),//2
10     closures.bar()//1)

你会发现内部的foo函数改变了父变量,与此同时也影响到了bar函数的结果。这是为什么?这是因为内部的两个函数在创建的时候都保存的是同一个父作用域—baz的作用域。这样一来父变量对象就是共享的了,所以里面的变量相互影响。(作用域包含变量对象,变量对象包含变量)

再来看我们遇到过的一个循环问题了,同样是父函数一某种方式改变了这个变量对象中的变量值。使得内部函数最后访问的值变成了循环最终值。

 1 function list(){
 2     var result=[];
 3     for(var i=0;i<3;i++){
 4         result[i]=function(){
 5             alert(i);
 6         }
 7     }
 8 }
 9 result[0];//3
10 result[1];//3
11 result[2];//3
闭包的用途

下面看看闭包的用途吧,闭包可以用在许多地方。

  1. 基本功能就是前面提到的可以读取函数外部的变量,就不在赘述了。
  2. 另一个就是让外部函数变量的值始终保持在内存中(直到不存在这些变量的引用)。

把你期望的值保存在一个地方,这与缓存的思想不谋而合。例如我们需要处理一个过程很耗时的函数对象,每次调用都会花费很长时间,那么我们需要把计算的值保存起来。调用的时候首先在缓存中查找,如果找不到则进行计算,然后更新缓存的值。如果找到了,直接返回找到的值即可。闭包正是可以做到这一点,因为一旦有外部函数引用,内部函数的值是可以保留的。

 1 var cacheSearchBox=(function(){
 2     var cache={};  4     return {
 5         attachSearchBox:function(boxid){
 6             if(boxid in cache){
 7                 return cache[boxid];
 8             }
 9             var fsb=new searchBox(boxid);
10             cache[boxid]=fsb;
11             return fsb
12         }
13     }
14 })();
15 cacheSearchBox.attachSearchBox("input1");

这样,当我们第二次调用cacheSearchBox.attachSearchBox("input1")时,我们可以从缓存中获取该对象,而不用再去创建一个新的searchbox对象。

3 面向对象中实现特权方法

在下面的例子中,在person之外的地方无法访问其内部变量。只有通过闭包构造的特权方法的形式来访问。同时实现了私有变量和访问私有变量的特权方法的封装:

 1 var person=function(){
 2     var name="deng";
 3     return{
 4         getName:function(){
 5             return name;
 6         },
 7         setName:function(newName){
 8             name=newName;
 9         }
10     }
11 }();
12 alert(person.name);//undefined
13 alert(person.getName());//deng
14 person.setName("jack");
15 alert(person.getName());//jack
使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。实现面向对象封装中特权方法时,慎重修改私有变量。

文章在上纲上线的同时,加入了很多自己的理解。也许存在纰漏,望探讨。

一步一步的理解闭包

时间: 2024-12-20 16:54:56

一步一步的理解闭包的相关文章

一步一步理解Paxos算法

一步一步理解Paxos算法 背景 Paxos 算法是Lamport于1990年提出的一种基于消息传递的一致性算法.由于算法难以理解起初并没有引起人们的重视,使Lamport在八年后重新发表到 TOCS上.即便如此paxos算法还是没有得到重视,2001年Lamport用可读性比较强的叙述性语言给出算法描述.可见Lamport对 paxos算法情有独钟.近几年paxos算法的普遍使用也证明它在分布式一致性算法中的重要地位.06年google的三篇论文初现“云”的端倪,其中的chubby锁服务使用p

一步一步教你认识闭包

在公众号中曾经介绍过两篇关于函数的文章,第一篇是 关于 Python 函数是第一类对象,第二篇是关于 Lambda 函数,今天来说说 Python 闭包. 什么是闭包?闭包有什么用?为什么要用闭包?今天我们就带着这3个问题来一步一步认识闭包. 闭包和函数紧密联系在一起,介绍闭包前有必要先介绍一些背景知识,诸如嵌套函数.变量的作用域等概念 作用域 作用域是程序运行时变量可被访问的范围,定义在函数内的变量是局部变量,局部变量的作用范围只能是函数内部范围内,它不能在函数外引用. 定义在模块最外层的变量

简单十步让你全面理解SQL

很多程序员认为SQL是一头难以驯服的野兽.它是为数不多的声明性语言之一,也因为这样,其展示了完全不同于其他的表现形式.命令式语言. 面向对象语言甚至函数式编程语言(虽然有些人觉得SQL 还是有些类似功能). 我每天都写SQL,我的开源软件JOOQ中也包含SQL.因此我觉得有必要为还在为此苦苦挣扎的你呈现SQL的优美!下面的教程面向于: 已经使用过但没有完全理解SQL的读者 已经差不多了解SQL但从未真正考虑过它的语法的读者 想要指导他人学习SQL的读者 本教程将重点介绍SELECT 语句.其他 

一步一步理解GB、GBDT、xgboost

GBDT和xgboost在竞赛和工业界使用都非常频繁,能有效的应用到分类.回归.排序问题,虽然使用起来不难,但是要能完整的理解还是有一点麻烦的.本文尝试一步一步梳理GB.GBDT.xgboost,它们之间有非常紧密的联系,GBDT是以决策树(CART)为基学习器的GB算法,xgboost扩展和改进了GDBT,xgboost算法更快,准确率也相对高一些. 1. Gradient boosting(GB) 机器学习中的学习算法的目标是为了优化或者说最小化loss Function, Gradient

一步一步理解线段树——转载自JustDoIT

一步一步理解线段树 目录 一.概述 二.从一个例子理解线段树 创建线段树 线段树区间查询 单节点更新 区间更新 三.线段树实战 -------------------------- 一 概述 线段树,类似区间树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn). 线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[a,b],那么(c=(a+b)/2)左儿子的区间

一步一步教你用Swift开发俄罗斯方块:No.7 下落机制

上一章节我们完成了shape的建立,现在游戏里面的元素(blocks,shapes)都已经完成了,背景也搭好了(array2D),让我们开始制定游戏规则吧.首先就是需要让我们的shape掉下来,还记得我们刚开始的时候每个600毫秒要刷新一下屏幕呢?那会还有一个closure我们都不太明白是干嘛用的,马上就知道了. 好了,今天章节过后,你的程序运行起来应该是这样的: 让我们来修改代码吧,这次要修改的代码比较多,而且没有上一章节那样重复的工作.不用太过担心,我们一步一步来: 在#1, 函数在执行时会

javascript深入理解闭包

一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. Js代码 var n=999; function f1(){ alert(n); } f1(); // 999 另一方面,在函数外部自然无法读取函数内的局部变量. Js代码 function f1(){ var n=999; } alert(n); // error 这里有一个地方需要注意,函数

js深入理解&quot;闭包&quot;

一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. Js代码 var n=999; function f1(){ alert(n); } f1(); // 999 另一方面,在函数外部自然无法读取函数内的局部变量. Js代码 function f1(){ var n=999; } alert(n); // error 这里有一个地方需要注意,函数

深入理解闭包系列第四篇——常见的一个循环和闭包的错误详解

前面的话 关于常见的一个循环和闭包的错误,很多资料对此都有文字解释,但还是难以理解.本文将以执行环境图示的方式来对此进行更直观的解释,以及对此类需求进行推衍,得到更合适的解决办法 犯错 function foo(){ var arr = []; for(var i = 0; i < 2; i++){ arr[i] = function(){ return i; } } return arr; } var bar = foo(); console.log(bar[0]());//2 以上代码的运行