Javascript中函數的定義和參數

函數的定義和參數

結構

  • 函數式的不同點到底是什么?
  • 函數作為對象的樂趣
  • 函數定義
  • 函數的實參和形參
  • 小結
  • 練習

函數式的不同點到底是什么?

函數式概念之所以如此重要, 原因之一在于函數是執行過程中的主要模塊單元.

既然大多數代碼都以函數作為模塊單元, 那么就應該盡可能少的限制函數的使用方式, 從而引出函數是一等公民的概念. 函數擁有對象的所有能力. 從一等公民的概念上看, 函數應該具有如下能力:

// 使用字面量創建一個函數
function f() {}

// 將函數賦值給變量
var v = f
// 將函數插入到數組中
var arr = []
arr.push(f)
// 將函數作為對象的屬性
var obj = {}
obj.f = f

// 將函數作為參數傳遞給另外一個函數
function call(func) {
  func()
}
call(f)

// 將函數作為一個值從另一個函數返回
function creator() {
  return function() {}
}

// 奇特的是, 函數其實是對象, 給一個函數添加屬性
// 函數和其他非函數的對象的主要區別在于函數是可調用的, 而其它對象不是
f.status = "done"

函數式編程是一種編程風格, 或稱為范式. 函數式編程不是唯一的編程范式, 對于同一個問題, 可以使用函數式風格解決, 也可以通過其他編程風格解決. 其他編程風格還有命令式(C語言), 面向對象式(Java). 函數式編程本身是一個較大的問題, 一般來說, 使用函數式編程的主要優勢在于其代碼易于測試, 擴展和模塊化.

回調函數

高效使用Javascript編程的關鍵在于使用回調函數. 基于Javascript中函數作為一等對象且函數可以定義在任何表達式出現的位置, 也就可以方便的將函數做為回調函數傳遞給另一個函數完成調用. 這樣的調用通常是異步的.

let arr = []

for(let i = 0; i < 20; i++) {
  arr.push(Number.parseInt(Math.random()*100))
}

// 將比較函數作為回調函數傳遞給排序函數, 該函數定義在參數列表中
arr.sort(function(v1, v2) {
  return v1 - v2
})

函數作為對象的樂趣

在Javascript中一個讓人感覺驚訝的地方是函數是對象. 和其他所有對象一樣可以賦予屬性, 屬性可以是任意的值. 如此可以產生一些特別的應用, 如賦值給函數一個id來管理函數的集合, 或者將函數每次計算得到的值緩存在其自身的屬性中, 如此可以在后續調用時首先根據入參查找是否已經緩存過結果, 從而提高運算性能.

管理函數集合

var store = {
  nextId: 1,
  cache: {},
  add: function(fn) {
    if(!fn.id) {
      fn.id = this.nextId++
      this.cache[fn.id] = fn
      return true
    }
  }
}

store對象的作用是存儲一個函數的集合用于如某個事件觸發之后調用其中存儲的所有回調函數. 也可以簡單的使用數組來存儲這樣一個集合, 這樣就需要一個方式確認向集合中添加函數時不會添加了重復的函數. 使用數組來存儲這個集合也就意味著每次添加函數時都需要遍歷一遍集合以確保集合中不會保存重復的函數. 而使用store對象則是更簡潔的方法, 添加函數進入集合時不需要每次都遍歷一遍集合, 只需簡單的在函數上設置一個標志位即可.

自記憶函數

function isPrime(value) {
  // 首先創建緩存區
  if(!isPrime.cache) {
    isPrime.cache = {}
  }
  // 在緩存區查找緩存值
  if(isPrime.cache[value] !== undefined) {
    return isPrime.cache[value]
  }
  // 沒有緩存值的情況下才執行運算
  let prime = value !== 0 && value !== 1
  for(let i = 2; i < value; i++) {
    if(value % i === 0) {
      prime = false
      break
    }
  }
  // 將執行運算的結果存入緩存區
  return isPrime.cache[value] = prime
}

以上代碼利用了函數是對象的特性創建了一個可以緩存計算結果的函數, 此函數將這個緩存操作封裝在其內部, 外部調用者無需以任何特殊方式取使用它就可以獲得性能的提升.

函數定義

函數通常使用函數字面量來創建函數值, 然而作為第一類對象, 函數可以使用程序中的值定義的, 如字符串或變量中的值. 一共有4類函數定義方式:

  • 函數定義和函數表達式
    function func() { return 1 }
    var func = function() { return 1 }
  • 箭頭函數
    param => param * 2
  • 函數構造函數
    new Function('a', 'b', 'return a + b')
  • 生成器函數
    function* gen() { yield 1 }

函數創建的方式影響了函數可被調用的時間, 函數的行為及函數可以在哪個對象上被調用.

函數聲明和函數表達式

首先是函數聲明, 其定義語法是: function func(a, b) { return a + b }, 它和函數表達式看起來類似, 其特點是函數聲明是獨立的Javascript代碼塊, 作為一個單獨的Javascript語句.

接著是函數表達式, 其定義語法是: function(a, b) { return a + b }, 可以看到它就像一個沒有名字的函數聲明. 實際上函數表達式幾乎總是其他表達式的一部分, 例如可以出現在賦值表達式的右值, 或出現在函數調用的參數列表中. 簡單來說它就是一個表達式.

最后是立即函數, 對于基本的函數調用而言首先求值得到函數的標識符作為左值, 接著使用函數調用運算符(即一對括號)調用這個函數. 函數表達式的定義本身就可以作為函數標識符的左值, 因此可以使用一對括號立即調用這個新定義的函數, 這就是立即調用函數表達式(IIFE).

// 從語法層面依然需要一對括號括起函數表達式的定義
// 如果使用函數表達式定義函數, 而該定義獨立的出現在程序代碼中
// 而非作為其他表達式的一部分, 解釋器將會報錯, 因為其認為這是一個
// 忘記寫函數名的函數聲明, 因此需要讓函數定義表達式出現在另一個表達式中,
// 如使用運算符通知解釋器這里是一個表達式
;(function(){
  console.log('immediately')
})()
;(function() {
  console.log('expression')
}())

// 使用4個一元運算符依然可以通知解釋器接下來的函數表達式是一個表達式而非語句
;+function(){
  console.log('+++')
}()
;-function(){
  console.log('---')
}()
;!function(){
  console.log('!!!')
}()
;~function(){
  console.log('~~~')
}()

箭頭函數

Javascript中會大量使用函數, 因此ES6標準中出于簡化創建函數方式的目的新增了箭頭函數, 一般來說箭頭函數就是函數表達式的簡化版. 箭頭函數有兩種可選方式:

// 函數體就是一個表達式時省略了return, 該表達式的求值結果就是函數的值
var greet = name => 'Greetings ' + name

// 和函數表達式一樣, 使用大括號包含了整個函數體
var greet = name => {
  var helloString = 'Greetings '
  return helloString + name
}

函數的實參和形參

  • 形參是定義函數時所列舉的變量
  • 實參是調用函數時所傳遞給函數的值

當調用一個函數時所列舉的實參會按照順序賦值給函數內的形參. 當實參多余形參或形參多余實參時都不會拋出錯誤. 多出的實參被簡單的丟棄, 而多出的形參則被賦值為undefined.

剩余參數

在形參列表中的最后一個形參可以使用剩余參數, 使用...標識這個形參是剩余參數, 它將接收所有還未被之前形參所接收的實參的值, 并將這些值放到一個數組中. 如果沒有任何可以接收的實參那么剩余參數將被賦值為空數組而非undefined.

默認參數

默認參數是ES6的新特性, 其目的依然是書寫簡潔的代碼, 看下面的例子中的ES6之前的默認參數處理方式和ES6之后的默認參數處理方式就可以看出其區別:

function sum(a, b) {
  // ES6之前處理默認參數需要寫出冗長的代碼
  a = typeof a === 'undefined' ? 0 : a
  b = typeof b === 'undefined' ? 0 : b
  return a + b
}

// ES6之后簡單的在參數列表中指定其默認值
function sum(a=0, b=0) {
  return a + b
}

// 默認參數可以引用參數列表中之前的參數的值
// 現在如果只向sum傳遞一個參數那么它的功能就變成了double
function sum(a=0, b=a) {
  return a + b
}
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容