1. JavaScript当然是会用的,不过没有深入系统的学习罢了。平常还是用JQuery比较多,原生的JS用到的很少。
  2. 不过前端时间学习Ruby,被动态语言的强大和魔幻给震惊了一把。了解Ruby后,我把目光转移到了这门很早就伴随我的语言。JQuery是一个华丽的外衣,一把好用的工具,但炫彩背后必有强大的后台,让我稍微掀起这幕布的一角吧。
  3.  
  4. ###作用域链
  5. 闭包这个词想必大家不陌生,可谓是动态语言了不起的几把刷子之一了。在JS中,要想理解闭包,首先要拿作用域链这个概念下手。
  6. 先来看一下JS语言的另一种定义:JS是基于词法作用域的语言--**通过阅读包含变量定义在内的源码就能知道变量的作用域。**全局变量在程序中始终是有定义的,**局部变量在声明它的函数体内以及所嵌套的函数内始终是有定义的。**
  7. 在顶层代码中,作用域链由一个全局对象组成。在不包含嵌套的函数体内,作用域链由两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。
  8. **当定义一个函数时,它实际上保存一个作用域链(*很重要,后面靠它混饭了,注意是定义的时候哦*)。**当调用函数时,它创建一个新的对象来储存它的局部变量,并将这个对象添加至保存的那个作用域链上,同时插进一个新的更长的表示函数调用作用域的链。
  9. 对于嵌套函数来说,每次调用外部函数时,内部函数又会重新定义一遍。
  10.  
  11. 这段定义看完之后,估计要彻底晕了。没关系,先对作用域链有个印象即可,后面才是大菜
  12.  
  13. ###闭包定义
  14. 函数对象可以通过作用域链相互关联,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中叫“闭包”。
  15. ###JS闭包
  16. JS采用词法作用域,也就是说,函数的执行依赖于变量作用域,**这个作用域是函数定义时决定的,而不是函数调用时决定的**。
  17. 为了实现语法作用域,JS函数对象内部状态引用当前的作用域链。
  18. 从技术角度看,所有JS函数都是闭包:它们都是对象,它们都关联到作用域链。
  19. 当调用函数时闭包所指向的作用域链和定义函数时的作用域链不是同一个作用域链时,事情就变得非常微妙。当一个函数嵌套了另外一个函数,外部函数将嵌套的函数对象作为返回值返回的时候往往会发生这种事情。很多强大的编程技术都利用了这类嵌套的函数闭包,以至于这种编程模式在JS中非常常见。
  20. 呼呼,这段话看完估计同志们又有点丈二和尚了,学术语言害死人啊。不过还是那句话:没关系,继续看。JS没有密不透风的墙啊。
  21.  
  22. ###嵌套函数的语法作用域
  23. 理解闭包首先要理解嵌套函数的语法作用域规则,看一下这段代码:
  24. ```
  25. var scope = "global scope";
  26. function checkscope(){
  27. var scope = "local scope";
  28. function f() { reutrn scope; };
  29. return f();
  30. }
  31. checkscopre() //"local scope"
  32. ```
  33. 因为返回的是f()的执行结果,所以对结果我们没有疑问。
  34. 稍微改动一下这个函数:
  35. ```
  36. var scope = "global scope";
  37. function checkscope(){
  38. var scope = "local scope";
  39. function f() { reutrn scope; };
  40. return f;
  41. }
  42. checkscopre()() //?
  43. ```
  44. 考虑一下哦,这个结果如何呢?`checkscopre()`返回一个函数,然后我们再执行这个返回的函数,那么很多人认为返回结果就是`"global scope"`喽。
  45. 饿,这其实就是作用域链的陷阱了。回想词法作用域的基本规则:JS函数的执行用到作用域链,这个作用域链是函数定义时创建的。 所以不管何时执行函数f(),这种绑定在执行f()时依然有效。创建时定义的作用域链scope的值是`"local scope"`,那么因此返回结果也是`”local scope”`
  46. 简单来说,闭包这个特性让人吃惊:它们可以捕捉到局部变量(和参数),并一直保持下来,看起来像这些变量绑定到了在其中定义它们的外部函数*(有点绕,建议阅读3遍)*。
  47.  
  48. ###解惑词法作用域规则
  49. 如果理解了词法作用域的规则,就很容易理解闭包:定义大多数函数时的作用域链在调用函数时依然有效。
  50. 理解闭包的困难在于:外部函数中定义的局部变量在函数返回后就不存在了,那么嵌套的函数如何能调用不存在的作用域链呢?
  51. 想搞清楚这个问题,就需要深入了解类似C语言这种更底层的编程语言,并了解基于栈的CPU架构:如果一个函数的局部变量定义在CPU栈中,那么函数返回时它们确实不存在了。
  52. 但回想一下我们是如何定义作用域链的。我们将作用域链描述为一个对象列表,而不是一个栈。每次调用JS函数,都会创建新的对象保持局部变量,把这个对象添加至作用域链中。当函数返回,就删除作用域链中的绑定变量的对象。如果不存在嵌套函数,也没有其他引用指向这个绑定变量的对象,它就会被垃圾回收。如果定义了嵌套函数,每个嵌套函数各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。
  53. 如果这个嵌套函数在外部函数中保存下来,那么它们会和指向变量绑定对象一样被垃圾回收。
  54. 但如果外部函数将嵌套函数作为返回值返回或储存在某个属性里,这时就有一个外部引用指向这个嵌套函数。它就不会被当做垃圾回收,所以它指向的变量绑定对象也不会被当做垃圾回收。
  55. **看到这里相比对闭包有一定了解了,拨云见月时机不远矣。下面通过几个例子练练手,想必闭包从此就是囊中之物喽。**
  56. ###闭包实现计数器
  57. 先看一个不健全的计数器实现:
  58. ```
  59. uniqueInteger.counter = 0;
  60. function uniqueInteger() {
  61. return uniqueInteger.counter++;
  62. }
  63. ```
  64. 这里函数作为对象被赋予了属性`counter `,调用`uniqueInteger()`,就是一个简单的计数器。
  65. 但明显的缺陷是:如果恶意的把`counter`重置或赋值给它一个非整数,就会导致函数错误。
  66. 利用闭包很好的解决这个问题,只要把计数器私有就可以了:
  67. ```
  68. var uniqueInteger = (function() {
  69. var counter = 0;
  70. return function() { return counter++; };
  71. }());
  72. ```
  73. `uniqueInter`就是一个嵌套函数,而`counter`则变成了私有的局部变量。因为第一次已经执行过了外部函数,所以当调用`uniqueInteger()`的时候,实际调用的是嵌套函数,这样`counter`就访问不到,但我们同时实现了功能。
  74. `counter`一样的私有变量不是只能用在一个单独的闭包内,在同一个外部函数内定义的多个嵌套函数也可以访问它,这多个嵌套函数都共享一个作用域链:
  75. ```
  76. function counter() {
  77. var n = 0;
  78. reutrn {
  79. count : function() { return n++; };
  80. reset : function() { n = 0; };
  81. };
  82. }
  83. var c = counter() , d = counter();
  84. c.count() //0
  85. d.count() //0,互不干扰
  86. c.reset() //0
  87. c.count() //0
  88. d.count() //1,因为没有重置d
  89. ```
  90.  
  91. ###闭包的不当用法
  92. 闭包如此美丽,以至于人们如此爱你。但别忘了玫瑰都带刺啊,下面看看闭包使用不当带来的意料之外的后果。
  93. 我们先来看一下正确的用法:
  94. ```
  95. function constfunc(v) { return function() { return v; }; }
  96.  
  97. var funcs = [];
  98. for(var i = 0; i < 10; i++) funcs[i] = constfunc(i);
  99.  
  100. funcs[5]() //5
  101. ```
  102. 我们稍微变一下形式,看看写类似代码往往会犯的一个错误:
  103. ```
  104. function constfuncs() {
  105. var funcs = [];
  106. for(var i = 0; i < 10; i++)
  107. funcs[i] = function() { return i; };
  108. return funcs;
  109. }
  110. var funcs = constfuncs();
  111. funcs[5](); //?
  112. ```
  113. 上面的闭包都是在同一个函数调用中定义的,因此他们可以共享变量。当constfuncs()返回时,i=10,所有闭包都共享这个值。因此,数组中函数返回值都是同一个值,这并不是我们想要的。
  114. 如果不理解,回想一下词法作用域的定义哦。
  115. ###后记
  116. 至此,闭包就讲完了。可能还是感觉有不理解的地方,如果是这样,那么建议把不理解的地方多读几遍就会明白了。
  117. 我是一个小白,但是我也想进步哈!

也谈闭包--小白的JS进阶之路的更多相关文章

  1. js进阶之路,关于UI资源的优化(转载)

    以下场景往往由于事件频繁被触发,因而频繁执行DOM操作.资源加载等重行为,导致UI停顿甚至浏览器崩溃. 1. window对象的resize.scroll事件 2. 拖拽时的mousemove事件 3 ...

  2. [ JS 进阶 ] 闭包,作用域链,垃圾回收,内存泄露

    原网址:https://segmentfault.com/a/1190000002778015 1. 什么是闭包? 来看一些关于闭包的定义: 闭包是指有权访问另一个函数作用域中变量的函数 --< ...

  3. 2. web前端开发分享-css,js进阶篇

    一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无从下手 ...

  4. 浅谈Android进阶之路

    过去十年是移动互联网蓬勃发展的黄金期,相信每个人也都享受到了移动互联网红利,在此期间,移动互联网经历了曙光期.成长期.成熟期.现在来说已经进入饱和期.依然记得在 2010-2013 年期间,从事移动开 ...

  5. 【 D3.js 进阶系列 】 进阶总结

    进阶系列的文章从去年10月开始写的,晃眼又是4个多月了,想在年前总结一下. 首先恭祝大家新年快乐.今年是羊年吧.前段时间和朋友聊天,聊到十二生肖里为什么没猫,我张口就道:不是因为十二生肖开会的时候猫迟 ...

  6. web前端开发分享-css,js进阶篇

    一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践 经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无 从 ...

  7. JS 进阶知识点及常考面试题

    将会学习到一些原理相关的知识,不会解释涉及到的知识点的作用及用法,如果大家对于这些内容还不怎么熟悉,推荐先去学习相关的知识点内容再来学习原理知识. 手写 call.apply 及 bind 函数 涉及 ...

  8. js进阶---12-11、jquery如何给动态创建出来的元素绑定事件

    js进阶---12-11.jquery如何给动态创建出来的元素绑定事件 一.总结 一句话总结:通过事件委托的方式,通过on方法 1.on方法在事件绑定的时候,data方式带额外参数时,字符串参数和其它 ...

  9. js进阶---12-12、jquery事件委托怎么使用

    js进阶---12-12.jquery事件委托怎么使用 一.总结 一句话总结:通过on方法(事件委托),给要绑定事件的元素的祖先绑定事件,从而达到效果. 1.事件委托是什么? 通过事件冒泡,让子元素绑 ...

随机推荐

  1. php基础07:流程控制

    <?php //1.PHP foreach循环只适用于数组,并用于遍历数组中的每个键/值对. $colors = array("red","green", ...

  2. JS调用Silverlight方法拾遗

    在最近做的物联网项目中,需要利用封装过的Silverlight刻度控件显示温度,湿度,二氧化碳浓度等值.由于最新的数据是通过js ajax获取的,所以需要把这些数据传递给silverlight显示,这 ...

  3. GEOS库学习之四:几何关系判断

    原理上一篇已经介绍过了,这篇就直接进行程序练习 #include "geos.h" GeometryFactory factory; //创建一条环线,与线的区别就是环线是闭合的. ...

  4. Java NIO框架Mina、Netty、Grizzly介绍与对比(zz)

    Mina:Mina(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络应用 ...

  5. 数据库服务器的安装 (MySQL Server 5.7) :

    MySQL 和 MariaDB 都是 Ubuntu 16.04 中的数据库服务器. MySQL Server 和 MariaDB Server的安装包都可以在Ubuntu 的默认软件源中找到,我们可以 ...

  6. WPF Binding INotifyPropertyChanged 多线程 深入理解

    例子 先来看一个例子 Person.cs public class Person : ObservableObject,INotifyPropertyChanged { private string ...

  7. 解放双手:如何在本地调试远程服务器上的Node代码

    写在前面 谈到node断点调试,目前主要有三种方式,通过node内置调试工具.通过IDE(如vscode).通过node-inspector,三者本质上差不多.本文着重点在于介绍 如何在本地通过nod ...

  8. CodeIgniter框架中关于URL(index.php)的那些事

    最近,在做自己的个人网站时,采用了轻量级的php框架CodeIgniter.乍一看上去,代码清晰简洁,MVC模型非常容易维护.开发时我采用的工具是Netbeans IDE 8.0,当然,本文的内容和开 ...

  9. Java问题:Quartz,Hibernate,Spring,Tomcat中定时任务无故停止,没有错误

    最近在做一个java项目的时候遇到一个十分奇怪的问题,想到大家可能也会遇到这样的问题,所以在此发出来,希望大家遇到的时候能够快速解决! 直入主题 问题:使用quartz进行定时任务自动执行的时候,用到 ...

  10. [USACO2005][poj2229]Sumsets(递推)

    http://poj.org/problem?id=2229 分析: 显然的递推 若n为奇数,那么肯定是在n-1的基础上前面每个数+1,即f[n]=f[n-1] 若n为偶数 当第一位数字是1的时候,等 ...