0作為一個特殊的符號,經常會跟瀏覽器打交道,在不同的場景下,0代表的意思不盡相同,因此瀏覽器眼中的0不一定就是符合人們感官上的認識,那究竟瀏覽器會怎麼對待它呢,今天我們就來探究一下各種場景中0的含義及瀏覽器的處理方式。
1.setTimeout
setTimeout在js中常用來推遲任務的執行,可以通過第二個引數設定延遲的毫秒數(如果不設定,預設為0),在一些程式碼中,可以看到delay=0的情況,如下:
window.setTimeout(() => { ...... }, 0);
瞭解js的同學應該知道,setTimeout的回撥函式不會在定時器超時後立即執行,如果delay大於0,比較好理解,但delay是0的時候呢,瀏覽器會怎麼對待呢,這裡要分兩種情況:
1.)timer巢狀
'Timers can be nested; after five such nested timers, however, the interval is forced to be at least four milliseconds.'
也就是setTimeout巢狀超過5層的,並且延遲不到4ms,才會變成4ms,同樣適用於setInterval,因此在這種情況下,delay=0其實會被設定成4;
2.)timer沒有巢狀
在沒有巢狀情況下,對於chrome來說,delay=0也會設定成1;
說完瀏覽器的處理方式之後,我們來看看網上搬過來的一個例子吧:
setTimeout(()=>{console.log(5)},5)
setTimeout(()=>{console.log(4)},4)
setTimeout(()=>{console.log(3)},3)
setTimeout(()=>{console.log(2)},2)
setTimeout(()=>{console.log(1)},1)
setTimeout(()=>{console.log(0)},0)
chrome列印結果:1 0 2 3 4 5;
firefox列印結果:0 1 2 3 4 5;
edge列印結果:1 2 0 3 4 5;
qq瀏覽器列印結果:1 0 2 3 4 5;
360瀏覽器列印結果:1 0 2 3 4 5;
從上面的列印結果來看,firefox是符合程式碼預期的,edge列印與chrome稍有不同,應該是edge處理delay=0情況稍有不同(設定成了2),qq和360瀏覽器跟chrome保持一致。
0ms定時器
在MDN文件上,還說到一種實現0ms延時的定時器的實現方案,大體思路是自定義一個setZeroTimeout
方法,通過 postMessage
來觸發定時回撥的執行,具體可看 https://dbaron.org/log/20100309-faster-timeouts ;
node的setTimeout
說完瀏覽器中的setTimeout,我們再來看看nodejs中的是否一樣呢,可以通過nodejs的原始碼窺探一二:
// https://github.com/nodejs/node/blob/master/lib/internal/timers.js
function Timeout(callback, after, args, isRepeat, isRefed) {
after *= 1; // Coalesce to number or NaN
if (!(after >= 1 && after <= TIMEOUT_MAX)) { // const TIMEOUT_MAX = 2 ** 31 - 1;
if (after > TIMEOUT_MAX) {
process.emitWarning(`${after} does not fit into` +' a 32-bit signed integer.' + '\nTimeout duration was set t 1.', 'TimeoutOverflowWarning');
}
after = 1; // Schedule on next tick, follows browser behavior
}
看過原始碼後,就知道node的處理策略了:如果delay=0,會設定為1,註釋也說得很清楚了,是為了遵循瀏覽器的行為。
2.+0&-0
雖然很少用到,但是js中確是存在+0和-0的,那麼有什麼區別呢:
+0 === -0; // true
+0 === 0; // true
-0 === 0; // true
可以看到,通過全等比較,+0,-0和0都是相等的,那是否就可以認為這三者就是一樣的呢,還不能這麼輕易下結論,有時候很有必要區分三者,那麼如何判斷呢,es6新增了一個方法Object.is(value1, value2),可以用來判斷,具體效果如下:
Object.is(0, -0); // false
Object.is(+0, -0); // false
Object.is(+0, 0); // true
這裡還需要說明的一點就是+0和0其實就是一樣的,因為+0等效Number(0),因此Object.is(+0, 0)是符合預期的,這裡順帶說一下Object.is的比較邏輯,根據MDN文件描述,
Object.is()
方法判斷兩個值是否為同一個值。如果滿足以下條件則兩個值相等:
與==
運算不同。 ==
運算子在判斷相等前對兩邊的變數(如果它們不是同一型別) 進行強制轉換 (這種行為的結果會將 "" == false
判斷為 true
), 而 Object.is
不會強制轉換兩邊的值。
與===
運算也不相同。 ===
運算子 (也包括 ==
運算子) 將數字 -0
和 +0
視為相等 ,而將Number.NaN
與NaN
視為不相等.
最後來看下如何生成-0和+0吧:
1/-Infinity; // -0
0*-1; // -0
1/+Infinity; // +0
0*+1;// +0
3. +[]
由於存在型別轉換,因此在判斷相等時,會有這樣的情況:
'' == 0; // true
[] == 0; // true
+[] == 0; // true
這裡我們說一下+[]的情況,很容易理解,+相當於Number([]),最終會轉換成0,這沒什麼大不了,如果是下面這段程式碼呢:
(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[+!+[]+[!+[]+!+[]+!+[]]]+[+!+[]]+([+[]]+![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[!+[]+!+[]+[+[]]]
其實代表的就是alert(1)
, http://www.jsfuck.com/ 這個奇葩網站專門幹這事的,原理就是使用的型別轉換,把我們熟悉的程式碼變成了這樣,真是騷操作。
4.font-size
font-size在css中使用頻繁,瀏覽器通常會預設設定成14px或者16px。如果設定成0呢?
.foo {
font-size: 0;
}
<span class="foo">foo</span>
文字真的就沒有了,這符合預期,但如果設定的值小於12(非負),各瀏覽器處理稍有不同,chrome/edge瀏覽器會設定最小字型為12px,firefox嚴格按照給定的值來顯示。
font-size:0的用處
在佈局過程中,經常會生成空白字元,例如:
<div class="box1">
<span>a</span>
<span>b</span>
</div>
<div class="box2">
<img src="xxx.jpg">
</div>
box1和box2高度其實是略高於實際的內容的,而且box1的兩個span中間有間隙,沒有緊挨著,因為空白字元的原因,引用css規範說明:On a block container element whose content is composed of inline-level elements, 'line-height' specifies the minimal height of line boxes within the element. The minimum height consists of a minimum height above the baseline and a minimum depth below it, exactly as if each line box starts with a zero-width inline box with the element's font and line height properties. We call that imaginary box a "strut.",大意就是一個塊級容器元素內容區域由inline-level元素組成的;而這些linline-level元素被放在每一行的line-box裡面,line-box高度是由它所有子元素的高度計算得出的。瀏覽器會計算這一行裡每個子元素的高度,再得出 line-box 的高度(具體來說就是從子元素的最高點到最低點的高度)。預設情況下,一個 line-box 總是有足夠的高度來容納它的子元素,而每一個行框可以想象為預設會有一個寬度為0的空白節點,字型大小和行高會影響該節點,具體規範可檢視 https://www.w3.org/TR/CSS2/visudet.html#line-height。
說完規範後,再來分析一下box1和box2的效果,為了便於理解,下面的程式碼手動加入了一個[x]
符號,代表strut:
<div class="box1">
<span>a</span> <span>b</span>
[x]
</div>
<div class="box2">
<img src="xxx.jpg">[x]
</div>
box1的兩個span發生了換行,相當於中間有個空格,因此會有間隙,如果span不換行,那麼中間的空隙也就沒有了。由於存在[x]
,而且vertical-align:baseline的緣故,span/img和[x]
的baseline對齊之後,[x]
處於baseline以下的部分會撐開整個line-box的高度,因此會外部塊級容器的高度會略高一些,說得不清楚,我們還是上一張圖來說明吧:
粉色的線是圖片的baseline,紅色的線是strut的baseline,對齊之後strut的baseline下面還有一部分高度,box的最終高度就是img的頂部到strut底部之間的距離。分析完成因後,終於可以使用font-size:0來解決問題了,在box上設定font-size:0後,strut就相當於沒有了,因此就不存在高度撐開的問題了,當然這裡還可以改變img的vertical-align屬性來修復這個問題,比如img{ vertical-align:bottom; }那麼img的粉色線就跟strut的底部對齊了,也就不會撐開容器高度了,這裡說了box2,box1原理差不多,這裡涉及到vertical-align的知識了,可查閱https://www.w3.org/TR/CSS2/visudet.html#vertical-align 進行了解。
5.width&height
width和height也是可以設定成0的,效果也是符合我們預期的,但如果我們的意圖是想通過設定0把元素隱藏的話,一般情況下會採用如下方案:
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
width: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
}
有些螢幕閱讀器會忽略width和height等於0的元素,因此這裡特意設定成1px,當然關於元素的隱藏還有很多實現方案,有興趣可參考【1】;
6.line-height
line-height及行高,原意是baseline之間的高度,在css中就是一行的高度。預設情況下,line-height是跟具體字型定義相關聯的,一般都是font-size的1.x倍,如果設定成0,在不同型別元素上的情況是不一樣的,可分為如下三種情況:
1.)非置換行類元素
line-height定義的是最終參與計算line-box(行盒)的高度的值,而不會影響non-replaced inline element(非置換行類元素)的實際高度;
<style>
.foo {
line-height:0;
}
</style>
<div>
<span class="foo">a</span> // class為foo的span高度不會受樣式影響
</div>
2.)行類塊級元素
會影響元素高度,如果line-height設定為0,那麼該元素高度就變成了0,如果設定了height,那麼height將會起作用;
3.)塊級元素
當塊級元素包含inline-level(display:inline|inline-block)元素時, 行高定義的每一個line-box(行盒)的最小高度,此外height也能影響塊級元素的最終高度,height比line-height有更高優先順序,當沒有height情況下,line-height起作用;
上面提到了line-box,如果有不瞭解的同學,可以看一下文末的連結【2】;
7.transform
transform常用來做樣式變化和動畫,在有時候,會設定成如下形式:
transform: translateZ(0);
這其實是為了啟用GPU加速渲染,元素會單獨在一個繪製層(Layer)裡進行繪製,而不會對其他層產生影響,因此也就少了很多計算和合成的功能,而且不會阻塞主執行緒,動畫會更加流暢,當然元素設定太多會導致效能降低,因為需要記憶體的維護。
參考資料:
【1】https://css-tricks.com/comparing-various-ways-to-hide-things-in-css/
【2】https://www.w3.org/TR/CSS2/visuren.html#line-box