java script 入门教程:从变量、函数到页面交互

百科问答9小时前发布 jdkjadmin
0 00
java script 入门教程:从变量、函数到页面交互

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 本身也不能保证程序逻辑正确、数据可信或代码安全;这些需要结合系统设计、测试和部署环境来保证。

入门阶段可以按这个顺序练习:在控制台操作变量和函数;写一个根据输入更新文字的小页面;再加入列表、表单校验和网络请求。每次只增加一个功能,并有意识地检查空值、错误输入、网络失败和用户可控内容,能比单纯记忆语法更快建立可靠的开发习惯。

© 版权声明

相关文章