摘要:大多數(shù)模板實現(xiàn)原理基本一致模板字符串首先通過各種手段剝離出普通字符串和模板語法字符串生成抽象語法樹然后針對模板語法片段進行編譯,期間模板變量均去引擎輸入的變量中查找模板語法片段生成出普通片段,與原始普通字符串進行拼接輸出。
前端模板的發(fā)展
模板可以說是前端開發(fā)最常接觸的工具之一。將頁面固定不變的內(nèi)容抽出成模板,服務端返回的動態(tài)數(shù)據(jù)裝填到模板中預留的坑位,最后組裝成完整的頁面html字符串交給瀏覽器去解析。
模板可以大大提升開發(fā)效率,如果沒有模板開發(fā)人員怕是要手動拼寫字符串。
var tpl = "" + user.name + "
"; $("body").append(tpl);
在近些年前端發(fā)展過程中,模板也跟著變化:
1. php模板 JSP模板
早期還沒有前后端分離時代,前端只是后端項目中的一個文件夾,這時期的php和java都提供了各自的模板引擎。以JSP為例:java web應用的頁面通常是一個個.jsp的文件,這個文件內(nèi)容是大部分的html以及一些模板自帶語法,本質(zhì)上是純文本,但是既不是html也不是java。
JSP語法:index.jsp
Hello World Hello World!
<% out.println("Your IP address is " + request.getRemoteAddr()); %>
這個時期的模板引擎,往往是服務端來編譯模板字符串,生成html字符串給客戶端。
2. handlebar mustache通用模板
09年node發(fā)布,JavaScript也可以來實現(xiàn)服務端的功能,這也大大的方便了開發(fā)人員。mustache和handlebar模板的誕生方便了前端開發(fā)人員,這兩個模板均使用JavaScript來實現(xiàn),從此前端模板既可以在服務端運行,也可以在客戶端運行,但是大多數(shù)使用場景都是js根據(jù)服務端異步獲取的數(shù)據(jù)套入模板,生成新的dom插入頁碼。 對前端后端開發(fā)都非常有利。
mustache語法:index.mustache
Username: {{user.name}}
{{#if (user.gender === 2)}}女
{{/if}}
3. vue中的模板 React中的JSX
接下來到了新生代,vue中的模板寫法跟之前的模板有所不同,而且功能更加強大。既可以在客戶端使用也可以在服務端使用,但是使用場景上差距非常大:頁面往往根據(jù)數(shù)據(jù)變化,模板生成的dom發(fā)生變化,這對于模板的性能要求很高。
vue語法:index.vue
Username: {{user.name}}
女
無論是從JSP到vue的模板,模板在語法上越來越簡便,功能越來越豐富,但是基本功能是不能少的:
變量輸出(轉(zhuǎn)義/不轉(zhuǎn)義):出于安全考慮,模板基本默認都會將變量的字符串轉(zhuǎn)義輸出,當然也實現(xiàn)了不轉(zhuǎn)義輸出的功能,慎重使用。
條件判斷(if else):開發(fā)中經(jīng)常需要的功能。
循環(huán)變量:循環(huán)數(shù)組,生成很多重復的代碼片段。
模板嵌套:有了模板嵌套,可以減少很多重復代碼,并且嵌套模板集成作用域。
以上功能基本涵蓋了大多數(shù)模板的基礎功能,針對這些基礎功能就可以探究模板如何實現(xiàn)的。
模板實現(xiàn)原理正如標題所說的,模板本質(zhì)上都是純文本的字符串,字符串是如何操作js程序的呢?
模板用法上:
var domString = template(templateString, data);
模板引擎獲得到模板字符串和模板的作用域,經(jīng)過編譯之后生成完整的DOM字符串。
大多數(shù)模板實現(xiàn)原理基本一致:
模板字符串首先通過各種手段剝離出普通字符串和模板語法字符串生成抽象語法樹AST;然后針對模板語法片段進行編譯,期間模板變量均去引擎輸入的變量中查找;模板語法片段生成出普通html片段,與原始普通字符串進行拼接輸出。
其實模板編譯邏輯并沒有特別復雜,至于vue這種動態(tài)綁定數(shù)據(jù)的模板有時間可以參考文末鏈接。
快速實現(xiàn)簡單的模板現(xiàn)在以mustache模板為例,手動實現(xiàn)一個實現(xiàn)基本功能的模板。
模板字符串模板:index.txt
Page Title Panda模板編譯
普通變量輸出
username: {{common.username}}
escape:{{common.escape}}
不轉(zhuǎn)義輸出
unescape:{{&common.escape}}
列表輸出:
escape{{common.escape}}
{{else}}unescape:{{&common.escape}}
{{/if}}模板對應數(shù)據(jù):
module.exports = { common: { username: "Aus", escape: "Aus
" }, shouldEscape: false, list: [ {key: "a", value: 1}, {key: "b", value: 2}, {key: "c", value: 3}, {key: "d", value: 4} ] };
模板的使用方法:
var fs = require("fs"); var tpl = fs.readFileSync("./index.txt", "utf8"); var state = require("./test"); var Panda = require("./panda"); Panda.render(tpl, state)
然后來實現(xiàn)模板:
1. 正則切割字符串模板引擎獲取到模板字符串之后,通常要使用正則切割字符串,區(qū)分出那些是靜態(tài)的字符串,那些是需要編譯的代碼塊,生成抽象語法樹(AST)。
// 將未處理過的字符串進行分詞,形成字符組tokens Panda.prototype.parse = function (tpl) { var tokens = []; var tplStart = 0; var tagStart = 0; var tagEnd = 0; while (tagStart >= 0) { tagStart = tpl.indexOf(openTag, tplStart); if (tagStart < 0) break; // 純文本 tokens.push(new Token("text", tpl.slice(tplStart, tagStart))); tagEnd = tpl.indexOf(closeTag, tagStart) + 2; if (tagEnd < 0) throw new Error("{{}}標簽未閉合"); // 細分js var tplValue = tpl.slice(tagStart + 2, tagEnd - 2); var token = this.classifyJs(tplValue); tokens.push(token); tplStart = tagEnd; } // 最后一段 tokens.push(new Token("text", tpl.slice(tagEnd, tpl.length))); return this.parseJs(tokens); };
這一步分割字符串通常使用正則來完成的,后面檢索字符串會大量用到正則方法。
在這一步通常可以檢查出模板標簽閉合異常,并報錯。2. 模板語法的分類
生成AST之后,普通字符串不需要再管了,最后會直接輸出,專注于模板語法的分類。
// 專門處理模板中的js Panda.prototype.parseJs = function (tokens) { var sections = []; var nestedTokens = []; var conditionsArray = []; var collector = nestedTokens; var section; var currentCondition; for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; var value = token.value; var symbol = token.type; switch (symbol) { case "#": { collector.push(token); sections.push(token); if(token.action === "each"){ collector = token.children = []; } else if (token.action === "if") { currentCondition = value; var conditionArray; collector = conditionArray = []; token.conditions = token.conditions || conditionsArray; conditionsArray.push({ condition: currentCondition, collector: collector }); } break; } case "else": { if(sections.length === 0 || sections[sections.length - 1].action !== "if") { throw new Error("else 使用錯誤"); } currentCondition = value; collector = []; conditionsArray.push({ condition: currentCondition, collector: collector }); break; } case "/": { section = sections.pop(); if (section && section.action !== token.value) { throw new Error("指令標簽未閉合"); } if(sections.length > 0){ var lastSection = sections[sections.length - 1]; if(lastSection.action === "each"){ collector = lastSection.chidlren; } else if (lastSection.action = "if") { conditionsArray = []; collector = nestedTokens; } } else { collector = nestedTokens; } break; } default: { collector.push(token); break; } } } return nestedTokens; }
上一步我們生成了AST,這個AST在這里就是一個分詞token數(shù)組:
[ Token {}, Token {}, Token {}, ]
這個token就是每一段字符串,分別記錄了token的類型,動作,子token,條件token等信息。
/** * token類表示每個分詞的標準數(shù)據(jù)結(jié)構(gòu) */ function Token (type, value, action, children, conditions) { this.type = type; this.value = value; this.action = action; this.children = children; this.conditions = conditions; }
在這一步要將循環(huán)方法中的子token嵌套到對應的token中,以及條件渲染子token嵌套到對應token中。
這步完成之后,一個標準的帶有嵌套關系的AST完成了。
3. 變量查找與賦值現(xiàn)在開始根據(jù)token中的變量查找到對應的值,根據(jù)相應功能生成值得字符串。
/** * 解析數(shù)據(jù)結(jié)構(gòu)的類 */ function Context (data, parentContext) { this.data = data; this.cache = { ".": this.data }; this.parent = parentContext; } Context.prototype.push = function (data) { return new Context(data, this); } // 根據(jù)字符串name找到真實的變量值 Context.prototype.lookup = function lookup (name) { name = trim(name); var cache = this.cache; var value; // 查詢過緩存 if (cache.hasOwnProperty(name)) { value = cache[name]; } else { var context = this, names, index, lookupHit = false; while (context) { // user.username if (name.indexOf(".") > 0) { value = context.data; names = name.split("."); index = 0; while (value != null && index < names.length) { if (index === names.length - 1) { lookupHit = hasProperty(value, names[index]); } value = value[names[index++]]; } } else { value = context.data[name]; lookupHit = hasProperty(context.data, name); } if (lookupHit) { break; } context = context.parent; } cache[name] = value; } return value; }
為了提高查找效率,采用緩存代理,每次查找到的變量存儲路徑方便下次快速查找。
不同于JavaScript編譯器,模板引擎在查找變量的時候找不到對應變量即終止查找,返回空并不會報錯。4. 節(jié)點的條件渲染與嵌套
這里開始講模板語法token和普通字符串token開始統(tǒng)一編譯生成字符串,并拼接成完整的字符串。
// 根據(jù)tokens和context混合拼接字符串輸出結(jié)果 Panda.prototype.renderTokens = function (tokens, context) { var result = ""; var token, symbol, value; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { value = undefined; token = tokens[i]; symbol = token.type; if (symbol === "#") value = this.renderSection(token, context); else if (symbol === "&") value = this.unescapedValue(token, context); else if (symbol === "=") value = this.escapedValue(token, context); else if (symbol === "text") value = this.rawValue(token); if (value !== undefined) result += value; } return result; }5. 繪制頁面
頁面字符串已經(jīng)解析完成,可以直接輸出:
Panda.prototype.render = function (tpl, state) { if (typeof tpl !== "string") { return new Error("請輸入字符串!"); } // 解析字符串 var tokens = this.cache[tpl] ? tokens : this.parse(tpl); // 解析數(shù)據(jù)結(jié)構(gòu) var context = state instanceof Context ? state : new Context(state); // 渲染模板 return this.renderTokens(tokens, context); };
輸出頁面字符串被瀏覽器解析,就出現(xiàn)了頁面。
以上只是簡單的模板實現(xiàn),并沒有經(jīng)過系統(tǒng)測試,僅供學習使用,源碼傳送門。成熟的模板引擎是有完整的異常處理,變量查找解析,作用域替換,優(yōu)化渲染,斷點調(diào)試等功能的。
總結(jié)前端模板這塊能做的東西還很多,很多框架都是集成模板的功能,配合css,js等混合編譯生成解析好樣式和綁定成功事件的dom。
另外實現(xiàn)模板的方式也有很多,本文的實現(xiàn)方式參考了mustache源碼,模板標簽內(nèi)的代碼被解析,但是是通過代碼片段分類,變量查找的方式來執(zhí)行的,將純字符串的代碼變成了被解釋器執(zhí)行的代碼。
另外向vue這種可以實現(xiàn)雙向綁定的模板可以抽空多看一看。
參考資料前端模板的原理與實現(xiàn)
Vue 模板編譯原理
現(xiàn)一個前端模板引擎
mustache
[如何選擇-Web-前端模板引擎
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97342.html
摘要:消息隊列技術介紹后端掘金一消息隊列概述消息隊列中間件是分布式系統(tǒng)中重要的組件,主要解決應用耦合異步消息流量削鋒等問題。的內(nèi)存優(yōu)化后端掘金聲明本文內(nèi)容來自開發(fā)與運維一書第八章,如轉(zhuǎn)載請聲明。 消息隊列技術介紹 - 后端 - 掘金一、 消息隊列概述 消息隊列中間件是分布式系統(tǒng)中重要的組件,主要解決應用耦合、異步消息、流量削鋒等問題。實現(xiàn)高性能、高可用、可伸縮和最終一致性架構(gòu)。是大型分布式系...
摘要:并總結(jié)經(jīng)典面試題集各種算法和插件前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快速搭建項目。 本文是關注微信小程序的開發(fā)和面試問題,由基礎到困難循序漸進,適合面試和開發(fā)小程序。并總結(jié)vue React html css js 經(jīng)典面試題 集各種算法和插件、前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快...
閱讀 1066·2021-11-12 10:34
閱讀 996·2021-09-30 09:56
閱讀 674·2019-08-30 15:54
閱讀 2608·2019-08-30 11:14
閱讀 1473·2019-08-29 16:44
閱讀 3212·2019-08-29 16:35
閱讀 2498·2019-08-29 16:22
閱讀 2448·2019-08-29 15:39