JavaScript中的函數(shù)與閉包

查閱書籍:JavaScript權(quán)威指南

函數(shù)聲明與函數(shù)表達式

sum(10,10)//20
function sum(n1,n1){
return n1+n2;
}
sum(10,10)//報錯
var sum=function(){
return n1+n2;
}

用函數(shù)聲明定義的函數(shù),函數(shù)可以在函數(shù)聲明之前調(diào)用,而用函數(shù)表達式定義的函數(shù)只能在聲明之后調(diào)用。

根本原因是

解析器會率先讀取函數(shù)聲明癣丧,將其加入執(zhí)行環(huán)境中,
函數(shù)表達式瓜浸,必須等到解析器執(zhí)行到它所在的代碼行泪酱,才會被真正的執(zhí)行
在權(quán)威指南是這么寫到的


image.png
ECMAScript規(guī)范中表示,函數(shù)聲明語句可以出現(xiàn)在全局代碼中录语,或者內(nèi)嵌在其他函數(shù)中庄拇,但是不能出現(xiàn)在循環(huán)劫流、條件判、或者try/finally以及with語句中丛忆。函數(shù)定義表達式可以出現(xiàn)在javascript代碼的任何地方祠汇。

匿名函數(shù)

JavaScript函數(shù)可以是匿名的。這意味著你可以從函數(shù)聲明中省略函數(shù)名熄诡。但是可很,函數(shù)必須存儲在變量中。
var addNumbers = function (x, y) { return x + y; }
上述語法被也被稱為函數(shù)表達式凰浮。

立即執(zhí)行函數(shù)表達式

(function() { 
  // Your code here
}());

這其中定義的任何變量或函數(shù)不能被這個范圍以外的任何代碼改變我抠。

這是一個在代碼中創(chuàng)建局部范圍的很好方法苇本。它們可以幫助你保護變量和函數(shù),以避免被應(yīng)用程序的其他部分更改或覆蓋菜拓。

嵌套函數(shù)

function hyotense(a,b){
  function square(x){return x*x};
  return Math.sqrt(square(a)+square(b));
}

他的特別之處在于他的變量作用域規(guī)則瓣窄,他們可以訪問嵌套他們的函數(shù)的參數(shù)和變量,這里要說一下作用域鏈

作用域鏈

作用域鏈中的下一個變量對象來自包含(外部)環(huán)境纳鼎,而在下一個變量對象則來自下一個包含環(huán)境俺夕,一直延續(xù)到全局執(zhí)行環(huán)境,全局執(zhí)行環(huán)境的變量對象始終是作用域的最后一個對象

函數(shù)調(diào)用

(1)作為函數(shù)
(2)作為方法
(3)作為構(gòu)造函數(shù)
(4)通過它們的call和apply間接調(diào)用

作為函數(shù)

就是我們通臣桑看到的最簡單的調(diào)用

 function square(x){return x*x};
 square(10);
image.png
image.png
作為方法
var calcuator={
    a:1,
    b:2,
    add:function(){
        this.result=this.a+this.b;
    }
}
calcuator.add();
calcuator.result; =>3
image.png
方法鏈

當(dāng)方法返回的是一個對象的時候劝贸,這個對象還可以調(diào)用它的方法,jQuery 就是這樣逗宁。
需要注意的是

image.png
image.png

作為構(gòu)造函數(shù)

函數(shù)可以充當(dāng)構(gòu)造器的角色映九,并且可以使用構(gòu)造函數(shù)來創(chuàng)建新的對象。這是JavaScript面向?qū)ο蟮奶攸c之一瞎颗。使用構(gòu)造函數(shù)的好處是件甥,你將能夠通過預(yù)定義的屬性和方法,創(chuàng)造盡可能多的對象哼拔。

function Programmer(name, company, expertise) {  
this.name = name;   
this.company = company;  
this.expertise = expertise;  
this.writeCode = function() {     
console.log("Writing some public static thing..");
   }   
this.makeSkypeCall = function() {    
  console.log("Making skype call..");
   }  
this.doSalsa = function() {    
  console.log("I'm a programmer, I can only do Gangnam style..");
   }  
this.canWriteJavaScript = function() {     
  return expertise === "JavaScript";
   }
}
始終使用new關(guān)鍵字來從構(gòu)造器創(chuàng)建新的對象引有。
var jsProgrammer = Programmer("Douglas Crockford", "Yahoo", "JavaScript")

最終將添加所有屬性和方法到全局的window對象,原因是管挟,除非明確指定,否則“this”指向全局的window對象弄捕。使用new 設(shè)置“this”上下文到被創(chuàng)建的當(dāng)前對象僻孝。

間接調(diào)用

javascript函數(shù)也是對象,函數(shù)對象也可以包括方法守谓,其中call()和apply也可以間接調(diào)用函數(shù)
call和apply都可以改變this的指向
call第二個參數(shù)是散列分布
apply以數(shù)組的形式傳第二個參數(shù)

ClassA.call(this,name,age);改變this值
ClassA.apply(this,[name,age]);
ClassA.apply(this,arguments);arguments是一個數(shù)組穿铆,里面放的是所有傳過來的實參

call可以用做繼承

function Parent(age){
this.name=['mike','jack','smith'];
this.age=age;
}
function Child(age){
Parent.call(this,age);//把this指向Parent,同時還可以傳遞參數(shù)
}
var test=new Child(21);
console.log(test.age);//21
console.log(test.name);
test.name.push('bill');
console.log(test.name);//mike,jack,smith,bill
ES5還定義了一個方法:bind(),它會創(chuàng)建一個函數(shù)的實例斋荞,其this值會被綁定到傳給bind()函數(shù)的值荞雏。如
window.color='red';
var o={color:'blue'};
function sayColor(){
console.log(this.color);
}
var objectSaycolor=sayColor.bind(o);
//var objectSaycolor=sayColor.bind();
objectSaycolor();//blue

在這里sayColor()調(diào)用bind()并傳入對象o,創(chuàng)建了objectSayColor()函數(shù)。objectSayColor()函數(shù)的this值等于o,因此即使是在全局作用域中調(diào)用這個函數(shù)平酿,也會看到blue凤优。

實參和形參

當(dāng)調(diào)用函數(shù)時,傳入的實參比函數(shù)聲明時指定的形參要少的話蜈彼,剩下的形參都會設(shè)為undefine
那么當(dāng)調(diào)用函數(shù)時筑辨,傳入的實參超過函數(shù)聲明時指定的形參的話,沒有辦法直接獲得其他形參幸逆,參數(shù)對象解決了這個問題棍辕,那么什么是參數(shù)對象呢暮现?

參數(shù)對象arguments

標(biāo)識符arguments是指向?qū)崊ο蟮囊茫瑢崊ο笫且粋€類數(shù)組對象楚昭,這樣通過數(shù)組下標(biāo)就可以訪問傳入函數(shù)的實參值
我們可以通過arguments[0]取到傳入實參的第一位栖袋,和真正的數(shù)組一樣arguments也有l(wèi)ength屬性,用來表示傳入實參的個數(shù)抚太。
arguments有一個重要的用處塘幅,讓函數(shù)可以操作任意數(shù)量的實參。下面的這個例子就利用arguments實現(xiàn)任意數(shù)量的數(shù)字去比較大小凭舶。


image.png

要注意的是晌块,實參對象并不是真正的數(shù)組,他不能夠使用數(shù)組的方法

image.png

實參對象的callee和caller屬性

callee是標(biāo)準(zhǔn)的帅霜,指向當(dāng)前正在執(zhí)行的函數(shù)匆背, 一般用于遞歸函數(shù) 可以實現(xiàn)低耦合 防止將函數(shù)賦值為其他函數(shù),
caller是非標(biāo)準(zhǔn)的身冀,調(diào)用當(dāng)前正在執(zhí)行的函數(shù)的函數(shù)

function factorial(num){
if(num <= 1){
return 1;
}else{
return num*arguments.callee(num-1);
}
}

閉包

用一句可以概括一下閉包:外部函數(shù)內(nèi)會定義一個內(nèi)部函數(shù) 2.內(nèi)部函數(shù)調(diào)用外部函數(shù)的變量 3.外部函數(shù)的變量不會回收
函數(shù)對象可以可以通過作用域鏈相互關(guān)聯(lián)起來钝尸,函數(shù)內(nèi)部的變量可以保存在函數(shù)內(nèi)作用域。
從技術(shù)角度講搂根,javascript的函數(shù)都是閉包珍促,定義大多數(shù)函數(shù)時的作用域鏈在調(diào)用函數(shù)時依然有效。

var scope="global scope";
function checkscope(){
  var scope="local scope"
 function f(){return scope};
 retunr f();
}
checkscope();

這個例子很明顯剩愧,會輸出local scope猪叙,將f函數(shù)執(zhí)行后的結(jié)果返回。
做一點改變

var scope="global scope";
function checkscope(){
  var scope="local scope"
 function f(){return scope};
 retunr f;
}
checkscope()();

答案是“l(fā)ocal scope”
書上的答案是這樣的


image.png
image.png
注意:函數(shù)定義時的作用域在函數(shù)執(zhí)行時依然有效

很多人會認為外部函數(shù)中定義的局部變量在函數(shù)返回后就不存在了仁卷,
其實原理是這樣的穴翩,

當(dāng)執(zhí)行流進入一個函數(shù)時,函數(shù)的環(huán)境(變量锦积,函數(shù))會被推進一個環(huán)境棧(我習(xí)慣叫運行棧)芒帕,在棧里面存的是對函數(shù)的一個引用,真正的函數(shù)是放在堆上面的丰介,當(dāng)函數(shù)執(zhí)行之后背蟆,正常的情況會將環(huán)境中的變量彈出來,斷開函數(shù)的引用哮幢,但是由于在外面checkscope變量指向了function(){return scope}這個函數(shù),而在函數(shù)里面還需要scope這個變量带膀,因此在這種情況下,編譯器會將scope捕捉(我理解就是復(fù)制了一份引用)橙垢,這樣我們就可以取到scope本砰,這就是在內(nèi)部函數(shù)里面調(diào)用外部函數(shù)的變量,外部函數(shù)的變量不會被釋放的原因钢悲。

閉包有什么用處呢点额?
看下面這個例子

for(var i=0;i<10;i++){
 ali[i].onclick=function(){
alert(i);
}
}

想想這個例子舔株,當(dāng)我們點擊不同的li時,會如我們所愿的輸出當(dāng)前l(fā)i的下標(biāo)么还棱?答案當(dāng)然是不载慈,點擊不同的li時都會輸出10,因為for循環(huán)瞬間執(zhí)行完畢的時候珍手,每次i出了函數(shù)办铡,都將i從棧里面彈出去,在重新給i賦值琳要,i最后變成了10寡具,這個時候無論點擊什么,都會alert 10稚补,閉包就可以解決這個問題童叠,將變量保存下來

for(var i=0;i<10;i++){
 (function(idx){
    ali[i].onclick=function(){
       alert(i);
    }
  })(i);
}

看一下這個閉包,通過這種方式就可以保存下來每一個下標(biāo)课幕,每個點擊事件都將當(dāng)前的i保留下來了厦坛,原理和上面那個例子是一樣的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乍惊,一起剝皮案震驚了整個濱河市杜秸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌润绎,老刑警劉巖撬碟,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異莉撇,居然都是意外死亡呢蛤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門稼钩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顾稀,“玉大人达罗,你說我怎么就攤上這事坝撑。” “怎么了粮揉?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵巡李,是天一觀的道長。 經(jīng)常有香客問我扶认,道長侨拦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任辐宾,我火速辦了婚禮狱从,結(jié)果婚禮上膨蛮,老公的妹妹穿的比我還像新娘。我一直安慰自己季研,他們只是感情好敞葛,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著与涡,像睡著了一般惹谐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上驼卖,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天氨肌,我揣著相機與錄音,去河邊找鬼酌畜。 笑死怎囚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的檩奠。 我是一名探鬼主播桩了,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼埠戳!你這毒婦竟也來了井誉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤整胃,失蹤者是張志新(化名)和其女友劉穎颗圣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屁使,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡在岂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛮寂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔽午。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酬蹋,靈堂內(nèi)的尸體忽然破棺而出及老,到底是詐尸還是另有隱情,我是刑警寧澤范抓,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布骄恶,位于F島的核電站,受9級特大地震影響匕垫,放射性物質(zhì)發(fā)生泄漏僧鲁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寞秃。 院中可真熱鬧斟叼,春花似錦、人聲如沸春寿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堂淡。三九已至馋缅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绢淀,已是汗流浹背萤悴。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留皆的,地道東北人覆履。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像费薄,于是被迫代替她去往敵國和親硝全。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容