Last time I checked, the following two lines returned true:
null == localStorage["foo"];
null == localStorage.getItem("foo");
Same applies when replacing null with undefined.
So the first question is, why are there two ways to address the localStorage? And why does
localStorage["foo"]
return undefined while
localStorage.getItem("foo")
returns null?
Do I need to take care of that when developing JS?
The Web Storage Specification requires that .getItem() returns null for an unknown key.
Note however that .getItem() and .setItem() are specifically defined in the IDL as being the designated getter and setter for the Storage interface, and therefore they're also fully supported ways of accessing the contents of the storage.
However the [] syntax is more akin to a normal object and/or array property getter, and like those returns undefined for an unknown property name.
The reason not to use [] syntax is that it will operate on object properties first and will quite happily allow you to overwrite real properties and methods of the localStorage object, c.f:
> localStorage['getItem'] = function() { return 0 }
> localStorage.getItem('getItem')
0
localStorage["..."] is invalid usage of localstorage. You are trying to access methods of the localstorage object, rather than accessing actual database.
You have to use
localStorage.getItem("...")
and
localStorage.setItem("...")
methods to access storage database.
In javascript you always get an undefined value for keys that does not exist inside an object.
a = {}; //new object
alert(a["test"]); // you get 'undefined' because "test" keys is not found
In localStorage .getItem is a method who does check keys inside the localStorage object and returns null if not found.
Don't blame javascript, it's just the localStorage object behaviour
Related
Is there any place where it is specified exactly how the properties of localStorage work? The only thing I found in the standards is:
value = storage.getItem (key)
value = storage[key]
Returns the current value associated with the given key, or null if the given key does not exist.
storage.setItem (key, value)
storage[key] = value
Sets the value of the pair identified by key to value, creating a new key/value pair if none > existed for key previously.
But it doesn’t say anything about what happens if key is a special property name like length or setItem (or __proto__ or constructor).
In practice this seems to work as one would expect: “special” properties work as usual, and only “unused” property names can be accessed with the bracket syntax.
But stuff gets a bit weird if you look at the details. At least on Chrome, if you add a value using as key a “special” property name, like localStorage.setItem('length', 'test'), then, somehow, Object.keys(localStorage) does include 'length' in the result. Also, if you try Object.getOwnPropertyNames(localStorage), 'length' is included. But if you try Object.getOwnPropertyDescriptors(localStorage), no property named 'length' is returned.
I can’t find any specification, or even description, of how these work. The ECMA standard mentions that “exotic” objects can override “abstract operations” with special behavior. But I couldn’t find any specs for how that is supposed to work, exactly, with localStorage. All that I could see are cursory mentions about behavior in specific browsers, and those are usually old and I presume obsolete.
So, in short: Is the detailed behavior of the localStorage specified anywhere?
This question is somewhat for understanding the prototype get-set.
I've a scenario where I need to map the SpeechSynthesis supported voice to Google Translation API supported language codes.
For example,
Now, I can do the same by either fetching the voices runtime or by storing the voice and hard-coding the mapping in some method in javascript.
If I go for runtime approach, I need to call getVoice() in speechSynthesis.onvoiceschanged = () => {} and then map the numbers, which gets called in every voice change event. So, I want to go for hard-coding one.
Now, when I store the mapping array in a variable and call it by index, I get the SpeechSynthesisVoice Object, just like what we do in getVoices()[index].
Further, If I set this object value to speechSynthesis.voice, I get an error:
Uncaught TypeError: Failed to set the 'voice' property on 'SpeechSynthesisUtterance': The provided value is not of type 'SpeechSynthesisVoice'.
This is due to mis-match of prototype of the manually stored object value.
For example,
1. SpeechSynthesisVoice object:
2. Manually stored value of SpeechSynthesisVoice object:
To resolve, this I've fetched the proto of the SpeechSynthesisVoice object using getVoice(), and then set it to a variable and further, set this variable to my manual mapped object.
Like,
Get:
voicePrototype = getVoices()[9].__proto__;
Set:
voices[index].SpeechSynthesisVoice.__proto__ = voicePrototype;
And, it gets set, as in below screenshot:
I've also tried through Object.setPrototypeOf() and got the same result.
Now, again when I want to set this object to speechSynthesis.voice, I still get the same error, although my prototype matches.
Can anyone, please advise, if its possible to set the object proto likewise and use it? Thanks in advance.
I'm updating a script that checks parameters like this:
if (config.param) {//do...}
This has lead to some issues in truthiness. I'd implemented a truthy() function as a prototype so as to be able to do the following:
if (config.param.truthy()) {//do...}
That seemed fine until I remembered that the config object only contains params being explicitly set. So config.value is usually undefined as opposed to false.
I'd added the prototype within an appropriate scope via String/Number/Boolean.prototype.truthy.
Is it possible to implement the prototype on the config object such that it will be able to return false on non-defined parameters?
Edit: My question is not about the truthiness (I can simply use a function), but instead how to implement a prototype on an object such that it is inherited by its properties, which may be different types or non-existent.
if ("param" in config && !!config.param) {//...
Or if you want to use a default value if undefined then
config.param = config.param || defaultValue
The whole problem with prototyping in this case is that falsey values will not get autoboxed to objects and so attempting to call methods of them will result in error.
Assuming that your params will all be different types of data, then your approach of adding prototype methods for Strings/Booleans/etc could work well. Then you could simply see if that property exists by doing
if (config.hasOwnProperty("param") && config.param.truthy() ) {//do...}
or if you're using dynamic property names
if( config.hasOwnProperty(param) && config[param].truthy() ) {//do...}
I've seen before that undefined semantically should only be used for variables that have been declared but not defined, and if a variable should at any point be set or unset, I should initially set it to null and check the variable against null rather than setting it back to undefined.
I'm wondering about the case where I am checking something that should be undefined, as in the case I am checking what an unset key in an object points to
i.e.
var eva = {'asuka': 2, 'rei': 0};
if I were to check eva['shinji'], I would have to check for undefined, because there are cases where I would not know all the possible keys that would be checked against.
I guess in this case, eva['shinji'] being undefined would be correct, though in the specific case of keys in objects, using ('shinji' in eva) would be best?
However, I have a feeling there may be other cases where objects that were unknown were checked against, that I could not use a 'in' for instead, but the case of object keys was most apparent to me.
In those cases, would it be best to check for undefined?
First of all, your statement is incorrect, should be:
var eva = {'asuka': 2, 'rei': ''};
Then you can find eva['asuka'] or eva.asuka will give 2.
If you want to check if a property inside an object.
There are multiple ways to do that.
You can simple check eva && eva.hasOwnProperty('asuka')
eva && typeof(eva.asuka) !== 'undefined'
3.
var found = false;
for (i in eva){
if (i == 'asuka') {
found = true;
break;
}
}
As mattclemens commented, if you do not understand the differences and best practices surrounding undefined vs null please check out the link he posted, or one of the other multitudes of blog/forum posts, books, or videos regarding this subject (i.e. duckduck something like "js best practices undefined null").
Based on the first paragraph of your question it seems you have a grasp on what they mean, and this question comes down to context...
I'm wondering about the case where I am checking something that should be undefined...
This seems like a loaded question to me. What does "should be undefined" mean? This tells me that your code never sets that property that "should be undefined" and you are assuming nothing else is setting it. But really, "should be undefined" doesn't make sense to me. You either know it's not because it's never been outside the current scope and you haven't defined it yourself, or you don't know and it's best practice to check whether it's defined before checking if it's null.
So I see this as 2 basic scenarios:
Internally/Privately created and used exclusively internal to code you control
Externally/Publicly created or crossing the public/private line
1. Internal/Private use only
In the first scenario the answer is simple: initialize the object with default values (falling back to null) for all properties that will ever exist on the object...
var eva = {'asuka': 2, 'rei': null};
...or since you have direct control over the object throughout its lifecycle, add properties explicitly as needed using the default operator...
eva.shinji = eva.shinji || null;
Then whenever you need to check the value of a specific property, your only concern is whether it is null. You will notice this strategy being employed in some of the most widely used js libraries, such as jQuery, where internal variables are only ever checked against null, or even assumed to exist within some contexts.
2. External/Public use at any point in the object's lifecycle
For objects you can't trust there are two approaches I would suggest, and which one is choosen depends, again, on the details of the context. If you are receiving some object, and will be using that object repeatedly or modifying the data you receive from it for internal use only, or if it is unsafe to change the value of the original object in any way, you may want to make your own copy of the object and then deal with that copy exclusively. Again, this is illustrated in libraries/frameworks, such as jQuery and AngularJS, where things like window and the undefined value itself, are passed in to the IIFE, and an internal copy is created/extended for internal use throughout the lifetime of the consumer.
However, this may be unnecessary overhead for your situation. Instead you could just verify the contents of eva when it crosses that external/internal boundary. The following example does so with the default operator.
function yourFunction(eva) {
eva = eva || {};
eva.asuka = eva.asuka || 2;
eva.rei = eva.rei || null;
}
Alternatively, you may have a string value or array of string values that are keys you wish to verify exist on the object. In that case please consider the following example using Object.keys(). This example also allows for the array of names of keys to be undefined.
function foo(eva, keysToFind) {
eva = eva || {};
keysToFind = keysToFind || ['asuka', 'shinji'];
var keysToCheck = Object.keys(eva);
for(var k in keysToFind) {
var keyName = keysToFind[k];
var keyIdx = keysToCheck.indexOf(keyName);
if(keyIdx == -1) {
eva[keyName] = null;
}
}
}
Finally, as RaymondM points out, you can take this a step further if you need to determine whether a property was added to the object literal, it's prototype, or inherited from a super/base class...
You can simple check eva && eva.hasOwnProperty('asuka')
Again, taking context in to consideration, if you have already identified the context as scenario 1 or 2 from above, and are checking any more than a single property's existence, then it will likely be more efficient to check for === null or typeof eva.asuka === 'undefined', respectively. Or even check if(eva && eva.asuka) { ... }, if you're certain asuka has been defined.
This code sums up my question:
localStorage.setItem('getItem', 4)
undefined
localStorage.getItem
function getItem() { [native code] }
localStorage['getItem'] = 'user'
"user"
localStorage.getItem
"user"
localStorage.getItem('getItem')
TypeError: Property 'getItem' of object #<Storage> is not a function
Using the setItem method or using the property accessor changes the way localStorage reacts.
Is this a bug? A specification inconsistency? Something else?
I feel like I should report this somewhere, but don't really know where.
Using the setItem method or using the property accessor changes the way localStorage reacts.
Is this a bug?
It doesn't seem like it. From the spec:
When the setItem(), removeItem(), and clear() methods are called on a
Storage object x that is associated with a local storage area, if
the methods did something, then in every Document object whose
Window object's localStorage attribute's Storage object is associated
with the same storage area, other than x, a storage event must be
fired, as described below.
The localStorage object is an IDL attribute with a reference to the Storage interface. The Storage interface extends Object.prototype, which brings in extra properties, and also defines its own set of properties.
The setItem method stores a property in the storage list. The object also fires an event when there are changes on the object properties, and updates the storage area in those cases.
So the expected behavior for setItem is to store the key value pair in the storage list. The expected behavior for getItem is to pull the value associated with a key from the storage list.
So what is the expected behavior when we try to access a property directly?. This line says that the key-value list acts as "supported properties":
The supported property names on a Storage object are the keys of each
key/value pair currently present in the list associated with the
object.
What does this mean? It means that those properties are mapped to the getters and setters defined by the interface spec. For Storage the interface specification looks like this:
interface Storage {
readonly attribute unsigned long length;
DOMString? key(unsigned long index);
getter DOMString getItem(DOMString key);
setter creator void setItem(DOMString key, DOMString value);
deleter void removeItem(DOMString key);
void clear();
};
So localStorage["x"] is mapped to the value of getItem("x"), which returns the value of "x" in the storage list, and localStorage["x"] = y will be mapped to setItem("x",y).
However, these will only be run if there is not a native property by that name on the object unless the overridebuiltins attribute is set on the interface (which it is not for Storage).
If the [OverrideBuiltins] extended attribute appears on an interface,
it indicates that for a platform object implementing the interface,
properties corresponding to all of the object’s supported property
names will appear to be on the object, regardless of what other
properties exist on the object or its prototype chain. This means that
named properties will always shadow any properties that would
otherwise appear on the object. This is in contrast to the usual
behavior, which is for named properties to be exposed only if there is
no property with the same name on the object itself or somewhere on
its prototype chain.
So for properties we should expect that if a function is defined on an object it will always access/set that on a request. Otherwise it will check the storage list and see if it has a value and change or return it.
Summary (TL;DR)
The expected behavior from the spec, and what we find when you test is that setItem and getItem will store values of pre-existing properties/functions as key/value pairs, and access them, without overwriting the existing values. When we attempt to access or modify those values directly through localStorage["getItem"], we'll instead hit the local properties, because the "supported property" spec calls for not overriding built in properties by default.