手写vue2学习笔记(三) 生成虚拟dom并转换为真实dom

目录

1. 将ast语法树转换为对应得字符串

2. 将对应的字符串使用模板引擎

3. 生成虚拟dom

4. 将虚拟dom转成真实dom 


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算法
    }
}

此为成功创建新元素

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值