js面向對象設計之function類


本文僅探討如何合理的使用 function 在 javascript中實現一個面向對象設計的類。總所周知,javascript 並不能實現一個真正意義上的類,比如 protect 比如 函數重載。
下面開始由淺入深的討論 function 作為類來使用如何盡可能的模擬傳統的面向對象設計。
還有一篇相關博文(關於 class)可對比閱讀:js面向對象設計之class類
下面的 Class01 一個最簡單的類。
function Class01( val, pVal ) { this.val = val; /*實例可直接讀寫的屬性*/ var pVal = pVal; /*實例無法直接讀寫的屬性*/ } Class01.prototype.printVal = function () { var _this = this; /*此處並不會出現this丟失,但推薦這么做*/ console.log( _this.val ); };

對於實例無法直接讀寫的屬性,需要提供接口,首先想到的是提供實例方法,如下面這個例子的 getpVal 和 setpVal。
這樣做的弊端十分明顯,每一個實例都將擁有getpVal和setpVal,是否可以在原型上定義getpVal和setpVal,先來試試。
function Class01( val, pVal ) { this.val = val; /*實例可直接讀寫的屬性*/ var pVal = pVal; /*實例無法直接讀寫的屬性*/ this.getpVal = function () { return pVal; }; this.setpVal = function ( v ) { pVal = v; return this; } }

下面的例子是通過原型方法讀取“私有”屬性pVal,試過發現,運行報錯,pVal並不存在,路子都被堵死了,采用這種方式定義“私有”屬性將只能通過實例方法讀寫。
這種方式顯然不能在工程中使用,下面介紹另外一種方法
function Class01( val, pVal ) { this.val = val; /*實例可直接讀寫的屬性*/ var pVal = pVal; /*實例無法直接讀寫的屬性*/ } Class01.prototype.printVal = function (){ var _this = this; console.log( _this.val ); }; Class01.prototype.getpVal = function (){ console.log( pVal ); }; var ins01 = new Class01( 1, 2 ); ins01.getpVal(); /*此處報錯*/
采用高階函數,return function的方式,在Class01的內部只定義可直接讀寫的屬性,而把“私有”屬性或方法放到Class作用域外部 var Class01 = ( function () { var pValue = ''; /*實例無法直接讀寫的屬性*/ function hello (){ console.log( '歡迎來到nDos的博客' ); } function Class( val, pVal ){ this.val = val; /*實例可直接讀寫的屬性*/ pValue = pVal; } Class.prototype.printVal = function (){ var _this = this; console.log( _this.val ); }; Class.prototype.getpVal = function (){ console.log( pValue ); return pValue; }; Class.prototype.setpVal = function ( v ){ pValue = v; return this; }; Class.prototype.sayHello = function (){ hello(); return this; }; return Class; } )(); var ins01 = new Class01( 1, 2 ); ins01.getpVal(); ins01.setpVal( '2222' ).getpVal(); ins01.sayHello(); 小問題:實例 ins01 是由 Class 實例化而來,還是由 Class01 實例化而來。 console.log( ins01.constructor.name ); /* 此處是 Class */ console.log( ins01 instanceof Class01 ); /* 此處是 true */ 顯然在這里會在類的使用過程中造成疑惑,也會使得項目的實例的歸屬產生問題。可以把 Class01 直接改為 Class 即可。

小tips:在 Google 開發者工具中找到 Class.__proto__.constructor["[[Scopes]]"]
在這里可以看到閉包、全局作用域等信息。

至此,function 類中已經可以實現實例屬性、實例私有屬性和方法、原型方法。
實例方法一般不會在類中定義,而是在實例化之后有客戶自行添加。
原型上也一般不會定義屬性,原因在於原型屬性並不能讀寫(對於數值字符串和布爾值),若可讀寫會影響所有實例。

小tips:對於私有屬性和方法,建議將它們寫入到一個對象當中,進行塊狀管理,在大型項目中代碼結構清晰一些。
var pV = { pVal:'', hello:() => console.log('ok') }

傳統面向對象設計中類還存在靜態方法和靜態屬性,在 javascript 中也可以輕松實現。
靜態方法只能通過類調用,靜態屬性也只能如此使用。靜態屬性和方法,類的實例無法獲取。
var Class = ( function (){ /*代碼略*/ Class.prototype.nDos = { name: 'nDos', sayHello: hello }; Class.prototype.noChangeVal = '實例拿我沒辦法'; Class.staticVal = 'Class的靜態屬性'; Class.staticMethod = function (){ console.log( 'Class的靜態方法' ); }; return Class; } )(); var ins01 = new Class( 1, 2 ), ins02 = new Class( 'a', 'b' ); ins01.nDos.name = 'ins01 say hello nDos'; /*對於數組也是一樣的*/ console.log( 'ins02中的name值:' + ins02.nDos.name ); try { ins01.noChangeVal = '實例1改變原型屬性值'; console.log( ins01.noChangeVal ); console.log( ins02.noChangeVal ); ins01.prototype.noChangeVal = '我就是想改變它'; /*報錯*/ } catch ( e ) { console.Error( e ); } 總結:
1、靜態屬性和原型屬性,都可以用來儲存實例化次數,同樣也可以用來儲存每個實例的引用。
此處建議盡量使用靜態屬性,不使用原型屬性的原因在於原型屬性容易被實例屬性覆蓋。
2、顯然 function 也可以被當作函數執行,在實際項目中為了防止這種情況發生,需要加入防御性代碼:
if ( this.constructor.name !== 'Class' ) {
throw new Error( '類只能被實例化' );
}
就算這么做了,還是可以繞過去:
function fakeClass () {
Class01.call(this, '1', '2');
}
fakeClass.prototype = Class.prototype;
var ins = new fakeClass();
當然這算是繼承里邊的內容。
3、為避免2中出現的情況,就是加入以下代碼:
if ( !new.target || new.target.name !== 'Class' ) {
throw new Error( '類只能被實例化' );
}
4、只有函數才能被實例化,實例化之后得到的變量是實例並不是函數或者說是 object。
5、function 類實例化過程,實際上是函數體的執行過程。這個執行過程也就是初始化的過程。
在這種過程當中可以做相當多的事情,比如 上述的防御性代碼、實例(this)的傳遞與儲存、使用 Object.assign 給this擴充功能(Mixin)
以及加入鈎子函數,讓類消費者自行決定如何實例化等
6、function 類一般不會有 return 當然也可以存在。若沒有 return,函數會自動返回 this 做為類的實例。
若存在 return,該類生成的內容便可比較靈活豐富,讀者可自行想象,比如通過不同的鈎子函數返回不同的內容。

結尾是學習用源代碼:
var Class = ( function (){
    var pValue = ''; /*實例無法直接讀寫的屬性*/
    function hello (){
        console.log( '歡迎來到nDos的博客' );
    }
    function Class( val, pVal ){
        if ( this.constructor.name !== 'Class' ){
            throw new Error( '類只能被實例化' );
        }
        if ( !new.target || new.target.name !== 'Class' ){
            throw new Error( '類只能被實例化' );
        }
        this.val = val; /*實例可直接讀寫的屬性*/
        pValue = pVal;
    }
    Class.prototype.printVal = function (){
        var _this = this;
        /*盡管此處並不會出現this丟失的情況,但推薦總是這么做*/
        console.log( _this.val );
    };
    Class.prototype.getpVal = function (){
        console.log( pValue );
        return pValue;
    };
    Class.prototype.setpVal = function ( v ){
        pValue = v;
        return this;
    };
    Class.prototype.sayHello = function (){
        hello();
        return this;
    };
    Class.prototype.nDos = {
        name: 'nDos',
        sayHello: hello
    };
    Class.prototype.noChangeVal = '實例拿我沒辦法';
    Class.staticVal = 'Class的靜態屬性';
    Class.staticMethod = function (){
        console.log( 'Class的靜態方法' );
    };
    return Class;
} )();
var ins01 = new Class( 1, 2 ),
    ins02 = new Class( 'a', 'b' );
ins01.nDos.name = 'ins01 say hello nDos'; /*對於數組也是一樣的*/
console.log( 'ins02中的name值:' + ins02.nDos.name );
try {
    ins01.noChangeVal = '實例1改變原型屬性值';
    console.log( ins01.noChangeVal );
    console.log( ins02.noChangeVal );
    /* ins01.prototype.noChangeVal = '我就是想改變它'; 報錯*/
} catch ( e ) {
    console.error( e );
}
function fakeClass(){
    Class.call( this, '1', '2' );
}
fakeClass.prototype = Class.prototype;
var ins = new fakeClass();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM