[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 原生函數