JS函數(shù)表達(dá)式(JS高級(jí)程序設(shè)計(jì)筆記)

函數(shù)表達(dá)式


JS定義函數(shù)的方式有兩種:一種是函數(shù)聲明窄绒,另一種就是函數(shù)表達(dá)式彰导。函數(shù)聲明的語(yǔ)法是這樣的位谋。

function functionName(arg0, arg1, arg2) {
  //function body
}

函數(shù)聲明掏父,它的一個(gè)重要特征就是函數(shù)聲明提升(function declaration hoisting),意思是在執(zhí)行代碼之前會(huì)先讀取函數(shù)聲明仅讽。這就意味著可以把函數(shù)聲明放在調(diào)用它的語(yǔ)句后面洁灵。

sayHi()

function sayHi(){
  alert("Hi!")
}

第二種創(chuàng)建函數(shù)的方式是使用函數(shù)表達(dá)式徽千。函數(shù)表達(dá)式有幾種不同的語(yǔ)法形式罐栈。下面是最常見的一種形式。

var functionName = function(arg0, arg1, arg2){
  //function body
}

這種形式實(shí)際上跟常規(guī)的變量賦值沒有區(qū)別, 就是創(chuàng)建一個(gè)變量, 然后將函數(shù)賦值給變量, 但是我們需要注意的是, 上面這種方式創(chuàng)建的函數(shù)function后面是沒有跟函數(shù)名的, 我們叫這種函數(shù)為匿名函數(shù)或者拉姆達(dá)函數(shù)

這里需要注意的是, 函數(shù)表達(dá)式?jīng)]有函數(shù)提升


函數(shù)表達(dá)式在遞歸中的應(yīng)用


假設(shè)我們有以下階乘函數(shù)

function factorial(num) {
  if(num < 1) {
     return 1
  }
  return num * factorial(num - 1)
}

這個(gè)函數(shù)看起來(lái)并沒有什么問(wèn)題, 但實(shí)際上確有一個(gè)隱藏的漏洞, 例如下面這種情況

let fn = factorial
factorial = null
console.log(fn(5))

上面的代碼中, 我們先將factorial賦值給fn, 然后將factorial指向null, 當(dāng)我們調(diào)用fn函數(shù)時(shí), 遞歸函數(shù)在運(yùn)行到return num * factorial(num - 1)這句, 由于factorial指向了null, 所以函數(shù)就會(huì)報(bào)錯(cuò)

我們可以通過(guò)arguments.callee來(lái)解決這個(gè)問(wèn)題, arguments.callee時(shí)一個(gè)指向正在執(zhí)行的函數(shù)的指針, 所以可以實(shí)現(xiàn)遞歸調(diào)用

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

但是, arguments.callee在es標(biāo)準(zhǔn)中已經(jīng)漸漸被拋棄, 并且在嚴(yán)格模式下不能訪問(wèn)arguments.callee, 因此我們需要更好的方法來(lái)解決這個(gè)問(wèn)題, 用函數(shù)表達(dá)式就是一個(gè)不錯(cuò)的選擇

let factorial = (function fn(num) {
    if(num < 1) {
     return 1
    }
    return num * fn(num - 1)
})

上面的代碼我們創(chuàng)建了一個(gè)fn具名函數(shù)表達(dá)式, 然后將fn賦值給factorial, 這時(shí)候, 即使把函數(shù)賦值給另外的其他變量, fn仍然有效, 就避免了上面的問(wèn)題


閉包


在《JavaScript高級(jí)程序設(shè)計(jì)》中, 對(duì)于閉包有著以下定義:
閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)。
創(chuàng)建閉包的常見方式, 就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)

function fn() {
  var a = 1
  function foo() {
    console.log(a)
  }

  return foo
}

let bar = foo()
bar() //2 閉包

上面的代碼就是一個(gè)典型的閉包, 我們?cè)谌汁h(huán)境中訪問(wèn)到了fn函數(shù)作用域中的變量a, 為了了解清楚這種情況的細(xì)節(jié), 我們必須要從作用域鏈入手

當(dāng)某個(gè)函數(shù)被調(diào)用時(shí), 會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境(execution context)及相應(yīng)的作用域鏈聂抢。然后, 使用arguments和其他命名參數(shù)的值來(lái)初始化函數(shù)的活動(dòng)對(duì)象(activation object)琳疏。但在作用域鏈中, 外部函數(shù)的活動(dòng)對(duì)象始終處于第二位, 外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象處于第三位, 以此類推, 直到全局執(zhí)行環(huán)境為止空盼。

函數(shù)執(zhí)行時(shí), 為了讀取或者寫入變量的值, 就需要在作用域鏈中查找變量, 假設(shè)我們有下面的例子

function compare(value1, value2) {
  if(value1 < value2) {
    return -1
  }else if(value1 > value2) {
    return 1
  }else {
    return 0
  }
}

let result = compare(5, 10)

當(dāng)我們調(diào)用compare()時(shí), 會(huì)創(chuàng)建一個(gè)compare的執(zhí)行環(huán)境, 該環(huán)境會(huì)初始化一個(gè)包含arguments, value1和value2的活動(dòng)對(duì)象揽趾。在創(chuàng)建執(zhí)行環(huán)境的同時(shí)還會(huì)創(chuàng)建其作用域鏈, 這條作用域鏈包含兩個(gè)執(zhí)行環(huán)境, 一個(gè)compare執(zhí)行環(huán)境和全局執(zhí)行環(huán)境(全局執(zhí)行環(huán)境中有一個(gè)包含result和compare的活動(dòng)對(duì)象)。 compare執(zhí)行環(huán)境處于作用域鏈的第一位, 全局執(zhí)行環(huán)境則處在作用域鏈的第二位

作用域鏈

每個(gè)執(zhí)行環(huán)境都有一個(gè)表示變量的對(duì)象——變量對(duì)象俐筋。全局環(huán)境的變量對(duì)象始終存在严衬,而像compare()函數(shù)這樣的局部環(huán)境的變量對(duì)象,則只在函數(shù)執(zhí)行的過(guò)程中存在腰奋。在創(chuàng)建compare()函數(shù)時(shí)抱怔,會(huì)創(chuàng)建一個(gè)預(yù)先包含全局變量對(duì)象的作用域鏈屈留,這個(gè)作用域鏈被保存在內(nèi)部的[[Scope]]屬性中灌危。當(dāng)調(diào)用compare()函數(shù)時(shí),會(huì)為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境沫勿,然后通過(guò)復(fù)制函數(shù)的[[Scope]]屬性中的對(duì)象構(gòu)建起執(zhí)行環(huán)境的作用域鏈产雹。此后蔓挖,又有一個(gè)活動(dòng)對(duì)象(在此作為變量對(duì)象使用)被創(chuàng)建并被推入執(zhí)行環(huán)境作用域鏈的前端。對(duì)于這個(gè)例子中compare()函數(shù)的執(zhí)行環(huán)境而言续膳,其作用域鏈中包含兩個(gè)變量對(duì)象:本地活動(dòng)對(duì)象和全局變量對(duì)象。顯然,作用域鏈本質(zhì)上是一個(gè)指向變量對(duì)象的指針列表,它只引用但不實(shí)際包含變量對(duì)象邪财。

無(wú)論什么時(shí)候在函數(shù)中訪問(wèn)一個(gè)變量時(shí),就會(huì)從作用域鏈中搜索具有相應(yīng)名字的變量糠馆。一般來(lái)講怎憋,當(dāng)函數(shù)執(zhí)行完畢后九昧,局部活動(dòng)對(duì)象就會(huì)被銷毀铸鹰,內(nèi)存中僅保存全局作用域(全局執(zhí)行環(huán)境的變量對(duì)象)蹋笼。但是剖毯,閉包的情況又有所不同教馆。

假設(shè)我們有下面這個(gè)閉包

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 compareNames = createComparisonFunction('name')
var result = compareNames({ name: 'Nicholas'}, { name: 'Greg' })

//解除對(duì)匿名函數(shù)的引用(以便釋放內(nèi)存)
compareNames = null

當(dāng)一個(gè)函數(shù)定義在另一個(gè)函數(shù)內(nèi)部時(shí), 該函數(shù)會(huì)將它的上層函數(shù)的活動(dòng)對(duì)象也添加到他的作用域中, 因此上面這個(gè)例子中的匿名函數(shù)function(object1, object2){}的作用域鏈中, 不但包含它自己的活動(dòng)對(duì)象, 還包含了createComparisonFunction()和全局活動(dòng)對(duì)象, 這樣, 匿名函數(shù)就可以訪問(wèn)createComparisonFunction()中定義的所有變量胶滋。更重要的一點(diǎn), createComparisonFunction()函數(shù)在執(zhí)行完畢后, 由于匿名函數(shù)的作用域鏈仍然引用這個(gè)活動(dòng)對(duì)象, 所以這個(gè)活動(dòng)對(duì)象不會(huì)被銷毀, 知道匿名函數(shù)被銷毀后, 它才會(huì)被銷毀镀钓。

由于閉包會(huì)攜帶包含它的函數(shù)的作用域,因此會(huì)比其他函數(shù)占用更多的內(nèi)存探遵。過(guò)度使用閉包可能會(huì)導(dǎo)致內(nèi)存占用過(guò)多箱季。雖然像V8等優(yōu)化后的JavaScript引擎會(huì)嘗試回收被閉包占用的內(nèi)存藏雏,但請(qǐng)大家還是要慎重使用閉包掘殴。

閉包和變量

作用域鏈有一個(gè)副作用我們必須要注意, 就是閉包只能取得變量的最后一個(gè)值粟誓。因?yàn)殚]包保存的時(shí)整個(gè)活動(dòng)對(duì)象而不是某個(gè)特殊的變量值

function fn() {
  var result = []
  for(val i=0; i<10; i++) {
      result[i] = function() {
          return i
      }
  }
  return result
}

這個(gè)函數(shù)會(huì)返回一個(gè)函數(shù)數(shù)組。表面上看病瞳,似乎每個(gè)函數(shù)都應(yīng)該返自己的索引值,即位置0 的函數(shù)返回0亲善,位置1 的函數(shù)返回1蛹头,以此類推掘而。但實(shí)際上袍睡,每個(gè)函數(shù)都返回10斑胜。因?yàn)槊總€(gè)函數(shù)的作用域鏈中都保存著fn() 函數(shù)的活動(dòng)對(duì)象嫌吠, 所以它們引用的都是同一個(gè)變量i 辫诅。當(dāng)fn()函數(shù)返回后么夫,變量i 的值是10肤视,此時(shí)每個(gè)函數(shù)都引用著保存變量i 的同一個(gè)變量對(duì)象邢滑,所以在每個(gè)函數(shù)內(nèi)部i 的值都是10

我們可以通過(guò)一個(gè)匿名函數(shù)強(qiáng)制讓閉包符合預(yù)期行為

function fn() {
  var result = []
  
  for(var i=0; i<10; i++) {
    result[i] = (function(num) {
      return function() {
        return num
      }
    })(i)
  }
  return result
}

在重寫了前面的fn()函數(shù)后乐纸,每個(gè)函數(shù)就會(huì)返回各自不同的索引值了摇予。在這個(gè)版本中庶喜,我們沒有直接把閉包賦值給數(shù)組,而是定義了一個(gè)匿名函數(shù)秩冈,并將立即執(zhí)行該匿名函數(shù)的結(jié)果賦給數(shù)組入问。這里的匿名函數(shù)有一個(gè)參數(shù)num稀颁,也就是最終的函數(shù)要返回的值匾灶。在調(diào)用每個(gè)匿名函數(shù)時(shí)阶女,我們傳入了變量i秃踩。由于函數(shù)參數(shù)是按值傳遞的,所以就會(huì)將變量i 的當(dāng)前值復(fù)制給參數(shù)num鸟赫。而在這個(gè)匿名函數(shù)內(nèi)部,又創(chuàng)建并返回了一個(gè)訪問(wèn)num 的閉包。這樣一來(lái)对碌,result 數(shù)組中的每個(gè)函數(shù)都有自己num 變量的一個(gè)副本荆虱,因此就可以返回各自不同的數(shù)值了。

關(guān)于this對(duì)象

在閉包中使用this也需要特別注意, 比如下面的例子

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name
    }
  }
}
alert(object.getNameFunc()()); //"The Window"(在非嚴(yán)格模式下)

按照作用域鏈來(lái)理解, 匿名函數(shù)應(yīng)該會(huì)一層一層向上找活動(dòng)對(duì)象, 那么這個(gè)this最終應(yīng)該會(huì)指向其上層對(duì)象, 但是實(shí)際情況this似乎并沒有指向其上層對(duì)象, 而是指向了window, 造成這個(gè)結(jié)果的原因是因?yàn)? 對(duì)于this和arguments這個(gè)兩個(gè)特殊變量, 內(nèi)部函數(shù)只會(huì)搜索到其自身的活動(dòng)對(duì)象為止, 并不會(huì)通過(guò)作用域鏈訪問(wèn)其外層活動(dòng)對(duì)象

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末朽们,一起剝皮案震驚了整個(gè)濱河市怀读,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骑脱,老刑警劉巖菜枷,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異叁丧,居然都是意外死亡啤誊,警方通過(guò)查閱死者的電腦和手機(jī)岳瞭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了痒给?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)碟贾,這世上最難降的妖魔是什么朱巨? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任跨琳,我火速辦了婚禮溅潜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布肖油。 她就那樣靜靜地躺著疲恢,像睡著了一般搓萧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天日熬,我揣著相機(jī)與錄音毕荐,去河邊找鬼虽填。 笑死墓陈,一個(gè)胖子當(dāng)著我的面吹牛仔拟,可吹牛的內(nèi)容都是我干的臀栈。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼宪哩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼圆恤!你這毒婦竟也來(lái)了炼团?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤踩衩,失蹤者是張志新(化名)和其女友劉穎驱富,沒想到半個(gè)月后叫榕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荞下,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡句携,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年牍疏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拨齐。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鳞陨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瞻惋,到底是詐尸還是另有隱情厦滤,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布歼狼,位于F島的核電站掏导,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏羽峰。R本人自食惡果不足惜趟咆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梅屉。 院中可真熱鬧忍啸,春花似錦、人聲如沸履植。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)玫霎。三九已至凿滤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庶近,已是汗流浹背翁脆。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鼻种,地道東北人反番。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像叉钥,于是被迫代替她去往敵國(guó)和親罢缸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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