1.JS基础与浏览器开发工具

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在方法内部定义也是局部变量
  1. JavaScript变量生命周期在它声明时初始化
  2. 局部变量在函数执行完毕后销毁。
  3. 全局变量在页面关闭后销毁。

第三节 函数

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 其他特性

  1. 如果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: 【图三】查看是否是另一个对象的原型
      Pasted image 20250206140545|650Pasted image 20250206140621|333Pasted image 20250206140634|319

第六节 常用数据结构

这几个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 0undefined

void 0 是 undefined 的安全写法(避免 undefined 被重写)

第12节 判断是 自定义方法/JS自带方法

|625

第13节 py和js转JSON时空格问题

  • $ 语法: Post请求发json=会自动json.dumps() [[4.Requests模块#第三节 发送POST请求post()使用 data传参]] link
    image-20250223133027910.png
    image-20250223134251348.png

第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

普通的 construct构造器生成的 eval生成的
link
link

第二节 堆栈的使用

  1. 和 “数据结构” 中的栈一样, 越在底部的, 越先调用
  2. 点击对应的栈后会跳到栈所在的js作用域 (控制台, 监视…都只能调用/展示该栈中有的方法)
    |234

第三节 事件监听器断点

第四节 完整复制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:什么时候需要解构 {}

Read , lines 1 to 15

Read

完整对比表

导出方式 导出语法 导入语法 是否解构
默认导出 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 © 明廷盛