摘要:基本語法構(gòu)造函數(shù)可創(chuàng)建一個(gè)正則表達(dá)式對(duì)象,用特定的模式匹配文本。要表示字符串,字面量形式不使用引號(hào),而傳遞給構(gòu)造函數(shù)的參數(shù)使用引號(hào)。當(dāng)使用構(gòu)造函數(shù)創(chuàng)造正則對(duì)象時(shí),需要常規(guī)的字符轉(zhuǎn)義規(guī)則在前面加反斜杠。結(jié)果替換與正則表達(dá)式匹配的子串。
前言文章來源:小青年原創(chuàng)
發(fā)布時(shí)間:2016-06-26
關(guān)鍵詞:JavaScript,正則表達(dá)式,js模板引擎
轉(zhuǎn)載需標(biāo)注本文原始地址: http://zhaomenghuan.github.io...
這年頭MVC、MVVM框架泛濫,很多時(shí)候我們只是用了這些框架,有時(shí)候想深入去了解這些框架背后的原理實(shí)現(xiàn)時(shí),閱讀源碼時(shí)發(fā)現(xiàn)無從下手,js基本功簡直渣渣,所以想利用業(yè)余時(shí)間還是要補(bǔ)補(bǔ)基礎(chǔ)。以前看JavaScript的一些書籍時(shí)總是把正則表達(dá)式這一章跳過了,遇到一些需要寫正則的時(shí)候然后都是各種copy,js要進(jìn)階感覺還是要系統(tǒng)學(xué)習(xí)一下正則,雖然看起來像亂碼一樣的匹配規(guī)則,但是如果熟練使用會(huì)很有用,那么今天就先從正則開始吧!和大部分書籍一樣,本文前篇也會(huì)是講解基礎(chǔ),本文的很多內(nèi)容都是摘自網(wǎng)絡(luò)進(jìn)行整理,有些內(nèi)容需要各位自己進(jìn)行實(shí)踐驗(yàn)證。
正則表達(dá)式正則表達(dá)式(英語:Regular Expression,在代碼中常簡寫為regex、regexp或RE)使用單個(gè)字符串來描述、匹配一系列符合某個(gè)句法規(guī)則的字符串搜索模式。搜索模式可用于文本搜索和文本替換。
基本語法RegExp 構(gòu)造函數(shù)可創(chuàng)建一個(gè)正則表達(dá)式對(duì)象,用特定的模式匹配文本。創(chuàng)建一個(gè)正則對(duì)象的兩種方法:字面量和構(gòu)造函數(shù)。要表示字符串,字面量形式不使用引號(hào),而傳遞給構(gòu)造函數(shù)的參數(shù)使用引號(hào)。
字面量: /pattern/flags 構(gòu)造函數(shù): RegExp(pattern [, flags])
pattern 正則表達(dá)式文本
flags 該參數(shù)可以是下面單個(gè)值或者幾個(gè)值的任意組合:
g 全局匹配
i 忽略大小寫
gi或ig 全局匹配、忽略大小寫
m 多行查找,讓開始和結(jié)束字符(^ 和 $)工作在多行模式(也就是,^ 和 $ 可以匹配字符串中每一行的開始和結(jié)束(行是由 n 或 r 分割的),而不只是整個(gè)輸入字符串的最開始和最末尾處。
u Unicode。將模式視為Unicode碼位(code points)序列。
y sticky。使用隱式的^錨點(diǎn)把正則錨定在了lastIndex所指定的偏移位置處,具體可以看看這篇文章:JavaScript:正則表達(dá)式的/y標(biāo)識(shí) 。
具體例子:
/ab+c/i; new RegExp("ab+c", "i"); new RegExp(/ab+c/, "i");
當(dāng)表達(dá)式被賦值時(shí),字面量形式提供正則表達(dá)式的編譯(compilation)狀態(tài),當(dāng)正則表達(dá)式保持為常量時(shí)使用字面量。例如當(dāng)你在循環(huán)中使用字面量構(gòu)造一個(gè)正則表達(dá)式時(shí),正則表達(dá)式不會(huì)在每一次迭代中都被重新編譯(recompiled)。
而正則表達(dá)式對(duì)象的構(gòu)造函數(shù),如 new RegExp("ab+c") 提供了正則表達(dá)式運(yùn)行時(shí)編譯(runtime compilation)。如果你知道正則表達(dá)式模式將會(huì)改變,或者你事先不知道什么模式,而是從另一個(gè)來源獲取,如用戶輸入,這些情況都可以使用構(gòu)造函數(shù)。
從ECMAScript 6開始,當(dāng)?shù)谝粋€(gè)參數(shù)為正則表達(dá)式而第二個(gè)標(biāo)志參數(shù)存在時(shí),new RegExp(/ab+c/, "i")不再拋出TypeError (“當(dāng)從其他正則表達(dá)式進(jìn)行構(gòu)造時(shí)不支持標(biāo)志”)的異常,取而代之,將使用這些參數(shù)創(chuàng)建一個(gè)新的正則表達(dá)式。
當(dāng)使用構(gòu)造函數(shù)創(chuàng)造正則對(duì)象時(shí),需要常規(guī)的字符轉(zhuǎn)義規(guī)則(在前面加反斜杠 )。比如,以下是等價(jià)的:
var re = new RegExp("w+"); var re = /w+/;正則表達(dá)式中的特殊字符
字符類別(Character Classes)
字符集合(Character Sets)
邊界(Boundaries)
分組(grouping)與反向引用(back references)
數(shù)量詞(Quantifiers)
^和$ —— 作用是分別指出一個(gè)字符串的開始和結(jié)束。
"^The":表示所有以"The"開始的字符串"There","The cat"等;
"of despair$":表示所以以"of despair"結(jié)尾的字符串;
"^abc$":表示開始和結(jié)尾都是"abc"的字符串——呵呵,只有"abc"自己了;
"notice":表示任何包含"notice"的字符串。
最后那個(gè)例子,如果你不使用兩個(gè)特殊字符,你就在表示要查找的串在被查找串的任意部分——你并不把它定位在某一個(gè)頂端。
*,+ , ? 和 {} —— 表示一個(gè)或一序列字符重復(fù)出現(xiàn)的次數(shù)。
分別表示“沒有或更多”,“一次或更多”,“沒有或一次”和指定重復(fù)次數(shù)的范圍。{}必須指定范圍的下限,如:"{0,2}"而不是"{,2}"。*、+和?相當(dāng)于{0,}、{1,}和{0,1}。
"ab*":表示一個(gè)字符串有一個(gè)a后面跟著零個(gè)或若干個(gè)b。("a", "ab", "abbb",……);
"ab+":表示一個(gè)字符串有一個(gè)a后面跟著至少一個(gè)b或者更多;
"ab?":表示一個(gè)字符串有一個(gè)a后面跟著零個(gè)或者一個(gè)b;
"a?b+$":表示在字符串的末尾有零個(gè)或一個(gè)a跟著一個(gè)或幾個(gè)b。
"ab{2}":表示一個(gè)字符串有一個(gè)a跟著2個(gè)b("abb");
"ab{2,}":表示一個(gè)字符串有一個(gè)a跟著至少2個(gè)b;
"ab{3,5}":表示一個(gè)字符串有一個(gè)a跟著3到5個(gè)b。
| —— 表示“或”操作
"hi|hello":表示一個(gè)字符串里有"hi"或者"hello";
"(b|cd)ef":表示"bef"或"cdef";
"(a|b)*c":表示一串"a""b"混合的字符串后面跟一個(gè)"c";
. —— 可以替代任何字符
"a.[0-9]":表示一個(gè)字符串有一個(gè)"a"后面跟著一個(gè)任意字符和一個(gè)數(shù)字;
"^.{3}$":表示有任意三個(gè)字符的字符串(長度為3個(gè)字符);
[] —— 表示某些字符允許在一個(gè)字符串中的某一特定位置出現(xiàn)
"[ab]":表示一個(gè)字符串有一個(gè)"a"或"b"(相當(dāng)于"a|b");
"[a-d]":表示一個(gè)字符串包含小寫的"a"到"d"中的一個(gè)(相當(dāng)于"a|b|c|d"或者"[abcd]");
"^[a-zA-Z]":表示一個(gè)以字母開頭的字符串;
"[0-9]%":表示一個(gè)百分號(hào)前有一位的數(shù)字;
",[a-zA-Z0-9]$":表示一個(gè)字符串以一個(gè)逗號(hào)后面跟著一個(gè)字母或數(shù)字結(jié)束。
可以在方括號(hào)里用"^"表示不希望出現(xiàn)的字符,"^"應(yīng)在方括號(hào)里的第一位。(如:%[^a-zA-Z]%表示兩個(gè)百分號(hào)中不應(yīng)該出現(xiàn)字母)。為了逐字表達(dá),你必須在^.$()|*+?{這些字符前加上轉(zhuǎn)移字符""。在方括號(hào)中,不需要轉(zhuǎn)義字符。
RegExp對(duì)象的屬性屬性 | 含義 |
---|---|
$1-$9 | 如果它(們)存在,是匹配到的子串 |
$_ | 參見input |
$* | 參見multiline |
$& | 參見lastMatch |
$+ | 參見lastParen |
$` | 參見leftContext |
$"" | 參見rightContext |
constructor | 創(chuàng)建一個(gè)對(duì)象的一個(gè)特殊的函數(shù)原型 |
global | 是否在整個(gè)串中匹配(bool型) |
ignoreCase | 匹配時(shí)是否忽略大小寫(bool型) |
input | 被匹配的串 |
lastIndex | 最后一次匹配的索引 |
lastParen | 最后一個(gè)括號(hào)括起來的子串 |
leftContext | 最近一次匹配以左的子串 |
multiline | 是否進(jìn)行多行匹配(bool型) |
prototype | 允許附加屬性給對(duì)象 |
rightContext | 最近一次匹配以右的子串 |
source | 正則表達(dá)式模式 |
lastIndex | 最后一次匹配的索引 |
詳情大家可以查看這里:MDN JavaScript 標(biāo)準(zhǔn)庫 RegExp屬性
RegExp對(duì)象的方法方法 | 含義 |
---|---|
compile | 正則表達(dá)式比較 |
exec | 執(zhí)行查找 |
test | 進(jìn)行匹配 |
toSource | 返回特定對(duì)象的定義(literal representing),其值可用來創(chuàng)建一個(gè)新的對(duì)象。重載Object.toSource方法得到的。 |
toString | 返回特定對(duì)象的串。重載Object.toString方法得到的。 |
valueOf | 返回特定對(duì)象的原始值。重載Object.valueOf方法得到 |
詳情大家可以查看這里:MDN JavaScript 標(biāo)準(zhǔn)庫 RegExp方法
不過在這里我們還是需要說明一下的是我們用得比較多的方法主要分為兩類:
RegExp 對(duì)象方法RegExp.prototype.compile() ——編譯正則表達(dá)式
用法:regexObj.compile(pattern, flags)
功能說明:compile() 方法用于在腳本執(zhí)行過程中編譯正則表達(dá)式,也可用于改變和重新編譯正則表達(dá)式。該方法可以編譯指定的正則表達(dá)式,編譯之后的正則表達(dá)式執(zhí)行速度將會(huì)提高,如果正則表達(dá)式多次被調(diào)用,那么調(diào)用compile方法可以有效的提高代碼的執(zhí)行速度,如果該正則表達(dá)式只能被使用一次,則不會(huì)有明顯的效果。
var str="Every man in the world! Every woman on earth!"; var patt=/man/g; var str2=str.replace(patt,"person"); document.write(str2+"
"); patt=/(wo)?man/g; patt.compile(patt); str2=str.replace(patt,"person"); document.write(str2); 結(jié)果: Every person in the world! Every woperson on earth! Every person in the world! Every person on earth!
RegExp.prototype.exec() —— 檢索字符串中指定的值。返回找到的值,并確定其位置。
用法:regexObj.exec(str)
功能說明:exec() 方法如果成功匹配,exec 方法返回一個(gè)數(shù)組,并且更新正則表達(dá)式對(duì)象的屬性。返回的數(shù)組包括匹配的字符串作為第一個(gè)元素,緊接著一個(gè)元素對(duì)應(yīng)一個(gè)成功匹配被捕獲的字符串的捕獲括號(hào)(capturing parenthesis)。(one item for each capturing parenthesis that matched containing the text that was captured.)如果匹配失敗,exec 方法將返回 null。
var str="Hello world,hello zhaomenghuan!"; // look for "Hello"或"hello" var patt=/hello/gi; while((result = patt.exec(str))!== null){ document.write("result:" + result +"的位置為"+ result.index + "
"); } 結(jié)果: result:Hello的位置為0 result:hello的位置為12
RegExp.prototype.test() —— 檢索字符串中指定的值。返回 true 或 false。
用法:regexObj.test(str)
功能說明:test() 方法用于檢測一個(gè)字符串是否匹配某個(gè)模式,如果字符串中有匹配的值返回 true ,否則返回 false。
var result = /hello/.test("This is a hello world!"); document.write(result); 結(jié)果: true支持正則表達(dá)式的 String 對(duì)象的方法
search —— 檢索與正則表達(dá)式相匹配的值
用法:string.search(searchvalue)
searchvalue 必須。查找的字符串或者正則表達(dá)式。
功能說明:search()方法用于檢索字符串中指定的子字符串,或檢索與正則表達(dá)式相匹配的子字符串。如果沒有找到任何匹配的子串,則返回 -1。
var str="Mr. Blue has a blue house"; document.write(str.search(/blue/i)); 結(jié)果: 4
match —— 找到一個(gè)或多個(gè)正則表達(dá)式的匹配。
用法:string.match(regexp)
regexp 必需。規(guī)定要匹配的模式的 RegExp 對(duì)象。如果該參數(shù)不是 RegExp 對(duì)象,則需要首先把它傳遞給 RegExp 構(gòu)造函數(shù),將其轉(zhuǎn)換為 RegExp 對(duì)象。返回值類型為Array。
功能說明:match() 方法可在字符串內(nèi)檢索指定的值,或找到一個(gè)或多個(gè)正則表達(dá)式的匹配。match() 方法將檢索字符串 String Object,以找到一個(gè)或多個(gè)與 regexp 匹配的文本。這個(gè)方法的行為在很大程度上有賴于 regexp 是否具有標(biāo)志 g。如果 regexp 沒有標(biāo)志 g,那么 match() 方法就只能在 stringObject 中執(zhí)行一次匹配。如果沒有找到任何匹配的文本, match() 將返回 null。否則,它將返回一個(gè)數(shù)組,其中存放了與它找到的匹配文本有關(guān)的信息。
var str = "The rain in SPAIN stays mainly in the plain"; var match=str.match(/ain/gi); document.getElementById("demo").innerHTML=match; 結(jié)果: ain,AIN,ain,ain
replace 替換與正則表達(dá)式匹配的子串。
用法:string.replace(searchvalue,newvalue)
searchvalue 必須。規(guī)定子字符串或要替換的模式的 RegExp 對(duì)象。
請注意,如果該值是一個(gè)字符串,則將它作為要檢索的直接量文本模式,而不是首先被轉(zhuǎn)換為 RegExp 對(duì)象。
newvalue 必需。一個(gè)字符串值。規(guī)定了替換文本或生成替換文本的函數(shù)。
返回值為String類型,一個(gè)新的字符串,是用 replacement 替換了 regexp 的第一次匹配或所有匹配之后得到的。
split 把字符串分割為字符串?dāng)?shù)組。
用法:string.split(separator,limit)
separator 可選。字符串或正則表達(dá)式,從該參數(shù)指定的地方分割 string Object。
limit 可選。該參數(shù)可指定返回的數(shù)組的最大長度。如果設(shè)置了該參數(shù),返回的子串不會(huì)多于這個(gè)參數(shù)指定的數(shù)組。如果沒有設(shè)置該參數(shù),整個(gè)字符串都會(huì)被分割,不考慮它的長度。
返回值為Array類型,一個(gè)字符串?dāng)?shù)組。該數(shù)組是通過在 separator 指定的邊界處將字符串 string Object 分割成子串創(chuàng)建的。返回的數(shù)組中的字串不包括 separator 自身。
var str="How are you doing today?"; var match = str.split(/ /); document.write(match); 結(jié)果: How,are,you,doing,today?正則表達(dá)式的應(yīng)用實(shí)例說明
校驗(yàn)是否全由數(shù)字組成 —— /^[0-9]{1,20}$/
^ 表示打頭的字符要匹配緊跟^后面的規(guī)則
$ 表示打頭的字符要匹配緊靠$前面的規(guī)則
[ ] 中的內(nèi)容是可選字符集
[0-9] 表示要求字符范圍在0-9之間
{1,20}表示數(shù)字字符串長度合法為1到20,即為[0-9]中的字符出現(xiàn)次數(shù)的范圍是1到20次。
/^ 和 $/成對(duì)使用應(yīng)該是表示要求整個(gè)字符串完全匹配定義的規(guī)則,而不是只匹配字符串中的一個(gè)子串。
校驗(yàn)登錄名:只能輸入5-20個(gè)以字母開頭、可帶數(shù)字、“_”、“.”的字串—— /^[a-zA-Z]{1}([a-zA-Z0-9]|[._]){4,19}$/
^[a-zA-Z]{1} 表示第一個(gè)字符要求是字母。
([a-zA-Z0-9]|[._]){4,19} 表示從第二位開始(因?yàn)樗o跟在上個(gè)表達(dá)式后面)的一個(gè)長度為4到9位的字符串,它要求是由大小寫字母、數(shù)字或者特殊字符集[._]組成。
校驗(yàn)密碼:只能輸入6-20個(gè)字母、數(shù)字、下劃線——/^(w){6,20}$/
w:用于匹配字母,數(shù)字或下劃線字符
校驗(yàn)普通電話、傳真號(hào)碼:可以“+”或數(shù)字開頭,可含有“-” 和 “ ”——/^[+]{0,1}(d){1,3}[ ]?([-]?((d)|[ ]){1,12})+$/
d:用于匹配從0到9的數(shù)字;
“?”元字符規(guī)定其前導(dǎo)對(duì)象必須在目標(biāo)對(duì)象中連續(xù)出現(xiàn)零次或一次
可以匹配的字符串如:+123 -999 999 ; +123-999 999 ;123 999 999 ;+123 999999等
校驗(yàn)URL——/^http[s]{0,1}://.+$/ 或 /^http[s]{0,1}://.{1,n}$/ (表示url串的長度為length(“https://”) + n )
/ :表示字符“/”。
. 表示所有字符的集
+ 等同于{1,},就是1到正無窮吧。
校驗(yàn)純中文字符——/^[u4E00-u9FA5]+$/
[u4E00-u9FA5] :中文字符集的范圍
以上表達(dá)式均在下面的javascript中測試通過:
js模板引擎實(shí)現(xiàn)原理
前面我們花了很長的篇幅講解正則表達(dá)式是為了大家看這部分是時(shí)候能夠有所理解,如果前面的內(nèi)容一下子沒有看懂也沒有關(guān)系,大家可以先看看這部分的內(nèi)容回過頭去查看剛剛的內(nèi)容。
我們首先會(huì)想到寫一個(gè)模板,我們常見的是寫成這樣:
當(dāng)然也可以使用標(biāo)簽,而且這個(gè)也是現(xiàn)在的流行趨勢,擁抱模塊化,不過本文不是講這個(gè)標(biāo)簽和模塊化,如果大家感興趣可以看看這兩篇文章:
HTML5 標(biāo)簽元素簡介
Web Components 是什么?它為什么對(duì)我們這么重要?
大家也可以看下面這個(gè)基礎(chǔ)例子:
name: 小青年
age: 22
回歸正題,我們繼續(xù)說上面的模板,開始寫模板引擎。
基礎(chǔ)模板引擎原理講解我們使用js模板引擎,可以認(rèn)為是在做一個(gè)MVC結(jié)構(gòu)的系統(tǒng),模型(model)-視圖(view)-控制器(controller)??刂破?controller)作為中間部分,首先要拿到模型,這里我們需要拿到模板里面與視圖相關(guān)的內(nèi)容,如上面的例子中{{ }}中的內(nèi)容,首先用正則查找:
var re = /{{(.+?)}}/g; while((match = re.exec(tpl))!==null) { console.log(match); } 結(jié)果: "{{name}},name" "{{age}},age"
/{{(.+?)}}/g的意思是查找開頭為{{和結(jié)尾為}}的子字符串。通過RegExp 對(duì)象exec()方法搜索匹配得到的是一個(gè)數(shù)組,我們可以通過match[0]表示匹配的原字符串,match[1]表示匹配的目標(biāo)字符串,我們通過執(zhí)行字符串替換方法就可以得到目標(biāo)字符串。
這里我們通過data["key"]的形式取值然后替換模板中的{{...}}的內(nèi)容實(shí)現(xiàn)了一個(gè)內(nèi)容的替換。上述代碼很簡單,基本實(shí)現(xiàn)了一個(gè)字符替換而已,我們上面是通過字符串替換實(shí)現(xiàn)了模板和數(shù)據(jù)的匹配,但是如果我們上面那個(gè)json數(shù)據(jù)是這樣的:
var data = { base: { name: "zhaomenghuan", age: 22 }, skills: ["html5","javascript","android"] }
我們直接通過data[match[1]]進(jìn)行顯然會(huì)有問題,我們雖然可以通過data.base["name"]獲取,但是對(duì)于模板引擎函數(shù)封裝來說是不夠完善的,而且也不能執(zhí)行JavaScript,好像并沒有類似于一些有名的js模板引擎庫中的語法功能,所以略顯low。下面我們在這個(gè)基礎(chǔ)上進(jìn)行改造。
下面我們說一下一種最原始的方法,通過Function構(gòu)造器實(shí)現(xiàn),根據(jù)字符串創(chuàng)建一個(gè)函數(shù)。在一篇文章中看到說這種方法執(zhí)行JavaScript性能低下,但是對(duì)于初學(xué)者來說,學(xué)習(xí)一下實(shí)現(xiàn)思路我覺得也是有意義的,畢竟對(duì)于新手來說談性能是件奢侈的事。我們首先看個(gè)例子:
var fn = new Function("arg", "console.log(arg + 1);"); fn(2); // outputs 3
fn可是一個(gè)貨真價(jià)實(shí)的函數(shù),它接受一個(gè)參數(shù),函數(shù)體是console.log(arg + 1);,上面那個(gè)例子等同于:
var fn = function(arg) { console.log(arg + 1); } fn(2);
我們通過new Function可以將字符串轉(zhuǎn)成JavaScript執(zhí)行,看起來是不是很すごい(厲害,"sigoyi",好像還有個(gè)單詞是‘daijobu’,女朋友每次問我,我每次都是回答‘daijo’,女朋友喜歡看動(dòng)漫,今天她放假先回家了,舍不得,想想她平時(shí)萌萌噠的樣子,越是想念,學(xué)幾個(gè)簡單單詞,下次見面說說,哈哈。)
接著說,我們有時(shí)候參數(shù)是多個(gè),我們雖然可以輸入多個(gè)參數(shù),但是這顯然不是最好的辦法,我們可以使用apply,這樣我們不必顯式地傳參數(shù)給這個(gè)函數(shù)。比如我們前面的例子:
var data = { name: "zhaomenghuan", age: 22 } new Function("console.log(this.name + " is " + this.age +" years old.");").apply(data); 結(jié)果: "zhaomenghuan is 22 years old."
apply()會(huì)自動(dòng)設(shè)定函數(shù)執(zhí)行的上下文,這就是為什么我們能在函數(shù)里面使用this.name,這里this指向data對(duì)象。這里我們得到什么啟示呢?我們考驗(yàn)通過給new Function傳入模板字符串和數(shù)據(jù)生成我們的內(nèi)容。
我們可以通過數(shù)組push()或者+=拼接方式將分隔的字符串連接起來,有文章中稱,“在現(xiàn)代瀏覽器使用+=會(huì)比數(shù)組push方法快,在v8引擎中使用+=方法會(huì)比數(shù)組拼接快4.7倍,而在IE6-8下push比+=拼接快”。至于二者效率比較不在本文范圍內(nèi),大家可以自行探究,但是我們?yōu)榱撕喕瘑栴},不考慮效率問題,我們可以將分隔的字符串用下列方法push拼接:
var r=[]; r.push(""); r.push(this.name); r.push("
"); r.push(this.age); r.push("
"); return r.join("");
我們?nèi)绻苯悠唇映蓴?shù)組然后轉(zhuǎn)成對(duì)象也可以,但是需要將<>轉(zhuǎn)義,為了方便,我們有時(shí)候可以這樣處理:
var data = { name: "zhaomenghuan", age: 22 } var code = "var r=[]; "; code += "r.push(""); "; code += "r.push(this.name); " code += "r.push("
"); "; code += "r.push(this.age); "; code += "r.push("
"); "; code += "return r.join("");"; console.log(code) var fn = new Function(code.replace(/[ ]/g, "")).apply(data); console.log(fn) 結(jié)果: "zhaomenghuan
22
"
寫到這里我相信聰明的人應(yīng)該知道我接下來要做的事情了,主要是兩個(gè):如何根據(jù)我們自定義的分隔字符分隔模板字符串,然后就是動(dòng)態(tài)生成字符串。不過我們可以看出來這里我們還有個(gè)沒有提到的是讓模板能夠嵌入JavaScript的語法關(guān)鍵詞,比如if,for等,這個(gè)處理方法和上面的略有不同,需要加以判斷,不過我們可以劃分為解析html和js兩大類。
自定義分隔字符分隔模板字符在講如何分割字符串前我們先看三個(gè)函數(shù):slice|splice|split。
哈哈,是不是懵逼了?反正我經(jīng)常是暈的,不行,還是要對(duì)比一下搞清楚。
常用函數(shù) slice | splice | split 對(duì)比String.prototype.slice() —— 從一個(gè)字符串中提取字符串并返回新字符串
語法:str.slice(beginSlice[, endSlice])
參數(shù):
beginSlice:從該索引(以 0 為基數(shù))處開始提取原字符串中的字符。如果值為負(fù)數(shù),會(huì)被當(dāng)做 sourceLength + beginSlice 看待,這里的sourceLength 是字符串的長度 (例如, 如果beginSlice 是 -3 則看作是: sourceLength - 3)
endSlice:可選。在該索引(以 0 為基數(shù))處結(jié)束提取字符串。如果省略該參數(shù),slice會(huì)一直提取到字符串末尾。如果該參數(shù)為負(fù)數(shù),則被看作是 sourceLength + endSlice,這里的 sourceLength 就是字符串的長度(例如,如果 endSlice 是 -3,則是, sourceLength - 3)。
Array.prototype.slice()—— 把數(shù)組中一部分的淺復(fù)制(shallow copy)存入一個(gè)新的數(shù)組對(duì)象中,并返回這個(gè)新的數(shù)組。
語法:arr.slice([begin[, end]])
參數(shù):
begin:從該索引處開始提取原數(shù)組中的元素(從0開始)。
如果該參數(shù)為負(fù)數(shù),則表示從原數(shù)組中的倒數(shù)第幾個(gè)元素開始提取,slice(-2)表示提取原數(shù)組中的倒數(shù)第二個(gè)元素到最后一個(gè)元素(包含最后一個(gè)元素)。如果省略 begin,則 slice 從索引 0 開始。
end:在該索引處結(jié)束提取原數(shù)組元素(從0開始)。slice會(huì)提取原數(shù)組中索引從 begin 到 end 的所有元素(包含begin,但不包含end)。
slice(1,4) 提取原數(shù)組中的第二個(gè)元素開始直到第四個(gè)元素的所有元素 (索引為 1, 2, 3的元素)。如果該參數(shù)為負(fù)數(shù), 則它表示在原數(shù)組中的倒數(shù)第幾個(gè)元素結(jié)束抽取。 slice(-2,-1)表示抽取了原數(shù)組中的倒數(shù)第二個(gè)元素到最后一個(gè)元素(不包含最后一個(gè)元素,也就是只有倒數(shù)第二個(gè)元素)。如果 end 被省略,則slice 會(huì)一直提取到原數(shù)組末尾。
描述:
slice 不修改原數(shù)組,只會(huì)返回一個(gè)包含了原數(shù)組中提取的部分元素的一個(gè)新數(shù)組。原數(shù)組的元素會(huì)按照下述規(guī)則拷貝("一級(jí)深拷貝"[one level deep]規(guī)則):
如果該元素是個(gè)對(duì)象引用 (不是實(shí)際的對(duì)象),slice 會(huì)拷貝這個(gè)對(duì)象引用到新的數(shù)組里。兩個(gè)對(duì)象引用都引用了同一個(gè)對(duì)象。如果被引用的對(duì)象發(fā)生改變,則改變將反應(yīng)到新的和原來的數(shù)組中。
對(duì)于字符串和數(shù)字來說(不是 String 和 Number 對(duì)象),slice 會(huì)拷貝字符串和數(shù)字到新的數(shù)組里。在一個(gè)數(shù)組里修改這些字符串或數(shù)字,不會(huì)影響另一個(gè)數(shù)組。
如果向兩個(gè)數(shù)組任一中添加了新元素,則另一個(gè)不會(huì)受到影響。
Array.prototype.splice() —— 用新元素替換舊元素,以此修改數(shù)組的內(nèi)容
語法:array.splice(start, deleteCount[, item1[, item2[, ...]]])
參數(shù):
start?
從數(shù)組的哪一位開始修改內(nèi)容。如果超出了數(shù)組的長度,則從數(shù)組末尾開始添加內(nèi)容;如果是負(fù)值,則表示從數(shù)組末位開始的第幾位。
deleteCount
整數(shù),表示要移除的數(shù)組元素的個(gè)數(shù)。如果 deleteCount 是 0,則不移除元素。這種情況下,至少應(yīng)添加一個(gè)新元素。如果 deleteCount 大于start 之后的元素的總數(shù),則從 start 后面的元素都將被刪除(含第 start 位)。
itemN
要添加進(jìn)數(shù)組的元素。如果不指定,則 splice() 只刪除數(shù)組元素。
返回值:
由被刪除的元素組成的一個(gè)數(shù)組。如果只刪除了一個(gè)元素,則返回只包含一個(gè)元素的數(shù)組。如果沒有刪除元素,則返回空數(shù)組。
描述:
如果添加進(jìn)數(shù)組的元素個(gè)數(shù)不等于被刪除的元素個(gè)數(shù),數(shù)組的長度會(huì)發(fā)生相應(yīng)的改變。請注意,splice() 方法與 slice() 方法的作用是不同的,splice() 方法會(huì)直接對(duì)數(shù)組進(jìn)行修改。
String.prototype.split() —— 通過把字符串分割成子字符串來把一個(gè) String 對(duì)象分割成一個(gè)字符串?dāng)?shù)組
語法:string.split(separator,limit)
我們在前面講解【支持正則表達(dá)式的 String 對(duì)象的方法】時(shí)講到這個(gè)方法了,這里不再贅述,列出來只為方便大家對(duì)比學(xué)習(xí)。
這里列出的方法中對(duì)于我們分隔字符串有用的是String.prototype.slice()和String.prototype.split(),另個(gè)方法的區(qū)別在于使用slice()方法們需要知道子字符串的索引值index,使用split()方法我們需要子字符串的內(nèi)容或者符合的正則表達(dá)式。很明顯我們的思路就出來了,接下來我們用代碼實(shí)現(xiàn)。
基于String.prototype.slice()方法分隔字符串我們這里參考前面的內(nèi)容寫一個(gè)基本函數(shù),設(shè)置一個(gè)變量cursor作為索引值指針,每次執(zhí)行完一個(gè)匹配操作,我們將指針移動(dòng)一下。我們前面講RegExp.prototype.exec()返回值時(shí)重點(diǎn)說到了三個(gè)參數(shù)。
若match = re.exec(str),則有:
match.index:匹配的對(duì)象起始位置
match[0]:表示匹配的原字符串
match[1]:表示匹配的目標(biāo)字符串
若我們明白了思路我們就可以寫下面的代碼:
通過上面的代碼我們已經(jīng)實(shí)現(xiàn)了將模板字符串分隔成子字符串。
基于String.prototype.split()方法分隔字符串使用字符串split()方法,下面我們不使用正則作為分隔符號(hào),這里就使用自定義符號(hào)作為分隔標(biāo)準(zhǔn),如:
var sTag = "{%";//開始標(biāo)簽 var eTag = "%}";//結(jié)束標(biāo)簽
然后我們以sTag為分隔符執(zhí)行split方法:
var matchs = tpl.split(sTag);
返回值matchs 為一個(gè)子字符串?dāng)?shù)組,然后對(duì)數(shù)組每一個(gè)子字符串以eTag為分隔符執(zhí)行split方法,進(jìn)一步得到的子字符串?dāng)?shù)組分為兩種類型,一種是于我們匹配子字符串參數(shù)有關(guān)的子串?dāng)?shù)組,一種是與匹配參數(shù)無關(guān)的子串?dāng)?shù)組(這種數(shù)組長度為1)。之所以要分得這么細(xì)是為了后面字符串連接時(shí)更方便的時(shí)候合適的方法連接。
動(dòng)態(tài)連接字符串
我們上面使用了字符串分隔函數(shù)把字符串進(jìn)行了分隔,并且提取了關(guān)鍵子字符串,下面我們講解如何將這些字符串連接起來。
我們這里定義一個(gè)這樣的模板:
很明顯我們這個(gè)模板就相對(duì)前面的復(fù)雜得多了,但是基本思路是一樣的,無非是提取{{...}}的內(nèi)容,然后結(jié)合數(shù)據(jù)重新組合成新的html字符串。但是與前面不同的是我們分隔的子字符串中有三種類型:
含普通html標(biāo)簽的子字符串(如:
name:)
含js對(duì)象值的子字符串(如:this.name)
含javascript關(guān)鍵字的代碼片段(如:if (this.sex) {)
我們剛剛前面一直只提到了第1、2兩種,這兩種直接可以使用數(shù)組push方法就可以連接起來,但是第3中不能使用數(shù)組push,而是應(yīng)該直接連接。
所以這里我們分兩種情況:
var reExp = /(^( )?(var|if|for|else|switch|case|break|{|}|;))(.*)?/g,; var code = "var r=[]; "; // 解析html function parsehtml(line) { // 單雙引號(hào)轉(zhuǎn)義,換行符替換為空格,去掉前后的空格 line = line.replace(/("|")/g, "$1").replace(/ /g, " ").replace(/(^s+)|(s+$)/g,""); code +="r.push("" + line + ""); "; } // 解析js代碼 function parsejs(line) { // 去掉前后的空格 line = line.replace(/(^s+)|(s+$)/g,""); code += line.match(reExp)? line + " " : "r.push(" + line + "); "; }
當(dāng)我們寫完這兩個(gè)函數(shù)的時(shí)候,我們直接替換上面我們分隔字符串時(shí)候得到的字字符串時(shí)候打印的函數(shù)即可。當(dāng)然我們會(huì)看到很多文章為了壓縮代碼,將這兩個(gè)函數(shù)合并成一個(gè)函數(shù),其實(shí)對(duì)于我們理解這個(gè)問題,還原問題本質(zhì)并沒有實(shí)際意義,這里建議還是很開寫更清晰。
完整代碼如下:
另外一個(gè)自定義標(biāo)簽的和這個(gè)代碼類似,大家可以自己試試,或者看本文所有的代碼演示完整工程。
至此我們完成了一個(gè)基于正則表達(dá)式的簡單js模板引擎,本文目的不在于造一個(gè)輪子,也不是為了重復(fù)制造文章,只是把相關(guān)問題進(jìn)行梳理,在自己知識(shí)體系中形成一個(gè)更加清晰的思路,通過這個(gè)實(shí)際例子將正則表達(dá)式、數(shù)組和字符串相關(guān)知識(shí)點(diǎn)串起來,方面后面自己查閱,也方便初學(xué)者可以學(xué)習(xí)借鑒。
參考文章MDN JavaScript 標(biāo)準(zhǔn)庫 RegExp
js正則表達(dá)式基本語法(精粹)
只有20行Javascript代碼!手把手教你寫一個(gè)頁面模板引擎
TemplateEngine.js源代碼
template.js源代碼
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/79771.html
摘要:當(dāng)我們的視圖和數(shù)據(jù)任何一方發(fā)生變化的時(shí)候,我們希望能夠通知對(duì)方也更新,這就是所謂的數(shù)據(jù)雙向綁定。返回值返回傳入函數(shù)的對(duì)象,即第一個(gè)參數(shù)該方法重點(diǎn)是描述,對(duì)象里目前存在的屬性描述符有兩種主要形式數(shù)據(jù)描述符和存取描述符。 前言 談起當(dāng)前前端最熱門的 js 框架,必少不了 Vue、React、Angular,對(duì)于大多數(shù)人來說,我們更多的是在使用框架,對(duì)于框架解決痛點(diǎn)背后使用的基本原理往往關(guān)注...
摘要:第三部分介紹一種模板引擎,之所以介紹他是因?yàn)?。。。。三簡介模板引擎是?shù)據(jù)與界面分離工作中最重要一環(huán)。是新一代模板引擎,它采用預(yù)編譯方式讓性能有了質(zhì)的飛躍,并且充分利用引擎特性,使得其性能無論在前端還是后端都有極其出色的表現(xiàn)。 嗯,這也是一個(gè)學(xué)習(xí)筆記,材料均來自網(wǎng)絡(luò),有改動(dòng)。。文章主要分為三部分,第一部分通過實(shí)現(xiàn)一個(gè)簡易的ERB模板引擎來介紹其原理,參考javascript模板引擎和實(shí)現(xiàn)...
摘要:最全正則表達(dá)式總結(jié)驗(yàn)證號(hào)手機(jī)號(hào)中文郵編身份證地址等是正則表達(dá)式的縮寫,作用是對(duì)字符串執(zhí)行模式匹配。學(xué)習(xí)目標(biāo)了解正則表達(dá)式語法在中使用正則表達(dá)式在中使 JS高級(jí)技巧 本篇是看的《JS高級(jí)程序設(shè)計(jì)》第23章《高級(jí)技巧》做的讀書分享。本篇按照書里的思路根據(jù)自己的理解和經(jīng)驗(yàn),進(jìn)行擴(kuò)展延伸,同時(shí)指出書里的一些問題。將會(huì)討論安全的類型檢測、惰性載入函數(shù)、凍結(jié)對(duì)象、定時(shí)器等話題。1. 安全的類型檢測...
摘要:個(gè)人前端文章整理從最開始萌生寫文章的想法,到著手開始寫,再到現(xiàn)在已經(jīng)一年的時(shí)間了,由于工作比較忙,更新緩慢,后面還是會(huì)繼更新,現(xiàn)將已經(jīng)寫好的文章整理一個(gè)目錄,方便更多的小伙伴去學(xué)習(xí)。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 個(gè)人前端文章整理 從最開始萌生寫文章的想法,到著手...
閱讀 2658·2021-11-24 09:38
閱讀 2659·2019-08-30 15:54
閱讀 993·2019-08-30 15:52
閱讀 1977·2019-08-30 15:44
閱讀 2774·2019-08-30 13:48
閱讀 838·2019-08-29 16:21
閱讀 1071·2019-08-29 14:03
閱讀 2264·2019-08-28 18:15