//不要這么做
if( condition ){
function sayHi(){
alert('Hello!');
}
} else {
function sayHi(){
alert('Yo!');
}
}
因為不同瀏覽器對上面代碼理解不同航闺,ie不管condition是否為真,都會執(zhí)行alert('Yo!')
//可以這么做
var sayHi;
if( condition ){
sayHi = function(){
alert('Hello!');
}
} else {
sayHi = function(){
alert('Yo!');
}
}
7.1 遞歸
經(jīng)典遞歸函數(shù):
function factorial(num){
if( num <= 1 ){
return 1;
} else {
return num * factorial(num-1);
}
}
可是,遇到下面情況缸夹,會出問題:
var another = factorial;
factorial = null;
console.log(another(2)) //出錯
因為在調(diào)用another時候术陶,factorial已經(jīng)不是一個函數(shù)了凑懂。
可以使用 arguments.callee 解決問題:
注意:arguments.callee 是一個指向正在執(zhí)行的函數(shù)的指針。
function factorial(num){
if( num <= 1 ){
return 1;
} else {
return num * arguments.callee(num-1);
}
}
7.2 閉包
閉包:指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)梧宫。
創(chuàng)建閉包的常見方式接谨,就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)。
函數(shù)被調(diào)用時塘匣,都會發(fā)生些什么脓豪?如何創(chuàng)建作用域鏈,作用域鏈有什么作用馆铁?
來看這一段代碼:
function compare(value1, value2){
if( value1 < value2 ){
return -1;
} else if ( value1 > value2 ) {
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
以上代碼中跑揉,當調(diào)用了compare()時,會創(chuàng)建一個作用域鏈埠巨,其中历谍,arguments、value1辣垒、value2處于作用域鏈的第一位望侈,全局執(zhí)行環(huán)境的變量(包含result和compare)則處于第二位。
很顯然勋桶,作用域鏈本質(zhì)上是一個指向變量對象的指針列表脱衙,它只引用但不實際包含變量對象侥猬。
再來看一個函數(shù):
function createComparisonFunction(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;
}
}
}
var compare = createComparisonFunction('name');
var result = compare({ name: 'jack' }, { name: 'mack'} )
注意:在另一個函數(shù)內(nèi)部定義的函數(shù)會將包含函數(shù)(即外部函數(shù))的活動對象添加到自己的作用域鏈中。因此捐韩,
在createComparisonFunction() 函數(shù)內(nèi)部定義的匿名函數(shù)的作用域鏈中退唠,實際上將會包含外部函數(shù)createComparisonFunction()的活動對象。
其實荤胁,就是說瞧预,對于閉包,它的作用域鏈不僅僅是自己內(nèi)部仅政,還包括自己的外部垢油。
接著說上面的函數(shù),
在匿名函數(shù)從createComparisonFunction()中被返回后圆丹,它的作用域鏈被初始化為包含createComparisonFunction()函數(shù)的活動對象和全局變量對象滩愁。
這樣,匿名函數(shù)就可以訪問在createComparisonFunction()中定義的所有變量辫封。
更為重要的是硝枉,createComparisonFunction()函數(shù)在執(zhí)行完畢后,其活動對象也不會被銷毀秸讹,因為匿名函數(shù)的作用域鏈仍然在引用這個活動對象檀咙。
換句話說,當createComparisonFunction() 函數(shù)返回后璃诀,其執(zhí)行環(huán)境的作用域鏈會被銷毀弧可,但它的活動對象仍然會留在內(nèi)存中,直到匿名函數(shù)被銷毀劣欢,
createComparisonFunction() 函數(shù)的活動對象才會被銷毀棕诵。
例如:
//創(chuàng)建函數(shù)
var compareNames = createComparisonFunction('name');
//調(diào)用函數(shù)
var result = compareNames({ name: 'jack' }, { name: 'mack'} );
//解除對匿名函數(shù)的引用(以便釋放內(nèi)存)
compareNames = null;
就是說,創(chuàng)建一個匿名函數(shù)后凿将,除非手動設(shè)置其為null來進行銷毀釋放內(nèi)存校套,否則,這個匿名函數(shù)以及其引用的外部變量依然會存在于內(nèi)存中牧抵。
建議:由于閉包會攜帶包含它的函數(shù)的作用域笛匙,因此會比其他函數(shù)占用更多的內(nèi)存。過度使用閉包可能會導(dǎo)致內(nèi)存占用過多犀变,因此妹孙,我們建議讀者只在絕對必要時再考慮使用閉包。
7.2.1 閉包與變量
作用域鏈的這種配置機制引出了一個值得注意的副作用获枝,即閉包只能取得包含函數(shù)中任何變量的最后一個值蠢正。別忘了,閉包所保存的是整個變量對象省店,而不是某個特殊的變量嚣崭。
function create(){
var result = [];
for(var i = 0; i < 10; i++){
result[i] = function() {
return i;
}
}
return result
}
console.log(create())
這個函數(shù)會返回一個數(shù)組笨触,但是每個函數(shù)都返回10。 因為每個函數(shù)的作用域鏈中都保存著 create() 函數(shù)的活動對象雹舀,所以它們引用的都是同一個變量 i芦劣。
怎么樣讓每個函數(shù)返回自己?我們可以創(chuàng)建另一個匿名函數(shù)強制讓閉包的行為符合預(yù)期葱跋。
function create(){
var result = [];
for(var i = 0; i < 10; i++){
result[i] = function(num) {
return function(){
return num;
}
}
console.log(result[i])
}
return result;
}
console.log(create())
7.2.2 關(guān)于this對象
我們知道持寄,this對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的:在全局函數(shù)中,this等于window娱俺,而當函數(shù)被作為某個對象的方法調(diào)用時,this等于那個對象废麻。
重點:不過荠卷,匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此其this對象通常指向window烛愧。
但是油宜,有時候,由于編寫閉包的方式不同怜姿,這一點可能不會那么明顯
var name = 'the window';
var object = {
name: 'my object',
getNameFunc: function(){
return function() {
return this.name;
}
}
}
console.log(object.getNameFunc()()); ==> the window
如何讓閉包訪問object中的name呢慎冤?可以這么做:
var name = 'the window';
var object = {
name: 'my object',
getNameFunc: function(){
var that = this;
return function() {
return that.name;
}
}
}
console.log(object.getNameFunc()());
7.3 模仿塊級作用域
由于JS沒用塊級作用域,即意味著在塊語句內(nèi)部定義的變量沧卢,全局環(huán)境中都可以訪問得到蚁堤。
比如:
function outputNumbers(count){
for( var i = 0; i < count; i++ ){
alert(i)
}
alert(i) ==> 5
}
alert(i) ==> 報錯
outputNumbers(5);
由于沒有塊級作用域,在for循環(huán)之內(nèi)的i變量但狭,被定義在了其外部函數(shù)中披诗。而在全局環(huán)境中查找不到i是因為js有函數(shù)作用域。
雖然js沒有塊級作用域立磁,但是我們可以通過匿名函數(shù)來模仿塊級作用域呈队。
function outputNumbers(count){
(function(){
for(var i = 0; i < count; i++){
alert(i);
}
})();
alert(i) ==> 報錯。i只能在for循環(huán)中訪問得到
}
在匿名函數(shù)中定義的任何變量唱歧,都會在執(zhí)行結(jié)束時被銷毀宪摧。因此,i只能在循環(huán)中使用颅崩,使用后即被銷毀几于。