目录
1. 将ast语法树转换为对应得字符串
通过上篇文章,我们已经生成了ast语法树。现在需要将语法树转换为对应得字符串格式,后生成random方法,对应格式如下:
_c('tag',{attribute},_v(_s()/children.....)
_c为元素,_v为文本,_s为变量也就是大括号表达式内得值,里面可以嵌套_c就也是其子元素
首先为codegen方法,通过这个方法我们生成对应得字符串格式。先生成_c的标签名tag和属性
function codegen(ast) {
let code = `_c('${ast.tag}',${ast.attrs.length > 0 ? genProps(ast.attrs) : null})`
return code
}
如果属性不为空那么就调用genProps方法对属性进行转换
function genProps(attrs) {
let str = '';
for (let i = 0;i < attrs.length;i++) {
let attr = attrs[i]
// 将style的格式变为style : {key:value}
if(attr.name === 'style') {
let obj = {}
attr.value.split(";").forEach(item => {
let [key,value] = item.split(":")
obj[key] = value
})
attr.value = obj
}
str += `${attr.name} : ${JSON.stringify(attr.value)},`
}
return `{${str.slice(0,-1)}}`
}
对于属性以key:value的形式拼接,如果为style属性,那么需要对其就是封装一层,要不然会变成以下这种情况:style : background-color : red, color : yellow;
对于子节点进行转换
转换时需要区分,1. 是什么类型的子元素,上文中我们将ast语法树分为了元素类型和文本类型,那么我们需要分别对这两种类型进行处理,2. 如果是文本类型,含不含有大括号表达式,对于不含有和含有的分别要做不同的处理
function getReg () {
return /\{\{((?:.|\r?\n)+?)\}\}/g;
}
const defaultReg = /\{\{((?:.|\r?\n)+?)\}\}/g;
function gen(node) {
if(node.type === 1) {
return codegen(node)
}else {
let text = node.text
if(getReg().test(text)) {
// 处理 hello {{name}} hello的情况
let tokens = []
let lastIndex = 0
let match
while (match = defaultReg.exec(text)) {
let index = match.index
if(index > lastIndex) {
tokens.push(JSON.stringify(text.slice(lastIndex,index)))
}
tokens.push(`_s(${match[1].trim()})`)
lastIndex = match.index + match[0].length
}
if(lastIndex < text.length) {
tokens.push(JSON.stringify(text.slice(lastIndex)))
}
return `_v(${tokens.join('+')})`
}else {
return `_v(${JSON.stringify(text)})`
}
}
}
function genChildren(el) {
return el.map(child => gen(child)).join(',')
}
function codegen(ast) {
let children = genChildren(ast.children)
let code = `_c('${ast.tag}',${ast.attrs.length > 0 ? genProps(ast.attrs) : null},
${ast.children.length > 0 ? children : ''})`
return code
}
2. 将对应的字符串使用模板引擎
对应的字符串使用模板引擎的方式,生成一个函数,该函数的格式为:with+new Function,主要目的就是为了取值时方便
export function compileToFunction(template) {
// 1. 将template转化为ast语法树
let ast = parseHTML(template);
//2. 生成random方法,方法执行后返回的就是虚拟DOM
let code = codegen(ast);
code = `with(this) {return ${code}}`
const random = new Function(code)
return random
}
3. 生成虚拟dom
在init.js文件内,$mount方法里,使用一个方法
mountComponent(vm,el)
这个方法的作用就是,1.生成的函数执行得到虚拟DOM
2.根据虚拟DOM生成真实DOM
3.挂载到el上
执行以上两步需要在Vue上面扩展两个方法,1.vm._rander()用来执行vm.$options.rander()
2.vm._update(vm._rander())用来将虚拟DOM生成为真实DOM
// 在index.js内
initLifeCycle(Vue)
//在lifeCycle.js内
export function initLifeCycle(Vue) {
Vue.prototype._random = function () {
console.log("random");
}
Vue.prototype._update = function () {
console.log("update");
}
}
首先执行第一步,生成虚拟dom大概需要三步
(1)通过_random调用vm.$optioms.random.call(this)
(2)需要三个方法,c,v,s
(3)创建一个虚拟节点的包VNode,有两个方法createElementVNode(vm,tag,date,..children)创建虚拟元素节点,createTextVNode(vm,text),创建虚拟元素节点
(1)(2)步
Vue.prototype._c = function () {
return createElementVNode(this,...arguments)
}
Vue.prototype._v = function () {
return createTextVNode(this,...arguments)
}
Vue.prototype._s = function (value) {
if(typeof value !== 'object') return value
return JSON.stringify(value)
}
Vue.prototype._random = function () {
return this.$options.random.call(this)
}
(3)步
export function createElementVNode(vm,tag,data,...children) {
if(data == null) data = {}
let key = data.key
if(key) delete data.key
return VNode(vm,tag,key,data,children)
}
export function createTextVNode(vm,text) {
return VNode(vm,undefined,undefined,undefined,undefined,text)
}
function VNode (vm,tag,key,data,children,text) {
return {
vm,
tag,
key,
data,
children,
text
}
}
这样子就成功生成了虚拟dom
VNode和ast语法树很类似,都是把语法或属性抽象为一个对象,不同的是ast描述的是语法本身而虚拟dom是描述的dom,可以增加一些自定义的属性。
4. 将虚拟dom转成真实dom
(1)需要patch(el,VNode)方法,
patch 方法既有初始化的功能又有更新的功能
patch内逻辑,先找到挂载元素el判断是否为真实元素,是那么就获取真实元素并拿到父元素,不是那就需要diff算法
(2)通过createElm(VNode) 创建真实元素
(3)
将新的元素放到老元素下面然后删掉老元素
function patchProps(el,props) {
for (const key in props) {
if(key === 'style') {
for (const styleName in props.style) {
el.style[styleName] = props.style[styleName]
}
}else {
el.setAttribute(key,props[key])
}
}
}
function createElm(VNode) {
let {tag,data,children,text} = VNode
if(typeof tag == 'string') {
VNode.el = document.createElement(tag)
// 弄一个方法去添加属性
if(data) patchProps(VNode.el,data)
// 循环递归添加子元素
children.forEach(child => {
VNode.el.appendChild(createElm(child))
})
}else {
// 真实元素挂载到VNode上面与虚拟节点相对应
VNode.el = document.createTextNode(text)
}
return VNode.el
}
function patch(oldVNode,VNode) {
let isRealDom = oldVNode.nodeType
if(isRealDom) {
let elm = oldVNode
let parentElm = elm.parentNode
let newElm = createElm(VNode)
// 将新的元素放到老元素下面然后删掉老元素
parentElm.insertBefore(newElm,elm.nextSibling)
parentElm.removeChild(elm)
}else {
// 使用diff算法
}
}
此为成功创建新元素