前言
在JavaScript的世界里,有一句广为人知的话——“万物皆对象” 。无论是我们日常使用的字符串、数字,还是自定义的函数、数组,本质上都和“对象”有着千丝万缕的联系。但很多新手在学习对象时,总会被“构造函数”“new关键字”“包装类”这些概念绕晕,今天就结合具体代码实例,用通俗易懂的语言,把JS对象的核心知识点讲明白、讲透彻。
一、先搞懂:什么是对象?
对象(Object)是JS中最核心的数据类型之一,它就像一个“收纳盒”,可以存放多个不同类型的数据(我们称之为“属性”)和可执行的操作(我们称之为“方法”)。比如一个“人”可以有姓名、年龄(属性),可以吃饭、睡觉(方法);一辆车可以有颜色、尺寸(属性),可以行驶、刹车(方法),对象就是用来描述这种“具有多种特征的事物”的。
在JS中,数据类型分为两大类,这也是理解对象的基础:
-
原始类型:简单数据类型,无法拥有属性和方法,包括:string(字符串)、number(数字)、bool(布尔值)、undefined(未定义)、null(空值)、Symbol(唯一值)、bigInt(大整数)。
-
引用类型:复杂数据类型,本质上都是对象,能拥有属性和方法,包括:函数(function)、数组(array)、普通对象(object)等。
这里要注意一个易错点:原始类型一定无法添加属性和方法,属性和方法是对象独有的,后面我们会通过“包装类”详细解释这一点
二、3种创建对象的方式
日常开发中,我们创建对象的方式主要有3种,各自有不同的使用场景,结合代码实例一看就懂!
1. 对象字面量 {}|最简洁常用
这是最直观、最常用的创建方式,用大括号 {} 包裹属性和值,属性和值之间用冒号 : 分隔,多个属性之间用逗号 , 分隔,适合创建单个、简单的对象。
// 实例1:创建一个“人物”对象
const person = {
name: '米奇', // 属性:姓名,值为字符串
age: 18, // 属性:年龄,值为数字
sayHi: function() { // 方法:打招呼
console.log('你好呀,我是米奇~');
}
}
console.log(person); // 输出:{ name: '米奇', age: 18, sayHi: [Function: sayHi] }
person.sayHi(); // 调用方法,输出:你好呀,我是米奇~
// 实例2:创建一个空对象,后续添加属性
const obj = {};
obj.name = '米妮';
obj.age = 17;
console.log(obj); // 输出:{ name: '米妮', age: 17 }
2. new Object()|构造函数创建 🛠️
这是通过JS内置的构造函数 Object() 创建对象,本质和对象字面量一样,只是写法更繁琐,适合动态添加属性的场景。
// 实例:用new Object()创建对象,并动态添加属性
const obj = new Object(); // 先创建一个空对象
obj.name = '米妮'; // 给对象添加name属性
const abc = 'age'; // 用变量作为属性名
obj[abc] = 18; // 动态添加age属性(括号语法适合属性名是变量的场景)
console.log(obj); // 输出:{ name: '米妮', age: 18 }
// delete关键字:删除对象的属性
delete obj[abc]; // 删除age属性
console.log(obj); // 输出:{ name: '米妮' }
这里补充一个小知识点:delete 关键字只能删除对象的属性,不能删除变量;而且删除后,再访问该属性会返回 undefined。
3. new 调用自定义构造函数|批量创建对象
当我们需要批量创建多个结构相同的对象(比如多个用户、多辆车)时,用前面两种方式就太繁琐了——这时候,自定义构造函数就派上用场了!
先明确一个概念:当一个函数被 new 关键字调用时,这个函数就被称为“构造函数”。构造函数的命名规范是:首字母大写(区分普通函数),目的是为了批量生成具有相同属性结构的对象。
// 实例1:自定义构造函数Person,批量创建人物对象
function Animal(name, age, role) {
// this指向当前创建的对象,给对象添加属性
this.name = name;
this.age = age;
this.role = role;
// 也可以添加方法
this.introduce = function() {
console.log(`我是${this.name},今年${this.age}岁,我的角色是${this.role}`);
}
}
// new调用构造函数,创建2个实例对象
const p1 = new Animal('米奇', 8, 'animal');
const p2 = new Animal('跳跳虎', 9, 'tigger');
console.log(p1); // 输出:Animal { name: '米奇', age: 8, role: 'animal', introduce: [Function (anonymous)] }
console.log(p2); // 输出:Animal { name: '跳跳虎', age: 9, role: 'tigger', introduce: [Function (anonymous)] }
p1.introduce(); // 调用方法,输出:我是米奇,今年8岁,我的角色是animal
// 实例2:自定义构造函数Car,批量创建汽车对象
function Car(color) {
// 固定属性(所有汽车都一样)
this.name = 'su7';
this.height = '1400';
this.lang = '4800';
this.weight = '1500';
// 动态属性(不同汽车可以不一样)
this.color = color;
}
// 批量创建2辆汽车
const car1 = new Car('purple'); // 紫色的su7
const car2 = new Car('pink'); // 粉色的su7
const car3 = Car('pink'); // 粉色的su7
console.log(car1); // 输出:Car { name: 'su7', height: '1400', lang: '4800', weight: '1500', color: 'purple' }
易错点提醒:构造函数必须用 new 调用!如果忘记写 new,函数会变成普通函数,this 会指向全局(浏览器中是window,Node环境中是global),此时返回值是undefined。
错误示例:忘记new,调用Car函数 const car3 = Car(black); console.log(car3); // 输出:undefined(因为普通函数没有return值)
三、重点突破:new关键字到底干了什么? 🔍
很多新手都会疑惑:为什么用 new 调用构造函数,就能创建出对象?其实 new 关键字背后只做了3件事,简单好记,结合代码拆解:
-
创建一个空的this对象:相当于执行
const _this = {},这个空对象就是未来要返回的实例对象。 -
执行构造函数中的代码,给this对象添加属性:把构造函数中
this.xxx的内容,都添加到第一步创建的空对象上。 -
返回这个this对象:不需要我们手动写return,new会自动把添加好属性的this对象返回,赋值给变量。
我们可以用一段“模拟new操作”的代码,更直观地理解这个过程:
// 模拟new关键字的作用,手动实现构造函数的逻辑
function car() {
// 1. 创建一个空对象(对应new的第一步)
const _this = {};
// 2. 给空对象添加属性(对应new的第二步)
_this.name = 'su7';
_this.color = 'green';
// 3. 返回这个对象(对应new的第三步)
return _this;
}
// 调用函数,得到实例对象(相当于new person())
const obj = car();
console.log(obj); // 输出:{ name: 'su7', color: 'green' }
new的核心就是“自动帮我们创建对象、添加属性、返回对象”,省去了我们手动写这些重复代码的麻烦~
四、易错点:包装类|为什么原始类型不能加属性? 🧐
前面我们说过“原始类型一定无法添加属性和方法”,但很多新手会写出这样的代码,然后疑惑为什么结果不符合预期:
// 看似给数字添加属性,实则无效
var num = 123;
num.a = 'aaa'; // 尝试给原始类型num添加属性a
delete num.a; // 尝试删除属性a
console.log(num.a); // 输出:undefined
// 看似给字符串添加属性,实则无效
var str = 'hello';
str.len = 2; // 尝试给原始类型str添加属性len
console.log(str.len); // 输出:undefined
这就涉及到JS中的“包装类”(Wrapper Class)概念,其实V8引擎在背后做了“隐式操作”,我们拆解一下过程:
什么是包装类?
包装类是JS内置的、对应原始类型的构造函数,包括:String()、Number()、Boolean()。它们的作用是:当我们对原始类型进行“属性操作”时,引擎会自动把原始类型转换成对应的包装类实例对象,操作完成后,再自动销毁这个实例对象——这就是“自动装箱”和“自动拆箱”。
拆解上面的错误示例:
var num = 123; // 原始类型(number)
num.a = 'aaa'; // 引擎隐式操作:
// 1. 自动装箱:new Number(123).a = 'aaa'(创建包装类实例,添加属性)
// 2. 操作完成后,自动销毁这个实例对象
delete num.a; // 同样隐式创建实例,删除属性后销毁
console.log(num.a); // 再次隐式创建新的实例,新实例没有a属性,所以返回undefined
核心总结:
-
当我们定义一个原始类型字面量(如var str ='hello'),V8引擎默认执行的还是
new String('hello')(创建包装类实例),但会自动拆箱成原始类型。 -
包装类的实例对象,参与运算时会自动拆箱成原始类型(比如123 + new Number(456) = 579)。
-
JS是弱类型语言,只有在赋值语句执行时,才会判断值的类型;当值被判定为原始类型时,就会自动将包装对象上添加的属性移除——所以我们给原始类型加属性,永远无效。
五、总结|核心知识点梳理 📝
看完上面的内容,相信你已经掌握了JS对象的核心知识点,我们用一张表格快速梳理,方便记忆:
| 知识点 | 核心内容 | 关键提醒 |
|---|---|---|
| 对象本质 | 存放属性和方法的“收纳盒”,引用类型的核心 | 万物皆对象,但原始类型不是对象 |
| 创建方式 | 1. 对象字面量 {};2. new Object();3. new 自定义构造函数 | 批量创建用自定义构造函数 |
| new的作用 | 1. 创建this空对象;2. 执行构造函数添加属性;3. 返回this对象 | 构造函数必须用new调用 |
| 包装类 | 原始类型属性操作时的隐式实例,操作后自动销毁 | 原始类型无法添加属性和方法 |
🐾 最后提醒:JS对象是前端开发的基础,后续的原型、原型链、继承等知识点,都建立在对象的基础上。祝大家学习顺利,写出更优雅的JS代码!