Bläddra i källkod

more hacks... kinda works?

Evan You 11 år sedan
förälder
incheckning
c461102247
3 ändrade filer med 85 tillägg och 29 borttagningar
  1. 12 1
      changes.md
  2. 64 21
      explorations/inheritance.js
  3. 9 7
      src/observer/observer.js

+ 12 - 1
changes.md

@@ -29,6 +29,15 @@ vm.$mount('#app') // actually compile the DOM
     <a v-my-table="{ data:hello, rows:5, cols:10 }">fsef</a>
     ```
 
+  - v-with
+
+    ``` html
+    <div v-component="my-comp" v-with="{
+      id: id,
+      name: name
+    }">
+    ```
+
   - v-repeat
 
     ``` html
@@ -79,7 +88,9 @@ Vue.filter('format', {
 
 In the previous version, nested Vue instances do not have prototypal inheritance of their data scope. Although you can access parent data properties in templates, you need to explicitly travel up the scope chain with `this.$parent` in JavaScript code or use `this.$get()` to get a property on the scope chain. The expression parser also needs to do a lot of dirty work to determine the correct scope the variables belong to.
 
-In the new model, we provide a scope inehritance system similar to Angular, in which you can directly access properties that exist on parent scopes. The major difference is that setting a primitive value property on a child scope WILL affect that on the parent scope! This is one of the major gotchas in Angular and it's no longer an issue in Vue, although if you are somewhat familiar with how prototype inehritance works, you might be surprised how this is possible. Well, the reason is that all data properties in Vue are getter/setters, and invoking a setter will not cause the child scope shadowing parent scopes.
+In the new model, we provide a scope inehritance system similar to Angular, in which you can directly access properties that exist on parent scopes. The major difference is that setting a primitive value property on a child scope WILL affect that on the parent scope! This is one of the major gotchas in Angular. If you are somewhat familiar with how prototype inehritance works, you might be surprised how this is possible. Well, the reason is that all data properties in Vue are getter/setters, and invoking a setter will not cause the child scope shadowing parent scopes. See the example [here](http://jsfiddle.net/yyx990803/Px2n6/).
+
+This is very powerful, but probably should only be available in implicit child instances created by `v-repeat` and `v-if`. Explicit components should retain its own root scope and use some sort of two way binding like `v-with` to sync with outer scope.
 
 ## (Experimental) Validators
 

+ 64 - 21
explorations/inheritance.js

@@ -3,27 +3,66 @@ var _ = require('../src/util')
 
 function Vue (options) {
 
-  // scope prototypal inehritance
+  var data = options.data
   var scope = this._scope = options.parent
     ? Object.create(options.parent._scope)
     : {}
 
   // copy instantiation data into scope
-  for (var key in options.data) {
+  for (var key in data) {
     if (key in scope) {
       // key exists on the scope prototype chain
       // cannot use direct set here, because in the parent
       // scope everything is already getter/setter and we
       // need to overwrite them with Object.defineProperty.
-      _.define(scope, key, options.data[key], true)
+      _.define(scope, key, data[key], true)
     } else {
-      scope[key] = options.data[key]
+      scope[key] = data[key]
     }
   }
 
   // create observer
   // pass in noProto:true to avoid mutating the __proto__
   var ob = this._observer = Observer.create(this._scope, true)
+  var dob = Observer.create(data)
+  var locked = false
+
+  // sync scope and original data.
+  ob
+  .on('set', guard(function (key, val) {
+    data[key] = val
+  }))
+  .on('added', guard(function (key, val) {
+    data.$add(key, val)
+  }))
+  .on('deleted', guard(function (key) {
+    data.$delete(key)
+  }))
+
+  // also need to sync data object changes to scope...
+  // this would cause cycle updates, so we need to lock
+  // stuff when one side updates the other
+  dob
+  .on('set', guard(function (key, val) {
+    scope[key] = val
+  }))
+  .on('added', guard(function (key, val) {
+    scope.$add(key, val)
+  }))
+  .on('deleted', guard(function (key) {
+    scope.$delete(key)
+  }))
+
+  function guard (fn) {
+    return function (key, val) {
+      if (locked || key.indexOf(Observer.pathDelimiter) > -1) {
+        return
+      }
+      locked = true
+      fn(key, val)
+      locked = false
+    }
+  }
 
   // relay change events from parent scope.
   // this ensures the current Vue instance is aware of
@@ -40,13 +79,14 @@ function Vue (options) {
   }
 
   // proxy everything on self
-  for (var key in this._scope) {
-    _.proxy(this, this._scope, key)
+  for (var key in scope) {
+    _.proxy(this, scope, key)
   }
 
   // also proxy newly added keys.
   var self = this
-  ob.on('added', function (key) {
+  ob
+  .on('added', function (key) {
     if (!self.hasOwnProperty(key)) {
       _.proxy(self, scope, key)
     }
@@ -62,21 +102,20 @@ Vue.prototype.$delete = function (key) {
   this._scope.$delete.call(this._scope, key)
 }
 
-window.vm = new Vue({
-  data: {
-    a: 'go!',
-    b: 2,
-    c: {
-      d: 3
-    },
-    arr: [{a:1}, {a:2}, {a:3}],
-    get hello () {
-      return 'hello!' + this.a
-    },
-    go: function () {
-      console.log(this.a)
-    }
+window.model = {
+  a: 'go!',
+  b: 2,
+  c: {
+    d: 3
+  },
+  arr: [{a:1}, {a:2}, {a:3}],
+  go: function () {
+    console.log(this.a)
   }
+}
+
+window.vm = new Vue({
+  data: model
 })
 
 window.child = new Vue({
@@ -91,6 +130,10 @@ window.child = new Vue({
   }
 })
 
+window.v2 = new Vue({
+  data: model.arr[0]
+})
+
 vm._observer.on('set', function (key, val) {
   console.log('vm set:' + key.replace(/[\b]/g, '.'), val)
 })

+ 9 - 7
src/observer/observer.js

@@ -22,11 +22,12 @@ var OBJECT = 1
  *
  * @constructor
  * @extends Emitter
- * @param {Array|Object} [value]
- * @param {Number} [type]
+ * @param {Array|Object} value
+ * @param {Number} type
+ * @param {Object} [options]
  */
 
-function Observer (value, type, noProto) {
+function Observer (value, type, options) {
   Emitter.call(this)
   this.value = value
   this.type = type
@@ -37,7 +38,7 @@ function Observer (value, type, noProto) {
       _.augment(value, arrayAugmentations)
       this.link(value)
     } else if (type === OBJECT) {
-      if (noProto) {
+      if (options && options.noProto) {
         _.deepMixin(value, objectAugmentations)
       } else {
         _.augment(value, objectAugmentations)
@@ -72,19 +73,20 @@ Observer.emitGet = false
  * or the existing observer if the value already has one.
  *
  * @param {*} value
+ * @param {Object} [options]
  * @return {Observer|undefined}
  * @static
  */
 
-Observer.create = function (value, noProto) {
+Observer.create = function (value, options) {
   if (value &&
       value.hasOwnProperty('$observer') &&
       value.$observer instanceof Observer) {
     return value.$observer
   } if (_.isArray(value)) {
-    return new Observer(value, ARRAY)
+    return new Observer(value, ARRAY, options)
   } else if (_.isObject(value)) {
-    return new Observer(value, OBJECT, noProto)
+    return new Observer(value, OBJECT, options)
   }
 }