diff --git a/packages/ember-metal/lib/events.js b/packages/ember-metal/lib/events.js index a62155c480b..8e03cd5cd1a 100644 --- a/packages/ember-metal/lib/events.js +++ b/packages/ember-metal/lib/events.js @@ -6,14 +6,13 @@ @module ember @submodule ember-metal */ -import { applyStr } from 'ember-utils'; +import { applyStr, functionMetaFor } from 'ember-utils'; import { assert } from './debug'; import { meta as metaFor, peekMeta } from './meta'; import { deprecate } from './debug'; import { ONCE, SUSPENDED } from './meta_listeners'; - /* The event system uses a series of nested hashes to store listeners on an object. When a listener is registered, or when an event arrives, these @@ -304,6 +303,9 @@ export function listenersFor(obj, eventName) { export function on(...args) { let func = args.pop(); let events = args; - func.__ember_listens__ = events; + + let meta = functionMetaFor(func); + meta.writeListeners(events); + return func; } diff --git a/packages/ember-metal/lib/meta.js b/packages/ember-metal/lib/meta.js index 77855b526a4..276e7e44816 100644 --- a/packages/ember-metal/lib/meta.js +++ b/packages/ember-metal/lib/meta.js @@ -2,7 +2,12 @@ // Remove "use strict"; from transpiled module until // https://bugs.webkit.org/show_bug.cgi?id=138038 is fixed -import { EmptyObject, lookupDescriptor, symbol } from 'ember-utils'; +import { + EmptyObject, + lookupDescriptor, + symbol, + HAS_NATIVE_WEAKMAP +} from 'ember-utils'; import isEnabled from './features'; import { protoMethods as listenerMethods } from './meta_listeners'; import { runInDebug, assert } from './debug'; @@ -448,17 +453,6 @@ if (isEnabled('mandatory-setter')) { }; } -const HAS_NATIVE_WEAKMAP = (function() { - // detect if `WeakMap` is even present - let hasWeakMap = typeof WeakMap === 'function'; - if (!hasWeakMap) { return false; } - - let instance = new WeakMap(); - // use `Object`'s `.toString` directly to prevent us from detecting - // polyfills as native weakmaps - return Object.prototype.toString.call(instance) === '[object WeakMap]'; -})(); - let setMeta, peekMeta; // choose the one appropriate for given platform @@ -472,12 +466,6 @@ if (HAS_NATIVE_WEAKMAP) { }; peekMeta = function WeakMap_peekMeta(obj) { - runInDebug(() => counters.peekCalls++); - - return metaStore.get(obj); - }; - - peekMeta = function WeakMap_peekParentMeta(obj) { let pointer = obj; let meta; while (pointer) { diff --git a/packages/ember-metal/lib/mixin.js b/packages/ember-metal/lib/mixin.js index be18e456427..3fb594c4786 100644 --- a/packages/ember-metal/lib/mixin.js +++ b/packages/ember-metal/lib/mixin.js @@ -11,7 +11,10 @@ import { guidFor, GUID_KEY, wrap, - makeArray + makeArray, + ROOT, + functionMetaFor, + peekFunctionMeta } from 'ember-utils'; import EmberError from './error'; import { @@ -39,9 +42,6 @@ import { removeListener } from './events'; -function ROOT() {} -ROOT.__hasSuper = false; - const a_slice = [].slice; function isMethod(obj) { @@ -310,29 +310,26 @@ function followAlias(obj, desc, m, descs, values) { return { desc: desc, value: value }; } -function updateObserversAndListeners(obj, key, observerOrListener, pathsKey, updateMethod) { - let paths = observerOrListener[pathsKey]; - - if (paths) { - for (let i = 0; i < paths.length; i++) { - updateMethod(obj, paths[i], null, key); - } +function updateObserversAndListeners(obj, key, observerOrListener, paths, updateMethod) { + for (let i = 0; i < paths.length; i++) { + updateMethod(obj, paths[i], null, key); } } function replaceObserversAndListeners(obj, key, observerOrListener) { let prev = obj[key]; + let m; - if ('function' === typeof prev) { - updateObserversAndListeners(obj, key, prev, '__ember_observesBefore__', _removeBeforeObserver); - updateObserversAndListeners(obj, key, prev, '__ember_observes__', removeObserver); - updateObserversAndListeners(obj, key, prev, '__ember_listens__', removeListener); + if ('function' === typeof prev && (m = peekFunctionMeta(prev))) { + updateObserversAndListeners(obj, key, prev, m.peekBeforeObservers(), _removeBeforeObserver); + updateObserversAndListeners(obj, key, prev, m.peekObservers(), removeObserver); + updateObserversAndListeners(obj, key, prev, m.peekListeners(), removeListener); } - if ('function' === typeof observerOrListener) { - updateObserversAndListeners(obj, key, observerOrListener, '__ember_observesBefore__', _addBeforeObserver); - updateObserversAndListeners(obj, key, observerOrListener, '__ember_observes__', addObserver); - updateObserversAndListeners(obj, key, observerOrListener, '__ember_listens__', addListener); + if ('function' === typeof observerOrListener && (m = peekFunctionMeta(observerOrListener))) { + updateObserversAndListeners(obj, key, observerOrListener, m.peekBeforeObservers(), _addBeforeObserver); + updateObserversAndListeners(obj, key, observerOrListener, m.peekObservers(), addObserver); + updateObserversAndListeners(obj, key, observerOrListener, m.peekListeners(), addListener); } } @@ -754,7 +751,9 @@ export function observer(...args) { throw new EmberError('Ember.observer called without a function'); } - func.__ember_observes__ = paths; + let meta = functionMetaFor(func); + meta.writeObservers(paths); + return func; } @@ -838,7 +837,9 @@ export function _beforeObserver(...args) { throw new EmberError('_beforeObserver called without a function'); } - func.__ember_observesBefore__ = paths; + let meta = functionMetaFor(func); + meta.writeBeforeObservers(paths); + return func; } diff --git a/packages/ember-runtime/lib/ext/function.js b/packages/ember-runtime/lib/ext/function.js index 75634bae481..e811009a0ab 100644 --- a/packages/ember-runtime/lib/ext/function.js +++ b/packages/ember-runtime/lib/ext/function.js @@ -11,7 +11,10 @@ import { observer } from 'ember-metal'; -const a_slice = Array.prototype.slice; +import { + functionMetaFor +} from 'ember-utils'; + const FunctionPrototype = Function.prototype; if (ENV.EXTEND_PROTOTYPES.Function) { @@ -184,9 +187,9 @@ if (ENV.EXTEND_PROTOTYPES.Function) { @for Function @public */ - FunctionPrototype.on = function () { - let events = a_slice.call(arguments); - this.__ember_listens__ = events; + FunctionPrototype.on = function (...events) { + let meta = functionMetaFor(this); + meta.writeListeners(events); return this; }; diff --git a/packages/ember-utils/lib/function-meta.js b/packages/ember-utils/lib/function-meta.js new file mode 100644 index 00000000000..8e93f3cf835 --- /dev/null +++ b/packages/ember-utils/lib/function-meta.js @@ -0,0 +1,133 @@ +import { HAS_NATIVE_WEAKMAP } from './weak-map-utils'; + +function ROOT_WRAPPED_FUNCTION () { } +let ROOT_WRAPPED_FUNCTION_META; + +function FunctionMeta(func, wrappedFunctionMeta) { + this._beforeObservers = undefined; + this._observers = undefined; + this._listeners = undefined; + this._hasSuper = undefined; + + // The function that we are wrapping + this.wrappedFunctionMeta = wrappedFunctionMeta || ROOT_WRAPPED_FUNCTION_META; +} + +FunctionMeta.prototype.hasWrappedFunction = function hasWrappedFunction() { + return this.wrappedFunctionMeta !== ROOT_WRAPPED_FUNCTION_META; +}; + +FunctionMeta.prototype.peekHasSuper = function hasSuper() { + return this._hasSuper; +}; + +FunctionMeta.prototype.writeHasSuper = function(value) { + this._hasSuper = value; +}; + +FunctionMeta.prototype.peekBeforeObservers = function peekBeforeObservers() { + return this._getInherited('_beforeObservers'); +}; + +FunctionMeta.prototype.writeBeforeObservers = function writeBeforeObservers(value) { + this._beforeObservers = value; +}; + +FunctionMeta.prototype.peekObservers = function peekObservers() { + return this._getInherited('_observers'); +}; + +FunctionMeta.prototype.writeObservers = function writeObservers(value) { + this._observers = value; +}; + +FunctionMeta.prototype.peekListeners = function peekListeners() { + return this._getInherited('_listeners'); +}; + +FunctionMeta.prototype.writeListeners = function writeListeners(value) { + this._listeners = value; +}; + +FunctionMeta.prototype._getInherited = function(key) { + let pointer = this; + while (pointer !== undefined) { + if (pointer[key] !== undefined) { + return pointer[key]; + } + pointer = pointer.wrappedFunctionMeta; + } +}; + +function buildRootWrappedFunction() { + let meta = new FunctionMeta(ROOT_WRAPPED_FUNCTION); + + meta.writeBeforeObservers([]); + meta.writeObservers([]); + meta.writeListeners([]); + + return meta; +} + +// setup shared wrapped function +ROOT_WRAPPED_FUNCTION_META = buildRootWrappedFunction(); + +let setFunctionMeta, peekFunctionMeta, deleteFunctionMeta; + +if (HAS_NATIVE_WEAKMAP) { + let metaStore = new WeakMap(); + + setFunctionMeta = function Fallback_setFunctionMeta(obj, meta) { + metaStore.set(obj, meta); + }; + + peekFunctionMeta = function Fallback_peekFunctionMeta(obj) { + return metaStore.get(obj); + }; + + deleteFunctionMeta = function Fallback_deleteFunctionMeta(obj) { + metaStore.set(obj, null); + }; +} else { + let FUNCTION_META_FIELD = '__ember_function_meta__'; + + setFunctionMeta = function Fallback_setFunctionMeta(obj, meta) { + obj[FUNCTION_META_FIELD] = meta; + }; + + peekFunctionMeta = function Fallback_peekFunctionMeta(obj) { + return obj[FUNCTION_META_FIELD]; + }; + + deleteFunctionMeta = function Fallback_deleteFunctionMeta(obj) { + obj[FUNCTION_META_FIELD] = null; + }; +} + +/** + Retrieves the meta hash for a function. + + A functions meta object contains information about observers, listeners, + if a function has been super wrapped, etc. + + @method functionMetaFor + @private + + @param {Object} obj The object to retrieve meta for + @return {Object} the meta hash for an object +*/ +export function functionMetaFor(func, wrappedFunctionMeta) { + let maybeMeta = peekFunctionMeta(func); + if (maybeMeta) { return maybeMeta; } + + let newMeta = new FunctionMeta(func, wrappedFunctionMeta); + setFunctionMeta(func, newMeta); + + return newMeta; +} + +export { + setFunctionMeta, + peekFunctionMeta, + deleteFunctionMeta +}; diff --git a/packages/ember-utils/lib/index.js b/packages/ember-utils/lib/index.js index 19b634220e8..40936d339c8 100644 --- a/packages/ember-utils/lib/index.js +++ b/packages/ember-utils/lib/index.js @@ -29,3 +29,5 @@ export { canInvoke, tryInvoke } from './invoke'; export { default as makeArray } from './make-array'; export { default as applyStr } from './apply-str'; export { default as toString } from './to-string'; +export { functionMetaFor, peekFunctionMeta } from './function-meta'; +export { HAS_NATIVE_WEAKMAP } from './weak-map-utils'; diff --git a/packages/ember-utils/lib/super.js b/packages/ember-utils/lib/super.js index 66a724ce8e6..44761bda154 100644 --- a/packages/ember-utils/lib/super.js +++ b/packages/ember-utils/lib/super.js @@ -1,3 +1,7 @@ +import { + functionMetaFor +} from './function-meta'; + const HAS_SUPER_PATTERN = /\.(_super|call\(this|apply\(this)/; const fnToString = Function.prototype.toString; @@ -17,14 +21,24 @@ export const checkHasSuper = (function () { }; }()); -function ROOT() {} -ROOT.__hasSuper = false; +export const ROOT = (function() { + function TERMINAL_SUPER_ROOT() {} + + // create meta for the terminal root + let meta = functionMetaFor(TERMINAL_SUPER_ROOT); + meta.writeHasSuper(false); + + return TERMINAL_SUPER_ROOT; +})(); -function hasSuper(func) { - if (func.__hasSuper === undefined) { - func.__hasSuper = checkHasSuper(func); +function hasSuper(func, meta) { + let hasSuper = meta.peekHasSuper(); + + if (hasSuper === undefined) { + hasSuper = checkHasSuper(func); + meta.writeHasSuper(hasSuper); } - return func.__hasSuper; + return hasSuper; } /** @@ -40,17 +54,22 @@ function hasSuper(func) { @return {Function} wrapped function. */ export function wrap(func, superFunc) { - if (!hasSuper(func)) { + let funcMeta = functionMetaFor(func); + if (!hasSuper(func, funcMeta)) { return func; } + + let superFuncMeta = functionMetaFor(superFunc); // ensure an unwrapped super that calls _super is wrapped with a terminal _super - if (!superFunc.wrappedFunction && hasSuper(superFunc)) { - return _wrap(func, _wrap(superFunc, ROOT)); + if (!superFuncMeta.hasWrappedFunction() && hasSuper(superFunc, superFuncMeta)) { + let wrappedSuperFunc = _wrap(superFunc, superFuncMeta, ROOT); + return _wrap(func, funcMeta, wrappedSuperFunc); } - return _wrap(func, superFunc); + + return _wrap(func, funcMeta, superFunc); } -function _wrap(func, superFunc) { +function _wrap(func, funcMeta, superFunc) { function superWrapper() { let orig = this._super; this._super = superFunc; @@ -59,10 +78,8 @@ function _wrap(func, superFunc) { return ret; } - superWrapper.wrappedFunction = func; - superWrapper.__ember_observes__ = func.__ember_observes__; - superWrapper.__ember_observesBefore__ = func.__ember_observesBefore__; - superWrapper.__ember_listens__ = func.__ember_listens__; + // setup the super wrapped functions meta + functionMetaFor(superWrapper, funcMeta); return superWrapper; } diff --git a/packages/ember-utils/lib/weak-map-utils.js b/packages/ember-utils/lib/weak-map-utils.js new file mode 100644 index 00000000000..b316fa569bc --- /dev/null +++ b/packages/ember-utils/lib/weak-map-utils.js @@ -0,0 +1,10 @@ +export const HAS_NATIVE_WEAKMAP = (function() { + // detect if `WeakMap` is even present + let hasWeakMap = typeof WeakMap === 'function'; + if (!hasWeakMap) { return false; } + + let instance = new WeakMap(); + // use `Object`'s `.toString` directly to prevent us from detecting + // polyfills as native weakmaps + return Object.prototype.toString.call(instance) === '[object WeakMap]'; +})();