Explorar o código

add config.proto option

Evan You %!s(int64=11) %!d(string=hai) anos
pai
achega
951ff6a15b

+ 4 - 0
changes.md

@@ -381,6 +381,10 @@ Vue.config.debug = true
 
   * Note you still cannot use `<` or `>` in delimiters because Vue uses DOM-based templating.
 
+- #### New config option: `proto`
+
+  Be default, Vue.js alters observed data objects' `__proto__` when available for faster method interception/augmentation. This is perfectly fine when your data objects are plain JSON-derived objects. However if you want to use Vue's observation on object created with custom prototypes (e.g. from constructors), you can set `Vue.config.proto = false` to prohibit this behavior.
+
 ## Transition API Change
 
 > Breaking

+ 9 - 0
src/config.js

@@ -25,6 +25,15 @@ module.exports = {
 
   silent: false,
 
+  /**
+   * Whether allow observer to alter data objects'
+   * __proto__.
+   *
+   * @type {Boolean}
+   */
+
+  proto: true,
+
   /**
    * Whether to parse mustache tags in templates.
    *

+ 22 - 29
src/observer/array.js

@@ -17,8 +17,7 @@ var arrayAugmentations = Object.create(Array.prototype)
 .forEach(function (method) {
   // cache original method
   var original = Array.prototype[method]
-
-  function mutator () {
+  _.define(arrayAugmentations, method, function mutator () {
     // avoid leaking arguments:
     // http://jsperf.com/closure-with-arguments
     var i = arguments.length
@@ -44,13 +43,7 @@ var arrayAugmentations = Object.create(Array.prototype)
     // notify change
     ob.binding.notify()
     return result
-  }
-  // define wrapped method
-  if (_.hasProto) {
-    _.define(arrayAugmentations, method, mutator)
-  } else {
-    arrayAugmentations[method] = mutator
-  }
+  })
 })
 
 /**
@@ -62,12 +55,16 @@ var arrayAugmentations = Object.create(Array.prototype)
  * @return {*} - replaced element
  */
 
-function $set (index, val) {
-  if (index >= this.length) {
-    this.length = index + 1
+_.define(
+  arrayAugmentations,
+  '$set',
+  function $set (index, val) {
+    if (index >= this.length) {
+      this.length = index + 1
+    }
+    return this.splice(index, 1, val)[0]
   }
-  return this.splice(index, 1, val)[0]
-}
+)
 
 /**
  * Convenience method to remove the element at given index.
@@ -76,21 +73,17 @@ function $set (index, val) {
  * @param {*} val
  */
 
-function $remove (index) {
-  if (typeof index !== 'number') {
-    index = this.indexOf(index)
-  }
-  if (index > -1) {
-    return this.splice(index, 1)[0]
+_.define(
+  arrayAugmentations,
+  '$remove',
+  function $remove (index) {
+    if (typeof index !== 'number') {
+      index = this.indexOf(index)
+    }
+    if (index > -1) {
+      return this.splice(index, 1)[0]
+    }
   }
-}
-
-if (_.hasProto) {
-  _.define(arrayAugmentations, '$set', $set)
-  _.define(arrayAugmentations, '$remove', $remove)
-} else {
-  arrayAugmentations.$set = $set
-  arrayAugmentations.$remove = $remove
-}
+)
 
 module.exports = arrayAugmentations

+ 37 - 2
src/observer/index.js

@@ -1,7 +1,10 @@
 var _ = require('../util')
+var config = require('../config')
 var Binding = require('../binding')
 var arrayAugmentations = require('./array')
 var objectAugmentations = require('./object')
+var arrayKeys = Object.getOwnPropertyNames(arrayAugmentations)
+var objectKeys = Object.getOwnPropertyNames(objectAugmentations)
 
 var uid = 0
 
@@ -12,6 +15,35 @@ var uid = 0
 var ARRAY  = 0
 var OBJECT = 1
 
+/**
+ * Augment an target Object or Array by intercepting
+ * the prototype chain using __proto__
+ *
+ * @param {Object|Array} target
+ * @param {Object} proto
+ */
+
+function protoAugment (target, src) {
+  target.__proto__ = src
+}
+
+/**
+ * Augment an target Object or Array by defining
+ * hidden properties.
+ *
+ * @param {Object|Array} target
+ * @param {Object} proto
+ */
+
+function copyAugment (target, src, keys) {
+  var i = keys.length
+  var key
+  while (i--) {
+    key = keys[i]
+    _.define(target, key, src[key])
+  }
+}
+
 /**
  * Observer class that are attached to each observed
  * object. Once attached, the observer converts target
@@ -29,11 +61,14 @@ function Observer (value, type) {
   this.active = true
   this.binding = new Binding()
   _.define(value, '__ob__', this)
+  var augment = config.proto && _.hasProto
+    ? protoAugment
+    : copyAugment
   if (type === ARRAY) {
-    _.augment(value, arrayAugmentations)
+    augment(value, arrayAugmentations, arrayKeys)
     this.observeArray(value)
   } else if (type === OBJECT) {
-    _.augment(value, objectAugmentations)
+    augment(value, objectAugmentations, objectKeys)
     this.walk(value)
   }
 }

+ 18 - 18
src/observer/object.js

@@ -10,11 +10,15 @@ var objectAgumentations = Object.create(Object.prototype)
  * @public
  */
 
-function $add (key, val) {
-  if (this.hasOwnProperty(key)) return
-  this.__ob__.convert(key, val)
-  this.__ob__.binding.notify()
-}
+_.define(
+  objectAgumentations,
+  '$add',
+  function $add (key, val) {
+    if (this.hasOwnProperty(key)) return
+    this.__ob__.convert(key, val)
+    this.__ob__.binding.notify()
+  }
+)
 
 /**
  * Deletes a property from an observed object
@@ -24,18 +28,14 @@ function $add (key, val) {
  * @public
  */
 
-function $delete (key) {
-  if (!this.hasOwnProperty(key)) return
-  delete this[key]
-  this.__ob__.binding.notify()
-}
-
-if (_.hasProto) {
-  _.define(objectAgumentations, '$add', $add)
-  _.define(objectAgumentations, '$delete', $delete)
-} else {
-  objectAgumentations.$add = $add
-  objectAgumentations.$delete = $delete
-}
+_.define(
+  objectAgumentations,
+  '$delete',
+  function $delete (key) {
+    if (!this.hasOwnProperty(key)) return
+    delete this[key]
+    this.__ob__.binding.notify()
+  }
+)
 
 module.exports = objectAgumentations

+ 8 - 0
src/util/env.js

@@ -1,3 +1,11 @@
+/**
+ * Can we use __proto__?
+ *
+ * @type {Boolean}
+ */
+
+exports.hasProto = '__proto__' in {}
+
 /**
  * Indicates we have a window
  *

+ 1 - 22
src/util/lang.js

@@ -154,25 +154,4 @@ exports.define = function (obj, key, val, enumerable) {
     writable     : true,
     configurable : true
   })
-}
-
-/**
- * Augment an target Object or Array by either
- * intercepting the prototype chain using __proto__,
- * or copy over properties with defineProperty.
- *
- * @param {Object|Array} target
- * @param {Object} proto
- */
-
-var define = exports.define
-var hasProto = exports.hasProto = '__proto__' in {}
-exports.augment = hasProto
-  ? function (target, proto) {
-      target.__proto__ = proto
-    }
-  : function (target, proto) {
-      for (var key in proto) {
-        define(target, key, proto[key])
-      }
-    }
+}

+ 23 - 0
test/unit/specs/observer_spec.js

@@ -1,4 +1,5 @@
 var Observer = require('../../../src/observer')
+var config = require('../../../src/config')
 var _ = require('../../../src/util')
 
 describe('Observer', function () {
@@ -164,4 +165,26 @@ describe('Observer', function () {
     expect(binding.notify.calls.count()).toBe(2)
   })
 
+  it('no proto', function () {
+    config.proto = false
+    // object
+    var obj = {a:1}
+    var ob = Observer.create(obj)
+    expect(obj.$add).toBeTruthy()
+    expect(obj.$delete).toBeTruthy()
+    spyOn(ob.binding, 'notify')
+    obj.$add('b', 2)
+    expect(ob.binding.notify).toHaveBeenCalled()
+    // array
+    var arr = [1, 2, 3]
+    var ob2 = Observer.create(arr)
+    expect(arr.$set).toBeTruthy()
+    expect(arr.$remove).toBeTruthy()
+    expect(arr.push).not.toBe([].push)
+    spyOn(ob2.binding, 'notify')
+    arr.push(1)
+    expect(ob2.binding.notify).toHaveBeenCalled()
+    config.proto = true
+  })
+
 })

+ 0 - 12
test/unit/specs/util/lang_spec.js

@@ -103,16 +103,4 @@ describe('Util - Language Enhancement', function () {
     expect(desc.enumerable).toBe(true)
   })
 
-  it('augment', function () {
-    var target = {}
-    var proto = { a: 1 }
-    _.augment(target, proto)
-    if ('__proto__' in {}) {
-      expect(target.__proto__).toBe(proto)
-    } else {
-      expect(Object.keys(target).length).toBe(0)
-      expect(target.a).toBe(1)
-    }
-  })
-
 })