js中call()和apply()方法的區別和用法詳解
今天又碰到了JacvaScript中的call()
和apply()
方法,然後看看學學,敲了遍程式碼,才大概對這兩個方法有些瞭解,這篇部落格是對這兩個方法的歸納整理,如果有寫的不夠詳細或者有錯誤的地方歡迎指出。
1.定義
每個函式都包含兩個非繼承而來的方法:call()
方法和apply()
方法。call
和apply
可以用來重新定義函式的執行環境,也就是this
的指向;call
和apply
都是為了改變某個函式執行時的context
,即上下文而存在的,換句話說,就是為了改變函式體內部this
的指向。
語法
call()
呼叫一個物件的方法,用另一個物件替換當前物件,可以繼承另外一個物件的屬性,它的語法是:
Function.call(obj[, param1[, param2[, [,...paramN]]]]);
obj
:這個物件將代替Function
類裡this
物件params
:一串引數列表
說明:call
方法可以用來代替另一個物件呼叫一個方法,call
方法可以將一個函式的物件上下文從初始的上下文改變為obj
指定的新物件,如果沒有提供obj
引數,那麼Global物件被用於obj
。
apply()
和call()
方法一樣,只是引數列表不同,語法:
Function.apply(obj[, argArray]);
obj
:這個物件將代替Function
類裡this
物件argArray
Function
說明:如果argArray
不是一個有效陣列或不是arguments
物件,那麼將導致一個TypeError
,如果沒有提供argArray
和obj
任何一個引數,那麼Global物件將用作obj
。
2.相同點
call()
和apply()
方法的相同點就是這兩個方法的作用是一樣的。都是在特定的作用域中呼叫函式,等於設定函式體內this
物件的值,以擴充函式賴以執行的作用域。
一般來說,this
總是指向呼叫某個方法的物件,但是使用call()
和apply()
方法時,就會改變this
的指向,看個例子:
function add(a, b) {
return a + b;
}
function sub(a, b) {
return a - b;
}
console.log(add.call(sub, 2, 1));//3
為什麼add.call(sub, 2, 1)
的執行結果是3
呢,因為call()
方法改變了this
的指向,使得sub
可以呼叫add
的方法,也就是用sub
去執行add
中的內容,再來看一個例子:
function People(name, age) {
this.name = name;
this.age = age;
}
function Student(name, age, grade) {
People.call(this, name, age);
this.grade = grade;
}
var student = new Student('小明', 21, '大三');
console.log(student.name + student.age + student.grade);//小明21大三
在這個例子中,我們並沒有給Student
的name
和age
賦值,但是存在這兩個屬性的值,這還是要歸功於call()
方法,它可以改變this
的指向。
在這個例子裡,People.call(this, name, age);
中的this
代表的是Student
,這也就是之前說的,使得Student
可以呼叫People
中的方法,因為People
中有this.name = name;
等語句,這樣就將name
和age
屬性建立到了Student
中。
總結一句話就是call()
可以讓括號裡的物件來繼承括號外函式的屬性。
至於apply()
方法作用也和call()
方法一樣,可以這麼寫:
People.apply(this, [name, age]);
或者這麼寫:
People.apply(this, arguments);
在這裡arguments
和[name, age]
是等價的。
3.不同點
從定義中也可以看出來,call()
和apply()
的不同點就是接收引數的方式不同。
- apply()方法接收兩個引數,一個是函式執行的作用域(
this
),另一個是引數陣列。 - call()方法不一定接受兩個引數,第一個引數也是函式執行的作用域(
this
),但是傳遞給函式的引數必須列舉出來。
在給物件引數的情況下,如果引數的形式是陣列的時候,比如之前apply()
方法示例裡面傳遞了引數arguments
,這個引數是陣列型別,並且在呼叫Person
的時候引數的列表是對應一致的(也就是Person
和Student
的引數列表前兩位是一致的)就可以採用apply()
方法。
但是如果Person
的引數列表是這樣的(age,name)
,而Student的引數列表是(name,age,grade)
,這樣就可以用call()
方法來實現了,也就是直接指定引數列表對應值的位置Person.call(this,age,name)
。
4.拓展
apply()的其他用法
apply
有一個巧妙的用處,就是可以將一個數組預設的轉換為一個引數列表([param1,param2,param3]
轉換為param1,param2,param3
),藉助apply
的這點特性,所以就有了以下高效率的方法:
1)Math.max可以實現得到陣列中最大的一項
因為Math.max
引數裡面不支援Math.max([param1,param2])
,也就是陣列,但是它支援Math.max(param1,param2,param3…)
,所以可以根據apply
的那個特點來解決:
var array = [1, 2, 3];
var max = Math.max.apply(null, array);
console.log(max);//3
這樣輕易的可以得到一個數組中最大的一項,apply
會將一個數組裝換為一個引數接一個引數的傳遞給方法,這塊在呼叫的時候第一個引數給了一個null
,這個是因為沒有物件去呼叫這個方法,我們只需要用這個方法幫我運算,得到返回的結果就行,所以直接傳遞了一個null
過去,當然,第一個引數使用this
也是可以的:
var array = [1, 2, 3];
var max = Math.max.apply(this, array);
console.log(max);//3
使用this
就相當於用全域性物件去呼叫Math.max
,所以也是一樣的。
2)Math.min可以實現得到陣列中最小的一項
同樣的Math.min
和Math.max
是一個思想:
var array = [1, 2, 3];
var min = Math.min.apply(null, array);
console.log(min);//1
當然,apply
的第一個引數可以用null
也可以用this
,這個是和Math.max
一樣的。
3)Array.prototype.push可以實現兩個數組合並
同樣的,push
方法沒有提供push
一個數組,但是它提供了push(param1,param,…paramN)
所以同樣也可以通過apply
來裝換一下這個陣列,即:
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1);//[ 1, 2, 3, 4, 5, 6 ]
可以這樣理解,arr1
呼叫了Array
的push
方法,引數是通過apply
將陣列裝換為引數列表的集合,其實,arr1
也可以呼叫自己的push
方法:
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
arr1.push.apply(arr1, arr2);
console.log(arr1);//[ 1, 2, 3, 4, 5, 6 ]
也就是隻要有push
方法,arr1
就可以利用apply
方法來呼叫該方法,以及使用apply
方法將陣列轉換為一系列引數,所以也可以這樣寫:
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
[].push.apply(arr1, arr2);
console.log(arr1);//[ 1, 2, 3, 4, 5, 6 ]
總結
一般在目標函式只需要n個引數列表,但是不接收一個數組的形式([param1[,param2[,…[,paramN]]]]
),我們就可以通過apply
的方式來巧妙地解決。