基础篇
学好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 对象类型
本文暂时没有评论,来添加一个吧(●'◡'●)