JavaScript函數表達式——“函數的遞歸和閉包”的注意要點

JavaScript函數表達式——“函數的遞歸和閉包”的注意要點

函數表達式的基本概念

name屬性和函數提升

首先,name屬性,通過這個屬性可以訪問到給函數指定的名字。(非標準的屬性)如:

function  People(){};

console.log(People.name);  //People

其次,函數聲明提升,意味着可以把函數聲明放在調用它的語句後面。如:


sayHi();  //調用函數

function  sayHi(){  //聲明函數

    console.log("Hi");

}  //不會報錯

使用函數表達式則不可以:

sayHi();

var  sayHi  =  function(){

    console.log("Hi");

}  //報錯

創建函數的兩種方式,一個是函數聲明(如第一種方式);一個是函數表達式(如第二種方式)。第二種函數創建方式創建的函數叫“匿名函數”或“拉姆達函數”,因爲function 關鍵字後面沒有標識符。

函數提升的常見錯誤

需要注意的是,作爲對比,下面的兩種代碼中,第一個是錯誤的(會導致各瀏覽器出現不同的問題);第二個才使正確的。代碼如下:

var  condition  =  true;

if  (condition){

    function  sayHI(){

        console.log("hi")

    }                                 //歡迎加入前端全棧開發交流圈一起學習交流:1007317281

    sayHI();  //"hello"

}else{

    function  sayHI(){

        console.log("hello")

    }

    sayHI();

}

報錯

var  condition  =  false;

var  sayHi;

if(condition){

    sayHi  =  function(){

        console.log("hi")

    };                        //歡迎加入前端全棧開發交流圈一起學習交流:1007317281

    sayHi();

}else{

    sayHi  =  function(){

        console.log("hello")

    };

    sayHi();  //hello

}

沒有錯誤

var  condition  =  true;

if(condition){

    var  sayHi  =  function(){

        console.log("hi")

    };

    sayHi();  //hi

}else{

    var  sayHi  =  function(){

        console.log("hello")

    };

    sayHi();  //hello

}

這裏也不會出現問題。出現上面問題的根源就是函數提升,就是函數聲明和函數表達式之間的區別所導致的。

給大家推薦一個技術交流學習圈,裏面概括移動應用網站開發,css,html,webpack,vue node angular以及面試資源等。獲取資料
對web開發技術感興趣的同學,可以加入交流圈👉👉👉1007317281,不管你是小白還是大牛都歡迎,還有大牛整理的一套高效率學習路線和教程與您免費分享,同時每天更新視頻資料。

函數的遞歸

遞歸函數就是在一個函數通過名字調用自身的情況下構成的。如:

function  factorial(num){

    if(num  <=  1){

        return  1;

    }else{

        return  num  *  factorial(num  -  1);

    }             //歡迎加入前端全棧開發交流圈一起學習交流:1007317281

}    

console.log(factorial(4));  //24 4*3*2*1

但是,函數裏面包含了函數自身所以,在應該使用arguments.callee來解決該問題。如:

function  factorial(num){

    if(num  <=  1){

        return  1;

    }else{

        return  num  *  arguments.callee(num  -  1);

    }

}    

console.log(factorial(4));  //24 4*3*2*1

但如果使用上面這種方法,則在嚴格模式下行不通。不過可以使用命名函數表達式來達成相同的結果。如:


var  factorial  =  (

    function  f(num){

        if(num  <=  1){

            return  1;

        }else{

            return  num  *  f(num  -  1);

        }

    }

);    

console.log(factorial(4));  //24 4*3*2*1

即,把它包含在一個變量裏面,在遇到其他這種需要使用arguments.callee的時候都可以這樣做。

函數的閉包

閉包就是有權訪問另一個函數作用域中的變量的函數。常見的創建閉包的方式就是在一個函數內部創建另一個函數。在此之前應該先掌握作用域鏈的概念(見《Javascript執行環境和作用域的注意要點》一文)

作用域鏈

以下面的代碼爲例

function  compare(value1,value2){

    if  (value1  >  value2){

        return  1;

    }else  if  (value1  <  value2){

        return  -1;

    }else  {

        return  0;

    }

}             //歡迎加入前端全棧開發交流圈一起學習交流:1007317281

var  result  =  compare(5,10);

調用函數compare時,函數執行環境中的作用域鏈:


作用域鏈本質上是一個指向變量對象的指針列表。

另一個例子:

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:  "Nicholas"},{name:  "Greg"});

這個就相當於:

var  compare  =  function(object1,object2){

    var  value1  =  object1["name"];

    var  value2  =  object2["name"];

    if  (value1  <  value2){

        return  -1;

    }else  if(value1  >  value2){

        return  1;

    }else{

        return  0;

    }                  //歡迎加入前端全棧開發交流圈一起學習交流:1007317281

};

var  result  =  compare({name:  "Nicholas"},{name:  "Greg"});

相當於:

var  result  =  function(){

    var  value1  =  {name:  "Nicholas"}["name"];

    var  value2  =  {name:  "Greg"}["name"];

    if  (value1  <  value2){

        return  -1;

    }else  if(value1  >  value2){

        return  1;

    }else{

        return  0;

    }

};

console.log(result());  //1

所以,完整的代碼如下:

var  compareNames  =  createComparisonFunction("name");

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;

        }                 //歡迎加入前端全棧開發交流圈一起學習交流:1007317281

    };

}

var  result  =  compareNames({name:  "Nicholas"},{name:  "Greg"});

compareNames  =  null;

調用compareNames()函數的過程中產生的作用域鏈之間的關係圖如下:

常見的閉包的模式一般都是這樣:


var  X  =  function  A(a){

    return  function(b1,b2){...a...}  //匿名函數

};    

var  Y  =  X(b1,b2);

舉個栗子:


var  obj1  =  {

    name:  "co",

    color:  ["white","black"]

};

var  obj2  =  {

    name:  "lor",

    color:  ["yellow","red"]

};

function  displayProperty(propertyName){

    return  function(obj1,obj2){

        var  value1  =  obj1[propertyName];

        var  value2  =  obj2[propertyName];

        if(typeof  value1  ===  "string"){

            return  value1  +  " and "  +  value2  +  "<br/>";

        }else  if(value1 instanceof  Array){

            return  value1.toString()  +  "<br/>"  +  value2.toString();

        }else{

            return  false;

        }                //歡迎加入前端全棧開發交流圈一起學習交流:1007317281

    };

}

var  displayP  =  displayProperty("name");

var  displayStr  =  displayP(obj1,obj2);

document.write(displayStr);

displayP  =  null;

var  displayP  =  displayProperty("color");

var  displayStr  =  displayP(obj1,obj2);

document.write(displayStr);    

/*

co and lor

white,black

yellow,red

*/

閉包會攜帶他包含的函數的作用域,因此會更多的佔用內存資源,建議只有在絕對必要的時候再考慮使用閉包。V8 優化後的js 引擎會嘗試收回被閉包占用的內存。

閉包與變量

閉包的副作用是閉包只能取得包含函數中任何變量的最後一個值。

this對象

全局函數中this = window;當函作爲位某個對象的方法調用時this = 那個對象;但是匿名函數的執行環境又全局性,this 通常指向window;但也有例外:

var  name  =  "the window";

var  obj  =  {

    name:  "the obj",

    getNameFunc:  function(){

        return  function(){

            return  this.name;

        };

    }                 //歡迎加入前端全棧開發交流圈一起學習交流:1007317281

};

console.log(obj.getNameFunc()());  //"the window" 別忘了要寫兩個小括號

因爲每個函數在被調用的時候都會自動取得兩個特殊的變量:this 和arguments;內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止。

var  obj  =  {

    name:  "Oliver",

    age:  18,

    friends:  ["alice","troy"],

    sayName:  function(){

        return  this.name;

    },

    sayFriends:  function(){

        return  function(){

            return  this.friends;

        };

    }

};

console.log(obj.sayFriends()());  //undefined

上面這個代碼就是因爲閉包的問題,導致錯誤。又如:

var  friends  =  "window's friends";

var  obj  =  {

    name:  "Oliver",

    age:  18,

    friends:  ["alice","troy"],

    sayName:  function(){

        return  this.name;

    },

    sayFriends:  function(){

        return  function(){

            return  this.friends;

        };

    }                    //歡迎加入前端全棧開發交流圈一起學習交流:1007317281

};

console.log(obj.sayFriends()());  //window's friends 匿名函數執行環境的全局性

解決這個問題的方法是:

var  friends  =  "window's friends";

var  obj  =  {

    name:  "Oliver",

    age:  18,

    friends:  ["alice","troy"],

    sayName:  function(){

        return  this.name;

    },

    sayFriends:  function(){

        var  outer  =  this;

        return  function(){

            return  outer.friends;

        };

    }

};

console.log(obj.sayFriends()());  //["alice","troy"] 這樣就可以正常搜索到了

又如:

var  friends  =  "window's friends";

var  obj  =  {

    name:  "Oliver",

    age:  18,

    friends:  ["alice","troy"],

    sayWindowFriends:  function(){

        return  function(){

            return  this.friends;

        };

    },

    sayFriends:  function(){

        var  outer  =  this;

        return  function(){

            return  function(){

                return  function(){

                    return  outer.friends

                };

            };

        };

    }

};

console.log(obj.sayWindowFriends()());  //"window's friends"

console.log(obj.sayFriends()()()());  //["alice", "troy"]

再舉個栗子:

var  obj  =  {

    name:  "Oliver",

    age:  18,

    friends:  ["alice","troy"],

    sayFriends:  function(i){

        var  outer  =  this;

        return  function(j){

            return  outer.friends[i]  +  outer.friends[j];

        };

    }

};

console.log(obj.sayFriends(0)(1));  //alicetroy

另外,在幾種特殊情況下,this 的值可能會發生改變。如:

var  name  =  "the window";

var  obj  =  {

    name:  "my object",

    getName:  function(){

        return  this.name;

    }

};

console.log(obj.getName());  //my object

console.log((obj.getName)());  //my object 與上面的是相同的,多了個括號而已

console.log((obj.getName  =  obj.getName)());  //the window

只要不用下面兩種方式調用函數即可。

內存泄露

經過上面的一番折騰,最明顯的就是window.name 一直佔用內存,無法清空。必須手動把它清理掉,用window.name = null來操作。
感謝您的觀看,如有不足之處,歡迎批評指正。

本次給大家推薦一個免費的學習羣,裏面概括移動應用網站開發,css,html,webpack,vue node angular以及面試資源等。獲取資料👈👈👈
對web開發技術感興趣的同學,歡迎加入Q羣:👉👉👉1007317281👈👈👈,不管你是小白還是大牛我都歡迎,還有大牛整理的一套高效率學習路線和教程與您免費分享,同時每天更新視頻資料。
最後,祝大家早日學有所成,拿到滿意offer,快速升職加薪,走上人生巔峯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章