
JS 入门:从变量、函数到 DOM 操作
JavaScript(简称 JS)是网页开发中常用的编程语言。它可以在浏览器中响应点击、校验输入、更新页面内容,也可以运行在服务器、命令行工具和桌面应用环境中。初学者接触 JS,通常从变量、条件判断、函数和 DOM 操作开始。
本文会逐步介绍这些基础内容,并说明它们适合解决什么问题、容易在哪里出错。示例以现代浏览器支持的 JavaScript 语法为主。将示例代码放进 HTML 页面的 <script> 标签,或放入浏览器开发者工具的 Console 中运行即可。涉及页面元素的示例需要先在页面中创建对应的 HTML 元素。
一、JavaScript 在哪里运行
浏览器内的 JavaScript 可以操作当前网页,但它并不是 HTML 的替代品。HTML 描述内容结构,CSS 描述样式,JavaScript 负责行为和动态逻辑。例如,按钮本身由 HTML 创建,颜色和布局由 CSS 控制,点击后显示提示则可以由 JavaScript 处理。
网页中的脚本可以写在 HTML 文件里,也可以放在单独的 .js 文件中。初学时可以用内嵌脚本快速验证;项目稍大后,建议拆分文件,便于维护。外部脚本通常这样引入:
<script src="app.js" defer></script>
defer 表示脚本在 HTML 解析完成后执行,并按文档中的顺序执行。对于需要访问页面元素的普通脚本,它可以避免脚本运行时元素尚未解析的问题。另一种做法是把脚本放在 </body> 结束标签前,但应选择一种清晰一致的方式。
二、变量:保存和更新数据
变量用来给数据起名字,让程序可以读取或更新它。现代 JavaScript 通常使用 const 和 let:
const用于不会重新赋值的变量。let用于需要重新赋值的变量。var是较早的声明方式,作用域规则与前两者不同,新代码通常优先使用const和let。
const siteName = "示例网站";
let visitCount = 0;
visitCount = visitCount + 1;
console.log(siteName);
console.log(visitCount);
const 限制的是变量绑定不能被重新赋值,并不代表它引用的对象内容不可更改:
const user = { name: "小林" };
user.name = "小周"; // 可以修改对象的属性
console.log(user.name);
// user = {}; // TypeError:不能给 const 变量重新赋值
变量名应当表达用途,例如 totalPrice 比 x 更易读。变量名区分大小写,不能以数字开头,也不能使用保留关键字。常见风格是使用小驼峰命名法:userName、isVisible。
三、常见数据类型与比较
初学阶段经常遇到的类型包括字符串、数字、布尔值、null、undefined、对象和数组。可以使用 typeof 查看很多值的类型,但它并不能对所有类型给出直观结果。
const title = "JavaScript";
const price = 19.9;
const isReady = true;
let currentUser;
const emptyValue = null;
console.log(typeof title); // "string"
console.log(typeof price); // "number"
console.log(typeof isReady); // "boolean"
console.log(typeof currentUser); // "undefined"
console.log(typeof emptyValue); // "object"(历史遗留行为)
undefined 常表示一个值尚未赋值,例如声明了变量但没有给它赋值;null 通常用于主动表示“这里没有对象或值”。这两者不是同一个值。
比较时,推荐优先使用严格相等运算符 === 和严格不等运算符 !==。它们不会像宽松比较 == 那样尝试进行类型转换。
console.log(5 === 5); // true
console.log(5 === "5"); // false
console.log(5 == "5"); // true,发生了类型转换
字符串与数字混用时,先明确转换规则,不要依赖宽松比较或隐式转换。比如从输入框读取到的内容通常是字符串,即使用户输入的是数字:
const inputValue = "42";
console.log(inputValue + 1); // "421",字符串拼接
console.log(Number(inputValue) + 1); // 43
Number() 可以尝试把值转换为数字,但不合法的输入会得到 NaN。可用 Number.isNaN() 检查转换结果。不要仅凭“看起来像数字”就假设转换一定成功。
const amount = Number("abc");
if (Number.isNaN(amount)) {
console.log("请输入有效数字");
}
四、条件判断与循环
条件判断适合根据不同状态执行不同逻辑。条件表达式会按真值或假值进行判断。常见假值包括 false、0、空字符串 ""、null、undefined 和 NaN;空数组和空对象则是真值。
const score = 86;
if (score >= 90) {
console.log("优秀");
} else if (score >= 60) {
console.log("通过");
} else {
console.log("需要继续练习");
}
使用真值判断时要留意:如果数字 0 是有意义的输入,直接写 if (value) 会把它当成假值。需要区分“没有提供值”和“提供了 0”时,应明确检查 null 或 undefined 等情况。
for 循环适合已知次数或遍历数组的情形;while 循环适合循环次数取决于条件的情况。循环条件必须能在合适的时候变为假,否则会造成无限循环。
const colors = ["红", "绿", "蓝"];
for (let i = 0; i < colors.length; i += 1) {
console.log(colors[i]);
}
let count = 3;
while (count > 0) {
console.log(count);
count -= 1;
}
遍历数组还可以使用 for...of,它直接取得每一项,通常比手动维护下标更简洁:
for (const color of colors) {
console.log(color);
}
五、函数:把逻辑组织起来
函数用于封装可重复使用的逻辑。它可以接收参数,也可以通过 return 返回结果。函数适合将较大的任务拆成有明确职责的小部分,例如格式化文本、计算金额或处理一次按钮交互。
function add(a, b) {
return a + b;
}
const result = add(3, 4);
console.log(result); // 7
函数声明可以在其所在作用域内先调用、后声明,这是声明提升的结果。函数表达式和箭头函数则通常要先完成赋值,再调用:
const multiply = (a, b) => {
return a * b;
};
console.log(multiply(3, 4)); // 12
只有一个参数时,箭头函数的参数括号可以省略;只有一个表达式并且要直接返回时,可以省略大括号和 return:
const double = number => number * 2;
console.log(double(6)); // 12
函数参数在 JavaScript 中没有统一的、面向所有环境和场景的“最多参数数量”保证。引擎、运行环境和具体调用方式可能存在实现限制,不应把极大的参数列表当作可靠设计。实际代码中应让函数职责清晰;参数较多时,可以传入对象并使用命名属性。
function createAccount({ name, email, isAdmin = false }) {
return { name, email, isAdmin };
}
const account = createAccount({
name: "小陈",
email: "[email protected]"
});
console.log(account);
解构参数中的默认值只会在属性值为 undefined 时生效,传入 null 不会触发默认值。调用函数时少传参数,缺少的参数通常是 undefined;多传的参数不会自动报错,但未命名接收的参数不会出现在普通参数变量中。需要读取可变数量的参数时,可以使用剩余参数语法:
function sum(...numbers) {
return numbers.reduce((total, number) => total + number, 0);
}
console.log(sum(2, 3, 5)); // 10
剩余参数必须放在参数列表末尾。它接收的是一个数组,和较早的 arguments 对象不同。设计函数时,最好检查参数是否符合业务要求;参数类型、范围和缺省行为应当清晰,而不要假设调用者总会传入正确数据。
函数还有作用域的概念。函数内部用 let 或 const 声明的变量,通常不能在函数外直接访问。合理的作用域有助于避免变量意外被其他逻辑修改。
六、数组与对象
数组用于按顺序保存多个值。常见操作包括读取下标、添加元素和遍历。数组下标从 0 开始,访问不存在的下标通常会得到 undefined。
const tasks = ["阅读", "练习"];
console.log(tasks[0]); // "阅读"
tasks.push("复习");
console.log(tasks.length); // 3
for (const task of tasks) {
console.log(task);
}
需要从数组中筛选、转换或查找数据时,可以使用 filter、map、find 等方法。这些方法会根据回调函数处理元素,适合表达“得到一个新数组”或“找出符合条件的项”。
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(number => number * 2);
const evenNumbers = numbers.filter(number => number % 2 === 0);
const firstLarge = numbers.find(number => number > 2);
console.log(doubled); // [2, 4, 6, 8]
console.log(evenNumbers); // [2, 4]
console.log(firstLarge); // 3
对象用于把相关数据组织成键值对。属性可以用点号访问,也可以在属性名动态变化时使用方括号。
const product = {
name: "笔记本",
price: 35
};
console.log(product.name);
const key = "price";
console.log(product[key]);
对象和数组赋值时传递的是引用。把对象赋给另一个变量,并不会自动复制对象本身:
const original = { count: 1 };
const alias = original;
alias.count = 2;
console.log(original.count); // 2
如果确实需要复制,浅拷贝可以使用展开语法,但它只复制第一层;嵌套对象仍可能共享引用。应根据数据结构选择合适的复制方式,不要误以为展开语法能完成任意深度的独立复制。
七、DOM:读取和修改网页元素
DOM(文档对象模型)是浏览器对 HTML 文档提供的编程接口。页面加载后,JavaScript 可以用 DOM 查找元素、更新文字、设置属性和监听事件。它适用于网页交互,不适用于没有网页 DOM 的普通运行环境,例如某些服务器端脚本环境。
现代代码通常使用 document.querySelector() 按 CSS 选择器获取第一个匹配元素;要获取多个匹配项,可以使用 document.querySelectorAll()。找不到元素时,querySelector() 会返回 null,所以调用属性或方法前要先确认元素存在。
<h1 id="page-title">欢迎</h1>
<p class="status">等待操作</p>
<script>
const title = document.querySelector("#page-title");
const status = document.querySelector(".status");
if (title && status) {
title.textContent = "JavaScript 入门";
status.textContent = "页面已就绪";
}
</script>
textContent 用于设置或读取纯文本。若目标是展示用户输入,优先使用它,而不是把输入拼成 HTML 后赋给 innerHTML。innerHTML 会把字符串按 HTML 解析;如果字符串含有不可信内容,可能造成跨站脚本攻击(XSS)。
const message = document.querySelector("#message");
const userInput = "<img src=x onerror=alert('危险')>";
if (message) {
message.textContent = userInput; // 按文本显示,不会把输入解析成标签
}
textContent 适合普通文字,但不负责验证链接、脚本、样式或其他业务规则。需要把内容作为 HTML 渲染时,不能简单认为“过滤几个字符”就足够安全。应使用经过维护的 HTML 清理工具,并根据具体上下文采取安全处理;对于不可信的 URL,也要检查协议和允许的目标。
八、事件:响应用户操作
事件机制让页面可以响应点击、输入、提交等操作。使用 addEventListener() 注册监听器,可以把事件行为与 HTML 结构分开。事件处理函数会收到事件对象,常见属性包括触发事件的目标元素。
<button id="counter-button" type="button">点击次数:0</button>
<script>
const button = document.querySelector("#counter-button");
let count = 0;
if (button) {
button.addEventListener("click", () => {
count += 1;
button.textContent = `点击次数:${count}`;
});
}
</script>
如果监听器需要在之后移除,可以保存同一个函数引用,再传给 removeEventListener()。临时写入的匿名函数没有可复用的引用,不能用另一个内容相同的匿名函数移除原监听器。
const button = document.querySelector("#save-button");
function handleSave() {
console.log("已保存");
}
if (button) {
button.addEventListener("click", handleSave);
// 需要时移除同一个函数引用
button.removeEventListener("click", handleSave);
}
表单提交时,浏览器默认会尝试提交表单并可能刷新页面。若要在当前页面自行处理,需要在提交事件中调用 event.preventDefault()。这只会阻止该次默认行为,不等于输入内容已安全或合法。
<form id="name-form">
<label for="name-input">姓名</label>
<input id="name-input" name="name" required>
<button type="submit">提交</button>
</form>
<p id="form-result"></p>
<script>
const form = document.querySelector("#name-form");
const input = document.querySelector("#name-input");
const result = document.querySelector("#form-result");
if (form && input && result) {
form.addEventListener("submit", event => {
event.preventDefault();
const name = input.value.trim();
if (name.length === 0) {
result.textContent = "请输入姓名";
return;
}
result.textContent = `你好,${name}`;
});
}
</script>
表单上的 required 等 HTML 属性可以提供浏览器端的基础约束,但不能替代服务端验证。用户可以关闭或绕过页面脚本,直接构造请求。凡是关系到权限、账户、价格、资源访问或数据完整性的规则,都必须在可信的服务端重新验证。
九、DOM 操作的时机与常见问题
常见错误之一是脚本运行时元素还不存在。例如脚本写在 <head> 中并立即查询页面下方的按钮,查询结果可能是 null。可以使用外部脚本的 defer,或等 DOM 解析完成后再执行代码:
document.addEventListener("DOMContentLoaded", () => {
const button = document.querySelector("#action-button");
if (!button) {
return;
}
button.addEventListener("click", () => {
console.log("DOM 已就绪,按钮被点击");
});
});
另一个常见问题是混淆 querySelector() 和 querySelectorAll():前者返回单个元素或 null,后者返回匹配元素集合,即使没有匹配项,也可以得到一个空集合。遍历多个元素时,可以这样写:
const items = document.querySelectorAll(".item");
items.forEach(item => {
item.classList.add("processed");
});
修改样式时,优先通过 classList 添加、移除或切换 CSS 类,而不是在脚本里到处写行内样式。这样能让样式职责仍由 CSS 管理。
const panel = document.querySelector(".panel");
if (panel) {
panel.classList.toggle("is-open");
}
十、初学者常见错误清单
- 变量未定义或拼写不一致:JavaScript 区分大小写,
userName与username是不同名称。善用开发者工具的错误提示。 - 把输入框内容当作数字:
input.value通常是字符串,需要按业务规则转换并检查结果。 - 使用
==导致意外转换:没有明确理由时使用===,减少难以察觉的比较行为。 - 忘记 DOM 查询可能失败:检查选择器是否写对、元素是否已加载,并在访问前处理
null。 - 把用户输入塞进
innerHTML:不可信字符串可能被浏览器当作 HTML 解析。展示文字优先用textContent。 - 只在浏览器校验数据:前端校验改善交互体验,但不能建立信任。服务端仍需验证并执行权限检查。
- 循环无法结束:检查循环变量是否更新,以及条件是否能最终变为假。
- 误以为
const会冻结对象:const阻止重新赋值,不会自动冻结对象或数组。
十一、组合练习:用 DOM 添加清单项目
下面把变量、事件、表单输入和 DOM 操作组合起来。将完整代码放入一个 HTML 文件中即可运行。提交非空文字后,页面会增加一条清单项目。示例使用 textContent 写入用户内容,避免把输入作为 HTML 解析。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>清单练习</title>
</head>
<body>
<h1>今日清单</h1>
<form id="task-form">
<label for="task-input">新任务</label>
<input id="task-input" name="task" required>
<button type="submit">添加</button>
</form>
<p id="notice" aria-live="polite"></p>
<ul id="task-list"></ul>
<script>
const form = document.querySelector("#task-form");
const input = document.querySelector("#task-input");
const list = document.querySelector("#task-list");
const notice = document.querySelector("#notice");
if (form && input && list && notice) {
form.addEventListener("submit", event => {
event.preventDefault();
const taskText = input.value.trim();
if (taskText.length === 0) {
notice.textContent = "请输入任务内容";
return;
}
const item = document.createElement("li");
item.textContent = taskText;
list.append(item);
input.value = "";
notice.textContent = "任务已添加";
input.focus();
});
}
</script>
</body>
</html>
练习时可以继续添加删除按钮、任务完成状态或本地保存功能。每增加一种行为,都先确定数据从哪里来、要修改哪一部分页面,以及用户提供的内容是否会被当作可执行代码。这样逐步扩展,比把所有逻辑堆进一个事件回调更容易调试。
结语
学习 JavaScript 的基础路线可以概括为:先用变量和数据类型表达状态,再用条件与循环控制流程,用函数拆分逻辑,最后通过 DOM 和事件让页面响应用户操作。写网页脚本时,尤其要处理好元素可能不存在、输入类型需要转换、前端校验不能代替服务端验证,以及不可信文本不能随意作为 HTML 渲染等边界。
掌握语法之后,建议在浏览器开发者工具中逐段运行代码,观察变量值、事件触发顺序和 DOM 变化。遇到问题时,先检查控制台报错,再确认选择器、数据类型和执行时机,通常就能定位大多数入门阶段的错误。




