Call, apply, bind, call, apply, call, bind, call, apply, call, bind At the same time, when reading some good open source projects, you will find that they often use the above API to reuse the original method, to achieve memory saving and code optimization effect. Next, let me take you through simple and detailed manual tearing and implementing new, Call, apply and bind yourself.

new

Constructors are often created with new, which intuitively feels like a keyword that generates instances from the constructor. So to actually disassemble, New does the following

  1. Generates a new instance object
  2. Give instance objects access to properties on the constructor prototype chain
  3. Bind the scope of the constructor to the instance object
  4. Execute the constructor code
  5. Return this instance object

Note that the new keyword expects to return a reference type. If we return a reference type in the constructor, new returns that reference type. If we force a non-reference type, we return an instance object.

Having said that, let’s take a look at the following code

function Person1(){
	this.name = 'william'
}
function Person2(){
	this.name = 'william'
	return (console.log('trigger') // undefined
}
function Person3(){
	this.name = 'william'
	return[]}var p1 = new Person1() // Instance object {name: 'William '}
var p2 = new Person2() // Still an instance object, although the return is triggered, it also prints trigger
var p3 = new Person3() / / []
Copy the code

Ok, now analyze these steps after new disassembly, among which 1 and 5 should be easy, and the difficulty lies in 2 and 3.

This involves properties on the link constructor prototype chain and changing function scope.

First, to link a prototype chain to an Object, you should be able to think of using object.create, the primitive inheritance from the previous article.

Interviewer: Talk about several inheritance methods commonly used in JS

In js, if you want to change the scope of a function, you can also think of the call, apply, and bind methods, so we’ll use them first and talk about them later.

Since you can’t override an operator or define a new one in JS, here we use functions to simulate it

function myNew(cto, ... args){
	const obj = {}
	obj.__proto__ = Object.create(cto.prototype) __proto__ is not recommended in production
	
	return cto.apply(obj, args) // Apply can change the this reference of a function
}
Copy the code

Function objects have the apply, call, and bind methods that can be used to change the scope of the Function that calls them.

const fn = function(){} fn.call(thisArg, params1, params2, ...) fn.apply(thisArg, [params1, params2, ...] ) fn.bind(thisArg, params1, params2, ...)Copy the code

Both involve changing the scope of a function. The difference is that bind returns a function that has changed this, while call and apply change this and immediately return the result. However, call and apply pass arguments differently. Call starts with the second argument, which is a parameter to the called function (fn in the example), while apply’s second argument is an array, and each item in the array is a parameter to the called function.

Look at an example

const wiliam = {
	name: 'william'.greet(greet_word){
		return `${greet_word} The ${this.name}`}}const abby = {
	name: 'abby'
	// There is no greet method on Abby
}

console.log(william.getName('hello')) // hello william
console.log(william.getName.call(abby, 'hello')) // hello abby
console.log(william.getName.apply(abby, 'hello')) // hello abby
const greetToAbby = william.getName.bind(abby, 'hello')) // Returns a function that changes this
console.log(greetToAbby()) // hello abby

Copy the code

This method of changing the direction of the function is often used in daily development, which can save a lot of code. Here are some common scenarios

Use the Object. The prototype. ToString to determine the data type

function getType(obj){
	if (typeofobj ! = ='object') return obj

	return Object.prototype.toString.call(obj).replace($/ / ^.'$1')}Copy the code

Judging by the Object. The prototype. The toString incoming obj string, so as to determine the data type

Deconstruct array pass-throughs

// In... without ES6 Deconstruction before
const arr = [1.5.7.9.11]
const min = Math.min.apply(null, arr) / / 1
const max = Math.max.apply(null, arr) / / 11
// or
Array.prototype.push.apply(arr, [0.0.0]
console.log(arr) // [1, 5, 7, 9, 11, 0, 0, 0]

// Now it is recommended to use...
Math.min(... arr)Math.max(... arr) arr.push(... [0.0.0])
Copy the code

React Component changes missing this because of the intermediate variable (onClick)

class Test extends React.Component{
	handleClick(){}

	render(){
		return (
			<div onClick={this.handleClick.bind(this)}></div>)}}Copy the code

Modify the multi-parameter function

const add = (. args) = > args.reduce((total, cur) = > total += cur, 0)
console.log(add(1.2.3)) / / 6

// create a method to add 10 to any number
const addTen = add.bind(null.10)

console.log(addTen(1)) / / 11
Copy the code

Well used can also simplify a lot of code and logic

Class arrays borrow methods from array primitives

const arrayLike = {
	0: 'william'.1: 'abby'.length: 2
}

// Borrow slice to transform into an array
const arr = Array.prototype.slice.call(arrayLink) // Return an array [' William ', 'Abby ']

// add an item using push
Array.prototype.push.call(arrayLike, 'skye')
console.log(arrayLike) // { '0': 'william', '1': 'abby', '2': 'skye', length: 3 }
Copy the code

Call and Apply implementations

The result of call, apply, and bind is to change the method’s this pointer, and then in js objects, the object method’s this pointer points to itself, so we can work on this area.

The basic principles of call and apply are the same, except for the different forms of parameter passing.

Function.prototype.call = function(context, ... args){
	context = context || window // The default point is window
	context.__fn = this // This is the function that calls the call method itself
	// const res = eval('context.fn(... Args)') // Do not use eval when you can
	const res = (new Function('return function(context, args){return context.__fn(... args)}'))()(
		context, args
	)
	delete context.__fn // Delete the contamination
	return res
}
Copy the code

The implementation is as simple as that. The context is referred to as a temporary variable, and then by mounting the function to the context passed in, the execution gets the desired result.

It is important to note that although using new Function instead of Eval, triple nested functions look much more complex than above, the performance overhead of Function calls is small and can be orders of magnitude faster and safer than Eval.

Click here to see more information about global objects eval and Function in the MDN documentation.

The realization of the bind

Bind is implemented in much the same way as Call, except that it does not need to be executed immediately, but simply returns a function that has changed its scope

Function.prototype.bind = function(context, ... args){
	const self = this;
	const fn = function(){
		self.apply(this instanceof self ? this : context, [...args, ...arguments]);
	}
	
	if(this.prototype){
		fn.prototype = Object.create(this.prototype)
	}

	return fn
}
Copy the code

The source code here is not hard to understand, first by wrapping a layer of functions that change the scope of the function call with the apply or call we mentioned above.

We then determine if the function has any prototype properties and use Object.create to ensure that the function’s prototype is mounted to our FN.

Then return fn.

conclusion

After this encounter, I found that actually these handwritten bind, call and so on are paper tigers. It’s really more of a look at the basics of the front end, and a lot of things are just simple concepts stacked on top of each other. If the content of that piece is stuck, you can first look up the relevant information, read and beat a few instances of yourself, you can also leave a message below, with everyone to discuss.