This paper introduces the basic use of call, apply, and bind methods in JavaScript, their usage considerations, common usage scenarios, etc. It also briefly introduces the implementation principles of these methods and provides the corresponding source code.
Call && apply method
call and apply are two important common methods in JavaScript. They basically have the same functions. They both modify this inside a function and execute the current function. If this function is a member of another object, you can also interpret their functions as methods of borrowing objects and bind them to this.
Let's start with code to see the basic use of call and apply.
// Call && apply basic use // (1) Modify this in the function // (2) Execute the function after modifying this /* Demo Code-01 */ function f1() { console.log("f1-1-this->", this) } /* 001-Calling functions directly */ f1(); /* Print results: f1-1-this->window */ /* 002-Calling functions through call and apply */ f1.call({ name: "zs" }); f1.apply({ name: "zs" }); /* Print results: f1-1-this->{name:'zs'} */ /* Print results: f1-1-this->{name:'zs'} */ /* Demo Code-02 */ function a() { console.log("a-1-this->", this) } function b() { console.log("b-1-this->", this) } a(); /* a-1-this->window */ b(); /* b-1-this->window */ a.call(b); /* a-1-this->function b */ a.call.call.call(b); /* b-1-this->window */ /* Demo Code-03 */ let o1 = { name: "Yong", showName() { console.log("Full name:" + this.name) } }; let o2 = { name: "Xia" }; // o1.showName(); /* Name: Yong */ // o2.showName(); /* Error: Uncaught TypeError: o2.showName is not a function */ /* Equivalent to o2.showName() */ o1.showName.call(o2); /* Name: Xia */ o1.showName.apply(o2); /* Name: Xia */
call and apply have the same basic functions, but there are also some differences in their use, which are reflected in two aspects.
- Parameters are passed differently, call s are passed through parameter lists, apply is passed through arrays
- The number of parameters (parameters expected to be passed) varies, with the call method having 0 parameters and the apply method having 1 parameter.
let o1 = { name: "Yong", show() { console.log("Full name:" + this.name + " Other:", arguments); } }; let o2 = { name: "Xia" }; /* (1) Parameters are passed differently */ o1.show(); /* Name: Yong Other: */ o1.show.call(o2); /* Name: Xia Other: */ o1.show.call(o2, 100, 200, "abc"); /* Name: Xia Other: Arguments (3) [100, 200,'abc'] */ o1.show.apply(o2, [10, 20, "abc"]); /* Name: Xia Other: Arguments (3) [100, 200,'abc'] */ /* (2) Different number of formal parameters */ console.log(Function.prototype.call === o1.show.call); /* true */ console.log(Function.prototype.call.length, Function.prototype.apply.length)/* 1,2 */
Based on the basic functions of the call method and the apply method and their differences, here's how they are implemented (source code), since all functions can call both methods, they should naturally be implemented in Function. Above prototype, the internal implementation handles two main tasks, namely, modifying this and executing functions, taking into account the passing of parameters and how they are handled when calling and executing functions.
/* call principle */ Function.prototype.call = function(context) { /* 01-Fault-tolerant handling of context environments, wrapped first if context is the original type */ context = context ? Object(context) : window; /* 02-Get the method and add it to the current object */ context.f = this; /* 03-Get the list of parameters (excluding the first parameter bound to this) */ let args = []; for (let i = 1; i < arguments.length; i++) { args.push(arguments[i]); } /* 04-Call and execute functions, using array toString to process parameters */ return eval("context.f(" + args + ")"); } /* apply principle */ Function.prototype.apply = function(context, args) { /* 01-Fault-tolerant handling of context environments, wrapped first if context is the original type */ context = context ? Object(context) : window; /* 02-Get the method and add it to the current object */ context.f = this; /* 03-Call directly and return if no arguments are passed in an array*/ if (!args) { return context.f(); } /* 04-If parameters are passed in an array, eval is used to execute the function and return the result */ return eval("context.f(" + args + ")"); } /* Test Code */ let o1 = { name: "Yong", show() { console.log("Full name:" + this.name + " Other:", arguments); } }; let o2 = { name: "Xia" }; o1.show.call(o2, 10, 20, 30); /* Name: Xia Other: Arguments(3) [10, 20, 30] */ o1.show.apply(o2, [100, 200, 300]); /* Name: Xia Other: Arguments(3) [100, 200, 300] */ console.log(Function.prototype.call === o1.show.call); /* true */ console.log(Function.prototype.call.length, Function.prototype.apply.length) /* 1,2 */
bind method
In JavaScript, the bind method is used less now. I personally feel that it is more cumbersome to use than call or apply and not readable. The bind method has the same function as call. It can also bind this in a function. The difference is that it does not execute a function. Instead, return the function bound (modified) this.
Here is a simple code to demonstrate the basic use of the bind method.
/* bind Basic use of methods */ /* (1) this in the binding function */ /* (2) Return the function bound to this */ /* (3) Allow multiple ways of passing parameters */ /* (4) The target function can be called with new */ /* (5) Instantiate object finds prototype object of original class */ /* Demo Code-01 */ let o1 = { name: "Yong", show() { console.log("Full name:" + this.name + " Other:", arguments); } }; let o2 = { name: "Xia" }; let fnc = o1.show.bind(o2); fnc(10, 20, 30); /* Name: Xia Other: Arguments(3) [10, 20, 30] */ /* Demo code 02 */ f1.prototype.say = function() { console.log("say ...") } function f1(a, b, c) { console.log("f1-this->", this, a, b, c); } function f2() { console.log("f2-this->", this); } /* [1] Allow two ways of passing parameters: */ /* Mode 1 */ // let F = f1.bind(f2,10,20,30); // F(); /* f1-this-> ƒ f2() 10,20,30 */ /* Mode 2 */ let F = f1.bind(f2, 10); F(20, 30); /* f1-this-> ƒ f2() 10,20,30 */ /* [2] Call the target function with new */ /* Note: the instantiated object f constructor is the original function f1 */ let f = new F(200, 300); /* f1-this-> f1 {} 10 200 300 */ console.log(f); /* f1 {} */ /* [3] Instantiated objects can find the prototype object of the original constructor */ f.say(); /* say ... */
If you just deal with modifying this in a function and return it, then the bind method will be much simpler to implement, as if you only need to do in Function as follows. Just add a bind function to the prototype.
Function.prototype.bind = function(context) { let that = this; return function() { that.call(context); } } /* Test Code */ function fn1() { console.log("fn1-", this) } function fn2() { console.log("fn2-", this) } let fn = fn1.bind(fn2); fn(); /* fn1- ƒ fn2() */
However, if factors such as parameter passing and constructor invocation need to be taken into account, the implementation inside the bind method may be slightly more complex, especially if it allows for two ways of passing parameters. The final version of the code is given below for reference.
/* bind Implementation principle of the method */ Function.prototype.bind = function(context) { let that = this; /* Get the first part parameter: ex Get let F = f1.bind(f2, 10); 10 in*/ let argsA = [].slice.call(arguments, 1); /* [10] */ function bindFunc() { /* Obtain the parameters of the second part: ex obtains F(20, 30); 20 and 30 in */ let argsB = [].slice.call(arguments); /* [20,30] */ let args = [...argsA, ...argsB]; return that.apply(this instanceof bindFunc ? this : context, args); } /* Prototype Processing */ function Fn() {}; Fn.prototype = this.prototype; bindFunc.prototype = new Fn(); /* Return function */ return bindFunc; }