原文地址 本文主要講述了使用JavaScript創建對象的幾種方式,分別是傳統的Object構造函數、對象字面量、工廠模式、構造函數模式、原型模式、組合模式,以及es6的class定義類。然後從babel的角度探究es5與es6創建對象的區別。 1.創建對象的幾種方式 (1).Object構造函數和 ...
本文主要講述了使用JavaScript創建對象的幾種方式,分別是傳統的Object構造函數、對象字面量、工廠模式、構造函數模式、原型模式、組合模式,以及es6的class定義類。然後從babel的角度探究es5與es6創建對象的區別。
1.創建對象的幾種方式
(1).Object構造函數和對象字面量
在早期js開發中,很多開發者會使用Object構造函數的方式來創建一個對象,通過調用Object構造函數new一個Object對象,然後再給這個對象的每一個屬性和方法進行賦值
1 var person = new Object(); 2 person.age = 22; 3 person.name = 'Dolanf'; 4 person.code = function() { 5 console.log(‘hello world!’); 6 };
後來出現了對象字面量的寫法,由於使用對象字面量創建對象的寫法簡單直觀,所以Object構造函數寫法漸漸被對象字面量的寫法所取代,對象字面量是通過在一個大括弧裡面使用鍵值對的方式表示每一個屬性和方法,每一個鍵值對之間使用逗號隔開
1 var person = { 2 age: 22, 3 name: 'Dolanf', 4 code: function() { 5 console.log('hello world!'); 6 } 7 }
雖然對象字面量簡單直觀,但是上面兩種方法都存在一個共同的問題:當需要創建很多很多個Person對象的時候,只能一個一個去創建,每一個對象的方法和屬性都需要單獨寫,這使得代碼沒有絲毫復用性可言,違背了對象封裝的特性。於是乎,工廠模式就隨之出現了
(2)工廠模式
工廠模式通過將對象的創建封裝到一個方法中,再通過在調用該方法時傳入參數而實現對象的實例化,解決了以上提到的產生大量重覆代碼的問題
1 function createPerson(age, name) { 2 var o = new Object(); 3 o.age = age; 4 o.name = name; 5 o.code = function() { 6 console.log('hello world!'); 7 }; 8 9 return o; 10 } 11 12 var person1 = createPerson(11, '小白'); 13 var person2 = createPerson(12, '小黑');
但是工廠模式也存在一個不足,就是通過該方法創建的對象的構造函數全都是Object,沒有辨識度。沒有辦法通過構造函數辨別一個對象到底是Person還是Dog,亦或是Cat。於是乎,為瞭解決這個問題,就引入了構造函數模式。
(3)構造函數模式
構造函數模式就是通過定義一個function函數,然後通過this給對象的屬性和方法進行賦值。當我們實例化對象時,只需在該函數前面加一個new關鍵字就可以了。
1 function Person(age, name) { 2 this.age = age; 3 this.name = name; 4 this.code = function() { 5 console.log('hello world!'); 6 }; 7 } 8 9 var person1 = new Person(11, '小白'); 10 var person2 = new Person(12, '小黑');
構造函數模式解決了工廠模式中的對象識別問題,通過:
1 console.log(person1 instanceof Person); // true
可以看出person1能成功被識別為一個Person對象。
但是,構造函數模式也同樣存在一個缺點,就是構造函數里的屬性和方法在每個對象上都要實例化一遍,包括對象共用的屬性和方法,這樣就造成了代碼的復用性差的問題。所以大多數人會考慮將構造函數模式和原型模式組合起來使用。在這裡先介紹一下原型模式。
(4)原型模式
原型模式是通過將所有的屬性和方法都定義在其prototype屬性上,達到這些屬性和方法能被所有的實例所共用的目的。代碼如下所示:
1 function Person(age, name) { 2 Person.prototype.age = age; 3 Person.prototype.name = name; 4 Person.prototype.code = function() { 5 console.log('hello world!'); 6 }; 7 } 8 9 var person1 = new Person(); 10 var person2 = new Person();
當然,這種方法在項目開發中是沒有人會使用的,因為當一個對象上的屬性改變時,所有對象上的屬性也會隨之改變,這是非常不切實際的。在這裡提及原型模式是為了介紹以下的構造函數+原型組合模式.
(5)構造函數+原型組合模式
組合模式是將構造函數模式和原型模式結合在一起,繼承了它們優點的同時又避免了各自的缺點。它將具有各自特點的屬性和方法定義在構造函數中,將實例間共用的屬性和方法定義在prototype上,成為了在es6出現之前使用最普遍的一種創建對象模式。
1 function Person(age, name) { 2 this.age = age; 3 this.name = name; 4 this.cry = function() { 5 console.log(name + 'is crying!!! T^T'); 6 } 7 } 8 Person.prototype = { 9 constructor: Person, 10 sayName: function() { 11 console.log(this.name); 12 } 13 } 14 var person1 = new Person(11, '小白'); 15 var person2 = new Person(12, '小黑');
(6)class定義類
當然,前面講的都是浮雲,現在大家都用class定義類啦,class的出現就是為了讓定義類能更加簡單。回到上面的Person構造函數上,我們現在將其改造成使用class定義的方式:
1 class Person{ 2 constructor(age, name) { 3 this.age = age; 4 this.name = name; 5 this.cry = function() { 6 console.log(name + 'is crying!!! T^T'); 7 } 8 } 9 sayName() { 10 console.log(this.name); 11 } 12 } 13 var person1 = new Person(11, '小白'); 14 var person2 = new Person(12, '小黑');
使用class定義類跟上面的構造函數+原型組合模式有一些相似之處,但又有所區別。
class定義的類上有個constructor方法,這就是構造方法,該方法會返回一個實例對象,this代表的就是實例對象,這跟上邊的構造函數模式很類似。
此外,class上的方法都是定義在prototype上的,這又跟原型模式有一些相似之處,這個class里的sayName等價於
1 Person.protorype.sayName = function() { 2 console.log(this.name); 3 }
雖然class定義的類跟es5中的構造函數+原型組合模式很相似,但是他們還是存在不少區別的,下麵對比如下:
2.es5與es6定義對象的區別
1)class的構造函數必須使用new進行調用,普通構造函數不用new也可執行。
2)class不存在變數提升,es5中的function存在變數提升。
3)class內部定義的方法不可枚舉,es5在prototype上定義的方法可以枚舉。
為什麼會存在以上這些區別呢?下麵使用babel將es6轉化成es5看看它的實現過程就知道了。
3.es6中class轉化為es5
下麵將講述使用babel將以上的class定義Person類轉換成使用es5實現:
es6代碼:
1 class Person{ 2 constructor(age, name) { 3 this.age = age; 4 this.name = name; 5 this.cry = function() { 6 console.log(name + 'is crying!!! T^T'); 7 } 8 } 9 sayName() { 10 console.log(this.name); 11 } 12 } 13 var person1 = new Person(11, '小白'); 14 var person2 = new Person(12, '小黑');
使用babel轉化成的es5後的代碼:
1 'use strict'; // es6中class使用的是嚴格模式 2 3 // 處理class中的方法 4 var _createClass = function () { 5 function defineProperties(target, props) { 6 for (var i = 0; i < props.length; i++) { 7 var descriptor = props[i]; 8 // 預設不可枚舉 9 descriptor.enumerable = descriptor.enumerable || false; 10 descriptor.configurable = true; 11 if ("value" in descriptor) descriptor.writable = true; 12 Object.defineProperty(target, descriptor.key, descriptor); 13 } 14 } 15 return function (Constructor, protoProps, staticProps) { 16 if (protoProps) defineProperties(Constructor.prototype, protoProps); 17 if (staticProps) defineProperties(Constructor, staticProps); 18 return Constructor; 19 }; 20 }(); 21 22 // 對構造函數進行判定 23 function _classCallCheck(instance, Constructor) { 24 if (!(instance instanceof Constructor)) { 25 throw new TypeError("Cannot call a class as a function"); 26 } 27 } 28 29 // class Person轉換為 es5的function 30 var Person = function () { 31 function Person(age, name) { 32 // 調用了_classCallCheck檢查Person是否為構造函數 33 _classCallCheck(this, Person); 34 35 this.age = age; 36 this.name = name; 37 this.cry = function () { 38 console.log(name + 'is crying!!! T^T'); 39 }; 40 } 41 42 // 調用_createClass處理定義在class中的方法。 43 _createClass(Person, [{ 44 key: 'sayName', 45 value: function sayName() { 46 console.log(this.name); 47 } 48 }]); 49 50 return Person; 51 }(); 52 53 var person1 = new Person(11, '小白'); 54 var person2 = new Person(12, '小黑');
這裡我將轉換後的代碼格式化並加上了一些註釋。
從以上代碼可以看出,class主要是通過兩個函數實現:_createClass和_classCallCheck。
所以為什麼會存在上述區別呢:
1)class的構造函數必須使用new進行調用,普通構造函數不用new也可執行。
class中的constructor會直接轉化為function構造函數,然後在function中通過 _classCallCheck的檢查該function是否是一個Constructor。因為有_classCallCheck檢查必須是instanceof Constructor,所以class必須使用new進行調用。
2)class不存在變數提升,es5中的function存在變數提升。
class轉變成了函數表達式進行聲明,因為是函數表達式聲明的,所以class不存在變數提升。
3)class內部定義的方法不可枚舉,es5在prototype上定義的方法可以枚舉。
class中定義的方法會傳入 _createClass中,然後 Object.defineProperty將其定義在Constructor.prototype上。所以class中的方法都是定義在Constructor.prototype上的。
由於defineProperties中的
1 descriptor.enumerable = descriptor.enumerable || false;
將屬性的 enumerable預設為false,所以class中定義的方法不可枚舉。
第一次寫博客,內容也是copy原作者,所有代碼都有手擼驗證過一遍,加深印象。