《JAVASCRIPT高级程序设计》闭包
一、闭包的概念
闭包是JAVASCRIPT中最重要的概念之一,闭包是指有权访问另一个函数作用域中变量的函数;创建闭包常见的方式,就是在一个函数内部,创建另一个函数。以下的例子创建了一个闭包,加粗的两行代码访问了外部函数中的变量propertyName,即使这个内部函数被返回后在其他地方调用,它仍然可以访问这个变量。
function createComparison(propertyName){ return function(object1, object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1 > value2){ return 1; }else if(value1 < value2){ return -1; }else{ return 0; } }; }
一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中只保留全局作用域,但是闭包的情况有所不同。要弄清楚其中的细节,必须彻底弄清楚函数在第一次调用时都发生了什么。
当某个函数被调用时,会创建一个执行环境以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性[[scope]];然后用this, arguments和其他命名参数的值来初始化函数的活动对象。在作用域链中,外部函数的活动对象处于第二位,外部函数的外部函数的活动对象处于第三位……一直到作为作用域终点的全局执行环境。
所以,在上面的例子中,当创建compare函数时,会创建一个包含全局变量的作用域链,并保存在内部的[[scope]]属性中;当调用compare函数时,会创建一个执行环境,并复制[[scope]]属性中保存的作用域链。当访问一个变量时,在作用域链中从前往后搜索;一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中只保留全局作用域,但是对于例子中的闭包,当它返回后,它的作用域链被初始化为包含函数的活动对象和全局变量对象。因此,返回的匿名函数可以返回所有的变量。另外,在createComparision执行完毕后,其活动对象也不会销毁,因为匿名函数的作用域链仍然在引用这个活动对象,直到匿名函数也被销毁,如下例:
// 创建闭包(函数) var compareNames = createComparison("name"); // 调用闭包(函数) var result = compareNames({name:"Lillian"},{name:"Matthew"}); // 解除对闭包(函数)的引用,以便安全释放内存 compareNames = null;
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,因此过度使用闭包可能导致内存占用过多,建议在绝对必要时,再考虑使用闭包。
二、副作用以及解决方法
1、变量
作用链机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何一个变量的最后一个值。
function createFunctions(){ var result = new Array(); for(var i = 0; i < 10; i++){ result[i] = function(){ return i; }; } return result; } var returnFunctions = createFunctions(); for(var i = 0; i< 10; i++){ console.log(returnFunctions[i]()); } // 结果是10个10
解决方法:不直接赋值给闭包,而是定义匿名函数并立即执行,将匿名函数的结果赋值给数组;这个匿名函数自身也是一个闭包,不过返回的值是变量i创建时的值。
function createFunctions(){ var result = new Array(); for(var i = 0; i < 10; i++){ result[i] = function(num){ return function(){ return num; }; }(i); } return result; } var returnFunctions = createFunctions(); for(var i = 0; i< 10; i++){ console.log(returnFunctions[i]()); } // 结果是0,1,2,3,4,5,6,7,8,9
2、this
我们知道,在全局环境中,this等于windows对象,而当函数被作为某个对象的方法调用时,this等于那个对象。但是在闭包中,每个函数被调用时,其活动对象都会自动取得两个特殊的变量,this和arguments。内部函数在搜索这两个变量时,只搜索到活动对象为止,因此永远不可能访问外部函数中的变量。如下例所示:
var name = "The Windows"; var object = { name :"my Object", getNameFun:function(){ return function(){ return this.name; }; } }; alert(object.getNameFun()()); //"The Windows"
我们可以通过以下这种方式来避免这个问题:
var name = "The Windows"; var object = { name :"my Object", getNameFun:function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFun()()); //"my Object"
3、内存泄露
闭包可能会带来内存泄露问题:由于闭包会引用包含函数的整个活动对象,如果闭包的作用域链中保存着一个html对象,那么久意味着该元素无法被销毁(活动对象至少保存着一个引用,通过引用计数来销毁内存的话,引用计数不为0,则无法销毁)。
三、模仿块级作用域
JAVASCRIPT没有块级作用域的概念,这就意味着在块语句中定义的变量,实际是保存在函数中而非语句中的。
function outputNumbers(count){ for(var i = 0; i< count; i++){ } alert(i); // 不会报错,由于没有块级作用域,for循环结束后,i并不会被销毁 }
我们函数声明转化成函数表达式,来模仿块级作用域:
(function(){ // 这里是块级作用域 })();
在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。通过这种方式来创建私有作用域,可以很好的避免这个问题:
function outputNumbers(count){ (function(){ for(var i = 0; i< count; i++){ } })(); alert(i); // 报错,i没有定义 }
四、函数的定义
说了这么多,我们再来回顾一下函数的定义。定义函数的方式有两种,一种是函数声明,一种是函数表达式。其中,函数声明的一个重要特征是函数声明提升,这意味着可以把函数声明放在函数调用的后面;而函数表达式没有这种特性。
// 正确 sayHi(); function sayHi(){ alert("Hi"); } // 报错 sayHello(); var sayHello = function(){ alert("helllo"); };
《JAVASCRIPT高级程序设计》闭包的更多相关文章
- JavaScript高级程序设计——闭包
前言 有很多人搞不清匿名函数和闭包这两个概念,经常混用.闭包是指有权访问另一个函数作用域中的变量的函数.匿名函数就是没有实际名字的函数. 闭包 概念 闭包,其实是一种语言特性,它是指的是程序设计语言中 ...
- 《JavaScript高级程序设计(第3版)》阅读总结记录第一章之JavaScript简介
前言: 为什么会想到把<JavaScript 高级程序设计(第 3 版)>总结记录呢,之前写过一篇博客,研究的轮播效果,后来又去看了<JavaScript 高级程序设计(第3版)&g ...
- 读javascript高级程序设计00-目录
javascript高级编程读书笔记系列,也是本砖头书.感觉js是一种很好上手的语言,不过本书细细读来发现了很多之前不了解的细节,受益良多.<br/>本笔记是为了方便日后查阅,仅作学习交流 ...
- 读javascript高级程序设计-目录
javascript高级编程读书笔记系列,也是本砖头书.感觉js是一种很好上手的语言,不过本书细细读来发现了很多之前不了解的细节,受益良多.<br/>本笔记是为了方便日后查阅,仅作学习交流 ...
- 读书笔记(03) - 性能 - JavaScript高级程序设计
作用域链查找 作用域链的查找是逐层向上查找.查找的层次越多,速度越慢.随着硬件性能的提升和浏览器引擎的优化,这个慢我们基本可以忽略. 除了层级查找损耗的问题,变量的修改应只在局部环境进行,尽量避免在局 ...
- 《Javascript高级程序设计》阅读记录(七):第七章
<Javascript高级程序设计>中,2-7章中已经涵盖了大部分精华内容,所以摘录到博客中,方便随时回忆.本系列基本完成,之后的章节,可能看情况进行摘录. 这个系列以往文字地址: < ...
- JavaScript高级程序设计第三版.CHM【带实例】
从驱动全球商业.贸易及管理领域不计其数的复杂应用程序的角度来看,说 JavaScript 已经成为当今世界上最流行的编程语言一点儿都不为过. JavaScript 是一种非常松散的面向对象语言,也是 ...
- javascript高级程序设计学习笔记
javascript高级程序设计,当枕头书已经好久了~zz 现在觉得自己在js的开发上遇到了一些瓶颈,归根究底还是基础太薄弱,所以重新刷一遍js高程希望有更新的认识. 一.javascript简介 ...
- JavaScript高级程序设计(读书笔记)之函数表达式
定义函数的方式有两种:一种是函数声明,另一种就是函数表达式. 函数声明的一个重要特征就是函数声明提升(function declaration hoisting),意思是在执行代码前会先读取函数声明. ...
- 《JavaScript高级程序设计(第3版)》笔记-序
很少看书,不喜欢看书,主要是上学时总坐不住,没有多大定性,一本书可以两天看完,随便翻翻,也可以丢在角落里几个月不去动一下. 上次碰到了<JavaScript高级程序设计(第3版)>感觉真的 ...
随机推荐
- Ibatis 3.0 之前使用的都是2.0 3.0与2.0的内容有很大的不同
以前用过ibatis2,但是听说ibatis3有较大的性能提升,而且设计也更合理,他不兼容ibatis2.尽管ibatis3还是beta10的状态,但还是打算直接使用ibatis3.0, ibatis ...
- c#之向ftp服务器传文件
.Net提供了FtpWebRequest类,代码如下: using System; using System.Collections.Generic; using System.IO; using S ...
- 缩进(Python很将就格式)
空白在Python中是重要的.事实上行首的空白是重要的.它称为缩进.在逻辑行首的空白(空格和制表符)用来决定逻辑行的缩进层次,从而用来决定语句的分组.这意味着同一层次的语句必须有相同的缩进.每一组这样 ...
- Big Data架构师技能图谱
大数据通用处理平台 Spark Flink Hadoop 分布式存储 HDFS 资源调度 Yarn Mesos 机器学习工具 Mahout Spark Mlib TensorFlow (Google ...
- Memcache第一篇---基础教程
Memcache是什么 Memcache是danga.com的一个项目,最早是为 LiveJournal 服务的,目前全世界不少人使用这个缓存项目来构建自己大负载的网站,来分担数据库的压力. 它可以应 ...
- HUST 1605 Gene recombination
简单广搜.4进制对应的10进制数来表示这些状态,总共只有(4^12)种状态. #include<cstdio> #include<cstring> #include<cm ...
- IOS开发中摇一摇是怎么实现的
三个方法,分别是开始摇一摇,结束摇一摇,取消摇一摇,我们可以在里面对应的进行事件处理,或者在ui上进行信息展示: 1.开始摇一摇:(在实际app中用需要处理的语句替换NSLog(@"开始摇一 ...
- xml数据传输
- 代码中使用bitmap资源并加载到控件上
1.从res/drawable/XX.jpg里引用图片资源: 1. Resources res = getResources(); Bitmap inDrawable= BitmapFactory.d ...
- 【Xilinx-Petalinux学习】-06-OpenCV通过USB摄像头采集图像。
占位, 实现USB摄像头的图像采集与保存