深入 css z-index 屬性
說來汗顏,一直以來對於 css 常常是不求勝解。直到自己需要打造自己的輪子
才發現對 z-index
完全不熟悉。
關於 z-index
的問題其實非常少人完全明白它到底是怎麼運作的。事實上這並不複雜,不過如果你不曾花些時間閱讀規範可能你不曾察覺一些重要的觀念。
說來汗顏,一直以來對於 css 常常是不求勝解。直到自己需要打造自己的輪子
才發現對 z-index
完全不熟悉。
關於 z-index
的問題其實非常少人完全明白它到底是怎麼運作的。事實上這並不複雜,不過如果你不曾花些時間閱讀規範可能你不曾察覺一些重要的觀念。
關於下面這三種宣告的差異
發生在當我們撰寫下面程式碼的時候所發現的奇怪行為。
1 | var a = 1; |
首先根據ECMA-262 §10.3,的定義 Variable environment
是一種特定
型別的 Lexical environment
,我們沒辦法透過任何方式直接存取。
一個 Lexical environment
用來記錄執行環境的資訊,可以把它想成是一個物件,我們會把在一個執行環境 Context
的變數,函數都存在這個物件的屬性上
針對函數那些定義的參數(Parameter)也會被記錄,舉例來說 function foo (a, b)()
中的 a
和 b
就會被記在 foo
的執行環境資訊中。
一個 Lexical environment
也有一個連結可以連結到外在的 Lexical environment
就是所謂的 scope chain
。
這個機制可以協助我們取得目前執行環境以外的變數,舉例來說就是 function 裡面可以拿到 global 的變數。
一個 Variable environment
就只是 Lexical environment
的一部份,本質上就是透過 var
宣告在執行環境中的變數或函數。
上面的 a
使用了 var
根據 ECMAScript 定義會被記錄在 Variable environment
根據定義 Variable environment
是不能手動刪除的。
也就是說除非用了 eavl
,否則是不能被 delete
的。
1 | var a = 1; |
當我們賦值卻沒有用 var
,Javascript 會嘗試在 Lexical environment
尋找同名的參考。
最大的不同是 Lexical environment
是嵌套的,就是它可以關聯到外面其他的 Lexical environment
。
當在本地找不到的時候就會往上層去找,換句話說每個 Lexical environment
都有個爸爸
,而最外層的就是 global
所以當我們不使用 var 而宣告一個變數時會開始在各個 scope 尋找同名變數,最終 Javascript 會拿一個 window(global) 的屬性來當作參考。
而物件的屬性是可以刪除的。
結論就是第一個 var 的變數被放在 Variable environment
是不能 delete
的而第二個沒有 var 的變數它是 global 的屬性。
然後你就會問我,那為什麼第一個 a
可以用 window.a
取得,因為全域的 variable object
就是 global(window)
本身。
但誰是屬性
誰放在variable environment
是有差的,因為程式碼看起來沒差所以會搞死人啊。
當我們使用 Vue.js
搭配 slim
時(事實上 Angular
應該也有相同的問題)時
1 | div id="app" |
立馬收到Slim::Parser::SyntaxError
的錯誤訊息。
但是改成這樣卻又正常
1 | div id="app" {{ message }} |
好啦!答案很明顯了就是我們有地方寫錯,讓 slim engine 誤會了。
這邊紀錄一下解法:
第一個最簡單的方式就是幫 p
補上隨意一個屬性
1 | div id="app" |
1 | div id="app" |
1 | div id="app" |
上面的解法都是因為 slim 預設會把 {}
()
[]
和 tag 後面接的 property=value
當作屬性(attributes)來解析。
所以我們只要把 {}
拿掉就正常了。
新增或修改 config/initializers/slim.rb
加入
1 | Slim::Engine.set_options :attr_list_delims => {'(' => ')', '[' => ']'} |
原文出處: 連結
話說網路上有很多文章在探討閉包
(Closures)時大多都是簡單的帶過。大多的都將閉包的定義濃縮成一句簡單的解釋,那就是一個閉包是一個函數能夠保留其建立時的執行環境
。不過到底是怎麼保留的?
另外為什麼一個閉包可以一直使用區域變數,即便這些變數在該 scope
內已經不存在了?
為了解開閉包的神秘面紗,我們將要假裝 Javascript 沒有閉包這東西而且也不能夠用嵌套 function
來重新實作閉包。這麼做我們將會發現閉包真實的本質是什麼以及在底層到底是怎麼運作的。
為了這個練習我們同時也需要假裝 Javascript 本身具備了另一個不存在的功能。那就是一個原始的物件當它如果被當成 function 調用的時候是可以執行的。
你可能已經在其他語言中看過這個功能,在 Python
中你可以定義一個 __call__
方法,在 PHP
則有一個特殊的方法叫 __invoke
這些方法(Method)
會在當物件被當作 function 調用時執行。如果我們假裝 Javascript 也有這個功能,我們可能需要這麼實作:
1 | let o = { |
這邊我們得到一個普通的物件,我們假裝我們可以把它當做 function
來呼叫,然後當我們這個做的同時其實我們是執行一個特殊的方法 __call__
如果你真的要實作記得用 o.__call__()
。
譯者註: 注意! 呼叫
可調用物件
例如上面的o()
都要換成o.__call__()
假如您想實作的時候。
現在讓我們先來看看一個簡單的閉包範例。
1 | function f() { |
外層的 function f
有一個區域變數,然後裡面的 function g
參考 f
的區域變數。
接著我們把內層的 g 回傳指派給 f
scope 外的變數。但我們好奇的是如果 f
執行完畢被釋放了,那為什麼 g 仍然可以取得已被釋放的 f 的區域變數呢?
這個的魔法便是 - 一個閉包不僅僅只是一個 function。它是一個物件,具有建構子和私有資料。然後我們可以它當作 function 來使用。
那如果 Javascript 沒有閉包這種用法,我們必須自己實作它呢?這就是我們接下來要看到的。
1 | class G { |
如果您曾看過 ECMAScript 規範,可能會對
實際上是參考自己私有的資料
這句話產生一些疑問,先別急著否定。這邊不過是試著用另外一個較淺的角度解釋。
這邊我們把內部的 function g
用一個 G class
的實例物件(即 new 出來的物件) 取代,然後我們透過把 f 的區域變數 n 傳進 G 的建構子,藉此將變數儲存在新的實例物件私有的資料中。最終我們可以取得 f 的區域變數(n)。
OK! 各位觀眾這就是一個閉包的行為。閉包就是一個可調用的物件,可以把透過建構子把傳入的參數保留在私有的空間中。
聰明的讀者已經發現還有一些行為我們還沒解釋清楚或者說我們的模擬實作是有漏洞的。讓我們來觀察其他的閉包範例
1 | function f() { |
在這個範例中,我們得到兩個閉包同時參考變數 n
。其中一個函數的操作變數會影響另外一個變數取得得值。
但如果 Javascript 沒有閉包,單靠我們上面的實作行為將不會一樣。
1 | class Get { |
跟上面一樣,我們取代了內部 function get
和 next
的部分改成使用物件。它們是透過將值保留在物件內部進而取得 f
的區域變數,每一個物件具有自己私有的資料。同時我們也注意到其中一個可調用物件
操作 n 並不會影響另外一個。這是因為它們是傳 n 的值 value
而不是傳址 reference
。白話文就是複製了一分資料。並不是操作變數本身。
為了要解釋為什麼 Javascript 的閉包會參考到相同的 n 即記憶體位置是一樣的。我們需要解釋變數本身。在底層,Javascript 的區域變數跟我們從其他語言理解的觀念並不相同,它們是負責動態分配與計算參考(reference)
的物件的屬性
,稱為 LexicalEnvironment
物件。Javascript 的閉包其實會有一個參考指向到整個 執行環境
, 上下文
, Context
的 LexicalEnvironment 物件,而不是特定的變數。
如果您對於 scope 與 context 還不是很了解強烈建議您觀賞這篇
讓我們來修改我們的可調用物件
讓其可以取得一個 lexical environment
而不是 n
。
1 | class Get { |
上面實作我們將區域變數 n 換成 lexicalEnvironment 物件,然後具有一個屬性 n 。
這時 Get
和 Next
的物件實例所存取的便是同一個參考(reference)即 lexical environment
物件。
所以現在修改的就是相同的地方了。基本上這就是一個閉包的行為。
閉包是一個物件而且當它們是函數時我們可以直接調用。而事實上任何一個 Javascript 中的函數都是一個可被調用的物件也稱作 function object
或者 functor
當它們被執行或者說被實例化時會帶有一個私有的 lexical environment 物件。而想要更了解關於這個物件的看官們可以參考Lexical environment。
在 Javascript 不是 function 創造閉包,function 本身就是一個閉包。
老實說譯者本身還是比較喜歡理解 context 與 variable object 的說明,接著用
一個閉包是一個函數能夠保留其建立時的執行環境
這句話來記憶。
不過原作者從這個角度來解釋的確是可以概略的理解整個運作機制,希望這篇文章能讓你有所收穫。
當增加不同 version 的尺寸需要對已上傳的圖片從新產生新尺寸的圖片時
1 | > rails c |
輸入
1 | Attachment.all.each do |att| |
這週閱讀到這篇有意思的文章,於是便動手
寫下間單的翻譯,如果有理解錯誤的地方歡迎指教。
Chrome 正在試圖改變當 <link rel="stylesheet">
寫在 <body>
的行為,從 blink-dev
的文章並不能很清楚的知道其優點。
所以這篇文章想要深入的介紹這點。
blink 是 Chrome 和 Opera 渲染引擎,而 blink-dev 是其開發社群
1 | <head> |
當 CSS 段落在渲染時,會讓使用者瞪著白白的頁面直到 all-of-my-styles.css
完全下載完畢。
通常我們會把站內所有的 CSS 封裝成較少,可能只有一兩個資源檔,但這同時也意味著使用者需要下載大量
的樣式設定(CSS Rules)卻沒有在該頁面使用。因為一個網站包含著各種不同的頁面與元件,而這些東西需要
套用不同的樣式規則,如果因此分拆成許多檔案而產生大量的請求 request
這在 HTTP/1
是非常耗效能的。
不過在 SPDY
和 HTTP/2
卻不是這樣,傳輸許多分散的小資源只會增加一點點的開銷,並且這些東西
可以個別暫存(cached)。
1 | <head> |
這樣一來就解決了許多問題,我們可以拆成很多小檔案個別載入,不過也意味著當我們在 <head>
下 <link>
時,
得要知道這些頁面各自需要哪些資源。
另外,瀏覽器在開始輸出之前,仍然得下載所有的 CSS。如果出現一個下載比較慢的 CSS 例如 /site-footer.css
將會造成渲染的東西延遲。請觀察範例。
1 | <head> |
上面程式碼,我們有一些內嵌的樣式(inline style)來讓我們可以快速的渲染初始化的樣式,接著把那些
還沒取得樣式的部分隱藏起來,然後開始透過 Javascript 非同步
下載剩餘的樣式。這些剩餘的 CSS
會覆寫掉在 .main-article
和其他選擇器內的 display: none
。
這種第一次先快速的初始化渲染,然後持續匯入的方法是許多效能專家所推薦的。
原作者實作了wiki-offline並將狀況紀錄如下圖
上面這張圖片是在 3G 的環境下測試。
不過這樣的方法還是有些不足的地方:
不幸的,因為 WebKit 的實作。當 <link rel="stylesheet">
一被加到頁面時,WebKit 會阻塞
渲染(render),直到樣式都被載入,即使這個樣式是透過 Javascript 加入的。
在 Firefox 和 IE/Edge,透過 JS 加入的樣式完全是非同步載入。Chrome 當前穩定版本然仍是遵循
WebKit 的行為,不過在 Canary
已經跟 Firefox/Edge 一樣了。
根據上面的模式,內嵌 CSS(inline CSS) 透過 display: none
隱藏尚未套用樣式的內容,然後非同步
得載入 CSS 之後呈現內容。如果您需要增加多個 CSS 檔案那麼結果可能就是內容不按順序出現
如果內容還在異動的過程結果周圍的廣告就先出現這通常會讓使用者覺得不開心就關閉你的網站了。
因此被限制在只有兩個載入階段,你必須要決定哪些是第一次渲染時就要出現,哪些是比較晚的。
當然,你希望上方的區塊越快顯示越好,不過所謂”上方的區塊”取決於 viewport
可視區域的大小。
最後你可能決定定義一個尺寸範圍套用在所有人身上。
如果你想讓事情更加複雜,當然你可以選擇客製 CSS 相依屬性來建立 CSS 之間渲染的相依性
1 | <head> |
這個概念是透過每一個 <link rel="stylesheet">
在其下載樣式時去阻塞跟在後面的內容,但允許
前面的內容先開始渲染。
樣式表(CSS)本身的載入機制是平行的,但是套用樣式卻是要照順序的。這讓 <link rel="stylesheet">
的行為類似為 <script>
假設說 site-header
, article
, footer
的樣式已經載完了,但剩下的還沒,其行為如下:
./comment.css
)./comment.css
)./comment.css
),即使自己的 CSS 已經載完了這讓我們可以照順序輸出頁面。您甚至不需要決定哪些是”上面的區塊”,只要在元件之前匯入元件需要的 CSS 即可。
不過你還是需要注意當使用內容決定佈局(layout system)
,例如 table, flexbox 時,在載入期間
應避免內容異動。
這不是現在才產生的問題了,只是在逐步顯示
這種機制之下更常遇到。如果你看不懂這段在描述什麼看一下下面的影片就知道了。
意思是說雖然 flexbox
已經很不錯了,但 Grid 還是更推薦的 Layout system。
HTML規範並不涵蓋網頁渲染時是否應該或該如何被 CSS 阻塞,
並且也不鼓勵把 <link rel="stylesheet">
寫在 body
中,不過所有瀏覽器都允許這麼做。
當然他們也都各自使用了自己的方式處理在 body 中的 <link>
<link rel="stylesheet">
就停止渲染(render),直到發現的樣式被載入完畢。往往導致在 <link>
上面還未渲染的內容被卡住。<link rel="stylesheet">
在 <head>
中會阻塞渲染直到所有發現的樣式都被載入完畢。在 body 中的話不會阻塞渲染,除非 head 裡已經有樣式阻塞住<link>
上面的內容渲染(render)。我們偏好像 IE/Edge 的行為,所以 Chrome 將會跟隨這樣的機制。目前 Chrome/Safari 的行為就只是
會卡比較久的時間,Firefox 的行為相對較複雜一些,不過有一些小技巧。
因為 Firefox 不會因為 body 中的 <link>
阻塞渲染。我們需要一點小技巧來避免 FOUC。幸虧有個非常簡單的方式就是透過 <script>
中斷解析器
讓他等一等待處理狀態的樣式
1 | <link rel="stylesheet" href="/article.css"><script> </script> |
這個標籤不能完全沒有內容,所以我們需要留一個”空白”。
Firefox 和 Edge/IE 將會循序的載入輸出,而 Chrome 和 Safari 則是先看到空白的頁面一陣子直到
CSS 全部載完。目前 Chrome/Safari 的行為比起把樣式連結放在 <head>
也沒有差到哪去,所以
現在就可以開始使用這個方式,很快的 Chrome 將會往 Edge 的機制修正,這麼一來渲染會更加迅速。
以上就是一個簡單的技巧協助我們加快速度並逐步載入 CSS
方法範例
1 | // push |
根據 MDN 的定義 arr.forEach(callback[, thisArg])
當我們要在 forEach 的匿名函數中使用 this
我們可以透過 thisArg
來設定。
如果不設定(undefined, null)或者不用 var self = this;
的方式保留 context,那麼預設會是指向 global
1 | var sum = 100; |
在 Chrome 或者 Firefox console 底下執行的確會執向 global,但是在 nodejs v5.6.0 底下結果卻是
1 | console.log(o.sum); // 0 |
要注意。
正確的範例如下
1 | function Counter () { |
⌥⌘I
開啟 Dev Tool⌘⇧C
-> 選取Shift
+ 點擊 CSS rule 的顏色會轉換 e.g. hex to rgbCommand+D
會把一樣的都選起來⌘S
可以暫時編輯檔案並存到瀏覽器的 Storage1 | console.log(); |
1 | $('selector') // 稱為 blind |
{}
按鈕可以把 minifed 檔案轉換成較易讀的格式⇧
+ Refresh 鍵強制全部重載發出 request
到回應即開始傳輸資料
的時間這篇文章希望從另外一個角度來看關於 Redux 的機制與運用,在網路上各式的教學文章中值得先推薦的自然是官方教學
如果英文不是很好的可以參考繁體中文版。這是小弟認為要學習 Redux 必讀的文件。另外還有A cartoon intro to redux也是值得一讀的好文可以加深記憶。
接著就讓我們開始吧,當然最後如果有任何錯誤,也歡迎您的指教。