iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 10
2
Modern Web

前端三十 - 成為更好的前端工程師系列 第 10

10. [JS] 一般函式與箭頭函式的差異?

https://ithelp.ithome.com.tw/upload/images/20190926/201113807WxHrAtqAr.jpg

旅程來到了第 10 天,這趟旅程真的有點硬啊...不過每天只要花一點點時間,就能讓逐漸讓自己變強,這樣的感覺是不是很棒呢?

今天的主題輕鬆一點,來看看 JavaScript ES6 中大家都會用的箭頭函式與一般函式的差異。

本系列文已經重新編校彙整編輯成冊,並正式出版囉!
《前端三十:從 HTML 到瀏覽器渲染的前端開發者必備心法》好評販售中!
喜歡我文章內容的讀者們,歡迎您 前往購買 支持!

一般函式

在 ES6 版本之前的 JavaScript 是沒有箭頭函式可以用的,僅有最基本的一般函式。基本語法相信大家少說也寫過成千上萬次,對它應該都不太陌生,那麼這邊僅針對幾點可能容易被忽略的特性,做一些簡單的分享:

建立

開發者可以透過 function() 關鍵字來定義、撰寫函式:

function fn() {
  // something awesome
}

另一種常見的寫法,是宣告一個變數,等號右邊放上函式宣告:

var newFunc = function fn() {
  // something awesome
}

兩種寫法在使用上唯一的差異,就是第二種寫法在宣告前不能使用。

快問快答小知識:
Q:請問第二種寫法中的 newFunc.name 是什麼?
A:是 fn,而不是 newFunc 喔~

另外還有一種透過函數建構子的宣告方法:

var func = new Function('str', 'console.log(str)')
func('test')

但這樣子撰寫就無法利用 JavaScript 引擎會事先解析程式並準備 EC 的特性,造成效能上的冗余浪費,非常不推薦使用。

屬性

函式在 JavaScript 中,其實是一個可被呼叫(Callable)的物件,除了可執行、擁有例如 apply()bind()call() 之類的方法外,也擁有一些函式特有的屬性:

  • name:函式的名稱
  • length:可接收參數的數量

這兩者是 readonly 的屬性,在函式建立後即不能被修改。

另外有兩個已從標準廢棄,但主流瀏覽器仍支援的屬性:

  • caller:呼叫函式的地方在哪,全域呼叫則回應 null
  • argument:函數執行時所接收到的參數,非執行時呼叫則回傳 null

由於已經是被廢棄的屬性,未來隨時可能會被瀏覽器放棄支援,所以開發上能不用就盡量不用吧。

另外,在函數執行的過程中,還有一個額外的 argument 物件可以使用,是一個傳入參數組成的類陣列(Array-like)物件

小提醒一下,被廢棄的是 Function.argument,而非在函式執行時直接呼叫的 argument 物件!

this

JavaScript 中還有個讓新手開發者難以捉摸的屬性 - this,不過別擔心, 在預設情況下,函式中的 this 都是指向呼叫他的物件的。

例如以下範例:

function whatIsThis(){
  console.log(this)
}

let obj = {
  whatIsThis: whatIsThis
}

whatIsThis()  // window
obj.whatIsThis()  // obj

剛剛說了「預設情況」,那什麼時候是非預設情況呢?

稍有經驗的開發者應該想到了,我們可以透過 apply()bind()call() 等函式,改變函式的 this,有興趣深入理解的讀者們,可以參考 筆者以前寫過的相關文章,這邊就不贅述了。

箭頭函式

雖然口頭上都稱呼為箭頭函式(Arrow Function),但實際上他是箭頭函式運算式(Arrow Function Expression)指的是類似這樣的語法:

const arrow = (param1, param2) => { 
  // do something awesome 
}

由於中間的 => 形似箭頭而得名;且撰寫起來快速直覺 ,看起來又很潮,深得開發者的喜愛。

建立

除了前述的語法外,在特定條件下箭頭函式的語法可以進一步簡化:

// 基本用法
const arrow1 = (param1, param2) => { ... } 

// 僅有一個參數時,可省略括號
const arrow2 = params => { ... } 

// 單一運算後直接回傳時,可省略大括號
const arrow3 = params => params ** 2 

另外,有一定熟悉度的開發者,可能會利用箭頭函式的特性,撰寫出如下的程式碼:

let fancy = x => y => z => x + y + z

遇到多層的箭頭函式,乍看之下可能會難以理解,但其實只要關注「箭頭」的位置,右到左的反向拆解,並把省略的部分加回去:

let fancy = (x) => {
  return (y) => {
    return (z) => {
      return x + y + z
    }
  }
}

就不會那麼難懂了對吧?

藉由箭頭函式的各種省略,開發者可以用更短的語法寫出更精簡的程式 ,而且看起來很潮,是不是很不錯呢?

屬性

與前面說明的一般函式相同,可以取得 namelength 屬性,就不重複說明了。

this

箭頭函式的重點特色來了;箭頭函式中的 this,會依據函式在哪裡建立而決定,而非與一般函式一樣,依照執行時被呼叫的地方決定。

一樣來看個範例:

var tmp = 'a'
var obj = {
  tmp: 'b',
  func: () => console.log(this.tmp)
}
console.log(tmp)  // a
obj.func()  // a

參照範例,由於 obj.func 是在全域環境中建立,this 就被綁訂到全域環境中;因此兩個執行結果印出來的結果都是全域變數 tmp 的值 a

另外,一般函式用來強制綁定 this 值的 apply()bind()call(),在箭頭函式中沒有 bind(),且 apply()call() 傳入的 this 值會被忽略,各位讀者在使用時務必要特別注意喔!

結語

今天仔細端詳了在程式開發過程中不斷頻繁使用的函式,並針對一般寫法及箭頭函式做說明和比較,希望有幫助讀者您更近一步的認識它們!

以上就是今天的箭頭函式,如果大家對文中內容有任何想法,都歡迎讀者您於文末留言回應。進行了三分之一的旅程也將繼續前進,明天也讓我們繼續一起逐步變強吧!

參考資料

筆者

Gary

半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。

相信一切安排都是最好的路。


上一篇
09. [JS] 什麼是閉包?
下一篇
11. [JS] 如何處理非同步事件?
系列文
前端三十 - 成為更好的前端工程師31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
guavaCake
iT邦新手 5 級 ‧ 2023-07-16 22:59:57
var tmp = 'a'
var obj = {
  tmp: 'b',
  func: () => console.log(this.tmp)
}
console.log(tmp)  // a
obj.func()  // a

你好,這段代碼obj.func() //undefined
並不是a
請問何者是正確的?感謝

AndrewYEE iT邦新手 3 級 ‧ 2023-07-20 18:35:22 檢舉

你好,我猜是因為你的JS環境是設定成StrictMode嚴格模式,嚴格模式 會防止this指標指向Global 區域,當夾帶this指標操作的函式在最外層被呼叫時,以往this會變成指向global。在StrictMode下,this會強制改為undefined。

我要留言

立即登入留言