[譯] Houdini: 你還沒聽說!這可能是 CSS 開發中最令人興奮的事!

其實…我想說這可能是最令我感到興奮..但又害怕頭痛的功能… - 原文連結

你曾經想要使用某個 CSS 的新功能,但是最後卻因為這個功能瀏覽器還未全面支援而放棄了嗎?甚至更糟糕的狀況,瀏覽器已經支援了但卻充滿問題。我敢打賭這些情況你肯定遇過了。如果上面這種情形你曾經遇過,那麼你是應該關心一下 Houdini

繼續閱讀

Cache 筆記

  • 瀏覽器 cache 是一種機制,透過特定狀態告知瀏覽器不需要重新下載檔案
  • cache 最早使用 Expires 和 Pragma,現今主要使用 Cache-Control 來控制
  • cache 大略流程,在實際下載檔案之前發生:
    1. cache 的實際處理機制存在瀏覽器中,也就是我們需要透過指令(cache-directives)告訴瀏覽器該怎麼處理
    2. 瀏覽器設定為不提供 cache ,完全忽略下面步驟直接請求資源
    3. 瀏覽器發送請求,此時可包含 cache-request-directive 這部分的資訊主要是拿來和 server 協商比對用的
    4. 伺服器判斷是否有更新並回傳通知 cache-response-directive
繼續閱讀

line-height 問題筆記

line-height 歸納

  • line-height 有五種屬性可用,預設是 normal 他會拿 font-size * 1.2
    • normal
    • number ex: 1.2, 1.8 => 倍數
    • unit ex: 18px, 2em, 3rem, etc…
    • percentage ex: 90%
    • inherit => line-height from parent
繼續閱讀

深入 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