国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

構(gòu)建一個(gè)使用 Virtual-DOM 的前端模版引擎

imccl / 2320人閱讀

摘要:目錄前言問(wèn)題的提出模板引擎和結(jié)合的實(shí)現(xiàn)編譯原理相關(guān)模版引擎的詞法分析語(yǔ)法分析與抽象語(yǔ)法樹代碼生成完整的結(jié)語(yǔ)前言本文嘗試構(gòu)建一個(gè)前端模板引擎,并且把這個(gè)引擎和進(jìn)行結(jié)合。于是就構(gòu)思了一個(gè)方案,在前端模板引擎上做手腳。

作者:戴嘉華

轉(zhuǎn)載請(qǐng)注明出處并保留原文鏈接( https://github.com/livoras/blog/issues/14 )和作者信息。

目錄

前言

問(wèn)題的提出

模板引擎和 Virtual-DOM 結(jié)合 —— Virtual-Template

Virtual-Template 的實(shí)現(xiàn)

4.1 編譯原理相關(guān)

4.2 模版引擎的EBNF

4.3 詞法分析

4.4 語(yǔ)法分析與抽象語(yǔ)法樹

4.5 代碼生成

完整的 Virtual-Template

結(jié)語(yǔ)

1. 前言

本文嘗試構(gòu)建一個(gè) Web 前端模板引擎,并且把這個(gè)引擎和 Virtual-DOM 進(jìn)行結(jié)合。把傳統(tǒng)模板引擎編譯成 HTML 字符串的方式改進(jìn)為編譯成 Virtual-DOM 的 render 函數(shù),可以有效地結(jié)合模板引擎的便利性和 Virtual-DOM 的性能。類似 ReactJS 中的 JSX。

閱讀本文需要一些關(guān)于 ReactJS 實(shí)現(xiàn)原理或者 Virtual-DOM 的相關(guān)知識(shí),可以先閱讀這篇博客:深度剖析:如何實(shí)現(xiàn)一個(gè) Virtual DOM 算法 , 進(jìn)行相關(guān)知識(shí)的了解。

同時(shí)還需要對(duì)編譯原理相關(guān)知識(shí)有基本的了解,包括 EBNF,LL(1),遞歸下降的方法等。

2. 問(wèn)題的提出

本人在就職的公司維護(hù)一個(gè)比較樸素的系統(tǒng),前端渲染有兩種方式:

后臺(tái)直接根據(jù)模板和數(shù)據(jù)直接把頁(yè)面吐到前端。

后臺(tái)只吐數(shù)據(jù),前端用前端模板引擎渲染數(shù)據(jù),動(dòng)態(tài)塞到頁(yè)面。

當(dāng)數(shù)據(jù)狀態(tài)變更的時(shí)候,前端用 jQuery 修改頁(yè)面元素狀態(tài),或者把局部界面用模板引擎重新渲染一遍。當(dāng)頁(yè)面狀態(tài)很多的時(shí)候,用 jQuery 代碼中會(huì)就混雜著很多的 DOM 操作,編碼復(fù)雜且不便于維護(hù);而重新渲染雖然省事,但是會(huì)導(dǎo)致一些性能、焦點(diǎn)消失的問(wèn)題(具體可以看這篇博客介紹)。

因?yàn)榱?xí)慣了 MVVM 數(shù)據(jù)綁定的編碼方式,對(duì)于用 jQuery 選擇器修改 wordings 等細(xì)枝末節(jié)的勞力操作個(gè)人感覺(jué)不甚習(xí)慣。于是就構(gòu)思能否在這種樸素的編碼方式上做一些改進(jìn),解放雙手,提升開發(fā)效率。其實(shí)只要加入數(shù)據(jù)狀態(tài) -> 視圖的 one-way data-binding 開發(fā)效率就會(huì)有較大的提升。

而這種已經(jīng)在運(yùn)作多年的多人維護(hù)系統(tǒng),引入新的 MVVM 框架并不是一個(gè)非常好的選擇,在兼容性和風(fēng)險(xiǎn)規(guī)避上大家都有諸多的考慮。于是就構(gòu)思了一個(gè)方案,在前端模板引擎上做手腳。可以在幾乎零學(xué)習(xí)成本的情況下,做到 one-way data-binding,大量減少 jQuery DOM 操作,提升開發(fā)效率。

3. 模板引擎和 Virtual-DOM 結(jié)合 —— Virtual-Template

考慮以下模板語(yǔ)法:

{title}

    {each users as user i}
  • NO.{i + 1} - {user.name} {if user.isAdmin} I am admin {elseif user.isAuthor} I am author {else} I am nobody {/if}
  • {/each}

這只一個(gè)普通的模板引擎語(yǔ)法(類似 artTemplate),支持循環(huán)語(yǔ)句(each)、條件語(yǔ)句(if elseif else ..)、和文本填充({...}), 應(yīng)該比較容易看懂,這里就不解釋。當(dāng)用下面數(shù)據(jù)渲染該模板的時(shí)候:

var data = {
  title: "Users List",
  users: [
    {id: "user0", name: "Jerry", isAdmin: true},
    {id: "user1", name: "Lucy", isAuthor: true},
    {id: "user2", name: "Tomy"}
  ]
}

會(huì)得到下面的 HTML 字符串:

Users List

  • NO.1 - Jerry I am admin
  • NO.2 - Lucy I am author
  • NO.3 - Tomy I am nobody

把這個(gè)字符串塞入文檔當(dāng)中就可以生成 DOM 。但是問(wèn)題是如果數(shù)據(jù)變更了,例如data.titleUsers List修改成Users,你只能用 jQuery 修改 DOM 或者直接重新渲染一個(gè)新的字符串塞入文檔當(dāng)中。

然而我們可以參考 ReactJS 的 JSX 的做法,不把模板編譯成 HTML, 而是把模板編譯成一個(gè)返回 Virtual-DOM 的 render 函數(shù)。render 函數(shù)會(huì)根據(jù)傳入的 state 不同返回不一樣的 Virtual-DOM ,然后就可以根據(jù) Virtual-DOM 算法進(jìn)行 diff 和 patch:

// setup codes
// ...

var render = template(tplString) // template 把模板編譯成 render 函數(shù)而不是 HTML 字符串
var root1 = render(state1) // 根據(jù)初始狀態(tài)返回的 virtual-dom

var dom = root.render() // 根據(jù) virtual-dom 構(gòu)建一個(gè)真正的 dom 元素
document.body.appendChild(dom)

var root2 = render(state2) // 狀態(tài)變更,重新渲染另外一個(gè) virtual-dom
var patches = diff(root1, root2) // virtual-dom 的 diff 算法
patch(dom, patches) // 更新真正的 dom 元素

這樣做好處就是:既保留了原來(lái)模板引擎的語(yǔ)法,又結(jié)合了 Virtual-DOM 特性:當(dāng)狀態(tài)改變的時(shí)候不再需要 jQuery 了,而是跑一遍 Virtual-DOM 算法把真正的 DOM 給patch了,達(dá)到了 one-way data-binding 的效果,總結(jié)流程就是:

先把模板編譯成一個(gè) render 函數(shù),這個(gè)函數(shù)會(huì)根據(jù)數(shù)據(jù)狀態(tài)返回 Virtual-DOM

用 render 函數(shù)構(gòu)建 Virtual-DOM;并根據(jù)這個(gè) Virtual-DOM 構(gòu)建真正的 DOM 元素,塞入文檔當(dāng)中

當(dāng)數(shù)據(jù)變更的時(shí)候,再用 render 函數(shù)渲染一個(gè)新的 Virtual-DOM

新舊的 Virtual-DOM 進(jìn)行 diff,然后 patch 已經(jīng)在文檔中的 DOM 元素

(恩,其實(shí)就是一個(gè)類似于 JSX 的東西)

這里重點(diǎn)就是,如何能把模板語(yǔ)法編譯成一個(gè)能夠返回 Virtual-DOM 的 render 函數(shù)?例如上面的模板引擎,不再返回 HTML 字符串了,而是返回一個(gè)像下面那樣的 render 函數(shù):

function render (state) {
  return el("div", {}, [
    el("h1", {}, [state.title]),
    el("ul", {}, state.users.map(function (user, i) {
       return el("li", {"class": "user-item"}, [
         el("img", {"src": "/avatars/" + user.id}, []),
         el("span", {}, ["No." + (i + 1) + " - " + user.name],
         (user.isAdmin 
           ? "I am admin"
           : uesr.isAuthor 
             ? "I am author"
             : "")
       ])
    }))
  ])
}

前面的模板和這個(gè) render 函數(shù)在語(yǔ)義上是一樣的,只要能夠?qū)崿F(xiàn)“模板 -> render 函數(shù)”這個(gè)轉(zhuǎn)換,就可以跑上面所說(shuō)的 Virtual-DOM 的算法流程,這樣就把模板引擎和 Virtual-DOM結(jié)合起來(lái)。為了方便起見,這里把這個(gè)結(jié)合體稱為 Virtual-Template 。

4. Virtual-Template 的實(shí)現(xiàn)

網(wǎng)上關(guān)于模板引擎的實(shí)現(xiàn)原理介紹非常多。如果語(yǔ)法不是太復(fù)雜的話,可以直接通過(guò)對(duì)語(yǔ)法標(biāo)簽和代碼片段進(jìn)行分割,識(shí)別語(yǔ)法標(biāo)簽內(nèi)的內(nèi)容(循環(huán)、條件語(yǔ)句)然后拼裝代碼,具體可以參考這篇博客。其實(shí)就是正則表達(dá)式使用和字符串的操作,不需要對(duì)語(yǔ)法標(biāo)簽以外的內(nèi)容做識(shí)別。

但是對(duì)于和 HTML 語(yǔ)法已經(jīng)差別較大的模板語(yǔ)法(例如 Jade ),單純的正則和字符串操作已經(jīng)不夠用了,因?yàn)槠湔Z(yǔ)法標(biāo)簽以外的代碼片段根本不是合法的 HTML 。這種情況下一般需要編譯器相關(guān)知識(shí)發(fā)揮用途:模板引擎本質(zhì)上就是把一種語(yǔ)言編譯成另外一種語(yǔ)言。

而對(duì)于 Virtual-Template 的情況,雖然其除了語(yǔ)法標(biāo)簽以外的代碼都是合法的 HTML 字符串,但是我們的目的是把它編譯成返回 Virtual-DOM 的 render 函數(shù),在構(gòu)建 Virtual-DOM 的時(shí)候,你需要知道元素的 tagName、屬性等信息,所以就需要對(duì) HTML 元素本身做識(shí)別。

因此 Virtual-Template 也需要借助編譯原理(編譯器前端)相關(guān)的知識(shí),把一種語(yǔ)言(模板語(yǔ)法)編譯成另外一種語(yǔ)言(一個(gè)叫 render 的 JavaScript 函數(shù))。

4.1 編譯原理相關(guān)

CS 本科都教過(guò)編譯原理,本文會(huì)用到編譯器前端的一些概念。在實(shí)現(xiàn)模板到 render 函數(shù)的過(guò)程中,要經(jīng)過(guò)幾個(gè)步驟:

詞法分析:把輸入的模板分割成詞法單元(tokens stream)

語(yǔ)法分析:讀入 tokens stream ,根據(jù)文法規(guī)則轉(zhuǎn)化成抽象語(yǔ)法樹(Abstract Syntax Tree)

代碼生成:遍歷 AST,生成 render 函數(shù)體代碼

所以這個(gè)過(guò)程可以分成幾個(gè)主要模塊:tokenizer(詞法分析器),parser(語(yǔ)法分析器),codegen(代碼生成)。在此之前,還需要對(duì)模板的語(yǔ)法做文法定義,這是構(gòu)建詞法分析和語(yǔ)法分析的基礎(chǔ)。

4.2 模板引擎的 EBNF

在計(jì)算機(jī)領(lǐng)域,對(duì)某種語(yǔ)言進(jìn)行語(yǔ)法定義的時(shí)候,幾乎都會(huì)用到 EBNF(擴(kuò)展的巴科斯范式)。在定義模板引擎的語(yǔ)法的時(shí)候,也可以用到 EBNF。Virtual-Template 擁有非常簡(jiǎn)單的語(yǔ)法規(guī)則,支持上面所提到的 each、if 等語(yǔ)法:

{each users as user i }
 
{user.name}
... {/each} {if user.isAdmin} ... {elseif user.isAuthor} ... {elseif user.isXXX} ... {/if}

對(duì)于 {user.name} 這樣的表達(dá)式插入,可以簡(jiǎn)單地看成是字符串,在代碼生成的時(shí)候再做處理。這樣我們的詞法和語(yǔ)法分析就會(huì)簡(jiǎn)化很多,基本只需要對(duì) each、if、HTML 元素進(jìn)行處理。

Virtual-Template 的 EBNF

Stat -> Frag Stat | ε
Frag -> IfStat | EachStat | Node | text

IfStat -> "{if ...}" Stat {ElseIf} [Else] "{/if}"
ElseIf -> "{elseif ...}" Stat
Else -> "{else}" Stat|e

EachStat -> "{each ...}" Stat "{/each}"

Node -> OpenTag NodeTail
OpenTag -> "/[w-d]+/" {Attr}
NodeTail -> ">" Stat "/<[wd]+>/" | "/>"

Attr -> "/[w-d]/+" Value
Value -> "=" "/"[sS]+"/" | ε

可以把該文法轉(zhuǎn)換成 LL(1) 文法,方便我們寫遞歸下降的 parser。這個(gè)語(yǔ)法還是比較簡(jiǎn)單的,沒(méi)有出現(xiàn)復(fù)雜的左遞歸情況。簡(jiǎn)單進(jìn)行展開和提取左公因子消除沖突獲得下面的 LL(1) 文法。

LL(1) 文法:

Stat -> Frag Stat | ε
Frag -> IfStat | EachStat | Node | text

IfStat -> "{if ...}" Stat ElseIfs Else "{/if}"
ElseIfs -> ElseIf ElseIfs | ε
ElseIf -> "{elseif ...}" Stat
Else -> "{else}" Stat | ε

EachStat -> "{each ...}" Stat "{/each}"

Node -> OpenTag NodeTail
OpenTag -> "/[w-d]+/" Attrs
NodeTail -> ">" Stat "/<[wd]+>/" | "/>"

Attrs -> Attr Attrs | ε 
Attr -> "/[w-d]/+" Value
Value -> "=" "/"[sS]+"/" | ε
4.3 詞法分析

根據(jù)上面獲得的 EBNF ,單引號(hào)包含的都是非終結(jié)符,可以知道有以下幾種詞法單元:

module.exports = {
  TK_TEXT: 1, // 文本節(jié)點(diǎn)
  TK_IF: 2, // {if ...}
  TK_END_IF: 3, // {/if}
  TK_ELSE_IF: 4, // {elseif ...}
  TK_ELSE: 5, // {else}
  TK_EACH: 6, // {each ...}
  TK_END_EACH: 7, // {/each}
  TK_GT: 8, // >
  TK_SLASH_GT: 9, // />
  TK_TAG_NAME: 10, // " }
{ type: 1, label: "NO." }
{ type: 1, label: "{i + 1} - " }
{ type: 1, label: "{user.name}" }
{ type: 13, label: "" }
{ type: 2, label: "{if user.isAdmin}" }
{ type: 1, label: "I am admin
        " }
{ type: 4, label: "{elseif user.isAuthor}" }
{ type: 1, label: "I am author
        " }
{ type: 5, label: "{else}" }
{ type: 1, label: "I am nobody
        " }
{ type: 3, label: "{/if}" }
{ type: 13, label: "" }
{ type: 7, label: "{/each}" }
{ type: 13, label: "" }
{ type: 13, label: "
" } { type: 100, label: "$" } 4.4 語(yǔ)法分析與抽象語(yǔ)法樹

拿到 tokens 以后就可以就可以按順序讀取 token,根據(jù)模板的 LL(1) 文法進(jìn)行語(yǔ)法分析。語(yǔ)法分析器,也就是 parser,一般可以采取遞歸下降的方式來(lái)進(jìn)行編寫。LL(1) 不允許語(yǔ)法中有沖突( conflicts ),需要對(duì)文法中的產(chǎn)生式求解 FIRST 和 FOLLOW 集。

FIRST(Stat) = {TK_IF, TK_EACH, TK_TAG_NAME, TK_TEXT}
FOLLOW(Stat) = {TK_ELSE_IF, TK_END_IF, TK_ELSE, TK_END_EACH, TK_CLOSE_TAG, TK_EOF}
FIRST(Frag) = {TK_IF, TK_EACH, TK_TAG_NAME, TK_TEXT}
FIRST(IfStat) = {TK_IF}
FIRST(ElseIfs) = {TK_ELSE_IF}
FOLLOW(ElseIfs) = {TK_ELSE, TK_ELSE}
FIRST(ElseIf) = {TK_ELSE_IF}
FIRST(Else) = {TK_ELSE}
FOLLOW(Else) = {TK_END_IF}
FIRST(EachStat) = {TK_EACH}
FIRST(OpenTag) = {TK_TAG_NAME}
FIRST(NodeTail) = {TK_GT, TK_SLASH_GT}
FIRST(Attrs) = {TK_ATTR_NAME}
FOLLOW(Attrs) = {TK_GT, TK_SLASH_GT}
FIRST(Value) = {TK_ATTR_EQUAL}
FOLLOW(Value) = {TK_ATTR_NAME, TK_GT, TK_SLASH_GT}

上面只求出了一些必要的 FIRST 和 FOLLOW 集,對(duì)于一些不需要預(yù)測(cè)的產(chǎn)生式就省略求解了。有了 FIRST 和 FOLLOW 集,剩下的編寫遞歸下降的 parser 只是填空式的體力活。

var Tokenizer = require("./tokenizer")
var types = require("./tokentypes")

function Parser (input) {
  this.tokens = new Tokenizer(input)
  this.parse()
}

var pp = Parser.prototype

pp.is = function (type) {
  return (this.tokens.peekToken().type === type)
}

pp.parse = function () {
  this.tokens.index = 0
  this.parseStat()
  this.eat(types.TK_EOF)
}

pp.parseStat = function () {
  if (
    this.is(types.TK_IF) ||
    this.is(types.TK_EACH) ||
    this.is(types.TK_TAG_NAME) ||
    this.is(types.TK_TEXT)
  ) {
    this.parseFrag()
    this.parseStat()
  } else {
    // end
  }
}

pp.parseFrag = function () {
  if (this.is(types.TK_IF)) return this.parseIfStat()
  else if (this.is(types.TK_EACH)) return this.parseEachStat()
  else if (this.is(types.TK_TAG_NAME)) return this.parseNode()
  else if (this.is(types.TK_TEXT)) {
    var token = this.eat(types.TK_TEXT)
    return token.label
  } else {
    this.parseError("parseFrag")
  }
}

// ...

完整的 parser 可以查看 parser.js。

抽象語(yǔ)法樹(Abstract Syntax Tree)

遞歸下降進(jìn)行語(yǔ)法分析的時(shí)候,可以同時(shí)構(gòu)建模版語(yǔ)法的樹狀表示結(jié)構(gòu)——抽象語(yǔ)法樹,模板語(yǔ)法有以下的抽象語(yǔ)法樹的節(jié)點(diǎn)類型:

Stat: {
    type: "Stat"
    members: [IfStat | EachStat | Node | text, ...]
}

IfStat: {
    type: "IfStat"
    label: ,
    body: Stat
    elifs: [ElseIf, ...]
    elsebody: Stat
}

ElseIf: {
    type: "ElseIf"
    label: ,
    body: Stat
}

EachStat: {
    type: "EachStat"
    label: ,
    body: Stat
}

Node: {
    type: "Node"
    name: ,
    attributes: ,
    body: Stat
}

因?yàn)?JavaScript 語(yǔ)法的靈活性,可以用字面量的 JavaScript 對(duì)象和數(shù)組直接表示語(yǔ)法樹的樹狀結(jié)構(gòu)。語(yǔ)法樹構(gòu)的建過(guò)程可以在語(yǔ)法分析階段同時(shí)進(jìn)行。最后,可以獲取到如下圖的語(yǔ)法樹結(jié)構(gòu):

完整的語(yǔ)法樹構(gòu)建過(guò)程,可以查看 parser.js 。

從模版字符串到 tokens stream 再到 AST ,這個(gè)過(guò)程只需要對(duì)文本進(jìn)行一次掃描,整個(gè)算法的時(shí)間復(fù)雜度為 O(n)。

至此,Virtual-Template 的編譯器前端已經(jīng)完成了。

4.5 代碼生成

JavaScript 從字符串中構(gòu)建一個(gè)新的函數(shù)可以直接用 new Function 即可。例如:

var newFunc = new Function("a", "b", "return a + b")
newFunc(1, 2) // => 3

這里需要通過(guò)語(yǔ)法樹來(lái)還原 render 函數(shù)的函數(shù)體的內(nèi)容,也就是 new Function 的第三個(gè)參數(shù)。

拿到模版語(yǔ)法的抽象語(yǔ)法樹以后,生成相應(yīng)的 JavaScript 函數(shù)代碼就很好辦了。只需要地對(duì)生成的 AST 進(jìn)行深度優(yōu)先遍歷,遍歷的同時(shí)維護(hù)一個(gè)數(shù)組,這個(gè)數(shù)組保存著 render 函數(shù)的每一行的代碼:

function CodeGen (ast) {
  this.lines = []
  this.walk(ast)
  this.body = this.lines.join("
")
}

var pp = CodeGen.prototype

pp.walk = function (node) {
  if (node.type === "IfStat") {
    this.genIfStat(node)
  } else if (node.type === "Stat") {
    this.genStat(node)
  } else if (node.type === "EachStat") {
    ...
  }
  ...
}

pp.genIfStat = function (node) {
  var expr = node.label.replace(/(^{s*ifs*)|(s*}$)/g, "")
  this.lines.push("if (" + expr + ") {")
  if (node.body) {
    this.walk(node.body)
  }
  if (node.elseifs) {
    var self = this
    _.each(node.elseifs, function (elseif) {
      self.walk(elseif)
    })
  }
  if (node.elsebody) {
    this.lines.push(indent + "} else {")
    this.walk(node.elsebody)
  }
  this.lines.push("}")
}

// ...

CodeGen 類接受已經(jīng)生成的 AST 的根節(jié)點(diǎn),然后 this.walk(ast) 會(huì)對(duì)不同的節(jié)點(diǎn)類型進(jìn)行解析。例如對(duì)于 IfStat 類型的節(jié)點(diǎn):

{ 
  type: "IfStat",
  label: "{if user.isAdmin}"
  body: {...}
  elseifs: [{...}, {...}, {...}],
  elsebody: {...}
}

genIfStat 會(huì)把 "{if user.isAdmin}" 中的 user.isAdmin 抽離出來(lái),然后拼接 JavaScript 的 if 語(yǔ)句,push 到 this.lines 中:

var expr = node.label.replace(/(^{s*ifs*)|(s*}$)/g, "")
this.lines.push("if (" + expr + ") {")

然后會(huì)遞歸的對(duì) elseifselsebody 進(jìn)行遍歷和解析,最后給 if 語(yǔ)句補(bǔ)上 }。所以如果 elseifselsebody 都不存在,this.lines 上就會(huì)有:

["if (user.isAdmin) {", , "}"]

其它的結(jié)構(gòu)和 IfStat 同理的解析和拼接方式,例如 EachStat:

pp.genEachStat = function (node) {
  var expr = node.label.replace(/(^{s*eachs*)|(s*}$)/g, "")
  var tokens = expr.split(/s+/)
  var list = tokens[0]
  var item = tokens[2]
  var key = tokens[3]
  this.lines.push(
    "for (var " + key + " = 0, len = " + list + ".length; " + key + " < len; " + key + "++) {"
  )
  this.lines.push("var " + item + " = " + list + "[" + key + "];")
  if (node.body) {
    this.walk(node.body)
  }
  this.lines.push("}")
}

最后遞歸構(gòu)造完成以后,this.lines.join(" ") 就把整個(gè)函數(shù)的體構(gòu)建起來(lái):

if (user.isAdmin) {
...
}

for (var ...) {
...
}

這時(shí)候 render 函數(shù)的函數(shù)體就有了,直接通過(guò) new Function 構(gòu)建 render 函數(shù):

var code = new CodeGen(ast)
var render = new Function("el", "data", code.body)

el 是需要注入的構(gòu)建 Virtual-DOM 的構(gòu)建函數(shù),data 需要渲染的數(shù)據(jù)狀態(tài):

var svd = require("simple-virtual-dom")
var root = render(svd.el, {users: [{isAdmin: true}]})

從模版 -> Virtual-DOM 的 render 函數(shù) -> Virtual-DOM 的過(guò)程就完成了。完整的代碼生成的過(guò)程可以參考:codegen.js

5. 完整的 Virtual-Template

其實(shí)拿到 render 函數(shù)以后,每次手動(dòng)進(jìn)行 diff 和 patch 都是重復(fù)操作。可以把 diff 和 patch 也封裝起來(lái),只暴露一個(gè) setData 的 API 。每次數(shù)據(jù)變更的時(shí)候,只需要 setData 就可以更新到 DOM 元素上(就像 ReactJS 的 setState):

// vTemplate.compile 編譯模版字符串,返回一個(gè)函數(shù)
var usersListTpl = vTemplate.compile(tplStr)

// userListTpl 傳入初始數(shù)據(jù)狀態(tài),返回一個(gè)實(shí)例
var usersList = usersListTpl({
  title: "Users List",
  users: [
    {id: "user0", name: "Jerry", isAdmin: true},
    {id: "user1", name: "Lucy", isAuthor: true},
    {id: "user2", name: "Tomy"}
  ]
})

// 返回的實(shí)例有 dom 元素和一個(gè) setData 的 API
document.appendChild(usersList.dom)

// 需要變更數(shù)據(jù)的時(shí)候,setData 一下即可
usersList.setData({
  title: "Users",
  users: [
    {id: "user1", name: "Lucy", isAuthor: true},
    {id: "user2", name: "Tomy"}
  ]
})

完整的 Virtual-Template 源碼托管在 github 。

6. 結(jié)語(yǔ)

這個(gè)過(guò)程其實(shí)和 ReactJS 的 JSX 差不多。就拿 Babel 的 JSX 語(yǔ)法實(shí)現(xiàn)而言,它的 parser 叫 babylon。而 babylon 基于一個(gè)叫 acorn 的 JavaScript 編寫的 JavaScript 解釋器和它的 JSX 插件 acorn-jsx。其實(shí)就是利用 acorn 把文本分割成 tokens,而 JSX 語(yǔ)法分析部分由 acorn-jsx 完成。

Virtual-Template 還不能應(yīng)用于實(shí)際的生產(chǎn)環(huán)境,需要完善的東西還有很多。本文記錄基本的分析和實(shí)現(xiàn)的過(guò)程,也有助于更好地理解和學(xué)習(xí) ReactJS 的實(shí)現(xiàn)。

(全文完)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/78656.html

相關(guān)文章

  • 深度剖析:如何實(shí)現(xiàn)一個(gè) Virtual DOM 算法

    摘要:本文所實(shí)現(xiàn)的完整代碼存放在。這就是所謂的算法。兩個(gè)樹的完全的算法是一個(gè)時(shí)間復(fù)雜度為的問(wèn)題。如果有差異的話就記錄到一個(gè)對(duì)象里面。如和的不同,會(huì)被所替代。這牽涉到兩個(gè)列表的對(duì)比算法,需要另外起一個(gè)小節(jié)來(lái)討論。 作者:戴嘉華 轉(zhuǎn)載請(qǐng)注明出處并保留原文鏈接( https://github.com/livoras/blog/issues/13 )和作者信息。 目錄: 1 前言 2 對(duì)前端應(yīng)用狀...

    vvpvvp 評(píng)論0 收藏0
  • 前端 最舒服躺姿 "搞定" Flutter (組件篇)

    摘要:是谷歌的移動(dòng)框架,可以快速在和上構(gòu)建高質(zhì)量的原生用戶界面。在全世界好了這些,大家早就知道了,來(lái)點(diǎn)實(shí)在的話說(shuō)隔壁師兄,閑魚是最早一批與谷歌展開合作,并在重要的商品詳情頁(yè)中使用技術(shù)上線的。一切皆來(lái)自的組件皆來(lái)自。是狀態(tài)不可變的稱為無(wú)狀態(tài)。 前言 要說(shuō)2018年最火的跨端技術(shù),當(dāng)屬于 Flutter 莫屬,應(yīng)該沒(méi)人質(zhì)疑吧。一個(gè)新的技術(shù)的趨勢(shì),最明顯的特征,就是它一定想把前浪拍死在沙灘上。這個(gè)...

    LMou 評(píng)論0 收藏0
  • 前端 最舒服躺姿 "搞定" Flutter (組件篇)

    摘要:是谷歌的移動(dòng)框架,可以快速在和上構(gòu)建高質(zhì)量的原生用戶界面。在全世界好了這些,大家早就知道了,來(lái)點(diǎn)實(shí)在的話說(shuō)隔壁師兄,閑魚是最早一批與谷歌展開合作,并在重要的商品詳情頁(yè)中使用技術(shù)上線的。一切皆來(lái)自的組件皆來(lái)自。是狀態(tài)不可變的稱為無(wú)狀態(tài)。 前言 要說(shuō)2018年最火的跨端技術(shù),當(dāng)屬于 Flutter 莫屬,應(yīng)該沒(méi)人質(zhì)疑吧。一個(gè)新的技術(shù)的趨勢(shì),最明顯的特征,就是它一定想把前浪拍死在沙灘上。這個(gè)...

    Donne 評(píng)論0 收藏0
  • 前端 最舒服躺姿 "搞定" Flutter (組件篇)

    摘要:是谷歌的移動(dòng)框架,可以快速在和上構(gòu)建高質(zhì)量的原生用戶界面。在全世界好了這些,大家早就知道了,來(lái)點(diǎn)實(shí)在的話說(shuō)隔壁師兄,閑魚是最早一批與谷歌展開合作,并在重要的商品詳情頁(yè)中使用技術(shù)上線的。一切皆來(lái)自的組件皆來(lái)自。是狀態(tài)不可變的稱為無(wú)狀態(tài)。 前言 要說(shuō)2018年最火的跨端技術(shù),當(dāng)屬于 Flutter 莫屬,應(yīng)該沒(méi)人質(zhì)疑吧。一個(gè)新的技術(shù)的趨勢(shì),最明顯的特征,就是它一定想把前浪拍死在沙灘上。這個(gè)...

    張遷 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<