PageRenderTime 51ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/packages/ember-metal/lib/map.js

https://gitlab.com/Aaeinstein54/ember.js
JavaScript | 497 lines | 225 code | 63 blank | 209 comment | 30 complexity | 05d8ca74419c2d4e1295d3f20b6e3bcf MD5 | raw file
  1. /**
  2. @module ember
  3. @submodule ember-metal
  4. */
  5. /*
  6. JavaScript (before ES6) does not have a Map implementation. Objects,
  7. which are often used as dictionaries, may only have Strings as keys.
  8. Because Ember has a way to get a unique identifier for every object
  9. via `Ember.guidFor`, we can implement a performant Map with arbitrary
  10. keys. Because it is commonly used in low-level bookkeeping, Map is
  11. implemented as a pure JavaScript object for performance.
  12. This implementation follows the current iteration of the ES6 proposal for
  13. maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets),
  14. with one exception: as we do not have the luxury of in-VM iteration, we implement a
  15. forEach method for iteration.
  16. Map is mocked out to look like an Ember object, so you can do
  17. `Ember.Map.create()` for symmetry with other Ember classes.
  18. */
  19. import Ember from 'ember-metal/core';
  20. import { guidFor } from 'ember-metal/utils';
  21. import EmptyObject from 'ember-metal/empty_object';
  22. function missingFunction(fn) {
  23. throw new TypeError(`${Object.prototype.toString.call(fn)} is not a function`);
  24. }
  25. function missingNew(name) {
  26. throw new TypeError(`Constructor ${name} requires 'new'`);
  27. }
  28. function copyNull(obj) {
  29. var output = new EmptyObject();
  30. for (var prop in obj) {
  31. // hasOwnPropery is not needed because obj is new EmptyObject();
  32. output[prop] = obj[prop];
  33. }
  34. return output;
  35. }
  36. function copyMap(original, newObject) {
  37. var keys = original._keys.copy();
  38. var values = copyNull(original._values);
  39. newObject._keys = keys;
  40. newObject._values = values;
  41. newObject.size = original.size;
  42. return newObject;
  43. }
  44. /**
  45. This class is used internally by Ember and Ember Data.
  46. Please do not use it at this time. We plan to clean it up
  47. and add many tests soon.
  48. @class OrderedSet
  49. @namespace Ember
  50. @constructor
  51. @private
  52. */
  53. function OrderedSet() {
  54. if (this instanceof OrderedSet) {
  55. this.clear();
  56. this._silenceRemoveDeprecation = false;
  57. } else {
  58. missingNew('OrderedSet');
  59. }
  60. }
  61. /**
  62. @method create
  63. @static
  64. @return {Ember.OrderedSet}
  65. @private
  66. */
  67. OrderedSet.create = function() {
  68. var Constructor = this;
  69. return new Constructor();
  70. };
  71. OrderedSet.prototype = {
  72. constructor: OrderedSet,
  73. /**
  74. @method clear
  75. @private
  76. */
  77. clear() {
  78. this.presenceSet = new EmptyObject();
  79. this.list = [];
  80. this.size = 0;
  81. },
  82. /**
  83. @method add
  84. @param obj
  85. @param guid (optional, and for internal use)
  86. @return {Ember.OrderedSet}
  87. @private
  88. */
  89. add(obj, _guid) {
  90. var guid = _guid || guidFor(obj);
  91. var presenceSet = this.presenceSet;
  92. var list = this.list;
  93. if (presenceSet[guid] !== true) {
  94. presenceSet[guid] = true;
  95. this.size = list.push(obj);
  96. }
  97. return this;
  98. },
  99. /**
  100. @since 1.8.0
  101. @method delete
  102. @param obj
  103. @param _guid (optional and for internal use only)
  104. @return {Boolean}
  105. @private
  106. */
  107. delete(obj, _guid) {
  108. var guid = _guid || guidFor(obj);
  109. var presenceSet = this.presenceSet;
  110. var list = this.list;
  111. if (presenceSet[guid] === true) {
  112. delete presenceSet[guid];
  113. var index = list.indexOf(obj);
  114. if (index > -1) {
  115. list.splice(index, 1);
  116. }
  117. this.size = list.length;
  118. return true;
  119. } else {
  120. return false;
  121. }
  122. },
  123. /**
  124. @method isEmpty
  125. @return {Boolean}
  126. @private
  127. */
  128. isEmpty() {
  129. return this.size === 0;
  130. },
  131. /**
  132. @method has
  133. @param obj
  134. @return {Boolean}
  135. @private
  136. */
  137. has(obj) {
  138. if (this.size === 0) { return false; }
  139. var guid = guidFor(obj);
  140. var presenceSet = this.presenceSet;
  141. return presenceSet[guid] === true;
  142. },
  143. /**
  144. @method forEach
  145. @param {Function} fn
  146. @param self
  147. @private
  148. */
  149. forEach(fn /*, ...thisArg*/) {
  150. if (typeof fn !== 'function') {
  151. missingFunction(fn);
  152. }
  153. if (this.size === 0) { return; }
  154. var list = this.list;
  155. var length = arguments.length;
  156. var i;
  157. if (length === 2) {
  158. for (i = 0; i < list.length; i++) {
  159. fn.call(arguments[1], list[i]);
  160. }
  161. } else {
  162. for (i = 0; i < list.length; i++) {
  163. fn(list[i]);
  164. }
  165. }
  166. },
  167. /**
  168. @method toArray
  169. @return {Array}
  170. @private
  171. */
  172. toArray() {
  173. return this.list.slice();
  174. },
  175. /**
  176. @method copy
  177. @return {Ember.OrderedSet}
  178. @private
  179. */
  180. copy() {
  181. var Constructor = this.constructor;
  182. var set = new Constructor();
  183. set._silenceRemoveDeprecation = this._silenceRemoveDeprecation;
  184. set.presenceSet = copyNull(this.presenceSet);
  185. set.list = this.toArray();
  186. set.size = this.size;
  187. return set;
  188. }
  189. };
  190. /**
  191. A Map stores values indexed by keys. Unlike JavaScript's
  192. default Objects, the keys of a Map can be any JavaScript
  193. object.
  194. Internally, a Map has two data structures:
  195. 1. `keys`: an OrderedSet of all of the existing keys
  196. 2. `values`: a JavaScript Object indexed by the `Ember.guidFor(key)`
  197. When a key/value pair is added for the first time, we
  198. add the key to the `keys` OrderedSet, and create or
  199. replace an entry in `values`. When an entry is deleted,
  200. we delete its entry in `keys` and `values`.
  201. @class Map
  202. @namespace Ember
  203. @private
  204. @constructor
  205. */
  206. function Map() {
  207. if (this instanceof this.constructor) {
  208. this._keys = OrderedSet.create();
  209. this._keys._silenceRemoveDeprecation = true;
  210. this._values = new EmptyObject();
  211. this.size = 0;
  212. } else {
  213. missingNew('OrderedSet');
  214. }
  215. }
  216. Ember.Map = Map;
  217. /**
  218. @method create
  219. @static
  220. @private
  221. */
  222. Map.create = function() {
  223. var Constructor = this;
  224. return new Constructor();
  225. };
  226. Map.prototype = {
  227. constructor: Map,
  228. /**
  229. This property will change as the number of objects in the map changes.
  230. @since 1.8.0
  231. @property size
  232. @type number
  233. @default 0
  234. @private
  235. */
  236. size: 0,
  237. /**
  238. Retrieve the value associated with a given key.
  239. @method get
  240. @param {*} key
  241. @return {*} the value associated with the key, or `undefined`
  242. @private
  243. */
  244. get(key) {
  245. if (this.size === 0) { return; }
  246. var values = this._values;
  247. var guid = guidFor(key);
  248. return values[guid];
  249. },
  250. /**
  251. Adds a value to the map. If a value for the given key has already been
  252. provided, the new value will replace the old value.
  253. @method set
  254. @param {*} key
  255. @param {*} value
  256. @return {Ember.Map}
  257. @private
  258. */
  259. set(key, value) {
  260. var keys = this._keys;
  261. var values = this._values;
  262. var guid = guidFor(key);
  263. // ensure we don't store -0
  264. var k = key === -0 ? 0 : key;
  265. keys.add(k, guid);
  266. values[guid] = value;
  267. this.size = keys.size;
  268. return this;
  269. },
  270. /**
  271. Removes a value from the map for an associated key.
  272. @since 1.8.0
  273. @method delete
  274. @param {*} key
  275. @return {Boolean} true if an item was removed, false otherwise
  276. @private
  277. */
  278. delete(key) {
  279. if (this.size === 0) { return false; }
  280. // don't use ES6 "delete" because it will be annoying
  281. // to use in browsers that are not ES6 friendly;
  282. var keys = this._keys;
  283. var values = this._values;
  284. var guid = guidFor(key);
  285. if (keys.delete(key, guid)) {
  286. delete values[guid];
  287. this.size = keys.size;
  288. return true;
  289. } else {
  290. return false;
  291. }
  292. },
  293. /**
  294. Check whether a key is present.
  295. @method has
  296. @param {*} key
  297. @return {Boolean} true if the item was present, false otherwise
  298. @private
  299. */
  300. has(key) {
  301. return this._keys.has(key);
  302. },
  303. /**
  304. Iterate over all the keys and values. Calls the function once
  305. for each key, passing in value, key, and the map being iterated over,
  306. in that order.
  307. The keys are guaranteed to be iterated over in insertion order.
  308. @method forEach
  309. @param {Function} callback
  310. @param {*} self if passed, the `this` value inside the
  311. callback. By default, `this` is the map.
  312. @private
  313. */
  314. forEach(callback/*, ...thisArg*/) {
  315. if (typeof callback !== 'function') {
  316. missingFunction(callback);
  317. }
  318. if (this.size === 0) { return; }
  319. var length = arguments.length;
  320. var map = this;
  321. var cb, thisArg;
  322. if (length === 2) {
  323. thisArg = arguments[1];
  324. cb = function(key) {
  325. callback.call(thisArg, map.get(key), key, map);
  326. };
  327. } else {
  328. cb = function(key) {
  329. callback(map.get(key), key, map);
  330. };
  331. }
  332. this._keys.forEach(cb);
  333. },
  334. /**
  335. @method clear
  336. @private
  337. */
  338. clear() {
  339. this._keys.clear();
  340. this._values = new EmptyObject();
  341. this.size = 0;
  342. },
  343. /**
  344. @method copy
  345. @return {Ember.Map}
  346. @private
  347. */
  348. copy() {
  349. return copyMap(this, new Map());
  350. }
  351. };
  352. /**
  353. @class MapWithDefault
  354. @namespace Ember
  355. @extends Ember.Map
  356. @private
  357. @constructor
  358. @param [options]
  359. @param {*} [options.defaultValue]
  360. */
  361. function MapWithDefault(options) {
  362. this._super$constructor();
  363. this.defaultValue = options.defaultValue;
  364. }
  365. /**
  366. @method create
  367. @static
  368. @param [options]
  369. @param {*} [options.defaultValue]
  370. @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
  371. `Ember.MapWithDefault` otherwise returns `Ember.Map`
  372. @private
  373. */
  374. MapWithDefault.create = function(options) {
  375. if (options) {
  376. return new MapWithDefault(options);
  377. } else {
  378. return new Map();
  379. }
  380. };
  381. MapWithDefault.prototype = Object.create(Map.prototype);
  382. MapWithDefault.prototype.constructor = MapWithDefault;
  383. MapWithDefault.prototype._super$constructor = Map;
  384. MapWithDefault.prototype._super$get = Map.prototype.get;
  385. /**
  386. Retrieve the value associated with a given key.
  387. @method get
  388. @param {*} key
  389. @return {*} the value associated with the key, or the default value
  390. @private
  391. */
  392. MapWithDefault.prototype.get = function(key) {
  393. var hasValue = this.has(key);
  394. if (hasValue) {
  395. return this._super$get(key);
  396. } else {
  397. var defaultValue = this.defaultValue(key);
  398. this.set(key, defaultValue);
  399. return defaultValue;
  400. }
  401. };
  402. /**
  403. @method copy
  404. @return {Ember.MapWithDefault}
  405. @private
  406. */
  407. MapWithDefault.prototype.copy = function() {
  408. var Constructor = this.constructor;
  409. return copyMap(this, new Constructor({
  410. defaultValue: this.defaultValue
  411. }));
  412. };
  413. export default Map;
  414. export {
  415. OrderedSet,
  416. Map,
  417. MapWithDefault
  418. };