从谷歌一行代码能学到哪些姿势

晴空闲云
• 阅读 319

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

[].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中的$$(*)代表什么和$选择器的由来:http://ourjs.com/detail/54ab768a5695544119000007

querySelectorAll vs NodeIterator vs TreeWalker:https://stackoverflow.com/questions/64551229/queryselectorall-vs-nodeiterator-vs-treewalker-fastest-pure-js-flat-dom-iterat

点赞
收藏
评论区
推荐文章
秃头王路飞 秃头王路飞
1星期前
webpack5手撸vue2脚手架
webpack5手撸vue相信工作个12年的小伙伴们在面试的时候多多少少怕被问到关于webpack方面的知识,本菜鸟最近闲来无事,就尝试了手撸了下vue2的脚手架,第一次发帖实在是没有经验,望海涵。 language JavaScript "name": "vuecliversion2", "version": "1.0.0", "desc
Jacquelyn38 Jacquelyn38
1年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。 1、使用解构获取json数据let jsonData   id: 1, status: "OK", data: ['a', 'b'] ; let  id, status, data: number   jsonData; console.log(id, status, number )
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录 问题 用navicat导入数据时,报错: 原因这是因为当前的MySQL不支持datetime为0的情况。 解决修改sql\mode: sql\mode:SQL Mode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。 全局s
小森森 小森森
1星期前
校园表白墙微信小程序V1.0 SayLove -基于微信云开发-一键快速搭建,开箱即用
后续会继续更新,敬请期待2.0全新版本 欢迎添加左边的微信一起探讨!项目地址:](https://www.aliyun.com/activity/daily/bestoffer?userCodesskuuw5n) \2. Bug修复更新日历 2. 情侣脸功能大家不要使用了,现在阿里云的接口已经要收费了(土豪请随意), \ \ 和 注意
Karen110 Karen110
1年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:Thu Feb 02 2019 09:59:51 GMT+0800 (中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。 1\. 显示日期使用
Java Record 的一些思考 - 默认方法使用以及基于预编译生成相关字节码的底层实现
快速上手 Record 类我们先举一个简单例子,声明一个用户 Record。public record User(long id, String name, int age) 这样编写代码之后,Record 类默认包含的元素和方法实现包括:1. record 头指定的组成元素(int id, String name, int age),并且,这些元素都是 f
Stella981 Stella981
11个月前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有`time`,和`datetime`两个,本文先说`time`模块。 ### 关于时间戳的几个概念 * 时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。 * 时间元组(`struct_time`),包含9个元素。  `time.struct_time(tm_y
Wesley13 Wesley13
11个月前
MySQL查询按照指定规则排序
1.按照指定(单个)字段排序 select * from table_name order id desc; 2.按照指定(多个)字段排序 select * from table_name order id desc,status desc; 3.按照指定字段和规则排序 selec
Wesley13 Wesley13
11个月前
PHP中的NOW()函数
是否有一个PHP函数以与MySQL函数`NOW()`相同的格式返回日期和时间? 我知道如何使用`date()`做到这一点,但是我问是否有一个仅用于此的函数。 例如,返回: 2009-12-01 00:00:00 * * * ### #1楼 使用此功能: function getDatetimeNow() {
helloworld_34035044 helloworld_34035044
3个月前
皕杰报表(关于日期时间时分秒显示不出来)
在使用皕杰报表设计器时,数据据里面是日期型,但当你web预览时候,发现有日期时间类型的数据时分秒显示不出来,只有年月日能显示出来,时分秒显示为0:00:00。1.可以使用tochar解决,数据集用select tochar(flowdate,"yyyyMMdd HH:mm:ss") fromtablename2.也可以把数据库日期类型date改成timestamp
helloworld_34035044 helloworld_34035044
2个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。 uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid() 或 uuid(sep)参数说明:sep 布尔值,生成的uuid中是否包含分隔符'',缺省为
晴空闲云
晴空闲云
Lv1
教师
专注前端,把复杂的编程讲简单,又不失深度。@公众号:晴空闲云
26
文章
3
粉丝
2
获赞