1.JS基础与浏览器开发工具
第一章 JS逆向Q&A
第一节 为什么要JS逆向?
> 回顾爬虫的流程:
①确定URL(包括确定 a.表单数据、b.cookie数据,c.请求头中的鉴权数据, ): **如果关键反爬数据加密==>需要JS逆向找解密**
②发送请求: request, scrapy, feapder, selenium....
③解析数据: re, xpath, bs4 **如果加密==>需要JS逆向找解密**
④存储数据: redis, mongodb, mysql
> 回顾网站数据交换的过程(假设高加密的网址):
①页面收集数据: pageindex, total, searchkey,...,时间戳,特定**鉴权信息**[加密A]
°页面发送数据给==>后端°
②后端处理数据: pageindex,...,对鉴权信息`解密A`并验证===>**返回数据**[加密B]
°后端返回数据给==>前端°
③页面拿到数据: 对后端给的数据`解密B`(抓包看到的),并渲染到页面上
> 一个网站的反爬手段
1. JS混淆(鉴权信息的加密,返回数据的加密)
2. 验证码,滑块,"选择以下有🚥的图片并点击😂"...
3. IP封禁(我们已经解决)
> 总结:为什么要JS逆向?
就是为了应对第一部分的反爬手段
> 写给自己:
爬虫高级=JS逆向+验证码专题; 正好解决了一个网址反爬的剩余两个手段, 当然随着更新反爬(爬虫对抗)的升级, 这些东西得与时俱进的学;
爬虫高级学完,基本上网页爬虫就结束了,后续是安卓和IOS逆向; 正好大三下学安卓开发,到时候学会了,正好来爬安卓🤗
我们表面是软件工程(写网站的),背地是爬网站的-->真的就是把网站玩透了,就想被古诗文,正背、倒背都如流🤣
第二节 JS逆向的要学什么❗
> 总结一句话"JS逆向"解决什么问题?
发包前对**鉴权信息**模拟[加密]; 发包后对**返回数据**进行`解密`
> JS逆向要学什么?
①想要分析JS代码==>a.JS语法 b.熟练运用浏览器开发工具
②想要加解密 ==>a.如何定位到加密的位置 b.常见的加解密算法
③想要本地跑爬虫==> a.补环境(网页浏览器的环境)
第二章 JS语法基础
第一节 四大变量类型
// ①str: 除了空串都是true
console.log(Boolean("")) // false
console.log(Boolean("xixi")) // true
// ②Number: 除了"NaN和0/-0"都是true
console.log(Boolean(0)) // false
console.log(Boolean(NaN)) // false
console.log(Boolean(-0)) // false
console.log(Boolean(1)) // true
console.log(Boolean(3.14)) // true
// ③对象: 除了null都是true
console.log(Boolean(null)) // false
console.log(Boolean({})) // true(空对象)
console.log(Boolean([])) // true(空数组)
console.log(Boolean(new Date())) // true(日期对象)
console.log(Boolean(/regex/)) // true(正则表达式对象)
console.log(Boolean(() => {})) // true(空函数)
// ④Undefined
console.log(Boolean(undefined)) // false
第二节 生命周期
- $ 语法: 全局和局部:在方法内部定义的就是局部变量, 除此之外都是全局变量
- 区分方法和块: 虽然都是
{}里面, 但块中的变量也是全局 - 区分var和全局: 不是var定义的都是全局, var在方法内部定义也是局部变量
- 区分方法和块: 虽然都是
- JavaScript变量生命周期在它声明时初始化
- 局部变量在函数执行完毕后销毁。
- 全局变量在页面关闭后销毁。
第三节 函数
2.3.1 定义函数的三种方式
- ①有名函数: 在声明前后都可以调用
// 1. 有名函数
func1(1, false, "3")
function func1(a, b, c) {
console.log("==>func1", a, b, c)
console.log(typeof a, typeof b, typeof c)
}
func1(1, false, "3")
- ②函数赋值表达式定义函数: 只能在声明后调用
// 2.函数赋值表达式 定义函数
// func2(1, false, "3") // 报错: ReferenceError: func2 is not defined
func2 = function (a, b, c) {
console.log("==>func2", a, b, c)
console.log(typeof a, typeof b, typeof c)
}
func2(1, false, "3")
- ③自执行函数: 运行到就执行
- ! 注意: 前面一定要有
~或!, 因为要与前面代码分开, 否则报错
- ! 注意: 前面一定要有
// 3.自执行函数
!(function (a, b) {
console.log("Hello World")
console.log(arguments)
})(1, "xixi") // 自执行函数也可以传参, 注意传参的位置
2.3.2 外内部函数外部调用
- $ 语法: 用于自执行方法, Hook时用的多
var _xl;
!(function () {
function xl(a, b){
console.log('hello', a, b)
}
_xl = xl;
})();
_xl("wo", "ha") //输出: hello wo ha
2.3.3 其他特性
- 如果return多个值, 只有最后一个会返回
function func() {
return 1, 2, 3;
}
console.log(func()) // 为3,只返回最后一个
第四节 函数传参
- $ 语法一: 实参: ①相传几个传几个 可以> 形参; 也可以<形参; ②想传什么类型传什么
- $ 语法二: 每个方法都默认有一个”形参”:
arguments
function func1(a, b, c) {
console.log("a, b, c: ", a, b, c)
console.log("arguments: ", arguments)
}
func1(1, "xixi", false)
func1(1, "xixi", false, [1, 2, "fd", true], {"name": "双双"})
第五节 对象
- $ 语法: JS对象中必有的属性【图一】
- $ 语法:
hasOwnProperty: 【图二】查看是否有某个属性(‘成员变量’) - $ 语法:
isPrototypeOf: 【图三】查看是否是另一个对象的原型


- $ 语法:
第六节 常用数据结构
这几个typeof 都是obj对象的数据类型
2.6.1 List
// 初始化
var arr = [1, 2]; // 字面量
const arrNew = new Array(1, 2); // 构造函数
// 操作示例
arr.push(3); // 增:[1,2,3]
arr.pop(); // 删(末尾):[1,2]
arr.splice(1, 1, 4); // 改(替换):[1,4]
const item = arr[0]; // 查:1
arr.length = 0; // 清空
// 遍历方式
arr = ['a', 'b', 'c'];
// 普通for循环[快]
for (let i = 0; i < arr.length; i++) {
console.log(`索引 ${i} 值 ${arr[i]}`);
}
// forEach方法[中]
arr.forEach((item, index) => {
console.log(`索引 ${index} 值 ${item}`); // 这里是反的
});
// 增强for循环(for...of)[最慢]
for (const item of arr) {
console.log(item);
}
2.6.2 Set
// 初始化
var set = new Set([1, 2]);
// 操作示例
set.add(3); // 增:{1,2,3}
set.delete(2); // 删:{1,3}
const has = set.has(1); // 查:true
set.clear(); // 清空
set = new Set(['a', 'b', 'c']);
// 普通for循环
const arrFromSet = Array.from(set);
for (let i = 0; i < arrFromSet.length; i++) {
console.log(arrFromSet[i]);
}
// forEach方法
set.forEach(value => {
console.log(value);
});
// 增强for循环(for...of)
for (const value of set) {
console.log(value);
}
2.6.3 Map
// 初始化
var map = new Map([['a', 1]]);
// 操作示例
map.set('b', 2); // 增
map.delete('a'); // 删
map.set('b', 3); // 改
const mapVal = map.get('b'); // 查:3
map.clear(); // 清空
map = new Map([['name', 'Bob'], ['age', 30]]);
// 普通for循环
const entries = Array.from(map.entries());
for (let i = 0; i < entries.length; i++) {
const [key, value] = entries[i];
console.log(`key: ${key} value: ${value}`);
}
// forEach方法
map.forEach((value, key) => {
console.log(`key: ${key} value: ${value}`);
});
// 增强for循环(for...of)
for (const [key, value] of map) {
console.log(`key: ${key} value: ${value}`);
}
2.6.4 Dict
// 初始化
var obj = {a: 1}; // 字面量
const objNew = new Object(); // 构造函数
// 操作示例
obj.b = 2; // 增:{a:1, b:2}
delete obj.a; // 删:{b:2}
obj.b = 3; // 改:{b:3}
const val = obj.b; // 查:3
Object.keys(obj).forEach(k => delete obj[k]); // 清空
obj = {name: 'Alice', age: 25};
// 普通for循环(遍历键)
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
console.log(`键 ${key} 值 ${obj[key]}`);
}
// 遍历键值对
Object.entries(obj).forEach(([key, value]) => {
console.log(`键 ${key} 值 ${value}`);
});
// 增强for循环(for...in)
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(`键 ${key} 值 ${obj[key]}`);
}
}
| 数据结构 | 初始化 | 增 | 删 | 改 | 查 | 清空 | 有序 | 键类型 |
|---|---|---|---|---|---|---|---|---|
| List | [] / new Array() | push/unshift | pop/shift/splice | splice/直接赋值 | find/indexOf | length=0 | ✔️ | 数字索引 |
| Set | new Set() | set.add(value) | set.delete(value) | 无直接修改方法 | set.has(value) | set.clear() | ✔️ | 值即键 |
| Map | new Map() | map.set(key, val) | map.delete(key) | map.set(key, val) | map.get(key) | map.clear() | ✔️ | 任意类型 |
| Dict | {} / new Object() | obj.key=value | delete obj.key | obj.key=newValue | obj.key / in | 遍历删除 | ❌ | 字符串/Symbol |
第8节 JSON转化
- $ 语法: JSON转对象
var obj = JSON.parse(or_str)❗从服务器接受JSON.parse - $ 语法: 对象转JSON
var json_str = JSON.stringify(obj)❗发给服务器时JSON.stringify
第9节 拦截对象的get和set方法
- $ 语法:
Object.defineProperty(obj, prop, descriptor))link Object.defineProperty为对象的属性赋值,替换对象属性基本语法- obj:需要定义属性的当前对象;
- prop:当前需要定义的属性名;
- descriptor: 需要拦截的方法;
// 有一个user类, 其中只有age一个变量, 初始值为'123'
user = {
age: '123'
}
aa = user.age // 为了不修改本身的逻辑, 先用一个变量存一下
// Object.defineProperty(obj, prop, descriptor)
Object.defineProperty(user, "age", {
get() {
console.log("拦截你获取的值")
return aa
},
set(newVal) {
console.log("拦截你设置的值",newVal);
aa = newVal
}
})
console.log(user.age)
console.log("------------------------")
user.age = '23342'
第10节 JS的异步Promise
同时学习下 “箭头函数” 的用法
2.10.1 基本写法
- & 说明: 异步的执行流程
- ①当执行
orderFood()时,立即同步执行Promise构造函数中的executor函数(即function(resolve, reject){…}) - ②计时器等待1s后, 若随机数<0.5,调用resolve(“🍔…”),Promise状态变为fulfilled; 否则调用reject(“❌…”),Promise状态变为rejected
- ③若调用resolve:执行.then()注册的回调函数,参数接收”🍔…”; 若调用reject:执行.catch()注册的回调函数,参数接收”❌…”
- ①当执行
// 1. 创建Promise对象(点外卖过程)
function orderFood() {
return new Promise(function (resolve, reject) { // 改用function写法
setTimeout(function () { // 模拟异步操作(等待1s)
if (Math.random() < 0.5) {
resolve("🍔 汉堡+薯条送达!");
} else {
reject("❌ 骑手找不到地址");
}
}, 1000);
});
}
// 2. 处理结果(接收外卖)
orderFood().then(success)
.catch(failed);
// 成功的回调
function success(res) {
console.log("收到:", res);
}
// 失败的回调
function failed(reason) {
console.log("失败:", reason);
}
2.10.2 实际写法
- & 说明: 和上面写法的区别
- ①这里用的参数接收, 上面是写了个方法返回 =>这里的orderFood每次调用结果都和首次调用一致, 并且只有首次调用会等待1s, 后续都是直接拿之前的值; 2.10.1不一样, 每次返回的都不一样, 都wait 1s等值
- ②这里用了箭头表达式
// 创建 Promise(点外卖过程)
const orderFood = new Promise((resolve, reject) => {
// 模拟异步操作(30%概率外卖失败)
setTimeout(() => {
Math.random() < 0.5 ?
resolve("🍔 汉堡+薯条送达!") :
reject("❌ 骑手找不到地址")
}, 1000); // 模拟2秒配送时间
});
// 首次调用(需要等待1s)
orderFood
.then(food => console.log("收到:", food)) // 成功回调
.catch(error => console.log("失败:", error)); // 失败回调
// 后续调用(无需等待,直接获取"首次调用"的值
orderFood
.then(food => console.log("收到:", food)) // 成功回调
.catch(error => console.log("失败:", error)); // 失败回调
第11节 void 0和undefined
void 0 是 undefined 的安全写法(避免 undefined 被重写)
第12节 判断是 自定义方法/JS自带方法

第13节 py和js转JSON时空格问题
- $ 语法: Post请求发
json=会自动json.dumps()[[4.Requests模块#第三节 发送POST请求post()使用 data传参]] link

第14节 JS的对象 function XXX() {} 和class XXX{}
- $ 语法:
function XXX() {}和class XXX{}都是JS的类, ①前者是 “构造方法” 后者是 标注的类创建②后者是ES6才有的语法
| 特性 | 构造函数写法 (function) | 类写法 (class) |
|---|---|---|
| 1. 静态方法 | ClassName.staticMethod = function() {} |
static staticMethod() {} |
| 2. 静态变量 | ClassName.staticVar = value |
类外部定义 或 static 块初始化 |
| 3. 成员方法 | ClassName.prototype.method = function() {} |
直接在类体中定义 method() {} |
| 4. 成员变量 | 在构造函数内 this.var = value |
在构造函数内 this.var = value |
| 5. 构造器 | 函数体本身 function ClassName() {} |
constructor() {} 方法 |
| 6. 创建实例 | new ClassName() (可不加 new,不安全) |
new ClassName() (必须加 new) |
| 7. 构造器创建 | 直接调用函数 function ClassName() {} |
通过 constructor() {} 方法 |
| 8. 调用静态 | ClassName.staticMethod() |
ClassName.staticMethod() |
| 9. 调用成员 | instance.method() |
instance.method() |
- $ 语法: 构造方法的写法
// ========== 构造函数写法 ==========
function OldClass() {
// ①成员方法or变量【内部定义】
this.memberVar = 10; // 成员变量
this.fun1 = function my_function() { // 成员方法
console.log("成员方法的 fun1",)
}
// ②构造方法(本身这个方法就是 "构造函数")
console.log('构造函数被调用');
// ③静态方法or变量【内部定义】
OldClass.staticVar2 = 200;
}
// ①成员方法or变量【外部定义】
OldClass.prototype.memberMethod = function () {
console.log(`成员方法访问成员变量: ${this.memberVar}`);
};
OldClass.prototype.memberVar2 = 100
// ③静态方法【外部定义】
OldClass.staticVar = 20; // 静态变量
OldClass.staticMethod = function () {// 静态方法
console.log(`静态方法访问静态变量: ${this.staticVar}`);
};
// ========== 实例化and调用 ==========
const oldInst = new OldClass(); // 实例化
oldInst.fun1() // 调用 成员方法
OldClass.staticMethod(); // 调用 静态方法
console.log(OldClass.staticVar2); // 调用 静态属性
console.log(oldInst.memberVar2); // 调用 成员属性
console.log(oldInst.staticVar2); // 静态内容,只能通过类名调用,不能通过实例调用[区别Java]
- $ 语法: ES6类的写法
// ========== 类写法 ==========
class NewClass {
// ①成员方法or变量
memberVar // 成员变量
memberMethod() {// 成员方法
console.log(`成员方法访问成员变量: ${this.memberVar}`);
}
// ②构造方法 【可以带参数】【JS只能有一个构造器】
constructor(value) { // 添加参数
this.memberVar = value; // 使用传入的参数初始化成员变量
console.log(`类构造器被调用,接收参数: ${value}`);
}
// ③静态方法or变量
static staticVar = 20; // 静态变量
static staticMethod() { // 静态方法
console.log(`静态方法访问静态变量: ${this.staticVar}`);
}
}
// ========== 实例化and调用 ==========
const newInst = new NewClass("成员变量的值"); // 实例化
newInst.memberMethod(); // 调用 成员方法
NewClass.staticMethod(); // 调用 静态方法
console.log(NewClass.staticVar); // 调用 静态属性
console.log(newInst.memberVar); // 调用 成员属性
// newInst.staticMethod() // 同样不支持,实例对象调用静态内容
第三章 浏览器开发工具
第一节 解决无限debugger
第二节 堆栈的使用
- 和 “数据结构” 中的栈一样, 越在底部的, 越先调用
- 点击对应的栈后会跳到栈所在的js作用域 (控制台, 监视…都只能调用/展示该栈中有的方法)

第三节 事件监听器断点

第四节 完整复制obj对象
- $ 语法: 使用
copy(对象)的方式, 直接复制到剪切板
// 在控制台输入
copy(h) // 假设h是你要复制的对象
// 执行后会直接复制到剪贴板,格式如下:
{
"announcementState": "",
"announcementTitle": "",
"announcementType": "1",
"page": 2,
"pageSize": 10,
"unitId": 1
}
第五节 刷新和发包的区别
所以, 当前确定某个 “全局变量” 时, 可能需要刷新, 而不是发包
- 发包只是: 修改部分值
- 刷新: 是全部接口
第四章 ES6的导出和引入模块
好问题!让我系统地为你讲解 ES6 模块的导入导出语法:
第一节 📦 导出(Export)的两种方式
4.1.1 先说结论
// 看导出决定导入方式:
// 导出有 default → 不用 {}
export default xxx;
import xxx from '...';
// 导出有 const/function/class → 用 {}
export const xxx = ...;
import { xxx } from '...';
// 自动找 index:
./folder → ./folder/index.ts
4.1.2 默认导出(Default Export)
一个文件只能有一个默认导出:
// router/index.ts
export default router; // 默认导出
// 或者
const router = createRouter({...});
export default router;
导入时:
- ✅ 不需要花括号
{} - ✅ 可以随意命名
- ✅ 自动查找
index.ts/index.js
import router from './router'; // ✅ 自动找到 router/index.ts
import router from './router/index'; // ✅ 完整写法,效果一样
import myRouter from './router'; // ✅ 可以改名
4.2.3 命名导出(Named Export)
一个文件可以有多个命名导出:
// guards.ts
export const beforeEach = async (to, from) => {...}; // 命名导出1
export const afterEach = (to, from) => {...}; // 命名导出2
// 或者集中导出
const beforeEach = async (to, from) => {...};
const afterEach = (to, from) => {...};
export { beforeEach, afterEach };
导入时:
- ❌ 必须用花括号
{} - ❌ 名字必须匹配(除非用 as 重命名)
- ❌ 不会自动找 index
import { beforeEach, afterEach } from './guards'; // ✅ 必须解构
import { beforeEach as before } from './guards'; // ✅ 可以重命名
import * as guards from './guards'; // ✅ 导入全部
import beforeEach from './guards'; // ❌ 错误!guards.ts 没有默认导出
第二节 🔍 回答你的具体问题
4.2.1 问题1:为什么 import router from './router' 不用写 /index?
Node.js/Bundler 的自动解析规则:
./router → 自动查找:
1. router.ts
2. router/index.ts ← 找到了!
3. router.js
4. router/index.js
完整写法对比:
import router from './router'; // ✅ 简写(推荐)
import router from './router/index'; // ✅ 完整写法
import router from './router/index.ts'; // ❌ 不要加扩展名
为什么 useUserStore 写全了?
// 因为文件名就叫 useUserStore.ts,不是 index.ts
import { useUserStore } from '../store/useUserStore'; // 必须写全
// 如果是这样的结构:
// store/
// useUserStore/
// index.ts ← useUserStore 在这里
// 那就可以省略:
import { useUserStore } from '../store/useUserStore'; // 自动找 index.ts
4.2.2 问题2:什么时候需要解构 {}?
完整对比表:
| 导出方式 | 导出语法 | 导入语法 | 是否解构 |
|---|---|---|---|
| 默认导出 | export default router |
import router from './router' |
❌ 不解构 |
| 命名导出 | export const beforeEach = ... |
import { beforeEach } from './guards' |
✅ 必须解构 |
| 命名导出 | export const useUserStore = ... |
import { useUserStore } from './store' |
✅ 必须解构 |
你代码中的实例:
// ===== pinia 库的导出(命名导出)=====
// node_modules/pinia/dist/index.d.ts
export declare function defineStore(...): ...; // 命名导出
export declare function createPinia(): ...; // 命名导出
// 所以导入要解构:
import { defineStore } from 'pinia'; // ✅ 解构
import { createPinia } from 'pinia'; // ✅ 解构
// ===== 你的 useUserStore.ts(命名导出)=====
export const useUserStore = defineStore(...); // 命名导出
// 导入:
import { useUserStore } from '@store/useUserStore'; // ✅ 必须解构
// ===== 你的 router/index.ts(默认导出)=====
export default router; // 默认导出
// 导入:
import router from './router'; // ✅ 不解构
import myRouter from './router'; // ✅ 可以改名
// ===== guards.ts(命名导出)=====
export const beforeEach = async (to, from) => {...}; // 命名导出
export const afterEach = (to, from) => {...}; // 命名导出
// 导入:
import { beforeEach, afterEach } from '@router/guards'; // ✅ 必须解构
4.2.3 问题3&4:什么时候用 default?
使用场景对比:
✅ 用默认导出(export default):
- 文件的主要功能/核心对象
- 一个文件只导出一个东西
- 例如:路由实例、组件、配置对象
// index.ts - 导出路由实例(核心对象)
export default router; // ✅ 用 default
// App.vue - 导出组件
export default {
name: 'App',
setup() {...}
};
✅ 用命名导出(export const):
- 工具函数、常量、多个功能
- 一个文件导出多个东西
- 例如:路由守卫、Store、工具函数
// guards.ts - 多个守卫函数
export const beforeEach = ...; // ✅ 命名导出
export const afterEach = ...; // ✅ 命名导出
// utils.ts - 多个工具函数
export const formatDate = ...;
export const parseJSON = ...;
export const API_URL = 'https://...';
混合使用:
// api.ts
const apiClient = axios.create({...});
export default apiClient; // 默认导出:主对象
export const get = (url) => apiClient.get(url); // 命名导出:辅助函数
export const post = (url, data) => apiClient.post(url, data);
// 导入:
import apiClient from './api'; // 默认导出
import { get, post } from './api'; // 命名导出
// 或者全部导入:
import apiClient, { get, post } from './api'; // 混合导入
- Title: 1.JS基础与浏览器开发工具
- Author: 明廷盛
- Created at : 2026-02-12 01:17:04
- Updated at : 2025-02-15 15:34:00
- Link: https://blog.20040424.xyz/2026/02/12/🐍爬虫工程师/第二部分 JS逆向/1.JS基础与浏览器开发工具/
- License: All Rights Reserved © 明廷盛





