move computed properties definition to component prototype when possible

This commit is contained in:
Evan You 2017-02-14 12:03:14 -05:00
parent 4f6b1014b3
commit 406352baba
4 changed files with 97 additions and 36 deletions

View File

@ -52,6 +52,7 @@ declare interface Component {
_renderContext: ?Component;
_watcher: Watcher;
_watchers: Array<Watcher>;
_computedWatchers: { [key: string]: Watcher };
_data: Object;
_props: Object;
_events: Object;

View File

@ -2,6 +2,7 @@
import config from '../config'
import { warn, mergeOptions } from '../util/index'
import { defineComputed } from '../instance/state'
export function initExtend (Vue: GlobalAPI) {
/**
@ -23,6 +24,7 @@ export function initExtend (Vue: GlobalAPI) {
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production') {
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
@ -33,6 +35,7 @@ export function initExtend (Vue: GlobalAPI) {
)
}
}
const Sub = function VueComponent (options) {
this._init(options)
}
@ -44,10 +47,16 @@ export function initExtend (Vue: GlobalAPI) {
extendOptions
)
Sub['super'] = Super
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
config._assetTypes.forEach(function (type) {
@ -57,13 +66,22 @@ export function initExtend (Vue: GlobalAPI) {
if (name) {
Sub.options.components[name] = Sub
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
// cache constructor
cachedCtors[SuperId] = Sub
return Sub
}
}
function initComputed (Comp) {
const computed = Comp.options.computed
for (const key in computed) {
defineComputed(Comp.prototype, key, computed[key])
}
}

View File

@ -1,7 +1,7 @@
/* @flow */
import Watcher from '../observer/watcher'
import Dep from '../observer/dep'
import Watcher from '../observer/watcher'
import {
set,
@ -108,6 +108,26 @@ function initData (vm: Component) {
observe(data, true /* asRootData */)
}
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
// create internal watcher for the computed property.
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
// component-defined computed properties are already defined on the
// component prototype. We only need to define on-the-fly computed
// properties here.
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
const computedSharedDefinition = {
enumerable: true,
configurable: true,
@ -115,46 +135,35 @@ const computedSharedDefinition = {
set: noop
}
function initComputed (vm: Component, computed: Object) {
for (const key in computed) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && key in vm) {
warn(
`existing instance property "${key}" will be ` +
`overwritten by a computed property with the same name.`,
vm
)
}
const userDef = computed[key]
if (typeof userDef === 'function') {
computedSharedDefinition.get = makeComputedGetter(userDef, vm)
computedSharedDefinition.set = noop
} else {
computedSharedDefinition.get = userDef.get
? userDef.cache !== false
? makeComputedGetter(userDef.get, vm)
: bind(userDef.get, vm)
: noop
computedSharedDefinition.set = userDef.set
? bind(userDef.set, vm)
: noop
}
Object.defineProperty(vm, key, computedSharedDefinition)
export function defineComputed (target: any, key: string, userDef: Object | Function) {
if (typeof userDef === 'function') {
computedSharedDefinition.get = createComputedGetter(key)
computedSharedDefinition.set = noop
} else {
computedSharedDefinition.get = userDef.get
? userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop
computedSharedDefinition.set = userDef.set
? userDef.set
: noop
}
Object.defineProperty(target, key, computedSharedDefinition)
}
function makeComputedGetter (getter: Function, owner: Component): Function {
const watcher = new Watcher(owner, getter, noop, {
lazy: true
})
function createComputedGetter (key) {
return function computedGetter () {
if (watcher.dirty) {
watcher.evaluate()
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}

View File

@ -107,4 +107,37 @@ describe('Options computed', () => {
vm.b
expect(spy.calls.count()).toBe(2)
})
it('as component', done => {
const Comp = Vue.extend({
template: `<div>{{ b }} {{ c }}</div>`,
data () {
return { a: 1 }
},
computed: {
// defined on prototype
b () {
return this.a + 1
}
}
})
const vm = new Comp({
computed: {
// defined at instantiation
c () {
return this.b + 1
}
}
}).$mount()
expect(vm.b).toBe(2)
expect(vm.c).toBe(3)
expect(vm.$el.textContent).toBe('2 3')
vm.a = 2
expect(vm.b).toBe(3)
expect(vm.c).toBe(4)
waitForUpdate(() => {
expect(vm.$el.textContent).toBe('3 4')
}).then(done)
})
})