前端开发必须知道的JS之闭包及应用

本文讲的是函数闭包,不涉及对象闭包(如用with实现)。如果你觉得我说的有偏差,欢迎拍砖,欢迎指教。
在前端开发必须知道的JS之原型和继承一文中说过下面写篇闭包,加之最近越来越发现需要加强我的闭包应用能力,所以此文不能再拖了。本文讲的是函数闭包,不涉及对象闭包(如用with实现)。如果你觉得我说的有偏差,欢迎拍砖,欢迎指教。
一. 闭包的理论
  首先必须了解以下几个概念: 

  执行环境
  每调用一个函数时(执行函数时),系统会为该函数创建一个封闭的局部的运行环境,即该函数的执行环境。函数总是在自己的执行环境中执行,如读写局部变量、函数参数、运行内部逻辑。创建执行环境的过程包含了创建函数的作用域,函数也是在自己的作用域下执行的。从另一个角度说,每个函数执行环境都有一个作用域链,子函数的作用域链包括它的父函数的作用域链。关于作用域、作用域链请看下面。 

  作用域、作用域链、调用对象
  函数作用域分为词法作用域和动态作用域。
  词法作用域是函数定义时的作用域,即静态作用域。当一个函数定义时,他的词法作用域就确定了,词法作用域说明的是在函数结构的嵌套关系下,函数作用的范围。这个时候也就形成了该函数的作用域链。作用域链就是把这些具有嵌套层级关系的作用域串联起来。函数的内部[[scope]]属性指向了该作用域链。
  动态作用域是函数调用执行时的作用域。当一个函数被调用时,首先将函数内部[[scope]]属性指向了函数的作用域链,然后会创建一个调用对象,并用该调用对象记录函数参数和函数的局部变量,将其置于作用域链顶部。动态作用域就是通过把该调用对象加到作用域链的顶部来创建的,此时的[[scope]]除了具有定义时的作用域链,还具有了调用时创建的调用对象。换句话说,执行环境下的作用域等于该函数定义时就确定的作用域链加上该函数刚刚创建的调用对象,从而也形成了新的作用域链。所以说是动态的作用域,并且作用域链也随之发生了变化。再看这里的作用域,其实是一个对象链,这些对象就是函数调用时创建的调用对象,以及他上面一层层的调用对象直到最上层的全局对象。 
  譬如全局环境下的函数A内嵌套了一个函数B,则该函数B的作用域链就是:函数B的作用域—>函数A的作用域—>全局window的作用域。当函数B调用时,寻找某标识符,会按函数B的作用域—>函数A的作用域—>全局window的作用域去寻找,实际上是按函数B的调用对象—>函数A的调用对象—>全局对象这个顺序去寻找的。也就是说当函数调用时,函数的作用域链实际上是调用对象链。 

  闭包
  在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据(看完下面的应用就会很好的体会这句话)。闭包的定义:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
  闭包就是嵌套在函数里面的内部函数,并且该内部函数可以访问外部函数中声明的所有局部变量、参数和其他内部函数。当该内部函数在外部函数外被调用,就生成了闭包。(实际上任何函数都是全局作用域的内部函数,都能访问全局变量,所以都是window的闭包)
  譬如下面这个例子:
复制代码 代码如下:

<script type="text/javascript">
function f(x) {
var a = 0;
a++;
x++;
var inner = function() {
return a + x;
}
return inner;
}
var test = f(1);
alert(test());
</script> 

垃圾回收机制:如果某个对象不再被引用,该对象将被回收。  
  再结合前面所讲的一些概念,在执行var test=f(1)时创建了f的调用对象,这里暂且记作obj,执行完后虽然退出了外部执行环境,但内部函数inner被外部函数f外面的一个变量test引用。由于外部函数创建的调用对象obj有一个属性指向此内部函数,而现在这个内部函数又被引用,所以调用对象obj会继续存在,不会被垃圾回收器回收,其函数参数x和局部变量a都会在这个调用对象中得以维持。虽然调用对象不能被直接访问,但是该调用对象已成为内部函数作用域链中的一部分,可以被内部函数访问并修改,所以执行test()时,可以正确访问x和a。所以说, 当执行了外部函数时,生成了闭包,被引用的外部函数的变量将继续存在。
二. 闭包的应用
  应用1:
  这个是我在用js模拟排序算法过程遇到的问题。我要输出每一次插入排序后的数组,如果在循环中写成
  setTimeout(function() { $("proc").innerHTML += arr + "<br/>"; }, i * 500);
会发现每次输出的都是最终排好序的数组,因为arr数组不会为你保留每次排序的状态值。为了保存会不断发生变化的数组值,我们用外面包裹一层函数来实现闭包,用闭包存储这个动态数据。下面用了2种方式实现闭包,一种是用参数存储数组的值,一种是用临时变量存储,后者必须要深拷贝。所有要通过闭包存储非持久型变量,均可以用临时变量或参数两种方式实现。 

  [Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

应用2:
  这个是无忧上的例子(点击这里查看原帖),为每个<li>结点绑定click事件弹出循环的索引值。起初写成
id.onclick = function(){ alert(i); }  id.onclick = function(){alert(i);}
发现最终弹出的都是4,而不是想要的 1、2、3,因为循环完毕后i值变成了4。为了保存i的值,同样我们用闭包实现: 

  [Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

(ps:var a = (function(){})(); 与 var a =new function(){}效果是一样的,均表示自执行函数。)
  应用3:
  下面的code是缓存的应用,catchNameArr。在匿名函数的调用对象中保存catch的值,返回的对象由于被CachedBox变量引用导致匿名函数的调用对象不会被回收,从而保持了catch的值。可以通过CachedBox.getCatch("regionId");来操作,若找不到regionId则从后台取,catchNameArr 主要是为了防止缓存过大。
复制代码 代码如下:

<script type="text/javascript">
var CachedBox = (function() {
var cache = {}, catchNameArr = [], catchMax = 10000;
return {
getCatch: function(name) {
if (name in cache) {
return cache[name];
}
var value = GetDataFromBackend();
cache[name] = value;
catchNameArr.push(name);
this.clearOldCatch();
return value;
},
clearOldCatch: function() {
if (catchNameArr.length > catchMax) {
delete cache[catchNameArr.shift()];
}
}
};
})();
</script> 

同理,也可以用这种思想实现自增长的ID。  
复制代码 代码如下:

<script type="text/javascript">
var GetId = (function() {
var id = 0;
return function() {
return id++;
}
})();
var newId1 = GetId();
var newId2 = GetId();
</script> 

应用4:
  这个是无忧上月MM的例子(点击这里查看原帖),用闭包实现程序的暂停执行功能,还蛮创意的。 

  [Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

把这个作用延伸下,我想到了用他来实现window.confirm。 

  [Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

看了上面的这些应用,再回到前面的一句话:在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据。这就是闭包的作用。也就说遇到需要存储动态变化的数据或将被回收的数据时,我们可以通过外面再包裹一层函数形成闭包来解决。
  当然,闭包会导致很多外部函数的调用对象不能释放,滥用闭包会使得内存泄露,所以在频繁生成闭包的情景下我们要估计下他带来的副作用。
  毕了。希望能对大家有所帮助。 

http://www.jb51.net/article/24156.htm

时间: 2024-10-11 02:19:00

前端开发必须知道的JS之闭包及应用的相关文章

前端工程师必须知道的vue前端面试题目汇总

①:说说Vue和Angular.ReactJS的相同点和不同点 ②:简单描述一下Vue中的MVVM模型③:v-if和v-show指令有什么区别?④:如何阻止Vue中的绑定事件不发生冒泡⑤:父.子组件间是如何通信的?⑥:非父子层级的组件如何实现通信?⑦:什么是动态组件?他的作用是什么?⑧:为什么组件中的data属性的值必须是一个函数?答案与详解Q说说Vue和Angular.ReactJS的相同点和不同点与React的相同:●都使用了Virtual DOM●提供了响应式和组件化的视图组件●将注意力集

投身移动开发必须知道的20件事

1.选择你的平台 选择什么样的平台取决于你想做什么以及你的用户是谁.这些平台的最顶层是web.如果你想出售你的应用程序,那么你可能想要将它放在应用商店.如果你需要使用相机或者其他设备的API,那么你可以使用本地方法,或者使用一些封装好了的框架比如AIR/PhoneGap/Titanium. 这里没有明确的答案,你选择什么样的平台取决于很多的事情,因此不妨回顾一下贴在墙上的那些话,也许它们会告诉你答案. 2.甭想快速暴富 很多人对比了当前的应用商店的热潮和19世纪40年代末的加利福尼亚淘金热,正如

五个你必须知道的javascript和web debug技术

转:http://js8.in/2013/11/20/%E4%BA%94%E4%B8%AA%E4%BD%A0%E5%BF%85%E9%A1%BB%E7%9F%A5%E9%81%93%E7%9A%84javascript%E5%92%8Cweb-debug%E6%8A%80%E6%9C%AF/ 在前端开发中,调试技术是必不可少的技能,本文将介绍五种前端开发必备的调试技术. Weinre移动调试 DOM 断点 debugger断点 native方法hook 远程映射本地调试 Weinre 在移动上面

架构师必须知道的26项PHP安全实践

架构师必须知道的26项PHP安全实践 PHP是一种开源服务器端脚本语言,应用很广泛.Apache web服务器提供了这种便利:通过HTTP或HTTPS协议,访问文件和内容.配置不当的服务器端脚本语言会带来各种各样的问题.所以,使用php时要小心.以下是25个PHP安全方面的最佳实践. 为PHP安全提示而提供的示例环境 文件根目录(DocumentRoot):/var/www/html 默认的Web服务器:Apache(可以使用Lighttpd或Nginx来取代Apache) 默认的PHP配置文件

从零开始学习jQuery(剧场版) 你必须知道的javascript

原文:从零开始学习jQuery(剧场版) 你必须知道的javascript 一.摘要 本文是jQuery系列教程的剧场版, 即和jQuery这条主线无关, 主要介绍大家平时会忽略的一些javascript细节.  适合希望巩固javascript理论知识和基础知识的开发人员阅读.   二.前言 最近面试过一些人, 发现即使经验丰富的开发人员, 对于一些基础的理论和细节也常常会模糊. 写本文是因为就我自己而言第一次学习下面的内容时发现自己确实有所收获和感悟.  其实我们容易忽视的javascrip

jQuery_review之使用jQuery的Ajax必须知道的,serialize、param方法以及全局函数

在项目中可能会碰到这样的几个问题: 第一个问题,我们需要实现一个基于ajax的异步程序,我们也相当的熟悉ajax中是可以通过{name:name,address:address}这种方式来进行传递参数的.但是,当任务下达的那一刻,我们发现前端的form表单非常的庞大,庞大的我没有耐心去一个一个的拼字符串. 第二个问题,我们需要将一个充满的checkbox的用户调查表传递给后台,额,难道需要我们写一个过滤器,然后通过each进行遍历,然后拼成这样的一个参数表么?如果你对jQuery的选择器比较了解

(转)【推荐】初级.NET程序员,你必须知道的EF知识和经验

转自:http://www.cnblogs.com/zhaopei/p/5721789.html [推荐]初级.NET程序员,你必须知道的EF知识和经验 阅读目录 [本文已下咒.先顶后看,会涨工资的哦 :)] 注意:以下内容如果没有特别申明,默认使用的EF6.0版本,code first模式. 推荐MiniProfiler插件 工欲善其事,必先利其器. 我们使用EF和在很大程度提高了开发速度,不过随之带来的是很多性能低下的写法和生成不太高效的sql. 虽然我们可以使用SQL Server Pro

《jQuery风暴》第2章 必须知道的JavaScript知识

第2章 必须知道的JavaScript知识 JavaScript是jQuery应用的基础,掌握JavaScript这门语言是使用jQuery的基础条件.本章不会全面细致的讲解JavaScript的全部, 而是讲解其精髓,这些知识可以提升大家的JavaScript内功.切忌,要修炼上乘的武功,必须要有深厚的内功基础,否则只可学到其招式而发挥不了功力.JavaScript实际上包括三部分: w   ECMAScript 描述了该语言的语法和基本对象. w   DOM 描述了处理网页内容的方法和接口.

php必须知道的300个问题-目录

php必须知道的300个问题 第1章 PHP开发规范与入门要点 问题1 如何在Windows下配置PHP开发环境? 答案 问题2 如何在Linux下配置PHP开发环境? 问题3 如何搭建IIS+PHP+MySQL环境? 问题4 PHP集成开发环境的特点有哪些? 问题5 如何应用AppServ搭建PHP开发环境? 问题6 如何通过XAMPP配置PHP开发环境? 问题7 XAMPP——Linux版PHP集成化安装包 问题8 Apache配置文件全解 问题9 PHP.INI配置文件全解 问题10 Ap