JavaScript 教程
JavaScript 是一种广泛用于 Web 开发的脚本语言,它可以使网页具有交互性和动态效果。通过本教程,你将学习 JavaScript 的基本概念和使用方法。
JavaScript 是一种轻量级的编程语言,主要用于 Web 前端开发,它可以嵌入到 HTML 页面中,使页面具有动态交互功能。
JavaScript 的特点
- 脚本语言:不需要编译,可以直接在浏览器中运行
- 解释型语言:代码逐行执行
- 面向对象:支持对象编程
- 动态类型:变量可以存储任何类型的数据
- 事件驱动:可以响应用户的交互事件
JavaScript 的用途
- 修改 HTML 内容和属性
- 修改 CSS 样式
- 响应用户事件(如点击、鼠标移动等)
- 验证表单数据
- 创建动画效果
- 与服务器进行通信(AJAX)
- 开发游戏和应用程序
JavaScript 的语法与 C 和 Java 类似,但也有自己的特点。
在 HTML 中使用 JavaScript
JavaScript 代码可以通过以下方式嵌入到 HTML 页面中:
1. 内联脚本
<script>
// JavaScript 代码
alert('Hello, World!');
</script>
2. 外部脚本
<!-- 在 HTML 文件中 -->
<script src="script.js"></script>
<!-- 在 script.js 文件中 -->
alert('Hello, World!');
基本语法规则
- 语句通常以分号结尾(在现代 JavaScript 中不是必须的,但建议使用)
- 区分大小写
- 使用双斜杠(//)添加单行注释
- 使用 /* ... */ 添加多行注释
- 代码块用花括号 {} 包裹
变量声明
JavaScript 有三种声明变量的方式:
// var: 函数作用域,可以重复声明
var x = 5;
var x = 10; // 允许
// let: 块级作用域,不能重复声明
let y = 15;
// let y = 20; // 错误
// const: 块级作用域,不能重新赋值
const z = 25;
// z = 30; // 错误
JavaScript 有多种数据类型,包括原始类型和引用类型。
原始数据类型
- String:字符串,用单引号或双引号包裹
- Number:数字,可以是整数或浮点数
- Boolean:布尔值,true 或 false
- Undefined:变量声明但未赋值时的默认值
- Null:表示空值或不存在的对象
- Symbol:ES6 新增,表示唯一标识符
- BigInt:ES2020 新增,可以表示任意精度的整数
引用数据类型
- Object:对象,由键值对组成
- Array:数组,用于存储多个值
- Function:函数
- Date:日期对象
- RegExp:正则表达式对象
数据类型示例
// 字符串
let name = 'JavaScript';
let greeting = "Hello, world!";
// 数字
let age = 25;
let price = 19.99;
// 布尔值
let isActive = true;
let isAvailable = false;
// undefined
let x;
console.log(x); // undefined
// null
let emptyValue = null;
// 数组
let fruits = ['apple', 'banana', 'orange'];
// 对象
let person = {
firstName: '张三',
lastName: '李四',
age: 30
};
// 函数
function greet() {
console.log('Hello!');
}
类型转换
// 显式转换
let numStr = '123';
let num = Number(numStr); // 123 (数字)
let str = String(123); // "123" (字符串)
let bool = Boolean('hello'); // true
// 隐式转换
let result = '5' + 3; // "53" (字符串)
let sum = '5' - 3; // 2 (数字)
// 其他转换方法
let num1 = parseInt('101'); // 101 (整数)
let num2 = parseFloat('3.14'); // 3.14 (浮点数)
let boolStr = String(true); // "true"
JavaScript 提供了多种运算符用于执行不同类型的操作。
算术运算符
let a = 10;
let b = 3;
let addition = a + b; // 13
let subtraction = a - b; // 7
let multiplication = a * b; // 30
let division = a / b; // 3.333...
let modulus = a % b; // 1 (余数)
let exponentiation = a ** b; // 1000 (ES6 新增)
赋值运算符
let x = 5;
let y = 10;
x = y; // x 现在是 10
x += y; // 等同于 x = x + y
x -= y; // 等同于 x = x - y
x *= y; // 等同于 x = x * y
x /= y; // 等同于 x = x / y
x %= y; // 等同于 x = x % y
比较运算符
let a = 5;
let b = '5';
console.log(a == b); // true (只比较值,不比较类型)
console.log(a === b); // false (比较值和类型)
console.log(a != b); // false
console.log(a !== b); // true
console.log(a > b); // false
console.log(a < b); // false
console.log(a >= b); // true
console.log(a <= b); // true
逻辑运算符
let a = true;
let b = false;
console.log(a && b); // false (逻辑与)
console.log(a || b); // true (逻辑或)
console.log(!a); // false (逻辑非)
// 短路求值
let x = 0 || 10; // 10 (如果第一个操作数为假,则返回第二个操作数)
let y = 5 && 10; // 10 (如果第一个操作数为真,则返回第二个操作数)
递增和递减运算符
let x = 5;
console.log(x++); // 5 (先返回 x 的值,然后增加)
console.log(++x); // 7 (先增加 x,然后返回新值)
console.log(x--); // 7 (先返回 x 的值,然后减少)
console.log(--x); // 5 (先减少 x,然后返回新值)
控制流语句用于控制代码的执行顺序,包括条件语句和循环语句。
条件语句
if 语句
let age = 18;
if (age >= 18) {
console.log('成年人');
} else if (age >= 13) {
console.log('青少年');
} else {
console.log('儿童');
}
switch 语句
let day = 3;
let dayName;
switch (day) {
case 1:
dayName = '星期一';
break;
case 2:
dayName = '星期二';
break;
case 3:
dayName = '星期三';
break;
case 4:
dayName = '星期四';
break;
case 5:
dayName = '星期五';
break;
case 6:
dayName = '星期六';
break;
case 7:
dayName = '星期日';
break;
default:
dayName = '无效的星期';
}
循环语句
for 循环
// 基本 for 循环
for (let i = 0; i < 5; i++) {
console.log(i); // 输出 0, 1, 2, 3, 4
}
// 遍历数组
let fruits = ['apple', 'banana', 'orange'];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
// for...of 循环 (ES6 新增)
for (let fruit of fruits) {
console.log(fruit);
}
// for...in 循环 (用于对象)
let person = {name: '张三', age: 30, job: '工程师'};
for (let key in person) {
console.log(key + ': ' + person[key]);
}
while 循环
let i = 0;
while (i < 5) {
console.log(i);
i++;
}
do...while 循环
let i = 0;
do {
console.log(i);
i++;
} while (i < 5);
跳转语句
// break: 退出循环
for (let i = 0; i < 10; i++) {
if (i === 5) {
break;
}
console.log(i); // 输出 0 到 4
}
// continue: 跳过当前迭代,继续下一次迭代
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) {
continue;
}
console.log(i); // 输出 1, 3, 5, 7, 9
}
函数是一组执行特定任务的代码块,可以重复使用。
函数声明
// 基本函数声明
function greet(name) {
return 'Hello, ' + name + '!';
}
// 调用函数
let message = greet('JavaScript');
console.log(message); // Hello, JavaScript!
函数表达式
// 函数表达式
let greet = function(name) {
return 'Hello, ' + name + '!';
};
// 箭头函数 (ES6 新增)
let greet = (name) => {
return 'Hello, ' + name + '!';
};
// 简化的箭头函数 (单行返回)
let greet = name => 'Hello, ' + name + '!';
let add = (a, b) => a + b;
参数和返回值
// 默认参数值 (ES6 新增)
function greet(name = 'World') {
return 'Hello, ' + name + '!';
}
console.log(greet()); // Hello, World!
console.log(greet('JavaScript')); // Hello, JavaScript!
// 剩余参数 (ES6 新增)
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// 返回多个值 (通过对象或数组)
function getUserInfo() {
return {
name: '张三',
age: 30,
job: '工程师'
};
}
let user = getUserInfo();
console.log(user.name); // 张三
函数作用域
// 全局作用域
let globalVar = '全局变量';
function test() {
// 函数作用域
let localVar = '局部变量';
console.log(globalVar); // 可以访问全局变量
console.log(localVar); // 可以访问局部变量
}
test();
console.log(globalVar); // 可以访问全局变量
// console.log(localVar); // 错误:无法访问局部变量
闭包
// 闭包示例
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
let counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
数组是一种特殊的对象,用于存储多个值。数组中的每个值都有一个索引,从 0 开始计数。
创建数组
// 使用数组字面量
let fruits = ['apple', 'banana', 'orange'];
// 使用 Array 构造函数
let numbers = new Array(1, 2, 3, 4, 5);
let emptyArray = new Array(3); // 创建包含 3 个空位的数组
// 使用 Array.of() (ES6 新增)
let colors = Array.of('red', 'green', 'blue');
访问和修改数组元素
let fruits = ['apple', 'banana', 'orange'];
// 访问元素
console.log(fruits[0]); // 'apple'
console.log(fruits[1]); // 'banana'
// 修改元素
fruits[1] = 'grape';
console.log(fruits); // ['apple', 'grape', 'orange']
// 获取数组长度
console.log(fruits.length); // 3
// 修改数组长度
fruits.length = 2;
console.log(fruits); // ['apple', 'grape']
数组方法
let fruits = ['apple', 'banana', 'orange'];
// 添加元素
fruits.push('grape'); // 添加到末尾,返回新长度
fruits.unshift('mango'); // 添加到开头,返回新长度
// 删除元素
fruits.pop(); // 删除末尾元素,返回被删除的元素
fruits.shift(); // 删除开头元素,返回被删除的元素
// 数组操作
let sliced = fruits.slice(1, 3); // 浅拷贝数组的一部分
fruits.splice(1, 1, 'pear'); // 在指定位置删除/添加元素
// 连接数组
let moreFruits = ['peach', 'cherry'];
let allFruits = fruits.concat(moreFruits);
// 查找元素
let index = fruits.indexOf('banana'); // 返回元素索引,未找到返回 -1
let includes = fruits.includes('banana'); // 检查数组是否包含指定元素
// 遍历数组
fruits.forEach(function(fruit, index) {
console.log(index + ': ' + fruit);
});
// 映射数组
let upperFruits = fruits.map(function(fruit) {
return fruit.toUpperCase();
});
// 过滤数组
let longFruits = fruits.filter(function(fruit) {
return fruit.length > 5;
});
// 归约数组
let sum = [1, 2, 3, 4, 5].reduce(function(total, num) {
return total + num;
}, 0);
数组方法示例
// map 示例:将所有数字平方
let numbers = [1, 2, 3, 4, 5];
let squared = numbers.map(num => num * num);
console.log(squared); // [1, 4, 9, 16, 25]
// filter 示例:筛选偶数
let evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
// reduce 示例:计算平均值
let average = numbers.reduce((total, num, index, array) => {
total += num;
return index === array.length - 1 ? total / array.length : total;
}, 0);
console.log(average); // 3
对象是 JavaScript 中的一种数据结构,用于存储键值对。对象可以包含属性和方法。
创建对象
// 使用对象字面量
let person = {
firstName: '张三',
lastName: '李四',
age: 30,
job: '工程师',
fullName: function() {
return this.firstName + ' ' + this.lastName;
}
};
// 使用构造函数
function Person(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.fullName = function() {
return this.firstName + ' ' + this.lastName;
};
}
let john = new Person('John', 'Doe', 35);
// 使用 Object.create()
let car = Object.create(Object.prototype, {
brand: { value: 'Toyota', writable: true },
model: { value: 'Camry' }
});
// ES6 类 (语法糖,底层仍然是原型继承)
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
makeSound() {
return this.name + ' says ' + this.sound;
}
}
访问和修改对象属性
let person = {
firstName: '张三',
lastName: '李四',
age: 30
};
// 点表示法
console.log(person.firstName); // '张三'
person.age = 31;
// 方括号表示法 (可以使用变量作为属性名)
let prop = 'firstName';
console.log(person[prop]); // '张三'
person['lastName'] = '王五';
// 添加新属性
person.job = '工程师';
// 删除属性
delete person.age;
// 检查属性是否存在
console.log('firstName' in person); // true
console.log(person.hasOwnProperty('firstName')); // true
对象遍历
let person = {
firstName: '张三',
lastName: '李四',
age: 30,
job: '工程师'
};
// for...in 循环
for (let key in person) {
if (person.hasOwnProperty(key)) {
console.log(key + ': ' + person[key]);
}
}
// 获取所有键
let keys = Object.keys(person);
console.log(keys); // ['firstName', 'lastName', 'age', 'job']
// 获取所有值
let values = Object.values(person);
console.log(values); // ['张三', '李四', 30, '工程师']
// 获取所有键值对
let entries = Object.entries(person);
entries.forEach(([key, value]) => {
console.log(key + ': ' + value);
});
原型继承
// 构造函数
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// 在原型上添加方法
Person.prototype.fullName = function() {
return this.firstName + ' ' + this.lastName;
};
// 创建实例
let john = new Person('John', 'Doe');
console.log(john.fullName()); // 'John Doe'
// ES6 类和继承
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
fullName() {
return this.firstName + ' ' + this.lastName;
}
}
class Employee extends Person {
constructor(firstName, lastName, jobTitle) {
super(firstName, lastName); // 调用父类构造函数
this.jobTitle = jobTitle;
}
getJobInfo() {
return this.fullName() + ' is a ' + this.jobTitle;
}
}
let employee = new Employee('Jane', 'Smith', 'Developer');
console.log(employee.getJobInfo()); // 'Jane Smith is a Developer'
DOM (Document Object Model) 是 HTML 和 XML 文档的编程接口。JavaScript 可以通过 DOM 来访问和操作网页元素。
查找元素
// 通过 ID 查找元素
let element = document.getElementById('myElement');
// 通过类名查找元素(返回元素集合)
let elements = document.getElementsByClassName('myClass');
// 通过标签名查找元素(返回元素集合)
let paragraphs = document.getElementsByTagName('p');
// 通过 CSS 选择器查找元素
let firstElement = document.querySelector('.myClass'); // 返回第一个匹配的元素
let allElements = document.querySelectorAll('p.myClass'); // 返回所有匹配的元素
修改元素
// 修改内容
let element = document.getElementById('myElement');
element.textContent = '新的文本内容'; // 修改文本内容
element.innerHTML = '新的 HTML 内容'; // 修改 HTML 内容
// 修改属性
let img = document.querySelector('img');
img.src = 'newImage.jpg';
img.alt = '新的图片描述';
// 获取属性值
let href = document.querySelector('a').getAttribute('href');
// 修改样式
element.style.color = 'red';
element.style.fontSize = '16px';
element.style.backgroundColor = '#f0f0f0';
// 添加和移除类
let element = document.getElementById('myElement');
element.classList.add('newClass');
element.classList.remove('oldClass');
element.classList.toggle('active'); // 如果存在则移除,不存在则添加
element.classList.contains('active'); // 检查是否包含指定类
创建和添加元素
// 创建新元素
let newDiv = document.createElement('div');
newDiv.textContent = '这是一个新的 div 元素';
newDiv.className = 'new-class';
newDiv.id = 'new-div';
// 添加到 DOM
let container = document.getElementById('container');
container.appendChild(newDiv); // 添加到容器末尾
// 在指定位置插入元素
let referenceElement = document.getElementById('reference');
container.insertBefore(newDiv, referenceElement); // 在参考元素之前插入
// 克隆元素
let clonedElement = newDiv.cloneNode(true); // true 表示深拷贝,包括所有子节点
container.appendChild(clonedElement);
// 删除元素
container.removeChild(newDiv);
DOM 遍历
let parent = document.getElementById('parent');
// 子节点
let children = parent.children; // 返回元素节点集合
let firstChild = parent.firstElementChild;
let lastChild = parent.lastElementChild;
// 兄弟节点
let nextSibling = firstChild.nextElementSibling;
let previousSibling = lastChild.previousElementSibling;
// 父节点
let parentNode = firstChild.parentElement;
JavaScript 事件是用户与网页交互时触发的动作,如点击、鼠标移动、键盘输入等。通过事件处理,我们可以对这些交互做出响应。
事件类型
- 鼠标事件:click, dblclick, mouseover, mouseout, mousedown, mouseup, mousemove
- 键盘事件:keydown, keyup, keypress
- 表单事件:submit, change, input, focus, blur
- 窗口事件:load, resize, scroll, unload
- 触摸事件:touchstart, touchend, touchmove
- 拖拽事件:dragstart, drag, dragend, dragover, drop
添加事件监听器
// 方法一:使用 addEventListener(推荐)
let button = document.getElementById('myButton');
button.addEventListener('click', function(event) {
console.log('按钮被点击了!');
console.log('事件对象:', event);
console.log('目标元素:', event.target);
});
// 使用命名函数
function handleClick(event) {
console.log('处理点击事件');
}
button.addEventListener('click', handleClick);
// 移除事件监听器
button.removeEventListener('click', handleClick);
// 方法二:使用 HTML 属性(不推荐)
// <button onclick="handleClick()">点击我</button>
// 方法三:使用 DOM 属性
button.onclick = function() {
console.log('按钮被点击了!');
};
// 移除事件处理
button.onclick = null;
事件对象
document.getElementById('myButton').addEventListener('click', function(event) {
// 阻止默认行为
event.preventDefault();
// 阻止事件冒泡
event.stopPropagation();
// 获取事件类型
console.log('事件类型:', event.type);
// 获取目标元素
console.log('目标元素:', event.target);
// 获取当前元素(绑定事件的元素)
console.log('当前元素:', event.currentTarget);
// 获取鼠标位置
console.log('鼠标 X 坐标:', event.clientX);
console.log('鼠标 Y 坐标:', event.clientY);
// 获取键盘按键
// 对于键盘事件
// console.log('按键代码:', event.keyCode);
// console.log('按键:', event.key);
});
事件冒泡和捕获
// HTML 结构
/*
点击我
*/
// 事件冒泡(默认)
document.getElementById('outer').addEventListener('click', function() {
console.log('外层 div 被点击了');
});
document.getElementById('middle').addEventListener('click', function() {
console.log('中层 div 被点击了');
});
document.getElementById('inner').addEventListener('click', function(event) {
console.log('内层 div 被点击了');
// event.stopPropagation(); // 阻止冒泡
});
// 事件捕获
// 使用 addEventListener 的第三个参数设置为 true
document.getElementById('outer').addEventListener('click', function() {
console.log('外层 div 捕获阶段');
}, true);
document.getElementById('middle').addEventListener('click', function() {
console.log('中层 div 捕获阶段');
}, true);
document.getElementById('inner').addEventListener('click', function() {
console.log('内层 div 捕获阶段');
}, true);
JavaScript 是单线程的,但它支持异步编程,允许在不阻塞主线程的情况下执行操作,如网络请求、文件操作等。
回调函数
回调函数是异步编程的基础,是指在异步操作完成后执行的函数。
// 回调函数示例
function fetchData(callback) {
// 模拟异步操作
setTimeout(function() {
const data = { name: 'JavaScript', version: 'ES6' };
callback(null, data); // 第一个参数是错误,第二个参数是数据
}, 1000);
}
// 使用回调函数
fetchData(function(error, data) {
if (error) {
console.error('发生错误:', error);
return;
}
console.log('获取的数据:', data);
});
Promise
Promise 是 ES6 引入的用于处理异步操作的对象,它代表一个异步操作的最终完成(或失败)及其结果值。
// 创建 Promise
function fetchData() {
return new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const success = true;
if (success) {
const data = { name: 'JavaScript', version: 'ES6' };
resolve(data); // 成功时调用
} else {
reject(new Error('获取数据失败')); // 失败时调用
}
}, 1000);
});
}
// 使用 Promise
fetchData()
.then(data => {
console.log('获取的数据:', data);
return data.name.toUpperCase();
})
.then(uppercaseName => {
console.log('转换后的名称:', uppercaseName);
})
.catch(error => {
console.error('发生错误:', error);
})
.finally(() => {
console.log('操作完成');
});
async/await
async/await 是 ES2017 引入的语法糖,基于 Promise,使异步代码更像同步代码。
// async 函数
async function processData() {
try {
const data = await fetchData(); // 等待 Promise 解决
console.log('获取的数据:', data);
const uppercaseName = data.name.toUpperCase();
console.log('转换后的名称:', uppercaseName);
return uppercaseName;
} catch (error) {
console.error('发生错误:', error);
} finally {
console.log('操作完成');
}
}
// 调用 async 函数
processData()
.then(result => {
console.log('最终结果:', result);
})
.catch(error => {
console.error('外部错误:', error);
});
AJAX 和 Fetch API
// 使用 XMLHttpRequest (传统方法)
function fetchWithXHR(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
callback(null, JSON.parse(xhr.responseText));
} else {
callback(new Error('请求失败'));
}
};
xhr.onerror = function() {
callback(new Error('网络错误'));
};
xhr.send();
}
// 使用 Fetch API (现代方法)
async function fetchWithFetchAPI(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP 错误!状态: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('获取数据时出错:', error);
throw error;
}
}
编写高质量 JavaScript 的建议
- 使用严格模式:在脚本开头添加 "use strict" 来启用严格模式
- 使用 let 和 const,避免使用 var:提供更好的作用域控制
- 遵循命名约定:变量和函数使用驼峰命名法,构造函数使用帕斯卡命名法
- 避免全局变量:使用模块或闭包来封装代码
- 处理错误:使用 try...catch 语句来处理可能的错误
- 使用 === 而不是 ==:避免类型转换导致的意外行为
- 注释代码:添加清晰的注释,解释复杂的逻辑
- 优化性能:避免不必要的计算,使用合适的数据结构
- 模块化代码:将代码分割成可管理的模块
- 测试代码:使用测试框架来确保代码的质量
常见错误和解决方案
- 忘记声明变量:始终使用 let 或 const 声明变量
- 混淆 == 和 ===:优先使用 === 来比较值和类型
- 忽略异步操作:正确处理 Promise 和异步函数
- this 绑定问题:了解 this 的指向,必要时使用箭头函数或 bind
- 内存泄漏:避免不必要的事件监听器和引用
- 不处理错误:始终捕获和处理可能的错误
调试技巧
// 使用 console.log() 调试
console.log('变量值:', variable);
// 使用 console.table() 以表格形式显示数据
console.table(arrayOfObjects);
// 使用 console.error() 和 console.warn()
console.error('错误信息');
console.warn('警告信息');
// 使用断点:在浏览器开发者工具中设置断点
// 使用 debugger 语句
function problematicFunction() {
// ... 代码 ...
debugger; // 执行到这里会暂停
// ... 代码 ...
}
JavaScript 框架和库
除了原生 JavaScript,还有许多流行的框架和库可以帮助你更高效地开发 Web 应用:
- React:由 Facebook 开发的用于构建用户界面的库
- Vue.js:渐进式 JavaScript 框架
- Angular:由 Google 维护的完整框架
- jQuery:简化 HTML 文档遍历和事件处理的库
- Node.js:允许在服务器端运行 JavaScript
- Express:基于 Node.js 的 Web 应用框架