call/apply/bind is used in daily encoding by developers to implement "object impersonation", i.e. "display binding this".
The interview question, "call/apply/bind source implementation", is actually a comprehensive assessment of JavaScript fundamentals.
Relevant knowledge points:
- Scope;
- this points to;
- Curitization of functions;
- Prototype and prototype chain;
Differences between call/apply/bind
- All three can be used to display the binding this;
-
The difference between call/apply is the way in which the parameters are passed.
- fn.call(obj, arg1, arg2,...), passes a list of parameters separated by commas;
- fn.call(obj, [arg1, arg2,...]), passing an array of parameters;
- bind returns a function to be executed, an application of the Curitized function, while call/apply executes the function immediately
A Preliminary Study of Ideas
Function.prototype.myCall = function(context) { // This in the prototype points to an instance object, so this points to [Function: bar] console.log(this); // [Function: bar] // In the incoming context object, create an attribute with a value pointing to the method bar context.fn = this; // foo.fn = [Function: bar] // Call this method, where the caller is Foo and this points to foo context.fn(); // Delete it after execution and use it only once to avoid it being used elsewhere (traversal) delete context.fn; }; let foo = { value: 2 }; function bar() { console.log(this.value); } // The declaration of the bar function is equivalent to: var bar = new Function("console.log(this.value))); bar.call(foo); // 2;
Source implementation of call
I have a general idea, the rest is to improve the code.
// ES6 Version Function.prototype.myCall = function(context, ...params) { // ES6 function Rest parameter so that it can specify an object, receive the remaining parameters of the function, and synthesize an array if (typeof context === 'object') { context = context || window; } else { context = Object.create(null); } // Use Symbol as attribute key value to keep uniqueness and avoid conflicts let fn = Symbol(); context[fn] = this; // Expand the parameter array and pass in as multiple parameters const result = context[fn](...params); // Delete to avoid persistence delete(context[fn]); // Functions can have return values return result; } // test var mine = { name: 'In the name of pleasure' } var person = { name: 'Anonymous person', sayHi: function(msg) { console.log('My name:' + this.name + ',', msg); } } person.sayHi.myCall(mine, 'Nice to meet you!'); // My name: It's a pleasure to meet you!
Knowledge point supplement:
- ES6 The new original data type, Symbol, represents unique values;
- Object.create(null) Creates an empty object
// How to create an empty object // eg.A let emptyObj = {}; // eg.B let emptyObj = new Object(); // eg.C let emptyObj = Object.create(null);
Empty objects created with Object.create(null) are not interfered with by the prototype chain.The end of the prototype chain points to null, has no constructor, and has no attributes such as toString, hasOwnProperty, valueOf, which come from Object.prototype.Partners with a prototype chain base should know that the prototype chain of all common objects points to Object.prototype.
So Object.create(null) creates empty objects that are cleaner than the other two methods and do not have attributes on the chain of Object prototypes.
ES5 Version:
- Processing parameters by oneself;
- Self-Implementing Symobo
// ES5 Version // Simulate Symbol function getSymbol(obj) { var uniqAttr = '00' + Math.random(); if (obj.hasOwnProperty(uniqAttr)) { // If it already exists, it recursively calls the function arguments.callee(obj); } else { return uniqAttr; } } Function.prototype.myCall = function() { var args = arguments; if (!args.length) return; var context = [].shift.apply(args); context = context || window; var fn = getSymbol(context); context[fn] = this; // No other parameters passed in if (!arguments.length) { return context[fn]; } var param = args[i]; // Type judgment, otherwise eval will run in error var paramType = typeof param; switch(paramType) { case 'string': param = '"' + param + '"' break; case 'object': param = JSON.stringify(param); break; } fnStr += i == args.length - 1 ? param : param + ','; // Execute with eval var result = eval(fnStr); delete context[fn]; return result; } // test var mine = { name: 'In the name of pleasure' } var person = { name: 'Anonymous person', sayHi: function(msg) { console.log('My name:' + this.name + ',', msg); } } person.sayHi.myCall(mine, 'Nice to meet you!'); // My name: Happy to know!
Source implementation of apply
call's source code implementation, then apply is simple, the two are just different ways of passing parameters.
Function.prototype.myApply = function(context, params) { // apply differs from call in that the second parameter is an array and there is no third parameter if (typeof context === 'object') { context = context || window; } else { context = Object.create(null); } let fn = Symbol(); context[fn] = this; const result context[fn](...params); delete context[fn]; return result; }
Source implementation of bind
- The difference between bind and call/apply is that it returns a function to be executed, not the result of its execution.
- The function returned by bind is used with new as a constructor, and the binding this needs to be ignored;
The value passed to the target function as the this parameter when the bound function is called.If a binding function is constructed using the new operator, the value is ignored.- MDN
Function.prototype.bind = function(context, ...initArgs) { // The method called by bind must be a function if (typeof this !== 'function') { throw new TypeError('not a function'); } let self = this; let F = function() {}; F.prototype = this.prototype; let bound = function(...finnalyArgs) { // Merge forward and backward parameters into return self.call(this instanceof F ? this : context || this, ...initArgs, ...finnalyArgs); } bound.prototype = new F(); return bound; }
Many partners will also encounter the question of how to bind without using call/apply?
Don't panic in Sao Nian. Without call/apply, it's equivalent to replacing call/apply with a corresponding self-realization method. It's a lazy trick.
This call/apply/bind source implementation is a further consolidation of previous article series knowledge points.
"Don't panic if you have your code in mind."
Reference documents:
More front-end cornerstones to build, in Github, look forward to Star!
https://github.com/ZengLingYong/blog
Author: In the name of Happy
This article is original, there are inappropriate places to welcome pointing out.Please indicate the source for reprinting.