在我们渗透测试的过程中,做抢购软件的过程中,通常会需要用到爬虫,因此我们在本系列笔记中主要去学习 JS 爬虫的原理及反爬虫以及 JS 逆向相关的知识点。
浏览器的组件及作用
浏览器通常包括五个组件,它们分别是:
- 用户界面:包括地址栏、前进/后退/刷新等按钮、页面主窗口等。
- 浏览器引擎:负责将用户的操作传递给对应的渲染引擎。
- 渲染引擎:能够调用解释器解释 HTML、CSS 和 Javascript 代码,然后根据解释结果重排页面。
- 数据存储:在本地存储一些体积较小的数据,如 Cookie、Storage 对象等。
- 网络:自动加载 HTML 文件中所需的其他资源。
浏览器通常也包含三个解释器、它们分别是 HTML 解释器、Javascript 解释器、CSS 解释器。
页面的渲染过程工作流程如下:
请注意,当我们用浏览器打开网页的时候,会进行的操作是:加载 HTML、解释 HTML、加载引用到的资源、解释 CSS、JS。
如果我们用工具,比如BurpSuite
或者 Python
进行请求,只会进行加载HTML
这一部的操作。
JavaScript 反爬虫的根本原因就是,浏览器和其他工具的差异,其他工具没有 Javascript 解释器。
JavaScript 语法全解
JavaScript 语法是函数式编程,主要分为四部分:数据类型、控制流、函数、特殊对象。
数据类型
Javascript 中常见的数据类型包括如下几个部分:
- Object 对象
- Array 数组
- String 字符串
- Number 数字
- Boolean 布尔值
- Map 映射
- Set 集合
- null/undefined
Object
** 对象:**对象用于存储键值对,适用于存储各种信息。
let person = {
firstName: "张",
lastName: "伟",
age: 30,
greet: function() {
return `Hello, my name is ${this.firstName}${this.lastName}`;
}
};
console.log(person.greet()); // 输出: Hello, my name is 张伟
Array
数组:数组用于存储有序集合,可以用来表示列表、队列、栈等数据结构。
let fruits = ["苹果", "香蕉", "樱桃"];
// 数组方法:push() 添加元素到末尾
fruits.push("橙子");
// 数组方法:pop() 移除数组末尾的元素
let last = fruits.pop();
console.log(fruits); // 输出: ["苹果", "香蕉", "樱桃", "橙子"]
String
字符串:**字符串用于表示文本数据。
let message = "Hello, World!";
// 字符串方法:indexOf() 查找子字符串的位置
let index = message.indexOf("World");
console.log(index); // 输出: 7
Number
数字:数字类型用于表示数值。
let isStudent = true;
// 逻辑运算
let isEligible = (isStudent && (age >= 18));
console.log(isEligible); // 输出: true(如果假设 age >= 18)
Boolean
布尔值:布尔值用于表示真(true)或假(false)。
let isStudent = true;
// 逻辑运算
let isEligible = (isStudent && (age >= 18));
console.log(isEligible); // 输出: true(如果假设 age >= 18)
Map
映射: Map 对象存储键值对,并保留键的原始插入顺序。
let map = new Map();
map.set('a', 1);
map.set('b', 2);
// 获取值
let value = map.get('a');
console.log(value); // 输出: 1
Set
集合:Set 对象允许你存储任何类型的唯一值,无论是原始值还是对象引用。
let set = new Set();
set.add("苹果");
set.add("香蕉");
// 检查一个值是否在集合中
let hasApple = set.has("苹果");
console.log(hasApple); // 输出: true
控制流
Javasript 的控制流包括循环、函数
其中循环中包含的控制流如下:
- while
- for
- Array
- Map
- foreach
以下是使用 while
、for
循环以及数组的 map
和 foreach
方法的一些示例。
使用 while
循环
let i = 0;
while (i < 5) {
console.log(`while 循环中的数字是:${i}`);
i++;
}
这段代码会输出 0 到 4 的数字。
使用 for
循环
for (let i = 0; i < 5; i++) {
console.log(`for 循环中的数字是:${i}`);
}
这段代码也会输出 0 到 4 的数字。
使用数组的 map
方法
const numbers = [1, 2, 3, 4, 5];
const squares = numbers.map(number => {
return number * number;
});
console.log(squares); // 输出:[1, 4, 9, 16, 25]
map
方法会创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
使用数组的 foreach
方法
numbers.forEach(number => {
console.log(`foreach 循环中的数字是:${number}`);
});
foreach
方法会按照遍历数组中的每个元素,执行一个提供的函数,但不会改变原始数组。
其中 Javascript 中,函数的知识点包括:
- 定义
- 变量作用域
- 高阶函数
- 闭包
- 特殊对象
函数定义
// 函数声明
function greet(name) {
return `Hello, ${name}!`;
}
// 函数表达式
const add = function(a, b) {
return a + b;
};
// 箭头函数
const subtract = (a, b) => {
return a - b;
};
变量作用域
- 变量定义
- var,const,let 定义的变量是有作用域的
- var 定义的变量在各自的函数内部起作用
- const、let 定义的变量为块级作用域
- 变量提升
- 扫描整个函数体的语句,将所有申明变量提升到函数顶部
- 全局作用域
- 如果不指定 var、const、let 申明关键字来定义变量,该变量将被绑定到全局变量 window 上
- 块级作用域
- let、const 将作用在 for、while 等循环语句中用
// 全局作用域
let globalVar = "I'm global";
function scopeTest() {
let localVar = "I'm local";
if (true) {
let blockVar = "I'm in a block";
console.log(blockVar); // 输出:I'm in a block
}
console.log(localVar); // 输出:I'm local
}
scopeTest();
console.log(globalVar); // 输出:I'm global
高阶函数
- 定义
- 接收另一个函数作为参数的函数被称为高阶函数
- 用途
- 回调
- callback
- 数组操作
- filter、sort、map、forEach
- 回调
// 高阶函数:接受一个函数作为参数或者返回一个函数的函数
function highOrderFunction(callback) {
console.log("Before callback");
callback();
console.log("After callback");
}
highOrderFunction(() => {
console.log("Callback function called");
});
闭包
- 定义
- 函数的返回值可以作为函数
- 所有参数和变量都保存在返回函数中
- 当调用返回函数时才会执行所有的运算逻辑
- 用途
- 匿名自执行函数
- 封装
- 结果缓存
function createCounter() {
let count = 0;
return function() {
count++;
console.log(`Current count is: ${count}`);
};
}
const counter = createCounter();
counter(); // 输出:Current count is: 1
counter(); // 输出:Current count is: 2
console.log(counter.count); // 输出:undefined(闭包内部访问的是自己的count,不是外部函数的count)
特殊对象
- JSON
- JSON、对象的序列化、反序列化操作
- JSON.stringfy 序列化
- JSON.parse 反序列化
- Date
- JS 的时间操作对象
- new Date(dateString)
// 箭头函数不绑定自己的this,而是捕获其所在上下文的this值
const person = {
name: "Alice",
sayName: function() {
setTimeout(function() {
console.log(this.name); // this指向全局对象,因为setTimeout的上下文是全局上下文
}, 1000);
setTimeout(() => {
console.log(this.name); // 箭头函数中的this指向定义时的上下文,所以这里指向person对象
}, 1000);
}
};
person.sayName(); // 输出:Alice
事件循环
事件循环是主线程不断的重复获取执行消息、再获取执行不断循环的机制称为事件循环。
由于 JS 是单线程的,同时我们再处理异步操作的时候需要事件循环机制,因此我们需要引入事件循环这个概念。
涉及到的相关概念:
- 堆(Heap):大块非结构化内存区域、存储对象、数据
- 栈(Stack):调用栈,存储该次循环主程序执行的任务
- 队列(Queue):事件队列,先进先出被推入调入栈中
原型链
在 JavaScript 中,原型链(Prototype Chain)是一种基于原型的继承机制,它允许对象继承另一个对象的属性和方法。原型链使得每个对象都能够访问其原型对象的属性和方法,这样就可以实现共享属性和方法,而无需在每个对象中单独定义它们。
每个 JavaScript 对象都有一个指向另一个对象的引用,这个引用被称为它的原型(prototype)。当你尝试访问一个对象的属性或方法时,如果这个对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到匹配的属性或方法或者达到原型链的顶端(通常是 Object.prototype
)。
原型链的工作原理如下:
- 每个 JavaScript 对象都有一个
__proto__
属性(在 ES6 中,这个属性被正式命名为Object.getPrototypeOf()
),它指向该对象的原型。 - 原型的
__proto__
属性又指向其原型的原型,这样就形成了一个链式结构。 - 当访问一个对象的属性或方法时,如果该对象自身没有这个属性或方法,JavaScript 引擎会检查该对象的原型是否有这个属性或方法。
- 如果原型中有这个属性或方法,则直接使用它。如果没有,则继续查找原型的原型,直到找到为止,或者达到原型链的顶端。
- 原型链的顶端通常是
Object.prototype
,它是所有对象的原型。在Object.prototype
上没有更多的原型,因此,当原型链查询到达这里时,如果还是没有找到属性或方法,则会返回undefined
。
原型链允许开发者通过继承现有的对象来创建新的对象,而不需要从头开始定义所有的属性和方法。这种机制是 JavaScript 中实现继承的基础,也是 JavaScript 动态特性的一个重要体现。
例如:
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function() {
console.log(this.name);
};
function Dog(name) {
Animal.call(this, name); // 调用 Animal 的构造函数
}
// 继承 Animal 的原型
Dog.prototype = Object.create(Animal.prototype);
// 设置 Dog 的构造函数为 Dog
Dog.prototype.constructor = Dog;
// 添加 Dog 的特有方法
Dog.prototype.bark = function() {
console.log('Woof woof');
};
var myDog = new Dog('Rex');
myDog.sayName(); // 输出 "Rex"
myDog.bark(); // 输出 "Woof woof"
在这个例子中,Dog
对象继承了 Animal
对象的原型,因此 myDog
对象既可以调用 Animal
的 sayName
方法,也可以调用 Dog
的 bark
方法。
异步编程
异步编程是一种编程范式,它允许程序在等待某些操作完成(如 I/O 操作、网络请求等)时继续执行其他任务。在 JavaScript 中,异步编程尤其重要,因为 JavaScript 是一种单线程语言,意味着它一次只能执行一个任务。为了克服这个限制,JavaScript 采用了事件驱动和异步编程的机制。
在 JavaScript 中,异步编程主要通过以下几种方式实现:
- 回调函数(Callback Functions):回调函数是最基本的异步编程技术。函数作为参数传递给其他函数,当异步操作完成时,会被调用。例如,
setTimeout
和XMLHttpRequest
使用回调函数来处理异步操作。
setTimeout(function() {
console.log('Async operation completed');
}, 1000);
- Promise 对象:Promise 是在 ES6 中引入的,它提供了一种更加优雅的处理异步操作的方法。Promise 代表一个可能现在不可用,但将来某个时候才会确定的值。它允许你为异步操作的成功结果和失败原因注册回调函数。
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error fetching data:', error));
- async/await 语法:ES2017 引入了
async
和await
关键字,它们使得异步代码看起来和同步代码更加相似,更易于编写和理解。async
函数总是返回一个 Promise,而await
关键字用于等待一个 Promise 的解析结果。
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
}
异步编程在 JavaScript 中至关重要,因为它允许程序在处理 I/O 密集型任务时保持响应性,同时不会导致主线程阻塞。这使得 JavaScript 能够高效地处理复杂的应用程序,如网页、服务器和网络应用。
浏览器缓存
浏览器常见的储存方式包括:Cookie
、Local Storage
、SessionSttorage
、IndexedDB
。
- Cookies
- 主要用于与服务端通信
- 存储量小
- Local Storage
- 存储量相较于 Cookies 更大
- 只能存储字符串
- Session Storage
- 只存在与当前 Session、关闭浏览器就丢失了
- 其他与 Local Storage 一样
- IndexedDB
- 相当于浏览器上的 SQL 数据库
- 相当于浏览器上的 SQL 数据库
Webpack 打包
Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
Webpack 的核心概念是将项目分割成多个小模块,然后将这些模块按照依赖关系打包成大的区块。每个模块或区块都是一个文件,Webpack 负责将这些文件合并成一个或多个打包文件(bundle),通常是一个 JavaScript 文件。
总结,在本篇文章中,我们首先了解了浏览器的工作原理及常用组件,然后在此基础上学习了 Javascript 常用的数据类型及控制流。
我们会发现,一些常见的概念,比如匿名函数、闭包与Python
语法是很相像的,因此触类旁通,Javascript
并不难,后续我们可以通过细致的分析学习理解如何进行Javascript
逆向。