The implementation of Call, Apply and Bind in JavaScript

We know that call, apply and bind can change this direction in javascript, so how are they implemented? What's the difference between them? First, let's analyze them separately. This article briefly introduces the idea of implementing call(), apply(), bind()

The implementation of Call, Apply and Bind in JavaScript

Interpretation and implementation of call

var leo = {
  name: "Leo",
  sayHai: function () {
    return "Hi I'm " + this.name
  },
  add: function (a, b) {
    console.log(this)
    return a + b
  },
}

var neil = {
  name: "Neil",
}

console.log(leo.sayHai.call(neil)) //Neil
console.log(leo.sayHai.call(null)) //undefine

As can be seen from the above output results:
1. The first parameter of call method is used to change this point, but if null/undefined value is passed in, this will point to window
2. The call method needs to pass in the actual parameters according to the number of parameters
Now that we know how to use call and its rules, let's encapsulate a call method of our own

//The ES3 implementation method uses the eval() function to express strings as JavaScript and execute them
Function.prototype.es3call = function (ctx) {
  var ctx = ctx || global || window
  ctx.fn = this
  var args = []
  for (var i = 1; i < arguments.length; i++) {
    args.push("arguments[" + i + "]")
  }
  var result = eval("ctx.fn(" + args.join(",") + ")")
  delete ctx.fn
  return result
}

//The ES6 implementation uses the rest operator
Function.prototype.es6call = function (ctx) {
  var ctx = ctx || window || global
  ctx.fn = this
  var args = []
  for (var i = 1; i < arguments.length; i++) {
    args.push("arguments[" + i + "]")
  }
  const result = ctx.fn(...args)
  return result
}

console.log(leo.sayHai.es3call(neil)) //Neil
console.log(leo.sayHai.es3call(null)) //undefine
console.log(leo.sayHai.es6call(neil)) //Neil
console.log(leo.sayHai.es6call(null)) //undefine

The interpretation and implementation of apply

console.log(leo.add.apply(neil, [2, 3])) //neil 5
console.log(leo.add.apply(null, [2, 3])) //window or global 5

1. The first parameter of the apply method is used to change this point, but if a null/undefined value is passed in, this will point to window
2. The second parameter of the apply method needs to pass in a list of arguments, that is, arguments

//The ES3 implementation method uses the eval() function to express strings as JavaScript and execute them
Function.prototype.es3apply = function (ctx, arr) {
  var ctx = ctx || global || window
  ctx.fn = this
  var result = null
  if (!arr) {
    result = ctx.fn()
  } else {
    if (!(arr instanceof Array)) throw new Error("params must Array")
    var args = []
    for (var i = 0; i < arr.length; i++) {
      args.push("arr[" + i + "]")
    }
    result = eval("ctx.fn(" + args.join(",") + ")")
  }
  delete ctx.fn
  return result
}

//The ES6 implementation utilizes the extension operator rest operator
Function.prototype.es6apply = function (ctx, arr) {
  var ctx = ctx || global || window
  ctx.fn = this
  var result = null
  if (!arr) {
    result = ctx.fn()
  } else {
    if (!(arr instanceof Array)) throw new Error("params must Array")
    var args = []
    for (var i = 0; i < arr.length; i++) {
      args.push("arr[" + i + "]")
    }
    result = ctx.fn(...args)
  }
  delete ctx.fn
  return result
}
console.log(leo.add.es3apply(neil, [2, 3])) //neil 5
console.log(leo.add.es3apply(null, [2, 3])) //window or global 5
console.log(leo.add.es6apply(neil, [2, 3])) //neil 5
console.log(leo.add.es6apply(null, [2, 3])) //window or global 5

Interpretation and implementation of bind

var value = "window"
var obj = {
  value: "obj",
}
Parent.prototype.value = "parent"
function Parent() {}
Child.prototype = new Parent()
function Child() {}
function show(name) {
  console.log(this.value, name)
}

show() //window undefined
var newShow1 = show.bind(obj)
newShow1() //obj undefined
var newShow2 = show.bind(null)
newShow2() //window undefined
var newShow3 = show.bind(obj, "test") //Function has default initial arguments
newShow3() //obj test
new newShow3() //undefined test
var oS = Child.bind(obj)
var fn = new oS()
console.log(fn, fn.value) //Child{} "parent" the parameter obj is not valid when the binding function is called with the new operator.

Based on the above running results, we can summarize the usage rules of bind:
1. The bind() function creates a new function (called a binding function), which has the same function body as the called function (the target function of the binding function)
2. The first parameter of bind method is also used to change this point. If null/undefined is passed in, this will point to window 3. A binding function can also use the new operator to create objects: this behavior is like treating the original function as a constructor. This value provided is ignored
4.bind method can make function have preset initial parameters. These parameters (if any) are followed by this (or other objects) as the second parameter of bind(), and then they will be inserted at the beginning of the parameter list of the target function, and the parameters passed to the binding function will follow them.

//ES3 implementation method
Function.prototype.es3bind = function (ctx) {
  var ctx = ctx || global || window
  var self = this
  var args = Array.prototype.slice.call(arguments, 1)
  if (typeof this !== "function")
    throw new Error("what is trying to be bind is not callback")
  var temp = function () {}
  var fn = function () {
    var fnArgs = Array.prototype.slice.call(arguments, 0)
    return self.apply(this instanceof fn ? this : ctx, args.concat(fnArgs))
  }
  // Let the temp s prototype method point to_ This is the prototype method of the original function, inheriting_ Properties of this
  temp.prototype = this.prototype
  // Then point the prototype method of fn, the new function to be returned, to the instantiated object of temp
  // In this way, fn can inherit_ When you modify the prototype chain of this property, it will not affect the_ Prototype chain of this
  fn.prototype = new temp() //Archetypal inheritance
  return fn
}

//ES6 implementation method
Function.prototype.es6bind = function (ctx, ...rest) {
  if (typeof this !== "function")
    throw new Error("what is trying to be bind is not callback")
  var self = this
  return function F(...args) {
    if (this instanceof F) {
      return new self(...rest, ...args)
    }
    return self.apply(ctx, rest.concat(args))
  }
}
show() //window undefined
var newShow1 = show.es3bind(obj)
newShow1() //obj undefined
var newShow2 = show.es3bind(null)
newShow2() //window undefined
var newShow3 = show.es3bind(obj, "test") //Function has default initial arguments
newShow3() //obj test
new newShow3() //undefined test
var oS = Child.es3bind(obj)
var fn = new oS()
console.log(fn, fn.value) //Child{} "parent" the parameter obj is not valid when the binding function is called with the new operator.

show() //window undefined
var newShow1 = show.es6bind(obj)
newShow1() //obj undefined
var newShow2 = show.es6bind(null)
newShow2() //window undefined
var newShow3 = show.es6bind(obj, "test") //Function has default initial arguments
newShow3() //obj test
new newShow3() //undefined test
var oS = Child.es6bind(obj)
var fn = new oS()
console.log(fn, fn.value) //Child{} "parent" the parameter obj is not valid when the binding function is called with the new operator.

Tags: Javascript REST

Posted on Mon, 08 Jun 2020 23:37:15 -0400 by ou812