我们通过表单验证的功能,来逐步演进面向对象的方式. 对于刚刚接触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();