call、apply、bind的區別與應用場景

前言:讀者在看這篇文章的時候,你必須弄懂做用域以及JavaScript中this的做用和運用場景。

1、概念

  • 爲何會有call和apply? call和apply兩個方法的做用基本相同,它們都是爲了改變某個函數執行時的上下文(context)而創建的, 他的真正強大之處就是可以擴充函數賴以運行的做用域。通俗一點講,就是改變函數體內部this 的指向javascript

舉個栗子:

window.color = "red";
var o = {color: "blue"};
function sayColor(){
	alert(this.color);
}
sayColor();//red
sayColor.call(this);//red,把函數體sayColor內部的this,綁到當前環境(做用域)(這段代碼所處的環境)
sayColor.call(window);//red,把函數體sayColor內部的this,綁到window(全局做用域)
sayColor.call(o);//blue複製代碼

解釋:上面的栗子,很明顯函數sayColor是在全局做用域(環境/window)中調用的,而全局做用域中有一個color屬性,值爲"red",sayColor.call(this)這一行代碼就是表示 把函數體sayColor內部的this,綁到當前環境(做用域),而sayColor.call(window)這一行代碼就是表示 把函數體sayColor內部的this,綁到window(全局做用域),之因此這兩行的輸出都是"red"就是由於他當前做用域的this就是window(this === window); 最後,sayColor.call(o)這一行代碼就表示 把函數體sayColor內部的this,綁到o這個對象的執行環境(上下文)中來,也就是說sayColor內部的this——> o

2、call( thisValue , arg1, arg2, ... )

window.color = "red";
var o = {color: "blue"};
function sayColor(){
	alert(this.color);
}
sayColor.call(this);//red
sayColor.call(window);//red
sayColor.call();
sayColor.call(null);
sayColor.call(undefined);
sayColor.call(o);//blue複製代碼

注意:若是call方法沒有參數,或者參數爲 null或undefined,則等同於指向 全局對象

應用場景

  • 判斷對象類型

var arr = [];
Object.prototype.toString.call(arr); // [object Array]
//把函數體Object.prototype.toString()方法內部的this,綁到arr的執行環境(做用域)複製代碼

一樣是檢測對象類型,arr.toString()的結果和Object.prototype.toString.call(arr)的結果不同,這是爲何?

這是由於toString()爲Object的原型方法,而Array ,function等引用類型做爲Object的實例,都重寫了toString方法。不一樣的對象類型調用toString方法時,根據原型鏈的知識,調用的是對應的 重寫以後的toString方法(function類型返回內容爲函數體的字符串,Array類型返回元素組成的字符串.....),而不會去調用Object上原型toString方法,因此採用arr.toString()不能獲得其對象類型,只能將arr轉換爲字符串類型;所以,在想要獲得對象的具體類型時,應該調用Object上原型toString方法。 參考: developer.mozilla.org/zh-CN/docs/…()_to_detect_object_class

手撕call

var foo = {
	  count: 1
	};
	function bar() {
	  console.log(this.count);
	}
	bar.myCall(foo); // 1
--------------------------------------------------------------------
	Function.prototype.myCall = function(context) {
	  // 取得傳入的對象(執行上下文),好比上文的foo對象,這裏的context就至關於上文的foo
	  // 不傳第一個參數,默認是window,
	  var context = context || window;
	  // 給context添加一個屬性,這時的this指向調用myCall的函數,好比上文的bar函數
	  context.fn = this;//這裏的context.fn就至關於上文的bar函數
	  // 經過展開運算符和解構賦值取出context後面的參數,上文的例子沒有傳入參數列表
	  var args = [...arguments].slice(1);
	  // 執行函數(至關於上文的bar(...args))
	  var result = context.fn(...args);
	  // 刪除函數
	  delete context.fn;
	  return result;
	};複製代碼

3、apply( thisValue , [arg1, arg2, ...] )

很明顯,咱們看標題的能夠知道call和apply的一個區別了,它們兩個惟一的區別就是傳參列表的不一樣,apply是接收的參數是一個數組。
java

手撕apply

var foo = {
    count: 1
};
function bar() {
    console.log(this.count);
}
bar.myApply(foo); // 1
--------------------------------------------------------------------
Function.prototype.myApply = function(context) {
      var context = context || window;
      context.fn = this;
      var result;
      // 判斷第二個參數是否存在,也就是context後面有沒有一個數組
      // 若是存在,則須要展開第二個參數
      if (arguments[1]) {
        result = context.fn(...arguments[1]);
      } else {
        result = context.fn();
      }
      delete context.fn;
      return result;
}複製代碼

應用場景

  1. 找出數組中最大或最小的元素

var a = [10, 2, 4, 15, 9];
	Math.max.apply(Math, a);//15
	Math.min.apply(null, a);//2複製代碼

2. 能夠將一個相似(僞)數組的對象(好比arguments對象)轉爲真正的數組。 **前提:**被處理的對象必須有length屬性,以及相對應的數字鍵。
git

//接收的是對象,返回的是數組
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]
//(切下)[].slice(1, n),返回索引爲1到索引爲n-1的數組複製代碼

3. 數組追加
github

var arr1 = [1,2,3];
var arr2 = [4,5,6];
[].push.apply(arr1, arr2);
console.log(arr1);//[1, 2, 3, 4, 5, 6]
console.log(arr2);//[4, 5, 6]複製代碼

4. 數組合並
數組

var arr1 = [1, 2, { id: 1, id: 2 }, [1, 2]];
var arr2 = ['ds', 1, 9, { name: 'jack' }];
// var arr = arr1.concat(arr2);//簡單作法
Array.prototype.push.apply(arr1,arr2)
console.log(arr1);複製代碼

4、bind( thisArg[, arg1[, arg2[, ...]]])

developer.mozilla.org/zh-CN/docs/…app

call和apply它們兩個是改變this的指向以後當即調用該函數,而bind則不一樣,它是建立一個新函數,咱們必須手動去調用它。 MDN說法:bind()方法建立一個新的函數,在調用時設置this關鍵字爲提供的值。並在調用新函數時,將給定參數列表做爲原函數的參數序列的前若干項。(雖然這句話我還不太懂)函數

  • bind()是ES5新增的一個方法ui

  • 傳參和call或apply相似this

  • 不會執行對應的函數,call或apply會自動執行對應的函數spa

  • bind會返回對函數的引用

舉個栗子

var a ={
       name : "Cherry",
       fn : function (a,b) {
           console.log( a + b)
       }
   }
   var b = a.fn;
   b.call(a,1,2);//當即調用該函數
   b.bind(a,1,2)();//手動調用(),它返回一個原函數的拷貝(新的,不是原函數),並擁有指定的this值和初始參數。複製代碼

var obj = {
			name:'JuctTr',
			age: 18
		};
		/** * 給document添加click事件監聽,並綁定ExecuteFun函數 * 經過bind方法設置ExecuteFun的this爲obj */
		//document.addEventListener('click',ExecuteFun.call(obj),false);
		document.addEventListener('click',ExecuteFun.bind(obj),false);		
		function ExecuteFun(a,b){
		    console.log(this.name, this.age);
		}複製代碼

若是把bind換成call或apply,頁面控制檯會直接輸出JuctTr 18,由於call和apply是改變this的指向以後當即執行ExecuteFun函數,而bind它是返回一個新的函數

應用場景

手撕bind

Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var _this = this
  var args = [...arguments].slice(1)
  // 返回一個函數
  return function F() {
    // 由於返回了一個函數,咱們能夠 new F(),因此須要判斷
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}複製代碼


更多文章請轉移:github.com/wangyicong

相關文章
相關標籤/搜索