程序员的知识教程库

网站首页 > 教程分享 正文

是时候让JavaScript面向对象了(javascript 面向对象)

henian88 2024-08-18 17:05:29 教程分享 10 ℃ 0 评论

基础篇


学好JS的面向对象,能很大程度上提高代码的重用率,像jQuery,easyui等,这篇文章主要从细节上一步步讲JS中如何有效地创建对象,也可以看到常见的创建对象的方式,最后也会附上一些JS面向对象的案例。

一、面向对象

1.对象:对象是一个整体,对外提供一些操作。

2.面向对象:使用对象时,只关注对象提供的功能,不关注其内部细节。比如电脑——有鼠标、键盘,我们只需要知道怎么使用鼠标,敲打键盘即可,不必知道为何点击鼠标可以选中、敲打键盘是如何输入文字以及屏幕是如何显示文字的。总之我们没必要知道其具体工作细节,只需知道如何使用其提供的功能即可,这就是面向对象。

3.JS的对象组成:方法 和 属性

在JS中,有函数、方法、事件处理函数、构造函数,其实这四个都是函数,只是作用不同。函数是独立的存在,方法属于一个对象,事件处理函数用来处理一个事件,构造函数用来构造对象。

首先通过常用的数组来认识下JS的对象:

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5         <title></title>
 6         <script>
 7             
 8             /**
 9              * 定义一个数组
10              */
11             var arr = [1, 2, 3, 4, 5];
12             
13             /**
14              * 弹出 object
15              * 说明数组就是个对象
16              */
17             alert(typeof arr); 
18             
19             /**
20              * 弹出5
21              * 对象的属性 length
22              */
23             alert(arr.length); 
24             
25             /**
26              * 对象的方法 push
27              * 弹出 1,2,3,4,5,6
28              */
29             arr.push(6); 
30             alert(arr); 
31         </script>
32     </head>
33     <body>
34     </body>
35 </html>

4.认识下JS中的this以及全局对象window

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5         <title></title>
 6         <script>
 7             /**
 8              * 定义一个全局函数
 9              */
10             function show(){
11                 alert(this);
12             }
13             //调用show()
14             show();
15             
16         </script>
17     </head>
18     <body>
19     </body>
20 </html>

此处的show()函数为一个全局函数,调用show(),alert(this)弹出来的是window对象,说明全局函数属于window。上面定义的show()等于为window添加一个方法,全局的函数和变量都是属于window的,上面的定义等价于下面。

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <script>
 5             /**
 6              * 为window定义一个show方法
 7              */
 8             window.show = function(){
 9                 alert(this);
10             }
11             //调用show()
12             window.show();
13             
14         </script>
15     </head>
16 </html>

同样的我们也可以根据自己的需求为其它的对象添加方法,比如显示数组:

但是我们不能在系统对象中随意附加方法和属性,否则可能会覆盖已有方法、属性。

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <script>
 5             var arr = [1,2,3,4,5];
 6             arr.show = function(){
 7                 alert(this);
 8             }
 9             arr.show(); //弹出 1,2,3,4,5
10         </script>
11     </head>
12 </html>

从上面的例子也可以看出来,this即表示当前函数的调用者是谁,但是在一种情况下不是的,就是使用new 来创建对象时,this并不是指向调用者的,在后面会有说明。

window是全局对象,可以看下属于window的全局属性和全局方法:

二、JS中自定义对象,逐步分析JS中的创建对象

1.通过Object创建简单对象:

这种方式有一个非常大的弊端,就是如果我有多个人怎么办,每次都要新建一个对象,然后添加属性、方法,这种方式是一次性的,会产生大量重复代码,这是不可取的。

 1 <!DOCTYPE html>
 2 <html>
 3     <meta charset="UTF-8" />
 4     <head>
 5         <script>
 6             /**
 7              * 创建一个新对象
 8              * new Object()创建出来的对象几乎是空白的,需要自己添加属性,方法
 9              */
10             var person = new Object();
11             //为person对象添加属性
12             person.name = "jiangzhou";
13             person.age = 22;
14             //为person对象添加方法
15             person.showName = function(){
16                 alert("姓名:"+this.name);
17             }
18             person.showAge = function(){
19                 alert("年龄:"+this.age);
20             }
21             //调用对象的方法
22             person.showName();
23             person.showAge();
24             
25         </script>
26     </head>
27 </html>

2.用工厂方式来构造对象:工厂,简单来说就是投入原料、加工、出厂。

通过构造函数来生成对象,将重复的代码提取到一个函数里面,避免像第一种方式写大量重复的代码。这样我们在需要这个对象的时候,就可以简单地创建出来了。

 1 <!DOCTYPE html>
 2 <html>
 3     <meta charset="UTF-8" />
 4     <head>
 5         <script>
 6             //构造函数:工厂
 7             function createPerson(name, age){
 8                 var person = new Object();
 9                 
10                 //原料
11                 person.name = name;
12                 person.age = age;
13                 
14                 //加工
15                 person.showName = function(){
16                     alert("姓名:"+this.name);
17                 }
18                 person.showAge = function(){
19                     alert("年龄:"+this.age);
20                 }
21                 //出厂
22                 return person;
23             }
24             //创建两个对象
25             var p1 = createPerson("jiangzhou", 22);
26             var p2 = createPerson("tom", 20);
27             
28             //调用对象方法
29             p1.showName();
30             p1.showAge();
31             
32             p2.showName();
33             p2.showAge();
34             
35         </script>
36     </head>
37 </html>

但是,这种方式有两个缺点:

①一般我们创建对象是通过new来创建,比如new Date(),这里使用的是方法创建。使用new来创建可以简化一些代码,也带来一些新的特性。

②每个对象都有一套自己的方法,浪费资源(虽然对于现在的计算机来说不算什么,但我们尽量将设计做到最好就行了)。

这里为什么说每个对象都有自己的一套方法呢,是因为创建function()的时候其本质是通过new Function()来创建的,会诞生一个新的函数对象,所以每个对象的方法是不一样的,这样就存在资源浪费的问题了。看第25行代码。

 1 <!DOCTYPE html>
 2 <html>
 3     <meta charset="UTF-8" />
 4     <head>
 5         <script>
 6             
 7             function createPerson(name, age, sex){
 8                 var person = new Object();
 9                 
10                 person.name = name;
11                 person.age = age;
12                 person.sex = sex;
13                 
14                 person.showName = function(){
15                     alert("姓名:"+this.name);
16                 }
17                 person.showAge = function(){
18                     alert("年龄:"+this.age);
19                 }
20                 
21                 /**
22                  *  person.showSex = function(){} 等价于 person.showSex = new Function('');
23                  *  也就是说我们在创建这个函数的时候就是新建了一个对象。
24                  */
25                 person.showSex = new Function('alert("性别:"+this.sex)');
26                 
27                 return person;
28             }
29             
30             //创建两个对象
31             var p1 = createPerson("jiangzhou", 22, "男");
32             var p2 = createPerson("Lyli", 20, "女");
33             
34             alert(p1.showName == p2.showName); //false
35             
36         </script>
37     </head>
38 </html>

3.使用new 来创建JS对象

 1 <!DOCTYPE html>
 2 <html>
 3     <meta charset="UTF-8" />
 4     <head>
 5         <script>
 6             
 7             function Person(name, age){
 8                 /**
 9                  * 可以假想成系统会创建一个对象
10                  * var this = new Object();
11                  */
12                 
13                 alert(this); //弹出Object
14                 
15                 this.name = name;
16                 this.age = age;
17                 
18                 this.showName = function(){
19                     alert("姓名:"+this.name);
20                 }
21                 this.showAge = function(){
22                     alert("年龄:"+this.age);
23                 }
24                 
25                 /**
26                  * 假想返回了对象
27                  * return this;
28                  */
29             }
30             
31             //创建两个对象
32             var p1 = new Person("jiangzhou", 22);//可以看到在外面new了在function里面就不用new了;在function里面new了,在外面就不用new了;O(∩_∩)O~
33             var p2 = new Person("Lyli", 20);
34             
35             alert(p1.showName == p2.showName); //false
36             
37         </script>
38     </head>
39 </html>

弹出信息显示this即是一个Object(第13行代码)。在方法调用前使用new来创建,function内的this会指向一个新创建的空白对象,而不是指向方法调用者,而且会自动返回该对象。

但是这种方式只解决了第一个问题,每个对象还是有自己的一套方法(第35行代码)。

4.在function原型(prototype)上进行扩展 —— 最终版

原型添加的方法不会有多个副本,不会浪费资源,所有的对象只有一套方法(看第29行代码)。 至于为什么是用的一套方法呢,看第31行代码:因为所有的方法都等于原型上的方法。

 1 <!DOCTYPE html>
 2 <html>
 3     <meta charset="UTF-8" />
 4     <head>
 5         <script>
 6             
 7             /**
 8              * Person构造函数:在JS中,构造函数其实就可以看成类,对某个对象的抽象定义。
 9              * @param {Object} name
10              * @param {Object} age
11              */
12             function Person(name, age){
13                 //属性:每个对象的属性各不相同
14                 this.name = name;
15                 this.age = age;
16             }
17             //在原型上添加方法,这样创建的所有对象都是用的同一套方法
18             Person.prototype.showName = function(){
19                 alert("姓名:"+this.name);
20             }
21             Person.prototype.showAge = function(){
22                 alert("年龄:"+this.age);
23             }
24             
25             //创建两个对象
26             var p1 = new Person("jiangzhou", 22);
27             var p2 = new Person("Lyli", 20);
28             
29             alert(p1.showName == p2.showName); //true
30             //这里为什么两个对象的方法是相等的呢,可以看成如下
31             alert(p1.showName == Person.prototype.showName); //true
32             
33         </script>
34     </head>
35 </html>

通过prototype我们还可以很方便的扩展系统对象,按照自己的需求来扩展,而且又能适用于所有地方,又不会浪费资源。如下面对Array进行扩展,求数组和的方法。

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5     </head>
 6     <script>
 7         /**
 8          * 对数组原型扩展一个求和的方法;
 9          * 注意不能只加在某个数组对象上,那样的话只能在那个对象上适用。
10          */
11         Array.prototype.sum = function(){
12             var sum = 0;
13             for(var i=0;i<this.length;i++){
14                 sum += this[i];
15             }
16             return sum;
17         }
18         //通过new Array() 和 [] 创建数组完全是一样的效果。
19         var arr1 = new Array(1,2,3,4,5,6);
20         var arr2 = [11,22,33,44,55];
21         
22         alert(arr1.sum());
23         alert(arr2.sum());
24         
25         alert(arr1.sum == arr2.sum); //true
26         alert(arr2.sum == Array.prototype.sum); //true
27     </script>
28 </html>


案例篇

案例——面向对象的选项卡:把面向过程的程序一步步改成面向对象的形式,使其能够更加的通用(但是通用的东西,一般会比较臃肿)。

下面是一个简单的选项卡,也是我们常见的面向过程的创建形式。

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <style>
 5         #tabBox input {
 6             background: #F6F3F3;
 7             border: 1px solid #FF0000;
 8         }
 9         #tabBox .active {
10             background: #E9D4D4;
11         }
12         #tabBox div {
13             width:300px; 
14             height:250px; 
15             display:none;
16             padding: 10px;
17             background: #E9D4D4;
18             border: 1px solid #FF0000;
19         }
20     </style>
21     <meta charset="utf-8" />
22     <title>选项卡</title>
23     <script>
24         window.onload=function(){
25             var tabBox = document.getElementById('tabBox');
26             var tabBtn = tabBox.getElementsByTagName('input');
27             var tabDiv = tabBox.getElementsByTagName('div');
28             
29             for(var i=0;i<tabBtn.length;i++){
30                 tabBtn[i].index = i;
31                 tabBtn[i].onclick = function (){
32                     for(var j=0;j<tabBtn.length;j++){
33                         tabBtn[j].className='';
34                         tabDiv[j].style.display='none';
35                     }
36                     this.className='active';
37                     tabDiv[this.index].style.display='block';
38                 };
39             }
40         };
41     </script>
42 </head>
43 
44 <body>
45     <div id="tabBox">
46         <input type="button" value="游戏" class="active" />
47         <input type="button" value="旅行" />
48         <input type="button" value="音乐" />
49         <div style="display:block;">GTA5、孤岛惊魂</div>
50         <div>澳大利亚、西藏</div>
51         <div>暗里着迷、一生有你</div>
52     </div>
53 </body>
54 </html>

效果:

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

下面来一步步改成面向对象的形式。

1.首先将嵌套的函数拿到window.onload外面,不能有函数嵌套,可以有全局变量。如下:所有的改写最终效果都不变。

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <style>
 5         #tabBox input {
 6             background: #F6F3F3;
 7             border: 1px solid #FF0000;
 8         }
 9         #tabBox .active {
10             background: #E9D4D4;
11         }
12         #tabBox div {
13             width:300px; 
14             height:250px; 
15             display:none;
16             padding: 10px;
17             background: #E9D4D4;
18             border: 1px solid #FF0000;
19         }
20     </style>
21     <meta charset="utf-8" />
22     <title>选项卡</title>
23     <script>
24         //将在嵌套函数里的变量提取到全局中
25         var tabBtn = null;
26         var tabDiv = null;
27         
28         window.onload = function(){
29             var tabBox = document.getElementById('tabBox');
30             tabBtn = tabBox.getElementsByTagName('input');
31             tabDiv = tabBox.getElementsByTagName('div');
32             
33             for(var i=0;i<tabBtn.length;i++){
34                 tabBtn[i].index = i;
35                 //此处调用函数即可
36                 tabBtn[i].onclick = clickBtn;
37             }
38         };
39         
40         //将嵌套函数提取到全局中
41         function clickBtn(){
42             for(var j=0;j<tabBtn.length;j++){
43                 tabBtn[j].className='';
44                 tabDiv[j].style.display='none';
45             }
46             this.className='active';
47             tabDiv[this.index].style.display='block';
48         };
49         
50     </script>
51 </head>
52 
53 <body>
54     <div id="tabBox">
55         <input type="button" value="游戏" class="active" />
56         <input type="button" value="旅行" />
57         <input type="button" value="音乐" />
58         <div style="display:block;">GTA5、孤岛惊魂</div>
59         <div>澳大利亚、西藏</div>
60         <div>暗里着迷、一生有你</div>
61     </div>
62 </body>
63 </html>

2.将全局的变量变为对象的属性,全局的函数变为对象的方法;将window.onload里的代码提取到一个构造函数里面,在window.onload里创建对象即可

这里必须注意:在构造函数Tab里的this跟之前this所代表的是不同的(此处是通过new来创建对象的);在上面的示例中,this指的是调用者;在构造函数里,this指向的是var tab = new Tab() ,即tab这个对象,注意是对象。

说一下这段代码的问题:我们在Tab的原型上添加clickBtn方法后,clickBtn方法里的this本应该是指向var tab = new Tab()的,但是我们在第44行将clickBtn添加给了this.tabBtn[i],即input按钮,clickBtn的所属由Tab对象变成了input按钮(第49行)。

clickBtn的所属变成input按钮后,那么clickBtn里的this指向按钮,那再来看clickBtn里的代码,this.tabBtn、this.tabDiv,input按钮里有这两个属性吗?没有,所以会出错!

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <style>
 5         #tabBox input {
 6             background: #F6F3F3;
 7             border: 1px solid #FF0000;
 8         }
 9         #tabBox .active {
10             background: #E9D4D4;
11         }
12         #tabBox div {
13             width:300px; 
14             height:250px; 
15             display:none;
16             padding: 10px;
17             background: #E9D4D4;
18             border: 1px solid #FF0000;
19         }
20     </style>
21     <meta charset="utf-8" />
22     <title>选项卡</title>
23     <script>
24         
25         window.onload = function(){
26             var tab = new Tab("tabBox");
27         }
28     
29         /**
30          * 将之前window.onload里的代码提到一个构造函数里
31          * [可以将这个Tab构造函数想象成一个Tab类]
32          * @param {Object} id:选项卡id以参数的形式传入
33          */
34         function Tab(id){
35             var tabBox = document.getElementById(id);
36             //将之前的全局变量变为对象的属性
37             this.tabBtn = tabBox.getElementsByTagName('input');
38             this.tabDiv = tabBox.getElementsByTagName('div');
39             
40             for(var i=0;i<this.tabBtn.length;i++){
41                 this.tabBtn[i].index = i;
42                 
43                 //此处这种方式调用函数,已经将clickBtn的所属变成this.tabBtn[i]
44                 this.tabBtn[i].onclick = this.clickBtn;
45             }
46         };
47         //将之前的全局函数添加到构造函数的原型里,作为对象的一个方法
48         Tab.prototype.clickBtn = function(){
49             alert(this); //HTMLInputElement
50             for(var j=0;j<this.tabBtn.length;j++){
51                 this.tabBtn[j].className='';
52                 this.tabDiv[j].style.display='none';
53             }
54             this.className='active';
55             this.tabDiv[this.index].style.display='block';
56         };
57         
58     </script>
59 </head>
60 
61 <body>
62     <div id="tabBox">
63         <input type="button" value="游戏" class="active" />
64         <input type="button" value="旅行" />
65         <input type="button" value="音乐" />
66         <div style="display:block;">GTA5、孤岛惊魂</div>
67         <div>澳大利亚、西藏</div>
68         <div>暗里着迷、一生有你</div>
69     </div>
70 </body>
71 </html>

将clickBtn赋给input按钮后,方法内的this也指向了input按钮:执行第49行代码的效果

3.将clickBtn的调用放在一个函数里,这样就不会改变clickBtn的所属了。

看第41-47行,注意在function里需要用到外部保存的变量_this。再看第52行,此时弹出的是一个Object,说明clickBtn的所属关系没变,还是Tab对象。

但是还有另一个问题,此时clickBtn里的this指向对象,那么看第57、58行中this.className、this.index,此处的this指的是tab对象,那么对象中有这两个属性吗?没有,还会出错!

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <style>
 5         #tabBox input {
 6             background: #F6F3F3;
 7             border: 1px solid #FF0000;
 8         }
 9         #tabBox .active {
10             background: #E9D4D4;
11         }
12         #tabBox div {
13             width:300px; 
14             height:250px; 
15             display:none;
16             padding: 10px;
17             background: #E9D4D4;
18             border: 1px solid #FF0000;
19         }
20     </style>
21     <meta charset="utf-8" />
22     <title>选项卡</title>
23     <script>
24         
25         window.onload = function(){
26             var tab = new Tab("tabBox");
27         }
28     
29         /**
30          * 选项卡
31          * @param {Object} id:选项卡id
32          */
33         function Tab(id){
34             var tabBox = document.getElementById(id);
35             
36             this.tabBtn = tabBox.getElementsByTagName('input');
37             this.tabDiv = tabBox.getElementsByTagName('div');
38             
39             for(var i=0;i<this.tabBtn.length;i++){
40                 this.tabBtn[i].index = i;
41                 //将this保存成一个变量,就可以在下面代码中调用对象的方法了
42                 var _this = this;
43                 //此处这种方式调用函数,就不会改变clickBtn方法的所属关系
44                 this.tabBtn[i].onclick = function(){
45                     //注意此处不能直接使用this,this指向this.tabBtn[i]
46                     _this.clickBtn();
47                 };
48             }
49         };
50         //点击选项卡按钮
51         Tab.prototype.clickBtn = function(){
52             alert(this); //Object
53             for(var j=0;j<this.tabBtn.length;j++){
54                 this.tabBtn[j].className='';
55                 this.tabDiv[j].style.display='none';
56             }
57             this.className='active';
58             this.tabDiv[this.index].style.display='block';
59         };
60         
61     </script>
62 </head>
63 
64 <body>
65     <div id="tabBox">
66         <input type="button" value="游戏" class="active" />
67         <input type="button" value="旅行" />
68         <input type="button" value="音乐" />
69         <div style="display:block;">GTA5、孤岛惊魂</div>
70         <div>澳大利亚、西藏</div>
71         <div>暗里着迷、一生有你</div>
72     </div>
73 </body>
74 </html>

4. 以参数的形式将点击的按钮传入clickBtn中,看第44行代码,以及54,55行代码

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <style>
 5         #tabBox input {
 6             background: #F6F3F3;
 7             border: 1px solid #FF0000;
 8         }
 9         #tabBox .active {
10             background: #E9D4D4;
11         }
12         #tabBox div {
13             width:300px; 
14             height:250px; 
15             display:none;
16             padding: 10px;
17             background: #E9D4D4;
18             border: 1px solid #FF0000;
19         }
20     </style>
21     <meta charset="utf-8" />
22     <title>选项卡</title>
23     <script>
24         
25         window.onload = function(){
26             var tab = new Tab("tabBox");
27         }
28     
29         /**
30          * 选项卡
31          * @param {Object} id:选项卡id
32          */
33         function Tab(id){
34             var tabBox = document.getElementById(id);
35             
36             this.tabBtn = tabBox.getElementsByTagName('input');
37             this.tabDiv = tabBox.getElementsByTagName('div');
38             
39             for(var i=0;i<this.tabBtn.length;i++){
40                 this.tabBtn[i].index = i;
41                 var _this = this;
42                 this.tabBtn[i].onclick = function(){
43                     //注意参数this代表的是this.tabBtn[i],即input按钮
44                     _this.clickBtn(this);
45                 };
46             }
47         };
48         //将点击的按钮以参数的形式传入
49         Tab.prototype.clickBtn = function(btn){
50             for(var j=0;j<this.tabBtn.length;j++){
51                 this.tabBtn[j].className='';
52                 this.tabDiv[j].style.display='none';
53             }
54             btn.className='active';
55             this.tabDiv[btn.index].style.display='block';
56         };
57         
58     </script>
59 </head>
60 
61 <body>
62     <div id="tabBox">
63         <input type="button" value="游戏" class="active" />
64         <input type="button" value="旅行" />
65         <input type="button" value="音乐" />
66         <div style="display:block;">GTA5、孤岛惊魂</div>
67         <div>澳大利亚、西藏</div>
68         <div>暗里着迷、一生有你</div>
69     </div>
70 </body>
71 </html>

5.最终版 —— 将代码提取到一个单独的js文件中,在用的时候引入即可。一般花大时间去写一个面向对象的程序,就是为了能够复用,以及方便的使用。

tab.js

 1 /**
 2  * 选项卡
 3  * @param {Object} id 选项卡id
 4  */
 5 function Tab(id){
 6     var tabBox = document.getElementById(id);
 7     this.tabBtn = tabBox.getElementsByTagName('input');
 8     this.tabDiv = tabBox.getElementsByTagName('div');
 9     
10     for(var i=0;i<this.tabBtn.length;i++){
11         this.tabBtn[i].index = i;
12         var _this = this;
13         this.tabBtn[i].onclick = function(){
14             _this.clickBtn(this);
15         };
16     }
17 };
18 /**
19  * 为Tab原型添加点击选项卡方法
20  * @param {Object} btn 点击的按钮
21  */
22 Tab.prototype.clickBtn = function(btn){
23     for(var j=0;j<this.tabBtn.length;j++){
24         this.tabBtn[j].className='';
25         this.tabDiv[j].style.display='none';
26     }
27     btn.className='active';
28     this.tabDiv[btn.index].style.display='block';
29 };

使用:tab.html 可以看到使用的时候,就可以很简单的创建两个选项卡出来了。

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <style>
 5         .tab input {
 6             background: #F6F3F3;
 7             border: 1px solid #FF0000;
 8         }
 9         .tab .active {
10             background: #E9D4D4;
11         }
12         .tab div {
13             width:300px; 
14             height:250px; 
15             display:none;
16             padding: 10px;
17             background: #E9D4D4;
18             border: 1px solid #FF0000;
19         }
20     </style>
21     <meta charset="utf-8" />
22     <title>选项卡</title>
23     <!-- 引入tab.js -->
24     <script type="text/javascript" src="../js/tab.js" ></script>
25     <script>
26         
27         window.onload = function(){
28             var tab1 = new Tab("tabBox1");
29             
30             var tab2 = new Tab("tabBox2");
31         }
32     
33     </script>
34 </head>
35 
36 <body>
37     <div class="tab" id="tabBox1">
38         <input type="button" value="游戏" class="active" />
39         <input type="button" value="旅行" />
40         <input type="button" value="音乐" />
41         <div style="display:block;">GTA5、孤岛惊魂</div>
42         <div>澳大利亚、西藏</div>
43         <div>暗里着迷、一生有你</div>
44     </div>
45     <br />
46     <div class="tab" id="tabBox2">
47         <input type="button" value="技术" class="active" />
48         <input type="button" value="工具" />
49         <input type="button" value="网站" />
50         <div style="display:block;">Java、Spring</div>
51         <div>Eclipse、HBuilder</div>
52         <div>博客园、CSD</div>
53     </div>
54 </body>
55 </html>

效果:

看的有点晕吧,一定要好好理解JS面向对象中的this。js中面向对象大部分时候出问题就是出在this的处理上,需要注意。

面向过程:简洁、美观、容易维护;

面向对象:容易出错、混乱、难以维护;面向对象相对面向过程来说,写代码的时候麻烦些,但是用起来很方便,面向过程则相反。

开发面向对象的程序需要有一个好的面向对象的思维,即将具体对象抽象成一个构造函数的过程。

总结一下JS面向对象中的this:

this一般会在两种情况下出问题,一是使用定时器、二是事件,从上面的例子中也可以看出来。注意下面的说法是在构造函数里哦,其它情况下,this指向的是调用者。

可以看到效果没有将姓名显示出来,其实看到这里原因应该很清楚了,就是第14行代码中this.name,此处的this指向谁?指向window,因为setInterval是属于window的。

 1 <!DOCTYPE html>
 2 <html>
 3     <meta charset="UTF-8" />
 4     <head>
 5         <script>
 6             
 7             function Person(name){
 8                 this.name = name;
 9                 //定时器
10                 setInterval(this.showName, 3000);
11             }
12             Person.prototype.showName = function(){
13                 alert(this); //window
14                 alert("姓名:"+this.name);
15             }
16             
17             var p1 = new Person("jiangzhou");
18             
19         </script>
20     </head>
21 </html>

效果:

解决办法:上面例子中已经列出来了,就是用一个function将要执行的代码包起来,使其所属关系不会发生变化,注意function里调用方法时使用的是外部变量'_this'。事件的处理在上面的例子中已经说明了。

 1 <!DOCTYPE html>
 2 <html>
 3     <meta charset="UTF-8" />
 4     <head>
 5         <script>
 6             
 7             function Person(name){
 8                 this.name = name;
 9                 
10                 var _this = this;
11                 
12                 setInterval(function(){
13                     _this.showName();
14                 }, 3000);
15             }
16             Person.prototype.showName = function(){
17                 alert(this); //[Object Object]
18                 alert("姓名:"+this.name); //姓名:jianghzou
19             }
20             
21             var p1 = new Person("jiangzhou");
22             
23         </script>
24     </head>
25 </html>

案例 —— 拖拽

原始的面向过程代码:

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5         <style>
 6             #box {
 7                 width: 100px; 
 8                 height: 100px; 
 9                 background: blue; 
10                 position: absolute;
11             }
12         </style>
13         <title>拖拽</title>
14         <script>
15             var oBox=null;
16             var disX=0;
17             var disY=0;
18             
19             window.onload=function(){
20                 oBox=document.getElementById('box');
21                 
22                 oBox.onmousedown=fnDown;
23             };
24             //鼠标按下事件
25             function fnDown(ev){
26                 var oEvent = ev||event;
27                 disX = oEvent.clientX - oBox.offsetLeft;
28                 disY = oEvent.clientY - oBox.offsetTop;
29                 
30                 document.onmousemove = fnMove;
31                 document.onmouseup = fnUp;
32             }
33             //鼠标移动事件
34             function fnMove(ev){
35                 var oEvent=ev||event;
36                 
37                 oBox.style.left = oEvent.clientX - disX + 'px';
38                 oBox.style.top = oEvent.clientY - disY + 'px';
39             }
40             //鼠标抬起事件
41             function fnUp(){
42                 document.onmousemove = null;
43                 document.onmouseup = null;
44             }
45         </script>
46     </head>
47 
48 <body>
49     <div id="box"></div>
50 </body>
51 </html>

下面是面向对象的代码

drag.js

 1 /**
 2  * 拖拽
 3  * @param {Object} id div的id
 4  */
 5 function Drag(id){
 6     this.oBox = document.getElementById(id);
 7     this.disX = 0;
 8     this.disY = 0;
 9     
10     var _this = this;
11     
12     this.oBox.onmousedown = function(){
13         _this.fnDown();
14     }
15 }
16 //鼠标按下
17 Drag.prototype.fnDown = function(ev){
18     var oEvent = ev || event;
19     
20     this.disX = oEvent.clientX - this.oBox.offsetLeft;
21     this.disY = oEvent.clientY - this.oBox.offsetTop;
22     
23     var _this = this;
24     
25     document.onmousemove = function(){
26         _this.fnMove();
27     };
28     document.onmouseup = function(){
29         _this.fnUp();
30     };
31 }
32 //鼠标移动
33 Drag.prototype.fnMove = function(ev){
34     var oEvent= ev || event;
35     
36     this.oBox.style.left = oEvent.clientX - this.disX + 'px';
37     this.oBox.style.top = oEvent.clientY - this.disY + 'px';
38 }
39 //鼠标抬起
40 Drag.prototype.fnUp = function(){
41     document.onmousemove = null;
42     document.onmouseup = null;
43 }

drag.html

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5         <style>
 6             div {
 7                 position: absolute;
 8             }
 9         </style>
10         <title>拖拽</title>
11         <script type="text/javascript" src="../js/drag.js" ></script>
12         <script>
13             window.onload = function(){
14                 var drag1 = new Drag("box1");
15                 
16                 var drag1 = new Drag("box2");
17             };
18         </script>
19     </head>
20 
21 <body>
22     <div id="box1" style="background: red;width: 200px;height: 200px;"></div>
23     
24     <div id="box2" style="background: blue;width: 100px;height: 300px;"></div>
25 </body>
26 </html>

效果:


高级篇

一、json方式的面向对象

首先要知道,js中出现的东西都能够放到json中。关于json数据格式这里推荐一篇博客:JSON 数据格式

先看下json创建的简单对象:相比基础篇中的构造函数、原型等的创建方式,json方式简单方便;但是缺点很明显,如果想创建多个对象,那么会产生大量重复代码,不可取。

JSON方式适用于只创建一个对象的情况,代码简介又优雅。

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5         <title></title>
 6         <script>
 7             var person = {
 8                 name: "jiangzhou",
 9                 age: 22,
10                 showName: function(){
11                     alert(this); //[Object Object]
12                     alert("姓名:"+this.name);
13                 },
14                 showAge: function(){
15                     alert("年龄:"+this.age);
16                 }
17             };
18             person.showName();
19             person.showAge();
20             
21         </script>
22     </head>
23 </html>

JSON在JS面向对象的应用中,主要的一个作用就是命名空间:如果有大量常用的js函数,利用json,我们可以将同一类函数放在一个“类”里,类似于java那样,这样我们就能很好的管理和查找使用这些js函数,看下面的例子就很好理解了。

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5         <title></title>
 6         <script>
 7             //仿java.lang包
 8             var lang = {};
 9             
10             /**
11              * 仿java.lang.Math类
12              */
13             lang.Math = {
14                 /**
15                  * 求绝对值
16                  * @param {Object} a
17                  */
18                 abs: function(a){
19                     return a > 0 ? a : -a;
20                 },
21                 /**
22                  * 求最大值
23                  * @param {Object} a
24                  * @param {Object} b
25                  */
26                 max: function(a, b){
27                     return a > b ? a : b;
28                 },
29                 /**
30                  * PI
31                  */
32                 PI: 3.1415926
33             }
34             
35             /**
36              * 仿java.lang.String类
37              */
38             lang.String = {
39                 /**
40                  * 求字符串长度
41                  * @param {Object} str
42                  */
43                 length: function(str){
44                     return str.length;
45                 },
46                 /**
47                  * 将字符串转为小写
48                  * @param {Object} str
49                  */
50                 toLowerCase: function(str){
51                     return str.toLowerCase();
52                 },
53                 /**
54                  * 将字符串转为大写
55                  * @param {Object} str
56                  */
57                 toUpperCase: function(str){
58                     return str.toUpperCase();
59                 }
60             }
61             
62             //调用
63             alert(lang.Math.abs(-19)); //19
64             alert(lang.Math.PI);
65             alert(lang.String.toUpperCase("abcdefg")); //ABCDEFG
66             
67         </script>
68     </head>
69 </html>

二、面向对象的继承

先举个简单的例子来说一下JS中的继承,Student <extends> Person;

在js中,通过call来调用父类的构造方法继承父类的属性(第33行),通过原型来继承父类的方法(第39行)。注意:先调用父类构造函数,再添加自己的属性;先继承父类的方法,再添加自己的方法。

这里解释下为什么调用Person.call(this, name, sex)就相当于是在调用父类的构造方法:先问一下这个call中的this是谁?这里指向对象student吧。

所以,在子构造函数中调用Person.call()时,那么构造函数Person里的两行代码this.name=name, this.sex=sex中this就是代表student了,所以这两行代码相当于是在为student添加name和sex属性。

但是,下面的通过原型来继承父类的方法,即Student.prototype = Person.prototype,是有问题的,这种方式将影响父类(继承是不能影响父类的),此时Person的原型中有了个showMajor方法(第50行),为什么呢?先思考下,下面解释。

 1 <!DOCTYPE html>
 2 <html>
 3     <meta charset="UTF-8" />
 4     <head>
 5         <script>
 6             
 7             /**
 8              * Person 父类 人
 9              * @param {Object} name 姓名
10              * @param {Object} sex 性别
11              */
12             function Person(name, sex){
13                 this.name = name;
14                 this.sex = sex;
15             }
16             Person.prototype.showName = function(){
17                 alert("姓名:"+this.name); 
18             }
19             Person.prototype.showSex = function(){
20                 alert("性别:"+this.sex); 
21             }
22             
23         /*-----------------------------------------------------*/
24             
25             /**
26              * Student   学生 继承 人
27              * @param {Object} name
28              * @param {Object} sex
29              * @param {Object} major 学生特有属性:专业
30              */
31             function Student(name, sex, major){
32                 //调用父类的构造函数
33                 Person.call(this, name, sex);
34                 
35                 //添加自己的属性
36                 this.major = major;
37             }
38             //继承父类原型中的方法
39             Student.prototype = Person.prototype;
40             //添加自己特有的方法
41             Student.prototype.showMajor = function(){
42                 alert("专业:"+this.major);
43             }
44             
45             var student = new Student("bojiangzhou", "男", "信息管理");
46             student.showName();
47             student.showSex();
48             student.showMajor();
49             
50             alert(Person.prototype.showMajor);
51         </script>
52     </head>
53 </html>

第50行弹出的信息:

为了解释为什么通过Student.prototype = Person.prototype来继承父类的方法会影响父类,下面举一个数组的例子,一看就知道怎么回事了。

为什么arr1和arr2弹出来的一样呢?第15、16行显示arr1和arr2是一个对象。对象!应该很清楚了吧,arr1和arr2都是指向这个数组对象的一个引用,所以改变arr2时,arr1也变了。

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5     </head>
 6     <script>
 7         var arr1 = [1,2,3,4,5];
 8         var arr2 = arr1;
 9         
10         arr2.push(6);
11         
12         alert(arr1); //弹出1,2,3,4,5,6
13         alert(arr2); //弹出1,2,3,4,5,6
14         
15         alert(typeof arr1); //object
16         alert(typeof arr2); //object
17     </script>
18 </html>

其实我们主要是想获得arr1数组的一个副本,怎么做才能不改变arr1呢,看下面:

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5     </head>
 6     <script>
 7         var arr1 = [1,2,3,4,5];
 8         var arr2 = [];
 9         
10         //复制arr1的数据即可
11         for(var i=0;i<arr1.length;i++){
12             arr2[i]=arr1[i];
13         }
14         
15         arr2.push(6);
16         
17         alert(arr1); //弹出1,2,3,4,5
18         alert(arr2); //弹出1,2,3,4,5,6
19         
20     </script>
21 </html>

同样的,我们也可以通过这种方式为继承的子类添加父类原型中的方法,而又不影响父类(38-41行):

 1 <!DOCTYPE html>
 2 <html>
 3     <meta charset="UTF-8" />
 4     <head>
 5         <script>
 6             
 7             /**
 8              * Person 父类 人
 9              * @param {Object} name 姓名
10              * @param {Object} sex 性别
11              */
12             function Person(name, sex){
13                 this.name = name;
14                 this.sex = sex;
15             }
16             Person.prototype.showName = function(){
17                 alert("姓名:"+this.name); 
18             }
19             Person.prototype.showSex = function(){
20                 alert("性别:"+this.sex); 
21             }
22             
23         /*-----------------------------------------------------*/
24             
25             /**
26              * Student   学生 继承 人
27              * @param {Object} name
28              * @param {Object} sex
29              * @param {Object} major 学生特有属性:专业
30              */
31             function Student(name, sex, major){
32                 //调用父类的构造函数
33                 Person.call(this, name, sex);
34                 
35                 //添加自己的属性
36                 this.major = major;
37             }
38             //继承父类原型中的方法
39             for(var p in Person.prototype){
40                 Student.prototype[p] = Person.prototype[p];
41             }
42             
43             //添加自己特有的方法
44             Student.prototype.showMajor = function(){
45                 alert("专业:"+this.major);
46             }
47             
48             var student = new Student("bojiangzhou", "男", "信息管理");
49             student.showName();
50             student.showSex();
51             student.showMajor();
52             
53             alert(Person.prototype.showMajor);
54         </script>
55     </head>
56 </html>

第53行弹出信息:Person中没有showMajor方法了。

最后,以案例篇中最后给出的拖拽例子来应用下继承,那个拖拽有一个问题,就是没有控制拖拽出边界的问题。

先贴出之前的拖拽版本:

drag.js:

 1 /**
 2  * 拖拽
 3  * @param {Object} id div的id
 4  */
 5 function Drag(id){
 6     this.oBox = document.getElementById(id);
 7     this.disX = 0;
 8     this.disY = 0;
 9     
10     var _this = this;
11     
12     this.oBox.onmousedown = function(){
13         _this.fnDown();
14     }
15 }
16 //鼠标按下
17 Drag.prototype.fnDown = function(ev){
18     var oEvent = ev || event;
19     
20     this.disX = oEvent.clientX - this.oBox.offsetLeft;
21     this.disY = oEvent.clientY - this.oBox.offsetTop;
22     
23     var _this = this;
24     
25     document.onmousemove = function(){
26         _this.fnMove();
27     };
28     document.onmouseup = function(){
29         _this.fnUp();
30     };
31 }
32 //鼠标移动
33 Drag.prototype.fnMove = function(ev){
34     var oEvent= ev || event;
35     
36     this.oBox.style.left = oEvent.clientX - this.disX + 'px';
37     this.oBox.style.top = oEvent.clientY - this.disY + 'px';
38 }
39 //鼠标抬起
40 Drag.prototype.fnUp = function(){
41     document.onmousemove = null;
42     document.onmouseup = null;
43 }

drag.html:

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5         <style>
 6             div {
 7                 position: absolute;
 8             }
 9         </style>
10         <title>拖拽</title>
11         <script type="text/javascript" src="../js/drag.js" ></script>
12         <script>
13             window.onload = function(){
14                 var drag1 = new Drag("box1");
15                 
16                 var drag1 = new Drag("box2");
17             };
18         </script>
19     </head>
20 
21 <body>
22     <div id="box1" style="background: red;width: 200px;height: 200px;"></div>
23     
24     <div id="box2" style="background: blue;width: 100px;height: 300px;"></div>
25 </body>
26 </html>

效果:可以看到红色和蓝色的都出边界了,但我们又不想去修改代码,那我们怎么做?学过java的应该都知道可以写一个子类来做一些更加具体的操作,又保留了父类的功能,就是继承。

DragLimit.js:DragLimit继承自Drag,控制了不能出边界

 1 /**
 2  * 限制边界的拖拽,继承自Drag
 3  * @param {Object} id
 4  */
 5 function DragLimit(id){
 6     Drag.call(this, id);
 7 }
 8 //继承方法
 9 for(var p in Drag.prototype){
10     DragLimit.prototype[p] = Drag.prototype[p];
11 }
12 /**
13  * 覆写父类的鼠标移动方法,控制不能移出边界
14  */
15 DragLimit.prototype.fnMove = function(ev){
16     var oEvent= ev || event;
17     
18     var left = oEvent.clientX - this.disX;
19     var top = oEvent.clientY - this.disY;
20     
21     //控制边界
22     if(left < 0){
23         left = 0;
24     } else if(left > document.documentElement.clientWidth-this.oBox.offsetWidth){
25         left = document.documentElement.clientWidth-this.oBox.offsetWidth;
26     }
27     if(top <= 0){
28         top = 0;
29     } else if(top > document.documentElement.clientHeight-this.oBox.offsetHeight){
30         top = document.documentElement.clientHeight-this.oBox.offsetHeight;
31     }
32     
33     this.oBox.style.left = left + 'px';
34     this.oBox.style.top = top + 'px';
35 }

dragLimit.html

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8">
 5         <style>
 6             body {
 7                 padding: 0;
 8                 margin: 0;
 9             }
10             div {
11                 position: absolute;
12             }
13         </style>
14         <title>拖拽</title>
15         <script type="text/javascript" src="../js/drag.js" ></script>
16         <script type="text/javascript" src="../js/dragLimit.js" ></script>
17         <script>
18             window.onload = function(){
19                 var drag1 = new Drag("box1");
20                 
21                 var drag1 = new DragLimit("box2");
22             };
23         </script>
24     </head>
25 
26 <body>
27     <div id="box1" style="background: red;width: 200px;height: 200px;"></div>
28     
29     <div id="box2" style="background: blue;width: 100px;height: 300px;"></div>
30 </body>
31 </html>

效果:蓝色是不能移出边界的。

三、JS中的对象

js中的对象分为本地对象、内置对象、宿主对象,这里给出W3School文档供参考:ECMAScript 对象类型

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表