眾所周知,JavaScript是單線程的,但是不可避免的,JavaScript也需要進行一些異步任務,比如下面這個例子
function foo() {
console.log("first");
setTimeout(( function(){
console.log( 'second' );
}),5);
}
for (var i=0; i < 1000000; i++) {
foo();
}
復制代碼
上面這個例子,執(zhí)行結果會首先全部輸出first,然后全部輸出second,而不是交替執(zhí)行
在這個過程中,很明顯發(fā)生了并發(fā)的異步任務,那么問題來了,單線程的JavaScript是怎么實現(xiàn)異步的?
作為一個Java程序員,在知道了JavaScript是單線程之后,首先的反應就是不解,為什么一種語言會被設計成單線程的呢? 它為什么不能使用多線程來提高效率呢?
JavaScript的單線程,與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內容,另一個線程刪除了這個節(jié)點,這時瀏覽器應該以哪個線程為準?
所以,為了避免復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特征,將來也不會改變。
現(xiàn)在我們已經了解了JavaScript為什么是單線程的了,那么它又是怎么實現(xiàn)異步的呢?
JavaScript的異步能力主要是由運行環(huán)境提供的
JavaScript Runtime也就是JavaScript代碼運行的地方。比如JavaScript 可以在 chrome 中執(zhí)行,也可以在node中執(zhí)行,chrome與node都是JavaScript Runtime
由上圖可知,JavaScript Runtime主要包括Js Engine與WebAPI等內容
Js Engine將我們編寫的 JavaScript 轉換為更高效的機器碼,以實現(xiàn)更好的性能。
chrome 瀏覽器中的 JavaScript 由 V8 引擎處理。V8引擎主要包括內存堆與執(zhí)行棧兩個部分
除了引擎,JavaScript Runtime也提供了WebAPI供JS代碼調用,WebAPI提供了網絡請求,定時器,事件監(jiān)聽等多種能力
因為JS Runtime并不是單線程的,而是持有一個線程池,因此WebAPI中的代碼是運行在其他線程的,自然也就提供了異步的能力
需要注意的是,一旦執(zhí)行棧中的所有同步任務執(zhí)行完畢(此時JS引擎空閑),系統(tǒng)就會讀取任務隊列,將可運行的異步任務添加到可執(zhí)行棧中
因此setTimeout設置的時間并不是準確的,可能在它推入到事件列表時,主線程還不空閑,正在執(zhí)行其它代碼,因此存在誤差。
JavaScript本質上是運行在瀏覽器里的腳本語言,為了簡單與避免操作DOM時引入同步問題,所以JavaScript被設計成了單線程的語言。
JavaScript的異步能力是由運行環(huán)境提供的,通過WebAPI與事件循環(huán)機制,單線程的JS也可以執(zhí)行異步任務。
如果你覺得此文對你有一丁點幫助,點個贊。或者可以加入我的開發(fā)交流群:1025263163相互學習,我們會有專業(yè)的技術答疑解惑
如果你覺得這篇文章對你有點用的話,麻煩請給我們的開源項目點點star:http://github.crmeb.net/u/defu不勝感激 !
完整源碼下載地址:https://market.cloud.tencent.com/products/33396
PHP學習手冊:https://doc.crmeb.com
技術交流論壇:https://q.crmeb.com
者:vivo 互聯(lián)網前端團隊 - Zhang Jiqi
本文主要講述了CSS中的級聯(lián)層(CSS@layer),討論了級聯(lián)以及級聯(lián)層的創(chuàng)建、嵌套、排序和瀏覽器支持情況。級聯(lián)層可以用于避免樣式沖突,提高代碼可讀性和可維護性。
一、什么是級聯(lián)層(Cascade Layers)?
1.1 級聯(lián)層的官方定義
我們參看Cascading and Inheritance Level 5(13 January 2022) 中6.4節(jié)所述:
級聯(lián)層提供了一種結構化的方式來組織和平衡單個起源中的問題。單個級聯(lián)層內的規(guī)則級聯(lián)在一起,不與層外的樣式規(guī)則交錯。
開發(fā)者可以創(chuàng)建層來表現(xiàn)元素默認值、第三方庫、主題、組件、覆蓋和其他樣式問題,并且能夠以顯式方式重新排序層級,而無需更改每個層內的選擇器或特異性,或依賴源順序來解決跨層的沖突。
單純看官方定義和概括可能比較晦澀,下面我們會結合案例來說清楚。
1.2 級聯(lián)層為了解決什么問題?
簡而言之:級聯(lián)層的出現(xiàn)就是為了使 CSS 開發(fā)者可以更簡單直接地控制級聯(lián)。
我們來假設日常開發(fā)中的一個場景,從場景去理解級聯(lián)層在解決什么問題。
如下圖:
我們原來的'display'文案是紅色,當我們引入了一個第三方組件庫,第三方庫中有以下樣式。
/* 開發(fā)者樣式 */
.item {
color: red;
}
/* 第三方庫 */
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
就會導致'display'文案變成綠色。
我們想要將'display'文案的顏色由綠色改成紅色一般的手段是增加選擇器特異性(優(yōu)先級)。比如在開發(fā)頁面中對開發(fā)者樣式進行修改:
/* 開發(fā)者樣式 */
#app div.item {
color: red;
}
/* 第三方庫 */
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
或者借助級聯(lián)中出場順序對優(yōu)先級的影響在用戶頁面中重寫
/* 第三方庫 */
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
/* 開發(fā)者樣式 */
#app .item {
color: red;
}
效果如下:
再舉個例子:
比如有可能第三方組件寫了
a { color: blue; }
那項目開發(fā)中由于引入這個第三方組件 就會導致樣式污染,因為第三方庫的樣式往往都在項目設置的通用樣式比如common.css后加載。
如果后面想在代碼中覆蓋某些屬性,使用高特異性選擇器的語句就可能會導致問題。然后因為有問題就會選擇更高特異性的擇器的語句或使用!important,這會使代碼變得冗長也可能會帶來副作用。低特異性選擇器的語句很容易被后面出現(xiàn)在代碼中的語句覆蓋。在自己的代碼之后加載第三方 CSS 時特別會出現(xiàn)這種問題。
所以級聯(lián)層就是為了解決以上場景出現(xiàn)的,級聯(lián)層在級聯(lián)中的的位置是在內聯(lián)樣式和選擇器特異性之間。當有些css聲明就是設置要低優(yōu)先級且不受選擇器特異性影響那么使用級聯(lián)層再合適不過。
運用級聯(lián)層解決第一個日常開發(fā)場景痛點的css代碼如下:
/* 排序層 */
@layer reset, lib;
/* 通用樣式 */
@layer reset {
#app .item {
color: black;
width: 100px;
padding: 1em;
}
}
/* 第三方庫樣式 */
/*我們將第三方庫的樣式全部放到lib層*/
/*這里一般使用@import導入的方式*/
/*為了示例簡單我們簡化了操作*/
@layer lib {
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
width: 130px;
}
}
/* 開發(fā)者樣式 */
.item {
color: red;
}
為了知道為什么上面的css代碼能解決沖突問題,更好地理解級聯(lián)層的作用,理解一些現(xiàn)象背后的根因,了解級聯(lián)層和級聯(lián)的關系,我們繼續(xù)往下看。
二、理解級聯(lián)層的前提——級聯(lián)(cascade)
2.1 什么是級聯(lián)?
CSS中有兩個重要的基礎規(guī)則,一個是繼承,一個是級聯(lián)。
繼承
指的是類似 color,font-family,font-size,line-height 等屬性父元素設置后,子元素會繼承的特性。
級聯(lián)
可以簡單理解為是CSS 用來解決要應用于元素的具體樣式的算法。也就是基于一些優(yōu)先級排序輸出給給定元素上屬性值一個級聯(lián)值。級聯(lián)值是級聯(lián)的結果。
2.2 當前級聯(lián)的排序標準
我們參看Cascading and Inheritance Level 5(13 January 2022) 中6.1節(jié),
相比于Cascading and Inheritance Level 4(14 January 2016) 中的定義有明顯變化。
最重要的變化就是增加了級聯(lián)層。由此級聯(lián)排序變成:
瀏覽器在確定最終元素樣式呈現(xiàn)的時候,會依據這些準則按照優(yōu)先權從高到低排序,并且會一個一個的檢查,直到確定最終樣式。
2.3 級聯(lián)起源(Cascading Origins)
2.3.1 三個核心起源
css中每個樣式規(guī)則有三個核心起源,它決定了它進入級聯(lián)的位置,理解起源優(yōu)先級是理解級聯(lián)的關鍵。
2.3.2 起源的優(yōu)先級
Css聲明的起源取決于它來自哪里,重要性在于它是否用!important聲明。
各種起源的優(yōu)先級按降序排列:
越靠前來源的聲明優(yōu)先級越高。過渡和動畫我們可以暫時忽略。
2.4 熟悉又陌生的 !important
通常作為開發(fā)者,!important會被我們視為一種增加特異性的方法,用以覆蓋內聯(lián)樣式或特異性較高的選擇器。
但是!important設計出來的初衷是作為整體級聯(lián)中的一個特性,來平衡開發(fā)者、用戶設置和瀏覽器內置之間對css優(yōu)先級的影響能力。
默認情況下三者的優(yōu)先級是:作者來源> 用戶來源>用戶代理來源(可以參看上文起源優(yōu)先級中6-8的排序)。但是當css聲明添加!important之后會使它們的優(yōu)先順序顛倒(參看上文起源優(yōu)先級中2-4的排序)。
如果站在瀏覽器和用戶的角度看!important提供了在必要時重獲優(yōu)先級控制權的能力,而非只是簡單的增加特異性。
舉個例子:
瀏覽器默認樣式表包含我們開發(fā)者無法覆蓋的重要樣式。
瀏覽器對具有'hidden'類型的input輸入框設置了默認的展示屬性并且將其聲明為重要。
input[type=hidden i] { display: none !important; }
看下面兩張圖例:
第一張可以看出我們對具有'hidden'類型的input輸入框的展示屬性設置成了顯示并且聲明為重要
第二張是樣式最終計算結果:隱藏
從上面的瀏覽器表現(xiàn)可以看到我們作為開發(fā)者在作者樣式表中設置的規(guī)則沒能覆蓋用戶代理樣式表中的相同規(guī)則。
這驗證了上面說的:在級聯(lián)中!important會顛倒三大核心起源默認優(yōu)先順序。
驗證的過程中還發(fā)現(xiàn)了一個chrome控制臺這邊的bug,看上面的第一張圖例:沒生效的規(guī)則不劃刪除線,生效的反而劃刪除線了。
再看一個官方文檔的例子加強一下理解:
圖片來源:w3.org
font-size的值最終是‘12pt ’。
因為作者樣式表中添加!important的規(guī)則優(yōu)先權高于用戶樣式表中普通規(guī)則。
text-indent的值最終是‘1em’。
因為雖然兩個樣式表都標注了!important,但是標注!important用戶聲明優(yōu)先級大于標注!important作者聲明。
2.5 一張圖帶你理解級聯(lián)
下圖可以幫助我們直觀的理解級聯(lián)以及級聯(lián)層在級聯(lián)中的位置:
圖片來源:css-tricks
我們會發(fā)現(xiàn)平時操作最多的選擇器特異性(selector specificity)只是級聯(lián)中的一小部分。也輕松地理解了為什么內聯(lián)樣式優(yōu)先級天然高。同時我們會發(fā)現(xiàn)!important在級聯(lián)中有特殊地位。他穿插在級聯(lián)規(guī)則的各個階段并能顛倒優(yōu)先級。
三、級聯(lián)層(CSS@layer) 使用探索
3.1 @layer 是什么?
我們來看MDN上的定義:
The @layer CSS at-rule is used to declare a cascade layer and can also be used to define the order of precedence in case of multiple cascade layers.
也就是說 @layer 這個at-rule(AT規(guī)則) 用于聲明級聯(lián)層(cascade layer),也能用于定義多個級聯(lián)層的優(yōu)先級。
At-rules 是什么?
At-rules是指導 CSS 如何表現(xiàn)的CSS 語句。它們以 at 符號 ' @' ( U+0040 COMMERCIAL AT) 開頭,后跟一個標識符,包括下一個分號 ' ;' ( U+003B SEMICOLON) 或下一個CSS 塊之前的所有內容。
我們開發(fā)常見的at-rule有@charset、@media、@font-face 、@keyframes 等。
3.2 @layer的句法規(guī)則
@layer的句法如下:
@layer layer-name {rules}
@layer layer-name;
@layer layer-name, layer-name, layer-name;
@layer {rules}
3.3 如何創(chuàng)建級聯(lián)層
可以通過多種方式創(chuàng)建級聯(lián)層。
第一種方法是:創(chuàng)建命名的級聯(lián)層,其中包含該層的 CSS 規(guī)則,如下所示:
@layer green {
.item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
}
@layer special {
.item {
color: rebeccapurple;
}
}
第二種方法是:創(chuàng)建一個命名的級聯(lián)層而不分配任何樣式。這可以是單層,如下所示:
@layer green;
可以一次定義多個層,如下:
@layer green, special
一次定義多個層有什么好處呢?
因為聲明層的初始順序決定了層的優(yōu)先級。與聲明一樣,如果在多個層中找到聲明,最后定義的層聲明將最終生效。看下面代碼:
@layer green,special;
@layer green {
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
}
@layer special {
.item {
color: rebeccapurple;
}
}
效果如下圖:
special層中item樣式規(guī)則將被應用即使它的特異性低于 green層中的規(guī)則。這是因為一旦層順序定義完成,就會忽略選擇器特異性。
同樣也會忽略出現(xiàn)的順序:
我們聲明層名稱并設置它們的順序后,可以通過重新聲明名稱來將 CSS 規(guī)則添加到圖層。然后將樣式附加到層,并且層順序不會更改。比如如下代碼雖然@layer green重新聲明了并在文件后方但是由于順序一開始已經設置所以字體顏色還是紫色:
@layer green,special;
@layer special {
.item {
color: rebeccapurple;
}
}
@layer green {
.item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
}
效果如下:
忽略選擇器特異性和代碼出現(xiàn)順序可以讓我們創(chuàng)建更簡單的 CSS 選擇器,并使代碼優(yōu)雅,因為不必確保選擇器具有足夠高的特異性來覆蓋其他css規(guī)則,只需要確保它出現(xiàn)在后面的層中。
第三種方法是:創(chuàng)建一個沒有名稱的級聯(lián)層。例如:
@layer {
.item {
color: black;
}
}
這將創(chuàng)建一個匿名級聯(lián)層,該層功能與命名層相同。但是使用匿名層有如下缺點:
平時我們盡量避免使用匿名層。但當我們是樣式庫的作者,并想將某些css代碼不被使用者修改可以借助匿名層做到這一點。
第四種方法是:使用@import。CSS 原生支持 @import 導入其他 CSS 文件。
@import url(index.css) layer(index);
同時,也支持匿名引入,例如:
@import url(index.css) layer;
我們在使用@import時候需要放在除@charset之外的樣式規(guī)則前,否則無法導入。
可能的第五種方式仍在討論中:通過元素上的屬性。請參閱[css-cascade] Provide an attribute for assigning ato a cascade layer #5853。
3.4 層的嵌套規(guī)則
圖層可以嵌套。例如:
@layer base {
p { max-width: 70ch; }
}
@layer framework {
@layer base {
p { margin-block: 0.75em; }
}
@layer theme {
p { color: #222; }
}
}
生成的層可以表示為一棵樹:
1.base
2.theme
或可以用扁平列表表示
要將樣式附加到嵌套層,您需要使用以下全名來引用它:
@layer framework {
@layer default {
p { margin-block: 0.75em; }
}
@layer theme {
p { color: #222; }
}
}
@layer framework.theme {
/* 這些樣式會被添加到framework層里面的theme層 */
blockquote { color: rebeccapurple; }
}
看效果:
3.5 層的排序規(guī)則
級聯(lián)層按照它們聲明的順序排序。第一層優(yōu)先級最低,最后一層優(yōu)先級最高。但是,未分層的樣式具有最高優(yōu)先級:
/* 未分層 */a { color: green; }
@layer layer-1 { a { color: red; } }
@layer layer-2 { a { color: orange; } }
@layer layer-3 { a { color: yellow; } }
優(yōu)先級順序如下:
看下圖示例:
層可以在一個地方被定義圖層(以建立圖層順序),然后在任何地方添加樣式
/* 定義在一個地方 */
@layer my-layer;
/* 其他樣式*/
...
/* 在某個地方添加樣式 */
@layer my-layer { a { color: red; } }
3.6 加上!important之后的排序規(guī)則
/* 未分層 */ a { color: green !important; }
@layer layer-1 { a { color: red !important; } }
@layer layer-2 { a { color: orange !important; } }
@layer layer-3 { a { color: yellow !important; } }
任何加上重要聲明的樣式都以相反的順序應用
優(yōu)先級順序如下:
看下圖示例:
這里我們看到對應規(guī)則在chrome瀏覽器的顯示是正確的。但是在開發(fā)者控制臺中的樣式一欄規(guī)則顯示有問題。應該是chrome瀏覽器開發(fā)者控制臺的bug。
3.7 嵌套層的排序規(guī)則
@layer layer-1 { a { color: red; } }
@layer layer-2 { a { color: orange; } }
@layer layer-3 {
@layer sub-layer-1 { a { color: yellow; } }
@layer sub-layer-2 { a { color: green; } }
/* 未嵌套 */ a { color: blue; }
}
/* 未分層 樣式 */ a { color: black; }
優(yōu)先級順序如下:
3.8 媒體查詢對層排序的影響
以下層順序將取決于匹配的媒體條件:
例如:
@media (min-width: 600px) {
@layer layout {
.item {
font-size: x-large;
}
}
}
@media (prefers-color-scheme: dark) {
@layer theme {
.item {
color: red;
}
}
}
如果兩個媒體查詢的規(guī)則中匹配一個那么對應的級聯(lián)層生效。如果兩者都匹配,那么圖層順序將為layout, theme都生效。如果兩個都不匹配則不定義層。下圖是兩者都匹配的場景。
四、現(xiàn)在就能使用級聯(lián)層嗎?
4.1 目前瀏覽器支持程度
圖片來源:developer.mozilla.org
目前所有現(xiàn)代瀏覽器均已經支持 @layer 規(guī)則。所有瀏覽器廠商都支持的特性未來一定比較實用。
4.2 W3C 鼓勵可以作為日常使用
SS 的標準化流程由 W3C Cascading Style Sheets Working Group (CSSWG)——W3C層疊樣式列表小組以及獨立CSS專家組成。W3C 本身并不制定標準,而是作為一個論壇式的平臺,接收來自小組成員的提交,并通過會議來商討制定標準,所有的提交以及討論都是公開透明的,可以在 W3C 網站上看到會議的記錄,可以簡單分為4個大階段:
下圖可以幫助理解:
圖片來源:w3.org
W3C 通過狀態(tài)碼表示規(guī)范的成熟度。成熟度從低到高排序如下圖。
圖片來源:w3.org
再看下圖:包含layer概念的標準討論已經到達CR階段。
圖片來源:w3.org
W3C 鼓勵從 CR階段的標準 開始可以作為日常使用。
五、總結
最后,我們回到通過級聯(lián)層如何解決“引入了一個第三方組件庫導致樣式覆蓋“的問題上。
css代碼如下:
/* 排序層 */
@layer reset, lib;
/* 通用樣式 */
@layer reset {
#app .item {
color: black;
width: 100px;
padding: 1em;
}
}
/* 第三方庫樣式 */
/*我們將第三方庫的樣式全部放到lib層,這里一般使用@import導入的方式,為了示例簡單我們簡化了操作*/
@layer lib {
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
width: 130px;
}
}
/* 開發(fā)者樣式 */
.item {
color: red;
}
我們將第三方庫的樣式全部放到lib層,將需要重置的一些樣式放到reset層,自己開發(fā)的樣式不放入層中(當然你也可以放入到一層然后排序在最后)。由此我們實現(xiàn)了樣式的分層解決了第三方組件庫導致樣式覆蓋的問題,而且做到開發(fā)者樣式簡單不冗長。
效果如下:
級聯(lián)層(CSS@layer)已經歷概念提出到到瀏覽器全面支持的階段。也許在不久的將來大家都會普遍使用它,期望本文能給大家?guī)硪欢◣椭?/span>
參考資料:
作者:Zhang Jiqi
來源:微信公眾號:vivo互聯(lián)網技術
出處:https://mp.weixin.qq.com/s/4M-RvnTq8rJuKDC3VZOONQ
于網頁視頻下載,前段時間我分享過一篇博文:如何下載微信公眾號文章中的視頻,其原理是利用IDM的嗅探功能找到網頁視頻的真實地址然后下載。這個方法適用于大部分網站,也就是說個別網站不適用,因為有的網站會把視頻分成若干段,下載后要自己合并,更過分的是有的網站每幾百KB就分一段,下載完成后有時會有數百個片段需要合并,令人崩潰。還有的網站雖然能嗅探到真實地址,使用IDM下載一段時間后速度就成0了,看來是服務器端對下載工具進行了限制。更令人頭大的是,這樣的“個別網站”有越來越多的趨勢。
為了能解決這些“個別網站”的視頻下載問題,我嘗試了github上的許多開源工具,如youtube-dl、you-get、annie等,這些工具都很強大,但每一個都不能覆蓋大部分網站,而且都是命令行方式運行的,對于小白很不友好。
后來我終于找到一個集眾家之長的軟件:CR TubeGet,它集成了youtube-dl、you-get、annie三大開源視頻下載工具,可以輕松下載各大視頻網站中的視頻,對國內外常見的視頻網站均支持良好,IDM不能下載的幾個網站都能順利下載。CR TubeGet使用方法簡單,只要粘貼視頻頁的網址或者是將視頻頁網址拖拽至界面中就可以進行下載,支持多個任務同時下載,沒有速度限制。軟件集成了格式轉換工具,可以根據自己的需要將下載的視頻轉換成不同格式。
CR TubeGet最新版本:1.6.4
安裝包下載:http://crtubeget01.bj01.bdysite.com/softs/crtubeget_full.exe
綠色包下載:http://crtubeget01.bj01.bdysite.com/softs/crtubeget.rar
需要注意的是,CR TubeGet不注冊的話會有下載數量限制,只能免費下載99個視頻,想繼續(xù)使用可以購買授權碼,有包月、季度、包年、終身等幾種類型可選,價格從19到299不等,打開軟件后按照菜單指示操作即可完成注冊。
如果不想花錢,也可以使用免費版(v0923),但是舊版只集成了youtube-dl,沒有you-get、annie支持,不過解析能力依舊很強,原生youtube-dl不支持的網站,實測v0923版的CR TubeGet照舊可以解析并下載。
v0.9.2.3_免費版下載:http://www.cr-soft.net/v0923.html
我最近下載過視頻的網站有好看視頻、西瓜視頻、騰訊視頻、優(yōu)酷、bilibili等,經測試,這款CR TubeGet都可以完美下載,有些甚至可以下載到沒有角標的高清版,可以說目前這款軟件是視頻下載的終極解決方案了。
*請認真填寫需求信息,我們會在24小時內與您取得聯(lián)系。