Vue源码学习之代码实现生成原理及render函数执行准备

第一步

在我们有了这样一棵语法树之后,我们需要将这棵树进行代码生成,生成如下格式:

// 生成树用的的DOM<div id="app" style="background:pink;color:aqua"><div style="color:red"> {{name}} hello {{age}}</div><span> word </span></div> 
render() {return _c('div', { id: 'app' }, _c('div', { style: { color: 'red' } }, _v(_s(name) + 'hello'),_c('span', null, _v(_s(age) + 'hello'))))
} 

这样我们就调用codegen()方法来生成对应的代码,核心思想就是字符串拼接,直接上代码:

// src/compiler/index

import { parseHTML } from "./parse";
// 生成孩子
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // 匹配到的内容就是表达式的变量
function gen(node) {// 判断是文本还是元素if (node.type === 1) {// 为1为元素return codegen(node)} else {// 文本 有可能是{{name}}hello 或者只有{{name}}等各种格式let text = node.text.trim()if (!defaultTagRE.test(text)) {return `_v(${JSON.stringify(text)})`} else {// _v( _s(name)+'helool'+_s(name)) s是JSON.stringifylet tokens = []let match// 用了exec并且正则中有g,他就从lastindex开始向后寻找,所以每次运行需要先重置一下lastindexdefaultTagRE.lastIndex = 0let lastIndex = 0while (match = defaultTagRE.exec(text)) {let index = match.index //匹配的位置 {{name}} hello {{name}}tokens.push(`_s(${match[1].trim()})`)if (index > lastIndex) {tokens.push(JSON.stringify(text.slice(lastIndex, index)))}lastIndex = index = match[0].length}if (lastIndex < text.length) {tokens.push(JSON.stringify(text.slice(lastIndex)))}return `_v(${tokens.join('+')})`}}
}
// 处理孩子
function genChildren(children) {if (children) {return children.map(child => gen(child)).join(',')}
}
// 处理属性
function genProps(attrs) {let str = '' //{name,value}for (let i = 0; i < attrs.length; i++) {let attr = attrs[i]if (attr.name === 'style') {// background:pink => {background:pink}let obj = {}attr.value.split(';').forEach(item => { // qs库或者正则表达式也可以处理let [key, value] = item.split(':')obj[key] = value});attr.value = obj}str += `${attr.name}:${JSON.stringify(attr.value)},` // a:b,c:d,}return `{${str.slice(0, -1)}}`
}
// 代码生成
function codegen(ast) {let children = genChildren(ast.children)let code = `_c('${ast.tag}',${ast.attrs.length > 0 ? genProps(ast.attrs) : 'null'}${ast.children.length ? `,${children}` : ''})`return code
}

export function compileToFcuntion(template) {......
} 

第二步

通过第一步的操作我们就成功将ast语法树拼接成了字符串代码code

// src/compiler/index

export function compileToFcuntion(template) {// 1.将template转换为ast语法树let ast = parseHTML(template)// 2.生成render方法 render方法执行后的结果就是虚拟DOMlet code = codegen(ast)code = `with(this){return ${code}}`let render = new Function(code) // 根据代码生成render函数return render
} 

:所有的模板引擎实现的原理都是 with + new Function

// init.js

 Vue.prototype.$mount = function (el) { ...... mountCompontent(vm,el) // 组建的挂载
 } 
// src/index.js
import { initLifeCycle } from "./lifecycle";
......
initLifeCycle(Vue)
...... 
// src/lifecycle.js

xport function initLifeCycle(Vue) {Vue.prototype._update = function () {console.log('update');}Vue.prototype._render = function () {console.log('render');}
}
export function mountCompontent(vm, el) {// 1.调用render方法产生虚拟节点 虚拟DOMvm._update(vm._render())// vm._render() // vm.$options.render()虚拟节点// vm._update(vm._render()) 把虚拟节点变成真实节点// 2. 根据虚拟DOM产生真实DOM// 3. 插入到el元素中
} 

mountCompontent()方法中第一步就是调用render方法产生虚拟节点,第二步根据虚拟DOM产生真实DOM,第三步插入到el元素中。如何去做源码中用了两个方法vm._render()执行返回虚拟节点和vm._update()执行后将虚拟节点变为真实DOM,那接下来就扩展这两个方法,需要在src/index.js中进行扩展。

最后

总结一下vue的核心流程:

  • 创造力响应式数据
  • 将模板转换成ast语法树
  • 将ast语法树转化成render函数
  • 后续每次数据更新可以只执行render函数,无需再次执行ast转化过程
  • render函数会去产生虚拟节点(使用响应式数据)
  • 根据生成的虚拟节点创造真实的DOM

最后就是调用render函数产生虚拟节点变成真实DOM,这个放到下一篇写,持续学习,加油!

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值