本文為HTML標準解讀系列文章,其他文章詳見這里捂蕴。
在寫ol
元素的時候譬涡,我們可以添加一個名為start
的屬性,來表示有序列表的起始點:
<ol start="3">
<li>第三個</li>
<li>第四個</li>
<li>第五個</li>
</ol>
于是渲染出來的列表會從3開始啥辨,而不是從1開始:
3.第三個
4.第四個
5.第五個
這很好理解涡匀,但假如你給start
屬性賦予以下的值,又會有渲染出什么樣的結(jié)果呢溉知?
<ol start=" 3 "></ol>
<ol start="-3"></ol>
<ol start="+3"></ol>
<ol start="- 3"></ol>
<ol start="'3'"></ol>
<ol start="three"></ol>
想要知道答案陨瘩,笨方法是一個一個地在瀏覽器上去測試。之所以稱之為笨方法级乍,是因為得到的結(jié)論并不具有通用性:
-
比如舌劳,如果我又給出一個新的值,你還得重新去測試一次(別覺得這個值很離譜玫荣,它還真的被HTML當成3來用):
<ol start="3sfhdsajfhaugwie"></ol>
如果我換一個屬性甚淡,你還得重新測試一次。
從根本上解決這個問題的方法是:閱讀并理解HTML標準里關(guān)于整數(shù)的解析規(guī)則 捅厂。
解析的基本模式
你可以把解析的過程看成一個函數(shù)贯卦,這個函數(shù)接收一個待解析的字符串string
作為參數(shù),然后從前往后依次讀取string
的每一個字符焙贷,根據(jù)定義的規(guī)則判斷解析的結(jié)果撵割。執(zhí)行的過程中,函數(shù)內(nèi)部有一個position
變量盈厘,指向string
當前被讀取的字符睁枕。
HTML對于整數(shù)的解析規(guī)則
直接把標準給出的算法搬運過來,是這樣子的:
Let input be the string being parsed.
Let position be a pointer into input, initially pointing at the start of the string.
Let sign have the value "positive".
Skip ASCII whitespace within input given position.
If position is past the end of input, return an error.
If the character indicated by position (the first character) is a U+002D HYPHEN-MINUS character (-):
- Let sign be "negative".
- Advance position to the next character.
- If position is past the end of input, return an error.
Otherwise, if the character indicated by position (the first character) is a U+002B PLUS SIGN character (+):
- Advance position to the next character. (The "
+
" is ignored, but it is not conforming.)- If position is past the end of input, return an error.
If the character indicated by position is not an ASCII digit, then return an error.
Collect a sequence of code points that are ASCII digits from input given position, and interpret the resulting sequence as a base-ten integer. Let value be that integer.
If sign is "positive", return value, otherwise return the result of subtracting value from zero.
把它意譯過來的版本是這樣的:
令變量 input 為被解析的字符串。
令變量 position 作為 input 的指針外遇,初始的時候指向 input 的第一個字符注簿。
令變量 sign 的值為 "positive"。
跳過 input 開頭的 ASCII 空格跳仿,得到新的 position诡渴。
如果此時 position 越過了 input 的末尾,返回一個錯誤菲语。
如果此時 position 指向的字符是一個 U+002D HYPHEN-MINUS 字符 (-):
- 令 sign 的值為 "negative"妄辩。
- 將 position 前進到下一個字符。
- 如果 position 越過了 input 的末尾山上,返回一個錯誤眼耀。
否則,如果此時 position 指向的字符是一個 U+002B PLUS SIGN 字符 (+):
- 將 position 前進到下一個字符佩憾。 (雖然 "
+
" 會被忽略但并不符合規(guī)范哮伟。)- 如果 position 越過了 input 的末尾,返回一個錯誤妄帘。
如果此時 position 指向的字符不是 ASCII 數(shù)字楞黄,則返回一個錯誤。
從當前的 position 開始 收集一個碼點序列 抡驼,序列中每一個碼點必須是ASCII 數(shù)字 鬼廓,將得到的序列解析為以10為底數(shù)的整數(shù)。 令 變量 value 為該整數(shù)致盟。
如果 sign 為 "positive"碎税,返回 value,否則返回0減去 value 的結(jié)果馏锡。
就像我們前面說的蚣录,可以把解析的過程看成一個函數(shù)。于是眷篇,我們可以把每一句話“翻譯”成js代碼:
function parseInteger(strToBeParsed){
// 1. 令變量 input 為被解析的字符串。
const input = strToBeParsed
// 2. 令變量 position 作為 input 的指針荔泳,初始的時候指向 input 的第一個字符蕉饼。
let position = 0
// 3. 令變量 sign 的值為 "positive"。
let sign = "positive"
// 4. input 開頭的 ASCII空格玛歌,得到新的 position
// 根據(jù)引用昧港,ASCII空格的碼點為 U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE.
const ASCIIwhitespaceCodePoint = [0x0009, 0x000A, 0x000C, 0x000D, 0x0020]
while(ASCIIwhitespaceCodePoint.includes(input.charCodeAt(position))) position++
// 5. 如果此時 position 越過了 input 的末尾,返回一個錯誤
if (position >= strToBeParsed.length) throw 'Error at step 5'
// 6. 如果此時 position 指向的字符是一個 U+002D HYPHEN-MINUS 字符 (-):
if (input.charCodeAt(position) === 0x002D ) {
// 1. 令 sign 的值為 "negative"支子。
sign = 'negative'
// 2. 將 position 前進到下一個字符创肥。
position++
// 3. 如果 position 越過了 input 的末尾,返回一個錯誤。
if (position >= strToBeParsed.length) throw 'Error at step 6'
// 否則叹侄,如果此時 position 指向的字符是一個 U+002B PLUS SIGN 字符 (+)
} else if (input.charCodeAt(position) === 0x002B) {
// 1. 將 position 前進到下一個字符巩搏。 (雖然 "`+`" 會被忽略但并不符合規(guī)范。)
position++
// 2. 如果 position 越過了 input 的末尾趾代,返回一個錯誤贯底。
if (position >= strToBeParsed.length) throw 'Error at step 6'
}
// 7. 如果此時 position 指向的字符不是ASCII數(shù)字,則返回一個錯誤撒强。
// 根據(jù)引用禽捆,ASCII數(shù)字的碼點范圍為 U+0030 (0) to U+0039 (9)。
if (input.charCodeAt(position) < 0x0030 || input.charCodeAt(position) > 0x0039) throw 'Error at step 7'
// 8. 從當前的 position 開始收集一個碼點序列飘哨,序列中每一個碼點必須是ASCII數(shù)字胚想,將得到的序列解析為以10為底數(shù)的整數(shù)。 令變量 value 為該整數(shù)芽隆。
let sequence = ""
while (input.charCodeAt(position) >= 0x0030 && input.charCodeAt(position) <= 0x0039) {
sequence += input[position]
position ++
}
const value = parseInt(sequence, 10)
// 9. 如果 sign 為 "positive"浊服,返回 value,否則返回0減去 value 的結(jié)果摆马。
if (sign === 'positive') {
return value
} else {
return 0 - value
}
}
通過執(zhí)行這個自己寫的parseInteger
函數(shù)臼闻,我們就可以知道開篇那個問題的答案了:
console.log(parseInteger("3")) // 結(jié)果為3
console.log(parseInteger(" 3 ")) // 結(jié)果為3
console.log(parseInteger("-3")) // 結(jié)果為-3
console.log(parseInteger("+3")) // 結(jié)果為3
console.log(parseInteger("- 3")) // 報錯:Error at step 7
console.log(parseInteger("'3'")) // 報錯:Error at step 7
console.log(parseInteger("three")) // 報錯:Error at step 7
console.log(parseInteger("3sfhdsajfhaugwie")) // 結(jié)果為3
當然了,即便你給start
屬性賦予"three"這個值囤采,HTML也不會報錯述呐,而是回退到默認值1
秤茅。這是因為標準對于解析的錯誤做了一層封裝:
An
ol
element has a starting value, which is an integer determined as follows:
- If the
ol
element has astart
attribute, then:
- Let parsed be the result of parsing the value of the attribute as an integer.
- If parsed is not an error, then return parsed.
- If the
ol
element has areversed
attribute, then return the number of ownedli
elements.- Return 1.
算法的規(guī)范
通過上面的例子奕纫,你對標準中的算法應該也有一個大致的認識了附迷。而標準之所以為標準读存,是因為它的各個方面都足夠嚴謹厨钻。所以栈戳,標準應該如何書寫算法牡彻,也是有相應的規(guī)范的腔呜。 不僅僅是HTML標準棉磨,所有的web標準在書寫算法步驟的時候江掩,都遵循這個規(guī)范。所幸的是乘瓤,整個算法規(guī)范的篇幅并不算長环形,而且很多地方都是符合直覺的,大框架基本可以總結(jié)如下:
1. 聲明算法
聲明一個算法衙傀,表達的格式是這樣的:
To [algorithm name] , given a [type1] [parameter1], a [type2] [parameter2], …, perform the following steps. They return a [return type].
這里有三個重要的部分:
- 算法名稱[algorithm name]
- 參數(shù)[parameter] 及其類型[type]
- 返回值的類型[return type]
在所有算法的聲明中抬吟,這3個部分不僅出現(xiàn)的先后順序一樣,文本格式也是不變的(名稱都會加粗统抬,參數(shù)都是斜體)火本。除了算法名稱以外危队,其他兩個都不是必須的。
舉個例子:以下是判斷元素是否懶加載的算法:
The will lazy load element steps, given an element element, are as follows:
- ... return false.
- ... return true
- Return false
這個算法的名稱為will lazy load element steps钙畔,接受一個類型為element的element參數(shù)茫陆。這里的返回類型沒有在聲明語句中表明,而是在具體步驟中表示出來刃鳄。
2. 變量
聲明一個變量使用let
盅弛,修改一個變量使用set
。
- <u>Let</u> value be null.
- If input is a string, then <u>set</u> value to input.
3. 控制流
返回使用return
叔锐,拋出錯誤使用throw
挪鹏。
4. 遍歷
For Each
:對于list或map中的每一個item執(zhí)行特定的步驟。
While
:當條件滿足的時候執(zhí)行特定的步驟愉烙。如:
While condition is "met":
- …
5. 斷言
使用Assert
讨盒。Assert
表明后面的句子必須為真:
Let x be "
Aperture Science
".Assert: x is "
Aperture Science
".
為何有的算法難以看得懂?
像javaScript這種高級程序語言步责,它既被要求能夠在只認識0和1的計算機上跑起來返顺,又要保證對于人類一定的可讀性。而標準里的算法完全是寫給人看的蔓肯,不需要兼顧計算機遂鹊,用的都是人話。從這個角度上看蔗包,相比于讀代碼秉扑,讀標準里的算法實際上應該更輕松才對。
但有時候我們覺得這些算法難以讀懂调限,我認為可以總結(jié)為兩點原因:
- 英語不行
- 概念不夠(核心原因)
英語不行
盡管HTML標準也有中文版舟陆,但我極度不建議大家依賴中文版。
一方面耻矮,中文版的翻譯有很多過時的內(nèi)容秦躯。就像我在HTML標準第一章解讀說到的一樣,現(xiàn)在HTML標準是living standard裆装,內(nèi)容是動態(tài)更新的踱承,上一次的更新就在今年的10月7日,距離發(fā)表這篇文章時的幾天前哨免。而中文版的翻譯完全跟不上這個更新速度勾扭,我在中文版中隨便搜了一下,目前有235處標記了「本小節(jié)的翻譯已過時」铁瞒。
另一方面,HTML標準是需要和很多其他的web標準一起配合閱讀的桅滋,就像標準第一章所給出的那張標準之間的關(guān)系圖一樣:
在閱讀的過程中慧耍,你點的鏈接時不時就會跳到其他的標準里身辨,而大多數(shù)的這些標準是沒有中文翻譯的。
更重要的是芍碧,依賴中文版會讓你永遠無法獲得專業(yè)級別的英文文檔閱讀能力煌珊。HTML標準里的每一個詞匯的使用、每一個句子的表達都是經(jīng)過W3C和WHATWG委員會仔細推敲泌豆、反復修訂定庵、嚴格審核而成的,用的都是世界上最地道的計算機英語踪危。你越是依賴中文資料蔬浙,你對英文文檔就越恐懼,于是英文水平就越可能成為你技術(shù)進步的巨大阻礙贞远。
所以畴博,我的建議,包括我自己也是這么做的:只讀英文版的標準蓝仲,在閱讀過程中遇到的所有問題俱病,也只用英文進行檢索。 這就好比如果你要通過舉啞鈴鍛煉肱二頭肌袱结,你需要刻意避免在舉啞鈴的過程中使用到腰部的力量亮隙,這樣才能最大程度地刺激肱二頭肌。為了打磨英文文檔閱讀的能力垢夹,你就應該刻意限制自己的中文使用溢吻。
概念不夠
然而,相比于英語不行棚饵,概念不夠才是更加核心的原因煤裙。
舉個例子,在本文所舉的整數(shù)解析的算法例子中噪漾,如果你的腦子里面沒有「字符集」硼砰、「字符編碼」的概念,那么你很難清晰地理解算法里所說的「U+002D 字符」是什么東西欣硼,「code point/碼點」又是什么東西题翰。相反的,如果你不但理解「字符集」诈胜、「字符編碼」豹障,你還知道有個東西叫「狀態(tài)機」,那么這段算法對你來說簡直就像白開水一樣焦匈。
另一個例子是HTML標準2.2 小節(jié)血公,這一小節(jié)只有短短的4行:
This document defines the following policy-controlled features:
- "
autoplay
", which has a default allowlist of'self'
.- "
cross-origin-isolated
", which has a default allowlist of'self'
.- "
document-domain
", which has a default allowlist of*
.
雖然只有短短的四行,英文也非常簡單缓熟。但如果你的腦子里面沒有「Permission Policy」或者「Feature Policy」的概念累魔,你不可能知道這是在講什么東西摔笤。
在閱讀標準的過程中,你會遇到大量前置引用的情況垦写。 比如一個在第八章才給予正式定義的概念吕世,在第二章的時候就拿出來用了。也有很大一部分的概念是在其他的web標準中定義的梯投,你需要跳到其他的標準才能看到這個概念的完整解釋命辖。而當你的腦子里缺乏必要的概念的時候,往往就很難正確理解一個句子分蓖。
我在閱讀標準的過程中也常常遇到讀不懂的地方尔艇,遇到這種情況,我的「算法」一般是這樣的:
反復多次閱讀咆疗。
-
刻意尋找句子中那些不明白意思的詞組漓帚,這些詞組往往對應著一個關(guān)鍵的概念,也是理解整個句子的關(guān)鍵午磁。
這里需要注意的是尝抖,有時候有的詞組/概念你經(jīng)常看到迅皇,以至于你以為你知道這是什么意思昧辽,但可能實際上并不是這樣的。衡量的標準是:你能否用自己的話清晰地表達出這個詞組/概念是什么意思登颓。
對于這些不理解的詞組:如果有索引搅荞,就跳到他的索引上去看。HTML標準的交叉索引做得非常好框咙,通過索引你常常能看到這個詞組的定義咕痛,或者這個詞組的多個不同的應用場景,我在第二章總結(jié)中給大家舉了一個例子喇嘱。
如果沒有索引茉贡,或者看了索引的資料也看不懂,就去谷歌者铜,使用MDN等輔助材料來幫助理解腔丧。
如果以上的步驟都做完了還是無法理解,可能說明這個句子有一些隱藏得更深的關(guān)鍵概念作烟,是我現(xiàn)在的知識網(wǎng)絡中沒有的愉粤。于是我會先跳過這部分,往后讀拿撩。我經(jīng)常會發(fā)現(xiàn)衣厘,當我過一段時間后再回來讀,會拍自己大腿:這么簡單的東西我當初咋看不懂呢压恒?
一切都是可以積累的
不管你的現(xiàn)狀是怎么樣头滔,一切都是可以積累的怖亭。英語不行,可以通過大量閱讀英文文檔來提升英文能力坤检。概念不夠,可以通過大量的刻意積累來編織健壯的知識網(wǎng)絡期吓。強如尤雨溪早歇,也不是一生出來就會寫js的。所以不必低估自己讨勤,覺得自己讀不懂標準箭跳。只要不斷改進、不斷積累潭千,保持耐心谱姓,每個人都是可以變得越來越好的。