Js学习记录
JS
在开始这部分模块之前,你应该熟悉基本的 HTML 和 CSS。
JavaScript 是脚本编程语言,符合ECMA 标准规范(即 ECMA-262 标准)。我们所熟知的 Node.js、Web JS、Vue.js 等都是在标准 JS 基础上,基于 ECMA 规范扩展而来的。JS 可以用来创建动态更新的内容、控制多媒体、制作图像动画等,能实现多种复杂功能。
概述
作为程序员,你或许听说过这两个术语:解释(interpret)和编译(compile)。
- 在解释型语言中,代码自上而下运行,且实时返回运行结果。代码在由浏览器执行前,不需要转化为其他形式,直接以文本格式被接收和处理。
- 相对的,编译型语言需要先将代码转化(编译)成另一种形式才能运行。比如 C/C++ 先被编译成机器码,然后才能由计算机运行,程序将以二进制格式运行,这些二进制内容由程序源代码生成。
JavaScript 是轻量级解释型语言。浏览器接收到 JavaScript 代码后,以代码自身的文本格式运行它。技术上,几乎所有 JavaScript 引擎都运用了即时编译(just-in-time compiling)技术:当 JavaScript 源代码被执行时,会被编译成二进制(如 wasm 格式,感兴趣的同学可以深入了解),使代码运行速度更快。尽管如此,JavaScript 仍然是解释型语言,因为编译过程发生在代码运行中,而非运行前。
JS 常用作客户端代码(即前端代码),在 Node.js 环境中也可作为后端代码。
JavaScript 是一门形式自由的语言:不必声明所有变量、类和方法;不必关心方法的访问权限(公有、私有、受保护);不需要显式实现接口;无需显式指定变量类型(动态类型语言)。
请注意,Web 文档中,代码通常按其在页面上出现的顺序加载和执行。为确保所有 HTML 元素完成加载后再执行 JS,建议将 JavaScript 放在 <body> 底部。
1 | <!-- 我们通常在script标签中写js代码 --> |
js 基础语法
1. 变量
声明变量:通过
let(块级作用域)或var(函数作用域)声明变量。
例:let myName;初始化:通过
=赋值,可在声明时直接赋值(例:let age = 18;)。var和let的区别:let:声明块级作用域局部变量(作用域被{}限制,如if/for代码块),可重新赋值。var:声明函数作用域变量(作用域为整个函数,若无函数则为全局),存在“变量提升”特性(声明被提升到作用域顶部,赋值不提升)。- 推荐:若变量无需重新赋值,优先使用
const(声明常量);需重新赋值时用let;尽量避免使用var。
变量提升与暂时性死区:
var声明的变量会“提升”:在声明前访问不会报错,值为undefined(例:console.log(a); var a = 1;输出undefined)。let/const声明的变量存在“暂时性死区”:在声明前访问会直接抛出ReferenceError(例:console.log(b); let b = 2;报错)。
变量作用域:
- 全局作用域:不在任何函数/块内的变量,可被所有代码访问(浏览器中全局变量挂载在
window对象上)。 - 函数作用域:函数内声明的变量(
var声明),仅在函数内可访问。 - 块级作用域:
{}内用let/const声明的变量,仅在块内可访问(如if (true) { let c = 3; } console.log(c);报错)。
- 全局作用域:不在任何函数/块内的变量,可被所有代码访问(浏览器中全局变量挂载在
2. 全局变量
在网页中,全局变量默认挂载在window对象上;在所有环境中,globalThis变量(自身也是全局变量)可用于读取和设置全局变量(例:globalThis.username = "Alice";)。
3. 常量(const)
const用于声明不可重新赋值的变量,必须在声明时初始化(例:const PI = 3.14;)。
- 对于基本类型(数字、字符串、布尔等):值不可修改(例:
PI = 3;报错)。 - 对于引用类型(对象、数组等):变量存储的“引用地址”不可修改,但内部属性/元素可修改(例:
const arr = [1]; arr.push(2);合法,数组变为[1,2])。
4. 数据结构和类型
ECMA 标准的 8 种类型:
- 7 种基本类型:
boolean(true/false)、null(空值)、undefined(未定义)、Number(整数/浮点数)、BigInt(大整数,例:123n)、String(字符串)、Symbol(实例唯一且不可变,例:Symbol("id"))。 - 1 种引用类型:
Object(对象,包括数组、函数、日期等)。
- 7 种基本类型:
数据类型转换:
JavaScript 是动态类型语言:声明变量时不必指定类型,运行时会自动转换。1
2
3
4
5
6
7
8
9
10
11let answer = 42;
answer = "嘿嘿,气不气"; // 合法,类型从Number转为String
x = "答案是 " + 42; // "答案是 42"(数字转字符串)
y = 42 + " 是答案"; // "42 是答案"
z = "37" + 7; // "377"(数字转字符串)
// 显式转换
parseInt("101", 2); // 5(二进制字符串转十进制数字)
Number("123"); // 123(字符串转数字)
String(123); // "123"(数字转字符串)类型判断方法:
typeof:判断基本类型(注意:typeof null返回"object",typeof []返回"object")。instanceof:判断对象类型(基于原型链,例:[] instanceof Array为true)。Object.prototype.toString.call():最准确的类型判断(例:Object.prototype.toString.call(null)返回"[object Null]")。
严格相等与非严格相等:
==:非严格相等,会先进行隐式类型转换再比较(例:0 == false为true)。===:严格相等,不转换类型,直接比较值和类型(例:0 === false为false,因类型不同)。推荐优先使用===。
字面量:
- 数组字面量:由
[]包裹的元素列表,用逗号分隔(例:const coffees = ["French Roast", "Colombian"];)。 - 布尔字面量:
true、false。 - 对象字面量:由
{}包裹的键值对列表(例:const person = { name: "Bob", age: 20 };)。 - 模板字面量:由反引号
`包裹,支持多行字符串和插值(${变量}):1
2
3const name = "Lev",
time = "today";
`你好 ${name},${time} 过得怎么样?`; // 输出"你好 Lev,today 过得怎么样?" - 数字字面量:整数(
123)、浮点数(123.45)、二进制(0b101)、十六进制(0x1f)等。
- 数组字面量:由
5. 条件语句
if-else 语句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19if (condition) {
statement1; // 条件为true时执行
} else {
statement2; // 条件为false时执行
}
// if可单独使用
if (condition) {
// 条件为true时执行
}
// if-else if-else
if (condition1) {
statement1;
} else if (condition2) {
statement2;
} else {
statementLast; // 所有条件都为false时执行
}switch 语句(适用于多值判断):
1
2
3
4
5
6
7
8
9
10switch (expression) {
case value1:
statement1;
break; // 跳出switch
case value2:
statement2;
break;
default: // 所有case不匹配时执行
statementDefault;
}try-catch 语句(处理异常):
用throw抛出异常,用try...catch捕获,finally无论是否异常都会执行。1
2
3
4
5
6
7try {
throw "我的异常"; // 主动抛出异常
} catch (err) {
console.log("捕获到异常:", err); // 处理异常
} finally {
console.log("无论是否异常,都会执行");
}
6. 循环语句
for 循环:
1
2
3for (let step = 0; step < 5; step++) {
console.log("第" + step + "步"); // 执行5次(step从0到4)
}while/do-while 循环:
1
2
3
4
5
6
7
8
9
10
11
12
13// while:先判断条件,再执行
let i = 0;
while (i < 3) {
console.log(i);
i++;
}
// do-while:先执行一次,再判断条件
let j = 0;
do {
console.log(j);
j++;
} while (j < 3);循环控制:
break:跳出当前循环(可配合标签跳出多层循环)。continue:跳过本次循环,直接进入下一次。
1
2
3
4
5
6
7
8
9// 带标签的循环(跳出多层循环)
outLoop: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
break outLoop; // 直接跳出outLoop标签对应的外层循环
}
console.log(i, j);
}
}for…in(遍历对象属性/数组下标,不推荐遍历数组):
1
2
3
4const obj = { name: "Alice", age: 18 };
for (let key in obj) {
console.log(key + ": " + obj[key]); // 输出"name: Alice"、"age: 18"
}for…of(遍历可迭代对象的元素,如数组、字符串):
1
2
3
4
5const arr = [3, 5, 7];
arr.foo = "hello"; // 给数组添加额外属性
for (let value of arr) {
console.log(value); // 输出3、5、7(仅遍历数组元素,忽略额外属性)
}
7. 函数
函数定义:
- 函数声明:
1
2
3function square(number) {
return number * number; // 返回计算结果
} - 函数表达式(匿名函数赋值给变量):
1
2
3const square = function (number) {
return number * number;
};
- 函数声明:
嵌套函数与闭包:
内部函数可访问外部函数的变量,形成闭包(外部函数执行后,内部函数仍能访问其变量)。1
2
3
4
5
6
7
8
9
10
11
12
13function outer() {
let count = 0;
function inner() {
// 嵌套函数(闭包)
count++;
return count;
}
return inner; // 返回内部函数
}
const func = outer();
console.log(func()); // 1
console.log(func()); // 2(count被闭包保存,未被销毁)箭头函数(ES6+,语法更简洁,无自己的
this和arguments):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 基本语法:(参数) => 返回值
const sum = (a, b) => a + b;
// 多行代码需用{}包裹
const square = (x) => {
return x * x;
};
// 无自己的this(继承外部作用域的this)
const person = {
age: 18,
grow: function () {
setInterval(() => {
this.age++; // this指向person对象(继承grow函数的this)
}, 1000);
},
};arguments对象:
函数内部的类数组对象,包含传入的所有实参(箭头函数无此对象,需用剩余参数...args)。1
2
3
4
5
6
7
8function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
sum(1, 2, 3); // 6剩余参数(
...args,ES6+,将剩余参数转为数组):1
2
3
4
5function sum(...args) {
// args是数组,可使用数组方法
return args.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
8. 表达式和运算符
解构赋值(快速提取数组/对象中的值,ES6+):
1
2
3
4
5
6// 数组解构
const [a, b] = [1, 2]; // a=1,b=2
const [c, ...rest] = [3, 4, 5]; // c=3,rest=[4,5]
// 对象解构
const { name, age } = { name: "Bob", age: 20 }; // name="Bob",age=20this关键字:
指向函数执行时的上下文对象(取决于调用方式):- 全局函数中,
this指向window(浏览器)或globalThis。 - 对象方法中,
this指向该对象。 - 构造函数中(
new调用),this指向新创建的对象。
- 全局函数中,
super关键字:
在类的方法中,指向父类的原型对象,用于调用父类方法(例:super.print())。
9. 数组(Array)
数组是特殊的对象,用于存储有序集合,补充常用方法:
遍历/转换:
map((item) => { ... }):返回新数组,每个元素为原元素处理后的值(例:[1,2,3].map(x => x*2)→[2,4,6])。filter((item) => { ... }):返回新数组,包含满足条件的元素(例:[1,2,3].filter(x => x>1)→[2,3])。forEach((item) => { ... }):遍历数组,无返回值。
聚合:
reduce((total, item) => { ... }, initial):累加/合并数组(例:[1,2,3].reduce((a,b) => a+b, 0)→6)。every((item) => { ... }):所有元素满足条件则返回true(例:[2,4,6].every(x => x%2===0)→true)。some((item) => { ... }):至少一个元素满足条件则返回true。
操作:
push(item)/pop():尾部添加/删除元素。unshift(item)/shift():头部添加/删除元素。slice(start, end):截取子数组(不修改原数组)。splice(start, deleteCount, ...items):修改原数组(删除/添加元素)。
10. 数字和字符串
数学对象
Math:Math.PI:圆周率(≈3.14159)。Math.abs(x):绝对值。Math.floor(x)/Math.ceil(x):向下取整/向上取整(例:Math.floor(2.8)→2,Math.ceil(2.1)→3)。Math.min(x1, x2, ...)/Math.max(x1, x2, ...):求最小值/最大值。Math.sqrt(x):平方根。- 三角函数:
Math.sin(x)、Math.cos(x)等(参数为弧度)。
日期对象
Date:
用于处理日期和时间,常用方法:1
2
3
4
5
6
7
8
9
10const today = new Date(); // 当前时间
const birthday = new Date(2000, 0, 1); // 2000年1月1日(月份从0开始)
// 获取日期
today.getFullYear(); // 年份(4位)
today.getMonth(); // 月份(0-11)
today.getDate(); // 日(1-31)
// 设置日期
today.setFullYear(2024); // 设置年份为2024
11. 正则表达式
正则表达式用于匹配字符串中的模式,是处理文本的强大工具。
创建方式:
1
2
3
4// 方式1:字面量(适合固定模式)
const re1 = /ab+c/;
// 方式2:RegExp对象(适合动态模式)
const re2 = new RegExp("ab+c");常用元字符与量词:
元字符:
.:匹配除换行外的任意字符。^/$:匹配字符串开头/结尾(例:/^abc$/匹配完整的”abc”)。*:前一项出现 0 次或多次(例:/ab*c/匹配”ac”、”abc”、”abbc”等)。+:前一项出现 1 次或多次(例:/ab+c/匹配”abc”、”abbc”等,不匹配”ac”)。?:前一项出现 0 次或 1 次;若紧跟量词后,变为非贪婪匹配(例:/a.*?b/匹配”a”到第一个”b”)。(x):捕获分组(记住匹配的内容,可通过\1引用)。(?:x):非捕获分组(仅分组,不记住内容)。x(?=y):先行断言(匹配 x,且 x 后紧跟 y,例:/a(?=b)/匹配”ab”中的”a”)。
字符类:
\d:匹配数字(等价于[0-9]),\D:匹配非数字。\w:匹配字母/数字/下划线(等价于[A-Za-z0-9_]),\W:匹配非单词字符。\s:匹配空白符(空格、制表符等),\S:匹配非空白符。
量词:
{n}:恰好 n 次(例:/a{2}/匹配”aa”)。{n,}:至少 n 次(例:/a{2,}/匹配”aa”、”aaa”等)。{n,m}:n 到 m 次(例:/a{1,3}/匹配”a”、”aa”、”aaa”)。
常用标志:
i:忽略大小写(例:/abc/i匹配”ABC”、”aBc”等)。g:全局匹配(找到所有匹配,而非第一个)。m:多行模式(^/$匹配每行开头/结尾)。
使用方法:
| 方法 | 说明 |
|——————————-|———————————————————-|
|re.test(str)| 测试 str 是否匹配 re,返回 true/false |
|re.exec(str)| 查找匹配,返回数组(含分组),无匹配则返回 null |
|str.match(re)| 同 exec,全局匹配时返回所有匹配的数组 |
|str.replace(re, newStr)| 替换匹配的子串,返回新字符串 |示例:
1
2
3
4
5const str = "Hello 2024, Hello 2025";
const re = /Hello (\d+)/g; // 全局匹配"Hello 数字",捕获数字
str.match(re); // ["Hello 2024", "Hello 2025"]
str.replace(re, "Hi $1"); // "Hi 2024, Hi 2025"($1引用捕获的数字)
12. 对象与原型
JavaScript 基于原型实现继承,对象是核心数据结构。
对象创建方式:
- 字面量:
const obj = { name: "Tom", age: 18 }; - 构造函数:
function Person(name) { this.name = name; } const p = new Person("Tom"); - 类(ES6+):
class Person { constructor(name) { this.name = name; } }
- 字面量:
原型与原型链:
- 每个对象有
__proto__属性(指向其原型对象),原型对象也是对象,形成“原型链”。 - 访问对象属性时,若自身没有,会沿原型链向上查找,直到
null。 - 例:
const arr = []; arr.__proto__ === Array.prototype; // true(数组的原型是 Array.prototype)。
- 每个对象有
常用对象方法:
Object.keys(obj):返回对象自有属性的键名数组。Object.values(obj):返回对象自有属性的键值数组。Object.assign(target, ...sources):合并对象(复制属性到 target)。
13. 异步编程
JavaScript 是单线程语言,异步编程用于处理耗时操作(如网络请求、定时器)。
回调函数:
异步操作完成后执行的函数(易出现“回调地狱”):1
2
3setTimeout(() => {
console.log("1秒后执行"); // 1秒后回调
}, 1000);Promise(ES6+,解决回调地狱):
表示异步操作的最终完成(或失败),有三种状态:pending(进行中)、fulfilled(成功)、rejected(失败)。1
2
3
4
5
6
7
8
9
10
11
12
13
14const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve("成功"); // 成功时调用
} else {
reject("失败"); // 失败时调用
}
}, 1000);
});
promise
.then((result) => console.log(result)) // 成功回调
.catch((error) => console.log(error)) // 失败回调
.finally(() => console.log("无论成功失败都执行"));async/await(ES7+,Promise 的语法糖):
让异步代码像同步代码一样直观:1
2
3
4
5
6
7
8
9async function fetchData() {
try {
const result = await promise; // 等待Promise完成
console.log(result);
} catch (error) {
console.log(error);
}
}
fetchData();
14. 模块化(ES6 Module)
通过export和import实现代码拆分与复用,需在<script type="module">中使用。
导出(
export):1
2
3
4
5
6
7
8
9// module.js
export const name = "JS"; // 导出变量
export function hello() {
console.log("Hello");
} // 导出函数
// 批量导出
const version = "ES6";
export { version };导入(
import):1
2
3
4
5
6
7
8// main.js
import { name, hello } from "./module.js";
console.log(name); // "JS"
hello(); // 输出"Hello"
// 导入所有(别名)
import * as module from "./module.js";
console.log(module.version); // "ES6"