您好,登录后才能下订单哦!
Vue.js 是一个渐进式 JavaScript 框架,广泛应用于构建用户界面。在 Vue 中,计算属性(Computed Properties)、方法(Methods)和侦听器(Watchers)是三种常用的数据响应式处理方式。它们各自有不同的使用场景和特点,理解它们的底层实现原理对于深入掌握 Vue 的工作原理至关重要。
本文将深入分析 Vue 中计算属性、方法和侦听器的源码实现,帮助开发者更好地理解它们的内部机制。
计算属性是 Vue 中用于处理复杂逻辑的一种方式。它们是基于它们的依赖进行缓存的,只有在依赖发生变化时才会重新计算。这使得计算属性在处理复杂逻辑时非常高效。
在 Vue 的源码中,计算属性的实现主要位于 src/core/instance/state.js
文件中。计算属性的核心逻辑是通过 defineComputed
函数来实现的。
function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
在这个函数中,createComputedGetter
是计算属性的核心逻辑。它会创建一个 getter 函数,这个 getter 函数会在计算属性被访问时执行。
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
在这个 getter 函数中,watcher.evaluate()
会执行计算属性的逻辑,并将结果缓存起来。如果依赖发生变化,watcher.dirty
会被设置为 true
,从而在下一次访问时重新计算。
计算属性的缓存机制是通过 Watcher
类来实现的。每个计算属性都会创建一个 Watcher
实例,这个 Watcher
实例会监听计算属性的依赖变化。
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
evaluate () {
this.value = this.get()
this.dirty = false
}
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
}
在 Watcher
类中,lazy
属性用于标识是否是计算属性的 Watcher
。如果是计算属性的 Watcher
,lazy
会被设置为 true
,并且在初始化时不会立即执行 get
方法,只有在计算属性被访问时才会执行。
计算属性的依赖收集是通过 Watcher
的 get
方法来实现的。在 get
方法中,pushTarget(this)
会将当前的 Watcher
实例设置为 Dep.target
,然后执行计算属性的逻辑。在执行过程中,所有被访问的响应式数据都会将当前的 Watcher
实例添加到它们的依赖列表中。
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
在 get
方法执行完毕后,popTarget()
会将 Dep.target
恢复到之前的状态。
方法是 Vue 组件中定义的函数,通常用于处理用户交互或其他逻辑。与计算属性不同,方法不会缓存结果,每次调用时都会重新执行。
在 Vue 的源码中,方法的实现相对简单。方法的定义是通过 initMethods
函数来实现的,这个函数位于 src/core/instance/state.js
文件中。
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
if (typeof methods[key] !== 'function') {
warn(
`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
在这个函数中,bind(methods[key], vm)
会将方法绑定到 Vue 实例上,确保在方法中可以通过 this
访问 Vue 实例。
方法的执行是通过 Vue 实例直接调用的。由于方法不会缓存结果,每次调用时都会重新执行。
export function bind (fn: Function, ctx: Object): Function {
return function boundFn (a) {
const l = arguments.length
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a)
: fn.call(ctx)
}
}
在 bind
函数中,fn.apply(ctx, arguments)
会将方法的上下文绑定到 Vue 实例上。
侦听器是 Vue 中用于监听数据变化的一种方式。当监听的数据发生变化时,侦听器会执行相应的回调函数。侦听器通常用于处理异步操作或复杂逻辑。
在 Vue 的源码中,侦听器的实现也是通过 Watcher
类来实现的。侦听器的定义是通过 initWatch
函数来实现的,这个函数位于 src/core/instance/state.js
文件中。
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
在 initWatch
函数中,createWatcher
函数会为每个侦听器创建一个 Watcher
实例。
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${expOrFn}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
在 $watch
方法中,new Watcher(vm, expOrFn, cb, options)
会创建一个 Watcher
实例。这个 Watcher
实例会监听 expOrFn
的变化,并在变化时执行 cb
回调函数。
侦听器的依赖收集也是通过 Watcher
的 get
方法来实现的。在 get
方法中,pushTarget(this)
会将当前的 Watcher
实例设置为 Dep.target
,然后执行侦听器的逻辑。在执行过程中,所有被访问的响应式数据都会将当前的 Watcher
实例添加到它们的依赖列表中。
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
在 get
方法执行完毕后,popTarget()
会将 Dep.target
恢复到之前的状态。
当侦听器监听的数据发生变化时,Watcher
的 update
方法会被调用,从而触发回调函数的执行。
export default class Watcher {
// ...
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
}
在 run
方法中,this.cb.call(this.vm, value, oldValue)
会执行侦听器的回调函数,并将新值和旧值作为参数传递给回调函数。
通过对 Vue 中计算属性、方法和侦听器的源码分析,我们可以更好地理解它们的内部机制。计算属性通过 Watcher
类实现了依赖收集和缓存机制,方法通过简单的函数绑定实现了逻辑处理,而侦听器则通过 Watcher
类实现了数据变化的监听和回调执行。
理解这些底层原理不仅有助于我们更好地使用 Vue,还能帮助我们在遇到问题时快速定位和解决问题。希望本文的分析能够帮助读者更深入地理解 Vue 的工作原理。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。