Is there any way to have DOM elements selectable through objects?
For example I want to be able to associate objects to DOM elements like so:
var obj = { a: 1, b:2 };
$('a').click(function() { this.selectThing = obj });
And later on...
$.something(obj);
Or even better:
$('a|selectThing?=', obj);
Something like that. You can see that I want to associate an object to a DOM element in such a way that I can grab the element with the object.
I know this can be done with the filter() method, my question is if there's a more elegant way that doesn't use filter() to do this.
EDIT:
To clarify, I want to be able to use an object kind of like a selector, so I can do something similar to this $(obj) obviously that won't work, but you get the idea (I hope)
EDIT #2:
I want to be able to do something like this:
var obj = { prop: 'prop' };
$('a').bindTo(obj);
$.retreive(obj) // should equal $('a')
I don't want it to alter obj in any way though (obj should still be {prop: 'prop'} only).
demo
var $div1 = $('.box1');
var $div2 = $('.box2');
var obj = { a: $div1, b: $div2 };
obj.a.css({background:'red'});
Or the short way: var obj = { a: $('.box1'), b: $('.box2') };
demo jsBin 2
var obj = $('.box1, .box2'); // store objects
obj.css({background:'red'}); // access collection
You're looking for $.data. This method associates any JavaScript object or primitive with a DOM element. Under the hood, it's not adding the data as an expando to the DOM element or anything--instead, jQuery maintains its own object cache of DOM elements and data hashes. But that's under the hood; the point is, I think it's exactly what you're looking for.
$('#example').data('foo', { bar: 'quux' }); // returns the jquery object containing '#example', like most jQuery methods
Then, later:
console.log($('#example').data('foo')); // returns {bar: 'quux'}
I dont think this is easily achievable. Let me clarify:
To achieve what you want you would require a hashmap that allows objects in the position of keys. JavaScript does not (yet) support objects as keys in hashmaps though. So, for example, the following does not work:
var key = {value: 'key'};
var data {value: 'data'};
var map = {};
map[key] = data;
There are other solutions to achieve this in current javascript implementations, eg. a double lookup:
var key = {value: 'key'};
var data {value: 'data'};
var map = { keys: [], data: [], get: function (key) {
var k = this.keys.indexOf(key);
if (k >= 0) {
return this.data[k];
} else return undefined;
}, set: function (key, val) {
var k = this.keys.indexOf(key);
if (k < 0) {
k = this.keys.push(k) - 1;
}
this.data[k] = val;
} };
map.set(key, data);
map.get(key).value;
This implementation however is of a terrible performance. There is a proposal for a so called WeakMap in JavaScript Harmony. Firefox I believe is currently the only browser implementing them, though. Since the feature required is not widely available and workarounds are of poor performance I would recommend trying to figure out a different way of achieving what you are trying to.
Extend jQuery with three methods:
jQuery.bindObj(data)
jQuery.unbindObj(data)
$.retrieve(data)
Your code looks like:
$('a').bindObj({blorg: 'shmorg'});
console.log($.retrieve({blorg: 'shmorg'})); // logs live result of $('a');
Full source: http://jsfiddle.net/nUUSV/6/.
The trick to this solution is storing the selectors/identifiers based to the jQuery constructor in one array, and the objects bound to those selectors/identifiers in another array, then using $.inArray to get the index of the object upon retrieval and using that index to grab the bound jQuery collection.
As I understand, you're looking for some sugar way to run multiple named searches on the DOM and have results filtered in a namespace object.
If so, I guess the following jquery extension might be helpfull to you:
$.fn.seek = function (selectors) {
var container = this,
found = {};
$.each(selectors, function (name) {
if ($.isPlainObject(selectors[name])) {
found[name] = $(container).seek(selectors[name]);
}
else if ($.type(selectors[name]) === 'string') {
found[name] = $(container).find(selectors[name]);
}
});
return found;
}
And here's the example of how the above extension might be applicable to your cases:
var res = $('body').seek({
links: 'a',
headers: 'h1,h2,h3,h4,h5,h6'
});
$(res.links).css({ color: 'green' });
$(res.headers).css({ color: 'red' });
I hope this helps you.
Not sure if this is what you are looking for. Perhaps you could write a custom selector based on the jquery selector, which handles objects with a selector-property the way you like. A selectable object would look like
var objSelector = {'selector' : '#select-me', 'a' : 'somestring', 'b' : 1243};
So you are free to use it like any other object, but you have to add the selector property. Than you add your custom selector:
$$ = (function($) {
return function(el, tag) {
if (typeof el === 'object' && el.selector !== undefined) {
return $(el.selector);
}
return $(el);
}
}($));
Now you can do things like
$$(objSelector).css({'border':'1px solid red'});
See an implementation on http://jsfiddle.net/JXcnJ/
If I understood correctly then, I think you need to define a property and say enumerable as false. See below,
Note: Below is just an example to demonstrate and not exactly meant to do such stuff,
DEMO
$(function() {
$.fn.bindTo = function(o) {
var _that = this;
Object.defineProperty(o, 'myFx', {
value: function() { return $(_that); },
writable: true,
enumerable: false,
configurable: true
});
}
$.retrieve = function(obj) {
return obj.myFx();
}
var obj = {
prop: 'prop'
};
$('#test').bindTo(obj);
$($.retrieve(obj)).html('Test');
//below is for proof
for (i in obj) {
alert(obj[i]);
}
});
Reference: http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/
Related
I find that when I write javascript I encounter a situation where I am forced to write ugly code. This is due to my inability to reconcile the following two criteria:
1) Define data using shorthand, e.g. var data = { a: 1, b: { c: 2, d: 3 ... } }
2) Use a primitive operator to check for property existence
For example consider a function which returns the intersection of two Object instances' keys as an Array of the intersecting keys:
var intersection = function(obj1, obj2) {
var result = [];
for (var k in obj1) if (k in obj2) result.push(k);
return result;
};
That code looks quite nice. But unfortunately, it doesn't always work as expected! This is due to the inconsistency between for (x in y), and if (x in y): Using for (x in y) will only iterate over "own" properties (properties which return true for hasOwnProperty), while if (x in y) while apply for "own" and "non-own" properties!
If I call intersection like so:
var obj1 = { toString: 'hahaha' };
var obj2 = {};
var intersectingKeys = intersection(obj1, obj2);
I will wind up with intersectingKeys === [ 'toString' ]; Obviously this is not correct: an intersection operation involving an empty set (as obj2 appears to be) must return an empty set. While {} is clearly intended to be "empty", our problem is that ('toString' in {}) === true. This also applies to terms such as 'constructor', 'valueOf', as well as any new properties that are introduced to Object.prototype in the future.
In my opinion, if a native operator can provide iteration over keys, a native operator should be able to verify whether a key will appear in an iteration. It feels inconsistent and ugly, to me, to use a native operator for one, but a function call for the other. For that reason I dislike this fix:
var safeIntersection = function(obj1, obj2) {
var result = [];
for (var k in obj1) if (obj2.hasOwnProperty(k)) result.push(k);
return result;
};
If if (x in y) must be used, I only see one other possible solution: Ensure that the parameters passed to intersection have no properties at all, apart from properties explicitly defined by our code. In other words, ensure that we only work with prototype-less objects:
var obj1 = Object.create(null, {
toString: {
configurable: true,
enumerable: true,
writable: true,
value: 'hahaha'
}
});
var obj2 = Object.create(null, {});
var intersectingKeys = intersection(obj1, obj2);
Note that this code uses intersection, not safeIntersection, and still works because obj1 and obj2 are prototype-less. But the problem is, now data definition is really, really clunky! Look at how much code it takes to define an object with a single "toString" property. This approach prevents us from using javascript's beautiful object-shorthand. Even if we write a utility function to encompass prototype-less object creation, the definition of nested objects is still incredibly clunky:
// Utility function for prototype-less object definition
var obj = function(props) {
return Object.create(null, props.map(function(v) {
return {
writable: true,
configurable: true,
enumerable: true,
value: v
};
}));
};
// Now defining `obj1` looks ok...
var obj1 = obj({ toString: 'hahaha' });
// But for large, nested object definitions it's sooper ugly:
var big = obj({
a: 'a value',
b: 'b value',
moreProps: obj({
wheee: 'yay',
evenMoreProps: obj({
prop: 'propMeUp'
/* ... */
})
/* ... */
})
});
Javascript's object-definition shorthand is a huge perk of the language, and throwing it away by being forced to wrap all { ... } instances in a function call seems like a tremendous pity.
My ideal solution to this problem would involve converting the shorthand object constructor to produce prototype-less objects. Perhaps a global setting:
// Perhaps along with other global settings such as:
'use strict';
Error.stackTraceLimit = Infinity;
// We could also have:
Object.shorthandIncludesPrototype = false;
Although even if this solutions were available it would break tons and tons of pre-existing libraries. :(
How do I reconcile the following criteria???:
1) Write code that works
2) Use the primitive in operator to check for property existence
3) Define objects using typical shorthand
Perhaps it's impossible to meet all these criteria simultaneously. In that case, what are some of the next-best approaches for keeping code clean in these cases?
You could get first the own properties of the first object with Object.keys and filter with Object.hasOwnProperty the second object's keys.
function intersection(o1, o2) {
return Object.keys(o1).filter({}.hasOwnProperty.bind(o2));
}
console.log(intersection({ a: 10, b: 20, e: 30 }, { a: 10, c: 20, d: 30 })); // ['a']
console.log(intersection({ toString: 'hahaha' }, {})); // []
So, you've set out three incompatible requirements. You say you have to use in. You say objects have to be defined as {} so they will have a prototype. But, you don't like the way in works. And, you want code that uses those, but works differently than they are designed. Those are your two choices.
There's NO answer with those requirements. in works the way it does. You can't change it. We can't change it. Yes, it works differently based on the context, but that's how it's implemented. Deal with that by finding a practical solution to a real problem or write your own language that works the way you want the language to work.
FYI, for a real-world solution you may want to consider a Map object for storing your data and use .get(), .has() and .set() on it. Simple, clear, works.
Prior Answer BEFORE question was massively edited
First off, you can just use Object.create(null); to create a prototype-less object. So, the only properties it will have are the ones you put there.
I'd suggest that rather than accessing a property directly off the object, you just create one reusable function that checks to see if the property name is valid before returning it:
function sendValidProperty(req, res) {
var propName = req.params.propertyName;
if (Object.prototype.hasOwnProperty.call(dataObject, propName) {
res.send(dataObject[propName]);
} else {
res.sendStatus(400);
}
}
router.get('/propertyOfObject/:propertyName', function(req, res) {
sendValidProperty(req, res);
});
Or, you could encapsulate a smaller piece of it:
function getValidProperty(obj, propName) {
return Object.prototype.hasOwnProperty.call(dataObject, propName) ? obj[propName] : null;
}
router.get('/propertyOfObject/:propertyName', function(req, res) {
let val = getValidProperty(dataObject, res.params.propertyName);
if (val !== null) {
res.send(val);
} else {
res.sendStatus(400);
}
});
In either case, you don't have to repeat the checking of a property. That's in the common, shared function. FYI, when you want to use object methods on a prototype-less object, you can use the form I show above: Object.prototype.hasOwnProperty.call(dataObject, propName) instead of dataObject.hasOwnProperty(propName).
One of the main rules of server development is to NEVER trust the input you get from a request. You always have to check it or sanitize it before using it. This whole question kind of sounds like you're trying to avoid doing that. You just can't shortcut checking input and have a fully reliable server.
I have 2 extra solutions that are probably worth mentioning here:
1) Recursive obj function
As I mentioned in the question, having an obj method which creates an un-prototyped value from a regular Object defined via shorthand gets really ugly when it needs to be applied all throughout a nested shorthand structure - but this problem can be somewhat solved if the obj method is recursive, so that it only needs to be applied to the root Object and not explicitly to every inner child property:
var obj = function(val) {
if (val.constructor === Object) {
var ret = Object.create(null);
for (var k in val) ret[k] = obj(val[k]);
} else if (val.constructor === Array) {
var ret = [];
for (var i = 0; i < val.length; i++) ret.push(obj(val[i]));
} else {
var ret = val;
}
return ret;
};
var thing = obj({
prop1: 'hello',
prop2: 'hi',
prop3: [
{ num: 1, val: 'lalala' },
{ num: 2, val: 'heehee' },
{ num: 3, val: 'hoho' },
],
really: {
deep: {
property: {
over: {
here: { val: 'soooo deep' }
}
}
}
}
});
console.log('toString' in thing);
console.log('toString' in thing.prop3[1]);
console.log('toString' in thing.really.deep.property.over.here);
Now it's important that all shorthand objects are wrapped in obj.
2) Clear out Object.prototype
I've found that chrome and node (v0.12.0) will allow me to delete all properties from Object.prototype. The prototype can even be restored at any point in the future if these removed properties are kept in memory:
var savedPrototype = {};
var removePrototype = function() {
savedPrototype = {};
var props = Object.getOwnPropertyNames(Object.prototype);
for (var i = 0; i < props.length; i++) {
savedPrototype[props[i]] = Object.prototype[props[i]];
delete Object.prototype[props[i]];
}
};
var restorePrototype = function() {
for (var k in savedPrototype) {
Object.prototype[k] = savedPrototype[k];
}
};
removePrototype();
var obj = { val: 'haha' };
console.log('toString' in obj); // false
restorePrototype();
console.log('toString' in obj); // true
As jfriend00 already wrote, the question proposes constraints that are fundamentally impossible in the current versions of JavaScript. The best we can do is write some abstractions within the limitations of the language.
We've already looked at some possibilities, such as using a wrapper function (like obj() in the question). Because "universal support" is not a criterion in the question, I'll propose one solution using ES6 Proxies. We can use the has() handler method of a Proxy to alter the behavior of the in operator so that it only considers own properties:
Object.prototype.own = function () {
return new Proxy(this, {
has: function (target, propertyName) {
return target.hasOwnProperty(propertyName);
}
});
};
document.writeln('toString' in {});
document.writeln('toString' in {}.own());
document.writeln('foo' in { foo: 1 });
document.writeln('foo' in { foo: 1 }.own());
This requires a modern browser (no IE; Edge OK) or Node 6.4.0+. Of course, adding members to the prototype of Object is a risky move. We can add additional handlers to the Proxy as needed, or rewrite the implementation for ES5 and below (but this requires significantly more code).
Compatibility concerns aside, this approach satisfies the requirements of the question: we can use object literals and the in operator, and the code is concise and readable. We just need to remember to call own() when we need it.
We can rewrite the intersection() method from the question using this convention:
Object.prototype.own = function () {
return new Proxy(this, {
has: function (target, propertyName) {
return target.hasOwnProperty(propertyName);
}
});
};
var intersection = function(obj1, obj2) {
var result = [];
for (var k in obj1.own()) if (k in obj2.own()) result.push(k);
return result;
};
document.writeln(intersection({ a: 1, b: 2, c: 3 }, { b: 2, c: 3, d: 4}));
Your question could be solved with some beautiful code.
ES5
var intersect = function(obj1, obj2) {
var keys1 = Object.keys(obj1);
var keys2 = Object.keys(obj2);
return keys1.filter(function(k) {
return keys2.indexOf(k) !== -1;
});
}
ES6
let intersect = (obj1, obj2) => {
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
return keys1.filter(k => keys2.includes(k));
}
This is the JavaScript way of solving such problems. It is way better, since you don't need to use any loop. The built-in methods like filter(), indexOf() or includes() would perform better than any loop.
This is such a fundamental question, that I'm sure it's a duplicate, so I apologize in advance, but is this how I write an object such that I use by saying:
myApplication.myFirstMethod(x);
x = myApplication.myFirstMethod();
Here's the code:
myApplication = {};
(function() {
myApplication.myFirstMethod = function() {
var local = {};
if (arguments.length) {
local.result = arguments[0];
}
return local.result;
}
myApplication.mySecondMethod = function() {
var local = {};
if (arguments.length) {
local.result = arguments[0];
}
return local.result;
}
})();
jsFiddle Demo
A more object oriented approach would be to use instantiation and prototype.
Setup
var Application = function(){
this.local = {};
};
Application.prototype.Value = function(){
if (arguments.length) {
this.local.result = arguments[0];
}else{
return this.local.result;
}
};
Used
var app = new Application();
app.Value(6);
alert(app.Value());//6
From a jQuery point of view, they will first screen to see if there are arguments, this code is direct from their source for the val function:
val: function( value ) {
if ( !arguments.length ) {
var elem = this[0];
...
It then goes on to use the element's native API and some other metrics to get the value for the element (In general, the only type of elements which will return a value from val are going to be elements such as input, select, etc. - form elements basically).
At the end of the if block it attempts to return various results based on if it found a value attached to the element (or set of elements). This guarantees that the clause of "setting" never executes when a "get" is encountered. If the case is that "set" is being used it goes through a slightly complex set of code to properly set a value to the element.
The reason that the code shows val: function() is because it is part of an object which is being used to "extend" the jQuery prototype using jQuery's extend functionality.
This is the exact code in a jsfiddle of jQuery's val function
There are many patterns for creating objects like this and everyone has their favorites. Addy Osmani does an excellent job of summarizing the most popular patterns in his Javascript Design Patterns "book". Specifically, this section:
http://addyosmani.com/resources/essentialjsdesignpatterns/book/#designpatternsjavascript
I reread this semi-annualy just to make sure I'm keeping all the patterns in my quiver.
I could not find any way to accomplish the task of such conversion as I could not find any means of getting Ember.js properties for the object. Ember.keys returns only the properties I set in create or with get and the properties declared in Ember.extend do not show up there. I use such properties to set up default values (e.g. [] for array properties)
Here is my dirty workaround
var newModel = JSON.parse(JSON.stringify(model));
I would do something similar to the person above, but I'd do it a little bit differently.
Mixin
App.NativeObject = Ember.Mixin.create({
toNative: function() {
var properties = [];
for (var key in this) {
if (jQuery.inArray(Ember.typeOf(object[key]), ['string', 'number', 'boolean']) !== -1) {
properties.push(key);
}
}
return this.getProperties(properties);
}
});
Object
Then you just need to implement the App.NativeObject mixin in your objects that you would like the toNative on:
var Object = Ember.Object.extend(App.NativeObject, {
name: 'Adam',
count: 4
});
We then have the toNative method on all the objects that implement our mixin.
Obligatory jsFiddle: http://jsfiddle.net/jumUx/
If your object is a subclass of ember-data model notice you can use the toJSON method otherwise you can use:
JSON.parse(JSON.stringify(emberObj))
To grab any values which support native json serialization (i.e. not functions/methods)
This worked for me:
myModel.toJSON({includeId: true})
I'm using Ember 3.
This is what I did and it works quite well. Note, this should be ready only, as any changes to an object or array in the copied object will affect the original object
App.BaseValidations = Ember.Object.create({
toObject: function() {
var destination = {}
for (var k in this) {
if (this.hasOwnProperty(k) && typeof(this[k]) !== 'function') {
destination[k] = this[k];
}
}
return destination;
}
})
something quite simple that worked properly enough for me is :
Ember.Object.reopen({
toJson: function() {
return JSON.parse(JSON.stringify(this));
}
});
at app loading time.
At the moment I solve it with the following snippet:
App.plainCopy = function (obj) {
if (Ember.isArray(obj)) {
return obj.map(App.plainCopy);
} else if (typeof(obj) === "object") {
if (App.Plainable.detect(obj)) {
return obj.plainCopy();
} else {
throw new Error(Ember.String.fmt("%# is not Plainable", [obj]));
}
} else {
return obj;
}
}
App.Plainable = Ember.Mixin.create({
plainCopy: function() {
var props = Ember.keys(this);
var proto = this.constructor.prototype;
for(p in proto) {
if (proto.hasOwnProperty(p) && typeof(this[p])!=="function") {
props.push(p);
}
}
var copy = {};
props.forEach(function(p) {
copy[p] = App.plainCopy(this.get(p));
}, this);
return copy;
}
});
It does not go up the class hierarchy and does not look into mixins (as I use for data objects which are quite simple form that point of view)
With modern (3.17) ember, I've used myEmberObject.getProperties('id', 'name', 'foo', 'bar')
It produces a plain object.
Another possible solution that may suit your needs while not being fully recursive for nested Ember objects:
// where myEmberObject is.. an ember object
var plainJavaScriptObject = myEmberObject.toJSON();
This will only include actual properties that you've defined and no Ember internals. Again, the drawback here is that any nested Ember objects will not, themselves, be converted but will appear as Strings in style of "".
I would like to add key-value pairs of metadata to arbitrary JavaScript objects. This metadata should not affect code that is not aware of the metadata, that means for example
JSON.stringify(obj) === JSON.stringify(obj.WithMetaData('key', 'value'))
MetaData aware code should be able to retrieve the data by key, i.e.
obj.WithMetaData('key', 'value').GetMetaData('key') === 'value'
Is there any way to do it - in node.js? If so, does it work with builtin types such as String and even Number? (Edit Thinking about it, I don't care about real primitives like numbers, but having that for string instances would be nice).
Some Background: What I'm trying to do is cache values that are derived from an object with the object itself, so that
to meta data unaware code, the meta data enriched object will look the same as the original object w/o meta
code that needs the derived values can get it out of the meta-data if already cached
the cache will get garbage collected alongside the object
Another way would be to store a hash table with the caches somewhere, but you'd never know when the object gets garbage collected. Every object instance would have to be taken care of manually, so that the caches don't leak.
(btw clojure has this feature: http://clojure.org/metadata)
You can use ECMA5's new object properties API to store properties on objects that will not show up in enumeration but are nonetheless retrievable.
var myObj = {};
myObj.real_property = 'hello';
Object.defineProperty(myObj, 'meta_property', {value: 'some meta value'});
for (var i in myObj)
alert(i+' = '+myObj[i]); //only one property - #real_property
alert(myObj.meta_property); //"some meta value"
More information here: link
However you're not going to be able to do this on primitive types such as strings or numbers, only on complex types.
[EDIT]
Another approach might be to utilise a data type's prototype to store meta. (Warning, hack ahead). So for strings:
String.prototype.meta = {};
String.prototype.addMeta = function(name, val) { this.meta[name] = val; }
String.prototype.getMeta = function(name) { return this.meta[name]; };
var str = 'some string value';
str.addMeta('meta', 'val');
alert(str.getMeta('meta'));
However this is clearly not ideal. For one thing, if the string was collected or aliased (since simple data types are copied by value, not reference) you would lose this meta. Only the first approach has any mileage in a real-world environment, to be honest.
ES6 spec introduces Map and WeakMap. You can enable these in node by running node --harmony and by enabling the experimental javascript flag in Chrome, (it's also in Firefox by default). Maps and WeakMaps allow objects to be used as keys which can be be used to store metadata about objects that isn't visible to anyone without access to the specific map/weakmap. This is a pattern I now use a lot:
function createStorage(creator){
creator = creator || Object.create.bind(null, null, {});
var map = new Map;
return function storage(o, v){
if (1 in arguments) {
map.set(o, v);
} else {
v = map.get(o);
if (v == null) {
v = creator(o);
map.set(o, v);
}
}
return v;
};
}
Use is simple and powerful:
var _ = createStorage();
_(someObject).meta= 'secret';
_(5).meta = [5];
var five = new Number(5);
_(five).meta = 'five';
console.log(_(someObject).name);
console.log(_(5).meta);
console.log(_(five).meta);
It also facilitates some interesting uses for separating implementation from interface:
var _ = createStorage(function(o){ return new Backing(o) });
function Backing(o){
this.facade = o;
}
Backing.prototype.doesStuff = function(){
return 'real value';
}
function Facade(){
_(this);
}
Facade.prototype.doSomething = function doSomething(){
return _(this).doesStuff();
}
There is no "comment" system in JSON. The best you can hope for is to add a property with an unlikely name, and add that key contaning the metadata. You can then read the metadata back out if you know it's metadata, but other setups will just see it as another property. And if someone uses for..in...
You could just add the Metadata as a "private" variable!?
var Obj = function (meta) {
var meta = meta;
this.getMetaData = function (key) {
//do something with the meta object
return meta;
};
};
var ins_ob = new Obj({meta:'meta'});
var ins_ob2 = new Obj();
if(JSON.stringify(ins_ob) === JSON.stringify(ins_ob2)) {
console.log('hoorai');
};
If you want object-level metadata, you could create a class that extends Object. Getters and setters are not enumerable and, obviously, neither are private fields.
class MetadataObject extends Object {
#metadata = undefined;
get metadata() { return this.#metadata; }
set metadata(value) { this.#metadata; }
}
var obj = new MetadataObject();
obj.a = 1;
obj.b = 2;
obj.metadata = { test: 123 };
console.log(obj); // { a: 1, b: 2 }
console.log(obj.metadata); // { test: 123 }
console.log(JSON.stringify(obj)); // '{"a":1,"b":2}'
You can even simplify the implementation using a Map. Without a setter on metadata, you have to use Map methods to modify it.
class MetadataObject extends Object {
#metadata = new Map();
get metadata() { return this.#metadata; }
}
var obj = new MetadataObject();
obj.a = 1;
obj.b = 2;
obj.metadata.set('test', 123);
console.log(obj); // { a: 1, b: 2 }
console.log(obj.metadata.get('test')); // 123
console.log(JSON.stringify(obj)); // '{"a":1,"b":2}'
I ran into a situation where I needed property level metadata, and used the latter implementation.
obj.id = 1;
obj.metadata.set('id', 'metadata for the id property');
I would like to make an object's structure immutable, preventing its properties from being subsequently replaced. The properties need to be readable, however. Is this possible?
I'm sure there are no language features (along the lines of final in Java and readonly in C#) to support this but wondered whether there might be another mechanism for achieving the same result?
I'm looking for something along these lines:
var o = {
a: "a",
f: function () {
return "b";
}
};
var p = o.a; // OK
o.a = "b"; // Error
var q = o.f(); // OK
o.f = function () { // Error
return "c";
};
ECMAScript 5 will have seal() and freeze(), but there's no good way to do this with current JavaScript implementations.
Source.
the best thing you can do is hide your properties inside of a closure.
var getMap = function(){
var hidden = "1";
return {
getHidden : function() { return hidden; }
}
}
var f = getMap ();
alert(f.getHidden());
I took a stab at it. In the above code you will need to not just return hidden but copy it into a new object perhaps. maybe you can use jquery's extend to do this for you, so you will be returning a new object, not the reference. This may be completely wrong though =)
Using var in an object constructor will create a private variable. This is essentially a closure. Then you can create a public function to access/modify it. More information and examples available on Private Members in Javascript by Douglas Crockford.
As mkoryak said, you can create a closure to hide properties
function Car(make, model, color) {
var _make = make, _model = model, _color = color;
this.getMake = function() {
return _make;
}
}
var mycar = new Car("ford", "mustang", "black");
mycar.getMake(); //returns "ford"
mycar._make; //error
Okay, so there's been already a couple of answers suggesting you return an object with several getters methods. But you can still replace those methods.
There's this, which is slightly better. You won't be able to replace the object's properties without replacing the function completely. But it's still not exactly what you want.
function Sealed(obj) {
function copy(o){
var n = {};
for(p in o){
n[p] = o[p]
}
return n;
}
var priv = copy(obj);
return function(p) {
return typeof p == 'undefined' ? copy(priv) : priv[p]; // or maybe copy(priv[p])
}
}
var mycar = new Sealed({make:"ford", model:"mustang", color:"black"});
alert( mycar('make') ); // "ford"
alert( mycar().make ); // "ford"
var newcopy = mycar();
newcopy.make = 'volkwagen';
alert( newcopy.make ); // "volkwagen" :(
alert( mycar().make ); // still "ford" :)
alert( mycar('make') ); // still "ford" :)
You can now force a single object property to be frozen instead of freezing the whole object. You can achieve this with Object.defineProperty and the parameter writable: false
var obj = {
"first": 1,
"second": 2,
"third": 3
};
Object.defineProperty(obj, "first", {
writable: false,
value: 99
});
In this example, obj.first now has its value locked to 99.