零基础轻松学JavaScript闭包


Posted in Javascript onDecember 30, 2016

本文面向初学者,大神轻喷。

闭包是什么?

初学javascript的人,都会接触到一个东西叫做闭包,听起来感觉很高大上的。网上也有各种五花八门的解释,其实我个人感觉,没必要用太理论化的观念来看待闭包。

事实上,你每天都在用闭包,只是你不知道罢了。

比如:

var cheese = '奶酪';
var test = function(){
  alert(cheese);
}

OK,你已经写了一个闭包。

函数也是一个数据类型

变量 cheese 是在全局作用域中的一个变量,当你创建了一个 test 函数,那么,test 和 cheese 就共享一个全局作用域。

你要额外明白的一点是,在js中,函数和变量本质上是一个东西。函数也是一个数据类型。

从上面的定义中也能看出来这一点。你要是不相信的话,我们来看一下咯。

alert(cheese);
alert(test);

 零基础轻松学JavaScript闭包

 零基础轻松学JavaScript闭包

让我们再来看看 test 和 cheese各是什么类型:

alert(typeof test);

 零基础轻松学JavaScript闭包

alert(typeof cheese);

 零基础轻松学JavaScript闭包

看到了吧,只是类型不同而已,他们都是数据类型。

唯一的不同点就是,函数类型的 test 可以拥有自己内部逻辑,而string类型的 cheese 只能存放一个字面值,这就是区别,仅此而已。

一目了然了,唯一不同的就是普通变量是字面值一样的存在,而函数需要打个括号才能执行而已。

你看,我现在打一个括号:

test();

零基础轻松学JavaScript闭包

打了括号,才会执行函数里面的逻辑。

作用域

让我们回到闭包,现在将之前的代码做一个小小的变动:

var cheese = '奶酪';
var test = function(){
  alert(cheese);
}
function test2(){
  var cheese = null;
  test();
}
test2();

那么,你觉得现在 alert 出来的是 null 还是奶酪呢?

思考一下。。。

对的,弹出来的还是奶酪。

零基础轻松学JavaScript闭包

之前已经说过了,函数 test 和 变量 cheese 同处于一片蓝天下 -- 同一个作用域。

函数 test 和 变量 cheese 共同享有的作用域叫做全局作用域,就好像地球一样,我们所有的人都享有这个地球,能够在这里呼吸,吃饭,玩耍。

对test而言,他能访问到的作用域只有它本身的闭包和全局作用域:

零基础轻松学JavaScript闭包

也就是说,正常情况下他访问不到其他闭包里的内容,在 test2 里面定义的变量跟它没有半毛钱关系,所以弹出来的 cheese 依旧是全局作用域里的 cheese。

函数可以创造自己的作用域。

我们刚才定义了一个 test 函数,{ } 包裹起来的部分就形成了一个新的作用域,也就是所谓的闭包。

其实你深刻了解了作用域的原理后,闭包也就理解了。

就好比地球是一个全局作用域,你自己家的房子是一个函数,你的房子是私人空间,就是一个局部作用域,也就是你自己建了一个闭包!

你透过窗户可以看见外边的景色,比如院子里的一棵芭蕉树,你于是通过眼镜观察看到了芭蕉树的颜色,高度,枝干的粗细等等。

这一棵芭蕉树相当于一个全局变量,你在自己的闭包内可以访问到它的数据。

所以,在这个例子中,test 就是一个房子,在里面可以通过窗户访问到全局作用域中的奶酪 —— 变量 cheese。

也就是说,cheese 在被 test 访问到的时候,就进入了它的闭包。

这样解释,你是否觉得好理解一点呢?

现在你是否可以理解一开始我说,闭包这东西其实我们天天都在用的意思了呢?

我们给出闭包的第一个注解:

1. 闭包就是在函数被创建的时候,存在的一个私有作用域,并且能够访问所有的父级作用域。

回到刚才的例子:

var cheese = '奶酪';
var test = function(){
  alert(cheese);
}
function test2(){
  var cheese = null;
  test();
}

在这个例子中,test 和 test2 各自享有一个作用域,对不对?而且他们互相不能访问。比如,我在 test 中定义的一个变量,test2就无法直接访问。

var test = function(){
  var i = 10;
}
function test2(){
  alert(i);
}
test2();

像这样,一旦执行 test2 函数,编译就不通过,因为在 test2的闭包内,根本找不到变量 i 。它首先会在自己的闭包内寻找 i,找不到的话就去父级作用域里找,这边的父级就是全局作用域,很遗憾,还是没有。这就是所谓的作用域链,它会一级一级往上找。如果找到最顶层,还是找不到的话,就会报错了。

零基础轻松学JavaScript闭包

在这里,还有一个需要注意的点就是:如果某一个闭包中对全局作用域(或父级作用域)中的变量进行了修改,那么任何引用该变量的闭包都会受到牵连。

这的确是一个需要注意的地方。

举个例子

var cheese = '奶酪';
var test = function(){
  cheese = '奶酪被偷吃了!'
}
function test2(){
  alert(cheese);
}
test();
test2();

结果是:

零基础轻松学JavaScript闭包

很有趣,是不是呢?

当我们在定义一个函数,就产生了一个闭包,如果这个函数里面又有若干的内部函数,就是闭包嵌套着闭包。

像这样:

function house(){
  var footBall = '足球';
  /* 客厅 */
  function livingRoom(){
    var table = '餐桌';
    var sofa = '沙发';
    alert(footBall);
  }
  
  /* 卧室 */
  function bedRoom(){
    var bed = '大床';
  }
  
  livingRoom();
}
house();

函数house是一个闭包,里面又定义了两个函数,分别是livingRoom客厅,和bedRoom卧室,它们各自形成一个自己的闭包。对它们而言,父级作用域就是house。

如果我们希望在客厅里踢足球,在livingRoom函数执行的时候,它会先在自己的闭包中找足球,如果没找到,就去house里面找。一层一层往上找,直至找到了为止。当然,这个例子可能不是很恰当。但起码展示了作用域,闭包之间的联系。

再说明一下, 闭包就是在函数被创建的时候,存在的一个私有作用域,并且能够访问所有的父级作用域。因此,从理论上讲,任何函数都是一个闭包!

2. 如何将私有数据暴露出去

之前有这样一个例子

var test = function(){
  var i = 10;
}
function test2(){
  alert(i);
}
test2();

函数 test 和 test2 各自形成一个闭包,两个闭包之间无法访问对方的私有数据。比如,在 test 中定义的变量,在 test2 里面是无法直接访问到的。

那么问题来了, 当然,这边和挖掘机没关系。这里的问题是,有没有什么办法让 test2 可以访问到其他闭包中的私有变量呢?

办法当然是有的,最直接的想法就是,大不了我定义一个全局变量,在 test 中将私有数据赋给全局变量,然后在 test2 里面就能访问到了。

是的,因为两个函数共同享有一个全局作用域,所以这个办法确实可行。我在很多项目里也的确看到很多人就是这么做的。

那么,有没有一种更好的方法呢?要知道,全局作用域是一个比较敏感的地方,一不小心就会出现变量名重复的问题。顺便说一句,在全局作用域中,尽量不要使用诸如 temp , a , b , c 这一类的大众化变量。

于是,这就牵扯到返回值的相关知识了,你在C语言的教材中肯定见惯了类似于这样的代码

int sum(int a,int b)
{
  return a + b;
}
int all = sum(3,5);

这是一个简单的求和函数,很多人慢慢地养成了这样一个观念,就是函数的返回值就是一个字面值,要么是数字类型,要么是布尔类型,或者是字符串。

在很多强类型的语言,诸如 Java,C,C++, 确实如此。但是 return 在 JavaScript 中却大有来头。

在上一节已经说明了,js 的函数也是一种数据类型,你可以把函数看成是和int , float , double 一样的东西。

那么,既然int可以当做函数的参数或者返回值,函数当然也可以!

请看下面两句话:

在js中

如果函数被当做参数传进去了,它就是所谓的回调函数。

如果函数被当做返回值return出去了,它就是把一个闭包return出去了。

这一章不讲回调函数,如果你不清楚啥叫回调函数,可以去看看有关文章

还是上面的那个例子,我们希望在 test2 中可以访问到 test 里面的变量,可以这样做:

var test = function(){
  var i = 10;
  /* 定义一个函数将变量i暴露出去 */
  var get = function(){
    return i ;
  }
  return get; //将获得i的函数暴露出去
}
function test2(){
  var fn= test();//接收test暴露出来的函数
  alert(fn()); //获得test中的私有数据
}
test2();

零基础轻松学JavaScript闭包

test 函数中的 get 方法是一个内部函数,它自己也形成了一个闭包, test 是他的父级作用域,因此它可以获取i的值。

i 进入 get 方法的闭包,被包了起来,然后最终被返回了出去。

而对于 test2 来说,是可以访问到 test函数的,因此可以调用并执行 test 函数,从而获取其返回值。

你可能会说,我直接在test中把i给return出去就好了嘛,干嘛这么麻烦。

是的,言之有道理。

可是,如果我要访问 test 中多个私有数据咋办捏?

这下你可明白了吧!

现在,我们给出关于闭包的第二个注解:

从应用的角度来看,闭包可以将函数或者对象的私有数据暴露出去,而不影响全局作用域。

零基础轻松学JavaScript闭包

通过这张图,是不是好理解一些了呢?我们这一节单说函数里的私有数据。

2. 将私有数据包装成json对象

刚才的例子说明,在js中,return出去的可以是基本数据类型,也可以是函数类型。

其实,JavaScript是一种基于对象的语言,也有对象的概念,所以,我们可以把你需要的东西包裹成一个对象返回出去!

上代码:

var test = function(){
  var apple = '苹果';
  var pear = '梨子';
  /* 定义一个函数将水果暴露出去 */
  var getFruit = {
    apple : apple ,
    pear : pear
  }
  return getFruit; //将获得i的函数暴露出去
}
function test2(){
  var getFruit = test();//接收test暴露出来的函数
  console.log(getFruit);
}
test2();

像这样用 { } 括起来的东西就是一个js对象,也就是所谓json。你可能经常会听到json这个词,觉得还挺高大上的。其实它就是一个用 { } 包起来的数据而已。

里面是键值对的形式,非常类似于Java里面的HashMap。

在这个例子中,我们可以直接把需要暴露的私有数据用一个 { } 包起来,构成一个json对象return出去就可以啦。

因为是 js 对象,alert 不能看到里面的具体内容,所以我们使用 console.log() ,结果如下:

零基础轻松学JavaScript闭包

展开后:

零基础轻松学JavaScript闭包

这样是不是也可以了?多出来的 proto 是原型链,以后会讲到。

3. 我们来做一个紫金葫芦

大家都还记得西游记里孙悟空用遮天的把戏骗来的紫金葫芦吗,只要你拿着这个葫芦,叫一声别人的名字,如果答应了,别人就会被吸进去。

OK,这个紫金葫芦里面不正如一个闭包吗?

对不对嘛,所以,我们用闭包的知识来做一个好玩的东西吧。

<body>
  <div id='box' style='width:50px;height:50px;background:#333;color:#fff;text-align:center;line-height:50px'>小妖</div>
</body>

零基础轻松学JavaScript闭包

紫金葫芦里面的源码大概是这样的:

var 紫金葫芦 = function(id){
  var domElement = document.getElementById(id);
  var returnObject = {
    domElement : domElement ,
    backgroundColor : function(color){
      domElement.style.backgroundColor = color;
    },
    click : function(fn){
      domElement.onclick = fn;
    }
  };
  return returnObject;
}

注:我纯粹是为了看起来方便而采用中文定义变量,在实际开发中,千万不要使用中文变量。

我们在返回出去的对象上加了三个东西:

1.domElement

你传进来一个id,我就用 document.getElementById 来包一下,得到一个dom元素,最终要操作的也就是这个dom元素。也就是说:

var box1 = 紫金葫芦('box').domElement;
var box2 = document.getElementById('box');
alert(box1 === box2);
Paste_Image.png

零基础轻松学JavaScript闭包

他们是一个东西,一样的。

紫金葫芦('box');

这行代码一旦执行,紫金葫芦就会返回 returnObject 对象,也就是说。我们喊一声 “box”,那个id为box的小妖一答应,就被装进来了,然后我们可以对它为所欲为!

比如,给它换一个背景色:

2.backgroundColor 给元素添加背景色的方法

var box = 紫金葫芦('box');
box.backgroundColor('red');

零基础轻松学JavaScript闭包

3.click 给元素添加点击事件,需要传入一个回调函数

var box = 紫金葫芦('box');
box.click(function(){
  alert('就没人吐槽这个无聊的作者么,小妖也有尊严的好么,啊喂!!');
});

结果:

零基础轻松学JavaScript闭包

也许你已经发现了,这些方法是不是和jQuery有点类似呢?

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
jquery showModelDialog的使用方法示例详解
Nov 19 Javascript
Javascript原型链和原型的一个误区
Oct 22 Javascript
在JavaScript中处理数组之reverse()方法的使用
Jun 09 Javascript
JS建造者模式基本用法实例分析
Jun 30 Javascript
JavaScript浏览器对象之一Window对象详解
Jun 03 Javascript
bootstrap读书笔记之CSS组件(上)
Oct 17 Javascript
重新理解JavaScript的六种继承方式
Mar 24 Javascript
Vue底层实现原理总结
Feb 17 Javascript
Layer弹出层动态获取数据的方法
Aug 20 Javascript
详解VUE Element-UI多级菜单动态渲染的组件
Apr 25 Javascript
layuiAdmin循环遍历展示商品图片列表的方法
Sep 16 Javascript
微信小程序实现自定义底部导航
Nov 18 Javascript
Html5+jQuery+CSS制作相册小记录
Dec 30 #Javascript
Chrome浏览器的alert弹窗禁止再次弹出后恢复的方法
Dec 30 #Javascript
bootstrap表格分页实例讲解
Dec 30 #Javascript
js中数组的常用方法小结
Dec 30 #Javascript
原生js实现可爱糖果数字时间特效
Dec 30 #Javascript
JS实现颜色梯度与渐变效果完整实例
Dec 30 #Javascript
详解JS对象封装的常用方式
Dec 30 #Javascript
You might like
自制短波长线天线频率预选器 - 成功消除B2K之流的镜像
2021/03/02 无线电
第十一节 重载 [11]
2006/10/09 PHP
php读取excel文件的简单实例
2013/08/26 PHP
php实现CSV文件导入和导出
2015/10/24 PHP
ThinkPHP中limit()使用方法详解
2016/04/19 PHP
laravel框架select2多选插件初始化默认选中项操作示例
2020/02/18 PHP
javascript支持firefox,ie7页面布局拖拽效果代码
2007/12/20 Javascript
window.js 主要包含了页面的一些操作
2009/12/23 Javascript
JS DOM 操作实现代码
2010/08/01 Javascript
jquery控制listbox中项的移动并排序的实现代码
2010/09/28 Javascript
angular.foreach 循环方法使用指南
2015/01/06 Javascript
JS实现的通用表单验证插件完整实例
2015/08/20 Javascript
jQuery实现获取绑定自定义事件元素的方法
2015/12/02 Javascript
jQuery UI Bootstrap是什么?
2016/06/17 Javascript
微信小程序 rich-text的使用方法
2017/08/04 Javascript
javascript将list转换成树状结构的实例
2017/09/08 Javascript
[03:55]DOTA2完美大师赛选手传记——LFY.MONET
2017/11/18 DOTA
Python爬取当当、京东、亚马逊图书信息代码实例
2017/12/09 Python
Python 3.8中实现functools.cached_property功能
2019/05/29 Python
详解Python time库的使用
2019/10/10 Python
Python3标准库之threading进程中管理并发操作方法
2020/03/30 Python
荷兰在线啤酒店:Beerwulf
2019/08/26 全球购物
全球领先的在线cosplay服装商店:RoleCosplay
2020/01/18 全球购物
adidas菲律宾官网:adidas PH
2020/02/07 全球购物
幼儿教师考核制度
2014/01/25 职场文书
《富饶的西沙群岛》教学反思
2014/04/09 职场文书
小学班干部竞选演讲稿
2014/04/24 职场文书
优秀的个人求职信范文
2014/05/09 职场文书
2016大学自主招生推荐信范文
2015/03/23 职场文书
2021-4-3课程——SQL Server查询【2】
2021/04/05 SQL Server
90行Python代码开发个人云盘应用
2021/04/20 Python
教你怎么用python selenium实现自动化测试
2021/05/27 Python
一文读懂navicat for mysql基础知识
2021/05/31 MySQL
浅谈Python数学建模之数据导入
2021/06/23 Python
图解排序算法之希尔排序Java实现
2021/06/26 Java/Android
MySQL数据库Innodb 引擎实现mvcc锁
2022/05/06 MySQL