All quizzesHard
Scheduler & Internals
Preview — 3 of 10 questions
Why is Vue 3s Proxy-based reactivity more powerful than Vue 2s Object.defineProperty?
javascript
// Vue 2 limitations with Object.defineProperty:
const vue2State = Vue.observable({ count: 0, user: { name: 'Alice' } })
// ❌ Adding new property — NOT reactive (Vue.set required)
vue2State.newProp = 'value'
// → UI doesn't update; newProp is not observed
// ❌ Deleting property — NOT reactive
delete vue2State.user.name
// → UI doesn't update
// ❌ Array mutations via index — NOT reactive
vue2State.items[0] = 'new'
// → UI doesn't update (push/pop/splice work because Vue patched them)
// Vue 3 with Proxy — all of these work:
const vue3State = reactive({ count: 0, user: { name: 'Alice' } })
vue3State.newProp = 'value' // ✅ Reactive — Proxy's 'set' trap catches it
delete vue3State.user.name // ✅ Reactive — Proxy's 'deleteProperty' trap
vue3State.items = []
vue3State.items[0] = 'new' // ✅ Reactive — Array index assignment tracked
// How Proxy achieves this:
new Proxy(target, {
get(target, key) { track(target, key); return target[key] },
set(target, key, value) {
target[key] = value
trigger(target, key) // Works for NEW keys too!
return true
},
deleteProperty(target, key) {
delete target[key]
trigger(target, key) // Deletion is tracked!
return true
},
has(target, key) { track(target, key); return key in target },
ownKeys(target) { track(target, 'iterate'); return Reflect.ownKeys(target) }
})AProxy is faster in all browsers; Object.defineProperty is deprecated
BProxy intercepts any operation on an object (get, set, delete, has, ownKeys) making dynamic property addition and deletion reactive; Object.defineProperty only intercepts predefined properties
CProxy supports TypeScript; Object.defineProperty does not
DProxy uses less memory because it doesn't create getter/setter for every property
How does Vue 3 determine when to invalidate a computed property's cache?
javascript
// Simplified Vue 3 computed internals
class ComputedRefImpl {
private _value: T
private _dirty = true // Start dirty — needs first computation
private effect: ReactiveEffect
constructor(getter) {
this.effect = new ReactiveEffect(getter, () => {
// This scheduler runs when a dependency changes
if (!this._dirty) {
this._dirty = true // Mark as dirty — cache is invalid
triggerRefValue(this) // Notify consumers of this computed
}
})
}
get value() {
if (this._dirty) {
this._dirty = false
this._value = this.effect.run() // Re-run getter, re-track dependencies
}
return this._value
}
}
// Practical implications:
const count = ref(0)
const name = ref('Alice')
const unrelated = ref('x')
const greeting = computed(() => {
// During this execution, Vue tracks: count, name
// 'unrelated' is NOT tracked — the getter never reads it
return `Hello ${name.value}, count: ${count.value}`
})
greeting.value // Executes getter, tracks count + name, caches result
greeting.value // Returns cached result (no getter execution)
unrelated.value = 'y' // Changes 'unrelated' — NOT tracked by greeting
greeting.value // Still returns cached result — unrelated isn't a dependency!
count.value++ // Triggers the scheduler → _dirty = true
greeting.value // _dirty = true → re-runs getter with new dependenciesATime-based expiration — computed values older than one render cycle are recomputed
BDependency-based invalidation: Vue tracks which reactive values are accessed during the getter's last execution, and marks the computed as "dirty" when any dependency changes
CReference equality check — if the computed returns the same reference, the cache is valid
DVue compares the computed's return value using JSON.stringify on each render
How does Vue 3's scheduler prevent unnecessary component re-renders when multiple reactive values change synchronously?
javascript
// Vue 3 scheduler internals (simplified)
const queue: SchedulerJob[] = []
let isFlushing = false
function queueJob(job: SchedulerJob) {
if (!queue.includes(job)) {
queue.push(job)
queueFlush()
}
}
function queueFlush() {
if (!isFlushing) {
isFlushing = true
Promise.resolve().then(flushJobs) // Schedule as microtask
}
}
function flushJobs() {
// Sort by component ID (parent before child)
queue.sort((a, b) => getId(a) - getId(b))
for (const job of queue) {
job() // Execute each component's update function
}
queue.length = 0
isFlushing = false
}
// Practical demonstration:
const count = ref(0)
const name = ref('Alice')
// These three changes happen synchronously
count.value++ // → queueJob(MyComponent.update)
count.value++ // → Job already queued — skipped
name.value = 'Bob' // → Job already queued — skipped
// MyComponent.update runs ONCE after this synchronous block
// It sees: count = 2, name = 'Bob'AVue re-renders immediately on each state change — there is no batching
BVue queues component update jobs in a microtask queue and flushes all updates at once after the current synchronous execution completes
CVue uses requestAnimationFrame to batch updates at 60fps
DVue merges all synchronous state changes into a single change event using a diff algorithm
Sign up free to play
Answer all 10 questions (7 more), see explanations for every answer, and track your score.