2

I have a Javascript object (it happens to be the database object from the CloudKit library, but that's not important). As per How to get all properties values of a Javascript Object (without knowing the keys)? I have tried the following to list this object's keys:

console.log(Object.keys(database))
console.log(Object.getOwnPropertyNames(database))
console.log(Reflect.ownKeys(database))

And they all log:

[ '_container', '_isPublic', '_partition' ]

(EDIT: I also tried using a for/in loop, and it also logged the above.)

However, I know this object also has performQuery and saveRecords methods, and if I log them specifically I can see them:

console.log(database.performQuery)
// logs [Function: value]
console.log(database.saveRecords);
// logs [Function: value]

Can anyone explain how I can get a list of all of this object's keys, including the "secret" methods?

Community
  • 1
  • 1
machineghost
  • 33,529
  • 30
  • 159
  • 234
  • 3
    They may be non-enumerable or deeper on the prototype chain. – Sebastian Simon Mar 05 '17 at 00:11
  • But so does that mean it's impossible to list them (even with Reflection and other ES2015 stuff)? – machineghost Mar 05 '17 at 00:12
  • I think it’s impossible to list non-enumerable properties. The other ones can be listed with a `for-in` loop or other methods. – Sebastian Simon Mar 05 '17 at 00:15
  • If it's actually the case that there are a class of "impossible to list" properties (ie. non-enumerable properties that can't be listed any other way) in JS I'd certainly accept that as a valid answer, especially if said answer explained how such properties get created and why they are impossible to list. – machineghost Mar 05 '17 at 00:21
  • 1
    The [`Object.defineProperty()` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) can create or change properties to be non-enumerable. – nnnnnn Mar 05 '17 at 00:24
  • While I get the concept of non-enumerable properties, per the MDN of `getOwnPropertyNames` "returns an array whose elements are strings corresponding to the enumerable **and non-enumerable** properties found directly upon obj" (emphasis mine). So it's not *just* that these properties aren't enumerable, or else `getOwnPropertyNames` would work. – machineghost Mar 05 '17 at 00:26
  • Oh right. So if they were non-enumerable properties from higher up the prototype chain then `getOwnPropertyNames()` wouldn't list them. – nnnnnn Mar 05 '17 at 00:27
  • Ok, so if `A` has non-enumerable property `foo`, and `B` is a subclass of `A`, and I do `const b = new B();`, then `foo` will be an "invisible" property of `b`, which can't possibly be listed any way (without access to `A)? And if that's correct, please do put it an answer so I can accept it and give you points :) – machineghost Mar 05 '17 at 00:30
  • maybe slightly off topic, but actually im interested in the output of "`[Function: value]`" I assume the variable is a function, however whenever I log a function or a function's to string its something like `function a.f()` or `function anonymous()` or a string of the entire functions source or for natives `function funcName() { [native code] }` so what browser/logging method are you using to get that output? – chiliNUT Mar 05 '17 at 00:47
  • @chiliNUT Node (I guess that's how Node `console.log`s a function). – machineghost Mar 05 '17 at 01:02
  • ahhh ok that makes a lot of sense, thanks – chiliNUT Mar 05 '17 at 01:07

1 Answers1

2

One of the notorious issues with for/in in javascript is that it picks up inherited properties. (Thus .hasOwnProperty().) So you could do something like this to get all properties, inherited or not:

function(target) {
  var results = [];
  for(var key in target) {
    results.push(key);
  }
  return results;
}

I learned a lot from your question. Thanks for that.

Here's the code we figured out:

function scanProperties(obj){
  var results = [];
  while(obj) {
    results = results.concat(Object.getOwnPropertyNames(obj));
    obj = Object.getPrototypeOf(obj);
  }
  return results;
}

Basically walks up the inheritance chain, grabbing property names as it goes. Could be improved by eliminating duplicate names when they show up. (Since objects of different "classes" can have some of the same properties, only the "closest" one would apply to the object that we start with.)

Ouroborus
  • 16,237
  • 4
  • 39
  • 62
  • A `for`/`in` loop lists the same three keys as `Object.keys` does. – machineghost Mar 05 '17 at 00:19
  • @machineghost ES6 javascript also has [`proxies`](https://ponyfoo.com/articles/es6-proxies-in-depth). I don't know that CloudKit uses them, but they make it possible to create virtual properties which won't be normally listable. – Ouroborus Mar 05 '17 at 00:21
  • Interesting, but I don't believe that's the case here (the string `prox` doesn't even appear in the CloudKit source). – machineghost Mar 05 '17 at 00:22
  • @machineghost Looks like those functions live in the hidden `__proto__` property (as in `database.__proto__`). This is getting beyond my expertise as I'm not really sure how `__proto__` actually works. – Ouroborus Mar 05 '17 at 00:52
  • It's actually not hard (but usually poorly explained). Every object has a `__proto__` property that only browser debuggers, not code, can see. If you do `someObject.someKey` and the object doesn't have it, it checks its `__proto__.someKey`, and then it's `__proto__.__proto__someKey`, and so on. Where does `__proto__` come from? When you make a new object, eg. `const foo = new Foo()` the `__proto__` of `foo` is set to the `prototype` of `Foo`. That's really all there is to it ... except that alone doesn't explain things as some of the techniques I tried would have found `__proto__` props. – machineghost Mar 05 '17 at 01:07
  • @machineghost Looks like you can create properties that aren't enumerable (described [here](https://coderwall.com/p/m-rmpq/dynamically-creating-properties-on-objects-using-javascript)). cloudkit.js does appear to make use of that capability. – Ouroborus Mar 05 '17 at 01:08
  • Right, but see my comment above: per the MDN of `getOwnPropertyNames` it "returns an array whose elements are strings corresponding to the enumerable **and non-enumerable** properties found directly upon obj" (emphasis mine). So it can't be *just* that these properties are non-enumerable, or else `getOwnPropertyNames` would have included them. – machineghost Mar 05 '17 at 01:10
  • @machineghost Maybe this: `function scanProperties(obj){var results = [];while(obj){results = results.concat(Object.getOwnPropertyNames(obj));obj = Object.getPrototypeOf(obj);}return results;}` – Ouroborus Mar 05 '17 at 01:41
  • Awesome, that worked! So basically you just combined `getOwnPropertyNames` (which I was already doing) with a walk up the prototype chain (which I wasn't)? If you add this to your answer I'll happily accept it. – machineghost Mar 05 '17 at 01:53