2
0
Evan You 11 жил өмнө
parent
commit
95ec463714

+ 8 - 4
src/api/data.js

@@ -15,7 +15,7 @@ var filterRE = /[^|]\|[^|]/
 exports.$get = function (exp) {
   var res = expParser.parse(exp)
   if (res) {
-    return res.get.call(this, this.$scope)
+    return res.get.call(this, this)
   }
 }
 
@@ -31,7 +31,7 @@ exports.$get = function (exp) {
 exports.$set = function (exp, val) {
   var res = expParser.parse(exp, true)
   if (res && res.set) {
-    res.set.call(this, this.$scope, val)
+    res.set.call(this, this, val)
   }
 }
 
@@ -44,7 +44,9 @@ exports.$set = function (exp, val) {
  */
 
 exports.$add = function (key, val) {
-  this.$scope.$add(key, val)
+  if (!_.isReserved(key)) {
+    this._data.$add(key, val)
+  }
 }
 
 /**
@@ -55,7 +57,9 @@ exports.$add = function (key, val) {
  */
 
 exports.$delete = function (key) {
-  this.$scope.$delete(key)
+  if (!_.isReserved(key)) {
+    this._data.$delete(key)
+  }
 }
 
 /**

+ 12 - 2
src/api/global.js

@@ -10,6 +10,15 @@ exports.nextTick   = _.nextTick
 exports.config     = require('../config')
 exports.transition = require('../transition')
 
+/**
+ * Each instance constructor, including Vue, has a unique
+ * cid. This enables us to create wrapped "child
+ * constructors" for prototypal inheritance and cache them.
+ */
+
+exports.cid = 0
+var cid = 1
+
 /**
  * Class inehritance
  *
@@ -19,10 +28,11 @@ exports.transition = require('../transition')
 exports.extend = function (extendOptions) {
   var Super = this
   var Sub = function (instanceOptions) {
-    Super.call(this, instanceOptions)
+    _.Vue.call(this, instanceOptions)
   }
   Sub.prototype = Object.create(Super.prototype)
-  _.define(Sub.prototype, 'constructor', Sub)
+  Sub.prototype.constructor = Sub
+  Sub.cid = cid++
   Sub.options = mergeOptions(Super.options, extendOptions)
   Sub.super = Super
   // allow further extension

+ 6 - 4
src/api/lifecycle.js

@@ -24,7 +24,7 @@ exports.$mount = function (el) {
     this._isAttached = true
     this._isReady = true
     this._callHook('ready')
-    this._initDOMHooks()
+    // this._initDOMHooks()
   })
   if (_.inDoc(this.$el)) {
     this._callHook('attached')
@@ -61,9 +61,11 @@ exports.$destroy = function (remove) {
     parent._children.splice(i)
   }
   // destroy all children.
-  i = this._children.length
-  while (i--) {
-    this._children[i].$destroy()
+  if (this._children) {
+    i = this._children.length
+    while (i--) {
+      this._children[i].$destroy()
+    }
   }
   // teardown data/scope
   this._teardownScope()

+ 1 - 1
src/directive.js

@@ -119,7 +119,7 @@ p._checkExpFn = function () {
     var fn = expParser.parse(expression).get
     var vm = this.vm
     this.update(function () {
-      fn.call(vm, vm.$scope)
+      fn.call(vm)
     })
     return true
   }

+ 2 - 2
src/directives/component.js

@@ -132,11 +132,11 @@ module.exports = {
       }
     }
     if (this.Ctor && !this.childVM) {
-      this.childVM = new this.Ctor({
+      this.childVM = this.vm._addChild({
         el: this.el.cloneNode(true),
         parent: this.vm,
         _noSync: true
-      })
+      }, this.Ctor)
       if (this.keepAlive) {
         this.cache[this.id] = this.childVM
       }

+ 1 - 1
src/directives/if.js

@@ -38,7 +38,7 @@ module.exports = {
   },
 
   build: function () {
-    this.childVM = new _.Vue({
+    this.childVM = this.vm._addChild({
       el: this.el,
       parent: this.vm,
       anonymous: true,

+ 5 - 5
src/directives/repeat.js

@@ -234,22 +234,22 @@ module.exports = {
     }
     // resolve constructor
     var Ctor = this.Ctor || this.resolveCtor(data)
-    var vm = new Ctor({
+    var vm = this.vm._addChild({
       el: this.el.cloneNode(true),
       data: data,
       parent: this.vm,
       _noSync: hasAlias
-    })
+    }, Ctor)
     // define alias
     if (hasAlias && !alias) {
-      vm.$add('$value', raw)
+      vm._defineMeta('$value', raw)
     }
     // define key
     if (this.converted) {
-      vm.$add('$key', original.key)
+      vm._defineMeta('$key', original.key)
     }
     // define index
-    vm.$add('$index', index)
+    vm._defineMeta('$index', index)
     // cache instance
     if (isObject) {
       this.cacheVm(raw, vm)

+ 1 - 0
src/filters/array-filters.js

@@ -25,6 +25,7 @@ exports._objToArray = function (obj) {
   var keys = Object.keys(obj)
   var i = keys.length
   var res = new Array(i)
+  var key
   while (i--) {
     key = keys[i]
     res[i] = {

+ 2 - 23
src/instance/init.js

@@ -18,6 +18,7 @@ exports._init = function (options) {
 
   this.$el          = null
   this.$            = {}
+  this.$root        = this.$root || this
   this._data        = options.data || {}
   this._rawContent  = null
   this._emitter     = new Emitter(this)
@@ -40,18 +41,6 @@ exports._init = function (options) {
   // directives such as v-if and v-repeat
   this._isAnonymous = options.anonymous
 
-  // setup parent relationship
-  this.$parent = options.parent
-  this._children = []
-  if (this.$parent) {
-    this.$parent._children.push(this)
-    var root = this.$parent
-    while (root.$parent) {
-      root = root.$parent
-    }
-    this.$root = root
-  }
-
   // merge options.
   this.$options = mergeOptions(
     this.constructor.options,
@@ -63,19 +52,9 @@ exports._init = function (options) {
   // have been set up & before data observation happens.
   this._callHook('created')
 
-  // create scope.
-  // @creates this.$scope
+  // initialize data observation and scope inheritance
   this._initScope()
 
-  // setup property proxying
-  this._initProxy()
-
-  // setup computed properties
-  this._initComputed()
-
-  // setup instance methods
-  this._initMethods()
-
   // setup binding tree.
   // @creates this._rootBinding
   this._initBindings()

+ 232 - 197
src/instance/scope.js

@@ -1,54 +1,120 @@
 var _ = require('../util')
+var Emitter = require('../emitter')
 var Observer = require('../observe/observer')
 var scopeEvents = ['set', 'mutate', 'add', 'delete']
+var allEvents = ['get', 'set', 'mutate', 'add', 'delete', 'add:self', 'delete:self']
 
 /**
- * Setup instance scope.
- * The scope is reponsible for prototypal inheritance of
- * parent instance propertiesm abd all binding paths and
- * expressions of the current instance are evaluated against
- * its scope.
+ * Setup the data scope of an instance.
  *
- * This should only be called once during _init().
+ * We need to setup the instance $observer, which emits
+ * data change events. The $observer relays events from
+ * the $data's observer, because $data might be swapped
+ * and the data observer might change.
+ *
+ * If the instance has a parent and is not isolated, we
+ * also need to listen to parent scope events and propagate
+ * changes down here.
  */
 
 exports._initScope = function () {
-  var parent = this.$parent
-  var inherit = parent && !this.$options.isolated
+  this._children = null
+  this._childCtors = null
+  this._initObserver()
+  this._initData()
+  this._initComputed()
+  this._initMethods()
+  // listen to parent scope events
+  if (this.$parent && !this.$options.isolated) {
+    this._linkScope()
+  }
+}
+
+/**
+ * Teardown the scope.
+ */
+
+exports._teardownScope = function () {
+  // turn of instance observer
+  this.$observer.off()
+  // stop relaying data events
+  var dataOb = this._data.__ob__
+  var proxies = this._dataProxies
+  var i = allEvents.length
+  var event
+  while (i--) {
+    event = allEvents[i]
+    dataOb.off(event, proxies[event])
+  }
+  // unset data reference
+  this._data = null
+  // stop propagating parent scope changes
+  if (this._scopeListeners) {
+    this._unlinkScope()
+  }
+}
+
+/**
+ * Setup the observer and data proxy handlers.
+ */
+
+exports._initObserver = function () {
+  // create observer
+  var ob = this.$observer = new Emitter(this)
+  // setup data proxy handlers
+  var proxies = this._dataProxies = {}
+  allEvents.forEach(function (event) {
+    proxies[event] = function (a, b, c) {
+      ob.emit(event, a, b, c)
+    }
+  })
+  var self = this
+  proxies['add:self'] = function (key) {
+    self._proxy(key)
+  }
+  proxies['delete:self'] = function (key) {
+    self._unproxy(key)
+  }
+}
+
+/**
+ * Initialize the data. 
+ */
+
+exports._initData = function () {
+  // proxy data on instance
   var data = this._data
-  var scope = this.$scope = inherit
-    ? Object.create(parent.$scope)
-    : {}
-  // copy initial data into scope
   var keys = Object.keys(data)
   var i = keys.length
   while (i--) {
-    // use defineProperty so we can shadow parent accessors
-    key = keys[i]
-    _.define(scope, key, data[key], true)
+    this._proxy(keys[i])
   }
-  // create scope observer
-  this.$observer = Observer.create(scope, {
-    callbackContext: this,
-    doNotAlterProto: true
-  })
-  // setup sync between data and the scope
-  this._syncData()
-
-  if (!inherit) {
-    return
+  // relay data changes
+  var ob = Observer.create(data)
+  var proxies = this._dataProxies
+  var event
+  i = allEvents.length
+  while (i--) {
+    event = allEvents[i]
+    ob.on(event, proxies[event])
   }
-  // relay change events that sent down from
-  // the scope prototype chain.
+}
+
+/**
+ * Listen to parent scope's events
+ */
+
+exports._linkScope = function () {
+  var self = this
   var ob = this.$observer
-  var pob = parent.$observer
+  var pob = this.$parent.$observer
   var listeners = this._scopeListeners = {}
   scopeEvents.forEach(function (event) {
     var cb = listeners[event] = function (key, a, b) {
       // since these events come from upstream,
       // we only emit them if we don't have the same keys
       // shadowing them in current scope.
-      if (!scope.hasOwnProperty(key)) {
+      if (!self.hasOwnProperty(key)) {
         ob.emit(event, key, a, b, true)
       }
     }
@@ -57,125 +123,114 @@ exports._initScope = function () {
 }
 
 /**
- * Teardown scope, unsync data, and remove all listeners
- * including ones attached to parent's observer.
- * Only called once during $destroy().
+ * Stop listening to parent scope events
  */
 
-exports._teardownScope = function () {
-  this.$observer.off()
-  this._unsyncData()
-  this._data = null
-  this.$scope = null
-  if (this.$parent) {
-    var pob = this.$parent.$observer
-    var listeners = this._scopeListeners
-    scopeEvents.forEach(function (event) {
-      pob.off(event, listeners[event])
-    })
-    this._scopeListeners = null
+exports._unlinkScope = function () {
+  var pob = this.$parent.$observer
+  var listeners = this._scopeListeners
+  var i = scopeEvents.length
+  var event
+  while (i--) {
+    event = scopeEvents[i]
+    pob.off(event, listeners[event])
   }
+  this._scopeListeners = null
 }
 
 /**
- * Called when swapping the $data object.
- *
- * Old properties that are not present in new data are
- * deleted from the scope, and new data properties not
- * already on the scope are added. Teardown old data sync
- * listeners and setup new ones.
+ * Swap the isntance's $data. Called in $data's setter.
  *
- * @param {Object} data
+ * @param {Object} newData
  */
 
-exports._setData = function (data) {
-  this._data = data
-  var scope = this.$scope
-  var key
-  // teardown old sync listeners
-  this._unsyncData()
-  // delete keys not present in the new data
-  for (key in scope) {
-    if (
-      key.charCodeAt(0) !== 0x24 && // $
-      scope.hasOwnProperty(key) &&
-      !(key in data)
-    ) {
-      scope.$delete(key)
+exports._setData = function (newData) {
+  var ob = this.$observer
+  var oldData = this._data
+  this._data = newData
+  var keys, key, i
+  // unproxy keys not present in new data
+  keys = Object.keys(oldData)
+  i = keys.length
+  while (i--) {
+    key = keys[i]
+    if (!_.isReserved(key) && !(key in newData)) {
+      this._unproxy(key)
+      ob.emit('delete', key)
     }
   }
-  // copy properties into scope
-  for (key in data) {
-    if (scope.hasOwnProperty(key)) {
-      // existing property, trigger set
-      scope[key] = data[key]
+  // proxy keys not already proxied,
+  // and trigger change for changed values
+  keys = Object.keys(newData)
+  i = keys.length
+  while (i--) {
+    key = keys[i]
+    if (this.hasOwnProperty(key)) {
+      // existing property, emit set if different
+      if (newData[key] !== oldData[key]) {
+        ob.emit('set', key, newData[key])
+      }
     } else {
       // new property
-      scope.$add(key, data[key])
+      this._proxy(key)
+      ob.emit('add', key)
     }
   }
-  // setup sync between scope and new data
-  if (!this.$options._noSync) {
-    this._syncData()
+  // teardown/setup data proxies
+  var newOb = Observer.create(newData)
+  var oldOb = oldData.__ob__
+  var proxies = this._dataProxies
+  var event, proxy
+  i = allEvents.length
+  while (i--) {
+    event = allEvents[i]
+    proxy = proxies[event]
+    newOb.on(event, proxy)
+    oldOb.off(event, proxy)
   }
 }
 
 /**
- * Proxy the scope properties on the instance itself,
- * so that vm.a === vm.$scope.a.
- *
- * Note this only proxies *local* scope properties. We want
- * to prevent child instances accidentally modifying
- * properties with the same name up in the scope chain
- * because scope perperties are all getter/setters.
+ * Proxy a property, so that
+ * vm.prop === vm._data.prop
  *
- * To access parent properties through prototypal fall
- * through, access it on the instance's $scope.
- *
- * This should only be called once during _init().
+ * @param {String} key
  */
 
-exports._initProxy = function () {
-  var scope = this.$scope
-
-  // scope --> vm
-
-  // proxy scope data on vm
-  var keys = Object.keys(scope)
-  var i = keys.length
-  while (i--) {
-    _.proxy(this, scope, keys[i])
-  }
-  // keep proxying up-to-date with added/deleted keys.
-  this.$observer
-    .on('add:self', function (key) {
-      _.proxy(this, scope, key)
-    })
-    .on('delete:self', function (key) {
-      delete this[key]
+exports._proxy = function (key) {
+  if (!_.isReserved(key)) {
+    var self = this
+    Object.defineProperty(self, key, {
+      configurable: true,
+      enumerable: true,
+      get: function () {
+        return self._data[key]
+      },
+      set: function (val) {
+        self._data[key] = val
+      }
     })
+  }
+}
 
-  // vm --> scope
+/**
+ * Unproxy a property.
+ *
+ * @param {String} key
+ */
 
-  // $parent & $root are read-only on $scope
-  scope.$parent = this.$parent
-  scope.$root = this.$root
-  // proxy $data
-  _.proxy(scope, this, '$data')
+exports._unproxy = function (key) {
+  delete this[key]
 }
 
 /**
- * Setup computed properties.
- * All computed properties are proxied onto the scope.
- * Because they are accessors their `this` context will
- * be the instance instead of the scope.
+ * Setup computed properties. They are essentially
+ * special getter/setters
  */
 
 function noop () {}
-
 exports._initComputed = function () {
   var computed = this.$options.computed
-  var scope = this.$scope
   if (computed) {
     for (var key in computed) {
       var def = computed[key]
@@ -188,115 +243,95 @@ exports._initComputed = function () {
       def.enumerable = true
       def.configurable = true
       Object.defineProperty(this, key, def)
-      _.proxy(scope, this, key)
     }
   }
 }
 
 /**
- * Setup instance methods.
- * Methods are also copied into scope, but they must
- * be bound to the instance.
+ * Setup instance methods. Methods must be bound to the
+ * instance since they might be called by children
+ * inheriting them.
  */
 
 exports._initMethods = function () {
   var methods = this.$options.methods
-  var scope = this.$scope
   if (methods) {
     for (var key in methods) {
-      var method = methods[key]
-      this[key] = method
-      scope[key] = _.bind(method, this)
+      this[key] = _.bind(methods[key], this)
     }
   }
 }
 
 /**
- * Setup two-way sync between the instance scope and
- * the original data. Requires teardown.
+ * Create a child instance that prototypally inehrits
+ * data on parent. To achieve that we create an intermediate
+ * constructor with its prototype pointing to parent.
+ *
+ * @param {Object} opts
+ * @param {Function} [BaseCtor]
  */
 
-exports._syncData = function () {
-  var data = this._data
-  var scope = this.$scope
-  var locked = false
-  var listeners = this._syncListeners = {
-    data: {
-      set: guard(function (key, val) {
-        data[key] = val
-      }),
-      add: guard(function (key, val) {
-        data.$add(key, val)
-      }),
-      delete: guard(function (key) {
-        data.$delete(key)
-      })
-    },
-    scope: {
-      set: guard(function (key, val) {
-        scope[key] = val
-      }),
-      add: guard(function (key, val) {
-        scope.$add(key, val)
-      }),
-      delete: guard(function (key) {
-        scope.$delete(key)
-      })
+exports._addChild = function (opts, BaseCtor) {
+  BaseCtor = BaseCtor || _.Vue
+  var ChildVue
+  if (BaseCtor.options.isolated) {
+    ChildVue = BaseCtor
+  } else {
+    var parent = this
+    var ctors = parent._childCtors
+    if (!ctors) {
+      ctors = parent._childCtors = {}
     }
-  }
-  // sync scope and original data.
-  this.$observer
-    .on('set:self', listeners.data.set)
-    .on('add:self', listeners.data.add)
-    .on('delete:self', listeners.data.delete)
-
-  this._dataObserver = Observer.create(data)
-  this._dataObserver
-    .on('set:self', listeners.scope.set)
-    .on('add:self', listeners.scope.add)
-    .on('delete:self', listeners.scope.delete)
-
-  /**
-   * The guard function prevents infinite loop
-   * when syncing between two observers. Also
-   * filters out properties prefixed with $ or _.
-   *
-   * @param {Function} fn
-   * @return {Function}
-   */
-
-  function guard (fn) {
-    return function (key, val) {
-      if (locked) {
-        return
-      }
-      var c = key.charCodeAt(0)
-      if (c === 0x24 || c === 0x5F) { // $ and _
-        return
+    ChildVue = ctors[BaseCtor.cid]
+    if (!ChildVue) {
+      ChildVue = function (options) {
+        this.$parent = parent
+        this.$root = parent.$root || parent
+        this.constructor = ChildVue
+        _.Vue.call(this, options)
       }
-      locked = true
-      fn(key, val)
-      locked = false
+      ChildVue.options = BaseCtor.options
+      ChildVue.prototype = this
+      ctors[BaseCtor.cid] = ChildVue
     }
   }
+  var child = new ChildVue(opts)
+  if (!this._children) {
+    this._children = []
+  }
+  this._children.push(child)
+  return child
 }
 
 /**
- * Teardown the sync between scope and previous data object.
+ * Define a meta property, e.g $index, $key, $value
+ * which only exists on the vm instance but not in $data.
+ *
+ * @param {String} key
+ * @param {*} value
  */
 
-exports._unsyncData = function () {
-  var listeners = this._syncListeners
-  if (!listeners) {
+exports._defineMeta = function (key, value) {
+  if (this.hasOwnProperty('key')) {
+    this[key] = value
     return
   }
-  this.$observer
-    .off('set:self', listeners.data.set)
-    .off('add:self', listeners.data.add)
-    .off('delete:self', listeners.data.delete)
-  this._dataObserver
-    .off('set:self', listeners.scope.set)
-    .off('add:self', listeners.scope.add)
-    .off('delete:self', listeners.scope.delete)
-  this._syncListeners = null
+  var ob = this.$observer
+  Object.defineProperty(this, key, {
+    enumerable: true,
+    configurable: true,
+    get: function () {
+      if (Observer.emitGet) {
+        ob.emit('get', key)
+      }
+      return value
+    },
+    set: function (val) {
+      if (val !== value) {
+        value = val
+        ob.emit('set', key, val)
+      }
+    }
+  })
+  ob.emit('add', key, value)
 }

+ 1 - 1
src/observe/array-augmentations.js

@@ -27,7 +27,7 @@ var arrayAugmentations = Object.create(Array.prototype)
       args[i] = arguments[i]
     }
     var result = original.apply(this, args)
-    var ob = this.$observer
+    var ob = this.__ob__
     var inserted, removed, index
 
     switch (method) {

+ 2 - 2
src/observe/object-augmentations.js

@@ -14,7 +14,7 @@ function $add (key, val) {
   if (this.hasOwnProperty(key)) return
   // make sure it's defined on itself.
   _.define(this, key, val, true)
-  var ob = this.$observer
+  var ob = this.__ob__
   ob.observe(key, val)
   ob.convert(key, val)
   ob.emit('add:self', key, val)
@@ -32,7 +32,7 @@ function $add (key, val) {
 function $delete (key) {
   if (!this.hasOwnProperty(key)) return
   delete this[key]
-  var ob = this.$observer
+  var ob = this.__ob__
   ob.emit('delete:self', key)
   ob.propagate('delete', key)
 }

+ 20 - 31
src/observe/observer.js

@@ -26,30 +26,22 @@ var OBJECT = 1
  * @extends Emitter
  * @param {Array|Object} value
  * @param {Number} type
- * @param {Object} [options]
- *                 - doNotAlterProto
- *                 - callbackContext
  */
 
-function Observer (value, type, options) {
-  Emitter.call(this, options && options.callbackContext)
+function Observer (value, type) {
+  Emitter.call(this)
   this.id = ++uid
   this.value = value
   this.type = type
   this.parents = null
   this.parentsHash = null
   if (value) {
-    _.define(value, '$observer', this)
+    _.define(value, '__ob__', this)
     if (type === ARRAY) {
       _.augment(value, arrayAugmentations)
       this.link(value)
     } else if (type === OBJECT) {
-      if (options && options.doNotAlterProto) {
-        _.define(value, '$add', objectAugmentations.$add)
-        _.define(value, '$delete', objectAugmentations.$delete)
-      } else {
-        _.augment(value, objectAugmentations)
-      }
+      _.augment(value, objectAugmentations)
       this.walk(value)
     }
   }
@@ -86,10 +78,8 @@ Observer.emitGet = false
  */
 
 Observer.create = function (value, options) {
-  if (value &&
-      value.hasOwnProperty('$observer') &&
-      value.$observer instanceof Observer) {
-    return value.$observer
+  if (value && value.hasOwnProperty('__ob__')) {
+    return value.__ob__
   } else if (_.isArray(value)) {
     return new Observer(value, ARRAY, options)
   } else if (
@@ -167,19 +157,19 @@ p.observe = function (key, val) {
     var parents = ob.parents
     var hash = ob.parentsHash
     if (!parents) {
-      ob.parents = parents = []
-      ob.parentsHash = hash = {}
+      parents = ob.parents = []
+      hash = ob.parentsHash = {}
     }
-    if (hash[this.id]) {
+    if (!hash[this.id]) {
+      var p = {
+        ob: this,
+        key: key
+      }
+      parents.push(p)
+      hash[this.id] = p
+    } else {
       _.warn('Observing duplicate key: ' + key)
-      return
-    }
-    var p = {
-      ob: this,
-      key: key
     }
-    parents.push(p)
-    hash[this.id] = p
   }
 }
 
@@ -191,9 +181,9 @@ p.observe = function (key, val) {
  */
 
 p.unobserve = function (val) {
-  if (val && val.$observer) {
-    val.$observer.parentsHash[this.id] = null
-    var parents = val.$observer.parents
+  if (val && val.__ob__) {
+    val.__ob__.parentsHash[this.id] = null
+    var parents = val.__ob__.parents
     var i = parents.length
     while (i--) {
       if (parents[i].ob === this) {
@@ -228,7 +218,6 @@ p.convert = function (key, val) {
       ob.unobserve(val)
       val = newVal
       ob.observe(key, newVal)
-      ob.emit('set:self', key, newVal)
       ob.propagate('set', key, newVal)
     }
   })
@@ -271,7 +260,7 @@ p.updateIndices = function () {
   var i = arr.length
   var ob
   while (i--) {
-    ob = arr[i] && arr[i].$observer
+    ob = arr[i] && arr[i].__ob__
     if (ob) {
       ob.parentsHash[this.id].key = i
     }

+ 12 - 22
src/util/lang.js

@@ -1,3 +1,15 @@
+/**
+ * Check is a string starts with $ or _
+ *
+ * @param {String} str
+ * @return {Boolean}
+ */
+
+exports.isReserved = function (str) {
+  var c = str.charCodeAt(0)
+  return c === 0x24 || c === 0x5F
+}
+
 /**
  * Guard text output, make sure undefined outputs
  * empty string
@@ -90,28 +102,6 @@ exports.extend = function (to, from) {
   }
 }
 
-/**
- * Proxy a property on one object to another.
- *
- * @param {Object} to
- * @param {Object} from
- * @param {String} key
- */
-
-exports.proxy = function (to, from, key) {
-  if (to.hasOwnProperty(key)) return
-  Object.defineProperty(to, key, {
-    enumerable: true,
-    configurable: true,
-    get: function () {
-      return from[key]
-    },
-    set: function (val) {
-      from[key] = val
-    }
-  })
-}
-
 /**
  * Object type check. Only returns true
  * for plain JavaScript objects.

+ 6 - 4
src/watcher.js

@@ -107,8 +107,9 @@ p.get = function () {
   if (this.isComputed) {
     this.beforeGet()
   }
-  var value = this.getter.call(this.vm, this.vm.$scope)
-  value = _.applyFilters(value, this.readFilters, this.vm)
+  var vm = this.vm
+  var value = this.getter.call(vm, vm)
+  value = _.applyFilters(value, this.readFilters, vm)
   if (this.isComputed) {
     this.afterGet()
   }
@@ -122,8 +123,9 @@ p.get = function () {
  */
 
 p.set = function (value) {
-  value = _.applyFilters(value, this.writeFilters, this.vm)
-  this.setter.call(this.vm, this.vm.$scope, value)
+  var vm = this.vm
+  value = _.applyFilters(value, this.writeFilters, vm)
+  this.setter.call(vm, vm, value)
 }
 
 /**