From 5a302bddef1e02ddc190e0094965e8ec24c3daa8 Mon Sep 17 00:00:00 2001 From: Alex Navasardyan Date: Fri, 5 Sep 2014 08:15:30 -0400 Subject: [PATCH] [BUGFIX beta] Extracts computed property set into a separate function This is done to work around V8 (it cannot optimize functions with try blocks). Some comparisons from a real app: `defineProperty` Before: ![defineproperty deopt](https://cloud.githubusercontent.com/assets/1131196/4164553/ad42a504-34f6-11e4-92cc-fdf17e88cc22.png) After: ![defineproperty cp-set](https://cloud.githubusercontent.com/assets/1131196/4164559/bf012a86-34f6-11e4-9bba-e5b2dc378946.png) `watchKey` Before: ![watch - key deopt](https://cloud.githubusercontent.com/assets/1131196/4164570/e518cc88-34f6-11e4-9d66-97901a75161e.png) After: ![watch-key - cp-set](https://cloud.githubusercontent.com/assets/1131196/4164573/ec00d28e-34f6-11e4-8798-222b1ee985bf.png) --- packages/ember-metal/lib/computed.js | 108 +++++++++++++++------------ 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/packages/ember-metal/lib/computed.js b/packages/ember-metal/lib/computed.js index 5a8c8f5254a..d6026f4141d 100644 --- a/packages/ember-metal/lib/computed.js +++ b/packages/ember-metal/lib/computed.js @@ -398,75 +398,85 @@ ComputedPropertyPrototype.get = function(obj, keyName) { @param {String} oldValue The old value being replaced. @return {Object} The return value of the function backing the CP. */ -ComputedPropertyPrototype.set = function(obj, keyName, value) { - var cacheable = this._cacheable; - var func = this.func; - var meta = metaFor(obj, cacheable); +ComputedPropertyPrototype.set = function computedPropertySetWithSuspend(obj, keyName, value) { var oldSuspended = this._suspended; + + this._suspended = obj; + + try { + this._set(obj, keyName, value); + } finally { + this._suspended = oldSuspended; + } +}; + +ComputedPropertyPrototype._set = function computedPropertySet(obj, keyName, value) { + var cacheable = this._cacheable; + var func = this.func; + var meta = metaFor(obj, cacheable); + var cache = meta.cache; var hadCachedValue = false; - var cache = meta.cache; + var funcArgLength, cachedValue, ret; if (this._readOnly) { throw new EmberError('Cannot set read-only property "' + keyName + '" on object: ' + inspect(obj)); } - this._suspended = obj; - - try { + if (cacheable && cache[keyName] !== undefined) { + if(cache[keyName] !== UNDEFINED) { + cachedValue = cache[keyName]; + } - if (cacheable && cache[keyName] !== undefined) { - if(cache[keyName] !== UNDEFINED) { - cachedValue = cache[keyName]; - } + hadCachedValue = true; + } - hadCachedValue = true; - } + // Check if the CP has been wrapped. If it has, use the + // length from the wrapped function. - // Check if the CP has been wrapped. If it has, use the - // length from the wrapped function. + funcArgLength = func.wrappedFunction ? func.wrappedFunction.__ember_arity__ : func.__ember_arity__; - funcArgLength = func.wrappedFunction ? func.wrappedFunction.__ember_arity__ : func.__ember_arity__; + // For backwards-compatibility with computed properties + // that check for arguments.length === 2 to determine if + // they are being get or set, only pass the old cached + // value if the computed property opts into a third + // argument. + if (funcArgLength === 3) { + ret = func.call(obj, keyName, value, cachedValue); + } else if (funcArgLength === 2) { + ret = func.call(obj, keyName, value); + } else { + defineProperty(obj, keyName, null, cachedValue); + set(obj, keyName, value); + return; + } - // For backwards-compatibility with computed properties - // that check for arguments.length === 2 to determine if - // they are being get or set, only pass the old cached - // value if the computed property opts into a third - // argument. - if (funcArgLength === 3) { - ret = func.call(obj, keyName, value, cachedValue); - } else if (funcArgLength === 2) { - ret = func.call(obj, keyName, value); - } else { - defineProperty(obj, keyName, null, cachedValue); - set(obj, keyName, value); - return; - } + if (hadCachedValue && cachedValue === ret) { return; } - if (hadCachedValue && cachedValue === ret) { return; } + var watched = meta.watching[keyName]; + if (watched) { + propertyWillChange(obj, keyName); + } - var watched = meta.watching[keyName]; - if (watched) { propertyWillChange(obj, keyName); } + if (hadCachedValue) { + cache[keyName] = undefined; + } - if (hadCachedValue) { - cache[keyName] = undefined; + if (cacheable) { + if (!hadCachedValue) { + addDependentKeys(this, obj, keyName, meta); } - - if (cacheable) { - if (!hadCachedValue) { - addDependentKeys(this, obj, keyName, meta); - } - if (ret === undefined) { - cache[keyName] = UNDEFINED; - } else { - cache[keyName] = ret; - } + if (ret === undefined) { + cache[keyName] = UNDEFINED; + } else { + cache[keyName] = ret; } + } - if (watched) { propertyDidChange(obj, keyName); } - } finally { - this._suspended = oldSuspended; + if (watched) { + propertyDidChange(obj, keyName); } + return ret; };