Creating range in JavaScript - strange syntax
Understanding this "hack" requires understanding several things:
- Why we don't just do
Array(5).map(...)
- How
Function.prototype.apply
handles arguments - How
Array
handles multiple arguments - How the
Number
function handles arguments - What
Function.prototype.call
does
They're rather advanced topics in javascript, so this will be more-than-rather long. We'll start from the top. Buckle up!
1. Why not just Array(5).map
?
What's an array, really? A regular object, containing integer keys, which map to values. It has other special features, for instance the magical length
variable, but at it's core, it's a regular key => value
map, just like any other object. Let's play with arrays a little, shall we?
var arr = ['a', 'b', 'c'];
arr.hasOwnProperty(0); //true
arr[0]; //'a'
Object.keys(arr); //['0', '1', '2']
arr.length; //3, implies arr[3] === undefined
//we expand the array by 1 item
arr.length = 4;
arr[3]; //undefined
arr.hasOwnProperty(3); //false
Object.keys(arr); //['0', '1', '2']
We get to the inherent difference between the number of items in the array, arr.length
, and the number of key=>value
mappings the array has, which can be different than arr.length
.
Expanding the array via arr.length
does not create any new key=>value
mappings, so it's not that the array has undefined values, it does not have these keys. And what happens when you try to access a non-existent property? You get undefined
.
Now we can lift our heads a little, and see why functions like arr.map
don't walk over these properties. If arr[3]
was merely undefined, and the key existed, all these array functions would just go over it like any other value:
//just to remind you
arr; //['a', 'b', 'c', undefined];
arr.length; //4
arr[4] = 'e';
arr; //['a', 'b', 'c', undefined, 'e'];
arr.length; //5
Object.keys(arr); //['0', '1', '2', '4']
arr.map(function (item) { return item.toUpperCase() });
//["A", "B", "C", undefined, "E"]
I intentionally used a method call to further prove the point that the key itself was never there: Calling undefined.toUpperCase
would have raised an error, but it didn't. To prove that:
arr[5] = undefined;
arr; //["a", "b", "c", undefined, "e", undefined]
arr.hasOwnProperty(5); //true
arr.map(function (item) { return item.toUpperCase() });
//TypeError: Cannot call method 'toUpperCase' of undefined
And now we get to my point: How Array(N)
does things. Section 15.4.2.2 describes the process. There's a bunch of mumbo jumbo we don't care about, but if you manage to read between the lines (or you can just trust me on this one, but don't), it basically boils down to this:
function Array(len) {
var ret = [];
ret.length = len;
return ret;
}
(operates under the assumption (which is checked in the actual spec) that len
is a valid uint32, and not just any number of value)
So now you can see why doing Array(5).map(...)
wouldn't work - we don't define len
items on the array, we don't create the key => value
mappings, we simply alter the length
property.
Now that we have that out of the way, let's look at the second magical thing:
2. How Function.prototype.apply
works
What apply
does is basically take an array, and unroll it as a function call's arguments. That means that the following are pretty much the same:
function foo (a, b, c) {
return a + b + c;
}
foo(0, 1, 2); //3
foo.apply(null, [0, 1, 2]); //3
Now, we can ease the process of seeing how apply
works by simply logging the arguments
special variable:
function log () {
console.log(arguments);
}
log.apply(null, ['mary', 'had', 'a', 'little', 'lamb']);
//["mary", "had", "a", "little", "lamb"]
//arguments is a pseudo-array itself, so we can use it as well
(function () {
log.apply(null, arguments);
})('mary', 'had', 'a', 'little', 'lamb');
//["mary", "had", "a", "little", "lamb"]
//a NodeList, like the one returned from DOM methods, is also a pseudo-array
log.apply(null, document.getElementsByTagName('script'));
//[script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script]
//carefully look at the following two
log.apply(null, Array(5));
//[undefined, undefined, undefined, undefined, undefined]
//note that the above are not undefined keys - but the value undefined itself!
log.apply(null, {length : 5});
//[undefined, undefined, undefined, undefined, undefined]
It's easy to prove my claim in the second-to-last example:
function ahaExclamationMark () {
console.log(arguments.length);
console.log(arguments.hasOwnProperty(0));
}
ahaExclamationMark.apply(null, Array(2)); //2, true
(yes, pun intended). The key => value
mapping may not have existed in the array we passed over to apply
, but it certainly exists in the arguments
variable. It's the same reason the last example works: The keys do not exist on the object we pass, but they do exist in arguments
.
Why is that? Let's look at Section 15.3.4.3, where Function.prototype.apply
is defined. Mostly things we don't care about, but here's the interesting portion:
- Let len be the result of calling the [[Get]] internal method of argArray with argument "length".
Which basically means: argArray.length
. The spec then proceeds to do a simple for
loop over length
items, making a list
of corresponding values (list
is some internal voodoo, but it's basically an array). In terms of very, very loose code:
Function.prototype.apply = function (thisArg, argArray) {
var len = argArray.length,
argList = [];
for (var i = 0; i < len; i += 1) {
argList[i] = argArray[i];
}
//yeah...
superMagicalFunctionInvocation(this, thisArg, argList);
};
So all we need to mimic an argArray
in this case is an object with a length
property. And now we can see why the values are undefined, but the keys aren't, on arguments
: We create the key=>value
mappings.
Phew, so this might not have been shorter than the previous part. But there'll be cake when we finish, so be patient! However, after the following section (which'll be short, I promise) we can begin dissecting the expression. In case you forgot, the question was how does the following work:
Array.apply(null, { length: 5 }).map(Number.call, Number);
3. How Array
handles multiple arguments
So! We saw what happens when you pass a length
argument to Array
, but in the expression, we pass several things as arguments (an array of 5 undefined
, to be exact). Section 15.4.2.1 tells us what to do. The last paragraph is all that matters to us, and it's worded really oddly, but it kind of boils down to:
function Array () {
var ret = [];
ret.length = arguments.length;
for (var i = 0; i < arguments.length; i += 1) {
ret[i] = arguments[i];
}
return ret;
}
Array(0, 1, 2); //[0, 1, 2]
Array.apply(null, [0, 1, 2]); //[0, 1, 2]
Array.apply(null, Array(2)); //[undefined, undefined]
Array.apply(null, {length:2}); //[undefined, undefined]
Tada! We get an array of several undefined values, and we return an array of these undefined values.
The first part of the expression
Finally, we can decipher the following:
Array.apply(null, { length: 5 })
We saw that it returns an array containing 5 undefined values, with keys all in existence.
Now, to the second part of the expression:
[undefined, undefined, undefined, undefined, undefined].map(Number.call, Number)
This will be the easier, non-convoluted part, as it doesn't so much rely on obscure hacks.
4. How Number
treats input
Doing Number(something)
(section 15.7.1) converts something
to a number, and that is all. How it does that is a bit convoluted, especially in the cases of strings, but the operation is defined in section 9.3 in case you're interested.
5. Games of Function.prototype.call
call
is apply
's brother, defined in section 15.3.4.4. Instead of taking an array of arguments, it just takes the arguments it received, and passes them forward.
Things get interesting when you chain more than one call
together, crank the weird up to 11:
function log () {
console.log(this, arguments);
}
log.call.call(log, {a:4}, {a:5});
//{a:4}, [{a:5}]
//^---^ ^-----^
// this arguments
This is quite wtf worthy until you grasp what's going on. log.call
is just a function, equivalent to any other function's call
method, and as such, has a call
method on itself as well:
log.call === log.call.call; //true
log.call === Function.call; //true
And what does call
do? It accepts a thisArg
and a bunch of arguments, and calls its parent function. We can define it via apply
(again, very loose code, won't work):
Function.prototype.call = function (thisArg) {
var args = arguments.slice(1); //I wish that'd work
return this.apply(thisArg, args);
};
Let's track how this goes down:
log.call.call(log, {a:4}, {a:5});
this = log.call
thisArg = log
args = [{a:4}, {a:5}]
log.call.apply(log, [{a:4}, {a:5}])
log.call({a:4}, {a:5})
this = log
thisArg = {a:4}
args = [{a:5}]
log.apply({a:4}, [{a:5}])
The later part, or the .map
of it all
It's not over yet. Let's see what happens when you supply a function to most array methods:
function log () {
console.log(this, arguments);
}
var arr = ['a', 'b', 'c'];
arr.forEach(log);
//window, ['a', 0, ['a', 'b', 'c']]
//window, ['b', 1, ['a', 'b', 'c']]
//window, ['c', 2, ['a', 'b', 'c']]
//^----^ ^-----------------------^
// this arguments
If we don't provide a this
argument ourselves, it defaults to window
. Take note of the order in which the arguments are provided to our callback, and let's weird it up all the way to 11 again:
arr.forEach(log.call, log);
//'a', [0, ['a', 'b', 'c']]
//'b', [1, ['a', 'b', 'c']]
//'b', [2, ['a', 'b', 'c']]
// ^ ^
Whoa whoa whoa...let's back up a bit. What's going on here? We can see in section 15.4.4.18, where forEach
is defined, the following pretty much happens:
var callback = log.call,
thisArg = log;
for (var i = 0; i < arr.length; i += 1) {
callback.call(thisArg, arr[i], i, arr);
}
So, we get this:
log.call.call(log, arr[i], i, arr);
//After one `.call`, it cascades to:
log.call(arr[i], i, arr);
//Further cascading to:
log(i, arr);
Now we can see how .map(Number.call, Number)
works:
Number.call.call(Number, arr[i], i, arr);
Number.call(arr[i], i, arr);
Number(i, arr);
Which returns the transformation of i
, the current index, to a number.
In conclusion,
The expression
Array.apply(null, { length: 5 }).map(Number.call, Number);
Works in two parts:
var arr = Array.apply(null, { length: 5 }); //1
arr.map(Number.call, Number); //2
The first part creates an array of 5 undefined items. The second goes over that array and takes its indices, resulting in an array of element indices:
[0, 1, 2, 3, 4]
Does JavaScript have a method like range() to generate a range within the supplied bounds?
It works for characters and numbers, going forwards or backwards with an optional step.
var range = function(start, end, step) {
var range = [];
var typeofStart = typeof start;
var typeofEnd = typeof end;
if (step === 0) {
throw TypeError("Step cannot be zero.");
}
if (typeofStart == "undefined" || typeofEnd == "undefined") {
throw TypeError("Must pass start and end arguments.");
} else if (typeofStart != typeofEnd) {
throw TypeError("Start and end arguments must be of same type.");
}
typeof step == "undefined" && (step = 1);
if (end < start) {
step = -step;
}
if (typeofStart == "number") {
while (step > 0 ? end >= start : end <= start) {
range.push(start);
start += step;
}
} else if (typeofStart == "string") {
if (start.length != 1 || end.length != 1) {
throw TypeError("Only strings with one character are supported.");
}
start = start.charCodeAt(0);
end = end.charCodeAt(0);
while (step > 0 ? end >= start : end <= start) {
range.push(String.fromCharCode(start));
start += step;
}
} else {
throw TypeError("Only string and number types are supported");
}
return range;
}
jsFiddle.
If augmenting native types is your thing, then assign it to Array.range
.
var range = function(start, end, step) { var range = []; var typeofStart = typeof start; var typeofEnd = typeof end;
if (step === 0) { throw TypeError("Step cannot be zero."); }
if (typeofStart == "undefined" || typeofEnd == "undefined") { throw TypeError("Must pass start and end arguments."); } else if (typeofStart != typeofEnd) { throw TypeError("Start and end arguments must be of same type."); }
typeof step == "undefined" && (step = 1);
if (end < start) { step = -step; }
if (typeofStart == "number") {
while (step > 0 ? end >= start : end <= start) { range.push(start); start += step; }
} else if (typeofStart == "string") {
if (start.length != 1 || end.length != 1) { throw TypeError("Only strings with one character are supported."); }
start = start.charCodeAt(0); end = end.charCodeAt(0);
while (step > 0 ? end >= start : end <= start) { range.push(String.fromCharCode(start)); start += step; }
} else { throw TypeError("Only string and number types are supported"); }
return range;
}
console.log(range("A", "Z", 1));console.log(range("Z", "A", 1));console.log(range("A", "Z", 3));
console.log(range(0, 25, 1));
console.log(range(0, 25, 5));console.log(range(20, 5, 5));
How to initialize an array's length in JavaScript?
Why do you want to initialize the length? Theoretically there is no need for this. It can even result in confusing behavior, because all tests that use the
length
to find out whether an array is empty or not will report that the array is not empty.
Some tests show that setting the initial length of large arrays can be more efficient if the array is filled afterwards, but the performance gain (if any) seem to differ from browser to browser.jsLint does not like
new Array()
because the constructer is ambiguous.new Array(4);
creates an empty array of length 4. But
new Array('4');
creates an array containing the value
'4'
.
Regarding your comment: In JS you don't need to initialize the length of the array. It grows dynamically. You can just store the length in some variable, e.g.
var data = [];
var length = 5; // user defined length
for(var i = 0; i < length; i++) {
data.push(createSomeObject());
}
do something N times (declarative syntax)
This answer is based on Array.forEach
, without any library, just native vanilla.
To basically call something()
3 times, use:
[1,2,3].forEach(function(i) {
something();
});
considering the following function:
function something(){ console.log('something') }
The output will be:
something
something
something
To complete this questions, here's a way to do call something()
1, 2 and 3 times respectively:
It's 2017, you may use ES6:
[1,2,3].forEach(i => Array(i).fill(i).forEach(_ => {
something()
}))
or in good old ES5:
[1,2,3].forEach(function(i) {
Array(i).fill(i).forEach(function() {
something()
})
}))
In both cases, the output will be
The output will be:
something
something
something
something
something
something
(once, then twice, then 3 times)
Check if a value is within a range of numbers
You're asking a question about numeric comparisons, so regular expressions really have nothing to do with the issue. You don't need "multiple if
" statements to do it, either:
if (x >= 0.001 && x <= 0.009) {
// something
}
You could write yourself a "between()" function:
function between(x, min, max) {
return x >= min && x <= max;
}
// ...
if (between(x, 0.001, 0.009)) {
// something
}
JS/Node: A function that takes a number N and returns an array with the values [0, 1, ... N-1]
http://underscorejs.org/#range
_.range(10);
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_.range(1, 11);
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
_.range(0, 30, 5);
=> [0, 5, 10, 15, 20, 25]
_.range(0, -10, -1);
=> [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
_.range(0);
=> []
Functional approach to basic array construction
Not in ES5, there's no real functional equivalent to it, as you have to have something which has an amount of 20 to apply map to...
var my20ElementArray = [0,1,2,3,4,5,6,7,8,9,10];
var myArray = my20ElementArray.map(Math.random);
You could create an xrange-like function what is in Python but that would just hide this "unused" variable inside a function.
Create simple array [x..y] with JavaScript
Well you could make a simple function...
function range(min, max) {
var len = max - min + 1;
var arr = new Array(len);
for (var i=0; i<len; i++) {
arr[i] = min + i;
}
return arr;
}
range(1,10);
// [1,2,3,4,5,6,7,8,9,10]
This answer is not the smallest amount of code, but it's very readable and tremendously faster than any other solution provided here.
Create an array with all numbers from min to max without a loop
You can think of a "functional" definition of range:
range(low, hi) = [], if low > hi
range(low, hi) = [low] (+) range(low+1,hi), otherwise,
which leads to the JS definition:
function range(low,hi){
function rangeRec(low, hi, vals) {
if(low > hi) return vals;
vals.push(low);
return rangeRec(low+1,hi,vals);
}
return rangeRec(low,hi,[]);
}
Related Topics
Are There JavaScript or Ruby Versions of "HTML Tidy"
Inline Ruby in :JavaScript Haml Tag
Explanation of [].Slice.Call in JavaScript
Is There an Internet Explorer Approved Substitute for Selectionstart and Selectionend
What Is the Lifecycle of an Angularjs Controller
Drawing a Line with Three.Js Dynamically
Resize Svg When Window Is Resized in D3.Js
Error: Require() of Es Modules Is Not Supported When Importing Node-Fetch
Firebase Cloud Function Won't Store Cookie Named Other Than "_Session"
How Should Look a Application.Scss File in Ruby
How to Read Console Logs of Wkwebview Programmatically
The .Replace() Method Does Change the String in Place
Using Socket.Io in Express 4 and Express-Generator's /Bin/Www
Calling Member Function of Number Literal
Dynamically Loading a Typescript Class (Reflection for Typescript)