In order to implement a method, you must first understand it. In fact, call, apply and bind are commonly used in our daily development. They all have a common effect: change the this direction.

Call and apply are essentially the same. The only difference is how param passes parameters. Call accepts continuous arguments, and apply accepts array arguments.

The difference between bind and call\apply is more obvious. Call \apply is executed immediately and returns the result of fn’s execution. Bind does not execute immediately. It returns a copy of fn. If you change this, you need to call the new function.

/ * *@params: targetThis (optional) The target of fn's this, which by default points to window@params: param (Optional) Pass the fn argument */fn.call(targetThis, param1, param2, param3 ...) fn.apply(targetThis, [param1, param2, param3 ...] ) fn.bind(targetThis, param1, param2, param3..) (PS: For the sake of unity, the following articles are described with FN, targetThis and ParamsCopy the code
  • The realization of the call

    Call changes the direction of this. Its execution can be broken down into the following steps:

    1. Pass fn as a private method of targetThis so that fn can be executed if this refers to targetThis
    2. Arguments intercepts continuously passed unquantified arguments
    3. Execute the FN and record the result
    4. Delete fn temporarily suspended in targetThis (without changing original targetThis)
    5. Return execution result
Function.prototype._call = function (context = window) {
  // Get the values of each parameter
  console.log('this'.this) // foo
  console.log('context', context) // target {value: 1}
  console.log('arguments'.arguments) // { {value: 1}, 'param1', 'param2'}
  let _context = context // The first parameter is not passed. The default is window
  _context.fn = this // Make foo a private method for target, this changes
  const args = [...arguments].slice(1) // Intercepts parameters with subscripts starting from 1 {'param1', 'param2'}
  constresult = _context.fn(... args)// Execute immediately
  delete _context.fn // Delete fn without changing 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
Copy the code
  • The realization of the apply

The implementation of Apply is basically the same as that of Call, except in the form of Params, where the parameters of Apply are arrays.

Function.prototype._apply = function (context = window, arr) {
  // Get the values of each parameter
  console.log('this'.this) // fn
  console.log('context', context) // target {value: 1}
  let _context = context// The first parameter is not passed. The default is window
  _context.fn = this // Make fn the private method of target, this is changed
  constresult = arr.length ? _context.fn(... arr) : _context.fn()// Determine whether arR is passed in
  delete _context.fn // Delete fn without changing 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
Copy the code
  • The realization of the bind

Due to ES5 built-in function.prototype.bind (..) The implementation is too complicated. Here we use call/apply to implement bind. Before we implement bind, we have a few key points:

  1. Bind is not executed immediately; it returns a function that is called to change the this reference
  2. In this binding rule, the new binding takes precedence over the hard binding.
  3. Quote from MDN:

Binding functions can also be constructed using the new operator, which behaves as if the target function has already been constructed. The supplied this value is ignored, but the leading argument is still supplied to the simulation function.

(Emphasis with exclamation point)

“Binding functions can also be constructed using a new operation” means that hard binding is used in new, and the new binding has a higher priority than hard binding. We need to make special treatment for new cases. “Preparameters will still be provided to the simulation function” means that using a hard-bound function in new can set some parameters in advance and pass in other parameters when using new for initialization, which is a kind of Cremation.

Function.prototype._bind = function (context = window) {
    // Make sure you understand the parameters first
    console.log('this'.this) // foo
    console.log('context', context) // target {value: 1}
    console.log('argument'.arguments) // { {value1: 1}, 'param1', 'param2'}
    const _this = this / / save this
    // where arguments are arguments to foo
    const args = [...arguments].slice(1) ['param1', 'param2']
    var fn =  function () {
        const bindArgs = [...arguments] // Arguments are fn
        When used as a constructor, this refers to the instance fn. This instanceof fn returns true
        When not used as a constructor, this refers to the window. This instanceof fn returns false. Change this to context
        // [...args,...bindArgs] merges arguments passed in advance with arguments passed in new instantiation
        return _this.apply( this instanceof fn ? this : context , [...args, ...bindArgs ])
    }
    // Change the binding function's prototype to foo's prototype, inheriting foo's properties
    // Create an empty object with __proto__ pointing to _this.prototype so that the prototye of fn does not affect 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
Copy the code