stringmap.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // stringmap.js
  2. // MIT licensed, see LICENSE file
  3. // Copyright (c) 2013 Olov Lassus <olov.lassus@gmail.com>
  4. var StringMap = (function() {
  5. "use strict";
  6. // to save us a few characters
  7. var hasOwnProperty = Object.prototype.hasOwnProperty;
  8. var create = (function() {
  9. function hasOwnEnumerableProps(obj) {
  10. for (var prop in obj) {
  11. if (hasOwnProperty.call(obj, prop)) {
  12. return true;
  13. }
  14. }
  15. return false;
  16. }
  17. // FF <= 3.6:
  18. // o = {}; o.hasOwnProperty("__proto__" or "__count__" or "__parent__") => true
  19. // o = {"__proto__": null}; Object.prototype.hasOwnProperty.call(o, "__proto__" or "__count__" or "__parent__") => false
  20. function hasOwnPollutedProps(obj) {
  21. return hasOwnProperty.call(obj, "__count__") || hasOwnProperty.call(obj, "__parent__");
  22. }
  23. var useObjectCreate = false;
  24. if (typeof Object.create === "function") {
  25. if (!hasOwnEnumerableProps(Object.create(null))) {
  26. useObjectCreate = true;
  27. }
  28. }
  29. if (useObjectCreate === false) {
  30. if (hasOwnEnumerableProps({})) {
  31. throw new Error("StringMap environment error 0, please file a bug at https://github.com/olov/stringmap/issues");
  32. }
  33. }
  34. // no throw yet means we can create objects without own enumerable props (safe-guard against VMs and shims)
  35. var o = (useObjectCreate ? Object.create(null) : {});
  36. var useProtoClear = false;
  37. if (hasOwnPollutedProps(o)) {
  38. o.__proto__ = null;
  39. if (hasOwnEnumerableProps(o) || hasOwnPollutedProps(o)) {
  40. throw new Error("StringMap environment error 1, please file a bug at https://github.com/olov/stringmap/issues");
  41. }
  42. useProtoClear = true;
  43. }
  44. // no throw yet means we can create objects without own polluted props (safe-guard against VMs and shims)
  45. return function() {
  46. var o = (useObjectCreate ? Object.create(null) : {});
  47. if (useProtoClear) {
  48. o.__proto__ = null;
  49. }
  50. return o;
  51. };
  52. })();
  53. // stringmap ctor
  54. function stringmap(optional_object) {
  55. // use with or without new
  56. if (!(this instanceof stringmap)) {
  57. return new stringmap(optional_object);
  58. }
  59. this.obj = create();
  60. this.hasProto = false; // false (no __proto__ key) or true (has __proto__ key)
  61. this.proto = undefined; // value for __proto__ key when hasProto is true, undefined otherwise
  62. if (optional_object) {
  63. this.setMany(optional_object);
  64. }
  65. };
  66. // primitive methods that deals with data representation
  67. stringmap.prototype.has = function(key) {
  68. // The type-check of key in has, get, set and delete is important because otherwise an object
  69. // {toString: function() { return "__proto__"; }} can avoid the key === "__proto__" test.
  70. // The alternative to type-checking would be to force string conversion, i.e. key = String(key);
  71. if (typeof key !== "string") {
  72. throw new Error("StringMap expected string key");
  73. }
  74. return (key === "__proto__" ?
  75. this.hasProto :
  76. hasOwnProperty.call(this.obj, key));
  77. };
  78. stringmap.prototype.get = function(key) {
  79. if (typeof key !== "string") {
  80. throw new Error("StringMap expected string key");
  81. }
  82. return (key === "__proto__" ?
  83. this.proto :
  84. (hasOwnProperty.call(this.obj, key) ? this.obj[key] : undefined));
  85. };
  86. stringmap.prototype.set = function(key, value) {
  87. if (typeof key !== "string") {
  88. throw new Error("StringMap expected string key");
  89. }
  90. if (key === "__proto__") {
  91. this.hasProto = true;
  92. this.proto = value;
  93. } else {
  94. this.obj[key] = value;
  95. }
  96. };
  97. stringmap.prototype.remove = function(key) {
  98. if (typeof key !== "string") {
  99. throw new Error("StringMap expected string key");
  100. }
  101. var didExist = this.has(key);
  102. if (key === "__proto__") {
  103. this.hasProto = false;
  104. this.proto = undefined;
  105. } else {
  106. delete this.obj[key];
  107. }
  108. return didExist;
  109. };
  110. // alias remove to delete but beware:
  111. // sm.delete("key"); // OK in ES5 and later
  112. // sm['delete']("key"); // OK in all ES versions
  113. // sm.remove("key"); // OK in all ES versions
  114. stringmap.prototype['delete'] = stringmap.prototype.remove;
  115. stringmap.prototype.isEmpty = function() {
  116. for (var key in this.obj) {
  117. if (hasOwnProperty.call(this.obj, key)) {
  118. return false;
  119. }
  120. }
  121. return !this.hasProto;
  122. };
  123. stringmap.prototype.size = function() {
  124. var len = 0;
  125. for (var key in this.obj) {
  126. if (hasOwnProperty.call(this.obj, key)) {
  127. ++len;
  128. }
  129. }
  130. return (this.hasProto ? len + 1 : len);
  131. };
  132. stringmap.prototype.keys = function() {
  133. var keys = [];
  134. for (var key in this.obj) {
  135. if (hasOwnProperty.call(this.obj, key)) {
  136. keys.push(key);
  137. }
  138. }
  139. if (this.hasProto) {
  140. keys.push("__proto__");
  141. }
  142. return keys;
  143. };
  144. stringmap.prototype.values = function() {
  145. var values = [];
  146. for (var key in this.obj) {
  147. if (hasOwnProperty.call(this.obj, key)) {
  148. values.push(this.obj[key]);
  149. }
  150. }
  151. if (this.hasProto) {
  152. values.push(this.proto);
  153. }
  154. return values;
  155. };
  156. stringmap.prototype.items = function() {
  157. var items = [];
  158. for (var key in this.obj) {
  159. if (hasOwnProperty.call(this.obj, key)) {
  160. items.push([key, this.obj[key]]);
  161. }
  162. }
  163. if (this.hasProto) {
  164. items.push(["__proto__", this.proto]);
  165. }
  166. return items;
  167. };
  168. // methods that rely on the above primitives
  169. stringmap.prototype.setMany = function(object) {
  170. if (object === null || (typeof object !== "object" && typeof object !== "function")) {
  171. throw new Error("StringMap expected Object");
  172. }
  173. for (var key in object) {
  174. if (hasOwnProperty.call(object, key)) {
  175. this.set(key, object[key]);
  176. }
  177. }
  178. return this;
  179. };
  180. stringmap.prototype.merge = function(other) {
  181. var keys = other.keys();
  182. for (var i = 0; i < keys.length; i++) {
  183. var key = keys[i];
  184. this.set(key, other.get(key));
  185. }
  186. return this;
  187. };
  188. stringmap.prototype.map = function(fn) {
  189. var keys = this.keys();
  190. for (var i = 0; i < keys.length; i++) {
  191. var key = keys[i];
  192. keys[i] = fn(this.get(key), key); // re-use keys array for results
  193. }
  194. return keys;
  195. };
  196. stringmap.prototype.forEach = function(fn) {
  197. var keys = this.keys();
  198. for (var i = 0; i < keys.length; i++) {
  199. var key = keys[i];
  200. fn(this.get(key), key);
  201. }
  202. };
  203. stringmap.prototype.clone = function() {
  204. var other = stringmap();
  205. return other.merge(this);
  206. };
  207. stringmap.prototype.toString = function() {
  208. var self = this;
  209. return "{" + this.keys().map(function(key) {
  210. return JSON.stringify(key) + ":" + JSON.stringify(self.get(key));
  211. }).join(",") + "}";
  212. };
  213. return stringmap;
  214. })();
  215. if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
  216. module.exports = StringMap;
  217. }