1 |
|
2 |
|
3 | import {
|
4 | warn,
|
5 | remove,
|
6 | isObject,
|
7 | parsePath,
|
8 | _Set as Set,
|
9 | handleError,
|
10 | noop
|
11 | } from '../util/index'
|
12 |
|
13 | import { traverse } from './traverse'
|
14 | import { queueWatcher } from './scheduler'
|
15 | import Dep, { pushTarget, popTarget } from './dep'
|
16 |
|
17 | import type { SimpleSet } from '../util/index'
|
18 |
|
19 | let uid = 0
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | export default class Watcher {
|
27 | vm: Component;
|
28 | expression: string;
|
29 | cb: Function;
|
30 | id: number;
|
31 | deep: boolean;
|
32 | user: boolean;
|
33 | lazy: boolean;
|
34 | sync: boolean;
|
35 | dirty: boolean;
|
36 | active: boolean;
|
37 | deps: Array<Dep>;
|
38 | newDeps: Array<Dep>;
|
39 | depIds: SimpleSet;
|
40 | newDepIds: SimpleSet;
|
41 | before: ?Function;
|
42 | getter: Function;
|
43 | value: any;
|
44 |
|
45 | constructor (
|
46 | vm: Component,
|
47 | expOrFn: string | Function,
|
48 | cb: Function,
|
49 | options?: ?Object,
|
50 | isRenderWatcher?: boolean
|
51 | ) {
|
52 | this.vm = vm
|
53 | if (isRenderWatcher) {
|
54 | vm._watcher = this
|
55 | }
|
56 | vm._watchers.push(this)
|
57 |
|
58 | if (options) {
|
59 | this.deep = !!options.deep
|
60 | this.user = !!options.user
|
61 | this.lazy = !!options.lazy
|
62 | this.sync = !!options.sync
|
63 | this.before = options.before
|
64 | } else {
|
65 | this.deep = this.user = this.lazy = this.sync = false
|
66 | }
|
67 | this.cb = cb
|
68 | this.id = ++uid
|
69 | this.active = true
|
70 | this.dirty = this.lazy
|
71 | this.deps = []
|
72 | this.newDeps = []
|
73 | this.depIds = new Set()
|
74 | this.newDepIds = new Set()
|
75 | this.expression = process.env.NODE_ENV !== 'production'
|
76 | ? expOrFn.toString()
|
77 | : ''
|
78 |
|
79 | if (typeof expOrFn === 'function') {
|
80 | this.getter = expOrFn
|
81 | } else {
|
82 | this.getter = parsePath(expOrFn)
|
83 | if (!this.getter) {
|
84 | this.getter = noop
|
85 | process.env.NODE_ENV !== 'production' && warn(
|
86 | `Failed watching path: "${expOrFn}" ` +
|
87 | 'Watcher only accepts simple dot-delimited paths. ' +
|
88 | 'For full control, use a function instead.',
|
89 | vm
|
90 | )
|
91 | }
|
92 | }
|
93 | this.value = this.lazy
|
94 | ? undefined
|
95 | : this.get()
|
96 | }
|
97 |
|
98 | |
99 |
|
100 |
|
101 | get () {
|
102 | pushTarget(this)
|
103 | let value
|
104 | const vm = this.vm
|
105 | try {
|
106 | value = this.getter.call(vm, vm)
|
107 | } catch (e) {
|
108 | if (this.user) {
|
109 | handleError(e, vm, `getter for watcher "${this.expression}"`)
|
110 | } else {
|
111 | throw e
|
112 | }
|
113 | } finally {
|
114 |
|
115 |
|
116 | if (this.deep) {
|
117 | traverse(value)
|
118 | }
|
119 | popTarget()
|
120 | this.cleanupDeps()
|
121 | }
|
122 | return value
|
123 | }
|
124 |
|
125 | |
126 |
|
127 |
|
128 | addDep (dep: Dep) {
|
129 | const id = dep.id
|
130 | if (!this.newDepIds.has(id)) {
|
131 | this.newDepIds.add(id)
|
132 | this.newDeps.push(dep)
|
133 | if (!this.depIds.has(id)) {
|
134 | dep.addSub(this)
|
135 | }
|
136 | }
|
137 | }
|
138 |
|
139 | |
140 |
|
141 |
|
142 | cleanupDeps () {
|
143 | let i = this.deps.length
|
144 | while (i--) {
|
145 | const dep = this.deps[i]
|
146 | if (!this.newDepIds.has(dep.id)) {
|
147 | dep.removeSub(this)
|
148 | }
|
149 | }
|
150 | let tmp = this.depIds
|
151 | this.depIds = this.newDepIds
|
152 | this.newDepIds = tmp
|
153 | this.newDepIds.clear()
|
154 | tmp = this.deps
|
155 | this.deps = this.newDeps
|
156 | this.newDeps = tmp
|
157 | this.newDeps.length = 0
|
158 | }
|
159 |
|
160 | |
161 |
|
162 |
|
163 |
|
164 | update () {
|
165 |
|
166 | if (this.lazy) {
|
167 | this.dirty = true
|
168 | } else if (this.sync) {
|
169 | this.run()
|
170 | } else {
|
171 | queueWatcher(this)
|
172 | }
|
173 | }
|
174 |
|
175 | |
176 |
|
177 |
|
178 |
|
179 | run () {
|
180 | if (this.active) {
|
181 | const value = this.get()
|
182 | if (
|
183 | value !== this.value ||
|
184 |
|
185 |
|
186 |
|
187 | isObject(value) ||
|
188 | this.deep
|
189 | ) {
|
190 |
|
191 | const oldValue = this.value
|
192 | this.value = value
|
193 | if (this.user) {
|
194 | try {
|
195 | this.cb.call(this.vm, value, oldValue)
|
196 | } catch (e) {
|
197 | handleError(e, this.vm, `callback for watcher "${this.expression}"`)
|
198 | }
|
199 | } else {
|
200 | this.cb.call(this.vm, value, oldValue)
|
201 | }
|
202 | }
|
203 | }
|
204 | }
|
205 |
|
206 | |
207 |
|
208 |
|
209 |
|
210 | evaluate () {
|
211 | this.value = this.get()
|
212 | this.dirty = false
|
213 | }
|
214 |
|
215 | |
216 |
|
217 |
|
218 | depend () {
|
219 | let i = this.deps.length
|
220 | while (i--) {
|
221 | this.deps[i].depend()
|
222 | }
|
223 | }
|
224 |
|
225 | |
226 |
|
227 |
|
228 | teardown () {
|
229 | if (this.active) {
|
230 |
|
231 |
|
232 |
|
233 | if (!this.vm._isBeingDestroyed) {
|
234 | remove(this.vm._watchers, this)
|
235 | }
|
236 | let i = this.deps.length
|
237 | while (i--) {
|
238 | this.deps[i].removeSub(this)
|
239 | }
|
240 | this.active = false
|
241 | }
|
242 | }
|
243 | }
|