js 全称 JavaScript(简称 JS),是 一种运行在浏览器和服务器端的脚本语言。
-
用途:
-
浏览器端交互(如:点击按钮出现弹窗)
-
网页动态内容渲染(如:淘宝、京东页面更新)
-
服务端开发(通过 Node.js)
-
自动化测试、爬虫、桌面程序、游戏开发等
-
-
运行环境:主要在浏览器(如 Chrome)中,也可在 Node.js 环境中运行
一、变量声明:var
/ let
/ const
的区别
特性 | var | let | const |
---|---|---|---|
声明作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
是否变量提升 | 是(初始化为 undefined ) | 是(但不能访问) | 是(但不能访问) |
是否可重复声明 | 可以 | 不可以(同一作用域) | 不可以(同一作用域) |
是否可修改值 | 可以 | 可以 | 不可以(但对象可改属性) |
是否有暂时性死区 | 没有 | 有 | 有 |
1. var
特点(老语法)
函数作用域
function test() {
if (true) {
var a = 1;
}
console.log(a); // 输出 1,因为 var 没有块作用域
}
变量提升(Hoisting)
console.log(a); // 输出 undefined(变量被提升但没有赋值)
var a = 10;
可以重复声明
var x = 5;
var x = 10; // 合法
2. let
特点(推荐使用)
块级作用域
if (true) {
let a = 2;
}
console.log(a); // 报错,a 不存在于 if 外部作用域
暂时性死区(TDZ)
变量在声明之前不可访问,称为“暂时性死区”:
console.log(b); // 报错 ReferenceError
let b = 3;
不能重复声明
let y = 1;
let y = 2; // 报错:Identifier 'y' has already been declared
3. const
特点(用于声明常量)
必须初始化
const z; // 报错,必须赋初值
不可重新赋值
const num = 5;
num = 10; // 报错
对象/数组可变
const
只是不能重新赋值引用地址,但对象或数组的内部内容是可以变的:
const obj = { a: 1 };
obj.a = 100; // 合法
obj = {}; // 报错
使用建议
-
使用
let
:大多数可变变量情况都推荐用let
-
使用
const
:不打算修改的变量(如配置、函数、类等)用const
-
避免使用
var
:容易出问题,只在老代码或兼容性需要时使用
举例
function example() {
console.log(a); // undefined(变量提升)
console.log(b); // 报错(暂时性死区)
var a = 1;
let b = 2;
}
二、数据类型
1. 基本类型(Primitive Types)——值直接存在栈中
-
Number
:数字(整数、小数、NaN、Infinity) -
String
:字符串 -
Boolean
:布尔值(true / false) -
undefined
:变量声明了但没有赋值 -
null
:表示“空对象”指针(历史遗留问题) -
Symbol
:唯一值(用于对象属性标识符,ES6) -
BigInt
:支持大整数(如 2^53 以上,ES2020)
2. 引用类型(Reference Types)——存在堆中,变量保存引用地址
-
Object
:普通对象{}
,数组[]
,函数function(){}
,日期对象等 -
引用类型访问的是真实对象的内存地址(指针),而非值本身。
3. typeof 操作符(用于判断类型)
typeof 123 // "number"
typeof 'abc' // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object"(历史 bug)
typeof Symbol() // "symbol"
typeof BigInt(10) // "bigint"
typeof {} // "object"
typeof [] // "object"(数组本质也是对象)
typeof function(){} // "function"(是特殊的对象)
4. 类型判断更精准的方法
使用 Object.prototype.toString.call()
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(new Date) // "[object Date]"
Object.prototype.toString.call(() => {}) // "[object Function]"
判断数组
Array.isArray([]) // true
typeof [] === "object" // 仅说明它是对象,不准确
5. 类型转换
1)转换为字符串(String)
-
自动转换:模板字符串或字符串拼接
-
手动转换:
String(123); // "123"
(123).toString(); // "123"
2)转换为数字(Number)
-
自动转换:参与运算时,如
1 + '2'
→ 字符串拼接
表达式类型 | 转换行为 | 示例 | 结果 |
---|---|---|---|
+ 运算符,有字符串 | → 字符串拼接 | 1 + '2' | '12' |
其他运算符(-*/% ) | → 转为数字再运算 | '5' - 2 | 3 |
比较(== ) | → 双方尝试类型转换 | '5' == 5 | true |
-
手动转换:
Number("123"); // 123
+"123"; // 123
parseInt("123px"); // 123
parseFloat("3.14"); // 3.14
值 | Number(...) 结果 |
---|---|
true | 1 |
false | 0 |
null | 0 |
undefined | NaN |
"123" | 123 |
"abc" | NaN |
3)转换为布尔值(Boolean)
这些值转换为 false(“假值”):
-
false
-
0
-
NaN
-
""
(空字符串) -
null
-
undefined
Boolean(0); // false
Boolean("hello"); // true
!!"abc" // true(常用于强制布尔类型)
6. 值类型 vs 引用类型的比较方式
基本类型:比较值
let a = 1, b = 1;
console.log(a === b); // true
引用类型:比较地址
let obj1 = {x: 1};
let obj2 = {x: 1};
console.log(obj1 === obj2); // false(不是同一个引用)
let obj3 = obj1;
console.log(obj1 === obj3); // true(同一引用)
举例
console.log(typeof null); // "object"(历史遗留问题)
console.log([] == false); // true(空数组转为布尔值是 true,但参与 == 运算先转为数字,[] -> 0)
console.log({} + []); // "[object Object]"({} 被当成代码块)
三、运算符
1. 算术运算符(Arithmetic Operators)
运算符 | 含义 | 示例 | 结果 |
---|---|---|---|
+ | 加法/拼接 | 1 + 2 | 3 |
- | 减法 | 5 - 3 | 2 |
* | 乘法 | 2 * 4 | 8 |
/ | 除法 | 10 / 2 | 5 |
% | 取模 | 10 % 3 | 1 |
** | 幂运算 | 2 ** 3 | 8 |
++ | 自增 | x++ | 先返回 x,再加 1 |
-- | 自减 | --x | 先减 1,再返回值 |
注意:+
用于字符串拼接时,"abc" + 1
→ "abc1"
2. 逻辑运算符(Logical Operators)
运算符 | 含义 | 示例 | 结果 |
---|---|---|---|
&& | 且(与) | a && b | 如果 a 为假,返回 a,否则返回 b |
` | ` | 或(或) | |
! | 非(取反) | !true | false |
常用于短路逻辑判断:
let x = null;
let y = x || "默认值"; // y === "默认值"
逻辑中也常用于函数执行:
let isReady = true;
isReady && doSomething(); // 如果为 true,才执行函数
3. 位运算符(Bitwise Operators)
JavaScript 内部对数字是以 32 位有符号整数 形式处理位运算的。
运算符 | 名称 | 示例 | 描述 |
---|---|---|---|
& | 按位与(AND) | 5 & 3 | 0101 & 0011 = 0001 = 1 |
` | ` | 按位或(OR) | `5 |
^ | 按位异或(XOR) | 5 ^ 3 | 0101 ^ 0011 = 0110 = 6 |
~ | 按位非(NOT) | ~5 | ~00000101 = 11111010 = -6 |
<< | 左移 | 3 << 1 | 00000011 << 1 = 00000110 = 6 |
>> | 右移(带符号) | 6 >> 1 | 00000110 >> 1 = 00000011 = 3 |
>>> | 无符号右移 | -1 >>> 1 | 转为 32 位无符号后右移一位 |
位运算常用于加密逆向:
异或加密(XOR)
let data = 0x45;
let key = 0xAB;
let encrypted = data ^ key;
let decrypted = encrypted ^ key; // 再次异或同一个 key 可还原原文
掩码提取(bitmask)
提取二进制中的某几位:
let val = 0b10110101;
let mask = 0b00001111;
let result = val & mask; // 提取低四位
位操作常用于“位图”算法、“状态标记”、“加密算法”
比如:
// 判断第 3 位是否为 1
if (value & (1 << 2)) {
// 第 3 位是 1
}
示例
某网站加密代码片段
function encode(str) {
let res = "";
for (let i = 0; i < str.length; i++) {
// 取出第 i 位字符的编码, 与 0x12(18)做异或加密, 再转回字符, 拼接到结果字符串中
res += String.fromCharCode(str.charCodeAt(i) ^ 0x12); // 每个字符异或处理
}
return res;
}
这种操作常见于:
-
登录数据加密
-
参数混淆
-
JS 中的行为分析与特征码加密
四、条件与循环
1. if / else if / else
条件语句
基本结构:
if (条件) {
// 条件为 true 时执行
} else if (另一个条件) {
// 另一个条件为 true 时执行
} else {
// 所有条件都不成立时执行
}
示例:
let score = 85;
if (score >= 90) {
console.log("优秀");
} else if (score >= 60) {
console.log("及格");
} else {
console.log("不及格");
}
注意:
-
条件表达式会被隐式转换为布尔值。
-
可与
==
、===
、&&
、||
、三元运算符组合使用。
2. switch
多条件判断(适用于“离散值分支”)
基本语法:
switch (变量) {
case 值1:
// 执行语句
break;
case 值2:
// 执行语句
break;
default:
// 都不匹配时执行
}
示例:
let key = "b";
switch (key) {
case "a":
console.log("选择了 A");
break;
case "b":
console.log("选择了 B");
break;
default:
console.log("未知选择");
}
注意:
-
===
严格比较,switch(1)
不等于case '1'
-
忘记写
break
会**“穿透”到下一个 case**
3. for
循环(结构最常见)
基本结构:
for (初始化; 条件; 更新) {
// 每次循环执行内容
}
示例:
for (let i = 0; i < 5; i++) {
console.log("i =", i);
}
应用:遍历数组
let arr = ["a", "b", "c"];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
4. while
循环(先判断条件,再执行)
while (条件) {
// 条件为 true 时执行
}
示例:
let i = 0;
while (i < 3) {
console.log(i);
i++;
}
5. do...while
(至少执行一次)
do {
// 先执行一次
} while (条件);
示例:
let i = 0;
do {
console.log(i);
i++;
} while (i < 3);
6. for...in
和 for...of
语法 | 用于 | 遍历的是 |
---|---|---|
for...in | 对象、数组 | “键名”(字符串) |
for...of | 可迭代对象(数组) | “键值”(真正的值) |
1)for...in
(遍历对象属性)
let obj = {a: 1, b: 2};
for (let key in obj) {
console.log(key, obj[key]); // a 1 b 2
}
2)for...of
(遍历数组值)
let arr = ['x', 'y'];
for (let val of arr) {
console.log(val); // x y
}
7. 控制语句:break
、continue
语句 | 含义 | 示例 |
---|---|---|
break | 中断整个循环 | if (i === 3) break; |
continue | 跳过本次,继续循环 | if (i === 3) continue; |
8. 三元运算符(简洁的 if...else
)
条件 ? 表达式1 : 表达式2;
示例:
let age = 18;
let type = age >= 18 ? "成人" : "未成年";
9. 逆向中真实常见结构还原
很多加密/混淆代码里,条件和循环常被写得很复杂,例如:
(function(){
var i = 0;
while(true){
switch(i++){
case 0:
a();
break;
case 1:
b();
break;
case 2:
return;
}
}
})();
这其实是反控制流(控制流平坦化),还原后就能看出执行顺序是:a() → b() → return
五、函数与作用域
1. 函数的几种声明方式
函数声明(Function Declaration)
function sayHi(name) {
return "Hi, " + name;
}
特点:会提升(Hoisting),可以在定义前调用。
函数表达式(Function Expression)
const sayHi = function(name) {
return "Hi, " + name;
};
特点:不会提升,只能在定义后使用。
箭头函数(Arrow Function)
const sayHi = (name) => "Hi, " + name;
特点:
-
没有自己的
this
、arguments
。 -
更简洁,常用于回调、闭包场景。
2. 函数参数与返回值
function sum(a, b = 0) {
return a + b;
}
console.log(sum(3)); // 3
JS 支持默认参数、可选参数(未传为 undefined)。
3. 作用域与作用域链
作用域就是变量的可访问范围。
类型 | 说明 |
---|---|
全局作用域 | 全文件可访问 |
函数作用域 | 函数内部有效 |
块级作用域 | let /const 支持的代码块内 |
示例:
let a = 1;
function test() {
let b = 2;
console.log(a); // 可以访问外部变量 a
}
console.log(b); // 报错:b is not defined
作用域链(Scope Chain)
函数在定义时就确定了作用域链,查找变量是“由内向外,一层层查找”。
let a = 1;
function outer() {
let b = 2;
function inner() {
let c = 3;
console.log(a, b, c); // 都能访问
}
inner();
}
4. 闭包(Closure)
函数访问其外部作用域的变量,就形成了闭包。
function outer() {
let counter = 0;
return function() {
counter++;
return counter;
};
}
const add = outer();
console.log(add()); // 1
console.log(add()); // 2
闭包 = 函数 + 它“定义时”能访问到的外部变量环境
一步步拆解:
1)定义阶段:outer()
定义了一个“返回函数”的函数。此时那个 return function()
就是闭包函数,它“捕获”了 counter
这个外部变量。
2)执行 outer()
,返回一个函数:但是!它不仅仅是函数,它还带着一个“记住了 counter = 0 的独立环境”。也就是说,这个函数“记住”了它当初在哪个作用域里被创建 —— 那个作用域中 counter = 0
。
3)调用 add()
第一次:执行时闭包访问到了 counter++
,结果变成 1,返回了 1。
4)再次调用 add()
:因为那个 counter
并没有消失,还存在于闭包中,所以继续自增。
特点:
-
counter
一直被内部函数引用,不会被释放。 -
每次调用
outer()
都是一个新的闭包。
闭包用途
用途 | 示例 |
---|---|
保持变量不被销毁 | 上面的 counter 示例 |
封装私有变量(模拟私有) | 不暴露内部变量 |
工厂函数、缓存、记忆化 | function memoized(){} |
逆向分析中的混淆函数 | _0xabc = function() { return ...; } 中保留状态 |
5. 立即执行函数 IIFE(立即执行函数表达式)
(function(){
console.log("立即执行");
})();
常用于封装变量、创建闭包,防止污染全局。
6. JS 逆向中的应用场景
混淆代码结构(多见于 packer / eval 还原)
(function(){
var _secret = "abc";
window.getSecret = function() {
return _secret;
};
})();
_secret
是闭包私有变量,外部不能直接访问,但能通过暴露的函数调用。
利用闭包保存状态(如加密 Key、动态参数)
var keyGen = (function(){
var key = 12345;
return function() {
return key ^ 0xdeadbeef;
}
})();
六、this、箭头函数、call/apply/bind
this
是 运行时绑定的上下文对象,指的是“当前执行环境下的拥有者”。
不是“函数在哪定义的决定的”,而是“函数如何被调用的”决定的。
1. this与函数的关系
-
普通函数:
this
指向调用者。 -
箭头函数:
this
继承外层作用域(定义时决定)。 -
全局函数中:浏览器下
this === window
。
场景 | this 指向 | 示例 |
---|---|---|
默认绑定 | 浏览器中为 window ,严格模式是 undefined | 普通函数直接调用 |
隐式绑定 | 谁调用函数,this 就是谁 | obj.func() ,this === obj |
显式绑定(call/apply) | 手动指定 this | func.call(obj) |
new绑定(构造函数) | 指向新创建的实例对象 | new Foo() 中 this = 实例对象 |
默认绑定
function show() {
console.log(this);
}
show(); // 浏览器中输出 window,严格模式下是 undefined
隐式绑定(最常见)
const obj = {
name: "Tom",
sayHi: function() {
console.log(this.name);
}
};
obj.sayHi(); // 输出 "Tom",this === obj
注意:如果脱离对象调用,this 就变了:
const fn = obj.sayHi;
fn(); // this 指向 window(默认绑定)
显式绑定(逆向中常见)
function greet() {
console.log(this.lang);
}
const obj = { lang: "JS" };
greet.call(obj); // 输出 JS,this 被指定为 obj
new 绑定
function Person(name) {
this.name = name;
}
const p = new Person("Alice");
console.log(p.name); // Alice
箭头函数:this
是静态的
const obj = {
name: "Tom",
sayHi: () => {
console.log(this.name);
}
};
obj.sayHi(); // undefined,this 不是 obj,而是外层作用域(window)
正确方式(用 function 保证 this 指向 obj):
const obj = {
name: "Tom",
sayHi() {
console.log(this.name); // "Tom"
}
};
调用方式 | this 指向 | 示例 |
---|---|---|
默认绑定 | 浏览器中是 window ,严格模式是 undefined | func() |
隐式绑定 | 谁调用函数,this 就是那个对象 | obj.func() |
显式绑定 | call / apply / bind 明确指定 | func.call(obj) |
new 绑定 | this 指向构造出的新对象 | new Foo() |
箭头函数 | 没有自己的 this ,继承定义时的作用域 | ()=>this |
2. 箭头函数
箭头函数最大的特点:
-
没有自己的
this
-
this
来自定义时的上下文环境(向上找第一个非箭头函数的this
) -
适合在回调、定时器、闭包中使用
示例一:普通函数 vs 箭头函数
const obj = {
name: "Tom",
sayHi: function() {
console.log(this.name); // Tom
},
sayHiArrow: () => {
console.log(this.name); // undefined,this来自 window
}
};
obj.sayHi(); // Tom
obj.sayHiArrow(); // undefined
示例二:箭头函数捕获外部 this
function Person() {
this.age = 0;
setInterval(() => {
this.age++; // this === Person 的实例
console.log(this.age);
}, 1000);
}
new Person();
箭头函数使 this
保持为构造函数内部的对象,避免了 setInterval 中的 this 指向 window 的问题。
-
普通函数:谁喊它,它就听谁的(
this
会变) -
箭头函数:一出生就决定跟谁混(
this
被词法绑定,不变)
箭头函数的 this
取决于定义时的外部上下文,不会被调用方式改变
const f = encrypt.bind({ secret: "xyz" });
console.log(f("123")); // 仍然输出 123abc
3. call
/ apply
/ bind
区别详解
call
、apply
、bind
都是 强制修改函数内部的 this
指向 的方法。
方法 | 参数形式 | 是否立即调用 | 改变 this |
---|---|---|---|
call | 逐个传参,如:fn.call(this, a, b) | 是 | 是 |
apply | 数组传参,如:fn.apply(this, [a, b]) | 是 | 是 |
bind | 返回新函数,如:let f = fn.bind(this) | 否 | 是 |
示例:
function greet(lang1, lang2) {
console.log(this.name + " speaks " + lang1 + " and " + lang2);
}
const person = { name: "Alice" };
greet.call(person, "JS", "Python");
greet.apply(person, ["JS", "Python"]);
const newGreet = greet.bind(person, "JS");
newGreet("Go"); // Alice speaks JS and Go
apply 实际应用举例:
function wrapper() {
console.log('wrapper this:', this);
func.apply(this, arguments); // this 就是 wrapper 的 this
}
function func(a, b) {
console.log('func this:', this);
console.log('a + b =', a + b);
}
// 绑定一个对象调用
const obj = {
name: '张三',
callWrapper: wrapper
};
obj.callWrapper(1, 2); // func 会以 obj 为 this 被调用
输出:
wrapper this: { name: '张三', callWrapper: f }
func this: { name: '张三', callWrapper: f }
a + b = 3
表达式 | 含义 |
---|---|
this | 当前函数的上下文调用者 |
arguments | 当前函数收到的所有参数 |
func.apply(this, arguments) | 把当前函数的参数原样转发给 func ,并让 func 也在当前 this 下执行 |
4. 应用场景详解
模拟继承(构造函数继承时用 call)
function Animal(name) {
this.name = name;
}
function Dog(name, breed) {
Animal.call(this, name); // this 指向 Dog 实例
this.breed = breed;
}
防止丢失 this
const obj = {
val: 42,
getVal: function() {
return this.val;
}
};
const f = obj.getVal;
console.log(f()); // undefined,this 被丢了
// 用 bind 修复:
const boundF = obj.getVal.bind(obj);
console.log(boundF()); // 42
立即执行箭头函数 vs bind
(() => {
console.log(this); // window 或 global
})();
(function() {
console.log(this); // window 或 global
}).call({ a: 1 }); // 显示为 {a: 1}
5. JS 逆向中常见 this 用法(举例)
混淆代码通过 bind 固定 this(防反调试)
(function(){
var _this = this;
var keyFunc = function() {
return _this.secret; // 使用外层 this
}.bind(this);
})();
加密函数写成箭头函数,用外层上下文(难以 hook)
const encrypt = (() => {
const secret = "abc";
return (input) => input + secret;
})();
七、异常处理:try/catch
、throw
1. 基本语法结构
try {
// 可能出错的代码
} catch (err) {
// 捕获异常
} finally {
// 总会执行的代码(可选)
}
执行流程图:
-
执行
try {...}
里的代码 -
如果出现错误,立刻跳到
catch(err)
,传入错误对象 -
无论有没有错误,都会执行
finally
(如果写了)
2. try / catch
示例详解
示例 1:正常捕获异常
try {
let a = b + 1; // b 未定义,抛出 ReferenceError
} catch (e) {
console.log("捕获到错误:", e.message); // 捕获执行
}
示例 2:不会捕获语法错误(编译时错误)
try {
eval("var a = ;"); // 语法错误,仍可被 try-catch 捕获
} catch (e) {
console.log("捕获到语法错误:", e.message);
}
说明:try
是运行时捕获,语法写错直接报错就挂了,不会走进 try
。
3. throw
手动抛出异常
可以使用 throw
抛出任何类型的值:
throw "我是一个字符串错误";
throw 123;
throw new Error("这是一个Error对象"); // 推荐
推荐抛出 Error
实例,语义更清晰,调试更容易。
自定义错误类型:
class MyError extends Error {
constructor(message) {
super(message);
this.name = "MyError";
}
}
throw new MyError("这是一个自定义错误");
4. finally
总是执行(即使中途 return)
function test() {
try {
return 1;
} catch (e) {
return 2;
} finally {
console.log("finally 总会执行");
}
}
test(); // 输出 finally,然后返回 1
5. 常见的错误类型(系统内置)
错误类型 | 描述 |
---|---|
ReferenceError | 访问未定义变量 |
TypeError | 变量类型错误,如 null.foo() |
SyntaxError | 代码语法错误(eval 可捕获) |
RangeError | 值超出合法范围,如栈溢出 |
URIError | URI 处理函数使用错误(如 decodeURI ) |
EvalError | eval() 使用不当(已较少见) |
6. 实际用途:异常处理的高级应用场景
处理用户输入或接口错误
function parseJSON(str) {
try {
return JSON.parse(str);
} catch (e) {
return {}; // 防止接口返回异常
}
}
逆向分析中:构造异常流 + 防调试
某些混淆代码使用 try/catch
故意制造异常阻止调试:
try {
(function(){
return (1).constructor.constructor("debugger")();
})();
} catch(e) {
// 捕捉异常,继续执行
}
反调试代码示例:
try {
Object.defineProperty(window, 'console', {
get: function() {
throw new Error("调试被禁止!");
}
});
} catch(e) {
// 黑盒继续运行
}
这段代码用 get
拦截访问 console
,用 throw
抛异常干扰开发者。