1. 从生活角度看JavaScript的面向对象
想象一下我们生活的世界,充满了各种各样的"东西 "(也就是对象 Object)。比如,你、我、一条狗、一辆汽车、一座房子等等,这些都是具体的、独立存在的事物。
面向对象编程,就是模仿我们认识世界的方式来组织代码。它主要有几个核心概念:
-
对象 (Object):万物皆对象
-
生活例子: 就拿"一条狗"来说吧。比如你家有一条叫"旺财"的金毛犬。这个具体的"旺财"就是一个对象。
-
JS 对应: 在JS里,你可以创建一个代表"旺财"的对象:
jslet wangcai = { name: "旺财", breed: "金毛", age: 3, color: "金色", bark: function() { console.log("汪汪汪!"); }, wagTail: function() { console.log("旺财开心地摇尾巴"); } };
-
-
属性 (Property):对象的特征
- 生活例子: "旺财"有哪些特征呢?它有名字("旺财")、品种("金毛")、年龄(3岁)、颜色("金色")。这些描述它状态和特征的东西,就是它的属性。
- JS 对应: 在上面的 wangcai 对象里,name, breed, age, color 就是它的属性。你可以通过 wangcai.name 来获取它的名字。
-
方法 (Method):对象的行为
- 生活例子: "旺财"能做什么?它会"叫"(bark)、会"摇尾巴"(wagTail)、会"跑"、会"吃"。这些它能执行的动作或行为,就是它的方法。
- JS 对应: 在 wangcai 对象里,bark 和 wagTail 这两个 function 就是它的方法。你可以通过 wangcai.bark() 来让它执行"叫"这个动作。
-
封装 (Encapsulation):打包与隐藏细节
- 生活例子: 想象一下"手机 "。它把打电话、发短信、上网等功能(方法)和里面的芯片、电池、屏幕等(属性/内部状态)都封装在一个壳子里。你只需要按按钮(调用方法),不需要关心内部电路是怎么工作的。手机保护了内部的复杂性,只提供简单的接口给你使用。
- JS 对应: 对象就是一种封装。它把描述这个事物的数据(属性)和能对这些数据进行操作的行为(方法)打包在一起。使用者只需要知道这个对象有什么属性、能调用什么方法,而不需要关心方法内部的具体实现逻辑。这让代码更模块化,更容易管理。
-
类 (Class):对象的蓝图或模板 (ES6 引入)
-
生活例子: 世界上有很多狗,不仅仅是"旺财"。虽然每条狗都是独立的个体(对象),但它们都共享一些共同的特征(都有名字、品种、年龄)和行为(都会叫、摇尾巴)。我们可以抽象出一个"狗 "的概念 或蓝图 。这个蓝图规定了"狗"应该有哪些基本属性和行为。建筑师画的"房屋设计图"也是一个蓝图,可以根据这张图建造很多栋相似但又略有不同的房子(对象)。
-
JS 对应: ES6 引入了 class 关键字,它就像一个模板或蓝图。你可以定义一个 Dog 类,规定所有"狗"对象都应该有 name, breed, age 属性,以及 bark 和 wagTail 方法。然后,你可以根据这个 Dog 类,创建出很多条具体的狗对象(比如 wangcai, xiaohei 等)。
-
js
class Dog {
constructor(name, breed, age, color) { // 构造函数,相当于出生时就设定好的属性
this.breed = breed;
this.age = age;
this.color = color;
}
bark() {
console.log(`${this.name} 在汪汪叫!`);
}
wagTail() {
console.log(`${this.name} 开心地摇尾巴`);
}
}
// 根据 Dog 蓝图创建具体的狗对象
let wangcai = new Dog("旺财", "金毛", 3, "金色");
let xiaohei = new Dog("小黑", "拉布拉多", 2, "黑色");
wangcai.bark(); // 输出: 旺财 在汪汪叫!
xiaohei.wagTail(); // 输出: 小黑 开心地摇尾巴
(注:在 ES6 之前,JS 主要通过构造函数和原型链来实现类似功能,class 是更清晰的语法糖)
-
继承 (Inheritance):基于现有蓝图创建新蓝图
- 生活例子: "狗"是一种"动物 "。"动物"有更普遍的特征(比如需要吃东西、会动)。"狗"继承了"动物"的这些基本特征,并添加了自己独特的特征(比如"汪汪叫")。同样,"金毛犬"是"狗"的一种,它继承了"狗"的所有特征,可能还有自己更具体的特征(比如性格温顺)。这就像儿子可能继承父亲的某些相貌特征一样。
- JS 对应: 一个类可以继承另一个类。比如,你可以创建一个 Animal 类,然后让 Dog 类继承 Animal 类。这样,Dog 类就自动拥有了 Animal 类定义的所有属性和方法(比如 eat() 方法),并且可以添加自己特有的方法(如 bark())。这实现了代码的复用,避免重复编写通用功能。
-
多态 (Polymorphism):同一种指令,不同的表现
- 生活例子: 你对不同的动物下达"发出声音"的指令。狗会"汪汪叫",猫会"喵喵叫",牛会"哞哞叫"。同一个指令("发出声音"),不同的对象(狗、猫、牛)会做出不同的具体行为。
- JS 对应: 不同的对象可以对同一个方法调用做出不同的响应。比如,如果你有一个 animal.makeSound() 方法,那么 dog.makeSound() 可能输出 "汪汪",cat.makeSound() 可能输出 "喵喵"。这让代码更灵活,可以处理不同类型的对象,只要它们都实现了某个约定的接口(方法)。
总结一下:
- 对象是具体的东西(一条狗、一辆车)。
- 属性是东西的特征(狗的名字、车的颜色)。
- 方法是东西能做的事(狗会叫、车能开)。在其场景下也可以称为函数
- 封装是把特征和行为打包在一起,隐藏内部复杂性(像手机一样)。
- 类是制造同类东西的蓝图(狗的设计图、房屋设计图)。
- 继承是基于现有蓝图创造新蓝图,实现复用(狗继承自动物)。
- 多态是同一种指令,不同东西有不同反应(都叫"发出声音",狗汪汪,猫喵喵)。
js
2. 常见创建对象的方式
创建一个对象,对一个人进行抽象(描述)
方法1:通过new Object()创建
这种方法以前会经常使用,但是现在有些麻烦。我们有了更方便的方法
js
var obj = new Object()
obj.name = 'why'
obj.age = 18
obj.height = 1.88
obj.running = function () {
console.log(this.name + '跑步中🚀')
}
方法2: 通过 var obj = {}
js
var obj = {
name: 'why',
age: 18,
height: 1.88,
eating: function() {
console.log(this.name + '吃饭😮')
}
}
3. 对对象属性的操作
js
var obj = {
name: 'why',
age: 18,
running: function () {
console.log(this.name + '在跑步🏃➡️')
}
}
//获取属性
console.log(obj.name)
console.log(obj.running())
//给属性赋值
obj.name = 'wei'
console.log(obj.name)
//删除属性
delete obj.name
console.log(obj)

但是有弊端,没有办法对属性进行更详细的控制。例如:这个属性不能删除,不能重新赋值等
js中给了我们属性描述符 Object.defineProperty
来对属性进行添加或者修改
4. 使用Object.defineProperty等对属性进行更细致的操作
Object.defineProperty
是一个方法
Object.defineProperty(obj, prop, descriptor)
接受三个参数
-
obj定义要被操作的对象
-
prop要被修改的obj中对象的属性,如果没有这个属性会被自动创建
-
descriptor要定义或修改的属性描述符,同时它也是个对象
补充- 它会有返回值:被传递给函数的对象
js
var obj = {
name: 'why',
age: 18
}
Object.defineProperty(obj, 'height', {
value: 1.88
})
console.log(obj) //{ name: 'why', age: 18 }
console.log(obj.height) //1.88
-
为什么打印obj的时候无法看到,而使用obj.height就可以看到
-
可以看到且不为undefined。就说明height真实存在
-
有某种东西限制了height
数据属性描述符的具体的方法
-
在属性描述符中,有两种方案分别是:存取属性描述符和数据属性描述符
-
两种描述符不能共存,但是他们都有
configurable
,enumerable

对比点 | 数据属性 | 存取属性(访问器) |
---|---|---|
定义的方式 | 使用 value 定义具体值 |
使用 get() 和 set() 定义读取和写入行为 |
是否有 setter/getter | ❌ 没有 | ✅ 有 getter/setter 函数 |
是否有 writable |
✅ 有 | ❌ 没有(不适用) |
是否有 value |
✅ 有 | ❌ 没有(不适用) |
适合的场景 | 储存静态值 | 控制属性行为、封装逻辑、依赖其他属性 |
是否可以用作计算属性 | ❌ 不可以 | ✅ 可以,如 fullName = first + last |
-
value:
- 属性的值。可以是任何有效的 JavaScript 值 (数字, 对象, 函数等)。
-
writable:
- 一个布尔值,表示属性的 value 是否可以通过赋值运算符 (=) 来修改。
- true: 值可以被修改。
- false: 只是只读的。尝试修改会静默失败(非严格模式)或抛出 TypeError(严格模式)。
-
enumerable:
- 一个布尔值,表示该属性是否会出现在对象的属性枚举中。
- true: 属性会出现在 for...in 循环、Object.keys()、Object.values()、Object.entries() 和 JSON.stringify() 的结果中(只要 value 不是 undefined、function 或 symbol)。
- false: 属性不会被枚举
-
configurable:
-
一个布尔值,表示该属性的描述符本身是否可以被修改 ,以及该属性是否可以从对象中删除。
-
true:
- 可以删除该属性 (delete obj.prop)。
- 可以修改该属性的特性(writable, enumerable, configurable)。
- 可以将数据属性更改为访问器属性(反之亦然)。
-
false:
- 不能删除该属性。
- 不能修改 configurable 和 enumerable 特性。
- 例外: 如果 writable 当前为 true,则可以将 writable 修改为 false。
- 例外: 如果 writable 当前为 true 或 false,只要当前值和新值相同,或者属性是可写的 (writable: true),则可以修改 value。(即:将 configurable: false 且 writable: false 的属性的值更改为另一个值会失败)。
-
默认值
字段名 | 含义 |
---|---|
value |
属性的值 |
writable |
是否可以修改属性的值 |
enumerable |
是否可以通过 for...in 或 Object.keys() 枚举该属性 |
configurable |
是否可以删除或修改描述符(如变成访问器属性,或修改 writable 等) |
不同情况下,有不同的默认值
数据描述符 | configurable | enumerable | writable |
---|---|---|---|
直接在对象中定义属性时 | true | true | true |
通过属性描述符定义属性时 | false | false | false |
js
const obj = {};
// 1. 使用 defineProperty 定义一个数据属性
Object.defineProperty(obj, 'readOnlyProp', {
value: 100,
writable: false, // 不可写 (默认也是 false)
enumerable: true, // 可枚举 (覆盖默认的 false)
configurable: false // 不可配置 (默认也是 false)
});
console.log(obj.readOnlyProp); // 输出: 100
存取属性描述符的具体的方法
字段名 | 含义 |
---|---|
get |
一个返回属性值的函数 |
set |
一个设置属性值的函数 |
enumerable |
是否可以通过 for...in 或 Object.keys() 枚举该属性 |
configurable |
是否可以删除或修改描述符 |
使用场景:
1. 隐藏某个私有属性,希望它不能够被外界使用和赋值
js
var obj = {
name: 'why',
age: 18,
_address: 'zhengzhou'
}
Object.defineProperty(obj, 'address', {
enumerable: true, // 在 for...in 循环和 Object.keys() 中会显示
configurable: true, // 以后可以被删除或重新定义
get: function() { // GETTER (读取器): 当你读取 obj.address 时调用
// console.log('访问了 address 的 getter'); // 加上这句更清晰
return this._address; // 读取 obj._address 的值并返回
},
set: function(value) { // SETTER (设置器): 当你给 obj.address 赋值时调用 (例如 obj.address = ...)
// console.log('访问了 address 的 setter,值为:', value); // 加上这句更清晰
this._address = value; // 将新值写入 obj._address
}
})
console.log(obj._address) //zhengzhou
obj._address = 'shanghai'
console.log(obj._address) //shanghai
console.log(obj.address) //shanghia
2. 如果我们希望截获某一个属性它的访问和设置值的过程
js
var obj = {
name: 'why',
age: 18,
_address: 'zhengzhou'
}
function foo() {
console.log('获取一次address的值')
}
function bar() {
console.log('设置了address的值')
}
Object.defineProperty(obj, 'address', {
enumerable: true,
configurable: true,
get: function() {
foo()
return this._address;
},
set: function(value) {
bar()
this._address = value;
}
})
console.log(obj.address)
obj.address = 'shanghai'
console.log(obj._address)
console.log(obj.address)
打印:
js
获取一次address的值
zhengzhou
设置了address的值
shanghai
获取一次address的值
shanghai
5. 获取属性描述符
Object.getOwnPropertyDescriptor(对象, 对象中的某个属性);
Object.getOwnPropertyDescriptors(对象)
js
const person = {
firstName: 'Tom',
lastName: 'Lee',
get fullName() {
return this.firstName + ' ' + this.lastName;
}
};
const desc = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(desc);
/*
{
get: [Function: get],
set: undefined,
enumerable: true,
configurable: true
}
*/
const allDesc = Object.getOwnPropertyDescriptors(person)
console.log(allDesc)
/*
{
firstName: {
value: 'Tom',
writable: true,
enumerable: true,
configurable: true
},
lastName: {
value: 'Lee',
writable: true,
enumerable: true,
configurable: true
},
fullName: {
get: [Function: get fullName],
set: undefined,
enumerable: true,
configurable: true
}
}*/
6. Object的方法对对象进行限制
我们可以使用上面的属性描述符,一个接一个设置。但是这样太麻烦了,于是我们使用js中对对象整体进行限制的。其中有一些方法无法通过属性描述符来替代完成
禁止对象继续添加新的属性 Object.preventExtensions(对象)
禁止对象配置/删除里面的属性 Object.seal(对象)
让属性不可以修改(writable: false) Object.freeze(对象)