Browse Source

Added config option for working with computed properties; also changed the implementation to no longer do function calls for normal attributes

Justin Berger 10 years ago
parent
commit
7953f5eaa9
4 changed files with 48 additions and 29 deletions
  1. 8 0
      src/config.js
  2. 22 10
      src/observer/index.js
  3. 9 0
      test/unit/lib/util.js
  4. 9 19
      test/unit/specs/observer/observer_spec.js

+ 8 - 0
src/config.js

@@ -30,6 +30,14 @@ module.exports = {
 
   warnExpressionErrors: true,
 
+  /**
+   * Whether or not to handle fully object properties which
+   * are already backed by getters and seters. Depending on
+   * use case and environment, this might introduce non-neglible
+   * performance penalties.
+   */
+  convertAllProperties: true,
+
   /**
    * Internal flag to indicate the delimiters have been
    * changed.

+ 22 - 10
src/observer/index.js

@@ -1,4 +1,5 @@
 var _ = require('../util')
+var config = require('../config')
 var Dep = require('./dep')
 var arrayMethods = require('./array')
 var arrayKeys = Object.getOwnPropertyNames(arrayMethods)
@@ -173,10 +174,21 @@ function copyAugment (target, src, keys) {
 function defineReactive (obj, key, val) {
   var dep = new Dep()
 
-  var property = Object.getOwnPropertyDescriptor(obj, key)
-  var getter = (property && property.get) || function () { return val }
-  var setter = (property && property.set) || function (v) { val = v }
-  var childOb = Observer.create(getter())
+  var target = {
+    val: val
+  }
+
+  if (config.convertAllProperties) {
+    var property = Object.getOwnPropertyDescriptor(obj, key)
+    if (property && property.get && property.set) {
+      Object.defineProperty(target, 'val', {
+        get: property.get.bind(obj),
+        set: property.set.bind(obj)
+      })
+    }
+  }
+
+  var childOb = Observer.create(target.val)
   Object.defineProperty(obj, key, {
     enumerable: true,
     configurable: true,
@@ -186,18 +198,18 @@ function defineReactive (obj, key, val) {
         if (childOb) {
           childOb.dep.depend()
         }
-        if (_.isArray(getter())) {
-          for (var e, i = 0, l = getter().length; i < l; i++) {
-            e = getter()[i]
+        if (_.isArray(target.val)) {
+          for (var e, i = 0, l = target.val.length; i < l; i++) {
+            e = target.val[i]
             e && e.__ob__ && e.__ob__.dep.depend()
           }
         }
       }
-      return getter()
+      return target.val
     },
     set: function metaSetter (newVal) {
-      if (newVal === getter()) return
-      setter(newVal)
+      if (newVal === target.val) return
+      target.val = newVal
       childOb = Observer.create(newVal)
       dep.notify()
     }

+ 9 - 0
test/unit/lib/util.js

@@ -2,6 +2,15 @@ var scope = typeof window === 'undefined'
   ? global
   : window
 
+// Some versions of phantomjs doesn't have bind defined. 
+// See https://github.com/ariya/phantomjs/issues/10522
+Function.prototype.bind = Function.prototype.bind || function (thisp) {
+  var fn = this;
+  return function () {
+    return fn.apply(thisp, arguments);
+  };
+};
+
 scope.hasWarned = function (_, msg, silent) {
   var count = _.warn.calls.count()
   while (count--) {

+ 9 - 19
test/unit/specs/observer/observer_spec.js

@@ -112,16 +112,16 @@ describe('Observer', function () {
   })
 
   it('observing object prop change on defined property', function () {
-    var obj = { }
-    var val = { b: 2 }
+    var obj = { val: 2 }
     Object.defineProperty(obj, 'a', {
       configurable: true,
       enumerable: true,
       get: function () {
-        return val
+        return this.val
       },
       set: function (v) {
-        val = v
+        this.val = v
+        return this.val
       }
     })
 
@@ -137,22 +137,12 @@ describe('Observer', function () {
     }
     // collect dep
     Dep.target = watcher
-    obj.a.b
+    expect(obj.a).toBe(2) // Make sure 'this' is preserved
     Dep.target = null
-    expect(watcher.deps.length).toBe(3) // obj.a + a.b + b
-    obj.a.b = 3
-    expect(watcher.update.calls.count()).toBe(1)
-    // swap object
-    obj.a = { b: 4 }
-    expect(watcher.update.calls.count()).toBe(2)
-    watcher.deps = []
-    Dep.target = watcher
-    obj.a.b
-    Dep.target = null
-    expect(watcher.deps.length).toBe(3)
-    // set on the swapped object
-    obj.a.b = 5
-    expect(watcher.update.calls.count()).toBe(3)
+    obj.a = 3
+    expect(obj.val).toBe(3) // make sure 'setter' was called
+    obj.val = 5
+    expect(obj.a).toBe(5) // make sure 'getter' was called
   })
 
   it('observing set/delete', function () {