1. 介紹
剛出來找前端工作的時候,最常見的面試題就是“談談你對call和apply的理解”,以前只知道這些名詞,但是一點也不理解。隨著對jquery的熟悉發現jquery源碼中很多都用到了apply方法,就順便總結了一些功能類似的call和bind方法的使用。
JavaScript中的每一個Function對象的原型上都有一個apply()方法和一個call()方法,call 和 apply 都是為了改變某個函數運行時的 context 即上下文而存在的,換句話說,就是為了改變函數體內部 this 的指向。call和apply是為了動態改變this而出現的,當一個對象沒有某個方法,但是其它對象有,我們可以借助call或apply用其它對象的方法來操作。
bind方法也可以用來改變當前函數執行的上下文,和call、apply不同的是bind返回對應函數,便于稍后調用而apply 、call 則是立即調用 。
1.1 相關定義:
-
call()
語法:fun.call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定義:調用一個對象的一個方法,以另一個對象替換當前對象。
說明: call 方法可以用來代替另一個對象調用一個方法。call 方法可將一個函數的對象上下文從初始的上下文改變為由 thisObj 指定的新對象。 如果沒有提供 thisObj 參數,那么 Global 對象被用作 thisObj。 -
apply()
語法:fun.apply([thisObj[,argArray]])
定義:應用某一對象的一個方法,用另一個對象替換當前對象。
說明: 如果 argArray 不是一個有效的數組或者不是 arguments 對象,那么將導致一個 TypeError。 如果沒有提供 argArray 和 thisObj 任何一個參數,那么 Global 對象將被用作 thisObj, 并且無法被傳遞任何參數。 -
bind()
語法:fun.bind(thisArg[, arg1[, arg2[, ...]]])
定義:創建一個新的函數, 當被調用時,將其this關鍵字設置為提供的值,在調用新函數時,可以在調用之前提供一個給定的參數序列。
說明: 參數arg1, arg2, ...當綁定函數被調用時,這些參數將置于實參之前傳遞給被綁定的方法。這些參數會被插入到目標函數的參數列表的開始位置,傳遞給綁定函數的參數會跟在它們的后面。bind返回由指定的this值和初始化參數改造的原函數拷貝。
1.2 差異:
- call和apply:call()方法接受的是若干個參數的列表,而apply()方法接受的是一個包含多個參數的數組。
- bind和call、apply:bind返回對應函數,便于稍后調用而apply 、call 則是立即調用 。
1.3 應用場景:
- 使用原生對象的方法
- 實現繼承
- bind方法的簡單使用
2. 使用
2.1 使用原生對象的方法
(1)類數組使用Array原生方法
let arrayLike = {0: "hello", 1: "sunny", 2: "~", length: 3};
let arrayLikeToArray1_1 = Array.prototype.slice.call(arrayLike); // ["hello", "sunny", "~"]
let arrayLikeToArray1_2 = Array.prototype.slice.call(arrayLike, 0, 2); // ["hello", "sunny"]
let arrayLikeToArray2_1 = Array.prototype.slice.apply(arrayLike); // ["hello", "sunny", "~"]
let arrayLikeToArray2_2 = Array.prototype.slice.apply(arrayLike, [0, 2]); // ["hello", "sunny"]
(2)擴展Array對象屬性,使類數組也可以使用這些靜態方法
測試的時候只擴展了部分屬性,如果有其它需求自己擴展呦~~~方法類似。
let array1 = ["hello", "sunny", "~"];
Array.join = function (a, sep) {
// return Array.prototype.join.call(a, sep);
return Array.prototype.join.apply(a, [sep]);
};
Array.slice = function (a, start, end) {
// return Array.prototype.slice.call(a, start, end);
return Array.prototype.slice.apply(a, [start, end]);
};
Array.map = function (a, callback, thisArg) {
// return Array.prototype.map.call(a, callback, thisArg);
return Array.prototype.map.apply(a, [callback, thisArg]);
};
console.log(Array.join(array1, "-")); // hello-sunny-~
console.log(Array.slice(array1, 0, 1)); // ["hello"]
Array.map(array1, function (value, index) {
console.log("index=" + index + ",value=" + value);
}); // index=0,value=hello index=1,value=sunny index=2,value=~
說明:用call和apply都是可以擴展的哈,注釋掉的方法也是可行的。
2.2 實現繼承
子類使用父類的方法,嚴格來講不算是繼承,只是調用其它對象的方法(該方法內的this指向使用者)。
function Animal(name) {
this.name = name || "Animal";
this.showName = function() {
console.log(this.name);
}
}
function Cat(name) {
this.name = name || "Cat";
}
let animal = new Animal();
let cat1 = new Cat();
let cat2 = new Cat("cat2");
// 子類使用父類的方法,也可以延伸至某些對象調用其它對象的方法
animal.showName.call(cat1); // Cat
animal.showName.apply(cat1); // Cat
animal.showName.call(cat2); // cat2
animal.showName.apply(cat2); // cat2
2.3 bind方法的簡單使用
說明:在實際開發中很少看到bind的使用,所以只做了兩個簡易的demo。對于bind的更高級用法,需要大家自己去摸索了,我的了解就僅限于這里了。
(1)創建綁定函數
window.name = "window";
let user = {
name: "user",
getName: function() {
console.log(this.name);
}
};
user.getName(); // user
let getUserName1 = user.getName; // this指向全局作用域
getUserName1(); // window
let getUserName2 = user.getName.bind(user);
getUserName2(); // user
(2)偏函數
function add() {
let sum = 0;
for (let i = 0, len = arguments.length; i < len; i++) {
sum += arguments[i];
}
console.log(this + ":sum=" + sum);
}
let add1 = add.bind("add1", 2, 3);
add1(); // add1:sum=5
add1(5); // add1:sum=10
let add2 = add.bind("add2", "hello");
add2(); // add2:sum=0hello
add2(" sunny"); // add2:sum=0hello sunny
3. 擴展
3.1 類數組
- 類數組是一個對象,雖然該對象并不是由Array構造函數所創建的,但它依然呈現出數組的行為。類數組對象擁有一個特性:可以在類數組對象上應用(利用call、apply方法)數組的操作方法。
- 在瀏覽器環境中,document.getElementsByTagName()語句返回的就是一個類數組對象。在function調用中,function代碼內的arguments變量(保存傳入的參數)也是一個類數組對象。在ECMAScript 5標準中,字符串string就是一個只讀的類數組對象。
幾種常見的類數組:
let obj = {0: "hello", 1: "sunny", 2: "~", length: 3};
let anchor = document.getElementsByTagName("a");
let msg = "hello";
function test() {
console.log(arguments); // arguments
}
3.2 柯里化
- 之前看函數式編程的時候了解過一點,大概意思就是把一個有n個參數的函數變成n個只有1個參數的函數。
柯里化的簡單實現:
// 普通函數
function sum1(x, y, z) {
console.log("sum1:x+y+z=" + (x + y + z));
}
sum1(1, 3, 4); // sum1:x+y+z=8
// 柯里化后的函數
function sum2(x) {
return function (y) {
return function (z) {
console.log("sum2:x+y+z=" + (x + y + z));
}
}
}
sum2(1)(3)(4); // sum2:x+y+z=8
3.3 偏函數
- 偏函數,固定函數中的某一個或幾個參數,返回一個新的函數,接收剩下的參數, 參數個數可能是1個,也可能是2個,甚至更多。具體示例見bind方法簡單使用-demo2