소스 검색

unobserve when setting new values

Evan You 12 년 전
부모
커밋
caed31fd02
4개의 변경된 파일94개의 추가작업 그리고 54개의 파일을 삭제
  1. 1 1
      Gruntfile.js
  2. 1 1
      component.json
  3. 50 25
      src/compiler.js
  4. 42 27
      src/observer.js

+ 1 - 1
Gruntfile.js

@@ -57,7 +57,7 @@ module.exports = function( grunt ) {
             },
             component: {
                 files: ['src/**/*.js', 'component.json'],
-                tasks: ['component_build:dev', 'concat:dev']
+                tasks: ['component_build:dev']
             }
         }
 

+ 1 - 1
component.json

@@ -9,7 +9,7 @@
         "src/compiler.js",
         "src/viewmodel.js",
         "src/binding.js",
-        "src/observe.js",
+        "src/observer.js",
         "src/directive-parser.js",
         "src/text-parser.js",
         "src/deps-parser.js",

+ 50 - 25
src/compiler.js

@@ -1,5 +1,5 @@
 var Emitter         = require('emitter'),
-    observe         = require('./observe'),
+    Observer        = require('./observer'),
     config          = require('./config'),
     utils           = require('./utils'),
     Binding         = require('./binding'),
@@ -61,6 +61,7 @@ function Compiler (vm, options) {
         options.init.apply(vm, options.args || [])
     }
 
+    // check for async compilation (vm.$wait())
     if (vm.__wait__) {
         var self = this
         this.observer.on('ready', function () {
@@ -74,13 +75,18 @@ function Compiler (vm, options) {
     
 }
 
-// for better compression
 var CompilerProto = Compiler.prototype
 
+/*
+ *  Actually parse the DOM nodes for directives, create bindings,
+ *  and parse dependencies afterwards. For the dependency extraction to work,
+ *  this has to happen after all user-set values are present in the VM.
+ */
 CompilerProto.compile = function () {
-
-    var key, vm = this.vm
-
+    var key,
+        vm = this.vm,
+        computed = this.computed,
+        contextBindings = this.contextBindings
     // parse the DOM
     this.compileNode(this.el, true)
 
@@ -95,22 +101,33 @@ CompilerProto.compile = function () {
     }
 
     // extract dependencies for computed properties
-    if (this.computed.length) DepsParser.parse(this.computed)
+    if (computed.length) DepsParser.parse(computed)
     this.computed = null
     
     // extract dependencies for computed properties with dynamic context
-    if (this.contextBindings.length) this.bindContexts(this.contextBindings)
+    if (contextBindings.length) this.bindContexts(contextBindings)
     this.contextBindings = null
     
     utils.log('\ncompilation done.\n')
 }
 
 /*
- *  setup observer
+ *  Setup observer.
+ *  The observer listens for get/set/mutate events on all VM
+ *  values/objects and trigger corresponding binding updates.
  */
 CompilerProto.setupObserver = function () {
-    var bindings = this.bindings, compiler = this
-    this.observer
+
+    var bindings = this.bindings,
+        observer = this.observer,
+        compiler = this
+
+    // a hash to hold event proxies for each root level key
+    // so they can be referenced and removed later
+    observer.proxies = {}
+
+    // add own listeners which trigger binding updates
+    observer
         .on('get', function (key) {
             if (DepsParser.observer.isObserving) {
                 DepsParser.observer.emit('get', bindings[key])
@@ -230,8 +247,9 @@ CompilerProto.compileTextNode = function (node) {
 CompilerProto.createBinding = function (key) {
     utils.log('  created binding: ' + key)
 
-    var binding = new Binding(this, key)
-    this.bindings[key] = binding
+    var bindings = this.bindings,
+        binding = new Binding(this, key)
+    bindings[key] = binding
 
     var baseKey = key.split('.')[0]
     if (binding.root) {
@@ -239,7 +257,7 @@ CompilerProto.createBinding = function (key) {
         this.define(baseKey, binding)
     } else {
         // TODO create placeholder objects
-        if (!this.bindings[baseKey]) {
+        if (!bindings[baseKey]) {
             // this is a nested value binding, but the binding for its root
             // has not been created yet. We better create that one too.
             this.createBinding(baseKey)
@@ -258,18 +276,19 @@ CompilerProto.define = function (key, binding) {
     utils.log('    defined root binding: ' + key)
 
     var compiler = this,
-        value = binding.value = this.vm[key] // save the value before redefinening it
+        vm = this.vm,
+        value = binding.value = vm[key] // save the value before redefinening it
 
     if (utils.typeOf(value) === 'Object' && value.get) {
         binding.isComputed = true
         binding.rawGet = value.get
-        value.get = value.get.bind(this.vm)
+        value.get = value.get.bind(vm)
         this.computed.push(binding)
     } else {
-        observe(value, key, compiler.observer) // start observing right now
+        Observer.observe(value, key, compiler.observer) // start observing right now
     }
 
-    Object.defineProperty(this.vm, key, {
+    Object.defineProperty(vm, key, {
         enumerable: true,
         get: function () {
             if (!binding.isComputed && !binding.value.__observer__) {
@@ -290,12 +309,15 @@ CompilerProto.define = function (key, binding) {
                     binding.value.set(value)
                 }
             } else if (value !== binding.value) {
+                // unwatch the old value!
+                Observer.unobserve(binding.value, key, compiler.observer)
+                // now watch the new one instead
+                Observer.observe(value, key, compiler.observer)
+                binding.value = value
                 compiler.observer.emit('set', key, value)
-                observe(value, key, compiler.observer)
             }
         }
     })
-
 }
 
 /*
@@ -376,11 +398,14 @@ CompilerProto.bindContexts = function (bindings) {
  */
 CompilerProto.destroy = function () {
     utils.log('compiler destroyed: ', this.vm.$el)
-    var i, key, dir, inss
+    var i, key, dir, inss,
+        directives = this.directives,
+        bindings = this.bindings,
+        el = this.el
     // remove all directives that are instances of external bindings
-    i = this.directives.length
+    i = directives.length
     while (i--) {
-        dir = this.directives[i]
+        dir = directives[i]
         if (dir.binding.compiler !== this) {
             inss = dir.binding.instances
             if (inss) inss.splice(inss.indexOf(dir), 1)
@@ -388,11 +413,11 @@ CompilerProto.destroy = function () {
         dir.unbind()
     }
     // unbind all bindings
-    for (key in this.bindings) {
-        this.bindings[key].unbind()
+    for (key in bindings) {
+        bindings[key].unbind()
     }
     // remove el
-    this.el.parentNode.removeChild(this.el)
+    el.parentNode.removeChild(el)
 }
 
 // Helpers --------------------------------------------------------------------

+ 42 - 27
src/observe.js → src/observer.js

@@ -27,32 +27,6 @@ methods.forEach(function (method) {
     }
 })
 
-// EXTERNAL
-function observe (obj, path, observer) {
-    if (isWatchable(obj)) {
-        path = path + '.'
-        var ob, alreadyConverted = !!obj.__observer__
-        if (!alreadyConverted) {
-            ob = new Emitter()
-            defProtected(obj, '__observer__', ob)
-        }
-        obj.__observer__
-            .on('get', function (key) {
-                observer.emit('get', path + key)
-            })
-            .on('set', function (key, val) {
-                observer.emit('set', path + key, val)
-            })
-            .on('mutate', function (key, val, mutation) {
-                observer.emit('mutate', path + key, val, mutation)
-            })
-        if (!alreadyConverted) {
-            watch(obj, null, ob)
-        }
-    }
-}
-
-// INTERNAL
 function watch (obj, path, observer) {
     var type = typeOf(obj)
     if (type === 'Object') {
@@ -114,4 +88,45 @@ function isWatchable (obj) {
     return type === 'Object' || type === 'Array'
 }
 
-module.exports = observe
+module.exports = {
+
+    observe: function (obj, path, observer) {
+        if (isWatchable(obj)) {
+            path = path + '.'
+            var ob, alreadyConverted = !!obj.__observer__
+            if (!alreadyConverted) {
+                ob = new Emitter()
+                defProtected(obj, '__observer__', ob)
+            }
+            var proxies = observer.proxies[path] = {
+                get: function (key) {
+                    observer.emit('get', path + key)
+                },
+                set: function (key, val) {
+                    observer.emit('set', path + key, val)
+                },
+                mutate: function (key, val, mutation) {
+                    observer.emit('mutate', path + key, val, mutation)
+                }
+            }
+            obj.__observer__
+                .on('get', proxies.get)
+                .on('set', proxies.set)
+                .on('mutate', proxies.mutate)
+            if (!alreadyConverted) {
+                watch(obj, null, ob)
+            }
+        }
+    },
+
+    unobserve: function (obj, path, observer) {
+        if (!obj.__observer__) return
+        path = path + '.'
+        var proxies = observer.proxies[path]
+        obj.__observer__
+            .off('get', proxies.get)
+            .off('set', proxies.set)
+            .off('mutate', proxies.mutate)
+        observer.proxies[path] = null
+    }
+}