深入理解JavaScript执行机制:从一道经典面试题说起

深入理解JavaScript执行机制:从一道经典面试题说起

本文将通过一道经典面试题,深入剖析JavaScript的执行机制,包括调用栈、执行上下文、变量环境、词法环境、编译阶段和执行阶段等核心概念。

Event-loop: JS执行机制-event loop

目录

  1. 引子:一道看似简单的题目
  2. JavaScript执行机制的核心概念
  3. 编译阶段:代码执行前的准备
  4. 执行阶段:逐行运行代码
  5. 调用栈的变化过程
  6. 完整的执行流程可视化
  7. 深度思考与扩展
  8. 总结与实战建议

一、引子:一道看似简单的题目

先看这道题,你能正确说出输出吗?

javascript 复制代码
var a = 1;

function fn(a) {
    console.log(a);
    var a = 2;
    function a() {};
    console.log(a);
}

fn(3);
console.log(a);

输出结果:

csharp 复制代码
[Function: a]
2
1

如果你答错了,或者不清楚为什么,那么这篇文章将帮助你彻底理解JavaScript的执行机制。


二、JavaScript执行机制的核心概念

2.1 执行上下文(Execution Context)

执行上下文是JavaScript代码执行的环境,它包含了代码执行所需的所有信息。

javascript 复制代码
ExecutionContext = {
  // 词法环境:存储 let/const/函数声明
  LexicalEnvironment: {
    EnvironmentRecord: { /* 标识符绑定 */ },
    outer: <外部环境引用>,
    ThisBinding: <this值>
  },
  
  // 变量环境:存储 var 声明
  VariableEnvironment: {
    EnvironmentRecord: { /* var变量 */ },
    outer: <外部环境引用>,
    ThisBinding: <this值>
  }
  
  // 可执行的代码
}
执行上下文的类型
  1. 全局执行上下文:代码运行时首先创建,全局只有一个
  2. 函数执行上下文:每次函数调用时创建,可以有无数个
  3. Eval执行上下文:eval函数内部的代码(不推荐使用)

2.2 调用栈(Call Stack)

调用栈是一个后进先出(LIFO)的数据结构,用于管理执行上下文。

scss 复制代码
执行前:[Global EC]
调用fn(3):[Global EC, fn EC]
fn执行完:[Global EC]

2.3 词法环境 vs 变量环境

特性 词法环境 变量环境
存储内容 letconst、函数声明 var 声明
块级作用域 ✅ 支持 ❌ 不支持
暂时性死区 ✅ 存在 ❌ 不存在
初始化 不初始化 初始化为undefined

2.4 编译阶段 vs 执行阶段

JavaScript代码执行分为两个阶段:

  1. 编译阶段(创建阶段)

    • 创建执行上下文
    • 处理变量声明和函数声明
    • 确定作用域链
    • 确定this指向
  2. 执行阶段

    • 逐行执行代码
    • 变量赋值
    • 函数调用
    • 表达式计算

三、编译阶段:代码执行前的准备

让我们从编译阶段开始,看看JavaScript引擎做了什么。

3.1 全局执行上下文的创建

javascript 复制代码
var a = 1;

function fn(a) {
    console.log(a);
    var a = 2;
    function a() {};
    console.log(a);
}

编译阶段(全局):

javascript 复制代码
GlobalExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      fn: <function fn>  // 函数声明完整提升
    },
    outer: null,  // 全局作用域外部为null
    ThisBinding: <global object>
  },
  
  VariableEnvironment: {
    EnvironmentRecord: {
      a: undefined  // var声明提升,初始化为undefined
    },
    outer: null,
    ThisBinding: <global object>
  }
}

关键点:

  • var a 声明被提升到变量环境,初始化为undefined
  • function fn 完整提升到词法环境,可以在声明前调用
  • ✅ 此时还没有执行任何赋值操作

3.2 函数执行上下文的创建

当执行到 fn(3) 时,创建函数执行上下文。

javascript 复制代码
function fn(a) {
    console.log(a);
    var a = 2;
    function a() {};
    console.log(a);
}

fn(3);  // 调用时触发编译

函数执行上下文的创建过程:

步骤1:处理形参
javascript 复制代码
FunctionEC = {
  VariableEnvironment: {
    EnvironmentRecord: {
      a: 3  // ① 参数赋值:a = 3
    }
  }
}
步骤2:处理函数声明(优先级最高!)
javascript 复制代码
FunctionEC = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      a: <function a>  // ② 函数声明:a = function a() {}
    }
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      // a: 3 被覆盖了!
    }
  }
}

⚠️ 关键:函数声明会覆盖参数!

步骤3:处理var声明
javascript 复制代码
// var a = 2;
// 由于 a 已经在词法环境中存在(函数声明),var a 被忽略
// 只保留赋值操作(a = 2)到执行阶段

最终的编译结果:

javascript 复制代码
FunctionExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      a: <function a>  // 函数声明
    },
    outer: <Global Lexical Environment>,
    ThisBinding: <global object>
  },
  
  VariableEnvironment: {
    EnvironmentRecord: {
      // var a 的声明被忽略(a已存在)
    },
    outer: <Global Variable Environment>,
    ThisBinding: <global object>
  }
}

3.3 提升优先级规则

csharp 复制代码
函数声明 > 函数参数 > var声明

详细规则:

  1. 函数声明:最高优先级,完整提升
  2. 函数参数:在函数执行上下文中,参数先被赋值
  3. var声明:如果变量名已存在,声明被忽略,只保留赋值
  4. let/const:不提升初始化,存在暂时性死区

四、执行阶段:逐行运行代码

编译阶段完成后,进入执行阶段。

4.1 全局代码执行

javascript 复制代码
// 此时的调用栈:[Global EC]

var a = 1;  // 执行赋值:GlobalEC.VariableEnvironment.a = 1
// 从 undefined 变为 1

function fn(a) { ... }  // 已在编译阶段处理,跳过

fn(3);  // 函数调用,创建新的执行上下文

全局变量a的变化:

ini 复制代码
编译阶段:a = undefined
执行阶段:a = 1  ← 赋值操作

4.2 函数代码执行

调用 fn(3) 时,进入函数执行上下文。

调用栈变化:

css 复制代码
[Global EC] → [Global EC, fn EC]

此时fn执行上下文中的a:

javascript 复制代码
a = function a() {}  // 编译阶段的结果
第1行:console.log(a)
javascript 复制代码
console.log(a);  // 输出:[Function: a]

为什么输出函数?

  • 编译阶段,函数声明 function a() {} 已经覆盖了参数 a = 3
  • 此时 a 的值就是这个函数

变量a的状态:

ini 复制代码
编译阶段:a = 3 (参数)
        ↓
编译阶段:a = function a() {} (函数声明覆盖)
        ↓
执行到这里:a = function a() {}
第2行:var a = 2
javascript 复制代码
var a = 2;

这行代码的本质:

  • 编译阶段:var a 的声明已被忽略(a已存在)
  • 执行阶段:只执行赋值 a = 2

变量a的变化:

ini 复制代码
之前:a = function a() {}
执行赋值后:a = 2
第3行:function a() {}
javascript 复制代码
function a() {};

这行代码的处理:

  • 编译阶段已经完整处理
  • 执行阶段直接跳过(什么都不做)
第4行:console.log(a)
javascript 复制代码
console.log(a);  // 输出:2

为什么输出2?

  • var a = 2 已经将a的值改为2
  • 此时输出的就是数字2

变量a的完整生命周期:

css 复制代码
编译阶段:
  步骤1:a = 3 (参数)
  步骤2:a = function a() {} (函数声明覆盖)
  步骤3:var a 被忽略

执行阶段:
  第1行:console.log(a) → 输出 function a() {}
  第2行:a = 2 (赋值)
  第3行:function a() {} (跳过)
  第4行:console.log(a) → 输出 2

4.3 函数执行完毕

javascript 复制代码
fn(3);  // 函数执行完毕,出栈

调用栈变化:

css 复制代码
[Global EC, fn EC] → [Global EC]

函数执行上下文被销毁,局部变量a也随之消失。

4.4 继续执行全局代码

javascript 复制代码
console.log(a);  // 输出:1

为什么输出1?

  • 此时访问的是全局变量a
  • 全局变量a的值在执行 var a = 1 时被赋值为1
  • 函数内部的a已经随函数执行上下文销毁

五、调用栈的完整变化过程

让我们用可视化的方式看看调用栈的变化。

5.1 时间线视图

yaml 复制代码
时刻0:代码开始执行
┌─────────────────────────┐
│   Global EC             │
│   VariableEnvironment:  │
│     a: undefined        │
│   LexicalEnvironment:   │
│     fn: <function>      │
└─────────────────────────┘

时刻1:var a = 1 执行
┌─────────────────────────┐
│   Global EC             │
│   VariableEnvironment:  │
│     a: 1  ← 赋值        │
│   LexicalEnvironment:   │
│     fn: <function>      │
└─────────────────────────┘

时刻2:fn(3) 调用 - 创建函数执行上下文
┌─────────────────────────┐
│   fn EC                 │
│   LexicalEnvironment:   │
│     a: function a() {}  │ ← 编译阶段的结果
│   outer: Global EC      │
└─────────────────────────┘
┌─────────────────────────┐
│   Global EC             │
│     a: 1                │
└─────────────────────────┘

时刻3:第一个 console.log(a)
┌─────────────────────────┐
│   fn EC                 │
│     a: function a() {}  │ ← 输出这个
└─────────────────────────┘
┌─────────────────────────┐
│   Global EC             │
│     a: 1                │
└─────────────────────────┘
输出:[Function: a]

时刻4:var a = 2 执行(只执行赋值)
┌─────────────────────────┐
│   fn EC                 │
│     a: 2  ← 赋值改变    │
└─────────────────────────┘
┌─────────────────────────┐
│   Global EC             │
│     a: 1                │
└─────────────────────────┘

时刻5:第二个 console.log(a)
┌─────────────────────────┐
│   fn EC                 │
│     a: 2  ← 输出这个    │
└─────────────────────────┘
┌─────────────────────────┐
│   Global EC             │
│     a: 1                │
└─────────────────────────┘
输出:2

时刻6:fn执行完毕,出栈
┌─────────────────────────┐
│   Global EC             │
│     a: 1                │
└─────────────────────────┘

时刻7:最后的 console.log(a)
┌─────────────────────────┐
│   Global EC             │
│     a: 1  ← 输出这个    │
└─────────────────────────┘
输出:1

5.2 作用域链的查找过程

javascript 复制代码
function fn(a) {
    console.log(a);  // 在当前fn EC中查找a ✅ 找到
    var a = 2;
    console.log(a);  // 在当前fn EC中查找a ✅ 找到
}

fn(3);
console.log(a);  // 在Global EC中查找a ✅ 找到

查找规则:

  1. 首先在当前执行上下文的词法环境中查找
  2. 如果找不到,通过outer引用到外层环境查找
  3. 一直查找到全局执行上下文
  4. 如果还找不到,返回ReferenceError

作用域链:

sql 复制代码
fn EC → Global EC → null

六、完整的执行流程可视化

6.1 内存结构图

yaml 复制代码
┌───────────────────────────────────────────────────┐
│                     堆内存 (Heap)                  │
├───────────────────────────────────────────────────┤
│  0x001: function fn(a) { ... }                    │
│  0x002: function a() {}                            │
└───────────────────────────────────────────────────┘

┌───────────────────────────────────────────────────┐
│                 调用栈 (Call Stack)                │
├───────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────┐  │
│  │  Function EC (fn)                           │  │
│  │  ─────────────────────────────────────────  │  │
│  │  LexicalEnvironment:                        │  │
│  │    a: 0x002 → [赋值后] → 2                 │  │
│  │  outer: → Global EC                         │  │
│  └─────────────────────────────────────────────┘  │
│  ┌─────────────────────────────────────────────┐  │
│  │  Global EC                                  │  │
│  │  ─────────────────────────────────────────  │  │
│  │  VariableEnvironment:                       │  │
│  │    a: undefined → 1                         │  │
│  │  LexicalEnvironment:                        │  │
│  │    fn: 0x001                                │  │
│  └─────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────┘

6.2 完整的执行流程图

javascript 复制代码
开始
  ↓
┌────────────────────────────────────┐
│ 全局编译阶段                        │
│ - 创建Global EC                    │
│ - var a: undefined                 │
│ - function fn: <function>          │
└────────────────────────────────────┘
  ↓
┌────────────────────────────────────┐
│ 全局执行阶段                        │
│ - var a = 1 (赋值)                 │
│ - 调用 fn(3)                       │
└────────────────────────────────────┘
  ↓
┌────────────────────────────────────┐
│ 函数编译阶段                        │
│ - 创建fn EC                        │
│ - 参数: a = 3                      │
│ - 函数声明: a = function a() {}    │
│   (覆盖参数!)                      │
│ - var a 声明被忽略                 │
└────────────────────────────────────┘
  ↓
┌────────────────────────────────────┐
│ 函数执行阶段                        │
│ - console.log(a)                   │
│   → 输出 [Function: a]             │
│ - var a = 2 (赋值)                 │
│ - function a() {} (跳过)           │
│ - console.log(a)                   │
│   → 输出 2                         │
└────────────────────────────────────┘
  ↓
┌────────────────────────────────────┐
│ 函数执行完毕                        │
│ - fn EC 出栈销毁                   │
│ - 返回Global EC                    │
└────────────────────────────────────┘
  ↓
┌────────────────────────────────────┐
│ 继续全局执行                        │
│ - console.log(a)                   │
│   → 输出 1                         │
└────────────────────────────────────┘
  ↓
结束

七、深度思考与扩展

7.1 为什么要分编译阶段和执行阶段?

性能优化:

javascript 复制代码
function hotFunction() {
  // 这个函数被频繁调用
  let x = 1;
  let y = 2;
  return x + y;
}

// V8引擎的处理:
// 1. 第一次:解释执行(编译 → 执行)
// 2. 多次调用后:识别为热点代码
// 3. TurboFan优化:编译为优化的机器码
// 4. 后续调用:直接执行机器码(超快!)

错误提前发现:

javascript 复制代码
// 编译阶段就能发现语法错误
function test() {
  let x = 1;
  let x = 2;  // SyntaxError: Identifier 'x' has already been declared
}
// 不需要等到执行到这一行才报错

7.2 变量提升的设计初衷

函数提升的优点:

javascript 复制代码
// 可以先调用后声明,代码组织更灵活
init();
run();
cleanup();

function init() { /* ... */ }
function run() { /* ... */ }
function cleanup() { /* ... */ }

// 主逻辑在上方,实现细节在下方,可读性更好

var提升的问题:

javascript 复制代码
console.log(x);  // undefined(不会报错,容易产生bug)
var x = 1;

// 如果用let/const
console.log(y);  // ReferenceError(立即发现问题)
let y = 1;

ES6的改进:let/const

  • 引入暂时性死区(TDZ)
  • 强制先声明后使用
  • 减少隐藏bug

7.3 闭包与执行上下文的关系

javascript 复制代码
function createCounter() {
  let count = 0;  // 存储在createCounter的词法环境
  
  return function increment() {
    count++;
    console.log(count);
  };
}

const counter = createCounter();
counter();  // 1
counter();  // 2

// 为什么count没有被销毁?
// 因为increment函数的outer引用指向createCounter的词法环境
// 形成闭包,保持了对count的引用

闭包的本质:

sql 复制代码
increment EC
    ↓ outer
createCounter EC (已执行完,但未销毁)
    ↓ outer
Global EC

7.4 this绑定与执行上下文

javascript 复制代码
const obj = {
  name: 'obj',
  method: function() {
    console.log(this.name);
    
    function inner() {
      console.log(this.name);  // undefined (this指向global)
    }
    
    const arrow = () => {
      console.log(this.name);  // 'obj' (箭头函数继承外层this)
    };
    
    inner();
    arrow();
  }
};

obj.method();

this绑定时机:

  • 普通函数:执行时确定(动态绑定)
  • 箭头函数:定义时确定(词法绑定)

7.5 常见面试陷阱

陷阱1:函数声明的位置
javascript 复制代码
function fn(a) {
    console.log(a);  // ?
    var a = 2;
    console.log(a);  // ?
    function a() {};
    console.log(a);  // ?
}

fn(3);

// 输出:
// [Function: a]
// 2
// 2 ← 注意:不是函数!

// 原因:函数声明在编译阶段就处理了
// 执行阶段function a() {} 这一行什么都不做
陷阱2:重复声明
javascript 复制代码
function fn() {
    console.log(x);  // ?
    var x = 1;
    var x = 2;
    console.log(x);  // ?
}

fn();

// 输出:
// undefined
// 2

// 解析:
// 编译阶段:var x (只声明一次)
// 执行阶段:x = 1, x = 2 (两次赋值)
陷阱3:参数与var
javascript 复制代码
function fn(a) {
    console.log(a);  // ?
    var a;
    console.log(a);  // ?
}

fn(1);

// 输出:
// 1
// 1

// 解析:
// var a 的声明被忽略(a作为参数已存在)
// var a; 这行相当于什么都不做

7.6 与其他语言的对比

JavaScript(变量提升):

javascript 复制代码
console.log(x);  // undefined
var x = 1;

Java(不允许):

java 复制代码
System.out.println(x);  // 编译错误
int x = 1;

Python(不允许):

python 复制代码
print(x)  # NameError
x = 1

JavaScript的变量提升是一种特殊机制,理解它是掌握JS的关键。


八、总结与实战建议

8.1 核心概念总结

kotlin 复制代码
JavaScript执行机制
│
├─ 执行上下文
│  ├─ 全局执行上下文(只有一个)
│  ├─ 函数执行上下文(每次调用创建)
│  └─ Eval执行上下文(不推荐)
│
├─ 执行上下文的组成
│  ├─ 词法环境(LexicalEnvironment)
│  │  └─ 存储 let/const/函数声明
│  ├─ 变量环境(VariableEnvironment)
│  │  └─ 存储 var 声明
│  └─ this绑定
│
├─ 执行阶段
│  ├─ 编译阶段(创建阶段)
│  │  ├─ 创建执行上下文
│  │  ├─ 处理声明(提升)
│  │  ├─ 确定作用域链
│  │  └─ 确定this
│  └─ 执行阶段
│     ├─ 逐行执行代码
│     ├─ 变量赋值
│     └─ 函数调用
│
└─ 调用栈(Call Stack)
   └─ 管理所有执行上下文(LIFO)

8.2 提升规则总结

javascript 复制代码
// 提升优先级(从高到低)
1. 函数声明      → 完整提升
2. 函数参数      → 赋值
3. var声明       → 提升并初始化为undefined
4. let/const声明 → 提升但不初始化(TDZ)

// 覆盖规则
函数声明 > 参数 > var声明

8.3 变量a的完整生命周期(回到原题)

javascript 复制代码
var a = 1;

function fn(a) {
    console.log(a);
    var a = 2;
    function a() {};
    console.log(a);
}

fn(3);
console.log(a);

全局变量a:

ini 复制代码
编译:a = undefined
执行:a = 1
最终:a = 1

函数参数a:

ini 复制代码
编译:a = 3 (参数)
     ↓
     a = function a() {} (函数声明覆盖)
     ↓
     var a 被忽略
执行:a = function a() {} (第一个console.log)
     ↓
     a = 2 (赋值)
     ↓
     a = 2 (第二个console.log)

8.4 实战建议

1. 避免变量提升带来的问题
javascript 复制代码
// ❌ 不好的写法
console.log(x);
var x = 1;

// ✅ 好的写法
let x = 1;
console.log(x);
2. 使用let/const代替var
javascript 复制代码
// ❌ 不好的写法
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 输出:3 3 3

// ✅ 好的写法
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 输出:0 1 2
3. 函数声明 vs 函数表达式
javascript 复制代码
// ✅ 函数声明:可以先调用后声明
foo();
function foo() {}

// ❌ 函数表达式:必须先声明后调用
bar();  // TypeError
var bar = function() {};

// ✅ 正确写法
const bar = function() {};
bar();
4. 理解作用域链
javascript 复制代码
// 理解变量查找规则
function outer() {
  let x = 1;
  
  function inner() {
    let y = 2;
    console.log(x);  // 1 (沿作用域链向上查找)
    console.log(y);  // 2 (当前作用域)
  }
  
  inner();
  // console.log(y);  // ReferenceError (y不在作用域链上)
}
5. 利用闭包保存状态
javascript 复制代码
function createCounter() {
  let count = 0;
  
  return {
    increment() {
      return ++count;
    },
    decrement() {
      return --count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment());  // 1
console.log(counter.increment());  // 2
console.log(counter.getCount());   // 2

8.5 面试答题模板

问:请解释这段代码的输出结果

答题框架:

  1. 分析编译阶段

    • 识别所有声明
    • 确定提升规则
    • 说明优先级
  2. 分析执行阶段

    • 逐行执行
    • 说明每次赋值的影响
    • 解释输出结果
  3. 总结关键点

    • 强调核心概念
    • 说明易错点

示例回答:

这段代码涉及变量提升和函数声明提升。在编译阶段,全局的var a被提升并初始化为undefined,function fn被完整提升。当调用fn(3)时,创建函数执行上下文,参数a=3,但函数声明function a(){}的优先级更高,会覆盖参数。因此第一个console.log输出函数,var a = 2执行赋值后,第二个console.log输出2。最后回到全局作用域,输出全局的a=1。

8.6 延伸阅读

  1. V8引擎的优化技术

    • 隐藏类(Hidden Classes)
    • 内联缓存(Inline Caching)
    • TurboFan编译器
  2. 事件循环机制

    • 宏任务与微任务
    • Promise执行时机
    • async/await原理
  3. 内存管理

    • 垃圾回收算法
    • 内存泄漏排查
    • 性能优化
  4. ES6+新特性

    • 块级作用域
    • 箭头函数
    • 类和继承

结语

JavaScript的执行机制看似复杂,但只要理解了执行上下文调用栈编译阶段执行阶段这些核心概念,就能透彻理解代码的运行过程。

记住这几个关键点:

  1. 编译阶段:处理声明,建立环境
  2. 执行阶段:逐行执行,变量赋值
  3. 调用栈:管理执行上下文,后进先出
  4. 作用域链:变量查找的路径
  5. 提升规则:函数声明 > 参数 > var声明

掌握这些知识,不仅能轻松应对面试,更能写出高质量的JavaScript代码!


参考资源:


附录:完整的测试代码

javascript 复制代码
// ============================================
// 原始题目
// ============================================
var a = 1;

function fn(a) {
    console.log(a);
    var a = 2;
    function a() {};
    console.log(a);
}

fn(3);
console.log(a);

// 输出:
// [Function: a]
// 2
// 1

// ============================================
// 变体1:改变函数声明位置
// ============================================
function test1(a) {
    function a() {};  // 函数声明提升,位置不影响结果
    console.log(a);
    var a = 2;
    console.log(a);
}

test1(3);
// 输出:
// [Function: a]
// 2

// ============================================
// 变体2:多个函数声明
// ============================================
function test2(a) {
    function a() { return 1; };
    function a() { return 2; };  // 后声明的覆盖前面的
    console.log(a);
}

test2(3);
// 输出:
// [Function: a]  (return 2的那个)

// ============================================
// 变体3:使用let/const
// ============================================
function test3(a) {
    console.log(a);  // 3 (参数)
    // let a = 2;  // SyntaxError: Identifier 'a' has already been declared
    // const a = 2;  // SyntaxError: Identifier 'a' has already been declared
}

test3(3);

// ============================================
// 变体4:没有函数声明
// ============================================
function test4(a) {
    console.log(a);  // 3
    var a = 2;
    console.log(a);  // 2
}

test4(3);
// 输出:
// 3
// 2

运行上述代码,验证你的理解! 🚀

相关推荐
阿凡达蘑菇灯2 小时前
langgraph---条件边
开发语言·前端·javascript
San302 小时前
深入理解浏览器渲染流程:从HTML/CSS到像素的奇妙旅程
javascript·css·html
拖拉斯旋风2 小时前
深入理解 JavaScript 执行机制之V8引擎:从编译到执行的完整生命周期
javascript·面试
RAY_CHEN.2 小时前
vue递归组件-笔记
前端·javascript·vue.js
Mintopia3 小时前
🤖 具身智能与 WebAIGC 的融合:未来交互技术的奇点漫谈
前端·javascript·aigc
『 时光荏苒 』3 小时前
网页变成PDF下载到本地
前端·javascript·pdf·网页下载成
十一.3664 小时前
37-38 for循环
前端·javascript·html
艾小码4 小时前
为什么你的JavaScript代码总是出bug?这5个隐藏陷阱太坑了!
前端·javascript
JELEE.8 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery