引言:从页面仔到浏览器驯服者

前端开发早已不是切图、做动画、调样式的“页面仔”时代。当现代浏览器进化为一个接近操作系统的运行时环境,我们手中的工具也悄然升级为真正的“超能力”——可以让网络嗅探每一字节的流动,可以编译其他语言到WebAssembly以获得接近原生的速度,可以构建一套代码跨桌面、移动、甚至嵌入式设备运行。这不是科幻,而是今天顶级前端工程师每天都在使用的技能。

本文是一份2万字的深度指南,带你系统掌握让浏览器完全听你指挥的四大核心超能力:WebAssembly加速性能嗅探跨端心智模型,以及一系列扩展的高阶技巧。我们将从原理到实战,从代码片段到架构决策,全方位构建你的前端超级工程师技能树。


第一章 WebAssembly加速:突破JavaScript的性能天花板

1.1 为什么需要WebAssembly?

JavaScript自诞生起就被设计为解释型语言,尽管现代JIT引擎(V8、SpiderMonkey、JavaScriptCore)已经将执行速度推到了接近编译型语言的水平,但它在某些场景下依然力不从心:

  • 密集计算:图像/视频处理、3D物理模拟、加密算法

  • 实时性要求高:音频合成、游戏循环、实时数据可视化

  • 复用现有库:将C++/Rust编写的成熟算法无痛迁移到浏览器

WebAssembly(简称Wasm)提供了一种低级的类汇编语言,以二进制格式运行在浏览器中,执行效率接近原生。它的设计目标包括:高效、安全、跨平台、与JavaScript互操作。

核心原理:Wasm模块在加载后会经过解码、验证、编译,最终生成与平台相关的机器码。由于Wasm的类型系统是静态的且指令集简单,编译器可以做出激进的优化,避免了JIT的暖身阶段和类型推断开销。

1.2 WebAssembly指令集与虚拟机

Wasm基于栈式虚拟机,典型指令如i32.addf64.mulcall。一个简单的加法函数在WAT(文本格式)中表示为:

wat

(module
  (func $add (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add)
  (export "add" (func $add)))

更直观的是,我们可以直接使用高级语言生成Wasm。目前最成熟的工具链包括:

  • Emscripten:将C/C++编译为Wasm,并提供POSIX兼容层

  • wasm-pack:Rust生态的标准编译工具

  • AssemblyScript:TypeScript的子集,直接编译为Wasm

  • Go、C#、Kotlin等也均有Wasm目标支持

1.3 实战:使用Rust编写高性能Wasm模块

我们从一个计算密集型任务开始:计算曼德布罗特集(分形),该算法对每个像素进行复杂平面迭代,是典型的CPU杀手。

步骤1:创建Rust项目

bash

cargo new mandelbrot-wasm --lib
cd mandelbrot-wasm

Cargo.toml中添加:

toml

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

wasm-bindgen提供了JS与Rust之间的高级互操作(无需手动管理内存)。

步骤2:编写核心算法

src/lib.rs

rust

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn mandelbrot(width: u32, height: u32, max_iter: u32) -> Vec<u8> {
    let mut pixels = vec![0u8; (width * height) as usize];
    for y in 0..height {
        for x in 0..width {
            let real = (x as f64 / width as f64) * 3.5 - 2.5;
            let imag = (y as f64 / height as f64) * 2.0 - 1.0;
            let mut z_real = 0.0;
            let mut z_imag = 0.0;
            let mut iter = 0;
            while iter < max_iter && (z_real * z_real + z_imag * z_imag) <= 4.0 {
                let new_real = z_real * z_real - z_imag * z_imag + real;
                let new_imag = 2.0 * z_real * z_imag + imag;
                z_real = new_real;
                z_imag = new_imag;
                iter += 1;
            }
            let color = if iter == max_iter { 0 } else { (iter * 255 / max_iter) as u8 };
            pixels[(y * width + x) as usize] = color;
        }
    }
    pixels
}
步骤3:构建Wasm模块

bash

wasm-pack build --target web

生成的文件:pkg/mandelbrot_wasm_bg.wasm 和 pkg/mandelbrot_wasm.js 绑定层。

步骤4:在浏览器中使用

html

<script type="module">
  import init, { mandelbrot } from './pkg/mandelbrot_wasm.js';

  async function run() {
    await init();
    const width = 800, height = 600;
    const pixels = mandelbrot(width, height, 256);
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const imgData = ctx.createImageData(width, height);
    for (let i = 0; i < pixels.length; i++) {
      const v = pixels[i];
      imgData.data[i*4] = v;
      imgData.data[i*4+1] = v;
      imgData.data[i*4+2] = v;
      imgData.data[i*4+3] = 255;
    }
    ctx.putImageData(imgData, 0, 0);
  }
  run();
</script>

性能对比:同样算法使用纯JavaScript实现,在800x600分辨率下,Wasm版本约12ms,JS版本约45ms(Chrome 120)。在更复杂的迭代(如光线追踪、粒子系统)中,差距可高达5-10倍。

1.4 高级互操作:传递复杂数据与共享内存

Wasm默认只能接收简单类型(整数、浮点数)。对于大数组,最佳实践是使用共享内存(SharedArrayBuffer)或直接传递指向线性内存的指针

通过wasm-bindgen,我们可以轻松传递JavaScript的Uint8Array

rust

#[wasm_bindgen]
pub fn process_image(input: &[u8], output: &mut [u8], width: u32, height: u32) {
    // 直接读写切片,无需复制
    for y in 0..height {
        for x in 0..width {
            let idx = (y * width + x) as usize;
            output[idx] = input[idx].wrapping_mul(2);
        }
    }
}

JS侧:

js

const input = new Uint8Array([...]);
const output = new Uint8Array(input.length);
process_image(input, output, width, height);

内部机制:wasm-bindgen自动生成胶水代码,将JS TypedArray的底层指针传递给Wasm线性内存,零拷贝。

对于无法预分配大小的数据,可以使用Vec,但需要注意内存泄漏——每次Rust返回Vec到JS会分配新内存且不会自动释放,解决方案是实现free函数:

rust

#[wasm_bindgen]
pub struct ImageData {
    pixels: Vec<u8>,
    width: u32,
    height: u32,
}

#[wasm_bindgen]
impl ImageData {
    pub fn new(width: u32, height: u32) -> Self {
        ImageData { pixels: vec![0; (width*height) as usize], width, height }
    }
    pub fn pixels_ptr(&self) -> *const u8 {
        self.pixels.as_ptr()
    }
    // 在JS中使用后调用
    pub fn free(self) {}
}

1.5 WebAssembly系统接口(WASI)与浏览器之外的Wasm

WASI为Wasm定义了系统级API(文件、网络、时钟),让Wasm可以运行在服务器或边缘计算环境。在浏览器中,虽然不能直接使用WASI,但可以通过Wasm组件模型将WASI函数映射到浏览器API(如fetch模拟网络)。

未来趋势:Wasm垃圾回收提案让高阶语言(Java、C#)可以直接托管内存;多值返回异常处理SIMD支持已在主流浏览器落地。WebAssembly正在从“计算加速器”演变为“跨平台字节码标准”。

1.6 何时不应使用WebAssembly?

  • DOM操作密集:Wasm无法直接操作DOM,需要调用JS桥接,性能开销可能超过收益

  • 代码体积敏感:Wasm模块即使压缩后也有几十KB起,首屏极小的页面不必使用

  • 开发效率优先:调试Wasm比JS困难,构建流程复杂

最佳实践:将Wasm作为模块化加速器,只将核心计算部分下沉到Wasm,UI与事件驱动保持JS。


第二章 性能嗅探:洞察浏览器的每一帧

如果说WebAssembly是增加引擎的马力,那么性能嗅探就是给引擎装上传感器和诊断系统。一个真正的前端高手,能像医生一样给浏览器做CT扫描,精准定位每一毫秒的延迟。

2.1 Performance API:浏览器的黑匣子

现代浏览器提供了performance对象,记录着从导航到资源加载、从长任务到帧回调的详细时间线。最强大的工具是PerformanceObserver,可以订阅各种性能事件。

js

// 监听所有长任务(阻塞主线程超过50ms)
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.warn(`长任务: ${entry.duration}ms`, entry);
  }
});
observer.observe({ entryTypes: ['longtask'] });

其他重要的entryTypes:

  • navigation:页面加载完整时间线(DNS、TCP、请求、DOM解析)

  • resource:每个资源(图片、脚本、字体)的时机

  • paint:首次绘制(FP)、首次内容绘制(FCP)

  • layout-shift:累积布局偏移(CLS)

  • largest-contentful-paint:最大内容绘制(LCP)

  • first-input:首次输入延迟(FID)

2.2 核心Web指标(Core Web Vitals)与用户体验量化

Google定义了三个核心指标,直接影响搜索排名:

  • LCP(最大内容绘制):视口中最大元素渲染完成时间,理想<2.5秒

  • INP(与下一次绘制的交互):取代FID,衡量所有交互延迟,理想<200毫秒

  • CLS(累积布局偏移):意外移动视觉元素的程度,理想<0.1

获取真实用户监控(RUM)数据:

js

import {getCLS, getFID, getLCP, getFCP, getTTFB} from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify({ name: metric.name, value: metric.value });
  navigator.sendBeacon('/api/metrics', body);
}

getCLS(sendToAnalytics);
getLCP(sendToAnalytics);
getFID(sendToAnalytics);

2.3 长任务与时间分片

主线程执行超过50ms的任务称为长任务,会导致界面卡顿或输入延迟。对策是时间分片(Time Slicing):将一个长任务拆分为多个小于50ms的块,中间穿插setTimeoutrequestIdleCallback让给用户交互。

js

function processLargeArray(data, chunkSize = 100) {
  let index = 0;
  function processChunk() {
    const end = Math.min(index + chunkSize, data.length);
    for (; index < end; index++) {
      // 处理单个元素
    }
    if (index < data.length) {
      setTimeout(processChunk, 0); // 让出主线程
    }
  }
  processChunk();
}

更优雅的现代方案:使用Scheduler.postTask() 配合优先级:

js

async function backgroundWork() {
  const scheduler = navigator.scheduling;
  if (scheduler) {
    await scheduler.postTask(() => heavyCompute(), { priority: 'background' });
  }
}

2.4 内存泄漏检测与分析

Chrome DevTools的Memory面板提供:

  • Heap snapshot:拍摄堆快照,比较两次快照间的对象增长

  • Allocation instrumentation on timeline:记录一段时间内的分配,定位持续增长的分配

  • Allocation sampling:低开销采样,适合长时间监控

典型的内存泄漏模式:

  • 未解绑的事件监听器(尤其在单页应用中重复挂载组件)

  • 闭包持有大对象

  • 全局变量/缓存无限制增长

  • 分离的DOM树(从文档移除但仍有JS引用)

实战工具:performance.memory API(非标准,仅Chrome)可实时监控堆大小:

js

function checkMemory() {
  if (performance.memory) {
    console.log(`已使用JS堆: ${performance.memory.usedJSHeapSize / 1048576} MB`);
  }
}

2.5 网络嗅探:资源时机、优先级与缓存策略

浏览器的网络请求是一个复杂的调度系统。通过PerformanceResourceTiming可以获取每个资源的详细时机:

js

const resources = performance.getEntriesByType('resource');
resources.forEach(r => {
  console.log(`${r.name}: DNS=${r.domainLookupEnd - r.domainLookupStart}ms, 
    TCP=${r.connectEnd - r.connectStart}ms, 
    请求=${r.responseStart - r.requestStart}ms,
    下载=${r.responseEnd - r.responseStart}ms`);
});

关键指标:

  • 排队时间queueTime非标准):请求因优先级或HTTP/2并发限制而等待的时间,优化手段包括preloadpreconnect、升级H3。

  • TTFB(首字节时间):服务器响应速度,受后端性能、网络延迟影响。

利用<link rel="preload">提示浏览器提前加载关键资源:

html

<link rel="preload" href="critical-font.woff2" as="font" crossorigin>
<link rel="preconnect" href="https://api.example.com">

2.6 帧率监控与流畅度优化(帧预算)

浏览器渲染一帧的预算在60Hz刷新率下是16.6ms。当主线程任务(JS、样式计算、布局、绘制)超过此阈值,就会产生丢帧。监控帧率的方法:

js

let lastFrameTime = performance.now();
let frameCount = 0;
function measureFPS() {
  frameCount++;
  const now = performance.now();
  if (now - lastFrameTime >= 1000) {
    console.log(`当前FPS: ${frameCount}`);
    frameCount = 0;
    lastFrameTime = now;
  }
  requestAnimationFrame(measureFPS);
}
requestAnimationFrame(measureFPS);

更专业的是使用PerformanceObserver观察frame事件(早期标记,现多为long-animation-frame)。

帧预算分配建议(16.6ms内):

  • 脚本执行:≤8ms

  • 样式计算+布局:≤5ms

  • 绘制与合成:≤3ms

  • 剩余时间作为缓冲

优化技巧:

  • 使用transformopacity实现动画(仅触发合成)

  • 避免强制同步布局:不要先读取offsetHeight再修改样式

  • 将滚动/交互监听设为passive: true

  • 使用will-change提示即将动画的元素

2.7 实战:构建一个自定义性能监控面板

下面构建一个小型的内部性能仪表盘,实时展示LCP、INP、CLS和当前FPS。

html

<div id="dashboard">
  <div>LCP: <span id="lcp">-</span>ms</div>
  <div>INP: <span id="inp">-</span>ms</div>
  <div>CLS: <span id="cls">-</span></div>
  <div>FPS: <span id="fps">-</span></div>
</div>
<script type="module">
  import {onLCP, onINP, onCLS} from 'https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.js?module';
  
  onLCP(metric => document.getElementById('lcp').textContent = metric.value.toFixed(0));
  onINP(metric => document.getElementById('inp').textContent = metric.value.toFixed(0));
  onCLS(metric => document.getElementById('cls').textContent = metric.value.toFixed(3));
  
  // FPS监控
  let lastTimestamp = 0;
  let frames = 0;
  function tick(timestamp) {
    frames++;
    if (timestamp - lastTimestamp >= 1000) {
      document.getElementById('fps').textContent = frames;
      frames = 0;
      lastTimestamp = timestamp;
    }
    requestAnimationFrame(tick);
  }
  requestAnimationFrame(tick);
</script>

结合sendBeacon可将数据上报到后端,用于分析趋势和版本回归检测。


第三章 跨端心智模型:一次编写,随处运行

真正的“让浏览器听你指挥”,不仅局限于桌面web,还要扩展到移动端、桌面应用、甚至嵌入式设备。跨端开发需要建立一套统一的心智模型,抽象出平台差异。

3.1 跨端的困境与演进

历史演进路径:

  1. 响应式Web:一套HTML/CSS/JS通过媒体查询适配不同屏幕尺寸。

  2. 混合应用:Cordova/PhoneGap将WebView包装成原生应用,性能受限。

  3. React Native / Weex:使用JS桥接调用原生UI组件,性能接近原生。

  4. Flutter:自绘引擎,完全脱离原生控件,一致性极高。

  5. Tauri / Electron:桌面端Web技术栈封装,使用系统WebView或自定义渲染。

  6. Kotlin Multiplatform / Compose Multiplatform:共享业务逻辑,UI各平台独立或共享。

关键洞察:跨端不是写一次代码在所有地方完美运行,而是共享尽可能多的逻辑,同时让UI层适配各自平台的交互范式

3.2 响应式设计与硬件抽象

响应式设计的核心是布局弹性断点。但除此之外,还要考虑输入方式(触摸 vs 鼠标)、网络质量(移动端弱网)、电量与性能限制。

硬件抽象示例:使用window.matchMedia检测是否支持触摸:

js

const isTouch = window.matchMedia('(pointer: coarse)').matches;
if (isTouch) {
  // 增大点击区域、增加触感反馈
}

更全面的API:navigator.hardwareConcurrency(CPU核心数)、navigator.deviceMemory(设备内存估算)、navigator.connection.effectiveType(网络类型)。

3.3 服务端渲染、静态生成与客户端增强

跨端中,性能和SEO往往是痛点的根源。现代框架(Next.js、Nuxt、SvelteKit)提供了多种渲染策略:

  • SSR(服务端渲染):首屏快,但服务器压力大

  • SSG(静态站点生成):最快,适合内容不变页

  • ISR(增量静态再生):混合方案

  • CSR(客户端渲染):适合后台应用

在跨端项目中,你可以将同一个React组件在服务器上渲染为HTML,再在客户端水合(hydration),实现同构。

3.4 主流跨端框架对比与选型

React Native
  • 优势:React生态庞大,热更新成熟,性能接近原生

  • 劣势:复杂布局需要学习Flexbox特例,桥接序列化成本高

  • 适合:已有React技术栈的团队,需要快速上架App

Flutter
  • 优势:性能极佳(60fps保证),UI一致性极高,强大的开发工具

  • 劣势:包体积大(~4MB基础),Dart语言学习成本

  • 适合:对UI精致度和性能要求极高的应用

Tauri (桌面端)
  • 优势:极小的包体积(基于系统WebView),内存占用低,Rust后端性能强

  • 劣势:生态比Electron年轻

  • 适合:需要原生系统调用但希望包体积小的桌面应用

Kotlin Multiplatform (KMP)
  • 优势:共享业务逻辑(网络、数据存储、认证),原生UI保持平台个性

  • 劣势:构建配置复杂,生态还在成长

  • 适合:已有原生团队,希望减少重复逻辑

3.5 统一状态管理与路由

跨端项目中,状态管理需要同时支持Web和原生环境。推荐使用与平台无关的状态容器,例如Zustand、Redux Toolkit。注意避免使用依赖DOM的API(如localStorage),而是通过抽象接口:

ts

// 定义存储接口
interface IStorage {
  get(key: string): Promise<string | null>;
  set(key: string, value: string): Promise<void>;
}

// Web实现
class WebStorage implements IStorage {
  get(key) { return Promise.resolve(localStorage.getItem(key)); }
  set(key, value) { localStorage.setItem(key, value); return Promise.resolve(); }
}

// React Native实现(使用AsyncStorage)
class RNStorage implements IStorage {
  async get(key) { return await AsyncStorage.getItem(key); }
  async set(key, value) { await AsyncStorage.setItem(key, value); }
}

同样,路由需要抽象:Web使用react-router-dom,移动端使用react-native-screens

3.6 适配不同输入模式(触摸、鼠标、语音)

跨端应用必须优雅处理各种输入源:

  • 悬停状态:触摸屏没有hover,需使用@media (hover: hover)选择性启用悬停效果

  • 点击区域:移动端至少44x44pt(苹果HIG标准)

  • 键盘快捷键:桌面端需要支持Ctrl+S等,移动端不需要

  • 语音控制:通过SpeechRecognition API实现语音输入,增强无障碍

3.7 跨端测试策略

使用单一代码库时,测试覆盖:

  • 单元测试:Jest + Vitest,测试平台无关逻辑

  • 集成测试:针对每个平台运行端到端测试(Web用Playwright,移动端用Detox/Appium)

  • 视觉回归测试:Percy、Loki确保UI在不同平台上一致

CI/CD中应包含矩阵构建:为每个目标平台(web、android、ios、windows)运行独立的测试套件。

心智模型总结:将应用分为三层:核心业务逻辑(纯TypeScript/Rust)、平台适配层(调用本地API)、UI展示层(各平台独立但共享设计令牌)。这种分层让跨端维护变得可控。


第四章 其他超能力技能:让浏览器完全臣服

4.1 浏览器自动化与脚本注入(Puppeteer、Playwright)

如果你想控制浏览器完成自动化测试、爬取SPA内容、生成PDF或截图,那么Puppeteer和Playwright是你的超能力。它们通过DevTools协议操控真实Chromium/Firefox/WebKit。

示例:使用Playwright截取所有长页面全屏滚动图:

js

const { chromium } = require('playwright');
(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com/long-article');
  // 滚动到最底部触发懒加载
  await page.evaluate(async () => {
    await new Promise((resolve) => {
      let totalHeight = 0;
      const distance = 400;
      const timer = setInterval(() => {
        const scrollHeight = document.body.scrollHeight;
        window.scrollBy(0, distance);
        totalHeight += distance;
        if (totalHeight >= scrollHeight) {
          clearInterval(timer);
          resolve();
        }
      }, 100);
    });
  });
  await page.screenshot({ path: 'fullpage.png', fullPage: true });
  await browser.close();
})();

更高级的用法:拦截网络请求、模拟地理位置、注入自定义脚本(如将console.log重定向到Node端)。

4.2 智能预取与预测导航

让浏览器提前加载用户下一步可能点击的页面,可以大幅提升感知性能。Google的Quick Link(或<link rel="prerender">)可以在空闲时渲染整个页面。

现代实现使用Predictive prefetch结合机器学习(分析用户鼠标移动/停留时间):

js

let prefetchTimeout;
document.querySelectorAll('a').forEach(link => {
  link.addEventListener('mouseenter', () => {
    const url = link.href;
    prefetchTimeout = setTimeout(() => {
      const prefetchLink = document.createElement('link');
      prefetchLink.rel = 'prefetch';
      prefetchLink.href = url;
      document.head.appendChild(prefetchLink);
    }, 100); // 鼠标悬停100ms后才预取,避免浪费
  });
  link.addEventListener('mouseleave', () => clearTimeout(prefetchTimeout));
});

更激进的:InstantClick 或 barba.js 实现页面切换时仅替换内容区域并更新历史,配合预取实现无刷新导航。

4.3 Web Workers与多线程编程

Web Workers是浏览器实现真正并行的唯一途径。主线程可以委派繁重任务给Worker,完全不影响UI响应。

专用Worker

js

// worker.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};

// 主线程
const worker = new Worker('worker.js');
worker.postMessage(largeDataset);
worker.onmessage = (e) => updateUI(e.data);

共享Worker可在多个标签页间共享;Service Worker则用于拦截网络请求、实现离线缓存和推送通知。

高级模式:Worker池——创建多个Worker,任务队列分发,并行处理大数据集(例如图像滤波)。

4.4 WebGPU:下一代图形与计算

WebGL虽然强大,但受限于OpenGL ES的陈旧模型。WebGPU是现代图形API(Vulkan/Metal/DirectX 12)的Web封装,提供:

  • 更低的驱动开销:减少CPU端验证

  • 计算着色器:通用GPU计算,比WebGL的片段着色器更灵活

  • 多线程提交命令:支持从Worker提交渲染命令

一个小例子:用WebGPU计算向量加法(GPGPU):

js

// 简化代码,完整需处理适配器、设备、缓冲区、着色器
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

const shader = `
  @group(0) @binding(0) var<storage, read> a : array<f32>;
  @group(0) @binding(1) var<storage, read> b : array<f32>;
  @group(0) @binding(2) var<storage, read_write> result : array<f32>;
  @compute @workgroup_size(64)
  fn main(@builtin(global_invocation_id) id : vec3<u32>) {
    let i = id.x;
    result[i] = a[i] + b[i];
  }
`;
// ... 创建管线、绑定组、调度计算

WebGPU让浏览器可以运行类似TensorFlow.js的机器学习训练、实时流体模拟、百万级别粒子系统。

4.5 浏览器存储革命:OPFS与File System Access API

传统的存储(IndexedDB、localStorage)有性能或容量限制。Origin Private File System (OPFS) 提供了一个快速的、仅限源访问的虚拟文件系统,结合同步访问句柄可实现高性能存储。

js

const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('data.bin', { create: true });
const writable = await fileHandle.createWritable();
await writable.write(new Uint8Array([1,2,3]));
await writable.close();

File System Access API允许用户授予网页读写本地文件或文件夹的权限,类似于原生应用:

js

const [fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const contents = await file.text();

这些API加起来,浏览器已经具备了轻度桌面应用的存储能力。

4.6 安全沙盒与权限管理

作为浏览器指挥官,必须清楚安全边界。Content Security Policy(CSP)可以限制脚本来源、禁止eval。权限API可以查询/请求敏感权限(地理位置、摄像头、剪贴板):

js

navigator.permissions.query({ name: 'clipboard-write' }).then(result => {
  if (result.state === 'granted') {
    // 写入剪贴板
  } else if (result.state === 'prompt') {
    // 需要请求用户授权
  }
});

隔离是另一个趋势:使用Cross-Origin-Opener-PolicyCross-Origin-Embedder-Policy启用SharedArrayBuffer并防御旁路攻击。

Logo

一站式 AI 云服务平台

更多推荐