
作者:李剑锋
对象
定义
在 JavaScript 里,对象是键值对的集合,键(属性名)→ 值(任意类型:基本类型、对象、函数都行),最后用{}
将其包裹起来即可。其典型用途就是描述实体(如 person
)、作为“字典/映射”、组织配置等。
比如我可以用一个对象来保存下我个人的信息:
const person = {
name: "李剑锋",
email: "7639@qq.com",
height: 175,
};
这里面的键(name
、email
、height
)必须唯一;而值可以是任何数据类型。
属性添加
那假如我们希望往对象里添加一些额外的键值对,我们可以通过以下方法实现:
person.height = 172;
person.web = "www.abc.com";
console.log(person);
// {name: '李剑锋', email: '7639@qq.com', height: 172, web: 'www.abc.com'}
那对于已有的属性来说,其会覆盖掉原值,因为对象的键是唯一的。而假如有新的属性,那会添加进去,比如web的属性。
属性删除
那假如有我们不希望存在的属性,我们也可以使用delete方法来实现,比如:
delete person.web;
console.log(person);
// {name: '李剑锋', email: '7639@qq.com', height: 172}
这个时候就会发现web属性被删除掉了。但是假如删除掉一些本身就不存在的属性,系统也不会报错。
检查属性是否存在
假如我们想知道某个属性(也就是键)是否存在,我们可以使用in
的方法来进行实现,比如说:
let has = "gender" in person;
console.log(has) // false
这里就是检查gender
这个属性是否在之前创建的person
对象中。假如存在的话,那就会返回true
,假如不存在就返回false
。
获取对象的属性数量
假如我们想知道对象有多少个属性,我们可以使用Object.keys(person).length
来实现,比如我们可以统计一下当前person
的属性数量:
console.log("length", Object.keys(person).length)
// length 3
对象转为数组
假如我们去掉最后的.length
,我们可以得到一个单纯由属性组成的数组:
console.log("keysArr", Object.keys(person)) //Object.keys() 用于获取对象属性名的数组
// ['name', 'email', 'height']
假如希望把整个对象转换为数组,我们可以通过Object.entries()
方法来获取键值对的数组,比如:
let arr = Object.entries(person) //Object.entries() 用于获取对象的键值对数组
console.log(arr)
// [['name', '李剑锋'], ['email', '7639@qq.com'], ['height', 172]]
数组转为对象
当然数组也是可以转为对象的,比如我们可以使用Object.fromEntries()
方法来进行转换:
let arr1 = [['name', '李剑锋'], ['email', '7639@qq.com'], ['height', 172]]
let obj = Object.fromEntries(arr);
console.log(obj); // {name: '李剑锋', email: '7639@qq.com', height: 172}
当然这种情况仅适合数组里已经是 [key, value]
形式的情况。
假如不是键对值已经存在的情况,我们也可以将索引值为键,对应的属性为值来实现对象的创建:
let arr2 = ['李剑锋', '男', 'abc.com'];
let obj2 = Object.assign({}, arr2);
console.log(obj2); // {0: "李剑锋", 1: "男", 2: "abc.com"}
又或者使用展开运算符:
let arr3 = ['李剑锋', '男', 'abc.com'];;
let obj3 = {...arr3};
console.log(obj3); // {0: "李剑锋", 1: "男", 2: "abc.com"}
这个和 Object.assign({}, arr)
一样,本质是把索引变成键。
遍历
我们可以和前面的数组和集合一样,使用for...in
的方式实现对象的遍历,但是直接对对象遍历,所获取的内容是属性名(也就是键),假如我们要把对应的值也获取,那需要通过对象名称[属性名]
的方式进行获取,比如:
for (let key in person) {
console.log(key, person[key]);
}
// name 李剑锋
// email 7639@qq.com
// height 172
这个时候我们将前面创建的对象person里的每一个键name, email和height进行获取,然后再使用person[key]
的方式将对应的值进行获取,最后得到的就是对应信息的打印。
Map 集合
定义
前面的内容里我们提到了Set集合的内容,其最大的特点就是值不能重复。那对于键对值的集合而言,假如我们希望键唯一,值可重复,那我们可以使用Map集合来实现。
那我们创建Map集合的方式和Set集合其实有点类似,也是需要先写一个new关键字,然后再用Map()
构造函数来进行创建:
// 空的 Map
let person = new Map();
但是这个构造函数是接收二维数组作为初始值,每个子数组是 [键, 值]
,比如:
// 带初始值的 Map
let person = new Map([
["name", "李剑锋"],
["gender", "男"],
["web", "abc.com"]
]);
相比于对象,键可以是任何类型(对象、数组、函数、基本类型都行),而对象 {}
的键只能是字符串或 Symbol。
添加或修改元素(set)
相比于前面对象直接用元素.属性名称=属性值
的方式来添加或更新元素,对于Map集合而言需要使用.set()
方法进行更新,比如:
person.set('height', 172);
person.set('web', "cbd.com");
console.log(person)
// Map(4) {'name' => '李剑锋', 'gender' => '男', 'web' => 'cbd.com', 'height' => 172}
这个时候相同的键会更新,而新增的键就会被添加进去了。并且和Set集合类似,也可以支持链式调用,比如:
person.set("name", 1).set("gender", 2);
console.log(person)
// Map(4) {'name' => 1, 'gender' => 2, 'web' => 'cbd.com', 'height' => 172}
删除元素(delete)
同样的,我们也可以使用delete()
方法来删除键值对,我们只需要把键名称放入括号中即可。比如我们要删除gender这个键的话,那可以使用:
console.log(person.delete('gender')); //true
console.log(person)
// Map(4) {'name' => 1, 'web' => 'cbd.com', 'height' => 172}
删除后返回布尔值,true
表示删除成功,false
表示键不存在。
检查元素是否存在(has)
我们也可以使用has()
方法来检查元素是否存在,那在这里放入的同样也是键值对里的键即可,假如存在就返回true,不存在就返回false:
person.has('gender'); // false
获取 Map 的大小(size)
我们可以通过.size的方法来获取Map集合当前键值对的数量:
console.log(person.size); // 当前键值对的数量 3
Map转换为数组
类似于Set集合转为数组,我们也可以使用Array.from()
的方法实现数组的转变,比如说:
let arr = Array.from(person);
console.log(arr);
// [['name', 1], ['web', 'cbd.com'], ['height', 172]]
其会转成二维数组(每个子数组是 [key, value]
)。
同样的我们也可以使用扩展运算符来进行实现,结果也是和上面是一样的:
let arr2 = [...person];
console.log(arr2);
// [['name', 1], ['web', 'cbd.com'], ['height', 172]]
遍历
for...of
方法
因为Map 是可迭代对象,所以直接用 for...of
会按插入顺序遍历,比如我们直接用键(key)和值(value)去承接person这个Map集合:
for (let [key, value] of person) {
console.log(key, value);
}
// name 1
// web cbd.com
// height 172
每次迭代返回 [key, value]
数组,所以常配合解构赋值。
forEach()
方法
同样我们也可以使用forEach()进行遍历,但是回调参数顺序是 (value, key)
,这和对象的 forEach 不一样,使用时需要注意顺序。
person.forEach((value, key) => {
console.log(key, value);
});
// name 1
// web cbd.com
// height 172
但是需要注意的是这个方法不能用 break
提前终止。
清空元素(clear)
当然我们也可以使用.clear()方法来将所有的元素删除清空,比如下面这样:
person.clear();
这样就可以删除所有的元素, 当我们使用.size查看其长度的时候,我们就会发现其长度会变成0。
类(class)
定义
在 JavaScript 中,类(class)是一种用于创建对象的语法结构,它封装了数据(属性)和操作数据的行为(方法)。它提供了一种更简洁、更接近面向对象语言的写法,用于定义对象的构造函数和原型方法。
但是创建好的类并不像函数一样可以直接使用,其需要先创建出一个实例以后才能具体的进行使用。我们可以这样来进行理解:
类(class) 是一张“蓝图”,描述“一个东西应该长什么样、会干什么”。 实例(instance) 是照着蓝图造出来的具体东西。
我们可以通过代码具体来看看到底什么是类。
类的创建
我们可以根据下面这个例子具体来看看如何创建并使用一个类:
class Person {
// 1) 实例字段(每个实例都会有)
name
web
// 2) 构造函数:new 的时候自动执行
constructor(name, web) {
// this 指向“正在被创建的那个实例”
this.name = name
this.web = web
}
// 3) 实例方法:所有实例都会“会”这个方法(共享同一份实现)
info() {
return`姓名:${this.name} 个人网站:${this.web}`
}
}
// 4) 用 new 创建实例(必须写 new)
const p = new Person("李剑锋", "abc.com")
console.log(p) // Person {name: '李剑锋', web: 'abc.com'}
console.log(p.web) // abc.com
console.log(p.info()) // 姓名:李剑锋 个人网站:abc.com
首先第一步,我们需要写入class
关键字,从而宣告我们要创建的是一个类。然后我们可以写入这个类的名称,比如这里是叫Person
。再然后就是写入一个花括号{}
,并将类的主体代码都放入。
class Person {
}
在类的最开始,我们要写入实例字段,表示这个类的每个实例都会有这些属性。这里我们写入的两个字段就是名字(name)和网页(web)。当然这里其实不写也是可以的(JS 会在构造函数 constructor
里赋值时自动创建),但显式声明更清晰。
class Person {
// 1) 实例字段(每个实例都会有)
name
web
}
之后我们就需要写入一个构造函数constructor
,这个构造函数里的参数就是上面我们写入的字段。我们在后续创建实例的时候需要的信息其实就是构造函数里面的参数,比如当我们写new Person('A','B')
时,其就会自动被调用。
然后我们就看到了this.name=name
和this.web=web
这两段有点奇怪的代码。我们上面其实说了,类创建好了以后是需要创建实例后才能进行使用的,而每一个实例其实都可能不一样,因此我们使用this其实就代表“当前这个实例对象”,也就是说,你 new 出来哪个对象,方法里的 this
就指向它。其实就是给当前的对象加了两个属性,我们后续可以通过person.web
或者person.name
来获取我们一开始传入的信息。
class Person {
// 1) 实例字段(每个实例都会有)
name
web
// 2) 构造函数:new 的时候自动执行
constructor(name, web) {
// this 指向“正在被创建的那个实例”
this.name = name
this.web = web
}
}
完成构造函数以后呢,我们其实还需要写入的就是具体的实例方法,也就是我们看到的info()
函数。我们使用实例方法的方式也很简单,就是使用p.info()
即可,并且所有的实例都可以进行使用。在这个函数里面我们就简单定义了一个打印信息展示我们传入的内容。至此一个简单的类就已经构造完成了,接下来我们就可以进行实例的创建并进行使用了。
class Person {
// 1) 实例字段(每个实例都会有)
name
web
// 2) 构造函数:new 的时候自动执行
constructor(name, web) {
// this 指向“正在被创建的那个实例”
this.name = name
this.web = web
}
// 3) 实例方法:所有实例都会“会”这个方法(共享同一份实现)
info() {
return`姓名:${this.name} 个人网站:${this.web}`
}
}
实例的创建其实也很简单,首先我们需要写入一个关键字new,这个是必须要有的,不然会报错Class constructor Person cannot be invoked without 'new'
。然后呢写入我们的类名称,并且在括号里传入构造函数所需要的变量(这里就是name和web)。这样实例其实就创建好了。
// 4) 用 new 创建实例(必须写 new)
const p = new Person("李剑锋", "abc.com")
创建好了以后其实我们就可以开始去调用里面的内容了,比如我们可以直接打印一下我们创建的类,可以看到其输出就是类的名称及一个对象(以实例对象为键,以传入内容为值的对象)。
console.log(p) // Person {name: '李剑锋', web: 'abc.com'}
我们可以通过变量名称.实例对象
的方式获取我们的值,比如我们想要获取传入的web信息,我们可以使用p.web来实现:
console.log(p.web) // abc.com
当然我们也可以使用类里面的方法,比如可以直接对info()函数进行调用,得到的结果就是函数根据传入参数运行的结果:
console.log(p.info()) // 姓名:李剑锋 个人网站:abc.com
私有属性
所谓私有属性其指的是只能在类的内部访问的属性,外部代码访问会报错。其主要目的是用来隐藏类的内部数据,防止被外部直接修改。在 JavaScript 中,私有属性用 #
前缀来定义(方法内部访问私有属性时,直接用 this.#属性名
)。其基本创建如下:
class Person {
// 私有字段(只能在类内部访问)
#age;
constructor(name, age) {
this.name = name; // 公共属性
this.#age = age; // 私有属性
}
}
const p = new Person("张三", 20);
console.log(p.name); // ✅ "张三"(公共属性可以访问)
console.log(p.age); // 返回 undefined
当我们设置age为私有属性的时候,直接通过p.age的方式会发现其返回的是undeifned
。这也意味着我们无法直接对该属性进行访问。
假如我们需要访问这个属性的时候,我们需要设置一个内部的存取器(其实就是一个内置方法)来获取私有属性,具体使用方式如下:
class Person {
// 私有字段(只能在类内部访问)
#age;
constructor(name, age) {
this.name = name; // 公共属性
this.#age = age; // 私有属性
}
// 公共方法,可以访问私有字段
getAge() {
returnthis.#age;
}
}
const p = new Person("张三", 20);
console.log(p.getAge()); // ✅ 20(通过方法访问私有属性)
这里我们就是使用了一个getAge()
的函数来实现在内部返回age的信息,这种情况下是可以返回的。
假如我们希望修改私有属性的话,我们也是需要使用新方法来实现的,比如下面这样:
class Person {
// 私有字段(只能在类内部访问)
#age;
constructor(name, age) {
this.name = name; // 公共属性
this.#age = age; // 私有属性
}
// 公共方法,可以访问私有字段
getAge() {
returnthis.#age;
}
setAge(newAge) {
this.#age = newAge;
}
}
const p = new Person("张三", 20);
p.setAge(25);
console.log(p.getAge()); // 25
我们在内部再设置了一个更改age 的函数setAge(),我们只需要传入新的值到这个函数中,就可以在内部进行数值的调整。然后我们再使用getAge()方法获取修改后的年龄,我们就可以实现查询和修改私有属性的方法了!
私有方法
那其实不仅有私有的属性,其实还有私有的方法,写法也是一致的,就是在方法前加#
,比如下面的checkAge:
class Person {
// 私有字段(只能在类内部访问)
#age;
constructor(name, age) {
this.name = name; // 公共属性
this.#age = age; // 私有属性
}
// 公共方法,可以访问私有字段
getAge() {
returnthis.#age;
}
setAge(newAge) {
this.#age = newAge;
}
#checkAge(age) {
return age >= 0 && age <= 120; // 简单的年龄验证
}
}
const p = new Person("张三", 20);
p.setAge(25);
console.log(p.getAge()); // 25
console.log(p.checkAge(20)); // 报错:p.checkAge is not a function
假如我们直接在外部调用的话就会报错说函数不存在。我们只能在内部使用该函数,比如使用checkAge()
函数在setAge()
内部进行判定:
class Person {
// 私有字段(只能在类内部访问)
#age;
constructor(name, age) {
this.name = name; // 公共属性
this.#age = age; // 私有属性
}
// 公共方法,可以访问私有字段
getAge() {
returnthis.#age;
}
setAge(newAge) {
if (this.#checkAge(newAge)) { // ✅ 调用私有方法进行检查
this.#age = newAge;
} else {
console.log("❌ 年龄不合法");
}
}
#checkAge(age) {
return age >= 0 && age <= 120; // 简单的年龄验证
}
}
const p = new Person("张三", 20);
console.log(p.checkAge(20)); // 报错:p.checkAge is not a function
p.setAge(25); // 合法
console.log(p.getAge()); // 25
p.setAge(150); // ❌ 年龄不合法
console.log(p.getAge()); // 25(没改掉)
继承
继承(Inheritance)是面向对象编程(OOP)的一个核心特性。其作用是让一个类(子类)直接复用另一个类(父类)已有的属性和方法,而不需要重复写一遍。其好处在于避免重复的代码,并且能够让代码结构更清晰、层次更分明,同时子类可以扩展父类的功能。
在JavaScript中,继承使用以下的语法来实现:
class 子类 extends 父类 {
// 子类代码
}
这里的extends
关键字:表示“继承自”。子类会自动拥有父类的属性和方法(包括实例方法和原型上的方法)。
除此之外,在子类的 constructor
中,必须在使用 this
之前先调用 super()
。super()
的目的是调用父类的构造函数,使其初始化父类的属性。比如我们现在有一个父类 Person:
// 父类
class Person {
name;
gender;
constructor(name, gender) {
this.name = name;
this.gender = gender;
}
sleep() {
return `${this.name} 休息中...`;
}
}
然后当我们需要设置一个子类的时候,我们需要先继承然后设置子类的实例字段。
class David extends Person {
web;
}
然后当我们设置构造函数的时候,我们需要先调用父类的构造函数,给this.name
及 this.gender
赋值。
class David extends Person {
web;
constructor(name, gender, web) {
super(name, gender); // 调用父类构造函数,给 this.name / this.gender 赋值
this.web = web; // 子类自己的属性
}
}
那这个时候我们就已经设置好我们的构造函数,当我们创建新的实例的时候,我们需要传入三个参数进去,比如下面这样:
class David extends Person {
web;
constructor(name, gender, web) {
super(name, gender); // 调用父类构造函数,给 this.name / this.gender 赋值
this.web = web; // 子类自己的属性
}
}
// 创建子类实例
let david = new David("李剑锋", "男", "abc.com");
那创建好的子类可以直接使用父类创建好的方法sleep()的,我们可以尝试调用一下:
console.log(david.sleep());// "李剑锋 休息中..."(父类方法)
对于子类而言,其本身也可以添加新的方法进去,比如:
// 子类
class David extends Person {
web;
constructor(name, gender, web) {
super(name, gender); // 调用父类构造函数,给 this.name / this.gender 赋值
this.web = web; // 子类自己的属性
}
eat() {
return `${this.name} 正在吃饭...`;
}
}
那我们同样可以调用这个方法:
console.log(david.eat()); // "李剑锋 正在吃饭..."(子类自己的方法)
这样同样也能够实现。除此之外,子类不仅能新增自己的方法,还能重写父类方法:
// 子类
class David extends Person {
web;
constructor(name, gender, web) {
super(name, gender); // 调用父类构造函数,给 this.name / this.gender 赋值
this.web = web; // 子类自己的属性
}
eat() {
return`${this.name} 正在吃饭...`;
}
// 重写父类方法
sleep() {
return`${this.name} 在图书馆休息...`;
}
}
这个时候去进行调用的话,可以看到原本父类的方法已经被改变了。以上就是本节课的所有内容了,下节课我们将继续深入讲解JS交互的使用,敬请期待!
-- 完 --
机智流推荐阅读:
1.LangGraph 高级实战:让 AI 会记忆、能暂停、可插手的断点恢复与流式控制
3.万字长文!从 0 到 1 搭建基于 LangGraph 的 AI Agent
4.万字长文!从零开始构建你的第一个 ReAct Agent
关注机智流并加入 AI 技术交流群,不仅能和来自大厂名校的 AI 开发者、爱好者一起进行技术交流,同时还有HuggingFace每日精选论文与顶会论文解读、Talk分享、通俗易懂的Agent知识与项目、前沿AI科技资讯、大模型实战教学活动等。 在「机智流」公众号后台回复下方标红内容即可加入对应群聊:
cc | 大模型技术交流群 hf | HuggingFace 高赞论文分享群 具身 | 具身智能交流群 硬件 | AI 硬件交流群 智能体 | Agent 技术交流群