在ES6出現之前,JavaScript不能真正被稱為 面向對象的編程語言壤躲,因為 class
僅僅作為其保留字而非關鍵字,而ES6之后备燃,引入了class
碉克,使程序員可以用自己更加熟悉的方式創(chuàng)建對象;
至于ES6和ES5有什么區(qū)別并齐,應該就是上面提到的可以讓程序員更爽地coding漏麦,而程序員爽了客税,根據 工作量守恒定律,總有某個事物要干更多的活撕贞,沒錯更耻,就是計算機。
為了兼容某些不支持ES6的瀏覽器捏膨,我們可以引入 Babel 庫將ES6代碼 “編譯” 成ES5之后執(zhí)行,而對于支持ES6的瀏覽器秧均,在其JavaScript引擎中會自動進行“編譯”操作;
所以号涯,總體上看目胡,ES6和ES5在功能上是等效的,即用ES6能完成的任務链快,用ES5必然能夠完成誉己,只是在語法上,ES6提供了更多的 語法糖域蜗,讓程序員嘗到甜頭巨双;所以為了更好的理解JavaScript對象,我們回歸初心地消,從ES5中窺視JavaScript所創(chuàng)建的對象世界炉峰;
對象的創(chuàng)建
對象有兩個基本元素:屬性和方法;
屬性用于存儲數據脉执,方法用于存儲代碼疼阔;接下來,我們從簡單到復雜半夷,來理解JavaScript創(chuàng)建對象的演變史婆廊。
創(chuàng)建Object對象并賦值時代
我們可以通過 new Object
創(chuàng)建Object,并為其賦屬性和方法來創(chuàng)建對象:
// 代碼段 1
var p = new Object();
p.name = "x";
p.age = 20;
p.say = function(){
console.log("My name is",this.name,",this year is",this.age);
}
這樣我們就得到一個含有2個屬性巫橄,1個方法的對象淘邻;
通過執(zhí)行 p.say()
得到輸出 : My name is x ,this year is 20
;
但是,這些幾行代碼非常松散湘换,每一行代碼都像一條單獨的語句宾舅,為了體現這些屬性和方法是一個整體,而不是一個個獨立的存在彩倚,我們需要進入下一個時代筹我;
字面量賦值時代
我們通過給一個變量賦一個字面量,即可創(chuàng)建一個對象:
// 代碼段 2
var p = {
name : "x" ,
age : 20 ,
say : function(){
console.log("My name is",this.name,",this year is",this.age);
}
}
這樣帆离,我們就創(chuàng)建好了一個對象蔬蕊,這個對象有了自己的屬性和方法;
通過 console.log(typeof p)
,輸出為object
可知哥谷, p 的類型是一個對象岸夯;
代碼段1和代碼段2相比麻献,代碼段2的結構更為合理,所有的屬性和方法都用大括號包含猜扮,更利于閱讀勉吻,也體現了整體性,但功能上是等效的破镰;
不過我們又發(fā)現餐曼,每次要創(chuàng)建一個相似的對象,都需要寫一遍屬性名鲜漩,非常地不優(yōu)雅源譬,所以,我們又要進入下一個時代孕似;
工廠模式時代
我們可以通過調用一個函數創(chuàng)建我們需要的對象踩娘,并返回該對象,從而使代碼可重用喉祭,這個函數就是傳說中的 工廠养渴,代碼如下:
// 代碼 3
function createPerson(name,age){
var t = new Object();
t.name = name;
t.age = age;
t.say = function(){
console.log("My name is",this.name,",this year is",this.age);
}
return t;
}
var p = createPerson("x",20);
代碼3是將代碼1變?yōu)榱斯S模式;
下面再將代碼2也變?yōu)楣S模式:
function createPerson(name,age){
return {
name:name,
age : age,
say = function(){
console.log("My name is",this.name,",this year is",this.age);
}
};
}
var p = createPerson("x",20);
```
工廠模式的代碼要比前面兩個時代的代碼優(yōu)雅泛烙,我們只需要調用一個函數即可獲得我們想要的對象理卑;
但是,我們又發(fā)現了一個新的問題(別問我為什么總是能發(fā)現新問題蔽氨,因為就是有一雙善于觀察的眼睛藐唠,手動傲嬌 ^_^):
通過上述3中方式創(chuàng)建的對象在使用 `typeof` 時,返回的都是 `object`鹉究,而通過 `實例 instanceof 類` 只有在類為 `Object`時宇立,才返回true,就是說自赔,上面3中方法創(chuàng)建的對象都是無差別的對象妈嘹,我們不能分辨出它們的類型;
這就麻煩了绍妨,比如我們有這么一個函數:
```javascript
function seeDoctor( o ){
if(o是人){
請人醫(yī)治療
}
if(o是動物){
請獸醫(yī)治療
}
}
```
那我們創(chuàng)建的對象因為不能判斷其是人是獸润脸,將不能選擇適合的治療方案;
要解決這個問題他去,有兩種思路:
- 給對象添加信息津函,即為每一個對象添加一個屬性 `type` ,用于指明其類型孤页;
- 讓js解釋器能夠判斷其類型;
第一種方式比較 *丑陋*涩馆,我們需要管理更多的數據行施,但比較容易理解允坚;第二種是更優(yōu)雅的方法,也推動我們進入下一個時代蛾号;
### 構造函數時代
構造函數時代的主要任務是讓創(chuàng)建的對象自帶類別說明屬性稠项,即通過`instanceof` 就能判斷出其所屬的類:
```javascript
// 類是一個函數,約定:
// 普通函數第一個字母小寫鲜结,類函數第一個字母大寫
function Person(name,age){
this.name = name;
this.age = age;
this.say = function(){
console.log("My name is",this.name,",this year is",this.age);
}
}
function Animal(){}
var p = new Person("x",20) ;
```
注意展运,創(chuàng)建對象時,必須使用關鍵字 **new** 精刷。
此時拗胜,我們通過 `p instanceof Person`,返回的結果為 `true`怒允,而通過 `p instanceof Animal` 埂软,返回結果為 `false`,從而使對象實例自帶類型屬性纫事;
完美勘畔! But,又雙叒叕發(fā)現了不足丽惶,我們用上述各種函數創(chuàng)建兩個對象:
```javascript
var p1 = new Person("x",20);
var p2 = new Person("y",21);
console.log(p1.say==p2.say) ; // 輸出的是false
```
我們發(fā)現:雖然 *say* 函數的代碼相同炫七,但兩個對象實例的 *say* 居然指向不同的代碼塊,如果我們有100個實例钾唬,相同的代碼塊就需要有100份万哪,極大的內存浪費,這是我們所不能忍受的知纷,因此迫切希望下一個時代的到來壤圃!
### 構造函數+原型時代
原型就是所有對象實例所共享的一個 **對象**,這個對象中的屬性就是共享屬性(在c++中稱為靜態(tài)變量)琅轧,方法就是共享方法伍绳;
```javascript
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.say = function(){
console.log("My name is",this.name,",this year is",this.age);
}
var p = new Person("x",20);
```
上述代碼創(chuàng)建的對象實例 p 有自己的屬性name和age,以及共享的方法say乍桂;通過 `p.say()` 即可打印出:*My name is x ,this year is 20* ;
執(zhí)行 `p.say()` 的時候冲杀,p先搜索其自身是否有方法 `say`,如果有睹酌,就執(zhí)行权谁,如果沒有,就搜索其原型對象是否有 `say` 方法憋沿,如果還是沒有旺芽,就搜索其原型對象的原型對象是否有say屬性(即沿著原型鏈搜索say方法,這也是繼承的實現機制),如果原型鏈上都沒有say方法采章,就拋出錯誤运嗜,否則,執(zhí)行搜索到的方法悯舟。
p通過屬性`p.__proto__` 指向原型對象 `Person.prototype` , 從而獲取原型上的所有屬性和方法担租;
如果p上也定義一個方法 `say`:
```javascript
p.say = function(){console.log("Hello,world");}
```
則該方法將會 **覆蓋** 原型上的say方法,即調用 `p.say` 輸出的將是 *Hello,world* 抵怎,而如果通過 `delete p.say` 刪除掉 `say` 屬性奋救,則調用 `p.say` 時,執(zhí)行的代碼又是原型上的 say 代碼反惕;
總結:原型就是一個類所創(chuàng)建的所有對象實例共享的一個對象尝艘;
但是,原型對象的定義和構造函數分開了承璃,這又使結構不太優(yōu)美利耍,所以,我們又得進入下一個時代盔粹;
### 構造原型時代
為了解決原型定義和構造函數分離的問題隘梨,我們決定將原型定義放到構造函數中,就出現了以下代碼:
```javascript
function Person(name,age){
this.name = name;
this.age = age;
Person.prototype.say = function(){
console.log("My name is",this.name,",this year is",this.age);
}
}
var p = new Person("x",20);
```
OK舷嗡,完成了原型定義和構造函數的合并轴猎,結構也變得更加優(yōu)美了,但是进萄,又出現了一個問題:
每次執(zhí)行創(chuàng)建 Person 對象實例的時候捻脖,都要重新定義一遍 `Person.prototype.say` 方法,雖然這不會增加內存泄漏(以前定義的say代碼由于沒有被引用中鼠,內存塊將會被自動回收)可婶,但卻增加了cpu的工作量,所以我們需要進入下一個時代援雇;
### 優(yōu)化構造原型時代
為了避免 `Person.prototype.say` 函數的重復定義矛渴,我們可以先判斷該函數是否已定義,如果沒有定義惫搏,再對其進行定義:
```javascript
function Person(name,age){
this.name = name;
this.age = age;
if(typeof(Person.prototype.say)=="undefined"){
Person.prototype.say = function(){
console.log("My name is",this.name,",this year is",this.age);
} ;
}
}
var p = new Person("x",20);
```
通過以上7個時代的迭代具温,我們終于在 JavaScript中創(chuàng)建了一個基本上符合我們要求的對象;
完筐赔!