Why can ES6 Symbol properties be made enumerable by Object.defineProperty? - javascript

In ES6 properties can be defined as symbol properties:
var symbol = Symbol();
var object = {};
object[symbol] = 'value';
MDN defines enumerable properties as 'those which can be iterated by a for..in loop' (1). Symbol properties are never iterated by a for...in loop, therefore they can be considered non-enumerable (2).
Does it make any sense, then, that you can do this:
Object.defineProperty(object, symbol, {
value: 'value',
enumerable: true
});
and that querying object for it's descriptor does indeed confirm that this property is enumerable:
Object.getOwnPropertyDescriptor(object, symbol)
// -> { enumerable: true }
Why? What use is this?
(1) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
(2) for...in uses [[Enumerate]], which only includes string keys. Probably the definition on MDN should be changed now that we have symbol properties.

Yes, there's a reason for allowing Symbol properties to be enumerable: Object.assign:
let s1 = Symbol();
let s2 = Symbol();
let s3 = Symbol();
let original = {};
original[s1] = "value1"; // Enumerable
Object.defineProperty(original, s2, { // Enumerable
enumerable: true,
value: "value2"
});
Object.defineProperty(original, s3, { // Non-enumerable
value: "value3"
});
let copy = {};
Object.assign(copy, original);
console.log("copy[s1] is " + copy[s1]); // value1, because it was enumerable
console.log("copy[s2] is " + copy[s2]); // value2, because it was enumerable
console.log("copy[s3] is " + copy[s3]); // undefined, because it wasn't enumerable
Live Copy on Babel's REPL.
Just for clarity:
MDN defines enumerable properties as 'those which can be iterated by a for..in loop' (1).
That's simply wrong for ES6 (ES2015). It was a reasonable, if simplistic, definition in ES5 and earlier, no it's no longer even simplistically correct because of Symbols. I've fixed the article.
This is a CW because it was the outgrowth of the comments on the question.

This is because the rules for enumeration include a clause requiring string keys. Bear in mind that enumeration and asking for keys are different operations with entirely different rules.
Looking at the section for for ... in/for ... of head evaluation (13.7.5.12), it states that the iteration is done using:
If iterationKind is enumerate, then
c. Return obj.[[Enumerate]]().
The description of [[Enumerate]] (9.1.11) very clearly states that it:
Return an Iterator object (25.1.1.2) whose next method iterates over all the String-valued keys of enumerable properties of O.
The check for enumerable properties comes later in the body, and the pseudo-code example makes this even more clear:
function* enumerate(obj) {
let visited=new Set;
for (let key of Reflect.ownKeys(obj)) {
if (typeof key === "string") { // type check happens first
let desc = Reflect.getOwnPropertyDescriptor(obj,key);
if (desc) {
visited.add(key);
if (desc.enumerable) yield key; // enumerable check later
}
}
}
...
}
(comments mine)
Clearly, properties with non-string keys will not be enumerated. Using this example:
var symbol = Symbol();
var object = {};
Object.defineProperty(object, symbol, {
value: 'value',
enumerable: true
});
Object.defineProperty(object, 'foo', {
value: 'bar',
enumerable: true
});
Object.defineProperty(object, 'bar', {
value: 'baz',
enumerable: false
});
Object.defineProperty(object, () => {}, {
value: 'bin',
enumerable: true
});
for (let f in object) {
console.log(f, '=', object[f]);
}
for (let k of Object.getOwnPropertyNames(object)) {
console.log(k);
}
you can verify that in Babel and Traceur.
However, you'll see two interesting things:
getOwnPropertyNames includes non-enumerable properties. This makes sense, as it follows completely different rules.
for...in includes non-string properties under both transpilers. This does not seem to match the spec, but does match ES5's behavior.

Related

Accessing API class names in global context

I am writing some ES/JS tooling. In DevTools I can see lots of classes, e.g. ArrayBuffer. Yet when I try to extract such names from window global context, I can't see them. This snippet gives similar results in Chrome, FireFox and Opera.
MWE on JSFiddle
console.log('typeof window.ArrayBuffer:',
typeof window.ArrayBuffer);
console.log("window.hasOwnProperty('ArrayBuffer'):",
window.hasOwnProperty('ArrayBuffer'));
let c = 0;
for (let n in window) {
// console.log(n);
if (n == 'ArrayBuffer') {
console.log('FOUND: ArrayBuffer');
}
c++;
}
console.log(c + ' attributes checked')
If I uncomment the // console.log(n); line, I can see the names of the attributes, but none of the classes.
How do I access those class (API) names?
It's matter of Object's Enumerable properties.
for in (as well as Object.keys/values/entries) does not account for non enumerable properties:
window.hasOwnProperty('ArrayBuffer'); // true
window.propertyIsEnumerable('ArrayBuffer'); // false
// Therefore will not show up in for-in loop
To create a non-enumerable object property, an example is by using Object.defineProperty
const obj = {a: 1, b: 2, c: 3};
for (const prop in obj) console.log(obj[prop]); // 1 2 3
Object.defineProperty(obj, "d", {
value: 4,
// enumerable: false, // defaults to false!!
// set to true to make it enumerable
});
for (const prop in obj) console.log(obj[prop]); // 1 2 3 (no 4 visible)
// Same goes for iterating using Object.keys and Object.values:
console.log(...Object.values(obj)) // 1 2 3 (no 4 visible)
To get all the properties of an Object you might use instead Object.getOwnPropertyNames:
const props = Object.getOwnPropertyNames(window);
console.log(props.includes('ArrayBuffer')); // true
console.log(props.some(pr => pr === 'ArrayBuffer')); // true
console.log(props.findIndex(pr => pr === 'ArrayBuffer') > -1); // true
console.log(props.length); // 963
console.log(Reflect.ownKeys(window).length); // 963
// See them all:
console.log(props);
// Loop them all:
// props.forEach(pr => console.log(pr));
Keep in mind that for...in loops only enumerable properties - with the exception of Symbols.
To get an Array ot the Object's own-property keys with Symbols use Reflect.ownKeys
The Reflect.ownKeys method returns an array of the target object's own property keys. Its return value is equivalent to Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
Additional read:
Enumerability and ownership of properties
What are the benefits of non-enumerable Object properties
Use Object.getOwnPropertyNames() to get all properties (including non-enumerable properties except for those which use Symbol). Then loop through them to find the class name:
Object.getOwnPropertyNames(window).forEach((n) => {
if (n === 'ArrayBuffer') {
console.log('FOUND: ArrayBuffer');
}
})

Iterate over an object and replace properties with setters and getters

I want to be able to have a function that will replace all of an objects properties with a setter and getter.
function myFunction(obj){
var private={};
for(var i in obj){
if(obj.hasOwnProperty(Ii){
private[i]=obj[i]
delete obj[i]
Object.defineProperty(obj, i, {
set:(function(i){return function(val){private[i]=val}})(i),
set:(function(i){return function(){return private[i]}})(i)
})}
}
return obj
}
Would this work or should I use a different method to iterate over the object, could this style of storing the data in a seperate object also cause issues.
Storing the data in a separate object is fine.
I would simplify the code a bit by using Object.keys, assuming that you didn't really mean to replace inherited properties (for-in enumerates both "own" and inherited properties; Object.keys just gives you an array of "own" enumerable properties):
function myFunction(obj){
var private = {};
Object.keys(obj).forEach(function(key) {
private[key] = obj[key];
// No need for the `delete`
Object.defineProperty(obj, key, {
set: function(value) {
private[key] = value;
},
get: function() {
return private[key];
},
enumerable: true,
configurable: true // Or leave it off if you don't want the
// property reconfigured
});
});
return obj;
}
Notes:
By using the forEach callback, we make the get/set function closures over key and so can avoid creating temporary wrapper functions.
enumerable defaults to false; I'm assuming you don't want to change the enumerability of the properties, so I've added enumerable: true
You had set twice, rather than get and set

Is it valid to use Object.defineProperties with symbols?

Let's take the following code:
var obj = {};
var x = Symbol();
Object.defineProperties(obj, {
[x]: {
value: true,
writable: true
},
"property2": {
value: "Hello",
writable: false
}
// etc. etc.
});
console.log(obj[x])
Is this valid?
With the native Object.defineproperties code we get in the console.log true.
With the polyfill of zone.js
which is of the form of :
Object.defineProperties = function (obj, props) {
Object.keys(props).forEach(function (prop) {
Object.defineProperty(obj, prop, props[prop]);
});
return obj;
};
we get for the same code of console.log undefined.
This is because of the Object.keys function. I googled around and did not find in any place if this should be allowed or not.
I googled around and did not find in any place if this should be allowed or not.
You can always check the spec, in this case § 19.1.2.3 Object.defineProperties(O, Properties).
It uses the OwnPropertyKeys internal method, which does indeed list all string and symbol keys of an object.
This is because of the Object.keys function
Correct. It should be Object.getOwnPropertyNames(props).concat(Object.getOwnPropertySymbols(props)) instead. You may want to file a bug with zone.js. I do however wonder why you'd need a polyfill for the ES5 Object.defineProperties function when you're working with ES6 symbols?

ECMAScript 5: Why are some property attributes inherited while others are not?

It appears that ECMAScript 5 property attributes are inherited while others are not. Based on a simple experiment in Google Chrome, Safari, and Firefox, it seems that enumerable and writable are inherited from prototypes, but configurable is not. Consider when a is the prototype of b, and a defines property x (but b does not). If x is not writable, then b cannot override the value of x with =, which would, if allowed, only change b's x, and not a's (which is the x marked as non-writable). However, even if x is neither configurable nor writable, b may override the value of x with Object.defineProperty(b, 'x', ...) (which would fail for a: Object.defineProperty(a, 'x', ...), because a's `x' is not configurable).
I don't see anything in the standard that explains this (perhaps it's there, but I can't find it). Is this inconsistent behaviour intended?
Test output (answers "do a's and b's property attribute behave the same way?):
Object {enumerable: true, configurable: false, writable: true}
Code used to test:
function isEnumerable(p, o) {
for (var key in o) {
if (key === p) {
return true;
}
return false;
}
}
function isConfigurable(p, d, o) {
try {
Object.defineProperty(o, p, d);
return true;
} catch (e) {
return false;
}
}
function isWritable(p, v, o) {
if (o[p] === v) {
throw 'Error: isWritable will not work with identical value';
}
o[p] = v;
return o[p] === v;
}
function inheritedAttributes(
parentGenerator,
propertyName,
differentDescriptor,
differentValue) {
var parent, child, rtn = {};
var fns = {
'enumerable': isEnumerable.bind(this, propertyName),
'configurable': isConfigurable.bind(
this,
propertyName,
differentDescriptor),
'writable': isWritable.bind(this, propertyName, differentValue)
};
for (var key in fns) {
parent = parentGenerator();
child = Object.create(parent);
rtn[key] = fns[key](parent) === fns[key](child);
}
return rtn;
}
var propertyName = 'x';
var starterProperties = {};
starterProperties[propertyName] = {
'writable': false,
'configurable': false,
'enumerable': false,
'value': 'foo'
};
var differentDescriptor = {
'writable': false,
'configurable': false,
'enumerable': false,
'value': 'bar'
};
var differentValue = 'baz';
var parentGenerator = function() {
return Object.create(Object.prototype, starterProperties);
};
window.console.log(inheritedAttributes(
parentGenerator,
propertyName,
differentDescriptor,
differentValue
));
This doesn't answer "why". I suspect the answer to "why" is, "because that's what the standard says". Nevertheless, here are some relevant links, excerpts, and paraphrases from the standard. Thanks to basilikum for pointing me in the right direction.
Enumerable
for (var key in obj) { ... }
The for-in statement documentation states (emphasis added):
Enumerating the properties of an object includes enumerating
properties of its prototype, and the prototype of the prototype, and
so on, recursively; but a property of a prototype is not enumerated if
it is “shadowed” because some previous object in the prototype chain
has a property with the same name. The values of [[Enumerable]]
attributes are not considered when determining if a property of a
prototype object is shadowed by a previous object on the prototype
chain.
Hence, enumerability is inherited.
Writable
obj.x = a;
Property assignment invokes the internal method [[Put]], which starts by checking the return value of [[CanPut]]. When the property is a value, not a getter+setter, [[CanPut]] will check property attributes in the following order:
obj's x property's writable attribute
obj's extensible attribute
For each prototype from obj's prototype down to (but not including) null:
prototype's extensible attribute
prototype's x property's writable attribute
If any of these attributes is defined, then its value is returned. Hence, even when other attributes are missing, a non-writable property somewhere down obj's prototype chain causes assignment to fail on obj; i.e., writability is inherited.
Configurable
Object.defineProperty(obj, 'x', { 'value': a, ... });
Object.defineProperty invokes the internal method [[DefineOwnProperty]], which is rather convoluted. In general, this method will concern itself only with the extensible attribute of obj, and the configurable attribute of obj's current own x property, if it exists. As such, a non-configurable property, x, somewhere down obj's prototype chain does not influence this procedure; i.e., configurability is not inherited.

if (key in object) or if(object.hasOwnProperty(key)

Do the following two statements produce the same output? Is there any reason to prefer one way to the other?
if (key in object)
if (object.hasOwnProperty(key))
Be careful - they won't produce the same result.
in will also return true if key gets found somewhere in the prototype chain, whereas Object.hasOwnProperty (like the name already tells us), will only return true if key is available on that object directly (its "owns" the property).
I'l try to explain with another example.
Say we have the following object with two properties:
function TestObj(){
this.name = 'Dragon';
}
TestObj.prototype.gender = 'male';
Let's create instance of TestObj:
var o = new TestObj();
Let's examine the object instance:
console.log(o.hasOwnProperty('name')); // true
console.log('name' in o); // true
console.log(o.hasOwnProperty('gender')); // false
console.log('gender' in o); // true
Conclusion:
in operator returns true always, if property is accessible by the object, directly or from the prototype
hasOwnProperty() returns true only if property exists on the instance, but not on its prototype
If we want to check that some property exist on the prototype, logically, we would say:
console.log(('name' in o) && !o.hasOwnProperty('name')); //false
console.log(('gender' in o) && !o.hasOwnProperty('gender')); //true - it's in prototype
Finally:
So, regarding to statement that these two conditions ...
if (key in object)
if (object.hasOwnProperty(key))
...produce the same result, the answer is obvious, it depends.
in will also check for inherited properties, which is not the case for hasOwnProperty.
In summary, hasOwnProperty() does not look in the prototype while in does look in the prototype.
Taken from O'Reilly High Performance Javascript:
You can determine whether an object has an instance member with a
given name by using the hasOwnProperty() method and passing in the
name of the member. To determine whether an object has access to a
property with a given name, you can use the in operator. For example:
var book = {
title: "High Performance JavaScript",
publisher: "Yahoo! Press"
};
alert(book.hasOwnProperty("title")); //true
alert(book.hasOwnProperty("toString")); //false
alert("title" in book); //true
alert("toString" in book); //true
In this code, hasOwnProperty() returns true when “title” is passed in
because title is an object instance; the method returns false when
“toString” is passed in because it doesn’t exist on the instance. When
each property name is used with the in operator, the result is true
both times because it searches the instance and prototype.
You got some really great answers.
I just want to offer something that will save you the need for checking "hasOwnProperty" while iterating an object.
When creating an object usually people will create it in this way:
const someMap = {}
// equivalent to: Object.create(Object.prototype)
// someMap.constructor will yield -> function Object() { [native code] }
Now, if you want to iterate through "someMap" you will have to do it this way:
const key
for(key in someMap ){
if (someMap.hasOwnProperty(key)) {
// Do something
}
}
We are doing so in order to avoid iterating over inherited properties.
If you intend to create a simple object that will only be used as a "map" (i.e. key - value pairs) you can do so like that:
const newMap = Object.create(null);
// Now, newMap won't have prototype at all.
// newMap.constructor will yield -> undefined
So now it will be safe to iterate like this:
for(key in cleanMap){
console.log(key + " -> " + newMap [key]);
// No need to add extra checks, as the object will always be clean
}
I learned this awesome tip here
The other form (called for in) enumerates the property names (or keys)
of an object. On each iteration, another property name string from the
object is assigned to the variable. It is usually necessary to test
object.hasOwnProperty(variable) to determine whether the property name
is truly a member of the object or was found instead on the prototype chain.
for (myvar in obj) {
if (obj.hasOwnProperty(myvar)) { ... } }
(from Crockford's Javascript: The Good Parts)
As other answers indicated, hasOwnProperty will check for an object own properties in contrast to in which will also check for inherited properties.
New method 2021 - Object.hasOwn() as a replacement for Object.hasOwnProperty()
Object.hasOwn() is intended as a replacement for Object.hasOwnProperty() and is a new method available to use (yet still not fully supported by all browsers like as you can see here - https://caniuse.com/?search=hasOwn
)
Object.hasOwn() is a static method which returns true if the specified object has the specified property as its own property. If the property is inherited, or does not exist, the method returns false.
const person = { name: 'dan' };
console.log(Object.hasOwn(person, 'name'));// true
console.log(Object.hasOwn(person, 'age'));// false
const person2 = Object.create({gender: 'male'});
console.log(Object.hasOwn(person2, 'gender'));// false
It is recommended to this method use over the Object.hasOwnProperty() because it also works for objects created by using Object.create(null) and for objects that have overridden the inherited hasOwnProperty() method. Although it's possible to solve these kind of problems by calling Object.prototype.hasOwnProperty() on an external object, Object.hasOwn() overcome these problems, hence is preferred (see examples below)
let person = {
hasOwnProperty: function() {
return false;
},
age: 35
};
if (Object.hasOwn(person, 'age')) {
console.log(person.age); // true - the remplementation of hasOwnProperty() did not affect the Object
}
let person = Object.create(null);
person.age = 35;
if (Object.hasOwn(person, 'age')) {
console.log(person.age); // true - works regardless of how the object was created
}
More about Object.hasOwn can be found here : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn
Browser compatibility - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn#browser_compatibility
Another way to have only ownproperties is :
<script type="text/javascript">"use strict";
const obj = Object.create({cle:"valeur"});
obj.a = "aaa";
obj.b = "bbb";
obj.c = "ccc";
for(let key=0 ; key < Object.keys(obj).length ; key++){
if(Object.keys(obj)[key]==="cle")
console.log(key , Object.keys(obj)[key] , Object.values(obj)[key]);
// none
if(Object.keys(obj)[key]==="b")
console.log(key , Object.keys(obj)[key] , Object.values(obj)[key]);
// 1 'b' 'bbb'
console.log(key , Object.keys(obj)[key] , Object.values(obj)[key]);
// 0 'a' 'aaa'
// 1 'b' 'bbb'
// 2 'c' 'ccc'
}
console.log(Object.getOwnPropertyDescriptor(obj,"cle"));
// undefined
console.log(Object.getOwnPropertyDescriptor(obj,"c"));
// {value:'ccc', writable:true, enumerable:true, configurable:true}
</script>
The first version is shorter (especially in minified code where the variables are renamed)
a in b
vs
b.hasOwnProperty(a)
Anyway, as #AndreMeinhold said, they do not always produce the same result.

Categories