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,快速升職加薪,走上人生巔峯。