JS 学习笔记--12---面向对象
练习中使用的浏览器为IE10,如果各位朋友有不同意见或者本文有什么错误地方,望指正
ECMASCript有两种开发模式:函数式(面向过程)和面向对象。面向对象有一个很明显的标志,那就是类,我们可以通过类来创建很多具有相同的属性和方法的对象。但是ECMAScript中没有类的概念,它和其他很多的面向对象的高级语言中的面向对象不一样。
1、创建对象方式一
通过以前的var obj=new Object()方式来创建一个对象,是可以的,但是如果我们想创建两个或者多个相同属性或者方法对象的时候,势必会造成很多的代码,故这种方式不可取
这种方式创建对象的时候,添加属性的时候必须要通过 对象名.属性名 的方式来增加属性,添加方法也是同样的,不过方法体可以声明为一个匿名方法,然后赋值给一个属性,方法中可以用this来代替对象obj,因为this代表的是当前的作用域所属的对象,也就是前面new出来的Obejct()
1 // 传统的声明对象的方法,声明了两个长得差不多的对象,box,box2
2 var box=new Object(); //创建一个对象
3 box.user='abc'; //给对象添加属性
4 box.age=22;
5 box.run=function(){ //给对象添加方法,
6 return this.user+" "+this.age+" "+"运行中。。。";//this表示当前作用域下的对象
7 }
8
9 // this 表示 new Object()实例化出来的那个对象
10 // this 要放在一个作用域下面,比如上面的box.run(){},这个是box对象作用域下的方法,方可用this,来表示box本身
11
12 alert(box.user); //abc
13 alert(box.run()); //abc 22 运行中。。。
14 alert(box instanceof Object); //true
15
16
17 var box2=new Object(); //创建第二个对象
18 box2.user='jack'; //添加属性
19 box2.age='33';
20 box2.run=function(){ //添加方法
21 return this.user+" "+this.age+" "+"运行中。。。";//this表示当前作用域下的对象,即box2
22 }
23
24 alert(box2.user); //jack
25 alert(box2.run()); //jack 33 运行中。。。
26 alert(box2 instanceof Object); //true
2、工厂模式创建对象
为解决上面问题而出现了工厂模式,这种方法就是为了解决实例化对象的时候产生大量的重复的代码的问题,创建方式如下:
1 // 模试
2 function createObject(user,age){
3 var obj=new Object(); //创建一个新的对象,每调用一次就创建一次
4 obj.user=user; //给每个创建属性
5 obj.age=age;
6 obj.run=function(){ //this 就是指的当前创建的这个对象,obj
7 return this.user+" " +this.age+" "+"运行中...";
8 }
9 return obj; //需要返回这个对象,返回的是对象的引用
10 }
11
12 //由模式创建变量 少了很多代码
13 var box1=createObject('abc',22); //创建对象box1
14 var box2=createObject('jack',33); //创建对象box2
15
16 alert(box1.run()); //abc 22 运行中... 运行对象box1的run方法
17 alert(box2.run()); //jack 33 运行中... 运行对象box2的run方法
上面写了一个工厂模式,并且创建了一个对象,以后就可以调用这个对象的方法和属性了,但是这种方法还有一个问题就是,当有多个工厂模式,多个不同的类型的对象的时候,是无法判断具体是那个类型的实例对象
1 // 模式2 内容中和模式1有一点小区别
2 function createObject2(user,age){
3 var obj=new Object();
4 obj.user=user;
5 obj.age=age+'a';
6 obj.run=function(){
7 return this.user+" " +this.age+" "+"运行中...";
8 }
9 return obj;
10 }
11 //由工程模式2创建的对象,
12 var box3=createObject2('tom',44); //用模式2创建对象box3
13 alert(box3.run()); //tom 44a 运行中... 运行对象box3的run方法
14
15 //判断三个变量结果都是object类型 但无法判断是那个工厂模式创建的
16 alert(box1 instanceof Object); //true
17 alert(box2 instanceof Object); //true
18 alert(box3 instanceof Object); //true //不管怎样,他们都是Object类型,就无法区分,谁到底是谁的对象了
如果用同一个模式创建了两个对象的话,即使赋初值是一样的,但是他们并不相等,因为他们是引用类型的,而且里面的方法也是引用类型的
1 alert(box1.run);//均返回的是上面创建的时候=后面的内容 2 alert(box2.run); 3 4 alert(box1==box2); //false 比较的是他们的引用,说明这是两个对象
3、构造函数初始化
ECMAScript 中可以通过构造函数(构造方法)的方式来创建对象,类似于Object对象,创建方式如下:
1 //定义了一个构造函数一
2 function Box(user,age){
3 this.user=user; //用构造函数创建的时候,后台会自动的创建一个对象,this就指向了这个对象
4 this.age=age; //由于是后台自动的创建,故不需要显示的new一个对象,而且用this代替对象名
5 this.run=function(){
6 return this.user+" "+this.age+" "+'运行中...';
7 } //构造函数创建的时候,是不需要显示返回的,后台会自动的返回他引用
8 }
9
10 //用构造函数创建对象
11 var box1 = new Box('abc',22); //用构造函数创建对象,需要通过方式:new 构造函数名(参数表)
12 var box2 = new Box('jack',33);
13
14 //运行两个对象的方法
15 alert(box1.run()); // abc 22 运行中...
16 alert(window.box1.run()); // abc 22 运行中...
17 alert(box2.run()); // jack 33 运行中...
从上面可以看出,其实和C#等高级语言中的类差不多,构造函数中写同样的类容,外面创建变量的时候通过 new 构造函数名(参数)【高级语言中构造函数和类同名】。
在用构造函数创建对象的时候,每当我们用new关键字来创建一个对象的时候,执行过程如下:
1)、当使用了构造函数,并且 new 构造函数() 的时候,后台就执行了new Object() 语句;
2)、将构造函数的作用域给了新的对象,(就是new Object() 出来的对象),而函数中的this就代表了刚刚创建出来的这个对象;
3)、执行构造函数中的代码;后台返回这个新对象的引用,后台直接返回
和工厂模式相比不同:
1)、我们省略了var obj=new Object(); 这一显示的实例化一个对象;
2)、在添加属性和方法的时候用直接赋值给了this对象,即this来代替了工厂模式中的对象名;
3)、没有了return语句,即不需要我们手动的将创建的这个对象进行返回,系统会自动的返回这个对象的引用
使用构造函数的时候遵循的一些规范:
1)、构造函数也是函数,但是构造函数名最好是大写开头(有助于和普通函数进行区别);
2)、使用构造函数创建对象的时候,必须通过 new 构造函数名(参数值) 来创建对象
用构造函数创建的对象解决了工厂模式中遗留的问题,就是已经能够判断变量是由那个类型[构造函数]实例化而来的,因为用instanceof来判读对象的时候,可以将构造函数名Box当做类型来处理,故能够判断出实例对象是由那个构造函数类型实例化而来
1 //构造函数二
2 function Box2(user,age){
3 this.user=user;
4 this.age=age;
5 this.run=function(){
6 return this.user+","+this.age+",运行中..."
7 }
8 }
9
10 var box3 = new Box2('tom',44);
11
12 //判断对象的类型 可以知道是通过什么构造函数来创建的对象,变量时那个对象的引用
13 alert(box1 instanceof Object); //true
14 alert(box1 instanceof Box); //true 知道是用构造函数1来创建的对象
15 alert(box2 instanceof Object); //true
16 alert(box2 instanceof Box); //true
17 alert(box3 instanceof Object); //true
18 alert(box3 instanceof Box2); //true 知道是用构造函数2来创建的对象,box3是Box2对象的引用
关于构造函数中的this:在全局中this代表的是window对象,在构造函数体内,this代表的就是当前构造函数声明的对象
构造函数和普通函数的唯一区别就在于他们的调用方式不同,只不过构造函数也是函数,必须使用 new 运算符来调用才能够创建一个对象,否则就是普通的函数
构造函数里面的方法返回的也是引用,当我们创建两个对象,传递相同的参数的时候,两个对象是不相等的,同时box1.run==box2.run [run是构造函数中定义的一个方法]的返回结果也是false,即方法是引用,具有唯一性的。既然是引用在创建方法的时候是可以用Functon类型来创建,即:this.run=new Function("return ...");这种方法来创建,但是这是没有必要的
1 //下面两个执行的结果是一样的
2 alert(box1.run); //返回的是整个函数体 包括关键字 function
3 alert(box2.run); //返回的是整个函数体 包括关键字 function
4
5 alert(box1.run==box2.run); //false 它们的返回值一样,但是不相等 因为他们比较的是引用
6
7 //既然构造函数中的方法是引用,也就是说可以写成this.run= new Function("this.user+this.age+'运行中...'");的形式,但是这样没有多少意义
通过构造函数外面绑定同一个函数的方法来保证引用地址的一致性,因为这个方法是在外面创建的,只是创建了一次,即构造函数中使用 this.run=run; 后面这run是在全局中定义的一个方法。这样使用了全局的函数 run()来解决了保证引用地址一致的问题,但这种方式又带来了一个新的问题,全局中的 this 在对象调用的时候是 Box 本身,而当作普通函数调用的时候,this 又代表 window。故完全没有必要这样,而且不利于代码的封装
1 //构造函数二
2 function Box2(user,age){
3 this.user=user;
4 this.age=age;
5 this.run=run;
6 }
7
8 function run(){
9 return this.user+" "+this.age+" " +'运行中...';
10 }
11
12 var box=new Box2('abc',22);
13 alert(box.run()); // abc 22 运行中...
冒充调用:可以再外面定义一个Object类型,或者其他类型的变量,然后通过冒充调用来获取构造函数中声明的属性和方法,即通过prototype属性中的call和apply方法;
1 var o=new Object(); 2 Box.call(o,'maochong',55); //通过对象冒充的行试来获取这个对象的所有方法和属性 3 alert(o.user); //maochong 4 alert(o.run()); //maochong 55 运行中...
这样,虽然对象o是Object类型的变量,但是由于冒充调用改变了它的作用域,它就具有构造函数Box中定义的所有的方法和属性