其實在不少文章都會寫call,apply,bind的應用和區別,可是總感受不是要的東西,因此本身總結一下,繼續關注我,後續我會仔細講解call,apply,bind的內部實現。喜歡的能夠關注面試
若是出現錯誤,請在評論中指出,我也好本身糾正本身的錯誤數組
author: thomaszhou瀏覽器
面試當中幾乎每次都會問到一個js中關於call、apply、bind的問題,好比…bash
首先,要明白這三個函數的存在乎義是什麼?答案是改變函數執行時的上下文,再具體一點就是改變函數運行時的this指向。有了這個認識,接下來咱們來看一下,怎麼使用這三個函數。app
let obj = {name: 'tony'};
function Child(name){
this.name = name;
}
Child.prototype = {
constructor: Child,
showName: function(){
console.log(this.name);
}
}
var child = new Child('thomas');
child.showName(); // thomas
// call,apply,bind使用
child.showName.call(obj);
child.showName.apply(obj);
let bind = child.showName.bind(obj); // 返回一個函數
bind(); // tony
複製代碼
咱們拿別人的showName方法,並動態改變其上下文幫本身輸出了信息,說到底就是實現了複用dom
bind方法是事先把fn的this改變爲咱們要想要的結果,而且把對應的參數值準備好,之後要用到了,直接的執行便可,也就是說bind一樣能夠改變this的指向,但和apply、call不一樣就是不會立刻的執行(如上一個例子)函數
注意:bind這個方法在IE6~8下不兼容。工具
上面看起來三個函數的做用差很少,乾的事幾乎是同樣的,那爲何要存在3個傢伙呢,留一個不就能夠。因此其實他們乾的事從本質上講都是同樣的動態的改變this上下文,可是多少仍是有一些差異的..ui
call和apply改變了函數的this上下文後便執行該函數,而bind則是返回改變了上下文後的一個函數。this
他們倆之間的差異在於參數的區別,call和apply的第一個參數都是要改變上下文的對象,而call從第二個參數開始以參數列表的形式展示,apply則是把除了改變上下文對象的參數放在一個數組裏面做爲它的第二個參數。
let arr1 = [1, 2, 19, 6];
//例子:求數組中的最值
console.log(Math.max.call(null, 1,2,19,6)); // 19
console.log(Math.max.call(null, arr1)); // NaN
console.log(Math.max.apply(null, arr1)); // 19 直接能夠用arr1傳遞進去
複製代碼
例子2:
function fn() {
console.log(this);
}
// apply方法結果同下
fn.call(); // 普通模式下this是window,在嚴格模式下this是undefined
fn.call(null); // 普通模式下this是window,在嚴格模式下this是null
fn.call(undefined); // 普通模式下this是window,在嚴格模式下this是undefined
複製代碼
js中的僞數組(例如經過document.getElementsByTagName獲取的元素、含有length屬性的對象)具備length屬性,而且能夠經過0、一、2…下標來訪問其中的元素,可是沒有Array中的push、pop等方法。就能夠利用call,apply來轉化成真正的數組,就可使用數組的方法了
case1: dom節點:
<div class="div1">1</div>
<div class="div1">2</div>
<div class="div1">3</div>
let div = document.getElementsByTagName('div');
console.log(div); // HTMLCollection(3) [div.div1, div.div1, div.div1] 裏面包含length屬性
let arr2 = Array.prototype.slice.call(div);
console.log(arr2); // 數組 [div.div1, div.div1, div.div1]
複製代碼
可是這個不適用於IE6~8,會報錯:
SCRIPT5014: Array.prototype.slice: 'this' 不是 JavaScript 對象 (報錯)
複製代碼
那麼在IE6~8下就只能經過循環一個個加到數組中了:
for (var i = 0; i < oLis.length; i++) {
ary[ary.length] = oLis[i];
}
複製代碼
基於IE6~8和標準瀏覽器中的區別,抽取出類數組對象轉換爲數組的工具類:
function listToArray(likeAry) {
var ary = [];
try {
ary = Array.prototype.slice.call(likeAry);
} catch (e) {
for (var i = 0; i < likeAry.length; i++) {
ary[ary.length] = likeAry[i];
}
}
return ary;
}
複製代碼
case2: fn內的arguments
function fn10() {
return Array.prototype.slice.call(arguments);
}
console.log(fn10(1,2,3,4,5)); // [1, 2, 3, 4, 5]
複製代碼
注意:對於arguments借用數組的方法是不存在任何兼容性問題的。
case3: 含有length屬性的對象
let obj4 = {
0: 1,
1: 'thomas',
2: 13,
length: 3 // 必定要有length屬性
};
console.log(Array.prototype.slice.call(obj4)); // [1, "thomas", 13]
複製代碼
let arr1 = [1,2,3];
let arr2 = [4,5,6];
//數組的concat方法:返回一個新的數組
let arr3 = arr1.concat(arr2);
console.log(arr3); // [1, 2, 3, 4, 5, 6]
console.log(arr1); // [1, 2, 3] 不變
console.log(arr2); // [4, 5, 6] 不變
// 用 apply方法
[].push.apply(arr1,arr2); // 給arr1添加arr2
console.log(arr1); // [1, 2, 3, 4, 5, 6]
console.log(arr2); // 不變
複製代碼
let arr1 = [1,2,3];
let str1 = 'string';
let obj1 = {name: 'thomas'};
//
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
console.log(fn1(arr1)); // true
// 判斷類型的方式,這個最經常使用語判斷array和object,null(由於typeof null等於object)
console.log(Object.prototype.toString.call(arr1)); // [object Array]
console.log(Object.prototype.toString.call(str1)); // [object String]
console.log(Object.prototype.toString.call(obj1)); // [object Object]
console.log(Object.prototype.toString.call(null)); // [object Null]
複製代碼
function Animal(name){
this.name = name;
this.showName = function(){
console.log(this.name);
}
}
function Cat(name){
Animal.call(this, name);
}
// Animal.call(this) 的意思就是使用this對象代替Animal對象,那麼
// Cat中不就有Animal的全部屬性和方法了嗎,Cat對象就可以直接調用Animal的方法以及屬性了
var cat = new Cat("TONY");
cat.showName(); //TONY
複製代碼
function Class1(a,b) {
this.showclass1 = function(a,b) {
console.log(`class1: ${a},${b}`);
}
}
function Class2(a,b) {
this.showclass2 = function(a,b) {
console.log(`class2: ${a},${b}`);
}
}
function Class3(a,b,c) {
Class1.call(this);
Class2.call(this);
}
let arr10 = [2,2];
let demo = new Class3();
demo.showclass1.call(this,1); // class1: 1,undefined
demo.showclass1.call(this,1,2); // class1: 1,1
demo.showclass2.apply(this,arr10); // class2: 1,2
複製代碼