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

資訊專欄INFORMATION COLUMN

Mustache.js源碼分析

mating / 1567人閱讀

摘要:是一個弱邏輯的模板引擎,語法十分簡單,使用很方便。源碼只有行,且代碼結構清晰。解析器解析器是整個源碼中最重要的方法,用于解析模板,將標簽與模板標簽分離。同時比較后還需將的最后一個刪除,才能進行下一輪比較。

mustache.js是一個弱邏輯的模板引擎,語法十分簡單,使用很方便。源碼(v2.2.1)只有600+行,且代碼結構清晰。

一般來說,mustache.js使用方法如下:

var template = "Hello, {{name}}";
var rendered = Mustache.render(template, {
    name: "World"
});
document.getElementById("container").innerHTML = rendered;

通過使用Chrome對上述Mustache.renderdebug,我們順藤摸瓜梳理了mustache.js5個模塊(暫且稱它們?yōu)椋?b>Utils, Scanner, Parser, Writer,Context)間的關系圖如下:

代碼層面,Mustache.render()方法是mustache.js向外暴露的方法之一,

mustache.render = function render(template, view, partials) {
    // 容錯處理
    if (typeof template !== "string") {
        throw new TypeError("Invalid template! Template should be a "string" " +
            "but "" + typeStr(template) + "" was given as the first " +
            "argument for mustache#render(template, view, partials)");
    }
    // 調用Writer.render
    return defaultWriter.render(template, view, partials);
};

在其內部,它首先調用了Writer.render()方法,

Writer.prototype.render = function render(template, view, partials) {
    // 調用Writer構造器的parse方法
    var tokens = this.parse(template);
    // 渲染邏輯,后文會分析
    var context = (view instanceof Context) ? view : new Context(view);
    return this.renderTokens(tokens, context, partials, template);
};

Writer.render()方法首先調用了Writer.parse()方法,

Writer.prototype.parse = function parse(template, tags) {
    var cache = this.cache;
    var tokens = cache[template];
    if (tokens == null)
        // 調用parseTemplate方法
        tokens = cache[template] = parseTemplate(template, tags);
    return tokens;
};

Writer.parse()方法調用了parseTemplate方法,
所以,歸根結底,Mustache.render()方法首先調用parseTemplate方法對html字符串進行解析,
然后,將一個對象渲染到解析出來的模板中去。

所以,我們得研究源碼核心所在——parseTemplate方法。在此之前,我們的先看一些前置方法:工具方法和掃描器。

工具方法(Utils
// 判斷某個值是否為數(shù)組
var objectToString = Object.prototype.toString;
var isArray = Array.isArray || function isArrayPolyfill(object) {
    return objectToString.call(object) === "[object Array]";
};
// 判斷某個值是否為函數(shù)
function isFunction(object) {
    return typeof object === "function";
}
// 更精確的返回數(shù)組類型的typeof值為"array",而非默認的"object"
function typeStr(obj) {
    return isArray(obj) ? "array" : typeof obj;
}
// 轉義正則表達式里的特殊字符
function escapeRegExp(string) {
    return string.replace(/[-[]{}()*+?.,^$|#s]/g, "$&");
}
// 判斷對象是否有某屬性
function hasProperty(obj, propName) {
    return obj != null && typeof obj === "object" && (propName in obj);
}
// 正則驗證,防止Linux和Windows下不同spidermonkey版本導致的bug
var regExpTest = RegExp.prototype.test;

function testRegExp(re, string) {
    return regExpTest.call(re, string);
}
// 是否是空格
var nonSpaceRe = /S/;

function isWhitespace(string) {
    return !testRegExp(nonSpaceRe, string);
}
// 將特殊字符轉為轉義字符
var entityMap = {
    "&": "&",
    "<": "<",
    ">": ">",
    """: """,
    """: "'",
    "/": "/",
    "`": "`",
    "=": "="
};

function escapeHtml(string) {
    return String(string).replace(/[&<>""`=/]/g, function fromEntityMap(s) {
        return entityMap[s];
    });
}
var whiteRe = /s*/; // 匹配0個以上的空格
var spaceRe = /s+/; // 匹配1個以上的空格
var equalsRe = /s*=/; // 匹配0個以上的空格加等號
var curlyRe = /s*}/; // 匹配0個以上的空格加}
var tagRe = /#|^|/|>|{|&|=|!/; // 匹配#,^,/,>,{,&,=,!
掃描器(Scanner
// Scanner構造器,用于掃描模板
function Scanner(string) {
    this.string = string; // 模板總字符串
    this.tail = string; // 模板剩余待掃描字符串
    this.pos = 0; // 掃描索引,即表示當前掃描到第幾個字符串
}
// 如果模板掃描完成,返回true
Scanner.prototype.eos = function eos() {
    return this.tail === "";
};
// 掃描的下一批的字符串是否匹配re正則,如果不匹配或者match的index不為0
Scanner.prototype.scan = function scan(re) {
    var match = this.tail.match(re);
    if (!match || match.index !== 0)
        return "";
    var string = match[0];
    this.tail = this.tail.substring(string.length);
    this.pos += string.length;
    return string;
};
// 掃描到符合re正則匹配的字符串為止,將匹配之前的字符串返回,掃描索引設為掃描到的位置
Scanner.prototype.scanUntil = function scanUntil(re) {
    var index = this.tail.search(re),
        match;
    switch (index) {
        case -1:
            match = this.tail;
            this.tail = "";
            break;
        case 0:
            match = "";
            break;
        default:
            match = this.tail.substring(0, index);
            this.tail = this.tail.substring(index);
    }
    this.pos += match.length;
    return match;
};

總的來說,掃描器,就是用來掃描字符串的。掃描器中只有三個方法:

eos: 判斷當前掃描剩余字符串是否為空,也就是用于判斷是否掃描完了

scan: 僅掃描當前掃描索引的下一堆匹配正則的字符串,同時更新掃描索引

scanUntil: 掃描到匹配正則為止,同時更新掃描索引

現(xiàn)在進入parseTemplate方法。

解析器(Parser

解析器是整個源碼中最重要的方法,用于解析模板,將html標簽與模板標簽分離。
整個解析原理為:遍歷字符串,通過正則以及掃描器,將普通html和模板標簽掃描并且分離,并保存為數(shù)組tokens

function parseTemplate(template, tags) {
    if (!template)
        return [];
    var sections = []; // 用于臨時保存解析后的模板標簽對象
    var tokens = []; // 保存所有解析后的對象
    var spaces = []; // 包括空格對象在tokens里的索引
    var hasTag = false; // 當前行是否有{{tag}}
    var nonSpace = false; // 當前行是否有非空格字符
    // 去除保存在tokens里的空格對象
    function stripSpace() {
        if (hasTag && !nonSpace) {
            while (spaces.length)
                delete tokens[spaces.pop()];
        } else {
            spaces = [];
        }
        hasTag = false;
        nonSpace = false;
    }
    var openingTagRe, closingTagRe, closingCurlyRe;
    // 將tag轉換為正則,默認tag為{{和}},所以轉成匹配{{的正則,和匹配}}的正則,以及匹配}}}的正則
    // 因為mustache的解析中如果是{{{}}}里的內容則被解析為html代碼
    function compileTags(tagsToCompile) {
        if (typeof tagsToCompile === "string")
            tagsToCompile = tagsToCompile.split(spaceRe, 2);
        if (!isArray(tagsToCompile) || tagsToCompile.length !== 2)
            throw new Error("Invalid tags: " + tagsToCompile);
        openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + "s*");
        closingTagRe = new RegExp("s*" + escapeRegExp(tagsToCompile[1]));
        closingCurlyRe = new RegExp("s*" + escapeRegExp("}" + tagsToCompile[1]));
    }
    compileTags(tags || mustache.tags);
    var scanner = new Scanner(template);
    var start, type, value, chr, token, openSection;
    while (!scanner.eos()) {
        start = scanner.pos;
        // 開始掃描模板,掃描至{{時停止掃描,并且將此前掃描過的字符保存為value
        value = scanner.scanUntil(openingTagRe);
        if (value) {
            // 遍歷{{之前的字符
            for (var i = 0, valueLength = value.length; i < valueLength; ++i) {
                chr = value.charAt(i);
                // 如果當前字符為空格,這用spaces數(shù)組記錄保存至tokens里的索引
                if (isWhitespace(chr)) {
                    spaces.push(tokens.length);
                } else {
                    nonSpace = true;
                }
                tokens.push(["text", chr, start, start + 1]);
                start += 1;
                // 如果遇到換行符,則將前一行的空格清除
                if (chr === "
")
                    stripSpace();
            }
        }
        // 判斷下一個字符串中是否有{{,同時更新掃描索引到{{的后一位
        if (!scanner.scan(openingTagRe))
            break;
        hasTag = true;
        // 掃描標簽類型,是{{#}}還是{{=}}或其他
        type = scanner.scan(tagRe) || "name";
        scanner.scan(whiteRe);
        // 根據(jù)標簽類型獲取標簽里的值,同時通過掃描器,刷新掃描索引
        if (type === "=") {
            value = scanner.scanUntil(equalsRe);
            // 使掃描索引更新為s*=后
            scanner.scan(equalsRe);
            // 使掃描索引更新為}}后,下面同理
            scanner.scanUntil(closingTagRe);
        } else if (type === "{") {
            value = scanner.scanUntil(closingCurlyRe);
            scanner.scan(curlyRe);
            scanner.scanUntil(closingTagRe);
            type = "&";
        } else {
            value = scanner.scanUntil(closingTagRe);
        }
        // 匹配模板閉合標簽即}},如果沒有匹配到則拋出異常,
        // 同時更新掃描索引至}}后一位,至此時即完成了一個模板標簽{{#tag}}的掃描
        if (!scanner.scan(closingTagRe))
            throw new Error("Unclosed tag at " + scanner.pos);
        // 將模板標簽也保存至tokens數(shù)組中
        token = [type, value, start, scanner.pos];
        tokens.push(token);
        // 如果type為#或者^,也將tokens保存至sections
        if (type === "#" || type === "^") {
            sections.push(token);
        } else if (type === "/") {
            // 如果type為/則說明當前掃描到的模板標簽為{{/tag}},
            // 則判斷是否有{{#tag}}與其對應
            openSection = sections.pop();
            // 檢查模板標簽是否閉合,{{#}}是否與{{/}}對應,即臨時保存在sections最后的{{#tag}}
            if (!openSection)
                throw new Error("Unopened section "" + value + "" at " + start);
            // 是否跟當前掃描到的{{/tag}}的tagName相同
            if (openSection[1] !== value)
                throw new Error("Unclosed section "" + openSection[1] + "" at " + start);
            // 具體原理:掃描第一個tag,sections為[{{#tag}}],
            // 掃描第二個后sections為[{{#tag}}, {{#tag2}}],
            // 以此類推掃描多個開始tag后,sections為[{{#tag}}, {{#tag2}} ... {{#tag}}]
            // 所以接下來如果掃描到{{/tag}}則需跟sections的最后一個相對應才能算標簽閉合。
            // 同時比較后還需將sections的最后一個刪除,才能進行下一輪比較。
        } else if (type === "name" || type === "{" || type === "&") {
            // 如果標簽類型為name、{或&,不用清空上一行的空格
            nonSpace = true;
        } else if (type === "=") {
            // 編譯標簽,為下一次循環(huán)做準備
            compileTags(value);
        }
    }
    // 確保sections中沒有開始標簽
    openSection = sections.pop();
    if (openSection)
        throw new Error("Unclosed section "" + openSection[1] + "" at " + scanner.pos);
    return nestTokens(squashTokens(tokens));
}

我們來看經(jīng)過解析器解析之后得到的tokens的數(shù)據(jù)結構:

每一個子項都類似下面這種結構

token[0]token的類型,可能的值有#^/&nametext,分別表示{}時,調用renderSection方法

Writer.prototype.renderSection = function renderSection(token, context, partials, originalTemplate) {
    var self = this;
    var buffer = "";
    // 獲取{{#xx}}中xx在傳進來的對象里的值
    var value = context.lookup(token[1]);

    function subRender(template) {
        return self.render(template, context, partials);
    }
    if (!value) return;
    if (isArray(value)) {
        // 如果為數(shù)組,說明要復寫html,通過遞歸,獲取數(shù)組里的渲染結果
        for (var j = 0, valueLength = value.length; j < valueLength; ++j) {
            buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
        }
    } else if (typeof value === "object" || typeof value === "string" || typeof value === "number") {
        // 如果value為對象或字符串或數(shù)字,則不用循環(huán),根據(jù)value進入下一次遞歸
        buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);
    } else if (isFunction(value)) {
        if (typeof originalTemplate !== "string")
            throw new Error("Cannot use higher-order sections without the original template");
        // 如果value是方法,則執(zhí)行該方法,并且將返回值保存
        value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);
        if (value != null)
            buffer += value;
    } else {
        // 如果不是上面所有情況,直接進入下次遞歸
        buffer += this.renderTokens(token[4], context, partials, originalTemplate);
    }
    return buffer;
};

當模板標簽類型為時,說明要當value不存在(nullundefined0"")或者為空數(shù)組的時候才觸發(fā)渲染。

看看renderInverted方法的實現(xiàn)

Writer.prototype.renderInverted = function renderInverted(token, context, partials, originalTemplate) {
    var value = context.lookup(token[1]);
    // 值為null,undefined,0,""或空數(shù)組
    // 直接進入下次遞歸
    if (!value || (isArray(value) && value.length === 0)) {
        return this.renderTokens(token[4], context, partials, originalTemplate);
    }
};
結語

到這為止,mustache.js的源碼解析完了,可以看出來,mustache.js最主要的是一個解析器和一個渲染器,以非常簡潔的方式實現(xiàn)了一個強大的模板引擎。

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

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108912.html

相關文章

  • js模版引擎介紹

    摘要:使用方法編譯模板并根據(jù)所給的數(shù)據(jù)立即渲染出結果僅編譯模版暫不渲染,它會返回一個可重用的編譯后的函數(shù)根據(jù)給定的數(shù)據(jù),對之前編譯好的模板進行數(shù)據(jù)渲染參考資料模板引擎概述 js模版引擎介紹 JavaScript 模板是將 HTML 結構從包含它們的內容中分離的方法。模板系統(tǒng)通常會引入一些新語法,但通常是非常簡單的,一個要注意的有趣的點是,替換標記通常是由雙花括號({ {……} })表示,這也...

    duan199226 評論0 收藏0
  • [譯] 用 Webpack 武裝自己

    摘要:現(xiàn)在來做一個的入口讓我們在文件里進行的配置。如果想要顯示它們,我們可以在運行的時候使用你還可以使用,在改變代碼的時候自動進行打包。新建文件,里面是一段,告訴使用進行預處理。 本文譯自:Webpack your bags這篇文章由入門到深入的介紹了webpack的功能和使用技巧,真心值得一看。 由于我英語水平有限,而且很少翻譯文章,所以文中的一些語句在翻譯時做了類似語義的轉換,望諒解。...

    Tychio 評論0 收藏0
  • Mustache學習筆記

    摘要:一個返回值渲染后的例那年那夏我是,年齡結果我是那年那夏,年齡的思想的核心是標簽和。從上面的代碼中可以看到定義模板時,使用了這樣的標記,這就是的標簽,只不過它用替代了,以免跟標簽的混淆。 Mustache學習筆記 Mustache 是一款基于javascript 實現(xiàn)的模板引擎,類似于 Microsoft’s jQuery template plugin,但更簡單易用,在前后端分離的技術...

    qylost 評論0 收藏0
  • mustache:web模板引擎

    摘要:簡介是一個輕邏輯模板解析引擎,它的優(yōu)勢在于可以應用在等多種編程語言中。這里主要是看中的應用。 mustache簡介 Mustache 是一個 logic-less (輕邏輯)模板解析引擎,它的優(yōu)勢在于可以應用在 Javascript、PHP、Python、Perl 等多種編程語言中。這里主要是看JavaScript中的應用。Javascript 語言的模板引擎,目前流行有 Mustac...

    klivitamJ 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<