AI全栈实战 | 2.1-03 JavaScript 核心:单线程+事件循环为什么不等同于多线程,原型链 vs Java 继承

上篇回顾 :2.1-02 把 CSS 布局从盒模型到 Flex/Grid 一次讲透,揭示「所有布局在解决空间分配矛盾」。本篇进入 JavaScript------前端的核心语言。对后端工程师来说,JS 最大的挑战不是语法(语法很简单),而是心智模型------原型链、闭包、this、事件循环,每一样都和 Java 截然不同。

一、开篇:一段让 Java 工程师困惑的 JS 代码

javascript 复制代码
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 输出:3 3 3(不是 0 1 2)

Java 工程师的第一反应:「为什么不是 0 1 2?循环不是顺序执行吗?」

答案是两个 JS 特性的叠加:var 的变量提升 + 事件循环把 setTimeout 推到循环结束后执行。此时 i 已经是 3。

改成 let 就好了:

javascript 复制代码
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 输出:0 1 2

let 是块级作用域,每次循环创建新的 i。

这个例子说明:不理解 JS 的作用域和事件循环,代码行为会完全违背直觉。本篇把 JS 核心特性与 Java 对照讲透。

二、原型链:JS 的继承 vs Java 的继承

2.1 Java 的类继承

java 复制代码
class Animal { void eat() {} }
class Dog extends Animal { void bark() {} }

Dog d = new Dog();
d.eat();   // 编译时确定,Animal 的方法
d.bark();  // Dog 的方法

Java 的继承是编译时确定的,基于类(class)。

2.2 JS 的原型继承

javascript 复制代码
function Animal() {}
Animal.prototype.eat = function() { console.log('eat'); };

function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.bark = function() { console.log('bark'); };

const d = new Dog();
d.eat();   // 沿原型链找到 Animal.prototype.eat
d.bark();  // Dog.prototype.bark

JS 的继承是运行时查找的,基于原型链(prototype chain)。

2.3 原型链查找过程

复制代码
d → Dog.prototype → Animal.prototype → Object.prototype → null

访问 d.eat 时,JS 引擎沿这条链向上查找,找到第一个 eat 就用。

2.4 ES6 class 语法糖

javascript 复制代码
class Animal {
  eat() { console.log('eat'); }
}

class Dog extends Animal {
  bark() { console.log('bark'); }
}

ES6 的 class 只是语法糖,底层仍是原型链。但写法更接近 Java,降低了心智负担。

关键区别:

维度 Java JS
继承基础 类(class) 原型(prototype)
确定时机 编译时 运行时
多继承 不支持(接口可多实现) 不支持(原型链单链)
实例与类关系 实例属于类 实例有原型指向

三、闭包:函数 + 作用域的捆绑

3.1 什么是闭包

闭包 = 函数 + 定义该函数时的作用域。

javascript 复制代码
function counter() {
  let count = 0;
  return function() {
    return ++count;
  };
}

const c = counter();
c();  // 1
c();  // 2
c();  // 3

count 是 counter 函数的局部变量,但被返回的内部函数「记住」了。即使 counter 执行完毕,count 也不会被回收------因为内部函数持有对它的引用。

3.2 闭包的真实用途

用途 示例
数据私有化 上例的 count 外部无法直接访问
回调中保持状态 事件监听器里记住上次点击时间
模块模式 IIFE 封装私有变量
柯里化 add(a)(b)(c)

3.3 闭包的陷阱

javascript 复制代码
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}

var 是函数作用域,整个循环共用一个 i。闭包记住的是同一个 i,循环结束后 i=3。

改成 let 后,每次循环创建新的块级作用域,闭包记住的是不同的 i。

四、this:JS 最混乱的概念

4.1 this 的四条规则

规则 this 指向 示例
默认绑定 全局对象(严格模式 undefined) foo()
隐式绑定 调用对象 obj.foo()
显式绑定 call/apply/bind 指定的对象 foo.call(obj)
new 绑定 新创建的对象 new Foo()

优先级:new > 显式 > 隐式 > 默认。

4.2 箭头函数的 this

箭头函数没有自己的 this,继承外层作用域的 this。

javascript 复制代码
const obj = {
  name: 'Tom',
  greet: function() {
    setTimeout(() => {
      console.log(this.name);   // Tom(继承 greet 的 this)
    }, 0);
    setTimeout(function() {
      console.log(this.name);   // undefined(默认绑定全局)
    }, 0);
  }
};

4.3 与 Java this 的区别

Java 的 this 永远指向当前实例。JS 的 this 取决于调用方式,同一个函数在不同调用场景下 this 不同。

五、事件循环:单线程怎么处理并发

5.1 JS 的单线程模型

JS 是单线程的------同一时间只执行一个任务。为什么选择单线程?因为浏览器里 JS 操作 DOM,多线程同时操作 DOM 会冲突。

但单线程不代表不能处理 IO------用事件循环 + 异步回调。

5.2 事件循环模型

复制代码
调用栈(Call Stack)
  ↑ 执行同步代码
  ↓ 遇到异步任务
Web APIs / Node APIs(浏览器/Node 提供的异步 API)
  ↓ 异步完成
任务队列(Task Queue)
  ← 宏任务:setTimeout、setInterval、IO
  ← 微任务:Promise.then、MutationObserver
  ↓ 调用栈空了
事件循环取一个微任务执行(清空所有微任务)
  ↓ 微任务清空后
取一个宏任务执行
  ↓ 循环

5.3 宏任务 vs 微任务的执行顺序

javascript 复制代码
console.log('1');                    // 同步

setTimeout(() => {
  console.log('2');                  // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('3');                  // 微任务
});

console.log('4');                    // 同步

// 输出:1 4 3 2

规则:同步代码 → 清空微任务 → 取一个宏任务 → 清空微任务 → 取下一个宏任务......

5.4 与 Java 多线程的对比

维度 JS 事件循环 Java 多线程
并发模型 单线程 + 异步回调 多线程 + 锁
IO 处理 非阻塞 IO + 回调 阻塞 IO 或 NIO
适用场景 IO 密集(网络请求) CPU 密集 + IO 密集
复杂度 回调地狱(async/await 缓解) 锁竞争、死锁

各自哲学:JS 用单线程避免 DOM 冲突,用事件循环处理 IO;Java 用多线程榨干 CPU,用锁保证安全。

六、Promise → async/await

6.1 回调地狱

javascript 复制代码
getUser(id, function(user) {
  getOrders(user.id, function(orders) {
    getOrderDetail(orders[0].id, function(detail) {
      console.log(detail);
    });
  });
});

6.2 Promise 链式调用

javascript 复制代码
getUser(id)
  .then(user => getOrders(user.id))
  .then(orders => getOrderDetail(orders[0].id))
  .then(detail => console.log(detail))
  .catch(err => console.error(err));

6.3 async/await:同步写法异步执行

javascript 复制代码
async function getOrderDetail(id) {
  try {
    const user = await getUser(id);
    const orders = await getOrders(user.id);
    const detail = await getOrderDetail(orders[0].id);
    console.log(detail);
  } catch (err) {
    console.error(err);
  }
}

async/await 是 Promise 的语法糖,让异步代码看起来像同步。但底层仍是事件循环。

七、ES6+ 高频语法

7.1 解构

javascript 复制代码
const { name, age } = user;        // 对象解构
const [first, second] = arr;       // 数组解构
function foo({ name, age = 18 }) {} // 默认值

7.2 展开

javascript 复制代码
const newObj = { ...oldObj, extra: 1 };   // 对象展开
const newArr = [...oldArr, 1, 2];          // 数组展开

7.3 模块

javascript 复制代码
// math.js
export function add(a, b) { return a + b; }
export default function multiply(a, b) { return a * b; }

// main.js
import multiply, { add } from './math.js';

ESM(ES Module)是 JS 官方模块标准,取代 CommonJS(require)。

八、动态类型 vs 静态类型

JS 是动态类型,变量类型运行时确定。灵活但容易出 bug。

javascript 复制代码
function add(a, b) { return a + b; }
add(1, 2);      // 3
add("1", 2);    // "12"(字符串拼接,不是加法)

8.1 TypeScript:给 JS 加类型

typescript 复制代码
function add(a: number, b: number): number {
  return a + b;
}
add("1", 2);  // 编译报错

TS 是 JS 的超集,编译时类型检查,运行时仍是 JS。大型项目推荐用 TS。

8.2 type hints + mypy 能把 Python 救回静态世界吗

能部分救------TS 和 Python 的 type hints 都是可选类型标注,不强制。但 TS 生态更成熟(主流框架都支持),Python 的 mypy 用得少。

九、小结表

概念 JS Java 对照
继承 原型链 class extends
闭包 函数+作用域 lambda 捕获变量
this 取决于调用方式 永远是当前实例
并发 单线程+事件循环 多线程+锁
模块 ESM import
类型 动态(TS 补静态) 静态
相关推荐
何何____1 小时前
Vue 全局事件总线详解
前端·javascript·vue.js
我要神龙摆尾1 小时前
IDEA 阅读插件
java·ide·intellij-idea·idea-plugs·plugs
涉密IT资质笔记2 小时前
涉密人员脱密期管理规范:期限分级模型、就业限制边界与违规认定标准
java·服务器·前端·网络·数据库
mmsx2 小时前
Android Dialog 插槽模式:用一套壳统一全应用的弹窗风格
android·java·dialog·开发语言
huanbailu2 小时前
Qt 跨线程信号槽:connect 返回 true 但槽函数不执行的坑
开发语言·c++·qt·信号槽·跨线程·元类型
u0111026752 小时前
工具页案例图如何编写 alt让图片说明与处理场景对应
java·前端·javascript·图像处理·人工智能·算法·ai作画
事圆则缓2 小时前
Flutter 状态管理框架对比(五):GetX 的响应式、依赖注册与生命周期
前端·javascript·flutter
ZealSinger2 小时前
Go slog生产落地LevelVar与共存
开发语言·后端·golang·go
码上有光2 小时前
Linux:进程间通信——匿名管道通信、命名管道通信
android·java·linux·linux通信·匿名通信·命名通信
落魄实习生2 小时前
Agent Scope Java 2.x 系列【11】AgentState 与状态存储
java·开发语言·ai