關於 JavaScript 的函式

(1) 函式的種類

傳統函式

  • 一段用於執行某個任務的程式碼
//語法function name([param]) {  statements}//example//宣告了一個名為square的函式並執行function square(number) {  return number * number}console.log(square(10)) // 100//以上例子中,number 是 parameter (參數)//執行此函式時,10是argument (引數)

箭頭函式

  • ES6 語法,可以更簡潔的撰寫函式
//example//原本const sayHi = function () {  console.log('hello')}//改寫const sayHi = () => {  console.log('hello')}
  • 精簡的寫法
// 參數只有一個時,參數的小括號可以省略const addOne = (n) => {  return n + 1}// 只有一行時,省略大括號和 return//ex1const addOne = (n) => n + 1//ex2const form = document.querySelector('form')form.addEventListener('click', (e) => e.preventDefault())// 直接return一個物件時,用小括號包起來const friend = (name) => ({ name })friend('Nobody')//{name: 'Nobody'}

(2) 傳統函式和箭頭函式的差別

1. This 關鍵字

  • 傳統函式的 this 依照呼叫的方法而定
  • 箭頭函式的 this 在定義時便被綁定
  • 箭頭函式沒有自己的 this,從自己的作用域上一層沿用 this
  • 箭頭函式的 this,是上一層作用域的 this 指向
//使用傳統函式時,this的指向是objconst obj = {  value: 1,  hello: function () {    console.log(this.value)  }}obj.hello() // 1//使用箭頭函式時,這裡的this是全域物件,回傳undefinedconst obj = {  value: 1,  hello: () => {    console.log(this.value)  }}//obj的this => window.objobj.hello() // undefined//傳統函式混合箭頭函式const obj = {  name: 'Andy',  sayHi: function () {    console.log(this)    //walk裡沒有this,沿用sayHi的this (obj)    const walk = () => {      console.log(this)    }    walk()  }}//傳統函式的this看怎麼被呼叫obj.sayHi()//{name:'Andy' , walk: f}//{name:'Andy' , walk: f}
  • 事件監聽時的差別
const btn = document.querySelector('.btn')//箭頭函式,this指向windowbtn.addEventListener('click',() => {    console.log(this)})//傳統函式,this指向DOM物件btn.addEventListener('click',function () => {    console.log(this)})
  • 使用 setTimeout 時的差別
const normal = {  msg: 'Hello',  fn: function () {    setTimeout(function () {      console.log(this.msg)    }, 10)  }}const arrow = {  msg: 'Bye',  fn: function () {    setTimeout(() => {      console.log(this.msg)    }, 10)  }}normal.fn() // undefined 因this指向全域arrow.fn() // Bye

2.arguments 物件不同

  • 傳統函式則會有自己的 arguments 物件
  • 箭頭函式沒有自己的 arguments 物件,而是會繼承其外層作用域的 arguments
  • arguments 物件是一個對應傳入函式之引數的類陣列(Array-like)物件。
// 傳統函式中,可以使用 arguments 對象來獲取傳遞給函式的所有參數,function func() {  console.log(arguments); /}func(1, 2, 3);// output: [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]//箭頭函式const func = () => {  console.log(arguments);};func(1, 2, 3);// output: ReferenceError: arguments is not defined//使用剩餘參數...args來代替const func = (...args) => {  console.log(args);};func(1, 2, 3);// output: [1, 2, 3]//

3. 構造函式

  • 普通函式可以用作構造函式,被用於創建新的對象(使用 new 關鍵字)
  • 箭頭函式不能用作構造函式,不能使用 new 關鍵字
const Arrow = () => {}const arrow = new Arrow() // TypeError: Arrow is not a constructor

4. prototype 屬性

  • 箭頭函式並沒有原型(prototype)屬性
function Arrow() {  console.log('Hello, prototype')}console.log(Arrow.prototype) // {constructor: f}const Arrow = () => {  console.log('Hello, prototype')}console.log(Arrow.prototype) // undefined

(3) 具名函式和匿名函式

具名函式

  • 使用 function 關鍵字宣告函式,並命名
  • 函式陳述式 (Function Declaration) ⇒ 不會回傳值或結果
//example//宣告一個function,名字是sayHifunction sayHi() {  console.log('hello')}sayHi()

匿名函式

  • 沒有命名的函式,無法直接呼叫
  • 通常出現在函式表達式 (Function Expressions)
  • 函式表達式也有可能使用具名函式
//ex1//把一個匿名函式賦值給一個變數 (expressions),然後再呼叫const sayHi = function () {  console.log('hello')}sayHi()//ex2const sayHi = function hello() {  console.log('hello')}//要用被賦值的變數呼叫執行sayHi()//錯誤呼叫hello() // hello is not defined

(4) 立即函式 IIFE

  • 定義完不用呼叫,便立即執行的函式表達式
  • 可用在只會執行一次的函式
  • 通常會是匿名函式,不需要取名
//語法1;(function () {  // 匿名函式的程式碼})()//語法2;(function () {  // 匿名函式的程式碼})()//ex1;(function () {  console.log('立刻執行') // 立刻執行})()//ex2;(function () {  console.log('立刻執行') // 立刻執行})()//ex3 傳入參數;(function (x, y) {  console.log(x + y)})(1, 2)
  • 變數的作用域只存在函式內,防止污染全域作用域
  • 可以避免與全域變數重名或衝突
//ex1 - 使用var宣告變數也不能在函式外取得;(function () {  var hobbit = 'walking'  console.log(hobbit) //'walking'})()console.log(hobbit) //is not defined
  • 可以是具名函式,但也無法在函式外再次被執行
;(function sayHi() {  console.log('Hello') //Hello})()sayHi() //sayHi is not defined
  • 因為是表達式,可以賦值給變數
const dinner = (function (food) {  return `晚餐吃${food}`})('pizza')//dinner用於接收立即函式return的值,不用加括號執行dinner // '晚餐吃pizza'//錯誤呼叫dinner() //dinner is not a function
  • 利用物件的 call by reference 特性,在匿名函式間傳遞值
const obj = {};(function (input) {  input.name = 'Mario'})(obj)console.log(obj) // {name: 'Mario'}//取得obj.name做處理;(function (input2) {  console.log(`${input2.name} says Hi`) // 'Mario says Hi'})(obj)
  • 立即函式之間必須用 ; 隔開
//放在後面;(function () {})();(function () {})()//或放在前面;(function () {})();(function () {})()
  • 將變數和函式註冊到全域(window 物件)上
;(function (obj) {  const sport = '跳繩'  const exercise = function (todo) {    console.log(`今天要${todo}`)  }  obj.sport = sport  obj.exercise = exercise})(window)//在全域讀取console.log(sport) // '跳繩'exercise(sport) // '今天要跳繩'

函式作用域&閉包

  • 過程說明
    1. IIFE 在執行時創建了一個新的作用域,並且在這個作用域中定義了 count 變數
    2. 由於 JavaScript 的閉包(Closure)特性,返回的匿名函數可以訪問(並保留)這個作用域中的 count 變數,即使 IIFE 已經執行完畢。
    3. 每次調用 uniqueId() 時,都會執行這個匿名函數,而這個匿名函數會對 count 進行增加操作。
    4. 由於這個匿名函數保留了對 count 的引用(閉包), count 的值會在每次調用 uniqueId() 後被保留和累加。
const uniqueId = (function () {  let count = 0  return function () {    ++count    return `id_${count}`  }})()console.log(uniqueId()) // "id_1"console.log(uniqueId()) // "id_2"console.log(uniqueId()) // "id_3"

(5) 函式的提升特性

提升(Hoisting)

  • JavaScript 引擎在執行代碼之前,將變數、函式陳述式及 class 移動到其範圍頂部的過程
  • 執行環境(execution context)是指 JavaScript 引擎模組化直譯程式碼時的區塊環境

執行環境在建立時會經歷兩個階段

  1. 創造階段 (Creation Phase)
    • 一行一行地執行程式碼。此時會把變數的記憶體空間準備好,但還不會給值。
  2. 執行階段 (Execution Phase)
    • 賦值給變數並執行被呼叫的函式

宣告變數時的提升

  • 使用 var 和 let / const 宣告時的差別
  • 只有變數的宣告會提升,賦值不會提升
//使用let / const 宣告console.log(a);let a = 3; //a is not definedconsole.log(b);let b = 5; //b is not defined執行過程://創造階段let a ; //暫時性死區TDZ,無法呼叫const b; //暫時性死區TDZ,無法呼叫//執行階段a = 3;b = 5;//使用var宣告console.log(c);var c = 7; //b is undefined;執行過程://創造階段var c ; //被賦予值undefined//執行階段c = 7;

宣告函式時的提升

  • 函式陳述式:在創造階段優先載入,可以先執行後定義
sayHi() // '說你好'function sayHi() {  console.log('說你好')}//創造階段執行過程: function sayHi() {  console.log('說你好')}//執行階段sayHi() // '說你好'
  • 函式表達式:無法先執行後定義
//使用var宣告console.log(sayHi)sayHi()var sayHi = (function () {  console.log('說你好')})(  //undefined  //Uncaught TypeError: sayHi is not a function  還沒有被賦予值)//使用let 宣告walk()let walk = function () {  console.log('散步中')}//ReferenceError: walk is not defined//創造階段:執行過程: var sayHi //先賦予值undefinedlet walk //暫時性死區//執行階段:sayHi = function () {  console.log('說你好')}walk = function () {  console.log('散步中')}

參考文章

# JavaScript