JS 核心语法:运算符流程控制、函数、数组与对象实战详解

写在前面

本文覆盖四块核心语法:

  1. 运算符 :算术、比较、逻辑,重点讲 == / === 的区别与判断规则;
  2. 流程控制 :分支与循环,以及 for...of / for...in 该用谁;
  3. 函数:定义方式、参数处理、作用域与闭包;
  4. 数组与对象 :这是重头戏------数组高频 API 逐一梳理,明确区分哪些会修改原数组、哪些返回新数组,配合业务代码示例。

全篇以"能直接写业务代码"为目标,示例尽量贴近真实场景(购物车、去重、校验、筛选等)。


一、运算符

1.1 算术运算符

+ - * / %(取余)**(幂)++ --

js 复制代码
console.log(7 % 3);     // 1(取余)
console.log(2 ** 10);   // 1024(幂)
console.log(5 + "5");   // "55"(+ 号遇字符串变拼接)
console.log(5 - "2");   // 3(- 号强制转数值)

1.2 比较运算符与 == / === 的本质区别

这是面试与业务排错的高频点。三条铁律:

  • ===(严格相等):类型相同且值相同 才为 true,不转换,推荐默认使用
  • ==(宽松相等):比较前做隐式类型转换,规则复杂、易出错;
  • Object.is:语义与 === 基本一致,但能区分 NaN±0
js 复制代码
1 === "1";        // false(类型不同)
1 == "1";         // true(字符串转数字)

null === undefined;  // false
null == undefined;   // true(== 的经典特例)

0 === -0;         // true
Object.is(0, -0); // false

NaN === NaN;      // false
Object.is(NaN, NaN); // true

== 的隐式转换核心规则(记几条常见的即可):

表达式 结果 原因
1 == "1" true 字符串转数字
null == undefined true 规范特例
0 == "" true 空字符串转 0
0 == false true 布尔转数字
[] == 0 true 空数组转 "" → 0
[] == ![] true ![] 为 false → 0,左 [] 为 0

结论:业务代码一律用 === 。遇到 == 先怀疑是历史包袱。

1.3 逻辑运算符与短路

js 复制代码
// &&:左侧为假则返回左侧,否则返回右侧
console.log(0 && 100);     // 0
console.log(1 && 100);     // 100

// ||:左侧为真则返回左侧,否则返回右侧
console.log(0 || 100);     // 100
console.log(1 || 100);     // 1

// !:取反
console.log(!0);           // true

短路求值是常用技巧:左侧结果已能决定整个表达式时,右侧不再执行。

js 复制代码
// 常用场景:函数参数兜底
function greet(name) {
  name = name || "陌生人";   // name 为空时给默认值
  console.log("你好," + name);
}

|| 有陷阱:0"" 也是假值,会被错误替换。ES2020 引入 ??(空值合并) :只在左侧为 nullundefined 时才取右侧:

js 复制代码
const a = 0 ?? 100;    // 0(0 不是 null/undefined,保留)
const b = null ?? 100; // 100
const c = "" ?? 100;   // ""(空字符串保留)

配套的逻辑赋值运算符

js 复制代码
x ||= 10;    // 等价 x = x || 10
x &&= 10;    // 等价 x = x && 10
x ??= 10;    // 等价 x = x ?? 10(仅在 null/undefined 时赋值)

1.4 其他常用运算符

js 复制代码
// 三元
const status = age >= 18 ? "成年" : "未成年";

// 一元 + 转数值
const num = +"42";          // 42

// typeof
typeof undefined;           // "undefined"

// instanceof:检查原型链
[] instanceof Array;        // true

// in:检查属性是否存在
"name" in { name: "Tom" };  // true

// delete:删除属性
delete obj.name;

二、流程控制

2.1 分支

if / else if / else------最常用:

js 复制代码
function level(score) {
  if (score >= 90) return "优秀";
  if (score >= 60) return "及格";
  return "不及格";
}

switch ------匹配多个值时更清晰(注意 break 防穿透):

js 复制代码
function weekday(n) {
  switch (n) {
    case 1: return "周一";
    case 2: return "周二";
    default: return "其他";
  }
}

三元------只适合简单的二选一,嵌套会降低可读性。

2.2 循环

循环 适用场景 示例
for 知道次数、需索引 for (let i = 0; i < 5; i++)
while 不确定次数、条件驱动 轮询、输入校验
do...while 至少执行一次 至少提示一次
for...of 遍历可迭代对象的值(数组/字符串/Set) for (const item of arr)
for...in 遍历对象的(含原型链,慎用) for (const key in obj)
js 复制代码
// for...of:遍历数组值,推荐
const arr = [10, 20, 30];
for (const item of arr) {
  console.log(item);          // 10 20 30
}

// for...in:遍历对象键,数组上慎用
const user = { name: "Tom", age: 18 };
for (const key in user) {
  console.log(key, user[key]);
}

// 提前退出
for (const item of arr) {
  if (item === 20) break;     // 终止整个循环
  // continue;                // 跳过本次
}

for...in 会遍历原型链上可枚举的属性 ,且遍历顺序不保证,遍历数组应改用 for...of 或数组方法。


三、函数

3.1 三种定义方式

js 复制代码
// ① 函数声明:有提升(可在定义前调用)
function add(a, b) { return a + b; }

// ② 函数表达式:无提升
const sub = function (a, b) { return a - b; };

// ③ 箭头函数:ES6,简写 + 词法 this
const mul = (a, b) => a * b;
const log = () => console.log("hi");
对比 函数声明 函数表达式 箭头函数
提升
this 动态(调用时决定) 动态 词法(定义时决定)
arguments
简写 - - 单表达式可省 {}return

箭头函数的 this 继承自外层作用域------这是它与普通函数最本质的区别,回调场景尤其重要:

js 复制代码
const obj = {
  name: "Tom",
  // 普通函数:this 指向调用者 obj
  sayName: function () {
    console.log(this.name);            // "Tom"
  },
  // 箭头函数:this 是定义时的外层(此处为全局)
  sayNameArrow: () => {
    console.log(this.name);            // undefined
  },
};

3.2 参数处理

js 复制代码
// 默认参数:ES6
function greet(name = "陌生人", age = 0) {
  return `${name}(${age})`;
}

// 实参多于形参:多余的被忽略
// 实参少于形参:缺失的为 undefined

// rest 参数:收集剩余实参为数组
function sum(...nums) {
  return nums.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3);    // 6

// 旧方案 arguments(箭头函数没有)
function oldSum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) total += arguments[i];
  return total;
}

3.3 作用域与闭包

作用域 :全局、函数、块级(let/const)。变量查找沿作用域链由内向外。

js 复制代码
let x = 10;                 // 全局
function outer() {
  let y = 20;               // outer 作用域
  function inner() {
    let z = 30;             // inner 作用域
    console.log(x + y + z); // 60:逐层向上查找
  }
  inner();
}

闭包:函数记住并访问其定义时的作用域,即使外层函数已返回。业务中常用于"私有变量"与"缓存":

js 复制代码
function createCounter() {
  let count = 0;                       // count 被闭包捕获,外部无法直接访问
  return function () {
    count += 1;
    return count;
  };
}
const counter = createCounter();
counter();   // 1
counter();   // 2
// count 外部不可见,但 counter 能持续修改它

IIFE(立即执行函数):定义后立刻执行,用于隔离作用域:

js 复制代码
const result = (function (a, b) {
  return a + b;
})(1, 2);        // 3

四、数组高频 API(核心:是否修改原数组)

这是本篇最有价值的部分。先用一句话记住:push/pop/shift/unshift/splice/sort/reverse 修改原数组;map/filter/reduce/slice/concat 返回新数组。

4.1 修改原数组的方法(Mutating)

js 复制代码
const arr = [1, 2, 3];

arr.push(4);        // 尾部追加 → [1,2,3,4],返回新长度 4
arr.pop();          // 删除尾部 → [1,2,3],返回删除的元素 3
arr.unshift(0);     // 头部插入 → [0,1,2,3],返回新长度
arr.shift();        // 删除头部 → [1,2,3],返回删除的元素 0

arr.splice(1, 1);   // 从索引 1 删 1 个 → [1,3](最灵活)

const b = [3, 1, 2];
b.sort();           // 默认按字符串排序 → [1,2,3]
b.reverse();        // 反转 → [3,2,1]

arr.fill(0);        // 全部填充 0 → [0,0,0]

sort 默认按字符串 排序,数字需传比较函数:arr.sort((a, b) => a - b)

4.2 返回新数组的方法(Non-mutating)------ 业务主力

js 复制代码
const arr = [1, 2, 3, 4, 5];

// map:映射,长度不变
arr.map(x => x * 10);           // [10,20,30,40,50]

// filter:筛选,长度可变
arr.filter(x => x > 2);         // [3,4,5]

// slice:切片(不含结束索引),最常用来"复制"数组
arr.slice(1, 3);                // [2,3]
arr.slice();                    // [1,2,3,4,5] 浅拷贝

// concat:拼接
arr.concat([6, 7]);             // [1,2,3,4,5,6,7]

// join:转字符串
arr.join("-");                  // "1-2-3-4-5"

// flat:拍平嵌套数组
[1, [2, [3]]].flat(2);          // [1,2,3]

// 查找
arr.find(x => x > 3);           // 4(返回第一个满足的值)
arr.findIndex(x => x > 3);      // 3(返回索引)
arr.includes(3);                // true
arr.indexOf(3);                 // 2

// 判断
arr.some(x => x > 4);           // true(任一满足)
arr.every(x => x > 0);          // true(全部满足)

// reduce:归并
arr.reduce((acc, x) => acc + x, 0);   // 15(求和)

map 不修改原数组------这是业务中最容易踩的坑:

js 复制代码
const prices = [10, 20, 30];
prices.map(p => p * 2);         // 结果被丢弃,原数组不变
const doubled = prices.map(p => p * 2);  // 必须接收返回值

4.3 遍历:forEach

js 复制代码
arr.forEach((item, index) => {
  console.log(index, item);
});
// 无法 break/continue;需要提前退出用 for...of 或 some

4.4 原地 vs 非原地速查表

方法 修改原数组? 返回
push / pop / shift / unshift 新长度 / 删除的元素
splice 删除的元素数组
sort / reverse 原数组本身
fill 原数组本身
map / filter 新数组
slice / concat 新数组
reduce 任意值
find / findIndex / includes 值 / 索引 / 布尔
some / every 布尔
join / flat 字符串 / 新数组
forEach ❌(只遍历)

五、对象操作

5.1 增删改查

js 复制代码
const user = { name: "Tom", age: 18 };

user.age = 19;             // 改
user.email = "t@e.com";    // 增
delete user.email;         // 删

// 方括号访问:键是动态变量时
const key = "name";
user[key];                 // "Tom"

// 动态键创建
const dynamic = "role";
const obj = { [dynamic]: "admin" };   // { role: "admin" }

5.2 遍历对象

js 复制代码
const user = { name: "Tom", age: 18 };

Object.keys(user);          // ["name","age"]
Object.values(user);        // ["Tom",18]
Object.entries(user);       // [["name","Tom"],["age",18]]

for (const [key, value] of Object.entries(user)) {
  console.log(key, value);
}

5.3 合并与拷贝(浅)

js 复制代码
// Object.assign:把源对象属性复制到目标
const base = { a: 1, b: 2 };
const extra = { b: 99, c: 3 };
Object.assign({}, base, extra);   // { a:1, b:99, c:3 }

// 展开运算符:更常用
const merged = { ...base, ...extra };   // 同上
const copy = { ...base };               // 浅拷贝

两者都是浅拷贝:嵌套对象仍共享。

5.4 解构赋值

js 复制代码
// 对象解构:提取 + 重命名 + 默认值
const { name, age, city = "北京" } = user;
const { name: userName } = user;     // 重命名为 userName

// 数组解构 + 剩余
const [first, second, ...rest] = [1, 2, 3, 4];
// first=1, second=2, rest=[3,4]

// 函数参数解构
function printUser({ name, age }) {
  console.log(name, age);
}
printUser(user);

5.5 判断属性

js 复制代码
"name" in user;            // true(含原型链)
Object.hasOwn(user, "name"); // true(只查自身,ES2022 推荐)
user.hasOwnProperty("name"); // true(旧写法)

5.6 Object.freeze 与 Set/Map(补充)

js 复制代码
const frozen = Object.freeze({ a: 1 });
// frozen.a = 2;  // 严格模式下报错

const set = new Set([1, 2, 2, 3]);   // Set 自动去重
[...set];                            // [1,2,3]

const map = new Map([["k", "v"]]);
map.get("k");                        // "v"

六、实战:业务代码综合示例

实战 1:购物车合计与数量统计

js 复制代码
const cart = [
  { id: 1, name: "手机", price: 2999, count: 1 },
  { id: 2, name: "耳机", price: 399, count: 2 },
  { id: 3, name: "充电器", price: 99, count: 3 },
];

// 合计金额:reduce
const total = cart.reduce((sum, item) => sum + item.price * item.count, 0);

// 商品总件数
const totalCount = cart.reduce((sum, item) => sum + item.count, 0);

// 满 3000 减 100 优惠
const payable = total >= 3000 ? total - 100 : total;

console.log(`合计 ${total} 元,共 ${totalCount} 件,应付 ${payable} 元`);

实战 2:数组去重与频次统计

js 复制代码
const nums = [1, 2, 2, 3, 3, 3, 4];

// 去重方案一:Set
const unique = [...new Set(nums)];        // [1,2,3,4]

// 去重方案二:filter + indexOf(保留首次出现)
const unique2 = nums.filter((v, i) => nums.indexOf(v) === i);

// 统计每个数字出现次数
const freq = nums.reduce((acc, v) => {
  acc[v] = (acc[v] || 0) + 1;
  return acc;
}, {});
// {1:1, 2:2, 3:3, 4:1}

实战 3:表单校验(短路 + every)

js 复制代码
const form = {
  username: "tom",
  password: "123456",
  email: "t@e.com",
};

const errors = [];

// 逐项校验:短路避免空值继续判断
if (!form.username) errors.push("用户名必填");
if (form.password.length < 6) errors.push("密码至少 6 位");
if (!form.email.includes("@")) errors.push("邮箱格式不正确");

// 或:用 every 批量校验
const valid = ["username", "password", "email"].every((key) =>
  Boolean(form[key])
);

console.log(errors.length ? errors : "校验通过");

实战 4:列表筛选 + 排序 + 分页

js 复制代码
const products = [
  { name: "A", price: 100, inStock: true },
  { name: "B", price: 50, inStock: false },
  { name: "C", price: 200, inStock: true },
  { name: "D", price: 80, inStock: true },
];

// 链式调用:都不修改原数组
const pageSize = 2;
const page = 1;

const result = products
  .filter(p => p.inStock)              // 筛选有货
  .sort((a, b) => a.price - b.price)   // 按价格升序
  .slice((page - 1) * pageSize, page * pageSize);  // 分页

console.log(result);   // 第一页 2 条

实战 5:对象深合并(递归)

js 复制代码
function deepMerge(target, source) {
  for (const key of Object.keys(source)) {
    const sv = source[key];
    if (
      sv && typeof sv === "object" && !Array.isArray(sv) &&
      target[key] && typeof target[key] === "object"
    ) {
      deepMerge(target[key], sv);      // 两边都是对象则递归
    } else {
      target[key] = sv;
    }
  }
  return target;
}

const defaults = { theme: { color: "blue", size: 12 } };
const custom = { theme: { color: "red" } };
deepMerge(defaults, custom);
// { theme: { color: "red", size: 12 } }   ------ size 被保留

七、高频误区清单

| 误区 | 正解 |
|-----------------------------|-----------------------------|-------------------|-------------------------------|
| 1 == "1"true 就用 == | 一律用 ===== 隐式转换是坑源 |
| 用 ` | | 兜底0/""` 被误替换 | 用 ??(只兜 null/undefined) |
| arr.map(...) 后没接收返回值 | map 返回新数组,必须接住 |
| sort() 直接对数字排序 | 数字须传 (a, b) => a - b |
| for...in 遍历数组 | 用 for...of 或数组方法 |
| 认为箭头函数有 arguments | 箭头函数无 arguments,用 rest 参数 |
| Object.assign 是深拷贝 | 是浅拷贝,嵌套对象共享 |
| 修改了 splice 得到的新数组 | splice 修改原数组,返回的是被删元素 |


八、总结

用一张表收束本篇核心:

主题 一句话记住
比较 默认 ===,兜底 ??,杜绝裸 ==
分支 if 打天下,多值用 switch,简单二选一用三元
循环 数组用 for...of,对象用 Object.entries,别用 for...in 遍历数组
函数 箭头函数无自己的 this/arguments,回调优先箭头
数组 改原数组:push/pop/shift/unshift/splice/sort/reverse;返回新数组:map/filter/reduce/slice/concat
对象 增删查走 Object.keys/entries,拷贝用展开,深合并用递归

三条实践心法收尾:

  1. 先想"会不会改原数组" :写 map/filter 时记得接住返回值;
  2. 优先不可变风格:用返回新数组的方法链式处理数据,状态更可控;
  3. 短路与解构提效率&&/||/?? 精简判断,解构让取值更清晰。
相关推荐
七牛开发者12 分钟前
拆解 dsh:Turn 与 Step 如何组织 Agent 主循环
前端·javascript·人工智能
空想兔13 分钟前
前端网络请求的前世今生:从 Ajax、XHR 到 Fetch 和 Axios
javascript
梨想橙汁13 分钟前
JavaScript 零基础入门:引入方式、变量与数据类型,吃透原始与引用类型
前端·javascript
不可能片场17 分钟前
puppeteer 调试实录:CDP 双层响应让取值永远 None
前端·electron
咖啡无伴侣18 分钟前
2. 从零搭建企业级 Monorepo 工程化模板:ESLint 10 (基础骨架)+ Prettier 配置与避坑指南
前端·架构
光影少年22 分钟前
react navite实现全局弹窗、Toast 组件
前端·react native·react.js
WebInfra26 分钟前
Rslib 1.0 正式发布:面向多场景的 JavaScript 库开发工具
前端·javascript·github
Tongsr1 小时前
别只混淆代码:用 Kaleido 加固整个 Android Release AAB
前端·算法·github
郭邯1 小时前
从零到一:我用 AI 写了一个贷款计算器,顺便把等额本息公式彻底搞懂了
前端