说明:此文章用作个人学习记录,若有任何问题或建议欢迎大家在评论区讨论
文章目录
前言
在前面的两篇文章《JavaScript 数据类型进阶篇 1》和《进阶篇 2》中,
我们已经详细讨论了 JavaScript 的 7 种基本数据类型。
接下来,我们将深入探讨复杂数据类型(也称为引用类型)
包括它们的定义、实现、用法,以及开发过程中常见的问题和解决方案
复杂数据类型之所以也被称为引用类型,是因为它们与基本数据类型在数据存储方式上不同
基本数据类型直接存储值本身,而引用类型则存储的是数据所在内存的地址,也就是对数据的引用
这种特性使得对引用类型的操作会影响到存储在内存中的实际数据,而不仅仅是变量本身。
引用类型包括:对象Object
、数组Array
、函数function
、日期Date
、正则表达式RegExp
、错误对象Error
,
以及ES6新加入的一些类型:Map
、Set
、WeakMap
、WeakSet
受限于篇幅以及本人的脑细胞死亡率,本篇将重点讲述对象Object
的相关内容
对象 (Object)
1. 定义
- 对象(Object) 是一个包含属性(键值对)的数据结构
- 每个属性由键(也叫属性名)和对应的值组成
- 对象的键是字符串(或者可以转换为字符串的类型)
- 对象的值可以是任何数据类型(包括原始类型、其他对象、数组、函数等)
示例:
const person = {
name: '小明', // 键值对,属性名为 'name',值为 '小明',值的类型为字符串
age: 18, // 键值对,属性名为 'age',值为 18,值的类型为数字
say: function() {
console.log('你好啊!'); // 键值对,键为 'say',值的类型为函数
}
};
2. 对象创建方式
2.1 使用对象字面量(Object Literal)
- 这是创建对象最简单、最常用的方法
const person = {
name: '小明',
age: 18
};
优点: 语法简单,系统消耗小,易读性强
缺点: 不适合大量创建对象,虽然也可以用Object.create()
实现原型和继承,但不够灵活直观
2.2 使用 Object() 构造函数
使用Object()
构造函数和new
关键字创建对象
const book = new Object();
book.title = 'JavaScript从零学起';
book.author = '史莱姆';
优点:
- 可以使用函数创建对象
- 能通过参数传递属性,使用更加灵活
- 适合创建复杂对象
缺点:
- 每次创建都会调用函数,系统资源消耗比字面量创建对象更多
- 语法较字面量创建更复杂,不直观
- 没有原生支持私有属性(但也可以通过闭包来实现)
2.3 使用自定义构造函数(Constructor Function)
- 使用自定义构造函数和
new
关键字来创建对象 - 这里强调
自定义
是为了区别于JavaScript内置的构造函数Object()
function Person(name, age) { //自定义构造函数通常以大写字母开头,可以更清晰的表示这是个构造函数
this.name = name; // 属性名是 name,变量名也是 name
this.age = age; // 在JS中属性名和变量名可以相同,在工厂函数的例子中我会使用不同的名字作为对比
}
const person1 = new Person('史莱姆', 30);
2.4 工厂函数(Factory Function)
- 工厂函数严格来说是一种模式,它可以使用任何方法,包括
字面量
、Object()
、构造函数
来创建和返回新对象 - 可以通过工厂函数创建大量相似的对象
下面是一个结合了object()
构造函数和工厂函数的例子
function createPerson(PersonName, PersonAge) {
return new Object({
name: PersonName, // 属性名是 name,变量名是 personName
age: PersonAge // 属性名是 age,变量名是 personAge
}); // 为了提高可读性,了解参数传递的具体位置,推荐使用不同的属性和变量名
}
// 使用函数动态创建对象
const person1 = createPerson('小明', 18);
const person2 = createPerson('史莱姆', 30);
console.log(person1);
console.log(person2);
输出结果:
2.5 类(Class)
class
是 ES6 版本引入的新特性,可以更简洁的自定义构造函数
并且 class
在 ES13版本中引入了对 私有属性 和 私有方法 的支持,
经测试最新版本的Chrome、Edge、Safari、火狐等浏览器都支持ES6和ES13
如果现在还在做IE项目的话,那就默认放弃所有新特性吧。。。
class Person {
#secret; // ‘#’ 表示#secret是私有属性
constructor(personName, personAge) { //constructor()是类的特有方法,
this.name = personName; //用于初始化类的实例,并且在使用new关键字创建对象时自动调用
this.age = personAge;
this.#secret = '身高2米8'; // 初始化私有属性
}
// 公共方法
say() {
console.log(`你好,我的名字是 ${this.name}`);
}
// 私有方法
#getSecret() { //‘#’ 表示#getSecret()是私有方法
return this.#secret;
}
// 公共方法访问私有方法
publicSecret() {
console.log(this.#getSecret());
}
}
const person1 = new Person('史莱姆', 30);
person1.say(); // 输出: 你好,我的名字是史莱姆
person1.publicSecret(); // 输出: 身高2米8
// person1.#getSecret(); // 会抛出错误,因为私有方法无法在外部访问
优点:
1. 原生支持私有属性和方法:
使用 #
前缀定义私有属性和方法,比闭包更为简洁明了
2. 支持继承:
可以使用 extends
关键字灵活实现继承
3. 语法简洁易懂
缺点: 大量创建对象时 class
创建对象的性能消耗,大于使用工厂函数的性能消耗
3. 对象的常见操作
3.1 访问和修改属性
- 可以使用点操作符或方括号语法访问和修改对象的属性
const person = { name: '史莱姆', age: 18};
console.log(person.name); // 输出: 史莱姆
person.age = 30;
console.log(person['age']); // 输出: 30
3.2 遍历对象
- 可以使用
for...in
循环来遍历对象的属性 此方法可以遍历出所有属性,包括原型链上被继承对象的属性
const user = { name: '史莱姆', age: 18};
for (let key in user) {
console.log(key + ': ' + user[key]);
}
3.3 删除属性
- 可以使用
delete
操作符删除对象的属性。
const user = { name: '史莱姆', age: 18 };
delete user.age;
console.log(user); // 输出: { name: '史莱姆' }
4. 开发过程中常见的问题和解决方案
4.1 对象属性未定义
- 问题:访问不存在的对象属性时返回
undefined
。 - 解决方案:在访问属性前,使用条件语句检查属性是否存在。
const animal = { name: 'cat' };
console.log(animal.age); // 输出: undefined
if ('age' in animal) {
console.log(animal.age);
}
4.2 对象引用的问题
- 问题:当你复制一个对象时,实际上复制的是它的引用,而不是对象本身
修改复制对象的属性会影响到原对象 - 解决方案:使用浅拷贝(如
Object.assign
)或深拷贝(如JSON.parse(JSON.stringify())
)来创建对象的副本
const original = { name: '史莱姆' };
const copy = original;
copy.name = '史莱姆复制体';
console.log(original.name); // 输出: 史莱姆复制体
// 使用浅拷贝
const lowCopy = Object.assign({}, original);
lowCopy.name = '史莱姆复制体2号';
console.log(original.name); // 输出: 史莱姆复制体,因为name属性的值是个字符串,
//字符串属于原始类型,所以浅拷贝的属性值被直接复制了,修改不影响原对象
// 深拷贝
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.name = '史莱姆复制体3号';
console.log(original.name); // 输出: 史莱姆复制体,因为深拷贝的对象与原对象完全独立
拓展阅读:浅拷贝与深拷贝
浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是两种复制对象的方法
浅拷贝(Shallow Copy):
- 浅拷贝是指创建一个新对象,这个新对象的属性值是原始对象属性值的副本
- 浅拷贝只会复制对象的第一层属性
- 如果属性是原始类型(如字符串、数字、布尔值),那么拷贝的是这个值本身
- 如果属性是引用类型(如对象、数组),拷贝的只是引用地址,而不是对象本身
深拷贝(Deep Copy):
- 深拷贝会递归地复制对象中的每一层属性
- 生成的新对象与原始对象完全独立
4.3 this 关键字的上下文问题
- 问题:在对象方法中,
this
的上下文可能会发生变化 - 解决方案:使用箭头函数或
bind()
方法来保持this
的正确上下文
const person = {
name: '小明',
say() {
setTimeout(function() { //setTimeout()是一个异步函数,function()是一个普通函数
console.log(`你好,我的名字叫 ${this.name}`); //当普通函数在异步回调中执行时,this 的上下文会改变
}, 1000); //在这种情况下,this 默认指向的是全局对象,而不是原来的 person 对象
//全局对象没有name属性,所以输出undefined
}
};
person.say(); // 你好,我的名字叫 undefined
// 解决方法:使用箭头函数
const person2 = {
name: '史莱姆',
say() {
setTimeout(() => { //箭头函数不会创建它自己的 this,它会继承外层函数的 this 值
console.log(`你好,我的名字叫 ${this.name}`); // this 正确地指向 person2 对象
}, 1000);
}
};
person2.say(); // 输出: 你好,我的名字叫史莱姆
标签:name,对象,age,Object,数据类型,史莱姆,进阶篇,零学起,属性 From: https://blog.csdn.net/2401_87242367/article/details/143020447