Javascript闭包实例详解


Posted in Javascript onNovember 29, 2015

什么是闭包

闭包是什么?闭包是Closure,这是静态语言所不具有的一个新特性。但是闭包也不是什么复杂到不可理解的东西,简而言之,闭包就是:

闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。

闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配当在一个函数内定义另外一个函数就会产生闭包上面的第二定义是第一个补充说明,抽取第一个定义的主谓宾——闭包是函数的‘局部变量'集合。只是这个局部变量是可以在函数返回后被访问。(这个不是官方定义,但是这个定义应该更有利于你理解闭包)

理解Javascript的闭包非常关键,本篇试图用最简单的例子理解此概念。

function greet(sth){
  return function(name){
    console.log(sth + ' ' + name);
  }
}
//hi darren
greet('hi')('darren');

或者可以写成这样:

var sayHi = greet('hi');
sayHi('darren');

我们要提的问题是:为什么greet的内部函数能使用sth这个变量?

其内部大致运作如下:

→ 创建全局上下文
→ 执行var sayHi = greet('hi');语句,创建greet上下文,变量sth存储在greet上下文中。
→ 继续执行greet函数内的语句,返回一个匿名函数,虽然greet上下文从堆栈上消失,但sth变量依旧存在于内存的某个空间。
→ 继续执行sayHi('darren');创建了sayHi上下文,并试图搜寻sth变量,但在sayHi这个上下文中没有sth变量。sayHi上下文会沿着一个作用域链找到sth变量对应的那个内存。 外层函数就像一个闭包,其内部函数可以使用外部函数的变量。

一个闭包的简单例子

function buildFunctions(){
  var funcArr = [];
  for(var i = 0; i < 3; i++){
    funcArr.push(function(){console.log(i)});
  }
  return funcArr;
}
var fs = buildFunctions();
fs[0](); //3
fs[1](); //3
fs[2](); //3

以上,为什么结果不是0, 1, 2呢?

--因为i作为一个闭包变量,当前值为3,被内部函数使用。要实现想要的效果,可以在遍历的时候每一次遍历创建一个独立的上下文使其不受闭包影响。而自触发函数可以实现独立上下文。

function buildFunctions(){
  var funcArr = [];

  for(var i = 0; i < 3; i++){
    funcArr.push((function(j){
      return function(){
       console.log(j);
      };
    }(i)));
  }

  return funcArr;
}
var fs = buildFunctions();
fs[0](); //0
fs[1](); //1
fs[2](); //2

本篇的两个例子正好体现了闭包的2个方面:一个是内部函数使用闭包变量,另一个是把内部函数写在自触发函数中从而避免受闭包影响。

做为局部变量都可以被函数内的代码访问,这个和静态语言是没有差别。闭包的差别在于局部变变量可以在函数执行结束后仍然被函数外的代码访问。这意味 着函数必须返回一个指向闭包的“引用”,或将这个”引用”赋值给某个外部变量,才能保证闭包中局部变量被外部代码访问。当然包含这个引用的实体应该是一个 对象,因为在Javascript中除了基本类型剩下的就都是对象了。可惜的是,ECMAScript并没有提供相关的成员和方法来访问闭包中的局部变 量。但是在ECMAScript中,函数对象中定义的内部函数() inner function是可以直接访问外部函数的局部变量,通过这种机制,我们就可以以如下的方式完成对闭包的访问了。

function greeting(name) {
   var text = 'Hello ' + name; // local variable
   // 每次调用时,产生闭包,并返回内部函数对象给调用者
   return function () { alert(text); }
}
var sayHello=greeting( "Closure" );
sayHello() // 通过闭包访问到了局部变量text

上述代码的执行结果是:Hello Closure,因为sayHello()函数在greeting函数执行完毕后,仍然可以访问到了定义在其之内的局部变量text。

好了,这个就是传说中闭包的效果,闭包在Javascript中有多种应用场景和模式,比如Singleton,Power Constructor等这些Javascript模式都离不开对闭包的使用。

ECMAScript闭包模型

ECMAScript到底是如何实现闭包的呢?想深入了解的亲们可以获取ECMAScript 规范进行研究,我这里也只做一个简单的讲解,内容也是来自于网络。

在ECMAscript的脚本的函数运行时,每个函数关联都有一个执行上下文场景(Execution Context) ,这个执行上下文场景中包含三个部分

文法环境(The LexicalEnvironment)
变量环境(The VariableEnvironment)
this绑定

其中第三点this绑定与闭包无关,不在本文中讨论。文法环境中用于解析函数执行过程使用到的变量标识符。我们可以将文法环境想象成一个对象,该对 象包含了两个重要组件,环境记录(Enviroment Recode),和外部引用(指针)。环境记录包含包含了函数内部声明的局部变量和参数变量,外部引用指向了外部函数对象的上下文执行场景。全局的上下文 场景中此引用值为NULL。这样的数据结构就构成了一个单向的链表,每个引用都指向外层的上下文场景。

例如上面我们例子的闭包模型应该是这样,sayHello函数在最下层,上层是函数greeting,最外层是全局场景。如下图:

 

因此当sayHello被调用的时候,sayHello会通过上下文场景找到局部变量text的值,因此在屏幕的对话框中显示出”Hello Closure”
变量环境(The VariableEnvironment)和文法环境的作用基本相似,具体的区别请参看ECMAScript的规范文档。

闭包的样列

前面的我大致了解了Javascript闭包是什么,闭包在Javascript是怎么实现的。下面我们通过针对一些例子来帮助大家更加深入的理解闭包,下面共有5个样例,例子来自于JavaScript Closures For Dummies(镜像)。

例子1:闭包中局部变量是引用而非拷贝

function say667() {
  // Local variable that ends up within closure
  var num = 666;
  var sayAlert = function() { alert(num); }
  num++;
  return sayAlert;
}
var sayAlert = say667();
sayAlert()

因此执行结果应该弹出的667而非666。

例子2:多个函数绑定同一个闭包,因为他们定义在同一个函数内。

function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 666;
  // Store some references to functions as global variables
  gAlertNumber = function() { alert(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}
setupSomeGolbals(); // 为三个全局变量赋值
gAlertNumber(); //666
gIncreaseNumber();
gAlertNumber(); // 667
gSetNumber(12);//
gAlertNumber();//12

例子3:当在一个循环中赋值函数时,这些函数将绑定同样的闭包

function buildList(list) {
  var result = [];
  for (var i = 0; i < list.length; i++) {
    var item = 'item' + list[i];
    result.push( function() {alert(item + ' ' + list[i])} );
  }
  return result;
}
function testList() {
  var fnlist = buildList([1,2,3]);
  // using j only to help prevent confusion - could use i
  for (var j = 0; j < fnlist.length; j++) {
    fnlist[j]();
  }
}

testList的执行结果是弹出item3 undefined窗口三次,因为这三个函数绑定了同一个闭包,而且item的值为最后计算的结果,但是当i跳出循环时i值为4,所以list[4]的结果为undefined.

例子4:外部函数所有局部变量都在闭包内,即使这个变量声明在内部函数定义之后。

function sayAlice() {
  var sayAlert = function() { alert(alice); }
  // Local variable that ends up within closure
  var alice = 'Hello Alice';
  return sayAlert;
}
var helloAlice=sayAlice();
helloAlice();

执行结果是弹出”Hello Alice”的窗口。即使局部变量声明在函数sayAlert之后,局部变量仍然可以被访问到。

例子5:每次函数调用的时候创建一个新的闭包

function newClosure(someNum, someRef) {
  // Local variables that end up within closure
  var num = someNum;
  var anArray = [1,2,3];
  var ref = someRef;
  return function(x) {
    num += x;
    anArray.push(num);
    alert('num: ' + num +
    '\nanArray ' + anArray.toString() +
    '\nref.someVar ' + ref.someVar);
  }
}
closure1=newClosure(40,{someVar:'closure 1'});
closure2=newClosure(1000,{someVar:'closure 2'});
closure1(5); // num:45 anArray[1,2,3,45] ref:'someVar closure1'
closure2(-10);// num:990 anArray[1,2,3,990] ref:'someVar closure2'

闭包的应用

Singleton 单件:

var singleton = function () {
  var privateVariable;
  function privateFunction(x) {
    ...privateVariable...
  }
  return {
    firstMethod: function (a, b) {
      ...privateVariable...
    },
    secondMethod: function (c) {
      ...privateFunction()...
    }
  };
}();

这个单件通过闭包来实现。通过闭包完成了私有的成员和方法的封装。匿名主函数返回一个对象。对象包含了两个方法,方法1可以方法私有变量,方法2访 问内部私有函数。需要注意的地方是匿名主函数结束的地方的'()',如果没有这个'()'就不能产生单件。因为匿名函数只能返回了唯一的对象,而且不能被 其他地方调用。这个就是利用闭包产生单件的方法。

Javascript 相关文章推荐
关于 文本框默认值 的操作js代码
Jan 12 Javascript
jquery中.add()的使用分析
Apr 26 Javascript
AngularJS + Node.js + MongoDB开发的基于高德地图位置的通讯录
Jan 02 Javascript
基于jquery实现人物头像跟随鼠标转动
Aug 23 Javascript
基于jquery实现的树形菜单效果代码
Sep 06 Javascript
基于jQuery的ajax方法封装
Jul 14 Javascript
Javascript中indexOf()和lastIndexOf应用方法实例
Aug 24 Javascript
微信小程序商城项目之商品属性分类(4)
Apr 17 Javascript
ExtJs的Ext.Ajax.request实现waitMsg等待提示效果
Jun 14 Javascript
用vue-cli开发vue时的代理设置方法
Sep 20 Javascript
javascript设计模式 ? 原型模式原理与应用实例分析
Apr 10 Javascript
关于angular 8.1使用过程中的一些记录
Nov 25 Javascript
jQuery实现图片上传和裁剪插件Croppie
Nov 29 #Javascript
javascript高级编程之函数表达式 递归和闭包函数
Nov 29 #Javascript
javascript实现拖动元素交换位置
Nov 29 #Javascript
javascript实现网页中涉及的简易运动(改变宽高、透明度、位置)
Nov 29 #Javascript
通用javascript代码判断版本号是否在版本范围之间
Nov 29 #Javascript
jQuery如何使用自动触发事件trigger
Nov 29 #Javascript
js性能优化技巧
Nov 29 #Javascript
You might like
『PHP』PHP截断函数mb_substr()使用介绍
2013/04/22 PHP
PHP基于curl后台远程登录正方教务系统的方法
2016/10/14 PHP
php微信公众号开发(2)百度BAE搭建和数据库使用
2016/12/15 PHP
PHP删除数组中指定值的元素常用方法实例分析【4种方法】
2018/08/21 PHP
ajax异步刷新实现更新数据库
2012/12/03 Javascript
javascript的原生方法获取数组中的最大(最小)值
2012/12/19 Javascript
document.getElementById获取控件对象为空的解决方法
2013/11/20 Javascript
js实现无限级树形导航列表效果代码
2015/09/23 Javascript
js实现模拟银行卡账号输入显示效果
2015/11/18 Javascript
深入理解JS实现快速排序和去重
2016/10/17 Javascript
javaScript中定义类或对象的五种方式总结
2016/12/04 Javascript
Bootstrap Table从零开始
2017/06/30 Javascript
js 发布订阅模式的实例讲解
2017/09/10 Javascript
React注册倒计时功能的实现
2018/09/06 Javascript
微信小程序日历弹窗选择器代码实例
2019/05/09 Javascript
微信小程序使用npm包的方法步骤
2019/08/13 Javascript
vuex实现数据状态持久化
2019/11/11 Javascript
python生成随机mac地址的方法
2015/03/16 Python
用Python解析XML的几种常见方法的介绍
2015/04/09 Python
Sanic框架安装与简单入门示例
2018/07/16 Python
Python 3 实现定义跨模块的全局变量和使用教程
2019/07/07 Python
使用Python实现微信拍一拍功能的思路代码
2020/07/09 Python
Python+pyftpdlib实现局域网文件互传
2020/08/24 Python
matplotlib相关系统目录获取方式小结
2021/02/03 Python
印尼综合在线预订网站:Tiket.com(机票、酒店、火车、租车和娱乐)
2018/10/11 全球购物
公司周年庆典策划方案
2014/05/17 职场文书
物业保安岗位职责
2014/07/02 职场文书
公司授权委托书样本
2014/09/15 职场文书
学生穿着不得体检讨书
2014/10/12 职场文书
少先队辅导员事迹材料
2014/12/24 职场文书
地球一小时活动总结
2015/02/27 职场文书
机器人总动员观后感
2015/06/09 职场文书
房屋质量投诉书
2015/07/02 职场文书
Django展示可视化图表的多种方式
2021/04/08 Python
python实现的web监控系统
2021/04/27 Python
elementui的el-popover修改样式不生效的解决
2021/06/30 Javascript