為了解析上標籤中的屬性,我們新建/src/ast/parseAttr.ts文件,並寫下以下代碼:
export default (attrsString: string) => {
const result = {};
if (!attrsString) return result;
};
這個方法接收的attrsString來自上標籤扣掉標籤名以外的部分,比方說可能會是這種格式:
class="ccc" style="color: pink;" href="https://www.youtube.com/channel/UCEQfDopfJepHUrdirQw5wlw"
我們可以從中看出一些規律:
再次強調,雙引號與雙引號之間的屬性的值是有可能出現空格的,因此沒辦法直接用split(' ')區隔屬性。我們需要遍歷這個屬性字串,挑出雙引號外的所有空格,以這些雙引號外的空格為準區分所有屬性。
因此解析屬性有三個關鍵:
說起遍歷,首先我們需要一個指針。然後我們還需要一個變數,來記錄目前遍歷到的字是否處在雙引號之間。
let inQuotation = false;
let point = 0;
然後就是慣例的遍歷了。
// 遍歷各標籤屬性,依序將其整理成物件形式
for (let i = 0; i < attrsString.length; i++) {
const char = attrsString[i]
if (char === '"') inQuotation = !inQuotation
else if (char === ' ' && !inQuotation) {
Object.assign(result, generateAttr(attrsString.substring(point, i)))
point = i
}
}
Object.assign(result, generateAttr(attrsString.substring(point)))
return result
只要看到雙引號",就切換inQuotation;只要是在!inQuotation的情況下看到空格,就代表我們剛看完一個屬性,這時就取出point到i之間的內容,之後會寫一個generateAttr函數來把這段內容加工成一個對象。把剛遍歷到的屬性家工成對象後併入result,遍歷完整段屬性字串後將result返回,就是我們要的屬性對象了。
我們再接著寫能把單一屬性字串加工成對象的generateAttr。
const generateAttr = (attrString: string) => {
attrString = attrString.trim();
if (attrString[0] === ':') attrString = parseAttrData(attrString);
const attrArr = attrString.match(/^(.+)="(.+)"$/);
const result: Record<string, any> = {};
if (attrArr[1] === 'style') {
result.style = parseStyles(attrArr[2]);
} else result[attrArr[1]] = attrArr[2];
return result;
};
generateAttr接收的參數attrString是單一屬性的字串,大部分的屬性,像是class或href,都只要把屬性的種類作為key,屬性的內容作為值,就能加工成一個代表屬性的對象,譬如class="some-class"可以直接變成{ class: 'some-class' },不需要額外做點甚麼,就是最單純的情況。
但如果是v-bind之後的屬性,例如:class或:style,就需要做一些額外處理;一般的style也需要做額外處理,因為我們在虛擬dom上期望的style也是一個對象,譬如{ style: { color: 'pink', border: '1px solid black' } }。
所以我們之後再寫一個parseAttrData來處理v-bind的屬性,以及parseStyles來處理style的情況。剩下的代碼就很單純,用正則表達式把()="()"括號內的東西拆開,組成對象即可。
如此一來我們便完成了解析屬性大部分的工作,明天我們會再著手進行v-bind及style的解析,完成最後的任務。
github - [Day 27]ast抽象語法樹 - 4——解析屬性