JavaScript 是什么

JavaScript(简称 JS)是网页的"行为层"语言:它可以操作页面元素、处理用户交互、发起网络请求。如今它也能在服务端(Node.js)、小程序、桌面应用中运行。

JS 在浏览器中由 V8 等引擎执行,与 Java 没有关系。

<!-- 在 HTML 中引入 JS -->
<script src="js/main.js"></script>

<!-- 或直接写在标签里 -->
<script>
  console.log("你好,JavaScript!");
</script>
⚠️ 注意
建议把 <script> 放在 <body> 末尾,或用 defer 属性,避免阻塞页面渲染。

变量与数据类型

声明变量用 let(可变)和 const(常量,优先使用):

const name = "小明";   // 字符串
let age = 25;          // 数字
const isActive = true; // 布尔值
const tags = ["html", "css", "js"];  // 数组
const user = { name: "小明", age: 25 }; // 对象

console.log(user.name); // 输出:小明
console.log(tags.length); // 输出:3

类型转换常用写法:

const num = Number("42");   // 字符串 → 数字
const str = String(42);     // 数字 → 字符串
const bool = Boolean(0);    // 0、""、null、undefined → false

// 模板字符串
console.log(`今年 ${age} 岁`);
💡 提示
ES6 之后推荐用 let/const,var 只在老代码中出现。

函数

函数把一段可复用的逻辑封装起来:

// 函数声明
function greet(name) {
  return `你好,${name}!`;
}

// 箭头函数(现代写法)
const add = (a, b) => a + b;

// 默认参数
const say = (msg = "默认消息") => console.log(msg);

console.log(greet("小明")); // 你好,小明!
console.log(add(1, 2));     // 3

数组常用方法(函数式编程基础):

const nums = [1, 2, 3, 4];

const doubled = nums.map(n => n * 2);     // [2, 4, 6, 8]
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
const total = nums.reduce((sum, n) => sum + n, 0); // 10
const hasBig = nums.some(n => n > 3);      // true

DOM 操作

DOM(文档对象模型)把 HTML 变成一棵对象树,JS 通过它增删改查页面元素:

// 查找元素
const btn = document.querySelector("#btn");
const cards = document.querySelectorAll(".card");

// 修改内容与样式
btn.textContent = "点击我";
btn.style.color = "#fff";

// 创建并插入元素
const li = document.createElement("li");
li.textContent = "新项目";
document.querySelector("ul").appendChild(li);

// 增删 class
btn.classList.add("active");
btn.classList.remove("active");
✅ 建议
querySelector/querySelectorAll 是万能选择器,语法与 CSS 选择器一致。

事件处理

事件是用户与页面交互的桥梁。最常用的是 click,还有 input、submit、scroll、keydown 等:

const btn = document.querySelector("#btn");
const count = document.querySelector("#count");

let num = 0;
btn.addEventListener("click", () => {
  num++;
  count.textContent = num;
});

// 表单提交
const form = document.querySelector("#form");
form.addEventListener("submit", (e) => {
  e.preventDefault(); // 阻止页面刷新
  const data = new FormData(form);
  console.log(Object.fromEntries(data));
});

语言定位

JavaScript 是唯一一门浏览器原生支持的编程语言,负责网页的交互与行为:点击响应、表单校验、异步请求、动画效果。

它和 Java 除了名字没有任何关系。现代的 JavaScript(ES6+)已经是一门功能完备的语言,能写前端(React/Vue)、后端(Node.js)、桌面应用(Electron)。

三大支柱回顾:HTML 结构、CSS 样式、JavaScript 行为——三者缺一不可。

💡 提示
写 JavaScript 不需要安装任何环境:浏览器按 F12 打开控制台(Console)即可直接执行代码调试。

引入方式与变量

引入方式

<!-- 外部文件(推荐) -->
<script src="js/main.js"></script>

<!-- 内联脚本(仅调试用) -->
<script>
  console.log("hello");
</script>

变量声明

let count = 0;        // 可变变量,优先使用
const PI = 3.14;      // 常量,不可重新赋值
var old = 1;          // 旧语法,有作用域坑,不建议再用

// 基本类型
let name = "小明";     // string
let age = 25;         // number
let isAdmin = false;  // boolean
let nothing = null;   // null
let undef;            // undefined
✅ 建议
一律使用 const 声明,需要重新赋值时才改用 let,代码更可预期。

基础运算与字符串

// 算术运算
let sum = 10 + 5;          // 15
let mod = 10 % 3;          // 1(取余)
let power = 2 ** 10;       // 1024(幂)

// 比较
console.log(5 === "5");    // false(严格相等,推荐)
console.log(5 == "5");     // true(宽松相等,有坑)

// 字符串:模板字符串
let user = "小明";
console.log(`你好,${user}!`);   // 你好,小明!

// 类型转换
let n = Number("42");      // 42
let s = String(42);        // "42"
⚠️ 注意
永远使用 === 和 !== 比较,避免隐式类型转换带来的诡异 bug。

函数声明与表达式

函数是 JavaScript 组织逻辑的基本单位,有几种写法:

// 1. 函数声明(有提升)
function add(a, b) {
    return a + b;
}

// 2. 函数表达式
const multiply = function (a, b) {
    return a * b;
};

// 3. 默认参数
function greet(name = "朋友") {
    return `你好,${name}!`;
}

console.log(greet());        // 你好,朋友!
console.log(greet("小明"));  // 你好,小明!

函数声明会提升(hoisting),所以可以在定义之前调用;函数表达式则不行。

箭头函数

ES6 引入的箭头函数写法更简洁,而且不绑定自己的 this(继承外层):

// 简化写法
const square = (x) => x * x;
const add = (a, b) => a + b;
const sayHi = () => console.log("hi");

// 多行函数体
const calc = (a, b) => {
    const sum = a + b;
    return sum * 2;
};

// 箭头函数与回调:数组方法的最佳搭档
const nums = [1, 2, 3, 4];
const doubled = nums.map(n => n * 2);     // [2, 4, 6, 8]
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
const total = nums.reduce((acc, n) => acc + n, 0); // 10
✅ 建议
回调场景优先用箭头函数;需要动态 this(如事件处理要解绑)时用普通函数。

作用域与闭包

作用域决定变量在哪里可见:

let global = "全局";       // 全局作用域

function outer() {
    let local = "函数内";   // 函数作用域
    if (true) {
        let block = "块级"; // let/const 是块级作用域
        console.log(block); // 可以访问
    }
    // console.log(block);  // ❌ 报错,块外不可见
}

闭包:函数"记住"了它定义时的作用域,即使外层函数已经返回:

function counter() {
    let count = 0;              // 私有变量,外部无法直接访问
    return function () {
        count += 1;
        return count;
    };
}

const next = counter();
console.log(next());  // 1
console.log(next());  // 2
// count 只能在闭包内被修改,实现了"封装"
💡 提示
闭包最常见的用途:封装私有状态、实现模块化、在循环中保留变量快照。

数组常用方法

数组方法分为修改原数组和返回新数组两类,分清很重要:

// 修改原数组
let arr = [3, 1, 2];
arr.push(4);          // 末尾添加 [3,1,2,4]
arr.pop();            // 末尾删除
arr.unshift(0);       // 开头添加
arr.shift();          // 开头删除
arr.sort();           // 排序

// 返回新数组(不修改原数组)
const nums = [1, 2, 3, 4, 5];
const d = nums.map(n => n * 2);        // 映射
const f = nums.filter(n => n > 2);     // 过滤
const fnd = nums.find(n => n > 3);     // 找第一个,返回 4
const idx = nums.indexOf(3);           // 找下标
const has = nums.includes(2);          // 是否包含 true
const sliced = nums.slice(1, 3);       // 切片 [2,3]
const joined = nums.join("-");         // "1-2-3-4-5"
✅ 建议
能用 map / filter / reduce 表达的逻辑,不要写 for 循环,代码更声明式、更易读。

对象操作

const user = {
    name: "小明",
    age: 25,
    skills: ["Python", "JS"],
};

// 增删改查
user.city = "北京";          // 新增
user.age = 26;               // 修改
delete user.city;            // 删除

// 遍历
for (const key of Object.keys(user)) {
    console.log(key, user[key]);
}
Object.entries(user).forEach(([k, v]) => console.log(k, v));

// 合并与拷贝
const more = { email: "a@b.com" };
const full = { ...user, ...more };   // 展开合并(浅拷贝)
const copy = { ...user };            // 浅拷贝

// 可选链与空值合并(ES2020)
console.log(user?.address?.city);    // undefined,不报错
const name = user.name ?? "匿名";    // 仅在 null/undefined 时取默认
⚠️ 注意
... 展开是浅拷贝,嵌套对象仍共享引用,深拷贝用 structuredClone()。

解构赋值

解构让"从数组/对象取值"变得非常优雅:

// 数组解构
const [first, second, ...rest] = [10, 20, 30, 40];
// first=10, second=20, rest=[30,40]

// 交换变量(无需临时变量)
let a = 1, b = 2;
[a, b] = [b, a];

// 对象解构
const point = { x: 100, y: 200, label: "A" };
const { x, y, label = "默认值" } = point;

// 重命名 + 默认值
const { x: px, y: py } = point;

// 函数参数解构(非常常用)
function draw({ x = 0, y = 0, color = "black" } = {}) {
    console.log(x, y, color);
}
draw({ x: 5 });   // 5 0 black
💡 提示
函数参数解构 + 默认值,是编写"可配置函数"的标准姿势,大量库的选项参数都这么写。

获取与修改元素

// 获取元素
const title = document.getElementById("title");
const cards = document.querySelectorAll(".card");  // 返回 NodeList
const btn = document.querySelector("#btn");        // 返回第一个

// 修改内容与属性
title.textContent = "新标题";           // 纯文本(安全)
title.innerHTML = "<b>富文本</b>";     // HTML(注意 XSS)
btn.setAttribute("disabled", "");      // 设置属性
btn.classList.add("active");           // 添加类
btn.classList.remove("active");        // 移除类
btn.classList.toggle("active");        // 切换类

// 修改样式
title.style.color = "#f97316";
title.style.fontSize = "20px";         // 驼峰命名

// 创建与插入节点
const li = document.createElement("li");
li.textContent = "新项目";
document.querySelector("ul").appendChild(li);
⚠️ 注意
把用户输入直接拼进 innerHTML 会造成 XSS 漏洞,展示文本务必用 textContent。

事件监听

const btn = document.querySelector("#btn");

// 绑定事件(推荐 addEventListener)
btn.addEventListener("click", (e) => {
    console.log("被点击了", e);
});

// 常用事件
// click 点击 / dblclick 双击 / mouseover 移入
// input 输入 / change 选择改变 / submit 表单提交
// keydown 按键 / scroll 滚动 / load 加载完成

// 表单提交(阻止默认刷新)
document.querySelector("form").addEventListener("submit", (e) => {
    e.preventDefault();        // 阻止页面刷新
    const value = document.querySelector("#input").value;
    console.log("提交了:", value);
});

// 事件委托:动态元素的点击
document.querySelector("#list").addEventListener("click", (e) => {
    if (e.target.tagName === "LI") {
        console.log("点击了列表项:", e.target.textContent);
    }
});
✅ 建议
动态生成的元素用事件委托(监听父容器),比给每个元素单独绑定性能更好、代码更少。

异步基础:Promise 与 fetch

网络请求是异步的,JavaScript 用 Promise 处理异步结果:

// fetch 发起请求(返回 Promise)
fetch("/api/tutorials")
    .then(res => res.json())      // 解析 JSON
    .then(data => {
        console.log(data);
    })
    .catch(err => console.error("请求失败", err));

// async/await 更直观的写法
async function loadData() {
    try {
        const res = await fetch("/api/tutorials");
        const data = await res.json();
        renderList(data);
    } catch (err) {
        console.error("请求失败", err);
    }
}

// 三种状态:pending 等待中 / fulfilled 成功 / rejected 失败
// Promise.all 并行请求
const [a, b] = await Promise.all([
    fetch("/api/a").then(r => r.json()),
    fetch("/api/b").then(r => r.json()),
]);
💡 提示
await 只能在 async 函数内使用;用 try/catch 包裹 await 处理网络错误是标准写法。
第 1 / 17 节