1. 程式人生 > >js面向對象設計之function類

js面向對象設計之function類

發現 文章 類實例化 執行過程 原型 js面向對象 靜態 java 執行


/*本文並非是介紹JavaScript的原型的原理的文章,
**僅僅討論function作為類來使用時如何近似傳統的面向對象設計
**/

/*function作為類的用法
**下面是一個最簡單的類
**實例將有自己的屬性val和pVal,也有方法printVal和pMethod
**/

function Class01( val, pVal )
{
    this.val = val; /*實例可直接讀寫的屬性*/

    var pVal = pVal; /*實例無法直接讀寫的屬性*/
}

Class01.prototype.printVal = function ()
{
    var _this = 
this; /*盡管此處並不會出現this丟失的情況,但推薦總是這麽做*/ console.log( _this.val ); }; /*實例可直接讀寫的屬性和方法不再多說 **對於不可直接讀寫的屬性pVal(這是傳統面向對象語言中的私有屬性) **需要通過公有接口來訪問pVal,如下面的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; /*盡管此處並不會出現this丟失的情況,但推薦總是這麽做*/ console.log( _this.val ); }; Class01.prototype.getpVal = function () { console.log( pVal ); }; var ins01 = new Class01( 1, 2 ); ins01.getpVal(); /*此處報錯*/ /*采用高階函數的方式,return Class的方式 **在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; /*盡管此處並不會出現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*/ /*Class01這個變量在ins01中找不到任何的痕跡 **想要搞清楚可能需要到JS引擎中去找 **本文目的顯然並不是要搞清楚這個問題,留給讀者研究或者持續關註本博客 **console.log( ins01 ) **在Google的開發者工具中找到如下的屬性 **Class.__proto__.constructor["[[Scopes]]"] **Google工具顯示了與Class有關的閉包,在內部可以看到pValue和hello() **顯然Class在全局作用域中也不存在 **/ /*該類已經實現的內容有 **公有、私有屬性和方法、原型方法 **至於公有方法,實例化之後在定義 **對於工程化來講,私有屬性和方法這麽寫維護會很困難 **可以將私有屬性和方法都放到object中方便維護 **var pV = { pVal:‘‘, hello:()=>console.log(‘ok‘) } **/ /*類還有靜態方法和靜態屬性 **通過Class.staticVal和Class.staticMethod來定義 **原型上也可以定義屬性,該屬性被所有的實例所共享 **原型上的屬性只能是引用(array和object)時,才能被存儲 **/ var Class01 = ( 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 Class01( 1, 2 ), ins02 = new Class01( ‘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 ); } /*Class的靜態屬性和方法都可以在Class01上找到 **原型上的屬性通過上述的了解也學習過了 **是時候來一波總結 **並不是總結學習了什麽,而是思考可以用來幹什麽 **以及其中可能存在的缺陷 **/ /*總結 **1、靜態屬性和原型屬性,都可以用來儲存實例化次數 ** 同樣也可以用來儲存每個實例的引用 此處建議將實例化次數和對實例的引用都儲存在靜態屬性中 因為原型屬性在編程過程中 並不容易分清楚它究竟是原型屬性還是實例屬性 **2、顯然,function也是可以被當作函數而執行 ** 避免這種情況,需要加入防禦性代碼 if ( this.constructor.name !== ‘Class‘ ) { throw new Error( ‘類只能被實例化‘ ); } ** 就算這麽做了,還是可以繞過去,比如 function fakeClass () { Class01.call(this, ‘1‘, ‘2‘); } fakeClass.prototype = Class01.prototype; var ins = new fakeClass(); ** 當然這也可以算是繼承的一種 **3、為避免2中出現的情況,就是加入以下代碼 if ( !new.target || new.target.name !== ‘Class‘ ) { throw new Error( ‘類只能被實例化‘ ); } **4、只有函數才能被實例化 實例化之後得到的變量是實例並不是函數或者說是object **5、function類實例化過程,實際上是函數體的執行過程 這個執行過程也就是初始化的過程 在這種過程當中可以做相當多的事情 上述的防禦性代碼 實例(this)的傳遞與儲存 使用Object.create給this擴充功能(Mixin) 加入鉤子函數,讓類消費者自行決定如何實例化 **6、function類一般不會有return當然也可以存在 這也是實例化的變化所在 通過不同的鉤子從而決定返回什麽樣的實例 希望下一篇能學懂並講解這種方式 這種實例化方式與多態繼承等也有關系 **/ /*function與類的學習希望對你有幫助 **下篇文章介紹Class(ES2015新語法)與類 **最後附上最終的Class源代碼 **/ var Class01 = ( 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 Class01( 1, 2 ), ins02 = new Class01( ‘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() { Class01.call( this, ‘1‘, ‘2‘ ); } fakeClass.prototype = Class01.prototype; var ins = new fakeClass();

js面向對象設計之function類