JavaScript学习笔记:11.对象

JavaScript学习笔记:11.对象

上一篇吃透了集合这个“高级收纳工具”,这一篇来攻克JS的核心支柱——对象(Object)。如果说数组是“有序收纳箱”,集合是“专用收纳工具”,那对象就是“万能容器”——能装属性、装方法、装数据,甚至能装其他对象,就像现实中的“多功能工具箱”,几乎所有复杂逻辑都离不开它。

新手常栽在对象的“坑”里:比如用方括号访问属性时忘了加引号,this指向“飘忽不定”,创建多个同类型对象时重复写代码,或是以为两个“长得一样”的对象相等。今天就用“生活化比喻+实战避坑”的方式,把对象的本质、用法、高级特性讲透,让你既能“用好对象”,又能“避开陷阱”。

一、先搞懂:对象的本质——属性的“键值对集合”

JS里的对象,本质是一系列属性的集合。每个属性都包含“键(名称)”和“值”,值可以是任意数据类型(数字、字符串、数组、函数等)。如果属性值是函数,这个属性就叫“方法”。

可以把对象比作“你的手机”:

  • 属性:品牌(“苹果”)、型号(“iPhone 15”)、内存(256)、是否开机(true)—— 描述手机的特征;
  • 方法:打电话()、发微信()、拍照()—— 手机能做的动作。

用代码表示就是:

const myPhone = {
  // 属性:键: 值
  brand: "苹果",
  model: "iPhone 15",
  storage: 256,
  isOn: true,
  // 方法:键: 函数
  call: function (name) {
    console.log(`${name}打电话`);
  },
  takePhoto: () => console.log("拍了一张照片")
};

核心特性:属性的“键”其实是字符串

你以为属性键可以是数字?其实JS会自动把非字符串键转为字符串。比如:

const obj = {
  123: "数字键",
  [true]: "布尔键",
  [{}]: "对象键"
};

console.log(Object.keys(obj)); // ["123", "true", "[object Object]"]

这就像你给工具箱贴标签,不管标签是数字还是贴纸,最终都会被统一整理成“文字标签”。

二、属性访问:两种方式,各有千秋

访问对象属性有两种核心方式:点符号(.)方括号([]),就像开门的两把钥匙,各有适用场景。

1. 点符号:简洁直接,“亲儿子待遇”

点符号是最常用的方式,简洁明了,但有个限制:属性键必须是“合法标识符”(不能有空格、连字符,不能以数字开头)。

// 访问属性
console.log(myPhone.brand); // "苹果"
// 调用方法
myPhone.call("张三"); // "给张三打电话"
// 修改属性
myPhone.storage = 512;
// 添加新属性
myPhone.color = "黑色";

2. 方括号:灵活万能,“万能钥匙”

方括号没有标识符限制,支持动态属性名、特殊字符属性名,甚至可以用变量作为属性名,堪称“万能钥匙”。

const obj = {
  "phone-number": "13800138000", // 特殊字符属性名,只能用方括号访问
  "": "空字符串键"
};

// 访问特殊字符属性名
console.log(obj["phone-number"]); // "13800138000"
// 访问空字符串键
console.log(obj[""]); // "空字符串键"

// 动态属性名:用变量作为键
const key = "brand";
console.log(myPhone[key]); // "苹果"(等价于myPhone.brand)

// 动态添加属性
const dynamicKey = "price";
obj[dynamicKey] = 9999;
console.log(obj.price); // 9999

避坑点:方括号里的“字符串陷阱”

很多新手会犯这样的错:用变量作为属性名时,不小心加了引号,导致访问失败:

const key = "brand";
// 反面例子:加了引号,变成访问"key"属性,而不是变量key的值
console.log(myPhone["key"]); // undefined
// 正面例子:不加引号,使用变量值作为键
console.log(myPhone[key]); // "苹果"

三、创建对象:三种方式,按需选择

JS创建对象有三种核心方式,就像“造房子”的三种方案:现成的毛坯房(对象字面量)、批量生产的商品房(构造函数)、定制化别墅(Object.create)。

1. 对象字面量({}):开发首选,简洁高效

这是最常用的方式,就像“拎包入住的毛坯房”,直接定义属性和方法,适合创建单个、结构简单的对象。

const person = {
  name: "张三",
  age: 25,
  sayHi() {
    console.log(`你好,我是${this.name}`);
  }
};

优势:语法简洁,一目了然;缺点:创建多个同类型对象时,代码重复。

2. 构造函数:批量生产“同类型对象”

如果需要创建多个结构相同的对象(比如多个用户、多个商品),用构造函数就像“批量建商品房”,一次定义,多次实例化。

// 1. 定义构造函数(首字母大写,约定俗成)
function Person(name, age) {
  // this指向新创建的对象
  this.name = name;
  this.age = age;
  this.sayHi = function () {
    console.log(`你好,我是${this.name}`);
  };
}

// 2. 用new关键字创建实例
const person1 = new Person("张三", 25);
const person2 = new Person("李四", 30);

person1.sayHi(); // "你好,我是张三"
person2.sayHi(); // "你好,我是李四"

优势:批量创建同类型对象,减少重复代码;缺点:每个实例的方法都是独立的,浪费内存(可通过原型优化,后续笔记详解)。

3. Object.create:指定原型的“定制化对象”

Object.create允许你指定新对象的“原型”,就像“定制别墅”,可以继承原型对象的属性和方法,适合实现继承。

// 原型对象
const animal = {
  type: "动物",
  eat() {
    console.log("吃东西");
  }
};

// 创建新对象,原型是animal
const cat = Object.create(animal);
cat.name = "小花"; // 自身属性

console.log(cat.type); // "动物"(继承自原型)
cat.eat(); // "吃东西"(继承自原型)

优势:灵活指定原型,实现继承;缺点:语法稍复杂,不适合简单场景。

对比总结:该选哪种方式?

创建方式适用场景优点缺点
对象字面量单个、简单对象简洁高效,易读批量创建重复代码
构造函数多个同类型对象批量创建,结构统一方法重复,浪费内存(可优化)
Object.create需要继承原型灵活实现继承语法复杂,不适合入门场景

四、对象的“隐藏技能”:属性的枚举与继承

1. 枚举属性:遍历对象的“三种姿势”

枚举属性就是“遍历对象的所有属性”,JS提供了三种常用方法,它们的“能力范围”不同:

const obj = { a: 1, b: 2 };
// 给obj添加不可枚举属性
Object.defineProperty(obj, "c", {
  value: 3,
  enumerable: false // 不可枚举
});
// 原型对象的属性(继承属性)
obj.__proto__.d = 4;

// 姿势1:for...in → 遍历自身+原型链的可枚举属性
for (const key in obj) {
  console.log(key); // "a", "b", "d"(c不可枚举,d是继承属性)
}

// 姿势2:Object.keys → 遍历自身的可枚举属性
console.log(Object.keys(obj)); // ["a", "b"](不包含c和d)

// 姿势3:Object.getOwnPropertyNames → 遍历自身的所有属性(无论是否可枚举)
console.log(Object.getOwnPropertyNames(obj)); // ["a", "b", "c"](不包含继承属性d)

避坑点:for…in会遍历原型链属性,容易出现意外,遍历自身属性优先用Object.keys或Object.getOwnPropertyNames。

2. 继承:对象的“血脉传承”

所有JS对象都有一个“原型”(proto),原型也是对象,会继承原型的属性和方法,这就是“原型链”。比如:

const arr = [1, 2, 3];
// arr继承自Array.prototype,所以能调用push、forEach等方法
arr.push(4); // 继承的方法
console.log(arr.__proto__ === Array.prototype); // true
// Array.prototype继承自Object.prototype
console.log(Array.prototype.__proto__ === Object.prototype); // true

简单说:对象的属性和方法,不仅可以自己有,还能“继承”自原型,这也是JS实现面向对象的核心。

五、方法与this:对象的“动作”与“指向”

对象的方法是“值为函数的属性”,而this是方法里的“特殊关键字”,指向“调用方法的对象”—— 这部分是新手的重灾区,因为this的指向“像爱情一样善变”。

1. 方法的定义:两种简洁写法

const person = {
  name: "张三",
  // 普通写法
  sayHi: function () {
    console.log(`你好,我是${this.name}`);
  },
  // 简洁写法(ES6+,推荐)
  sayHello() {
    console.log(`Hello, ${this.name}`);
  }
};

person.sayHi(); // "你好,我是张三"(this指向person)
person.sayHello(); // "Hello, 张三"(this指向person)

2. this的“指向陷阱”:脱离对象调用方法

当方法脱离对象,单独调用时,this的指向会变(非严格模式下指向全局对象,严格模式下指向undefined):

const person = {
  name: "张三",
  sayHi() {
    console.log(`你好,我是${this.name}`);
  }
};

// 反面例子:单独调用方法,this指向全局
const hi = person.sayHi;
hi(); // "你好,我是undefined"(浏览器中this指向window,window.name为空)

// 正面例子:绑定对象调用
hi.call(person); // "你好,我是张三"(用call指定this指向person)

避坑点:不要单独提取对象的方法调用,如需提取,用call、apply、bind绑定this指向。

六、getter与setter:属性的“读写控制器”

getter和setter是特殊的属性,用来控制属性的“读取”和“赋值”,就像给属性装了“智能门锁”,可以在读写时做额外逻辑。

1. 基本用法:定义getter和setter

const temperature = {
  _celsius: 0, // 下划线表示“私有属性”(约定俗成,非真正私有)
  
  // getter:读取fahrenheit时触发
  get fahrenheit() {
    return this._celsius * 9/5 + 32; // 摄氏度转华氏度
  },
  
  // setter:给fahrenheit赋值时触发
  set fahrenheit(value) {
    this._celsius = (value - 32) * 5/9; // 华氏度转摄氏度,存到_celsius
  }
};

// 读取fahrenheit,触发getter
console.log(temperature.fahrenheit); // 32(0℃对应32℉)

// 给fahrenheit赋值,触发setter
temperature.fahrenheit = 68;
console.log(temperature._celsius); // 20(68℉对应20℃)

优势:可以隐藏内部逻辑,控制属性的读写,比如数据转换、验证。

2. 用Object.defineProperty定义getter/setter

除了对象字面量,还可以用Object.defineProperty给已存在的对象添加getter/setter:

const person = {
  name: "张三"
};

// 给person添加age属性,带getter/setter
Object.defineProperty(person, "age", {
  get() {
    return this._age || 18; // 默认18岁
  },
  set(value) {
    if (value >= 0 && value <= 120) {
      this._age = value;
    } else {
      console.log("年龄不合法");
    }
  }
});

console.log(person.age); // 18(默认值)
person.age = 25;
console.log(person.age); // 25(合法赋值)
person.age = 150; // "年龄不合法"(赋值失败)

七、删除属性:delete的“正确用法”

delete操作符用来删除对象的“自身属性”,注意:不能删除继承属性,也不能删除var声明的全局变量。

const obj = { a: 1, b: 2 };
// 删除自身属性a
delete obj.a;
console.log(obj.a); // undefined
console.log(obj.b); // 2

// 不能删除继承属性
delete obj.toString; // 无效,toString是继承自Object.prototype的方法
console.log(obj.toString); // 函数本身

// 删除非var声明的全局变量
c = 3; // 非var声明,全局对象的属性
delete c;
console.log(c); // ReferenceError: c is not defined

// 不能删除var声明的全局变量
var d = 4;
delete d;
console.log(d); // 4(删除无效)

避坑点:delete只是删除属性,不会删除属性的值对应的内存(垃圾回收会处理),且对数组的length无影响。

八、比较对象:“长得一样”≠“真的一样”

JS中对象是“引用类型”,比较的是“引用地址”,而不是“属性值”—— 就像两个长得一模一样的双胞胎,身份证号不同,就是两个人。

const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };
const obj3 = obj1; // obj3和obj1指向同一个对象

console.log(obj1 === obj2); // false(引用不同)
console.log(obj1 === obj3); // true(引用相同)

// 想要比较属性值是否相同,需要手动遍历
function isEqual(objA, objB) {
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
  if (keysA.length !== keysB.length) return false;
  return keysA.every(key => objA[key] === objB[key]);
}

console.log(isEqual(obj1, obj2)); // true(属性值相同)

避坑点:不要用===比较两个不同实例的对象,如需比较属性值,需手动实现或使用工具库(如Lodash的isEqual)。

九、总结:对象的“核心心法”

  1. 对象是属性的键值对集合,属性值可以是任意类型,函数属性叫方法;
  2. 属性访问用点符号(简洁)或方括号(灵活),方括号支持动态属性名;
  3. 创建对象优先用字面量,批量创建用构造函数,需要继承用Object.create;
  4. this的指向取决于“调用方式”,对象调用时指向对象,单独调用时需绑定;
  5. getter/setter控制属性读写,delete删除自身属性;
  6. 对象比较是引用比较,属性值相同≠对象相等。

对象是JS的核心,掌握它的用法,能让你轻松处理复杂数据结构和逻辑。下一篇笔记,我们会深入原型链和继承,解锁JS面向对象的高级玩法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿蒙Armon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值