简要解读Vue.js源码-数据驱动


简要解读Vue.js源码-数据驱动

Vue源码的学习-数据驱动

new Vue发生了什么

初始化

在Vue2项目中,main文件可以看到

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

在源码中

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) // 这里调用初始化函数
}	
初始化做了什么:

初始化函数会初始化生命周期,props,computed,watcher,data等,还会初始化渲染,初始化中心事件

挂载

在init函数最后会判断如果存在el属性,就会调用vm.$mount挂载vm,挂载的目的就是将模本渲染为最终的DOM

Vue挂载的实现

首先mount方法里面对参数el做了限制,限制了Vue的原型不能挂载在body,html这样的根节点上

最关键的是,当没有定义render方法的时候会将template和el字符串转换为render方法.在vue2中任何的组件最终都会转化为render方法.最后调用原型上的$mount方法进行挂载

$mount调用mountComponent

mountCompoent核心就是调用vm.render方法先生成虚拟Node,在实例化一个Watcher,最后调用updateComponent更新DOM,最终调用vm._update更新DOM。

let updateComponent
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)
}
// 调用updateCompoent然后调用vm.update方法来进行更新
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
watcher在这里起什么作用
  • 初始化的时候执行回调函数
  • vm实例中监测的数据发生变化的时候执行回调函数
总结

在*$mount*中最重要的方法就是mountComponet,然后里面最核心的方法就是vm._render和vm._update

render

作用

_render是实例的一个私有的方法,他的作用是将实例渲染为一个虚拟Node

Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
// reset _rendered flag on slots for duplicate slot check
if (process.env.NODE_ENV !== 'production') {
for (const key in vm.$slots) {
// $flow-disable-line
vm.$slots[key]._rendered = false
}
}
if (_parentVnode) {
vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
}
// 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 {
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') {
if (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
}
} else {
vnode = vm._vnode
}
}
// 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
}

这段代码中最关键的就是render的调用,在平时手写render方法很少,一般都是直接写templete模板,然后在vue源码中会调用mount方法将templete转化为render方法

$createElement

render函数的第一个参数就是createElement

<div id="app">
{{ message }}
</div>

等同于

render: function (createElement) {
return createElement('div', {
attrs: {
id: 'app'
},
}, this.message)
}

再回到 _render 函数中的 render ⽅法的调⽤:

 vnode = render.call(vm._renderProxy, vm.$createElement)

可以看到, render 函数中的 createElement ⽅法就是 vm.$createElement ⽅法:

export function initRender (vm: Component) {
// ...
// bind the createElement fn to this instance
33
render
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}
总结

总结

vm._render 最终是通过执⾏ createElement ⽅法并返回的是 vnode ,它是⼀个虚拟 Node。Vue

2.0 相⽐ Vue 1.0 最⼤的升级就是利⽤了 Virtual DOM。因此在分析 createElement 的实现前,我们

先了解⼀下 Virtual DOM 的概念。

Virtual DOM

Virtual DOM 有什么作用

因为在浏览器中Virtual DOM 是很复杂的,如果我们频繁去更新Virtual DOM 是十分消耗性能的.

VIrtual DOM是什么

但是Virtual DOM是使用原生的js对象来描述一个DOM节点,在Vue中Virual DOM是用一个叫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
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
}
}

Vue的虚拟DOM是借鉴开源库snabbdom

总结

由于 VNode只是⽤来映射到真实 DOM 的渲染,不需要包含操作 DOM 的⽅法,因此它是⾮常轻量和简单的

VNode 的 create、diff、patch 等过程。那么在 Vue.js 中,VNode 的 create 是通过之前提到的 createElement ⽅法创建的,我们接下来分析这部分的实现。

createElement

作用

VNode是怎么创建的?Vue是利用createElement方法来创建VNode

createElement函数是如何实现的,如何创建VNode的

createElement其实是对 _createElement的封装,是为了能传入参数的时候更加灵活,真正创建的VNode的函数是 _createElement函数

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 (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()
}
}

_createElement接收5个参数,这里就说几个稍微重要一些的,context表示上下文,tag表示标签,为字符串,也可以是一个component,data表示VNode的数据

createElement流程很复杂,但是最重要的还是children的规范化VNode的创建

children的规范化

Virtual DOM是一个树状结构,是由多个VNode节点组成,每一个VNode节点也有许多小节点

然后前面说到_createElement接收了5个参数,第4个参数接收的children,我们要将children也转化为VNode节点

总结

我们了解到createElement创建VNode的过程,每个 VNode 有children , children 每个元素也是⼀个 VNode,这样就形成了⼀个 VNode Tree,它很好的描述了我们的 DOM Tree。

update

Vue的_update是实例的一个私有的方法,只有在首次渲染,数据更新的时候被调用,我们这里先说首次渲染被调用的时候

_update的作用就是吧VNode渲染为真实的DOM

update的核心就是调用 vm. patch_ 方法,但是也要看在什么环境下面,当在服务端渲染的时候,其实是不需要进行转换的,因为服务端渲染中没真实的DOM

但是具体vm._patch_是如何实现的,这里不深入了解,但是在createPatchFunction 有个很值得学习的地方,这里createPacthFunction回调了一个patch函数,巧妙的实现了函数柯里化减少了调用patch函数传入参数的操作

update其实是替换掉原来的DOM,这也是为什么在写的时候要用new Vue({

}).$mount(‘#app’)

不能挂载到body,html这些根节点上

总结

在这里插入图片描述

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值