9

I'm trying to define a non-enumerable toJSON function on a prototype object without much luck. I'm hoping for something similar to ECMAScript 5 toJSON:

Object.defineProperty(obj, prop, { enumerable: false });

However this defines it as a property which cannot be accessed as a method.
[EDIT: Nick is wrong; it can be accessed as a method. His mistake was in code that is not shown in this question - see his comments on answers below, for details.]

I was hoping to be able to define the function in a non-enumerable fashion, as I was planning to define in the prototypes of all primitive types (String, Number, Boolean, Array, and Object), so that I can recursively apply the function through complex objects.

The end goal here is to be able JSONify a Backbone model/collection with nested collections recursively.

I guess in total I have two main questions:

  1. Is it possible to define a non-enumerable function on a prototype? If so how?
  2. Is there a better way to JSONify nested Backbone models?
ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196
Nick Mitchinson
  • 5,452
  • 1
  • 25
  • 31
  • A method by definition *is* a property of an object. You're drawing a distinction that doesn't exist. If you want it to be non-enumerable, then obviously you're enumerating properties of an object. –  Jul 03 '13 at 02:10
  • 1
    Yes, however I would like it to be a non-enumerable property, in the same way that Object.defineProperty is non-enumerable. ie, is not iterated through when using `for (x in obj)` – Nick Mitchinson Jul 03 '13 at 02:15
  • 1
    Right. Again, a method is a property of an object that references a function. The code that you have is what you'd use to make the property non-enumerable. –  Jul 03 '13 at 02:16
  • To clarify @user2437417's point: Nick's code *is correct* - so his question is a bit confused/confusing. When he says *"However this defines it as a property which cannot be accessed as a method."* - he is *wrong*; it **can** be accessed as a method. Reading his comment's on answers below, it turns out his *real problem* was that the way he accessed it was incorrect. If he had shown the line of code were he tried to use it - that didn't do what he wanted - this would have been immediately apparent. – ToolmakerSteve Oct 04 '19 at 11:16

3 Answers3

16

I don't get it, why can't you access it as a method?

var foo = {};

Object.defineProperty(foo, 'bar', {
    enumerable: false,
    value: function () {console.log('foo.bar\'d!');}
});

foo.bar(); // foo.bar'd!

If you wanted it on the prototype, it's as easy as

Object.defineProperty(foo.prototype, /* etc */);

or even directly in Object.create

foo.prototype = Object.create(null, {
    'bar': {value: function () {/* ... */}}
});

However, unless you're creating instances of foo, it won't show up if you try to foo.bar, and only be visible as foo.prototype.bar.

If foo has it's own prototype (e.g. foo = Object.create({})), you can get it with Object.getPrototypeOf, add the property to that and then foo.bar would work even if it is not an instance.

var proto = Object.getPrototypeOf(foo); // get prototype
Object.defineProperty(proto, /* etc */);

You can see visibility of enumerable vs non-enumerable properties here.

Paul S.
  • 64,864
  • 9
  • 122
  • 138
  • Awesome, thanks! The problem was that I was using `get` instead of `value`, which I wasn't able to access as a function. – Nick Mitchinson Jul 03 '13 at 02:14
  • 1
    _get_ invokes the _function_ without passing any arguments, then pretends the property is equal to the `return` of the function, so you were doing `SJON()(args)` when you wanted to do `SJON(args)` – Paul S. Jul 03 '13 at 02:16
  • Okay. One last question if you don't mind; will this approach cause some of the same issues as extending the Object prototype with an enumerable function? – Nick Mitchinson Jul 03 '13 at 02:20
  • @NickMitchinson no, this will only add the property to the `foo` user object, he is not adding the property to foo.prototype ;) – csuwldcat Jul 03 '13 at 02:24
  • Non-enumerable properties will show up as [described **here**](https://developer.mozilla.org/en-US/docs/Enumerability_and_ownership_of_properties#Detection_Table). Also, editing. – Paul S. Jul 03 '13 at 02:24
2

Paul S. is right about needing to set the property definition's value instead of a get, but I wanted to add that you don't need to pass enumerable: false, because false is the default for that option in Object.defineProperty() The answer can be simplified to:

var foo = {};    

Object.defineProperty(foo, 'bar', {
    value: function(){ console.log('calling bar!'); }
});

foo.bar();
csuwldcat
  • 8,021
  • 2
  • 37
  • 32
0

Always you can avoid enumerable functions properties in object when you looping through it. And instead of define property in each object and set enumerable to false , you can create function which will call to any object with the property you want and put a condition to not take the property in the looping list. here is the example :

const obj = {
name: "myName",
title: "developer"
}


function prop() {
this.loop = function(i) {
    for (i in this) {
        if (typeof(this[i]) == "function") {
            continue;
        } else {
            console.log(this[i]);
        }
    }
}
}
prop.call(obj);
obj.loop(); 
output >>  myName, developer
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • Why/when would you use this technique, rather than marking your new function as non-enumerable? – ToolmakerSteve Oct 04 '19 at 11:22
  • For high performance, i will not go so deep but in v8 engine there algorithm says [ All then one ] this algorithm for global error object when you are writing Object.defineProperty(object, "property" => algorithm will work from this point and mini machine will start run to apply in recursion stack all objects and properties you have [All] then will take object and properties which defined [one] imagine if you have big objects you are killing your CPU literally , fn.call will work [!one then all] means if you didn't chose (this) then this will go for all (window object). – newhorizon Dec 02 '19 at 23:31