JavaScript 匿名函數(anonymous function)與閉包(closure)

來源:互聯網
上載者:User

本文內容
引入
匿名函數
閉包
變數範圍
函數外部存取函數內部的局部變數
用閉包實現私人成員
引入
閉包是用匿名函數來實現。閉包就是一個受到保護的變數空間,由內嵌函數產生。“保護變數”的思想在幾乎所有的程式設計語言中都能看到。
先看下 JavaScript 範圍:
JavaScript 具有函數級的範圍。這意味著,不能在函數外部存取定義在函數內部的變數。
JavaScript 的範圍又是詞法性質的(lexically scoped)。這意味著,函數運行在定義它的範圍中,而不是在調用它的範圍中。這是 JavaScript 的一大特色,將在後面說明。
把這兩個因素結合在一起,就能通過把變數包裹在匿名函數中而對其加以保護。你可以這樣建立類的私人變數: 複製代碼 代碼如下:var baz;
(function() {
var foo = 10;
var bar = 2;
baz = function() {
return foo * bar;
};
})();
baz();

儘管在匿名函數外執行,但 baz 仍然可以訪問 foo 和 bar。

說明:

1,第 1 行,baz 是全域變數;

2,第 3 ~第 9 行,定義一個匿名函數;

3,第 4 和 5 行,foo 和 bar 是匿名函數內的局部變數;第 6 ~ 8 行,在匿名函數內定義一個匿名函數,並將其賦值給全域變數 baz;

4,第 10 行,調用 baz。若改成 "alert(baz());",將顯示 20;

5,按理說,在匿名函數外不能訪問 foo 和 bar,但是現在可以。

在說明閉包前,先瞭解一下匿名函數。

匿名函數
匿名函數是指那些無需定義函數名的函數。匿名函數與 Lambda 運算式(拉姆達運算式)是一回事。唯一的不同——文法形式不同。Lambda 運算式更進一步。本質上,它們的作用都是:產生方法——內聯方法,也就是說,省去函數定義,直接寫函數體。

Lambda 運算式一般形式:

(input parameters) => {statement;}
其中:

參數列表,可以有多個、一個或者無參數。參數可以隱式或者顯式定義。
運算式或者語句塊,也就是函數體。
上面代碼,第 6 ~ 8 行,沒有函數名,是個匿名函數,採用 Lambda 運算式,嚴格意義上,雖然文法有差異,但目的一樣。

樣本1: 複製代碼 代碼如下:var baz1 = function() {
var foo = 10;
var bar = 2;
return foo * bar;
};
function mutil() {
var foo = 10;
var bar = 2;
return foo * bar;
};
alert(baz1());
var baz2 = mutil();
alert(baz2);

說明:

1,baz1 與 baz2 完全一樣,但 baz1 與 baz2 相比,省去了函數定義,直接函數體——看上去多簡約。

閉包
變數範圍
樣本2:函數內部可以訪問全域變數。

複製代碼 代碼如下:var baz = 10;
function foo() {
alert(baz);
}
foo();

這是可以。

樣本3:函數外部不能訪問函數內部的局部變數。

複製代碼 代碼如下:function foo() {
var bar = 20;
}
alert(bar);

這會報錯。

另外,函數內部聲明變數時,一定要使用 var 關鍵字,否則,聲明的是一個全域變數。

樣本4:

複製代碼 代碼如下:function foo() {
bar = 20;
}
alert(bar);

函數外部存取函數內部的局部變數
實際情況,需要我們從函數外部獲得函數內部的局部變數。先看樣本5。

樣本5:

複製代碼 代碼如下:function foo() {
var a = 10;
function bar() {
a *= 2;
}
bar();
return a;
}
var baz = foo();
alert(baz);

a 定義在 foo 內,bar 可以訪問,因為 bar 也定義在 foo 內。現在,如何讓 bar 在 foo 外部被調用?

樣本6:

複製代碼 代碼如下:function foo() {
var a = 10;
function bar() {
a *= 2;
return a;
}
return bar;
}
var baz = foo();
alert(baz());
alert(baz());
alert(baz());

var blat = foo();
alert(blat());

說明:

1,現在可以從外部存取 a;

2,JavaScript 的範圍是詞法性的。a 是運行在定義它的 foo 中,而不是運行在調用 foo 的範圍中。 只要 bar 被定義在 foo 中,它就能訪問 foo 中定義的變數 a,即使 foo 的執行已經結束。也就是說,按理,"var baz = foo()" 執行後,foo 已經執行結束,a 應該不存在了,但之後再調用 baz 發現,a 依然存在。這就是 JavaScript 特色之一——運行在定義,而不是啟動並執行調用。

其中, "var baz = foo()" 是一個 bar 函數的引用;"var blat= foo()" 是另一個 bar 函數引用。

用閉包實現私人成員
現在,需要建立一個只能在對象內部訪問的變數。用閉包再適合不過,因為通過閉包你可以建立只允許特定函數訪問的變數,而且這些變數在這些函數的各次調用間依然存在。

為了建立私人屬性,你需要在建構函式的範圍中定義相關變數。這些變數可以被定義於該範圍中的所有函數訪問,包括那些特權方法。

樣本7:

複製代碼 代碼如下:var Book = function(newIsbn, newTitle, newAuthor) {
// 私人屬性
var isbn, title, author;
// 私人方法
function checkIsbn(isbn) {
// TODO
}
// 特權方法
this.getIsbn = function() {
return isbn;
};
this.setIsbn = function(newIsbn) {
if (!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.');
isbn = newIsbn;
};
this.getTitle = function() {
return title;
};
this.setTitle = function(newTitle) {
title = newTitle || 'No title specified.';
};
this.getAuthor = function() {
return author;
};
this.setAuthor = function(newAuthor) {
author = newAuthor || 'No author specified.';
};
// 構造器代碼
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
};

// 共有、非特權方法
Book.prototype = {
display: function() {
// TODO
}
};

說明:

1,用 var 聲明變數 isbn、title 和 author,而不是 this,意味著它們只存在 Book 構造器中。checkIsbn 函數也是,因為它們是私人的;

2,訪問私人變數和方法的方法只需聲明在 Book 中即可。這些方法稱為特權方法。因為,它們是公用方法,但卻能訪問私人變數和私人方法,像 getIsbn、setIsbn、getTitle、setTitle、getAuthor、setAuthor(取值器和構造器)。

3,為了能在對象外部存取這些特權方法,這些方法前邊加了 this 關鍵字。因為這些方法定義在 Book 構造器的範圍裡,所以它們能夠訪問私人變數 isbn、title 和 author。但在這些特權方法裡引用 isbn、title 和 author 變數時,沒有使用 this 關鍵字,而是直接引用。因為它們不是公開的。

4,任何不需要直接存取私人變數的方法,像 Book.prototype 中聲明的,如 display。它不需要直接存取私人變數,而是通過 get*、set* 簡介訪問。

5,這種方式建立的對象可以具有真正私人的變數。其他人不能直接存取 Book 對象的任何內部資料,只能通過賦值器和。這樣一切盡在掌握。

但這種方式的缺點是:

“門戶大開型”對象建立模式中,所有方法都建立在原型 prototype 對象中,因此不管產生多少對象執行個體,這些方法在記憶體中只有一份。
而採用本節的做法,沒產生一個新的對象執行個體,都將為每個私人方法(如,checkIsbn)和特權方法(如,getIsbn、setIsbn、getTitle、setTitle、getAuthor、setAuthor)產生一個新的副本。
因此,本節方法,只適於用在真正需要私人成員的場合。另外,這種方式也不利於繼承。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.