Javascript - apply、call、bind

boxZhang發表於2019-03-03

不知大家有沒有像我一樣,一聽到call apply,就一頭霧水,感覺怎麼也弄不清楚。為了能讓自己妥妥的記住,特記錄了這篇小文,以便再忘記了的時候,能夠對學習的痕跡有所追尋。

ECMAScript 規範給所有函式都定義了callapply 兩個方法,它們的應用非常廣泛,作用也是一模一樣,只是傳參形式有區別而已,並且他們的目的都是為了改變this的指向

一、apply()

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

// apply語法
func.apply(thisArg, [argsArray])
複製程式碼
  • thisArg(可選值)

    func 函式執行時使用的 this 值,即func的this指向thisArg這個引數。

    請注意,this可能不是該方法看到的實際值:如果這個函式處於非嚴格模式下,則指定為 nullundefined 時會自動替換為指向全域性物件,原始值會被包裝。

  • argsArray(可選值)

    一個陣列或者類陣列物件,其中的陣列元素將作為單獨的引數傳給 func 函式。如果該引數的值為 nullundefined,則表示不需要傳入任何引數。從ECMAScript 5 開始可以使用類陣列(類陣列:具有.length屬性的物件)物件。

  • 返回值

    使用呼叫者提供的this值和引數呼叫該函式的返回值。若該方法沒有返回值,則返回undefined

例如:

var obj = {
    name : 'boxZhang'
}
function func(person1, person2){
    console.log(person1 + ' ' + this.name + ' ' + person2);
}
func.apply(obj, ['小明','小紅']);    // 小明 boxZhang 小紅

// 其中 obj 作為函式上下文的物件,函式func中的 this 指向了 obj 這個物件,所以func方法裡面的this.name是obj.name。
// 引數 '小明' 和 '小紅' 放在陣列中傳入 func 函式,分別對應 func 引數的列表元素person1,person2。
複製程式碼

小練習:用apply將陣列arrayA新增到陣列arrayB中

var arrayA = ['a','b','c'];
var arrayB = [1,2,3];
arrayA.push.apply(arrayB, arrayA);
console.log(arrayB);	//[1, 2, 3, "a", "b", "c"]
console.log(arrayA);	//["a", "b", "c"]
複製程式碼

apply()的便易,可以通過下面一段程式碼簡單介紹一下【摘自MDN apply()】:

/* 找出陣列中最大/小的數字 */
var numbers = [5, 6, 2, 3, 7];

/* 應用(apply) Math.min/Math.max 內建函式完成 */ 
/*當心:如果某個引擎限制了方法引數,你的引數陣列又非常大,用下面這種方法 會導致傳入的陣列有可能是不完整的。*/
var max = Math.max.apply(null, numbers); /* 基本等同於 Math.max(numbers[0], ...) 或 Math.max(5, 6, ..) */
var min = Math.min.apply(null, numbers);

/* 程式碼對比: 用簡單迴圈完成 */
max = -Infinity, min = +Infinity;
for (var i = 0; i < numbers.length; i++) {
  if (numbers[i] > max)
    max = numbers[i];
  if (numbers[i] < min) 
    min = numbers[i];
}
複製程式碼

二、call()

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

// call() 語法
fun.call(thisArg, arg1, arg2, ...)
複製程式碼
  • thisArg

    fun函式執行時指定的this

    請注意,this可能不是該方法看到的實際值:如果這個函式處於非嚴格模式下,則指定為 nullundefined 時會自動替換為指向全域性物件,原始值會被包裝。

  • arg1, arg2, ...

    指定的引數列表。

  • 返回值

    使用呼叫者提供的this值和引數呼叫該函式的返回值。若該方法沒有返回值,則返回undefined

例如:

var obj = {
    name : 'boxZhang'
}
function func(person1, person2){
    console.log(person1 + ' ' + this.name + ' ' + person2);
}
func.call(obj, '小明','小紅');    // 小明 boxZhang 小紅

// 其中 obj 作為函式上下文的物件,函式func中的 this 指向了 obj 這個物件,所以func方法裡面的this.name是obj.name。
// 引數 '小明' 和 '小紅' 作為單獨的引數中傳入 func 函式,分別對應 func 引數的列表元素person1,person2。
// 對比apply可以看出,引數 '小明' 和 '小紅' ,不是放入陣列中傳入的,而是作為單獨引數傳入的。
複製程式碼

使用call方法,呼叫父建構函式:

function Product(name, price) {
  this.name = name;
  this.price = price;
}
function Food(name, price) {
  Product.call(this, name, price);
  this.category = 'food';
}
var cheese = new Food('feta', 5);
console.log(cheese.name);	//feta
複製程式碼

三、call() 和 apply()

注意:

call()方法的作用和 apply() 方法類似

  • 第一個引數都是 fun函式執行時指定的this

區別就是,第二個引數(或call中的第二個以及第二個以後的引數):

  • call()方法接受的是引數列表(把引數按順序穿進呼叫call的方法裡) :func.call(this, arg1, arg2);
  • apply()方法接受的是一個引數陣列(把引數放在陣列裡統一傳到呼叫call的方法裡):func.apply(this, [arg1, arg2])

四、bind()

bind()方法與call和apply相似,也是可以改變this指向。

bind() 函式會建立一個新函式,叫繫結函式。當呼叫這個繫結函式時,繫結函式會以建立它時傳入 bind()方法的第一個引數作為 this,傳入 bind() 方法的第二個以及以後的引數加上繫結函式執行時本身的引數按照順序作為原函式的引數來呼叫原函式。呼叫繫結函式通常會導致執行包裝函式

// bind()語法
function.bind(thisArg[, arg1[, arg2[, ...]]])
複製程式碼
  • thisArg

    呼叫繫結函式時作為this引數傳遞給目標函式的值。 如果使用new運算子構造繫結函式,則忽略該值。當使用bindsetTimeout中建立一個函式(作為回撥提供)時,作為thisArg傳遞的任何原始值都將轉換為object。如果bind函式的引數列表為空,執行作用域的this將被視為新函式的thisArg

  • arg1, arg2, ...

    當目標函式被呼叫時,預先新增到繫結函式的引數列表中的引數。

  • 返回值

    返回一個原函式的拷貝,並擁有指定的this值和初始引數。

例如:

var bar = function(){
	console.log(this.x);
}
var foo = {x:3}
bar(); // undefined
var func = bar.bind(foo);	//bind()函式會建立一個新的函式,叫繫結函式
func(); // 3 呼叫繫結函式

// 這裡建立了一個新的函式 func,當使用 bind() 建立一個繫結函式之後,它被執行的時候,它的 this 會被設定成 foo , 而不是像我們呼叫 bar() 時的全域性作用域。
複製程式碼

bind的引數

function func(a, b, c) {
    console.log(a, b, c);
}
var func1 = func.bind(null,'boxZhang');	//建立了一個新的函式func1

func('A', 'B', 'C');            // A B C
func1('A', 'B', 'C');           // boxZhang A B
func1('B', 'C');                // boxZhang B C
func.call(null, 'boxZhang');      // boxZhang undefined undefined

//可見,bind中的實參 是在 bind 中引數的基礎上再往後排。
複製程式碼

五、apply()、call()、bind() 比較

var obj = {
    name: 'boxZhang'
}
function func() {
   return this.name;
}
console.log(func.bind(obj)());  //boxZhang 多了個括號,因為bind()要返回一個原函式的拷貝
console.log(func.call(obj));    //boxZhang
console.log(func.apply(obj));   //boxZhang

// 三個輸出的都是boxZhang,但是注意看使用 bind() 方法的,他後面多了對括號。
// 也就是說,區別是,bind 方法不會立即執行,而是回撥執行的時候,使用 bind() 方法。bind()的返回值是一個函式。
// 而 apply和call 則會立即執行函式。
複製程式碼

六、總結

  • apply 、 call 、bind 三者都是用來改變函式的this物件的指向的;
  • apply 、 call 、bind 三者第一個引數都是this要指向的物件,也就是想指定的上下文;
  • apply 、 call 、bind 三者都可以利用後續引數傳參;
  • bind 返回值是對應的函式,便於稍後呼叫
  • apply 、call 則是立即呼叫 。

原文地址追溯

【優雅程式碼】深入淺出 妙用Javascript中apply、call、bind

MDN-javascript:


如若發現文中紕漏請留言,歡迎大家糾錯,我們一起成長。

相關文章