第3篇:吃透JavaScript语言规则,写出规范不踩坑的代码

86 阅读14分钟

大家好,我是橘朵~ 上一篇我们说的是JS代码的嵌入方式(<script><noscript>标签),这一篇我们讲一讲「JavaScript的语言规则」——就像说话要讲语法,写JS代码也有明确的规则,只有吃透这些规则,才能写出规范、不报错、易维护的代码,为后续学习函数、DOM操作、框架打下坚实基础。

适合人群:零基础前端新手、刚入门JS,写代码经常报错(语法错误、变量问题)的开发者;想规范代码风格,避免低级坑的初学者。

阅读收获:吃透JS基本语法规则、标识符命名规范、变量/常量声明(var/let/const区别)、数据类型、类型转换、运算符、流程控制、函数定义等核心规则,掌握8个实战避坑技巧,能独立写出规范的基础JS代码。

1. 核心语法规则

有些人会觉得”规矩是束缚“,但在实际开发中,不遵守规则会带来很多麻烦:比如代码报错无法运行、多人协作开发时别人看不懂你的代码、代码维护困难。

过往的经验教会我,重视语法规则,能避免很多不必要的错误。新手一定要记住:语言规则是JS的“地基”,地基打牢了,后续学习才会更轻松。

1.1 基本语法规则

1.1.1 区分大小写

JavaScript 是大小写敏感的。userNameusername是两个不同的变量。

// 区分大小写示例
let userName = "橘朵"; // 变量1:userName(驼峰命名)
let username = "前端新手"; // 变量2:username(全小写)
let UserName = "JS学习者"; // 变量3:UserName(帕斯卡命名)

console.log(userName); // 输出:橘朵
console.log(username); // 输出:前端新手
console.log(UserName); // 输出:JS学习者
// 三个变量是不同的,不会互相覆盖

在开发时,一定要注意保持变量名大小写的一致性,尤其是函数名、属性名,一定要注意大小写。

1.1.2 语句分隔

语句通常以分号 ; 结束。虽然浏览器在某些情况下会自动补全分号,但还是建议显式的添加分号,这样可以避免潜在的错误或是代码压缩时产生问题,同时,在某些情况下也有助于提升性能(不加分号解析器会尝试在合适的位置补上分号来纠正语法错误)。

// 错误写法:省略分号(不推荐)
let a = 10
let b = 20
console.log(a + b)

// 正确写法:显式加分号(推荐)
let c = 10;
let d = 20;
console.log(c + d); // 输出:30
// 隐藏坑:省略分号可能导致代码解析错误
let e = 5
let f = e + 3 // 看似没问题,但压缩后可能变成 let e=5let f=e+3,报错

从一开始就养成”每句代码结束加分号“的习惯,不用纠结”什么时候可以省“,直接全加,避免踩坑。

1.1.3 代码块

使用花括号 {}来定义代码块,比如if语句、循环和函数等。就算代码块中只有一条语句,也建议使用 {},这样能提高代码清晰度,也能避免不必要的错误。

// 有效,但容易导致错误,应该避免 
if (test)   
console.log(test);  

// 推荐 
if (test) {   
console.log(test); 
 } 

1.1.4 注释:代码的“说明书”

注释是代码的解释,注释的主要作用是:方便后续维护代码。

  • 单行注释:以 //开头。
  • 多行注释:以 /*开头,以 */结尾。
// 定义一个变量,存储用户姓名(单行注释,说明变量用途)
let userName = "橘朵";

/*
 * 定义一个函数,计算两个数字的和
 * 参数a:第一个数字
 * 参数b:第二个数字
 * 返回值:两个数字的和
 */
function add(a, b) {
    return a + b; // 单行注释:返回计算结果
}

复杂逻辑一定要写注释,否则过几天自己都看不懂;不要注释掉代码后忘记删除,导致代码冗余。

1.1.5 严格模式:规范代码,提前报错

严格模式是一种不同的 JavaScript 解析和执行模型,ES5引入这个特性,ES6+完全兼容并推荐使用,能禁用ECMAScript 3的不规范写法,对不安全的操作抛出错误(比如未声明变量就使用),帮助我们提前发现问题,避免隐藏bug。

对整个脚本启用严格模式,可以在脚本开头加上这一行:

"use strict";//预处理指令

let x = 10;
// y = 20; // 报错:y未声明(严格模式下,未声明的变量不能使用)

单独指定一个函数在严格模式下执行,可以放到函数体开头:

function doSomething() {   
    "use strict";   
     let m = 5;
    // n = 10; // 报错:n未声明
    console.log(m); // 输出:5
} 

1.1.6 模块化importexport(ES6特性,代码复用)

ES6 引入了模块系统,使用 importexport关键字来导入和导出模块,目前已成为JS模块化的标准,替代了过去的CommonJS(require/module.exports)、AMD等方案,所有现代浏览器和Node.js(14.13+)均全面支持,是当前主流的代码复用方式。

第一步:创建模块文件utils.js,导出函数/变量:

// utils.js(模块文件)
// 导出单个函数
export function add(a, b) {
    return a + b;
}

// 导出单个变量
export const PI = 3.14159;

// 导出多个函数/变量(推荐)
const multiply = (x, y) => x * y;
const subtract = (x, y) => x - y;
export { multiply, subtract };

// 补充:默认导出(ES6+新增,适用于单个核心导出)
export default function divide(x, y) {
    return x / y;
}

第二步:在主文件main.js中,导入并使用模块:

// main.js(主文件)
// 导入模块(注意路径要写对,相对路径需加./)
import { add, PI, multiply, subtract } from "./utils.js";
// 导入默认导出(无需大括号,可以自定义名称)
import divide from "./utils.js";

// 使用导入的函数/变量
console.log(add(2, 3)); // 输出:5
console.log(PI); // 输出:3.14159
console.log(multiply(4, 5)); // 输出:20
console.log(subtract(10, 3)); // 输出:7
console.log(divide(10, 2)); // 输出:5

模块化需要在支持ES6模块的环境中运行(浏览器需要给script标签加type="module",Node.js中需要把文件后缀改为.mjs或在package.json中设置"type": "module");路径一定要写对,相对路径必须加./../,否则导入失败;默认导出与按需导出不可以混用(需分开导入)。

1.2 标识符和命名

标识符就是变量、函数、属性或函数参数的名称。命名有明确的规则,违反会报错;命名方式是推荐,可以让代码更易读。

1.2.1 命名规则

  • 第一个字符必须以字母、下划线 _或美元符号 $开头。
  • 后续字符可以是字母、数字、下划线或美元符号。
  • 不能使用 JS 的关键字保留字(比如 if, let, function等,JS有专门的关键字列表,新手不用死记,编辑器会提示)。
  • ES6+新增:可以使用Unicode字符(如中文、希腊字母)命名,但不推荐(影响兼容性和可读性)。
// 合法命名(推荐)
let userName = "橘朵"; // 字母开头,驼峰命名
let _age = 25; // 下划线开头
let $userId = 1001; // 美元符号开头
let user123Name = "张三"; // 后续字符包含数字

// 非法命名(报错)
// let 123user = "李四"; // 错误:以数字开头
// let user-name = "王五"; // 错误:包含连字符(-)
// let let = "变量"; // 错误:使用关键字let
// let 123 = 10; // 错误:以数字开头

1.2.2 命名方式(推荐遵守,提升可读性)

没有强制要求,但行业内有通用的命名方式,新手建议遵循,方便多人协作,也符合当前主流编码规范:

  • 最常见的命名方式是驼峰命名法(camelCase),第一个字母小写,后面的单词首字母大写,比如userName、userNameCode。适用于变量、函数、属性。
  • 帕斯卡命名法(PascalCase),也就是每个单词首字母都大写,比如MyClass。适用于构造函数、类名、组件(前端框架常用)。
  • 常量通常全部大写,单词之间用下划线连接,比如MAX_SIZE。
  • ES6+类/模块:类名必须用帕斯卡命名,模块导出的函数/变量优先用驼峰命名。
// 驼峰命名法(变量、函数)
let userEmail = "test@example.com";
function calculateTotalPrice() {
    return 100;
}

// 帕斯卡命名法(类名)
class User {
    constructor(name) {
        this.name = name;
    }
}

// 常量命名(全部大写)
const MAX_UPLOAD_SIZE = 5 * 1024 * 1024; // 5MB
const API_BASE_URL = "https://api.example.com";

// 模块导出命名(驼峰)
export function formatTime() {
    return new Date().toLocaleString();
}

命名要“见名知意”,不要用a、b、c这种无意义的名称;尽量使用英文命名,避免中文(中文可能导致编码问题和兼容性问题);遵循命名方式,让代码更专业、易读。

1.3 变量与常量声明

在JavaScript中,可以使用varlet来声明变量和const来声明常量。

  • var:通过var声明的变量会被提升到函数或全局代码顶部,具有函数作用域或全局作用域。var因为存在很多问题(作用域混乱、变量提升等),已经被行业淘汰。
  • let:在ES6 引入,通过let声明的变量不存在变量提升,作用域由{}界定的跨级作用域,而且在声明前访问会报错,称为”暂时性死区“。
  • const:和let类似,只是const声明的是常量,而且必须初始化,不可以重新赋值,也就是说不可更改。但使用const 声明的对象或数组,可以修改内部的属性或元素。

推荐使用 let const 代替 varconst优先,let次之。

声明方式作用域变量提升重复声明赋值规则
var函数作用域/全局作用域存在(声明提升到顶部,值为undefined)允许可重复赋值
let块级作用域({}界定)不存在(暂时性死区,无法提前访问)不允许(同一作用域)可重复赋值
const块级作用域({}界定)不存在(暂时性死区,无法提前访问)不允许(同一作用域)必须初始化,不可重复赋值(对象/数组可修改内部属性)
// 1. var的问题(不推荐,已淘汰)
var a = 10;
var a = 20; // 允许重复声明,不报错
console.log(a); // 输出:20

function testVar() {
    if (true) {
        var b = 30; // 函数作用域,整个函数内都能访问
    }
    console.log(b); // 输出:30(if块外也能访问,不符合直觉)
}
testVar();

// 2. let的用法(推荐,变量)
let c = 10;
// let c = 20; // 不允许重复声明,报错
c = 20; // 可重复赋值
console.log(c); // 输出:20

function testLet() {
    if (true) {
        let d = 30; // 块级作用域,只在if块内有效
    }
    // console.log(d); // 报错:d未定义(块外无法访问,符合直觉)
}
testLet();

// 3. const的用法(优先推荐,常量)
const PI = 3.14; // 必须初始化,否则报错
// const PI; // 报错:未初始化
// PI = 3.1415; // 不允许重复赋值,报错

// 注意:const声明的对象/数组,可修改内部属性/元素
const user = { name: "橘朵" };
user.name = "张三"; // 允许,不报错
console.log(user.name); // 输出:张三

const arr = [1, 2, 3];
arr.push(4); // 允许,不报错
console.log(arr); // 输出:[1,2,3,4]

1.4 数据类型

JavaScript 是动态类型语言,变量在声明时不需要指定类型,运行时可以改变类型。数据类型分为原始类型(基本类型)和引用类型(对象类型),二者区别很大,新手容易混淆。ES6新增了BigInt、Symbol两种原始类型,目前已全面支持,是最新规范。

1.4.1 原始数据类型(不可变,共7种)

  • Undefined:变量已声明但未赋值。
  • Null:空值或不存在的对象。
  • Booleantruefalse
  • Number:整数和浮点数。
  • BigInt:用于表示任意精度的整数,在数字末尾加 n,如 12345678901234567890n。用于解决Number类型的精度限制。
  • String:文本数据,可用单引号 ''、双引号 ""或反引号 ``(模板字符串)定义。
  • Symbol:用于表示唯一值,常用于对象属性的键。
// 1. Undefined:变量已声明但未赋值
let undef;
console.log(undef); // 输出:undefined
console.log(typeof undef); // 输出:"undefined"

// 2. Null:空值或不存在的对象
let nullVar = null;
console.log(nullVar); // 输出:null
console.log(typeof nullVar); // 输出:"object"(历史遗留bug,记住即可)

// 3. Boolean:布尔值(true/false)
let isSuccess = true;
let isError = false;
console.log(isSuccess); // 输出:true

// 4. Number:整数和浮点数(存在精度限制,最大安全整数2^53-1)
let intNum = 10; // 整数
let floatNum = 3.14; // 浮点数
let nanNum = NaN; // 非数字(Not a Number),NaN !== NaN(唯一不与自身相等的值)
console.log(intNum + floatNum); // 输出:13.14
console.log(Number.MAX_SAFE_INTEGER); // 输出:9007199254740991(最大安全整数)

// 5. BigInt:ES6+新增,任意精度整数(数字末尾加n,解决Number精度问题)
let bigNum = 12345678901234567890n;
console.log(bigNum * 2n); // 输出:24691357802469135780n(不会溢出)
// 注意:BigInt不能与Number直接运算,需先转换类型
console.log(Number(bigNum) + 10); // 输出:12345678901234567900(可能丢失精度)

// 6. String:文本数据(单引号、双引号、反引号,反引号为ES6+新增)
let str1 = '单引号字符串';
let str2 = "双引号字符串";
let str3 = `反引号字符串(模板字符串),可以换行:
第二行内容`;
let name = "橘朵";
let str4 = `你好,我是${name}`; // 模板字符串,插入变量(ES6+新增)
console.log(str4); // 输出:"你好,我是橘朵"

// 7. Symbol:ES6+新增,唯一值(常用于对象属性键,避免属性名冲突)
let sym1 = Symbol("key");
let sym2 = Symbol("key");
console.log(sym1 === sym2); // 输出:false(即使描述相同,也是不同的值)
// 应用:对象唯一属性
const obj = {
    [sym1]: "value1",
    [sym2]: "value2"
};
console.log(obj[sym1]); // 输出:"value1"

1.4.2 引用数据类型(对象类型,可变):

引用类型是复杂数据类型,值可变,本质是对象。

  • Object:各种对象的基类。
  • Array:数组是特殊的对象,用于表示有序列表。
  • Function:函数也是对象。
  • 其他:如 Date, RegExp, Map, Set等。MapSet是ES6新增的引用类型,比传统对象、数组更灵活,当前已广泛使用。
// 1. Object:键值对集合(基础引用类型)
let user = {
    name: "橘朵",
    age: 25,
    isStudent: false
};
console.log(user.name); // 输出:"橘朵"(访问属性)
user.age = 26; // 修改属性(可变)
console.log(user.age); // 输出:26

// 2. Array:有序数据集合(ES6+新增诸多方法,如push、map、filter等)
let fruits = ["苹果", "香蕉", "橙子"];
console.log(fruits[0]); // 输出:"苹果"(访问元素)
fruits.push("葡萄"); // 添加元素(可变)
console.log(fruits); // 输出:["苹果","香蕉","橙子","葡萄"]
// ES6+新增方法示例
console.log(fruits.map(fruit => fruit + "(新鲜)")); // 输出:["苹果(新鲜)",...]

// 3. Function:函数(也是对象,ES6+支持箭头函数)
function sayHello() {
    console.log("你好!");
}
sayHello(); // 输出:"你好!"(调用函数)

// 4. Date:日期对象(无版本更新,沿用至今)
let now = new Date();
console.log(now); // 输出当前日期时间
console.log(now.getFullYear()); // 输出当前年份

// 5. Map:ES6+新增,映射(键可以是任意类型,解决对象键只能是字符串/符号的问题)
let map = new Map();
map.set("name", "橘朵");
map.set(Symbol("id"), 1001);
map.set(123, "数字键");
console.log(map.get("name")); // 输出:"橘朵"
console.log(map.size); // 输出:3(获取映射长度)

// 6. Set:ES6+新增,集合(不重复元素,自动去重)
let set = new Set([1, 2, 2, 3]);
console.log(set); // 输出:Set(3) {1,2,3}(自动去重)
set.add(4); // 添加元素
set.delete(2); // 删除元素
console.log(set.has(3)); // 输出:true(判断元素是否存在)

1.5 类型转换

JavaScript在执行操作时会自动进行隐式类型转换。比如字符串与数字相加转换为字符串,这是JS的特点,也是新手最容易踩坑的地方。 也可以使用Number(), String(), Boolean()等函数进行显式转换(推荐)

1.5.1 隐式类型转换

隐式转换是JS自动进行的,新手要记住常见的转换规则,避免报错。

  1. 字符串+数字 转换为 字符串
  2. 数字 + 布尔值 转换为 数字
  3. 比较运算符 == 进行隐式转换, === 严格相等不进行转换
  4. 逻辑运算符转为布尔值,除false、0、""(空字符串)、nullundefinedNaN外,其他都是真值
  5. BigInt只能与BigInt 运算
  6. Symbol禁止隐式转换,会报错
// 1. 字符串 + 数字 → 字符串(最常见坑)
let num1 = 10;
let str1 = "20";
console.log(num1 + str1); // 输出:"1020"(不是30,新手容易误以为是加法)

// 2. 数字 + 布尔值 → 数字(true=1false=0)
let num2 = 5;
let bool1 = true;
console.log(num2 + bool1); // 输出:6(true转换为1)

// 3. 比较运算符的隐式转换(== vs ===)
console.log(10 == "10"); // 输出:true(==会进行隐式转换,认为10和"10"相等)
console.log(10 === "10"); // 输出:false(===严格相等,不进行转换,类型不同不相等)
console.log(null == undefined); // 输出:true(特殊规则)
console.log(null === undefined); // 输出:false

// 4. 逻辑运算符的隐式转换(转为布尔值)
// 假值:false、0、""(空字符串)、null、undefined、NaN
// 真值:除假值外的所有值
console.log(!!0); // 输出:false(双感叹号显式转为布尔值)
console.log(!!""); // 输出:false
console.log(!!"hello"); // 输出:true
console.log(!!null); // 输出:false

// 5. ES6+新增:BigInt的隐式转换(仅能与BigInt运算)
let big1 = 10n;
let big2 = 20n;
console.log(big1 + big2); // 输出:30n(正常运算)
// console.log(big1 + 10); // 报错:BigInt不能与Number隐式运算

// 6. ES6+新增:Symbol的隐式转换(禁止隐式转换,会报错)
let sym = Symbol("key");
// console.log(sym + "123"); // 报错:Cannot convert a Symbol value to a string
// console.log(Number(sym)); // 报错:Cannot convert a Symbol value to a number

1.5.2 显式类型转换(推荐)

显式转换是手动调用函数进行转换,清晰、不易错,新手推荐使用。

  • 转为数字:Number()parseInt()parseFloat()
  • 转为字符串:String()toString()
  • 转为布尔值:Boolean()
  • BigInt()
  • Symbol()
// 1. 转为数字:Number()、parseInt()、parseFloat()
let strToNum1 = Number("123");
console.log(strToNum1); // 输出:123(成功转换)
let strToNum2 = Number("123abc");
console.log(strToNum2); // 输出:NaN(转换失败)

let parseIntNum = parseInt("123.45abc");
console.log(parseIntNum); // 输出:123(只解析整数部分)
let parseFloatNum = parseFloat("123.45abc");
console.log(parseFloatNum); // 输出:123.45(解析浮点数部分)

// 2. 转为字符串:String()、toString()
let numToString1 = String(123);
console.log(numToString1); // 输出:"123"
let boolToString = String(true);
console.log(boolToString); // 输出:"true"

let numToString2 = 123.toString();
console.log(numToString2); // 输出:"123"

// 3. 转为布尔值:Boolean()
let numToBool = Boolean(123);
console.log(numToBool); // 输出:true
let nullToBool = Boolean(null);
console.log(nullToBool); // 输出:false

// 4. ES6+新增:BigInt的显式转换
let numToBig = BigInt(123);
console.log(numToBig); // 输出:123n
let bigToString = String(123n);
console.log(bigToString); // 输出:"123"

// 5. ES6+新增:Symbol的显式转换(仅能转为字符串)
let sym = Symbol("key");
console.log(String(sym)); // 输出:"Symbol(key)"
// console.log(Number(sym)); // 报错:无法转为数字

1.5.3小结

  1. 尽量使用===(严格相等)代替==(相等),避免隐式转换导致的判断错误,比如10 == "10"返回true
  2. 字符串和数字相加时,一定要先显式转换类型,避免得到拼接字符串,比如Number(str1) + num1
  3. 记住JS的“假值列表”,逻辑判断时避免出错。
  4. BigInt只能与BigInt运算,Symbol不能隐式转换,否则会报错。

1.6 运算符(JS的“计算工具”,必学)

运算符用于执行运算(算术、赋值、比较、逻辑等),是JS的基础工具,新手要掌握常用运算符的用法和优先级。

  • 算术运算符+, -, *, /, %(取模), **(指数幂)。
  • 赋值运算符=, +=, -=, *=, /=, %=
  • 比较运算符<, >, <=, >=, ==(相等,会进行类型转换), ===(严格相等,推荐), !=, !==
  • 逻辑运算符&&(逻辑与), ||??(逻辑或), !(逻辑非) 。支持短路求值
  • 三元运算符condition ? expr1 : expr2
  • 可选链运算符?
  • 扩展运算符...
// 1. 算术运算符:+(加)、-(减)、*(乘)、/(除)、%(取模)、**(指数幂,ES6+新增)
let a = 10, b = 3;
console.log(a + b); // 输出:13(加法)
console.log(a - b); // 输出:7(减法)
console.log(a * b); // 输出:30(乘法)
console.log(a / b); // 输出:3.333...(除法)
console.log(a % b); // 输出:1(取模,余数)
console.log(a ** b); // 输出:1000(指数幂,10^3,ES6+新增,替代Math.pow)

// 2. 赋值运算符:=、+=、-=、*=、/=、%=、**=(ES6+新增)
let c = 5;
c += 3; // 等价于 c = c + 3
console.log(c); // 输出:8
c **= 2; // 等价于 c = c ** 2(ES6+新增)
console.log(c); // 输出:64

// 3. 比较运算符:<、>、<=、>=、==、===、!=、!==(无新增,沿用规范)
let d = 10, e = "10";
console.log(d > 5); // 输出:true
console.log(d == e); // 输出:true(隐式转换)
console.log(d === e); // 输出:false(严格相等,推荐)
console.log(d != e); // 输出:false
console.log(d !== e); // 输出:true(推荐)

// 4. 逻辑运算符:&&(逻辑与)、||(逻辑或)、!(逻辑非)(支持短路求值)
// ES6+新增:空值合并运算符??(解决||的假值误判问题)
let f = true, g = false;
console.log(f && g); // 输出:false(逻辑与,都为真才真)
console.log(f || g); // 输出:true(逻辑或,一个为真就真)
console.log(!f); // 输出:false(逻辑非,取反)

// 短路求值(实用技巧)
let username = "";
let defaultName = username || "默认用户"; // 若username为假值,取默认值(存在假值误判)
let defaultName2 = username ?? "默认用户"; // ES6+新增,仅当username为null/undefined时取默认值
console.log(defaultName); // 输出:"默认用户"
console.log(defaultName2); // 输出:""(空字符串不是null/undefined,不替换)

// 5. ES6+新增:可选链运算符?.(安全访问对象属性,避免报错)
let userInfo = { name: "橘朵" };
// console.log(userInfo.age.name); // 报错:Cannot read properties of undefined
console.log(userInfo.age?.name); // 输出:undefined(不会报错,安全访问)

// 6. 三元运算符:condition ? expr1 : expr2(条件判断,简化if语句)
let score = 85;
let result = score >= 60 ? "及格" : "不及格";
console.log(result); // 输出:"及格"

// 7. ES6+新增:扩展运算符...(用于数组/对象的展开和合并)
let arr1 = [1, 2, 3];
let arr2 = [4, 5, ...arr1]; // 展开arr1
console.log(arr2); // 输出:[4,5,1,2,3]

let obj1 = { name: "橘朵" };
let obj2 = { age: 25, ...obj1 }; // 展开obj1
console.log(obj2); // 输出:{age:25, name:"橘朵"}

小结:

  1. 算术运算符中,+既可以做加法,也可以做字符串拼接,新手要注意类型(数字+数字=加法,数字+字符串=拼接);
  2. 比较运算符中,推荐使用===!==,避免隐式转换;
  3. 逻辑运算符支持短路求值,ES6+新增的???.运算符更实用,??避免假值误判,?.避免对象属性不存在报错;
  4. 三元运算符适合简单的二选一判断,复杂逻辑还是用if语句,避免代码可读性变差;
  5. 扩展运算符...常用于数组/对象的合并、传递参数,是当前主流用法。

1.7 流程控制

流程控制用于控制程序的执行顺序,包括条件语句(根据条件执行不同代码)和循环语句(重复执行代码),是编写复杂逻辑的基础。ES6+新增了for...of循环、for await...of(异步循环,新手暂不涉及),优化了流程控制的便捷性,当前主流用法以ES6+为准。

  1. 条件语句
  • if...elseelse if:用于基于不同条件执行不同代码分支。
  • switch:用于多条件分支选择,注意每个 case后通常要加 break防止穿透。
  1. 循环语句
  • for:传统计数循环。
  • while:当条件为真时循环。
  • do...while:至少执行一次循环体,再判断条件。
  • for...of:遍历可迭代对象(如数组、字符串、Map、Set)的值。
  • for...in:遍历对象的可枚举属性(通常包括原型链上的),不推荐使用 for...in 遍历数组

1.7.1 条件语句(if...else/else if、switch)

根据不同的条件执行不同的代码。

// 1. if...else/else if(适用于少量条件分支)
let score = 85;
if (score >= 90) {
    console.log("优秀");
} else if (score >= 80) {
    console.log("良好");
} else if (score >= 60) {
    console.log("及格");
} else {
    console.log("不及格");
}
// 输出:"良好"

// 2. switch(适用于多条件分支,注意break防止穿透)
let fruit = "苹果";
switch (fruit) {
    case "苹果":
        console.log("苹果单价:5元/斤");
        break; // 必须加break,否则会穿透到下一个case
    case "香蕉":
        console.log("香蕉单价:3元/斤");
        break;
    case "橙子":
        console.log("橙子单价:4元/斤");
        break;
    default:
        console.log("暂无此水果价格"); // 所有case不匹配时执行
}

1.7.2 循环语句(for、while、do...while、for...of、for...in)

重复执行一段代码,适合处理批量数据(比如数组遍历)。

// 1. for循环(传统计数循环,适用于已知循环次数)
console.log("for循环:");
for (let i = 0; i < 5; i++) { // 初始化→条件判断→循环体→更新变量
    console.log(i); // 输出:0,1,2,3,4
}

// 2. while循环(适用于未知循环次数,条件为真时循环)
console.log("while循环:");
let j = 0;
while (j < 5) {
    console.log(j); // 输出:0,1,2,3,4
    j++;
}

// 3. do...while循环(至少执行一次循环体,再判断条件)
console.log("do...while循环:");
let k = 0;
do {
    console.log(k); // 输出:0,1,2,3,4
    k++;
} while (k < 5);

// 4. for...of循环(遍历可迭代对象,如数组、字符串,推荐)
console.log("for...of循环(数组):");
let fruits = ["苹果", "香蕉", "橙子"];
for (let fruit of fruits) {
    console.log(fruit); // 输出:"苹果","香蕉","橙子"
}

console.log("for...of循环(字符串):");
let str = "hello";
for (let char of str) {
    console.log(char); // 输出:"h","e","l","l","o"
}

// 5. for...in循环(遍历对象可枚举属性,不推荐遍历数组)
console.log("for...in循环(对象):");
let user = { name: "橘朵", age: 25 };
for (let key in user) {
    console.log(`${key}: ${user[key]}`); // 输出:"name: 橘朵", "age: 25"
}

// 不推荐用for...in遍历数组(会遍历原型链上的属性)
let arr = [1, 2, 3];
Array.prototype.test = "测试"; // 给数组原型添加属性
console.log("for...in循环(数组,不推荐):");
for (let index in arr) {
    console.log(arr[index]); // 输出:1,2,3,"测试"(意外遍历到test属性)
}

1.8 函数定义(JS的“功能模块”,一等公民)

函数是 JavaScript 的一等公民,可以像变量一样被传递、赋值、作为参数,是代码复用和组织的核心。JS有三种常见的函数定义方式:函数声明、函数表达式、箭头函数。

  • 函数声明function functionName(parameters) { // 函数体 }
  • 函数表达式const functionName = function(parameters) { // 函数体 }
  • 箭头函数const functionName = (parameters) => { // 函数体 }const functionName = parameter => expression
// 1. 函数声明(function关键字,有函数提升,可在声明前调用)
console.log(add(2, 3)); // 输出:5(声明前调用有效)
function add(a, b) {
    return a + b;
}

// 2. 函数表达式(赋值给变量,无函数提升,不能在声明前调用)
// console.log(subtract(5, 2)); // 报错:subtract未定义(声明前调用无效)
const subtract = function(a, b) {
    return a - b;
};
console.log(subtract(5, 2)); // 输出:3

// 3. 箭头函数(ES6特性,简洁,无this绑定,无arguments)
const multiply = (a, b) => {
    return a * b;
};
console.log(multiply(4, 5)); // 输出:20

// 简化写法:只有一个参数时,括号可省;函数体只有一条return语句时,花括号和return可省
const double = num => num * 2;
console.log(double(6)); // 输出:12

// 箭头函数无this绑定(新手注意:不适合作为对象方法)
const user = {
    name: "橘朵",
    sayHello: () => {
        console.log(`你好,我是${this.name}`); // this指向全局,不是user对象
    }
};
user.sayHello(); // 输出:"你好,我是undefined"(不推荐这样用)

1.9 对象与数组

对象和数组是JS中最基础、最常用的引用类型,贯穿整个前端开发,新手必须熟练掌握他们的核心定义和基础用法,为后续复杂逻辑开发打下基础。

  • 对象:是键值对的集合。可以使用字面量 {}创建,属性键可以是字符串或 Symbol,值可以是任何类型。对象的属性可动态添加、修改和删除,是存储复杂数据的核心载体。
  • 数组:是有序的数据集合。可以使用字面量 []创建,数组元素可以是任何类型,并且数组有很多内置方法(如 push, pop, map, filter, reduce等),这些方法能高效处理数组数据,简化开发流程。
// 对象的基础用法
const person = { // 字面量创建对象
    name: "橘朵", // 字符串键
    [Symbol("id")]: 1001, // Symbol键(避免属性名冲突)
    age: 25, // 数字值
    isStudent: false, // 布尔值
    hobbies: ["看书", "敲代码"], // 数组值(引用类型)
    sayHi: function() { // 函数值(引用类型)
        console.log(`你好,我是${this.name}`);
    }
};
// 访问属性
console.log(person.name); // 输出:橘朵
console.log(person[Symbol("id")]); // 输出:undefined(Symbol键需用变量存储访问)
// 修改属性
person.age = 26;
// 添加属性
person.gender = "女";
// 删除属性
delete person.isStudent;
person.sayHi(); // 输出:你好,我是橘朵

// 数组的基础用法+常用内置方法
const fruits = ["苹果", "香蕉", "橙子"]; // 字面量创建数组
// 访问元素
console.log(fruits[0]); // 输出:苹果
// 内置方法使用
fruits.push("葡萄"); // 新增元素,输出:4(数组长度)
fruits.pop(); // 删除最后一个元素,输出:葡萄
const newFruits = fruits.map(fruit => fruit + "(新鲜)"); // 映射新数组
console.log(newFruits); // 输出:["苹果(新鲜)", "香蕉(新鲜)", "橙子(新鲜)"]
const filterFruits = fruits.filter(fruit => fruit !== "香蕉"); // 筛选数组
console.log(filterFruits); // 输出:["苹果", "橙子"]

2 一些小建议

  1. 始终使用 let const,避免使用 var

  2. 使用严格相等运算符 === !==

  3. 总是使用分号 ; 明确结束语句。

  4. 即使代码块只有一条语句,也使用花括号 {}

  5. 为变量和函数使用描述性的名称并遵循命名惯例。

  6. 合理使用注释解释复杂的逻辑。

  7. 注意隐式类型转换可能带来的陷阱。

  8. 考虑使用严格模式

3 结尾总结

这篇文章,我们吃透了JavaScript的核心语言规则,从基本语法、命名规则,到变量常量、数据类型、运算符、流程控制、函数定义、对象与数组,覆盖了JS语法的“地基”知识点。

总结一下核心要点:

  • 基础语法:区分大小写、显加分号、用花括号包裹代码块、合理写注释、启用严格模式;
  • 命名规则:见名知意,变量/函数用驼峰命名,类用帕斯卡命名,常量全大写;
  • 变量常量:优先用const,其次用let,放弃varconst声明的对象/数组可修改内部属性;
  • 数据类型:分为原始类型(7种:UndefinedNullBooleanNumberBigIntStringSymbol,值不可变)和引用类型(本质是对象,如ObjectArrayFunction等,值可变),核心区别是赋值时传递值本身还是引用;
  • 类型转换:优先用显式转换(Number()String()Boolean()),避免隐式转换坑,判断相等用===
  • 运算符:掌握常用算术、赋值、逻辑运算符,善用短路求值和ES6新增运算符,避免+号拼接陷阱;
  • 流程控制:if...else适合少量分支,switch需加break防穿透;遍历数组优先用for...of,避免for...in
  • 函数定义:三种方式各有场景,箭头函数无this绑定,不适合作为对象方法。
  • 对象与数组:对象是键值对的集合,用{}创建;数组是是有序的数据集合,用[]创建。

下一篇文章,我们将聚焦「关键字与保留字」——这是JS语法的“红线”。