JavaScript初階(三)--------函數、閉包、立即執行函數
函數
有時候我們的代碼重復了很多次,編程裏面稱為耦合,但是編程要講究高內聚,弱耦合。為了將重復多的聚在一起就出現了函數。
定義
函數基本要素:函數聲明(function),函數名稱,參數(形參,實參),返回值。
1.首先函數命名方式采用小駝峰式寫法,即第一個單詞小寫,後面的單詞首字母大寫,如 function oneNumber(){}
2.函數表達方式裏面有函數表達式,匿名函數表達式
var a = function lala() {}//函數表達式
var b = function () {}//匿名函數表達式
為了便於使用以及方便,我們之後基本都是采用匿名函數表達式,並且稱為函數表達式。
return返回值
作用一:返回函數最終執行結果
作用二:終止函數
函數作用域
變量和函數生效的區域叫作用域,作用域分為全局作用域和局部作用域。訪問時,裏面的作用域可以訪問外面的,外面的不能訪問裏面的。
[[scope]]:每個javascript函數都是一個對象,對象中有些屬性我們可以訪問,但有些不可以,這些屬性僅供javascript引擎存取,[[scope]]就是其中一個。
[[scope]]指的就是我們所說的作用域,其中存儲了運行期上下文的集合。
作用域鏈:[[scope]]中所存儲的執行期上下文對象的集合,這個集合呈鏈式鏈接,我們把這種鏈式鏈接叫做作用域鏈。
運行期上下文:當函數執行時,會創建一個稱為執行期上下文的內部對象。一個執行期上下文定義了一個函數執行時的環境,函數每次執行時產生對應的執行上下
文都是獨一無二的,所以多次調用一個函數會導致創建多個執行上下文,當函數執行完畢,執行上下文被銷毀。
預編譯
js運行三步:語法分析,預編譯,開始執行
暗示全局變量
函數裏面沒有聲明直接賦值的變量稱為暗示全局變量,能夠被全局調用,而且全局變量都是window上的屬性,可以通過window.name來調用。
預編譯四部曲
1.創建AO對象(執行上下文對象)
2.找函數聲明和函數中的變量聲明,並賦值undefined
3.賦值(形參和實參統一)
4.函數體賦值給對應的AO屬性
舉個栗子看看吧
function fn(a){
console.log(a); // ?unction a(){ }
var a = 123;
console.log(a); // 123
function a(){ }
console.log(a); //123
console.log(b); //undefined
var b = function(){};
console.log(b); //function () {}
console.log(d); // function d(){}
function d(){}
}
fn(1);
首先產生執行期上下文,然後找函數聲明和變量聲明a,b,d,下面是整個預編譯過程。
a --> undefined --> 1 --> function a ( ) { } --> 123
b --> undefined --> function ( ) { }
d --> undefined --> function d( ) { }
最後得出各AO屬性對應的值
function與var之間覆蓋的問題,例如:
function bar(){ return foo; function foo(){ } var foo = 111 } console.log(bar()); // 一開始就被return
function bar(){ foo = 10; function foo(){ {; var foo = 11; return foo; } console.log(bar()); // 11 foo被賦值為11,最後才被return!!
以上就是整個預編譯過程
閉包
說了那麽多其實就是為了給閉包做鋪墊,閉包會導致多個執行函數共用一個公有變量。所以一般如果不是特殊需要,盡量少用。容易汙染全局變量。
舉個栗子
function a(){ function b(){ var bbb = 234; console.log(aaa); } var aaa = 123; return b; } a(); //*****************************function b() {}
var demo = a();
demo();
最後我們會發現,demo --> function b () { },demo() --> 123。這是因為當b執行完了相當於a也執行完了,這樣a會把執行上下文銷毀,但是b已經被return出去並且給了demo,當demo執行時也就是相當於function b () { }執行,得出123。這就是內存泄露,原有的作用域鏈不釋放導致的。
來個栗子鞏固一下:
function a() { var aaa = 100; function b() { console.log(aaa); } return b; } var demo = a(); demo(); //100 b的勞動成果已經保存到了demo裏面
當a函數定義的時候,產生一個執行上下文,裏面有一個aaa,和一個函數b,當b定義的時候,已經含有a的勞動成果,意思就是它已經有a的執行上下文,並且在b執行的時候,產生它自己的執行上下文,最後當a函數執行完之後,把函數b返回到了全局作用域,雖然a執行完,並且銷毀了它自己的執行上下文,但是因為其內部b函數的存在,仍然有a的全部執行上下文,所以,仍然可以通過demo來訪問function a裏面的aaa變量。
閉包的應用
1.實現公有變量
function add() {
var num = 0;
function demo(){
num++;
console.log(num);
}
return demo;
}
var test = add();
test();//1
test();//2
對於上述栗子,公有變量就是num。add函數將demo函數返回出去,demo函數依然有add函數的執行上下文,每次執行test(),就相當於執行demo函數,每次訪問的num都是同一個
num變量,這樣num就是一個公有變量了,通過這種方式就能利用閉包產生一個累加器了。
2.做緩存機構
function test(){
var num = 0;
function a() {
console.log(++num);
}
function b() {
console.log(--num);
}
return [a,b];
}
var arr = test();
arr[0]();//1
arr[1]();//0
a函數和b函數都被return到了外部,這樣a函數和b函數都與num產生了一個閉包,並且a和b執行的都是同一個變量,當a改變num的時候,b的num也會發生改變,同理,b操作了
num,a的num也會發生改變,因為它們指向的num是同一個num,這就相當於一個緩存。
再來一個栗子
function eater() {
var food = "";
var obj = {
eat : function() {
if(food == "" ){
console.log(‘empty‘);
}else{
console.log("I am eating " + food);
food = "";
}
},
push : function (myFood) {
food = muFood
}
}
return obj;
}
var eater1 = eater();
eater1.eat();
eater1.push(‘orange‘);
eater1.eat();
和上一個栗子一樣,obj對象被return到了外部,並且用eater1來接收。eater1.eat()和eater1.push()所對應的food是同一個,這就是利用閉包產生緩存機構。
3.私有化變量
這個作用的了解需要先了解一下構造函數
function Deng(){
var prepareWife = "xiaozhang";
var obj = {
name : "Laodeng",
age : 40,
sex : "male",
wife : "xiaoliu",
divorce : function () {
this.wife = delete this.wife;
},
getMarried :function () {
this.wife = prepareWife;
},
changePrepare : function (someone) {
preparewife = someone;
},
sayMywife : function (){
console.log(this.wife);
}
}
return obj ;
}
deng = Deng();
運行一下看看
deng.sayMyWife() //"xiaoliu" deng.divorce() //undefined (沒有返回值) deng.sayMywife() // true已經刪除 deng.changePrepare(‘xiaoxiaozhang‘) //undefined (函數沒有返回值)******** deng.getMarried() //undefined deng.sayMywife() //"xiaoxiaozhang" deng.prepareWife //undefined
prepareWife函數裏面的變量,不在全局作用域裏,所以我們訪問不了,但是我們所有的操作都是圍繞prepareWife來進行的,它們都可以正常訪問這個變量,所以,像這種只能通過與
這個變量產生閉包的方法,屬性,才能給對那個變量進行訪問,所以,我們就稱之為,私有化變量,我們可以通過定制接口(各種方法),來對變量的安全程度進行設置。
立即執行函數
此類函數沒有聲明,在一次執行過後即釋放。適合做初始化工作。比如有些數學公式,或者是其他一些常數的計算,我們沒有必要把它一直放在全局的空間裏,這樣會很好內存,於
是就誕生了立即執行函數。
var x = (function(a, b){
return(a + b) ;
}(1, 2)) // x = 3
立即執行函數特點就是當JavaScript引擎解析到這個語句的時候就會馬上執行,執行結束後馬上把自己的執行上下文都銷毀。這樣就可以釋放這裏的內存,立即執行函數可以有返回值
以及形參等。
我們要知道函數聲明不能執行,只有函數才能執行,所以
函數聲明----->表達式
function test() { } // 函數聲明,不是表達式
var test = function () ; // 函數表達式
我們可以把函數聲明轉換成表達式
1.+function test(){ } -----> +號運算符,這樣就將函數聲明轉換成表達式,就可以執行了
2. !function test(){ } ------> !
3. (function test(){ })( ) -------> ( )
利用立即執行函數解決閉包問題
想要實現打印0-9,結果卻出人意料。。。。。。。(出現了10個10)。這個我想了好久才想通了,唉,腦子不夠用。。。
function test() { var arr = [ ]; for(var i = 0 ;i < 10; i++){ arr[i] = function (){ console.log(i + ","); } } return arr; } var demo = test(); for(var j = 0; j < 10 ;j ++ ){ demo[j](); }// 10 * 10
這是為什麽呢?輸出的10個全是10,說明這裏的i都是同一個i,為什麽會這樣呢,原來是function執行的時候i已經就是10了,由於test與arr之間產生了閉包,先這樣說吧,每次return出
去一個arr,但是function現在還沒有執行,也就是都是arr[i],當終止循環的時候i=10,這時候function執行的時候10個i都是相加到10的那個i,是同一個i,所以最後打印出10個10。理解了
嗎?感覺說的太通俗了,希望大家能夠理解哈。
如果覺得我說的有問題,歡迎提出來,大家一起進步哈!
function test() { var arr = []; for(var i = 0; i < 10; i++){ (function (j) { console.log(j); }(i)) } return arr; }
利用立即執行函數,每次訪問的i都不一樣,所以打印出來的就是0-9了。
最後再來一個栗子
a = 100 ;
function demo(e) {
function e() {}
arguments[0] = 2;
document.write(e); //2 因為形參列表將e改變為2
if(a) {
var b = 123;
function c() { }
}
var c ;
a = 10 ;
var a ;
document.write(b); // undefined
f = 123;
docuemnt.write(c); //function (){}
docuemnt.write(a); // 10
}
var a;
demo(1)
docuemnt.write(a);
document.write(f);
JavaScript初階(三)--------函數、閉包、立即執行函數