Detailed explanation of the implementation principles of call, apply, and bind in JavaScript

Detailed explanation of the implementation principles of call, apply, and bind in JavaScript

Preface

As we all know, the functions of call, apply, and bind are to "change" the scope, but the Internet is vague about this "change" and does not provide detailed explanations. Does "change" mean directly replacing the scope? Who replaces who? How does it produce the effect? If you don't understand these questions clearly, you probably won't remember them for long even if you see the handwritten implementation.

Therefore, this article introduces the usage of call, apply, and bind and their respective implementation principles.

call

The call() method calls a function with a specified this value and one or more arguments given individually.

That is, you can change the this pointer of the current function and make the current function execute.

usage

function fun() {
  console.log(this.name, arguments)
}
let obj = { name: 'clying' }
fun.call(obj, 'deng', 'deng')
// clying [Arguments] { '0': 'deng', '1': 'deng' }

accomplish

The implementation of call and apply is to put the function into a property of the literal obj, so that this in the function points to the literal object obj.

A simple implementation version:

Function.prototype.mycall = function (context) {
  context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  context.fn()
  delete context.fn
}

Add the mycall method to the function prototype and create a context object context. If the passed object does not exist, it will point to the global window. By adding the fn attribute to context, the fn of context refers to the function fun that calls the method and executes fun. Delete the attribute fn after execution is completed.

It is necessary to get the passed parameters first, then it becomes a string array.

The execution method uses the eval function, which calculates the string, executes the code in it, and returns the calculation result.

Upgraded version:

Pass parameters to the call.

Function.prototype.mycall = function (context) {
  context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  let arr = []
  for (let i = 1; i < arguments.length; i++) {
    arr.push('argument[' + i + ']') // ["arguments[1]", "arguments[2]"]
  }
  let r = eval('context.fn(' + arr + ')') // Execute function fun and pass in parameter delete context.fn
  return r
}

In addition, call can also be implemented through deconstruction syntax.

Function.prototype.mycall = function (context, ...args) {
  context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  context.fn(...args)
  delete context.fn
}

If you want to be able to call the call method multiple times, you can save context.fn(...args) to a variable and return it at the end.

Function.prototype.mycall = function (context, ...args) {
  context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  let r = context.fn(...args)
  delete context.fn
  return r
}

apply

Similar to the call method, the call method receives a parameter list, while the apply method receives an array containing multiple parameters.

usage

Set this in the function to point to the first parameter passed in, and the second parameter is an array.

function fun() {
  console.log(this.name, arguments);
}
let obj = {
  name: 'clying'
}
fun.apply(obj, [22, 1])
// clying Arguments(2) [22, 1]

accomplish

Implement an apply method myapply yourself. The implementation method is similar to call, but when receiving parameters, you can use an args as the second parameter passed in. Directly judge if the second parameter is not passed in, and execute the function directly; otherwise, use eval to execute the function.

Function.prototype.myapply = function (context, args) {
 context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  if(!args) return context.fn()
  let r = eval('context.fn('+args+')')
  delete context.fn
  return r
}

bind

The bind() method creates a new function and does not execute automatically. You need to call bind() manually. The this of the new function is assigned as the first parameter of bind(), and the remaining parameters will be used as parameters of the new function when it is called.

usage

Bind obj to the this of the fun function. The fun function can use the properties inside obj and the passed-in variables.

function fun() {
  console.log(this.name, arguments);
}
let obj = {
  name: 'clying'
}
let b = fun.bind(obj,2)
b(3)
// clying Arguments(2) [2, 3]

In addition, the function bound by the bind method can also create a new instance, but this will change at this time.

Upgraded version - using prototype properties usage:

function fun() {
  console.log(this.name, arguments);
}
let obj = {
  name: 'clying'
}
fun.prototype.age = 23
let b = fun.bind(obj, 3)
let instance = new b(4)
console.log(instance.age);
//undefined Arguments(2) [3, 4]
// twenty three

accomplish

Basic version:

The implementation of bind can be based on call and apply.

Because bind is not executed immediately, it can return a function to allow the user to execute it manually. In the returned function, use call or apply to pass in the specified this object and parameters.

Apply implements bind

Function.prototype.mybind = function (context) {
  let that = this
  let bindargs = Array.prototype.slice.call(arguments, 1)
  return function () {
    let args = Array.prototype.slice.call(arguments)
    return that.apply(context, bindargs.concat(args))
  }
}

The apply method is mainly used to obtain and process the parameters passed in by bind, as well as the parameters passed in by the user when executing the function. Use the slice method of the Array prototype method to intercept the required parameters.

When getting the parameters passed in by bind, you need to start intercepting from the second parameter, so the starting position is 1.

call implements bind

Function.prototype.mybind = function (context, ...args1) {
  let that = this
  return function (...args2) {
    return that.call(context, ...args1, ...args2)
  }
}

To implement call, simply concatenate the parameters to the end of the call method.

Upgraded version:

In addition to changing the pointer of this, bind also allows users to pass in parameters after bind and also allows users to pass in parameters when executing the command. You can also let the execution function perform new operations.

When a bound function is used to construct a value, the originally provided this is ignored. However, the provided parameter list will still be inserted before the parameter list when the constructor is called.

apply

Function.prototype.mybind = function (context) {
  let that = this
  let bindargs = Array.prototype.slice.call(arguments, 1)
  function fBind() {
    let args = Array.prototype.slice.call(arguments)
    // If new is used, this will point to the fBind instance. If this is not the current instance, use the context object return that.apply(this instanceof fBind ? this : context, bindargs.concat(args))
  }
  return fBind
}

When using the new operator, please note that you need to change the pointer of this. If it is new, then this points to the instance. If new is not used, it points to the first parameter currently passed in by bind.

In addition, it also involves the original function being able to add its own method attributes. If you want to be able to use fun's own prototype method, you also need to use fBind.prototype = this.prototype to achieve prototype sharing. However, for reference type property value sharing, it cannot be changed without changing other instances (if a prototype method or property is changed, all references will be changed).

Function.prototype.mybind = function (context) {
  let that = this
  let args = Array.prototype.slice.call(arguments, 1)
  function fBind() { // Execute the bind function let bindargs = Array.prototype.slice.call(arguments)
    return that.apply(this instanceof fBind ? this : context, args.concat(bindargs))
  }
  function Fn(){} // The prototypes of the two classes are not shared, but the prototype method is found through the prototype chain Fn.prototype = this.prototype
  fBind.prototype = new Fn()
  return fBind
}

For the above situation, you can use a function middleware to use the prototype chain to find the original function prototype method or property.

call

The difference between call and apply is only the difference in processing parameters, and everything else is similar.

Function.prototype.mybind = function (context, ...args1) {
  let that = this
  function fBind(...args2) {
    return that.call(this instanceof fBind ? this : context, ...args1, ...args2)
  }
  function Fn() { }
  Fn.prototype = this.prototype
  fBind.prototype = new Fn()
  return fBind
}

Summarize

This concludes this article on the implementation principles of call, apply, and bind in JavaScript. For more information on the principles of call, apply, and bind, please search previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Example code for using JS to simply implement the apply, call and bind methods
  • How to implement call, apply and bind in native js
  • How to implement a simple version of call, apply, bind using 50 lines of javascript code
  • Detailed explanation of the code for implementing call, bind, and apply in Javascript

<<:  Practical experience of implementing nginx to forward requests based on URL

>>:  Solutions to MySQL batch insert and unique index problems

Recommend

How to quickly use mysqlreplicate to build MySQL master-slave

Introduction The mysql-utilities toolset is a col...

How to build php-nginx-alpine image from scratch in Docker

Although I have run some projects in Docker envir...

Detailed explanation of efficient MySQL paging

Preface Usually, a "paging" strategy is...

How to build a DHCP server in Linux

Table of contents 1. Basic knowledge: 2. DHCP ser...

mysql query data for today, this week, this month, and last month

today select * from table name where to_days(time...

Detailed analysis of when tomcat writes back the response datagram

The question arises This question arose when I wa...

A brief discussion of four commonly used storage engines in MySQL

Introduction to four commonly used MySQL engines ...

Detailed explanation of the difference between in and exists in MySQL

1. Prepare in Advance For your convenience, I cre...

Example usage of JavaScript tamper-proof object

Table of contents javascript tamper-proof object ...

js to implement web calculator

How to make a simple web calculator using HTML, C...

Detailed explanation of JavaScript BOM composition and common events

Table of contents 1. BOM 2. Composition of BOM 2....

Advantages of MySQL covering indexes

A common suggestion is to create indexes for WHER...

Detailed tutorial on using Docker to build Gitlab based on CentOS8 system

Table of contents 1. Install Docker 2. Install Gi...

Use of Linux xargs command

1. Function: xargs can convert the data separated...