Ray 已發佈 2019-11-19

有點長的淺談 JavaScript function 函式

前言

Function

昨天在六角學院做了線上直播的介紹,過程真的是緊張到爆炸,我知道自己其實滿容易健忘的,所以不寫文章筆記心裡有點過意不去,因此這一篇 「有點長的淺談 JavaScript function 函式」就誕生了。

身為一名開發工程師,絕對很常使用 Function,但是我們在開發時已經對於 Function 習以為常,所以這一篇就來一下有點長的淺談 Function,順便替自己提升記憶力以及觀念釐清。

※這一篇不講函式陳述式以及函式表達式,只單純講 function。

主要兩種撰寫函式方式

  1. 具名函式 (Named Function)
  2. 匿名函式 (Anonymous Function)

具名函式

具名函式簡單來講就是 有名子的函式,這一個比較好理解一點,撰寫模式也非常簡單也就是 function + 名稱 就是屬於具名函式的一種。

撰寫範例如下:

function sayHi() {
  console.log('Hello');
}

當然也有另一種具名函式寫法,另外這有另一個名稱

var a = function sayHi() {
  console.log('sayHi');
}

雖然這種寫法我個人比較少用,但是在 jQuery 的原始碼中就有滿多地方都是使用這種方式撰寫。

jQuery 原始碼 (前幾行就可以看到了)

匿名函式

匿名函式呢?依照字面上來看就是一個 沒有名稱的函式,我自己在撰寫是很少使用到匿名函式。

前面有提到 var a = function sayHi() {...} 這種寫法我個人比較少用,其主要原因是因為可以忽略 function 後面的名稱。

撰寫範例如下:

var a = function() {
  console.log('sayHi');
}

以及比較常見的 IIFE,但是 IIEF 就先留到後面再講。

實際開發

實際開發來講往往很容易無意識就使用這兩種技巧,首先先來講講在 Vue 開發的狀況好了。

在 Vue 專案底下我們在宣告 Vue 實例化的時候程式碼如下:

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!',
  },
  methods: {
    sayHi: function() {
      console.log(this.message);
    }
  },
  created: function() {
    this.sayHi();
  }
});

其實就已經使用了匿名函式寫法,這種寫法在 Vue 中非常常見,如果你是已經開始接觸 ES6 的開發者你應該會發現你目前的寫法都變成了這樣子 ↓

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!',
  },
  methods: {
    sayHi() {
      console.log(this.message);
    }
  },
  created() {
    this.sayHi();
  }
});

而這也就是所謂的 ES6 縮寫,後面會再來繼續談到 ES6 縮寫。

此外這邊也來額外談一下六角人才牆的開發程式碼,人才牆是使用 CoffeeScript + Gulp 開發的,而裡面就使用了滿滿的匿名函式,所以滿適合拿出來講一下。

六角人才牆程式碼在這裡:GitHub

以下是我擷取人才牆的一小段程式碼。

hopeArea = (area) ->
  return "<div class='col-md-12'>
    <h3>
      <i class='fas fa-map-marker-alt text-primary'></i>
      他們希望在 <span class='text-primary'>#{area}</span> 工作
    </h3>
    <hr/>
  </div>"

在 CoffeeScript 中 -> 代表 function 的意思,所以實際程式碼是像這樣子

var hopeArea = function (area) {
  return "<div class='col-md-12'>" +
    "<h3>" +
      "<i class='fas fa-map-marker-alt text-primary'></i>" +
      "他們希望在 <span class='text-primary'>" + area +"</span> 工作" +
    "</h3>" +
    "<hr/>" +
  "</div>";
  }

所以其實實際開發時,我們已經 無意識 地使用了這些技巧,只是已經太習慣就這樣子理所當然地寫而已。

最後你可能會問為什麼不使用具名函式寫法,其主要原因是 CoffeeScript 說明文件是建議你用匿名函式寫法

CoffeeScript function

Function

上面講了那麼多原理,但還是沒講到 Function 到底是什麼東西,這邊先來引用 MDN 對於 Function 的說法

函式是構成 JavaScript 的基本要素之一。一個函式本身就是一段 JavaScript 程序—包含用於執行某一個任務或計算的語法。要呼叫某一個函式之前,你必需先在這個函式欲執行的 scope 中定義它。

基本上看起來非常文謅謅,所以我這邊會有另一種說法

「Function 其實是一個小型程式碼區塊且是將需要重複執行的程式碼或任務打包起來的一個包裝功能。」

當然更生活化一點的舉例就是「你也可以把 Function 看成紙箱,而紙箱(Function)裡面放滿著我們將會使用的工具(Code Block),當我們需要使用的時候就會去打開紙箱(Calling functions),並依照順序組裝工具並執行。

Function

Calling Functions (函式呼叫)

在前面幾個範例中我們定義了一個 Function,但你會發現單純將程式碼貼進瀏覽器的 DevTools Console 是不會運作的,其主要原因是我們沒有做 Calling Functions (函式呼叫)的動作,在 Function 定義後它就只會靜靜躺在那邊等著你叫它起床,這邊也來擷取 MDN 一小段說法

定義一個函式並不會自動的執行它。定義了函式僅僅是賦予函式以名稱並明確函式被呼叫時該做些什麼。呼叫函式才會以給定的參數真正執行這些動作。

而如果要呼叫函式則只需要使用括號就可以達到呼叫效果

function sayHi() {
  console.log('Hello');
}

sayHi(); // Hello

而匿名函式則本身也是相同

var a = function() {
  console.log('sayHi');
}
a();

IIFE (立即執行函式)

接下來談談 IIFE 全名為 Immediately Invoked Function Expression,意思就是定義完這個函式後馬上就執行的意思,此外它又可以稱之為 Self-Executing Anonymous Function,中文意思是自我執行匿名函式,而 IIFE 的好處是可以避免汙染到全域(global)。

這邊來擷取 六角學院電子書 一部分經典範例:

雖然這個經典範例主要是講 var 的變數,但我覺得拿來講 IIFE 也很滿 ok 的,這個題目的意思是說,當 setTimeout 結束時會出現 10

for(var i = 0; i < 10; i++){
  setTimeout(function(){
    console.log(i); // 10
  }, 1000);
}

但是如果希望出現的是 0 ~ 9 的結果,那麼就可以使用 IIFE。

for(var i = 0; i < 10; i++){
  (function(j) {
    setTimeout(function (){
      console.log(j); // 0 ~ 9
    }, 1000);
  })(i)
}

這部分主要跟記憶體釋放有關係,所以也可以這樣子解

for(var i = 0; i < 10; i++){
  function sayHi(j) {
    setTimeout(function(){
    console.log(j); // 0 ~ 9
  }, 1000);
  }
  sayHi(i);
}

但是這邊就不談關於記憶體的東西,畢竟這並不是本篇所講重點。

那為什麼要了解 IIFE 呢?這邊就來看一下實際開發案例,這邊你可以看看 Vue & jQuery 的原始碼

Vue

jQuery

前面就可以看到 (function(){...})(),你可以發現 IIFE 在實際開發時是非常常見的一個手法,所以也是必須了解的東西。

生活化 Function

前面講了那麼多原理,所以可以試著思考一下如何將自己生活作息轉換成 Function。

假使我早上會起床、刷牙然後上班,中午則是吃飯、睡午覺然後工作,最後下午就是下班、回家,然後洗洗睡。

你可以發現大多行為都是固定重複的,在前面我有講到這一段

「Function 其實是一個小型程式碼區塊且是將需要重複執行的程式碼或任務打包起來的一個包裝功能。」

所以就可以這樣子寫

function 早上() {
  console.log('起床');
  console.log('刷牙');
  console.log('上班');
  中午();
}

function 中午() {
  console.log('吃飯');
  console.log('睡午覺');
  console.log('工作');
  下午();
}

function 下午() {
  console.log('下班');
  console.log('回家');
  console.log('洗洗睡');
}

早上();

你也可以試著自己將自己生活給 Function 化看看。

Function 是一個物件

JavaScript 的 Function 其實是一個很特別的東西,雖然我們可以透過 typeof 查看型別得到 function 這個結果

function sayHi() {
  console.log('Hello');
}

typeof(sayHi); // function

此外在六角學院課程 JavaScript 核心篇 有許多的課後測驗,課後測驗中就有一題是這樣子

function fu(a) {
  a = '小明';
}
fu.a = '小美';
fu('大明');

console.log(fu.a); // 小美

這邊我也特別引用一段 MDN 的簡短說明

Function 建構函式可建立一個新的 Function 物件。在 JavaScript 中,所有的函式實際上都是 Function 物件。

這邊你要注意一段 Function 物件,在 MDN 中就已經有說明在 JavaScript Function 是一個物件,此外你也可以看到 Kuro 大大在先前 IT 鐵人賽 - Day 10 函式 Functions 的基本概念 中也有提到

在前面介紹變數型別的時候曾經說過,除了基本型別以外的都是物件。
當我們透過 typeof 去檢查一個「函式 (function) 」的時候,雖然你會得到 "function" 的結果,讓你以為 function 也是 JavaScript 定義的一種型別,但實際上它仍屬於 Object 的一種。
你可以把它想像成是一種可以被呼叫 (be invoked) 的特殊物件 (值)。

歸納 Function

接下來我這邊歸納一下 Function 的幾個重點

名稱

名稱可以被忽略。

當被名稱被忽略後就會變成所謂的匿名函式。

具名函式:

function sayHi() {
  console.log('Hello');
}

匿名函式:

var a = function sayHi() {
  console.log('sayHi');
}

程式碼區塊

花括號內可撰寫程式碼。

function sayHi() {
  // Code block
}

可以被呼叫

透過名稱 + 括號呼叫。

function sayHi() {
  console.log('Hello');
}

sayHi(); // Hello

本身是一個物件

JavaScript 中 Function 本身就是一個物件。

function fu(a) {
  a = '小明';
}
fu.a = '小美';
console.log(fu.a) // 小美

屬於自己的作用域

function 內定義的變數與外層是不同的世界。

function fu() {
  var a = 'Ray';
}
console.log(a); // a is not defined

可以帶入參數

可以自己決定帶入多少參數

function fu(a, b, c) {
  console.log(a, b, c);
}
fu('1', 'Ray', 'Function');

ES6 的 Function 變化

最後來講講 ES6 Function 變化。

前面有講到 Function 可以分為兩種,也就是 具名函式 以及 匿名函式,但是在 ES6 之後出現了一個叫做箭頭函式的東西,而箭頭函式的特點就是擁有比原本函式更簡短的寫法,所以在 ES6 之後的開發者都會再將函式歸納為 箭頭函式 以及 傳統函式

其寫法如下:

var a = () => {
  console.log('sayHi');
}

但是這邊要注意一點不能將原本的函式宣告修改為以下

sayHi() => {
  console.log('Hello');
}

sayHi(); // invalid arrow-function arguments (parentheses around the arrow-function may help)

可是如果是 IIFE 的話則是可以改用箭頭函式撰寫

(() => {
console.log('Hi')
})()

此外箭頭函式還有幾個特點,它並沒有自己的 this、arguments 等語法,詳細可以看卡斯伯老師的 文章

ES6 物件函式縮寫

最後來談一下 ES6 的物件函式縮寫,前面有舉了一個 Vue 實例化的例子,在使用傳統函式撰寫的方式如下:

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!',
  },
  methods: {
    sayHi: function() {
      console.log(this.message);
    }
  },
  created: function() {
    this.sayHi();
  }
});

而 ES6 則可以使用縮寫的形式來撰寫函式,而這邊要注意一個重點這個縮寫的本質還是屬於 傳統函式

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!',
  },
  methods: {
    sayHi() {
      console.log(this.message);
    }
  },
  created() {
    this.sayHi();
  }
});

為什麼前面會說物件中的 ES6 函式縮寫是屬於傳統函式呢?這邊你可以試著修改成箭頭函式

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!',
  },
  methods: {
    sayHi: () => {
      console.log(this.message);
    }
  },
  created: () => {
    this.sayHi();
  }
});

你會發現此時的 Vue 會跳出 TypeError: this.sayHi is not a function 的錯誤訊息,最主要原因是 箭頭函式的 this 指向與傳統函式不同,ES6 的物件函式縮寫是屬於傳統函式的一種,當然我們也可以透過程式碼來驗證

ES6 物件函式縮寫:

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!',
  },
  methods: {
    sayHi() {
      console.log(this.message);
    }
  },
  created() {
    console.log(this) // 指向 Vue 本身
    this.sayHi();
  }
});

ES6 箭頭函式:

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!',
  },
  methods: {
    sayHi: () => {
      console.log(this.message);
    }
  },
  created: () => {
    console.log(this) // 指向 Window
    this.sayHi();
  }
});

經過上面各種範例、生活化案例以及實際開發案例後相信對於 Function 的觀念絕對會更清晰。

參考文獻

關於筆者

暱稱:Ray

介紹:廢話不多說,讓我們來學習寫程式吧 :D 目前經營一個技術部落格:https://hsiangfeng.github.io/

文章列表 文章列表