Skip to content

Commit 406352b

Browse files
committed
move computed properties definition to component prototype when possible
1 parent 4f6b101 commit 406352b

File tree

4 files changed

+97
-36
lines changed

4 files changed

+97
-36
lines changed

flow/component.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ declare interface Component {
5252
_renderContext: ?Component;
5353
_watcher: Watcher;
5454
_watchers: Array<Watcher>;
55+
_computedWatchers: { [key: string]: Watcher };
5556
_data: Object;
5657
_props: Object;
5758
_events: Object;

src/core/global-api/extend.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import config from '../config'
44
import { warn, mergeOptions } from '../util/index'
5+
import { defineComputed } from '../instance/state'
56

67
export function initExtend (Vue: GlobalAPI) {
78
/**
@@ -23,6 +24,7 @@ export function initExtend (Vue: GlobalAPI) {
2324
if (cachedCtors[SuperId]) {
2425
return cachedCtors[SuperId]
2526
}
27+
2628
const name = extendOptions.name || Super.options.name
2729
if (process.env.NODE_ENV !== 'production') {
2830
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
@@ -33,6 +35,7 @@ export function initExtend (Vue: GlobalAPI) {
3335
)
3436
}
3537
}
38+
3639
const Sub = function VueComponent (options) {
3740
this._init(options)
3841
}
@@ -44,10 +47,16 @@ export function initExtend (Vue: GlobalAPI) {
4447
extendOptions
4548
)
4649
Sub['super'] = Super
50+
51+
if (Sub.options.computed) {
52+
initComputed(Sub)
53+
}
54+
4755
// allow further extension/mixin/plugin usage
4856
Sub.extend = Super.extend
4957
Sub.mixin = Super.mixin
5058
Sub.use = Super.use
59+
5160
// create asset registers, so extended classes
5261
// can have their private assets too.
5362
config._assetTypes.forEach(function (type) {
@@ -57,13 +66,22 @@ export function initExtend (Vue: GlobalAPI) {
5766
if (name) {
5867
Sub.options.components[name] = Sub
5968
}
69+
6070
// keep a reference to the super options at extension time.
6171
// later at instantiation we can check if Super's options have
6272
// been updated.
6373
Sub.superOptions = Super.options
6474
Sub.extendOptions = extendOptions
75+
6576
// cache constructor
6677
cachedCtors[SuperId] = Sub
6778
return Sub
6879
}
6980
}
81+
82+
function initComputed (Comp) {
83+
const computed = Comp.options.computed
84+
for (const key in computed) {
85+
defineComputed(Comp.prototype, key, computed[key])
86+
}
87+
}

src/core/instance/state.js

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* @flow */
22

3-
import Watcher from '../observer/watcher'
43
import Dep from '../observer/dep'
4+
import Watcher from '../observer/watcher'
55

66
import {
77
set,
@@ -108,53 +108,62 @@ function initData (vm: Component) {
108108
observe(data, true /* asRootData */)
109109
}
110110

111+
const computedWatcherOptions = { lazy: true }
112+
113+
function initComputed (vm: Component, computed: Object) {
114+
const watchers = vm._computedWatchers = Object.create(null)
115+
116+
for (const key in computed) {
117+
const userDef = computed[key]
118+
const getter = typeof userDef === 'function' ? userDef : userDef.get
119+
// create internal watcher for the computed property.
120+
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
121+
122+
// component-defined computed properties are already defined on the
123+
// component prototype. We only need to define on-the-fly computed
124+
// properties here.
125+
if (!(key in vm)) {
126+
defineComputed(vm, key, userDef)
127+
}
128+
}
129+
}
130+
111131
const computedSharedDefinition = {
112132
enumerable: true,
113133
configurable: true,
114134
get: noop,
115135
set: noop
116136
}
117137

118-
function initComputed (vm: Component, computed: Object) {
119-
for (const key in computed) {
120-
/* istanbul ignore if */
121-
if (process.env.NODE_ENV !== 'production' && key in vm) {
122-
warn(
123-
`existing instance property "${key}" will be ` +
124-
`overwritten by a computed property with the same name.`,
125-
vm
126-
)
127-
}
128-
const userDef = computed[key]
129-
if (typeof userDef === 'function') {
130-
computedSharedDefinition.get = makeComputedGetter(userDef, vm)
131-
computedSharedDefinition.set = noop
132-
} else {
133-
computedSharedDefinition.get = userDef.get
134-
? userDef.cache !== false
135-
? makeComputedGetter(userDef.get, vm)
136-
: bind(userDef.get, vm)
137-
: noop
138-
computedSharedDefinition.set = userDef.set
139-
? bind(userDef.set, vm)
140-
: noop
141-
}
142-
Object.defineProperty(vm, key, computedSharedDefinition)
138+
export function defineComputed (target: any, key: string, userDef: Object | Function) {
139+
if (typeof userDef === 'function') {
140+
computedSharedDefinition.get = createComputedGetter(key)
141+
computedSharedDefinition.set = noop
142+
} else {
143+
computedSharedDefinition.get = userDef.get
144+
? userDef.cache !== false
145+
? createComputedGetter(key)
146+
: userDef.get
147+
: noop
148+
computedSharedDefinition.set = userDef.set
149+
? userDef.set
150+
: noop
143151
}
152+
Object.defineProperty(target, key, computedSharedDefinition)
144153
}
145154

146-
function makeComputedGetter (getter: Function, owner: Component): Function {
147-
const watcher = new Watcher(owner, getter, noop, {
148-
lazy: true
149-
})
155+
function createComputedGetter (key) {
150156
return function computedGetter () {
151-
if (watcher.dirty) {
152-
watcher.evaluate()
153-
}
154-
if (Dep.target) {
155-
watcher.depend()
157+
const watcher = this._computedWatchers && this._computedWatchers[key]
158+
if (watcher) {
159+
if (watcher.dirty) {
160+
watcher.evaluate()
161+
}
162+
if (Dep.target) {
163+
watcher.depend()
164+
}
165+
return watcher.value
156166
}
157-
return watcher.value
158167
}
159168
}
160169

test/unit/features/options/computed.spec.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,37 @@ describe('Options computed', () => {
107107
vm.b
108108
expect(spy.calls.count()).toBe(2)
109109
})
110+
111+
it('as component', done => {
112+
const Comp = Vue.extend({
113+
template: `<div>{{ b }} {{ c }}</div>`,
114+
data () {
115+
return { a: 1 }
116+
},
117+
computed: {
118+
// defined on prototype
119+
b () {
120+
return this.a + 1
121+
}
122+
}
123+
})
124+
125+
const vm = new Comp({
126+
computed: {
127+
// defined at instantiation
128+
c () {
129+
return this.b + 1
130+
}
131+
}
132+
}).$mount()
133+
expect(vm.b).toBe(2)
134+
expect(vm.c).toBe(3)
135+
expect(vm.$el.textContent).toBe('2 3')
136+
vm.a = 2
137+
expect(vm.b).toBe(3)
138+
expect(vm.c).toBe(4)
139+
waitForUpdate(() => {
140+
expect(vm.$el.textContent).toBe('3 4')
141+
}).then(done)
142+
})
110143
})

0 commit comments

Comments
 (0)