Minghsin University of Science and Technology
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

明新科大 AQI 70 24°C PM2.5 19
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 11

函式

函式宣告、匿名函式、箭頭函式、this 與 bind
Interaction實作 25 minute(s)

Learning Objectives

  • 用函式宣告、函式運算式與箭頭函式定義函式,並說明函式宣告的提升
  • 使用參數預設值與其餘參數,說明 return 與回傳 undefined 的情況
  • 把函式當成值傳遞(callback),並使用立即執行函式
  • 依呼叫方式判斷 this,並用 call、apply、bind 指定 this
  • 說明箭頭函式沒有自己的 this 與 arguments

函式:把一段程式包起來重複使用

函式(function)把一段完成特定工作的程式包起來,取一個名字,需要時再呼叫。好處是同樣的程式不必寫很多次, 要修改時也只要改一個地方。

function square(number) {   // square 是名稱,number 是參數
  return number * number;   // 大括號裡是函式的本體
}

square(5);                  // 呼叫函式,5 是引數,得到 25
  • 宣告函式不會執行它,只是告訴 JavaScript「被呼叫時要做什麼」;呼叫 square(5) 時才真的執行
  • 定義時括號裡的名稱叫參數(parameter),呼叫時傳進去的值叫引數(argument)
  • return 結束函式,並把後面的值交回給呼叫的地方;沒有 return(或 return; 後面沒有值)時,函式回傳 undefined
  • JavaScript 不檢查引數的個數:少給的參數是 undefined,多給的引數被忽略

宣告、呼叫與回傳

第一步就看右邊:程式還沒執行到第 4 行,square、greet、show 已經在變數區了,這是函式宣告的提升(hoisting),所以第 2 行可以先呼叫 square。呼叫函式時,右邊會多出一個框架,回傳之後就消失。

三種定義函式的寫法

寫法 例子 提升
函式宣告(function declaration) function cube(n) { return n ** 3; } 整個函式被提升,可以在宣告之前呼叫
函式運算式(function expression) const cube = function (n) { return n ** 3; }; 跟著變數的規則:const/let 在宣告前不能使用
箭頭函式(arrow function) const cube = n => n ** 3; 同函式運算式

函式是值:函式運算式把一個函式(通常沒有名字,叫匿名函式)存進變數,就和把數字存進變數一樣。

箭頭函式的寫法:

const double = n => n * 2;            // 只有一個參數:小括號可以省略
const add = (a, b) => a + b;          // 本體只有一個運算式:不寫大括號,自動回傳它的值
const add2 = (a, b) => {              // 寫了大括號:要自己寫 return
  const sum = a + b;
  return sum;
};
const point = (x, y) => ({ x, y });   // 要直接回傳物件,外面加小括號,否則大括號會被當成本體
const hello = () => '你好';           // 沒有參數:寫空的小括號

投影片用 var 宣告函式運算式,在宣告之前呼叫會得到 TypeError: square is not a function(變數已提升但值是 undefined); 改用 const 時得到的是 ReferenceError(變數在宣告之前不能使用)。兩種都會出錯,結論一樣:函式運算式要先定義再呼叫。

函式運算式與箭頭函式

cube、double、add、makePoint 都是存在變數裡的函式。注意第 17 行在 later 宣告之前呼叫它,會丟出錯誤,被 catch 接住。

參數預設值與其餘參數

預設值:在參數後面寫 = 值,沒有傳入(或傳入 undefined)時就使用預設值。

function price(amount, discount = 1) { return amount * discount; }
price(100);        // 100
price(100, 0.8);   // 80

其餘參數(rest parameter):最後一個參數前面加 ...,把多出來的引數收集成一個真正的陣列,可以使用 length、join、reduce 等方法。

function sum(...nums) { return nums.reduce((t, n) => t + n, 0); }
sum(1, 2, 3, 4);   // 10

舊的程式會用每個一般函式裡都有的 arguments:它包含所有傳入的引數,有 length 和索引,但不是陣列,沒有陣列的方法 (這種東西叫「類陣列」)。箭頭函式沒有自己的 arguments。現在請一律使用其餘參數。

預設值、其餘參數與 arguments

看呼叫 sum(1, 2, 3, 4) 時右邊的框架:nums 是一個陣列 [1, 2, 3, 4]。first 的第一個引數給了 label,剩下的才進入 rest。

函式是值:callback 與立即執行函式

JavaScript 的函式是一級物件(first-class object):可以存進變數、放進物件或陣列、當成引數傳給別的函式, 也可以被函式回傳。

把函式當成引數傳進去,讓對方在適當的時機呼叫,這個函式就叫 callback。你在上一課用過的 forEach、map、filter, 以及之後的 setTimeout、addEventListener,都是收 callback 的函式。

函式回傳另一個函式的用法(閉包)在第 12 課〈作用域與閉包〉說明。

立即執行函式(IIFE,Immediately Invoked Function Expression):用小括號把函式包起來,後面再加一組小括號馬上呼叫:

(function () {
  const secret = 42;   // 只活在這個函式裡
})();

在還沒有 let、const 與模組的年代,IIFE 是避免變數跑到全域、互相干擾的主要方法,舊程式裡很常見。

callback 與 IIFE

repeat 收到的 action 是一個函式,第 4 行每呼叫一次,右邊就多一個框架。第 19 行的 IIFE 定義完立刻以 8、7 呼叫;它裡面的 secret 在外面看不到。

想一想:定義與參數

1.

下面的程式會印出什麼?

console.log(typeof hello);
function hello() {
  return 'hi';
}
2.

下面的程式會印出什麼?

const f = (a, b) => {
  a + b;
};
console.log(f(1, 2));
3.

下面的程式會印出什麼?

function tag(name, ...classes) {
  return name + ':' + classes.length;
}
console.log(tag('div'), tag('p', 'a', 'b'));
第 1 次作答

this:看函式「怎麼被呼叫」

一般函式裡的 this 不是在定義時決定,而是在呼叫時依呼叫方式決定:

呼叫方式 this 是
obj.method() 點前面的物件 obj
fn()(單獨呼叫) 非嚴格模式是全域物件(瀏覽器裡是 window);嚴格模式('use strict'、模組、class)是 undefined
fn.call(obj, a, b) 第一個引數 obj,後面的引數一個一個給
fn.apply(obj, [a, b]) 第一個引數 obj,引數放在陣列裡
fn.bind(obj) 不會呼叫,而是回傳一個新函式,之後怎麼呼叫,this 都固定是 obj
new Fn() 新建立的物件(第 27 課〈prototype〉說明)
事件處理函式 觸發事件的元素(第 19 課〈事件處理〉說明)

最常見的錯誤是把方法拿出來單獨呼叫:const f = amy.hello; f(); 這時 this 已經不是 amy 了。 需要把方法交給別人呼叫時(例如當成 callback),可以先用 bind 綁定。

同一個函式,不同的 this

hello 只寫了一次,但第 8 行和第 12 行呼叫時,右邊框架裡的 this 分別是 amy 和 ben。後半段用 call、apply、bind 指定 intro 的 this。

箭頭函式沒有自己的 this

箭頭函式沒有自己的 this(也沒有自己的 arguments)。它裡面的 this,就是外層函式的 this, 不管它被誰、用什麼方式呼叫,call、apply、bind 也改不了。

這讓箭頭函式很適合當作方法裡的 callback:

const timer = {
  seconds: 0,
  start() {
    [1, 2].forEach(() => { this.seconds++; });   // this 是 start 的 this,也就是 timer
  },
};

反過來,不要用箭頭函式寫物件的方法:{ hello: () => this.name } 裡的 this 是物件外面的 this,不是這個物件。

一般函式與箭頭函式當 callback

withFunction 裡交給 forEach 的是一般函式,它被 forEach 單獨呼叫,this 不是 timer。withArrow 交出去的是箭頭函式,this.seconds++ 改到的正是 timer.seconds,看右邊的值從 0 變成 2。

想一想:this

1.

下面的程式會印出什麼?

const cat = {
  name: 'Mimi',
  say() {
    return this.name;
  },
};
const dog = { name: 'Lucky', say: cat.say };
console.log(cat.say(), dog.say());
2.

下面的程式會印出什麼?

function full(sep) {
  return this.first + sep + this.last;
}
const p = { first: 'Ada', last: 'Lovelace' };
const f = full.bind(p);
console.log(f(' '), full.call(p, '-'));
3.

關於箭頭函式,下列哪一個說法正確?

第 1 次作答

實作:其餘參數與 callback

完成兩個函式:average 用箭頭函式與其餘參數,收任意個數字,回傳平均(沒有數字時回傳 0);applyTo 對 list 的每個元素呼叫 fn,把回傳值收集成新陣列回傳(自己寫迴圈,不要用 map)。

你的輸出
預期輸出

重點精華回顧

  • 函式宣告 function f() {} 會整個被提升;函式運算式與箭頭函式存在變數裡,要先定義再呼叫。
  • 沒有 return 的函式回傳 undefined;少給的參數是 undefined,多給的引數被忽略。
  • 參數預設值 (a, b = 1);其餘參數 (...rest) 是真正的陣列;舊式的 arguments 是類陣列,箭頭函式沒有它。
  • 箭頭函式:單一運算式不寫大括號就自動回傳;寫了大括號要自己 return;回傳物件要加小括號。
  • 函式是值:可以當成 callback 傳給別的函式;IIFE 定義完立刻執行,避免變數跑到全域。
  • this 由呼叫方式決定:obj.method() 是 obj;call/apply 指定 this 並立刻呼叫;bind 回傳綁定好 this 的新函式。
  • 箭頭函式沒有自己的 this,用的是外層函式的 this;適合當方法裡的 callback,不適合拿來寫物件的方法。
這一課完成了!
Next lesson:作用域與閉包
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。