Is it possible to create a non-enumerable property in javascript
Asked Answered
G

6

41

I want to create an object with a non-enumerable property(a property that does not show up in a for (var x in obj loop). Is it possible to do this?

Glisson answered 14/4, 2010 at 10:7 Comment(1)
It would be nice to have such a feature. For instance, in node JS where models map to a DB table, we'd be able to hide certain fields without having to call functions to remove/filter out the properties.Pheni
I
53

It isn't possible in ECMAScript 3 (which was what the major browsers implemented at the time this question was asked in 2010). However, in ECMAScript 5, which current versions of all major browsers implement, it is possible to set a property as non-enumerable:

var obj = {
   name: "Fred"
};

Object.defineProperty(obj, "age", {
    enumerable: false,
    writable: true
});

obj.age = 75;

/* The following will only log "name=>Fred" */
for (var i in obj) {
   console.log(i + "=>" + obj[i]);
}

This works in current browsers: see http://kangax.github.com/es5-compat-table/ for details of compatibility in older browsers.

Note that the property must also be set writable in the call to Object.defineProperty to allow normal assignments (it's false by default).

Instantaneous answered 14/4, 2010 at 10:53 Comment(6)
+1 - defineProperty is almost supported in IE8, the almost meaning that it's on DOM objects only. msdn.microsoft.com/en-us/library/dd548687(VS.85).aspxElvaelvah
Andy: thanks, that's interesting. I hadn't looked up what the "almost" meant.Instantaneous
Object.getOwnPropertyNames(obj) will still get the name of the hidden property.Chadd
You could also add a convention for naming your properties, so for example, you use a double underscore in front of your private parameters, and then skip those in your code. obj.__age and typically you'll use obj.hasOwnProperty(i) when iterating through list, but instead you can say obj.hasOwnProperty(i) && i.charAt(0)!=="_" The naming convention is also a good way to remember which properties are public/private. However, you could certainly add a naming convention to the solution above and it will work just as well.Unconditional
console.log(obj); //still show {name: "Fred", age: 75}. Is it possible to hide with something else because defineProperty does not work here?Lothians
@PauliSudarshanTerho: defineProperty is working fine, at least in terms of the original question. In Chrome, the age property is slightly lighter, indicating it's not enumerable. It's still possible to list non-enumerable properties of an object using Object.getOwnPropertyNames so I'd assume that's what the console does.Instantaneous
D
38

To keep things current, this is the state of things in ES6+. I'm going a bit beyond the scope of the question and talking about how to hide properties in general, not just from the for ... in loop.

There are several ways to create what might be called "hidden properties", without looking at things like variables closed by closures, which are limited by scoping rules.

Now-classic, the non-enumerable property

As with previous versions of ECMAScript, you can use Object.defineProperty to create properties that are not marked enumerable. This makes the property not show up when you enumerate the object's properties with certain methods, such as the for ... in loop and the Object.keys function.

Object.defineProperty(myObject, "meaning of life", {
    enumerable : false,
    value : 42
});

However, you can still find it using the Object.getOwnPropertyNames function, which returns even non-enumerable properties. And of course, you could still access the property by its key, which is just a string that anyone can build, in theory.

A (non-enumerable) symbol property

In ES6, it's possible to make properties with keys of a new primitive type -- symbol. This type is used by Javascript itself to enumerate an object using a for ... of loop and by library writers to do all kinds of other things.

Symbols have a descriptive text, but they are reference types that have a unique identity. They aren't like strings, which are equal if they have the same value. For two symbols to be equal, they must be two references for exactly the same thing.

You create a symbol using the Symbol function:

let symb = Symbol("descriptive text");

You can use the Object.defineProperty function to define properties with symbols as keys.

let theSecretKey = Symbol("meaning of life");
Object.defineProperty(myObject, theSecretKey, {
    enumerable : false,
    value : 42
});

Unless someone gets a reference to that exact symbol object, they can't look up the value of the property by key.

But you can also use the regular syntax:

let theSecretKey = Symbol("meaning of life");
myObject[theSecretKey] = 42;

Properties with this key type will never show up in for ... in loops or the like, but can still be enumerable and non-enumerable, as functions like Object.assign work differently for non-enumerable properties.

Object.getOwnPropertyNames won't get you the symbol keys of the object, but the similarly named Object.getOwnPropertySymbols will do the trick.

Weak maps

The strongest way to hide a property on an object is not to store it on the object at all. Before ES6, this was kind of tricky to do, but now we have weak maps.

A weak map is basically a Map, i.e. a key-value store, that doesn't keep (strong) references to the keys so they can be garbage collected. A weak map is very limited, and doesn't allow you to enumerate its keys (this is by design). However, if you get a reference to one of the map's keys, you can get the value that goes with it.

They are primarily designed to allow extending objects without actually modifying them.

The basic idea is to create a weak map:

let weakMap = new WeakMap();

And use objects you want to extend as keys. Then the values would be sets of properties, either in the form of {} objects, or in the form of Map data structures.

weakMap.set(myObject, {
    "meaning of life" : 42
});

The advantage of this approach is that someone needs to get a reference to your weakMap instance and the key in order to get the values out, or even know they exist There's no way around that. So it's 100%, guaranteed to be secure. Hiding properties in this way ensures no user will ever discover them and your web application will never be hacked*

The biggest flaw in all this, of course, is that this doesn't create an actual property. So it doesn't participate in the prototype chain and the like.

(*) This is a lie.

Private class fields

A fairly recent addition to ECMAScript is the private class field. This feature is currently in Stage 3 and is not in the final standard yet. However, it's supported in all modern browsers and more recent versions of Node.

Private class fields are a specific variant on class field declarations. You can only use them when defining a JavaScript class. They cannot be defined anywhere else. The syntax is as follows:

class Example {
    #thisIsPrivate;
    constructor(v) {
        this.#thisIsPrivate = v;
    }
}

This field is truly private. It can only be accessed by code inside the syntactic definition of Example and nowhere else. There is no reflection API or other feature that lets you access the field. It will never appear as the result of functions such as Object.getOwnPropertyNames. The name of the field always starts with #.

Donal answered 9/2, 2018 at 19:37 Comment(2)
Just wanted to say thanks so much for this incredibly detailed and well-written response! Really feel like I learned a lot here, and super pumped to be playing around with WeakMap for the first time. I ended up using Map() for my use case instead of WeakMap() (wanted to be able to clone the map for a shallow comparison later) but the idea was the same. Super excited to not be mutating data objects.Rape
Excellent answer! Thank you for revisiting this old question and adding a new relevant answer. Private class fields is exactly what I was looking for.Skew
S
13

It's a bit tricky!

function secret() {
  var cache = {};
  return function(){
    if (arguments.length == 1) { return cache[arguments[0]];}
    if (arguments.length == 2) { cache[arguments[0]] = arguments[1]; }
  };
}
var a = secret();

a.hello = 'world';
a('hidden', 'from the world');

If you are a real pro though, you can do it this way!

var a = new (secret())();

a.hello = 'world';
a.constructor('hidden', 'from the world');

Now if you look a in firebug it will be an object ... but you know better! ;-)

Sueannsuede answered 10/4, 2011 at 6:8 Comment(2)
Great trick, exactly what I was looking for! => +1 ! Works even with IE ;-). Hint: To retrieve the value in the example, use a.constructor('hidden');.Bellows
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Sims
C
0
var Foo=function(s){
    var hidden
    this.setName=function(name){theName=name}
    this.toString=function(){return theName}
    this.public=s
}
var X=new Foo('The X')
X.setName('This is X')
X // returns 'This is X'
X.public // returns 'The X'
X.hidden // returns 'undefined'
Coheir answered 23/9, 2017 at 10:40 Comment(0)
C
0

Try this:

Object.defineProperty(
    objectName,
    'propertiesName', {
        enumerable: false
    }
)
Cards answered 26/9, 2018 at 8:47 Comment(1)
after having formatted your code, I see there is too much ) ??Lamartine
G
0

Here's a solution using the Proxy object.

an example event emitter:

class Event {
  constructor(opts = {}) {
    this.events = new Map
    this.proxy = new class {}
    Object.defineProperty(this.proxy, 'on', { value: this.on.bind(this) })
    Object.defineProperty(this.proxy, 'emit', { value: this.emit.bind(this) })
    Object.defineProperty(this.proxy, 'length', { get: () => this.length })
    Object.defineProperty(this.proxy.constructor, 'name', {
      value: this.constructor.name
    })
    return new Proxy(this.proxy, {})
  }
  on(topic, handler) {
    if (!this.events.has(topic))
      this.events.set(topic, new Set)
    this.events.get(topic).add(handler)
    return this.remove.bind(this, topic, handler)
  }
  emit(topic, ...payload) {
    if (!this.events.has(topic))
      return
    const set = this.events.get(topic)
    for (const fn of set)
      fn(...payload)
  }
  remove(topic, handler) {
    if (!this.events.has(topic))
      return
    const set = this.events.get(topic)
    if (set.has(handler))
      set.delete(handler)
  }
  get length() {
    return this.events.size
  }
}

Notice, in the constructor where it returns a new Proxy with a reference to the proxy property. I "decorated" the proxy object to make it look like the original class. You can get the length because I exposed that getter, but there's no way (as far as I know) to access the events Map directly and iterate over the keys. I guess this is sort of like a reverse closure? I'm not sure how this works in terms of garbage collection. But it does work to encapsulate functionality away from the user so they can't muck things up.

UPDATE: So, that approach was interfering with prototypal inheritance. Here I figured out a similar but better technique by hooking into the construct method when the class is created and hoisting the "hidden" variable events.

let Event = 
class Event {
  on(topic, handler) {
    if (!events.has(topic))
      events.set(topic, new Set)
    events.get(topic).add(handler)
    return this.remove.bind(this, topic, handler)
  }
  emit(topic, ...payload) {
    if (!events.has(topic))
      return
    const set = events.get(topic)
    for (const fn of set)
      fn(...payload)
  }
  remove(topic, handler) {
    if (!events.has(topic))
      return
    const set = events.get(topic)
    if (typeof handler === 'undefined')
      return events.delete(topic)
    if (set.has(handler))
      set.delete(handler)
  }
  get length() {
    return events.size
  }
}
let events
Event = new Proxy(Event, {
  construct (target, args, self) {
    events = new Map
    return Reflect.construct(target, args, self)
  }
})

Here's my gist for the more fully featured Event Emitter using this concept: https://gist.github.com/aronanda/18b6397c355da88ca170d01e0cc02628

Garrow answered 14/4, 2020 at 2:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.