UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

276 lines (243 loc) 7.92 kB
/** Strongly hint runtimes to intern the provided string. When do I need to use this function? For the most part, never. Pre-mature optimization is bad, and often the runtime does exactly what you need it to, and more often the trade-off isn't worth it. Why? Runtimes store strings in at least 2 different representations: Ropes and Symbols (interned strings). The Rope provides a memory efficient data-structure for strings created from concatenation or some other string manipulation like splitting. Unfortunately checking equality of different ropes can be quite costly as runtimes must resort to clever string comparison algorithms. These algorithms typically cost in proportion to the length of the string. Luckily, this is where the Symbols (interned strings) shine. As Symbols are unique by their string content, equality checks can be done by pointer comparison. How do I know if my string is a rope or symbol? Typically (warning general sweeping statement, but truthy in runtimes at present) static strings created as part of the JS source are interned. Strings often used for comparisons can be interned at runtime if some criteria are met. One of these criteria can be the size of the entire rope. For example, in chrome 38 a rope longer then 12 characters will not intern, nor will segments of that rope. Some numbers: http://jsperf.com/eval-vs-keys/8 Known Trick™ @private @return {String} interned version of the provided string */ function intern(str) { let obj = Object.create(null); obj[str] = 1; for (let key in obj) { if (key === str) { return key; } } return str; } /** Returns whether Type(value) is Object. Useful for checking whether a value is a valid WeakMap key. Refs: https://tc39.github.io/ecma262/#sec-typeof-operator-runtime-semantics-evaluation https://tc39.github.io/ecma262/#sec-weakmap.prototype.set @private @function isObject */ function isObject(value) { return value !== null && (typeof value === 'object' || typeof value === 'function'); } /** @module @ember/object */ /** @private @return {Number} the uuid */ let _uuid = 0; /** Generates a universally unique identifier. This method is used internally by Ember for assisting with the generation of GUID's and other unique identifiers. @public @return {Number} [description] */ function uuid() { return ++_uuid; } /** Prefix used for guids through out Ember. @private @property GUID_PREFIX @for Ember @type String @final */ const GUID_PREFIX = 'ember'; // Used for guid generation... const OBJECT_GUIDS = new WeakMap(); const NON_OBJECT_GUIDS = new Map(); /** A unique key used to assign guids and other private metadata to objects. If you inspect an object in your browser debugger you will often see these. They can be safely ignored. On browsers that support it, these properties are added with enumeration disabled so they won't show up when you iterate over your properties. @private @property GUID_KEY @for Ember @type String @final */ const GUID_KEY = intern(`__ember${Date.now()}`); /** Generates a new guid, optionally saving the guid to the object that you pass in. You will rarely need to use this method. Instead you should call `guidFor(obj)`, which return an existing guid if available. @private @method generateGuid @static @for @ember/object/internals @param {Object} [obj] Object the guid will be used for. If passed in, the guid will be saved on the object and reused whenever you pass the same object again. If no object is passed, just generate a new guid. @param {String} [prefix] Prefix to place in front of the guid. Useful when you want to separate the guid into separate namespaces. @return {String} the guid */ // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types function generateGuid(obj, prefix = GUID_PREFIX) { let guid = prefix + uuid().toString(); if (isObject(obj)) { OBJECT_GUIDS.set(obj, guid); } return guid; } /** Returns a unique id for the object. If the object does not yet have a guid, one will be assigned to it. You can call this on any object, `EmberObject`-based or not. You can also use this method on DOM Element objects. @public @static @method guidFor @for @ember/object/internals @param {Object} obj any object, string, number, Element, or primitive @return {String} the unique guid for this instance. */ function guidFor(value) { let guid; if (isObject(value)) { guid = OBJECT_GUIDS.get(value); if (guid === undefined) { guid = `${GUID_PREFIX}${uuid()}`; OBJECT_GUIDS.set(value, guid); } } else { guid = NON_OBJECT_GUIDS.get(value); if (guid === undefined) { let type = typeof value; if (type === 'string') { guid = `st${uuid()}`; } else if (type === 'number') { guid = `nu${uuid()}`; } else if (type === 'symbol') { guid = `sy${uuid()}`; } else { guid = `(${value})`; } NON_OBJECT_GUIDS.set(value, guid); } } return guid; } const HAS_SUPER_PATTERN = /\.(_super|call\(this|apply\(this)/; const fnToString = Function.prototype.toString; const checkHasSuper = (() => { let sourceAvailable = fnToString.call(function () { return this; }).indexOf('return this') > -1; if (sourceAvailable) { return function checkHasSuper(func) { return HAS_SUPER_PATTERN.test(fnToString.call(func)); }; } return function checkHasSuper() { return true; }; })(); const HAS_SUPER_MAP = new WeakMap(); const ROOT = Object.freeze(function () {}); HAS_SUPER_MAP.set(ROOT, false); function hasSuper(func) { let hasSuper = HAS_SUPER_MAP.get(func); if (hasSuper === undefined) { hasSuper = checkHasSuper(func); HAS_SUPER_MAP.set(func, hasSuper); } return hasSuper; } class ObserverListenerMeta { listeners = undefined; observers = undefined; } const OBSERVERS_LISTENERS_MAP = new WeakMap(); function createObserverListenerMetaFor(fn) { let meta = OBSERVERS_LISTENERS_MAP.get(fn); if (meta === undefined) { meta = new ObserverListenerMeta(); OBSERVERS_LISTENERS_MAP.set(fn, meta); } return meta; } function observerListenerMetaFor(fn) { return OBSERVERS_LISTENERS_MAP.get(fn); } function setObservers(func, observers) { let meta = createObserverListenerMetaFor(func); meta.observers = observers; } function setListeners(func, listeners) { let meta = createObserverListenerMetaFor(func); meta.listeners = listeners; } const IS_WRAPPED_FUNCTION_SET = new WeakSet(); /** Wraps the passed function so that `this._super` will point to the superFunc when the function is invoked. This is the primitive we use to implement calls to super. @private @method wrap @for Ember @param {Function} func The function to call @param {Function} superFunc The super function. @return {Function} wrapped function. */ function wrap(func, superFunc) { if (!hasSuper(func)) { return func; } // ensure an unwrapped super that calls _super is wrapped with a terminal _super if (!IS_WRAPPED_FUNCTION_SET.has(superFunc) && hasSuper(superFunc)) { return _wrap(func, _wrap(superFunc, ROOT)); } return _wrap(func, superFunc); } function _wrap(func, superFunc) { function superWrapper() { let orig = this._super; this._super = superFunc; let ret = func.apply(this, arguments); this._super = orig; return ret; } IS_WRAPPED_FUNCTION_SET.add(superWrapper); let meta = OBSERVERS_LISTENERS_MAP.get(func); if (meta !== undefined) { OBSERVERS_LISTENERS_MAP.set(superWrapper, meta); } return superWrapper; } export { GUID_KEY as G, ROOT as R, generateGuid as a, intern as b, checkHasSuper as c, setListeners as d, guidFor as g, isObject as i, observerListenerMetaFor as o, setObservers as s, uuid as u, wrap as w };