diff --git a/src/core/instance/init.js b/src/core/instance/init.js index 9b2ec80c44d..7c5ab2da963 100644 --- a/src/core/instance/init.js +++ b/src/core/instance/init.js @@ -117,32 +117,12 @@ export function resolveConstructorOptions (Ctor: Class) { function resolveModifiedOptions (Ctor: Class): ?Object { let modified const latest = Ctor.options - const extended = Ctor.extendOptions const sealed = Ctor.sealedOptions for (const key in latest) { if (latest[key] !== sealed[key]) { if (!modified) modified = {} - modified[key] = dedupe(latest[key], extended[key], sealed[key]) + modified[key] = latest[key] } } return modified } - -function dedupe (latest, extended, sealed) { - // compare latest and sealed to ensure lifecycle hooks won't be duplicated - // between merges - if (Array.isArray(latest)) { - const res = [] - sealed = Array.isArray(sealed) ? sealed : [sealed] - extended = Array.isArray(extended) ? extended : [extended] - for (let i = 0; i < latest.length; i++) { - // push original options and not sealed options to exclude duplicated options - if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) { - res.push(latest[i]) - } - } - return res - } else { - return latest - } -} diff --git a/src/core/util/options.js b/src/core/util/options.js index 333d08ab755..fe9b6f30298 100644 --- a/src/core/util/options.js +++ b/src/core/util/options.js @@ -140,13 +140,26 @@ function mergeHook ( parentVal: ?Array, childVal: ?Function | ?Array ): ?Array { - return childVal + const res = childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal + return res + ? dedupeHooks(res) + : res +} + +function dedupeHooks (hooks) { + const res = [] + for (let i = 0; i < hooks.length; i++) { + if (res.indexOf(hooks[i]) === -1) { + res.push(hooks[i]) + } + } + return res } LIFECYCLE_HOOKS.forEach(hook => { @@ -376,7 +389,7 @@ export function mergeOptions ( } if (typeof child === 'function') { - child = child.extendOptions + child = child.options } normalizeProps(child, vm) diff --git a/test/unit/features/global-api/mixin.spec.js b/test/unit/features/global-api/mixin.spec.js index 0b83773a5d4..14b65918c50 100644 --- a/test/unit/features/global-api/mixin.spec.js +++ b/test/unit/features/global-api/mixin.spec.js @@ -167,4 +167,31 @@ describe('Global API: mixin', () => { it('chain call', () => { expect(Vue.mixin({})).toBe(Vue) }) + + // #9198 + it('should not mix global mixin lifecycle hook twice', () => { + const spy = jasmine.createSpy('global mixed in lifecycle hook') + Vue.mixin({ + created: spy + }) + + const mixin1 = Vue.extend({ + methods: { + a() {} + } + }) + + const mixin2 = Vue.extend({ + mixins: [mixin1] + }) + + const Child = Vue.extend({ + mixins: [mixin2], + }) + + const vm = new Child() + + expect(typeof vm.$options.methods.a).toBe('function') + expect(spy.calls.count()).toBe(1) + }) }) diff --git a/test/unit/features/options/mixins.spec.js b/test/unit/features/options/mixins.spec.js index 65c4e6055ff..373f8867091 100644 --- a/test/unit/features/options/mixins.spec.js +++ b/test/unit/features/options/mixins.spec.js @@ -110,31 +110,35 @@ describe('Options mixins', () => { expect(vm.$options.directives.c).toBeDefined() }) - it('should not mix global mixined lifecycle hook twice', () => { - const spy = jasmine.createSpy('global mixed in lifecycle hook') - Vue.mixin({ - created() { - spy() - } - }) + it('should accept further extended constructors as mixins', () => { + const spy1 = jasmine.createSpy('mixinA') + const spy2 = jasmine.createSpy('mixinB') - const mixin1 = Vue.extend({ + const mixinA = Vue.extend({ + created: spy1, + directives: { + c: {} + }, methods: { - a() {} + a: function () {} } }) - const mixin2 = Vue.extend({ - mixins: [mixin1], + const mixinB = mixinA.extend({ + created: spy2 }) - const Child = Vue.extend({ - mixins: [mixin2], + const vm = new Vue({ + mixins: [mixinB], + methods: { + b: function () {} + } }) - const vm = new Child() - - expect(typeof vm.$options.methods.a).toBe('function') - expect(spy.calls.count()).toBe(1) + expect(spy1).toHaveBeenCalledTimes(1) + expect(spy2).toHaveBeenCalledTimes(1) + expect(vm.a).toBeDefined() + expect(vm.b).toBeDefined() + expect(vm.$options.directives.c).toBeDefined() }) })