深入 css z-index 屬性

說來汗顏,一直以來對於 css 常常是不求勝解。直到自己需要打造自己的輪子才發現對 z-index 完全不熟悉。
關於 z-index 的問題其實非常少人完全明白它到底是怎麼運作的。事實上這並不複雜,不過如果你不曾花些時間閱讀規範可能你不曾察覺一些重要的觀念。

繼續閱讀

關於 delete 變數詭異行為與解釋

問題

發生在當我們撰寫下面程式碼的時候所發現的奇怪行為。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = 1;
b = 2;

console.log(a); // 1
console.log(b); // 2

// 使用瀏覽器測試請使用 window 如果是用 node 的 REPL 環境那請改成 global
console.log(window.a); // 1
console.log(window.b); // 2

// 但接下來這麼操作

delete a; // false 不能刪
delete b; // true 可以刪

a; // 1
b; // undefined

解釋

首先根據ECMA-262 §10.3,的定義 Variable environment 是一種特定
型別的 Lexical environment,我們沒辦法透過任何方式直接存取。

一個 Lexical environment 用來記錄執行環境的資訊,可以把它想成是一個物件,我們會把在一個執行環境 Context的變數,函數都存在這個物件的屬性上
針對函數那些定義的參數(Parameter)也會被記錄,舉例來說 function foo (a, b)() 中的 ab 就會被記在 foo 的執行環境資訊中。

一個 Lexical environment 也有一個連結可以連結到外在的 Lexical environment 就是所謂的 scope chain
這個機制可以協助我們取得目前執行環境以外的變數,舉例來說就是 function 裡面可以拿到 global 的變數。

一個 Variable environment 就只是 Lexical environment 的一部份,本質上就是透過 var 宣告在執行環境中的變數或函數。

使用了 var

上面的 a 使用了 var 根據 ECMAScript 定義會被記錄在 Variable environment 根據定義 Variable environment 是不能手動刪除的。
也就是說除非用了 eavl,否則是不能被 delete 的。

1
2
3
4
5
6
7
var a = 1;
delete a; // false
console.log(a); // 1

// 記得清除整個環境
eval('var a = 1; delete a; console.log(a)'); // undefined

沒使用 var

當我們賦值卻沒有用 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 in Slim 語法的小問題 Slim::Parser::SyntaxError

當我們使用 Vue.js 搭配 slim 時(事實上 Angular 應該也有相同的問題)時

1
2
3
4
5
6
7
8
9
10
div id="app"
p {{message}}

javascript:
new Vue({
el: '#app',
data: {
message: "Hello, Vue.js"
}
})

立馬收到Slim::Parser::SyntaxError的錯誤訊息。

但是改成這樣卻又正常

1
div id="app" {{ message }}

好啦!答案很明顯了就是我們有地方寫錯,讓 slim engine 誤會了。

這邊紀錄一下解法:

補上屬性

第一個最簡單的方式就是幫 p 補上隨意一個屬性

1
2
3
4
5
6
7
8
9
10
div id="app"
p class="" {{ message }}

javascript:
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js',
}
})

加上 [], (), {} 任何一種

1
2
3
4
5
6
7
8
9
10
div id="app"
p () {{ message }}

javascript:
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js',
}
})

使用 |

1
2
3
4
5
6
7
8
9
10
11
div id="app"
p
| {{ message }}

javascript:
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js',
}
})

修改設定

上面的解法都是因為 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
2
3
4
5
6
7
8
9
10
11
let o = {
n: 42,
__call__() {
return this.n;
}
};

// 當我們把物件當作 function 一樣調用時
o(); // 42, 當然現在你會得到 `TypeError: o is not a function` 的錯誤

// 譯者註: 之後遇到這種呼叫的情況,請使用 o.__call__()

這邊我們得到一個普通的物件,我們假裝我們可以把它當做 function 來呼叫,然後當我們這個做的同時其實我們是執行一個特殊的方法 __call__ 如果你真的要實作記得用 o.__call__()

譯者註: 注意! 呼叫 可調用物件 例如上面的 o() 都要換成 o.__call__() 假如您想實作的時候。

現在讓我們先來看看一個簡單的閉包範例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function f() {
// 下面這個變數是 f() 的區域變數
// 通常,當我們離開 f 的 scope 時,這個變數 n 就應該要被回收了
let n = 42;

// 嵌套的 function 參考了 n
function g() {
return n;
}

return g;
}

// 讓我們透過 f() 來建立一個 g 函數
let g = f();

// 理論上這個變數 n 在 f() 執行完畢之後就應該要立即被回收,對吧?
// 畢竟 f 已經執行完畢了,而且我們也離開了該 scope
// 那為什麼 g 可以繼續參考一個已經被釋放的變數呢?
g(); // 42

外層的 function f 有一個區域變數,然後裡面的 function g 參考 f 的區域變數。

接著我們把內層的 g 回傳指派給 f scope 外的變數。但我們好奇的是如果 f 執行完畢被釋放了,那為什麼 g 仍然可以取得已被釋放的 f 的區域變數呢?

這個的魔法便是 - 一個閉包不僅僅只是一個 function。它是一個物件,具有建構子和私有資料。然後我們可以它當作 function 來使用。
那如果 Javascript 沒有閉包這種用法,我們必須自己實作它呢?這就是我們接下來要看到的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class G {
constructor(n) {
this._n = n
}

__call__() {
return this._n;
}
}

function f() {
let n = 42;

// 這就是一個閉包
// 這個內層的 function 其實不只是一個 function
// 它其實是一個可以被調用的物件,然後我們傳入 n 到它的建構子
let g = new G(n);


return g;
}

// 透過呼叫 f() 取得一個可以被調用的物件 g
let g = f();

// 現在就算原來從 f 拿到的區域變數 n 被回收了也沒關係
// 可被調用的物件 g 實際上是參考自己私有的資料
g(); // 42

如果您曾看過 ECMAScript 規範,可能會對實際上是參考自己私有的資料這句話產生一些疑問,先別急著否定。這邊不過是試著用另外一個較淺的角度解釋。

這邊我們把內部的 function g 用一個 G class 的實例物件(即 new 出來的物件) 取代,然後我們透過把 f 的區域變數 n 傳進 G 的建構子,藉此將變數儲存在新的實例物件私有的資料中。最終我們可以取得 f 的區域變數(n)。

OK! 各位觀眾這就是一個閉包的行為。閉包就是一個可調用的物件,可以把透過建構子把傳入的參數保留在私有的空間中。

更深入的問題?

聰明的讀者已經發現還有一些行為我們還沒解釋清楚或者說我們的模擬實作是有漏洞的。讓我們來觀察其他的閉包範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function f() {
let n = 42;

// 內部函數取得變數 n
function get() {
return n;
}

// 另外一個內部函數也同時存取 n
function next() {
return n++;
}

return { get, next };
}

let o = f();
o.get(); // 42
o.next();
o.get(); // 43

在這個範例中,我們得到兩個閉包同時參考變數 n 。其中一個函數的操作變數會影響另外一個變數取得得值。
但如果 Javascript 沒有閉包,單靠我們上面的實作行為將不會一樣。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Get {
constructor(n) {
this._n = n;
}

__call__() {
return this._n;
}
}

class Next {
constructor(n) {
this._n = n;
}

__call__() {
this._n++;
}
}

function f() {
let n = 42;

// 這邊的閉包我們一樣換成可調用的物件
// 它們可以將參數傳入建構子,進而將值保留起來
let get = new Get(n);
let next = new Next(n);

return { get, next };
}

let o = f();
o.get(); // 42
o.next();
o.get(); // 42

跟上面一樣,我們取代了內部 function getnext 的部分改成使用物件。它們是透過將值保留在物件內部進而取得 f 的區域變數,每一個物件具有自己私有的資料。同時我們也注意到其中一個可調用物件 操作 n 並不會影響另外一個。這是因為它們是傳 n 的值 value而不是傳址 reference。白話文就是複製了一分資料。並不是操作變數本身。

為了要解釋為什麼 Javascript 的閉包會參考到相同的 n 即記憶體位置是一樣的。我們需要解釋變數本身。在底層,Javascript 的區域變數跟我們從其他語言理解的觀念並不相同,它們是負責動態分配與計算參考(reference)的物件的屬性,稱為 LexicalEnvironment 物件。Javascript 的閉包其實會有一個參考指向到整個 執行環境, 上下文, Context 的 LexicalEnvironment 物件,而不是特定的變數。

如果您對於 scope 與 context 還不是很了解強烈建議您觀賞這篇

讓我們來修改我們的可調用物件讓其可以取得一個 lexical environment 而不是 n

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Get {
constructor(lexicalEnvironment) {
this._lexicalEnvironment = lexicalEnvironment;
}

__call__() {
return this._lexicalEnvironment.n;
}
}

class Next {
constructor(lexicalEnvironment) {
this._lexicalEnvironment = lexicalEnvironment;
}

__call__() {
this._lexicalEnvironment.n++;
}
}

function f() {
let lexicalEnvironment = {
n: 42
}

// 現在這個可調用變數是透過一個參考 lexical environment 來改變 n
// 所以現在變更的是同一個 n 了
let get = new Get(lexicalEnvironment);
let next = new Next(lexicalEnvironment);
return { get, next }
}

// 現在我們實作的物件行為跟 javascript 一致了
// 還是請注意如果您要時作,記得 o.get() 要換成 o.get.__call__() 喔
let o = f();
o.get(); // 42
o.next();
o.get(); // 43

上面實作我們將區域變數 n 換成 lexicalEnvironment 物件,然後具有一個屬性 n 。
這時 GetNext 的物件實例所存取的便是同一個參考(reference)即 lexical environment 物件。
所以現在修改的就是相同的地方了。基本上這就是一個閉包的行為。

結論

閉包是一個物件而且當它們是函數時我們可以直接調用。而事實上任何一個 Javascript 中的函數都是一個可被調用的物件也稱作 function object 或者 functor 當它們被執行或者說被實例化時會帶有一個私有的 lexical environment 物件。而想要更了解關於這個物件的看官們可以參考Lexical environment
在 Javascript 不是 function 創造閉包,function 本身就是一個閉包。

老實說譯者本身還是比較喜歡理解 context 與 variable object 的說明,接著用 一個閉包是一個函數能夠保留其建立時的執行環境 這句話來記憶。
不過原作者從這個角度來解釋的確是可以概略的理解整個運作機制,希望這篇文章能讓你有所收穫。

開發時期,CarrierWave 重產不同 version 圖片

當增加不同 version 的尺寸需要對已上傳的圖片從新產生新尺寸的圖片時

1
> rails c

輸入

1
2
3
4
Attachment.all.each do |att|
att.image.recreate_versions!
att.save!
end

[譯] CSS 載入的未來

這週閱讀到這篇有意思的文章,於是便動手
寫下間單的翻譯,如果有理解錯誤的地方歡迎指教。

Chrome 正在試圖改變當 <link rel="stylesheet"> 寫在 <body> 的行為,從 blink-dev 的文章並不能很清楚的知道其優點。
所以這篇文章想要深入的介紹這點。

blink 是 Chrome 和 Opera 渲染引擎,而 blink-dev 是其開發社群

目前的 CSS 載入機制

1
2
3
4
5
6
<head>
<link rel="stylesheet" href="/all-of-my-styles.css">
</head>
<body>
…content…
</body>

當 CSS 段落在渲染時,會讓使用者瞪著白白的頁面直到 all-of-my-styles.css 完全下載完畢。

通常我們會把站內所有的 CSS 封裝成較少,可能只有一兩個資源檔,但這同時也意味著使用者需要下載大量
的樣式設定(CSS Rules)卻沒有在該頁面使用。因為一個網站包含著各種不同的頁面與元件,而這些東西需要
套用不同的樣式規則,如果因此分拆成許多檔案而產生大量的請求 request 這在 HTTP/1 是非常耗效能的。

不過在 SPDYHTTP/2 卻不是這樣,傳輸許多分散的小資源只會增加一點點的開銷,並且這些東西
可以個別暫存(cached)。

1
2
3
4
5
6
7
8
9
10
<head>
<link rel="stylesheet" href="/site-header.css">
<link rel="stylesheet" href="/article.css">
<link rel="stylesheet" href="/comment.css">
<link rel="stylesheet" href="/about-me.css">
<link rel="stylesheet" href="/site-footer.css">
</head>
<body>
content
</body>

這樣一來就解決了許多問題,我們可以拆成很多小檔案個別載入,不過也意味著當我們在 <head><link> 時,
得要知道這些頁面各自需要哪些資源。
另外,瀏覽器在開始輸出之前,仍然得下載所有的 CSS。如果出現一個下載比較慢的 CSS 例如 /site-footer.css
將會造成渲染的東西延遲。請觀察範例

現階段載入 CSS 較先進的機制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<head>
<script>
// https://github.com/filamentgroup/loadCSS
!function(e){"use strict"
var n=function(n,t,o){function i(e){return f.body?e():void setTimeout(function(){i(e)})}var d,r,a,l,f=e.document,s=f.createElement("link"),u=o||"all"
return t?d=t:(r=(f.body||f.getElementsByTagName("head")[0]).childNodes,d=r[r.length-1]),a=f.styleSheets,s.rel="stylesheet",s.href=n,s.media="only x",i(function(){d.parentNode.insertBefore(s,t?d:d.nextSibling)}),l=function(e){for(var n=s.href,t=a.length;t--;)if(a[t].href===n)return e()
setTimeout(function(){l(e)})},s.addEventListener&&s.addEventListener("load",function(){this.media=u}),s.onloadcssdefined=l,l(function(){s.media!==u&&(s.media=u)}),s}
"undefined"!=typeof exports?exports.loadCSS=n:e.loadCSS=n}("undefined"!=typeof global?global:this)
</script>
<style>
/* The styles for the site header, plus: */
.main-article,
.comments,
.about-me,
footer {
display: none;
}
</style>
<script>
loadCSS("/the-rest-of-the-styles.css");
</script>
</head>
<body>
</body>

上面程式碼,我們有一些內嵌的樣式(inline style)來讓我們可以快速的渲染初始化的樣式,接著把那些
還沒取得樣式的部分隱藏起來,然後開始透過 Javascript 非同步下載剩餘的樣式。這些剩餘的 CSS
會覆寫掉在 .main-article 和其他選擇器內的 display: none

這種第一次先快速的初始化渲染,然後持續匯入的方法是許多效能專家所推薦的。

看看範例

原作者實作了wiki-offline並將狀況紀錄如下圖

上面這張圖片是在 3G 的環境下測試。

不過這樣的方法還是有些不足的地方:

需要一個輕量的 Javascript 函式庫

不幸的,因為 WebKit 的實作。當 <link rel="stylesheet"> 一被加到頁面時,WebKit 會阻塞
渲染(render),直到樣式都被載入,即使這個樣式是透過 Javascript 加入的。

在 Firefox 和 IE/Edge,透過 JS 加入的樣式完全是非同步載入。Chrome 當前穩定版本然仍是遵循
WebKit 的行為,不過在 Canary 已經跟 Firefox/Edge 一樣了。

載入流程被限制在兩個階段(Inline css and A css file)

根據上面的模式,內嵌 CSS(inline CSS) 透過 display: none 隱藏尚未套用樣式的內容,然後非同步
得載入 CSS 之後呈現內容。如果您需要增加多個 CSS 檔案那麼結果可能就是內容不按順序出現

檢視範例

如果內容還在異動的過程結果周圍的廣告就先出現這通常會讓使用者覺得不開心就關閉你的網站了。

因此被限制在只有兩個載入階段,你必須要決定哪些是第一次渲染時就要出現,哪些是比較晚的。
當然,你希望上方的區塊越快顯示越好,不過所謂”上方的區塊”取決於 viewport 可視區域的大小。
最後你可能決定定義一個尺寸範圍套用在所有人身上。

如果你想讓事情更加複雜,當然你可以選擇客製 CSS 相依屬性來建立 CSS 之間渲染的相依性

更簡單,更好的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<head>
</head>
<body>
<!-- HTTP/2 push this resource, or inline it, whichever's faster -->
<link rel="stylesheet" href="/site-header.css">
<header></header>

<link rel="stylesheet" href="/article.css">
<main></main>

<link rel="stylesheet" href="/comment.css">
<section class="comments"></section>

<link rel="stylesheet" href="/about-me.css">
<section class="about-me"></section>

<link rel="stylesheet" href="/site-footer.css">
<footer></footer>
</body>

這個概念是透過每一個 <link rel="stylesheet"> 在其下載樣式時去阻塞跟在後面的內容,但允許
前面的內容先開始渲染。
樣式表(CSS)本身的載入機制是平行的,但是套用樣式卻是要照順序的。這讓 <link rel="stylesheet"> 的行為類似為 <script>

假設說 site-header, article, footer 的樣式已經載完了,但剩下的還沒,其行為如下:

  • Header: 已輸出
  • Article: 已輸出
  • Comments: 未呈現,在該標籤之前地 CSS 還沒載完(./comment.css)
  • About me: 未呈現,在該標籤之前地 CSS 還沒載完(./comment.css)
  • Footer: 未呈現,在該標籤之前地 CSS 還沒載完(./comment.css),即使自己的 CSS 已經載完了

這讓我們可以照順序輸出頁面。您甚至不需要決定哪些是”上面的區塊”,只要在元件之前匯入元件需要的 CSS 即可。

不過你還是需要注意當使用內容決定佈局(layout system),例如 table, flexbox 時,在載入期間
應避免內容異動。
這不是現在才產生的問題了,只是在逐步顯示這種機制之下更常遇到。如果你看不懂這段在描述什麼看一下下面的影片就知道了。

意思是說雖然 flexbox 已經很不錯了,但 Grid 還是更推薦的 Layout system。

Chrome 的改變

HTML規範並不涵蓋網頁渲染時是否應該或該如何被 CSS 阻塞,
並且也不鼓勵把 <link rel="stylesheet"> 寫在 body 中,不過所有瀏覽器都允許這麼做。

當然他們也都各自使用了自己的方式處理在 body 中的 <link>

  • Chrome & Safari: 一旦發現 <link rel="stylesheet"> 就停止渲染(render),直到發現的樣式被載入完畢。往往導致在 <link> 上面還未渲染的內容被卡住。
  • Firefox: <link rel="stylesheet"><head> 中會阻塞渲染直到所有發現的樣式都被載入完畢。在 body 中的話不會阻塞渲染,除非 head 裡已經有樣式阻塞住
    這可能會導致 FOUC (Flash of unstyled content) ,就是畫面先以預設的樣子呈現後閃一下才套上樣式。
  • IE/Edge: 中斷分析器直到樣式載完,不過允許 <link> 上面的內容渲染(render)。

我們偏好像 IE/Edge 的行為,所以 Chrome 將會跟隨這樣的機制。目前 Chrome/Safari 的行為就只是
會卡比較久的時間,Firefox 的行為相對較複雜一些,不過有一些小技巧。

Firefox 處理方式

因為 Firefox 不會因為 body 中的 <link> 阻塞渲染。我們需要一點小技巧來避免 FOUC。幸虧有個非常簡單的方式就是透過 <script> 中斷解析器
讓他等一等待處理狀態的樣式

1
2
<link rel="stylesheet" href="/article.css"><script> </script>
<main></main>

這個標籤不能完全沒有內容,所以我們需要留一個”空白”。

實際執行的結果

查閱範例

Firefox 和 Edge/IE 將會循序的載入輸出,而 Chrome 和 Safari 則是先看到空白的頁面一陣子直到
CSS 全部載完。目前 Chrome/Safari 的行為比起把樣式連結放在 <head> 也沒有差到哪去,所以
現在就可以開始使用這個方式,很快的 Chrome 將會往 Edge 的機制修正,這麼一來渲染會更加迅速。

以上就是一個簡單的技巧協助我們加快速度並逐步載入 CSS

Javascript Array 筆記

方法範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// push
var arr = [1, 2, 3];
var result = arr.push(4); // result: 4, arr: [1, 2, 3, 4]

// pop
var arr = [1, 2, 3];
var result = arr.pop(); // result: 3, arr: [1, 2]

// length
var arr = [1, 2];
arr.length; // 2

// unshift
var arr = [1, 2, 3];
var result = arr.unshift(0); // result: 4, arr: [0, 1, 2, 3]

// shift
var arr = [1, 2, 3];
var result = arr.shift(); // result: 1, arr: [2, 3]

// splice 拼接
// Array.splice(start_index, remove_num, element01, element02...)
var arr = [1, 2, 3, 4, 5];
var result01 = arr.splice(2, 1, 6); // result01: [3], arr: [1, 2, 6, 4, 5]
var arr = [1, 2, 3, 4, 5];
var result02 = arr.splice(2, 0, 6); // result02: [], arr: [1, 2, 6, 3, 5, 6]
// 沒移除元素會放在該 index 元素會被往後推
var arr = [1, 2, 3, 4, 5];
var result03 = arr.splice(2, 0, 6, 7, 8) // result03: [], arr: [1, 2, 6, 7, 8, 3, 4, 5]

// slice 切片
var arr = [1, 2, 3, 4, 5];
var result01 = arr.slice(1, 3); // result01: [2, 3], arr: [1, 2, 3, 4, 5]
var arr = [1, 2, 3, 4, 5];
var result02 = arr.slice(-2); // result02: [4, 5], arr: [1, 2, 3, 4, 5]
var arr = [1, 2, 3, 4, 5];
var result03 = arr.slice(-2, 0); // result03: [], arr: [1, 2, 3, 4, 5]
var arr = [1, 2, 3, 4, 5];
var result04 = arr.slice(0); // result04: [1, 2, 3, 4, 5], arr: [1, 2, 3, 4, 5]

// indexOf
var arr = [1, 2, 3, 4, 5];
var result01 = arr.indexOf(3); // result01: 2
var arr = ['a', 'ab', 'abc', 'abcd'];
var result02 = arr.indexOf('a'); // result02: 0
var result03 = arr.indexOf('b'); // result03: -1
var result04 = arr.indexOf('ab'); // result04: 1

// lastIndexOf
var arr = [1, 2, 3, 2, 1];
var result01 = arr.lastIndexOf(1); // result01: 4
var arr = ['a', 'ab', 'abc', 'ab', 'a'];
var result02 = arr.lastIndexOf('a'); // result02: 4
var result03 = arr.lastIndexOf('b'); // result03: -1
var result04 = arr.lastIndexOf('ab'); // result04: 3

// some 陣列是否有元素符合測試
var arr = [1, 2, 3, 4, 5];
var result01 = arr.some(function (element, index, array) {
return element > 3
}); // result01: true
var result02 = arr.some(element => element > 5); // result02: false

// every 陣列中是否所有元素都符合測試
var arr = [1, 2, 3, 4, 5];
var result01 = arr.every(function (element, index, array) {
return element > 3
}); // result01: false
var result02 = arr.every(element => element < 6); // result02: true

// join 串聯成字串
var arr = [1, 2, 3, 4, 5];
var result = arr.join(','); // result: "1,2,3,4,5"

// sort 由小排到大
var arr = [5, 4, 3, 2, 1];
var result = arr.sort(); // result: [1, 2, 3, 4, 5], arr: [1, 2, 3, 4, 5]
var arr = ['c', 'b', 'a', 0];
var result = arr.sort(); // result: [0, 'a', 'b', 'c'], arr: [0, 'a', 'b', 'c']

// reverse 由大排到小
var arr = [1, 2, 3, 4, 5];
var result = arr.reverse(); // result: [5, 4, 3, 2, 1], arr: [5, 4, 3, 2, 1]
var arr = [0, 'a', 'b', 'c'];
var result = arr.reverse(); // result: ['c', 'b', 'a', 0], arr: ['c', 'b', 'a', 0]

// concat 組合並回傳一個新的陣列(flat element)
var a = [1, 2];
var b = [3, 4];
var c = a.concat(b); // a: [1, 2], b: [3, 4], c: [1, 2, 3, 4]
var d = [...a, ...b]; // a: [1, 2], b: [3, 4], d: [1, 2, 3, 4]
var e = a.concat(0, [3, 4]); // e: [1, 2, 0, 3, 4]

// forEach
var arr = [1, 2, 3, 4, 5];
arr.forEach(function (element, index, array) {
console.log(element, index, array);
});
// 1, 0, [1, 2, 3, 4, 5]
// 2, 1, [1, 2, 3, 4, 5]
// 3, 2, [1, 2, 3, 4, 5]
// 4, 3, [1, 2, 3, 4, 5]
// 5, 4, [1, 2, 3, 4, 5]
// 注意: 例外無法阻止 forEach 停止

// map 遍歷所有元素至參數 callback 然後回傳一個新的陣列
var arr = [1, 2, 3, 4, 5];
var result01 = arr.map(function (element) {
return element + 10;
}); // arr: [1, 2, 3, 4, 5], result01: [ 11, 12, 13, 14, 15 ]
var arr = [1, 4, 9];
var result02 = arr.map(Math.sqrt); // arr: [1, 4, 6], result02: [1, 2 ,3]

// filter 過濾元素,產生新陣列
var arr = [1, 2, 3];
var result = arr.filter(function (element) {
return element > 2;
}); // arr: [1, 2, 3], result: [3]

// reduce 從左至右當作累加器把陣列變成單值
var arr = [1, 2, 3, 4, 5];
var result = arr.reduce(function (previous, current, currentIndex, array) {
return previous + current;
}, 100); // result: 115

// reduceRight
var arr = ['ab', 'bc', 'de'];
var result = arr.reduceRight(function (pre, cur, index, arr) {
return pre + cur;
}, 'I am initial:'); // result: "I am initial:debcab"

// 其他補充
var a; // a: undefined
var b = a += 2; // a: NaN, b: NaN => undefined += 2 => NaN

var a = null;
var b = a += 2; // a:2, b: 2

Array.forEach 額外補充

根據 MDN 的定義 arr.forEach(callback[, thisArg]) 當我們要在 forEach 的匿名函數中使用 this 我們可以透過 thisArg 來設定。
如果不設定(undefined, null)或者不用 var self = this; 的方式保留 context,那麼預設會是指向 global

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var sum = 100;
function Counter () {
this.sum = 0;
}

Counter.prototype.add = function (arr) {
arr.forEach(function (el, i, arr) {
this.sum += el;
})
};

var o = new Counter();
o.add([1, 2, 3]);
console.log(o.sum); // 0
console.log(sum); // 106

在 Chrome 或者 Firefox console 底下執行的確會執向 global,但是在 nodejs v5.6.0 底下結果卻是

1
2
console.log(o.sum); // 0
console.log(sum); // 100

要注意。

正確的範例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Counter () {
this.sum = 0;
}

Counter.prototype.add = function (arr) {
arr.forEach(function (el, i, arr) {
this.sum += el;
}, this); // <-- here this "thisArg"
}

var o = new Counter();
o.add([1, 2, 3]);
console.log(o.sum); // 6
console.log(sum); // undefined

callback methods

  • filter (has thisArg)
  • some (has thisArg)
  • every (has thisArg)
  • forEach (has thisArg)
  • map (has thisArg)
  • reduce
  • reduceRight

Chrome Dev tool 筆記

Element Panel

  • ⌥⌘I 開啟 Dev Tool

選取元素的方式

  • 對元素點右鍵 -> Inspect
  • 開啟 Dev Tool -> 點擊 Dev Panel 左上角 bar 的箭頭 ICON -> 選取元素
  • ⌘⇧C -> 選取

編輯樣式

  • 選定一個 Element 之後右邊 panel 會顯示該 Element 之樣式
  • 開關樣式
  • 新增或刪除樣式
  • 右上角 -> Change state(hover, active, focus, visited)
  • Computed -> 觀察 box model
  • 點擊連結切換到 Source
  • Shift + 點擊 CSS rule 的顏色會轉換 e.g. hex to rgb
  • 編輯樣式時,選取某個字串 ->Command+D會把一樣的都選起來

Source Panel

  • 進入 Source panel ⌘S 可以暫時編輯檔案並存到瀏覽器的 Storage
  • 右鍵 -> Local Modifications 可以檢視歷史紀錄

Console

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log();
console.assert(false, 'Information'); // 錯了就噴第二個參數
console.count('go'); // go: 1
console.count('go'); // go: 2
console.log("document body: %O", document.body);

console.group("Authenticating user '%s'", user);
console.log("User authenticated");
console.groupEnd();

console.log('%c Hello', 'color: orange;'); // %c 使用 css
console.log("%O ", document.body); // %O Javascript Object
console.log("%O ", document.body); // %o DOM

詳細 Console API

  • 學會查看 Error,切換到 Source
  • 在 Source panel 下 ESC 開啟第二層 Console
1
2
3
4
5
6
$('selector') // 稱為 blind
// 如果載入 jQuery 預設的 blind 會被覆寫
// 回傳的是一個陣列
inspect($('#title')) // 選取元素並切換到 Element Panel
$0 // 當前選取的 Element
$1 // 上一個
  • Source panel 右上角的 Pause on 按鈕打開的話,下次噴錯就會暫停
  • Source panel 左下角的 {} 按鈕可以把 minifed 檔案轉換成較易讀的格式

Local Storage

  • 切換到 Resource panel

Network panel

  • Refresh 之後會記錄每個 Request
  • + Refresh 鍵強制全部重載
  • Size = Transfer size
  • Content = Actual size
  • 最底下的 Bar 有總和
  • Waterfall 裡面淺色的部分代表發出 request回應即開始傳輸資料的時間
  • 實心的部分代表開始下載資料
  • 顏色的意義
    • HTML 藍色
    • JS 橘色
    • CSS 綠色
    • 圖片 紫色
    • 右邊垂直的藍色線代表 DOM loaded 意為瀏覽器解析完畢 HTML to DOM
    • 右邊垂直紅色線則表示圖片等資源下載完畢
  • CSS 放在 JS 之前

Timeline

  • 顏色的意義
    • Loading 藍色
    • 執行 Script 黃色
    • Rendering 紫色
    • Painting 綠色
  • Size vs Content

從<琅琊榜>學 Redux

前言

這篇文章希望從另外一個角度來看關於 Redux 的機制與運用,在網路上各式的教學文章中值得先推薦的自然是官方教學
如果英文不是很好的可以參考繁體中文版。這是小弟認為要學習 Redux 必讀的文件。另外還有A cartoon intro to redux也是值得一讀的好文可以加深記憶。

接著就讓我們開始吧,當然最後如果有任何錯誤,也歡迎您的指教。

繼續閱讀