[TOC]
# 对象
## 概念
  JavaScript的对象是无序属性的集合。其属性可以包含基本值,对象或者函数。可以包含基本类型值和复杂类型值。js中对象就是一组键值对
  对象是一种复合值,它将很多值(原始值或其他对象)聚合在一起,可通过属性名访问这些值。。而属性名可以是包含空字符串在内的任意字符串。
  JavaScript中的对象是现实生活中对象的抽象。即显示生活中的对象使用JavaScript来描述。
* 事物的特征在对象中用**属性**来表示。
* 事物的行为在对象中用**方法**来表示。
<br>
JavaScript对象也可以称作一种数据结构。
<br>
## **属性和方法**
1. 如果一个变量属于一个对象所有,那么该变量就可以称之为该对象的一个属性,属性一般是名词,用来描述对象的特征。
2. 如果一个函数属于一个对象所有,那么该函数就可以称之为该对象的一个方法,方法是动词,描述对象的行为和功能。
3. 属性和方法统称为对象的成员(成员变量)
## **创建对象**
创建对象有四种方式
<br>
### **一、通过字面量创建对象**
  对象也可以用字面量方式表示,而且也可以对象的字面量值赋值给一个变量,此时这个变量就代表这个对象。
  使用字面量方式表示对象,又叫创建字面量对象,如下:
```
var person = {
name: '冯宝宝',
age: 18
};
```
<br>
#### **语法格式**
>[success]var 对象名 = {键:值, 键:值...} ;
  语法格式分析
1. 对象名 -> 本质是变量名;
2. {} -> 固定格式,表示封装一段代码,类似于函数的大括号;
3. 键:值 -> 键-值对,固定格式;
  键 -> 表示 属性名,类似于变量名;
  值 -> 表示 属性值,类似于变量值,可以是基本数据类型(String,Number...)或引用数据类型(数组,对象);
6. 键 和 值之间使用 ":" 隔开,键值对和键值对之间用 "," 隔开;
注意:对象名类似于变量名,键也类似于变量名,对象中存在键,好比变量中存在变量。
  根据以上语法格式就可以创建对象了。我们知道,JavaScript对象是由属性和方法组成的,所以只要确定了一个对象的属性和方法,就可以使用代码创建出这个对象了。
<br>
**例:**
1、创建一个 "人" 对象
属性:name(冯宝宝),age(18),gender(true)
方法:sayHi(打招呼-瓜娃子)
```
var person = {
name: '冯宝宝',
age: 18,
gender: true,
sayHi: function () {
console.log('你好,瓜娃子,我叫 '+this.name);
}
};
```
<br>
### **二、 通过Object构造函数创建对象(内置构造函数)**
  构造函数实质是函数,Object()是JavaScript内置的一个构造函数,可以用来创建对象
<br>
#### **语法格式**
>[success]var 对象名 = new 内置构造函数名();
语法格式分析
new -> 用于调用内置构造函数的关键字
步骤:
1. 创建出一个空对象
```
var obj = new Object();
console.log(obj);
```
2. 给对象添加属性和方法并赋值
**语法格式**
>[success]对象名.属性名 = 属性值;
对象名.方法名 = function(){...}
区别于字面量方式的 键值对!
{ 属性名:属性值,方法名:function(){...} }
利用内置构造函数的方式,创建一个完整的hero对象,如下
```
//使用new关键字来调用Object()构造函数,创建出一个空的对象
var obj = new Object();
console.log(obj);
//动态地给对象设置属性和方法
obj.name = '孙尚香';
obj.gender = false;
obj.weapon = '弩炮';
obj.equipment = ['急速战靴', '宗师之力', '无尽战刃', '泣血之刃', '闪电匕首', '破晓'];
obj.attack = function () {
console.log(this.name + ':收割人头');
}
obj.run = function () {
console.log(this.name + ':加速奔跑');
}
console.log(obj.name);
console.log(obj.equipment);
obj.attack();
obj.run();
```
**注意**
  创建字面量对象的方式本质上是对内置构造函数方式缩写,字面量方式的底层也是使用new调用内置构造函数来完成的。两者比较,内置构造函数方式比字面量的方式更灵活。
<br>
### **三、自定义构造函数**
  自定义构造函数和内置构造函数类似,自定义构造函数是由自己定义,而内置构造函数是JavaScript库内置(已经定义好的)的。
**语法格式**
```
//定义一个构造函数,帕斯卡命名:所有单词首字母都大写。
function Hero(name) {
//this 表示当前构造函数创建的对象
//this 用来设置对象的属性和方法
this.name = name;
}
```
**注意:**
>[info]1、使用帕斯卡命名:函数名首字母都大写,如果函数由多个单词组成,每个单词首字母都要大写。
2、使用this表示当前构造函数创建的对象
3、使用this设置对象的属性和方法
  自定义构造函数 跟 内置构造函数 的调用方式一样! 都是使用**new**关键字来调用。
```
var hero = new Hero('孙尚香');//调用构造函数时,传入实参
```
  使用自定义构造函数创建一个Hero的完整代码如下。
```
//定义一个构造函数,帕斯卡命名:所有单词首字母都大写。
function Hero(name, gender, weapon, equipment, blood) {
//this 表示当前构造函数创建的对象
//this 用来设置对象的属性和方法
this.name = name;
this.gender = gender;
this.weapon = weapon;
this.equipment = equipment;
this.blood = blood;
this.attack = function () {
console.log(this.name + ':收割人头');
}
this.run = function () {
console.log(this.name + ':加速奔跑');
}
}
var hero1 = new Hero('孙尚香', false, '弩炮', ['急速战靴', '宗师之力', '无尽战刃', '泣血之刃', '闪电匕首', '破晓'], 100);
var hero2 = new Hero('刘备', true, '剑', ['头盔', '铠甲'], 100);
console.log(hero1, hero2);
```
>[info]**构造函数是这几种创建对象的方式中,最灵活,最常用的一种。**
<br>
### **四、工厂模式**
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。
工厂模式:使用创建并返回特定类型的对象的**工厂函数**(其实就是普通函数,没啥区别,只是叫法不同),可以创建多个相似的对象。
工厂函数:
  用函数来封装特定接口创建对象的细节。
  把实现同一事情的相同代码,放到一个函数中,以后如果再想实现这个功能,就不需要重新编写这些代码了,只要执行当前的函数即可,这就是函数的封装,体现了**高内聚、低耦合**的思想:减少页面的中的冗余代码,提高代码的重复利用率。
```
~~~
//在函数内创建一个对象,给对象赋予属性及方法再将对象返回即可。
function createPerson(name) {
var o = new Object();
o.name = name;
o.sayName = function () {
alert(this.name);
};
return o;
}
var obj = createPerson("张三");//将'张三'作为实参调用函数,此时对象o的name属性的值为张三。
var obj2 = createPerson("李四");
alert(obj instanceof Object);
alert(obj instanceof createPerson)
~~~
```
<br>
### 各种创建函数的方法的优劣
>[info]
>
>**1、字面量方式**
  优点:方便快捷,即写即用
  缺点:每个对象都要写一堆代码
>**2、内置构造函数方式**
  优点:没什么优点
  缺点:每个对象都要写一堆代码
>**3、自定义构造函数**
  优点:较前三种方式灵活,函数只需创建一次,可重复使用,提高开发效率;能确定当前被创建的对象的类型。
  缺点:如果对象只使用一次,要比字面量方式多些几行代码
>**4、工厂函数**
  优点:大量创建相似对象时,可以节省大量代码。
  缺点:不能确定不能确定对象类型
<br>
## 对象的访问
**访问对象的成员**
访问属性的格式:
  对象名.属性名;(点语法) dog.name;或dog['name'];(中括号语法)
访问方法的格式:
  对象名.方法名(); dog.bark(); 或 dog.['bark'] (同上,有点语法和中括号语法)
```
var dog = {
name: '花卷',
age: 2,
gender: true,
bark: function () {
console.log(this.name + ':汪~汪');
}
}
console.log(dog.name);// 访问属性,又叫调用属性
dog.bark();// 调用方法
```
函数和方法的区别,从定义和调用的角度来看
>[info]1、定义
函数:独立存在
方法:依赖于对象存在,语法跟匿名函数语法相似
2、调用
函数:函数名();
方法:对象名.方法名();
<br>
## **new关键字和this关键字详解**
<br>
### **new关键字**
构造函数 ,是一种特殊的函数。它总是与new关键字一起使用,完成对象的创建。
**举例:**
~~~
// 定义构造函数
function Person(name, age, gender) {
// 1、在内存中创建一个新的空对象;
// var obj = new Object();
// 2、将this指向创建出来的新对象
// this = obj;
// 3、执行构造函数的代码(设置属性和方法)
// 添加属性
this.name = name;
this.age = age;
this.gender = gender;
// 添加方法
this.sayHi = function () {
console.log('你好,瓜娃子~,我是' + this.name);
}
// 4、返回这个新对象;
// return this;
}
var per = new Person('冯宝宝', 18, true);
console.log(per);
~~~
new在调用构造函数中所执行的步骤
>[info]1. 在内存中创建一个新的空对象; var obj = new Object();
>2. 将this指向创建出来的新对象; this = obj;
>3. 执行构造函数的代码(设置属性和方法)
>4. 返回这个新对象; return this;
<br>
### **this关键字**
this关键字总是指向一个对象(引用类型),this所指向的对象跟 **函数的调用方式** 有关
```
// this关键字总是指向一个对象(引用类型),this所指向的对象跟 函数的调用方式 有关
console.log(this);// this --> window
function person() {
console.log(this);
}
person();// this --> window
// person 作为构造函数来使用
var per = new person();// this --> person
per.sayHello = function () {
console.log(this);
}
per.sayHello();// this --> person
```
this指向的对象跟 函数的调用方式 有关,如下
>[info]1. 函数作为普通函数的调用中,this --> window
>2. 函数作为构造函数的调用中,this --> 当前被创建出来的对象
>3. 函数作为对象的方法调用中,this --> 当前调用该方法的对象
<br>
## 对象的使用
<br>
### 遍历对象的成员
**语法格式**
>[success]通过for..in语法可以遍历一个对象。格式:
>for(var key in obj) {
>
>}
>obj是要遍历的对象。
key是从obj对象中遍历出的属性名,
(obj[key])是从obj对象中遍历出的。
注意:这里只能用中括号语法。
**代码案例**
```
var obj = {};
for (var i = 0; i < 10; i++) {
obj[i] = i * 2;
}
for(var key in obj) {
console.log(key); //对象的属性名,
console.log(obj[key]); //对象的属性值。
console.log(key + "==" + obj[key]);
}
```
### 删除对象的成员
**语法格式**
>[success]利用delete关键字
>delete 成员名;
**代码案例**
```
function fun() {
this.name = 'mm';
}
var obj = new fun();
console.log(obj.name); // mm
delete obj.name;
console.log(obj.name); // undefined
```
## 对象的存储
### 基本类型和引用类型的区别
  基本数据类型简称基本类型,又叫简单类型或值类型,引用数据类型简称引用类型,又叫复杂类型或对象类型
  基本类型的存储特点:在存储数据时,变量中存储的是值本身,因此基本类型又称  值类型。引用类型的存储特点:在存储数据时,变量中存储的仅仅是数据的地址(地址又称引用)。
#### 堆和栈的概念
堆栈空间分配区别:
1、栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
2、堆(操作系统): 存储引用类型(对象),一般由程序员分配释放, 若程序员不释放,由垃圾回收机制回收,分配方式类似于链表。
内存中的堆栈可简化为下图
![](https://box.kancloud.cn/5802cbe0164460dc6ab999887adcb1a9_697x488.png)
```
//定义基本数据类型变量
var a = 5;
var b = a;
a = 6;
console.log(a, b);
```
### 基本类型的数据存储图示如下
![](https://box.kancloud.cn/61e760358bda8ef588f854c1957467a2_731x479.png)
由上图可以知道,基本类型数据只存储在占内存中,跟堆内存块无关。
### 引用类型在内存中的存储
  引用类型又称对象类型,对象是通过new关键字创建出来的,所以引用类型的数据存储图跟new关键字紧密相关。
  当执行new关键字时,JS解析器就在堆中开辟一块内存,并给这块内存分配一个地址,比如 0xaabb,用于存储对象的数据(属性和方法),同时在栈中开辟一块内存,用于存储堆内存的地址(0xaabb)。此时栈中存储的数据就是堆内存块的地址,所以我们称 “ 栈上的地址指向了这块堆内存 ” 。
分析以下代码在执行过程中的变量的值的变化,并画出内存图作进一步分析。
```
// 定义构造函数
function Person(name, age) {
this.name=name;
this.age=age;
this.sayHi=function () {
console.log('你好,我是' + this.name);
}
}
// 调用构造函数
var p1 = new Person('冯宝宝', 100); //将 '冯宝宝', 100 传入函数Person中,得到对象{name='冯宝宝';age=100;},此时对象的属性和方法都存储在堆内存中,
var p2=p1; //将对象p1赋值给对象p2,两个对象名都指向同一个堆内存。
p2.name='张楚岚'; //将对象p2中的name属性的值改为'张楚岚',即将堆内存中的name='冯宝宝'改为name='张楚岚',此时对象p1的属性值也随之给为name='张楚岚'。
console.log(p1);// 输出结果:Person {name: "张楚岚", age: 100, sayHi: ƒ}
console.log(p2);// 输出结果:Person {name: "张楚岚", age: 100, sayHi: ƒ}
```
### 基本类型作为函数的参数
```
function fn(a, b) {
a = a + 2;
b = b + 2;
console.log(a, b);
}
// 在函数外部的x,y是否受影响?
var x = 2;
var y = 3;
fn(x, y);
console.log(x, y);
//注意:函数的参数是局部变量
```
基本类型的数据作为函数参数时的存储图示如下
![](https://box.kancloud.cn/df0cbde0bf1d4b27f891da9a025763df_916x408.png)
根据以下存储图示可知,在**函数内部修改了变量的值,不影响外部变量原有的值。**我们可以画出内存图作进一步分析,就很容易看出不影响的原因。
基本数据类型的数据都是存放在栈内存中,遵循先进后出的原则,当在函数内部修改了变量的值时,实际是新开辟了给内存空间存储改变后的数据。
### 引用类型作为函数的参数
```
// 定义构造函数
function Person(name, age) {
this.name=name;
this.age=age;
this.sayHi=function () {
console.log('你好,我是' + this.name);
}
}
```
方式一、调用自定义构造函数,分析调用前和调用后数据有什么不同。在函数内部修改变量的值。
```
// 定义一个函数,参数用于接收一个person对象
function fn(person) {
person.name='张楚岚';
console.log(person);
}
var p1 = new Person('冯宝宝', 100);
fn(p1);
console.log(p1);
```
引用类型的数据作为函数参数时的存储图示如下
![](https://box.kancloud.cn/bbc3e41266aa3e60d2891ada7190003d_990x404.png)
方式二、调用自定义构造函数,分析调用前和调用后数据有什么不同。在函数内部修改变量的值。
```
// 定义一个函数,参数用于接收一个person对象
function fn(person) {
person.name='张楚岚';
person = new Person('王也',22);
console.log(person);
}
var p1 = new Person('冯宝宝', 100);
fn(p1);
console.log(p1);
```
引用类型的数据作为函数参数时的存储图示如下
![](https://box.kancloud.cn/34853185f00fe23690512f8269c28145_986x391.png)
```
// 定义函数
function fn(array) {
array[0] = -1;
console.log(array);// array[0] -> -1
}
//定义一个数组
var arr = [9, 5, 2, 7, 1, 3, 6];
// 调用函数
fn(arr);
console.log(arr);// arr[0] -> -1
```
![](https://box.kancloud.cn/34205c90582d426cc1539515ccc39728_929x414.png)