在javascript無處不在的今天,我們每天都能很容易地學習到新的東西。一旦你掌握了這門語言的基本知識,就可以隨處找到蘊含著豐富知識的程式碼。Bookmarklets是一個完美的多種功能組合起來的工具,每當我發現一個有用的功能,我都會開心地去學習其原始碼,從中探索出怎麼實現這樣強大的功能。另外,如Google Analytics Code,或是Facebook Likebox的一些呼叫外部服務的小程式碼段,能教給我們的比一些書還多。
今天我想逐條跟大家分析Addy Osmani 幾天前分享的一個單行程式碼 。這行程式碼可以幫你debug你的各層CSS。為了方便觀看,我將其分為三行顯示:
1 2 3 |
[].forEach.call($ $(""),function(a){ a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16) }) |
將它放在你的瀏覽器控制檯中執行,頁面中各層的HTML就會被不同的顏色標記出來。很酷對不對?基本上,這行程式碼獲取了頁面中所有元素,然後給它們加上1px,顏色隨機的邊框。雖然它的原理很簡單,但是想要自己寫出這樣的程式碼,你得熟練掌握Web開發的方方面面。下面讓我們一起學習它們。
選取一個頁面上所有的元素
首先需要做的是選取所有的元素,Addy用了只能在瀏覽器控制檯中使用的$ $
。你可以在自己的瀏覽器的javascript控制檯中輸入$ $(‘a’)
,然後你會得到一個含有當前頁面所有錨元素的列表。
$ $
函式是現代瀏覽器命令列的API的一部分,它等同於使用document.querySelectorAll
方法。你可以將一個CSS選擇器作為引數傳入document.querySelectorAll去選取當前頁面的元素。所以如果你想在瀏覽器的控制檯以外使用那個單行程式碼,你可以用document.querySelectorAll('*')
來替代$ $('*')
。點選這個stackoverflow問答可以進一步瞭解$ $
。
非常好!對於我來說,能從這行程式碼中學習到$ $
函式就已經很滿足了。然而在一個頁面上選取所有元素的方法還有很多。如果你有看過gist上面的評論,你會發現有人在討論這個話題。其中一個人就是Mathias Bynens(那裡有很多大神!),他告訴我們可以用document.all
去實現這個功能,並且能在所有的瀏覽器上執行。
遍歷元素
於是我們得到了儲存所有的元素的NodeList
,然後想給它們一個一個加上彩色的邊框。不過等等,我們的程式碼裡到底是怎麼寫的呢?
1 2 |
[].forEach.call( $ $('*'), function( element ) { /* And the modification code here */ }); [].forEach.call( $ $('*'), function( element ) { /* 對元素作出改變的程式碼 */ }); |
NodeList
看起來像Array
,你可以使用方括號去訪問它的節點,還可以訪問length屬性去了解它包含了多少元素,但是它並沒有實現Array
的所有介面,因此$ $(‘*’).forEach
會失效。在Javascript裡,有很多看起來像但不是陣列的物件,例如函式裡的arguments變數。我們有一個很好用的方法去處理這些物件:通過call和apply去實現像NodeList
一樣的非array物件有機會去呼叫array的函式。我幾個月前談過這些函式,它們呼叫另外一個函式,並將第一個引數作為該函式裡面的this物件。
1 2 3 4 5 6 7 8 9 10 11 12 |
function say(name) { console.log( this + ' ' + name ); } say.call( 'hola', 'Mike' ); // Prints out 'hola Mike' in the console say.call( 'hola', 'Mike' ); // 在console中輸出’hola Mike’ // Also you can use it on the arguments object //你也可以在arguments物件上使用它 function example( arg1, arg2, arg3 ) { return Array.prototype.slice.call(arguments, 1); // Returns [arg2, arg3] } |
單行程式碼用[].forEach.call
來替代Array.prototype.forEach.call
。通過Array
物件[]
去呼叫Array
的函式這樣的方式(哈,又是一個給力的小技巧),節省了一些位元組。這相當於在$ $(‘*’).forEach
中,把$ $(‘*’)
當成一個Array
來使用。
如果你再去看看評論區,你會發現有些人為了讓程式碼更短些而使用for(i=0;A=$ $(‘*’);)
。這確實可行,但是它會洩漏全域性變數,所以如果你想要在控制檯以外的地方使用這種方法,你最好在一個乾淨的環境中使用。
1 |
for(var i=0,B=document.querySelectorAll('*');A=B[i++];){ /* your code here */ } |
如果你在瀏覽器的控制檯中使用就無所謂了,從你在裡面定義它們開始,i
和A
變數將會一直在裡面。
給元素上色
為了使這些元素都有漂亮的邊框,程式碼使用了CSS的outline
屬性。如果你還不知道的話,顯示的邊框是在CSS區塊模型外的,它並不對元素本身在佈局中的大小和位置產生任何影響,因此該屬性非常適合用來實現我們的需求。 它的語法就像border
的一樣,所以理解下面的部分應該不難:
1 |
a.style.outline="1px solid #" + color |
有意思的是這裡定義顏色的方式:
1 |
~~(Math.random()*(1<<24))).toString(16) |
被嚇到了嗎?當然,我不是一個位操作的專家,因此這是我最喜歡的部分,因為我從中學到了很多新東西。
我們想得到的是十六進位制表示的顏色,如白色 FFFFFF
, 或是藍色0000FF
,亦或是…誰知道呢…37f9ac
吧。像我一樣的普通人都習慣了使用十進位制數字,而我們心愛的程式碼對十六進位制非常地瞭解。
首先我們可以從中學到用toString
方法把十進位制整數轉換成十六進位制整數。該方法以接收的引數作為其進位制基數,將一個數轉換為一個字串表示的數。如果沒有傳入引數,將會預設進行十進位制轉換,但其實你可以使用其他基數。
1 2 3 4 5 6 7 8 9 10 11 |
(30).toString(); // "30" (30).toString(10); // "30" (30).toString(16); // "1e" Hexadecimal (30).toString(2); // "11110" Binary (30).toString(36); // "u" 36 is the maximum base allowed (30).toString(); // "30" (30).toString(10); // "30" (30).toString(16); // "1e" 十六進位制 (30).toString(2); // "11110" 二進位制 (30).toString(36); // "u" 36 是所允許的最大基數 |
反過來,你可以使用parseInt
方法的第二個引數將十六進位制數的字串形式轉換為十進位制數。
1 2 3 4 5 |
parseInt("30"); // "30" parseInt("30", 10); // "30" parseInt("1e", 16); // "30" parseInt("11110", 2); // "30" parseInt("u", 36); // "30" |
我們需要一個介於0
和十六進位制數 ffffff
之間的隨機數,那麼就是parseInt("ffffff", 16) == 16777215
。16777215正好是2^24 - 1
。
你喜歡二進位制算術嗎?不喜歡的話,你只需要知道1<<24 == 16777216
就好了(建議在控制檯裡試試)。
如果喜歡二進位制算術,你得知道每次你在1的右側加一個0,就相當於做了一次2^n
操作,其中n
為你加0的次數。
1 2 3 4 |
1 // 1 == 2^0 100 // 4 == 2^2 10000 // 16 == 2^4 1000000000000000000000000 // 16777216 == 2^24 |
左移運算x<<n
是向x
的二進位制表示加入n
個0
,因此1<<24
是16777216的簡寫版,進而通過Math.random()*(1<<4)
我們可以得到一個介於0
和16777216
之間的隨機數。
這還沒完,因為Math.random
返回的值是浮點數,而我們只需要整數部分。我們這裡使用了波浪符號(~)去實現。波浪符號用於對一個變數按位取反。如果你不明白我在說什麼,這裡有個很好的Javascript波浪符號講解。
但是這些程式碼重點不在於按位取反,而在於利用位操作符會丟棄浮點數中的小數部分的特性,因此連續兩次按位取反是一個替代parseInt
的便捷方法:
1 2 3 4 5 6 7 |
var a = 12.34, // ~~a = 12 b = -1231.8754, // ~~b = -1231 c = 3213.000001; // ~~c = 3213 ~~a == parseInt(a, 10); // true ~~b == parseInt(b, 10); // true ~~c == parseInt(c, 10); // true |
再提醒大家一遍,如果你去gist中看看評論區,你會發現大家在用更簡短的程式碼去獲取parseInt
的結果。使用OR
位操作符你可以去掉我們隨機數中的小數部分。
1 2 3 |
~~a == 0|a == parseInt(a, 10) ~~b == 0|b == parseInt(b, 10) ~~c == 0|c == parseInt(c, 10) |
Or
操作符的優先順序在最後,所以使用時可以省掉括號。這裡是Javascript操作符的優先順序介紹,感興趣的話可以看看。
終於我們有了介於0
和16777216
之間的隨機數,即我們的隨機顏色。為了使用它,我們現在只需要用toString(16)
將其轉換成字串形式的十六進位制數即可。
一些感想
當一名程式設計師並不容易。我們瘋狂地寫程式碼,有時卻沒有意識到我們需要多少知識去做我們做的事情。而消化掉所有我們在工作中用到的概念,需要很長的時間。
我想突出強調的是我們工作的複雜度,因為我知道程式設計師通常都會被低估(尤其在我的國家,西班牙)。在大部分公司裡我們扮演著重要角色,並且我們的工作非常有價值——能經常這樣說,是一件很好的事情。
如果你第一眼看到這單行程式碼,你就能理解它,你可以為你感到驕傲。
如果不能,但是你還是把文章看到這裡,不要擔心,你很快就有能力寫出這樣的程式碼,因為你是一個學習者!
如果你看到文章的第二行就在想太長不看,但你還是看到這裡了,你真是個奇葩,但我仍歡迎你在評論區寫下你的意見:)