好傢伙,書接上回
在上一篇Vue原始碼學習(二):<templete>渲染第一步,模板解析中,我們完成了模板解析
現在我們繼續,將模板解析的轉換為ast語法樹
程式碼已開源https://github.com/Fattiger4399/analytic-vue.git手動偵錯一遍,
勝過我解釋給你聽一萬遍
function start(tag, attrs) { //開始標籤
console.log(tag, attrs, '開始的標籤')
}
function charts(text) { //獲取文字
console.log(text, '文字')
}
function end(tag) { //結束的標籤
console.log(tag, '結束標籤')
}
在這裡,我們知道start,charts,end分別可以拿到
我們的`開始標籤`,`文字`,`結束標籤`
效果如下:(仔細看,這也是我們實驗要用到的例子)
隨後我們開始改造這幾個方法
確定我們ast樹節點的結構:
let root; //根元素
let createParent //當前元素的父親
let stack = []
function createASTElement(tag, attrs) {
return {
tag,
attrs,
children: [],
type: 1,
parent: null
}
}
節點元素分別為
function start(tag, attrs) { //開始標籤
let element = createASTElement(tag, attrs) //生成一個開始標籤元素
//檢視root根元素是否為空
//若是,將該元素作為根
//非原則
if (!root) {
root = element
}
createParent = element
stack.push(element)
console.log(tag, attrs, '開始的標籤')
}
此處,生成一個開始標籤元素,判斷root是否為空,若為空,則將該元素作為根元素
隨後將該元素作為父元素.
function charts(text) { //獲取文字
console.log(text, '文字')
// text = text.replace(/a/g,'')
if(text){
createParent.children.push({
type:3,
text
})
}
// console.log(stack,'stack')
}
這個好理解,將"文字內容"作為父元素的孩子
function end(tag) { //結束的標籤
let element = stack.pop()
createParent = stack[stack.length - 1]
if (createParent) { //元素閉合
element.parent = createParent.tag
createParent.children.push(element)
}
console.log(tag, '結束標籤')
}
此處,我們先將棧stack最新的元素彈出棧(作為當前元素,我們要對他進行操作),
隨後獲取棧的前一個元素作為父元素,
當前元素的父元素屬性指向父元素的標籤屬性
隨後將該元素推入父元素的children中,
emmmm,我還是說人話吧
假設現在stack=['div','h1']
然後pop了,createParent = 'h1'
'h1'.parent =>'div'
'div'.children =>'h1'
(多看幾遍就理解了,其實非常簡單)
來看看最終實現的ast語法樹長什麼樣子
(父子關係和諧)
搞定啦!
const attribute =
/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
//屬性 例如: {id=app}
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; //標籤名稱
const qnameCapture = `((?:${ncname}\\:)?${ncname})` //<span:xx>
const startTagOpen = new RegExp(`^<${qnameCapture}`) //標籤開頭
const startTagClose = /^\s*(\/?)>/ //匹配結束標籤 的 >
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) //結束標籤 例如</div>
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
let root; //根元素
let createParent //當前元素的父親
let stack = []
function createASTElement(tag, attrs) {
return {
tag,
attrs,
children: [],
type: 1,
parent: null
}
}
function start(tag, attrs) { //開始標籤
let element = createASTElement(tag, attrs) //生成一個開始標籤元素
//檢視root根元素是否為空
//若是,將該元素作為根
//非原則
if (!root) {
root = element
}
createParent = element
stack.push(element)
console.log(tag, attrs, '開始的標籤')
}
function charts(text) { //獲取文字
console.log(text, '文字')
// text = text.replace(/a/g,'')
if(text){
createParent.children.push({
type:3,
text
})
}
// console.log(stack,'stack')
}
function end(tag) { //結束的標籤
let element = stack.pop()
createParent = stack[stack.length - 1]
if (createParent) { //元素閉合
element.parent = createParent.tag
createParent.children.push(element)
}
console.log(tag, '結束標籤')
}
export function parseHTML(html) {
while (html) { //html 為空時,結束
//判斷標籤 <>
let textEnd = html.indexOf('<') //0
// console.log(html,textEnd,'this is textEnd')
if (textEnd === 0) { //標籤
// (1) 開始標籤
const startTagMatch = parseStartTag() //開始標籤的內容{}
if (startTagMatch) {
start(startTagMatch.tagName, startTagMatch.attrs);
continue;
}
// console.log(endTagMatch, '結束標籤')
//結束標籤
let endTagMatch = html.match(endTag)
if (endTagMatch) {
advance(endTagMatch[0].length)
end(endTagMatch[1])
continue;
}
}
let text
//文字
if (textEnd > 0) {
// console.log(textEnd)
//獲取文字內容
text = html.substring(0, textEnd)
// console.log(text)
}
if (text) {
advance(text.length)
charts(text)
// console.log(html)
}
}
function parseStartTag() {
//
const start = html.match(startTagOpen) // 1結果 2false
// console.log(start,'this is start')
// match() 方法檢索字串與正規表示式進行匹配的結果
// console.log(start)
//建立ast 語法樹
if (start) {
let match = {
tagName: start[1],
attrs: []
}
// console.log(match,'match match')
//刪除 開始標籤
advance(start[0].length)
//屬性
//注意 多個 遍歷
//注意>
let attr //屬性
let end //結束標籤
//attr=html.match(attribute)用於匹配
//非結束位'>',且有屬性存在
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
// console.log(attr,'attr attr'); //{}
// console.log(end,'end end')
match.attrs.push({
name: attr[1],
value: attr[3] || attr[4] || attr[5]
})
advance(attr[0].length)
//匹配完後,就進行刪除操作
}
//end裡面有東西了(只能是有">"),那麼將其刪除
if (end) {
// console.log(end)
advance(end[0].length)
return match
}
}
}
function advance(n) {
// console.log(html)
// console.log(n)
html = html.substring(n)
// substring() 方法返回一個字串在開始索引到結束索引之間的一個子集,
// 或從開始索引直到字串的末尾的一個子集。
// console.log(html)
}
// console.log(root)
return root
}