函式

  • JavaScript 是透過建構式來模擬物件類別,可建立實體。

  • 若函式內已建立物件,並回傳物件,則可直接使用,不需再用 new 建立物件。

  • 建構式最外層一定是函數,但此函數內可有屬性及方法(function)。

  • parameters: 指的是在函式的那些傳入參數名稱的定義

  • arguments: 指的是當函式被呼叫時,傳入到函式中真正的那些值。我們在文章中會以"實際傳入參數值"來說明。

函數種類

//1.函式定義 - 使用有名稱的函式
function sum(a, b){
    return a+b
}

//2.函式表達式 - 常數指定為匿名函式
//此處的常數就是函數,可被當成參數使用或做為一個變數的值
const sum = function(a, b) {
    return a+b
}

//3.建構式(內含 this 關鍵字)
function PhoneTemplate(brand, modal, withCamera) {
  this.brand = brand;
  this.modal = modal;
  this.withCamera = withCamera || false;
  this.takePhoto = function () {
    if (this.withCamera) {
      console.log(this.modal + ' 照相');
    } else {
      console.log(this.modal + ' 這台沒有照相功能');
    }
  }
}

3.箭頭函式

傳入參數預設值

//用邏輯或(||)
const link = function (point, url) {
    let point = point || 10
    let url = url || 'http://google.com'
    ...
}

在ES6中加入了函式傳入參數的預設值指定語法,現在可以直接在傳入參數時就定義這些參數的預設值,這個作法是建議的用法:

const link = function (point = 10, url = 'http://google.com') {
    ...
}
  • 在JavaScript中function可當成參數傳入,而且也可以return function。

const addOne = function(value){
    return value + 1
}

const addOneAndTwo = function(value, fn){
    return fn(value) + 2
}

console.log(addOneAndTwo(10, addOne)) //13

不固定傳入參數(Variadic)與其餘(rest)參數

function sum(...value) {
    let total = 0
    for (let i = 0 ; i< value.length; i++){
        total += value[i]
    }
    return total
}

作用範圍(scope)

php不同,在外的變數無法帶入function內,除非加上use()。

或本身就是全域變數。例:$_POST, $_GET...

$that = $this;

$wrapper = function() use($that) {
    return $that->my_function_name('arg1', 'arg2');
};
// ES6中在函數外宣告的常數或變數,是會影響到函數內的變數。
// 也代表函數的參數不一定要直接賦予,也可由外部宣告的值代替。

var scope='var'
let scope2='let'
const scope3='const'

function show(){
  console.log(scope)
  console.log(scope2)
  console.log(scope3)
}

show() //var let const


//如果是使用var來定義變數,x並不是在函式中定義的,所以會變為"全域變數"
//因此當if內的花括號已執行,x會傳出
if (true) {
  var x = 5
}

console.log(x) //5

//函數花括號內的y不會傳出(只進不出)
function test(){
  var y=5
}

console.log(y) //y is not defined

對比使用let來宣告變數,let y位於區塊{}中,無法在外部環境獲取

if (true) {
  let y = 5
}

console.log(y) //y is not defined

靜態(Static)成員

  • 所謂靜態是指建構式直接取值或賦值,不必實體化。

let Util = function (){}
Util.version = '1.0.0'
Util.getFunction = function(){}

不只是函數可用靜態方式來擴充成員。函式在實體化後,也可擴充成員。

提升(Hoisting)

  • ES6函數中的變數/常數、函式、類別都有提升(Hoisting)的特性。 非順序執行到時才影響函數內的值,與一般程式不同。

    const x = 1
    
    function test() {
      console.log(x)
      //會提升到最上方,使x=2
      x = 2
    }
    
    test() //x=2

CPS風格(確保依序執行,如CALLBACK)

  • 延續傳遞風格(Continuation-passing style, CPS)

  • JavaScript中會大量使用CPS風格,主因是只有單執行緒。

  • 優點:CPS用的是明確地移轉控制權到下一個函式中,也就是使用"延續函式"的方式

  • 缺點:CPS風格在愈複雜的應用情況時,程式碼愈不易撰寫與組織,維護性與閱讀性也很低

//CPS風格
function func(x, cb) {
    cb(x)
}

回調(callback)

使用匿名函式的語法,直接寫在函式的傳入參數中。 回調(callback)提供了一種彈性的機制,讓程式開發者可以自行定義在此函式的最後完成時,要如何進行下一步。

function showMessage(greeting, name, callback) {
    console.log('you call showMessage')
    callback(greeting, name)
}

showMessage('Hello!', 'Eddy', function(param1, param2) {
    console.log(param1 + ' ' + param2)
})

異步回調函式(不阻塞主線程式執行)

  • 同步是由上往下依順序執行,一個執行程序完成後才會再接著下一個,一般的程式語言都是按照這樣的流程來執行。但遇到執行的動作需要等候時,就產生阻塞的問題。

  • 異步是為了解決阻塞,而把需等候的動作放入queue中,等候執行。最常使用的例子是用setTimeout這個內建的方法。

  • Callbacks(回調),常常用來呼叫異步回調函式

  • 異步程序(函式)間沒辦法保証執行的時間順序,但原則是先進先出

  • 事件大多是異步程序。例:在JavaScript中有一個不斷偵測事件發生的事件迴圈(Event Loop),所有在網頁上的DOM元素註冊的事件,都會進入一個佇列(queue)中,等待被觸發事件

function aFunc(value, callback){
  callback(value)
}

function bFunc(value, callback){
  setTimeout(callback, 0, value)
}

function cb1(value){ console.log(value) }
function cb2(value){ console.log(value) }
function cb3(value){ console.log(value) }
function cb4(value){ console.log(value) }

aFunc(1, cb1)
bFunc(2, cb2)
aFunc(3, cb3)
bFunc(4, cb4)

//最後的執行結果是1 -> 3 -> 2 -> 4
  • 所有的同步回調函式都執行完成了,才會開始依順序執行異步的回調函式。

  • bFunc中使用了計時器setTimeout會把傳入的回調函式進行異步執行,也就是先移到工作佇列中,等執行主執行緒的呼叫堆疊空了,在某個時間回到主執行緒再執行。所以即使它的時間設定為0秒,裡面的回調函式並不是立即執行,而是會暫緩(延時)執行的一種回調函式,一般稱為異步回調函式。

  • 異步回調函式還有另一個名稱是延時回調(defer callback)

閉包 closure

  • 閉包的最大特點(賣點)就是它會記憶函式建立時的環境,取得function建立當下的預設值。

  • 在閉包中所記憶的變數與值,通常稱為自由(free)變數或獨立(independent)變數,這些變數是在函式中使用,但被封入作用域之中。

  • this非作用鏈域的一員,不會記憶在閉包中

function aFunc(x){
  return function(){
    console.log( x++ )
  }
}

const newFunc = aFunc(1)
newFunc() //1
//x值會在aFunc呼叫後會持續保留在新的newFunc函式裡
newFunc() //2

this的分界

// 函數未包在物件內時,this 指向的是 window
var bar = function() {
  console.log( this.a ); // undefined
};

var foo = function() {
  var a = 123;
  this.bar();
};

foo(); // undefined

解決方式是要利用作用域鏈(Scope Chain)的設計,也就是說,雖然inner函式與外面的outter分屬不同函式,但inner函式具有存取得到outter函式的作用域的能力,所以可以用這樣的解決方法:

const obj = {a:1}

function outter() {

  // this 轉為 that 後可存在這一層,代表 obj
  const that = this

  function inner(){
    console.log(that) //用作用域鏈讀取outter中的that值
  }

  inner()
}

outter.call(obj) // Object {a: 1}

this (call , apply , bind)

JavaScript語言中因為在設計上並不是以類別為基礎的物件導向,所以this的指向的是目前呼叫函式或方法的擁有者(owner)物件,也就是說它與函式如何被呼叫或調用有關,雖然是同一函式的呼叫,因為不同的物件呼叫,也有可能是不同的this值。

call(obj) 或 bind(obj) 內的 obj 是 this 指向的物件,不論在 func 或 obj 內的 this 皆指向相同的 obj。也就是 this 的指向改為後方帶入的obj 參數。被戲稱為認賊作父。

  • call 和 apply

    兩者皆可動態改變this的指向,區別在於call()第二個參數後放值,apply第二個參數後放陣列。(第一個參數都放物件,this會改為指向此物件)

//語法
func.call(obj, arg1, arg2);
func.apply(obj, [arg1, arg2])

//call範例
const apple={
  color: "red",
  say() {
    console.log("My color is " + this.color);
  }
}
apple.say(); 
//My color is red

banana = {
  color: "yellow"
}

apple.say.call(banana); 
//My color is yellow


//apply範例
function sum(a, b, c) {
  return a + b + c;
}

var args = [1, 2, 3];
sum.apply(undefined, args) ; // 6
  • bind

  • 也可動態改變this的指向,並且回傳新函數。

  • 傳入的第一個參數為this,之後傳入的參數,會依原參數的順序傳入。

function funcA(param1, param2){
    console.log(this, param1, param2)
}

const objB = { a: 1, b: 2 }

funcA() //undefined undefined undefined

const funcB = funcA.bind(objB, objB.a)

funcB() //Object {a: 1, b: 2} 1 undefined
funcB(objB.b) //Object {a: 1, b: 2} 1 2

匿名函式與IIFE

匿名函式實現只執行一次的函式,也就是IIFE結構。 IIFE在執行環境一讀取到時,就會立即執行,而不像一般的函式需要呼叫才會執行

//兩種寫法
(function () { … })()
(function () { … }())


//會鎖住函式裡面的變數值的閉包,這個樣式通常會用來模擬靜態變數。
//例1
const counter = (function() {
  let i = 1
  //回傳此函數執行的結果
  return function() {
    console.log(i++)
  }
}())

counter() //1
counter() //2

//例2
var feature = (function() {

    // Private variables and functions
    var privateThing = "secret";
    var publicThing = "not secret";

    var changePrivateThing = function() {
        privateThing = "super secret";
    };

    var sayPrivateThing = function() {
        console.log( privateThing );
        changePrivateThing();
    };

    // Public API
    return {
        publicThing: publicThing,
        sayPrivateThing: sayPrivateThing
    };
})();

feature.publicThing; // "not secret"

箭頭函數的限制

  • this 指向的是 callback 函數,而非物件,所以箭頭函式可當成無法用 this。

const calculate = {
  array: [1, 2, 3],
  sum: () => {
    return this.array.reduce((result, item) => result + item)
  }
}

//TypeError: Cannot read property 'array' of undefined
calculate.sum()
  • 箭頭函式無法用於建構式(constructor),使用new會產生錯誤。

const Message = (text) => {
  this.text = text;
}
// Throws "TypeError: Message is not a constructor"
const helloMessage = new Message('Hello World!');
  • 只有單一個傳入參數時,括號可以不用。

//無花括號,自動return
const sum = (a, b) => a+b

//一個傳入參數可無括號
const func = x => x + 1

Last updated