Browse Source

ensure mutating extended constructor options does not affect parent (fix #4767)

Evan You 9 years ago
parent
commit
769c4dc203

+ 5 - 3
src/core/instance/state.js

@@ -39,12 +39,14 @@ const isReservedProp = { key: 1, ref: 1, slot: 1 }
 
 function initProps (vm: Component, props: Object) {
   const propsData = vm.$options.propsData || {}
-  const keys = vm.$options._propKeys = Object.keys(props)
+  // cache prop keys so that future props updates can iterate using Array
+  // instead of dyanmic object key enumeration.
+  const keys = vm.$options._propKeys = []
   const isRoot = !vm.$parent
   // root instance props should be converted
   observerState.shouldConvert = isRoot
-  for (let i = 0; i < keys.length; i++) {
-    const key = keys[i]
+  for (const key in props) {
+    keys.push(key)
     /* istanbul ignore else */
     if (process.env.NODE_ENV !== 'production') {
       if (isReservedProp[key]) {

+ 3 - 3
src/core/util/options.js

@@ -110,7 +110,7 @@ strats.data = function (
 }
 
 /**
- * Hooks and param attributes are merged as arrays.
+ * Hooks and props are merged as arrays.
  */
 function mergeHook (
   parentVal: ?Array<Function>,
@@ -155,7 +155,7 @@ config._assetTypes.forEach(function (type) {
  */
 strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {
   /* istanbul ignore if */
-  if (!childVal) return parentVal
+  if (!childVal) return Object.create(parentVal || null)
   if (!parentVal) return childVal
   const ret = {}
   extend(ret, parentVal)
@@ -178,7 +178,7 @@ strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {
 strats.props =
 strats.methods =
 strats.computed = function (parentVal: ?Object, childVal: ?Object): ?Object {
-  if (!childVal) return parentVal
+  if (!childVal) return Object.create(parentVal || null)
   if (!parentVal) return childVal
   const ret = Object.create(null)
   extend(ret, parentVal)

+ 9 - 0
test/unit/features/global-api/extend.spec.js

@@ -122,4 +122,13 @@ describe('Global API: extend', () => {
     const B = Vue.extend(options)
     expect(A).toBe(B)
   })
+
+  // #4767
+  it('extended options should use different identitfy from parent', () => {
+    const A = Vue.extend({ computed: {}})
+    const B = A.extend()
+    B.options.computed.b = () => 'foo'
+    expect(B.options.computed).not.toBe(A.options.computed)
+    expect(A.options.computed.b).toBeUndefined()
+  })
 })