Previous series -this/apply/call ask point

preface

The previous series is front-end interview series, which mainly focuses on some questions frequently asked in front-end interview.

There is no tedious explanation process in the series of questions and answers, and strive to ensure that the interviewer gives the interviewer a concise and focused answer, so it is suitable for front-end children with a certain knowledge base 👨🎓. Of course, at the end of each question I will also post a better article about this chapter, in order to provide a better understanding of the knowledge points mentioned.

Please look for github address: Lindaidai-fi

First, the interview

1. 5 bindings for this

  • Default binding (non-strict mode this points to a global object, strict modethisWill be bound toundefined)
  • Implicit binding (when a function reference hasContext objectWhen, if theobj.foo()Is called by,fooWithin thethisPoint to theobj)
  • Explicit binding (passescall()orapply()Method directly specifiedthis, such asfoo.call(obj))
  • The new binding
  • Arrow function binding (thisThe pointer to the outer scope is determined.

Note ⚠ ️

Implicit loss

Implicitly bound functions lose their binding object in certain cases. Apply the default binding and bind this to the global object or undefined:

  1. Use another variable to alias the function:
function foo () {
	console.log(this.a)
}
var obj = {
	a: 1.foo: foo
}
var bar = obj.foo; // Assign to another variable
var a = 2;
bar(); / / 2
Copy the code
  1. Functions are implicitly assigned when passed as arguments. It is very common for callback functions to lose the this binding:
// Implicit binding loss due to parameter passing
function foo() {
	console.log(this.a)
}
var obj = {
	a: 1.foo: foo // Foo () => foo() doesn't work
}

function doFoo(fn) {
	fn();
}
var a = 2;
doFoo(obj.foo) / / 2
Copy the code

Resolve missing bindings in explicit bindings

  1. Hard binding, creating a wrapper function that accepts parameters and returns values
/ / hard binding
function foo(params) {
	console.log(this.a, params);
	return this.a + params;
}
var bar = function() {
	return foo.apply(obj, arguments);
}
var obj = {
	a: 1
}
var a = 2;
console.log(bar(3)) / / 1, 3; return 4
Copy the code
// 1. Simple auxiliary binding functions
function bind (obj, fn) {
	return function () {
		return fn.apply(obj, arguments); }}// 2. ES5 has function.prototype.bind built in
var bar = foo.bind(obj);
Copy the code
  1. JSSome of the built-in functions in theForEach, Map, and filter), which can specify a bindingthis, its function andbindThe same:
// An optional argument provided by the built-in function specifying the binding of this
function foo(el) {
	console.log(el, this.a)
}
var obj = {
	a: 'obj a'
};
var a = 'global a';
var arr = [1.2.3];
arr.forEach(foo, obj) // The second argument is the function's this pointer
// 1 'obj a', 2 'obj a', 3 'obj a'
Copy the code

Detailed guide: “Wood easy Yang front-end advanced -JavaScript in-depth history of the most complete -5 kinds of this binding comprehensive analysis”

2. What happens when new is used to create objects 🤔️?

  1. A new object is created (or constructed)
  2. This new object goes[[prototype]]Connect to point the prototype of the new object to the constructor so that the new object can access properties in the constructor prototype
  3. Change constructorthisPoints to the newly created object so that the new object can access the properties in the constructor
  4. If the function has no other return value, the function call using the new expression automatically returns the new object

Detailed guide: “Wood easy Yang front-end advanced -JavaScript in-depth history of the most complete -5 kinds of this binding comprehensive analysis”

3. Use scenarios of Apply and Call

Grammar:

func.apply(thisArg, [argsArray])
func.call(thisArg, arg1, arg2, ...)
Copy the code
  1. Merge two arrays (Array.prototype.push.apply(arr1, arr2))
  2. Get the maximum and minimum values in the array (Math.max.apply(null, arr))
  3. Get the data type (Object.prototype.toString.call(obj))
  4. Enables array-like objects to use array methods (Array.prototype.slice.call(domNodes)or[].slice.call(domNodes))
  5. Call the parent constructor to implement inheritance (SuperType.call(this))
  6. useObject.prototype.hasOwnProperty.call(obj)To detectObject.create(null)This kind of object

Note ⚠ ️ :

On Point 6:

All normal objects can access hasOwnProperty(…) via object.prototype’s delegate. , but for some special objects (Object. The create (null) create) not connected to the Object. The prototype, this must use Object. The prototype. The hasOwnProperty. Call (obj, “a”), Explicitly bind to obj. This is a call again.

For example 🌰 :

var obj = Object.create(null);
obj.name = 'objName';
console.log(Object.prototype.hasOwnProperty.call(obj5, 'name')); // true
Copy the code

Detailed guide: “Wood Yiyang Front-end Advanced – In-depth Analysis of Call and Apply principles, Usage Scenarios and Implementation”

4. How to merge two arrays using apply/call when the length of the second number is too large 🤔️?

Cause of the problem:

  1. We know that we can merge two arrays in the following way:
Array.prototype.push.apply(arr1, arr2);
// or
Array.prototype.push.call(arr1, ... arr2);Copy the code
  1. It is also known that a function can accept a limited number of parameters, different engines have different limits, the JS core is limited to 65535.

Therefore, in order to solve the problem that the length of the second number is too large, we can cut the parameter array and loop it into the target array:

function connectArray (arr1, arr2) {
	const QUANTUM = 32768;
	for (let i = 0, len = arr2.length; i < len; i += QUANTUM) {
		Array.prototype.push.apply(
			arr1,
			arr2.slice(i, Math.min(i + QUANTUM, len))
		)
	}
	return arr1;
}
Copy the code

Testing:

var arr1 = [- 3.2 -.- 1];
var arr2 = [];
for (let i = 0; i < 100000; i++) {
  arr2.push(i);
}
connectArray(arr1, arr2);
// arr1.length // 100003
Copy the code

Detailed guide: “Wood Yiyang Front-end Advanced – In-depth Analysis of Call and Apply principles, Usage Scenarios and Implementation”

5. How do I use call to obtain the data type 🤔️?

In the Object. The prototype. The toString () has not been modified, we can use it to call to get data types:

[[Class]] is an internal attribute with a type string value that can be used to determine the type of the value.

// Write a function to get the data type
function getClass(obj) {
	let typeString = Object.prototype.toString.call(obj); // "[object Array]"
	return typeString.slice(8.- 1);
}
console.log(getClass(new Date)) // Date
console.log(getClass(new Map)) // Map
console.log(getClass(new Set)) // Set
console.log(getClass(new String)) // String
console.log(getClass(new Number)) // Number
console.log(getClass(NaN)) // Number
console.log(getClass(null)) // Null
console.log(getClass(undefined)) // Undefined
console.log(getClass(Symbol(42))) // Symbol
console.log(getClass({})) // Object
console.log(getClass([])) // Array
console.log(getClass(function() {})) // Function
console.log(getClass(document.getElementsByTagName('p'))) // HTMLCollection

console.log(getClass(arguments)) // Arguments
Copy the code

6. What are the methods to convert an array-like object to an object 🤔️?

Array.prototype.slice.call(arguments);
// equivalent to [].slice.call(arguments);

ES6:
let arr = Array.from(arguments);
let arr = [...arguments];
Copy the code

Array.from() can turn two types of objects into true arrays: array-like objects and iterable objects (including the new ES6 data structures Set and Map), such as:

var map1 = new Map(a); map1.set("key1"."value1")
map1.set("key2"."value2")
var mapArr = Array.from(map1)
console.log(map1) // Map
console.log(mapArr) // [["key1", "value1"], ["key2", "value2"]] Two-dimensional array
Copy the code

Extend one: why through the Array. The prototype. Slice. The call () the class Array object can be turned into an Array 🤔 ️?

A: Because Slice subscripts array-like objects into a new array

Extension 2: through the Array. The prototype. Slice. The call () enough? What is the problem 🤔️?

A: under the low version of Internet explorer does not support Array. The prototype. Slice. The call (args) this kind of writing, because of the low version of Internet explorer (IE < 9) under the DOM object is implemented in the form of com objects, js object with the com object can not convert.

Compatible writing is:

function toArray (nodes) {
  try {
    return Array.prototype.slice.call(nodes);
  } catch (err) {
    var arr = [],
        len = nodes.length;
    for (var i = 0; i < len; i++) {
      arr.push(nodes[i]);
    }
    returnarr; }}Copy the code

Extension 3: Why have array-like objects? Or why did array-like objects appear to solve what problems 🤔️?

In short, it allows faster manipulation of complex data, such as audio and video editing, accessing raw webSockets data, etc.

7. Use scenarios of bind

Grammar:

func.bind(thisArg, arg1, arg2, ...)
Copy the code

As we know, bind() creates a new function. When the new function is called, this points to the first argument of bind(), and the rest of the arguments are used as arguments to the new function.

So the biggest difference from Apply/Call is that bind returns a function of the binding context, whereas the other two execute that function directly.

In terms of usage scenarios:

  1. Change according to the actual business situationthisFor example, to solve implicit binding function lossthisIn the case
  2. Can be combined withFunction.prototype.call.bind(Object.prototype.toString)To get the data type (providedObject.prototype.toStringMethods are not overridden
  3. becausebindIs going to return a new function, so we can also use it for Currization,bindThis is itself a use scenario for closures.

Detailed guide: “Wood Yiyang front-end advanced – in-depth analysis of bind principle, Use scenarios and Simulation implementation”

Second, the written test

1. This points to the problem

/** * non-strict mode */
var name = 'window'

var person1 = {
  name: 'person1'.show1: function () {
    console.log(this.name)
  },
  show2: (a)= > console.log(this.name),
  show3: function () {
    return function () {
      console.log(this.name)
    }
  },
  show4: function () {
    return (a)= > console.log(this.name)
  }
}
var person2 = { name: 'person2' }

person1.show1()
person1.show1.call(person2)

person1.show2()
person1.show2.call(person2)

person1.show3()()
person1.show3().call(person2)
person1.show3.call(person2)()

person1.show4()()
person1.show4().call(person2)
person1.show4.call(person2)()
Copy the code



empty

white




The answer:

person1.show1() // person1 implicitly binds, this points to the caller
person1.show1.call(person2) // person2 explicitly bound, this refers to person2

person1.show2() // window, arrow function binding, this refers to the outer scope, i.e. the global scope
person1.show2.call(person2) // window, hard binding person2 with call is useless. This points to the outer scope, i.e., the global scope

person1.show3()() // window, the default binding, this function is a higher-order function, the caller is window
									Var bar = person1.show3(); var bar = person1.show3();

person1.show3().call(person2)// person2, explicitly bound to point this of the function 'var bar = person1.show3()' to person2

person1.show3.call(person2)() Var bar = person1.show3(); // window, the default binding, although this points to person2 in the first function, but the inner function 'var bar = person1.show3()' still calls window

person1.show4()() // person1, the first function this is person1, the inner layer is the arrow function, pointing to the outer scope person1
person1.show4().call(person2) // person1, the first function this is person1, the inner function is the arrow, the call hard binding person2 also does not work,this still refers to the outer scope person1

person1.show4.call(person2)() // person2, change the this pointer of the first function to person2, and the inner arrow function to the outer scope person2
Copy the code

Another way: use constructors to create objects and perform the same 4 show methods:

Note: The difference between objects created using the new operator and objects generated by direct var is that:

Using the new operator creates a new constructor scope so that the this in the arrow function points to that scope, not the whole world

var name = 'window'

function Person (name) {
  this.name = name;
  this.show1 = function () {
    console.log(this.name)
  }
  this.show2 = (a)= > console.log(this.name)
  this.show3 = function () {
    return function () {
      console.log(this.name)
    }
  }
  this.show4 = function () {
    return (a)= > console.log(this.name)
  }
}

var personA = new Person('personA')
var personB = new Person('personB')

personA.show1()
personA.show1.call(personB)

personA.show2()
personA.show2.call(personB)

personA.show3()()
personA.show3().call(personB)
personA.show3.call(personB)()

personA.show4()()
personA.show4().call(personB)
personA.show4.call(personB)()
Copy the code



empty

white




The answer:

personA.show1() // personA, implicit binding, caller is personA
personA.show1.call(personB) // personB, explicitly bound, caller is personB

personA.show2() // personA, "this" refers to the outer scope of the personA function
personA.show2.call(personB) // personA, arrow functions using call hard binding are useless

personA.show3()() // window, the default binding, the caller is window, as in the first problem
personA.show3().call(personB) // personB, explicitly bound
personA.show3.call(personB)() // window, the default binding, the caller is window, as in the first problem

personA.show4()() // personA, arrow function binding, this points to the outer scope, i.e., personA function scope
personA.show4().call(personB) // personA, arrow function binding, call does not change the outer scope,
personA.show4.call(personB)() // personB, the scope of the first layer function is personB, the memory function is the arrow function, this points to the outer scope, that is, the personB function scope
Copy the code

2. Write a new implementation

function create () {
	var obj = new Object(),
      Con = [].shift.call(arguments);
  obj.__proto__ = Con.prototype;
  var ret = Con.apply(obj, arguments);
  return ret instanceof Object ? ret : obj;
}
Copy the code



empty

white




Process analysis:

function create () {
  // create a new object
	var obj = new Object(),
  // 2. Take the first argument, which is the constructor we pass in; Arguments is also removed from the first argument
      Con = [].shift.call(arguments);
  // 3. Point obj's prototype to the constructor so that obj can access properties in the constructor prototype
  obj.__proto__ = Con.prototype;
  // 4. Use apply to change the reference of constructor this to the newly created object, so that obj can access the properties in constructor
  var ret = Con.apply(obj, arguments);
  // 5. Return the object returned by the constructor first
  return ret instanceof Object ? ret : obj;
}
Copy the code

Detailed guide: Wood Poplar Front-end Advanced-Depth Analytical New Principle and Simulation Implementation

3. Write a call function implementation

ES3 of writing:

// Create a unique fn function name
function fnFactory(context) {
    var unique_fn = 'fn';
    while (context.hasOwnProperty(unique_fn)) {
        unique_fn = "fn" + Math.random();
    }
    return unique_fn;
}
Function.prototype.call2 = function (context) {
  context = context ? Object(context) : window;
  var args = [];
  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + '] ');
  }
  var fn = fnFactory(context)
  context[fn] = this;
  var result = eval('context[fn](' + args + ') ');
  delete context[fn];
  return result;
}
Copy the code

ES6 writing:

Function.prototype.call3 = function (context) {
	context = context ? Object(context) : window;
	var fn = Symbol(a); context[fn] =this;
	
	let args = [...arguments].slice(1);
	letresult = context[fn](... args);delete context[fn];
	return result;
}
Copy the code



empty

white




Process analysis:

// Create a unique fn function name
function fnFactory(context) {
    var unique_fn = 'fn';
    while (context.hasOwnProperty(unique_fn)) {
        unique_fn = "fn" + Math.random();
    }
    return unique_fn;
}
Function.prototype.call2 = function(context) {
    // 1. If context is passed in as null or undefined, point to window;
    // 2. If a primitive data type is passed in, the native call calls the Object() conversion
    context = context ? Object(context) : window;
  	// create a unique name for the fn function
   	var fn = fnFactory(context);
  	// 4. This refers to the function that calls call
  	// 5. Assign the function called to context so that when context.fn is executed later, this in fn refers to the context
    context[fn] = this;
    Define an array of strings for each item: ['agruments[1]', 'arguments[2]']
    var args = [];
    // 7. To start with the first term, the 0th term is context
    for (var i = 1, l = arguments.length; i < l; i++) {
        args.push('arguments[' + i + '] ')}// 8. Use eval() to execute fn and pass args in one by one
    var result = eval('context[fn](' + args + ') ');
    // 9. The context is attached with an additional attribute fn, so it needs to be deleted when it is used up
    delete context[fn];
    Function fn may have a return value that needs to be returned
    return result;
}
Copy the code

Test code:

var obj = {
    name: 'objName'
}

function consoleInfo(sex, weight) {
    console.log(this.name, sex, weight)
}
var name = 'globalName';
consoleInfo.call2(obj, 'man'.100); // 'objName' 'man' 100
consoleInfo.call3(obj, 'woman'.120); // 'objName' 'woman' 120
Copy the code

4. Write a apply function implementation by hand

ES3:

// Create a unique fn function name
function fnFactory (context) {
  var unique_fn = 'fn';
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = 'fn' + Math.random();
  }
  return unique_fn;
}
Function.prototype.apply2 = function (context, arr) {
	context = context ? Object(context) : window;
	var fn = fnFactory(context);
	context[fn] = this;
	
	var result;
	if(! arr) { result = context[fn](); }else {
		var args = [];
		for (var i = 0, len = arr.length; i < len; i++) {
			args.push('arr[' + i + '] ');
		}
		result = eval('context[fn](' + args + ') ');
	}
	delete context[fn];
	return result;
}
Copy the code

ES6:

Function.prototype.apply3 = function (context, arr) {
	context = context ? Object(context) : window;
	let fn = Symbol(a); context[fn] =this;
  
  letresult = arr ? context[fn](... arr) : context[fn]();delete context[fn];
  return result;
}
Copy the code



empty

white




Process analysis:

// Create a unique fn function name
function fnFactory (context) {
  var unique_fn = 'fn';
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = 'fn' + Math.random();
  }
  return unique_fn;
}
Function.prototype.apply2 = function (context, arr) {
  // 1. If context is passed in as null or undefined, point to window;
  // 2. If a primitive data type is passed in, the native call calls the Object() conversion
	context = context ? Object(context) : window;
  // create a unique name for the fn function
	var fn = fnFactory(context);
  // 4. This refers to the function that calls call
  // 5. Assign the function called to context so that when context.fn is executed later, this in fn refers to the context
	context[fn] = this;
	
	var result;
  // 6. Check if there is a second argument
	if(! arr) { result = context[fn](); }else {
    ['arr[0]', 'arr[1]']
		var args = [];
		for (var i = 0, len = arr.length; i < len; i++) {
			args.push('arr[' + i + '] ');
		}
    // 8. Use eval() to execute fn and pass args in one by one
		result = eval('context[fn](' + args + ') ');
	}
  // 9. The context is attached with an additional attribute fn, so it needs to be deleted when it is used up
	delete context[fn];
  Function fn may have a return value that needs to be returned
	return result;
}
Copy the code

5. Write a bind function implementation

Tip:

  1. Within the functionthisRepresents the function being called
  2. Context can be passed in and modifiedthisThe point to
  3. Return a function
  4. You can pass in parameters
  5. Currie,
  6. A bound function can also be usednewManipulators create objects and providethisWill be ignored
Function.prototype.bind2 = function (context) {
	if (typeof this! = ="function") {
		throw new Error("Function.prototype.bind - what is trying to be bound is not callable")}var self = this;
	var args = Array.prototype.slice.call(arguments.1);
  
	var fBound = function () {
		var innerArgs = Array.prototype.slice.call(arguments);
		return self.apply(
			this instanceof fNOP ? this : context,
			args.concat(innerArgs)
		)
	}
  
	var fNOP = function () {};
	fNOP.prototype = this.prototype;
	fBound.prototype = new fNOP();
	return fBound;
}
Copy the code



empty

white




Function.prototype.bind2 = function(context) {
    // 1. Check whether bind is a function
    if (typeof this! = ="function") {
        throw new Error("Function.prototype.bind - what is trying to be bound is not callable")}// 2. The outer this refers to the caller (i.e. the function being called).
    var self = this;
    // 3. Collect additional arguments for calling bind
    var args = Array.prototype.slice.call(arguments.1);
    
    // 4. Create a return function
    var fBound = function() {
        // 6. Collect additional arguments passed when calling the new function
        var innerArgs = Array.prototype.slice.call(arguments);
        // 7. Use apply to change the direction of this when calling functions
        When called as a constructor, this represents the newly generated object. When not called as a constructor, context is passed
        return self.apply(
            this instanceof fNOP ? this : context,
            args.concat(innerArgs)
        )
    }
    // 5. Create an empty function and point the prototype to the caller's prototype (in order to use the attributes in the caller's prototype)
    // fBoun. Prototype = this.prototype
    var fNOP = function() {};
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    Return the final result
    return fBound;
}
Copy the code

After the language

The guy who likes Lin Dull also hopes to follow his official account 👇👇👇.

I will update some front-end knowledge content and my original article 🎉 from time to time

Your encouragement is my motivation to continue to create 😊.

Related recommendations:

JavaScript Advanced – Execution Context (Understanding execution Context is enough)

The most detailed BPMN.js textbook in the whole Web

Why don’t you talk about browser caching?

How to Connect your Front-end pages quickly