从浏览器组件出发,理解Javascript工作流程

在我们渗透测试的过程中,做抢购软件的过程中,通常会需要用到爬虫,因此我们在本系列笔记中主要去学习 JS 爬虫的原理及反爬虫以及 JS 逆向相关的知识点。

浏览器的组件及作用

浏览器通常包括五个组件,它们分别是:

  • 用户界面:包括地址栏、前进/后退/刷新等按钮、页面主窗口等。
  • 浏览器引擎:负责将用户的操作传递给对应的渲染引擎。
  • 渲染引擎:能够调用解释器解释 HTML、CSS 和 Javascript 代码,然后根据解释结果重排页面。
  • 数据存储:在本地存储一些体积较小的数据,如 Cookie、Storage 对象等。
  • 网络:自动加载 HTML 文件中所需的其他资源。

浏览器通常也包含三个解释器、它们分别是 HTML 解释器、Javascript 解释器、CSS 解释器。
页面的渲染过程工作流程如下:
image.png
请注意,当我们用浏览器打开网页的时候,会进行的操作是:加载 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

以下是使用 whilefor 循环以及数组的 mapforeach 方法的一些示例。
使用 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)。
原型链的工作原理如下:

  1. 每个 JavaScript 对象都有一个 __proto__ 属性(在 ES6 中,这个属性被正式命名为 Object.getPrototypeOf()),它指向该对象的原型。
  2. 原型的 __proto__ 属性又指向其原型的原型,这样就形成了一个链式结构。
  3. 当访问一个对象的属性或方法时,如果该对象自身没有这个属性或方法,JavaScript 引擎会检查该对象的原型是否有这个属性或方法。
  4. 如果原型中有这个属性或方法,则直接使用它。如果没有,则继续查找原型的原型,直到找到为止,或者达到原型链的顶端。
  5. 原型链的顶端通常是 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 对象既可以调用 AnimalsayName 方法,也可以调用 Dogbark 方法。

异步编程

异步编程是一种编程范式,它允许程序在等待某些操作完成(如 I/O 操作、网络请求等)时继续执行其他任务。在 JavaScript 中,异步编程尤其重要,因为 JavaScript 是一种单线程语言,意味着它一次只能执行一个任务。为了克服这个限制,JavaScript 采用了事件驱动和异步编程的机制。
在 JavaScript 中,异步编程主要通过以下几种方式实现:

  1. 回调函数(Callback Functions):回调函数是最基本的异步编程技术。函数作为参数传递给其他函数,当异步操作完成时,会被调用。例如,setTimeoutXMLHttpRequest 使用回调函数来处理异步操作。
setTimeout(function() {
  console.log('Async operation completed');
}, 1000);
  1. 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));
  1. async/await 语法:ES2017 引入了 asyncawait 关键字,它们使得异步代码看起来和同步代码更加相似,更易于编写和理解。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 能够高效地处理复杂的应用程序,如网页、服务器和网络应用。

浏览器缓存

浏览器常见的储存方式包括:CookieLocal StorageSessionSttorageIndexedDB

  • Cookies
    • 主要用于与服务端通信
    • 存储量小
  • Local Storage
    • 存储量相较于 Cookies 更大
    • 只能存储字符串
  • Session Storage
    • 只存在与当前 Session、关闭浏览器就丢失了
    • 其他与 Local Storage 一样
  • IndexedDB
    • 相当于浏览器上的 SQL 数据库

Webpack 打包

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
Webpack 的核心概念是将项目分割成多个小模块,然后将这些模块按照依赖关系打包成大的区块。每个模块或区块都是一个文件,Webpack 负责将这些文件合并成一个或多个打包文件(bundle),通常是一个 JavaScript 文件。


总结,在本篇文章中,我们首先了解了浏览器的工作原理及常用组件,然后在此基础上学习了 Javascript 常用的数据类型及控制流。
我们会发现,一些常见的概念,比如匿名函数、闭包与Python语法是很相像的,因此触类旁通,Javascript并不难,后续我们可以通过细致的分析学习理解如何进行Javascript逆向。

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值