call、apply、bind應用的介紹

時光_靜好發表於2019-05-24

call、apply、bind應用的介紹


call,apply,bind三者都是改變當前this的指向,但具體的使用方式,都存在著一定的區別,下面將從使用方式原始碼兩個角度來簡單的闡述一下之間的區別。

使用簡介

call

call() 方法呼叫一個函式, 其具有一個指定的 this 值和分別地提供的引數(引數的列表)。

注意:該方法的作用和 `apply()` 方法類似,只有一個區別,就是 `call()` 方法接受的是若干個引數的列表,而 `apply()` 方法接受的是一個包含多個引數的陣列。

語法:

fun.call(thisArg[, arg1[, arg2[, ...]]])
複製程式碼

引數:

  • thisArg

    • 在 fun 函式執行時指定的 this 值
    • 如果指定了 null 或者 undefined 則內部 this 指向 window
  • arg1, arg2, ...

    • 指定的引數列表

apply

apply() 方法呼叫一個函式, 其具有一個指定的 this 值,以及作為一個陣列(或類似陣列的物件)提供的引數。

注意:該方法的作用和 `call()` 方法類似,只有一個區別,就是 `call()` 方法接受的是若干個引數的列表,而 `apply()` 方法接受的是一個包含多個引數的陣列。

語法:

fun.apply(thisArg, [argsArray])
複製程式碼

引數:

  • thisArg
  • argsArray

apply()call() 非常相似,不同之處在於提供引數的方式。 apply() 使用引數陣列而不是一組引數列表。例如:

fun.apply(this, ['eat', 'bananas'])
複製程式碼

**call 與apply的區別:** apply、call 的區別

對於 apply、call 二者而言,作用完全一樣,只是接受引數的方式不太一樣。例如,有一個函式定義如下:

var func = function(arg1, arg2) {
     
};
複製程式碼

就可以通過如下方式來呼叫:

func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])
複製程式碼

其中 this 是你想指定的上下文,他可以是任何一個 JavaScript 物件(JavaScript 中一切皆物件),call 需要把引數按順序傳遞進去,而 apply 則是把引數放在陣列裡。  

bind

bind() 函式會建立一個新函式(稱為繫結函式),新函式與被調函式(繫結函式的目標函式)具有相同的函式體(在 ECMAScript 5 規範中內建的call屬性)。 當目標函式被呼叫時 this 值繫結到 bind() 的第一個引數,該引數不能被重寫。繫結函式被呼叫時,bind() 也接受預設的引數提供給原函式。 一個繫結函式也能使用new操作符建立物件:這種行為就像把原函式當成構造器。提供的 this 值被忽略,同時呼叫時的引數被提供給模擬函式。

語法:

fun.bind(thisArg[, arg1[, arg2[, ...]]])
複製程式碼

引數:

  • thisArg

    • 當繫結函式被呼叫時,該引數會作為原函式執行時的 this 指向。當使用new 操作符呼叫繫結函式時,該引數無效。
  • arg1, arg2, ...

    • 當繫結函式被呼叫時,這些引數將置於實參之前傳遞給被繫結的方法。

返回值:

返回由指定的this值和初始化引數改造的原函式拷貝。

示例:

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();  // 一秒鐘後, 呼叫'declare'方法
複製程式碼

小結

  • call 和 apply 特性一樣

    • 都是用來呼叫函式,而且是立即呼叫
    • 但是可以在呼叫函式的同時,通過第一個引數指定函式內部 this 的指向
    • call 呼叫的時候,引數必須以引數列表的形式進行傳遞,也就是以逗號分隔的方式依次傳遞即可
    • apply 呼叫的時候,引數必須是一個陣列,然後在執行的時候,會將陣列內部的元素一個一個拿出來,與形參一一對應進行傳遞
    • 如果第一個引數指定了 null 或者 undefined 則內部 this 指向 window
  • bind

    • 可以用來指定內部 this 的指向,然後生成一個改變了 this 指向的新的函式
    • 它和 call、apply 最大的區別是:bind 不會呼叫
    • bind 支援傳遞引數,它的傳參方式比較特殊,一共有兩個位置可以傳遞
        1. 在 bind 的同時,以引數列表的形式進行傳遞
        1. 在呼叫的時候,以引數列表的形式進行傳遞
      • 那到底以誰 bind 的時候傳遞的引數為準呢還是以呼叫的時候傳遞的引數為準
      • 兩者合併:bind 的時候傳遞的引數和呼叫的時候傳遞的引數會合併到一起,傳遞到函式內部

原始碼介紹

call原始碼

call的使用

  1. call可以改變前面函式中的this指向,指向call方法中的第一個引數
  2. 給方法傳遞引數,可以依次進行傳值
  3. call的第1個引數是基本資料型別,this指向建立這個基本資料型別的構造器
  4. call的第1個引數可以是函式

簡單call原理

沒有引數傳遞的原理, x.cal(y) :在y 上掛載一個 x , 在y內部呼叫 x ,最終x內的x的指向y,

//定義一個呼叫者
function f(a, b, c) {
    return a + b + c
}

// 自定義call方法
Function.prototype.call = function (context) {
    // 將當前的呼叫者掛到第一個引數上
    context.fn = this;
    console.log(arguments);

    // 將傳遞的引數存放到一個陣列裡面
    let args = []
    for (let i = 1; i < arguments.length; i++) {
        args.push('arguments[' + i + ']')
    }

    // eval:可以執行裡面的js程式碼
    let r = eval('context.fn(' + args + ')')

    // 刪掛載上的那個函式
    delete context.fn;
    return r;
}

// 測試
let obj = { name: 'wangcai' };
let result = f.call(obj, 1, 2, 3)
console.log(result);
複製程式碼

call沒有引數的問題,防止出錯

當call沒有引數的時候,程式可能會報錯

function f(){
    .... 
}

Function.prototype.call = function(context){
    context = context ? Object(context) : window;
    ....
}

let result = f.call()
....

複製程式碼

注意:

特別提醒:執行環境是在瀏覽器 在node中不支援

apply原始碼

apply

apply與call的原理一直,就是傳遞引數的方式不一樣,此處不過多的進行闡述

function f(a,b,c,d) {
    console.log("f.....");
    console.log(this);
    return a+b+c+d
}

Function.prototype.apply = function (context) {
    //將呼叫者掛載到引數上
    context.fn = this;

    // 獲取到傳遞的引數
    let args =arguments[1]

    // eval:內的js程式碼會直接執行
    let r = eval("context.fn(" + args + ')')
    delete context.fn;
    return r;
}

// 測試
let obj = { name: 'wangcai' }
let res = f.apply(obj,[1,2,3,4])
console.log(res);
複製程式碼

bind原始碼

bind原理

  1. bind可以改變前面函式中的this指向,指向call方法中的第一個引數
  2. 這點與call一樣,但是bind返回一個繫結後的函式
  3. bind需要自己進行呼叫,不會自己呼叫
  4. bind中繫結時傳遞的引數的優先順序比呼叫時傳遞的引數的優先順序高
  5. 優先使用繫結的引數,後續使用呼叫傳遞的引數

簡單的bind原理

bind主要是藉助於call或者bind進行資料的繫結,且進行對應的資料引數的傳遞


// 定義一個普通的函式
function f() {
    console.log("f..");
    console.log(this);
}

// 重寫bind的這個方法
Function.prototype.bind = function (context) {
    let that = this;
    // 獲取繫結時的引數
    let newArr = Array.prototype.slice.call(arguments, 1);

    return function () {

        // 獲取呼叫時的引數 將arguments偽陣列轉成真實的陣列
        let newArr2 = Array.prototype.slice.call(arguments)
        return that.apply(context, newArr.concat(newArr2))
    }

}

// 測試
let obj = { name: 'wangcai' }
let newF = f.bind(obj, 55, 66)

newF(1, 2, 3);

複製程式碼

注意:

當繫結的時候進行傳值與新函式呼叫時傳值同時有的時候,繫結的值優先與新函式的值, 整個的引數個數是:繫結引數+新函式引數

相關文章