js call、apply、bind的實現

三個函數實際上是老生常談了,網上也有太多關於他們的實現。開始只是理解別人的實現,實際上是似懂非懂。只有本身實現出來,而且逐行理解,纔是真的懂了。 文章從三個函數的使用入手,結合使用場景逐步實現。

call的使用

window.name = 'window'
var obj = {
 name: 'obj'
}
function getName(p1, p2) {
 console.log(p1, p2)
 console.log(this.name)
}
getName('str1', 'str2') 
getName.call(obj, 'str1', 'str2') 

// 函數運行結果能夠思考一下。
複製代碼

怎麼記住call呢,其實就是getName這個方法執行了,不是window去執行,是call括號內的參數去執行。數組

能夠聯想到一個很典型的使用場景 [].prototype.shift.call(arguments),起初很不理解這種寫法,後來一想,其實就是 arguments 不是數組,它沒有shift方法能夠直接來用,就把數組的shift方法拿來用。app

call的實現

1 明確是誰調用call,答案,是函數。函數

2 call接收的參數是什麼?第一個參數是要改變的this指針,也就是上面說到了,是誰去執行這個函數。若無指定,默認爲windowthis

3 call接收的第二個,第三個,等等,參數,是用來作什麼的?答,就是做爲調用call的那個函數所需的參數。spa

function myCall(context) {
  // 1
  if (typeof this !== 'function'){
	throw new TypeError('error')
  }
  // 2
  context = context || window
  // 3
  context.fn = this
  // 4
  const args = [...arguments].slice(1)
  // 5
  const result = context.fn(...args)
  // 6
  delete context.fn
  return result
}
Function.prototype.myCall = myCall

getName.myCall(obj, 'str1', 'str2')
複製代碼
  • 1 在myCall方法實現體中的this是什麼? 回想一下,誰調用函數,this就是誰。那誰來調用call方法呢,是函數,因此這個this,就是調用call方法的函數,對於本例子來講,就是getName。若是不是函數,直接報錯。
  • 2 myCall方法若是沒有參數,那麼默認爲window
  • 3 本例中的context是傳進來的obj對象,給這個對象添加一個方法,這個方法,就是this,也就是getName
  • 4 獲取參數 ,在本例中,對應的是 'str1', 'str2'
  • 5 結果很明確了,就是 fn是方法,是getName。context是傳進來的obj對象,那麼就是實現了,obj調用getName方法,參數爲'str1', 'str2'。(誰調用方法,this就指向誰)
  • 6 刪除對象上函數,返回結果

apply的使用

apply使用與call大致一致,只是接受參數的方法不一樣。call能夠接收多個參數。apply接收的第一個參數是this,第二個參數是 所需參數所組成的數組prototype

window.name = 'window'
var obj = {
 name: 'obj'
}
function getName(p1, p2) {
  console.log(p1, p2)
  console.log(this.name)
}
getName('str1', 'str2') 
getName.apply(obj, ['str1', 'str2'])
複製代碼

apply的實現

Function.prototype.myApply = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = context || window
  context.fn = this
  var result
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}
複製代碼

和上述的call實現基本相似,就參數處理有些不一樣,再也不贅述。指針

bind的使用

爲何有call apply後還要有個bind ? 當咱們須要綁定一個點擊事件的時候,就改變回調函數的this,怎麼破?由於 call apply都是當即執行了,因此bind登場。看一下下面這個例子吧。code

var obj = {
  name: 'obj'
}

document.addEventListener('click',myClick.bind(obj,'p1','p2'),false);

function myClick(p1,p2){
  console.log(this.name, p1, p2)
}
複製代碼

MDN的解釋是:bind()方法會建立一個新函數,稱爲綁定函數,當調用這個綁定函數時,綁定函數會以建立它時傳入 bind()方法的第一個參數做爲 this,傳入 bind() 方法的第二個以及之後的參數加上綁定函數運行時自己的參數按照順序做爲原函數的參數來調用原函數。cdn

注意:bind方法的返回值是函數對象

bind的實現

bind()最簡單的用法是建立一個函數,使這個函數不論怎麼調用都有一樣的this值。

由於bind返回值是函數,那麼函數除了直接運行以外,還能夠做爲構造函數放在new 操做符以後,因此bind的實現就要把這種狀況考慮進去。

前置知識點 new

任何函數均可以做爲構造函數,放在 new 以後使用,那麼new的過程是怎麼樣的呢?大致分爲如下幾步,具體不深究。

  1. 生成空對象
  2. 空對象的原型屬性指向構造函數的原型對象
  3. 給這個空對象添加屬性
  4. 返回這個對象。

上面所說的那個空對象就是構造函數內部的this,而且 對於 new 的狀況來講,不會被任何方式改變 this

window.name = 'window'
var obj = {
  name: 'obj'
}
function Fun(p1,p2){
  console.log(this)
  console.log(this.__proto__ === Fun.prototype)
  console.log(this.name)
  this.a = p1
  this.b = p2
  console.log(this)
}
var c = new Fun('str1', 'str2')
console.log(c)
複製代碼

運行結果以下:

再來看一下,直接執行Fun的返回結果,代碼不作修改,直接執行 Fun('str1', 'str2')

兩次結果的不一樣,本文不展開敘述。那麼舉這個例子是想說明什麼呢? 答 : 函數充當構造函數和普通函數,運行時內部的this指向不一樣。 接下來看bind函數的模擬實現

Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
const _this = this
const args = [...arguments].slice(1)
// 返回函數
return function F() {
  // 1 判斷是否用做構造函數
  if (this instanceof F) {
    return new _this(...args, ...arguments)
  }
  // 2 用做普通函數
  return _this.apply(context, args.concat(...arguments))
 }
}
// 仍是用上述舉例子
window.name = 'window'
var obj = {
  name: 'obj'
}
function Fun(p1, p2){
  this.a = p1
  this.b = p2
  console.log(this.name)
  console.log(p1, p2)
}
var f1 = Fun.bind(obj, 'str1')
f1('str2')
複製代碼

運行結果以下,可見 改變了fn1函數的this指向

再來看一下 ,去掉 f1('str2') ,換成以下語句的運行結果

// f1('str2')
var b = new f1('str2')
console.log(b)
複製代碼

沒有逐行的說明,不過對於內部實現,有new的前置介紹和註釋,相信開始的call實現代碼都看懂了的話,這個bind方法也會一目瞭然

相關文章
相關標籤/搜索