I got "Maximum call stack size exceeded." error when using Array.apply() to convert a large Uint8Array to an Array.
I found that apply() passes my parameter array as arguments in Array's constructor and function's arguments are stored in stack. Therefore, if I pass a large array to apply(), it'll exceed the stack.
After some searching, I changed to use Array.from() and it solved my issue.
I read the algorithm in ECMAScript but I can't understand it. So, could someone tell me the difference between apply() and from()?
Array.apply calls Function.prototype.apply with a calling context (a this value) of Array. If the second parameter is an array-like object, it will call the Array constructor with all of the elements of that array-like object as arguments. Eg:
Array.apply(null, [1, 2, 3, 4])
results in, and is equivalent to
Array(1, 2, 3, 4)
But argument lists have a size limit. It depends on the engine, but it looks like you probably shouldn't try to pass more than 10,000 arguments or so.
In contrast, Array.from invokes the iterator of the array-like object. If the iterator doesn't exist, but the object has a length property, it will iterate from 0 up to the value of the length - 1 and create an array from those values. Here, since a Uint8Array has an iterator, that iterator is what gets invoked when you use Array.from.
Iterators don't have the same sort of size limit that argument lists have (unless they're spread into an argument list, or something similar). The iterator's .next method is called, returning a value to put into the array, until the iterator is exhausted. It's pretty similar to the following:
const arr = [0, 1, 2];
// Create a new array by iterating through arr's iterator
// just like Array.from is doing:
const newArr = [];
const iterator = arr[Symbol.iterator]();
let iterObj = iterator.next();
while (!iterObj.done) {
newArr.push(iterObj.value);
iterObj = iterator.next();
}
console.log(newArr);
There are no limits on how long the iterator may be, either in the above code, or in Array.from.
So, if the iterable or array-like object you have is very large, constructing an array from it with Array.apply (or by spreading into the Array constructor) may throw an error, whereas using Array.from will not.
Related
I know it is used to make arguments a real Array, but I don‘t understand what happens when using Array.prototype.slice.call(arguments);.
What happens under the hood is that when .slice() is called normally, this is an Array, and then it just iterates over that Array, and does its work.
How is this in the .slice() function an Array? Because when you do:
object.method();
...the object automatically becomes the value of this in the method(). So with:
[1,2,3].slice()
...the [1,2,3] Array is set as the value of this in .slice().
But what if you could substitute something else as the this value? As long as whatever you substitute has a numeric .length property, and a bunch of properties that are numeric indices, it should work. This type of object is often called an array-like object.
The .call() and .apply() methods let you manually set the value of this in a function. So if we set the value of this in .slice() to an array-like object, .slice() will just assume it's working with an Array, and will do its thing.
Take this plain object as an example.
var my_object = {
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three',
'4': 'four',
length: 5
};
This is obviously not an Array, but if you can set it as the this value of .slice(), then it will just work, because it looks enough like an Array for .slice() to work properly.
var sliced = Array.prototype.slice.call( my_object, 3 );
Example: http://jsfiddle.net/wSvkv/
As you can see in the console, the result is what we expect:
['three','four'];
So this is what happens when you set an arguments object as the this value of .slice(). Because arguments has a .length property and a bunch of numeric indices, .slice() just goes about its work as if it were working on a real Array.
The arguments object is not actually an instance of an Array, and does not have any of the Array methods. So, arguments.slice(...) will not work because the arguments object does not have the slice method.
Arrays do have this method, and because the arguments object is very similar to an array, the two are compatible. This means that we can use array methods with the arguments object. And since array methods were built with arrays in mind, they will return arrays rather than other argument objects.
So why use Array.prototype? The Array is the object which we create new arrays from (new Array()), and these new arrays are passed methods and properties, like slice. These methods are stored in the [Class].prototype object. So, for efficiency sake, instead of accessing the slice method by (new Array()).slice.call() or [].slice.call(), we just get it straight from the prototype. This is so we don't have to initialise a new array.
But why do we have to do this in the first place? Well, as you said, it converts an arguments object into an Array instance. The reason why we use slice, however, is more of a "hack" than anything. The slice method will take a, you guessed it, slice of an array and return that slice as a new array. Passing no arguments to it (besides the arguments object as its context) causes the slice method to take a complete chunk of the passed "array" (in this case, the arguments object) and return it as a new array.
Normally, calling
var b = a.slice();
will copy the array a into b. However, we can’t do
var a = arguments.slice();
because arguments doesn’t have slice as a method (it’s not a real array).
Array.prototype.slice is the slice function for arrays. .call runs this slice function, with the this value set to arguments.
Array.prototype.slice.call(arguments) is the old-fashioned way to convert an arguments into an array.
In ECMAScript 2015, you can use Array.from or the spread operator:
let args = Array.from(arguments);
let args = [...arguments];
First, you should read how function invocation works in JavaScript. I suspect that alone is enough to answer your question. But here's a summary of what is happening:
Array.prototype.slice extracts the slice method from Array's prototype. But calling it directly won't work, as it's a method (not a function) and therefore requires a context (a calling object, this), otherwise it would throw Uncaught TypeError: Array.prototype.slice called on null or undefined.
The call() method allows you to specify a method's context, basically making these two calls equivalent:
someObject.slice(1, 2);
slice.call(someObject, 1, 2);
Except the former requires the slice method to exist in someObject's prototype chain (as it does for Array), whereas the latter allows the context (someObject) to be manually passed to the method.
Also, the latter is short for:
var slice = Array.prototype.slice;
slice.call(someObject, 1, 2);
Which is the same as:
Array.prototype.slice.call(someObject, 1, 2);
// We can apply `slice` from `Array.prototype`:
Array.prototype.slice.call([]); //-> []
// Since `slice` is available on an array's prototype chain,
'slice' in []; //-> true
[].slice === Array.prototype.slice; //-> true
// … we can just invoke it directly:
[].slice(); //-> []
// `arguments` has no `slice` method
'slice' in arguments; //-> false
// … but we can apply it the same way:
Array.prototype.slice.call(arguments); //-> […]
// In fact, though `slice` belongs to `Array.prototype`,
// it can operate on any array-like object:
Array.prototype.slice.call({0: 1, length: 1}); //-> [1]
Its because, as MDN notes
The arguments object is not an array. It is similar to an array, but
does not have any array properties except length. For example, it does
not have the pop method. However it can be converted to a real array:
Here we are calling slice on the native object Array and not on its implementation and thats why the extra .prototype
var args = Array.prototype.slice.call(arguments);
Dont forget, that a low-level basics of this behaviour is the type-casting that integrated in JS-engine entirely.
Slice just takes object (thanks to existing arguments.length property) and returns array-object casted after doing all operations on that.
The same logics you can test if you try to treat String-method with an INT-value:
String.prototype.bold.call(11); // returns "<b>11</b>"
And that explains statement above.
Array.prototype.slice=function(start,end){
let res=[];
start=start||0;
end=end||this.length
for(let i=start;i<end;i++){
res.push(this[i])
}
return res;
}
when you do:
Array.prototype.slice.call(arguments)
arguments becomes the value of this in slice ,and then slice returns an array
It uses the slice method arrays have and calls it with its this being the arguments object. This means it calls it as if you did arguments.slice() assuming arguments had such a method.
Creating a slice without any arguments will simply take all elements - so it simply copies the elements from arguments to an array.
Let's assume you have: function.apply(thisArg, argArray )
The apply method invokes a function, passing in the object that will be bound to this
and an optional array of arguments.
The slice() method selects a part of an array, and returns the new array.
So when you call Array.prototype.slice.apply(arguments, [0]) the array slice method is invoked (bind) on arguments.
when .slice() is called normally, this is an Array, and then it just iterates over that Array, and does its work.
//ARGUMENTS
function func(){
console.log(arguments);//[1, 2, 3, 4]
//var arrArguments = arguments.slice();//Uncaught TypeError: undefined is not a function
var arrArguments = [].slice.call(arguments);//cp array with explicity THIS
arrArguments.push('new');
console.log(arrArguments)
}
func(1,2,3,4)//[1, 2, 3, 4, "new"]
Maybe a bit late, but the answer to all of this mess is that call() is used in JS for inheritance.
If we compare this to Python or PHP, for example, call is used respectively as super().init() or parent::_construct().
This is an example of its usage that clarifies all:
function Teacher(first, last, age, gender, interests, subject) {
Person.call(this, first, last, age, gender, interests);
this.subject = subject;
}
Reference: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance
/*
arguments: get all args data include Length .
slice : clone Array
call: Convert Object which include Length to Array
Array.prototype.slice.call(arguments):
1. Convert arguments to Array
2. Clone Array arguments
*/
//normal
function abc1(a,b,c){
console.log(a);
}
//argument
function: function abc2(){
console.log(Array.prototype.slice.call(arguments,0,1))
}
abc1('a','b','c');
//a
abc2('a','b','c');
//a
I have the following piece of code that defines an array, then an iterator object with a generator yields the value from this array and I output each value with the spread operator:
const arr = ['0', '1', '4', 'a', '9'];
const my_obj = {
[Symbol.iterator]: function*() {
for(let index of arr) {
yield `${index}`;
}
}
};
const all = [...my_obj]
console.log(...my_obj)
The result is:
0
1
4
a
9
What I don’t understand is how is the spread operator variable “...my_obj” getting the values of the array if “my_obj” is an object, not an array. From what I understand: “my_obj” is reciving an object and if you apply the spread operator it should get the “key:value”.
Could someone explain how did it obtain the values?
The spread operator and for...of statements call the iterable protocol of an object. Some objects, like Array, String, Set and Map have built in iterable protocols. That means that they have the ##iterator method.
You yourself just created an object and gave it a property of [Symbol.iterator]. Now your object knows what to do when the spread syntax of for...of is called on this object, which is to call this iterator and loop over an iterable object created by the generator function in the [Symbol.iterator] key.
And in your generator function you've specified that on each iteration a value of the arr should be yielded until that loop is complete.
MDN showns an example here where it says:
Some built-in constructs—such as the spread syntax—use the same iteration protocol under the hood:
console.log([...someString]); // ["h", "i"]
A pattern you will see sometimes is that an objects has a values method. Most of the times this is actually a generator function. When the [Symbol.iterator] is called it returns the generator function which then loops over the values in the object.
Down here I've created a little demo which takes in a string and loops over the letters with for...of and lays them out with the spread operator. The [Symbol.iterator] will call a generator function that looks up each letter's position in the alphabet. Both are using the same iterable protocol.
class LettersObjects {
*values() {
// Here we use the iterable protocol of a string.
for (const letter of this.letters) {
const position = this.alphabet.indexOf(letter.toLowerCase()) + 1;
yield position.toString().padStart(2, '0');
}
}
// This is called with for...of and ...spread.
[Symbol.iterator]() {
return this.values();
}
constructor(letters) {
this.letters = letters;
this.alphabet = 'abcdefghijklmnopqrstuvwxyz';
}
}
const letters = new LettersObjects('Hello world');
// Call the iterable protocol on our LetterObjects instance.
for (const letter of letters) {
console.log('loop:', letter);
}
// Call the iterable protocol on our LetterObjects instance.
console.log('Spread:', ...letters);
This happens because an object is an iterable.
The spread operator spreads iterables and not arrays specifically. it can spread objects, even strings for that matter.
Spread operator documentation
The spread operator allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.
We can easily grasp the concept of iterables by making one of our own.
For instance, we have an object that is not an array, but looks suitable for for..of.
When for..of starts, it calls that method once (or errors if not found). The method must return an iterator – an object with the method next.
Onward, for..of works only with that returned object.
When for..of wants the next value, it calls next() on that object.
The result of next() must have the form {done: Boolean, value: any}, where done=true means that the iteration is finished, otherwise value is the next value.
If the object satisfies for iterable, i.e. for..of loop. It can be spread using the spread operator and can be assigned in array because array is also an iterable.
Ref:
iterables in javascript
spread operator
On the one hand, a lack of an equivalent to Python 3's range is an annoyance in ES6. On the other hand, there are lots of workarounds. My question is why one workaround I tried actually works. To illustrate:
[...Array(10).keys()];
In case the reason I find this mysterious is not obvious, note that Array(10).keys() is at least apparently empty.
I'm aware this wastefully creates two arrays, as do most of the popular workarounds, and, that (at the cost of creating a generator function) using a generator can avoid that. E.g.,
[...(function*(){let i = 0; while(i<10) yield i++;})()];
My question is only about why the first workaround produces the desired result.
Edit:
Judging from the answers, some people believe that the evaluation of Array(10) is equivalent to the evaluation of Array.apply(null,Array(10)). They are not. For example, .hasOwnProperty(0) is false for the former but true for the latter. However, I am open to being persuaded they are the same in some way that matters here, since my understanding is clearly lacking at some key point. I suspect that the answer is that the result of iterating over the keys is determine by the length property, which both share, rather than the actual array indexes that have been defined. If so, I would like to know that this behavior is normative.
Array#keys returns an Array iterator. Spread syntax then completely exhausts that iterator by accessing the next value in the iterator until there are no more values. Then it collects all the values from the iterator and spreads them into a new array.
Array(10) creates an array exotic object that does not have any actual integer-indexed keys, but just a length property -- so it would 'empty' but Array(10).keys() is not. The fact is that using Array#keys doesn't depend on actual elements, just the length property. The internal operation CreateArrayIterator creates a key iterator from an array by creating an iterator via the intrinsic %ArrayIteratorPrototype% object. Looking at %ArrayIteratorPrototype%.next(), you'll see that the length of the array is used. For Array#keys the index is continually incremented until it reaches the length of the array. That's how the iterator is created that gives you all the keys of the array without actually having said integer keys in the first place.
If you're curious about the abstract steps, see Section 12.2.5.2 ArrayAcculumation of the ECMAScript Language Specification, particularly the SpreadElement : ... AssignmentExpression production which outlines the process of stepping through an iterator that is used in conjunction with spread syntax.
To see the abstract steps for collecting these values into a new array, see Section 12.2.5.3 Evaluation. Specifically, the ArrayLiteral : [ ElementList ] production is the production [...Array.keys()] falls under. The aforementioned ArrayAcculumation process is performed which aggregates iterates through the iterator and sets them into the new array.
Array.prototype.keys is returning a new Array Iterator.
Array(10) is an array of 10 (empty) slots.
When you spread the array's keys, you are iterating over it and creating a new array. But this time the new array's items are the slots from the first array.
You can see it with for of loop:
const arr = Array(10).keys();
for (let key of arr) {
console.log(key);
}
By the way, you can use Array.from which takes a map function as it's second arguments. so you can just return the index or whatever you want:
const arr = Array.from(Array(10), (_, idx) => idx);
console.log(arr);
Edit
As a followup to your edited question about the Array(10) result:
The DOCS mentions:
If the only argument passed to the Array constructor is an integer
between 0 and 232-1 (inclusive), this returns a new JavaScript array
with its length property set to that number (Note: this implies an
array of arrayLength empty slots, not slots with actual undefined
values). If the argument is any other number, a RangeError exception
is thrown.
So you actually get:
A new array
The length property is updated with the number provided to the
function
You get n-slots
Now .keys will create an Iterator, which will return each slot index as the value.
for example:
let it = Array(10).keys();
and calling:
it.next();
Will return:
{value: 0, done: false}
Calling it again and again will yield
{value: n, done: false}
up until it will get to the last slot:
{value: 9, done: false}
then the next .next call will return
{value: undefined, done: true}
which will flag that this is the end of the iterations.
running example:
const it = Array(10).keys();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next()); // done: true
Array(10) creates a length-10 array, each element of which is undefined.
Array(10).keys() returns an iterator, which iterates over the keys of the array...the numbers 0 through 9.
When the spread syntax is used to define an array literal (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator), it fully-iterates any iterator it's given and adds the returns from the iterator to the created array (in order).
What is the difference between using arguments and Array.prototype.slice.call(arguments,0) in a function?
I don't see there is any much difference between both, so How would I know when I am supposed to use which one?
function arr(){
return arguments; // or return Array.prototype.slice.call(arguments,0);
}
arr([1,2,3],[4,5,6]);
The difference is that arguments is an "array-like" object, not an array.
You can convert the arguments object to a real array by slicing it, like so
Array.prototype.slice.call(arguments, 0);
This gives you an array, with array properties like forEach, pop etc. which objects like arguments don't have (except length, which arguments do have).
It is generally (almost) never a good idea to slice the arguments object, MDN gives the warning
You should not slice on arguments because it prevents optimizations in
JavaScript engines (V8 for example). Instead, try constructing a new
array by iterating through the arguments object.
Also there should be no real need to pass arguments to a function, only to return them.
The arguments object is not a real array. It is a special type of object and does not have any Array properties except "length".
To make an array from the arguments object, use Array.prototype.slice.call(arguments, 0);
arguments variable is special kind of Object, and is not Array. So, you can't use .forEach, .map, '.push' and other array functions with it.
You have to convert arguments to Array and then you can work with value as array
function test(){
console.log(arguments.forEach); // undefined
var argsArray = Array.prototype.slice.call(arguments,0);
console.log(argsArray.forEach); // function
}
test();
I came across the following code:
var f = function () {
var args = Array.prototype.slice.call(arguments).splice(1);
// some more code
};
Basically, the result in args is an array that is a copy of the arguments without its first element.
But what I can't understand exactly is why f's arguments (which is an object that holds the function's inputted arguments into an array-like object) object is being passed to the slice method and how slice(1) is removing the first element (positioned at index 0).
Can anyone please explain it for me?
P.S. The code is from this partial application function
<Note>
The actual code from that linked answer is:
var args = Array.prototype.slice.call(arguments, 1);
i.e. "slice", not "splice"
</Note>
First of all, the slice method is often used to make a copy of the array it's called on:
var a = ['a', 'b', 'c'];
var b = a.slice(); // b is now a copy of a
var c = a.slice(1); // c is now ['b', 'c']
So the short answer is that the code is basically emulating:
arguments.slice(1); // discard 1st argument, gimme the rest
However you can't do that directly. The special arguments object (available inside the execution context of all JavaScript functions), although Array-like in that it supports indexing via the [] operator with numeric keys, is not actually an Array; You can't .push onto it, .pop off it, or .slice it, etc.
The way the code accomplishes this is by "tricking" the slice function (which again is not available on the arguments object) to run in the context of arguments, via Function.prototype.call:
Array.prototype.slice // get a reference to the slice method
// available on all Arrays, then...
.call( // call it, ...
arguments, // making "this" point to arguments inside slice, and...
1 // pass 1 to slice as the first argument
)
Array.prototype.slice.call(arguments).splice(1) accomplishes the same thing, but makes an extraneous call to splice(1), which removes elements from the array returned from Array.prototype.slice.call(arguments) starting at index 1 and continuing to the end of the array. splice(1) doesn't work in IE (it's technically missing a 2nd parameter telling it how many items to remove that IE and ECMAScript require).
var args = Array.prototype.slice.call(arguments).splice(1);
First takes a copy of arguments(*), then removes all but the first item from it (in a non-standard way), and assigns those items being removed to args.
The extra array being produced, then altered and thrown away is quite redundant. It would be better to say — as the version in the answer you linked to indeed does:
var args = Array.prototype.slice.call(arguments, 1);
Partial function application is also a feature of the function.bind method, being standardised by ECMAScript Fifth Edition. Until browsers have implemented it, you can pick up an a fallback JS-native version from the bottom of this answer.
*: array.slice() is the normal idiom for copying an array, and array.slice(1) for taking the tail. It has it be called explicitly through the Array.prototype because arguments is not an Array, even though it looks just like one, so doesn't have the normal array methods. This is another of JavaScript's weird mistakes.
You quite often see people using the Array.prototype methods on objects that aren't Arrays; the ECMAScript Third Edition standard goes out of its way to say this is OK to do for the arguments array-like, but not that you may also do it on other array-likes that may be host objects, such as NodeList or HTMLCollection. Although you might get away with calling Array.prototype methods on a non-Array in many browsers today, the only place it is actually safe to do so is on arguments.
The returned value of a splice is an array of the elements that were removed,
but the original array (or array-like object), is truncated at the splice index.
Making a copy with slice preserves the original arguments array,
presumably for use later in the function.
In this case the same result can be had with args = [].slice.call(arguments, 1)
function handleArguments(){
var A= [].slice.call(arguments).splice(1);
//arguments is unchanged
var s= 'A='+A+'\narguments.length='+arguments.length;
var B= [].splice.call(arguments, 1);
// arguments now contains only the first parameter
s+= '\n\nB='+B+'\narguments.length='+arguments.length;
return s;
}
// test
alert(handleArguments(1, 2, 3, 4));
returned value:
//var A= [].slice.call(arguments).splice(1);
A=2,3,4
arguments.length=4
//var B= [].splice.call(arguments, 1);
B=2,3,4
arguments.length=1