Przeglądaj źródła

properly initialize state

Evan You 10 lat temu
rodzic
commit
f1bdaa8bf4
2 zmienionych plików z 217 dodań i 29 usunięć
  1. 9 29
      src/instance/index.js
  2. 208 0
      src/instance/internal/state.js

+ 9 - 29
src/instance/index.js

@@ -1,26 +1,19 @@
-import { observe } from '../observer/index'
 import Watcher from '../observer/watcher'
 import Watcher from '../observer/watcher'
 import { h, patch } from '../vdom/index'
 import { h, patch } from '../vdom/index'
-import { nextTick, isReserved, query } from '../util/index'
+import { nextTick, query } from '../util/index'
+import stateMixin from './internal/state'
 
 
-export default function Component (options) {
+export default function Vue (options) {
   this.$options = options
   this.$options = options
-  this._data = options.data
+  this._watchers = []
+  this._initState()
   this._el = query(options.el)
   this._el = query(options.el)
   this._el.innerHTML = ''
   this._el.innerHTML = ''
-  Object.keys(options.data).forEach(key => proxy(this, key))
-  if (options.methods) {
-    Object.keys(options.methods).forEach(key => {
-      this[key] = options.methods[key].bind(this)
-    })
-  }
-  this._ob = observe(options.data)
-  this._watchers = []
   this._watcher = new Watcher(this, options.render, this._update)
   this._watcher = new Watcher(this, options.render, this._update)
   this._update(this._watcher.value)
   this._update(this._watcher.value)
 }
 }
 
 
-Component.prototype._update = function (vtree) {
+Vue.prototype._update = function (vtree) {
   if (!this._tree) {
   if (!this._tree) {
     patch(this._el, vtree)
     patch(this._el, vtree)
   } else {
   } else {
@@ -29,20 +22,7 @@ Component.prototype._update = function (vtree) {
   this._tree = vtree
   this._tree = vtree
 }
 }
 
 
-function proxy (vm, key) {
-  if (!isReserved(key)) {
-    Object.defineProperty(vm, key, {
-      configurable: true,
-      enumerable: true,
-      get: function proxyGetter () {
-        return vm._data[key]
-      },
-      set: function proxySetter (val) {
-        vm._data[key] = val
-      }
-    })
-  }
-}
+Vue.prototype.__h__ = h
+Vue.nextTick = nextTick
 
 
-Component.prototype.__h__ = h
-Component.nextTick = nextTick
+stateMixin(Vue)

+ 208 - 0
src/instance/internal/state.js

@@ -0,0 +1,208 @@
+import Watcher from '../../observer/watcher'
+import Dep from '../../observer/dep'
+import { observe } from '../../observer/index'
+
+import {
+  warn,
+  hasOwn,
+  isReserved,
+  isPlainObject,
+  bind
+} from '../../util/index'
+
+export default function (Vue) {
+  /**
+   * Accessor for `$data` property, since setting $data
+   * requires observing the new object and updating
+   * proxied properties.
+   */
+
+  Object.defineProperty(Vue.prototype, '$data', {
+    get () {
+      return this._data
+    },
+    set (newData) {
+      if (newData !== this._data) {
+        this._setData(newData)
+      }
+    }
+  })
+
+  /**
+   * Setup the scope of an instance, which contains:
+   * - observed data
+   * - computed properties
+   * - user methods
+   * - meta properties
+   */
+
+  Vue.prototype._initState = function () {
+    this._initMethods()
+    this._initData()
+    this._initComputed()
+  }
+
+  /**
+   * Initialize the data.
+   */
+
+  Vue.prototype._initData = function () {
+    var data = this.$options.data
+    data = this._data = typeof data === 'function'
+      ? data()
+      : data || {}
+    if (!isPlainObject(data)) {
+      data = {}
+      process.env.NODE_ENV !== 'production' && warn(
+        'data functions should return an object.',
+        this
+      )
+    }
+    // proxy data on instance
+    var keys = Object.keys(data)
+    var i = keys.length
+    while (i--) {
+      this._proxy(keys[i])
+    }
+    // observe data
+    observe(data, this)
+  }
+
+  /**
+   * Swap the instance's $data. Called in $data's setter.
+   *
+   * @param {Object} newData
+   */
+
+  Vue.prototype._setData = function (newData) {
+    newData = newData || {}
+    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 (!(key in newData)) {
+        this._unproxy(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 (!hasOwn(this, key)) {
+        // new property
+        this._proxy(key)
+      }
+    }
+    oldData.__ob__.removeVm(this)
+    observe(newData, this)
+    this._digest()
+  }
+
+  /**
+   * Proxy a property, so that
+   * vm.prop === vm._data.prop
+   *
+   * @param {String} key
+   */
+
+  Vue.prototype._proxy = function (key) {
+    if (!isReserved(key)) {
+      // need to store ref to self here
+      // because these getter/setters might
+      // be called by child scopes via
+      // prototype inheritance.
+      var self = this
+      Object.defineProperty(self, key, {
+        configurable: true,
+        enumerable: true,
+        get: function proxyGetter () {
+          return self._data[key]
+        },
+        set: function proxySetter (val) {
+          self._data[key] = val
+        }
+      })
+    }
+  }
+
+  /**
+   * Unproxy a property.
+   *
+   * @param {String} key
+   */
+
+  Vue.prototype._unproxy = function (key) {
+    if (!isReserved(key)) {
+      delete this[key]
+    }
+  }
+
+  /**
+   * Setup computed properties. They are essentially
+   * special getter/setters
+   */
+
+  function noop () {}
+  Vue.prototype._initComputed = function () {
+    var computed = this.$options.computed
+    if (computed) {
+      for (var key in computed) {
+        var userDef = computed[key]
+        var def = {
+          enumerable: true,
+          configurable: true
+        }
+        if (typeof userDef === 'function') {
+          def.get = makeComputedGetter(userDef, this)
+          def.set = noop
+        } else {
+          def.get = userDef.get
+            ? userDef.cache !== false
+              ? makeComputedGetter(userDef.get, this)
+              : bind(userDef.get, this)
+            : noop
+          def.set = userDef.set
+            ? bind(userDef.set, this)
+            : noop
+        }
+        Object.defineProperty(this, key, def)
+      }
+    }
+  }
+
+  function makeComputedGetter (getter, owner) {
+    var watcher = new Watcher(owner, getter, null, {
+      lazy: true
+    })
+    return function computedGetter () {
+      if (watcher.dirty) {
+        watcher.evaluate()
+      }
+      if (Dep.target) {
+        watcher.depend()
+      }
+      return watcher.value
+    }
+  }
+
+  /**
+   * Setup instance methods. Methods must be bound to the
+   * instance since they might be passed down as a prop to
+   * child components.
+   */
+
+  Vue.prototype._initMethods = function () {
+    var methods = this.$options.methods
+    if (methods) {
+      for (var key in methods) {
+        this[key] = bind(methods[key], this)
+      }
+    }
+  }
+}