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的闭包的一个示例说明
Nov 18 Javascript
js 屏蔽鼠标右键脚本附破解方法
Dec 03 Javascript
jquery1.4 教程二 ajax方法的改进
Feb 25 Javascript
在一个js文件里远程调用jquery.js会在ie8下的一个奇怪问题
Nov 28 Javascript
JavaScript 处理Iframe自适应高度(同或不同域名下)
Mar 29 Javascript
js中单引号与双引号冲突问题解决方法
Oct 04 Javascript
JS采用绝对定位实现回到顶部效果完整实例
Jun 20 Javascript
jQuery操作dom实现弹出页面遮罩层(web端和移动端阻止遮罩层的滑动)
Aug 25 Javascript
javascript中闭包概念与用法深入理解
Dec 15 Javascript
JS获取子、父、兄节点方法小结
Aug 14 Javascript
js中this对象用法分析
Jan 05 Javascript
vuejs数据超出单行显示更多,点击展开剩余数据实例
May 05 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 win下Socket方式发邮件类
2009/08/21 PHP
Linux fgetcsv取得的数组元素为空字符串的解决方法
2011/11/25 PHP
实现PHP+Mysql无限分类的方法汇总
2015/03/02 PHP
ThinkPHP 3.2.3实现加减乘除图片验证码
2018/12/05 PHP
PHP的mysqli_set_charset()函数讲解
2019/01/23 PHP
tp5框架基于Ajax实现列表无刷新排序功能示例
2020/02/10 PHP
javascript删除字符串最后一个字符
2014/01/14 Javascript
node.js使用nodemailer发送邮件实例
2014/03/10 Javascript
自制的文件上传JS控件可支持IE、chrome、firefox etc
2014/04/18 Javascript
Js删除数组中某一项或几项的几种方法(推荐)
2016/07/27 Javascript
jQuery simpleModal插件的使用介绍
2016/08/30 Javascript
Vue.js学习之过滤器详解
2017/01/22 Javascript
requirejs按需加载angularjs文件实例
2017/06/08 Javascript
基于vue实现网站前台的权限管理(前后端分离实践)
2018/01/13 Javascript
jQuery实现的下雪动画效果示例【附源码下载】
2018/02/02 jQuery
利用Dectorator分模块存储Vuex状态的实现
2019/02/05 Javascript
vue基础之事件v-onclick=&quot;函数&quot;用法示例
2019/03/11 Javascript
一文快速详解前端框架 Vue 最强大的功能
2019/05/21 Javascript
Vue使用路由钩子拦截器beforeEach和afterEach监听路由
2020/11/16 Javascript
Python编码爬坑指南(必看)
2016/06/10 Python
python发送邮件实例分享
2017/07/28 Python
Python2/3中urllib库的一些常见用法
2017/12/19 Python
Python实现批量执行同目录下的py文件方法
2019/01/11 Python
canvas绘制圆角头像的实现方法
2019/01/17 HTML / CSS
Peter Millar官网:美国高档生活服饰品牌
2018/07/02 全球购物
DC Shoes荷兰官方网站:美国极限运动品牌
2019/10/22 全球购物
俄罗斯设计师家具购物网站:The Furnish
2019/12/01 全球购物
经理管理专业自荐信范文
2013/12/31 职场文书
如何撰写一封出色的求职信
2014/04/27 职场文书
银行奉献演讲稿
2014/09/16 职场文书
收款授权委托书
2014/10/02 职场文书
小石潭记导游词
2015/02/03 职场文书
毕业论文答辩稿范文
2015/06/23 职场文书
2016秋季小学开学寄语
2015/12/03 职场文书
聊聊pytorch测试的时候为何要加上model.eval()
2021/05/23 Python
一文了解Java动态代理的原理及实现
2022/07/07 Java/Android