想要實現實現一個方法,必然要先了解它。其實call、apply、bind都是咱們平常開發中比較經常使用的一些方法。他們都有一個共同的做用:改變this指向。數組
call和apply本質上來講並無什麼區別,他們的做用是同樣,區別僅在於param的傳參方式。call接受連續傳參,apply接受數組傳參。markdown
bind和call\apply的區別想對來講就大一些,call\apply都是當即執行的,它們的返回結果就是fn的執行結果;而bind並不當即執行,它的返回結果是fn的拷貝,改變this指向後不會當即執行,須要自行調用這個新函數。app
/** @params: targetThis (可選) fn的this的目標指向,默認指向window @params: param (可選) 傳入fn的參數 */
fn.call(targetThis, param1, param2, param3 ...)
fn.apply(targetThis, [param1, param2, param3 ...])
fn.bind(targetThis, param1, param2, param3..)
(PS: 爲了統一,下面文章都用fn、targetThis、params來敘述
複製代碼
call的實現函數
call的做用只說一句改變this指向就太籠統了,它的執行能夠大體拆分爲以下幾個步驟:ui
Function.prototype._call = function (context = window) {
// 首先了解各個參數的值
console.log('this', this) // foo
console.log('context', context) // target {value: 1}
console.log('arguments', arguments) // { {value: 1}, 'param1', 'param2'}
let _context = context // 第一個參數不傳默認爲window
_context.fn = this // 將foo做爲target的私有方法, this改變
const args = [...arguments].slice(1) // 截取下標從1開始的參數 {'param1', 'param2'}
const result = _context.fn(...args) // 當即執行
delete _context.fn // 刪除fn 不改變target
return result
}
// eg
let target = {
value: 1
}
function foo(param1, param2) {
console.log(param1)
console.log(param2)
console.log(this.value)
}
foo._call(target, 'param1', 'param2') // param1 param2 1
複製代碼
apply的實現與call基本上是一致的,區別僅在於params的形式上,apply的參數爲數組形式。this
Function.prototype._apply = function (context = window, arr) {
// 首先了解各個參數的值
console.log('this', this) // fn
console.log('context', context) // target {value: 1}
let _context = context// 第一個參數不傳默認爲window
_context.fn = this // 將fn做爲target的私有方法, this改變
const result = arr.length ? _context.fn(...arr) : _context.fn() // 判斷是否傳入arr
delete _context.fn // 刪除fn 不改變target
return result
}
// eg
let target = {
value: 1
}
function fn(param1, param2) {
console.log(param1)
console.log(param2)
console.log(this.value)
}
fn._apply(target, ['param1', 'param2']) // param1 param2 1
複製代碼
因爲ES5內置的Function.prototype.bind(..)實現過於複雜,這裏咱們藉助call/apply來實現bind,在實現bind以前咱們明確幾個重點:spa
綁定函數也可使用 new 運算符構造,它會表現爲目標函數已經被構建完畢了似的。提供的 this 值會被忽略,但前置參數仍會提供給模擬函數。prototype
(劃重點加感嘆號)code
咱們來解讀一下, 「綁定函數也可使用new運算構造 」 就是說會在new中使用硬綁定,而new綁定的優先級又高於硬綁定,咱們須要對有new的狀況作特殊處理;「前置參數仍會提供給模擬函數」就是說,在new中使用硬綁定函數能夠預先設置一些參數,在使用new進行初始化時能夠傳入其餘參數,是柯里化的一種。orm
Function.prototype._bind = function (context = window) {
// 首先搞清楚各個參數的含義
console.log('this', this) // foo
console.log('context', context) // target {value: 1}
console.log('argument', arguments) // { {value1: 1}, 'param1', 'param2'}
const _this = this // 保存this
// 這裏arguments爲foo的arguments
const args = [...arguments].slice(1) // 截取下邊爲1開始的參數 ['param1', 'param2']
var fn = function () {
const bindArgs = [...arguments] // 這裏的arguments爲fn的
// 做爲構造函數時,this指向實例fn this instanceof fn 返回true
// 不做爲構造函數時, this指向window, this instanceof fn 返回false 須要將this指向改變爲context
// [...args, ...bindArgs] 合併預先傳入的參數和new 實例化時傳入的參數
return _this.apply( this instanceof fn ? this : context , [...args, ...bindArgs ])
}
// 修改綁定函數的prototype爲foo的prototype,繼承foo的屬性
// 建立一個空對象,讓空對象__proto__指向_this.prototype, 作到修改fn的prototye不會影響到foo
fn.prototype = Object.create(_this.prototype)
return fn
}
// eg
let target = {
value: 1
}
function foo(param1, param2, param3) {
console.log(param1)
console.log(param2)
console.log(param3)
console.log(this.value)
}
const bindFn = foo._bind(target, 'param1', 'param2')
bindFn() // param1 param2 undefined 1
const newBindFn = new bindFn('param3') // param1 param2 param3 undefined
複製代碼