Skip to content

Commit

Permalink
move computed properties definition to component prototype when possible
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Feb 14, 2017
1 parent 4f6b101 commit 406352b
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 36 deletions.
1 change: 1 addition & 0 deletions flow/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ declare interface Component {
_renderContext: ?Component;
_watcher: Watcher;
_watchers: Array<Watcher>;
_computedWatchers: { [key: string]: Watcher };
_data: Object;
_props: Object;
_events: Object;
Expand Down
18 changes: 18 additions & 0 deletions src/core/global-api/extend.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import config from '../config'
import { warn, mergeOptions } from '../util/index'
import { defineComputed } from '../instance/state'

export function initExtend (Vue: GlobalAPI) {
/**
Expand All @@ -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)) {
Expand All @@ -33,6 +35,7 @@ export function initExtend (Vue: GlobalAPI) {
)
}
}

const Sub = function VueComponent (options) {
this._init(options)
}
Expand All @@ -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) {
Expand All @@ -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])
}
}
81 changes: 45 additions & 36 deletions src/core/instance/state.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* @flow */

import Watcher from '../observer/watcher'
import Dep from '../observer/dep'
import Watcher from '../observer/watcher'

import {
set,
Expand Down Expand Up @@ -108,53 +108,62 @@ 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,
get: noop,
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()
}
if (Dep.target) {
watcher.depend()
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
return watcher.value
}
}

Expand Down
33 changes: 33 additions & 0 deletions test/unit/features/options/computed.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})

0 comments on commit 406352b

Please sign in to comment.