Call() and apply()
The call() method calls a function that has a specified
this
Value and separately suppliedparameters
(List of parameters)。
//The difference is thatcall()The method accepted isa list of several parameters,andapply()The method accepted isAn array of multiple parameters
var func = function(arg1, arg2) {
...
};
func.call(this, arg1, arg2); / / Use call, parameter list
func.apply(this, [arg1, arg2]) / / Use apply, parameter array
Scenes to be used:
Here are some common usages
1. merge two arrays
var vegetables = ['parsnip', 'potato'];
var moreVegs = ['celery', 'beetroot'];
// merge the second array into the first array
// is equivalent to vegetables.push('celery', 'beetroot');
Array.prototype.push.apply(vegetables, moreVegs);
// 4
vegetables;
// ['parsnip', 'potato', 'celery', 'beetroot']
When the second array (as in the example moreVegs) Don't use this method to merge arrays when they are too large, becauseThe number of parameters a function can accept is limited. Different engines have different limits. The JS core is limited to 65535. Some engines throw exceptions, some don't throw exceptions but lose redundant parameters.
How to solve it? The method isLoop the parameter array into a target method after it is diced
function concatOfArray(arr1, arr2) {
var QUANTUM = 32768;
for (var i = 0, len = arr2.length; i < len; i += QUANTUM) {
Array.prototype.push.apply(
arr1,
arr2.slice(i, Math.min(i + QUANTUM, len) )
);
}
return arr1;
}
// verification code
var arr1 = [-3, -2, -1];
var arr2 = [];
for(var i = 0; i < 1000000; i++) {
arr2.push(i);
}
Array.prototype.push.apply(arr1, arr2);
// Uncaught RangeError: Maximum call stack size exceeded
concatOfArray(arr1, arr2);
// (1000003) [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]
2. merge two arrays
var numbers = [5, 458 , 120 , -215 ];
Math.max.apply(Math, numbers); //458
Math.max.call(Math, 5, 458 , 120 , -215); //458
// ES6
Math.max.call(Math, ...numbers); // 458
3. verify whether it is an array
function isArray(obj){
return Object.prototype.toString.call(obj) === '[object Array]';
}
isArray([1, 2, 3]);
// true
// Use toString() directly
[1, 2, 3].toString(); // "1,2,3"
"123".toString(); // "123"
123.toString(); // SyntaxError: Invalid or unexpected token
Number(123).toString(); // "123"
Object(123).toString(); // "123"
Another verify Array Metthod
var toStr = Function.prototype.call.bind(Object.prototype.toString);
function isArray(obj){
return toStr(obj) === '[object Array]';
}
isArray([1, 2, 3]);
// true
// Use the modified toStr
toStr([1, 2, 3]); // "[object Array]"
toStr("123"); // "[object String]"
toStr(123); // "[object Number]"
toStr(Object(123)); // "[object Number]"
- In the above method first
Function.prototype.call(Function)
specifies athis
Value, then.bind
Return a new function that will always beObject.prototype.toString
Set to pass in parameters. Actually equivalent toObject.prototype.toString.call()
4. the class array object (Array-like Object) using the array method
var domNodes = document.getElementsByTagName("*");
domNodes.unshift("h1");
// TypeError: domNodes.unshift is not a function
var domNodeArrays = Array.prototype.slice.call(domNodes);
domNodeArrays.unshift("h1"); // 505 data is different in different environments
// (505) ["h1", html.gr__hujiang_com, head, meta, ...]
Copy code
Class array objects have the following two properties
- with: a numeric index subscript to the object element and length Attributes
- does not have:
push
、shift
、forEach
as well asindexOf
Methods such as array objects
To be explained, the class array object is a Object
. There is a name in JS Class array-Object structure, such as arguments
Objects, as well as returned by the DOM API NodeList Objects belong to class array objects
, class array objects cannot be used push/pop/shift/unshift Array method, pass Array.prototype.slice.call
to convert to a real array and you can use the methods.
Class array object to arrayOther methods:
// The above code is equivalent to
var arr = [].slice.call(arguments);
ES6:
let arr = Array.from(arguments);
let arr = [...arguments];
Copy code
Array.from() You can turn two types of objects into real arrays: Class array Object and Traversable(iterable) objects (including ES6's new data structures Set and Map
).
Question : Why do you have class array objects? Or why the class array object is what solves the problem?
JavaScript typed arrays are an array-like Object And provides a mechanism for accessing raw binary data. Array-stored objects can be dynamically increased and decreased, and any JavaScript value can be stored. The JavaScript engine does some internal optimizations so that operations on arrays can be fast. However, as web applications become more powerful, especially with new additions such as audio and video editing, access to raw data from WebSockets, etc., it's clear that sometimes using JavaScript code can quickly and easily pass typed arrays. It would be very helpful to manipulate the raw binary data.
In a word, you can manipulate complex data faster.
5. call the parent constructor to achieve inheritance
function SuperType(){
this.color=["red", "green", "blue"];
}
function SubType(){
// core code, inherited from SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
console.log(instance1.color);
// ["red", "green", "blue", "black"]
var instance2 = new SubType();
console.log(instance2.color);
// ["red", "green", "blue"]
Copy code
Disadvantages:
add a throttle valve to the operation function.
- Can only inherit the parent class Instance Properties and methods, cannot inherit prototype properties/methods
- Unable to implement reuse, each subclass has a copy of the parent class instance function, affecting performance
Call simulation implementation
The following content refers to JavaScript deep call and apply simulation implementation
var value = 1;
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
Copy code
Through the above introduction we know that call() There are two main points
- call() Changed the direction of this
- function bar Executed
Simulation implementation first step
var foo = {
value: 1,
bar: function() {
console.log(this.value);
}
};
foo.bar(); // 1
Copy code
This change can be implemented: change the pointer to this and execute the function bar. But writing this has side effects
, that is, giving foo Added an additional attribute, how to solve it?
The solution is simple, use delete
Just delete it.
So as long as the following 3 steps are implemented, the simulation can be implemented.
- Set the function to the properties of the object: foo.fn = bar
- the execution function: foo.fn()
- delete the function: delete foo.fn
- The code is implemented as follows:
// first edition
Function.prototype.call2 = function(context) {
// First get the function that calls call, use this to get
context.fn = this; // foo.fn = bar
context.fn(); // foo.fn()
delete context.fn; // delete foo.fn
}
// have a test
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call2(foo); // 1
Copy code
perfect!
Simulation implementation of the second step
The first version has a problem, that is the function bar Can't receive parameters, so we can get from arguments Get the parameters, take the second to the last parameter and put it in the array, why should we discard the first parameter, because the first parameter is this
。
The method of converting an array object into an array has already been introduced above, but this is done using the ES3 scheme.
Anti shake idea
:
- I first set a delay time (timer), in this time, if you operate five times,
- I will clear the previous four operations (clear timer triggered function), do not let the previous four operations to execute.
- When the delay time is up, you can perform your fifth operation.
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
Copy code
The parameter array is fixed, the next thing to do is to execute the function.context.fn()。
context.fn( args.join(',') ); // That does not work
Copy code
The above direct call will definitely not work,args.join(',')Will return a string and will not execute.
Adopted here eval
The method is implemented to form a function.
eval('context.fn(' + args +')')
Copy code
In the above code args Will be called automatically args.toString() Method because'context.fn(' + args +')' Essentially string concatenation, it will be called automatically toString() Method, the following code:
var args = ["a1", "b2", "c3"];
console.log(args);
// ["a1", "b2", "c3"]
console.log(args.toString());
// a1,b2,c3
console.log("" + args);
// a1,b2,c3
Copy code
So the second version is implemented, the code is as follows:
// second edition
Function.prototype.call2 = function(context) {
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
eval('context.fn(' + args +')');
delete context.fn;
}
// have a test
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call2(foo, 'kevin', 18);
// kevin
// 18
// 1
Copy code
Simulation implementation of the third step
There are 2 more details to note:
- this parameter can be passednull OrundefinedAt this time this points to the window
- this parameter can pass basic type data, the original call will automatically use Object () conversion
- the function can have a return value
// Third edition
Function.prototype.call2 = function (context) {
context = context ? Object(context) : window; // implementation details 1 and 2
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn
return result; // implementation details 2
}
// have a test
var value = 2;
var obj = {
value: 1
}
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age
}
}
function foo() {
console.log(this);
}
bar.call2(null); // 2
foo.call2(123); // Number {123, fn: ƒ}
bar.call2(obj, 'kevin', 18);
// 1
// {
// value: 1,
// name: 'kevin',
// age: 18
// }
Copy code
Call and apply simulation implementation summary
Call simulation implementation
ES3:
Function.prototype.call = function (context) {
context = context ? Object(context) : window;
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn
return result;
}
Copy code
ES6:
Function.prototype.call = function (context) {
context = context ? Object(context) : window;
context.fn = this;
let args = [...arguments].slice(1);
let result = context.fn(...args);
delete context.fn
return result;
}
Copy code
Analog implementation of apply
ES3
Function.prototype.apply = function (context, arr) {
context = context ? Object(context) : window;
context.fn = this;
var result;
/ / Determine whether there is a second parameter
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 code
ES6:
Function.prototype.apply = function (context, arr) {
context = context ? Object(context) : window;
context.fn = this;
let result;
if (!arr) {
result = context.fn();
} else {
result = context.fn(...arr);
}
delete context.fn
return result;
}
Copy code
Top comments (0)