Skip to content

Instantly share code, notes, and snippets.

@creationix
Created March 20, 2013 16:24
Show Gist options
  • Save creationix/5206044 to your computer and use it in GitHub Desktop.
Save creationix/5206044 to your computer and use it in GitHub Desktop.
Idea to make private properties and methods non-enumerable instead of prefixing them with underscores (in ES5 and above environments of course)
Object.defineProperty(Object.prototype, "hide", {
value: function () {
[].forEach.call(arguments, function (name) {
var descriptor = Object.getOwnPropertyDescriptor(this, name);
if (descriptor.enumerable) {
descriptor.enumerable = false;
Object.defineProperty(this, name, descriptor);
}
}, this);
}
});
function Rectangle(w, h) {
this.w = w;
this.h = h;
// For some reason, we want "w" and "h" to be private properties
this.hide("w", "h");
}
Rectangle.prototype.privateMethod = function () {};
Rectangle.prototype.hide("privateMethod");
@ggoodman
Copy link

I'm not a big fan of the convention, but may be too shortsighted to see the advantages.

To me, this seems to be mostly a cosmetic change that does not prevent outside use of the properties, but instead just outside enumeration of them. You mention on twitter that this makes the object much cleaner from an inspector's perspective and that is probably true. But beyond that, could you clarify the benefits of this sort of convention?

@mjackson
Copy link

If I were you I'd use quotes around the word "private". Whenever I see that, I immediately think of Java's private, which actually restricts access to the containing object. Here you're just hiding the property from for..in, but it's still readable/writable from anywhere.

var r = new Rectangle(20, 10);
console.log(r.w); // 20
r.w = 'foo';
console.log(r.w); // foo

@Gozala
Copy link

Gozala commented Mar 21, 2013

@creationix I usually distinguish privacy in few different ways:

  1. Really private other scripts having access to my objects can't temper with them or gain more capabilities. This sort of privacy today is only achievable through closures or weakmaps. In a future possibly with private names
  2. Marking properties as private a la _private that communicates that property is private and changes to it from
    the external may cause unpredictable reactions. For this purpose _ prefix works very well, and in fact marking
    such properties non-enumerable is has risks of other changing them by accident. Of course later comment may
    or may not apply to your case.
  3. Privacy in a sense that you wanna avoid name collisions or accidents where people unintentionally change properties on your objects (happenes mostly on sub-classing when some properties get unintentionally overridden
    by derivee).

In SDK we use weak-maps for private properties since capability leak is a real issue and can cause harm. In personal projects I rarely have such concerns and mostly care about name collisions and use following technique for making names unique:

var width = "width@" + module.filename;
var height = "height@" + module.filename;

function Rectangle(w, h) {
  this[width] = w;
  this[height] = h;
}

This also goes well along with private names that may or may not happen one day.

Finally I'm starting to drift more and more in favor of using polymorphic functions for designing APIs
that avoid name collisions and have all the function composibily advantages:

var width = method("width@package-name")
var height = method("height@package-name")

// Using closures

function Rectangle(w, h) {
   width.implement(this, function() { return w })
   height.implement(this, function() { return h })
}

var r = new Rectangle(20, 10);
Object.keys(r) // => []
width(r) // => 20
height(r) // => 10


// Approach with weak maps

var models = new WeakMap();
function Rectangle(w, h) {
  models.set(this, { w: w, h: h })
}
width.define(Rectangle, function(rectangle) {
  return models.get(rectangle).w
})
height.define(Rectangle, function(rectangle) {
  return models.get(rectangle).h
})

var r = new Rectangle(20, 10);
Object.keys(r) // => []
width(r) // => 20
height(r) // => 10


// Approach with just unique name

var _width = module.id + "#width"
var _height = module.id + "#height"

function Rectangle(w, h) {
  this[_width] = w
  this[_height] = h
}
width.define(Rectangle, function(rectangle) {
  return rectangle[_width]
})
height.define(Rectangle, function(rectangle) {
  return rectangle[_height]
})

var r = new Rectangle(20, 10);
Object.keys(r) // => ["module/id#width", "module/id#height"]
width(r) // => 20
height(r) // => 10

The best part is here is that all the code deals with just width and height functions that can be passed
a rectangle or anything that implements these functions. And implementation is type / instance specific.
Even better functions definitions are independent of type definitions and there for different types can
implement same interface (by which a mean set of polymorphic functions) without being aware of each
other. Even better you can define definitions for types from other libraries to make them API compatible:

// rectangle.js
var shapes = require("my/shape/lib")
var width = shapes.width;
var height = shapes.height;

function Rectangle(w, h) {
  // ...
}
width.define(Rectangle, function(r) {
  // ...
})
height.define(Rectangle, function(r) {
  // ...
})

// independent.js

var shapes = require("my/shape/lib")
var width = shapes.width;
var height = shapes.height;

function Whatever() {}
width.define(Rectangle, function(r) {
  // ...
})
height.define(Rectangle, function(r) {
  // ...
})


// dom.js

var shapes = require("my/shape/lib")
var width = shapes.width;
var height = shapes.height;

width.define(HTMLElement, function(element) {
  return element.style.width
})
height.define(HTMLElement, function(element) {
  return element.style.width
})


// adapter-for-third-party-lib.js

var shapes = require("my/shape/lib")
var width = shapes.width;
var height = shapes.height;

var graphics = require("third/party/graphic/lib")
var View = graphics.View

width.define(View, function(view) {
  // ...
})
height.define(HTMLElement, function(element) {
  // ...
})

@wilmoore
Copy link

@creationix:

I can think of a couple special case uses for this. Just glancing at it, I was wondering if this wouldn't be safer if line 5 were instead:

if (descriptor.enumerable && descriptor.configurable) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment