Path: blob/trunk/third_party/closure/goog/object/object.js
4509 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Utilities for manipulating objects/maps/hashes.8*/9goog.module('goog.object');10goog.module.declareLegacyNamespace();1112const utils = goog.require('goog.utils');1314/**15* Calls a function for each element in an object/map/hash.16* @param {?Object<K,V>} obj The object over which to iterate.17* @param {function(this:T,V,?,?Object<K,V>):?} f The function to call for every18* element. This function takes 3 arguments (the value, the key and the19* object) and the return value is ignored.20* @param {T=} opt_obj This is used as the 'this' object within f.21* @return {void}22* @template T,K,V23*/24function forEach(obj, f, opt_obj) {25for (const key in obj) {26f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);27}28}2930/**31* Calls a function for each element in an object/map/hash. If that call returns32* true, adds the element to a new object.33* @param {?Object<K,V>} obj The object over which to iterate.34* @param {function(this:T,V,?,?Object<K,V>):boolean} f The function to call for35* every element. This function takes 3 arguments (the value, the key and36* the object) and should return a boolean. If the return value is true the37* element is added to the result object. If it is false the element is not38* included.39* @param {T=} opt_obj This is used as the 'this' object within f.40* @return {!Object<K,V>} a new object in which only elements that passed the41* test are present.42* @template T,K,V43*/44function filter(obj, f, opt_obj) {45const res = {};46for (const key in obj) {47if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {48res[key] = obj[key];49}50}51return res;52}5354/**55* For every element in an object/map/hash calls a function and inserts the56* result into a new object.57* @param {?Object<K,V>} obj The object over which to iterate.58* @param {function(this:T,V,?,?Object<K,V>):R} f The function to call for every59* element. This function takes 3 arguments (the value, the key and the60* object) and should return something. The result will be inserted into a61* new object.62* @param {T=} opt_obj This is used as the 'this' object within f.63* @return {!Object<K,R>} a new object with the results from f.64* @template T,K,V,R65*/66function map(obj, f, opt_obj) {67const res = {};68for (const key in obj) {69res[key] = f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);70}71return res;72}7374/**75* Calls a function for each element in an object/map/hash. If any76* call returns true, returns true (without checking the rest). If77* all calls return false, returns false.78* @param {?Object<K,V>} obj The object to check.79* @param {function(this:T,V,?,?Object<K,V>):boolean} f The function to call for80* every element. This function takes 3 arguments (the value, the key and81* the object) and should return a boolean.82* @param {T=} opt_obj This is used as the 'this' object within f.83* @return {boolean} true if any element passes the test.84* @template T,K,V85*/86function some(obj, f, opt_obj) {87for (const key in obj) {88if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {89return true;90}91}92return false;93}9495/**96* Calls a function for each element in an object/map/hash. If97* all calls return true, returns true. If any call returns false, returns98* false at this point and does not continue to check the remaining elements.99* @param {?Object<K,V>} obj The object to check.100* @param {?function(this:T,V,?,?Object<K,V>):boolean} f The function to call101* for every element. This function takes 3 arguments (the value, the key102* and the object) and should return a boolean.103* @param {T=} opt_obj This is used as the 'this' object within f.104* @return {boolean} false if any element fails the test.105* @template T,K,V106*/107function every(obj, f, opt_obj) {108for (const key in obj) {109if (!f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {110return false;111}112}113return true;114}115116/**117* Returns the number of key-value pairs in the object map.118* @param {?Object} obj The object for which to get the number of key-value119* pairs.120* @return {number} The number of key-value pairs in the object map.121*/122function getCount(obj) {123let rv = 0;124for (const key in obj) {125rv++;126}127return rv;128}129130/**131* Returns one key from the object map, if any exists.132* For map literals the returned key will be the first one in most of the133* browsers (a know exception is Konqueror).134* @param {?Object} obj The object to pick a key from.135* @return {string|undefined} The key or undefined if the object is empty.136*/137function getAnyKey(obj) {138for (const key in obj) {139return key;140}141}142143/**144* Returns one value from the object map, if any exists.145* For map literals the returned value will be the first one in most of the146* browsers (a know exception is Konqueror).147* @param {?Object<K,V>} obj The object to pick a value from.148* @return {V|undefined} The value or undefined if the object is empty.149* @template K,V150*/151function getAnyValue(obj) {152for (const key in obj) {153return obj[key];154}155}156157/**158* Whether the object/hash/map contains the given object as a value.159* An alias for containsValue(obj, val).160* @param {?Object<K,V>} obj The object in which to look for val.161* @param {V} val The object for which to check.162* @return {boolean} true if val is present.163* @template K,V164*/165function contains(obj, val) {166return containsValue(obj, val);167}168169/**170* Returns the values of the object/map/hash.171* @param {?Object<K,V>} obj The object from which to get the values.172* @return {!Array<V>} The values in the object/map/hash.173* @template K,V174*/175function getValues(obj) {176const res = [];177let i = 0;178for (const key in obj) {179res[i++] = obj[key];180}181return res;182}183184/**185* Returns the keys of the object/map/hash.186* @param {?Object} obj The object from which to get the keys.187* @return {!Array<string>} Array of property keys.188*/189function getKeys(obj) {190const res = [];191let i = 0;192for (const key in obj) {193res[i++] = key;194}195return res;196}197198/**199* Get a value from an object multiple levels deep. This is useful for200* pulling values from deeply nested objects, such as JSON responses.201* Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)202* @param {?Object} obj An object to get the value from. Can be array-like.203* @param {...(string|number|!IArrayLike<number|string>)} var_args A number of204* keys (as strings, or numbers, for array-like objects). Can also be205* specified as a single array of keys.206* @return {*} The resulting value. If, at any point, the value for a key in the207* current object is null or undefined, returns undefined.208*/209function getValueByKeys(obj, var_args) {210const isArrayLike = utils.isArrayLike(var_args);211const keys = isArrayLike ?212/** @type {!IArrayLike<number|string>} */ (var_args) :213arguments;214215// Start with the 2nd parameter for the variable parameters syntax.216for (let i = isArrayLike ? 0 : 1; i < keys.length; i++) {217if (obj == null) return undefined;218obj = obj[keys[i]];219}220221return obj;222}223224/**225* Whether the object/map/hash contains the given key.226* @param {?Object} obj The object in which to look for key.227* @param {?} key The key for which to check.228* @return {boolean} true If the map contains the key.229*/230function containsKey(obj, key) {231return obj !== null && key in obj;232}233234/**235* Whether the object/map/hash contains the given value. This is O(n).236* @param {?Object<K,V>} obj The object in which to look for val.237* @param {V} val The value for which to check.238* @return {boolean} true If the map contains the value.239* @template K,V240*/241function containsValue(obj, val) {242for (const key in obj) {243if (obj[key] == val) {244return true;245}246}247return false;248}249250/**251* Searches an object for an element that satisfies the given condition and252* returns its key.253* @param {?Object<K,V>} obj The object to search in.254* @param {function(this:T,V,string,?Object<K,V>):boolean} f The function to255* call for every element. Takes 3 arguments (the value, the key and the256* object) and should return a boolean.257* @param {T=} thisObj An optional "this" context for the function.258* @return {string|undefined} The key of an element for which the function259* returns true or undefined if no such element is found.260* @template T,K,V261*/262function findKey(obj, f, thisObj = undefined) {263for (const key in obj) {264if (f.call(/** @type {?} */ (thisObj), obj[key], key, obj)) {265return key;266}267}268return undefined;269}270271/**272* Searches an object for an element that satisfies the given condition and273* returns its value.274* @param {?Object<K,V>} obj The object to search in.275* @param {function(this:T,V,string,?Object<K,V>):boolean} f The function to276* call for every element. Takes 3 arguments (the value, the key and the277* object) and should return a boolean.278* @param {T=} thisObj An optional "this" context for the function.279* @return {V} The value of an element for which the function returns true or280* undefined if no such element is found.281* @template T,K,V282*/283function findValue(obj, f, thisObj = undefined) {284const key = findKey(obj, f, thisObj);285return key && obj[key];286}287288/**289* Whether the object/map/hash is empty.290* @param {?Object} obj The object to test.291* @return {boolean} true if obj is empty.292*/293function isEmpty(obj) {294for (const key in obj) {295return false;296}297return true;298}299300/**301* Removes all key value pairs from the object/map/hash.302* @param {?Object} obj The object to clear.303* @return {void}304*/305function clear(obj) {306for (const i in obj) {307delete obj[i];308}309}310311/**312* Removes a key-value pair based on the key.313* @param {?Object} obj The object from which to remove the key.314* @param {?} key The key to remove.315* @return {boolean} Whether an element was removed.316*/317function remove(obj, key) {318let rv;319if (rv = key in /** @type {!Object} */ (obj)) {320delete obj[key];321}322return rv;323}324325/**326* Adds a key-value pair to the object. Throws an exception if the key is327* already in use. Use set if you want to change an existing pair.328* @param {?Object<K,V>} obj The object to which to add the key-value pair.329* @param {string} key The key to add.330* @param {V} val The value to add.331* @return {void}332* @template K,V333*/334function add(obj, key, val) {335if (obj !== null && key in obj) {336throw new Error(`The object already contains the key "${key}"`);337}338set(obj, key, val);339}340341/**342* Returns the value for the given key.343* @param {?Object<K,V>} obj The object from which to get the value.344* @param {string} key The key for which to get the value.345* @param {R=} val The value to return if no item is found for the given key346* (default is undefined).347* @return {V|R|undefined} The value for the given key.348* @template K,V,R349*/350function get(obj, key, val = undefined) {351if (obj !== null && key in obj) {352return obj[key];353}354return val;355}356357/**358* Adds a key-value pair to the object/map/hash.359* @param {?Object<K,V>} obj The object to which to add the key-value pair.360* @param {string} key The key to add.361* @param {V} value The value to add.362* @template K,V363* @return {void}364*/365function set(obj, key, value) {366obj[key] = value;367}368369/**370* Adds a key-value pair to the object/map/hash if it doesn't exist yet.371* @param {?Object<K,V>} obj The object to which to add the key-value pair.372* @param {string} key The key to add.373* @param {V} value The value to add if the key wasn't present.374* @return {V} The value of the entry at the end of the function.375* @template K,V376*/377function setIfUndefined(obj, key, value) {378return key in /** @type {!Object} */ (obj) ? obj[key] : (obj[key] = value);379}380381/**382* Sets a key and value to an object if the key is not set. The value will be383* the return value of the given function. If the key already exists, the384* object will not be changed and the function will not be called (the function385* will be lazily evaluated -- only called if necessary).386* This function is particularly useful when used with an `Object` which is387* acting as a cache.388* @param {?Object<K,V>} obj The object to which to add the key-value pair.389* @param {string} key The key to add.390* @param {function():V} f The value to add if the key wasn't present.391* @return {V} The value of the entry at the end of the function.392* @template K,V393*/394function setWithReturnValueIfNotSet(obj, key, f) {395if (key in obj) {396return obj[key];397}398399const val = f();400obj[key] = val;401return val;402}403404/**405* Compares two objects for equality using === on the values.406* @param {!Object<K,V>} a407* @param {!Object<K,V>} b408* @return {boolean}409* @template K,V410*/411function equals(a, b) {412for (const k in a) {413if (!(k in b) || a[k] !== b[k]) {414return false;415}416}417for (const k in b) {418if (!(k in a)) {419return false;420}421}422return true;423}424425/**426* Returns a shallow clone of the object.427* @param {?Object<K,V>} obj Object to clone.428* @return {!Object<K,V>} Clone of the input object.429* @template K,V430*/431function clone(obj) {432const res = {};433for (const key in obj) {434res[key] = obj[key];435}436return res;437}438439/**440* Clones a value. The input may be an Object, Array, or basic type. Objects and441* arrays will be cloned recursively.442* WARNINGS:443* <code>unsafeClone</code> does not detect reference loops. Objects444* that refer to themselves will cause infinite recursion.445* <code>unsafeClone</code> is unaware of unique identifiers, and446* copies UIDs created by <code>getUid</code> into cloned results.447* @param {T} obj The value to clone.448* @return {T} A clone of the input value.449* @template T450*/451function unsafeClone(obj) {452if (!obj || typeof obj !== 'object') return obj;453if (typeof obj.clone === 'function') return obj.clone();454if (typeof Map !== 'undefined' && obj instanceof Map) {455return new Map(obj);456} else if (typeof Set !== 'undefined' && obj instanceof Set) {457return new Set(obj);458} else if (obj instanceof Date) {459return new Date(obj.getTime());460}461const clone = Array.isArray(obj) ? [] :462typeof ArrayBuffer === 'function' &&463typeof ArrayBuffer.isView === 'function' && ArrayBuffer.isView(obj) &&464!(obj instanceof DataView) ?465new obj.constructor(obj.length) :466{};467for (const key in obj) {468clone[key] = unsafeClone(obj[key]);469}470return clone;471}472473/**474* Returns a new object in which all the keys and values are interchanged475* (keys become values and values become keys). If multiple keys map to the476* same value, the chosen transposed value is implementation-dependent.477* @param {?Object} obj The object to transpose.478* @return {!Object} The transposed object.479*/480function transpose(obj) {481const transposed = {};482for (const key in obj) {483transposed[obj[key]] = key;484}485return transposed;486}487488/**489* The names of the fields that are defined on Object.prototype.490* @type {!Array<string>}491*/492const PROTOTYPE_FIELDS = [493'constructor',494'hasOwnProperty',495'isPrototypeOf',496'propertyIsEnumerable',497'toLocaleString',498'toString',499'valueOf',500];501502/**503* Extends an object with another object.504* This operates 'in-place'; it does not create a new Object.505* Example:506* var o = {};507* extend(o, {a: 0, b: 1});508* o; // {a: 0, b: 1}509* extend(o, {b: 2, c: 3});510* o; // {a: 0, b: 2, c: 3}511* @param {?Object} target The object to modify. Existing properties will be512* overwritten if they are also present in one of the objects in `var_args`.513* @param {...(?Object|undefined)} var_args The objects from which values514* will be copied.515* @return {void}516* @deprecated Prefer Object.assign517*/518function extend(target, var_args) {519let key;520let source;521for (let i = 1; i < arguments.length; i++) {522source = arguments[i];523for (key in source) {524target[key] = source[key];525}526527// For IE the for-in-loop does not contain any properties that are not528// enumerable on the prototype object (for example isPrototypeOf from529// Object.prototype) and it will also not include 'replace' on objects that530// extend String and change 'replace' (not that it is common for anyone to531// extend anything except Object).532533for (let j = 0; j < PROTOTYPE_FIELDS.length; j++) {534key = PROTOTYPE_FIELDS[j];535if (Object.prototype.hasOwnProperty.call(source, key)) {536target[key] = source[key];537}538}539}540}541542/**543* Creates a new object built from the key-value pairs provided as arguments.544* @param {...*} var_args If only one argument is provided and it is an array545* then this is used as the arguments, otherwise even arguments are used as546* the property names and odd arguments are used as the property values.547* @return {!Object} The new object.548* @throws {!Error} If there are uneven number of arguments or there is only one549* non array argument.550*/551function create(var_args) {552const argLength = arguments.length;553if (argLength == 1 && Array.isArray(arguments[0])) {554return create.apply(null, arguments[0]);555}556557if (argLength % 2) {558throw new Error('Uneven number of arguments');559}560561const rv = {};562for (let i = 0; i < argLength; i += 2) {563rv[arguments[i]] = arguments[i + 1];564}565return rv;566}567568/**569* Creates a new object where the property names come from the arguments but570* the value is always set to true571* @param {...*} var_args If only one argument is provided and it is an array572* then this is used as the arguments, otherwise the arguments are used as573* the property names.574* @return {!Object} The new object.575*/576function createSet(var_args) {577const argLength = arguments.length;578if (argLength == 1 && Array.isArray(arguments[0])) {579return createSet.apply(null, arguments[0]);580}581582const rv = {};583for (let i = 0; i < argLength; i++) {584rv[arguments[i]] = true;585}586return rv;587}588589/**590* Creates an immutable view of the underlying object, if the browser591* supports immutable objects.592* In default mode, writes to this view will fail silently. In strict mode,593* they will throw an error.594* @param {!Object<K,V>} obj An object.595* @return {!Object<K,V>} An immutable view of that object, or the original596* object if this browser does not support immutables.597* @template K,V598*/599function createImmutableView(obj) {600let result = obj;601if (Object.isFrozen && !Object.isFrozen(obj)) {602result = Object.create(obj);603Object.freeze(result);604}605return result;606}607608/**609* @param {!Object} obj An object.610* @return {boolean} Whether this is an immutable view of the object.611*/612function isImmutableView(obj) {613return !!Object.isFrozen && Object.isFrozen(obj);614}615616/**617* Get all properties names on a given Object regardless of enumerability.618* <p> If the browser does not support `Object.getOwnPropertyNames` nor619* `Object.getPrototypeOf` then this is equivalent to using620* `getKeys`621* @param {?Object} obj The object to get the properties of.622* @param {boolean=} includeObjectPrototype Whether properties defined on623* `Object.prototype` should be included in the result.624* @param {boolean=} includeFunctionPrototype Whether properties defined on625* `Function.prototype` should be included in the result.626* @return {!Array<string>}627* @public628*/629function getAllPropertyNames(630obj, includeObjectPrototype = undefined,631includeFunctionPrototype = undefined) {632if (!obj) {633return [];634}635636// Naively use a for..in loop to get the property names if the browser doesn't637// support any other APIs for getting it.638if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {639return getKeys(obj);640}641642const visitedSet = {};643644// Traverse the prototype chain and add all properties to the visited set.645let proto = obj;646while (proto && (proto !== Object.prototype || !!includeObjectPrototype) &&647(proto !== Function.prototype || !!includeFunctionPrototype)) {648const names = Object.getOwnPropertyNames(proto);649for (let i = 0; i < names.length; i++) {650visitedSet[names[i]] = true;651}652proto = Object.getPrototypeOf(proto);653}654655return getKeys(visitedSet);656}657658/**659* Given a ES5 or ES6 class reference, return its super class / super660* constructor.661* This should be used in rare cases where you need to walk up the inheritance662* tree (this is generally a bad idea). But this work with ES5 and ES6 classes,663* unlike relying on the superClass_ property.664* Note: To start walking up the hierarchy from an instance call this with its665* `constructor` property; e.g. `getSuperClass(instance.constructor)`.666* @param {function(new: ?)} constructor667* @return {?Object}668*/669function getSuperClass(constructor) {670const proto = Object.getPrototypeOf(constructor.prototype);671return proto && proto.constructor;672}673674exports = {675add,676clear,677clone,678contains,679containsKey,680containsValue,681create,682createImmutableView,683createSet,684equals,685every,686extend,687filter,688findKey,689findValue,690forEach,691get,692getAllPropertyNames,693getAnyKey,694getAnyValue,695getCount,696getKeys,697getSuperClass,698getValueByKeys,699getValues,700isEmpty,701isImmutableView,702map,703remove,704set,705setIfUndefined,706setWithReturnValueIfNotSet,707some,708transpose,709unsafeClone,710};711712713