предисловие
В этой статье в основном написан исходный код Vue2.0 от руки.Принцип составления шаблона
В прошлой статье мы в основном представили данные Vue.Отзывчивые принципыДля промежуточного и продвинутого интерфейса принцип отзывчивости — это в основном базовый класс исходного кода, который необходимо протестировать для интервью Vue.Если он не очень понятен, его в основном пропустят.Так что сегодняшний принцип компиляции рукописного шаблона также является сравнение интервью Vue.частоОдин из пунктов и сложность выше, чем адаптивный принцип.В основном это включает в себя аст и большое количество регулярных сопоставлений.После обучения вы можете посмотреть карту разума и написать ее вместе, чтобы углубить свое впечатление.
Для людей:Учащиеся, у которых нет времени смотреть официальный исходный код или которые запутались и не хотят смотреть исходный код
Предложение: Студенты, которые хотят изучать регулярные выражения, могут прочитать эту статью Xiaobian.Front-end расширенный высокооплачиваемый обязательный к просмотру — обычные статьи
текст
// Vue实例化
new Vue({
el: "#app",
data() {
return {
a: 111,
};
},
// render(h) {
// return h('div',{id:'a'},'hello')
// },
// template:`<div id="a">hello</div>`
});
Вы должны быть знакомы с приведенным выше кодом.Согласно диаграмме жизненного цикла, приведенной на официальном сайте, мы можем вручную настроить шаблон или рендеринг в параметрах, которые мы передаем.
Примечание 1. В обычной разработке мы используем версию Vue без скомпилированной версии (только для времени выполнения) и напрямую передаем параметр шаблона в параметрах и сообщаем об ошибке в среде разработки.
Примечание 2: параметр шаблона, переданный здесь, не следует путать с шаблоном в файле .vue.Шаблон однофайлового компонента vue должен быть обработан vue-loader
Параметры el или шаблона, которые мы передаем, в конечном итоге будут проанализированы функцией рендеринга, чтобы поддерживать согласованность синтаксического анализа шаблона.
1. Порт компиляции шаблона
// src/init.js
import { initState } from "./state";
import { compileToFunctions } from "./compiler/index";
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
// 这里的this代表调用_init方法的对象(实例对象)
// this.$options就是用户new Vue的时候传入的属性
vm.$options = options;
// 初始化状态
initState(vm);
// 如果有el属性 进行模板渲染
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
// 这块代码在源码里面的位置其实是放在entry-runtime-with-compiler.js里面
// 代表的是Vue源码里面包含了compile编译功能 这个和runtime-only版本需要区分开
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
// 如果不存在render属性
if (!options.render) {
// 如果存在template属性
let template = options.template;
if (!template && el) {
// 如果不存在render和template 但是存在el属性 直接将模板赋值到el所在的外层html结构(就是el本身 并不是父元素)
template = el.outerHTML;
}
// 最终需要把tempalte模板转化成render函数
if (template) {
const render = compileToFunctions(template);
options.render = render;
}
}
};
}
В основном мы заботимся о методе $mount и, наконец, преобразуем обработанный шаблон шаблона в функцию рендеринга.
2. Базовый метод преобразования шаблонов compileToFunctions
// src/compiler/index.js
import { parse } from "./parse";
import { generate } from "./codegen";
export function compileToFunctions(template) {
// 我们需要把html字符串变成render函数
// 1.把html代码转成ast语法树 ast用来描述代码本身形成树结构 不仅可以描述html 也能描述css以及js语法
// 很多库都运用到了ast 比如 webpack babel eslint等等
let ast = parse(template);
// 2.优化静态节点
// 这个有兴趣的可以去看源码 不影响核心功能就不实现了
// if (options.optimize !== false) {
// optimize(ast, options);
// }
// 3.通过ast 重新生成代码
// 我们最后生成的代码需要和render函数一样
// 类似_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))
// _c代表创建元素 _v代表创建文本 _s代表文Json.stringify--把对象解析成文本
let code = generate(ast);
// 使用with语法改变作用域为this 之后调用render函数可以使用call改变this 方便code里面的变量取值
let renderFn = new Function(`with(this){return ${code}}`);
return renderFn;
}
Новая папка компилятора представляет функции, связанные с компиляцией. Ядро экспортирует функцию compileToFunctions. Существует три основных шага: 1. Генерация ast 2. Оптимизация статических узлов 3. Генерация функции рендеринга в соответствии с ast
3. Разобрать 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"'=<>`]+)))?/; // 匹配属性 形如 id="app"
let 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) {
// 栈结构 []
// 比如 <div><span></span></div> 当遇到第一个结束标签</span>时 会匹配到栈顶<span>元素对应的ast 并取出来
let element = stack.pop();
// 当前父元素就是栈顶的上一个元素 在这里就类似div
currentParent = 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) {
// 把解析好的标签名和属性解析生成ast
handleStartTag(startTagMatch);
continue;
}
// 匹配结束标签</
const endTagMatch = html.match(endTag);
if (endTagMatch) {
advance(endTagMatch[0].length);
handleEndTag(endTagMatch[1]);
continue;
}
}
let text;
// 形如 hello<div></div>
if (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);
}
// 返回生成的ast
return root;
}
Используйте обычную совпадающую строку html, чтобы встретить начальный тег, конечный тег и анализ текста, сгенерировать соответствующий ast и установить соответствующую ассоциацию родитель-потомок. Непрерывное продвижение перехватывает оставшиеся строки до тех пор, пока весь html не будет проанализирован. Здесь мы в основном пишем о начале Обработка атрибутов --parseStartTag
4. Перегенерируйте код в соответствии с ast
// src/compiler/codegen.js
const 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属性 不然会引发匹配bug
let 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里面还有剩余的普通文本 那么继续push
if (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;
}
После получения сгенерированного ast вам нужно преобразовать ast во что-то вроде _c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c ('span',undefined,_v("world")))) такая строка
5. функция рендеринга генерации строки кода
export function compileToFunctions(template) {
let code = generate(ast);
// 使用with语法改变作用域为this 之后调用render函数可以使用call改变this 方便code里面的变量取值 比如 name值就变成了this.name
let renderFn = new Function(`with(this){return ${code}}`);
return renderFn;
}
6. Интеллект-карта составления шаблона
резюме
На данный момент принцип компиляции шаблона Vue завершен. Вы можете посмотреть на интеллект-карту и написать основной код самостоятельно. Следует отметить, что в этой статье используется много сращивания строк и обычные знания. Если вы не понимаете, вы можно прочитать больше информации Добро пожаловать Комментарий
Наконец, если вы найдете эту статью полезной, помнитеКак СанлианБольшое спасибо!
Ссылка на сериал (будет обновлена позже)
- Написанный от руки исходный код Vue2.0 (1) — принцип адаптивных данных
- Рукописный исходный код Vue2.0 (2) - принцип компиляции шаблона
- Рукописный исходный код Vue2.0 (3) — принцип начального рендеринга
- Рукописный исходный код Vue2.0 (четыре) — принцип обновления рендеринга
- Рукописный исходный код Vue2.0 (5) - принцип асинхронного обновления
- Рукописный исходный код Vue2.0 (6) - принцип алгоритма diff
- Рукописный исходный код Vue2.0 (7) - Принцип микширования
- Рукописный исходный код Vue2.0 (8)-компонентный принцип
- Рукописный исходный код Vue2.0 (9) - принцип свойства прослушивания
- Рукописный исходный код Vue2.0 (10) — Принцип вычисляемых свойств
- Рукописный исходный код Vue2.0 (11) — принцип глобального API
- Самые полные вопросы интервью Vue + подробные ответы
- Рукописный исходный код vue-router
- Рукописный исходный код vuex
- Рукописный исходный код vue3.0
Группа передовых рыболовных технологий Brother Shark
Приветствую всех на технических биржахСвязь