
vue2源码
约 5724 字大约 19 分钟
2024-07-20
new Vue的过程
1. 如何认识入口
从源码的 package.json
查看
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",
三个打包命令执行的都是 npm run build
,只不过是参数不同。
查看打包入口:scripts/build.js
,关键路径
// builds是一个包含rollup的打包配置对象的数组,每个配置对象包含入口、出口、插件等信息
let builds = require("./config").getAllBuilds()
// 根据配置对象不同,构建不同版本的Vue库(运行时、Dev、Pro等)
build(builds)
function build() {}
在script/config.js
中我们关注 web-full-cjs-dev
配置对象:
"web-full-cjs-prod": {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.common.prod.js'),
format: 'cjs',
env: 'production',
alias: { he: './entity-decoder' },
banner
},
从这里获取到打包的入口: web/entry-runtime-with-compiler.js
2. 从入口出发
- 从源码的
web/entry-runtime-with-compiler.js
来看:
import Vue from "./runtime/index"
Vue.prototype.$mount = function (el) {}
export default Vue
这里从 引入了Vue
,在原型上定义了$mount
方法
- 接着看
./runtime/index
:
import Vue from "core/index"
import config from "core/config"
import { extend, noop } from "shared/util"
import { mountComponent } from "core/instance/lifecycle"
import { devtools, inBrowser } from "core/util/index"
import {
query,
mustUseProp,
isReservedTag,
isReservedAttr,
getTagNamespace,
isUnknownElement,
} from "web/util/index"
import { patch } from "./patch"
import platformDirectives from "./directives/index"
import platformComponents from "./components/index"
// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
export default Vue
这里做了一下几个事:
- 对
Vue
定义了静态属性config
- 在原型上定义了
$mount、__patch__
方法 - 在
Vue
的options
上定义了默认的指令(v-model、v-show)、组件(Transition、TransitionGroup)
提示
在这里可以知道: 在runtime
版本中已经在原型上定义了$mount
方法,在runtime-with-compiler
版本中重写了该方法。
- 关注
Vue
的来源,接着看core/index
:
import Vue from "./instance/index"
import { initGlobalAPI } from "./global-api/index"
initGlobalAPI(Vue)
Vue.version = "__VERSION__"
export default Vue
这里也是引入了Vue
,对它进行了属性增强,包括初始化全局的 API(delete、set、nextTick、observable、options、use、mixin、extend、directive、filter、component)
- 接着看
./instance/index
import { initMixin } from "./init"
import { stateMixin } from "./state"
import { renderMixin } from "./render"
import { eventsMixin } from "./events"
import { lifecycleMixin } from "./lifecycle"
import { warn } from "../util/index"
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)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
在这里找到Vue
的定义, 他是一个构造函数,在 new
的时候执行了原型上的_init
方法。此外这里还定义了以下几个方面:
- initMixin(Vue): 在原型上定义了
_init
方法 - stateMixin(Vue): 在原型上定义了
$set、$delete、$watch、$data、$props
- eventsMixin(Vue): 在原型上定义了
$on、$emit、$once、$off
- lifecycleMixin(Vue): 在原型上定义了
_update、$forceUpdate、$destory
- renderMixin(Vue): 在原型上定义了
$nextTick、$_render
3. 进入 Vue 构造函数中
此时重点关注_init
方法的执行:进入./init
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== "production" && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== "production") {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// 重点关注
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, "beforeCreate")
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, "created")
/* istanbul ignore if */
if (process.env.NODE_ENV !== "production" && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
// 重点关注
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
这里 通过initLifecycle(vm)、initEvents(vm)、initRender(vm)
来给 vue 实例增加一些属性;之后执行callHook(vm,beforeCreate)
之后可以看出组件中 对数据的初始化顺序: inject->state->provide。state 包含的东西请接着往下看。在对数据进行初始化之后,执行callHook(vm,created)
,最后执行挂载方法vm.$mount(vm.$options.el)
,完成组件渲染。
4. 关注 initState
在上面的文件中,我们关注initState
函数,进入./state
export function initState(vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe((vm._data = {}), true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
这里的逻辑就比较简单了。 通过之前的代码, 可以得出组件中的一些初始化顺序:inject->props->methods->data->computed->watch
。在这个方法中,重点关注其中一个initData
。
function initData(vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== "production" &&
warn(
"data functions should return an object:\n" +
"https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function",
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== "production") {
if (methods && hasOwn(methods, key)) {
warn(`Method "${key}" has already been defined as a data property.`, vm)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== "production" &&
warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
这里关注两个东西:proxy(vm,
_data, key)
和observe(data, true /* asRootData */)
,前者是代理实例 data 数据的访问,后者是对 data 数据进行观察。
这里我们从observe(data)
路线出发:../observer/index
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
export function observe(value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
这里函数式输入一个 data,返回一个 ob,这个 ob 是一个 Observe(后面再看这个东西)类型的数据。这里有个判断逻辑,如果 data 对象上有——ob——
属性,则说明已经被观察了,取出来即可;如果没有的话需要进行观察,通过构造函数 Observe 来增强 data 属性。
此时重点关注 对 data 属性的增强:new Observe(data)
export class Observer {
value: any
dep: Dep
vmCount: number // number of vms that have this object as root $data
constructor(value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, "__ob__", this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk(obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray(items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
在这个类中,我们首先关注构造函数:
- this.dep = new Dep() : 实例有个 dep 属性
- def(value, 'ob', this): 将 data 的ob属性指向该 ob 实例
- this.walk(value): 将 data 作为参数,执行实例的 walk 方法
此时来看 Observer 的原型方法 walk:
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
这里的逻辑页比较简单,直接看 defineReactive
:这里的入参是 data 和 data 的 key
/**
* Define a reactive property on an Object.
*/
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== "production" && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
},
})
}
这里关注几个东西:
- const dep = new Dep():data 的属性被访问时触发 dep 实例 depend 方法;在 data 属性被设置时触发 dep 实例的 notify 方法。这里 dep 实例已经跟 data 属性绑定了起来。
这里我们先去看看这个 dep 实例是什么东西: ./Dep
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
static target: ?Watcher
id: number
subs: Array<Watcher>
constructor() {
this.id = uid++
this.subs = []
}
addSub(sub: Watcher) {
this.subs.push(sub)
}
removeSub(sub: Watcher) {
remove(this.subs, sub)
}
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify() {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== "production" && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
首先看一下他的构造函数:定义了两个属性。dep 的 id 属性和 subs 属性(订阅者),Dep 类有一个 target 属性,这是一个 Watcher 实例(这个马上就会说到)。
在触发 getter 属性时 会触发dep.depend()
,实际执行的是Dep.target.addDep(this)
。在触发 setter 属性时,会触发dep.notify()
,实际执行的是dep.subs[i].update()
。 对 data 的响应式定义就结束了。
此时我们探究一下, 组件是在什么时候会触发 data 的 key 的 getter 的属性。 Watcher 又是一个什么东西。
5. 原型上的 $mount
这里简单说一下 运行时版本和(运行时+编译器版本)的区别:运行时版本是使用的 render
函数选项, 有编译器的是使用的模板语法template
。为方便流程看清重要流程,这里以运行时为例做分析。
- 关注
entry-runtime.js
:platform/web/entry-runtime.js
import Vue from "./runtime/index"
export default Vue
接着看:./runtime/index
import Vue from "core/index"
import { extend, noop } from "shared/util"
import { mountComponent } from "core/instance/lifecycle"
import { devtools, inBrowser } from "core/util/index"
import { patch } from "./patch"
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
export default Vue
这里关注两个点: __patch__
和$mount
的定义:
首先看$mount
:
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
当我们直接使用 render 函数时,以 vue 脚手架的生成的代码为例:
const app = new Vue({
store,
router,
render: (h) => h(App),
})
app.$mount("#app")
最终执行的是mountComponent(this, el, false)
接着看 mountComponent
: core/instance/lifecycle
export function mountComponent(
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== "production") {
/* istanbul ignore if */
if (
(vm.$options.template && vm.$options.template.charAt(0) !== "#") ||
vm.$options.el ||
el
) {
warn(
"You are using the runtime-only build of Vue where the template " +
"compiler is not available. Either pre-compile the templates into " +
"render functions, or use the compiler-included build.",
vm
)
} else {
warn(
"Failed to mount component: template or render function not defined.",
vm
)
}
}
}
callHook(vm, "beforeMount")
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== "production" && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, "beforeUpdate")
}
},
},
true /* isRenderWatcher */
)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, "mounted")
}
return vm
}
这里关注以下几条语句:
callHook(vm, "beforeMount")
let updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, "beforeUpdate")
}
},
},
true /* isRenderWatcher */
)
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, "mounted")
}
关键我们看 new Watcher
时做了些什么事:
- 传入了 vm 实例
- 传入了 updateComponent 函数: 实际执行的是
vm._update(vm._render(), false)
此时看一下 Watcher
: ../observer/watcher
/* @flow */
import {
warn,
remove,
isObject,
parsePath,
_Set as Set,
handleError,
noop,
} from "../util/index"
import { traverse } from "./traverse"
import { queueWatcher } from "./scheduler"
import Dep, { pushTarget, popTarget } from "./dep"
import type { SimpleSet } from "../util/index"
let uid = 0
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
export default class Watcher {
vm: Component
expression: string
cb: Function
id: number
deep: boolean
user: boolean
lazy: boolean
sync: boolean
dirty: boolean
active: boolean
deps: Array<Dep>
newDeps: Array<Dep>
depIds: SimpleSet
newDepIds: SimpleSet
before: ?Function
getter: Function
value: any
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()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get() {
pushTarget(this)
let value
const vm = this.vm
try {
// todo
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
}
/**
* Add a dependency to this directive.
*/
addDep(dep: 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
} else if (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) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} 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' subscriber 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.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
这里主要关注93行: this.get()
,这里执行的实际就是 new Watcher()
传进来的第二个参数 updateComponent
, updateComponent
是在$mount
中定义的:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
从这里来看,需要关注两个东西; vm_update
和 vm_render()
:
- 先看
vm_render()
, 这个是定义在render.js
中的
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
// There's no need to maintain a stack becaues all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== "production" && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(
vm._renderProxy,
vm.$createElement,
e
)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== "production" && Array.isArray(vnode)) {
warn(
"Multiple root nodes returned from render function. Render function " +
"should return a single root node.",
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
这里关注第 22 行的vnode = render.call(vm._renderProxy, vm.$createElement)
, 他是生成 vnode 的关键语句;vm._renderProxy 就是 vm.
回到我们书写 render 函数:
new Vue({
render: (h) => h("div", "style:{}", ["子元素"]),
})
这里的h
就是源码中的vm.$createElement
,这个是在 initRender 函数中定义的:
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
关注 createElement
: ../vdom/create-element
// wrapper function for providing a more flexible interface
// without getting yelled at by flow
export function createElement(
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
这个函数返回的是 _createElement(context, tag, data, children, true)
,这有点尾调用,高阶函数(函数式编程)
接着看_createElement(context, tag, data, children, true)
:
export function _createElement(
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== "production" &&
warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(
data
)}\n` + "Always create fresh vnode data objects in each render!",
context
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (
process.env.NODE_ENV !== "production" &&
isDef(data) &&
isDef(data.key) &&
!isPrimitive(data.key)
) {
if (!__WEEX__ || !("@binding" in data.key)) {
warn(
"Avoid using non-primitive value as key, " +
"use string/number value instead.",
context
)
}
}
// support single function children as default scoped slot
if (Array.isArray(children) && typeof children[0] === "function") {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === "string") {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag),
data,
children,
undefined,
undefined,
context
)
} else if (
(!data || !data.pre) &&
isDef((Ctor = resolveAsset(context.$options, "components", tag)))
) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(tag, data, children, undefined, undefined, context)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
关注 58 行:vnode = new VNode()
,此时来看一个 Vnode 构造函数: ./vnode
export default class VNode {
tag: string | void
data: VNodeData | void
children: ?Array<VNode>
text: string | void
elm: Node | void
ns: string | void
context: Component | void // rendered in this component's scope
key: string | number | void
componentOptions: VNodeComponentOptions | void
componentInstance: Component | void // component instance
parent: VNode | void // component placeholder node
// strictly internal
raw: boolean // contains raw HTML? (server only)
isStatic: boolean // hoisted static node
isRootInsert: boolean // necessary for enter transition check
isComment: boolean // empty comment placeholder?
isCloned: boolean // is a cloned node?
isOnce: boolean // is a v-once node?
asyncFactory: Function | void // async component factory function
asyncMeta: Object | void
isAsyncPlaceholder: boolean
ssrContext: Object | void
fnContext: Component | void // real context vm for functional nodes
fnOptions: ?ComponentOptions // for SSR caching
devtoolsMeta: ?Object // used to store functional render context for devtools
fnScopeId: ?string // functional scope id support
constructor(
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child(): Component | void {
return this.componentInstance
}
}
他就是生成了一个具有一些属性的虚拟节点,到此: vm_render()
看完了, 最终返回的是一个 vnode。
- 回到
vm._update(vm._render(), hydrating)
: 此时我们来看一下vm_update
的定义, 他是在lifecycle
中定义的:
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
关注第 11 行: vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
这里的__patch__
函数是在 platform/runtime/index.js
中定义的
Vue.prototype.__patch__ = inBrowser ? patch : noop
关注patch
函数: ./patch
import * as nodeOps from "web/runtime/node-ops"
import { createPatchFunction } from "core/vdom/patch"
import baseModules from "core/vdom/modules/index"
import platformModules from "web/runtime/modules/index"
// the directive module should be applied last, after all
// built-in modules have been applied.
const modules = platformModules.concat(baseModules)
export const patch: Function = createPatchFunction({ nodeOps, modules })
这里的 modules 是做一些 vnode 的属性(attrs,klass,events,domProps,style,transition),指令等的增强。
createPatchFunction 是来自于:core/vdom/patch
,这是一个好几百行的函数, 最终返回一个函数
export function createPatchFunction() {
// ...
return function patch(oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env.NODE_ENV !== "production") {
warn(
"The client-side rendered virtual DOM tree is not matching " +
"server-rendered content. This is likely caused by incorrect " +
"HTML markup, for example nesting block-level elements inside " +
"<p>, or missing <tbody>. Bailing hydration and performing " +
"full client-side render."
)
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
}
这里的 patch 函数就是对 VDom 做增删改操作,diff 的逻辑在这里。
但是在这里我们关注一下第 16 行的: createElm(vnode, insertedVnodeQueue)
看一下: createElm()
:
function createElm(
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode)
}
vnode.isRootInsert = !nested // for transition enter check
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
if (process.env.NODE_ENV !== "production") {
if (data && data.pre) {
creatingElmInVPre++
}
if (isUnknownElement(vnode, creatingElmInVPre)) {
warn(
"Unknown custom element: <" +
tag +
"> - did you " +
"register the component correctly? For recursive components, " +
'make sure to provide the "name" option.',
vnode.context
)
}
}
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
// in Weex, the default insertion order is parent-first.
// List items can be optimized to use children-first insertion
// with append="tree".
const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
if (!appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
createChildren(vnode, children, insertedVnodeQueue)
if (appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
} else {
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
if (process.env.NODE_ENV !== "production" && data && data.pre) {
creatingElmInVPre--
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
这里我们关注: nodeOps
,向上查找的到这里的 nodeOps 就是:platform/runtime/patch.js
中的 nodeOps,
看一下: nodeOps
的定义:
import { namespaceMap } from "web/util/index"
export function createElement(tagName: string, vnode: VNode): Element {
const elm = document.createElement(tagName)
if (tagName !== "select") {
return elm
}
// false or null will remove the attribute but undefined will not
if (
vnode.data &&
vnode.data.attrs &&
vnode.data.attrs.multiple !== undefined
) {
elm.setAttribute("multiple", "multiple")
}
return elm
}
export function createElementNS(namespace: string, tagName: string): Element {
return document.createElementNS(namespaceMap[namespace], tagName)
}
export function createTextNode(text: string): Text {
return document.createTextNode(text)
}
export function createComment(text: string): Comment {
return document.createComment(text)
}
export function insertBefore(
parentNode: Node,
newNode: Node,
referenceNode: Node
) {
parentNode.insertBefore(newNode, referenceNode)
}
export function removeChild(node: Node, child: Node) {
node.removeChild(child)
}
export function appendChild(node: Node, child: Node) {
node.appendChild(child)
}
export function parentNode(node: Node): ?Node {
return node.parentNode
}
export function nextSibling(node: Node): ?Node {
return node.nextSibling
}
export function tagName(node: Element): string {
return node.tagName
}
export function setTextContent(node: Node, text: string) {
node.textContent = text
}
export function setStyleScope(node: Element, scopeId: string) {
node.setAttribute(scopeId, "")
}
这个文件就定义了 最底层的对 DOM 的操作。由此, 当我们在进行 DOM 操作时就会访问到 vm 上的相关 state(prop、data),此时就会触发数据的getter
访问器,就会将当前前组件对应的watcher
实例记录为该数据的订阅者,当 data 数据变化时就会触发数据的setter
,执行dep.notify()
,这里的dep
收集着很多watcher
实例,最终执行的就是watcher
的update
方法。 这里就又去执行__update__
的逻辑了。
至此, 我们可以算完成了一轮的分析。 对new Vue()
之后生成响应式的 MVVM 视图有了个基本的了解。
看源码, 我能学到什么?
目录文件
文件配置
- editorconfig:为 Python 和 JavaScript 文件设置行尾和缩进样式;取消编辑器和系统之间的差异。
- flowconfig: Flow 是 JavaScript 代码的静态类型检查器。
- *.d.ts: ts的类型声明文件
资源包
- chalk: 终端输出字符的美化库
版权所有
版权归属:tuyongtao1