iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
Modern Web

JavaScript學習日記系列 第 20

JavaScript學習日記 : Day20 - call、apply、bind

  • 分享至 

  • xImage
  •  

1. 語法

func.call(thisArg, param1, param2, ...)//func是个函数

func.apply(thisArg, [param1,param2,...])

func.bind(thisArg, param1, param2, ...)

返回值 :

call / apply : 返回func值行結果。
bind : 返回func的拷貝,並擁有指定的this值合初始參數。

參數 :
thisArg (可選) :

  1. func的this指向thisArg對象
  2. 非嚴格模式下,若thisArg指定為null,undefined,則func的this指向window對象。
  3. 嚴格模式下,func的this為undefined
  4. 值為原始值(數字、字符串、布爾值)的this會指向該原始值的自動包裝對象,如String、Number、Boolean。

param1、param2(可選):傳給func的參數。

2.必須是函數才能調用call/apply/bind

call、apply、bind是掛在Function對象上的三個方法,只有函數有這些方法,例如: Object.prototype.toString就是函數,我們經常看到這樣的用法Object.prototype.tostring.call(data)

3. call/apply vs bind

執行 :

  1. call/apply改變this後馬上執行函數
  2. bind則是返回改變this指向後的函數,不執行該函數。

返回值 :

  1. call/apply返回func執行結果
  2. bind返回func的拷貝,並指定了func的this指向,保存了func的參數。

4. 類數組

類數組的特徵有,可以透過索引(index)調用,如array[0];具有長度屬性,可以透過for循環或forEach方法。

那類數組是什麼? 顧名思義就是具備與數組特徵類似的對象。例如下面例子 :

let arrayLike = {
    0: 1,
    1: 2,
    2: 3,
    length: 3
};

類數組比較常見在譬如,獲取DOM節點的方法,返回的就是一個類數組,在函數中使用augument所獲取的所有參數,也是類數組。

但注意,類數組無法使用splice,push等數組原型練上的方法,那就要用到call/apply/bind的核心理念---借用方法:

以類數組做比喻,如果它想借用Array原型鍊上slice的方法,可以這樣:

let domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

借用方法的場景

  1. 類數組對象借用數組的方法
// 類數組對象
let arrayLike = {
  0: "name",
  1: "height",
  length: 2
};

Array.prototype.push.call(arrayLike, "weight", "id");

console.log(arrayLike);
// {"0":"name","1":"height","2":"weight","3":"id","length":4}
function hash() {
  alert( [].join.call(arguments) ); // 1,2
}

hash(1, 2);
  1. apply獲取數組最大最小值

apply直接使用數組作為參數,也省去了展開數組這一步。

const arr = [15, 6, 12, 13, 16];

const max = Math.max.apply(Math, arr); // 16

const min = Math.min.apply(Math, arr); // 6

5. call、apply使用時機

  1. 參數數量/順序確定call,參數數量/順序不確定的話就用apply。
  2. 參數數量少用call,多則用apply。
  3. 參數集合已經是數組的情況,用apply,例如上面的獲取最大/最小值。

參數數量不確定的例子 :

const obj = {
    age: 24,
    name: 'John',
}

const obj2 = {
    age: 25
}

// 依據某些條件來決定要傳入的參數數量與順序
function callObj(thisAge, fn) {
    let params = [];

    if (thisAge.name) {
        params.push(thisAge.name);
    }

    if (thisAge.age) {
        params.push(thisAge.age);
    }

    fn.apply(thisAge, params); // 參數數量不確定,用apply
}

function handle(...params) {
    console.log('params', params);
}

callObj(obj, handle); // params  ["John", 24]
callObj(obj2, handle); // params [25]

6. bind使用的時機

  1. 保存函數參數

經典面試題 :

for(var i = 1; i < 5; i++) {
    setTimeout(function test() {
        console.log(i) // 5 5 5 5 5
    },i*1000)
}

原因在於等到非同步setTimeout執行時,i已經變成5了。用bind可以解決此問題 :

for(var i = 1; i < 5; i++) {
    setTimeout(function test(i) {
        console.log(i) // 5 5 5 5 5
    }.bind(null, i),i*1000)
}

實際上這裡也運用了閉包。它保存了函數this的指向(setTimeout與setInterval預設都是指向window,這邊只是為了只用bind方法,null也是指向window)、初始參數,每次i邊更都會被bind的閉包存起來,所以輸出1~5。

  1. 回調函數丟失問題
class Cat {
    constructor(name,callback) {
        this.name = name;
        this.callback = callback;
        this.callback();
    }
}

class Dog {
    constructor(name) {
        this.name = name;
        this.friend = new Cat('white cat',this.run)
    }
    
    run() {
        console.log(`${this.name} run with cat!`);
    }
}

new Dog("white dog");

我們預期希望是顯示出white dog run with cat!,但實際上顯示出white cat run with cat!,原因在於當new Cat時,所傳入的this.run是記憶體位子(call by reference),並沒有邦定this的指向,這時候可以用bind來解決:

class Cat {
    constructor(name,callback) {
        this.name = name;
        this.callback = callback;
        this.callback();
    }
}

class Dog {
    constructor(name) {
        this.name = name;
        this.friend = new Cat('white cat',this.run.bind(this))
    }
    
    run() {
        console.log(`${this.name} run with cat!`);
    }
}

new Dog("white dog"); // white dog run with cat!

或是把run改為arrow function也可以:

class Cat {
    constructor(name,callback) {
        this.name = name;
        this.callback = callback;
        this.callback();
    }
}

class Dog {
    constructor(name) {
        this.name = name;
        this.friend = new Cat('white cat',this.run.bind(this))
    }
    
    run = () => {
        console.log(`${this.name} run with cat!`);
    }
}

new Dog("white dog"); // white dog run with cat!

上一篇
JavaScript學習日記 : Day19 - Class繼承
下一篇
JavaScript學習日記 : Day21 - 數組方法(一)
系列文
JavaScript學習日記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言