本文為譯文,初次翻譯,如有誤導,請多多包含,如閱讀英文,可直接戳連結即可js之工程建構函式模式
若想聽音訊內容,可戳連結js之工廠建構函式模式
類模式
前言
在面向(oriented)物件程式設計中,一個類是一個可擴充套件的程式程式碼的模板,用於建立物件,為狀態(成員變數)和行為實現(implementations)(成員函式或方法)提供初始值
JavaScript中有一個特殊的語法結構和關鍵字類。但在學習之前,我們應該考慮“類”這個術語來自於物件導向程式設計的理論。定義在上面引用,它是語言無關獨立的
在JavaScript中有幾個眾所周知的程式設計模式,即使不使用class關鍵字也可以編寫類。在這裡,我們首先來談談他們
這個類的構造將在下一章中描述,但是在JavaScript中它是一個“語法糖”,是我們在這裡學習的一種模式的擴充套件
功能類模式
根據定義,下面的構造器函式可以被認為是“類
/*
*
* 用new關鍵字+函式名(),那麼這個函式為構造器函式
* @construtor: User
* @methods:sayHi
*
*
*/
functoin User(name){
this.sayHi = function(){
alert(name);
}
}
let user = new User("john");
user.sayHi(); // john
複製程式碼
結果如下所示:
![功能類模式](https://i.iter01.com/images/f38113ccdfabf666c8047b85799d9c4ddf29d42c3453aea775de08653686fe1a.gif)
- 它是一個用於建立物件的“程式程式碼模板”(可以用new來呼叫)
- 它提供了狀態的初始值(引數名稱)
- 它提供了方法(sayHi)
在函式類模式中,使用者內部的區域性變數和巢狀函式,沒有分配給它,從內部可見,但在外部程式碼無法訪問
所以我們可以很容易地新增內部函式和變數,比如calcAge()
/*
* 新增內部函式和變數
* @constructor: User
* @parameter: 引數name,birthday
* @function calcAge
* @methods: sayHi
* @return 時間戳
*
*/
function User(name,birthday){
// only visible from other methods inside User
function calcAge(){
return new Date().getFullYear()-birthday.getFullYear();
}
this.sayHi = function(){
alert(`${name,age:${calcAge()}}`);
}
}
let user = new User("john",new Date(2000,0,1));
user.sayHi(); // john,age:18
複製程式碼
結果如下所示:
![內部函式與變數](https://i.iter01.com/images/1e549fc36dba9f4f32ea6a101ed4a58eb0528b04cf8c35411f77cc1e4a04c9d6.gif)
另一方面,說Hi就是外在的,公開的方法。建立使用者的外部程式碼可以訪問它
這樣我們就可以從外部程式碼中隱藏內部實現(internal implementation)細節和輔助方法。只有分配給這個建構函式才可以看得見外面的
工廠類模式
我們可以建立一個班級,而不使用新的
像這樣
/*
* 工廠類模式
* @constructor User
* @parameter 形式引數:name,birthday
* @function calcAge
* @return 當前的年份-出生的年份
* @return User函式返回一個sayHi函式,將名字和年齡結果進行輸出
*
*
*/
function User(name,birthday){
// only visible from other methods inside User
function calcAge(){
return new Date().getFullYear()-birthday.getFullYear();
}
return{
sayHi(){
alert(`${name},age:${calcAge()}`);
}
}
}
let user = User("john",new Date(2000,0,1)); // 函式名的呼叫,函式表示式賦值
user.sayHi();
複製程式碼
實現的效果如下所示
![工廠類模式](https://i.iter01.com/images/90c352371cd1820817ddac1ba129935f9b1f4334be9313d59c0d5586e9fc55b8.gif)
基於原型的類
基於原型的課程是最重要的,也是最好的。功能和工廠類模式在實踐中很少使用
不久你就會明白為什麼
這是用原型重寫的同一個類
/*
*
* 基於原型重寫的一個類
* @function User
* @parameter name,birthday
* @prototype
* @methods: _calcAge,sayHi
*
*
*/
function User(name,birthday){
this._name = name;
this._birthday = birthday;
}
User.prototype._calcAge = function(){
return new Date().getFullYear()-this._birthday.getFullYear();
}
User.prototype._calcAge = function(){
return new Date().getFullYear()-this_birthday.getFullYear();
}
User.prototype.sayHi = function(){
alert(`${this._name},age:${this._calcAge()}`);
}
let user = new User("john",new Date(2000,0,1));
user.sayHi();
複製程式碼
實現效果如下所示:
![基於原型的類](https://i.iter01.com/images/f4675539e705a5eb782f82502e52ee8718df409937418906ff75cab3785893ca.gif)
- 建構函式User僅初始化當前的物件狀態
- 方法被新增到User.prototype中
正如我們所看到的,方法在詞法作用域上不在函式User內部,它們並不共享一個通用的作用域環境.如果我們在函式User中宣告變數,那麼它們將不會被方法看到
所以,有一個眾所周知的協議,內部屬性和方法字首為下劃線“”。像_name或_calcAge()。從技術上講,這只是一個協議,外部程式碼仍然可以訪問它們。但大多數開發人員認識到“”的含義,並儘量不要觸控外部程式碼中的字首屬性和方法
以下是功能模式的優點:
-
在功能模式中,每個物件都有自己的每個方法的副本。我們在建構函式中分配了this.sayHi = function(){...}和其他方法的單獨副本
-
在原型模式中,所有的方法都是在所有使用者物件之間共享的User.prototype中。一個物件本身只儲存資料
所以原型模式更具有記憶效率
但不僅如此。原型允許我們以非常有效的方式設定繼承。內建的JavaScript物件都使用原型。還有一個特殊的語法結構:“類”,為他們提供漂亮的語法。還有更多,所以讓我們繼續他們
類基於原型的繼承
假設我們有兩個基於原型的類
兔子:
/*
*
* 類基於原型的繼承
*
* @function Rabbit
* @paraterm name
* @method jump
* @constructor: Rabbit
*
*/
function Rabbit(name){
this.name = name;
}
Rabbit.prototype.jump = function(){
alert(`${this.name}jimp!`);
}
let rabbit = new Rabbit("my rabit");
rabbit.jump(); // my rabit jimp
複製程式碼
實現的效果圖如下
![類基於原型的繼承](https://i.iter01.com/images/acc3fc666977be10a11f79d6b415681fb2b3c987e995869309e97af4e1c82526.gif)
![過程圖解](https://i.iter01.com/images/acfebe472cec80d33b0c620e2a74f913d71a025c23a20c59cb5662e762f9530d.png)
/*
* @constructor:Animal
* @methods:eat
*
*
*/
function Animal(name){
this.name = name;
}
Animal.prototype.eat = function(){
alert(`${this.name}eats`);
}
let animal = new Animal("My animal");
animal.eat();
複製程式碼
程式碼例項效果如下:
![js之工廠建構函式模式(譯)](https://i.iter01.com/images/f72e3eb3158e4127257c02f2a8a9b0c4770f8ec1a6e084d204bafc60c1fab3aa.gif)
![類的繼承](https://i.iter01.com/images/1abc15205de57476f183fdf9a6908c7bb9702066e28550fba610629c6ffde058.png)
但是我們希望兔子能擴充套件動物。換句話說,兔子應該以動物為基礎,可以使用動物的方法,並用自己的方法擴充套件它們
原型語言是什麼意思?
現在兔子物件的方法在Rabbit.prototype
中。如果在Rabbit.prototype
中找不到方法,我們希望兔子使用Animal.prototype
作為“後備”
所以原型鏈應該是Rabbit
→Rabbit.prototype
→Animal.prototype
。
像這樣
![原型鏈](https://i.iter01.com/images/d98c3dd08e94cb42970fdd503dab0822476b3076525780fbc15b6ca6c4c629da.png)
/*
* @constructor Animal,Rabbit
* @paraterm name
* @methods:eat,jump
*
*
*
*/
// same Animal as before
function Animal(name){
this.name = name;
}
// All animals can eat,right?
Animal.prototype.eat = function(){
alert(`${this.name}eats`);
}
// same Rabbit as before
function Rabbit(name){
this.name = name;
}
Rabbit.prototype.jump = function(){
alert(`${this.name}jumps`);
}
// setup the inheritance chain
Rabbit.prototype._proto_= Animal.prototype; // (*)
let rabbit = new Rabbit("white Rabbit");
rabbit.eat(); // rabbits can eat too
rabbit.jump();
複製程式碼
![js之工廠建構函式模式(譯)](https://i.iter01.com/images/1a9a2c912194fc902cf4a27bdee2f497f69c00d901d91474c726cc20bbb65019.gif)
Rabbit.prototype
中搜尋方法,然後是Animal.prototype
。然後,為了完整起見,如果在Animal.prototype中沒有找到該方法,則繼續在Object.prototype
中進行搜尋,因為Animal.prototype
是一個普通的普通物件,所以它繼承了它
所以這是完整的畫面
![過程圖解](https://i.iter01.com/images/64d28b1cf8f63ed102c6b6bd51ba8b2d1b4650f640dc94c9e3f178f331040ca3.png)
小結
術語“類”來自物件導向程式設計。在JavaScript中,它通常意味著功能類模式或原型模式。原型模式更強大,更高效,所以它建議堅持下去
根據原型模式
- 方法儲存在Class.prototype中
- 原型相互繼承
總結
在本節當中,主要講的是工廠建構函式模式,用於建立物件的模板,其中模板可以粗俗的理解模具,它是基於一份模具建立很多個不同的物件,工廠建構函式就是用於建立多個共享特性和行為的物件,通過建構函式生成的物件具有預設的屬性和方法,而原型就是更改物件下面公用的屬性和方法,讓公用的屬性和方法達到共用一份,一是為了減少記憶體的開銷,提高效能,另一方面是為了擴充,當需要在程式碼的其餘所有部分通過遮蔽較為複雜的的物件建立方法來簡化某些特定物件的建立過程時,使用工廠模式最為合適,其實它也就是物件導向的一種寫法