通过一行代码学习javascript

410 查看

在javascript无处不在的今天,我们每天都能很容易地学习到新的东西。一旦你掌握了这门语言的基本知识,就可以随处找到蕴含着丰富知识的代码。Bookmarklets是一个完美的多种功能组合起来的工具,每当我发现一个有用的功能,我都会开心地去学习其源码,从中探索出怎么实现这样强大的功能。另外,如Google Analytics Code,或是Facebook Likebox的一些调用外部服务的小代码段,能教给我们的比一些书还多。

今天我想逐条跟大家分析Addy Osmani 几天前分享的一个单行代码 。这行代码可以帮你debug你的各层CSS。为了方便观看,我将其分为三行显示:

将它放在你的浏览器控制台中运行,页面中各层的HTML就会被不同的颜色标记出来。很酷对不对?基本上,这行代码获取了页面中所有元素,然后给它们加上1px,颜色随机的边框。虽然它的原理很简单,但是想要自己写出这样的代码,你得熟练掌握Web开发的方方面面。下面让我们一起学习它们。

选取一个页面上所有的元素

首先需要做的是选取所有的元素,Addy用了只能在浏览器控制台中使用的$ $。你可以在自己的浏览器的javascript控制台中输入$ $(‘a’),然后你会得到一个含有当前页面所有锚元素的列表。

$ $函数是现代浏览器命令行的API的一部分,它等同于使用document.querySelectorAll 方法。你可以将一个CSS选择器作为参数传入document.querySelectorAll去选取当前页面的元素。所以如果你想在浏览器的控制台以外使用那个单行代码,你可以用document.querySelectorAll('*')来替代$ $('*')。点击这个stackoverflow问答可以进一步了解$ $

非常好!对于我来说,能从这行代码中学习到$ $函数就已经很满足了。然而在一个页面上选取所有元素的方法还有很多。如果你有看过gist上面的评论,你会发现有人在讨论这个话题。其中一个人就是Mathias Bynens(那里有很多大神!),他告诉我们可以用document.all去实现这个功能,并且能在所有的浏览器上运行。

遍历元素

于是我们得到了存储所有的元素的NodeList,然后想给它们一个一个加上彩色的边框。不过等等,我们的代码里到底是怎么写的呢?

NodeList看起来像Array,你可以使用方括号去访问它的节点,还可以访问length属性去了解它包含了多少元素,但是它并没有实现Array的所有接口,因此$ $(‘*’).forEach会失效。在Javascript里,有很多看起来像但不是数组的对象,例如函数里的arguments变量。我们有一个很好用的方法去处理这些对象:通过call和apply去实现像NodeList一样的非array对象有机会去调用array的函数。我几个月前谈过这些函数,它们调用另外一个函数,并将第一个参数作为该函数里面的this对象。

单行代码用[].forEach.call来替代Array.prototype.forEach.call。通过Array对象[]去调用Array的函数这样的方式(哈,又是一个给力的小技巧),节省了一些字节。这相当于在$ $(‘*’).forEach中,把$ $(‘*’)当成一个Array来使用。

如果你再去看看评论区,你会发现有些人为了让代码更短些而使用for(i=0;A=$ $(‘*’);)。这确实可行,但是它会泄漏全局变量,所以如果你想要在控制台以外的地方使用这种方法,你最好在一个干净的环境中使用。

如果你在浏览器的控制台中使用就无所谓了,从你在里面定义它们开始,iA变量将会一直在里面。

给元素上色

为了使这些元素都有漂亮的边框,代码使用了CSS的outline属性。如果你还不知道的话,显示的边框是在CSS区块模型外的,它并不对元素本身在布局中的大小和位置产生任何影响,因此该属性非常适合用来实现我们的需求。 它的语法就像border的一样,所以理解下面的部分应该不难:

有意思的是这里定义颜色的方式:

被吓到了吗?当然,我不是一个位操作的专家,因此这是我最喜欢的部分,因为我从中学到了很多新东西。

我们想得到的是十六进制表示的颜色,如白色 FFFFFF, 或是蓝色0000FF,亦或是…谁知道呢…37f9ac吧。像我一样的普通人都习惯了使用十进制数字,而我们心爱的代码对十六进制非常地了解。

首先我们可以从中学到用toString方法把十进制整数转换成十六进制整数。该方法以接收的参数作为其进制基数,将一个数转换为一个字符串表示的数。如果没有传入参数,将会默认进行十进制转换,但其实你可以使用其他基数。

反过来,你可以使用parseInt方法的第二个参数将十六进制数的字符串形式转换为十进制数。

我们需要一个介于0和十六进制数 ffffff之间的随机数,那么就是parseInt("ffffff", 16) == 16777215。16777215正好是2^24 - 1

你喜欢二进制算术吗?不喜欢的话,你只需要知道1<<24 == 16777216就好了(建议在控制台里试试)。

如果喜欢二进制算术,你得知道每次你在1的右侧加一个0,就相当于做了一次2^n操作,其中n为你加0的次数。

左移运算x<<n是向x的二进制表示加入n0,因此1<<24是16777216的简写版,进而通过Math.random()*(1<<4)我们可以得到一个介于016777216之间的随机数。

这还没完,因为Math.random返回的值是浮点数,而我们只需要整数部分。我们这里使用了波浪符号(~)去实现。波浪符号用于对一个变量按位取反。如果你不明白我在说什么,这里有个很好的Javascript波浪符号讲解

但是这些代码重点不在于按位取反,而在于利用位操作符会丢弃浮点数中的小数部分的特性,因此连续两次按位取反是一个替代parseInt的便捷方法:

再提醒大家一遍,如果你去gist中看看评论区,你会发现大家在用更简短的代码去获取parseInt的结果。使用OR位操作符你可以去掉我们随机数中的小数部分。

Or操作符的优先级在最后,所以使用时可以省掉括号。这里是Javascript操作符的优先级介绍,感兴趣的话可以看看。

终于我们有了介于016777216之间的随机数,即我们的随机颜色。为了使用它,我们现在只需要用toString(16)将其转换成字符串形式的十六进制数即可。

一些感想

当一名程序员并不容易。我们疯狂地写代码,有时却没有意识到我们需要多少知识去做我们做的事情。而消化掉所有我们在工作中用到的概念,需要很长的时间。

我想突出强调的是我们工作的复杂度,因为我知道程序员通常都会被低估(尤其在我的国家,西班牙)。在大部分公司里我们扮演着重要角色,并且我们的工作非常有价值——能经常这样说,是一件很好的事情。

如果你第一眼看到这单行代码,你就能理解它,你可以为你感到骄傲。

如果不能,但是你还是把文章看到这里,不要担心,你很快就有能力写出这样的代码,因为你是一个学习者!

如果你看到文章的第二行就在想太长不看,但你还是看到这里了,你真是个奇葩,但我仍欢迎你在评论区写下你的意见:)