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)。
九、总结:对象的“核心心法”
- 对象是属性的键值对集合,属性值可以是任意类型,函数属性叫方法;
- 属性访问用点符号(简洁)或方括号(灵活),方括号支持动态属性名;
- 创建对象优先用字面量,批量创建用构造函数,需要继承用Object.create;
- this的指向取决于“调用方式”,对象调用时指向对象,单独调用时需绑定;
- getter/setter控制属性读写,delete删除自身属性;
- 对象比较是引用比较,属性值相同≠对象相等。
对象是JS的核心,掌握它的用法,能让你轻松处理复杂数据结构和逻辑。下一篇笔记,我们会深入原型链和继承,解锁JS面向对象的高级玩法。
1657

被折叠的 条评论
为什么被折叠?



