绕过断点

调试 JS 代码时,单步执行(F11)可跟踪所有操作。例如这段代码,每次调用 alert 时都会被断住:

debugger
alert(11)
alert(22)
alert(33)
alert(44)

有没有什么办法能让单步执行失效,一次执行多个操作?

事实上有一些巧妙的办法。例如通过数组回调执行这些 alert 函数:

debugger
[11, 22, 33, 44].forEach(alert)

这样只有 forEach 之前和之后会被断住,中间所有 alert 调用都不会被断住。

由此可见,通过 内置回调 执行 原生函数,调试器是无法断住的!

利用这个特性,我们可将一些重要的操作隐藏起来,从而能在调试者眼皮下悄悄执行。

应用案例

主流浏览器的调试器允许拦截特定事件,例如触发 mousemove 时断点;

addEventListener('mousemove', e => {
console.log(e)
})

因此调试者很容易找到事件回调函数,从而分析相应的处理逻辑。

如何防止事件回调被断点?这就需要前面讲解的黑科技了。我们对上述代码稍微修改,将自己的回调函数改成原生函数:

addEventListener('mousemove', console.log)

这时,每次触发 mousemove 事件都不会被断住!

然而现实中的回调逻辑远比 console.log 复杂,又该如何应用?

事实上我们可以做一些调整,将事件的回调逻辑变得足够简单,简单到只需一个操作 —— 保存结果:

const Q = []
addEventListener('mousemove', Q.push.bind(Q))

现在触发 mousemove 事件不仅不会被断住,而且还能将结果追加到数组 Q 中。

至于读取则有很多办法,例如渲染事件、空闲事件、定期轮询等。

setInterval(() => {
for (const v of Q) {
console.log(v)
}
Q.length = 0
}, 20)

如果 JS 只是采集信息而没有交互,可用更低的读取频率。

属性访问

前面的案例都是函数调用,例如 alert 函数、数组 push 函数。但属性读写又该如何实现?例如:

window.onclick = function() {
document.title = 'hello'
}

其实也不难。属性读写本质上是 getter 和 setter 函数的调用。例如:

const setter = Object.getOwnPropertyDescriptor(Document.prototype, 'title').set
setter.call(document, 'hello')

当然这样会立即执行,而不是在 onclick 事件时执行。

因此我们可以给 setter 柯里化,创建一个已绑定参数的新函数,作为事件回调:

const setter = Object.getOwnPropertyDescriptor(Document.prototype, 'title').set
onclick = setter.bind(document, 'hello')

这样只有在点击时才会执行。并且调试器的 click 事件断点不会触发。

对象属性

除了原型上的属性,普通对象的属性又该如何访问?例如:

const obj = {}
window.onclick = function() {
obj.name = 'jack'
}

事实上 JS 基本操作都可通过 Reflect API 实现。例如:

const obj = {}
Reflect.set(obj, 'name', 'jack')

不过需注意的是,Reflect.set 的参数必须是 3 个,多一个也不行。例如:

const obj = {}
Reflect.set(obj, 'age', 20, {})
obj.age // undefined

这样将其柯里化成事件回调函数是有问题的,因为事件回调还会加上一个 event 参数。

不过 Reflect.apply 方法倒没有这个限制,往后再加几个参数也不影响执行:

Reflect.apply(alert, null, ['hello'], 100, 200, 300)

因此我们可通过 Reflect.apply 执行 Reflect.set,从而过滤多余的参数:

const obj = {}
Reflect.apply(Reflect.set, null, [obj, 'age', 20])
obj.age // 20

然后将其柯里化成事件回调函数:

const obj = {}
onclick = Reflect.apply.bind(null, Reflect.set, null, [obj, 'age', 20])

这样即可通过原生函数执行 obj.age = 20,并且 click 事件断点依然不会触发。

多个操作

前面讲解的都是单个操作,是否可以一次执行多个操作?例如:

console.log('hello')
console.log('world')
alert(123)

最容易想到的办法,就是将每个操作放入数组,然后通过 forEach 回调 Reflect.apply 执行每个操作:

[
Reflect.apply.bind(null, console.log, null, ['hello']),
Reflect.apply.bind(null, console.log, null, ['world']),
Reflect.apply.bind(null, alert, null, [123]),
].forEach(Reflect.apply)

幸运的是 forEach 的回调函数和 Reflect.apply 函数都是 3 个参数,并且第 3 个都是数组类型:

forEach_callback(element, index, array)

Reflect.apply(target, thisArgument, argumentsList)

这样通过 forEach 回调 Reflect.apply 是完全没问题的。于是可以一次执行多个操作,并且都无法断住!

除了上述提到的,其实还有更多玩法,大家可发挥想象~

(2021/11/01)

如何让 JS 代码不可断点的更多相关文章

  1. Firebug调试js代码

    Firebug功能异常强大,不仅可以调试DOM,CSS,还可以调试JS代码,下面介绍一下调试JS. 1.认识console对象 console对象是Firebug内置的对象,该对象可以在代码中写入,可 ...

  2. js数组特定位置元素置空,非null和undefined,实现echarts现状图效果;谷歌格式化压缩js代码

    一.想要实现eCharts线状图表的断点效果,如图(后来又查到数据格式为data:['-', 2, 3,'-' , 5, 6, 7]:也可以断点显示) 这种效果,在设置数据的时候应该是这样: data ...

  3. Javascript系列: Google Chrome调试js代码(zz)

    你 是怎么调试 JavaScript 程序的?最原始的方法是用 alert() 在页面上打印内容,稍微改进一点的方法是用 console.log() 在 JavaScript 控制台上输出内容.嗯~, ...

  4. webpages框架使用@razor语法向js代码传递Json字符串

    进入web开发时间太短,一个人尝试着做了几个初级项目,遇到了太多的困难.尽管不是学开发专业的,仅为爱好所以硬着头皮坚持了下来. 将遇到的问题记录下来,备查. 使用vs2015中asp.net razo ...

  5. 初探内联方式的 onload="doSomething()"为何要加"()"?而js代码的 onload="doSomething" 和 addEventListener 为何不加"()"?

    问题引入:在看<Jquery基础教程>第四版的时,P34页有这样一段话 引用函数与调用函数 这里在将函数指定为处理程序时,省略了后面的圆括号,只使用了函数名.如果带着圆括号,函数会被立即调 ...

  6. 如何用浏览器调试js代码

    按F12打开调试工具

  7. Google Chrome调试js代码

    你 是怎么调试 JavaScript 程序的?最原始的方法是用 alert() 在页面上打印内容,稍微改进一点的方法是用 console.log() 在 JavaScript 控制台上输出内容.嗯~, ...

  8. 如何查找元素对应事件的js代码,检测定位js事件

    比如一张图片当鼠标放到上面时,图片改变.想找到这个事件对应的js代码,假设另存为html之后,文件夹中有.js文件. 如果你会调试,可以用打开浏览器的调试功能,以chrome为例,按F12打开调试窗口 ...

  9. js分析 快速定位 js 代码, 还原被混淆压缩的 js 代码

    -1.目录 0.参考 1.页面表现 2. 慢镜头观察:低速网络请求 3. 从头到尾调试:Fiddler 拦截 index.html 并添加 debugger; 4. 快速定位 js 代码 5. 还原被 ...

随机推荐

  1. Java 对象实现 Serializable 的原因

    java.io.Serializable 是 Java 中的一种标记接口(marker interface).标记接口是一种特殊的接口,java.io.Serializable 接口没有任何方法,也没 ...

  2. CoaXPress 简介

    CoaXPress 背景 CoaXPress (简称CXP)是指一种采用同轴线缆进行互联的相机数据传输标准,主要用于替代之前的cameralink协议,常见于科学相机.工业相机.医学图像.航空防务等场 ...

  3. 微信小程序避坑指南——input框里的图标在部分安卓机里无法点击的问题

    问题场景: 下图中的显隐密码和验证码均为包裹在 input标签 中的 image标签, 但在开发测试中发现点击不了这俩个image标签,因为是被input标签的padding挡住了. 解决方法:将im ...

  4. Spring IOC源码研究笔记(2)——ApplicationContext系列

    1. Spring IOC源码研究笔记(2)--ApplicationContext系列 1.1. 继承关系 非web环境下,一般来说常用的就两类ApplicationContext: 配置形式为XM ...

  5. 《HALCON数字图像处理》第一、二章笔记

    目录 第一章 绪论 1.1 图像和图像处理 1.1.1 图像 1.1.2 数字图像 1.1.3 图像处理及其发展过程 1.2 数字图像处理的步骤和方法 1.3 数字图像处理系统的硬件组成 1.4 数字 ...

  6. 从零开始实现lmax-Disruptor队列(二)多消费者、消费者组间消费依赖原理解析

    MyDisruptor V2版本介绍 在v1版本的MyDisruptor实现单生产者.单消费者功能后.按照计划,v2版本的MyDisruptor需要支持多消费者和允许设置消费者组间的依赖关系. 由于该 ...

  7. Java常用类-包装类

    包装类 ​ Java中的基本类型功能简单,不具备对象的特性,为了使基本类型具备对象的特性,所以出现了包装类,就可以像操作对象一样操作基本类型数据;包装类不是为了取代基本数据类型,而是在数据类型需要使用 ...

  8. Linux常用操作:文件及文件夹

    一.创建 (1)mkdir 创建一个目录 (2)touch 创建一个空文件 注:-r 表示递归操作 二.删除 rm 删除文件 rm -r 删除目录 rm -r * 删除目录下的所有文件 注:-f 不用 ...

  9. 1.4 操作系统的其余功能 -《zobolの操作系统学习札记》

    1.4 操作系统的其余功能 操作系统除了虚拟化.并发.存储管理三个主要功能,还有许多子功能,我主要介绍几种常见的功能比如 目录 1.4 操作系统的其余功能 稳定性 高性能 隔离保护 易用性(可视化) ...

  10. nodeJS与MySQL实现分页数据以及倒序数据

    大家在做项目时肯定会遇到列表类的数据,如果在前台一下子展示,速度肯定很慢,那么我们可以分页展示,比如说100条数据,每10条一页,在需要的时候加载一页,这样速度肯定会变快了.那么这里我给大家介绍如何在 ...