stringset.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. // stringset.js
  2. // MIT licensed, see LICENSE file
  3. // Copyright (c) 2013 Olov Lassus <olov.lassus@gmail.com>
  4. var StringSet = (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("StringSet environment error 0, please file a bug at https://github.com/olov/stringset/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("StringSet environment error 1, please file a bug at https://github.com/olov/stringset/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. // stringset ctor
  54. function stringset(optional_array) {
  55. // use with or without new
  56. if (!(this instanceof stringset)) {
  57. return new stringset(optional_array);
  58. }
  59. this.obj = create();
  60. this.hasProto = false; // false (no __proto__ item) or true (has __proto__ item)
  61. if (optional_array) {
  62. this.addMany(optional_array);
  63. }
  64. };
  65. // primitive methods that deals with data representation
  66. stringset.prototype.has = function(item) {
  67. // The type-check of item in has, get, set and delete is important because otherwise an object
  68. // {toString: function() { return "__proto__"; }} can avoid the item === "__proto__" test.
  69. // The alternative to type-checking would be to force string conversion, i.e. item = String(item);
  70. if (typeof item !== "string") {
  71. throw new Error("StringSet expected string item");
  72. }
  73. return (item === "__proto__" ?
  74. this.hasProto :
  75. hasOwnProperty.call(this.obj, item));
  76. };
  77. stringset.prototype.add = function(item) {
  78. if (typeof item !== "string") {
  79. throw new Error("StringSet expected string item");
  80. }
  81. if (item === "__proto__") {
  82. this.hasProto = true;
  83. } else {
  84. this.obj[item] = true;
  85. }
  86. };
  87. stringset.prototype.remove = function(item) {
  88. if (typeof item !== "string") {
  89. throw new Error("StringSet expected string item");
  90. }
  91. var didExist = this.has(item);
  92. if (item === "__proto__") {
  93. this.hasProto = false;
  94. } else {
  95. delete this.obj[item];
  96. }
  97. return didExist;
  98. };
  99. // alias remove to delete but beware:
  100. // ss.delete("key"); // OK in ES5 and later
  101. // ss['delete']("key"); // OK in all ES versions
  102. // ss.remove("key"); // OK in all ES versions
  103. stringset.prototype['delete'] = stringset.prototype.remove;
  104. stringset.prototype.isEmpty = function() {
  105. for (var item in this.obj) {
  106. if (hasOwnProperty.call(this.obj, item)) {
  107. return false;
  108. }
  109. }
  110. return !this.hasProto;
  111. };
  112. stringset.prototype.size = function() {
  113. var len = 0;
  114. for (var item in this.obj) {
  115. if (hasOwnProperty.call(this.obj, item)) {
  116. ++len;
  117. }
  118. }
  119. return (this.hasProto ? len + 1 : len);
  120. };
  121. stringset.prototype.items = function() {
  122. var items = [];
  123. for (var item in this.obj) {
  124. if (hasOwnProperty.call(this.obj, item)) {
  125. items.push(item);
  126. }
  127. }
  128. if (this.hasProto) {
  129. items.push("__proto__");
  130. }
  131. return items;
  132. };
  133. // methods that rely on the above primitives
  134. stringset.prototype.addMany = function(items) {
  135. if (!Array.isArray(items)) {
  136. throw new Error("StringSet expected array");
  137. }
  138. for (var i = 0; i < items.length; i++) {
  139. this.add(items[i]);
  140. }
  141. return this;
  142. };
  143. stringset.prototype.merge = function(other) {
  144. this.addMany(other.items());
  145. return this;
  146. };
  147. stringset.prototype.clone = function() {
  148. var other = stringset();
  149. return other.merge(this);
  150. };
  151. stringset.prototype.toString = function() {
  152. return "{" + this.items().map(JSON.stringify).join(",") + "}";
  153. };
  154. return stringset;
  155. })();
  156. if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
  157. module.exports = StringSet;
  158. }