关于 js:1. 基础语法与核心概念

#王者杯·14天创作挑战营·第1期#

js 全称 JavaScript(简称 JS),是 一种运行在浏览器和服务器端的脚本语言。

  • 用途

    • 浏览器端交互(如:点击按钮出现弹窗)

    • 网页动态内容渲染(如:淘宝、京东页面更新)

    • 服务端开发(通过 Node.js)

    • 自动化测试、爬虫、桌面程序、游戏开发等

  • 运行环境:主要在浏览器(如 Chrome)中,也可在 Node.js 环境中运行


一、变量声明:var / let / const 的区别

特性varletconst
声明作用域函数作用域块级作用域块级作用域
是否变量提升 是(初始化为 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' - 23
比较(==→ 双方尝试类型转换'5' == 5true
  • 手动转换:

Number("123");     // 123
+"123";            // 123
parseInt("123px"); // 123
parseFloat("3.14"); // 3.14
Number(...) 结果
true1
false0
null0
undefinedNaN
"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 + 23
-减法5 - 32
*乘法2 * 48
/除法10 / 25
%取模10 % 31
**幂运算2 ** 38
++自增x++先返回 x,再加 1
--自减--x先减 1,再返回值

注意:+ 用于字符串拼接时,"abc" + 1"abc1"

2. 逻辑运算符(Logical Operators)

运算符含义示例结果
&&且(与)a && b如果 a 为假,返回 a,否则返回 b
``或(或)
!非(取反)!truefalse

常用于短路逻辑判断

let x = null;
let y = x || "默认值"; // y === "默认值"

逻辑中也常用于函数执行:

let isReady = true;
isReady && doSomething(); // 如果为 true,才执行函数

3. 位运算符(Bitwise Operators)

JavaScript 内部对数字是以 32 位有符号整数 形式处理位运算的。

运算符名称示例描述
&按位与(AND)5 & 30101 & 0011 = 0001 = 1
``按位或(OR)`5
^按位异或(XOR)5 ^ 30101 ^ 0011 = 0110 = 6
~按位非(NOT)~5~00000101 = 11111010 = -6
<<左移3 << 100000011 << 1 = 00000110 = 6
>>右移(带符号)6 >> 100000110 >> 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...infor...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. 控制语句:breakcontinue

语句含义示例
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;

特点:

  • 没有自己的 thisarguments

  • 更简洁,常用于回调、闭包场景。

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)手动指定 thisfunc.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,严格模式是 undefinedfunc()
隐式绑定谁调用函数,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 区别详解

callapplybind 都是 强制修改函数内部的 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/catchthrow

1. 基本语法结构

try {
  // 可能出错的代码
} catch (err) {
  // 捕获异常
} finally {
  // 总会执行的代码(可选)
}

执行流程图:

  1. 执行 try {...} 里的代码

  2. 如果出现错误,立刻跳到 catch(err),传入错误对象

  3. 无论有没有错误,都会执行 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值超出合法范围,如栈溢出
URIErrorURI 处理函数使用错误(如 decodeURI
EvalErroreval() 使用不当(已较少见)

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 抛异常干扰开发者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值