从谷歌一行代码学到的姿势
网上很流行的一行代码,据说是谷歌工程师写的,它的作用是给页面所有元素增加一个随机颜色的外边框。
[].forEach.call($$("*"),function(a){a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)})
运行效果如下图:
这个代码虽然只有一行,但是包含的知识点不少,网上有很多解析。我也说下自己的理解,然后最后推荐在实务中使用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>
运行效果:
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…