闭包是什么鬼?

15年10月份初到现在的公司时,有天晚上加班后临下班时,当时的组长问我知道闭包不,由于我是半路出家来做程序的,几乎很少用到闭包这个东东,并不是很了解这个概念,组长写出了这么段代码。

var temp = {

  data: [],

  totalRecords: 0

};

(function(dm){

  for(var i=0;i<10;i++){

    dm.data.push(i*i);

    dm.totalRecords += 1;

  }
})(temp);

console.log(temp); //结果得到 object{data:Array[10],totalRecords:10}

我当时就觉得很神奇了,函数内部的dm变量,经过这么一折腾,在外面也就可以访问到其变化之后的值了,原因何在?于是乎,后来我花了时间来研究这个闭包,到底是什么鬼?到底会有什么时候需要用到?

首先来说我们平常遇到的一个很普遍的变量的作用域的问题,例如有如下js代码:

var strA = "xiangxiao's weekend";

function f1(){

  var strB = strA + " is writing code."

  document.write(strB);

}

f1(); //结果当然是2个字符串拼接起来 xiangxiao's weekend is writing code.

这个无需多解释,因为在js函数内部肯定是可以访问并使用在函数体外面的变量的嘛;

再来看另一段js代码:

function f1(){

  var strA = "xiangxiao's weekend";

  var strB = strA + " is writing code."

};

document.write(strB); //此处报错

报错的原因就是这个strA和strB是函数f1 的局部变量,它 的作用域仅限于f1函数体内部,外部是访问不了的,这里牵扯到作用域的问题,既然说到这儿也就耗一点篇幅来讲这个作用域的东东吧。

在JavaScript中,函数也是对象,也可以通过var func = new Function('a','b','return a+b')这样的形式来定义函数,一般不这样做的原因是将众多代码弄在一个超长的字符串里,代码的可读性太差。简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。

1、全局作用域

即在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:

(1)最外层函数和在最外层函数外面定义的变量拥有全局作用域,例如:

var authorName = "xiangxiao";
function say(){
  var blogName = "dearxiangxiao";
  function innerSay(){
    document.write(blogName);
  };
  innerSay();
}
document.write(authorName); //结果是xiangxiao
document.write(blogName); //报错
say(); // 结果是dearxiangxiao
innerSay();//报错

(2)未使用var关键字定义的变量也被赋予了全局的作用域;

function say(){
  var authorName = "xiangxiao";
  blogName = "dearxiangxiao";
  document.write(authorName);

};

say(); //结果是xiangxiao

document.write(authorName);//报错

document.write(blogName);//结果是dearxiangxiao

(3)所有window对象的属性拥有全局作用域。

这个就无需举例了,window.innerWidth诸如此类的东东,用得也蛮多的了。window对象还可自定义属性和方法,当然跟这里没啥关系了。

2、局部作用域

和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所以在一些地方也会看到有人把这种作用域称为函数作用域。

之前1中的(1)那个例子

var authorName = "xiangxiao";
function say(){
  var blogName = "dearxiangxiao";
  function innerSay(){
    document.write(blogName);
  };
  innerSay();
}
document.write(authorName); //结果是xiangxiao
document.write(blogName); //报错
say(); // 结果是dearxiangxiao
innerSay();//报错
 
这里面的变量blogName和函数innerSay就只拥有局部作用域。
 
那么,要怎么样才能访问到函数内部的变量呢?在上面的代码中,函数innerSay就被包括在函数say内部,这时say内部的所有局部变量,对innerSay都是可见的。但是反过来就不行,innerSay内部的局部变量,对say就是不可见的。这就是JavaScript语言特有的“链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
现在想到一点东西了,结合以前遇到的闭包的使用场景,可以做如下思考:既然innerSay函数可以访问到say函数内部的变量,我们可以将innerSay整个函数作为say函数的返回,再使用say外面这个函数时,就可以毫无阻力的访问innerSay内层函数的诸多变量了。说干就干,于是乎,我改了一下上面的函数:
function say(){
  var authorName = "xiangxiao";
  function innerSay(){
    document.write(authorName)
  };
  return innerSay;
};
var fn = say();
fn(); //结果打印出了xiangxiao
 
果然,实现了我的想法,同时,我在思考如果返回的不是函数怎么处理,比如:object对象,也好办嘛,也是可以返回的嘛,搜了一下之后,我写了段计算圆面积的代码来做实验,代码如下:
var Circle = function(){
  var obj= new Object();
  circle.PI = 3.1416;
  circle.area=function(r){
    return this.PI * r * r;
  };
  return circle;
};
var newCircle = new Circle();
document.write(newCircle(3)); //结果计算出了半径为3 的圆面积是28.2744
写到这里,大致明白了闭包的本质:就是能够读取其他函数内部变量的函数,而且是从外部来读取的,上面的innerSay函数和circle的area函数,都可以视做闭包,由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
 
到这里,然而最开头我们组长写的那段代码,我还是都点云里雾里,仔细查了下这个(function(){})()的原理,明白了个中道理,看来组长还是懂这些东东的。我的理解如下:
对于javascript来说,也对函数的声明和执行进行了区分
 
function Fn() { //some code}     // 这是定义,Declaration;定义只是让解释器知道其存在,但是不会运行。
Fn();                   // 这是语句,Statement;解释器遇到语句是会运行它的。
 
于是,有人觉得啰嗦,定义和执行还必须分开来写,同时,因为作用域的存在,某些共用的变量还必须定义为全局的,污染了命名空间(一不小心就定义了个别人也在用的变量,溴大了)。于是想找一个可以解决以上问题的写法。那么像下面这么写行不行呢?

function Fn(){ //some code}();

当然是不能,但是为什么呢?因为 function Fn(){ //some code} 这个部分只是一个声明,对于解释器来说,就好像你写了一个字符串 "function Fn(){ //some code}",它需要使用解析函数,比如 eval() 来执行它才可以。所以把 () 直接放在声明后面是不会执行,这是错误的语法。

如何把它变得正确?说起来也简单,只要把 声明 变成 表达式(Expression) 就可以了。实际上转变表达式的办法还是很多的,最常见的办法是把函数声明用一对 () 包裹起来,于是就变成了:

(function Fn() { //some code})();

这样等价于:

function Fn() { //some code};

Fn();

另外,还有很多其他写法可以将函数声明变为表达式,比如:

!function() { //some code}();

+function() {// some code}();

再回到那段组长写的代码

var temp = {

  data: [],

  totalRecords: 0

};

(function(dm){

  for(var i=0;i<10;i++){

    dm.data.push(i*i);

    dm.totalRecords += 1;

  }
})(temp);

console.log(temp);

这里相当于定义了一个以temp为参数的函数,(function(dm){ //......省略代码})() 这部分里第一个括号里相当于定义了一个函数,返回值是一个函数,第二个括号就是在执行这个被返回的函数了,dm只不过是形参,temp的值在那个匿名函数里改变了,后面再打印temp的值,理所当然的是 object{data:Array[10],totalRecords:10}了。

这就是这篇博文的全部内容了,个人简见解,不喜勿喷,后面我还会研究下闭包的具体用途。

ps:吐槽一下成都的这个鬼天气,今年感觉没春天啊,冬天之后立马到夏天,放个清明节下了2天雨。

折腾自己的js闭包(一)的更多相关文章

  1. 折腾自己的js闭包(二)

    前面我大致探讨了js里的闭包的相关概念,那么,到底在什么时候用它最好呢?存在即真理,只不过以前没发现它而已,先来看看下面的这几个用途吧 一.我首先想到的就是从函数外面访问它的内部变量,从而达到自己的一 ...

  2. js闭包的作用域以及闭包案列的介绍:

    转载▼ 标签: it   js闭包的作用域以及闭包案列的介绍:   首先我们根据前面的介绍来分析js闭包有什么作用,他会给我们编程带来什么好处? 闭包是为了更方便我们在处理js函数的时候会遇到以下的几 ...

  3. 大部分人都会做错的经典JS闭包面试题

    由工作中演变而来的面试题 这是一个我工作当中的遇到的一个问题,似乎很有趣,就当做了一道题去面试,发现几乎没人能全部答对并说出原因,遂拿出来聊一聊吧. 先看题目代码: function fun(n,o) ...

  4. Js闭包常见三种用法

        Js闭包特性源于内部函数可以将外部函数的活动对象保存在自己的作用域链上,所以使内部函数的可以将外部函数的活动对象占为己有,可以在外部函数销毁时依然存有外部函数内的活动对象内容,这样做的好处是可 ...

  5. js闭包之初步理解( JavaScript closure)

    闭包一直是js中一个比较难于理解的东西,而平时用途又非常多,因此不得不对闭包进行必要的理解,现在来说说我对js闭包的理解. 要理解闭包,肯定是要先了解js的一个重要特性, 回想一下,那就是函数作用域, ...

  6. (原创)JS闭包看代码理解

    <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...

  7. js闭包理解

    js闭包的作用是使函数外可以访问函数内部的变量,是通过 在函数内部 定义 访问函数内变量 的函数实现的,内部的一个函数产生一个闭包 function a() { var i=0; return fun ...

  8. js闭包理解实例小结

    Js闭包 闭包前要了解的知识  1. 函数作用域 (1).Js语言特殊之处在于函数内部可以直接读取全局变量 <script type="text/javascript"> ...

  9. Js闭包的用途

    本来想总结一点JavaScript中的闭包的一些用法,在查资料的时候发现了一篇很好的文章,就转过来收藏了,下面附上传送门: js闭包的用途 ---------sunlylorn 我们来看看闭包的用途. ...

随机推荐

  1. Coursera课程笔记----C程序设计进阶----Week 3

    函数的递归(Week 3) 什么是递归 引入 函数可以嵌套调用:无论嵌套多少层,原理都一样 函数不能嵌套定义:不能在一个函数里再定义另一个函数,因为所有函数一律平等 问题:一个函数能调用它自己吗? 举 ...

  2. for do-while while区别

    分别用for  do-while while求1-100的和

  3. Programmatically add an application to Windows Firewall

    Programmatically add an application to Windows Firewall 回答1   Not sure if this is the best way, but ...

  4. 【Hadoop离线基础总结】Hadoop High Availability\Hadoop基础环境增强

    目录 简单介绍 Hadoop HA 概述 集群搭建规划 集群搭建 第一步:停止服务 第二步:启动所有节点的ZooKeeper 第三步:更改配置文件 第四步:启动服务 简单介绍 Hadoop HA 概述 ...

  5. 【Hadoop离线基础总结】oozie调度hive

    目录 1.拷贝hive的案例模板 2.编辑hive模板 3.上传工作文件到hdfs 4.执行oozie的调度 5.查看调度结果 1.拷贝hive的案例模板 cd /export/servers/ooz ...

  6. 前端面试题-WebSocket的实现和应用

    (1)什么是WebSocket? WebSocket是HTML5中的协议,支持持久连续,http协议不支持持久性连接.Http1.0和HTTP1.1都不支持持久性的链接,HTTP1.1中的keep-a ...

  7. 解决 es CircuitBreakingException 问题

    比如频繁报如下错误, [2019-06-16T15:31:22,778][DEBUG][o.e.a.a.c.n.i.TransportNodesInfoAction] [node-xxx] faile ...

  8. MySQL表的CRUD及多表查询

    数据库表的增删改查操作: 增.删.改 查: 单表查询 简单查询.where约束.group by分组.聚合查询.having过滤.order by排序.limit限制.正则匹配 多表查询 连表查询:交 ...

  9. Kafka架构原理

    Kafka架构原理 最终大家会掌握 Kafka 中最重要的概念,分别是 Broker.Producer.Consumer.Consumer Group.Topic.Partition.Replica. ...

  10. mysql运维入门4:索引、慢查询、优化

    MySQL索引用来快速地寻找那些具有特定值的记录,所有MySQL索引都是以B-树的形式保存 如果没有索引,执行查询时,MySQL必须从第一个记录开始整表扫描,知道查询到符合要求的记录,记录越大,花费时 ...