普通视图

发现新文章,点击刷新页面。
昨天 — 2025年9月8日首页

Web Workers:前端多线程解决方案

作者 wallflower丶
2025年9月8日 12:01

Web Workers 是 HTML5 提供的一个 JavaScript 多线程解决方案,允许在后台线程中运行脚本,而不会影响主线程(通常是UI线程)的性能。这意味着你可以执行一些耗时较长的任务(如大量计算、数据处理等)而不阻塞用户界面。

为什么需要 Web Workers?

在传统的 JavaScript 中,由于是单线程的,所有任务都在主线程上执行。如果执行一个耗时的任务(比如复杂的计算、大数据的处理、长时间的网络请求等),就会导致页面卡顿,用户无法进行其他操作,直到任务完成。Web Workers 就是为了解决这个问题,它允许将一些任务放到后台线程中去执行,从而释放主线程。


核心价值

  1. 避免 UI 阻塞:耗时任务(如复杂计算、大数据处理)在 Worker 中执行,保持页面响应

  2. 多线程并行:利用多核 CPU 提升性能

  3. 隔离环境:Worker 线程无法直接操作 DOM,保证线程安全


Web Workers 的特点

  1. 独立线程:Web Worker 运行在另一个全局上下文中,与主线程是分离的。因此,它不能直接访问 DOM,也不能使用一些默认的方法和属性(如window对象、document对象等)。

  2. 通信机制:主线程和 Worker 线程之间通过消息传递进行通信。使用postMessage()发送消息,通过onmessage事件处理函数来接收消息。数据是通过复制而不是共享来传递的(除了ArrayBuffer等可以通过转移所有权的方式传递)。

  3. 脚本限制:Worker 线程中只能运行部分 JavaScript 特性,不能操作 DOM,不能使用alertconfirm等,但可以使用XMLHttpRequestfetch进行网络请求,也可以使用setTimeoutsetInterval等。

  4. 同源限制:Worker 脚本必须与主脚本同源(协议、域名、端口相同)。

  5. 关闭 Worker:主线程可以随时终止 Worker,Worker 也可以自己关闭。

技术架构

image.png


使用场景

  1. CPU 密集型任务

    ∙ 大数据排序/过滤

    ∙ 图像/视频处理(如 WebAssembly + Canvas)

    ∙ 物理引擎计算

  2. 实时数据处理

    ∙ WebSocket 消息处理

    ∙ 日志分析

  3. 预加载资源

    ∙ 提前加载和解析数据


代码示例

1. 基础使用

步骤1:创建 Worker 脚本

首先,你需要创建一个单独的 JavaScript 文件作为 Worker 的脚本。例如,我们创建一个worker.js

// worker.js
// 监听主线程发来的消息
self.onmessage = function(e) {
  console.log('Worker: Message received from main script');
  const data = e.data;
  // 执行一些耗时操作
  const result = heavyTask(data);
  // 将结果发送回主线程
  self.postMessage(result);
};

function heavyTask(data) {
  // 这里模拟一个耗时操作,比如复杂计算
  let result = 0;
  for (let i = 0; i < data; i++) {
    result += i;
  }
  return result;
}

步骤2:在主线程中创建 Worker 并通信

在主线程的 JavaScript 文件中:

// main.js
// 创建一个新的 Worker,传入脚本的URL
const worker = new Worker('worker.js');

// 向 Worker 发送数据
worker.postMessage(1000000000); // 发送一个很大的数,模拟耗时计算

// 接收来自 Worker 的消息
worker.onmessage = function(e) {
  console.log('Main: Message received from worker', e.data);
};

// 错误处理
worker.onerror = function(error) {
  console.error('Worker error:', error);
};

终止 Worker

当不再需要 Worker 时,应该终止它以释放资源。

// 主线程中终止 Worker
worker.terminate();

// 或者在 Worker 内部自己关闭
self.close();

注意事项

  1. 数据传输:通过postMessage传递的数据是深拷贝的,所以如果传递的数据量很大,可能会影响性能。对于大数据,可以使用Transferable对象(如ArrayBuffer)来转移数据的所有权,这样数据不会被复制,而是直接转移,原上下文将无法访问该数据。

    // 在 Worker 中
    const buffer = new ArrayBuffer(32);
    self.postMessage(buffer, [buffer]); // 第二个参数表示要转移的对象数组
    

    这样,主线程收到后,原 Worker 中的 buffer 将不可用。

  2. 作用域:在 Worker 内部,全局对象是self(也可以使用this),而不是window

  3. 引入其他脚本:在 Worker 中可以使用importScripts()来同步导入其他脚本:

    importScripts('script1.js', 'script2.js');
    

2. 图像处理示例

假设我们有一个图像处理的任务,比如将图像转换为灰度图。这个操作可能很耗时,特别是对于大图像。

worker.js(图像处理Worker)

self.onmessage = function(e) {
  const imageData = e.data;
  const data = imageData.data;
  // 灰度化处理:每个像素的RGB值取平均值
  for (let i = 0; i < data.length; i += 4) {
    const avg = (data[i] + data[i+1] + data[i+2]) / 3;
    data[i] = avg;   // R
    data[i+1] = avg; // G
    data[i+2] = avg; // B
  }
  self.postMessage(imageData);
};

主线程代码

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 假设我们有一个图像
const img = new Image();
img.onload = function() {
  ctx.drawImage(img, 0, 0);
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  
  // 创建Worker
  const worker = new Worker('worker.js');
  worker.postMessage(imageData); // 注意:imageData包含一个Uint8ClampedArray,它是可转移的
  worker.onmessage = function(e) {
    const processedImageData = e.data;
    ctx.putImageData(processedImageData, 0, 0);
    worker.terminate(); // 处理完成,终止Worker
  };
};
img.src = 'example.jpg';

高级技巧

  1. Transferable Objects

    // 零拷贝传输大数据
    worker.postMessage(largeBuffer, [largeBuffer]);
    
  2. Worker 池管理

    class WorkerPool {
      constructor(size, script) {
        this.workers = Array(size).fill().map(() => new Worker(script));
      }
      // ...任务队列管理
    }
    
  3. 动态加载 Worker

    const worker = new Worker(URL.createObjectURL(
      new Blob([`(${workerFunction.toString()})()`])
    ));
    

注意事项

  1. 通信成本:频繁的小消息传递可能抵消性能收益

  2. 功能限制

    ∙ 无法访问:DOM、window、document

    ∙ 受限访问:navigator、location(只读)

  3. 调试技巧

    ∙ Chrome DevTools → Sources → Threads

    console.log在 Worker 中可用

  4. 终止机制

    // 主线程中
    worker.terminate();
    
    // Worker 内部
    self.close();
    

性能对比

操作类型 主线程耗时 Worker 耗时 优势
10万次浮点运算 120ms 30ms ⚡️ 4倍
5MB 图像处理 阻塞UI 800ms 无阻塞 300ms 🚀 零卡顿
实时数据流处理 丢帧率 35% 丢帧率 3% 💯 流畅体验

总结

Web Workers 为前端开发提供了多线程能力,可以显著提高复杂应用的性能和用户体验。但需要注意,Worker 线程不能操作 DOM,与主线程的通信是异步的,并且需要谨慎处理数据传输的性能问题。在需要执行耗时任务时,合理使用 Web Workers 可以避免阻塞主线程,保持页面的流畅响应。

昨天以前首页

JavaScript 入门精要:从变量到对象,构建稳固基础

作者 San30
2025年9月6日 22:28

本文系统梳理 JavaScript 核心概念,涵盖数据类型、变量声明、对象操作等基础知识,助你打下坚实 JS 基础。

一、代码书写位置与基本语法

在浏览器环境中,JavaScript 代码有两种书写方式:

<!-- 方式1:直接写在script标签中 -->
<script>
  console.log("这是内部JS代码");
</script>

<!-- 方式2:引用外部JS文件(推荐) -->
<script src="script.js"></script>

推荐使用外部文件的原因在于代码分离原则:内容(HTML)、样式(CSS)、功能(JS)三者分离,更易于维护和阅读。

注意

  • 页面中可以存在多个 script 元素,执行顺序为从上到下
  • script 元素引用了外部文件,其内部不能再书写任何代码
  • type 属性为可选属性,用于指定代码类型

基本语法规则

// 语句以分号结束(非强制但建议)
let name = "张三";

// 大小写敏感
let age = 20;
let Age = 30; // 这是不同的变量

// 代码从上到下执行
console.log("第一行");
console.log("第二行");

输入输出语句

需要注意的是,所有的输入输出语句都不是 ES 标准。

// 输出语句示例
document.write("这是页面输出"); // 输出到页面
alert("这是弹窗提示"); // 弹窗显示
console.log("这是控制台输出"); // 输出到控制台

// 输入语句示例
let userAge = prompt("请输入你的年龄"); // 获取用户输入
console.log("用户年龄是:" + userAge);

注释的使用

注释是提供给代码阅读者使用的,不会参与执行。

// 这是单行注释

/*
  这是多行注释
  可以跨越多行
*/

// 实际代码
let score = 100; // 设置分数为100

VScode 快捷键

  • Ctrl + /: 快速添加/取消单行注释
  • Alt + Shift + A: 快速添加/取消多行注释

二、数据类型与字面量

数据是指有用的信息,数据类型则是数据的分类。

原始类型(基本类型)

原始类型指不可再细分的类型:

// 1. 数字类型 (number)
let integer = 100; // 整数
let float = 3.14; // 浮点数
let hex = 0xff; // 十六进制:255
let octal = 0o10; // 八进制:8
let binary = 0b1100; // 二进制:12

// 2. 字符串类型 (string)
let str1 = 'Hello'; // 单引号
let str2 = "World"; // 双引号
let str3 = `Hello 
World`; // 模板字符串,可以换行

// 转义字符
let escaped = "这是第一行\n这是第二行\t这里有一个制表符";

// 3. 布尔类型 (boolean)
let isTrue = true; // 真
let isFalse = false; // 假

// 4. undefined 类型
let notDefined; // 值为undefined
console.log(notDefined); // 输出: undefined

// 5. null 类型
let empty = null; // 表示空值

引用类型

// 对象 (Object)
let person = {
  name: "张三",
  age: 25,
  isStudent: false
};

// 函数 (Function)
function sayHello() {
  console.log("Hello!");
}

获取数据类型

使用 typeof 运算符可以获取数据的类型。

console.log(typeof 100); // "number"
console.log(typeof "Hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (注意这是JS的著名特性)

注意typeof null 得到的是 Object,这是 JavaScript 的一个著名特性(或称为 bug)。

字面量(常量)

直接书写的具体数据称为字面量

// 数字字面量
123
3.14

// 字符串字面量
"Hello"
'World'

// 布尔字面量
true
false

// 对象字面量
{ name: "张三", age: 25 }

// 数组字面量
[1, 2, 3, 4]

三、变量的声明与使用

变量是一块内存空间,用于保存数据。

变量的使用步骤

  1. 声明变量

    var message; // 使用var声明(旧方式)
    let count; // 使用let声明(推荐)
    const PI = 3.14; // 使用const声明常量
    

    变量声明后,其值为 undefined

  2. 变量赋值

    let name; // 声明
    name = "张三"; // 赋值
    
    let age = 25; // 声明并赋值
    
  3. 声明与赋值合并

    let name = "张三", age = 25, isStudent = true; // 多个变量声明
    

    这是语法糖——仅为方便代码书写或记忆,不会有实质性改变。

变量命名规范(标识符)

标识符是需要自行命名的位置,遵循以下规范:

// 合法标识符
let userName;
let _privateData;
let $element;
let data2023;

// 不合法标识符
// let 123abc; // 不能以数字开头
// let user-name; // 不能包含连字符
// let let; // 不能使用关键字

驼峰命名法

  • 大驼峰:每个单词首字母大写 UserName
  • 小驼峰:除第一个单词外,首字母大写 userName

目前常用的是小驼峰命名法。

重要特性

  • 变量提升: var变量的声明会自动提升到代码最顶部(但不超越脚本块)
// 变量提升示例
console.log(x); // undefined (不会报错)
var x = 5;
console.log(x); // 5

// 注意:let和const不存在变量提升
// console.log(y); // 报错
let y = 10;

// 重复声明
var z = 1;
var z = 2; // 允许重复声明
console.log(z); // 2

let w = 1;
// let w = 2; // 报错,不能重复声明
  • 任何可以书写数据的地方都可以书写变量
  • 使用未声明的变量会导致错误(例外:typeof 未声明的变量得到 undefined
  • 变量提升: 所有使用var声明的变量会自动提升到代码最顶部(但不超越脚本块)
  • JS 允许使用var定义多个同名变量(提升后会合并为一个)

四、变量与对象操作

在变量中存放对象

// 创建对象
let person = {
  name: "张三",
  age: 25,
  "favorite-color": "blue" // 属性名包含特殊字符
};

// 1. 读取对象属性
console.log(person.name); // "张三"
console.log(person.age); // 25
console.log(person.hobby); // undefined (属性不存在)
// console.log(nullObj.name); // 报错 (对象不存在)

// 2. 更改对象属性
person.age = 26; // 修改属性
person.hobby = "读书"; // 添加新属性

// 3. 删除属性
delete person.age;
console.log(person.age); // undefined

// 4. 属性表达式
console.log(person["name"]); // "张三"
let propName = "age";
console.log(person[propName]); // 26 (动态访问属性)
console.log(person["favorite-color"]); // "blue" (访问含特殊字符的属性)

注意: JS 对属性名的命名不严格,属性可以是任何形式的名字,但属性名只能是字符串(数字会自动转换为字符串)。

全局对象

// 浏览器环境中
console.log(window); // 全局对象

// 开发者定义的变量会成为window对象的属性
var globalVar = "我是全局变量";
console.log(window.globalVar); // "我是全局变量"

// 但使用let/const声明的变量不会添加到window对象
let localVar = "我是局部变量";
console.log(window.localVar); // undefined
  • 浏览器环境中,全局对象为 window(表示整个窗口)
  • 全局对象的所有属性可直接使用,无需写上全局对象名
  • 使用var定义的变量实际上会成为 window 对象的属性
  • 使用let/const定义的变量不会添加到window对象

五、引用类型的深层理解

// 原始类型存放具体值
let a = 10;
let b = a; // b是a的值副本
a = 20;
console.log(a); // 20
console.log(b); // 10 (值不变)

// 引用类型存放内存地址
let obj1 = { value: 10 };
let obj2 = obj1; // obj2和obj1指向同一对象
obj1.value = 20;
console.log(obj1.value); // 20
console.log(obj2.value); // 20 (值也跟着变了)

// 对象字面量每次都会创建新对象
let o1 = {};
let o2 = {}; // 这是两个不同的对象
console.log(o1 === o2); // false
  • 原始类型变量存放的是具体值
  • 引用类型变量存放的是内存地址
  • 凡是出现对象字面量的位置(两个大括号),都会在内存中创建一个新对象

拓展知识:垃圾回收

JavaScript 拥有自动垃圾回收机制:

function createObject() {
  let obj = { value: 100 }; // 对象被创建
  return obj;
}

let myObj = createObject(); // 对象被引用,不会被回收
myObj = null; // 对象不再被引用,将成为垃圾被回收
  • 垃圾回收器会定期发现内存中无法访问的对象
  • 这些对象被称为垃圾
  • 垃圾回收器会在合适的时间释放它们占用的内存

结语

掌握 JavaScript 的基础概念是成为优秀开发者的第一步。从变量声明到对象操作,从数据类型到内存管理,这些基础知识构成了 JavaScript 编程的基石。建议初学者多加练习,深入理解每个概念背后的原理,为后续学习更高级的 JavaScript 特性打下坚实基础。

学习建议:多动手实践,尝试不同的代码组合,使用控制台查看结果,加深对每个知识点的理解。

// 实践示例:创建一个简单的个人信息对象
let person = {
  name: prompt("请输入你的姓名"),
  age: parseInt(prompt("请输入你的年龄")),
  hobbies: ["读书", "运动", "音乐"]
};

console.log("姓名:", person.name);
console.log("年龄:", person.age);
console.log("爱好数量:", person.hobbies.length);

// 动态添加新属性
let newHobby = prompt("请添加一个新爱好");
person.hobbies.push(newHobby);

console.log("更新后的爱好:", person.hobbies);

通过这样的实践,你可以更好地理解JavaScript对象和变量的工作方式。

❌
❌