-
-
Save creationix/5206044 to your computer and use it in GitHub Desktop.
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"); |
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
@creationix I usually distinguish privacy in few different ways:
- 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
- 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. - 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) {
// ...
})
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) {
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?