在前端开发必须知道的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种方式实现闭包,一种是用参数存储数组的值,一种是用临时变量存储,后者必须要深拷贝。所有要通过闭包存储非持久型变量,均可以用临时变量或参数两种方式实现。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript"><!-- var arr = [4, 5, 6, 8, 7, 9, 3, 2, 1, 0]; var $ = function(id) { return document.getElementById(id); } var Sort = { Insert: function() { for (var i = 1; i < arr.length; i++) { for (var j = 0; j < i; j++) { if (arr[i] < arr[j]) { arr[i] = [arr[j], arr[j] = arr[i]][0]; } } setTimeout((function() { var m = []; for (var j = 0; j < arr.length; j++) { m[j] = arr[j]; } return function() { $("proc").innerHTML += m + "<br />"; } })(), i * 500); //or 写成下面这样也可以 /* setTimeout((function(m) { return function() { $("proc").innerHTML += m + "<br />"; } })(arr.join(",")), i * 500); */ } return arr; } } // --></script> </head> <body> <div> var a = [4, 5, 6, 8, 7, 9, 3, 2, 1, 0];</div> <div> <input type="button" value="插入排序" οnclick="Sort.Insert();" /> </div> Proc:<br /> <div id="proc"> </div> </body> </html>

  应用2:

  这个是无忧上的例子(点击这里查看原帖),为每个<li>结点绑定click事件弹出循环的索引值。起初写成

id.onclick = function(){
alert(i);
}

  id.onclick = function(){alert(i);}

发现最终弹出的都是4,而不是想要的 1、2、3,因为循环完毕后i值变成了4。为了保存i的值,同样我们用闭包实现:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript"><!-- window.onload = function() { for (var i = 1; i < 4; i++) { var id = document.getElementById("a" + i); id.onclick = (function(i) { return function() { alert(i); } })(i); } } // --></script> </head> <body> <ul> <li id="a1">aa</li> <li id="a2">aa</li> <li id="a3">aa</li> </ul> </body> </html>

  (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的例子(点击这里查看原帖),用闭包实现程序的暂停执行功能,还蛮创意的。

<input type="button" value="继续" οnclick='st();'/> <script type="text/javascript"><!-- var st = (function() { alert(1); alert(2); return function() { alert(3); alert(4); } })(); // --></script>

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript"> var $ = function(id) { return "string" == typeof id ? document.getElementById(id) : id; } var doConfirm = function(divId) { $(divId).style.display = ""; function closeDiv() { $(divId).style.display = "none"; } return function(isOk) { if (isOk) { alert("Do deleting..."); } closeDiv(); } } </script> <style type="text/css"> body { font-family: Arial; font-size: 13px; background-color: #FFFFFF; } #confirmDiv { width: 200px; height: 100px; border: dashed 1px black; position: absolute; left: 200px; top: 150px; } </style> </head> <body> <div> <input name="btn2" type="button" value="删除" οnclick="doConfirm('confirmDiv');" /> <div id="confirmDiv" style="display: none;"> <div style='position: absolute; left: 50px; top: 15px;'> <p> 你确定要删除吗?</p> <input type="button" value="确定" οnclick="doConfirm('confirmDiv')(true);" /> <input type="button" value="取消" οnclick="doConfirm('confirmDiv')(false);" /> </div> </div> </div> </body> </html>

  看了上面的这些应用,再回到前面的一句话:在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据。这就是闭包的作用。也就说遇到需要存储动态变化的数据或将被回收的数据时,我们可以通过外面再包裹一层函数形成闭包来解决。

  当然,闭包会导致很多外部函数的调用对象不能释放,滥用闭包会使得内存泄露,所以在频繁生成闭包的情景下我们要估计下他带来的副作用。

  毕了。希望能对大家有所帮助。

转载于:https://www.cnblogs.com/ljchow/archive/2010/07/06/1768749.html

前端开发必须知道的JS(二) 闭包及应用相关推荐

  1. 13个你必须知道的JS数组技巧

    在Javascript中,数组是一个重要且常见的知识点,我们经常将数据存储在数组中.作为一名Javascript工程师,数组必须要运用自如.这篇文章,向大家展示了在日常开发中,数组有哪些奇淫技巧值得关 ...

  2. js数组按中文拼音排序_收藏 | JS开发必须知道的41个技巧

    作者:火狼https://cloud.tencent.com/developer/article/1666138 JS是前端的核心,但有些使用技巧你还不一定知道: 本文梳理了JS的41个技巧,帮助大家 ...

  3. 收藏 | JS开发必须知道的41个技巧

    [本文来自转载]Vue中文社区 转载来自-作者:火狼-腾讯云开发文章: 腾讯云开发文章 本博客转载源码地址: https://github.com/lanzhsh/react-vue-koa 2, O ...

  4. js多层对象数组 合并_13个你必须知道的JS数组技巧

    在Javascript中,数组是一个重要且常见的知识点,我们经常将数据存储在数组中.作为一名Javascript工程师,数组必须要运用自如.这篇文章,向大家展示了在日常开发中,数组有哪些奇淫技巧值得关 ...

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

    ①:说说Vue和Angular.ReactJS的相同点和不同点 ②:简单描述一下Vue中的MVVM模型 ③:v-if和v-show指令有什么区别? ④:如何阻止Vue中的绑定事件不发生冒泡 ⑤:父.子 ...

  6. css设置按钮竖直方向居中_前端设计师必须知道的10个重要的CSS技巧

    对于一个初入门的前端设计师,在设计修改网站前端的时候,我们需要编写一些CSS.JS的内容达到界面效果.今天分享10个对于前端设计师来说重要的CSS技巧,这也是我在给许多客户做网站的过程当中总结出来的. ...

  7. vue x 兼容iphone_作为前端你必须知道的iPhoneX适配

    ​1.  iPhoneX的介绍 屏幕尺寸 我们熟知的iPhone系列开发尺寸概要如下: △ iPhone各机型的开发尺寸 转化成我们熟知的像素尺寸: △ 每个机型的多维度尺寸 倍图其实就是像素尺寸和开 ...

  8. IOS开发必须知道的3DLabel实现过程

    最近写了很多关于SceneKit 的入门教程文章,初衷就是想给应用增加一点色彩,今天就教大家实现一个简单的3DLabel 的小框架.如果你的应用中需要实现3D字体展示的功能,这个可能是你最好的选择. ...

  9. 网页最少要有一个html标签,PHP_网页开发人员必须知道的10个不常用HTML标签, 网页开发人员常常希望能 - phpStudy...

    网页开发人员必须知道的10个不常用HTML标签 网页开发人员常常希望能够了解并掌握多种语言,结果是,学习一门语言的所有内容是棘手的,但是却很容易发现你并没有完全利用那些比较特殊却很有用的标签. 不幸的 ...

  10. 你必须知道的.NET(第2版)

      王涛 编著 ISBN 978-7-121-14128-7   2011年7月出版 定价:79.00元 16开 548页 宣传语:以深入浅出的笔法,触摸.NET底层和框架的有趣角落,了解.NET平台 ...

最新文章

  1. .net内存回收与Dispose﹐Close﹐Finalize方法
  2. 【 MATLAB 】常用的离散时间序列的 Matlab 产生
  3. 导入导出 Oracle 分区表数据
  4. plsql只提交存储过程里的事务_plsql 存储过程 事务 | 学步园
  5. 可耻的客户端,可怕的征兆,必须趁早消灭它
  6. 推荐常用的小程序Ui框架
  7. php 读取数据库信息,php读取数据库信息的几种方法
  8. MongoTemplate 关于 insert 和 save 函数的区别
  9. cudnn的安装 linux
  10. 经典 55道 MySQL面试题及答案
  11. tesseract-ocr安装简体中文语言包
  12. 【校招VIP】产品行测之逻辑计算题
  13. VM虚拟机安装win7系统(亲测可用!!!)
  14. JavaScript操作canvas制作前端H5小游戏——Flappy Bird
  15. 解决无法读取META-INF.services里面定义的类
  16. 去除图片链接边框及其链接虚线
  17. 在OpenWrt上配置原生IPv6 NAT,,实现校园网路由器使用ipv6
  18. 快讯 I Nexperia 超低电容 ESD 保护二极管保护汽车数据接口
  19. Linux命令:使用dig命令解析域名
  20. pwngdb+pwngef+peda三合一+pwngdb+pwndbg联合使用

热门文章

  1. 【SPOJ - DQUERY】D-query【主席树 —— 区间中不同数的个数】
  2. 【POJ-2796】Feel Good【单调栈】
  3. 【15年浙江省赛 C ZOJ 3871】Convex Hull【极角排序】
  4. 使用EDD枚举域数据
  5. 西门子S7系列中间人攻击:防御和流量异常检测(三)
  6. 766.托普利茨矩阵(力扣leetcode) 博主可答疑该问题
  7. 第一台全自动电子计算机,关于世界上第一台电子计算机ENIAC的叙述错误的是() senny全自动微电脑水位控制仪...
  8. selenium-js
  9. python学习笔记:操作Excle
  10. Day.js 是一个仅 2kb 大小的轻量级 JavaScript 时间日期处理库,和 Moment.js 的 API 设计保持完全一样,dayjs...