一、window
对象
window
是浏览器中最顶层的全局对象,所有变量、函数、DOM、BOM 等都归属于它。
可理解为:
// 这两句是等价的
var a = 123;
window.a === 123; // true
1. window 的核心作用
分类 | 说明 |
---|---|
全局作用域 | JS 中所有全局变量、函数、对象都挂在 window 上 |
浏览器控制 | 可以操作地址栏、历史记录、弹窗、窗口等 |
定时器控制 | 提供 setTimeout、setInterval 等方法 |
页面信息获取 | 提供 location 、navigator 、screen 等 |
事件注册 | 支持绑定事件、监听页面加载 |
检测 webdriver | 是反爬虫中常用于检测 puppeteer 等的目标对象 |
2. 常用属性和方法分类
1)全局变量、函数存储器
var a = 100;
console.log(window.a); // 100
function sayHi() {}
console.log(window.sayHi); // ƒ sayHi() {}
2)DOM 接口入口
window.document // 当前页面的 DOM 树
window.document.body // 获取 body 节点
3)地址栏与跳转
window.location.href // 当前 URL
window.location.reload() // 刷新
window.location.assign('https://baidu.com') // 跳转
4)浏览器信息
window.navigator.userAgent // 浏览器 UA
window.navigator.webdriver // true 表示是自动化环境(如 puppeteer)
5)定时器
window.setTimeout(() => console.log('1秒后执行'), 1000);
window.setInterval(() => console.log('循环执行'), 2000);
window.clearTimeout(timerId); // 清除 timeout
window.clearInterval(intervalId); // 清除 interval
6)弹窗函数
window.alert('提示'); // 弹窗提示
window.confirm('确认吗'); // 返回 true/false
window.prompt('请输入'); // 输入框
7)事件监听
window.addEventListener('load', () => {
console.log('页面加载完成');
});
window.addEventListener('resize', () => {
console.log('窗口大小变化');
});
8)本地存储
// 在浏览器的 localStorage 中设置一条永久(直到手动删除)的键值对。
window.localStorage.setItem('key', 'value');
// 在当前“标签页”的 sessionStorage 中设置一条临时键值对。
window.sessionStorage.setItem('sid', 'xyz');
9)浏览器窗口操作(弹窗、关闭)
let win = window.open('https://example.com');
win.close(); // 关闭窗口
3. 反爬虫/逆向中 window
的关键点
用法 | 说明 |
---|---|
window.navigator.webdriver | 检测是否为自动化浏览器环境 |
window.outerHeight / innerHeight | 检测是否为真实用户行为(Puppeteer 常伪装失败) |
window.chrome | Chrome 环境特征,某些网站用来检测 |
window.performance.timing | 检测加载时间,识别脚本干扰行为 |
window.name | 某些网站用来存信息(登录 token、来源判断) |
4. 常见提权手法(JS 逆向中)
在 JS 逆向中经常看到某些函数、对象挂在 window
上:
window._encrypt = function (s) {
// 混淆过的加密函数
};
逆向流程中我们会这样做:
-
控制台输入:
window._encrypt.toString()
查看源码。 -
hook 它:
window._encrypt = new Proxy(window._encrypt, {
apply(target, thisArg, args) {
console.log('加密入参:', args);
return target.apply(thisArg, args);
}
});
总结
类别 | 方法/属性 | 用途 |
---|---|---|
控制页面 | window.location | 跳转、刷新 |
页面信息 | window.navigator | 浏览器信息、反爬点 |
DOM 接口 | window.document | 页面结构分析入口 |
本地存储 | window.localStorage / sessionStorage | 获取登录态、参数 |
时间控制 | setTimeout / setInterval | 延时/周期任务 |
事件 | window.addEventListener | 页面加载、大小变化 |
自动化检测 | window.navigator.webdriver | 判断是否是爬虫环境 |
二、document
对象
document
是 window.document
,表示当前网页的 DOM(文档对象模型)树。
可以理解成:document
就是 JS 操作 HTML 的入口,是“页面”的 JS 表示。
1. DOM 树结构概念
以一个简单的 HTML 为例:
<html>
<head><title>页面标题</title></head>
<body>
<div id="box">Hello</div>
</body>
</html>
JS 中 DOM 树是由各种 节点对象(Node) 构成的,主要包括:
-
元素节点(Element)
-
文本节点(Text)
-
注释节点(Comment)
-
属性节点(Attr)
而 document
是 DOM 树的根节点,可以从它开始遍历、查询、修改整个页面内容。
2. 常用 API 分类讲解
1)元素选择器(核心)
单个选择器
document.getElementById("box") // 通过 id
document.querySelector("#box") // 支持 css 选择器
多个元素
document.getElementsByClassName("item") // HTMLCollection(类数组)
document.getElementsByTagName("div")
document.querySelectorAll(".item") // NodeList(可遍历)
2)节点属性获取
let el = document.getElementById("box");
el.innerText // 只获取用户可见的文本
el.innerHTML // 获取内部 HTML 内容
el.textContent // 获取所有文本(包括隐藏的)
el.id // 获取 id
el.className // 获取类名
el.getAttribute("data-id") // 获取自定义属性
爬虫中经常用 innerText
提取内容、用 getAttribute
提取加密参数。
3)DOM 修改操作(动态分析重点)
设置文本/HTML
el.innerText = "修改后";
el.innerHTML = "<b>加粗</b>";
添加/删除子节点
let newEl = document.createElement("p");
newEl.innerText = "新段落";
document.body.appendChild(newEl); // 加到页面中
el.remove(); // 删除这个元素
4)事件绑定
document.getElementById("btn").addEventListener("click", function (e) {
console.log("按钮点击了");
});
可以动态 hook 某些点击行为、输入行为,分析加密逻辑。
5)DOM 遍历与查找
el.parentNode // 父节点
el.children // 所有子节点
el.firstChild // 第一个子节点(可能是文本)
el.nextElementSibling // 下一个元素
6)动态创建元素(JS 动态渲染网页)
let img = document.createElement("img");
img.src = "xxx.jpg";
document.body.appendChild(img);
在逆向过程中,很多 JS 会在 DOMContentLoaded
后再通过 JS 创建元素 —— 所以就得等它渲染完再提取。
7)常见写法总结
用法 | 含义 |
---|---|
document.body | 获取 <body> 元素 |
document.documentElement | 获取 <html> 元素 |
document.title | 设置/获取页面标题 |
document.cookie | 读写 Cookie |
document.readyState | 页面加载状态:loading / interactive / complete |
3. 爬虫/逆向常见使用场景
场景1:页面参数嵌在 HTML 中
<input type="hidden" id="w" value="加密参数123" />
let w = document.getElementById("w").value; // 获取参数
场景2:数据写在 DOM 中被加密 JS 使用
let token = document.querySelector('meta[name="csrf-token"]').getAttribute("content");
场景3:数据是 JS 动态渲染的
你可能需要等待 DOMContentLoaded
或手动触发渲染函数:
document.addEventListener("DOMContentLoaded", function () {
// 等待 JS 动态填充数据后再提取
});
4. 特殊技巧与逆向用途
Hook DOM 方法
let old = document.createElement;
document.createElement = function(tag) {
console.log("正在创建:", tag);
return old.call(this, tag);
}
用于监控某些脚本动态创建 iframe、canvas 等可疑元素。
重写 document.write
document.write = function(str) {
console.log("页面写入了", str);
}
总结
功能 | 常用方法 |
---|---|
获取元素 | getElementById , querySelector , getElementsByClassName |
读取属性 | innerText , innerHTML , getAttribute() |
修改内容 | innerText= , innerHTML= , setAttribute() |
添加/删除节点 | createElement , appendChild , remove() |
动态监听 | addEventListener , DOMContentLoaded |
Cookie 操作 | document.cookie |
页面状态 | document.readyState , document.title |
三、navigator对象
-
navigator
是window.navigator
,表示浏览器的客户端信息对象。 -
它不控制页面,而是提供有关浏览器、设备、操作系统、网络、语言等信息。
经常用于:
-
浏览器特征识别(UA、语言、平台)
-
判断是否为爬虫环境(webdriver 等)
-
获取插件、硬件信息(mediaDevices)
-
某些 JS 加密/验证逻辑也依赖其中字段
1. 常用属性分类整理
类型 | 关键字段 | 作用 |
---|---|---|
浏览器信息 | userAgent , appName , appVersion | 判断浏览器 |
系统平台 | platform , language , languages | 判断系统和语言 |
自动化检测 | webdriver , permissions | 检测是否为 Puppeteer 等 |
硬件信息 | deviceMemory , hardwareConcurrency | 伪装真实设备参数 |
插件信息 | plugins , mimeTypes | 用于反爬虫检测插件差异 |
多媒体 | mediaDevices | 获取摄像头、麦克风设备 |
安全性 | credentials , cookieEnabled | 获取 cookie 支持信息等 |
2. 常用属性详解 + 示例
1)navigator.userAgent
– 浏览器 UA
console.log(navigator.userAgent);
// eg: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...
用途:
-
网站根据它判断系统、浏览器类型
-
反爬中常校验其是否伪造(过于简洁/不一致就会被判定为爬虫)
-
puppeteer 默认 UA 非常明显,会被识别
2)navigator.webdriver
– 自动化检测最重要字段!
navigator.webdriver // true 表示是 WebDriver 控制的浏览器(如 puppeteer)
这是反爬网站检测最常用的字段!默认使用 Puppeteer 启动的浏览器中该字段是 true
。
绕过方式:
-
使用
Object.defineProperty
伪造:
Object.defineProperty(navigator, 'webdriver', {
get: () => false,
});
3)navigator.language
/ languages
navigator.language // 'zh-CN'
navigator.languages // ['zh-CN', 'en-US']
-
表示浏览器的语言偏好
-
多用于防止国外 bot 爬取国内网站,或判断是否为国外用户
4)navigator.platform
navigator.platform // 'Win32' / 'Linux x86_64' / 'MacIntel'
-
真实操作系统平台信息
-
与 UA 对不上,会被认为是伪造环境
5)navigator.plugins
/ navigator.mimeTypes
navigator.plugins.length // 插件数量
navigator.plugins[0].name // 插件名称
-
Chrome 插件大多数默认加载
-
无插件、或插件结构不一致,很容易被识别为“伪造浏览器”或 headless 环境
6)navigator.deviceMemory
和 hardwareConcurrency
navigator.deviceMemory // 8 (单位:GB)
navigator.hardwareConcurrency // 8 (逻辑核心数)
-
表示设备的性能能力
-
许多反爬虫系统通过检测这些字段判断是否为真实设备(虚拟机环境通常数值较低)
7)navigator.cookieEnabled
navigator.cookieEnabled // 是否允许使用 Cookie(true/false)
-
通常是 true
-
如果 false,一些验证逻辑或登录可能失败
8)navigator.mediaDevices
(高级用法)
navigator.mediaDevices.enumerateDevices().then(devices => {
console.log(devices);
});
-
获取摄像头、麦克风等信息
-
自动化环境经常获取不到设备,也会成为检测点
9)navigator.credentials
管理网站登录凭据(用于自动登录系统),基本不用但存在于浏览器标准中。
3. 反爬虫检测常用字段
字段 | 说明 | 默认值(真实浏览器) |
---|---|---|
navigator.webdriver | 是否为 WebDriver 控制 | false |
navigator.plugins | 插件列表 | 有一定数量 |
navigator.languages | 浏览器语言 | 至少 1 个 |
navigator.deviceMemory | 设备内存 | 通常为 4~16 |
navigator.hardwareConcurrency | 逻辑核心数 | 通常为 4~16 |
navigator.userAgent | 用户代理字符串 | 真实结构复杂 |
这些字段在自动化环境下常常出现“异常”,例如:
-
webdriver = true
-
plugins.length = 0
-
deviceMemory = undefined
-
languages.length = 0
5. 逆向/自动化中实用技巧
检测并 Hook navigator 字段(console 中调试):
// hook UA
Object.defineProperty(navigator, 'userAgent', {
get: () => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...'
});
// hook webdriver
Object.defineProperty(navigator, 'webdriver', {
get: () => false
});
自动化环境中手动设置 navigator 字段(绕过检测)
在 puppeteer 启动脚本中加:
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => false,
});
});
总结
功能类型 | 常用属性/方法 | 说明 |
---|---|---|
浏览器识别 | userAgent , appName | 判断设备 |
自动化检测 | webdriver , plugins | 爬虫检测核心 |
系统语言 | language , languages | 区域识别 |
性能识别 | deviceMemory , hardwareConcurrency | 是否是低配虚拟机 |
多媒体检测 | mediaDevices | 检查摄像头、麦克风 |
安全性 | cookieEnabled , credentials | 基础检测 |
四、location
对象
-
location
是window.location
的简写,表示当前窗口的地址栏 URL 信息。 -
它是一个可读可写的对象,可以:
-
读取当前网址
-
提取各部分 URL 参数
-
实现跳转、重定向、刷新
-
1. 常用属性列表
假设当前 URL 为:
https://www.example.com:8080/path/page.html?token=abc123#section2
对应字段如下:
属性 | 含义 | 值 |
---|---|---|
location.href | 整个地址 | https://www.example.com:8080/path/page.html?token=abc123#section2 |
location.protocol | 协议 | https: |
location.hostname | 主机名 | www.example.com |
location.port | 端口 | 8080 |
location.host | 主机 + 端口 | www.example.com:8080 |
location.pathname | 路径 | /path/page.html |
location.search | URL 查询字符串(带 ?) | ?token=abc123 |
location.hash | 哈希值(# 之后) | #section2 |
2. 实战用法与示例
1)获取当前 URL 各部分参数
location.href // 获取整个地址
location.hostname // www.example.com
location.pathname // /path/page.html
location.search // ?token=abc123
location.hash // #section2
2)提取 URL 参数(token、id 等)
方法 1:原始方法
let params = new URLSearchParams(location.search);
let token = params.get('token'); // abc123
方法 2:手动拆解
let search = location.search.substring(1); // 去掉 ?
let obj = {};
search.split('&').forEach(pair => {
let [k, v] = pair.split('=');
obj[k] = v;
});
console.log(obj['token']);
3)页面跳转 / 重定向
// 跳转到新页面(可被后退按钮记录)
location.href = 'https://baidu.com';
// 跳转并替换历史记录(不可回退)
location.replace('https://baidu.com');
// 刷新页面(等价于 F5)
location.reload();
4)控制 hash 页面锚点跳转
location.hash = '#section3'; // 页面跳转到锚点位置
很多单页应用(SPA)利用 hash 控制前端路由。
5)动态构造请求 URL
当 JS 加密数据要提交到某接口(如 submit?callback=__jsonp123
),可以:
let url = '/submit?callback=' + encodeURIComponent(callback);
3. 逆向与反爬虫常见用法
1)判断是否在正确页面环境
if (location.hostname !== 'www.realhost.com') {
alert('非法访问!');
}
在模拟器或爬虫环境里访问,可能会触发这种逻辑。
2)根据参数生成 JS 加密字段
很多站会这样写:
let token = location.search.match(/token=(.*?)&/)[1];
let sign = md5(token + 'salt');
必须正确还原 token
参数才能逆向 sign
的生成。
3)重定向跳转验证机制
有的网站加密流程如下:
-
页面 A 获取参数 → JS 加密生成跳转链接
-
使用
location.href = 目标链接
-
若参数不对,会被重定向回 A
可用 location.href
Hook 或打印看跳转流程:
// 保存原始 setter
const originalHref = Object.getOwnPropertyDescriptor(window.location.__proto__, 'href');
// 重写 getter 和 setter
Object.defineProperty(window.location, 'href', {
get() {
console.log('[HOOK] 读取 location.href');
return originalHref.get.call(location);
},
set(val) {
console.log('[HOOK] 设置 location.href =', val);
debugger; // 可以打断点看堆栈
return originalHref.set.call(location, val);
}
});
或更简单方式:
// Hook 写操作
Object.defineProperty(window.location, 'href', {
set(val) {
console.log('[跳转拦截] href 被设置为:', val);
debugger;
}
});
有些浏览器可能不允许直接修改 location.href
的原型链属性(安全限制)。
可以考虑改 window.location
为代理对象或使用 Proxy(进阶玩法):
const loc = window.location;
Object.defineProperty(window, 'location', {
get() {
return loc;
},
set(val) {
console.log('location 被设置为:', val);
debugger;
}
});
4)利用 location 实现伪装跳转(绕过 Referer 检测)
let jumpUrl = 'https://target.com/api?refer=' + encodeURIComponent(document.referrer);
location.href = jumpUrl;
总结
任务 | 属性/方法 | 示例值 |
---|---|---|
获取完整 URL | location.href | https://xx.com?a=1 |
获取主机名 | location.hostname | xx.com |
获取路径 | location.pathname | /page |
获取查询参数 | location.search | ?a=1&b=2 |
获取锚点 | location.hash | #part1 |
跳转新页面 | location.href = url | 页面跳走 |
替换页面 | location.replace(url) | 跳转但无法返回 |
刷新页面 | location.reload() | 页面刷新 |
五、DOM 操作
DOM 是浏览器提供的接口,让 JS 可以操作网页的结构、样式、内容。
可以理解为:
网页加载后,HTML 被浏览器“转化”为一个对象模型 —— document
,可以用 JS 访问、修改、删除、添加这些 HTML 元素和内容。
1. 常用 DOM 查询方式
方法 | 说明 | 示例 |
---|---|---|
document.getElementById(id) | 通过 ID 获取元素 | document.getElementById('btn') |
document.getElementsByClassName(cls) | 获取某类名的元素列表 | document.getElementsByClassName('item')[0] |
document.getElementsByTagName(tag) | 获取某标签元素 | document.getElementsByTagName('a') |
document.querySelector(sel) | 返回第一个匹配的元素(推荐) | document.querySelector('.price') |
document.querySelectorAll(sel) | 返回所有匹配元素 NodeList | document.querySelectorAll('.list li') |
querySelector
是逆向中最常用的,因为它支持 CSS 选择器,非常灵活。
2. DOM 节点操作(提取动态数据核心)
1)读取元素文本内容
document.querySelector('.price').innerText; // 可见文本(常用于展示价格)
document.querySelector('.price').textContent; // 所有文本(包括隐藏的)
在加密或反爬网站中,价格、token、id 等常被隐藏在页面里。
2)获取 HTML 属性值
let link = document.querySelector('a');
console.log(link.href); // 完整跳转链接
console.log(link.getAttribute('href')); // 原始 href 属性内容
这在处理跳转验证、提取 data-token
这类自定义属性时非常关键。
3)设置/修改内容
let el = document.querySelector('#username');
el.innerText = 'newText';
el.setAttribute('data-val', 'abc');
逆向调试时可以用这些方法修改页面中的 DOM 内容,测试结果是否影响加密逻辑。
4)创建/插入/删除元素
let div = document.createElement('div');
div.innerText = 'Hello';
document.body.appendChild(div); // 插入到页面中
document.body.removeChild(div); // 删除
自动化环境中经常利用此方法插入 DOM,用于模拟用户行为或 hook 页面 JS。
5)监听节点变化(检测某些动态加载元素)
let observer = new MutationObserver((mutations) => {
console.log('DOM 发生变化');
});
observer.observe(document.body, { childList: true, subtree: true });
如果某些加密字段是通过 JS 动态加载后插入 DOM 的,可以用这个方式监听加载时机。
3. 逆向实战常见场景举例
场景 1:从隐藏字段提取 token
<input type="hidden" id="auth" value="abc123">
let token = document.querySelector('#auth').value;
场景 2:提取 JS 混淆中写入 DOM 的数据
<script>
(function(){
var a = 'abc';
var b = a + '123';
document.querySelector('#sign').setAttribute('data-sign', b);
})();
</script>
就要:
document.querySelector('#sign').getAttribute('data-sign'); // 得到 'abc123'
场景 3:复杂网页中抓取列表数据
<ul class="list">
<li><span class="price">¥199</span></li>
<li><span class="price">¥299</span></li>
</ul>
document.querySelectorAll('.list .price').forEach(el => {
console.log(el.innerText); // 循环打印所有价格
});
场景 4:网站用 DOM 节点储存加密参数
<div id="data" data-key="a1b2c3" data-sign="xxx123"></div>
let key = document.querySelector('#data').dataset.key; // a1b2c3
let sign = document.querySelector('#data').dataset.sign; // xxx123
dataset
是对所有 data-xxx
属性的封装,逆向中非常常见!
4. 爬虫自动化控制中的 DOM 操作
JS 中操作 | Selenium/Puppeteer 中实现 |
---|---|
querySelector | find_element(By.CSS_SELECTOR, 'xxx') |
innerText | element.text |
value | element.get_attribute('value') |
click() | element.click() |
setAttribute | driver.execute_script(...) |
dataset.xxx | element.get_attribute('data-xxx') |
总结
DOM 操作是 JS 逆向和自动化的“手眼”,通过它才能真正看到页面上 JS 动态写入了哪些加密参数、展示了哪些真实数据、触发了哪些事件。
六、事件模型(捕获/冒泡、事件绑定、移除)
事件模型是 JS 浏览器中事件触发的传播机制,主要包括三个阶段:
三个阶段(重点)
捕获阶段 → 目标阶段 → 冒泡阶段
-
捕获阶段(capture phase):从
window
->document
->html
-> ... 一层层向下传递,直到目标元素。 -
目标阶段(target phase):事件到达目标元素本身。
-
冒泡阶段(bubble phase):从目标元素向上传递,直到
window
。
1. 示例理解:捕获 VS 冒泡
HTML 示例:
<div id="outer">
<button id="inner">Click Me</button>
</div>
JS 绑定:
document.getElementById('outer').addEventListener('click', () => {
console.log('outer click');
}, true); // 捕获阶段
document.getElementById('inner').addEventListener('click', () => {
console.log('inner click');
}, false); // 冒泡阶段(默认)
点击按钮后输出顺序是:
outer click 捕获
inner click 冒泡
如果都用冒泡阶段(false
),就会输出:
inner click
outer click
事件监听函数中的 true
和 false
是什么?
element.addEventListener(eventType, handler, useCapture);
这个第三个参数 useCapture
:
值 | 含义 |
---|---|
true | 事件在 捕获阶段 执行这个监听函数 |
false (默认) | 事件在 冒泡阶段 执行这个监听函数 |
2. 事件绑定方法
1)addEventListener
(推荐使用)
element.addEventListener('click', handler, useCapture);
-
useCapture
为true
:绑定在捕获阶段 -
useCapture
为false
(默认):绑定在冒泡阶段
示例:
button.addEventListener('click', function () {
alert('clicked');
}, false); // 冒泡阶段绑定
2)DOM0 级绑定(不推荐)
element.onclick = function() {
alert('clicked');
};
劣势:
-
覆盖已有处理函数
-
不支持多个监听器
3)移除事件监听
function handler(e) {
console.log('clicked');
}
element.addEventListener('click', handler);
// 移除监听
element.removeEventListener('click', handler);
注意:
-
必须是同一个函数对象才能移除
-
所以不要写成匿名函数
() => {}
,否则无法移除
3. 事件对象和控制方法
事件处理函数会默认接收一个参数:event
,它是事件对象。
button.addEventListener('click', function(event) {
console.log(event.type); // click
});
常用控制方法:
方法 | 作用 |
---|---|
event.stopPropagation() | 阻止事件冒泡(不向父元素传递) |
event.preventDefault() | 阻止默认行为(如点击 a 不跳转) |
event.target | 触发事件的真实目标元素 |
event.currentTarget | 当前绑定监听器的元素 |
event.stopImmediatePropagation() | 阻止后续绑定的同类事件执行 |
案例:阻止冒泡和默认行为
document.querySelector('a').addEventListener('click', function(event) {
event.preventDefault(); // 不跳转
event.stopPropagation(); // 不冒泡
});
4. 逆向常见用途
1)抓事件中传参加密的处理逻辑
button.addEventListener('click', function() {
let token = genToken();
fetch(`/submit?token=${token}`);
});
用于抓取 点击时才生成的加密参数(如验证码、签名等)
2)Hook 事件阻止点击跳转/检测
document.querySelectorAll('a').forEach(a => {
a.addEventListener('click', (e) => {
e.preventDefault(); // 不跳转
console.log('跳转拦截', a.href);
});
});
3)模拟用户点击行为(自动化)
document.querySelector('#btn').click();
等价于用户真正点击按钮,事件处理器会触发。
4)Hook 所有点击事件(调试/逆向神器)
document.addEventListener('click', function(e) {
console.log('点击了:', e.target);
}, true); // 捕获阶段,拦截所有点击
5)强行移除某个事件绑定
可以在控制台移除网站的跳转限制:
let btn = document.querySelector('#submit');
btn.onclick = null; // 清空 onclick
总结
概念 | 描述 |
---|---|
捕获阶段 | 从最外层往目标元素传递事件 |
冒泡阶段 | 从目标向外层回传事件 |
addEventListener | 推荐的事件绑定方式 |
removeEventListener | 移除事件监听器 |
stopPropagation | 阻止冒泡 |
preventDefault | 阻止默认行为 |
click() | 触发点击事件 |
七、cookie
Cookie 是浏览器为网站存储的小型文本信息,用来在客户端保存:
-
登录凭证(如
token=abc123
) -
用户 ID / 登录状态
-
一些认证或追踪参数(例如
captcha_token
,sessionid
)
当浏览器再次请求该网站时,Cookie 会自动带到请求头中,帮助服务器识别用户状态。
1. Cookie 的组成结构
每条 Cookie 是一个键值对,还可以包含附加属性:
Set-Cookie: name=value; Path=/; Domain=xx.com; Expires=日期; Secure; HttpOnly; SameSite
属性 | 作用 |
---|---|
name=value | 键值对 |
Path | 哪个路径下有效(默认当前) |
Domain | 哪个域名下有效(默认当前域) |
Expires | 过期时间(持久 cookie) |
Max-Age | 有效秒数(优先于 Expires) |
Secure | 仅 HTTPS 传输 |
HttpOnly | JS 无法读取(防止 XSS) |
SameSite | 限制跨站请求时是否携带 Cookie(防 CSRF) |
2. JS 中操作 Cookie(核心)
浏览器端操作 Cookie 使用 document.cookie
,注意它是一个字符串接口:
1)读取 Cookie
console.log(document.cookie);
输出例子:
token=abc123; uid=10086; theme=dark
如果想获取某一个 cookie:
function getCookie(name) {
let match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}
getCookie('token'); // 返回 abc123
2)设置 Cookie
document.cookie = "token=abc123; path=/; max-age=3600";
说明:
-
设置的是一条 cookie
-
多次设置会追加,而不是覆盖全部
-
path=/
:整个网站都有效 -
max-age=3600
:保存 1 小时
只要 path、domain 不同,浏览器允许存在多个同名 Cookie!
3)删除 Cookie
document.cookie = "token=; path=/; max-age=0";
实质上是通过设置过期时间让浏览器自动清除。
3. Cookie 的 JS 限制
限制点 | 说明 |
---|---|
不能读写 HttpOnly Cookie | 只能被服务端设置,防 XSS,JS 访问不到 |
每次只能设置一条 Cookie | document.cookie = 只能处理单条 |
不能设置 Secure 属性 | Secure 只能由服务端设置,JS 无法控制 |
4. 逆向和爬虫中的 Cookie 用法
1)网站使用 JS 动态设置 Cookie
例如某反爬机制中,页面加载后通过 JS 设置一个 Cookie:
document.cookie = "__verify_token=abc123xyz; path=/; max-age=300";
如果用 requests 请求不会有这个 cookie,需要借助 Puppeteer/Selenium 来执行 JS。
2)Cookie 控制访问权限(登录态)
某些页面必须携带 token:
Cookie: token=abc123; uid=10086
可以在登录后提取浏览器中的 Cookie,用来模拟登录状态。
3)利用 DevTools 手动修改 Cookie
可以打开控制台 ➜ Application ➜ Cookies,直接改写或注入:
document.cookie = "debug=true";
可触发某些“开发者调试页面”、“跳过验证码”等逻辑。
5. 安全属性详解
属性 | 用途 |
---|---|
HttpOnly | JS 无法访问(防止 XSS 窃取 cookie) |
Secure | 仅在 HTTPS 下发送 |
SameSite | 限制跨站请求时是否带 cookie,防止 CSRF 攻击 |
SameSite
有 3 种值:
-
Strict
:完全禁止第三方携带 -
Lax
:GET 请求允许(表单、a 标签) -
None
:允许任何请求,但必须搭配Secure
总结
操作 | 示例代码 |
---|---|
读取 cookie | document.cookie |
获取指定值 | 用正则解析 cookie 字符串 |
设置 cookie | document.cookie = 'token=abc123; path=/; max-age=3600' |
删除 cookie | document.cookie = 'token=; max-age=0' |
限制 | JS 无法读写 HttpOnly / Secure / SameSite 设置的 cookie |
八、localStorage与sessionStorage
localStorage
和 sessionStorage
这两个存储 API 是现代浏览器中常用的客户端存储方式,它们比 Cookie 更适合存储较大的数据,并且在很多场景中(例如爬虫、自动化、Web 安全等)都非常有用。
localStorage
:
-
持久化存储,数据即使浏览器关闭后依然存在。
-
适用于需要长期保存的数据,比如用户设置、缓存数据、离线应用数据等。
-
每个域名最多可以存储 5MB(浏览器不同可能略有差异)。
sessionStorage
:
-
会话存储,数据在浏览器标签页关闭后即消失。
-
适用于需要在页面会话之间保存数据的场景,比如单页应用(SPA)中的临时状态。
-
每个域名最多存储 5MB(与
localStorage
相同)。
1. 基本 API 操作
1)存储数据:setItem
存储数据时,需要指定键(key)和值(value),它们都是字符串类型。
// 存储数据
localStorage.setItem('username', 'johnDoe');
sessionStorage.setItem('sessionID', 'abc123');
2)读取数据:getItem
通过 getItem
获取指定键的值,如果键不存在则返回 null
。
// 读取数据
let username = localStorage.getItem('username'); // 'johnDoe'
let sessionID = sessionStorage.getItem('sessionID'); // 'abc123'
3)删除数据:removeItem
删除指定键的数据,删除后再读取返回 null
。
// 删除数据
localStorage.removeItem('username');
sessionStorage.removeItem('sessionID');
4)清除所有数据:clear
清除当前域名下的所有 localStorage
或 sessionStorage
数据。
// 清除所有数据
localStorage.clear();
sessionStorage.clear();
5)获取存储长度:length
返回当前存储中键值对的数量。
let localStorageLength = localStorage.length; // 记录有多少条数据
6)获取所有键:key
返回指定索引位置的键名。
// 获取第一个键
let firstKey = localStorage.key(0); // 'username' 等
2. localStorage
与 sessionStorage
的差异
特性 | localStorage | sessionStorage |
---|---|---|
生命周期 | 持久化存储(即使关闭浏览器也存在) | 会话结束时自动清除,关闭标签页后消失 |
数据存储范围 | 所有标签页共享(同一浏览器窗口或标签页中同一域名下) | 仅限当前标签页(标签页之间不可共享) |
用途 | 长期存储数据,适用于用户设置、缓存等 | 短期存储数据,适用于临时状态 |
存储大小 | 5MB(根据浏览器不同有所不同) | 5MB(与 localStorage 相同) |
3. 常见操作场景(应用于爬虫、自动化等)
1)爬虫应用:模拟登录状态
很多网站会将登录状态等信息保存在 localStorage
或 sessionStorage
中,可以通过提取并模拟这些存储的内容来模拟登录。
例如:
localStorage.setItem('authToken', 'xyz123');
在自动化脚本中,可以提取这些存储数据并模拟请求,避免使用 Cookie 模拟。
2)页面抓取动态数据:读取本地存储的数据
有些网站将数据缓存到 localStorage
或 sessionStorage
中,可以通过访问它们来获取动态加载的数据,绕过一些反爬机制。
let data = localStorage.getItem('userData');
3)自动化测试:模拟用户行为(Puppeteer/Selenium)
在进行 Web 自动化测试时,可以通过 localStorage
和 sessionStorage
来模拟用户登录态,减少每次测试时的重复登录步骤。
await page.evaluate(() => {
localStorage.setItem('authToken', 'xyz123');
});
4)Web 安全:绕过认证
通过获取并手动设置某些网站的 localStorage
或 sessionStorage
,可以模拟用户的认证状态,绕过一些验证逻辑。
例如,修改存储的用户权限字段:
localStorage.setItem('userRole', 'admin');
总结
操作 | localStorage | sessionStorage |
---|---|---|
存储数据 | localStorage.setItem(key, value) | sessionStorage.setItem(key, value) |
读取数据 | localStorage.getItem(key) | sessionStorage.getItem(key) |
删除数据 | localStorage.removeItem(key) | sessionStorage.removeItem(key) |
清除所有数据 | localStorage.clear() | sessionStorage.clear() |
存储范围 | 同一域名下所有标签页共享 | 仅当前标签页有效 |
生命周期 | 持久化存储(关闭浏览器也存在) | 会话结束时自动清除 |
存储大小 | 5MB | 5MB |
九、定时器
JavaScript 提供了两个主要的定时器方法:
-
setTimeout
:用于延时执行某个代码片段一次。 -
setInterval
:用于周期性执行某个代码片段,每隔一段时间重复执行。
这两个定时器方法都会返回一个“定时器 ID”,通过该 ID 可以清除定时器。
1. setTimeout
(延时执行)
setTimeout
用于设置一个延时执行的函数或代码块。
语法:
setTimeout(function, delay, arg1, arg2, ...)
-
function
:指定需要执行的回调函数。 -
delay
:延迟时间(以毫秒为单位,1 秒 = 1000 毫秒)。 -
arg1, arg2, ...
:可选的参数,传递给回调函数。
示例:
// 延时 2 秒输出 "Hello, World!"
setTimeout(function() {
console.log('Hello, World!');
}, 2000);
setTimeout
返回定时器 ID:
let timerId = setTimeout(function() {
console.log('This will run after 2 seconds');
}, 2000);
timerId
可以用来取消定时器(通过 clearTimeout
)。
清除定时器:
可以使用 clearTimeout()
来取消定时器,避免函数被执行。
clearTimeout(timerId); // 取消定时器
2. setInterval
(周期性执行)
setInterval
用于设置一个周期性执行的函数或代码块,每隔一段时间就会执行一次。
语法:
setInterval(function, interval, arg1, arg2, ...)
-
function
:指定需要执行的回调函数。 -
interval
:间隔时间(以毫秒为单位,1 秒 = 1000 毫秒)。 -
arg1, arg2, ...
:可选的参数,传递给回调函数。
示例:
// 每 2 秒输出一次 "Ping"
setInterval(function() {
console.log('Ping');
}, 2000);
setInterval
返回定时器 ID:
和 setTimeout
类似,setInterval
也返回一个定时器 ID,可以用来清除定时器。
let intervalId = setInterval(function() {
console.log('This will run every 2 seconds');
}, 2000);
清除定时器:
使用 clearInterval()
来取消周期性执行的定时器。
clearInterval(intervalId); // 停止定时器
3. 定时器的应用场景
1)延时执行某个操作:
常见的用途是延时执行某个函数,例如:动画效果、加载提示、延时请求等。
setTimeout(() => {
alert('这是延时 3 秒弹出的对话框');
}, 3000);
2)周期性执行操作:
例如定期获取数据、定时刷新的操作。
let counter = 0;
let intervalId = setInterval(() => {
console.log(`Count: ${counter}`);
counter++;
if (counter > 5) {
clearInterval(intervalId); // 达到 5 次后停止计数
}
}, 1000);
3)模拟倒计时:
可以利用 setTimeout
或 setInterval
来实现一个倒计时功能。
let count = 10;
let countdown = setInterval(() => {
console.log(count);
count--;
if (count < 0) {
clearInterval(countdown);
console.log('时间到!');
}
}, 1000);
总结
方法 | 作用 | 用法示例 |
---|---|---|
setTimeout() | 延迟执行一次函数 | setTimeout(() => { alert('Hi'); }, 2000) |
setInterval() | 周期性执行函数 | setInterval(() => { console.log('Ping'); }, 1000) |
clearTimeout() | 清除定时器,停止 setTimeout() | clearTimeout(timerId) |
clearInterval() | 清除定时器,停止 setInterval() | clearInterval(intervalId) |
十、页面加载顺序
页面加载顺序是指当一个 Web 页面加载时,浏览器如何按一定的顺序处理和渲染页面内容。
1. 页面加载的整体流程
浏览器加载页面的过程大致可以分为以下几个阶段:
-
DNS 查询
-
建立 TCP 连接
-
发送 HTTP 请求
-
服务器响应(返回 HTML)
-
解析 HTML
-
解析和执行 CSS
-
下载和执行 JavaScript
-
页面渲染
2. 详细的加载顺序
1)DNS 查询
当浏览器访问一个 URL 时,首先会进行 DNS 查询(域名解析)来将域名转换成 IP 地址。这个过程是异步的,可能会缓存先前解析的结果。
2)建立 TCP 连接
浏览器根据解析到的 IP 地址与服务器建立 TCP 连接。这包括了三次握手的过程,用来确保客户端与服务器之间的通信是可靠的。
3)发送 HTTP 请求
TCP 连接建立后,浏览器会通过 HTTP 协议向服务器发送请求,获取页面的 HTML 内容。
4)服务器响应(返回 HTML)
服务器处理请求后,会返回 HTML 内容给浏览器。此时,浏览器收到 HTML 后会开始解析和渲染。
5)解析 HTML
浏览器接收到 HTML 后会开始解析。浏览器会将 HTML 解析成 DOM(文档对象模型)树。HTML 中的标签会转换为 DOM 元素节点,浏览器根据 DOM 结构来构建页面的内容。
DOM 构建过程中,遇到 <script>
标签时会出现不同的情况:
-
同步加载的
<script>
(<script>
标签默认行为): 浏览器会立即停止解析 HTML,去加载并执行脚本。执行完脚本后,才会继续解析 HTML。如果脚本较大或加载较慢,会导致页面加载过程变慢。 -
异步加载的
<script>
(<script async>
):async
属性告诉浏览器可以在后台异步加载脚本,并且不会阻塞 HTML 的解析。脚本加载完成后,会立即执行。 -
延迟加载的
<script>
(<script defer>
):defer
属性告诉浏览器延迟执行脚本,直到整个 HTML 被解析完再执行。这种方式不会阻塞 HTML 解析。
6)解析和执行 CSS
在解析 HTML 的过程中,浏览器会遇到 <style>
或 <link>
标签,这时浏览器会去请求并解析 CSS。解析 CSS 的顺序是:
-
浏览器会从头到尾逐行解析 HTML。
-
当遇到
<style>
或<link>
时,浏览器会请求外部 CSS 文件(如果有的话)并解析它们。 -
CSS 会影响页面的布局和样式,浏览器必须等待 CSS 文件完全加载和解析后,才能开始渲染页面。
CSS 文件加载会阻塞页面渲染:
如果在 HTML 中没有使用 async
或 defer
属性的 <script>
标签,浏览器会在获取到 CSS 之前,不会开始页面的渲染。这样可以确保样式在页面内容渲染之前已经准备好。
7)下载和执行 JavaScript
在 HTML 中,JavaScript 文件的加载与执行是一个关键的步骤。通常,JavaScript 会影响页面的结构、数据处理、用户交互等,因此脚本加载和执行的顺序非常重要。
-
同步脚本:
<script>
标签默认是同步加载和执行的,这会阻塞 HTML 的解析和渲染。浏览器会在执行完脚本后,继续解析剩余的 HTML。 -
异步脚本: 使用
async
属性的<script async>
会让浏览器异步下载 JavaScript 文件,并在文件下载完成后立即执行。此时,HTML 的解析不会被阻塞。 -
延迟脚本: 使用
defer
属性的<script defer>
会让浏览器将脚本的执行延迟到 HTML 解析完成之后才执行,确保 DOM 已经构建完毕。
8)页面渲染
当 HTML 和 CSS 被完全解析后,浏览器开始渲染页面。在此过程中,浏览器会按照 DOM 树和 CSS 样式规则,绘制页面的布局。这个过程包括了:
-
构建渲染树(Render Tree): 浏览器根据 DOM 和 CSSOM(CSS 对象模型)树,生成渲染树,渲染树包含了每个节点的样式信息。
-
布局: 根据渲染树,浏览器计算每个元素的最终位置和大小。
-
绘制: 浏览器将元素绘制到屏幕上。
3. 影响页面加载的因素
1)脚本加载顺序
如果 JavaScript 脚本没有正确配置 async
或 defer
,会导致页面在加载时阻塞。为了提高页面性能,通常建议将脚本放置在页面的底部,或者使用 defer
来确保脚本的加载不会阻塞页面的渲染。
2)外部资源的加载
页面中的图片、视频、字体、外部 CSS 和 JavaScript 文件等资源可能会增加加载时间,尤其是较大的资源会显著影响页面的加载速度。可以使用 lazyload
(懒加载)技术来推迟图片或其他资源的加载,直到需要时才加载。
3)网络延迟与服务器性能
网络延迟和服务器响应速度也是影响页面加载的重要因素。通过减少 HTTP 请求数量、优化服务器响应时间等方法可以提高页面加载速度。
4. 优化页面加载顺序
1)使用 async
和 defer
加速 JavaScript 加载
通过合理使用 async
和 defer
属性,避免阻塞 HTML 的解析,提高页面加载速度。
<!-- 异步加载 JavaScript -->
<script async src="script.js"></script>
<!-- 延迟执行 JavaScript -->
<script defer src="script.js"></script>
2)将脚本放在页面底部
将 <script>
标签放在 HTML 的底部,确保页面的内容优先加载,然后再加载和执行脚本。
3)使用 HTTP/2
HTTP/2 协议支持并发请求和更有效的资源传输,通过减少请求的延迟和优化资源传输,显著提高加载速度。
4)压缩和合并资源
通过压缩 JavaScript 和 CSS 文件,减少文件大小;将多个 JavaScript 或 CSS 文件合并为一个文件,减少 HTTP 请求的数量。
5)使用缓存
合理利用浏览器缓存,减少重复请求,提高页面加载速度。
总结
-
页面加载的顺序是浏览器获取、解析、渲染页面的过程,关键步骤包括 DNS 查询、建立连接、请求响应、HTML 解析、CSS 解析和 JS 执行。
-
<script>
标签的加载顺序和属性(async
和defer
)会影响页面的解析顺序。 -
优化建议: 合理使用
async
、defer
、图片懒加载、文件合并压缩等技术,减少阻塞,提升页面加载性能。
十一、DOMContentLoaded
DOMContentLoaded
是前端开发、自动化测试、逆向爬虫中一个非常重要的事件。它表示 HTML 文档被完全加载和解析完成,但样式表、图片等其他资源可能还没有加载完成。
DOMContentLoaded
是 document
对象上的一个事件,它在HTML 完全加载并解析完成后被触发,不必等待 CSS、图片、iframe 等资源加载完成。
对比几个常见事件的触发时机:
事件名 | 触发时机 |
---|---|
DOMContentLoaded | DOM 树构建完成,外部资源如图片、CSS 可能还未加载 |
load | 所有资源加载完成(包括图片、视频、iframe 等) |
beforeunload | 页面将要被关闭或刷新时触发 |
1. 事件触发流程(以浏览器加载为例)
加载顺序如下:
-
浏览器开始解析 HTML。
-
构建 DOM 树。
-
不等待图片、CSS 加载,DOM 构建完成后,立即触发
DOMContentLoaded
。 -
继续加载 CSS、图片、视频等资源。
-
所有资源加载完后,才触发
load
事件。
2. 常见用法
1)基本写法:
document.addEventListener("DOMContentLoaded", function () {
console.log("DOM fully loaded and parsed");
// 这里可以安全操作 DOM 元素
});
这样写的好处是可以确保你的 JS 操作在 DOM 完全构建后进行,防止找不到 DOM 元素的错误。
2)ES6 简写方式(在模块或框架中):
window.onload = () => {
console.log("所有资源加载完成,包括图片等");
};
document.addEventListener("DOMContentLoaded", () => {
console.log("只等 DOM,不等图片等资源");
});
3)区分两者(DOM 完成 vs 全部加载完成):
window.addEventListener("DOMContentLoaded", () => {
console.log("DOM 完成(早)");
});
window.addEventListener("load", () => {
console.log("所有资源加载完成(晚)");
});
3. 在爬虫/逆向中的应用
1)通过 Puppeteer/Playwright 等自动化工具等待 DOMContentLoaded:
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
说明:适用于只需要页面结构(DOM)但不需要图片等资源的场景(速度更快)。
2)绕过 JavaScript 动态加载:
有的网站会在 DOMContentLoaded
后通过 JS 动态插入数据。你可以监听 DOMContentLoaded
然后 hook 某些函数:
document.addEventListener("DOMContentLoaded", () => {
const originalFetch = window.fetch;
window.fetch = function () {
console.log("拦截 fetch 请求");
return originalFetch.apply(this, arguments);
};
});
3)逆向分析中监听何时页面数据出现:
一些网站页面初始没有数据,而是在 DOMContentLoaded
后通过 JS 请求接口,可以 hook 这些操作。
4. 测试 DOMContentLoaded
和 load
的时间差
可以打开 Chrome 控制台,执行:
window.addEventListener("load", () => {
console.log("load 时间", performance.now());
});
document.addEventListener("DOMContentLoaded", () => {
console.log("DOMContentLoaded 时间", performance.now());
});
会看到 DOMContentLoaded
一般更早触发几百毫秒到几秒。
总结
项目 | DOMContentLoaded | load |
---|---|---|
触发时机 | DOM 解析完毕,不等图片和 CSS | 所有资源(DOM、CSS、图片、JS)都加载完毕 |
是否常用于爬虫/逆向? | 是(快、数据早) | 慢 |
是否适合操作 DOM? | 是 | 也可以 |