[TOC]
## 7.2 闭包
* 一个函数返回值是另一个函数;
* 返回的函数调用了父函数内部的其他变量;
* 返回的函数在外部被执行。
于是,产生了**闭包**。
### 7.2.1.变量的作用域
变量的作用域有两种:**全局变量和局部变量。**
Javascript语言的特殊之处,就在于**函数内部可以直接读取全局变量。**
~~~
var n=999;
function f1(){
alert(n);
}
f1(); // 999
~~~
另一方面,在函数外部自然无法读取函数内的局部变量。
~~~
function f1(){
var n=999;
}
alert(n); // error
~~~
出于种种原因,我们有时候需要得到函数内的局部变量。于是采用变通的手段:**在函数的内部,再定义一个函数。**
~~~
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
~~~
在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"作用域链"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们就可以在f1外部读取它的内部变量了。
~~~
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
~~~
### 7.2.2 闭包的作用
本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的最大用处有两个:一个是可以**读取函数内部的变量**,另一个就是**让函数内部变量始终保持在内存中**,即闭包可以使得它诞生环境一直存在。
请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。
~~~
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
~~~
上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,**闭包可以看作是函数内部作用域的一个接口**。
为什么会这样呢?原因就在于inc始终在内存中,而`inc的存在依赖于createIncrementor`,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。
闭包的另一个用处,是**封装对象的私有属性和私有方法。**
~~~
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25
~~~
上面代码中,函数Person的内部变量`_age`,通过闭包`getAge和setAge`,变成了返回对象p1的**私有变量**。
注意:外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以**内存消耗很大**。因此不能滥用闭包,否则会造成网页的性能问题。
### 7.2.3 关于this对象
**this对象**是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用是,this等于那个对象。
但是,**匿名函数**的执行环境具有**全局性**,因此其this对象通常指向window。(通过call()或apply()改变执行环境情况除外)
~~~
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //“The Window”
~~~
为什么匿名函数没有取得其包含作用域(或外部作用域)的this对象?
每个函数在被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此不可能直接访问外部函数中的这两个变量。
~~~
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this; ←看这
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"
~~~
把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。
### 7.2.4 模仿块级作用域
匿名函数可以用来模仿块级作用域,避免重复声明同一变量;
用作私有作用域的匿名函数语法如下:
~~~
(function(){
//块级作用域(私有作用域);
})();
~~~
将函数声明包含在一对括号内,会将函数声明转换成函数表达式,函数表达式后面紧跟的`()`会立即调用这个函数。
~~~
function outputNumbers(count) {
for (var i = 0; i < count; i++) {
alert(i); //0,1,2,3,4
}
alert(i); //5
}
~~~
改写后
~~~
function outputNumbers(count) {
(function () {
for (var i = 0; i < count; i++) {
alert(i); //0,1,2,3,4
}
})();
alert(i); //error
}
~~~
在匿名函数中定义的任何变量,都会在执行结束时被销毁,因此,变量i只能在循环中使用。
在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,能够访问包含作用域中的所有变量。
- 前言
- 第一章 JavaScript简介
- 第三章 基本概念
- 3.1-3.3 语法、关键字和变量
- 3.4 数据类型
- 3.5-3.6 操作符、流控制语句(暂略)
- 3.7函数
- 第四章 变量的值、作用域与内存问题
- 第五章 引用类型
- 5.1 Object类型
- 5.2 Array类型
- 5.3 Date类型
- 5.4 基本包装类型
- 5.5 单体内置对象
- 第六章 面向对象的程序设计
- 6.1 理解对象
- 6.2 创建对象
- 6.3 继承
- 第七章 函数
- 7.1 函数概述
- 7.2 闭包
- 7.3 私有变量
- 第八章 BOM
- 8.1 window对象
- 8.2 location对象
- 8.3 navigator、screen与history对象
- 第九章 DOM
- 9.1 节点层次
- 9.2 DOM操作技术
- 9.3 DOM扩展
- 9.4 DOM2和DOM3
- 第十章 事件
- 10.1 事件流
- 10.2 事件处理程序
- 10.3 事件对象
- 10.4 事件类型
- 第十一章 JSON
- 11.1-11.2 语法与序列化选项
- 第十二章 正则表达式
- 12.1 创建正则表达式
- 12.2-12.3 模式匹配与RegExp对象
- 第十三章 Ajax
- 13.1 XMLHttpRequest对象
- 你不知道的JavaScript
- 一、作用域与闭包
- 1.1 作用域
- 1.2 词法作用域
- 1.3 函数作用域与块作用域
- 1.4 提升
- 1.5 作用域闭包
- 二、this与对象原型
- 2.1 关于this
- 2.2 全面解析this
- 2.3 对象
- 2.4 混合对象“类”
- 2.5 原型
- 2.6 行为委托
- 三、类型与语法
- 3.1 类型
- 3.2 值
- 3.3 原生函数