object.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Utilities for manipulating objects/maps/hashes.
  16. * @author arv@google.com (Erik Arvidsson)
  17. */
  18. goog.provide('goog.object');
  19. /**
  20. * Whether two values are not observably distinguishable. This
  21. * correctly detects that 0 is not the same as -0 and two NaNs are
  22. * practically equivalent.
  23. *
  24. * The implementation is as suggested by harmony:egal proposal.
  25. *
  26. * @param {*} v The first value to compare.
  27. * @param {*} v2 The second value to compare.
  28. * @return {boolean} Whether two values are not observably distinguishable.
  29. * @see http://wiki.ecmascript.org/doku.php?id=harmony:egal
  30. */
  31. goog.object.is = function(v, v2) {
  32. if (v === v2) {
  33. // 0 === -0, but they are not identical.
  34. // We need the cast because the compiler requires that v2 is a
  35. // number (although 1/v2 works with non-number). We cast to ? to
  36. // stop the compiler from type-checking this statement.
  37. return v !== 0 || 1 / v === 1 / /** @type {?} */ (v2);
  38. }
  39. // NaN is non-reflexive: NaN !== NaN, although they are identical.
  40. return v !== v && v2 !== v2;
  41. };
  42. /**
  43. * Calls a function for each element in an object/map/hash.
  44. *
  45. * @param {Object<K,V>} obj The object over which to iterate.
  46. * @param {function(this:T,V,?,Object<K,V>):?} f The function to call
  47. * for every element. This function takes 3 arguments (the value, the
  48. * key and the object) and the return value is ignored.
  49. * @param {T=} opt_obj This is used as the 'this' object within f.
  50. * @template T,K,V
  51. */
  52. goog.object.forEach = function(obj, f, opt_obj) {
  53. for (var key in obj) {
  54. f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);
  55. }
  56. };
  57. /**
  58. * Calls a function for each element in an object/map/hash. If that call returns
  59. * true, adds the element to a new object.
  60. *
  61. * @param {Object<K,V>} obj The object over which to iterate.
  62. * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to call
  63. * for every element. This
  64. * function takes 3 arguments (the value, the key and the object)
  65. * and should return a boolean. If the return value is true the
  66. * element is added to the result object. If it is false the
  67. * element is not included.
  68. * @param {T=} opt_obj This is used as the 'this' object within f.
  69. * @return {!Object<K,V>} a new object in which only elements that passed the
  70. * test are present.
  71. * @template T,K,V
  72. */
  73. goog.object.filter = function(obj, f, opt_obj) {
  74. var res = {};
  75. for (var key in obj) {
  76. if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
  77. res[key] = obj[key];
  78. }
  79. }
  80. return res;
  81. };
  82. /**
  83. * For every element in an object/map/hash calls a function and inserts the
  84. * result into a new object.
  85. *
  86. * @param {Object<K,V>} obj The object over which to iterate.
  87. * @param {function(this:T,V,?,Object<K,V>):R} f The function to call
  88. * for every element. This function
  89. * takes 3 arguments (the value, the key and the object)
  90. * and should return something. The result will be inserted
  91. * into a new object.
  92. * @param {T=} opt_obj This is used as the 'this' object within f.
  93. * @return {!Object<K,R>} a new object with the results from f.
  94. * @template T,K,V,R
  95. */
  96. goog.object.map = function(obj, f, opt_obj) {
  97. var res = {};
  98. for (var key in obj) {
  99. res[key] = f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);
  100. }
  101. return res;
  102. };
  103. /**
  104. * Calls a function for each element in an object/map/hash. If any
  105. * call returns true, returns true (without checking the rest). If
  106. * all calls return false, returns false.
  107. *
  108. * @param {Object<K,V>} obj The object to check.
  109. * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to
  110. * call for every element. This function
  111. * takes 3 arguments (the value, the key and the object) and should
  112. * return a boolean.
  113. * @param {T=} opt_obj This is used as the 'this' object within f.
  114. * @return {boolean} true if any element passes the test.
  115. * @template T,K,V
  116. */
  117. goog.object.some = function(obj, f, opt_obj) {
  118. for (var key in obj) {
  119. if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
  120. return true;
  121. }
  122. }
  123. return false;
  124. };
  125. /**
  126. * Calls a function for each element in an object/map/hash. If
  127. * all calls return true, returns true. If any call returns false, returns
  128. * false at this point and does not continue to check the remaining elements.
  129. *
  130. * @param {Object<K,V>} obj The object to check.
  131. * @param {?function(this:T,V,?,Object<K,V>):boolean} f The function to
  132. * call for every element. This function
  133. * takes 3 arguments (the value, the key and the object) and should
  134. * return a boolean.
  135. * @param {T=} opt_obj This is used as the 'this' object within f.
  136. * @return {boolean} false if any element fails the test.
  137. * @template T,K,V
  138. */
  139. goog.object.every = function(obj, f, opt_obj) {
  140. for (var key in obj) {
  141. if (!f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
  142. return false;
  143. }
  144. }
  145. return true;
  146. };
  147. /**
  148. * Returns the number of key-value pairs in the object map.
  149. *
  150. * @param {Object} obj The object for which to get the number of key-value
  151. * pairs.
  152. * @return {number} The number of key-value pairs in the object map.
  153. */
  154. goog.object.getCount = function(obj) {
  155. var rv = 0;
  156. for (var key in obj) {
  157. rv++;
  158. }
  159. return rv;
  160. };
  161. /**
  162. * Returns one key from the object map, if any exists.
  163. * For map literals the returned key will be the first one in most of the
  164. * browsers (a know exception is Konqueror).
  165. *
  166. * @param {Object} obj The object to pick a key from.
  167. * @return {string|undefined} The key or undefined if the object is empty.
  168. */
  169. goog.object.getAnyKey = function(obj) {
  170. for (var key in obj) {
  171. return key;
  172. }
  173. };
  174. /**
  175. * Returns one value from the object map, if any exists.
  176. * For map literals the returned value will be the first one in most of the
  177. * browsers (a know exception is Konqueror).
  178. *
  179. * @param {Object<K,V>} obj The object to pick a value from.
  180. * @return {V|undefined} The value or undefined if the object is empty.
  181. * @template K,V
  182. */
  183. goog.object.getAnyValue = function(obj) {
  184. for (var key in obj) {
  185. return obj[key];
  186. }
  187. };
  188. /**
  189. * Whether the object/hash/map contains the given object as a value.
  190. * An alias for goog.object.containsValue(obj, val).
  191. *
  192. * @param {Object<K,V>} obj The object in which to look for val.
  193. * @param {V} val The object for which to check.
  194. * @return {boolean} true if val is present.
  195. * @template K,V
  196. */
  197. goog.object.contains = function(obj, val) {
  198. return goog.object.containsValue(obj, val);
  199. };
  200. /**
  201. * Returns the values of the object/map/hash.
  202. *
  203. * @param {Object<K,V>} obj The object from which to get the values.
  204. * @return {!Array<V>} The values in the object/map/hash.
  205. * @template K,V
  206. */
  207. goog.object.getValues = function(obj) {
  208. var res = [];
  209. var i = 0;
  210. for (var key in obj) {
  211. res[i++] = obj[key];
  212. }
  213. return res;
  214. };
  215. /**
  216. * Returns the keys of the object/map/hash.
  217. *
  218. * @param {Object} obj The object from which to get the keys.
  219. * @return {!Array<string>} Array of property keys.
  220. */
  221. goog.object.getKeys = function(obj) {
  222. var res = [];
  223. var i = 0;
  224. for (var key in obj) {
  225. res[i++] = key;
  226. }
  227. return res;
  228. };
  229. /**
  230. * Get a value from an object multiple levels deep. This is useful for
  231. * pulling values from deeply nested objects, such as JSON responses.
  232. * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
  233. *
  234. * @param {!Object} obj An object to get the value from. Can be array-like.
  235. * @param {...(string|number|!IArrayLike<number|string>)}
  236. * var_args A number of keys
  237. * (as strings, or numbers, for array-like objects). Can also be
  238. * specified as a single array of keys.
  239. * @return {*} The resulting value. If, at any point, the value for a key
  240. * is undefined, returns undefined.
  241. */
  242. goog.object.getValueByKeys = function(obj, var_args) {
  243. var isArrayLike = goog.isArrayLike(var_args);
  244. var keys = isArrayLike ? var_args : arguments;
  245. // Start with the 2nd parameter for the variable parameters syntax.
  246. for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
  247. obj = obj[keys[i]];
  248. if (!goog.isDef(obj)) {
  249. break;
  250. }
  251. }
  252. return obj;
  253. };
  254. /**
  255. * Whether the object/map/hash contains the given key.
  256. *
  257. * @param {Object} obj The object in which to look for key.
  258. * @param {?} key The key for which to check.
  259. * @return {boolean} true If the map contains the key.
  260. */
  261. goog.object.containsKey = function(obj, key) {
  262. return obj !== null && key in obj;
  263. };
  264. /**
  265. * Whether the object/map/hash contains the given value. This is O(n).
  266. *
  267. * @param {Object<K,V>} obj The object in which to look for val.
  268. * @param {V} val The value for which to check.
  269. * @return {boolean} true If the map contains the value.
  270. * @template K,V
  271. */
  272. goog.object.containsValue = function(obj, val) {
  273. for (var key in obj) {
  274. if (obj[key] == val) {
  275. return true;
  276. }
  277. }
  278. return false;
  279. };
  280. /**
  281. * Searches an object for an element that satisfies the given condition and
  282. * returns its key.
  283. * @param {Object<K,V>} obj The object to search in.
  284. * @param {function(this:T,V,string,Object<K,V>):boolean} f The
  285. * function to call for every element. Takes 3 arguments (the value,
  286. * the key and the object) and should return a boolean.
  287. * @param {T=} opt_this An optional "this" context for the function.
  288. * @return {string|undefined} The key of an element for which the function
  289. * returns true or undefined if no such element is found.
  290. * @template T,K,V
  291. */
  292. goog.object.findKey = function(obj, f, opt_this) {
  293. for (var key in obj) {
  294. if (f.call(/** @type {?} */ (opt_this), obj[key], key, obj)) {
  295. return key;
  296. }
  297. }
  298. return undefined;
  299. };
  300. /**
  301. * Searches an object for an element that satisfies the given condition and
  302. * returns its value.
  303. * @param {Object<K,V>} obj The object to search in.
  304. * @param {function(this:T,V,string,Object<K,V>):boolean} f The function
  305. * to call for every element. Takes 3 arguments (the value, the key
  306. * and the object) and should return a boolean.
  307. * @param {T=} opt_this An optional "this" context for the function.
  308. * @return {V} The value of an element for which the function returns true or
  309. * undefined if no such element is found.
  310. * @template T,K,V
  311. */
  312. goog.object.findValue = function(obj, f, opt_this) {
  313. var key = goog.object.findKey(obj, f, opt_this);
  314. return key && obj[key];
  315. };
  316. /**
  317. * Whether the object/map/hash is empty.
  318. *
  319. * @param {Object} obj The object to test.
  320. * @return {boolean} true if obj is empty.
  321. */
  322. goog.object.isEmpty = function(obj) {
  323. for (var key in obj) {
  324. return false;
  325. }
  326. return true;
  327. };
  328. /**
  329. * Removes all key value pairs from the object/map/hash.
  330. *
  331. * @param {Object} obj The object to clear.
  332. */
  333. goog.object.clear = function(obj) {
  334. for (var i in obj) {
  335. delete obj[i];
  336. }
  337. };
  338. /**
  339. * Removes a key-value pair based on the key.
  340. *
  341. * @param {Object} obj The object from which to remove the key.
  342. * @param {?} key The key to remove.
  343. * @return {boolean} Whether an element was removed.
  344. */
  345. goog.object.remove = function(obj, key) {
  346. var rv;
  347. if (rv = key in /** @type {!Object} */ (obj)) {
  348. delete obj[key];
  349. }
  350. return rv;
  351. };
  352. /**
  353. * Adds a key-value pair to the object. Throws an exception if the key is
  354. * already in use. Use set if you want to change an existing pair.
  355. *
  356. * @param {Object<K,V>} obj The object to which to add the key-value pair.
  357. * @param {string} key The key to add.
  358. * @param {V} val The value to add.
  359. * @template K,V
  360. */
  361. goog.object.add = function(obj, key, val) {
  362. if (obj !== null && key in obj) {
  363. throw Error('The object already contains the key "' + key + '"');
  364. }
  365. goog.object.set(obj, key, val);
  366. };
  367. /**
  368. * Returns the value for the given key.
  369. *
  370. * @param {Object<K,V>} obj The object from which to get the value.
  371. * @param {string} key The key for which to get the value.
  372. * @param {R=} opt_val The value to return if no item is found for the given
  373. * key (default is undefined).
  374. * @return {V|R|undefined} The value for the given key.
  375. * @template K,V,R
  376. */
  377. goog.object.get = function(obj, key, opt_val) {
  378. if (obj !== null && key in obj) {
  379. return obj[key];
  380. }
  381. return opt_val;
  382. };
  383. /**
  384. * Adds a key-value pair to the object/map/hash.
  385. *
  386. * @param {Object<K,V>} obj The object to which to add the key-value pair.
  387. * @param {string} key The key to add.
  388. * @param {V} value The value to add.
  389. * @template K,V
  390. */
  391. goog.object.set = function(obj, key, value) {
  392. obj[key] = value;
  393. };
  394. /**
  395. * Adds a key-value pair to the object/map/hash if it doesn't exist yet.
  396. *
  397. * @param {Object<K,V>} obj The object to which to add the key-value pair.
  398. * @param {string} key The key to add.
  399. * @param {V} value The value to add if the key wasn't present.
  400. * @return {V} The value of the entry at the end of the function.
  401. * @template K,V
  402. */
  403. goog.object.setIfUndefined = function(obj, key, value) {
  404. return key in /** @type {!Object} */ (obj) ? obj[key] : (obj[key] = value);
  405. };
  406. /**
  407. * Sets a key and value to an object if the key is not set. The value will be
  408. * the return value of the given function. If the key already exists, the
  409. * object will not be changed and the function will not be called (the function
  410. * will be lazily evaluated -- only called if necessary).
  411. *
  412. * This function is particularly useful for use with a map used a as a cache.
  413. *
  414. * @param {!Object<K,V>} obj The object to which to add the key-value pair.
  415. * @param {string} key The key to add.
  416. * @param {function():V} f The value to add if the key wasn't present.
  417. * @return {V} The value of the entry at the end of the function.
  418. * @template K,V
  419. */
  420. goog.object.setWithReturnValueIfNotSet = function(obj, key, f) {
  421. if (key in obj) {
  422. return obj[key];
  423. }
  424. var val = f();
  425. obj[key] = val;
  426. return val;
  427. };
  428. /**
  429. * Compares two objects for equality using === on the values.
  430. *
  431. * @param {!Object<K,V>} a
  432. * @param {!Object<K,V>} b
  433. * @return {boolean}
  434. * @template K,V
  435. */
  436. goog.object.equals = function(a, b) {
  437. for (var k in a) {
  438. if (!(k in b) || a[k] !== b[k]) {
  439. return false;
  440. }
  441. }
  442. for (var k in b) {
  443. if (!(k in a)) {
  444. return false;
  445. }
  446. }
  447. return true;
  448. };
  449. /**
  450. * Returns a shallow clone of the object.
  451. *
  452. * @param {Object<K,V>} obj Object to clone.
  453. * @return {!Object<K,V>} Clone of the input object.
  454. * @template K,V
  455. */
  456. goog.object.clone = function(obj) {
  457. // We cannot use the prototype trick because a lot of methods depend on where
  458. // the actual key is set.
  459. var res = {};
  460. for (var key in obj) {
  461. res[key] = obj[key];
  462. }
  463. return res;
  464. // We could also use goog.mixin but I wanted this to be independent from that.
  465. };
  466. /**
  467. * Clones a value. The input may be an Object, Array, or basic type. Objects and
  468. * arrays will be cloned recursively.
  469. *
  470. * WARNINGS:
  471. * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
  472. * that refer to themselves will cause infinite recursion.
  473. *
  474. * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
  475. * copies UIDs created by <code>getUid</code> into cloned results.
  476. *
  477. * @param {T} obj The value to clone.
  478. * @return {T} A clone of the input value.
  479. * @template T
  480. */
  481. goog.object.unsafeClone = function(obj) {
  482. var type = goog.typeOf(obj);
  483. if (type == 'object' || type == 'array') {
  484. if (goog.isFunction(obj.clone)) {
  485. return obj.clone();
  486. }
  487. var clone = type == 'array' ? [] : {};
  488. for (var key in obj) {
  489. clone[key] = goog.object.unsafeClone(obj[key]);
  490. }
  491. return clone;
  492. }
  493. return obj;
  494. };
  495. /**
  496. * Returns a new object in which all the keys and values are interchanged
  497. * (keys become values and values become keys). If multiple keys map to the
  498. * same value, the chosen transposed value is implementation-dependent.
  499. *
  500. * @param {Object} obj The object to transpose.
  501. * @return {!Object} The transposed object.
  502. */
  503. goog.object.transpose = function(obj) {
  504. var transposed = {};
  505. for (var key in obj) {
  506. transposed[obj[key]] = key;
  507. }
  508. return transposed;
  509. };
  510. /**
  511. * The names of the fields that are defined on Object.prototype.
  512. * @type {Array<string>}
  513. * @private
  514. */
  515. goog.object.PROTOTYPE_FIELDS_ = [
  516. 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
  517. 'toLocaleString', 'toString', 'valueOf'
  518. ];
  519. /**
  520. * Extends an object with another object.
  521. * This operates 'in-place'; it does not create a new Object.
  522. *
  523. * Example:
  524. * var o = {};
  525. * goog.object.extend(o, {a: 0, b: 1});
  526. * o; // {a: 0, b: 1}
  527. * goog.object.extend(o, {b: 2, c: 3});
  528. * o; // {a: 0, b: 2, c: 3}
  529. *
  530. * @param {Object} target The object to modify. Existing properties will be
  531. * overwritten if they are also present in one of the objects in
  532. * {@code var_args}.
  533. * @param {...Object} var_args The objects from which values will be copied.
  534. */
  535. goog.object.extend = function(target, var_args) {
  536. var key, source;
  537. for (var i = 1; i < arguments.length; i++) {
  538. source = arguments[i];
  539. for (key in source) {
  540. target[key] = source[key];
  541. }
  542. // For IE the for-in-loop does not contain any properties that are not
  543. // enumerable on the prototype object (for example isPrototypeOf from
  544. // Object.prototype) and it will also not include 'replace' on objects that
  545. // extend String and change 'replace' (not that it is common for anyone to
  546. // extend anything except Object).
  547. for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
  548. key = goog.object.PROTOTYPE_FIELDS_[j];
  549. if (Object.prototype.hasOwnProperty.call(source, key)) {
  550. target[key] = source[key];
  551. }
  552. }
  553. }
  554. };
  555. /**
  556. * Creates a new object built from the key-value pairs provided as arguments.
  557. * @param {...*} var_args If only one argument is provided and it is an array
  558. * then this is used as the arguments, otherwise even arguments are used as
  559. * the property names and odd arguments are used as the property values.
  560. * @return {!Object} The new object.
  561. * @throws {Error} If there are uneven number of arguments or there is only one
  562. * non array argument.
  563. */
  564. goog.object.create = function(var_args) {
  565. var argLength = arguments.length;
  566. if (argLength == 1 && goog.isArray(arguments[0])) {
  567. return goog.object.create.apply(null, arguments[0]);
  568. }
  569. if (argLength % 2) {
  570. throw Error('Uneven number of arguments');
  571. }
  572. var rv = {};
  573. for (var i = 0; i < argLength; i += 2) {
  574. rv[arguments[i]] = arguments[i + 1];
  575. }
  576. return rv;
  577. };
  578. /**
  579. * Creates a new object where the property names come from the arguments but
  580. * the value is always set to true
  581. * @param {...*} var_args If only one argument is provided and it is an array
  582. * then this is used as the arguments, otherwise the arguments are used
  583. * as the property names.
  584. * @return {!Object} The new object.
  585. */
  586. goog.object.createSet = function(var_args) {
  587. var argLength = arguments.length;
  588. if (argLength == 1 && goog.isArray(arguments[0])) {
  589. return goog.object.createSet.apply(null, arguments[0]);
  590. }
  591. var rv = {};
  592. for (var i = 0; i < argLength; i++) {
  593. rv[arguments[i]] = true;
  594. }
  595. return rv;
  596. };
  597. /**
  598. * Creates an immutable view of the underlying object, if the browser
  599. * supports immutable objects.
  600. *
  601. * In default mode, writes to this view will fail silently. In strict mode,
  602. * they will throw an error.
  603. *
  604. * @param {!Object<K,V>} obj An object.
  605. * @return {!Object<K,V>} An immutable view of that object, or the
  606. * original object if this browser does not support immutables.
  607. * @template K,V
  608. */
  609. goog.object.createImmutableView = function(obj) {
  610. var result = obj;
  611. if (Object.isFrozen && !Object.isFrozen(obj)) {
  612. result = Object.create(obj);
  613. Object.freeze(result);
  614. }
  615. return result;
  616. };
  617. /**
  618. * @param {!Object} obj An object.
  619. * @return {boolean} Whether this is an immutable view of the object.
  620. */
  621. goog.object.isImmutableView = function(obj) {
  622. return !!Object.isFrozen && Object.isFrozen(obj);
  623. };
  624. /**
  625. * Get all properties names on a given Object regardless of enumerability.
  626. *
  627. * <p> If the browser does not support {@code Object.getOwnPropertyNames} nor
  628. * {@code Object.getPrototypeOf} then this is equivalent to using {@code
  629. * goog.object.getKeys}
  630. *
  631. * @param {?Object} obj The object to get the properties of.
  632. * @param {boolean=} opt_includeObjectPrototype Whether properties defined on
  633. * {@code Object.prototype} should be included in the result.
  634. * @param {boolean=} opt_includeFunctionPrototype Whether properties defined on
  635. * {@code Function.prototype} should be included in the result.
  636. * @return {!Array<string>}
  637. * @public
  638. */
  639. goog.object.getAllPropertyNames = function(
  640. obj, opt_includeObjectPrototype, opt_includeFunctionPrototype) {
  641. if (!obj) {
  642. return [];
  643. }
  644. // Naively use a for..in loop to get the property names if the browser doesn't
  645. // support any other APIs for getting it.
  646. if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
  647. return goog.object.getKeys(obj);
  648. }
  649. var visitedSet = {};
  650. // Traverse the prototype chain and add all properties to the visited set.
  651. var proto = obj;
  652. while (proto &&
  653. (proto !== Object.prototype || !!opt_includeObjectPrototype) &&
  654. (proto !== Function.prototype || !!opt_includeFunctionPrototype)) {
  655. var names = Object.getOwnPropertyNames(proto);
  656. for (var i = 0; i < names.length; i++) {
  657. visitedSet[names[i]] = true;
  658. }
  659. proto = Object.getPrototypeOf(proto);
  660. }
  661. return goog.object.getKeys(visitedSet);
  662. };