2 认识js的面向对象 和 Object 的属性描述符

1. 从生活角度看JavaScript的面向对象

想象一下我们生活的世界,充满了各种各样的"东西 "(也就是对象 Object)。比如,你、我、一条狗、一辆汽车、一座房子等等,这些都是具体的、独立存在的事物。

面向对象编程,就是模仿我们认识世界的方式来组织代码。它主要有几个核心概念:

  1. 对象 (Object):万物皆对象

    • 生活例子: 就拿"一条狗"来说吧。比如你家有一条叫"旺财"的金毛犬。这个具体的"旺财"就是一个对象。

    • JS 对应: 在JS里,你可以创建一个代表"旺财"的对象:

      js 复制代码
      let wangcai = {
        name: "旺财",
        breed: "金毛",
        age: 3,
        color: "金色",
        bark: function() {
          console.log("汪汪汪!");
        },
        wagTail: function() {
          console.log("旺财开心地摇尾巴");
        }
      };
          
  2. 属性 (Property):对象的特征

    • 生活例子: "旺财"有哪些特征呢?它有名字("旺财")、品种("金毛")、年龄(3岁)、颜色("金色")。这些描述它状态和特征的东西,就是它的属性
    • JS 对应: 在上面的 wangcai 对象里,name, breed, age, color 就是它的属性。你可以通过 wangcai.name 来获取它的名字。
  3. 方法 (Method):对象的行为

    • 生活例子: "旺财"能做什么?它会"叫"(bark)、会"摇尾巴"(wagTail)、会"跑"、会"吃"。这些它能执行的动作或行为,就是它的方法
    • JS 对应: 在 wangcai 对象里,bark 和 wagTail 这两个 function 就是它的方法。你可以通过 wangcai.bark() 来让它执行"叫"这个动作。
  4. 封装 (Encapsulation):打包与隐藏细节

    • 生活例子: 想象一下"手机 "。它把打电话、发短信、上网等功能(方法)和里面的芯片、电池、屏幕等(属性/内部状态)都封装在一个壳子里。你只需要按按钮(调用方法),不需要关心内部电路是怎么工作的。手机保护了内部的复杂性,只提供简单的接口给你使用。
    • JS 对应: 对象就是一种封装。它把描述这个事物的数据(属性)和能对这些数据进行操作的行为(方法)打包在一起。使用者只需要知道这个对象有什么属性、能调用什么方法,而不需要关心方法内部的具体实现逻辑。这让代码更模块化,更容易管理。
  5. 类 (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 是更清晰的语法糖)

  1. 继承 (Inheritance):基于现有蓝图创建新蓝图

    • 生活例子: "狗"是一种"动物 "。"动物"有更普遍的特征(比如需要吃东西、会动)。"狗"继承了"动物"的这些基本特征,并添加了自己独特的特征(比如"汪汪叫")。同样,"金毛犬"是"狗"的一种,它继承了"狗"的所有特征,可能还有自己更具体的特征(比如性格温顺)。这就像儿子可能继承父亲的某些相貌特征一样。
    • JS 对应: 一个类可以继承另一个类。比如,你可以创建一个 Animal 类,然后让 Dog 类继承 Animal 类。这样,Dog 类就自动拥有了 Animal 类定义的所有属性和方法(比如 eat() 方法),并且可以添加自己特有的方法(如 bark())。这实现了代码的复用,避免重复编写通用功能。
  2. 多态 (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
  1. value:

    • 属性的值。可以是任何有效的 JavaScript 值 (数字, 对象, 函数等)。
  2. writable:

    • 一个布尔值,表示属性的 value 是否可以通过赋值运算符 (=) 来修改。
    • true: 值可以被修改。
    • false: 只是只读的。尝试修改会静默失败(非严格模式)或抛出 TypeError(严格模式)。
  3. enumerable:

    • 一个布尔值,表示该属性是否会出现在对象的属性枚举中。
    • true: 属性会出现在 for...in 循环、Object.keys()、Object.values()、Object.entries() 和 JSON.stringify() 的结果中(只要 value 不是 undefined、function 或 symbol)。
    • false: 属性不会被枚举
  4. 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...inObject.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...inObject.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(对象)

相关推荐
学Java的bb4 小时前
JavaWeb-后端Web实战(IOC + DI)
前端
pe7er4 小时前
React Native 多环境配置全攻略:环境变量、iOS Scheme 和 Android Build Variant
前端·react native·react.js
柯北(jvxiao)5 小时前
Vue vs React 多维度剖析: 哪一个更适合大型项目?
前端·vue·react
JefferyXZF5 小时前
Next.js 中间件:掌握请求拦截与处理的核心机制(六)
前端·全栈·next.js
知识分享小能手5 小时前
Vue3 学习教程,从入门到精通,Vue 3 + Tailwind CSS 全面知识点与案例详解(31)
前端·javascript·css·vue.js·学习·typescript·vue3
石小石Orz5 小时前
React生态蓝图梳理:前端、全栈与跨平台全景指南
前端
袁煦丞6 小时前
8.12实验室 指尖魔法变出艺术感 Excalidraw:cpolar内网穿透实验室第495个成功挑战
前端·程序员·远程工作
烛阴6 小时前
Dot
前端·webgl
Gene_20226 小时前
使用行为树控制机器人(三) ——通用端口
前端·机器人
excel7 小时前
JavaScript 中的二进制数据:ArrayBuffer 与 SharedArrayBuffer 全面解析
前端