[js高手之路]javascript物件導向的常見寫法與優缺點

daqianmen發表於2021-09-09

我們透過表單驗證的功能,來逐步演進物件導向的方式. 對於剛剛接觸javascript的朋友來說,如果要寫一個驗證使用者名稱,密碼,郵箱的功能, 一般可能會這麼寫:

1         //表單驗證
 2         var checkUserName = function(){
 3             console.log( '全域性checkUserName' );
 4         };
 5         var checkUserEmail = function(){
 6             console.log( '全域性checkUserEmail' );
 7         };
 8         var checkUserPwd = function(){
 9             console.log( '全域性checkUserPwd' );
10         };

這種寫法,從功能上來說 沒有什麼問題, 但是在團隊協作的時候, 會造成覆蓋全域性變數的問題, 那要大大降低覆蓋的可能性, 一般會在外面套一個物件

1         var Utils = {
 2             checkUserName : function(){
 3                 console.log( 'Utils->checkUserName' );
 4             },
 5             checkUserEmail : function(){
 6                 console.log( 'Utils->checkUserEmail' );
 7             },
 8             checkUserPwd :  function(){
 9                 console.log( 'Utils->checkUserPwd' );
10             }
11         }
12 
13         checkUserEmail();
14         Utils.checkUserEmail();

上面這種方式,是字面量方式新增,在設計模式裡面,也稱為單例(單體)模式, 與之類似的可以透過在函式本身新增屬性和方法,變成靜態屬性和方法,達到類似的效果:

1         var Utils = function(){
 2 
 3         }
 4         Utils.checkUserName = function(){
 5             console.log( 'Utils.checkUserName' );
 6         }
 7         Utils.checkUserPwd = function(){
 8             console.log( 'Utils.checkUserPwd' );
 9         }
10         Utils.checkUserEmail = function(){
11             console.log( 'Utils.checkUserEmail' );
12         }
13 
14         Utils.checkUserEmail();
15 
16         for( var key in Utils ){
17             ( Utils.hasOwnProperty( key ) ) ? console.log( key ) : '';
18         }
19 
20         //加在函式上面的屬性和方法,無法透過物件使用
21         var oUtil = new Utils(); 
22         oUtil.checkUserEmail();//錯誤

還可以透過函式呼叫方式,返回一個物件,把方法和屬性寫在物件中, 這種方式 跟物件導向沒有什麼關係,只是從函式的返回值角度來改造

1         //使用函式的方式 返回一個物件
 2         var Util = function(){
 3             return {
 4                 checkUserName : function(){
 5                     console.log( 'userName...' );
 6                 },
 7                 checkUserPwd : function(){
 8                     console.log( 'userPwd...' );
 9                 },
10                 checkUserEmail : function(){
11                     console.log( 'userEmail...' );
12                 }
13             }
14         }
15         Util().checkUserEmail();

還可以透過類似傳統面嚮物件語言,使用建構函式方式 為每個例項新增方法和屬性, 這種方式,存在一個問題, 不能達到函式共用,每個例項都會複製到方法.

1         var Util = function(){
 2             this.checkUserName = function(){
 3                 console.log('userName');
 4             };
 5             this.checkUserEmail = function(){
 6                 console.log('userEmail');
 7             };
 8             this.checkUserPwd = function(){
 9                 console.log('userPwd');
10             };
11         }
12 
13         var oUtil1 = new Util();
14         var oUtil2 = new Util();
15         console.log( oUtil1.checkUserEmail === oUtil2.checkUserEmail );//false

一般,我們可以透過原型屬性(prototype)改造這種方式,達到不同例項共用同一個方法

1         var Util = function(){
 2             
 3         };
 4         Util.prototype.checkUserName = function(){
 5             console.log('userName');
 6         };
 7         Util.prototype.checkUserPwd = function(){
 8             console.log('userPwd');
 9         };
10         Util.prototype.checkUserEmail = function(){
11             console.log('userEmail');
12         };
13         var oUtil1 = new Util();
14         var oUtil2 = new Util();
15         console.log( oUtil1.checkUserEmail === oUtil2.checkUserEmail );//true

也可以把原型物件上的所有方法,使用字面量方式簡寫

1         var Util = function(){
 2             
 3         };
 4         Util.prototype = {
 5             checkUserEmail : function(){
 6                 console.log( 'userEmail' );
 7             },
 8             checkUserName : function(){
 9                 console.log( 'userName' );
10             },
11             checkUserPwd : function(){
12                 console.log( 'userPwd' );
13             }
14         };
15         var oUtil1 = new Util();
16         var oUtil2 = new Util();
17         console.log( oUtil1.checkUserEmail === oUtil2.checkUserEmail );//true

注意: 字面量方式和原型物件一個個新增 這兩種不要混用, 否則會產生覆蓋

如果我們想把物件導向的使用方式更加的優雅,比如鏈式呼叫, 我們應該在每個方法中返回物件本身,才能繼續呼叫方法, 即返回this

 1         var Util = function(){
 2             return {
 3                 checkUserName : function(){
 4                     console.log( 'userName...' );
 5                     return this;
 6                 },
 7                 checkUserPwd : function(){
 8                     console.log( 'userPwd...' );
 9                     return this;
10                 },
11                 checkUserEmail : function(){
12                     console.log( 'userEmail...' );
13                     return this;
14                 }
15             }
16         }
17         // 方法中如果沒有返回this,下面這種呼叫方式是錯誤的
18         Util().checkUserEmail().checkUserName();
19 
20         // 方法中返回物件本身,可以鏈式呼叫
21         Util().checkUserEmail().checkUserName().checkUserPwd();
 1         var Util = function(){
 2             this.checkUserName = function(){
 3                 console.log('userName');
 4                 return this;
 5             };
 6             this.checkUserEmail = function(){
 7                 console.log('userEmail');
 8                 return this;
 9             };
10             this.checkUserPwd = function(){
11                 console.log('userPwd');
12                 return this;
13             };
14         }
15 
16         new Util().checkUserEmail().checkUserName().checkUserPwd();
        var Util = function(){

        };
        Util.prototype = {
            checkUserEmail : function(){
                console.log( 'userEmail' );
                return this;
            },
            checkUserName : function(){
                console.log( 'userName' );
                return this;
            },
            checkUserPwd : function(){
                console.log( 'userPwd' );
                return this;
            }
        };

        new Util().checkUserEmail().checkUserName().checkUserPwd();
 1         var Util = function(){
 2             
 3         };
 4         Util.prototype.checkUserName = function(){
 5             console.log('userName');
 6             return this;
 7         };
 8         Util.prototype.checkUserPwd = function(){
 9             console.log('userPwd');
10             return this;
11         };
12         Util.prototype.checkUserEmail = function(){
13             console.log('userEmail');
14             return this;
15         };
16 
17         new Util().checkUserEmail().checkUserName().checkUserPwd();

在實際開發中,我們經常需要擴充套件一些功能和模組。擴充套件可以在本物件或者父類物件或者原型上

1         Function.prototype.checkUserName = function(){
 2             console.log('ghostwu');
 3         };
 4 
 5         var fn1 = function(){};
 6         var fn2 = function(){};
 7 
 8         console.log( 'checkUserName' in fn1 ); //true
 9         console.log( 'checkUserName' in fn2 ); //true
10 
11         fn1.checkUserName(); //ghostwu
12         fn2.checkUserName(); //ghostwu

如果我們使用上面這種方式擴充套件,從功能上來說,是沒有問題的,但是確造成了全域性汙染:通俗點說,並不是說有的函式都需要checkUserName這個方法,而我們這樣寫,所有的函式在建立過程中都會從父類的原型鏈上繼承checkUserName, 但是這個方法,我們根本不用, 所以浪費效能, 為了解決這個問題,我們應該要在需要使用這個方法的函式上新增,不是所有的都新增,另外關於in的用法,如果不熟悉,可以看下我的這篇文章:立即表示式的多種寫法與注意點以及in運算子的作用

1         Function.prototype.addMethod = function( name, fn ){
 2             this[name] = fn;
 3         }
 4 
 5         var fn1 = function(){};
 6         var fn2 = function(){};
 7 
 8         fn1.addMethod( 'checkUserName', function(){console.log('ghostwu');});
 9 
10         fn1.checkUserName(); //ghostwu
11         fn2.checkUserName(); //報錯

透過上述的改造,成功解決了全域性汙染, fn2這個函式上面沒有新增checkUserName這個方法,只在fn1上面新增

我們繼續把上面的方式,改成鏈式呼叫: 這裡需要改兩個地方, 一種是新增方法addMethod可以鏈式新增, 一種是新增完了之後,可以鏈式呼叫

1         Function.prototype.addMethod = function( name, fn ){
 2             this[name] = fn;
 3             return this;
 4         };
 5 
 6         var fn1 = function(){};
 7 
 8         fn1.addMethod( 'checkUserName', function(){
 9             console.log( 'userName:ghostwu' );
10             return this;
11         } ).addMethod( 'checkUserEmail', function(){
12             console.log( 'userEmail' );
13             return this;
14         } ).addMethod( 'checkUserPwd', function(){
15             console.log( 'userUserPwd' );
16             return this;
17         } );
18         fn1.checkUserName().checkUserEmail().checkUserPwd();

上面是屬於函式式 鏈式 呼叫, 我們可以改造addMethod方法, 在原型上新增函式,而不是例項上, 這樣我們就可以達到類式的鏈式呼叫

1         Function.prototype.addMethod = function( name, fn ){
 2             this.prototype[name] = fn;
 3             return this;
 4         };
 5 
 6         var fn1 = function(){};
 7 
 8         fn1.addMethod( 'checkUserName', function(){
 9             console.log( 'userName:ghostwu' );
10             return this;
11         } ).addMethod( 'checkUserEmail', function(){
12             console.log( 'userEmail' );
13             return this;
14         } ).addMethod( 'checkUserPwd', function(){
15             console.log( 'userUserPwd' );
16             return this;
17         } );
18         new fn1().checkUserName().checkUserEmail().checkUserPwd();

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1727/viewspace-2799344/,如需轉載,請註明出處,否則將追究法律責任。

相關文章