# 认识对象
# 概念
对象(Object) 是 “键值对” 的集合,表示属性和值的映射关系。
对象是一种复杂的数据类型,有了对象为了编写复杂的大型引用奠定了基础。
# 对象的定义
# 方式一
var xiaoLuo = {
name: '小小罗',
age: 32,
sex: '男',
hobbies:['足球'],
'read-books': ['一个足球明星的自我修养']
}
上面的例子中我们就定义了一个对象(xiaoMing), 并且为该对象定义了四个属性, 性名(name)、 年龄(age)、 性别(sex)、爱好(hobbies)
属性是一种键值对的形式 key (属性名): value(属性值)
如果对象的属性键名不符合JS标识符命名规范,则这个键名必须用引号包裹, 例如上面的 read-books
属性。 现实开发中,我们应该尽量避免不符合js命名规范的写法。
# 方式二
方式而分为两步
- 使用构造函数创建一个类
- 实例化类形成一个对象
// 构造函数创建一个类
function Player(name, age, sex, hobbies) {
this.name = name;
this.age = age;
this.sex = sex;
this.hobbies = hobbies;
}
// 创建一个 对象 ,使用new关键字
var xiaoLuo = new Player('小罗', 32, '男', ['足球']);
console.log(xiaoLuo);
什么是类?
类表示一类事物, 通常比较抽象和宽泛。比如运动员是一类, 歌唱家是一类
什么是对象?
对象是具体, 比如: 足球运动员小罗
什么是构造函数?
构造函数也是函数,指的是专门用来声明类的函数,为了区分和普通函数的区别,增加可读性,通常首字母大写
类和对象的关系
对比两种方式, 我们可以发现, 方式一是单例的, 我们直接初始化一个对象出来,我们希望单例的对象在内存中就只有一份
对于方式二, 是为了封装一个类事物, 根据特定的场景, 实例化特定的对象来表述不同对象之间的差异
# 对象属性访问
如果想访问一个对象,采用如下的语法
// 访问普通的属性
xiaoLuo.hobbies;
// 不符合命名规范的, 使用中括号包裹的形式
xiaoLuo['read-books'];
如果一个变量携带了某个对象的属性名, 也可以使用中括号的形式访问, 以中括号方位的形式很像操作属性, 属性名是下标索引, 通过它就可以得到属性值
如果属性名以变量形式存储,则必须使用方括号形式
var nameKey = 'name';
var name = xiaoLuo[nameKey];
// 下面这种方位方式不生效 , undei
xiaoLuo.nameKey;
# 添加/更新属性
// 对象已存在, 添加一个属性
xiaoLuo.shcool = '小小足球培训学校';
// 更改属性值
xiaoLuo.shcool = '小小足球培训学校分部';
# 删除某个对象属性
// 删除某个school属性
delete xiaoLuo.shcool;
# 对象的方法
如果某个属性值是函数,则它也被称为对象的 “方法”
<script>
var xiaoLuo = {
name: '小小罗',
age: 32,
sex: '男',
hobbies:['足球'],
'read-books': ['一个足球明星的自我修养'],
sayHello: function() {
console.log('大家好,我是小罗');
}
}
xiaoLuo.sayHello();
</script>
# 对象遍历
和数组变量相似, 对象也可以被遍历, 使用 for…in…语法循环
// key : 属性名
// obj 要遍历的对象
for(key in obj) {
console.log('属性:' + k + '的值是' + obj[k]);
}
例如
<script>
var xiaoLuo = {
name: '小小罗',
age: 32,
sex: '男',
hobbies:['足球'],
'read-books': ['一个足球明星的自我修养'],
sayHello: function() {
console.log('大家好,我是小罗');
}
}
for(var key in xiaoLuo) {
console.log('key:', key, 'value:', xiaoLuo[key])
}
</script>
# 克隆
我们都知道,对象属于引用类型值, 如果一个对象作为另外一个对象属性值的时候, 该属性名只能是持有该对象的地址。
如上图, 小罗的妻子是小红, 在JS世界中, 我们给小罗定义了一个属性 wife, 将 其妻子小红的引用赋值给了小罗
也就说小红这个“对象”在计算机内存中只存在一份, 小罗对象持有的只是小红的引用
克隆: 就是拷贝,在内存中生成一个独立的副本
**引用类型值克隆:**对于引用类型值, 用等号并不能实现内存的拷贝,这里的等号只是将引用传递给了另外一个变量
var a = new Object();
a.name = '小红';
var b = a;
b.name = '小兰';
// 输出true
console.log(a === b);
// 输出 小兰, 改了 b的名字后 a的也随之改了
console.log(a.name);
上面的例子中, b 和 a 持有的是同一个对象的引用, a 和 b 是一个对象, 并没有实现克隆
针对引用类型值,有两种克隆方式
- 浅克隆
- 深克隆
# 浅克隆
值克隆该对象的属性, 如果该对象的属性依然是个对象或者数组, 不进行进一步处理
使用 for…in… 循环即可实现对象的浅克隆
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>浅克隆</title>
</head>
<body>
<script>
var obj1 = {
a: 1,
b: 2,
c: [4,5,6]
}
var obj2 = {}
for(var key in obj1) {
obj2[key] = obj1[key];
}
obj1.a ++;
obj1.c.push(7);
// 从打印结果也能看出 浅克隆 并没有真正的将 数组c 分开,只是把引用传递给了 obj2.c
// 值引用的的a 被分开了
console.log(obj1 == obj2);
console.log(obj2);
console.log(obj1);
</script>
</body>
</html>
# 深克隆
拷贝到底, 如果属性值是对象或者数组,则一直拷贝下去, 知道所有的属性值都在内存中有一个完整副本为止。
我们这里 使用递归来实现。
ps: 递归的概念: 定义一个函数,设定一个退出条件, 可以自己调用自己循环调用下去,知道触发了推出条件为止。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>深克隆</title>
</head>
<body>
<script>
var obj1 = {
a: 1,
b: 2,
c: [4, 5, {
d: 6,
e: 7,
f: [9, 10]
}]
};
function deepClone(o) {
if(Array.isArray(o)) {
// 数组
var result = [];
for(var i = 0; i< o.length; i++) {
result.push(deepClone(o[i]));
}
return result;
}
if(typeof o == 'object') {
// 对象
var result = {};
for(var key in o) {
result[key] = deepClone(o[key]);
}
return result;
}
// 基本类型值
var result = o;
return result;
}
var obj2 = deepClone(obj1);
obj1.c[2].d ++;
console.log(obj1);
console.log(obj2);
</script>
</body>
</html>