java script 入门教程:从变量、函数到页面交互
JavaScript(常写作 JS)是一种编程语言,常用于浏览器中的页面交互,也可运行在 Node.js 等环境里,用于命令行工具、服务端程序和自动化任务。它和 Java 是不同的语言;“JavaScript”这个名称不表示它是 Java 的简化版。
本文从可以直接运行的代码开始,逐步介绍变量、数据类型、函数、数组与对象,再把它们组合成页面交互。示例面向现代浏览器,不依赖第三方库。学习时可以把片段复制到浏览器开发者工具的控制台,或保存到本地 HTML 文件中运行。
一、JavaScript 在哪里运行
浏览器为 JavaScript 提供了网页相关的能力,例如查找和修改 HTML 元素、响应点击事件、发送网络请求。Node.js 则提供文件、进程等服务端或本地运行能力。语言核心语法有许多共同之处,但浏览器的 document 和 Node.js 的文件 API 并不是通用语言本身的一部分,不能假设任意 JS 运行环境都有它们。
在 HTML 中可以通过 script 元素加载脚本。初学时推荐把代码放在单独文件中,并使用 defer,让浏览器先解析页面,再执行脚本:
<script src="app.js" defer></script>
也可以把代码放在浏览器开发者工具的 Console(控制台)里执行。控制台适合尝试短代码,但页面刷新后其中的修改通常不会保留。
二、变量:保存和更新数据
JavaScript 使用 let、const 和较旧的 var 声明变量。新代码通常优先使用 const;只有确实要给变量重新赋值时才使用 let。除非需要维护旧代码,一般不建议新手使用 var,因为它的作用域和提升规则较容易造成意外。
const siteName = "示例站点";
let visitCount = 0;
visitCount = visitCount + 1;
console.log(siteName);
console.log(visitCount);
const 表示变量绑定不能重新指向另一个值,并不意味着它引用的对象内容绝对不能修改:
const user = { name: "小林" };
user.name = "小周"; // 可以修改对象属性
// user = {}; // 报错:不能重新给 const 变量赋值
因此,const 并不会自动冻结对象。若需要不可变数据,应通过代码约定、复制对象或其他专门方式实现;不要把“用 const 声明”误解成对象不可修改。
变量有作用域。用 let 或 const 声明的变量通常受所在代码块限制:
if (true) {
const message = "只在这个代码块内可见";
console.log(message);
}
// console.log(message); // 报错:message 不在当前作用域中
好的变量名能说明数据用途。通常使用 camelCase(小驼峰),例如 totalPrice、isVisible。避免用 name、x 等过于含糊的名字,除非它们的含义在上下文中非常清楚。
三、常见数据类型和运算
JavaScript 常见的原始类型包括字符串、数字、布尔值、undefined、null、bigint 和 symbol。日常入门中,字符串、数字和布尔值最常遇到;数组、函数和普通对象属于对象类型。
const title = "欢迎";
const count = 3;
const isReady = true;
let notAssigned;
const emptyValue = null;
console.log(typeof title); // "string"
console.log(typeof count); // "number"
console.log(typeof isReady); // "boolean"
console.log(typeof notAssigned); // "undefined"
typeof null 会返回 "object",这是 JavaScript 的历史行为,不代表 null 是普通对象。判断值是否为 null 时,应直接比较:value === null。
数字类型包括整数和小数,运算时要留意浮点数精度:
console.log(0.1 + 0.2); // 通常得到 0.30000000000000004
console.log(10 / 0); // Infinity
这不是 JavaScript 独有的问题,而是常见二进制浮点数表示带来的结果。涉及金额时,不要依赖浮点数直接精确表示小数金额;常见做法是以最小货币单位的整数计算,例如把“12.34 元”转换成“1234 分”,并根据业务规则处理换算和舍入。
普通 JS 数字能精确表示的整数范围有限,可用 Number.MAX_SAFE_INTEGER 查看上界。超过安全整数范围的整数计算可能失去精度。需要处理特别大的整数时,可以考虑 BigInt,但它不能和普通 Number 直接混合运算,也不能直接用作 JSON 数字序列化:
const big = 9007199254740993n;
console.log(big + 1n); // 9007199254740994n
// big + 1; // 报错:BigInt 和 Number 不能直接混合运算
比较值时,通常使用严格相等运算符 === 和 !==。宽松相等 == 会进行类型转换,初学阶段容易因此误判:
console.log(5 == "5"); // true:发生了类型转换
console.log(5 === "5"); // false:类型和值都要相同
条件判断也要注意“假值”。false、0、空字符串、null、undefined 和 NaN 在布尔上下文中会被当作假。若数字 0 本身是有效输入,不要仅用 if (value) 判断它是否存在,应按业务需求明确检查类型或空值。
四、条件与循环
用 if...else 表达分支,用 switch 处理多个离散情况。条件要尽量明确,避免依赖隐式类型转换。
const score = 86;
if (score >= 90) {
console.log("优秀");
} else if (score >= 60) {
console.log("通过");
} else {
console.log("需要继续练习");
}
需要重复执行时,可以使用 for、while 等循环。循环条件必须能在适当时候结束,否则会形成无限循环,导致页面卡顿。
const colors = ["红", "绿", "蓝"];
for (let i = 0; i < colors.length; i += 1) {
console.log(colors[i]);
}
// 遍历数组元素时,也常用 for...of
for (const color of colors) {
console.log(color);
}
for...of 适合遍历数组等可迭代对象的值。for...in 遍历的是对象可枚举属性名,不建议把它当作数组遍历的默认写法。
五、函数:把操作组织起来
函数可以接收输入、执行操作并返回结果。函数声明、函数表达式和箭头函数都很常见:
function add(a, b) {
return a + b;
}
const multiply = function (a, b) {
return a * b;
};
const square = (number) => number * number;
console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6
console.log(square(4)); // 16
箭头函数只有一个参数时可以省略参数括号;函数体只有一个表达式时可以省略花括号,并隐式返回该表达式的结果。初学时不必追求最短写法,复杂逻辑使用花括号和明确的 return 往往更易读。
JavaScript 不会像某些静态类型语言那样,在调用函数时自动强制要求实参数量与形参数量完全相同。少传的参数值为 undefined,多传的参数不会自动报“参数过多”错误;普通函数可以通过 arguments(箭头函数没有自己的 arguments)或更推荐的剩余参数语法处理可变参数。
function greet(name, greeting = "你好") {
return `${greeting},${name ?? "访客"}`;
}
console.log(greet("小林")); // 你好,小林
console.log(greet("小林", "早上好")); // 早上好,小林
console.log(greet()); // 你好,访客
function sum(...numbers) {
return numbers.reduce((total, number) => total + number, 0);
}
console.log(sum(1, 2, 3)); // 6
默认参数只会在实参缺省或为 undefined 时生效;传入 null 不会触发默认值。上例用空值合并运算符 ??,只在值为 null 或 undefined 时提供备用值。它和 || 不同:0、false、空字符串都不会触发 ?? 的备用值。
函数参数本身没有自动的类型或业务范围校验。需要可信输入时,应显式检查,例如:
function calculateDiscount(price, rate) {
if (typeof price !== "number" || !Number.isFinite(price) || price < 0) {
throw new TypeError("price 必须是非负有限数字");
}
if (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0 || rate > 1) {
throw new RangeError("rate 必须在 0 到 1 之间");
}
return price * (1 - rate);
}
console.log(calculateDiscount(100, 0.2)); // 80
检查方式应由接口约定决定。不要只检查“不是空值”就认定输入合法;还需考虑类型、取值范围、格式以及业务规则。客户端校验能改善用户体验,但不能代替服务端对不可信输入的验证。
六、数组和对象
数组用于保存有顺序的一组值,对象用于把相关属性组织在一起:
const tasks = ["读文档", "写代码"];
tasks.push("运行测试");
const profile = {
name: "小周",
level: 1,
active: true
};
console.log(tasks[0]); // 读文档
console.log(profile.name); // 小周
profile.level = 2;
数组索引从 0 开始。访问不存在的索引或属性通常会得到 undefined,这不一定会立即报错,因此在使用数据前要确认其存在。遍历或转换数组时,常用 map、filter 和 find:
const products = [
{ name: "笔记本", price: 20 },
{ name: "笔", price: 3 },
{ name: "文件夹", price: 8 }
];
const names = products.map((item) => item.name);
const affordable = products.filter((item) => item.price < 10);
const notebook = products.find((item) => item.name === "笔记本");
console.log(names); // ["笔记本", "笔", "文件夹"]
console.log(affordable); // 笔和文件夹
console.log(notebook); // 对应的对象
这些方法不会自动验证数组元素的结构。若数据来自网络或用户输入,应先验证数据,再按预期读取属性。也要注意对象和数组赋值通常复制的是引用:
const original = { count: 1 };
const copy = original;
copy.count = 2;
console.log(original.count); // 2,因为两者指向同一个对象
如需浅拷贝,可以使用展开语法:
const original = { name: "小林", settings: { theme: "light" } };
const copy = { ...original };
copy.name = "小周";
copy.settings.theme = "dark";
console.log(original.name); // 小林
console.log(original.settings.theme); // dark:嵌套对象仍共享引用
展开语法是浅拷贝,不会递归复制嵌套对象。复杂数据结构应根据需求选择合适的复制策略,避免以为“复制了对象”就完全隔离了内部数据。
七、操作网页:DOM 与事件
DOM 是浏览器提供的网页文档对象模型。可以用选择器找到元素、读取表单值并更新页面。下面是一个完整示例:保存为 index.html 后,用浏览器打开即可运行。点击按钮会把输入的名字显示在页面中。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>打招呼示例</title>
<script src="app.js" defer></script>
</head>
<body>
<label for="name-input">你的名字</label>
<input id="name-input" type="text" autocomplete="name">
<button id="greet-button" type="button">打招呼</button>
<p id="message" aria-live="polite">等待输入</p>
</body>
</html>
将下面代码保存为同一目录下的 app.js:
const input = document.querySelector("#name-input");
const button = document.querySelector("#greet-button");
const message = document.querySelector("#message");
button.addEventListener("click", () => {
const name = input.value.trim();
if (name === "") {
message.textContent = "请先输入名字。";
input.focus();
return;
}
message.textContent = `你好,${name}!`;
});
querySelector 返回匹配的第一个元素;找不到时返回 null。如果脚本执行时页面元素还没有解析到,选择器也可能找不到目标。本例用 defer 加载外部脚本,让它在文档解析后执行。另一种常见做法是把脚本放在相关 HTML 元素之后。
addEventListener 用于注册事件处理函数。用户点击按钮时,浏览器调用传入的函数。事件回调中的代码只在事件发生时执行,因此不要把按钮逻辑误写成页面加载时就立即运行的函数调用。
示例使用 textContent 插入用户输入。它会把内容作为文本显示,而不是把字符串解析成 HTML。不要把不可信输入直接放进 innerHTML:如果输入被解释为标记或脚本相关内容,可能造成跨站脚本(XSS)漏洞。需要渲染富文本时,应使用经过审查的消毒方案,并根据具体上下文正确编码;简单场景优先使用 textContent 和安全的 DOM API。
表单交互通常需要监听 submit,而不是只监听按钮点击,这样键盘提交也能正常工作。提交事件的默认行为可能会刷新或导航页面;在确实需要阻止默认提交时,可调用 event.preventDefault():
const form = document.querySelector("#task-form");
const taskInput = document.querySelector("#task-input");
const list = document.querySelector("#task-list");
form.addEventListener("submit", (event) => {
event.preventDefault();
const value = taskInput.value.trim();
if (value === "") return;
const item = document.createElement("li");
item.textContent = value;
list.append(item);
taskInput.value = "";
taskInput.focus();
});
这段代码需要页面中有对应的 form、输入框和列表元素。用 createElement 创建节点并设置 textContent,比拼接用户输入生成 HTML 字符串更安全。
八、异步操作:等待网络结果
网络请求可能需要时间。JavaScript 常通过 Promise 和 async/await 表达异步流程。以下示例从一个公开测试接口读取 JSON,并在页面上显示结果。网络可能失败、被浏览器策略或服务端限制,也可能返回非成功状态,因此要处理异常和 HTTP 状态:
async function loadPost() {
const output = document.querySelector("#result");
output.textContent = "加载中……";
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
if (!response.ok) {
throw new Error(`请求失败:HTTP ${response.status}`);
}
const post = await response.json();
output.textContent = post.title;
} catch (error) {
output.textContent = "加载失败,请稍后重试。";
console.error(error);
}
}
document.querySelector("#load-button")
.addEventListener("click", loadPost);
运行这段代码时,页面需要包含 id="result" 的元素和 id="load-button" 的按钮。await 只能放在 async 函数中(现代环境也支持部分模块顶层使用);它不会让网络请求变成同步阻塞,而是让后续逻辑以更易读的方式等待 Promise 结果。
成功调用 fetch 不代表 HTTP 状态一定成功:例如服务器返回 404 时,Promise 通常仍会兑现,因此要检查 response.ok 或状态码。跨域请求还受浏览器同源策略和服务器 CORS 响应头限制,客户端代码不能自行绕过这些限制。涉及密钥的服务不应把秘密凭据硬编码在浏览器脚本中,因为用户可以查看和提取前端代码;应由受控的服务端处理需要保密的凭据。
九、常见错误与排查方法
- 拼写和大小写不一致:JavaScript 区分大小写,
userName与username是不同名称。 - 把赋值当比较:
=用于赋值,比较通常使用===。在条件中意外写成赋值可能改变变量值。 - 未处理空元素:
querySelector可能返回null。检查选择器是否写对、脚本是否执行过早,并确认页面确实有对应元素。 - 混淆字符串和数字:表单的
input.value通常是字符串。要做数字运算,先按规则转换并验证,例如检查Number.isFinite(Number(value));空字符串转换为数字时会得到0,所以应先处理空值。 - 循环条件写错:确认循环变量会朝结束条件变化,并检查边界是小于还是小于等于,防止死循环或访问越界。
- 异步结果尚未返回就使用:网络请求、定时器等异步操作需要在 Promise 的后续流程中处理结果,不能假设它们会立即完成。
- 把用户输入当安全内容:输入框数据、URL 参数和网络返回值都可能是不可信数据。按用途进行校验,并优先用安全的 DOM API 输出。
排错时先看浏览器开发者工具的 Console,定位错误信息指出的文件和行号;再逐步检查变量值、元素是否找到、事件是否触发。必要时使用 console.log 或断点,不要只靠反复改代码碰运气。还要留意浏览器控制台中的网络错误、CORS 提示和脚本加载失败信息。
十、适用场景与边界
JavaScript 适合制作表单校验、菜单展开、内容筛选、交互式组件、网络数据展示等浏览器功能;在 Node.js 中也能用于服务端接口、开发工具和自动化脚本。对于纯静态内容,HTML 和 CSS 已经足够,不必为了使用 JS 而增加脚本。复杂应用可以使用框架,但理解语言、DOM、异步流程和安全边界仍然重要。
浏览器端代码运行在用户设备上,用户可以查看、修改,也可以绕过页面上的校验。因此,前端校验适合及时提示错误,不适合作为权限控制、支付金额确认或数据访问控制的唯一防线。服务端必须独立验证身份、权限、数据格式和业务规则。JavaScript 本身也不能保证程序逻辑正确、数据可信或代码安全;这些需要结合系统设计、测试和部署环境来保证。
入门阶段可以按这个顺序练习:在控制台操作变量和函数;写一个根据输入更新文字的小页面;再加入列表、表单校验和网络请求。每次只增加一个功能,并有意识地检查空值、错误输入、网络失败和用户可控内容,能比单纯记忆语法更快建立可靠的开发习惯。





