標籤:

CSS中屬性的快捷寫法是如何被識別的?

比如:

效果是一樣的。

那麼後面兩種的寫法是怎麼被識別出來的?(怎麼知道哪個數值是賦給color或者width或者style?)如果是一個個進行遍歷檢查豈不是效率很低?


被 @貘吃饃香 @ 過來的。

要我說啥?

我要說的很簡單,你(們)的答案太他媽啰嗦了(特別是貼瀏覽器引擎代碼的,完全是拿大炮打蚊子)。

一句話回答:CSS length和color的token是無歧義的,也就是不會有個字元序列既可能是length又可能是color。

要多說的話:

為什麼它們是無歧義的?因為token設計為無歧義,詞法分析器才能工作,否則就需要額外的語義輔助。CSS語言本身要保持簡單,自然不會引入這種複雜性。而且對機器方便的規則通常對人也是方便的——你也一眼就能看出那是個length還是個color。

至於效率問題,parse的開銷跟layout比可忽略不計。如果題主對如何寫高性能parser有興趣,請學習編譯器前端(不是Web前端)相關知識。


目前 CSS Parser 的開源實現主要在 WebKit 系布局引擎和 Gecko 里,因為 WebKit 系的 CSS 用的是 bison 自動生成的 Parser, 比較難說明下面怎麼跑的,這裡選擇了有手寫 Parser (而且文檔/注釋多很多的)的 Gecko 的源代碼來分析(前一步的 lexer 怎麼跑的我就不分析了,學過編譯原理的應該都懂……)。

因為 Gecko 文檔比較全,先 Google 看看有沒有相關線索,在 MDN 找到了在 Gecko 代碼里添加新 shorthand 解析的步驟:Adding a new CSS property,我們可以看到這樣一段話:

For further understanding of how the parsing code works, you should read and understand the code in nsCSSParser.cpp.

嗯,那就去代碼倉庫里找吧,在 nsCSSParser.cpp 里搜索 border,仔細瀏覽,找到了相關的部分:firefox mozilla/layout/style/nsCSSParser.cpp

5381 PRBool CSSParserImpl::ParseBorderSide(nsresult aErrorCode,
5382 const nsCSSProperty aPropIDs[],
5383 PRBool aSetAllSides)
5384 {
5385 const PRInt32 numProps = 3;
5386 nsCSSValue values[numProps];
5387
5388 PRInt32 found = ParseChoice(aErrorCode, values, aPropIDs, numProps);
5389 if ((found &< 1) || (PR_FALSE == ExpectEndProperty(aErrorCode))) { 5390 return PR_FALSE; 5391 } 5392 5393 if ((found 1) == 0) { // Provide default border-width 5394 values[0].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated); 5395 } 5396 if ((found 2) == 0) { // Provide default border-style 5397 values[1].SetNoneValue(); 5398 } 5399 if ((found 4) == 0) { // text color will be used 5400 values[2].SetIntValue(NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR, eCSSUnit_Enumerated); 5401 } 5402 5403 if (aSetAllSides) { 5404 static const nsCSSProperty kBorderSources[] = { 5405 eCSSProperty_border_left_color_ltr_source, 5406 eCSSProperty_border_left_color_rtl_source, 5407 eCSSProperty_border_right_color_ltr_source, 5408 eCSSProperty_border_right_color_rtl_source, 5409 eCSSProperty_border_left_style_ltr_source, 5410 eCSSProperty_border_left_style_rtl_source, 5411 eCSSProperty_border_right_style_ltr_source, 5412 eCSSProperty_border_right_style_rtl_source, 5413 eCSSProperty_border_left_width_ltr_source, 5414 eCSSProperty_border_left_width_rtl_source, 5415 eCSSProperty_border_right_width_ltr_source, 5416 eCSSProperty_border_right_width_rtl_source, 5417 eCSSProperty_UNKNOWN 5418 }; 5419 5420 InitBoxPropsAsPhysical(kBorderSources); 5421 5422 // Parsing "border" shorthand; set all four sides to the same thing 5423 for (PRInt32 index = 0; index &< 4; index++) { 5424 NS_ASSERTION(numProps == 3, "This code needs updating"); 5425 AppendValue(kBorderWidthIDs[index], values[0]); 5426 AppendValue(kBorderStyleIDs[index], values[1]); 5427 AppendValue(kBorderColorIDs[index], values[2]); 5428 } 5429 } 5430 else { 5431 // Just set our one side 5432 for (PRInt32 index = 0; index &< numProps; index++) { 5433 AppendValue(aPropIDs[index], values[index]); 5434 } 5435 } 5436 return PR_TRUE; 5437 }

可以看出這裡使用了 ParseChoice 去解析值放到 values 裡面,然後判斷值的個數。如果值有 3 個,就將 values 里的三個值依次放到所有 4 條邊對應的屬性數組裡面去。

然後我們再往上看,誰調用 ParseBorderSide 這個函數,搜索找到

4257 PRBool CSSParserImpl::ParseProperty(nsresult aErrorCode,
4258 nsCSSProperty aPropID)

裡面有

4267 case eCSSProperty_border:
4268 return ParseBorderSide(aErrorCode, kBorderTopIDs, PR_TRUE);

所以 Parser 應該是發現屬性名為 border,就按照 CSS Backgrounds and Borders Module Level 3 裡面定義的:

& || & || &

去嘗試解析屬性值(註:在 spec 裡面 || 表示值的順序隨意且可選填,按照類型(顏色、幾何維度、某些特定關鍵詞)區分,參考About the CSS 2.1 Specification)。

另外注意到這裡傳入的形參 aPropIDs,對應實參是 kBorderTopIDs,再找找看 kBorderTopIDs 的定義:

3574 static const nsCSSProperty kBorderTopIDs[] = {
3575 eCSSProperty_border_top_width,
3576 eCSSProperty_border_top_style,
3577 eCSSProperty_border_top_color
3578 };

嗯看來只是復用了 border-top 的值列表,因為反正都是那三個值。

接下來我們再挖一下 ParseBorderSide 里的這一行調用:

PRInt32 found = ParseChoice(aErrorCode, values, aPropIDs, numProps);

4117 PRInt32 CSSParserImpl::ParseChoice(nsresult aErrorCode, nsCSSValue aValues[],
4118 const nsCSSProperty aPropIDs[], PRInt32 aNumIDs)
4119 {
4120 PRInt32 found = 0;
4121 nsAutoParseCompoundProperty compound(this);
4122
4123 PRInt32 loop;
4124 for (loop = 0; loop &< aNumIDs; loop++) { 4125 // Try each property parser in order 4126 PRInt32 hadFound = found; 4127 PRInt32 index; 4128 for (index = 0; index &< aNumIDs; index++) { 4129 PRInt32 bit = 1 &<&< index; 4130 if ((found bit) == 0) { 4131 if (ParseSingleValueProperty(aErrorCode, aValues[index], aPropIDs[index])) { 4132 found |= bit; 4133 } 4134 } 4135 } 4136 if (found == hadFound) { // found nothing new 4137 break; 4138 } 4139 }

事到如今很明顯了,就是遍歷每種可能值的類型(比如 border 的情況就是依次嘗試 width、style、color),嘗試匹配讀到的每一個值(看到兩個 for loop 是不是有種微醺的感覺哈哈哈哈)。

我們再看看 ParseSingleValueProperty 的實現:

4637 case eCSSProperty_border_bottom_color:
4638 case eCSSProperty_border_end_color_value: // for internal use
4639 case eCSSProperty_border_left_color_value: // for internal use
4640 case eCSSProperty_border_right_color_value: // for internal use
4641 case eCSSProperty_border_start_color_value: // for internal use
4642 case eCSSProperty_border_top_color:
4643 return ParseVariant(aErrorCode, aValue, VARIANT_HCK,
4644 nsCSSProps::kBorderColorKTable);
4645 case eCSSProperty_border_bottom_style:
4646 case eCSSProperty_border_end_style_value: // for internal use
4647 case eCSSProperty_border_left_style_value: // for internal use
4648 case eCSSProperty_border_right_style_value: // for internal use
4649 case eCSSProperty_border_start_style_value: // for internal use
4650 case eCSSProperty_border_top_style:
4651 return ParseVariant(aErrorCode, aValue, VARIANT_HOK,
4652 nsCSSProps::kBorderStyleKTable);
4653 case eCSSProperty_border_bottom_width:
4654 case eCSSProperty_border_end_width_value: // for internal use
4655 case eCSSProperty_border_left_width_value: // for internal use
4656 case eCSSProperty_border_right_width_value: // for internal use
4657 case eCSSProperty_border_start_width_value: // for internal use
4658 case eCSSProperty_border_top_width:
4659 return ParsePositiveVariant(aErrorCode, aValue, VARIANT_HKL,
4660 nsCSSProps::kBorderWidthKTable);

其中kBorderColorKTable、kBorderStyleKTable、kBorderWidthKTable 的定義可以在 mozilla-central mozilla/layout/style/nsCSSProps.cpp 找到,就是把各種類型的值的可能取值列出來,比如:

863 const KTableValue nsCSSProps::kBorderStyleKTable[] = {
864 eCSSKeyword_none, NS_STYLE_BORDER_STYLE_NONE,
865 eCSSKeyword_hidden, NS_STYLE_BORDER_STYLE_HIDDEN,
866 eCSSKeyword_dotted, NS_STYLE_BORDER_STYLE_DOTTED,
867 eCSSKeyword_dashed, NS_STYLE_BORDER_STYLE_DASHED,
868 eCSSKeyword_solid, NS_STYLE_BORDER_STYLE_SOLID,
869 eCSSKeyword_double, NS_STYLE_BORDER_STYLE_DOUBLE,
870 eCSSKeyword_groove, NS_STYLE_BORDER_STYLE_GROOVE,
871 eCSSKeyword_ridge, NS_STYLE_BORDER_STYLE_RIDGE,
872 eCSSKeyword_inset, NS_STYLE_BORDER_STYLE_INSET,
873 eCSSKeyword_outset, NS_STYLE_BORDER_STYLE_OUTSET,
874 eCSSKeyword_UNKNOWN,-1
875 };

再在 ParseVariant 里找到這幾行:

3804 if ((aVariantMask VARIANT_KEYWORD) != 0) {
3805 PRInt32 value;
3806 if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
3807 aValue.SetIntValue(value, eCSSUnit_Enumerated);
3808 return PR_TRUE;
3809 }
3810 }

嗯就是查表,如果類型配上了就設值(VARIANT_* 是堆 mask,定義請參見firefox mozilla/layout/style/nsCSSParser.cpp )。如果沒配上就 Unget 掉(上一層調用的那個 for loop 會繼續嘗試其他的類型或者進入錯誤處理):

3912 UngetToken();
3913 return PR_FALSE;

ParsePositiveVariant 是調用 ParseVariant 的,多了一個正負判斷而已,這裡不贅述。

所以綜上所述,就是題主你說的那樣,效率很低地遍歷匹配的……當然你要考慮這是 C++,而且其實並不會遍歷那麼多遍(比如 border 才 3 x 3 而已),所以其實沒有那麼糟糕……(以前看 WebKit 代碼的時候也經常有類似「卧槽你直接線性查找嗎」 的時候,但是仔細一想好像最壞的場景數據量也大不到哪裡去,真正比較關鍵的地方他們一般會做一些優化不這麼暴力的)

按理來說 WebKit 系那個 bison 生成的 shift-reduce parser 應該也是逐個嘗試的,每遇上一個新值就查表找類型看符不符合寫的規則,如果沒有一個符合就進入錯誤處理,符合了就 shift 滿了 3 個(可能不是硬寫的 3,大概會有一些 trick 讓它是個變數)(可能還要判斷下分號)就 reduce 然後分析下一個。感興趣的話可以去看比如 Blink 的那個塞給 bison 的文件 [blink] Contents of /trunk/Source/core/css/parser/CSSGrammar.y


CSS 屬性值里如果有多個值,如果類型不同則能直接按類型對號入座,否則規範中一定會定義「第一個 & 表示 xx,第二個 & 表示 yy」於是可以根據順序選擇對應屬性。

舉個 animation 的例子:

Note that order is important within each animation definition: the first value in each & that can be parsed as a & is assigned to the animation-duration, and the second value in each & that can be parsed as a & is assigned to animation-delay.

題主覺得簡寫降低運行效率,那是很有可能的,但是在整個體系中這是次要得不能再次要的地方。簡寫降低了開發和傳輸的成本,


瀉藥

其實偶不知道這個

偶瞎猜的湊合答下

(偶就是不說看源碼和規範,這下 @賀師俊 看你怎麼說 :P)

基本上可以說是遍歷檢查的

估計大概是這樣一套邏輯

首先是 Token 化

這個規則 W3C 上有,自己查吧

關鍵字:CSS Syntax Module Level 3

一個 CSS 字元串根據語法規則先分解為不同 Token

然後進行 parse

parse 過程中可以根據 Token 順序進行屬性id(CSSPropertyID) 賦值

如你的問題里

這三個屬性值跟在 border 後面,

border 又是個 border 系列樣式的簡寫

都會是 CSSPropertyBorder

然後會進行parser short property 的操作

其實裡頭就是一堆 switch case 啦

進來的 CSSPropertyID 如果是 CSSPropertyBorder 就做短轉長

這裡會對 Border 短對長做個數組,拆分成 border-xxx-xxx 樣子

由於 CSS 規範里有規定屬性取值範圍

所以 CSSPropertyBorder 里的所有屬性值與歸類都有了

比如 2px 這種是 dimension 的

這個只有 border-width 才有

那麼 border-top-width ... 的 4 個都給這個值

比如 solid 這樣是 ident 的就走下值表

查到屬於 border-style 的

就 border-right-style .... 4 個都給這值

比如 #ff0000 這樣的是 color 的就給

就 border-right-style .... 4 個都給這值

當然 red 之類, paser 時候(可能)已經轉為對應#0000ff 之類串了

類似rgb(x,x,x)也是這麼處理的興許

三個屬性再這裡又進行一遍 switch case 先

各歸各位

如果遇到缺的

比如一個值兩個值

短轉長里會給剩下沒值的賦規範規定的初始值

這樣出來是一套 border-xxx-xxx 樣式值設定

特註:以上完全靠猜


很明顯啊,#ff0000明顯只可能是顏色,solid明顯只可能是線的類型,1px出只可能是長度,只要無二義性就行了

如果能找到有二義性組合屬性再來提也不遲


貌似下面兩種的寫法是不推薦的,印象中挺多屬性簡寫順序錯了的話是會不生效的,現在竟然可以識別了,大概就是瀏覽器幫傻缺程序員填的坑,請至少遵從這種語法層面的規範,不要去挑戰瀏覽器的自動糾錯能力

印象中border簡寫的順序是 width, style, color


如果只是這麼一種演算法瀏覽器都算複雜的話,那css3的那些陰影效果,動畫,漸變豈不是瀏覽器要奔潰了。


其實c++裡面就是遍歷的,看答覆里貼的底層c++代碼也能看出來。 前端人員應該是被優化各種前端性能問題嚇習慣了, 習慣性用js思維考慮底層實現。 最後我們發現任何一段渣渣c或c++底層代碼實際在瀏覽器的性能, 都超過前端js精心調優的一段js代碼, 同理css


這個因為是三個值都不一樣吧,可以通過屬性值的類型判斷屬性名。其他的有相同值的,就不好判斷的了,瀏覽器是增加容錯性的了,沒有報錯。


推薦閱讀:

Adobe Muse 會改變前端設計師這個職業嗎?
為什麼要用刪除線,刪除線意義何在?
分子生物學專業學習應用程序開發有用嗎?

TAG:網頁設計 | CSS |