Appearance
JavaScript 基础语法
Node.js 脚本最终由 JavaScript 语法构成。运维场景最密集使用的那部分语法:变量、六种常见类型、条件分支、循环、函数、数组和对象的方法、JSON 互转、异常处理。
一段代码怎么跑起来
node <文件名> 按文件从上到下解释执行。语法错误在执行前就报出来(SyntaxError),运行时错误在走到对应位置才抛出。
js
// hello.js
console.log("hello node");
// 正常结果走 stdout,后面可以被管道继续处理bash
node hello.jsjs
console.log("before");
throw new Error("here fails");
// 手动抛异常后,后面代码不会继续执行
console.log("after"); // 这一行跑不到JavaScript 是单线程事件驱动模型。主线程上代码一行一行执行,异步操作(文件读写、网络请求、定时器)注册到事件循环中,等主线程空闲后再通过回调或 Promise 触发后续逻辑。一个脚本启动后,事件循环开始直到没有可触发的回调才退出。
变量:const 和 let
现代 JS 只用两个声明关键字:
js
const serviceName = "nginx"; // 不变的值用 const
let retryCount = 0; // 会变的值用 let
retryCount = retryCount + 1;var 是老写法,变量提升和作用域规则容易让人迷惑。新的运维脚本里已经不需要它。
一个容易被误会的点——const 不表示值完全不可变:
js
const ports = [80, 443];
ports.push(8080); // 没有重新赋值,合法
console.log(ports); // [80, 443, 8080]
// ports = [80, 8080]; // 重新赋值会报 TypeErrorconst 约束的是"变量名不能重新指向新的值"。数组的内容、对象的属性仍然可以修改——它不提供深层的不可变保护。
六种常见类型
js
const name = "web-01"; // string
const port = 8080; // number(不分 int/float)
const enabled = true; // boolean
const remark = null; // null——明确表示"空值",是有意设置的
let token; // undefined——声明了但没赋值| 类型 | typeof 结果 | 出现场景 |
|---|---|---|
| string | "string" | 配置、路径、命令输出 |
| number | "number" | 端口、延迟、阈值、计数 |
| boolean | "boolean" | 开关、检查结果 |
| null | "object"(历史 bug) | 主动设为空,和 undefined 区分 |
| undefined | "undefined" | 参数没传、属性不存在 |
| object | "object" | 服务器信息、配置对象、JSON |
字符串模板(反引号)拼接比 + 更清晰:
js
const host = "10.0.0.11";
const port = 8080;
// ${} 里可以是变量、表达式甚至函数调用
const url = `http://${host}:${port}/healthz`;
console.log(url);条件判断
js
const status = "active";
if (status === "active") {
console.log("running");
} else if (status === "inactive") {
console.error("stopped");
} else {
console.error("unknown status:", status);
}=== vs == 的本质差异不是语法而是类型转换:== 在比较前会对两边的值做隐式类型转换,=== 不会。运维编程里环境变量读出来永远是字符串,用 == 比较数字可能会踩到"字符串 '80' == 数字 80"为 true 但逻辑上不该相等的情况。
js
console.log(1 === "1"); // false——类型不同
console.log(1 == "1"); // true——字符串被转为数字
console.log(0 == false); // true——两边都转为 0
console.log(null == undefined); // true——只有这俩互相相等多个条件用 &&(且)和 ||(或):
js
const code = 200;
const costMs = 35;
if (code >= 200 && code < 300 && costMs < 500) {
console.log("request ok");
}
if (code >= 500 || costMs > 5000) {
console.error("request needs attention");
}循环
最常用的三种循环方式:
js
// 数组遍历——for...of
const services = ["nginx", "sshd", "crond"];
for (const service of services) {
console.log(`checking ${service}`);
}
// 需要下标——普通 for
const hosts = ["web-01", "web-02", "web-03"];
for (let i = 0; i < hosts.length; i += 1) {
console.log(`${i}\t${hosts[i]}`);
}
// 对象遍历——Object.entries()
const labels = { app: "nginx", env: "prod" };
for (const [key, value] of Object.entries(labels)) {
console.log(`${key}=${value}`);
}for...in 遍历的是对象键名(包括原型链上的),不适合数组。数组用 for...of,对象用 Object.entries/keys/values。
函数
函数是把一段逻辑命名,后续调用。运维脚本里函数主要用来拆分重复逻辑:
js
function formatServer(name, ip) {
return `${name}\t${ip}`; // 只处理数据,不写 console.log
}
const line = formatServer("web-01", "10.0.0.11");
console.log(line);箭头函数适合短小的回调:
js
const services = ["nginx", "sshd"];
// 箭头函数里只有一个 return 时可以省略 {} 和 return
const lines = services.map((s) => `checking ${s}`);
console.log(lines);参数默认值:
js
function buildUrl(host, port = 80) {
// port 不传就用默认值 80
return `http://${host}:${port}`;
}
console.log(buildUrl("10.0.0.11")); // http://10.0.0.11:80
console.log(buildUrl("10.0.0.11", 443)); // http://10.0.0.11:443数组:filter、map、includes
数组是运维脚本里用的最多的数据结构——服务列表、IP 列表、文件列表、检查结果列表。
js
const services = ["nginx", "sshd", "crond"];
services.push("docker"); // 追加
console.log(services.includes("nginx")); // 是否包含
console.log(services.length); // 长度
console.log(services[0]); // 取第一个两个核心的链式转换方法:
js
const ports = [80, 443, 8080, 9090];
// filter:保留满足条件的元素,返回新数组,不修改原数组
const highPorts = ports.filter((p) => p >= 1024);
console.log(highPorts); // [8080, 9090]
// map:把每个元素转换成新值,返回新数组,不修改原数组
const urls = ports.map((p) => `http://127.0.0.1:${p}`);
console.log(urls);filter 和 map 都可以链式调用:
js
const urls = ports
.filter((p) => p !== 443) // 去掉 443
.map((p) => `http://127.0.0.1:${p}/healthz`);对象:组织关联数据
对象把有名字的字段放在一起:
js
const server = {
name: "web-01",
ip: "10.0.0.11",
labels: { app: "nginx", env: "prod" },
ports: [80, 443]
};
console.log(server.name); // 点号取值
console.log(server["ip"]); // 方括号取值——变量键名时用这个解构赋值——从对象里一次性取多个字段:
js
const { name, ip } = server;
console.log(name, ip);展开运算符合并对象:
js
const base = { env: "prod", enabled: true };
const web = { ...base, name: "web-01" };
// web === { env: "prod", enabled: true, name: "web-01" }后面的字段会覆盖前面的同名字段:{ ...base, env: "staging" } 的结果 env 是 "staging"。
JSON 互转
JSON 是运维数据交换的最常用格式。JS 原生支持:
js
// 对象 → JSON 字符串
const server = { name: "web-01", ip: "10.0.0.11" };
const text = JSON.stringify(server, null, 2);
// null 是 replacer(不过滤),2 是缩进空格数——不加的话是一长行,人读不了
console.log(text);
// JSON 字符串 → 对象
const parsed = JSON.parse(text);
// JSON.parse 碰到不合法的 JSON 会直接抛 SyntaxError,脚本里必须 try/catch
console.log(parsed.name);JSON 不支持注释。配置文件如果需要注释,要么用 YAML(然后转),要么在 README 里写字段说明。生产配置里塞 // comment 会导致 JSON.parse 失败。
异常处理:try/catch
会失败的操作用 try/catch 包住:
js
function parseConfig(raw) {
try {
return JSON.parse(raw);
} catch (err) {
// err.message 是简要说明,err.stack 是完整堆栈
throw new Error(`invalid config json: ${err.message}`);
}
}脚本入口要兜底:
js
function main() {
const port = Number("abc");
if (Number.isNaN(port)) {
throw new Error("port must be a number");
}
}
try {
main();
} catch (err) {
console.error(err.message); // stderr,不污染 stdout 数据流
process.exit(1); // 非零退出码——调度系统识别失败
}退出码是关键。运维脚本最怕的就是"打印了错误但退出码是 0"——crontab、CI、调度器看到退出码 0 就认为一切正常,后面的步骤照常执行。
数组、对象、JSON、异常处理是运维脚本写的最密集的几块。原型链、类继承、this 绑定规则、Symbol、Proxy 这些在写巡检工具、API 和 CLI 时用得少,碰到具体场景再补。