万字上文!从零到精通,开启你的Javascript动态交互能力之路

机智流 2025-08-29 23:15

资讯配图

作者:李剑锋

基本语法

变量与常量

一、变量

变量是用于存储数据的容器,在程序运行过程中,其值可以改变

在python中,变量与常量其实没有本质上的区别,我们都可以通过赋值的方式简单的创建变量和常量。

x = 10
x = 20 
print(x)
# 此时输出为20

PI = 3.14
PI = 3.1415926
print(PI)
# 此时输出为3.1415926

但是在JavaScript里,变量与常量却是有明显不同。通常来说我们在JavaScript定义第一个变量的时候,我们都是需要通过一个关键字(let/var)来进行定义,比如说:

let x = 20
console.log(x)
// 此时输出为20

var y = 30
console.log(y)
// 此时输出30

letvar有什么区别呢,首先其中最显著的区别就是关键字let的变量只能定义一次,重复定义同样名称的变量就会报错。

let x = 20
let x = 30
console.log(x)
// 显示报错

但其实这并不意味着let不能更新其变量值,我们可以通过下面的方式实现更新:

let x = 20
x = 30
console.log(x)
// 输出为 30

var是允许重复进行变量定义的。

var y = 20
var y = 30
console.log(y)
// 输出为 30

虽然varlet都能够定义变量,但是我们最常用的方式还是使用let来进行变量的定义,这是因为var的变量定义更像是python里的全局变量,其作用域是整个代码里的,即便是在函数里面定义,函数外同样会受到影响,因此很容易造成各种各样的Bug。

因此,let 比 var 更安全、更合理,作用域清晰、不会提升、不允许重复声明,能有效减少 bug。在现代 JavaScript 开发中,let 已完全替代 var 。

二、常量

常量是值不能改变的变量,一旦被赋值后,在整个生命周期中保持不变,比如重力加速度大约就是9.8m/s,圆周率是3.14这样。

对于常量的定义,我们需要通过关键字(const)来进行定义,比如说下面的例子:

const PI = 3.14;
console.log(PI)
// 程序输出3.14

但是常量相比于变量有一个最大的不同,那就是常量无法发生改变:

const PI = 3.14;
PI = 3.1415926
console.log(PI)
// 此时程序会出现报错

所以在所有不会重新赋值的地方优先使用const

在 JavaScript 中,如果不显式使用letvarconst声明变量,变量会被隐式地创建为全局变量(在浏览器环境中会成为window对象的属性)。这种方式称为隐式全局变量(Implicit Global Variable)。但这种方式可能有以下问题:

  1. 污染全局作用域:可能与其他代码产生命名冲突。
  2. 难以调试:变量来源不明确,尤其在大型项目中。
  3. 没有块级作用域:隐式全局变量会贯穿整个上下文,不会受iffor等块的限制。
因此建议始终使用let(块级作用域)、const(常量)或var(函数作用域)显式声明变量,避免隐式全局变量。现代 JavaScript(ES6+)推荐优先使用letconst,以减少作用域相关的 Bug。

数据类型

在 JavaScript 中,数据类型是非常重要的基础知识,掌握它们是理解语言运行机制、变量行为和内存管理的前提。下面我们就来讲述一下JavaScript和Python中场景的数据类型吧。

Python 常见数据类型详解

在 Python 中,数据类型决定了变量能存储什么类型的值。Python 是动态类型语言,每个变量在运行时自动绑定其类型。我们通常将类型分为基础类型(不可变)和容器类型(可变或不可变)两大类。

一、基础数据类型(不可变)

这些类型的值一旦创建就无法修改,是最常见的数据类型。

1. 整数(int)

表示整数值,包括正数、负数和零。

age = 25

2. 浮点数(float)

表示小数或科学计数法。

pi = 3.14159
temp = -12.5

3. 布尔值(bool)

只有两个值:True 和 False,用于逻辑判断。

is_login = True

4. 字符串(str)

不可变的文本类型,可以使用单引号、双引号或三引号定义。

name = "小明"
quote = 'Hello, world!'
text = '''多行
字符串'''

二、容器数据类型(可变或不可变)

1. 列表(list)✅ 可变

有序集合,可存储任意类型元素,支持增删改查。

fruits = ["apple""banana""orange"]
fruits.append("grape")
# 此时的fruits列表变成 ["apple", "banana", "orange", "grape"]

2. 元组(tuple)❌ 不可变

有序集合,不可修改,适合存储固定结构。

position = (100200)

3. 字典(dict)✅ 可变

键值对集合,键唯一,值可变。

person = {"name""小王""age"18}
person["gender"] = "male"
# 此时的person字典会变成 {"name": "小王", "age": 18, "gender" : "male"}

4. 集合(set)✅ 可变

集合(set)是 Python 内置的数据结构之一,用来存储一组无序不重复 的元素。

  • 无序:集合中的元素没有固定顺序。
  • 不重复:集合中不允许出现重复元素。
  • 可变:集合本身可以修改(增删元素),但集合中的元素必须是“可哈希的”(即不可变,比如数字、字符串、元组)。
tags = {"python""AI""ML"}
tags.add("deep learning")
# 此时的集合tags变成 {"python", "AI", "ML", "deep learning"}

5. 冻结集合(frozenset)❌ 不可变

不可变集合,常用于字典键或集合元素。

fs = frozenset([123])

JavaScript 的数据类型

在 JavaScript 中,数据类型是非常重要的基础知识,掌握它们是理解语言运行机制、变量行为和内存管理的前提。本资料将从基础到进阶,系统讲解 JavaScript 的各种数据类型、定义方式、特性、应用场景以及常见误区。

一、基本数据类型(Primitive Types)

基本数据类型在内存中是按值存储的,不可再分解。

1. 字符串(String)

定义:用于表示文本,由单引号、双引号或反引号包裹的字符序列。

let name = "李剑锋";
let greeting = `Hello, ${name}`; // 模板字符串
console.log(greeting) 
// 此时输出的就是 Hello, 李剑锋

特点

  • 字符串是不可变的(每次修改都会返回新字符串)
  • 可使用模板字符串(反引号)插入变量或表达式
  • 提供大量字符串操作方法: lengthslice()toUpperCase()replace() 等
  • 与python相比,其在单引号与双引号上的用法是一致的,但是在多行字符串的表达上,python用的是三引号的表达方式(’’’,””” ),而JavaScript使用的是反引号的表达方式(``` )。
  • 并且对于像是f-string这种在字符串里插入变量的方式也只能在反引号中才能够实现,在python里单引号双引号中其实也都是可以的,这也是两者在基本语法上最根本的不同。

2. 数值(Number)

定义:表示整数、浮点数、NaN、Infinity。

let balance = -100;
let weight = 60.5;
let infinity = Infinity;
let notANumber = NaN;

特点

  • 和python划分整数和浮点数相比,JS里整数和浮点数都属于数值类型。
  • NaN 表示非法数值,比如在用字符串减数值的时候就会显示,又比如让0/0或者对-1开平方根这种非法情况的时候才会显示。我们可通过 Number.isNaN() 判断是否为NaN
  • Infinity 表示无穷大,JS里最大的数值约等于 1.79 × 10^308 ,假如结果超过这个数字,或者说1/0 这种情况才可能实现。

3. 布尔(Boolean)

定义:只包含两个值:true 和 false,用于逻辑判断。

let tv = true;
let isAdult = false;

特点

  • 这个和python其实非常类似,通常作为条件判断的结果,只不过python里的布尔值是大写的T,而JS里的是小写的t。
  • 可通过比较表达式或逻辑运算符获得。

4. 空值(Null)

定义:表示"无对象",是一个空值。

在程序中,如果某个变量没有具体值,又不能报错,通常就用“空值”来表示。这是编程语言中对“缺失、未知、尚未赋值”的一种统一处理机制。

let user = null;

特点

  • 常用于手动清空对象或初始化占位值。
let user = null;
// 表示当前没有用户对象,但准备好将来赋值

let data = { name"Tom" };
data = null;  // 释放引用,利于垃圾回收(GC)
  • 这个其实和python的None其实有点类似,并且在作为布尔判定的时候都是返回False的。

5. 未定义(Undefined)

定义:声明变量未赋值时的默认值。

let x;
console.log(x); // undefined

特点

  • JavaScript 默认初始化值,可用于判断变量是否被初始化。常见的场景是变量没赋值、函数没有返回值以及访问对象中不存在的属性时会出现。 和python相比,JS 函数没有返回值时是 undefined,而 Python 函数默认返回 None
  • 这个在python里是没有的,python里的变量在创建的时候就一定要被赋值,即便是空的值None其也是要赋值上去的,所以不会有undefined的存在。

6. 大整数(BigInt)

定义:用于表示超过 Number 最大安全值(2^53 - 1)的大整数

let big = 1234567890123456789012345678901234567890n;
console.log(typeof big); // "bigint"

特点

  • ES2020 引入的一种新的原始数据类型,专门用于处理超大整数
  • 在 JavaScript 中,普通的 Number 类型只能安全表示范围为 ±(2^53 - 1) 以内的整数,再大就会出现精度丢失。
  • BigInt 用 n 结尾来定义,例如:123456789n
  • 不支持与 Number 混合运算,必须同为 BigInt 类型才能参与运算。
let a = 10n;
let b = 20n;
console.log(a + b); // 30n

let x = 10n;
let y = 5;
console.log(x + y); // ❌ TypeError: Cannot mix BigInt and other types
  • Python 的 int 类型本身就是任意精度,没有大小限制,不需要特殊语法。
  • 所以在 Python 中你根本不需要类似 BigInt 的语法:
x = 999999999999999999999999999999999999
print(x)  # ✅ 正常打印,无需特殊标记

7. 唯一标识符(Symbol)

定义:表示唯一且不可变的值,通常用于对象属性键,防止命名冲突。

let s1 = Symbol("id");
let s2 = Symbol("id");
console.log(s1 === s2); // false

特点

  • Symbol 是 JS 的第七种原始类型(primitive type)。
  • 每个 Symbol 都是独一无二的,即使描述相同也不相等。
  • 主要用作对象的“私有属性键”,不会被 for...inObject.keys() 枚举出来。
  • 也可用于定义一些 JS 内部协议,如 Symbol.iteratorSymbol.toStringTag 等。
const id = Symbol("id");
const user = {
  name"Alice",
  [id]: 12345
};
console.log(user[id]); // 12345

二、引用数据类型(Reference Types)

引用类型在内存中是按引用存储的,变量实际保存的是地址。

1. 对象(Object)

定义:由键值对组成的无序集合。

let person = {
  name: "Alice",
  age: 25,
  isStudent: true
};

console.log(person["name"]);  // "Alice"
console.log(person.age);      // 25

特点

  • 和python的字典其实类似,写法和使用方式都是类似,但是调用上JS既支持用.来进行检索,也支持用[]进行检索。而python只支持用[]来进行检索。
  • 键名只能为字符串或 Symbol,而python里只要是不可变的数据类型都可以为键。而值可嵌套其他对象、数组、函数。
  • 常用于描述复杂结构的数据。

2. 数组(Array)

定义:有序的元素集合,可包含任意类型值。

let str = ["a""b""c""b"];
let arr = ["1", 3.14, "a"];

特点

  • 这个和python里的列表类似,和python一样是使用数字索引访问元素,比如str[0]”a”,并且支持动态添加、删除元素。
  • 常用方法:push()pop()splice()map()filter() 等。

3. 函数(Function)

定义:一段可调用的代码块,接受参数并返回结果。

function add(a, b) {
  return a + b;
}
console.log(add(1, 2));

特点

  • 函数本身是对象(可被赋值、作为参数)
  • 支持匿名函数、箭头函数、闭包等

4. 类(Class)

定义:ES6 引入的语法糖,用于创建对象的模板。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  info() {
    console.log(`姓名: ${this.name}, 年龄: ${this.age}`);
  }
}
const person = new Person("瑶瑶", 20);
person.info();

特点

  • 提供更清晰的面向对象结构
  • 支持继承、构造函数、原型方法

5. Map(映射)

定义:有序的键值对集合,键可以是任意类型。

let girl = new Map([
  ["name""Luna"],
  ["age", 20],
  ["weight", 50.5]
]);
console.log(girl.get("name"));

特点

  • 其实和前面的Object类似,但是Object 是 JS 最基础的数据结构,适合存储结构化数据;Map 是专门为“键值对集合”设计的,功能更强大、更灵活。
  • 保持插入顺序,所以可以通过顺序来进行查找,python里的字典本质上也是无序的。
  • 提供专用方法:get()set()delete()has()
  • 更适合频繁增删查改的场景

6. Set(集合)

定义:存储不重复值的集合结构。

let number = new Set([1, 2, 3, 4, 5, 1]);
console.log(number);
// 这个是打印的还是[1, 2, 3, 4, 5]

特点

  • 和python的set集合一样,也可以自动去重
  • 可使用 add()has()delete() 操作
  • 可用于数组去重

函数

函数定义

那对于JavaScript中的函数写法,其实和python非常类似,我们可以先回忆一下python的写法:

在 Python 中,定义函数时需使用关键字def,例如我们可以创建一个名为add的函数。定义的格式为:在def add后加上括号,括号内填入所需的参数(这里是ab),参数列表结束后以冒号结尾。

函数体部分需要换行并缩进(通常为 4 个空格),在其中编写具体的功能逻辑。比如这里我们用return语句返回a + b的计算结果,即return a + b,这行代码的作用是将参数ab相加后,把结果作为函数的返回值。

当需要使用这个函数时,通过 “函数名 + 括号” 的方式调用,即add(),并在括号内传入实际的参数(简称 “实参”)。例如传入12,调用写法为add(1, 2)。此时,实参12会分别传递给函数定义中的参数ab,函数执行a + b的计算后返回结果3

def add(a, b):
  return a + b
print(add(1,2))
# 结果返回3

这就是 Python 中函数从定义到调用的完整逻辑和基本写法。

在 JavaScript 里,函数的定义和使用思路和 Python 是相近的,但也存在一些语法上的差异。定义函数时,Python 用的关键字是def,而 JavaScript 则是function

在 JavaScript 中定义函数时,function后面紧接着是函数名(比如add),然后是用括号括起来的参数列表(像ab)。与 Python 用冒号不同,JavaScript 使用大括号{}来标识函数体的开始和结束,函数的具体实现代码都要写在这个大括号里面,而不是像 Python 那样通过换行缩进。

调用函数的方式和 Python 一样,都是使用 “函数名加括号” 的形式,并且在括号里传入实际参数。例如,要计算 1 和 2 的和,就可以这样调用:add(1, 2)。调用之后,实参12会被传递给函数里的参数ab,函数执行a + b的运算,最后返回结果3

    function add(a, b{
        return a + b
    }
    console.log(add(12))
    // 返回的结果也为3

总的来说,JavaScript 和 Python 在函数的基本概念和调用方式上是一致的,主要区别在于定义函数时的语法结构。

同样的,在函数的参数里一样可以像python设置默认值,比如我可以设置b的默认值为1,那我这个b的值其实可以传,也可以不传进去。传进去的话就按照我传进去的值进行相加,不传进去就按默认值1进行与传进去的b相加。

    function add(a, b=1{
        return a + b
    }
    console.log(add(3))
    // 返回的结果为4,此时a=3, b=1, a + b=4
    console.log(add(12))
    // 返回的结果也为3

匿名函数(Anonymous Function)

除了使用传统的方式定义函数以外,Python 中还支持一种被称为匿名函数的写法。顾名思义,匿名函数最大的特点就是没有函数名称,而是通过变量来接收并调用函数。它通常用于快速定义一些逻辑简单的函数,无需额外定义函数名,简洁高效。

这种匿名函数的概念不仅存在于 Python,在 JavaScript 等语言中也同样适用。而在 Python 中,匿名函数有一个更正式的名字叫做 lambda 表达式。其基本语法结构如下所示:

lambda 参数1, 参数2, ... : 表达式

这个lambda函数就等价于:

def 函数名(参数1, 参数2, ...):
    return 表达式

但 lambda 只能写单个表达式,不能包含复杂逻辑和多条语句。

比如我们上面讲的添加函数假如用lambda就可以这样写:

add = lambda a, b: a + b
print(add(35))  # 输出:8

这个在JS里其实也是类似的,我们也可以通过一个变量定义的方式来完成JS中匿名函数的调用:

let sub = function(x, y{
    return x + y;
}
console.log(sub(35// 输出:8

JS 的匿名函数相比起python更强大,它不仅限于单表达式,还能写复杂的多行逻辑:

let sub = function(x, y{
    console.log("你输入了", x, y);
    return x - y;
}

匿名函数通常会被当作回调函数来进行使用。所谓回调函数,其实就是一种在特定事件或条件发生时被调用的函数, 回调函数通常用于异步编程之中。

简单来说,这个回调函数其实就是把一个函数作为另外一个函数的参数穿进去了,然后在使用的时候可以直接调用,比如在python里面可以这样来使用(把print_upper这个函数作为callback这个参数的实参传入并进行调用):

def process_data(data, callback):
    print("正在处理数据:", data)
    callback(data)

# 定义一个回调函数
def print_upper(text):
    print("结果是:", text.upper())

# 执行并传入回调函数
process_data("hello", print_upper)

我们可以举一个JS中的例子,比如现在我们有一个数组,然后呢我们调用了sort()的方法,默认情况下,sort()按字符串升序排序,但对于对象数组,我们必须手动指定排序规则

接下来我们在sort()方法里写入了一个回调函数,实际上就是告诉函数我们要具体怎么进行排序,这个匿名函数在JS中也被称为是比较函数(compare function)

  • 如果比较函数返回 负数(如 -1),表示:a 应该排在 b 前面
  • 如果返回 正数(如 1),表示:b 应该排在 a 前面
  • 如果返回 0,表示 a 和 b 相等,顺序不变。

所以这个函数的意思就是说,若 a.age < b.age,则返回负数,a 排在 b 前面;若 a.age > b.age,则返回正数,b 排在 a 前面;若相等,则顺序不发生改变。这就实现了按照 age 从小到大排序。

let users = [
    { name"Tom"age25 },
    { name"Alice"age20 },
    { name"Bob"age30 }
];

// 传入匿名函数作为排序规则(回调函数)
users.sort(function(a, b{
    return a.age - b.age;
});

console.log(users);
/// 输出结果为:
资讯配图

当然这个匿名函数的写法其实有点复杂,更简洁的写法应该是我们下面要讲的箭头函数:

users.sort((a, b) => a.age - b.age);

箭头函数

箭头函数(Arrow Function) 是 JavaScript ES6 中引入的一种更简洁的函数写法,特别适用于函数体很简单、常用作回调的场景,其基本语法如下:

(参数) => {
    函数体
}

或者更简化的版本(只有一行表达式):

(参数) => 表达式

这个写法就已经和python中的lambda非常接近了,这在JS中也被称为是隐式返回,因为其将大括号 {}return 关键字都给省略掉了。

像我们刚刚写的函数add就可以改写成:

let add = (x, y) => {
    return x + y;
}

或者再简化一点(只有一个表达式时,可以省略 {} 和 return):

let add = (x, y) => x + y;

总的来说,箭头函数就是 JavaScript 中的一种简洁函数表达方式,适合写短逻辑、回调、嵌套函数;但不能用于需要 this 或构造器的复杂函数。


数组

定义

在 JavaScript 中数组是有序的元素集合,可以存放任意类型的数据。方括号 [] 表示一个数组,元素之间用英文逗号 , 分隔。比如我们可以建立一个简单的数组,并在里面写入数字1和2。

let arr = [12];
console.log("arr", arr); // [1, 2]

那既然里面可以放入任意类型的数据,那我们也可以存放一下字符串以及布尔值等内容到里面:

let arr = ["李剑锋"18true];
console.log(arr);
资讯配图

元素获取

假如我们想要获取里面的元素,我们可以通过索引值来实现,比如:

let arr = [102030];
console.log(arr[0]); // 10
console.log(arr[2]); // 30

当然我们能一次性提取多个:

let [a, b] = [102030];
console.log(a, b); // 10 20

添加与删除

尾部添加(push)

假如我们希望向数组的尾部添加某个元素的话,我们可以使用数组的方法.push(...)来实现:

let arr = [12];
let arrLength = arr.push(34);
console.log(arr);       // [1, 2, 3, 4]
console.log(arrLength); // 4

这里我们向原本保存了1和2的数组后面添加了3和4两个值,从打印arr的信息看到其真的发生改变。

另外,之所以这个arrLength返回的是4,是因为.push()方法除了让原本的数组在尾部增加内容以外,还会返回列表的长度,因此这里承接的4就是列表的长度信息。

头部添加(unshift)

那有向尾部添加元素的方法,自然也有向头部添加的方法,那就是.unshift(...) 。我们可以尝试添加-1和0两个值到头部:

let arr = [12];
let arrLength = arr.push(34);
arrLength = arr.unshift(-10);
console.log(arr);       // [-1, 0, 1, 2, 3, 4]
console.log(arrLength); // 6

这样我们也是成功的在头部添加了元素。

头部删除(shift)

除了头部添加以外,自然也有头部删除的方法,其就是方法.shift(...) 。与.unshift(...) 使用方法其实类似,但是返回的值不一样,这个返回的是被删除元素而非其长度,我们可以尝试将原本添加进来的-1进行删除,可以看到其返回的元素确实是被删除掉的元素:

let arr = [12];
let arrLength = arr.push(34);
arrLength = arr.unshift(-10);
let delarr = arr.shift(-1)
console.log(arr);       // [0, 1, 2, 3, 4]
console.log(arrLength); // 6
console.log(delarr); // -1

尾部删除(pop)

有头部删除自然也有尾部删除,其方法就是.pop()。和头部删除一样,其返回的值也是被删除的元素:

let arr = [12];
let arrLength = arr.push(34);
arrLength = arr.unshift(-10);
let delarr = arr.shift(-1)
delarr = arr.pop(4)
console.log(arr);       // [0, 1, 2, 3]
console.log(arrLength); // 6
console.log(delarr); // 4

指定添加与删除(splice)

除了头部和尾部的添加与删除以外,我们还可以通过一个极为强大的数组方法splice()。其用于在指定位置添加、删除或替换元素。它直接修改原数组,并返回被删除的元素组成的数组。基本语法如下所示:

array.splice(start, deleteCount, item1, item2, ...);
  • **start**:开始修改数组的索引位置。如果为负数,则从数组末尾倒数(如 1 表示最后一个元素)。
  • **deleteCount**(可选):要删除的元素个数。若为 0,则不删除任何元素,仅添加新元素。
  • **item1, item2, ...**(可选):要添加到数组的新元素。

比如我想要在数字2后面添加一个2.5的话,那我应该要设置的是:

  • start = 3
  • deleteCount = 0 (因为是添加而非删除)
  • item1 = 2.5
let arr = [12];
let arrLength = arr.push(34);
arrLength = arr.unshift(-10);
let delarr = arr.shift(-1)
delarr = arr.pop(4)
delarr = arr.splice(302.5)
console.log(arr);       // [0, 1, 2, 2.5, 3]
console.log(arrLength); // 6
console.log(delarr); // [] 
// 因为这里没有内容被删除,因此是空数组

假如我要把数组[1, 2.5, 3]都进行删除的话,那我应该设置的是:

  • start = 1
  • deleteCount = 3
  • item1 不需要写,因为没有内容需要添加
et arr = [12];
let arrLength = arr.push(34);
arrLength = arr.unshift(-10);
let delarr = arr.shift(-1)
delarr = arr.pop(4)
delarr = arr.splice(302.5)
delarr = arr.splice(13)
console.log(arr);       // [0, 3]
console.log(arrLength); // 6
console.log(delarr); // [1, 2, 2.5] 

数组排序

数字排序

在数组的操作中,有一个专门的方法sort()是用来进行排序的,这个方法会修改原数组,并返回排序后的数组。比如[1,5,7,3]的数组在使用sort()方法后会变成[1,3,5,7]

但是这个方法有一个比较严重的问题是,其默认是将数组元素转为字符串后进行排序的,所以是按照字母顺序比较的,我们可以举一个简单的例子来展示一下:

let arr = [1025100];
arr.sort();
console.log(arr); // [10, 100, 2, 5]

理论上来说,返回的数组应该是[2, 5, 10, 100],但是其返回的却是[10, 100, 2, 5]。这是因为当转换成字符串以后,其对比的就不再是数值的大小,而是对比的是字符串的长短和大小了。

例如比较 "10" 和 "100"

  1. 第一个字符:"1" vs "1" → 相等 → 继续比下一位
  2. 第二个字符:"0" vs "0" → 相等 → 继续比下一位
  3. "10" 已经没有了,但 "100" 还有一个 "0"

在字符串排序中:短的字符串会被认为更小!所以就从小到大的排在前面了。

那100和2的排序也是同理的,因为字符串"1" < "2”,所以认为100是比2小的内容,因此排序出来这么别扭也是这个原因。


为了让数字按大小排序,我们需要传入一个比较函数,这才是 .sort() 真正的强大之处。其实这部分我们在前面讲函数的时候已经涉及过,那这里我们就更深入的讲一下。

当我们希望数字能从小到大的进行排列,我们需要在sort()方法里加入(a, b) => a - b 这个比较函数,也就是写成下面这样:

let arr = [5201314];
arr.sort((a, b) => a - b);
console.log(arr); // [1, 4, 5, 13, 20]

这个所谓的比较函数其实也就是我们前面讲到的匿名函数里的箭头函数。这个函数呢有两个参数ab,然后呢其传入后呢执行的就是a-b这样。

sort()方法判定的准则也很简单,只要a-b传回去的数是负数,那就代表a<b,也就是说a应该在前面,b应该是在后面。假如相等那就不改变位置。那假如传回去的数是正数,自然是b<a,那就应该b在前头,a在后头。这样一来自然就可以实现升序排序。

那对于降序排序是不是也是一样的原理,我们只需要把这个比较函数修改一下,让其从a-b变成b-a。反正sort()里面的判定就是看返回的值是正数负数来调整位置,那一更换方法自然就变化了:

let arr = [5201314];
arr.sort((a, b) => b - a);
console.log(arr); // [20, 13, 5, 4, 1]

这样我们就实现了数字的排序了。

字符串排序

那对于字符串的排序,这个对于sort()来说就相对比较好办一些,因为sort()本身就是按着字符串来进行排序的,所以默认情况下其都会按着升序(A-Z)来进行,比如下面这样:

let arr = ["banana""apple""orange"];
arr.sort();
console.log(arr); // ["apple", "banana", "orange"]

但假如我们想按着降序(Z-A)来进行排序,或者说要别的语言,比如中文要进行排序的话,我们其实要用到一个新的比较函数localeCompare() 。

localeCompare() 是 JavaScript 提供的字符串方法,用于比较两个字符串的顺序,会根据语言环境(locale)来决定哪个字符串排在前。其基本语法如下:

a.localeCompare(b, [locales], [options])
  • 假如这个函数返回的结果是负数,则a 排在 b 前面(a < b)。
  • 假如返回0,则a与b相等。
  • 假如为整数,则a 排在 b 后面(a > b)。

举个例子:

"apple".localeCompare("banana"); // -1,表示"apple" < "banana"
"banana".localeCompare("apple"); // 1,表示"banana" > "apple"
"apple".localeCompare("apple");  // 0

那这个函数怎么配合sort()使用呢,其实和之前一样,也是写成一个箭头函数的方式,将其写成传入ab两个参数,然后执行某个操作后返回正数和负数,然后让sort()根据这个结果来进行位置的调整,最后得到修正后的数组。以下就是将其实现降序的方法:

let arr = ["banana""Apple""orange"];
arr.sort((a, b) => b.localeCompare(a));
console.log(arr); // ["orange", "banana", "apple"]

注意这里一定要是b.localeCompare(a) ,因为假如是a.localeCompare(b)就和原生的sort()没有区别了!

那假如我们需要考虑其他语言怎么办呢,我们就需要使用到localeCompare() 里后面的一个参数[locales] ,比如说我们要对中文进行比较,我们就需要设置成’zh-cn’

let arr = ["李四""王五""张三""赵六"];
arr.sort((a, b) => a.localeCompare(b, 'zh-cn')); // 按拼音排
console.log(arr); // ["李四", "王五", "张三", "赵六"]

那其他国家也是类似的,这里举几个常见的语言环境代码:

语言
代码示例
英文(美国)
'en-US'
英文(英国)
'en-GB'
中文(简体)
'zh-CN'
中文(繁体)
'zh-TW'
日语
'ja'

那这里我们也顺带提一下最后一个参数options ,这是一个对象,用于设置更细致的比较规则,例如:

属性名
用法说明
sensitivity
是否区分大小写、重音(最常用!)
ignorePunctuation
是否忽略标点符号(如 "-"、".")
numeric
按数值排序字符串("10" > "2")而不是按字典顺序
caseFirst
指定大写或小写排在前("upper" 或 "lower")

这样,我们其实就实现了字符串数组的排序了。

数组筛选

那有了排序、删除增加以外,我们其实也可以对数组进行定向的筛选,也就是从一组数据中找出满足某些条件的元素。那这个时候就需要用到 filter() 方法。filter() 是数组的一个方法,用于从原数组中筛选出满足特定条件的元素,并返回一个新的数组(不改变原数组)。其语法如下:

let newArray = arr.filter(value => 条件);

比如说我希望在下面的数组中筛选出大于10的数字,我就可以将箭头函数写成item => item > 10

let arr = [5101520];
let result = arr.filter(item => item > 10);
console.log(result); // [15, 20]

又比如说我们要从数组中筛选出偶数,那也可以写成item => item % 2 === 0 (这里的===其实就是数学上的等于):

let arr = [123456];
let result = arr.filter(item => item % 2 === 0);
console.log(result); // [2, 4, 6]

这样我们就可以来进行指定的筛选了。

数组合并

当我们有两个数组的时候,有什么办法能让其进行合并呢?比如说我们有两个数组:

let arr1 = [12];
let arr2 = [34];

我们想要得到一个新的数组:

[1234]

这个时候其实有三个方法可以实现,分别是.concat()...展开运算以及push(...x) 。

使用 .concat() 方法

那这个方法的基本语法如下:

let newArray = arr1.concat(arr2, arr3, ..., value1, value2, ...);
  • arr2, arr3 其实就是其他的数组
  • value1,value 就是还有其他单值需要添加

比如说我们要合并上面的arr1和arr2:

let arr1 = [12];
let arr2 = [34];
let result = arr1.concat(arr2);
console.log(result); // [1, 2, 3, 4]

这里就是arr1.concat(arr2),那就是arr1在前面,arr2在后面,因此得到的就是[1, 2, 3, 4]。假如是arr2.concat(arr1),那就是[3, 4, 1, 2]

假如我们要合并多个数组和单个值的话,那就要按着下面的方式来写:

let arr1 = [12];
let arr2 = [34];
let arr3 = [56];
let result = arr1.concat(arr2, arr3, 78);
console.log(result); // [1, 2, 3, 4, 5, 6, 7, 8]

这样就能将全部的数组合并后,并且在后面加上7和8两个值。

展开运算符(...

这种方式是 ES6 之后最常用、最推荐的方式。比如我们要合并两个数组,我们可以按下面的方式进行:

let arr1 = [12];
let arr2 = [34];
let result = [...arr1, ...arr2];
console.log(result); // [1, 2, 3, 4]

我们只需要添加三个点即可将两个数组合并成一个新的数组。

假如我们还需要合并其他值在这个数组中,我们可以这样写:

let arr1 = [12];
let arr2 = [34];
let result = [...arr1, 100, ...arr2];
console.log(result); // [1, 2, 100, 3, 4]

这样我们就可以把100加入进去了。这种写法比起前面的.concat()更灵活也更加的简介,并且也不改变原数组。

与 push() 结合(改变原数组 ❌)

其具体的使用方式:

let arr1 = [12];
let arr2 = [34];

// 展开后 push,每个元素被单独推入
arr1.push(...arr2);
console.log(arr1); // [1, 2, 3, 4]

但是这个方法会改变 arr1 的内容! 如果不希望修改原数组,建议用 .concat() 或 [...arr]

数组的遍历

数组的遍历(也叫迭代)是 JavaScript 中最基础也最常用的操作之一,几乎写任何逻辑都要用到。

那什么时候需要用到遍历呢?比如说我们有一个数组:

let arr = ["apple""banana""orange"];

假如我们希望:

  • 把每个元素都打印出来;
  • 给每个水果加上编号;
  • 过滤或修改数组内容……

这时候就要用到 遍历操作

对于数组的遍历呢,常见的有三种方法,分别是for 传统循环、forEach()for...of。下面我们就来逐一介绍一下:

for 传统循环

具体的语法如下:

for (初始值; 条件判断; 步进操作) {
    // 每次循环执行的代码
}
部分
作用
初始化
定义一个变量作为计数器(一般是 let i = 0
条件判断
判断是否继续循环(如 i < arr.length
每轮变化
循环一次后变量怎么变(如 i++,表示每次加1,假如要跳着来的话就是i+=2

比如说下面的例子:

let arr = ["苹果""香蕉""橘子"];

for (let i = 0; i < arr.length; i++) {
    console.log("第", i + 1"个水果是", arr[i]);
}
  • let i = 0; → 初始化变量 i,从 0 开始(数组的第一个位置)
  • i < arr.length; → 条件:只要 i 小于数组长度,就继续执行循环
    • arr.length 是数组长度,这里是 3
  • i++ → 每次循环完后,i 增加1

循环会进行 3 次:

第几次
i 值
arr[i]
控制台输出
第1次
0
"苹果"
第 1 个水果是 苹果
第2次
1
"香蕉"
第 2 个水果是 香蕉
第3次
2
"橘子"
第 3 个水果是 橘子
第4次
3
❌ 停止了,因为 3 < 3 不成立

这三者其实缺一不可的,因为缺少了更新变量的话那就会无限的输出第一个值。假如条件判断写错的话可能根本运行不了,所以在使用的时候需要进行注意。

forEach() 方法

forEach() 是 JavaScript 数组原型上的一个内置方法,专门用于遍历数组元素,是日常开发中非常常用的遍历方式。它的设计理念是**专注于 "对每个元素执行相同操作"**,语法简洁且易于理解。

其具体语法如下:

数组.forEach(function(当前元素, 索引, 原数组{
  // 对当前元素的处理逻辑
});

回调函数参数说明function(当前元素, 索引, 原数组)

  • 第一个参数(必填):当前正在遍历的元素(通常命名为 itemcurrent 、value等)
  • 第二个参数(可选):当前元素的索引(整数,从 0 开始)
  • 第三个参数(可选):正在被遍历的原数组本身,这可能会直接对原数组进行修改。

我们可以举一个简单的例子来展示一下,比如我们希望像上面的方法一样将一个个的元素都打印出来,我们可以尝试:

const fruits = ["苹果""香蕉""橘子"];

// 遍历数组,打印每个元素
fruits.forEach(fruit => {
  console.log(fruit);
});
// 输出:
// 苹果
// 香蕉
// 橘子

那这里就是首先通过forEach函数将fruits里的每个值提取出来,然后传入回调函数当中,最后逐一的进行打印出来。

那假如我们希望在打印的内容中加上索引的信息,我们可以这样来写:

const fruits = ["苹果""香蕉""橘子"];

// 同时使用元素和索引
fruits.forEach((fruit, index) => {
  console.log(`第 ${index + 1} 个水果:${fruit}`);
});
// 输出:
// 第 1 个水果:苹果
// 第 2 个水果:香蕉
// 第 3 个水果:橘子

那这里其实就是通过forEach函数获取了两个信息fruitindex(索引),然后由于系统默认的index的第一个值是0,因此需要在原index的基础上加一才能符合我们日常的语言习惯第一第二第三个这样。

最后我们假如希望去修改原数组的信息,通过forEach函数也是可以实现的,比如下面这个例子:

const numbers = [123];

numbers.forEach((num, index, arr) => {
  // 修改原数组(不推荐,forEach 设计初衷是遍历而非修改)
  arr[index] = num * 2;
});

console.log(numbers); // 输出:[2, 4, 6]

在这里我们传入的就是全部的三个参数,然后通过数组的索引arr[index]来获取到对应的值,然后对这个原本的值乘以2来实现修改。因此在结束遍历后,所有数组的值都被乘以了2。

for...of 方法

for...of 是 ES6 新增的一种循环语法,专门用于遍历可迭代对象(如数组、字符串、Map、Set 等),是日常开发中遍历数组的推荐方式之一。它的设计理念是简洁地获取对象中的每个元素,无需关心索引或遍历机制。

其基本语法如下:

for (const 元素变量 of 可迭代对象) {
  // 对元素的处理逻辑
}
  • 元素变量:每次循环时,会被赋值为可迭代对象中的 “当前元素”(可以用 let 或 const 声明,建议用 const 除非需要修改该变量)。
  • 可迭代对象:指具有迭代器接口的数据结构,如数组、字符串、argumentsNodeList(DOM 节点集合)、MapSet 等。

比如我们还是进行最基本的数组遍历打印,我们可以按以下方式来写:

const fruits = ["苹果""香蕉""橘子"];

// 遍历数组,直接获取每个元素
for (const fruit of fruits) {
  console.log(fruit);
}
// 输出:
// 苹果
// 香蕉
// 橘子

这里我们定义了元素变量fruit,然后将用其来承接数据fruits里的每一个元素,然后基于这个获取的变量来进行逐个的打印,最终得到一个个值的输出。

如果需要知道元素的索引,可以配合数组的 entries() 方法(返回包含 [索引, 元素] 的迭代器):

const fruits = ["苹果""香蕉""橘子"];

// 使用 entries() 获取索引和元素
for (const [index, fruit] of fruits.entries()) {
  console.log(`第 ${index + 1} 个水果:${fruit}`);
}
// 输出:
// 第 1 个水果:苹果
// 第 2 个水果:香蕉
// 第 3 个水果:橘子

相比传统 for 循环的 arr[i]for...of 直接获取元素值,语法更简洁,避免了索引操作的繁琐和出错风险。并且支持使用 break 终止整个循环,或 continue 跳过当前迭代(这是 forEach() 做不到的):

const numbers = [12345];

// 找到第一个偶数就停止遍历
for (const num of numbers) {
  if (num % 2 === 0) {
    console.log("找到第一个偶数:", num);
    break// 终止循环
  }
  console.log("当前数字:", num);
}
// 输出:
// 当前数字:1
// 找到第一个偶数:2(循环终止)

Set集合

Set 是一种“集合”数据结构,用于存储一组“不重复”的值。相比之下,数组是有顺序的列表,可以有重复,而Set 是无重复的“独特项”集合。所以两者最大的区别就是是否有重复。

我们可以举一个最简单的例子来看看两者的区别:

let arr = ['a''b''a'];
let set = new Set(['a', 'b', 'a']);
console.log(arr); // ['a', 'b', 'a']
console.log(set); // Set(2) {'a''b'}

一个是创建了一个名叫arr的数组,另一个是在集合里放入了一个数组。可以看到说数组保留了两个 'a',而 Set 自动去掉了重复的 'a'。

集合的创建方式

创建集合的语法如下:

let set = new Set(iterable);

左边大家应该都清楚就是变量的场景与赋值,右边new Set(iterable)才是创建集合的具体内容。这里的new是一个关键字,这个是必须存在的,因为 Set 是一个构造函数(Constructor)。在JavaScript中规定,构造函数(如 SetMapDate 等)必须通过 new 调用来创建它的实例对象。

然后我们就可以在Set()构造函数里放入一个可迭代对象iterable。所谓可迭代对象就是可以被循环的东西,比如数组和字符串:

类型
示例
数组
['a', 'b', 'c']
字符串
'abc'

我们可以用数组来进行集合的初始化:

let set = new Set([1, 2, 3, 2, 1]);
console.log(set); // Set(3) {123}

可以看到返回的结果是{1, 2, 3},这也是因为我们上面说的不重复的特质,因此把两个重复的值给去掉了。

另外我们也可以尝试用字符串来初始化集合:

let set = new Set('banana');
console.log(set); // Set(3) {'b''a''n'}

我们会发现字符串会被拆成一个个的字符,然后也是经过去重后得到最终的结果。当然假如我们啥也不传的话,那就是一个空的集合了:

let set = new Set();
console.log(set); // Set(0) {}

添加与删除

添加(add)

对于集合而言,添加一个元素的方式是通过add()方法来实现的,其具体的语法如下:

set.add(value)

其具体执行的内容就是将一个值(value)添加到 Set 中,如果这个值已经存在,不会重复添加。最后返回的是这个 Set 对象本身(可以通过变量接收或者直接打印出来)。

比如我们可以添加一个普通的内容进入空的集合当中:

let fruits = new Set();
fruits.add('apple');
fruits.add('banana');
fruits.add('apple'); // 重复的,不会添加
console.log(fruits); // Set(2) {'apple', 'banana'}

首先我们创建了一个空的集合,然后往里面添加了apple和banana两个元素(虽然apple添加了两次,但是由于相同所以并不会重复),最终我们可以看到打印的集合就是我们添加的两个元素。

除了一个个添加以外,我们还可以链式添加多个元素,但这个方法并不推荐,因为一行一个的方法可读性更强一些:

let s = new Set();
s.add(1).add(2).add(3);
console.log(s); // Set(3) {1, 2, 3}

删除(delete)和清空(clear)

那对于删除集合元素来说,使用的方法就是delete()。其基本语法如下:

set.delete(value)

其实和前面add()是一样的,也是删除指定的值。如果值存在,删除成功并返回 true;如果值不存在,返回 false。但需要注意的是,其只删一个值,不是像数组那样按下标删(也就是根据序号来删除)!

比如说我们可以删除一个集合里面的部分元素:

let fruits = new Set(['apple''banana''mango']);
fruits.delete('banana'); // 返回 true
fruits.delete('grape');  // 返回 false(因为不存在)
console.log(fruits);     // Set(2) {'apple', 'mango'}

我们运行会发现这里的fruits.delete('banana'); 并未返回true,这是因为虽然其返回了值但是我们并未打印出来,所以当我们加上打印信息console.log(fruits.delete('banana')); ,这样就可以在控制台看到true的返回。

假如我们不是希望删除某一个元素,而是希望清空整个集合,我们可以使用方法clear(),其使用方法如下:

let fruits = new Set(['apple''banana''mango']);
fruits.clear();
console.log(fruits); // Set(0) {}

这样我们就可以把所有的元素都清空掉。

检查元素是否存在(has)

对于检查集合中的元素是否存在,我们所使用的方法是has(),其具体语法如下:

set.has(value)

这里的value就是我们要去集合里检索的内容, 然后其返回值是true或者false。基本的使用方式如下:

let fruits = new Set(['apple''banana''orange']);

console.log(fruits.has('banana')); // true
console.log(fruits.has('mango'));  // false

这里:'banana' 存在,所以返回 true。然后又因为'mango' 不存在,所以返回 false 。

获取集合大小(size)

假如我们希望知道集合内元素的个数,我们可以使用.size方法来进行获取,其具体使用方式如下:

let fruits = new Set(['apple''banana''orange']);
console.log(fruits.size); // 3

fruits.add('mango');
console.log(fruits.size); // 4

fruits.delete('banana');
console.log(fruits.size); // 3

当然在数组里也有类似的方法,其使用的就是arr.length

集合与数组转换

前面我们提到了,数组和集合的最大区别其实就是是否能够容纳相同的元素,因此有些场景下我们需要数组和集合的相互转换来实现。下面我们就来看看两者是如何完成相关转换的。

集合转换成数组(from)

对于集合转换为数组,我们可以使用方法from()来实现,其语法如下:

let arr = Array.from(set);

从本质上来说Array.from() 会把任何可迭代对象转换成数组。由于Set 本身就是可迭代对象,所以可以直接转。其他可迭代对象还包括前面提到的字符串等。

那实际转换的示例如下:

let fruits = new Set(['apple''banana''orange']);
let arr = Array.from(fruits);
console.log(arr); // ['apple', 'banana', 'orange']

拓展运算符([...set]

除了from的方法以外,我们还有一个可以转换的方法就是使用拓展运算符(…)。其具体语法如下:

let arr = [...set];

这里面的... 会把“可迭代对象”或“对象的可枚举属性展开成一个个独立的元素/属性。然后当我们放在 [] 中,就成了一个新的数组,比如:

let fruits = new Set(['apple''banana''orange']);
let arr = [...fruits];
console.log(arr); // ['apple', 'banana', 'orange']

当我们...fruits 的时候,其实就是把apple, banana, orange三个元素单独提取出来了,然后一起放到[]里就变成了数组了。转换为数组后,你就可以使用数组的所有方法mapfilterreduce 等),而 Set 本身不支持这些方法。

当然拓展运算符其实有更深层次的用法,比如说拼接数组这种:

const a = [12];
const b = [34];

const merged = [...a, ...b];     // [1, 2, 3, 4]
const withMiddle = [0, ...a, 5]; // [0, 1, 2, 5]

数组转换成集合

其实数组转换成集合的方法就是创建集合时候的方法,我们直接把数组放入集合中即可,例如:

let arr = [123321];
let mySet = new Set(arr);

console.log(mySet); // Set(3) {1, 2, 3}
console.log(mySet.size); // 3

当数组被转换为集合后,其目的就是为了去重。我们可以用更简便的方式实现这一操作:

const arr = [1,2,3,3,2,1];
const unique = [...new Set(arr)]; // [1,2,3]

我们首先通过new Set(arr)来将数组转换为集合,然后再使用拓展运算符将其暂开,并使用[]将其转为数组,这样就能高效的实现数组的去重了。

遍历集合

遍历集合其实和遍历数组的方式是类似的,比如我们还是可以用for...of的方式来进行遍历,比如下面这样:

let fruits = new Set(['apple''banana''orange']);

for (let item of fruits) {
    console.log(item);
}
// apple
// banana
// orange

另外呢,我们也可以用forEach()函数来进行遍历,但是这里的forEach()函数与数组的有所区别,其语法如下:

set.forEach((value, key, setObj) => {
    // value: 元素值
    // key:   元素值(Set 没有键,value === key)
    // setObj: 当前遍历的 Set 对象
});

只不过呢,Set 没有“键”的概念,所以对于forEach()函数来说, key 参数其实和 value 一样,主要是为了和 Map 保持一致。

let fruits = new Set(['apple''banana''orange']);

fruits.forEach((value, key, s) => {
    console.log(`value=${value}, key=${key}`);
});
// value=apple, key=apple
// value=banana, key=banana
// value=orange, key=orange

我们可以用一个表格来对比一下两者的区别:

特性
for...offorEach()
简洁性
✅ 更简洁
稍繁琐
参数
只返回值
返回值、键、Set 本身
控制循环
可以用 break / continue
❌ 不能中途退出(除非抛异常)
回调函数
✅(方便链式调用、配合高阶函数)

-- 完 --


机智流推荐阅读

1.LangGraph 高级实战:让 AI 会记忆、能暂停、可插手的断点恢复与流式控制

2.万字上文!从零到精通,开启你的CSS布局能力之路

3.万字长文!从 0 到 1 搭建基于 LangGraph 的 AI Agent

4.万字长文!从零开始构建你的第一个 ReAct Agent

关注机智流并加入 AI 技术交流群,不仅能和来自大厂名校的 AI 开发者、爱好者一起进行技术交流,同时还有HuggingFace每日精选论文顶会论文解读Talk分享通俗易懂的Agent知识与项目前沿AI科技资讯大模型实战教学活动等。
在「机智流」公众号后台回复下方标红内容即可加入对应群聊:
  • cc | 大模型技术交流群
  • hf | HuggingFace 高赞论文分享群
  • 具身 | 具身智能交流群
  • 硬件 | AI 硬件交流群
  • 智能体 | Agent 技术交流群

声明:内容取材于网络,仅代表作者观点,如有内容违规问题,请联系处理。 
IP
more
iPhone 17 Pro核心配置爆料大盘点!
iPhone17 Pro升级汇总!128G内存终于砍了,价格微涨…
雷军最喜欢的手机,果然还是 iPhone 啊|澎湃 OS 3 初版体验
iPhone17 Pro官方手机壳偷跑,暗示三大变化,你能接受吗?
今天公布的直屏新机,让我有点不想买iPhone 17了
苹果 iPhone 17 系列斜挎挂绳配件曝光﹨iOS 18.6.1 验证已关闭﹨Apple Store 入驻抖音
吊打iPad?华为这新机有点炸裂啊
iPhone16ProMax千万别乱买,又涨价了!
国产半导体行业大整合:从“卡脖子”到抢滩前沿,EDA、硅光、IP全布局
iPhone17ProMax有12项升级,这次真的要封神了!
Copyright © 2025 成都区角科技有限公司
蜀ICP备2025143415号-1
  
川公网安备51015602001305号