[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、堆(操作系統): 存儲引用類型(對象),一般由程序員分配釋放, 若程序員不釋放,由垃圾回收機制回收,分配方式類似于鏈表。
內存中的堆棧可簡化為下圖

```
//定義基本數據類型變量
var a = 5;
var b = a;
a = 6;
console.log(a, b);
```
### 基本類型的數據存儲圖示如下

由上圖可以知道,基本類型數據只存儲在占內存中,跟堆內存塊無關。
### 引用類型在內存中的存儲
  引用類型又稱對象類型,對象是通過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);
//注意:函數的參數是局部變量
```
基本類型的數據作為函數參數時的存儲圖示如下

根據以下存儲圖示可知,在**函數內部修改了變量的值,不影響外部變量原有的值。**我們可以畫出內存圖作進一步分析,就很容易看出不影響的原因。
基本數據類型的數據都是存放在棧內存中,遵循先進后出的原則,當在函數內部修改了變量的值時,實際是新開辟了給內存空間存儲改變后的數據。
### 引用類型作為函數的參數
```
// 定義構造函數
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);
```
引用類型的數據作為函數參數時的存儲圖示如下

方式二、調用自定義構造函數,分析調用前和調用后數據有什么不同。在函數內部修改變量的值。
```
// 定義一個函數,參數用于接收一個person對象
function fn(person) {
person.name='張楚嵐';
person = new Person('王也',22);
console.log(person);
}
var p1 = new Person('馮寶寶', 100);
fn(p1);
console.log(p1);
```
引用類型的數據作為函數參數時的存儲圖示如下

```
// 定義函數
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
```
