教程目錄
1.手把手教你從零寫一個簡單的 VUE
2.手把手教你從零寫一個簡單的 VUE--模板篇
Hello,我又回來了,上一次的文章教會了大家如何書寫一個簡單 VUE,里面實現了VUE 的數據驅動視圖渲染模板,更新到頁面的過程,簡單的帶大家了解了類似 VUE 這樣子的數據驅動視圖框架的工作流程,今天我來給大家講一講作為一個前端框架最為核心的部分---模板,代碼還是放在文章的最后,請隨意下載
模板的分類
在介紹我們實現的模板語言之前,我們先來了解下,現在市面上比較流行的模板語言:
PHP/ASP/JSP風格
<%if(list.length ){%> <ol> <%for(n = 0; n < list.length; ++n ){%> <li> <%=list[n]%> </li> <%}%> </ol> <%}%>
這種是最接近於 js 變成語言的語法,比較直觀,但是由於存在< >
的分隔符,對 IDE不太友好,不太好進行格式化處理
mustcache風格
{{#if list.length}} <ol> {{#each list item}} <li> {{item}} </li> {{/each}} </ol> {{/if}}
這種是artTemplate
默認的語法,高級語法有限,通常難自定義拓展
DSL
風格語法
<ol dsl-if="list.length"> <li dsl-for="item in list"> </li> </ol>
首先介紹下什么是DSL
, DSL
全稱是Domain Specific Language/DSL
領域專用語言,其基本思想是求專不求全,用於解決一個類型,一個領域的問題。比如Vue
里面的v-xxx
,Vue
稱之為指令
,其實就是一個DSL,用於解決模板語法等問題,這種模板由於在html
語法里面相當於標簽的屬性,所以對IDE
友好,不會影響格式化操作。
Vue
的模板語法相當於結合了 DSL
語法和 mustcache風格, 邏輯控制部分使用DSL
語法,輸出展示部分使用 mustcache風格
模板引擎設計思路
下面是這個模板引擎的思路:
字符串模板語法定義
首先我們要定義一種模板語法,按照上一節的說明,我們使用DSL風格語法
,下面是我們測試用的模板
我們采用最簡單的將模板寫在script
標簽的配置方式,可以看到我們定義了幾個DSL
,分別是dsl-if
,dsl-for
,dsl-html
,分別用於判斷,循環和直接輸出 html,還有使用mustcache
作為字符串輸出語法。當然這個只是一個簡單的模板DSL
語言,主要為了講解思路,真正的模板需要更加多的模板語法,具體可以參照 VUE
文檔
模板解析成為 AST
首先解釋下什么是AST
,AST 全稱為abstract syntax tree(抽象語法樹)
,是源代碼的抽象語法結構的樹狀表現形式,每種源碼都可以被抽象成為AST
,比如我們常用的 js,css,json 等,都可以解析成為 AST
把模板解析成為AST
,就是將模板的 html 結構進行解析,變成一顆附帶結構、關系、屬性的抽象樹,這樣做方便與后面我們多次對模板進行處理,減少了多次解析字符串帶來的損耗,同時變成一顆樹的數據結構之后更加方便於我們的遍歷,關於AST
的優點缺點大家可以執行搜索,這里就不展開說明了
上面的字符串模板解析完成之后,會變成以下的一個AST
可以看到字符串模板變成了一個object數組,每個 obj 代表一個節點,里面包含了這個 obj 的屬性,類型,父子關系,用到的DSL
等等。這個可以看成是我們的模板的一個中間態,為我們進行進一步處理打下了基礎。
AST 轉換成為 模板函數
聯系上一篇文章,其實模板函數的構造都大同小異,基本是都是通過拼接函數字符串,然后通過Function
對象轉換成一個函數,變成一個函數之后,只要傳入對應的數據,函數就會返回一個模板數據渲染好的 html 字符串。下面是例子中通過AST
這是個函數體,然后使用new Function
,就變成一個真正的函數了,至於這個函數體的解釋,我將放在下面具體實現進行講解
數據與模板函數結合生成 html
由於本文主要是講模板的實現,因此數據部分還是使用延續上一篇文章的綁定,在初始化或者數據發生改變的時候,響應的函數會對數據所關聯的模板函數進行重新調用,生成新的html,重新進行渲染。
模板的開發思路我們就在上面都說明了,主要總結下就是將字符串模板變成 ast,ast 變成模板函數,然后就可以結合數據進行 html 生成及渲染了
具體實現方法
首先說明下本教程的方法是對思路的實現,並非完全使用 vue 的實現方法,vue 是一個完整的框架,里面涉及的東西比較多,我們的實現是為了讓大家更好的了解 vue 的原理,而非完全實現
字符串模板變成AST
部分
1.模板預處理:
由於字符串模板是人為處理的,因此書寫的時候可能會出現標簽不配對,標簽未關閉等問題,因此我們要先做些預處理,來去除這些干擾,做法有很多種,比如通過一些語法分析的工具進行解析,這里我們使用一種比較簡單的方式進行處理,代碼如下(/src/core/render.js
):
可以看到我創建了一個div
標簽,然后將字符串模板放進去里面,這樣子瀏覽器會對模板進行解析處理,然后我們再通過innerHTML
去除前后空格之后拿出來,這樣就對字符串模板進行了處理。
備注:我們按照 vue 的規則,一個模板只有一個根節點,所以我們取了childNodes[0]
2.生成 ast:
上面我們對字符串模板進行了預處理,接下去我們要將字符串模板轉換成ast,代碼比較長,大家有興趣可以看下/src/compiler/ast/parse.js
,下面說下解析思路
解析通過正則表達式配合 String.replace(regExt,fn)
,正則表達式為/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g
,解析出來標簽和標簽上面的屬性,然后按照需求進行存儲,就生成 ast
ast 生成模板函數
生成模板函數的思路就是遞歸遍歷ast 樹,對不同的類型節點,不同的NSL
,調用不同的生成函數,最后組合成為模板函數字符串,代碼如下(/src/compiler/compiler-helper.js):
可以看到,處理的函數對 DSL
還有不同的標簽類型進行處理,然后都返回了一個輔助函數的調用,比如_i,_f,_c
等等,這里的輔助函數是在模板函數被調用的時候才真正的被調用的,下面我們舉例說明一個輔助函數_c
這個輔助函數的功能是用於生成節點,可以看到調用了這個函數之后,對應的 ast 里面的節點被真正生成,變成dom
節點,並且會把孩子節點進行插入,通過很多輔助函數的遞歸嵌套調用,最終模板函數一調用,就可以結合數據渲染出來真實的dom節點
下面說一個比較細的知識點,就是輔助函數的調用,我們知道上面的輔助函數調用在生成的時候,其實都是字符串,然后通過new Function
讓他變成真正的函數,那么問題就來了,我們知道new Function
是的作用域和運行時的代碼是隔離的,是調用不到外面的_c,_f
等輔助函數的,那是如何實現調用的呢,這里用了一個我們很少使用的關鍵字with
,這個關鍵字在很多書籍里面都不推薦使用,因為他的作用是修改with
包含代碼塊的作用域,如果濫用會導致代碼的邏輯不可控,但是在模板函數里面這個關鍵字有奇效,他可以方便的規定把當前的代碼作用域傳到模板函數里面,從而使得模板函數里面可以調用到運行時作用域的函數。大家可以看下上一小節生成的模板函數字符串,會發現就是用整個with(that){}
包裹起來的,在模板函數運行時,將當前作用域直接傳入即可,代碼如下:
結合數據運行
至此,我們已經生成了模板函數,通過傳入數據運行模板函數,就可以生成 dom,代碼如下:
可以我們直接把compiler_helper
附帶上 data 作為作用域,直接調用了模板函數,就可以生成dom,再結合我們第一篇文章寫的數據監聽,就可以實現簡單的數據驅動視圖
后話
至此,我們的VUE模板的基本實現已經介紹完成了,這里主要是介紹如何去實現一個模板引擎的思路,所以功能上上面的實現不是完整的,只是實現了一些簡單的語法,大家可以下下代碼繼續補充。
思考
細心的人可能會發現,我們上面的模板有個問題,就是如果改了數據中的其中一個數值,那么整個模板都得重新編譯,重新渲染,這其實是非常損耗性能的,這其實就是我下一篇文章要講的,模板渲染的效率問題,先提出幾個關鍵詞 虛擬dom,diff 算法,最小化渲染
,吊吊大家的胃口,哈哈,下一篇文章我會進行全面的介紹,相信學習完下一篇文章,大家會對現有市面上的數據驅動框架的模板部分有個全面的了解~下一篇文章更加精彩哦~~求關注
最后附上源碼點我點我,各位客官給個 star 唄~~