1.JavaScript 基础语法

目录
[1.JavaScript 基础语法](#1.JavaScript 基础语法)
[1. 基本类型(值类型):存储的是实际的值](#1. 基本类型(值类型):存储的是实际的值)
[2. 引用类型:存储的是对内存中对象的引用(地址)](#2. 引用类型:存储的是对内存中对象的引用(地址))
[3. 基本类型包装对象](#3. 基本类型包装对象)
[4. 类型检测:如何知道一个值是什么类型?](#4. 类型检测:如何知道一个值是什么类型?)
[5. 类型转换:数据之间的"变身"术](#5. 类型转换:数据之间的“变身”术)
[6. ToPrimitive 抽象操作(对象转原始类型)](#6. ToPrimitive 抽象操作(对象转原始类型))
[1. 算术运算符](#1. 算术运算符)
[2. 比较运算符](#2. 比较运算符)
[3. 逻辑运算符](#3. 逻辑运算符)
[4. 位运算符(直接操作整数在内存中的二进制位)](#4. 位运算符(直接操作整数在内存中的二进制位))
[5. 赋值运算符与复合赋值](#5. 赋值运算符与复合赋值)
[1. 条件语句:根据条件选择执行不同的代码](#1. 条件语句:根据条件选择执行不同的代码)
[iif...else if...else 语句](#iif...else if...else 语句)
[2. 循环与迭代:重复执行某段代码](#2. 循环与迭代:重复执行某段代码)
[循环控制:break 和 continue](#循环控制:break 和 continue)
[break、continue 和 return 的区别](#break、continue 和 return 的区别)
for...in循环:遍历对象的可枚举属性(包括原型链上的)
for...of循环:遍历可迭代对象(数组、字符串、Map、Set等)的值
[for...in vs for...of](#for...in vs for...of)
[1. 函数声明与函数表达式](#1. 函数声明与函数表达式)
[函数声明提升 vs 函数表达式不提升](#函数声明提升 vs 函数表达式不提升)
[2. 箭头函数](#2. 箭头函数)
[3. 参数:传递给函数的输入](#3. 参数:传递给函数的输入)
[4. 返回值与递归](#4. 返回值与递归)
[基准条件(Base Case)](#基准条件(Base Case))
[递归条件(Recursive Case)](#递归条件(Recursive Case))
[1. 代码风格建议](#1. 代码风格建议)
[2. 注释风格](#2. 注释风格)
变量声明:给数据贴上"标签"
变量就像一个容器或一个标签,用来存储各种信息(数字、文字、复杂对象等),方便在程序中反复使用和修改。
声明一个变量,就像创建一个带名字的盒子,你可以在里面放东西,也可以随时更换。
三种声明方式:let、const、var
|-----------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------|------------------------------------------------|
| 声明方式 | 特点 | 一句话理解 | 适用场景 |
| let | 块级作用域 ,可重复赋值 ,不允许重复声明 ,存在暂时性死区****csdn.net | 像一次性贴纸,贴上后可以撕下来重贴,但不能在同一位置贴两次。 | 当你需要一个值会改变的变量时。 |
| const | 块级作用域 ,声明后必须立即赋值 ,不能再重新赋值 (基本类型),属性可以修改 (引用类型),不允许重复声明 ,存在暂时性死区****csdn.net | 像带锁的保险箱,一旦锁上(声明并赋值),就不能再换别的东西,但箱子里的文件(属性)还是可以改的。 | 当你需要一个常量 ,或者一个不会改变引用但内容可变的变量(如对象、数组)时。 |
| var | 函数作用域 ,可重复声明 ,变量提升(在声明前可用,值为undefined),没有块级作用域 | 像老式黑板,可以随意擦写,但在任何地方都能看到它(作用域),且可能会出现"写之前先擦"的情况(变量提升)。 | 现代开发中尽量避免使用 ,主要存在于老旧代码中。 |
默认使用 const,当需要重新赋值时再使用 let,尽量避免使用 var。
这样能避免许多潜在的作用域问题,让代码更安全、更易读。
数据类型:容器里能装什么?
JavaScript 的数据类型分为两大类:基本类型和引用类型。
这决定了数据在内存中如何存储和操作。
1. 基本类型(值类型):存储的是实际的值
|---------------|----------------------------|--------------------------------------------|----------------------------------------------------|
| 类型 | 描述 | 示例 | 注意 |
| Undefined | 变量已声明但未赋值 | let a; console.log(a); // undefined | 表示"缺少值 " |
| Null | 表示一个空的、不存在的对象引用 | let b = null; console.log(b); // null | 表示"有意为之的空值 " |
| Boolean | 布尔值,表示真或假 | let isTrue = true; let isFalse = false; | 主要用于条件判断 |
| Number | 数字(整数或浮点数) | let age = 25; let price = 3.14; | Infinity(无穷大)、-Infinity(无穷小)、NaN(Not a Number,非数值) |
| BigInt | ES2020新增,用于表示任意大的整数 | let bigNum = 9007199254740991n; | 数字末尾加 n,或使用 BigInt() 构造函数 |
| String | 字符串(文本),用单引号'、双引号"或反引号`包裹 | let name = 'Alice'; let message = "Hello"; | 转义字符:\n换行、\t制表符、\\反斜杠本身、\'单引号等 |
| Symbol | ES6新增,表示独一无二的值 | let sym = Symbol('description'); | 常用于对象属性名,防止冲突 |
2. 引用类型:存储的是对内存中对象的引用(地址)
|--------------|------------------------|------------------------------------------|
| 类型 | 描述 | 示例 |
| Object | 对象,包含属性 和方法的集合 | let person = { name: 'Bob', age: 30 }; |
| Array | 数组,有序的列表,可存储任意类型数据 | let colors = ['red', 'green', 'blue']; |
| Function | 函数,一段可重复执行的代码块 | function greet() { console.log('Hi!'); } |
3. 基本类型包装对象
当你尝试对基本类型值(如字符串、数字、布尔值)调用方法或访问属性时,JavaScript 会临时创建一个包装对象,操作完成后立即销毁。
这是为了方便地操作基本类型。
javascript
let str = "hello";
console.log(str.length); // 5
// 临时创建了 String 包装对象,访问了 length 属性,然后销毁
let num = 123.456;
console.log(num.toFixed(2)); // "123.46"
// 临时创建了 Number 包装对象,调用了 toFixed() 方法,然后销毁
let bool = true;
console.log(bool.toString()); // "true"
// 临时创建了 Boolean 包装对象,调用了 toString() 方法,然后销毁
⚠️ 注意:不要手动创建包装对象(如 new String("text")),这会导致类型判断混乱(typeof new String("text") 返回 "object",而 typeof "text" 返回 "string")。直接使用基本类型值即可,JavaScript 会自动处理包装。
4. 类型检测:如何知道一个值是什么类型?
|--------------------------------------|-----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|
| 方法 | 描述 | 返回值示例 | 适用场景 |
| typeof | 返回一个字符串,表示操作数的数据类型csdn.net | typeof 42 → "number" typeof "Hello" → "string" typeof true → "boolean" typeof undefined → "undefined" typeof null → "object" (历史遗留问题) typeof Symbol('sym') → "symbol" typeof 123n → "bigint" typeof {} → "object" typeof [] → "object" typeof function(){} → "function" | 快速判断基本类型 (除 null 外)和函数。 |
| instanceof | 检查构造函数的 prototype 属性是否出现在对象的原型链中csdn.net | [] instanceof Array → true {} instanceof Object → true function(){} instanceof Function → true new Date() instanceof Date → true | 判断引用类型 的具体类型(如数组、日期、自定义对象实例)。 |
| Object.prototype.toString.call() | 返回一个表示该对象的字符串,格式为 "[object Type]"segmentfault.com+2 | Object.prototype.toString.call([]) → "[object Array]" Object.prototype.toString.call({}) → "[object Object]" Object.prototype.toString.call(null) → "[object Null]" Object.prototype.toString.call(undefined) → "[object Undefined]" Object.prototype.toString.call(123) → "[object Number]" | 最准确、最可靠的类型检测方法 ,能准确区分所有内置类型和自定义类型。 |
类型检测示例
javascript
let num = 42;
let str = "Hello";
let bool = true;
let und = undefined;
let nul = null;
let obj = {};
let arr = [];
let func = function(){};
let date = new Date();
typeof
javascript
console.log(typeof num); // "number"
console.log(typeof str); // "string"
console.log(typeof bool); // "boolean"
console.log(typeof und); // "undefined"
console.log(typeof nul); // "object" (注意这个历史遗留问题)
console.log(typeof obj); // "object"
console.log(typeof arr); // "object"
console.log(typeof func); // "function"
instanceof
javascript
console.log(arr instanceof Array); // true
console.log(obj instanceof Object); // true
console.log(date instanceof Date); // true
Object.prototype.toString.call()
javascript
console.log(Object.prototype.toString.call(num)); // "[object Number]"
console.log(Object.prototype.toString.call(str)); // "[object String]"
console.log(Object.prototype.toString.call(bool)); // "[object Boolean]"
console.log(Object.prototype.toString.call(und)); // "[object Undefined]"
console.log(Object.prototype.toString.call(nul)); // "[object Null]"
console.log(Object.prototype.toString.call(arr)); // "[object Array]"
console.log(Object.prototype.toString.call(obj)); // "[object Object]"
console.log(Object.prototype.toString.call(func)); // "[object Function]"
console.log(Object.prototype.toString.call(date)); // "[object Date]"
5. 类型转换:数据之间的"变身"术
隐式转换(自动转换)
|-------------------------|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 场景 | 转换规则 | 示例 |
| 算术运算(除 + 外) | 将非Number类型转换为Number | '10' - 5 → 5 (字符串转数字) '10' * 2 → 20 (字符串转数字) true - 1 → 0 (布尔转数字: true=1) null - 1 → -1 (null转数字: null=0) undefined - 1 → NaN (undefined转数字: undefined=NaN) |
| + 运算 | 字符串拼接优先 :如果一侧是String,则另一侧也转为String进行拼接;否则进行数值相加 | '1' + 1 → '11' (字符串拼接) 1 + true → 2 (true转数字1) 1 + null → 1 (null转数字0) 1 + undefined → NaN (undefined转数字NaN) '1' + '1' → '11' (字符串拼接) 1 + 1 → 2 (数值相加) |
| == 比较 | 先进行类型转换,再比较值(但不推荐使用,推荐使用===) | 1 == '1' → true (字符串转数字) null == undefined → true (它们被视为相等) 0 == false → true (false转数字0) '' == false → true (空字符串转数字0, false转数字0) [] == 0 → true (数组转数字0) |
这就像一个"自动翻译器",在不同类型的值进行运算或比较时,它会自动将它们转换为同一类型。
但有时会"翻译错",导致意想不到的结果。
显式转换(手动转换)
|---------------------------------------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 方法 | 描述 | 示例 |
| Number() | 将值转换为数字 | Number('123') → 123 Number('123abc') → NaN (无法转换) Number('') → 0 (空字符串转0) Number(true) → 1 Number(false) → 0 Number(null) → 0 Number(undefined) → NaN |
| String() | 将值转换为字符串 | String(123) → "123" String(true) → "true" String(null) → "null" String(undefined) → "undefined" String({}) → "[object Object]" String([1,2,3]) → "1,2,3" |
| Boolean() | 将值转换为布尔值 | Boolean(123) → true Boolean('hello') → true Boolean([]) → true Boolean({}) → true Boolean(0) → false Boolean('') → false Boolean(null) → false Boolean(undefined) → false Boolean(NaN) → false Boolean(false) → false |
| parseInt() / parseFloat() | 从字符串中解析出整数或浮点数 | parseInt('123abc') → 123 parseFloat('123.45abc') → 123.45 parseInt('abc123') → NaN (开头无法解析) parseInt('10', 2) → 2 (第二个参数是进制,解析二进制字符串) |
| toString() | 将值转换为字符串(除null/undefined外) | (123).toString() → "123" true.toString() → "true" [1,2,3].toString() → "1,2,3" {}.toString() → "[object Object]" // null.toString() // ❌ 报错 // undefined.toString() // ❌ 报错 |
这就像"手动指定翻译目标",更可控、更安全。
隐式转换示例
javascript
console.log(5 + '5'); // "55" (字符串拼接)
console.log(5 - '5'); // 0 (数值相减)
console.log('10' > '2'); // false (字符串比较,按字典序)
console.log(10 > '2'); // true (数字比较,字符串转数字)
console.log(null == undefined); // true
console.log(0 == false); // true
console.log('' == false); // true
显式转换示例
javascript
console.log(String(123)); // "123"
console.log(Number('123')); // 123
console.log(Boolean('hello')); // true
console.log(parseInt('123abc')); // 123
console.log(parseFloat('123.45abc')); // 123.45
console.log((123).toString()); // "123"
6. ToPrimitive 抽象操作(对象转原始类型)
当对象需要转换为原始类型(数字、字符串、布尔)时,JavaScript 会调用内部的 ToPrimitive 抽象操作。
其规则如下:
如果对象有 Symbol.toPrimitive 方法,优先调用该方法。
否则,根据预期类型(hint):
- Hint "number"(如数学运算)
- 先调用 valueOf(),如果返回原始值则使用;
- 否则调用 toString(),如果返回原始值则使用;
- 否则报错。
- Hint "string"(如字符串拼接)
- 先调用 toString(),如果返回原始值则使用;
- 否则调用 valueOf(),如果返回原始值则使用;
- 否则报错。
javascript
const obj = {
valueOf() { return 10; },
toString() { return '20'; }
};
console.log(obj + 5); // 15 (hint为"number",使用valueOf返回的10)
console.log(String(obj)); // "20" (hint为"string",使用toString返回的"20")
运算符:对数据进行操作的符号
运算符就像"操作员",对变量和值进行操作并产生结果。
1. 算术运算符
|---------|-----------------------------|---------------------------------------------------------------------|
| 运算符 | 描述 | 示例 |
| + | 加法 / 字符串拼接 | 10 + 5 → 15 'Hello' + ' World' → 'Hello World' |
| - | 减法 | 10 - 5 → 5 |
| * | 乘法 | 10 * 5 → 50 |
| / | 除法 | 10 / 5 → 2 |
| % | 取模(求余数) | 10 % 3 → 1 (10除以3余1) |
| ++ | 自增(i++ 是先使用再加1,++i 是先加1再使用) | let i = 5; console.log(i++); // 5 let j = 5; console.log(++j); // 6 |
| -- | 自减(i-- 是先使用再减1,--i 是先减1再使用) | let i = 5; console.log(i--); // 5 let j = 5; console.log(--j); // 4 |
2. 比较运算符
|---------|-----------------------------|----------------------------------|
| 运算符 | 描述 | 示例 |
| == | 相等(会进行类型转换 ,不推荐使用) | 5 == '5' → true |
| === | 严格相等(不进行类型转换 ,推荐使用) | 5 === '5' → false 5 === 5 → true |
| != | 不相等(会进行类型转换) | 5 != '5' → false |
| !== | 严格不相等(不进行类型转换) | 5 !== '5' → true 5 !== 5 → false |
| > | 大于 | 10 > 5 → true |
| < | 小于 | 5 < 10 → true |
| >= | 大于或等于 | 10 >= 5 → true |
| <= | 小于或等于 | 5 <= 10 → true |
始终使用 === 和 !==,除非你非常明确自己需要隐式类型转换的行为。这能避免许多潜在的bug。
3. 逻辑运算符
|----------|------------------------|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 运算符 | 描述 | 短路行为 | 示例 |
| && | 逻辑与(AND ) | 如果第一个操作数是假值,则返回第一个操作数;否则返回第二个操作数。 | true && true → true true && false → false 0 && 'hello' → 0 (0是假值,直接返回0) 'hello' && 'world' → 'world' (hello是真值,返回world) |
| || | 逻辑或(OR ) | 如果第一个操作数是真值,则返回第一个操作数;否则返回第二个操作数。 | true || false → true false || true → true 'hello' || 'world' → 'hello' (hello是真值,直接返回hello) '' || 'world' → 'world' (空字符串是假值,返回world) |
| ! | 逻辑非(NOT ) | 返回操作数的布尔取反值。 | !true → false !false → true !0 → true !'hello' → false |
| ?? | 空值合并运算符 (ES2020新增) | 如果左侧操作数是null或 undefined,则返回右侧操作数;否则返回左侧操作数cnblogs.com+2 。 | null ?? 'default' → 'default' undefined ?? 'default' → 'default' 0 ?? 'default' → 0 (0不是null或undefined) '' ?? 'default' → '' (空字符串不是null或undefined) false ?? 'default' → false (false不是null或undefined) |
| ?. | 可选链运算符 (ES2020新增) | 允许安全地访问嵌套对象属性,如果链中的任何引用是 null 或 undefined,则短路返回 undefined,而不会抛出错误cnblogs.com+2 。 | const adventurer = { name: 'Alice', cat: { name: 'Dinah' } }; adventurer.dog?.name → undefined (dog不存在,返回undefined,不报错) adventurer.someNonExistentMethod?.() → undefined (方法不存在,返回undefined,不报错) |
逻辑运算符的"短路"行为
逻辑运算符的"短路"行为非常重要,它意味着:
- &&:如果第一个操作数是假值,第二个操作数根本不会被求值(因为结果已经确定为假)。
- ||:如果第一个操作数是真值,第二个操作数根本不会被求值(因为结果已经确定为真)。
使用 || 设置默认值(但要注意0和空字符串会被认为是假值)
javascript
function greet(name) {
name = name || 'Guest'; // 如果name是假值(如'', 0, null, undefined),则使用'Guest'
console.log('Hello, ' + name);
}
greet(); // Hello, Guest
greet('Alice'); // Hello, Alice
使用 ?? 更精确地设置默认值(只处理null和undefined)
javascript
function greet(name) {
name = name ?? 'Guest'; // 只有当name是null或undefined时,才使用'Guest'
console.log('Hello, ' + name);
}
greet(); // Hello, Guest
greet(''); // Hello, (空字符串被保留)
greet(0); // Hello, 0 (数字0被保留)
4. 位运算符(直接操作整数在内存中的二进制位)
|------------|-------------------------------|-------------------------------------------------|
| 运算符 | 描述 | 示例 |
| & | 按位与(AND) | 5 & 3 → 1 (二进制: 101 & 011 = 001) |
| | | 按位或(OR) | 5 | 3 → 7 (二进制: 101 | 011 = 111) |
| ^ | 按位异或(XOR) | 5 ^ 3 → 6 (二进制: 101 ^ 011 = 110) |
| ~ | 按位非(NOT) | ~5 → -6 (二进制取反) |
| << | 左移(各二进位全部左移若干位) | 5 << 1 → 10 (二进制: 101 << 1 = 1010 = 十进制10) |
| >> | 右移(各二进位全部右移若干位,正数高位补0,负数高位补1) | 5 >> 1 → 2 (二进制: 101 >> 1 = 010 = 十进制2) |
| >>> | 无符号右移(各二进位全部右移若干位,高位始终补0) | 5 >>> 1 → 2 (二进制: 101 >>> 1 = 010 = 十进制2) |
注意:位运算符会将操作数转换为32位整数,然后进行操作,结果也是一个32位整数。对于非常大的数字,这可能会导致精度丢失。位运算符主要用于底层操作、性能优化等场景。
5. 赋值运算符与复合赋值
|-------------|-----------|------------------------------|
| 运算符 | 描述 | 等同于 |
| = | 赋值 | let x = 10; |
| += | 加并赋值 | x += 5 → x = x + 5 |
| -= | 减并赋值 | x -= 5 → x = x - 5 |
| *= | 乘并赋值 | x *= 5 → x = x * 5 |
| /= | 除并赋值 | x /= 5 → x = x / 5 |
| %= | 取模并赋值 | x %= 5 → x = x % 5 |
| **= | 幂并赋值(ES6) | x **= 2 → x = x ** 2 |
| <<= | 左移并赋值 | x <<= 2 → x = x << 2 |
| >>= | 右移并赋值 | x >>= 2 → x = x >> 2 |
| >>>= | 无符号右移并赋值 | x >>>= 2 → x = x >>> 2 |
| &= | 按位与并赋值 | x &= 3 → x = x & 3 |
| |= | 按位或并赋值 | x |= 3 → x = x | 3 |
| ^= | 按位异或并赋值 | x ^= 3 → x = x ^ 3 |
javascript
let x = 10;
x += 5; // x = 10 + 5 = 15
x = 2; // x = 15 2 = 30
console.log(x); // 30
控制流:决定代码的执行顺序
控制流语句就像"交通指挥灯"或"岔路口的指示牌",决定代码执行哪条路径。
1. 条件语句:根据条件选择执行不同的代码
if...else if...else 语句
javascript
let score = 85;
if (score >= 90) {
console.log('优秀!');
} else if (score >= 80) {
console.log('良好!'); // 输出这个
} else if (score >= 60) {
console.log('及格!');
} else {
console.log('不及格!');
}
三元运算符(条件运算符):简化版的if...else
javascript
// 条件 ? 表达式1 (条件为真时执行) : 表达式2 (条件为假时执行)
let grade = score >= 60 ? '及格' : '不及格';
console.log(grade); // "及格"
// switch 语句:用于基于不同条件执行不同代码(特别适合枚举值)
let day = 'Monday';
switch (day) {
case 'Monday':
console.log('星期一');
break; // 遇到break则跳出switch,继续执行后面的代码
case 'Tuesday':
console.log('星期二');
break;
case 'Wednesday':
console.log('星期三');
break;
default: // 相当于else
console.log('其他');
}
2. 循环与迭代:重复执行某段代码
循环就像"唱片机或跑步机,反复做同一件事。
基本循环
for循环:适合已知循环次数的情况
javascript
for (let i = 0; i < 5; i++) {
console.log('for循环:当前数字是:' + i);
}
// 输出:
// for循环:当前数字是:0
// for循环:当前数字是:1
// for循环:当前数字是:2
// for循环:当前数字是:3
// for循环:当前数字是:4
while循环:适合不确定循环次数,只关注循环条件的情况
javascript
let j = 0;
while (j < 3) {
console.log('while循环:' + j);
j++;
}
// 输出:
// while循环:0
// while循环:1
// while循环:2
do...while循环:至少会执行一次代码块
javascript
let k = 0;
do {
console.log('do-while循环:' + k);
k++;
} while (k < 3);
// 输出:
// do-while循环:0
// do-while循环:1
// do-while循环:2
循环控制:break 和 continue
|--------------|------------------------------------------------|--------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| 语句 | 作用 | 适用循环 | 示例 |
| break | 立即终止 当前循环csdn.net+1 | for, while, do...while, for...in, for...of | javascript for (let i = 0; i < 10; i++) { if (i === 5) { break; // 当i等于5时,终止循环 } console.log(i); } // 输出: 0, 1, 2, 3, 4 |
| continue | 跳过 当前循环迭代的剩余代码,立即开始下一次迭代****csdn.net+1 | for, while, do...while, for...in, for...of | javascript for (let i = 0; i < 5; i++) { if (i % 2 === 0) { continue; // 当i是偶数时,跳过当前迭代 } console.log(i); } // 输出: 1, 3 |
break、continue 和 return 的区别
|--------------|------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 语句 | 作用范围 | 示例 |
| break | 终止当前循环 或switch语句 | javascript for (let i = 0; i < 10; i++) { if (i === 5) { break; // 终止循环 } console.log(i); } |
| continue | 跳过当前循环迭代 | javascript for (let i = 0; i < 5; i++) { if (i % 2 === 0) { continue; // 跳过当前迭代 } console.log(i); } |
| return | 从函数中返回一个值,并终止函数的执行 (如果在循环中,也会终止整个函数)csdn.net | javascript function findFirstPositive(numbers) { for (let i = 0; i < numbers.length; i++) { if (numbers[i] > 0) { return numbers[i]; // 找到第一个正数,立即返回并终止函数 } } return -1; } |
break和continue只影响当前所在的循环。
如果存在嵌套循环,它们只影响包含它们的那一层循环。
javascript
// 嵌套循环中的break和continue
for (let i = 0; i < 2; i++) {
console.log('外层循环 i:', i);
for (let j = 0; j < 3; j++) {
if (j === 1) {
break; // 只终止内层循环
}
console.log(' 内层循环 j:', j);
}
}
// 输出:
// 外层循环 i: 0
// 内层循环 j: 0
// 外层循环 i: 1
// 内层循环 j: 0
高级迭代循环
for...in循环:遍历对象的可枚举属性(包括原型链上的)
javascript
// 注意:不推荐用于遍历数组,顺序可能不保证
const person = {
name: 'Alice',
age: 30,
job: 'Developer'
};
for (const key in person) {
console.log(key + ': ' + person[key]);
}
// 输出:
// name: Alice
// age: 30
// job: Developer
for...of循环:遍历可迭代对象(数组、字符串、Map、Set等)的值
javascript
// 注意:不能遍历普通对象
const colors = ['red', 'green', 'blue'];
for (const color of colors) {
console.log(color);
}
// 输出:
// red
// green
// blue
// 遍历字符串
const str = "hello";
for (const char of str) {
console.log(char);
}
// 输出:
// h
// e
// l
// l
// o
for...in vs for...of
|----------|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| 特性 | for...in | for...of |
| 遍历目标 | 对象 的可枚举属性(键) | 可迭代对象 的值(如数组、字符串、Map、Set、arguments、NodeList等) |
| 遍历内容 | 键 (key) | 值 (value) |
| 适用场景 | 遍历对象属性 | 遍历数组、字符串等迭代器对象 |
| 顺序 | 不一定 按顺序(对于数组) | 按顺序 |
| 原型链 | 会遍历原型链上的可枚举属性 | 只遍历对象自身的可迭代属性 |
| 示例 | javascript const obj = {a:1, b:2}; for (const key in obj) { console.log(key); // 输出: a, b } | javascript const arr = [1, 2, 3]; for (const value of arr) { console.log(value); // 输出: 1, 2, 3 } |
- 遍历数组:for...of 或 forEach、map、filter 等数组方法;
- 遍历对象:for...in 或 Object.keys()/Object.values()/Object.entries()。
函数基础:可重复使用的代码块
函数就像"机器"或"工具",你给它一些输入(参数),它就经过内部处理,然后给你一个输出(返回值)。
定义一次,可以多次调用,大大提高了代码的复用性和可维护性。
1. 函数声明与函数表达式
函数声明:会提升(可以在声明前调用)
javascript
function sayHello(name) {
console.log('Hello, ' + name + '!');
}
sayHello('Alice'); // Hello, Alice!
函数表达式:不会提升,必须先定义后调用
javascript
const sayGoodbye = function(name) {
console.log('Goodbye, ' + name + '!');
};
sayGoodbye('Bob'); // Goodbye, Bob!
箭头函数(ES6新增):更简洁的函数表达式语法
|----------------------|----------------------------------------------------|---------------------------------|
| 特性 | 箭头函数 | 普通函数 |
| 语法 | (params) => { statements } 或 param => expression | function(params) { statements } |
| this 绑定 | 没有自己的 this,会捕获其所在上下文的this值csdn.net+2 | 有自己的this,取决于如何调用(谁调用它,this就指向谁) |
| arguments 对象 | 没有 arguments对象 ,可以使用剩余参数代替 | 有自己的arguments对象 |
| new 调用 | 不能作为构造函数使用 (不能用new调用) | 可以作为构造函数使用 |
| super 调用 | 没有 super | 有super(用于调用父类方法) |
| yield | 不能使用 yield关键字 (不能用作生成器函数) | 可以使用yield(用作生成器函数) |
javascript
const sayHi = (name) => {
console.log('Hi, ' + name + '!');
};
sayHi('Charlie'); // Hi, Charlie!
函数声明提升 vs 函数表达式不提升
函数声明会被"提升"到当前作用域的顶部,可以在函数声明之前调用它。
javascript
sayHello(); // Hello! (可以调用)
function sayHello() {
console.log('Hello!');
}
函数表达式(包括箭头函数)不会被提升,必须先定义,然后才能调用。
javascript
// sayGoodbye(); // ❌ 报错: sayGoodbye is not a function
const sayGoodbye = function() {
console.log('Goodbye!');
};
sayGoodbye(); // Goodbye!
2. 箭头函数
箭头函数是ES6引入的更简洁的函数书写方式,但它与普通函数也有一些重要区别。
无参数:必须使用圆括号
javascript
const greet = () => console.log('Hello!');
单个参数:可以省略圆括号
javascript
const double = num => num*2;
多个参数:必须使用圆括号
javascript
const add = (a, b) => a + b;
函数体有多条语句:必须使用大括号
javascript
const describe = (name, age) => {
console.log('Name:', name);
console.log('Age:', age);
};
返回对象字面量:需要用小括号包裹对象
javascript
const createPerson = (name, age) => ({ name, age });
// 等同于: return { name, age };
箭头函数的this指向问题
javascript
const person = {
name: 'Alice',
regularFunc: function() {
console.log(this.name); // "Alice" (this指向person对象)
},
arrowFunc: () => {
console.log(this.name); // undefined (this指向定义时的上下文,这里是全局或window,没有name属性)
}
};
person.regularFunc(); // Alice
person.arrowFunc(); // undefined
3. 参数:传递给函数的输入
默认参数(ES6)
javascript
// 为参数设置默认值
function greet(name = 'Guest', greeting = 'Hello') {
console.log(greeting + ', ' + name + '!');
}
greet(); // Hello, Guest!
greet('Alice'); // Hello, Alice!
greet('Bob', 'Hi'); // Hi, Bob!
剩余参数(ES6):收集不定数量的参数到一个数组中
javascript
// 使用...args收集所有剩余参数
function sum(...args) {
return args.reduce((total, current) => total + current, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
// 剩余参数必须放在最后
function describePerson(name, ...hobbies) {
console.log('Name:', name);
console.log('Hobbies:', hobbies);
}
describePerson('Alice', 'reading', 'coding', 'traveling');
// Name: Alice
// Hobbies: [ 'reading', 'coding', 'traveling' ]
命名参数(通过对象解构实现)
javascript
// 通过对象解构实现命名参数,使代码更清晰
function createUser({ name, age = 30, isAdmin = false }) {
return {
name,
age,
isAdmin
};
}
const user = createUser({ name: 'Alice', age: 25 });
console.log(user); // { name: 'Alice', age: 25, isAdmin: false }
4. 返回值与递归
javascript
// 函数返回值:如果没有显式返回,则返回undefined
function add(a, b) {
return a + b; // 返回计算结果
}
const sum = add(5, 3);
console.log(sum); // 8
// 没有return语句的函数
function sayHello() {
console.log('Hello!'); // 这条语句会执行
// 没有return,隐式返回undefined
}
const result = sayHello();
console.log(result); // undefined
// 递归函数:函数调用自身
function factorial(n) {
if (n === 0 || n === 1) {
return 1; // 基准条件:结束递归
} else {
return n factorial(n - 1); // 递归条件:调用自身
}
}
console.log(factorial(5)); // 120 (5 4 3 2 1)
递归的基本要素
基准条件(Base Case)
一个或多个可以直接求解而无需递归的情况,用于终止递归。例如上面factorial函数中的n === 0 || n === 1。
递归条件(Recursive Case)
将问题分解为更小的、与原问题形式相同的问题,并调用自身来求解这些子问题。例如上面factorial函数中的n factorial(n - 1)。
递归的注意事项
确保基准条件能够被触发,否则会导致无限递归,引发"栈溢出"(Stack Overflow)错误。
递归调用的参数必须向基准条件逼近。
javascript
// 无限递归的例子(会导致栈溢出)
function infiniteRecursion() {
return infiniteRecursion(); // 没有基准条件
}
// infiniteRecursion(); // ❌ RangeError: Maximum call stack size exceeded
基本代码规范与注释风格
代码规范就像"语法和拼写规则",而注释就像"课文中的解释说明"。它们都为了让你的代码更容易被别人(以及未来的自己)理解和维护。
1. 代码风格建议
|---------|---------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|--------------------------------------------------------------------|
| 规则 | 建议 | 好的例子 | 不好的例子 |
| 缩进 | 使用2 个空格 或4 个空格 (不要混用****Tab 和空格 ) | javascript function add(a, b) { return a + b; } | javascript function add(a,b){return a+b;} |
| 命名 | 变量和函数使用驼峰命名法(camelCase);常量使用全大写****+ 下划线 (UPPER_SNAKE_CASE);类使用帕斯卡命名法(PascalCase) | let userName = 'Alice'; const MAX_SIZE = 100; class UserService {} | let user_name = 'Alice'; const maxsize = 100; class userService {} |
| 空行 | 在函数之间 、逻辑块之间添加空行,增加可读性 | javascript function a() {} <br><br> function b() {} | javascript function a(){} function b(){} |
| 分号 | 虽然JavaScript有自动分号插入(**ASI ) 机制,但建议始终使用分号**,避免潜在问题 | javascript let x = 5; | javascript let x = 5 (在某些情况下可能导致问题) |
| 字符串 | 优先使用单引号(')或反引号(`),双引号也可以,但要保持一致 | javascript let str = 'hello'; | javascript let str = "hello" (混用) |
| 比较 | 始终使用严格相等 ===和严格不相等 !== | javascript if (x === 5) {} | javascript if (x == 5) {} |
| 大括号 | 始终使用大括号 包裹代码块,即使只有一条语句(不要省略大括号) | javascript if (true) { console.log('yes'); } | javascript if (true) console.log('yes'); |
2. 注释风格
单行注释示例
javascript
let userName = 'Alice'; // 用户名
多行注释示例
javascript
/**
*这是一个多行注释
*可以写很多行
*通常用于解释一段复杂的逻辑
*/
JSDoc风格注释示例(推荐用于函数)
javascript
/**
* 根据用户ID获取用户信息
* @param {number} userId - 用户ID
* @returns {Object|null} 用户对象或null(如果未找到)
* @example
* const user = getUserById(1);
* console.log(user.name); // 输出用户名
*/
function getUserById(userId) {
// ...实现代码
}
注意事项
- 解释"为什么",而不是"是什么"(代码本身已经说了"是什么")。
- 保持注释最新,过时的注释比没有注释更糟糕。
- 避免不必要的注释,如果代码本身已经很清晰,就不需要注释。
- 对复杂的算法、临时解决方案、重要的业务逻辑进行注释。