闭包是什么鬼?

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. 【Spark】SparkStreaming从不同基本数据源读取数据

    文章目录 基本数据源 文件数据源 注意事项 步骤 一.创建maven工程并导包 二.在HDFS创建目录,并上传要做测试的数据 三.开发SparkStreaming代码 四.运行代码后,往HDFS文件夹 ...

  2. 【Hadoop离线基础总结】MapReduce案例之自定义groupingComparator

    MapReduce案例之自定义groupingComparator 求取Top 1的数据 需求 求出每一个订单中成交金额最大的一笔交易 订单id 商品id 成交金额 Order_0000005 Pdt ...

  3. 字节码编程,Byte-buddy篇一《基于Byte Buddy语法创建的第一个HelloWorld》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相对于小傅哥之前编写的字节码编程: ASM.Javassist 系列,Byte Bu ...

  4. 如何在MATLAB下把模糊推理系统转化为查询表(转载)

    如何在MATLAB下把模糊推理系统转化为查询表(原创) http://foundy.blog.163.com/blog/static/2633834420090212202156/?mode=edit ...

  5. JDBC04 PreparedStatement

    PreparedStatement类 存在预编译,用占位符去填参数(参数索引从1开始算),可以防止SQL注入 try { Class.forName("com.mysql.cj.jdbc.D ...

  6. IDEA 创建Spring项目后org.springframework.boot报错

    IDEA 创建 Spring boot 项目后 ,在pom.xml文件中 org.springframework.boot出错,刷新也没有作用. 如图: 可以降低 org.springframewor ...

  7. Selenium + Python + Chrome 自动化测试 环境搭建

    一.下载Python 相关的教程很多,此处不详细记录了,下面是官网下载地址: https://www.python.org/downloads/ 我使用的python版本为 Python 3.6.1 ...

  8. 2020网鼎杯 白虎组reverse:hero

    主函数,当bossexist的值不为0时,while循环dround()函数,循环结束输出flag outflag()函数的flag值由6段数据拼凑而成 while循环的dround()函数有三个选择 ...

  9. AIRAC

    AIRAC是国际上划定的,以28天为一个周期的航行资料定期生效制. 2020年比较特殊,共有14个周期,分别是: 2001 2020/01/02 2020/01/29 2002 2020/01/30 ...

  10. ql自动化测试之路-概述篇

    前言:本节主要讲解自动化测试的基本概述,包括分层自动化测试.自动化测试中用到的工具.以及关于自动化测试的想法 一.分层自动化测试 上图是经典的测试金字塔.用它来形容目前测试投入的价值是比较适合的,同样 ...