Path: blob/trunk/third_party/closure/goog/object/object.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Utilities for manipulating objects/maps/hashes.16* @author [email protected] (Erik Arvidsson)17*/1819goog.provide('goog.object');202122/**23* Whether two values are not observably distinguishable. This24* correctly detects that 0 is not the same as -0 and two NaNs are25* practically equivalent.26*27* The implementation is as suggested by harmony:egal proposal.28*29* @param {*} v The first value to compare.30* @param {*} v2 The second value to compare.31* @return {boolean} Whether two values are not observably distinguishable.32* @see http://wiki.ecmascript.org/doku.php?id=harmony:egal33*/34goog.object.is = function(v, v2) {35if (v === v2) {36// 0 === -0, but they are not identical.37// We need the cast because the compiler requires that v2 is a38// number (although 1/v2 works with non-number). We cast to ? to39// stop the compiler from type-checking this statement.40return v !== 0 || 1 / v === 1 / /** @type {?} */ (v2);41}4243// NaN is non-reflexive: NaN !== NaN, although they are identical.44return v !== v && v2 !== v2;45};464748/**49* Calls a function for each element in an object/map/hash.50*51* @param {Object<K,V>} obj The object over which to iterate.52* @param {function(this:T,V,?,Object<K,V>):?} f The function to call53* for every element. This function takes 3 arguments (the value, the54* key and the object) and the return value is ignored.55* @param {T=} opt_obj This is used as the 'this' object within f.56* @template T,K,V57*/58goog.object.forEach = function(obj, f, opt_obj) {59for (var key in obj) {60f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);61}62};636465/**66* Calls a function for each element in an object/map/hash. If that call returns67* true, adds the element to a new object.68*69* @param {Object<K,V>} obj The object over which to iterate.70* @param {function(this:T,V,?,Object<K,V>):boolean} f The function to call71* for every element. This72* function takes 3 arguments (the value, the key and the object)73* and should return a boolean. If the return value is true the74* element is added to the result object. If it is false the75* element is not included.76* @param {T=} opt_obj This is used as the 'this' object within f.77* @return {!Object<K,V>} a new object in which only elements that passed the78* test are present.79* @template T,K,V80*/81goog.object.filter = function(obj, f, opt_obj) {82var res = {};83for (var key in obj) {84if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {85res[key] = obj[key];86}87}88return res;89};909192/**93* For every element in an object/map/hash calls a function and inserts the94* result into a new object.95*96* @param {Object<K,V>} obj The object over which to iterate.97* @param {function(this:T,V,?,Object<K,V>):R} f The function to call98* for every element. This function99* takes 3 arguments (the value, the key and the object)100* and should return something. The result will be inserted101* into a new object.102* @param {T=} opt_obj This is used as the 'this' object within f.103* @return {!Object<K,R>} a new object with the results from f.104* @template T,K,V,R105*/106goog.object.map = function(obj, f, opt_obj) {107var res = {};108for (var key in obj) {109res[key] = f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);110}111return res;112};113114115/**116* Calls a function for each element in an object/map/hash. If any117* call returns true, returns true (without checking the rest). If118* all calls return false, returns false.119*120* @param {Object<K,V>} obj The object to check.121* @param {function(this:T,V,?,Object<K,V>):boolean} f The function to122* call for every element. This function123* takes 3 arguments (the value, the key and the object) and should124* return a boolean.125* @param {T=} opt_obj This is used as the 'this' object within f.126* @return {boolean} true if any element passes the test.127* @template T,K,V128*/129goog.object.some = function(obj, f, opt_obj) {130for (var key in obj) {131if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {132return true;133}134}135return false;136};137138139/**140* Calls a function for each element in an object/map/hash. If141* all calls return true, returns true. If any call returns false, returns142* false at this point and does not continue to check the remaining elements.143*144* @param {Object<K,V>} obj The object to check.145* @param {?function(this:T,V,?,Object<K,V>):boolean} f The function to146* call for every element. This function147* takes 3 arguments (the value, the key and the object) and should148* return a boolean.149* @param {T=} opt_obj This is used as the 'this' object within f.150* @return {boolean} false if any element fails the test.151* @template T,K,V152*/153goog.object.every = function(obj, f, opt_obj) {154for (var key in obj) {155if (!f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {156return false;157}158}159return true;160};161162163/**164* Returns the number of key-value pairs in the object map.165*166* @param {Object} obj The object for which to get the number of key-value167* pairs.168* @return {number} The number of key-value pairs in the object map.169*/170goog.object.getCount = function(obj) {171var rv = 0;172for (var key in obj) {173rv++;174}175return rv;176};177178179/**180* Returns one key from the object map, if any exists.181* For map literals the returned key will be the first one in most of the182* browsers (a know exception is Konqueror).183*184* @param {Object} obj The object to pick a key from.185* @return {string|undefined} The key or undefined if the object is empty.186*/187goog.object.getAnyKey = function(obj) {188for (var key in obj) {189return key;190}191};192193194/**195* Returns one value from the object map, if any exists.196* For map literals the returned value will be the first one in most of the197* browsers (a know exception is Konqueror).198*199* @param {Object<K,V>} obj The object to pick a value from.200* @return {V|undefined} The value or undefined if the object is empty.201* @template K,V202*/203goog.object.getAnyValue = function(obj) {204for (var key in obj) {205return obj[key];206}207};208209210/**211* Whether the object/hash/map contains the given object as a value.212* An alias for goog.object.containsValue(obj, val).213*214* @param {Object<K,V>} obj The object in which to look for val.215* @param {V} val The object for which to check.216* @return {boolean} true if val is present.217* @template K,V218*/219goog.object.contains = function(obj, val) {220return goog.object.containsValue(obj, val);221};222223224/**225* Returns the values of the object/map/hash.226*227* @param {Object<K,V>} obj The object from which to get the values.228* @return {!Array<V>} The values in the object/map/hash.229* @template K,V230*/231goog.object.getValues = function(obj) {232var res = [];233var i = 0;234for (var key in obj) {235res[i++] = obj[key];236}237return res;238};239240241/**242* Returns the keys of the object/map/hash.243*244* @param {Object} obj The object from which to get the keys.245* @return {!Array<string>} Array of property keys.246*/247goog.object.getKeys = function(obj) {248var res = [];249var i = 0;250for (var key in obj) {251res[i++] = key;252}253return res;254};255256257/**258* Get a value from an object multiple levels deep. This is useful for259* pulling values from deeply nested objects, such as JSON responses.260* Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)261*262* @param {!Object} obj An object to get the value from. Can be array-like.263* @param {...(string|number|!IArrayLike<number|string>)}264* var_args A number of keys265* (as strings, or numbers, for array-like objects). Can also be266* specified as a single array of keys.267* @return {*} The resulting value. If, at any point, the value for a key268* is undefined, returns undefined.269*/270goog.object.getValueByKeys = function(obj, var_args) {271var isArrayLike = goog.isArrayLike(var_args);272var keys = isArrayLike ? var_args : arguments;273274// Start with the 2nd parameter for the variable parameters syntax.275for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {276obj = obj[keys[i]];277if (!goog.isDef(obj)) {278break;279}280}281282return obj;283};284285286/**287* Whether the object/map/hash contains the given key.288*289* @param {Object} obj The object in which to look for key.290* @param {?} key The key for which to check.291* @return {boolean} true If the map contains the key.292*/293goog.object.containsKey = function(obj, key) {294return obj !== null && key in obj;295};296297298/**299* Whether the object/map/hash contains the given value. This is O(n).300*301* @param {Object<K,V>} obj The object in which to look for val.302* @param {V} val The value for which to check.303* @return {boolean} true If the map contains the value.304* @template K,V305*/306goog.object.containsValue = function(obj, val) {307for (var key in obj) {308if (obj[key] == val) {309return true;310}311}312return false;313};314315316/**317* Searches an object for an element that satisfies the given condition and318* returns its key.319* @param {Object<K,V>} obj The object to search in.320* @param {function(this:T,V,string,Object<K,V>):boolean} f The321* function to call for every element. Takes 3 arguments (the value,322* the key and the object) and should return a boolean.323* @param {T=} opt_this An optional "this" context for the function.324* @return {string|undefined} The key of an element for which the function325* returns true or undefined if no such element is found.326* @template T,K,V327*/328goog.object.findKey = function(obj, f, opt_this) {329for (var key in obj) {330if (f.call(/** @type {?} */ (opt_this), obj[key], key, obj)) {331return key;332}333}334return undefined;335};336337338/**339* Searches an object for an element that satisfies the given condition and340* returns its value.341* @param {Object<K,V>} obj The object to search in.342* @param {function(this:T,V,string,Object<K,V>):boolean} f The function343* to call for every element. Takes 3 arguments (the value, the key344* and the object) and should return a boolean.345* @param {T=} opt_this An optional "this" context for the function.346* @return {V} The value of an element for which the function returns true or347* undefined if no such element is found.348* @template T,K,V349*/350goog.object.findValue = function(obj, f, opt_this) {351var key = goog.object.findKey(obj, f, opt_this);352return key && obj[key];353};354355356/**357* Whether the object/map/hash is empty.358*359* @param {Object} obj The object to test.360* @return {boolean} true if obj is empty.361*/362goog.object.isEmpty = function(obj) {363for (var key in obj) {364return false;365}366return true;367};368369370/**371* Removes all key value pairs from the object/map/hash.372*373* @param {Object} obj The object to clear.374*/375goog.object.clear = function(obj) {376for (var i in obj) {377delete obj[i];378}379};380381382/**383* Removes a key-value pair based on the key.384*385* @param {Object} obj The object from which to remove the key.386* @param {?} key The key to remove.387* @return {boolean} Whether an element was removed.388*/389goog.object.remove = function(obj, key) {390var rv;391if (rv = key in /** @type {!Object} */ (obj)) {392delete obj[key];393}394return rv;395};396397398/**399* Adds a key-value pair to the object. Throws an exception if the key is400* already in use. Use set if you want to change an existing pair.401*402* @param {Object<K,V>} obj The object to which to add the key-value pair.403* @param {string} key The key to add.404* @param {V} val The value to add.405* @template K,V406*/407goog.object.add = function(obj, key, val) {408if (obj !== null && key in obj) {409throw Error('The object already contains the key "' + key + '"');410}411goog.object.set(obj, key, val);412};413414415/**416* Returns the value for the given key.417*418* @param {Object<K,V>} obj The object from which to get the value.419* @param {string} key The key for which to get the value.420* @param {R=} opt_val The value to return if no item is found for the given421* key (default is undefined).422* @return {V|R|undefined} The value for the given key.423* @template K,V,R424*/425goog.object.get = function(obj, key, opt_val) {426if (obj !== null && key in obj) {427return obj[key];428}429return opt_val;430};431432433/**434* Adds a key-value pair to the object/map/hash.435*436* @param {Object<K,V>} obj The object to which to add the key-value pair.437* @param {string} key The key to add.438* @param {V} value The value to add.439* @template K,V440*/441goog.object.set = function(obj, key, value) {442obj[key] = value;443};444445446/**447* Adds a key-value pair to the object/map/hash if it doesn't exist yet.448*449* @param {Object<K,V>} obj The object to which to add the key-value pair.450* @param {string} key The key to add.451* @param {V} value The value to add if the key wasn't present.452* @return {V} The value of the entry at the end of the function.453* @template K,V454*/455goog.object.setIfUndefined = function(obj, key, value) {456return key in /** @type {!Object} */ (obj) ? obj[key] : (obj[key] = value);457};458459460/**461* Sets a key and value to an object if the key is not set. The value will be462* the return value of the given function. If the key already exists, the463* object will not be changed and the function will not be called (the function464* will be lazily evaluated -- only called if necessary).465*466* This function is particularly useful for use with a map used a as a cache.467*468* @param {!Object<K,V>} obj The object to which to add the key-value pair.469* @param {string} key The key to add.470* @param {function():V} f The value to add if the key wasn't present.471* @return {V} The value of the entry at the end of the function.472* @template K,V473*/474goog.object.setWithReturnValueIfNotSet = function(obj, key, f) {475if (key in obj) {476return obj[key];477}478479var val = f();480obj[key] = val;481return val;482};483484485/**486* Compares two objects for equality using === on the values.487*488* @param {!Object<K,V>} a489* @param {!Object<K,V>} b490* @return {boolean}491* @template K,V492*/493goog.object.equals = function(a, b) {494for (var k in a) {495if (!(k in b) || a[k] !== b[k]) {496return false;497}498}499for (var k in b) {500if (!(k in a)) {501return false;502}503}504return true;505};506507508/**509* Returns a shallow clone of the object.510*511* @param {Object<K,V>} obj Object to clone.512* @return {!Object<K,V>} Clone of the input object.513* @template K,V514*/515goog.object.clone = function(obj) {516// We cannot use the prototype trick because a lot of methods depend on where517// the actual key is set.518519var res = {};520for (var key in obj) {521res[key] = obj[key];522}523return res;524// We could also use goog.mixin but I wanted this to be independent from that.525};526527528/**529* Clones a value. The input may be an Object, Array, or basic type. Objects and530* arrays will be cloned recursively.531*532* WARNINGS:533* <code>goog.object.unsafeClone</code> does not detect reference loops. Objects534* that refer to themselves will cause infinite recursion.535*536* <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and537* copies UIDs created by <code>getUid</code> into cloned results.538*539* @param {T} obj The value to clone.540* @return {T} A clone of the input value.541* @template T542*/543goog.object.unsafeClone = function(obj) {544var type = goog.typeOf(obj);545if (type == 'object' || type == 'array') {546if (goog.isFunction(obj.clone)) {547return obj.clone();548}549var clone = type == 'array' ? [] : {};550for (var key in obj) {551clone[key] = goog.object.unsafeClone(obj[key]);552}553return clone;554}555556return obj;557};558559560/**561* Returns a new object in which all the keys and values are interchanged562* (keys become values and values become keys). If multiple keys map to the563* same value, the chosen transposed value is implementation-dependent.564*565* @param {Object} obj The object to transpose.566* @return {!Object} The transposed object.567*/568goog.object.transpose = function(obj) {569var transposed = {};570for (var key in obj) {571transposed[obj[key]] = key;572}573return transposed;574};575576577/**578* The names of the fields that are defined on Object.prototype.579* @type {Array<string>}580* @private581*/582goog.object.PROTOTYPE_FIELDS_ = [583'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',584'toLocaleString', 'toString', 'valueOf'585];586587588/**589* Extends an object with another object.590* This operates 'in-place'; it does not create a new Object.591*592* Example:593* var o = {};594* goog.object.extend(o, {a: 0, b: 1});595* o; // {a: 0, b: 1}596* goog.object.extend(o, {b: 2, c: 3});597* o; // {a: 0, b: 2, c: 3}598*599* @param {Object} target The object to modify. Existing properties will be600* overwritten if they are also present in one of the objects in601* {@code var_args}.602* @param {...Object} var_args The objects from which values will be copied.603*/604goog.object.extend = function(target, var_args) {605var key, source;606for (var i = 1; i < arguments.length; i++) {607source = arguments[i];608for (key in source) {609target[key] = source[key];610}611612// For IE the for-in-loop does not contain any properties that are not613// enumerable on the prototype object (for example isPrototypeOf from614// Object.prototype) and it will also not include 'replace' on objects that615// extend String and change 'replace' (not that it is common for anyone to616// extend anything except Object).617618for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {619key = goog.object.PROTOTYPE_FIELDS_[j];620if (Object.prototype.hasOwnProperty.call(source, key)) {621target[key] = source[key];622}623}624}625};626627628/**629* Creates a new object built from the key-value pairs provided as arguments.630* @param {...*} var_args If only one argument is provided and it is an array631* then this is used as the arguments, otherwise even arguments are used as632* the property names and odd arguments are used as the property values.633* @return {!Object} The new object.634* @throws {Error} If there are uneven number of arguments or there is only one635* non array argument.636*/637goog.object.create = function(var_args) {638var argLength = arguments.length;639if (argLength == 1 && goog.isArray(arguments[0])) {640return goog.object.create.apply(null, arguments[0]);641}642643if (argLength % 2) {644throw Error('Uneven number of arguments');645}646647var rv = {};648for (var i = 0; i < argLength; i += 2) {649rv[arguments[i]] = arguments[i + 1];650}651return rv;652};653654655/**656* Creates a new object where the property names come from the arguments but657* the value is always set to true658* @param {...*} var_args If only one argument is provided and it is an array659* then this is used as the arguments, otherwise the arguments are used660* as the property names.661* @return {!Object} The new object.662*/663goog.object.createSet = function(var_args) {664var argLength = arguments.length;665if (argLength == 1 && goog.isArray(arguments[0])) {666return goog.object.createSet.apply(null, arguments[0]);667}668669var rv = {};670for (var i = 0; i < argLength; i++) {671rv[arguments[i]] = true;672}673return rv;674};675676677/**678* Creates an immutable view of the underlying object, if the browser679* supports immutable objects.680*681* In default mode, writes to this view will fail silently. In strict mode,682* they will throw an error.683*684* @param {!Object<K,V>} obj An object.685* @return {!Object<K,V>} An immutable view of that object, or the686* original object if this browser does not support immutables.687* @template K,V688*/689goog.object.createImmutableView = function(obj) {690var result = obj;691if (Object.isFrozen && !Object.isFrozen(obj)) {692result = Object.create(obj);693Object.freeze(result);694}695return result;696};697698699/**700* @param {!Object} obj An object.701* @return {boolean} Whether this is an immutable view of the object.702*/703goog.object.isImmutableView = function(obj) {704return !!Object.isFrozen && Object.isFrozen(obj);705};706707708/**709* Get all properties names on a given Object regardless of enumerability.710*711* <p> If the browser does not support {@code Object.getOwnPropertyNames} nor712* {@code Object.getPrototypeOf} then this is equivalent to using {@code713* goog.object.getKeys}714*715* @param {?Object} obj The object to get the properties of.716* @param {boolean=} opt_includeObjectPrototype Whether properties defined on717* {@code Object.prototype} should be included in the result.718* @param {boolean=} opt_includeFunctionPrototype Whether properties defined on719* {@code Function.prototype} should be included in the result.720* @return {!Array<string>}721* @public722*/723goog.object.getAllPropertyNames = function(724obj, opt_includeObjectPrototype, opt_includeFunctionPrototype) {725if (!obj) {726return [];727}728729// Naively use a for..in loop to get the property names if the browser doesn't730// support any other APIs for getting it.731if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {732return goog.object.getKeys(obj);733}734735var visitedSet = {};736737// Traverse the prototype chain and add all properties to the visited set.738var proto = obj;739while (proto &&740(proto !== Object.prototype || !!opt_includeObjectPrototype) &&741(proto !== Function.prototype || !!opt_includeFunctionPrototype)) {742var names = Object.getOwnPropertyNames(proto);743for (var i = 0; i < names.length; i++) {744visitedSet[names[i]] = true;745}746proto = Object.getPrototypeOf(proto);747}748749return goog.object.getKeys(visitedSet);750};751752753