function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? data.call(vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object.',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length
while (i--) {
// data中的字段不能和props中的重复
if (props && hasOwn(props, keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${keys[i]}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else {
// 代理
proxy(vm, keys[i])
}
}
// observe data
observe(data)
data.__ob__ && data.__ob__.vmCount++
}
首先代理data里面的字段:
在vue中通常这样访问一个值
this.msg
而不是
this._data.msg
正是因为proxy(vm, keys[i])已经对key值做了代理,如下:
function proxy (vm: Component, key: string) {
if (!isReserved(key)) {
Object.defineProperty(vm, key, {
configurable: true,
enumerable: true,
get: functionproxyGetter () {
// 访问vm[key]返回的事实上是vm._data[key]
return vm._data[key]
},
set: function proxySetter (val) {
// 设置vm[key]事实上给vm._data[key]赋值
vm._data[key] = val
}
})
}
}
export default class Watcher {
constructor (vm, expOrFn, cb, options) {
this.vm = vm
vm._watchers.push(this)
// options
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.expression = expOrFn.toString()
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()
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
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()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
const value = this.getter.call(this.vm, this.vm)
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
return value
}
/**
* Add a dependency to this directive.
*/
addDep (dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} elseif (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
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) {
process.env.NODE_ENV !== 'production' && warn(
`Error in watcher "${this.expression}"`,
this.vm
)
/* istanbul ignore else */
if (config.errorHandler) {
config.errorHandler.call(null, e, this.vm)
} else {
throw e
}
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subcriber list.
*/
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed or is performing a v-for
// re-render (the watcher list is then filtered by v-for).
if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
get () {
pushTarget(this)
const value = this.getter.call(this.vm, this.vm)
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
return value
}
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) {
process.env.NODE_ENV !== 'production' && warn(
`Error in watcher "${this.expression}"`,
this.vm
)
/* istanbul ignore else */
if (config.errorHandler) {
config.errorHandler.call(null, e, this.vm)
} else {
throw e
}
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
exportfunction initRender (vm: Component) {
// 对于组件适用 其在父树的占位
vm.$vnode = null // the placeholder node in parent tree
// 虚拟dom
vm._vnode = null // the root of the child tree
vm._staticTrees = null
vm._renderContext = vm.$options._parentVnode && vm.$options._parentVnode.context
vm.$slots = resolveSlots(vm.$options._renderChildren, vm._renderContext)
// bind the public createElement fn to this instance
// so that we get proper render context inside it.
// 这就是render函数里面我们传递的那个参数
// 它的作用是生成vnode(虚拟dom)
vm.$createElement = bind(createElement, vm)
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
Vue.prototype._mount = function (el, hydrating) {
const vm = this
vm.$el = el
callHook(vm, 'beforeMount')
vm._watcher = new Watcher(vm, () => {
vm._update(vm._render(), hydrating)
}, noop)
hydrating = false
// root instance, call mounted on self
// mounted is called for child components in its inserted hook
// 假如vm是根实例 那么其$root属性就是其自身
if (vm.$root === vm) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}