Why can I access object property with an array? - javascript

Can somebody explain the behaviour of the following code?
let obj = {a:1, b:2}
let i = ['a']
console.log(obj[i])
>> 1
Why is it that even an array can be used to access a property inside an object? As a side note this only works with an array of length 1. I have tried researching this but there's no documentation as far as I know that explains why this should work.

Property names are always strings or symbols.
If you pass something which isn't a string or symbol, it gets converted to a string.
The default toString() method on an array is roughly:
String.prototype.toString = function () { return this.join(","); }
So ['a'] gets converted to 'a'.
As a side note this only works with an array of length 1.
It works fine with arrays that are longer. You just need a matching value:
const o = {
"a,b": "Hello"
}
const a = ["a", "b"];
console.log("" + a);
console.log(o[a]);
And since any object can be converted to a string, and you can customise the toString method, you can do really weird things:
const data = {
"42": "Hello"
}
class Weird {
constructor(x) {
this.x = x;
}
toString() {
return this.x + 40;
}
}
const w = new Weird(2);
console.log(data[w]);
(Note that doing really weird things is usually a stupid idea that makes it hard to debug your own code two weeks later).

let obj = {a:1, b:2}
First you declare an object with 2 properties, a and b with values 1 and 2, respectively.
let i = ['a']
Then a variable i is declared, with it's value set to a string array with a single element, 'a'.
console.log(obj[i])
In this statement, the value of i is resolved to a string because the array only contains one element, as you mentioned. Due to this, 'a' is a valid property name for obj because all object properties are either strings, and if you pass something such as an array, it's converted to a string.
If you reference the variable icontaining the array, and it has multiple elements, it cannot resolve to a single property name without you being more explicit such as obj[i[0]]. If you only have one value, it'll resolves to that value.
obj['a'] is a valid property
obj['a,b'] is not.

Related

Object.keys() array with one element treated as string?

What kind of magic is that, when you Object.keys() on an object with one key only, it will be treated as a string when you reference later using square brackets?
See below for example:
let chosenProducts = [
{
toys: 20
}
];
let toys = "toys";
chosenProducts.forEach(product => {
let name = Object.keys(product);
console.log(Array.isArray(name)); // true
console.log(name) // ['toys']
console.log(toys) // "toys" - string
console.log(product[name]); // 20 - array used = wtf?
console.log(product[toys]); // 20 - string used
});
Object.keys returns an array.
All objects have a toString method.
If you use an object in string context, toString is called implicitly.
Square-bracket property accessor notation is, essentially, string context (unless you pass a Symbol)
The default toString method on an array looks something like: function () { return this.join(","); }
const example1 = ["foo", "bar"];
console.log("" + example1);
const example2 = ["baz"];
console.log("" + example2);
Object.keys returns an array of keys. Your name variable is an array with one element ['toys']. When you access product[name] implicit coercion of name array to string happens which results in product["toys"]
In javascript there are two types of coercions implicit and explicit coercion
When you access with Object's properties they will be string, in case of an array they will be indexes (numbers), but array is also an object, when you add any property in array it can be act as object property(string) and will not get counted towards the length of array.
let arr = [1,3];
arr.val = 20;
console.log(arr.length); // 2
In your example you are accessing object's property product[name] here name is an array. What will happen here is implicit coercion of array to string.
Javascript will automatically call toString method on your object which is an array in this case. There is toString method for Array which is linked to their prototype Array.prototype.toString. You can override that if you want to get different result.
In simple terms when you try to access variable in if conditional expression javascript automatically coerce to Boolean value.
let a = 10;
if(a){
console.log("implicit coercion");
}
if(Boolean(a)){
console.log("explicit coercion");
}
To learn more about Javascript grammar and types.

Why does JavaScript convert an array of one string to a string, when used as an object key? [duplicate]

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".

What are JavaScript Objects and Properties?

Can anyone explain objects and properties to me in Javascript in layman's terms? The Javascript MDN documentation is confusing me.
I am trying to solve a problem in a Javascript tutorial about objects and properties The question is below and my attempt at the code, I think I followed the MDN as far as I can grasp it. Any help would be appreciated!
Question: Add the value of the property argument as a key on the object argument.
The value of the new property should be set to null.
Return object after adding the new property.
Input Example:
{}, 'hello'
{ x: 5 }, 'y'
Output Example:
{ hello: null }
{ x: 5, y: null }
note: the property name is NOT 'property'. The name is the value of the argument called property (a string).
My code:
//NOTE: the function addProperty(object, property) was already in the console and I have to write the solution inside of it.
function addProperty(object, property) {
// code here
let result = addProperty({x: 5}, 'y');
obj[property] = null;
return obj;
}
addProperty(x, 'y');
Layman's explanation:
An object is a collection of properties. You can give a name to the object to keep things organized. For example, lets create a person object.
var person = {};
The object has no properties right now. To further describe the person we can add properties to the object.
person.Name = 'Zim';
person.Age = 29;
person.Gender = 'Male';
person.Weight = 80;
Now this object has some properties to help describe it. A different way to write the same thing:
var person = { Name: 'Zim', Age: 29, Gender: 'Male', Weight: 80 };
If we had to create a program that displays a list of people, storing all of our information inside objects would help keep things organized.
Object properties are sometimes referred to as keys.
Adding properties to objects:
You can add a property to an object using brackets, just like you had in your addProperty function. If you just need it to add a property, set that property to null and return the result it would look something like this:
function addProperty(object, property) {
// code here
object[property] = null;
return object;
}
This would let us create a properies on our object from above by calling
addProperty(person, 'Occupation');
addProperty(person, 'Income');
addProperty(person, 'Height');
I think you're over-thinking it.
First the question:
Add the value of the property argument as a key on the object
argument. The value of the new property should be set to null. Return
object after adding the new property.
emphasis added
So, property will be the KEY (of a key/value pair), and the VALUE will be null of object, which we are also passing in as an argument.
One way to interrogate key/value pairs on a javascript object is through the square-brackets []. So, if you have a key/value pair: { foo: "bar" }, you can get "bar" by: object['foo']. You can also create new key/value pairs like this, so your function can look like:
function addProperty(object, property) {
object[property] = null;
return object;
}
var obj = {};
obj = addProperty(obj, "hello");
console.log(obj);
console.log(addProperty({x: 5}, 'y'));
What our function is doing is taking the object passed into it (as an argument), creating a new KEY with our property argument, and setting its VALUE to null, and simply returning the object.
*Side note -
Be careful, the code you have posted will create an endless recursive loop, as you keep calling the same function with no way to break out of it.
This particular example is simple:
var one={};
var two={x:5};
function addProperty(object property){
obj[property]=null;
return obj;
}
addProperty(one, 'hello');
addProperty(two, 'y');
Objects in javascript are really flexible, their properties can be added or removed, even when set from the very beginning.
If you take:
var x={};
x will be an object with nothing in it, but if instead of that you write:
var x={
inner:'b'
};
x will be an object with a property called inner which value is 'b', now, if we want to access that property we could do something like this:
var valueOfInner=x.inner;
or
var valueOfInner=x['inner'];
The same if we want to change the value of that property:
x.inner=8;
or
x['inner']=8;
Now, you'll notice that when we use x.['inner'], we could very well use instead:
var propertyName='inner';
x[propertyName]=8;
So you can access and modify a property of an object without actually knowing exactly which property you're manipulating.
Finally, if you're (willing or by accident) trying to set the value of a property that doesn't exists, the property will be automatically be created, for example:
x['blah']=456;
Will create the property blah even when it wasn't defined at first.
Edit: yes, you can define the object and define its properties later:
var x={};
//more code or something
x['y']=777;//now x has a y property with the value 777
a property is a key:
let obj = {
name: 'Jordan',
age: 15
}
obj.name is a property, same goes for obj.age.

how do i get a reference of primitive value?

var number = 12345;
var obj = {};
do something like
obj.look ((((((some operator or function to get a reference of number ))))) number
anyway, let's think now obj has a reference to number.
so I would like to wanna do it this way.
obj.look = 'abc';
console.log (number); // hopefully 'abc'
i would like it's gonna be:
not method ( like obj.look() )
but can refer in property ( like obj.look )
all I'm asking is how to get a reference of primitive value.
I believe there must be some way. please help me out.
You can use Object.defineProperty:
var number = 12345;
var obj = {};
Object.defineProperty(obj, 'look', {
get() {
return number;
},
set(value) {
number = value;
}
});
document.write(obj.look + '<br>');
obj.look = 'abc';
document.write(number);
You can have an object, copy that reference to another variable, mutate the object and since both variables hold a reference to the same object any changes done from one variable will also be accessible from the other variable.
Primitive values can be wrapped into objects (new Object) but they can not be referenced otherwise. String objects are immutable (all properties are non-configurable, non-writable) and Number objects values are not accessible either. So you can't change the value of either object.
That means when you do:
obj.look = 'abc';
You are wiping the old reference and setting up a new string there (object or primitive). number will still hold the old reference and will remain unchanged.
So, no. I don't think this is possible to do in Javascript.

JavaScript variable assignments behaving surprisingly

This question actually is an outcome from another question, for which i have made some experiments, results are confused me more. I'm expecting answers which explains what actually happens internally.
What i have tried is,
I kept this as the base assumption because i got some clear explanation
here,
var a = [];
a['a1'] = [];
a['a2'] = [];
console.log(a); // []
console.log(a['a1']); // []
console.log(a['a2']); // []
TEST 1
This confused me a lot, since it prints b[0] as a and can be able to access length property, i thought var b can be treated as a character array, therefore i tried to assign another value , but ended up without success. From the base assumption, if this one can be treated as a char array(more generally as an array), the assignment should have been successful. It breaks the base assumption.
var b = 'abcd';
b['b1'] = [];
console.log(b); // abcd
console.log(b.length); // 4
console.log(b['b1']); // undefined
TEST 2
But if i create like this, the assignments are happens,
var bb = ['a', 'b', 'c', 'd'];
bb[4] = [];
console.log(bb); // ["a", "b", "c", "d", []]
console.log(bb.length); // 4
console.log(bb[0]); // a
console.log(bb[4]); // []
From this, i thought that, b[4] = []; may be successful, but
var b = 'abcd';
b[4] = [];
console.log(b); // abcd
console.log(b.length); // 4
console.log(b[4]); // undefined
My question is, why these assignments behaving differently while the variables sharing some common functionalities?
Here is the demo
Can anyone please give me a clear cut explanation about what actually happening internally?
Extra tests
Then if i tried with numerical assignment, it behaves quite differently form those two.
var c = 1234;
c[4] = [];
console.log(c); //1234
console.log(c.length); // undefined
console.log(c[0]); //undefined
console.log(c[4]); //undefined
When you access anything but an object with [], a temporary object instantiated with correct prototype (like String or Number) is provided for you. You can read and write properties of this object as you could with any other - try alert(3["constructor"]) for example. However, since this object is not referenced anywhere it goes to garbage right after you've done indexing it and next time you try to read same property you've just set, you're actually accessing a property on new temporary object, which, naturally, is empty.
Test 1: b is a string internally, not an array, so you can't assign something in a b position.
Test 2: Off course it works, since now bb is an array.
My question is, why these assignments behaving differently while the
variables sharing some common functionalities?
Because their types are different.
Test 3:
c is a Number, not an array.
Maybe you have some C background, where strings are char arrays terminated by a null char (\0). In JavaScript strings are built int types and they behave differently of arrays. The [] operator is just a convenience to access one especific char as Jonathan said. Here are some links, take a look:
JavaScript String
JavaScript Array
JavaScript Number
Reasoning
Your "Base Assumption" works because a is an array. "Test 2" is the only other test case that you have written that uses an array, which is why that is the only other test case that works.
You seem to be assuming that providing a square-bracket notation and having a length method indicates that an object is an array. This is not true. To test to see if an object is an array, you can use JavaScript's instanceof method, as follows:
var a = [];
var b = 'abcd';
var bb = ['a', 'b', 'c', 'd'];
var c = 1234;
a instanceof Array // => true
b instanceof Array // => false
bb instanceof Array // => true
c instanceof Array // => false
Notice that the cases where instanceof Array returns true are the ones that are acting as you expected, since you are trying to perform array operations.
Why "Test 1" Failed
For "Test 1", square-bracket notation for strings performs the string class's charAt function. Given b = 'abcd', performing b[0] is the same as performing b.charAt(0). It is read-only and simply returns the character at that index, which is "a".
See the Mozilla Developer Network documentation for Strings for more details.
Why "Extra Test" Failed
For your "Extra Test", integers do not provide a square-bracket notation or a length method, and thus all of those calls failed.
I guess I don't follow what your question is. Javascript treats strings, arrays, and integers differently. You should not expect them to behave in the same manner. Also there is no such thing as associative arrays in javascript to where your first example would work. The a['a1'] type of notation is actually only an alternative means for accessing object properties and have no meaning in the context of a string.

Categories