3. k6 脚本基础:JavaScript 语法速览、模块系统、导入与导出、默认函数与生命周期

在深入 k6 自定义扩展与 Go 插件开发之前,必须牢固掌握 k6 脚本的核心编写范式。k6 使用 JavaScript(ES6+)作为脚本语言,但其运行环境并非浏览器或 Node.js,而是基于 Go 的 goja 引擎。这意味着 k6 脚本在语法上遵循 JavaScript 标准,但在 API 和模块系统上有着独特的实现。

3.1 JavaScript 语法速览(k6 环境)

k6 支持 ES6 的大部分语法特性,但受限于 goja 引擎,部分现代 API(如 Promise.allSettled、Array.prototype.flatMap)可能不被支持。以下是在 k6 脚本中高频使用的语法元素:

语法类别 常用特性 k6 中的注意事项
变量声明 let、const 推荐使用 const 声明不变引用;避免使用 var
箭头函数 () => {} 广泛用于定义 default 函数和回调
模板字符串 `${variable}` 用于拼接 URL、请求体等动态内容
解构赋值 const { key } = obj 常用于从 http.get() 返回值中提取 status、body
展开运算符 ...arr 用于合并配置对象或参数数组
对象简写 { name, age } 简化配置对象的构建
💡 关键提示: k6 脚本中 不要使用 document、window、setTimeout 等浏览器 API。所有异步操作必须通过 k6 提供的 http、check、sleep 等内置模块完成。

3.2 k6 模块系统:导入与导出

k6 的模块系统遵循 ES6 模块规范,但路径解析规则与 Node.js 不同。模块分为三类:内置模块、本地文件模块 和 远程 HTTP(S) 模块。

3.2.1 模块分类与导入方式

模块类型 导入语法 示例
内置模块 import { x } from 'k6/x' import http from 'k6/http'
本地文件模块 import { x } from './relative/path.js' import { randomUser } from './data/generator.js'
远程模块 import { x } from 'https://raw.githubusercontent.com/...' import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'
📌 最佳实践: 将可复用的辅助函数(如数据生成、断言逻辑)抽取为本地模块,放在 ./lib/ 或 ./helpers/ 目录下,通过相对路径导入。远程模块仅用于官方维护的 jslib 库,避免依赖不可控的外部源。

3.2.2 导出语法

k6 支持 export default 和 export named 两种导出方式:

// 文件: ./lib/helpers.js
// 命名导出
export function generateToken() {
  return 'Bearer ' + Math.random().toString(36).substring(2);
}

export const BASE_URL = 'https://api.example.com';

// 默认导出(一个文件只能有一个)
export default class DataGenerator {
  static randomEmail() {
    return `user${Date.now()}@test.com`;
  }
}
// 主脚本: script.js
import http from 'k6/http';
import { generateToken, BASE_URL } from './lib/helpers.js';
import DataGenerator from './lib/helpers.js';

export default function () {
  const  = generateToken();
  const email = DataGenerator.randomEmail();
  http.post(`${BASE_URL}/users`, JSON.stringify({ email }), {
    headers: { Authorization:  },
  });
}

3.3 默认函数与生命周期

k6 脚本的执行遵循严格的 生命周期钩子 模型。理解这些阶段是编写可靠压测脚本的基础。

⚠️ 常见误区: 将初始化代码(如数据库连接、大文件加载)放在 default 函数内,会导致每次 VU 迭代都重复执行,严重降低压测性能。初始化代码应放在全局作用域或 setup 函数中。

3.3.1 生命周期阶段

  1. 初始化阶段(Init):加载模块、声明全局变量、读取配置。此阶段代码在 VU 初始化时执行一次。
  2. Setup 阶段:可选,在 VU 开始迭代前执行一次,用于准备共享数据(如登录 )。
  3. VU 迭代阶段(Default):export default function 是压测的核心,每个 VU 会按 stages 配置重复执行此函数。
  4. Teardown 阶段:可选,在所有 VU 迭代结束后执行一次,用于清理资源。
  5. HandleSummary 阶段:可选,用于自定义结果汇总输出。

3.3.2 生命周期代码结构

// 1. 初始化阶段(全局作用域)
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

// 全局变量,所有 VU 共享
const BASE_URL = 'https://test-api.example.com';
const users = new SharedArray('users', function () {
  return JSON.parse(open('./data/users.json'));
});

// 2. Setup 阶段(可选)
export function setup() {
  const res = http.post(`${BASE_URL}/auth/login`, {
    username: 'admin',
    password: 'secret',
  });
  return { : res.json().access_ };
}

// 3. VU 迭代阶段(核心)
export default function (data) {
  // data 是 setup 函数的返回值
  const  = data.;

  const res = http.get(`${BASE_URL}/dashboard`, {
    headers: { Authorization: `Bearer ${}` },
  });

  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });

  sleep(1);
}

// 4. Teardown 阶段(可选)
export function teardown(data) {
  // 清理资源,如删除测试用户
  http.del(`${BASE_URL}/cleanup`, null, {
    headers: { Authorization: `Bearer ${data.}` },
  });
}

// 5. HandleSummary 阶段(可选)
export function handleSummary(data) {
  return {
    'stdout': textSummary(data, { indent: '  ', enableColors: true }),
    'summary.json': JSON.stringify(data),
  };
}

3.4 生命周期与 VU 执行模型

下面的 SVG 图展示了 k6 脚本的生命周期与 VU(虚拟用户)执行模型之间的关系:

k6 脚本生命周期与 VU 执行模型 初始化阶段 加载模块 / 全局变量 Setup 阶段 准备共享数据(可选) VU 迭代阶段 export default function(data) 每个 VU 重复执行 迭代循环 Teardown 阶段 清理资源(可选) HandleSummary 阶段 自定义结果汇总(可选) 关键说明 • Setup / Teardown / HandleSummary 均为可选阶段 • 初始化阶段在 VU 创建时执行,不在迭代中重复 • VU 迭代阶段是压测负载的实际执行单元 • 每个 VU 独立执行 default 函数,互不干扰 • data 参数传递 setup 的返回值给所有 VU • 全局变量在 VU 间共享,需注意线程安全

3.5 模块系统与生命周期结合示例

下面的 SVG 图展示了模块导入、导出与生命周期钩子如何协同工作,构成一个完整的压测脚本:

模块系统与生命周期协同架构 模块导入层 import http from 'k6/http' 内置 HTTP 模块 import { check } from 'k6' 内置检查模块 import { BASE_URL } from './config.js' 本地模块(命名导出) import DataGen from './data.js' 本地模块(默认导出) import { uuidv4 } from 'https://...' 远程模块(jslib) 生命周期层 全局作用域(Init) 加载模块、声明变量 export function setup() 准备共享数据 export default function(data) VU 迭代核心 发送请求、检查响应 export function teardown() 清理资源 export function handleSummary() 自定义结果汇总 执行流程 ① 初始化(一次) 加载所有模块 ② Setup(一次) 获取 等 ③ VU 迭代(多次) 每个 VU 独立执行 按 stages 配置重复 ④ Teardown(一次) 清理测试数据 ⑤ HandleSummary(一次) 输出 JSON / 文本

3.6 常见陷阱与避坑指南

陷阱 错误示例 正确做法
在 default 函数中加载数据 export default function() { const data = JSON.parse(open('./data.json')); } 使用 SharedArray 或全局变量在初始化阶段加载
使用浏览器 API localStorage.setItem('key', 'value') 使用 k6 内置的 http、cookieJar 等模块
模块路径错误 import { x } from 'helpers.js'(缺少 ./) 本地模块必须使用相对路径 ./helpers.js
异步操作未处理 http.get(url).then(res => {})(k6 不支持 Promise) k6 的 HTTP 调用是同步的,直接使用返回值
在 setup 中生成大量数据 在 setup() 中生成 10 万条用户数据 使用 SharedArray 按需加载,或分批生成
📌 核心要点:
  • k6 脚本使用 ES6 模块语法,但运行在 goja 引擎上,不支持浏览器或 Node.js 特有 API。
  • 模块导入分为内置、本地、远程三类,路径规则与 Node.js 不同。
  • 生命周期包含 Init → Setup → Default → Teardown → HandleSummary 五个阶段,其中 Default 是压测核心。
  • 初始化代码(模块加载、数据准备)必须放在全局作用域,避免在 VU 迭代中重复执行。
  • 使用 SharedArray 在 VU 间共享只读数据,使用 data 参数传递 setup 的返回值。