I am confused about placement of operators:
Given:
var a = [0, 1, 2];
So far as I can tell, each of the following is correct:
var len = a.length;
var lastElt = a.pop();
var str = String(a);
var typeStr = typeof a;
Is there an easy way to remember/think about whether the operator goes before or after the operand and whether it requires parentheses? Or is it simply rote memorization?
Thanks.
Let's go:
case #1
var len = a.length;
In this case you're calling the method lenght of the array store in the variable a.
case #2
var lastElt = a.pop();
As before, here you're calling the method pop of the array.
case #3
var str = String(a);
You aren't not calling any array's method. Here you're casting the array. i.e. you are stringifying the array.
case #4
var typeStr = typeof a;
Here you also aren't calling any array's method. You are calling the typeof method of the window object and passing the array a as an argument.
As you can see. In the first two cases, you are calling array's methods. In the last two cases, you are calling window object's methods and passing your array as an argument.
Your list there mostly consists of things that are not operators:
length is a property belonging to Array objects. It returns the number of items in the Array.
pop() is a method belonging to Array objects. It removes the last item from the Array and retuns it.
String() is a global object which is the constructor for a String object. It takes any object as a parameter and converts it to a String
typeof is an operator. It returns a string that indicates the type of the subsequent operand.
For more reference, here is some information about JavaScript operators.
I highly recommend searching Google for some beginning JavaScript tutorials so you can learn the basic concepts.
Thanks for all the prompt replies. In reply to the question 'What do you mean by "operator" here?' I come from a background of mathematics and (lately) c programming. By "operator" I wanted to speak abstractly about anything that mapped its argument to something useful, using it to include methods, properties, etc., without enumerating them. My attempt to abstract these is probably the source of my confusion. I now understand that what is required is rote memorization, something like declensions in Latin ;-).
Operators refer to +,=*,/ etc.
I don't think there is an easy way to remember. If it is a 'property', there will be no parentheses. If it is a function, there will be parentheses. I always remember that length is a property of an array, and that push and pop are actions you can do to the array. Type casting always starts with the 'type' capitalized followed by what you want casted in the parenthesis. typeof is just a weird one.
Related
This question sparked when I was setting the base case of the following code:
function withoutReverse(str, arrayFor = []){
arrayFor.unshift(str[0]);
if (str.length === 1)
return arrayFor.join("");
else return withoutReverse(str.slice(1), arrayFor) }
let hola = "hello";
withoutReverse(hola);//-->olleh
I thought that the base should be str.length === 0, cause I assumed that unshift literally took each element from the source object to put it into arrayFor. Then, I realized that that was what I was using slice for.
I didn't found a conclusive information in mdn. Am I right? unshift() doesn't really take those values (when stored in an object)? if so, how is that handled in memory?
Thank you
When you get to the 5th call:
console.log(str) // "o"
arrayFor.unshift(str[0]); // str[0] === "o"
console.log(str.length) // "1" because accessing the property here does not change it.
Therefore your exit condition is now true.
For functions in JS:
numbers/strings/booleans are passed as values
arrays/objects are passed as references
Extra info about strings:
Unlike some programming languages (such as C), JavaScript strings are immutable. This means that once a string is created, it is not possible to modify it.
Source MDN
return 'cat'[1] // returns "a"
When using bracket notation for character access, attempting to delete or assign a value to these properties will not succeed. The properties involved are neither
writable nor configurable. (See Object.defineProperty() for more information.)
Source MDN
This question already has answers here:
Why can I access object property with an array?
(2 answers)
Closed 2 years ago.
I ran into a scenario where JavaScript behaves in a way that is somewhat baffling to me.
Let's say we have an object with two keys foo & bar.
a = { foo: 1, bar: 2 }
Then, I have an array of strings, in this case one 'foo'
b = ['foo']
I would expect the following:
a[b] == undefined
a[b[0]] == 1
BUT, this is what happens:
a[b] == 1
a[b[0]] == 1
Why does JavaScript convert ['foo'] -> 'foo' when used as a key?
Does anyone out there know the reason?
How can this be prevented?
let a = { foo: 1, bar: 2 }
let b = ['foo']
console.log(a[b] == 1) // expected a[b] to be undefined
console.log(a[b[0]] == 1) // expected a[b] to be 1
All the object keys are string, so it eventually convert everything you place inside [] (Bracket notation) to string, if it's an expression it evaluates the expression and convert it's value to string and use as key
console.log(['foo'].toString())
Have a look at this example to understand, here [a] eventually converts a toString using a.toString() and then set it as key to b object
let a = { a : 1}
let b = {
[a] : a
}
// object converted to string
console.log(a.toString())
// object built using [] computed property access
console.log(b)
How can i stop this
In practical scenarios you should never do this, but just to illustrate, you can intercept or override the toString method of your object and return value as string with [] around:
let a = { foo: 1, bar: 2 }
let b = ['foo']
b.toString = function() {
let string = this.join(',')
return "[" + string + "]"
}
console.log(b.toString())
console.log(a[b])
When using an array as a key, javascript call the 'toString()' method of that array, and then try to find the stringified version of the array as the key. And if you call ['foo'].toString() you see this method returns "foo".
Why does JavaScript convert ['foo'] -> 'foo' when used as a key?
Does anyone out there know the reason?
Any time there is confusion as to why JavaScript acts in a way which may be unexpected, then looking at the language definition is the surefire way to exactly figure out what happened.
https://www.ecma-international.org/ecma-262/10.0/ is the most current language definition at the time of posting this.
First, you will want to find the area pertaining to Array access. It is in language lingo though.
12.3.2.1 Runtime Semantics: Evaluation
MemberExpression : MemberExpression [ Expression ]
...
3. Let propertyNameReference be the result of evaluating Expression.
4. Let propertyNameValue be ? GetValue(propertyNameReference).
6. Let propertyKey be ? ToPropertyKey(propertyNameValue).
So, what is happening here is you are accessing your array (the MemberExpression) using [] with an Expression.
In order to access with [] the Expression will be evaluated, and then GetValue will be called. Then ToPropertyKey will be called.
propertyNameReference = Evaluate Expression b = b
propertyNameValue = GetValue(propertyNameReference) = ['foo']
propertyKey = ToPropertyKey(propertyNameValue) = 'foo'
ToPropertyKey, in our situation, leads to ToPrimitive and then to ToOrdinaryPrimitive which states that we should call "toString" on the argument (['foo'] in our case).
This is where the implementation comes in to play. On the implementation side,
The Array object overrides the toString method of Object. For Array objects, the toString method joins the array and returns one string containing each array element separated by commas" MDN - Array toString
When there is only one value in the array, the result will simply be that value.
How can this be prevented?
This is the current way it is implemented. In order to change that, you must either change the default implementation, use detection to prevent the call, or use guidance to prevent the call.
Guidance
Document and enforce calling mechanisms in your code. This may not always be possible. It is at the very least reasonable to expect programmers to not call property access with arrays though.
Detection
This will depend on the current environment. With the most recent iteration of JavaScript, you can use type enforcement to ensure that property access is Number or String. Typescript makes this rather easy (here is a good example). It would essentially just require the access to be defined as:
function ArrayAccess(value: string | number) {
and this would prevent anyone from using the array as an accessor value.
Default Implementation
Changing the default implementation is a terrible idea. It will more than likely cause all sorts of breaking changes, and should not be done. However, just for completeness, here is what it would look like. Primarily I am showing this so you can hopefully recognize it if you see it somewhere and then kill it with fire (or check in some code to fix it if there were no spiders near it).
var arrayToString = [].toString;
Array.prototype.toString = function(){
if(this.length === 1) return;
return arrayToString.call(this);
};
Changing the instance implementation is not much of a better idea either. That is covered by #Code Maniac in a separate answer. "In practical scenarios you should never do this" #Code Maniac states, which I also agree with.
When using an array as a key, javascript call the 'toString()' method of that array, and then try to find the stringified version of the array as the key. And if you call ['foo'].toString() you see this method returns "foo".
If I declare the following in my Chrome console:
var object = {0:0, 1:1}
I can call object[0] and object[1] and get their values. I can also call object["0"] and object["1"]. Next, if I declare:
var object = {"0":0, "1":1}
I can also make all four of the above calls. But if I declare:
var object = {a:0, 1:1}
I get a ReferenceError of "a is not defined" when I call object[a], but object["a"] returns 0, even though the property name in the declaration is not a string. I guess JavaScript thinks I'm calling a variable that doesn't exist in the first example. But why do calling object[0] and object["0"] both work? It seems that JavaScript is doing some kind of automatic conversion for numbers (presumably since they can't be variable names), but what are the rules for this? And is this behavior universal to other places it might come up or just to the bracket notation for objects?
When you use brackets, the expression inside the brackets is evaluated. What's the value of the expression
a
?? Well, if "a" isn't a declared variable, it's nonsense. When you use . notation, the identifier (and it must be an identifier) following the operator is treated as a string. It's just the way the language works.
The reason you're getting a ReferenceError for object[a] is because a literal a is a variable in javascript. "a" is a string containing the letter a.
You can use the dot notation object.a or the bracket notation with object["a"]
object.a; //=> 0
object["a"]; //=> 0
object[1]; //=> 1
object["1"]; //=> 1
Or you can use a variable for access
var x = "a";
object[x]; //=> 0
var y = 1;
object[y]; //=> 1
You are correct.
a there is a token which the engine assumes is a variable.
If you type "a" JS knows it's a string-primitive.
If you type 0, JS knows it's a number-primitive.
So on top of obj.a, obj["a"], obj[0], obj["0"], you can also say:
var a = 0;
obj[a]; // 0
Your app is exploding, because a hasn't been defined yet, and now you want to use it.
And yes, this is the expected behaviour.
What's inside of the brackets isn't seen as a "part" of the object -- it's a way of saying "give me the value of the object which is referenced by this key", where the key might be a number or string (or something that can be coerced into a string or number).
In the future, with maps and weakmaps, you would actually be able to use other objects/functions as keys as well.
var obj = new Map(),
func = function () { },
el = document.getElementById("myId");
obj[func] = 1;
obj[el] = 2;
Right now, these things technically work... ...but only because they're converted to their string values... ...so if you had two functions which were written the same (but technically two different objects), you would overwrite values, currently.
Inside of a map, they'd be treated as separate objects.
Using DOM elements is even worse, right now, as it might be useful to store hundreds of those and bind references to them, so that you don't have to keep looking for them... ...but for now, you need to make a unique ID number/key for each one, and store that, and remember the keys, and then create a child object to hold the data you want...
Whereas in the future, with maps, you could say:
var my_els = document.querySelector(".lots-of-els");
for (let el of my_els /* also the future */) {
console.log( my_map_of_data[el].info );
}
the index variable below is incorrectly initialized because f() will be returning stuff other than numbers, like strings. So what's the worst that can happen here? My testing seems to indicate that it has no effect, but now I am wondering...
function index(o, f) {
var index = []; // should be index = {};
each(o, function(k, v, o) { index[f(k, v, o)] = v; });
return index;
}
Javascript arrays are special objects that have an automatically set length property and inherit Array.prototype.
Unless you use a length property, there is no harm in treating an array as an object.
An array is an object, thus it can be treated as such without much side effects. Doing so however might result in some confusion, as the length property does not count non-numeric keys, and all the array prototype functions will likewise ignore them.
Just change [] to {}
You'll be creating an associative array, which is a valid JavaScript structure. Although, it is technically different than an object, you can interact with the array just like you would an object (for ... in to iterate, myarray[key] to fetch values). You may want to consider returning an object instead of an array if you suspect some keys will be strings.
"f() will be returning stuff other than numbers, like strings"
If f() only returns strings, then you're good to go, you're just using your array as an object and adding properties. The only downside is that the array itself remains empty, so for example you cannot count how many items you have added.
If f() can return both strings and numbers, it's going to create a mess. The loop will populate sometimes the array, sometimes the object properties.
I am not sure what you mean by "like strings", but if what f() returns is neither a number nor a string then it's not going to work.
Are there any pitfalls to code like this?
var Foo = function() {
this.bar = function() { return 'bar'; };
};
var f = new Foo();
f[0] = 'hi';
f[1] = 'there';
Note that I'm creating a new function object with some misc properties, and then I'm treating the object like an array. Also how are the array values being stored in the object? Are 0 and 1 treated like property names?
Well, yes, 0, and 1 will be just two property names.
When you assign a property with the bracket notation, the expression between the brackets will be converted to String, and that string will be used as the property name.
In fact, even the indexes for real arrays are just that, properties:
var realArray = ['a'];
realArray.hasOwnProperty('0'); // true
The difference is that real array objects on every property assignment that correspond to a valid index[1], track internally the value of their length property.
That's one of the reasons why "subclassing" array objects is difficult, even with the new ECMAScript 5 extensions, or at the moment also with the proposed ECMAScript-Harmony Proxies 2 3, can't be completely done, in a stanard way.
That can be a problem, depending on how you plan to iterate the numeric properties.
If you enumerate the properties with the for-in statement, other members will appear, not just the numeric properties.
[1] A valid array index is any unsigned 32-bit integer in the range of 0 to (2^32)-1.
I think you might have problems if you try to loop through that with a for in loop; the loop will also get bar. There are ways around this, so you just have to be careful.
If you want to extend Array behavious please use : Array.prototype.yourfunc = function()....