从谷歌一行代码学到的姿势

7,888 阅读2分钟

网上很流行的一行代码,据说是谷歌工程师写的,它的作用是给页面所有元素增加一个随机颜色的外边框

[].forEach.call($$("*"),function(a){a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)})

运行效果如下图:

3

这个代码虽然只有一行,但是包含的知识点不少,网上有很多解析。我也说下自己的理解,然后最后推荐在实务中使用TreeWalker对象进行遍历。

我的理解其中主要包含如下4个知识点:

1. [].forEach.call
2. $$("*")
3. a.style.outline
4. (~~(Math.random()*(1<<24))).toString(16)

1 [].forEach.call

1.1 [].forEach

forEach是数组遍历的一个方法,接收一个函数参数用来处理每一个遍历的元素,常规的使用姿势是:

let arr = [3, 5, 8];
arr.forEach((item) => {
	console.log(item);
})
// 控制台输出:
//	3
//	5
//	8

那么下面的写法:

[].forEach

只是为了得到 forEach 这个方法,这个方法是定义都在Array.prototype上的方法,[] 表示空数组,可以访问到数组原型对象上的方法。

得到 forEach 这个方法后,就可以通过 call 发起调用。

1.2 call

call函数用来调用一个函数,和普通调用不同,call调用可以修改函数内this指向。

常规调用函数的姿势:

let object1 = {
	id: 1,
	printId() {
		console.log(this.id)
	}
}
object1.printId();
// 控制台输出:
//	1

因为是正常调用,方法内的this指向object1对象,所以上例输出1。

使用call调用printId方法,并传入另外一个对象object2:

let object2 = {
	id: 2
}
object1.printId.call(object2);
// 控制台输出:
//	2

这里使用call调用object1.printId函数,传入了object2对象,那么printId函数内的this就是指向object2这个对象,所以结果输出2。

1.3 综合分析

综合来看:

[].forEach.call( $$("*"), function(a){} )

这行代码的意思就是遍历如下对象:

$$("*") 

然后用如下方法处理每个元素:

function(a){}

其中,a就是遍历的的每一个元素。

那么

$$("*") 

指什么呢?我们接着往后看。

2 $$("*")

这个写法用来获取页面所有元素,相当于

document.querySelectorAll('*')

只是

$$("*") 

只能在浏览器开发控制台内使用,这个是浏览器开发控制台提供出来的预定义API,至于为什么,大家可以参考底部的参考文章。

3 a.style.outline

设置元素边框,估计很多人都知道,但是设置外边框就比较少人了解了,外边框的效果和边框类似,唯一不同的点是外边框盒子模型的算式,仅仅做装饰使用。

<style type="text/css">
	#swiper {
		width: 100px;
		height: 100px;
		outline: 10px solid;
	}
</style>

<div id="swiper"></div>

运行效果:

1

div元素实际的宽高还是100 * 100,如果把outline改成border,那么div元素的实际宽高就是120 * 120,因为要加上border的宽度。

外边框设置的最大作用就是:

可以设置元素边框效果,但是不影响页面布局。

4 (~~(Math.random()*(1<<24))).toString(16)

这个代码从结果是得到一个16进制的颜色值,但是为什么能得到呢?

16进制的颜色值:81f262

4.1 Math.random()

这个容易理解,就是随机 [0, 1) 的小数。

4.2 1<<24

这个表示1左移24位,二进制表示如下所示:

1 0000 0000 0000 0000 0000 0000  

十进制就是表示:

2^24

那么

Math.random() * (1<<24)

就会得到如下范围的一个随机浮点数:

[0, 2^24) 

4.3 两次按位取反

因为Math.random()得到是一个小数,所以两次按位取反就是为了过滤掉小数部分,最后得到整数。

所以

(~~(Math.random()*(1<<24)))

就会得到如下范围的一个随机整数:

[0, 2^24) 

4.4 转成字符串toString(16)

最后就是把上面得到的数字转成16进制,我们知道toString()是用来把相关的对象转成字符串的,它可以接收一个进制参数,转成不同的进制,默认是转成10进制。

对象.toString(2); // 转成2进制
对象.toString(8); // 转成8进制
对象.toString(10); // 转成10进制
对象.toString(16); // 转成16进制

上面的得到的随机整数用二进制表示就是:

0000 0000 0000 0000 0000 0000  

1111 1111 1111 1111 1111 1111

那么2进制转成16进制,是不是就是每4位转一个?

最终是不是就得到一个6个长度的16进制数了?

这个字符串加上#是不是就是16进制的颜色值了?

形如:

#ac83ce
#b74384
等等...

实务应用

虽然上面的代码简短,并且知识含量也很高,但是在实务中如果要遍历元素,我并不建议使用这样的方式。

主要原因是两个:

1. $$("*") 只在开发控制台可以用,正常项目代码中不能用。
2. 选中所有元素再遍历,性能低。

如果实务中要遍历元素,建议是用 TreeWalker。querySelectorAll是一次性获取所有元素然后遍历,TreeWalker是迭代器的方式,性能上 TreeWalker 更优,另外 TreeWalker 还支持各种过滤。

参考如下示例:

// 实例化 TreeWalker 对象
let walker = document.createTreeWalker(
	document.documentElement,
	NodeFilter.SHOW_ELEMENT
);
// 遍历
let node = walker.nextNode();
while (node !== null) {
	node.style.outline = "1px solid #" + (~~(Math.random() * (1 << 24))).toString(16);
	node = walker.nextNode();
}

虽然代码更多,当时性能更好,并且支持各种过滤等,功能也更加强大。

如果大家有学到新姿势,麻烦帮忙点个赞,谢谢。欢迎大家留言讨论。

参考资料

JavaScript中的$$(*)代表什么和$选择器的由来:ourjs.com/detail/54ab…

querySelectorAll vs NodeIterator vs TreeWalker:stackoverflow.com/questions/6…