vue源码学习——初始化data
vue源码学习——响应式数据
在vue中,我们一直使用的是template模板,而不是真正的html,所以我们才能在template模板中使用各种指令v-if,{ {}}表达式等。但最终template需要经过编译过程,转化为真正的html语言,才能渲染成我们的页面~
Vue官方介绍:
1、Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
2、 在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM操作次数减到最少。
3、如果你熟悉虚拟 DOM 并且偏爱 JavaScript 的原始力量,你也可以不用模板,直接写渲染(render) 函数
,使用可选的 JSX语法。
在new Vue()时,Vue.prototype._init()
方法,最终会调用vm.$mount(vm.$options.el)
进行DOM挂载。Vue.prototype.$mount
在多个文件中都有定义。
web
和weex
,platforms目录 是Vue.js 的入口,2个目录代表了2个主要入口,分别运行在不同平台。Vue.prototype.$mount
方法,但是不带模板编译功能。在【platforms/web/entry-runtime-with-compiler.js】中重新定义了Vue.prototype.$mount
方法加入了模板编译功能(template生成render函数的过程)。web/entry-runtime-with-compiler.js
import {
compileToFunctions } from './compiler/index'
//....
const mount = Vue.prototype.$mount
// 重写了$mount方法,添加了模板编译功能
Vue.prototype.$mount = function (
el?: string | Element, // 挂载的元素,可以是字符串,也可以是 DOM 对象
hydrating?: boolean
): Component {
el = el && query(el) //查找元素
//不能挂载到body,html元素上, 因为挂载点是会被组件模板自身替换点, 显然body/html不能被替换
if (el === document.body || el === document.documentElement) {
return this
}
const options = this.$options
// 检查options是否有render,无则需要编译生成render函数
if (!options.render) {
// 无render方法,把el或template进行编译,生成render方法。
let template = options.template
if (template) {
// 无render,有template,用template内容compile解析
if (typeof template === 'string') {
// template的类型是字符串
if (template.charAt(0) === '#') {
// template是ID
template = idToTemplate(template)
}
} else if (template.nodeType) {
template = template.innerHTML // template的类型是元素节点,则使用该元素的 innerHTML 作为模板
} else {
//template既不是字符串又不是元素节点
return this
}
} else if (el) {
// 无render,无template,那么使用el元素的outerHTML作为模板内容
template = getOuterHTML(el)
}
if (template) {
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
// !!获取转换后的render函数与staticRenderFns,并挂在$options上
const {
render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render // 编译生成的render函数挂载到options
options.staticRenderFns = staticRenderFns
// 非produceiton环境时统计编译器性能, config是全局配置对象
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${
this._name} compile`, 'compile', 'compile end')
}
}
}
// 调用上面公共的mount方法(web/runtime/index.js),options若一开始有render方法,直接到这,不需要编译过程。若一开始无,需要经过编译,生成render,并挂载到options中。
return mount.call(this, el, hydrating)
}
render
方法的判断。判断new Vue(options)中options是否传入了render。$mount
基础方法往下走。compileToFunctions
方法,进行编译,生成render,再赋给options: options.render = render; options.staticRenderFns = staticRenderFns;
compileToFunctions
传入的第一个参数就是模板字符串template,第二个参数是配置选项options。compileToFunctions
最后追溯到是在【src/compiler】目录中定义的,compiler目录里面都是编译相关内容。// plaltforms/web/comipler/index.js
import {
createCompiler } from 'compiler/index'
const {
compile, compileToFunctions } = createCompiler(baseOptions)
compileToFunctions追根溯源
import {
createCompiler } from 'compiler/index'
const {
compile, compileToFunctions } = createCompiler(baseOptions)
export {
compile, compileToFunctions }
compileToFunctions(template,options,vm)
在【src/platform/web/compiler/index.js】目录中定义,这个函数写的过程很绕,但最终是返回了ast、render、staticRenderFns。
createCompiler
在【src/compiler/index.js】定义:
import {
createCompilerCreator } from './create-compiler'
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 1、模板解析
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
// 2、优化
optimize(ast, options)
}
// 3、代码生成
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
declare type ASTNode = ASTElement | ASTText | ASTExpression
元素-ASTElement:
declare type ASTElement = {
type: 1;
tag: string;
attrsList: Array<ASTAttr>;
attrsMap: {
[key: string]: any };
rawAttrsMap: {
[key: string]: ASTAttr };
parent: ASTElement | void;
children: Array<ASTNode>;
//...
};
表达式-ASTExpression:{ {}}
declare type ASTExpression = {
type: 2;
expression: string;
text: string;
tokens: Array<string | Object>;
static?: boolean;
ssrOptimizability?: number;
start?: number;
end?: number;
has$Slot?: boolean
};
文本-ASTText:
declare type ASTText = {
type: 3;
text: string;
static?: boolean;
isComment?: boolean;
ssrOptimizability?: number;
start?: number;
end?: number;
has$Slot?: boolean
};
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
isStaticKey = genStaticKeysCached(options.staticKeys || '')
isPlatformReservedTag = options.isReservedTag || no
// first pass: mark all non-static nodes.
markStatic(root)
// second pass: mark static roots.
markStaticRoots(root, false)
}
markStatic(root)
递归标记每个节点是否为静态节点: node.static = isStatic(node)
,也是为 markStaticRoots 服务的,先把每个节点都处理之后,更方便找静态根节点。markStaticRoots(root, false)
标记区域静态根节点:node.staticRoot = true
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
//核心部分,生成render表达式字符串主体
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${
code}}`, //最外层用with(this)包裹
staticRenderFns: state.staticRenderFns //被标记为 staticRoot 节点的 VNode 就会单独生成 staticRenderFns
}
}
genElement(ast, state)
为生成render表达式字符串的核心函数。<div>
<header>
<h1>I'm a template!h1>
header>
<p v-if="message">{
{ message }}p>
<p v-else>No message.p>
div>
render:
function anonymous(
) {
with(this){
return _c('div',[_m(0),(message)?_c('p',[_v(_s(message))]):_c('p',[_v("No message.")])])}
}
staticRenderFns:
_m(0): function anonymous(
) {
with(this){
return _c('header',[_c('h1',[_v("I'm a template!")])])}
}
render code的大致结构:
_c(
// 1、标签
'div',
//2、数据对象
{
attr:{
...}
...
},
//3、子节点数组,循环其模型
[
_c(...)
]
)
_c
是在initState-initRender中定义,其实是createElement
方法。with(this)
中this指向的是proxy data对象。若code中使用了变量message
,会查找this.message
$options
中无render
函数的vue实例经过compileToFunctions(template,options,vm)
编译完,得到了render,staticRenderFns函数,并挂载到$options
中。继续下面的生成vnode流程。