初探JavaScript的函數(shù)

什么是函數(shù)

函數(shù)是對(duì)象的一種,也是一段可以重復(fù)使用的代碼塊,開(kāi)發(fā)人員為了完成某項(xiàng)功能,把相關(guān)代碼塊放到一起。

函數(shù)內(nèi)部可以傳參,也可以被當(dāng)做參數(shù)傳遞

目前定義函數(shù)有五種方法

具名函數(shù)來(lái)定義

function f(x, y){
  return x + y
}
f.name //'f'

匿名函數(shù)來(lái)定義

var f
f = function(x, y){
  return x + y
}
f.name //'f'

具名函數(shù)定義了又賦值給了變量

var f1
f1 = function f(a, b){
  return a + b
}
f1.name //'f'

要注意:雖然f1.name='f',但是f只在函數(shù)內(nèi)部可用,實(shí)際上函數(shù)的名字還是f1

window.Function來(lái)構(gòu)造

var f2 = new Function('x', 'y', 'return x + y')
f2.name //'anonymous'

箭頭函數(shù)

var f3 = (x, y) => {return x - y}
var sum = (x, y) => x + y //函數(shù)體內(nèi)只有一行代碼,可以省略大括號(hào)和return
var n2 = n => n*n //只有一個(gè)參數(shù),可以省略小括號(hào)

常用的定義方法是1、2、5這三種方法。

函數(shù)的一些必備知識(shí)

函數(shù)的name屬性

由上面的五種定義方法,我們可以知道函數(shù)具有name屬性,而且不同的定義方法,name屬性也很奇葩。

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

為了理解后面的this,推薦使用call()方法,而不是使用常見(jiàn)的f()

以第一種定義方法為例

f.call(undefined, 1, 3)
4

call()方法的第一個(gè)參數(shù)就是this,后面的參數(shù)才是函數(shù)的執(zhí)行參數(shù)。

下面用代碼檢驗(yàn)一下

function f1(m, n){
    console.log(this)
    console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
Window {postMessage: ?, blur: ?, focus: ?, close: ?, frames: Window, …} //不是應(yīng)該打印undefined,為啥是window呢?
4 //這才是函數(shù)的執(zhí)行內(nèi)容

執(zhí)行f1.call(undefined, 1, 3)后,this不是應(yīng)該打印出undefined嗎,為啥打印了Window呢(注意實(shí)際上是個(gè)小寫(xiě)的window,不是瀏覽器打印的大寫(xiě)的Window),可以用代碼驗(yàn)證打印的就是小寫(xiě)的window

function f1(m, n){
    console.log(this === window)
    console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
true //說(shuō)明是小寫(xiě)得到window
4
function f1(m, n){
    console.log(this === Window)
    console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
false //并不是大寫(xiě)的Window
4

我真是服啦,那window和Window有啥區(qū)別呢。真是蛋疼啊,竟然考慮這個(gè)問(wèn)題……

答案就是 var object = new Object,那var window = new Window。而且Window毫無(wú)探討的意義,倒是這個(gè)window是個(gè)全局屬性,多少有點(diǎn)用。


有時(shí)候自己真是有點(diǎn)鉆牛角尖,鉆進(jìn)去后,還不會(huì)舉一反三。如果立刻想到obj的例子就不用浪費(fèi)時(shí)間了。


這就是藏著的this

這是因?yàn)闉g覽器搗的鬼,他把undefined變成了window。接下來(lái)使用嚴(yán)格模式,讓undefined現(xiàn)身

function f1(m, n){
    'use strict'
    console.log(this)
    console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
undefined //這個(gè)undefined就是call()方法的第一個(gè)參數(shù)undefined
4
  • 而且call()的第一個(gè)參數(shù)是啥,this就是啥
function f1(m, n){
    'use strict'
    console.log(this)
    console.log(m + n)
}
undefine
f1.call('我是啥this就是啥', 1, 3)
我是啥this就是啥 //打印的依然是call()的第一個(gè)參數(shù)
4

arguments

前面分析了call()的第一個(gè)參數(shù),那后倆參數(shù)是啥呢。

對(duì),你沒(méi)猜錯(cuò),那就是arguments。

當(dāng)你寫(xiě)call(undefined, 1, 3)的時(shí)候。undefined可以被認(rèn)為是this,[1, 3]就是arguments

函數(shù)的call stack

上面我們接觸了call()方法,現(xiàn)在我們學(xué)習(xí)一下當(dāng)有多個(gè)函數(shù)調(diào)用的時(shí)候,JavaScript解析器是如何調(diào)用棧的。

MDN的解釋如下

調(diào)用棧是解析器(如瀏覽器中的的javascript解析器)的一種機(jī)制,可以在腳本調(diào)用多個(gè)函數(shù)時(shí),跟蹤每個(gè)函數(shù)在完成執(zhí)行時(shí)應(yīng)該返回控制的點(diǎn)。(如什么函數(shù)正在執(zhí)行,什么函數(shù)被這個(gè)函數(shù)調(diào)用,下一個(gè)調(diào)用的函數(shù)是誰(shuí))

  • 當(dāng)腳本要調(diào)用一個(gè)函數(shù)時(shí),解析器把該函數(shù)添加到棧中并且執(zhí)行這個(gè)函數(shù)。
  • 任何被這個(gè)函數(shù)調(diào)用的函數(shù)會(huì)進(jìn)一步添加到調(diào)用棧中,并且運(yùn)行到它們被上個(gè)程序調(diào)用的位置。
  • 當(dāng)函數(shù)運(yùn)行結(jié)束后,解釋器將它從堆棧中取出,并在主代碼列表中繼續(xù)執(zhí)行代碼。
  • 如果棧占用的空間比分配給它的空間還大,那么則會(huì)導(dǎo)致“堆棧溢出”錯(cuò)誤。

以下是通過(guò)三個(gè)方面去理解call stack這個(gè)概念的。

普通調(diào)用

代碼如下,直觀的動(dòng)圖可以看上述的鏈接

function a(){
    console.log('a')
  return 'a'  
}

function b(){
    console.log('b')
    return 'b'
}

function c(){
    console.log('c')
    return 'c'
}

a.call()
b.call()
c.call()

如上的代碼,先有三個(gè)函數(shù)聲明,然后是三個(gè)調(diào)用。瀏覽器先執(zhí)行a.call(),然后執(zhí)行b.call(),c.call(),下面結(jié)合圖具體詳細(xì)分析。

[圖片上傳失敗...(image-57cf0d-1515141332134)]

  • 第一步:瀏覽器入口是a.call(),a函數(shù)入棧,執(zhí)行a函數(shù)內(nèi)部代碼
  • 第二步:console.log('a')執(zhí)行完畢,就出棧,接著a函數(shù)結(jié)束,出棧死亡
  • 第三步:b.call()入棧,執(zhí)行b函數(shù)內(nèi)部代碼
  • 第四步: console.log('b')執(zhí)行完畢就出棧,接著b函數(shù)結(jié)束,出棧死亡
  • 第五步:c.call()入棧,執(zhí)行c函數(shù)內(nèi)部代碼
  • 第六步:console.log('c')執(zhí)行完畢就出棧,接著c函數(shù)結(jié)束,出棧死亡。
  • 整個(gè)代碼結(jié)束,瀏覽器恢復(fù)平靜。

嵌套調(diào)用

function a(){
    console.log('a1')
    b.call()
    console.log('a2')
  return 'a'  
}
function b(){
    console.log('b1')
    c.call()
    console.log('b2')
    return 'b'
}
function c(){
    console.log('c')
    return 'c'
}
a.call()
console.log('end')
嵌套調(diào)用
  • 第一步:瀏覽器的入口還是a.call(),a.call()入棧,執(zhí)行a函數(shù)內(nèi)部的代碼
  • 第二步: a函數(shù)的第一行語(yǔ)句console.log('a1'),入棧,打印出a1,這句話就出棧死亡。此時(shí)a函數(shù)繼續(xù)執(zhí)行下面的代碼。
  • 第三步: a函數(shù)的第二行語(yǔ)句b.call()入棧。執(zhí)行b函數(shù)內(nèi)部的代碼。
    • 第四步:進(jìn)入b函數(shù)內(nèi)部,b函數(shù)的第一行語(yǔ)句console.log('b1')入棧,打印出b1,就出棧死亡。
    • 第五步:b函數(shù)的第二行c.call()入棧,又進(jìn)入c函數(shù)內(nèi)部
      • 第六步:進(jìn)入c函數(shù)的內(nèi)部,第一行語(yǔ)句console.log('c')入棧,打印出c,就出棧死亡。
      • 第七步:c函數(shù)執(zhí)行完畢,出棧死亡。
    • 第八步:回到b函數(shù)內(nèi)部,執(zhí)行第三行代碼console.log('b2')入棧,打印出b2,出棧死亡。
    • 第九步: b函數(shù)執(zhí)行完畢,出棧死亡。
  • 第十步: 回到a函數(shù)內(nèi)部,執(zhí)行第三行代碼console.log('a2'),入棧,打印出a2,就出棧死亡。
  • 第十一步:a函數(shù)執(zhí)行完畢,出棧死亡。
  • 第十二步:console.log('end')入棧,打印出end,出棧死亡。
  • 整個(gè)代碼運(yùn)行完,瀏覽器歸于平靜。

遞歸調(diào)用

遞歸調(diào)用就是上面的嵌套調(diào)用的復(fù)雜變化,細(xì)心點(diǎn),分析就能明白具體的代碼順序。

函數(shù)作用域

除了全局變量,其他變量只能在自己的函數(shù)內(nèi)部被訪問(wèn)到,其他區(qū)域無(wú)法訪問(wèn)。通過(guò)幾個(gè)面試題來(lái)學(xué)習(xí)一下。

  • 第一道面試題
var a = 1
function f1(){
    alert(a) // 是多少
    var a = 2
}
f1.call()

問(wèn):alert出什么東西?

這種題切忌上去就做,容易打錯(cuò)成了 a是2 一定要先把變量提升。變成如下這樣的

var a = 1
function f1(){
    var a 
    alert(a) 
    a = 2
}
f1.call()

這樣一提升就知道啦,答案:a是undefined。

  • 第二道面試題
var a = 1
function f1(){
    var a = 2
    f2.call()
}
function f2(){
    console.log(a) // 是多少
}
f1.call()

問(wèn):a是多少

這個(gè)題用就近原則好做。

樹(shù)形結(jié)構(gòu)

用樹(shù)形結(jié)構(gòu)來(lái)分析,當(dāng)上面的代碼被瀏覽器渲染之后

  • 全局變量里面有:var a = 1,f1、f2函數(shù)
  • f1函數(shù)作用域里面又重新聲明了一個(gè)var a = 2
  • f2函數(shù)作用域里面是console.log(a)

所以打印的那個(gè)a就是全局的a,答案是a=1

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 函數(shù)和對(duì)象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對(duì)于任何一門(mén)語(yǔ)言來(lái)說(shuō)都是核心的概念。通過(guò)函數(shù)可以封裝任意多條語(yǔ)句,而且...
    道無(wú)虛閱讀 4,961評(píng)論 0 5
  • Lua 5.1 參考手冊(cè) by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 14,258評(píng)論 0 38
  • ??引用類型的值(對(duì)象)是引用類型的一個(gè)實(shí)例。 ??在 ECMAscript 中,引用類型是一種數(shù)據(jù)結(jié)構(gòu),用于將數(shù)...
    霜天曉閱讀 1,220評(píng)論 0 1
  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實(shí)現(xiàn)同樣的效果;這時(shí)候需要使用工廠模式。簡(jiǎn)單...
    舟漁行舟閱讀 8,131評(píng)論 2 17
  • 創(chuàng)建物體的三種方法,這三種方法實(shí)際上都是屬于GameObject類的方法 通過(guò)代碼給游戲物體添加組件如:添加一個(gè)剛...
    夜行水寒閱讀 1,835評(píng)論 0 0

友情鏈接更多精彩內(nèi)容