JS之对象
一、对象
1、面向对象简介
面向对象:可以创建自定义的类型,很好的支持继承和多态。
面向对象的特征:封装、继承、多态。
2、对象的概念
在 JavaScript 中,对象是一组无序的相关属性和方法的集合。
对象的作用是:封装信息。比如Student类里可以封装学生的姓名、年龄、成绩等。
对象具有特征(属性)和行为(方法)。
保存一个值时,可以使用变量,保存多个值(一组值)时,可以使用数组。
比如,如果要保存一个人的信息,通过数组的方式可以这样保存:
```javascript
var arr = ['王二', 35, '男', '180'];
```
上面这种表达方式比较乱。而如果用JS中的对象表达,结构会更清晰。如下:
```javascript
var person = {};
person.name = '王二';
person.age = 35;
person.sex = '男';
person.height = '180';
```
由此可见,对象里面的属性均是**键值对**:
- 键:相当于属性名。
- 值:相当于属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)。
两条补充
补充1:对象的属性值可以是任何的数据类型,也可以是个**函数**:(也称之为方法)
```javascript
var obj = new Object();
obj.sayName = function () {
console.log('smyhvae');
};
console.log(obj.sayName); //没加括号,就是获取方法
console.log('-----------');
console.log(obj.sayName()); //加了括号,就是调用方法。即:执行函数内容,并执行函数体的内容
```
补充2:对象中的属性值,也可以是一个对象。
举例:
```javascript
//创建对象 obj1
var obj1 = new Object();
obj1.test = undefined;
//创建对象 obj2
var obj2 = new Object();
obj2.name = "smyhvae";
//将整个 obj2 对象,设置为 obj1 的属性
obj1.test = obj2;
console.log(obj1.test.name);
```
打印结果为:smyhvae
二、对象和数据类型之间的关系
1、数据类型分类
- **基本数据类型(值类型)**:String 字符串、Number 数值、Boolean 布尔值、Null 空值、Undefined 未定义。
- **引用数据类型(引用类型)**:Object 对象。
**基本数据类型**:
基本数据类型的值直接保存在**栈内存**中,值与值之间是独立存在,修改一个变量不会影响其他的变量。
**对象**:
1)只要不是那五种基本数据类型,就全都是对象。
2)如果使用基本数据类型的数据,我们所创建的变量都是独立的,不能成为一个整体。
3)对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性。
4)对象是保存到**堆内存**中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间。变量保存的是对象的内存地址(对象的引用)。换而言之,对象的值是保存在**堆内存**中的,而对象的引用(即变量)是保存在**栈内存**中的。
5)如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响。
一个经典的例子
```javascript
var obj = new Object();
obj.name = "孙悟空";
var obj2 = obj;
//修改obj2的name属性
obj2.name = "猪八戒";
```
上面的代码中,当我修改 obj2 的name属性后,会发现,obj 的 name 属性也会被修改。因为obj和obj2指向的是堆内存中的同一个地址。
对于引用类型的数据,赋值相当于地址拷贝,a、b占用了同一段地址。所以改了b,a也会变;本质上a、b就是一个东西。
如果你打算把引用类型 A 的值赋值给 B,让A和B相互不受影响的话,可以通过 Object.assign() 来复制对象。效果如下:
```js
var obj = {name: '孙悟空'};
// 复制对象:把 obj 赋值给 obj3。两者之间互不影响
var obj3 = Object.assign({}, obj);
```
2、基本数据类型不能绑定属性和方法
属性和方法只能添加给对象,不能添加给基本数据类型。
1、基本数据类型:
注意,基本数据类型`string`是**无法绑定属性和方法**的。比如说:
```javascript
var str = "qianguyihao";
str.aaa = 12;
console.log(typeof str); //打印结果为:string
console.log(str.aaa); //打印结果为:undefined
```
上方代码中,当我们尝试打印`str.aaa`的时候,会发现打印结果为:undefined。也就是说,不能给 `string` 绑定属性和方法。
当然,我们可以打印str.length、str.indexOf("m")等等。因为这两个方法的底层做了数据类型转换(**临时**将 `string` 字符串转换为 `String` 对象,然后再调用内置方法),也就是我们在上一篇文章中讲到的**包装类**。
2、引用数据类型:
引用数据类型`String`是可以绑定属性和方法的。如下:
```javascript
var strObj = new String("smyhvae");
strObj.aaa = 123;
console.log(strObj);
console.log(typeof strObj); //打印结果:Object
console.log(strObj.aaa);
```
打印结果:
![](http://img.smyhvae.com/20180202_1351.png)
内置对象Number也有一些自带的方法,比如:
- Number.MAX_VALUE;
- Number.MIN_VALUE;
内置对象Boolean也有一些自带的方法,但是用的不多。
三、对象的分类
1.内置对象:
- 由ES标准中定义的对象,在任何的ES的实现中都可以使用
- 比如:Object、Math、Date、String、Array、Number、Boolean、Function等。
2.宿主对象:
- 由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象。
- 比如 BOM DOM。比如`console`、`document`。
3.自定义对象:
- 由开发人员自己创建的对象
通过 new 关键字创建出来的对象实例,都是属于对象类型,比如Object、Array、Date等。
四、包装类(基本数据类型的数据转换为对象)
1、三个包装类:
- String():将基本数据类型字符串,转换为String对象。
- Number():将基本数据类型的数字,转换为Number对象。
- Boolean():将基本数据类型的布尔值,转换为Boolean对象。
通过上面这这三个包装类,我们可以**将基本数据类型的数据转换为对象**。
代码举例:
```javascript
var num = new Number(3);
var str = new String("hello");
var bool = new Boolean(true);
console.log(typeof num); // 打印结果:object
```
需要注意的是:我们在实际应用中不会使用基本数据类型的对象。如果使用基本数据类型的对象,在做一些比较时可能会带来一些**不可预期**的结果。
比如说:
```javascript
var boo1 = new Boolean(true);
var boo2 = new Boolean(true);
console.log(boo1 === boo2); // 打印结果竟然是:false
```
再比如说:
```javascript
var boo3 = new Boolean(false);
if (boo3) {
console.log('qianguyihao'); // 这行代码竟然执行了
}
```
2、基本包装类型【重要】
当我们对一些基本数据类型的值去调用属性和方法时,浏览器会**临时使用包装类将基本数据类型转换为引用数据类型**,这样的话,基本数据类型就有了属性和方法,然后再调用对象的属性和方法;调用完以后,再将其转换为基本数据类型。
举例:
```js
var str = 'qianguyihao';
console.log(str.length); // 打印结果:11
```
比如,上面的代码,执行顺序是这样的:
```js
// 步骤(1):把简单数据类型 string 转换为 引用数据类型 String,保存到临时变量中
var temp = new String('qianguyihao);
// 步骤(2):把临时变量的值 赋值给 str
str = temp;
// 步骤(3):销毁临时变量
temp = null;
```
在底层,字符串以字符数组的形式保存
代码举例:
```javascript
var str = "smyhvae";
console.log(str.length); // 获取字符串的长度
console.log(str[2]); // 获取字符串中的第2个字符
```
上方代码中,`smyhvae`这个字符串在底层是以`["s", "m", "y", "h", "v", "a", "e"]`的形式保存的。因此,我们既可以获取字符串的长度,也可以获取指定索引index位置的单个字符。这很像数组中的操作。
再比如,String 对象的很多内置方法,也可以直接给字符串用。此时,也是临时将字符串转换为 String 对象,然后再调用内置方法。
五、内置对象
JavaScript 中的对象分为3种:自定义对象 、内置对象、 浏览器对象。
前面两种对象:是JS的基础内容,属于 ECMAScript; 第三个浏览器对象:属于JS独有,即 JS 内置的API。
**内置对象**:就是指这个语言自带的一些对象,供开发者使用,这些对象提供了一些常用或者最基本而必要的功能(属性和方法)。
内置对象最大的优点就是帮助我们快速开发。
**JavaScript的内置对象**:
| 内置对象 | 对象说明 |
|:-------------|:-------------|
| Arguments | 函数参数集合|
| Array | 数组|
| Boolean | 布尔对象|
| Math | 数学对象|
| Date | 日期时间|
| Error | 异常对象|
| Function | 函数构造器|
| Number | 数值对象|
| Object | 基础对象|
| RegExp | 正则表达式对象|
| String | 字符串对象|
六、自定义对象的创建
创建自定义对象的几种方法
1、对象字面量
**对象的字面量**就是一个{}。里面的属性和方法均是**键值对**:
- 键:相当于属性名。
- 值:相当于属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)。
使用对象字面量来创建一个对象,非常简洁,举例如下::
```javascript
var obj = {};
```
使用对象字面量,可以在创建对象时,直接指定对象中的属性。语法:{属性名:属性值,属性名:属性值....}
例如:
```javascript
var obj2 = {
name: "猪八戒",
age: 13,
gender: "男",
test: {
name: "沙僧"
}
sayName: function(){
console.log('smyhvae');
}
};
```
例如:
```javascript
var obj = {
name: "张三",
age: 26,
isBoy: true,
// 还可以存放一个签到的对象
test: {
id: 123,
tel: 180
}
//我们还可以在对象中增加一个方法。以后可以通过obj.sayName()的方式调用这个方法
sayName: function() {
console.log(this.name);
}
};
console.log(JSON.stringify(obj));
```
对象字面量的属性名可以加引号也可以不加,建议不加。如果要使用一些特殊的名字,则必须加引号。
属性名和属性值是一组一组的键值对结构,键和值之间使用`:`连接,多个值对之间使用`,`隔开。如果一个属性的后面没有其他的属性了,就不要写`,`,因为它是对象的最后一个属性。
2、工厂模式
通过该方法可以大批量的创建对象。
```javascript
/*
* 使用工厂方法创建对象
* 通过该方法可以大批量的创建对象
*/
function createPerson(name, age, gender) {
//创建一个新的对象
var obj = new Object();
//向对象中添加属性
obj.name = name;
obj.age = age;
obj.gender = gender;
obj.sayName = function() {
alert(this.name);
};
//将新的对象返回
return obj;
}
var obj2 = createPerson("猪八戒", 28, "男");
var obj3 = createPerson("白骨精", 16, "女");
var obj4 = createPerson("蜘蛛精", 18, "女");
```
第一次看到这种工厂模式时,你可能会觉得陌生。如果简化一下,可以写成下面这种形式,更容易理解:(也就是,利用new Object创建对象)
```javascript
var obj = new Obect();
obj.name = '猪八戒';
obj.age = 28;
obj.gender = '男';
obj.sayHi = function() {
alert('hello world');
};
```
**弊端:**
使用工厂方法创建的对象,使用的构造函数都是Object。**所以创建的对象都是Object这个类型,就导致我们无法区分出多种不同类型的对象**。
3、利用构造函数
```javascript
//利用构造函数自定义对象
var stu1 = new Student("smyh");
console.log(stu1);
stu1.sayHi();
var stu2 = new Student("vae");
console.log(stu2);
stu2.sayHi();
// 创建一个构造函数
function Student(name) {
this.name = name; //this指的是当前对象实例【重要】
this.sayHi = function () {
console.log(this.name + "厉害了");
}
}
```
七、对象的操作
1、向对象中添加属性
在对象中保存的值称为属性。
向对象添加属性的语法:
```javascript
对象.属性名 = 属性值;
```
举例:
```javascript
var obj = new Object();
//向obj中添加一个name属性
obj.name = "孙悟空";
//向obj中添加一个gender属性
obj.gender = "男";
//向obj中添加一个age属性
obj.age = 18;
console.log(JSON.stringify(obj)); // 将 obj 以字符串的形式打印出来
```
打印结果:
```
{
"name":"孙悟空",
"gender":"男",
"age":18
}
```
2、获取对象中的属性
**方式1**:
语法:
```javascript
对象.属性名
```
如果获取对象中没有的属性,不会报错而是返回`undefined`。
举例:
```javascript
var obj = new Object();
//向obj中添加一个name属性
obj.name = "孙悟空";
//向obj中添加一个gender属性
obj.gender = "男";
//向obj中添加一个age属性
obj.age = 18;
// 获取对象中的属性,并打印出来
console.log(obj.gender); // 打印结果:男
console.log(obj.color); // 打印结果:undefined
```
**方式2**:可以使用`[]`这种形式去操作属性
对象的属性名不强制要求遵守标识符的规范,不过我们尽量要按照标识符的规范去做。
但如果确实要使用特殊的属性名,就不能采用`.`的方式来操作对象的属性。比如说,`123`这种属性名,如果我们直接写成`obj.123 = 789`来操作属性,是会报错的。那怎么办呢?办法如下:
语法格式如下:(读取时,也是采用这种方式)
```javascript
// 注意,括号里的属性名,必须要加引号
对象['属性名'] = 属性值
```
上面这种语法格式,举例如下:
```javascript
obj['123'] = 789;
```
**重要**:使用`[]`这种形式去操作属性,更加的灵活,因为,我们可以在`[]`中直接传递一个**变量**。
3、修改对象的属性值
语法:
```javascript
对象.属性名 = 新值
```
```javascript
obj.name = "tom";
```
4、删除对象的属性
语法:
```javascript
delete obj.name;
```
5、in 运算符
通过该运算符可以检查一个对象中是否含有指定的属性。如果有则返回true,没有则返回false。
语法:
```javascript
"属性名" in 对象
```
举例:
```javascript
//检查obj中是否含有name属性
console.log("name" in obj);
```
我们平时使用的对象不一定是自己创建的,可能是别人提供的,这个时候,in 运算符可以派上用场。
6、遍历对象:for in
语法:
```javascript
for (var 变量 in 对象名) {
}
```
解释:对象中有几个属性,循环体就会执行几次。每次执行时,会将对象中的**每个属性的 属性名 赋值给变量**。
语法举例:
```javascript
for (var key in obj) {
console.log(key); // 这里的 key 是:对象属性的键(也就是属性名)
console.log(obj[key]); // 这里的 obj[key] 是:对象属性的值(也就是属性值)
}
```
举例:
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
var obj = {
name: "smyhvae",
age: 26,
gender: "男",
address: "shenzhen",
sayHi: function() {
console.log(this.name);
}
};
// 遍历对象中的属性
for (var key in obj) {
console.log("属性名:" + key);
console.log("属性值:" + obj[key]); // 注意,因为这里的属性名 keu 是变量,所以,如果想获取属性值,不能写成 obj.key,而是要写成 obj[key]
}
</script>
</head>
<body>
</body>
</html>
```
打印结果:
```
属性名:name
属性值:smyhvae
属性名:age
属性值:26
属性名:gender
属性值:男
属性名:address
属性值:shenzhen
属性名:sayHi
属性值:function() {
console.log(this.name);
}
```
八、构造函数
构造函数可以创建自定义对象,所以这里顺带介绍构造函数
1、构造函数概念
是一种特殊的函数,主要用来创建和初始化对象,也就是为对象的成员变量赋初始值。它与 `new` 一起使用才有意义。
我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个构造函数里面。
```javascript
// 创建一个构造函数,专门用来创建Person对象
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function() {
alert(this.name);
};
}
var per = new Person("孙悟空", 18, "男");
var per2 = new Person("玉兔精", 16, "女");
var per3 = new Person("奔波霸", 38, "男");
// 创建一个构造函数,专门用来创建 Dog 对象
function Dog() {}
var dog = new Dog();
```
2、构造函数和普通函数的区别
1)构造函数的创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。
2)构造函数和普通函数的区别就是**调用方式**的不同:普通函数是直接调用,而构造函数需要使用new关键字来调用。
3)this的指向也有所不同:
- 1.以函数的形式调用时,this永远都是window。比如`fun();`相当于`window.fun();`
- 2.以方法的形式调用时,this是调用方法的那个对象
- 3.以构造函数的形式调用时,this是新创建的实例对象
new 一个构造函数的执行流程
new 在执行时,会做下面这四件事:
(1)开辟内存空间,在内存中创建一个新的空对象。
(2)让 this 指向这个新的对象。
(3)执行构造函数里面的代码,给这个新对象添加属性和方法。
(4)返回这个新对象(所以构造函数里面不需要return)。
因为this指的是new一个Object之后的对象实例。于是,下面这段代码:
```javascript
// 创建一个函数
function createStudent(name) {
var student = new Object();
student.name = name; //第一个name指的是student对象定义的变量。第二个name指的是createStudent函数的参数。二者不一样
}
```
可以改进为:
```javascript
// 创建一个函数
function Student(name) {
this.name = name; //this指的是构造函数中的对象实例
}
```
3、类、实例
类:使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。
实例:通过一个构造函数创建的对象,称为该类的实例。
4、instanceof
使用 instanceof 可以检查**一个对象是否为一个类的实例**。
**语法如下**:
```javascript
对象 instanceof 构造函数
```
如果是,则返回true;否则返回false。
**代码举例**:
```javascript
function Person() {}
function Dog() {}
var person1 = new Person();
var dog1 = new Dog();
console.log(person1 instanceof Person); // 打印结果: true
console.log(dog1 instanceof Person); // 打印结果:false
console.log(dog1 instanceof Object); // 所有的对象都是Object的后代。因此,打印结果为:true
```
根据上方代码中的最后一行,需要补充一点:**所有的对象都是Object的后代,因此 `任何对象 instanceof Object` 的返回结果都是true**。