深圳市住建设局网站,免费个人网站模版下载,自己做软件需要学什么,网站建设类岗位有哪些组件渲染的过程
template -- ast -- render -- vDom -- 真实的Dom -- 页面
Runtime-Compiler和Runtime-Only的区别 - 简书
编译步骤
模板编译是Vue中比较核心的一部分。关于 Vue 编译原理这块的整体逻辑主要分三个部分#xff0c;也可以说是分三步 ast -- render -- vDom -- 真实的Dom -- 页面
Runtime-Compiler和Runtime-Only的区别 - 简书
编译步骤
模板编译是Vue中比较核心的一部分。关于 Vue 编译原理这块的整体逻辑主要分三个部分也可以说是分三步前后关系如下
第一步将模板字符串转换成element ASTs 解析器 parse 第二步对 AST 进行静态节点标记主要用来做虚拟DOM的渲染优化优化器 optimize 第三步使用element ASTs生成render函数代码字符串代码生成器 generate 编译后的AST结构
template 模板
div classboxp{{name}}/p
/div
AST 抽象语法树
ast: {tag: div // 元素标签名type: 1, // 元素节点类型 1标签 2包含字面量表达式的文本节点 3普通文本节点或注释节点staticRoot: false, // 是否静态根节点static: false, // 是否静态节点plain: true, parent: undefined,attrsList: [], // 标签节点的属性名和值的对象集合attrsMap: {}, // 和attrsList类似不同在它是以键值对保存属性名和值children: [{tag: ptype: 1,staticRoot: false,static: false,plain: true,parent: {tag: div, ...},attrsList: [],attrsMap: {},children: [{type: 2,text: {{name}},static: false,expression: _s(name) // type为2时才有这个属性表示表达式的内容}]}]
}
generate将AST转换成可以直接执行的JavaScript字符串
with(this) {return _c(div, [_c(p, [_v(_s(name))]), _v( ), _m(0)])
} 注意一平常开发中 我们使用的是不带编译版本的 Vue 版本runtime-only直接在 options 传入 template 选项 在开发环境报错 注意二这里传入的 template 选项不要和.vue 文件里面的模板搞混淆了 vue 单文件组件的 template 是需要 vue-loader 进行处理的 我们传入的 el 或者 template 选项最后都会被解析成 render 函数 这样才能保持模板解析的一致性
以下代码实现是在在entry-runtime-with-compiler.js里面和runtime-only版本需要区分开
1、模板编译入口
export function initMixin (Vue) {Vue.prototype._init function (options) {const vm this;vm.$options options;initState(vm);// 如果有 el 属性进行模板渲染if (vm.$options.el) {vm.$mount(vm.$options.el)}}Vue.prototype.$mount function (el) {const vm this;const options vm.$options;el document.querySelector(el);// 不存在 render 属性的几种情况if (!options.render) {// 不存在 render 但存在 templatelet template options.template;// 不存在 render 和 template 但是存在 el 属性直接将模板赋值到 el 所在的外层 html 结构 就是 el 本身并不是父元素if (!template el) {template el.outerHTML;}// 最后把处理好的 template 模板转化成 render 函数if (template) {const render compileToFunctions(template);options.render render;}}}
}
先初始化状态initState(vm)再在 initMixin 函数中判断是否有el有则直接调用 vm.$mount(vm.$options.el) 进行模板渲染没有则手动调用在 Vue.prototype.$mount 方法中判断是否存在 render 属性存在则给 template 赋值如果不存在 render 和 template 但存在 el 属性直接将模板赋值到 el 所在的外层 html 结构就是el本身并不是父元素通过 compileToFunctions 将处理好的 template 模板转换成 render 函数。
咱们主要关心$mount 方法 最终将处理好的 template 模板转成 render 函数 2、模板转化核心方法 compileToFunctions
export function compileToFunctions (template) {// html → ast → render函数// 第一步 将 html 字符串转换成 ast 语法树let ast parse(template);// 第二步 优化静态节点if (mergeOptions.optimize ! false) {optimize(ast, options);}// 第三步 通过 ast 重新生成代码let code generate(ast);// 使用 with 语法改变作用域为 this 之后调用 render 函数可以使用 call 改变 this方便 code 里面的变量取值。let renderFn new Function(with(this){return ${code}});return renderFn;
}
html → ast → render函数
这里需要把 html 字符串变成 render 函数分三步
1parse函数 把 HTML 代码转成 AST 语法树 AST 是用来描述代码本身形成的树形结构不仅可以描述 HTML也可以描述 css 和 js 语法很多库都运用到了 AST比如 webpackbabeleslint 等
2 optimize函数 优化静态节点主要用来做虚拟DOM的渲染优化 先遍历 AST对 AST 进行静态节点标记即节点永远不会发生变化并做出一些特殊的处理。例如
移除静态节点的 v-once 指令因为它在这里没有任何意义。移除静态节点的 key 属性。因为静态节点永远不会改变所以不需要 key 属性。将一些静态节点合并成一个节点以减少渲染的节点数量。例如相邻的文本节点和元素节点可以被合并为一个元素节点。
3generate函数 通过 AST 重新生成代码
最后生成的代码需要和 render 函数一样
类似这样的结构
_c(div,{id:app},_c(div,undefined,_v(hello_s(name)),_c(span,undefined,_v(world))))
_c 代表 createElement 创建节点_v 代表 createTextVNode 创建文本节点_s 代表 toString 把对象解析成字符串
模板引擎的实现原理 with new Function使用 with 语法改变作用域为 this 之后调用 render 函数可以使用 call 改变 this方便 code 里面的变量取值。
parse函数解析 html 并生成 ast
// src/compiler/parse.js// 以下为源码的正则 对正则表达式不清楚的同学可以参考小编之前写的文章(前端进阶高薪必看 - 正则篇);
const ncname [a-zA-Z_][\\-\\.0-9_a-zA-Z]*; //匹配标签名 形如 abc-123
const qnameCapture ((?:${ncname}\\:)?${ncname}); //匹配特殊标签 形如 abc:234 前面的abc:可有可无
const startTagOpen new RegExp(^${qnameCapture}); // 匹配标签开始 形如 abc-123 捕获里面的标签名
const startTagClose /^\s*(\/?)/; // 匹配标签结束
const endTag new RegExp(^\\/${qnameCapture}[^]*); // 匹配标签结尾 如 /abc-123 捕获里面的标签名
const attribute /^\s*([^\s\/])(?:\s*()\s*(?:([^]*)|([^]*)|([^\s])))?/; // 匹配属性 形如 idapplet root, currentParent; //代表根节点 和当前父节点
// 栈结构 来表示开始和结束标签
let stack [];
// 标识元素和文本type
const ELEMENT_TYPE 1;
const TEXT_TYPE 3;
// 生成ast方法
function createASTElement(tagName, attrs) {return {tag: tagName,type: ELEMENT_TYPE,children: [],attrs,parent: null,};
}// 对开始标签进行处理
function handleStartTag({ tagName, attrs }) {let element createASTElement(tagName, attrs);if (!root) {root element;}currentParent element;stack.push(element);
}// 对结束标签进行处理
function handleEndTag(tagName) {// 栈结构 []// 比如 divspan/span/div 当遇到第一个结束标签/span时 会匹配到栈顶span元素对应的ast 并取出来let element stack.pop();// 当前父元素就是栈顶的上一个元素 在这里就类似divcurrentParent stack[stack.length - 1];// 建立parent和children关系if (currentParent) {element.parent currentParent;currentParent.children.push(element);}
}// 对文本进行处理
function handleChars(text) {// 去掉空格text text.replace(/\s/g, );if (text) {currentParent.children.push({type: TEXT_TYPE,text,});}
}// 解析标签生成ast核心
export function parse(html) {while (html) {// 查找let textEnd html.indexOf();// 如果在第一个 那么证明接下来就是一个标签 不管是开始还是结束标签if (textEnd 0) {// 如果开始标签解析有结果const startTagMatch parseStartTag();if (startTagMatch) {// 把解析好的标签名和属性解析生成asthandleStartTag(startTagMatch);continue;}// 匹配结束标签/const endTagMatch html.match(endTag);if (endTagMatch) {advance(endTagMatch[0].length);handleEndTag(endTagMatch[1]);continue;}}let text;// 形如 hellodiv/divif (textEnd 0) {// 获取文本text html.substring(0, textEnd);}if (text) {advance(text.length);handleChars(text);}}// 匹配开始标签function parseStartTag() {const start html.match(startTagOpen);if (start) {const match {tagName: start[1],attrs: [],};//匹配到了开始标签 就截取掉advance(start[0].length);// 开始匹配属性// end代表结束符号 如果不是匹配到了结束标签// attr 表示匹配的属性let end, attr;while (!(end html.match(startTagClose)) (attr html.match(attribute))) {advance(attr[0].length);attr {name: attr[1],value: attr[3] || attr[4] || attr[5], //这里是因为正则捕获支持双引号 单引号 和无引号的属性值};match.attrs.push(attr);}if (end) {// 代表一个标签匹配到结束的了 代表开始标签解析完毕advance(1);return match;}}}//截取html字符串 每次匹配到了就往前继续匹配function advance(n) {html html.substring(n);}// 返回生成的astreturn root;
}
generate函数把 ast 转化成 render 函数结构
// src/compiler/codegen.jsconst defaultTagRE /\{\{((?:.|\r?\n)?)\}\}/g; //匹配花括号 {{ }} 捕获花括号里面的内容function gen(node) {// 判断节点类型// 主要包含处理文本核心// 源码这块包含了复杂的处理 比如 v-once v-for v-if 自定义指令 slot等等 咱们这里只考虑普通文本和变量表达式{{}}的处理// 如果是元素类型if (node.type 1) {// 递归创建return generate(node);} else {// 如果是文本节点let text node.text;// 不存在花括号变量表达式if (!defaultTagRE.test(text)) {return _v(${JSON.stringify(text)});}// 正则是全局模式 每次需要重置正则的lastIndex属性 不然会引发匹配buglet lastIndex (defaultTagRE.lastIndex 0);let tokens [];let match, index;while ((match defaultTagRE.exec(text))) {// index代表匹配到的位置index match.index;if (index lastIndex) {// 匹配到的{{位置 在tokens里面放入普通文本tokens.push(JSON.stringify(text.slice(lastIndex, index)));}// 放入捕获到的变量内容tokens.push(_s(${match[1].trim()}));// 匹配指针后移lastIndex index match[0].length;}// 如果匹配完了花括号 text里面还有剩余的普通文本 那么继续pushif (lastIndex text.length) {tokens.push(JSON.stringify(text.slice(lastIndex)));}// _v表示创建文本return _v(${tokens.join()});}
}// 处理attrs属性
function genProps(attrs) {let str ;for (let i 0; i attrs.length; i) {let attr attrs[i];// 对attrs属性里面的style做特殊处理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)}};
}// 生成子节点 调用gen函数进行递归创建
function getChildren(el) {const children el.children;if (children) {return ${children.map((c) gen(c)).join(,)};}
}
// 递归创建生成code
export function generate(el) {let children getChildren(el);let code _c(${el.tag},${el.attrs.length ? ${genProps(el.attrs)} : undefined}${children ? ,${children} : });return code;
}
code 字符串生成 render 函数
export function compileToFunctions (template) {let ast parseHTML(template);let code genCode(ast);// 将模板变成 render 函数通过 with new Function 的方式让字符串变成 JS 语法来执行const render new Function(with(this){return ${code}});return render;
} https://juejin.cn/spost/7267858684667035704
Vue 模板 AST 详解 - 文章教程 - 文江博客
滑动验证页面
手写Vue2.0源码二-模板编译原理技术点评_慕课手记
前端鲨鱼哥 的个人主页 - 文章 - 掘金
前端进阶高薪必看-正则篇 - 掘金
从vue模板解析学习正则表达式