Skip to content

JavaScript 基础语法

Node.js 脚本最终由 JavaScript 语法构成。运维场景最密集使用的那部分语法:变量、六种常见类型、条件分支、循环、函数、数组和对象的方法、JSON 互转、异常处理。

一段代码怎么跑起来

node <文件名> 按文件从上到下解释执行。语法错误在执行前就报出来(SyntaxError),运行时错误在走到对应位置才抛出。

js
// hello.js
console.log("hello node");
// 正常结果走 stdout,后面可以被管道继续处理
bash
node hello.js
js
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];    // 重新赋值会报 TypeError

const 约束的是"变量名不能重新指向新的值"。数组的内容、对象的属性仍然可以修改——它不提供深层的不可变保护。

六种常见类型

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 时用得少,碰到具体场景再补。