Procházet zdrojové kódy

cache primitive instances in v-repeat too

Evan You před 11 roky
rodič
revize
2e99321f36

+ 1 - 1
src/api/global.js

@@ -104,7 +104,7 @@ function createAssetRegisters (Constructor) {
    */
    */
 
 
   Constructor.component = function (id, definition) {
   Constructor.component = function (id, definition) {
-    if (_.isObject(definition)) {
+    if (_.isPlainObject(definition)) {
       definition = _.Vue.extend(definition)
       definition = _.Vue.extend(definition)
     }
     }
     this.options.components[id] = definition
     this.options.components[id] = definition

+ 4 - 4
src/directive.js

@@ -16,8 +16,8 @@ var expParser = require('./parse/expression')
  *                 - {String} expression
  *                 - {String} expression
  *                 - {String} [arg]
  *                 - {String} [arg]
  *                 - {Array<Object>} [filters]
  *                 - {Array<Object>} [filters]
- * @param {Object} def
- * @param {Function} [linker]
+ * @param {Object} def - directive definition object
+ * @param {Function} [linker] - pre-compiled linker function
  * @constructor
  * @constructor
  */
  */
 
 
@@ -152,14 +152,14 @@ p._teardown = function () {
       this.unbind()
       this.unbind()
     }
     }
     var watcher = this._watcher
     var watcher = this._watcher
-    if (watcher) {
+    if (watcher && watcher.active) {
       watcher.removeCb(this._update)
       watcher.removeCb(this._update)
       if (!watcher.active) {
       if (!watcher.active) {
         this.vm._watchers[this.expression] = null
         this.vm._watchers[this.expression] = null
       }
       }
     }
     }
     this._bound = false
     this._bound = false
-    this.vm = this.el = null
+    this.vm = this.el = this._watcher = null
   }
   }
 }
 }
 
 

+ 55 - 18
src/directives/repeat.js

@@ -1,4 +1,5 @@
 var _ = require('../util')
 var _ = require('../util')
+var isObject = _.isObject
 var textParser = require('../parse/text')
 var textParser = require('../parse/text')
 var expParser = require('../parse/expression')
 var expParser = require('../parse/expression')
 var templateParser = require('../parse/template')
 var templateParser = require('../parse/template')
@@ -30,6 +31,8 @@ module.exports = {
     this.checkComponent()
     this.checkComponent()
     // setup ref node
     // setup ref node
     this.ref = document.createComment('v-repeat')
     this.ref = document.createComment('v-repeat')
+    // cache for primitive value instances
+    this.cache = Object.create(null)
     _.replace(this.el, this.ref)
     _.replace(this.el, this.ref)
     // check if this is a block repeat
     // check if this is a block repeat
     if (this.el.tagName === 'TEMPLATE') {
     if (this.el.tagName === 'TEMPLATE') {
@@ -143,6 +146,7 @@ module.exports = {
 
 
   diff: function (data, oldVms) {
   diff: function (data, oldVms) {
     var converted = this.converted
     var converted = this.converted
+    var init = !oldVms
     var vms = new Array(data.length)
     var vms = new Array(data.length)
     var ref = this.ref
     var ref = this.ref
     var obj, raw, vm, i, l
     var obj, raw, vm, i, l
@@ -153,7 +157,7 @@ module.exports = {
     for (i = 0, l = data.length; i < l; i++) {
     for (i = 0, l = data.length; i < l; i++) {
       obj = data[i]
       obj = data[i]
       raw = converted ? obj.value : obj
       raw = converted ? obj.value : obj
-      vm = this.getVm(raw)
+      vm = !init && this.getVm(raw)
       if (vm) { // reusable instance
       if (vm) { // reusable instance
         vm._reused = true
         vm._reused = true
         vm.$index = i // update $index
         vm.$index = i // update $index
@@ -165,12 +169,12 @@ module.exports = {
       }
       }
       vms[i] = vm
       vms[i] = vm
       // insert if this is first run
       // insert if this is first run
-      if (!oldVms) {
+      if (init) {
         vm.$before(ref)
         vm.$before(ref)
       }
       }
     }
     }
     // if this is the first run, we're done.
     // if this is the first run, we're done.
-    if (!oldVms) {
+    if (init) {
       return vms
       return vms
     }
     }
     // Second pass, go through the old vm instances and
     // Second pass, go through the old vm instances and
@@ -230,9 +234,8 @@ module.exports = {
     var raw = this.converted
     var raw = this.converted
       ? data.value
       ? data.value
       : data
       : data
-    var isObject = raw && typeof raw === 'object'
     var alias = this.arg
     var alias = this.arg
-    var hasAlias = !isObject || alias
+    var hasAlias = !isObject(raw) || alias
     // wrap the raw data with alias
     // wrap the raw data with alias
     data = hasAlias ? {} : raw
     data = hasAlias ? {} : raw
     if (alias) {
     if (alias) {
@@ -257,9 +260,7 @@ module.exports = {
     // define index
     // define index
     vm._defineMeta('$index', index)
     vm._defineMeta('$index', index)
     // cache instance
     // cache instance
-    if (isObject) {
-      this.cacheVm(raw, vm)
-    }
+    this.cacheVm(raw, vm)
     return vm
     return vm
   },
   },
 
 
@@ -306,19 +307,38 @@ module.exports = {
   },
   },
 
 
   /**
   /**
-   * Save a vm's reference on a data object as a hidden
-   * property. This mimics a Map that allows us to determine
-   * a data object's owner instance during the diff phase.
+   * Cache a vm instance based on its data.
+   *
+   * If the data is an object, we save the vm's reference on
+   * the data object as a hidden property. Otherwise we
+   * cache them in an object and for each primitive value
+   * there is an array in case there are duplicates.
    *
    *
    * @param {Object} data
    * @param {Object} data
    * @param {Vue} vm
    * @param {Vue} vm
    */
    */
 
 
   cacheVm: function (data, vm) {
   cacheVm: function (data, vm) {
-    if (data.hasOwnProperty(this.id)) {
-      data[this.id] = vm
+    if (isObject(data)) {
+      var id = this.id
+      if (data.hasOwnProperty(id)) {
+        if (data[id] === null) {
+          data[id] = vm
+        } else {
+          _.warn(
+            'Duplicate objects are not supported in v-repeat.'
+          )
+        }
+      } else {
+        _.define(data, this.id, vm)
+      }
     } else {
     } else {
-      _.define(data, this.id, vm)
+      var cache = this.cache
+      if (!cache[data]) {
+        cache[data] = [vm]
+      } else {
+        cache[data].push(vm)
+      }
     }
     }
     vm._raw = data
     vm._raw = data
   },
   },
@@ -331,20 +351,37 @@ module.exports = {
    */
    */
 
 
   getVm: function (data) {
   getVm: function (data) {
-    return data[this.id]
+    if (isObject(data)) {
+      return data[this.id]
+    } else {
+      var cached = this.cache[data]
+      if (cached) {
+        var i = 0
+        var vm = cached[i]
+        // since duplicated vm instances might be reused
+        // already, we need to return the first non-reused
+        // instance.
+        while (vm && vm._reused) {
+          vm = cached[++i]
+        }
+        return vm
+      }
+    }
   },
   },
 
 
   /**
   /**
    * Delete a cached vm instance.
    * Delete a cached vm instance.
-   * This assumes the data already has a vm cached on it.
    *
    *
    * @param {Vue} vm
    * @param {Vue} vm
    */
    */
 
 
   uncacheVm: function (vm) {
   uncacheVm: function (vm) {
-    if (vm._raw) {
-      vm._raw[this.id] = null
+    var data = vm._raw
+    if (isObject(data)) {
+      data[this.id] = null
       vm._raw = null
       vm._raw = null
+    } else {
+      this.cache[data].pop()
     }
     }
   }
   }
 
 

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

@@ -15,7 +15,7 @@ exports._objToArray = function (obj) {
   if (_.isArray(obj)) {
   if (_.isArray(obj)) {
     return obj
     return obj
   }
   }
-  if (!_.isObject(obj)) {
+  if (!_.isPlainObject(obj)) {
     _.warn(
     _.warn(
       'Invalid value for v-repeat: ' + obj +
       'Invalid value for v-repeat: ' + obj +
       '\nOnly Arrays and Objects are allowed.'
       '\nOnly Arrays and Objects are allowed.'

+ 3 - 5
src/instance/lifecycle.js

@@ -107,14 +107,12 @@ exports.$destroy = function (remove) {
   }
   }
   // teardown data/scope
   // teardown data/scope
   this._teardownScope()
   this._teardownScope()
-  // teardown all watchers
-  for (i in this._watchers) {
-    this._watchers[i].teardown()
-  }
+  // teardown all user watchers.
   for (i in this._userWatchers) {
   for (i in this._userWatchers) {
     this._userWatchers[i].teardown()
     this._userWatchers[i].teardown()
   }
   }
-  // teardown all directives
+  // teardown all directives. this also tearsdown all
+  // directive-owned watchers.
   i = this._directives.length
   i = this._directives.length
   while (i--) {
   while (i--) {
     this._directives[i]._teardown()
     this._directives[i]._teardown()

+ 1 - 1
src/observe/observer.js

@@ -83,7 +83,7 @@ Observer.create = function (value, options) {
   } else if (_.isArray(value)) {
   } else if (_.isArray(value)) {
     return new Observer(value, ARRAY, options)
     return new Observer(value, ARRAY, options)
   } else if (
   } else if (
-    _.isObject(value) &&
+    _.isPlainObject(value) &&
     !value.$observer // avoid Vue instance
     !value.$observer // avoid Vue instance
   ) {
   ) {
     return new Observer(value, OBJECT, options)
     return new Observer(value, OBJECT, options)

+ 3 - 14
src/parse/path.js

@@ -1,3 +1,4 @@
+var _ = require('../util')
 var Cache = require('../cache')
 var Cache = require('../cache')
 var pathCache = new Cache(1000)
 var pathCache = new Cache(1000)
 var identRE = /^[$_a-zA-Z]+[\w$]*$/
 var identRE = /^[$_a-zA-Z]+[\w$]*$/
@@ -276,7 +277,7 @@ exports.set = function (obj, path, val) {
   if (typeof path === 'string') {
   if (typeof path === 'string') {
     path = exports.parse(path)
     path = exports.parse(path)
   }
   }
-  if (!path || !isSettable(obj)) {
+  if (!path || !_.isObject(obj)) {
     return false
     return false
   }
   }
   var last, key
   var last, key
@@ -284,7 +285,7 @@ exports.set = function (obj, path, val) {
     last = obj
     last = obj
     key = path[i]
     key = path[i]
     obj = obj[key]
     obj = obj[key]
-    if (!isSettable(obj)) {
+    if (!_.isObject(obj)) {
       obj = {}
       obj = {}
       add(last, key, obj)
       add(last, key, obj)
     }
     }
@@ -298,18 +299,6 @@ exports.set = function (obj, path, val) {
   return true
   return true
 }
 }
 
 
-/**
- * Check if a value is an object that can have values
- * set on it. Slightly faster than _.isObject.
- *
- * @param {*} val
- * @return {Boolean}
- */
-
-function isSettable (val) {
-  return val && typeof val === 'object'
-}
-
 /**
 /**
  * Add a property to an object, using $add if target
  * Add a property to an object, using $add if target
  * has been augmented by Vue's observer.
  * has been augmented by Vue's observer.

+ 6 - 6
src/util/debug.js

@@ -14,24 +14,24 @@ function enableDebug () {
   /**
   /**
    * Log a message.
    * Log a message.
    *
    *
-   * @param {*...}
+   * @param {String} msg
    */
    */
 
 
-  exports.log = function () {
+  exports.log = function (msg) {
     if (hasConsole && config.debug) {
     if (hasConsole && config.debug) {
-      console.log.apply(console, arguments)
+      console.log('[Vue info]: ' + msg)
     }
     }
   }
   }
 
 
   /**
   /**
    * We've got a problem here.
    * We've got a problem here.
    *
    *
-   * @param {*...}
+   * @param {String} msg
    */
    */
 
 
-  exports.warn = function () {
+  exports.warn = function (msg) {
     if (hasConsole && !config.silent) {
     if (hasConsole && !config.silent) {
-      console.warn.apply(console, arguments)
+      console.warn('[Vue warn]: ' + msg)
       if (config.debug && console.trace) {
       if (config.debug && console.trace) {
         console.trace()
         console.trace()
       }
       }

+ 15 - 2
src/util/lang.js

@@ -103,7 +103,20 @@ exports.extend = function (to, from) {
 }
 }
 
 
 /**
 /**
- * Object type check. Only returns true
+ * Quick object check - this is primarily used to tell
+ * Objects from primitive values when we know the value
+ * is a JSON-compliant type.
+ *
+ * @param {*} obj
+ * @return {Boolean}
+ */
+
+exports.isObject = function (obj) {
+  return obj && typeof obj === 'object'
+}
+
+/**
+ * Strict object type check. Only returns true
  * for plain JavaScript objects.
  * for plain JavaScript objects.
  *
  *
  * @param {*} obj
  * @param {*} obj
@@ -111,7 +124,7 @@ exports.extend = function (to, from) {
  */
  */
 
 
 var toString = Object.prototype.toString
 var toString = Object.prototype.toString
-exports.isObject = function (obj) {
+exports.isPlainObject = function (obj) {
   return toString.call(obj) === '[object Object]'
   return toString.call(obj) === '[object Object]'
 }
 }
 
 

+ 1 - 1
src/util/merge-option.js

@@ -125,7 +125,7 @@ function guardComponents (components) {
     var def
     var def
     for (var key in components) {
     for (var key in components) {
       def = components[key]
       def = components[key]
-      if (_.isObject(def)) {
+      if (_.isPlainObject(def)) {
         components[key] = _.Vue.extend(def)
         components[key] = _.Vue.extend(def)
       }
       }
     }
     }

+ 1 - 1
test/unit/specs/directive_spec.js

@@ -52,7 +52,7 @@ describe('Directive', function () {
       d._teardown()
       d._teardown()
       expect(def.unbind).toHaveBeenCalled()
       expect(def.unbind).toHaveBeenCalled()
       expect(d._bound).toBe(false)
       expect(d._bound).toBe(false)
-      expect(d._watcher.active).toBe(false)
+      expect(d._watcher).toBe(null)
       done()
       done()
     })
     })
   })
   })

+ 8 - 6
test/unit/specs/util/debug_spec.js

@@ -1,5 +1,7 @@
 var _ = require('../../../../src/util')
 var _ = require('../../../../src/util')
 var config = require('../../../../src/config')
 var config = require('../../../../src/config')
+var infoPrefix = '[Vue info]: '
+var warnPrefix = '[Vue warn]: '
 config.silent = true
 config.silent = true
 
 
 if (typeof console !== 'undefined') {
 if (typeof console !== 'undefined') {
@@ -16,25 +18,25 @@ if (typeof console !== 'undefined') {
     
     
     it('log when debug is true', function () {
     it('log when debug is true', function () {
       config.debug = true
       config.debug = true
-      _.log('hello', 'world')
-      expect(console.log).toHaveBeenCalledWith('hello', 'world')
+      _.log('hello')
+      expect(console.log).toHaveBeenCalledWith(infoPrefix + 'hello')
     })
     })
 
 
     it('not log when debug is false', function () {
     it('not log when debug is false', function () {
       config.debug = false
       config.debug = false
-      _.log('bye', 'world')
+      _.log('bye')
       expect(console.log.calls.count()).toBe(0)
       expect(console.log.calls.count()).toBe(0)
     })
     })
 
 
     it('warn when silent is false', function () {
     it('warn when silent is false', function () {
       config.silent = false
       config.silent = false
-      _.warn('oops', 'ops')
-      expect(console.warn).toHaveBeenCalledWith('oops', 'ops')
+      _.warn('oops')
+      expect(console.warn).toHaveBeenCalledWith(warnPrefix + 'oops')
     })
     })
 
 
     it('not warn when silent is ture', function () {
     it('not warn when silent is ture', function () {
       config.silent = true
       config.silent = true
-      _.warn('oops', 'ops')
+      _.warn('oops')
       expect(console.warn.calls.count()).toBe(0)
       expect(console.warn.calls.count()).toBe(0)
     })
     })
 
 

+ 20 - 3
test/unit/specs/util/lang_spec.js

@@ -60,10 +60,27 @@ describe('Util - Language Enhancement', function () {
 
 
   it('isObject', function () {
   it('isObject', function () {
     expect(_.isObject({})).toBe(true)
     expect(_.isObject({})).toBe(true)
-    expect(_.isObject([])).toBe(false)
-    expect(_.isObject(null)).toBe(false)
+    expect(_.isObject([])).toBe(true)
+    expect(_.isObject(null)).toBeFalsy()
+    expect(_.isObject(123)).toBeFalsy()
+    expect(_.isObject(true)).toBeFalsy()
+    expect(_.isObject('hi')).toBeFalsy()
+    expect(_.isObject(undefined)).toBeFalsy()
+    expect(_.isObject(function(){})).toBeFalsy()
+  })
+
+  it('isPlainObject', function () {
+    expect(_.isPlainObject({})).toBe(true)
+    expect(_.isPlainObject([])).toBe(false)
+    expect(_.isPlainObject(null)).toBe(false)
+    expect(_.isPlainObject(null)).toBeFalsy()
+    expect(_.isPlainObject(123)).toBeFalsy()
+    expect(_.isPlainObject(true)).toBeFalsy()
+    expect(_.isPlainObject('hi')).toBeFalsy()
+    expect(_.isPlainObject(undefined)).toBeFalsy()
+    expect(_.isPlainObject(function(){})).toBe(false)
     if (_.inBrowser) {
     if (_.inBrowser) {
-      expect(_.isObject(window)).toBe(false)
+      expect(_.isPlainObject(window)).toBe(false)
     }
     }
   })
   })