整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JS|7种JavaScript代码调试的方法

S是解释型语言,是逐条语句解释执行的,如果错误发生在某个语句块,此语句块以前的语句一般都可以正常执行。这不同于C等编译型语言。

代码调试的重点在于找到错误发生点,然后才能有的放矢。

1 alert()

通常可以使用警告框来提示变量信息。

alert(document.body.innerHTML);

当警告框弹出时,用户将需要单击“确定”来继续。

2 html标签内使用document.write()

<div id="demo">
    <script>
        var arr=[1,2,3,4,5]
        document.write(arr[2] + ' ') //3
    </script>
</div>

需要注意的是是,以下写法会替换整个页面的内容:

<button type="button" onclick="document.write(5 + 6)">试一试</button>

即使是函数调用也是如此。

3 innerHTML()

document.getElementById("demo").innerHTML =""

<h1>JavaScript Array.filter()</h1>

<p>使用通过测试的所有数组元素创建一个新数组。</p>

<p id="demo"></p> //45,25

<script>
    var numbers = [45, 4, 9, 16, 25];
    var over18 = numbers.filter(myFunction);

    document.getElementById("demo").innerHTML = over18;

    function myFunction(value, index, array) {
        return value > 18;
}
</script>

4 console()

JS的运行环境是浏览器,由浏览器引擎解释执行JS代码,一般来说,浏览器也提供调试器,如chrome按F12即可调出高试器:

<!DOCTYPE html>
<html>
<body>
<h4>我的第一张网页</h4>
<p>使用F12在浏览器(Chrome、IE、Firefox)中激活调试,然后在调试器菜单中选择“控制台”。</p>
    <script>
        a = 5;
        b = 6;
        c = a + b;
        console.log(c);
    </script>
</body>
</html>

如果您的浏览器支持调试,那么您可以使用 console.log() 在调试窗口中显示 JavaScript 的值:

内置的调试器可打开或关闭,强制将错误报告给用户。

通过调试器,您也可以设置断点(代码执行被中断的位置),并在代码执行时检查变量。

5 try catch捕获错误

<p id="demo"></p>

<script>
try {
  adddlert("欢迎您,亲爱的用户!");
}
catch(err) {
  demo.innerHTML = err.message; //adddlert is not defined
}
</script>

JavaScript 实际上会创建带有两个属性的 Error 对象:name 和 message。

name 设置或返回错误名。

message 设置或返回错误消息(一条字符串)。

6 debugger

debugger停止执行 JavaScript,并调用调试函数(如果可用)。

7 注释法或增量法

可以注释掉一些可疑代码来确定错误发生点。

或者考虑逐步增加代码的方法,逐步验证,以避免错误。

8 一些常见的 JavaScript 错误。

8.1 意外使用赋值运算符

如果程序员在 if 语句中意外使用赋值运算符(=)而不是比较运算符(===),JavaScript 程序可能会产生一些无法预料的结果。

8.2 令人困惑的加法和级联

加法用于加数值。

级联(Concatenation)用于加字符串。

在 JavaScript 中,这两种运算均使用相同的 + 运算符。

正因如此,将数字作为数值相加,与将数字作为字符串相加,将产生不同的结果:

var x = 10 + 5;          // x 中的结果是 15
var x = 10 + "5";         // x 中的结果是 "105"

而加法以外的其它算法运算符可以将字符串进行自动类型转换。

10-"5" // 5

8.3 令人误解的浮点数

JavaScript 中的数字均保存为 64 位的浮点数(Floats),符合IEEE754的标准。

所有编程语言,包括 JavaScript,都存在处理浮点值的困难:

var x = 0.1;
var y = 0.2;
var z = x + y             // z=0.30000000000000004

8.4 错位的分号

因为一个错误的分号,此代码块无论 x 的值如何都会执行:

if (x == 19);
{
     // code block
}

在一行的结尾自动关闭语句是默认的 JavaScript 行为。

在 JavaScript 中,用分号来关闭(结束)语句是可选的。

8.5 对象使用命名索引

在 JavaScript 中,数组使用数字索引。

在 JavaScript 中,对象使用命名索引。

如果您使用命名索引,那么在访问数组时,JavaScript 会将数组重新定义为标准对象。

<p id="demo"></p>

<script>
var person = [];
person["firstName"] = "Bill";
person["lastName"] = "Gates";
person["age"] = 46;
var x = person.length;         // person.length 将返回 0
var y = person[0];              // person[0] 将返回 undefined
y=person["age"]; //ok
y=person.age;//ok
document.getElementById("demo").innerHTML = y
</script>

8.6 Undefined 不是 Null

Undefined 的类型是 Undefined,Null的类型是Object。

JavaScript 对象、变量、属性和方法可以是未定义的。

此外,空的 JavaScript 对象的值可以为 null。

在测试非 null 之前,必须先测试未定义:

if (typeof myObj !== "undefined" && myObj !== null)

8.7 JS没有块作用域(与C语言不同)

在 ES2015 之前,JavaScript 只有两种类型的作用域:全局作用域和函数作用域。

<!DOCTYPE html>
<html>
<body>

<h2>JavaScript</h2>

<p>JavaScript不会为每个代码块创建新的作用域。</p>

<p>此代码将显示 i(10)的值,即使在 for 循环块之外:</p>

<p id="demo"></p>

<script>
for (var i = 0; i < 10; i++) {
  // some code
}
document.getElementById("demo").innerHTML = i; //10
</script>

</body>
</html>

ES2015 引入了两个重要的 JavaScript 新关键词:let 和 const。

这两个关键字在 JavaScript 中提供了块作用域(Block Scope)变量(和常量)。

for (let i = 0; i < 10; i++) {

  // some code

}

document.getElementById("demo").innerHTML = i; //不能访问

-End-

质文章,及时送达

原文 | https://medium.com/@mattburgess/beyond-console-log-2400fdf4a9d8

译文 | https://segmentfault.com/a/1190000018756503

Web开发最常用的高度就是 console.log,虽然console.log占有一席之地,但很多人并没有意识到console本身除了基本log方法之外还有很多其他方法。适当使用这些方法可以使调试更容易,更快速,更直观。

console.log

console.log中有很多人们意想不到的功能。虽然大多数人使用console.log(object)来查看对象,但是你也可以使用console.log(object, otherObject, string),它会把它们都整齐地记录下来,偶尔也会很方便。

不仅如此,还有另一种格式化的: console.log(msg, values),这很像 C 或 PHP 中的sprintf

console.log('I like %s but I do not like %s.', 'Skittles', 'pus');

会像你预期的那样输出:

> I like Skittles but I do not like pus.

常见的占位符 %o(这是字母o,不是0),它接受对象,%s接受字符串,%d表示小数或整数。

另一个有趣的是 %c,这可能与你所想不太相同,它实际上是CSS值的占位符。使用%c占位符时,对应的后面的参数必须是CSS语句,用来对输出内容进行CSS渲染。常见的输出方式有两种:文字样式、图片输出

console.log('I am a %cbutton', 'color: white; background-color: orange; padding: 2px 5px; border-radius: 2px')

它并不优雅,也不是特别有用。当然,这并不是一个真正的按钮。

它有用吗? 恩恩恩。

console.dir

在大多数情况下,console.dir的函数非常类似于log,尽管它看起来略有不同。

下拉小箭头将显示与上面相同的对象详细信息,这也可以从console.log版本中看到。当你查看元素的结构时候,你会发现它们之间的差异更大,也更有趣。

let element = document.getElementById('2x-container');

使用 console.log查看:

打开了一些元素,这清楚地显示了 DOM,我们可以在其中导航。但是console.dir(element)给出了更加方便查看 DOM 结构的输出:

这是一种更客观地看待元素的方式。有时候,这可能是您真正想要的,更像是检查元素。

console.warn

Tips:关注微信公众号:Java后端,每日技术博文推送。

可能是最明显的直接替换 log,你可以以完全相同的方式使用console.warn。唯一真正的区别是输出字的颜色是黄色的。具体来说,输出处于警告级别而不是信息级别,因此浏览器将稍微区别对待它。这具有使其在杂乱输出中更明显的效果。

不过,还有一个更大的优势,因为输出是警告而不是信息,所以你可以过滤掉所有console.log并仅保留console.warn。这对于偶尔会在浏览器中输出大量无用废话的应用程序尤其有用。清除一些无用的信息可以让你更轻松地看到你想要的输出。

console.table

令人惊讶的是,这并不是更为人所知,但是 console.table函数旨在以一种比仅仅转出原始对象数组更整洁的方式显示表格数据。

例如,这里有一个数据列表。

const data = [{ id: "7cb1-e041b126-f3b8", seller: "WAL0412", buyer: "WAL3023", price: 203450, time: 1539688433},{ id: "1d4c-31f8f14b-1571", seller: "WAL0452", buyer: "WAL3023", price: 348299, time: 1539688433},{ id: "b12c-b3adf58f-809f", seller: "WAL0012", buyer: "WAL2025", price: 59240, time: 1539688433}];

如果我们使用 console.log来输出上面的内容,我们会得到一些非常无用的输出:

▶ (3) [{…}, {…}, {…}]

点击这个小箭头可以展开看到对象的内容,但是,它并不是我们想要的“一目了然”。

但是 console.table(data)的输出要有用得多。

第二个可选参数是所需列的列表。显然,所有列都是默认值,但我们也可以这样做:

> console.table(data, ["id", "price"]);

这里要注意的是这是乱序的 - 最右边的列标题上的箭头显示了原因。我点击该列进行排序。找到列的最大或最小,或者只是对数据进行不同的查看非常方便。顺便说一句,该功能与仅显示一些列无关,它总是可用的。

console.table只能处理最多1000行,因此它可能不适合所有数据集。

console.assert

assertlog是相同的函数,assert是对输入的表达式进行断言,只有表达式为false时,才输出相应的信息到控制台,示例如下:

var arr = [1, 2, 3];console.assert(arr.length === 4);

有时我们需要更复杂的条件句。例如,我们已经看到了用户 WAL0412的数据问题,并希望仅显示来自这些数据的事务,这是直观的解决方案。

console.assert(tx.buyer === 'WAL0412', tx);

这看起来不错,但行不通。记住,条件必须为false,断言才会执行,更改如下:

console.assert(tx.buyer !== 'WAL0412', tx);

与其中一些类似,console.assert并不总是特别有用。但在特定的情况下,它可能是一个优雅的解决方案。

console.count

另一个具有特殊用途的计数器,count只是作为一个计数器,或者作为一个命名计数器,可以统计代码被执行的次数。

for(let i = 0; i < 10000; i++) { if(i % 2) { console.count('odds'); } if(!(i % 5)) { console.count('multiplesOfFive'); } if(isPrime(i)) { console.count('prime'); }}

这不是有用的代码,而且有点抽象。这边也不打算演示 isPrime函数,假设它是成立的。

执行后我们会得到一个列表:

odds: 1odds: 2prime: 1odds: 3multiplesOfFive: 1prime: 2odds: 4prime: 3odds: 5multiplesOfFive: 2...

还有一个相关的 console.countReset,可以使用它重置计数器。

console.trace

trace在简单的数据中很难演示。当您试图在类或库中找出是哪个实际调用者导致了这个问题时,它的优势就显现出来了。

例如,可能有 12 个不同的组件调用一个服务,但是其中一个组件没有正确地设置依赖项。

export default class CupcakeService {

constructor(dataLib) { this.dataLib = dataLib; if(typeof dataLib !== 'object') { console.log(dataLib); console.trace; } } ...}

这里使用 console.log仅告诉我们传递数据dataLib是什么 ,而没有具体的传递的路径。不过,console.trace会非常清楚地告诉我们问题出在Dashboard.js,我们可以看到是new CupcakeService(false)导致错误。

console.time

console.time是一个用于跟踪操作时间的专用函数,它是跟踪 JavaScript执行时间的好方法。

function slowFunction(number) { var functionTimerStart = new Date.getTime; // something slow or complex with the numbers. // Factorials, or whatever. var functionTime = new Date.getTime - functionTimerStart; console.log(`Function time: ${ functionTime }`);}var start = new Date.getTime;

for (i = 0; i < 100000; ++i) { slowFunction(i);}

var time = new Date.getTime - start;console.log(`Execution time: ${ time }`);

这是一种老派的做法,我们使用 console.time来简化以上代码。

const slowFunction = number => { console.time('slowFunction'); // something slow or complex with the numbers. // Factorials, or whatever. console.timeEnd('slowFunction');}console.time;

for (i = 0; i < 100000; ++i) { slowFunction(i);}console.timeEnd;

我们现在不再需要做任何计算或设置临时变量。

console.group

// this is the global scopelet number = 1;console.group('OutsideLoop');console.log(number);console.group('Loop');for (let i = 0; i < 5; i++) { number = i + number; console.log(number);}console.groupEnd;console.log(number);console.groupEnd;console.log('All done now');

输出如下

并不是很有用,但是您可以看到其中一些是如何组合的。

class MyClass { constructor(dataAccess) { console.group('Constructor'); console.log('Constructor executed'); console.assert(typeof dataAccess === 'object', 'Potentially incorrect dataAccess object'); this.initializeEvents; console.groupEnd; } initializeEvents { console.group('events'); console.log('Initialising events'); console.groupEnd; }}let myClass = new MyClass(false);

这是很多工作和很多调试信息的代码,可能不是那么有用。但它仍然是一个有趣的想法,这样写使你的日志记录更加清晰。

选择DOM元素

如果熟悉jQuery,就会知道 $(‘.class’)$(‘#id’)选择器有多么重要。它们根据与之关联的类或 ID 选择 DOM 元素。

但是当你没有引用 jQuery时,你仍然可以在谷歌开发控制台中进行同样的操作。

(‘.class’) (‘.class #id’) 等效于document.querySelector(‘ ‘),这将返回 DOM 中与选择器匹配的第一个元素。

可以使用 $$(tagName)$$(.class), 注意双元符号,根据特定的选择器选择DOM的所有元素。这也将它们放入数组中,你也可以通过指定数组中该元素的位置来从中选择特定的元素。

例如,&dollar;&dollar;(‘.className’) 获取具有类 className的所有元素,而$$(‘.className’)[0]$$(‘.className’)[1]获取到分别是第一个和第二个元素。

将浏览器转换为编辑器

你有多少次想知道你是否可以在浏览器中编辑一些文本?答案是肯定的,你可以将浏览器转换为文本编辑器。你可以在 DOM 中的任何位置添加文本和从中删除文本。

你不再需要检查元素并编辑HTML。相反,进入开发人员控制台并输入以下内容:

document.body.contentEditable=true

这将使内容可编辑。现在,你几乎可以编辑DOM中的任何内容。

查找与DOM中的元素关联的事件

调试时,需要查找 DOM 中某个元素的事件侦听器感时,谷歌控制台了 getEventListeners使找到这些事件更加容易且直观。

getEventListeners($(‘selector’))返回一个对象数组,其中包含绑定到该元素的所有事件。你可以展开对象来查看事件:

要找到特定事件的侦听器,可以这样做:

getEventListeners($(‘selector’)).eventName[0].listener

这将显示与特定事件关联的侦听器。这里 eventName[0]是一个数组,它列出了特定事件的所有事件。例如:

getEventListeners($(‘firstName’)).click[0].listener

将显示与 ID 为 ‘firstName’的元素的单击事件关联的侦听器。

监控事件

如果希望在执行绑定到 DOM 中特定元素的事件时监视它们,也可以在控制台中这样做。你可以使用不同的命令来监控其中的一些或所有事件:

如果希望在执行绑定到DOM中特定元素的事件时监视它们,也可以在控制台中这样做。你可以使用不同的命令来监控其中的一些或所有事件:

  • monitorEvents($(‘selector’))将监视与选择器的元素关联的所有事件,然后在它们被触发时将它们打印到控制台。例如,monitore($(#firstName))将打印 ID 为firstName元素的所有事件。

  • monitorEvents($(‘selector’),’eventName’)将打印与元素绑定的特定事件。你可以将事件名称作为参数传递给函数。这将仅记录绑定到特定元素的特定事件。例如,monitorEvents($(‘#firstName’),’click’) 将打印绑定到ID为'firstName'的元素的所有click事件。

  • monitore($(selector),[eventName1, eventName3', .])将根据您自己的需求记录多个事件。与其传递单个事件名作为参数,不如传递包含所有事件的字符串数组。例如monitore($(#firstName),[click, focus])将记录与ID firstName元素绑定的click事件和focus事件。

  • unmonitorevent ($(selector)):这将停止监视和打印控制台中的事件。

检查 DOM 中的一个元素

你可以直接从控制台检查一个元素:

  • inspect($(‘selector’)) 将检查与选择器匹配的元素,并转到 Chrome Developer Tools中的 Elements 选项卡。例如, inspect($(‘#firstName’))将检查 ID为'firstName' 的元素,spect($(‘a’)[3])将检查 DOM 中的第 4 个a元素。

  • 1, 等可以帮助你获取最近检查过的元素。例如,>$0<表示最后检查的 DOM 元素,而倒数第二个检查的 DOM 元素。

检索最后一个结果的值

你可以将控制台用作计算器。当你这样做的时候,你可能需要用第二个来跟踪一个计算。以下是如何从内存中检索先前计算的结果:

$_

过程如下:

2+3+49 //- The Answer of the SUM is 9$_9 // Gives the last Result$_ * $_81 // As the last Result was 9Math.sqrt($_)9 // As the last Result was 81$_9 // As the Last Result is 9

清除控制台和内存

如果你想清除控制台及其内存,输入如下:

clear

本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!

逆向目标

  • 目标:某商盟登录
  • 逆向参数:Query String Parameters:j_mcmm: 351faaef3ba8f4db2001ec621344dbbf

DOM 简介

在以前的案列中,我们都是通过直接搜索来定位加密参数的位置的,直接搜索出来的定位通常是比较准确的,但是有个弊端就是搜索的结果可能会非常多,需要人工去过滤,需要一定的经验去判断准确的加密位置,而且对于一些反爬力度较大的站点来说,可能做了很多混淆,根本就搜索不到,那么今天的案列中,我们将介绍另一种方法,即 DOM 事件断点,需要注意的是,DOM 事件断点也是有弊端的,通过这种方法找到的位置通常在加密处理之前,也就是说想要找到准确的加密位置,还需要进一步分析上下文才能确定。

DOM 全称 Document Object Model,即文档对象模型,是 HTML 和 XML 文档的编程接口,定义了访问和操作 HTML 文档的标准方法。

一个网页其实就是一个 HTML 文件,经过浏览器的解析,最终呈现在用户面前,一个简单的 HTML 页面代码如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>我的第一个HTML页面</title>
</head>

<body>
    <h1>我的第一个标题</h1>
    <p>我的第一个段落</p>
</body>

</html>

在 HTML 页面代码中,head、body 等标签不是随意排列的,它们有自己的规则。首先,它们是嵌套的,一层套一层,比如 html 套 body,body 又套 h1,其次, 每一层可以同时存在很多标签,比如 head 和 body 属于同一层,它们被外面的 html 套着,这样的结构很像计算机里的文件夹,例如,我的电脑是最外层,里面套着 C、D、E、F 盘,每个盘里又有很多文件夹,文件夹里又有文件夹,逐个打开后才能看到具体的文件。

为什么要按照这种结构来组织呢?目的其实是方便解析和查询,解析的时候,从外向里循序渐进,好比按照图纸盖房子,先盖围墙,再盖走廊,最后才盖卧室。查询的时候,会遵循一条明确的路线,一层一层地缩小范围,查找效率会非常高。

所以,浏览器在解析 HTML 文档时,会把每个标签抽象成代码里的对象,按照这种层次分明的结构组织,这就是 DOM,HTML DOM 结构如下图所示:

逆向过程

本次逆向的目标是某商盟的登录密码,本案例的加密参数为 j_mcmm,加密比较简单,直接全局搜索也很容易找到加密的地方,但是本次我们不使用全局搜索,改用 DOM 事件断点来定位加密位置。

打开开发者工具,点击左上角箭头按钮,再点击登陆按钮,即可定位到该按钮元素的位置,在 Elements 面板,右边选择 Event Listeners,即事件监听列表,可以看到一些鼠标点击、鼠标移动、提交、加载等事件:

我们将这些事件展开具体看一下,submit 提交事件,定位到 div 标签,div 标签下有一个 form 表单,form 的作用就是为用户输入创建 HTML 表单,向服务器传输数据,跟进这个 submit 用到的 JS 文件,大概率就能够找到加密的地方,这里还有个小技巧,如果事件太多,不太好判断哪个是提交数据的,或者哪个是登录事件的,可以选择性的点击 Remove,移除一些事件,再登录,如果登录不能点击,或者 Network 里没有提交请求,就说明 Remove 的这个事件刚好就是目标事件。

跟进 submit 事件用到的 JS,会定位到 function e() 的位置,往下看,就可以找到疑似加密的地方,这里出现了两个 j_mcmm,分别是 g.j_mcmmP.j_mcmm,埋下断点进行调试,经过对比可以发现 g.j_mcmm 是最终需要的值:

g.j_mcmm = b 语句中,b 的值就是最终加密后的值,往上找,第 1125 和 1126 行 var e = b; b = F(F(b) + c);,把明文密码赋值给 b,c 为验证码,经过 F 这个函数的处理后得到加密值,继续跟进 F 函数:

可以看到其实就是经过以下函数的处理:

function d(a) {
    return n(e(o(m(a + "{1#2$3%4(5)6@7!poeeww$3%4(5)djjkkldss}")), 32))
}

这个函数中,又包含 n, e, o, m 函数,这里不再每个函数去剥离,直接将这个函数往下所有单个字母的函数 copy 下来本地调试即可。

完整代码

GitHub 关注 K 哥爬虫,持续分享爬虫相关代码!欢迎 star !https://github.com/kgepachong/

以下只演示部分关键代码,不能直接运行!完整代码仓库地址:https://github.com/kgepachong/crawler/

JavaScript 加密关键代码架构

function getEncryptedPassword(a, b, c) {
    // a: 用户名, b: 密码, c: 验证码
    function d(a) {
        return n(e(o(m(a + "{1#2$3%4(5)6@7!poeeww$3%4(5)djjkkldss}")), 32))
    }

    function e(a, b) {}

    function f(a, b, c, d, e, f) {}

    function g(a, b, c, d, e, g, h) {}

    function h(a, b, c, d, e, g, h) {}

    function i(a, b, c, d, e, g, h) {}

    function j(a, b, c, d, e, g, h) {}

    function k(a, b)  {}

    function l(a, b)  {}

    function m(a)  {}

    function n(a)  {}

    function o(a)  {}

    c.hex_md5 = d
    b = d(d(b) + c);
    return b
}

// 测试样例
// console.log(getEncryptedPassword('123123', '1231234', '6798'))

Python 登录关键代码