javascript面向对象程序设计
面向对象语言有一个标志,就是都有类的概念,通过类可以创建任意多个具有相同属性和方法的对象。但是在javascript中是没有类这个概念的,所以它与其他基于类的面向对象语言也存在差异。在讲javascript的面向对象程序设计之前,我们还是先来温习一下javascript的一些基础知识。相当多的人都在项目中用javascript,但也有很多的人与我之前一样,敲了相当长时间的js代码,却不知道javascript中有哪些类型。言归正传,咱们从数据类型开始吧。
一、数据类型
Javascript中有五种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number和String;一种复杂数据类型:Object。
var message; alert(message); // "undefined"
var code=null; alter(typeof code); //Object alter(null==undefined); //true
alter(null===undefined); //false
数据类型 | 转换为true | 转换为false |
Boolean | true | false |
String | 任何非空字符串 | ""(空字符) |
Number | 任何非0数字 | 0和NaN |
Object | 任何对象 | null |
Undefined | N/A(不适用) | undefined |
Number类型的包括5e-324到1.79e+308之间的所有浮点数和NaN(非数值)。javascript中没有整型和浮点型,Number类型用于表示所有数值。
NaN值有两个特点:
1.任何涉及NaN的操作都会返回NaN;
2.NaN与任何值都不相等,包括NaN。
alter(NaN==NaN); //false
alert(isNaN(NaN)); //true alter(isNaN(10)); // false alter(isNaN("10")); // false alter(isNaN("hello")); //true alter(isNaN(true)); // false
从上面的代码中可以看出,只要能转换成数值型的都不是NaN。
Number的转换规则:
1.如果是数值,则直接返回
2.如果是Boolean,则true=1,false=0
3.如果是null,则返回0
4.如果是undefined,则返回NaN
5.如果是字符串,当字符串是数值,则转为数值返回,忽略前导0;当字符串是十六进制的字符串,转为十进制数返回;当字符串是""(空值)时,返回0;当字符串为其他值时,为NaN
6.如果是Object,则先调用对象本身的.valueOf(),然后依据前面的规则进行转换,如果得到的值是NaN,再调用对象的.toString()再依据前面的规则转换。
说到数值转换,javascript中的两个方法顺带提一下,parseInt()和parseFloat(),除了一个转换整数,一个可以转换小数以外,他们还存在一些差别的:
1.parseInt()可以转换二进制、八进制、十进制、十六进制的数值,而parseFloat只能转换十进制的数
2.parseFloat()始终都会忽略前导0
String类型,除了null和undefined,任何对象都有一个.toString()方法,String类型的转换就是直接返回.toString()
Object类型就是一组数据和函数的集合,Object类型是所有它的实例的基础,我们可以使用new关键词来创建它的实例。下面两种方式都是OK的。
var 0=new Object; //true var o=new Object(); //true
Object类型可以直接添加属性和方法,无需定义。但是我们不能给任何基本类型的值添加属性。
var person=new Object(); person.name="Sherry"; alert(person.name); //"Sherry"
var age=30; age.name="meco"; alter(age.name); //undefined
Object的每一个实例都具备下面的属性和方法:
1.constructor——保存着用于创建当前对象的函数。
2.hasOwnProperty(propertyName)——用于检查给定的属性在当前的对象实例中是否存在。
3.isPropertyOf(object)——用于检查传入的对象是不是另外一个对象的原型。
4.propertyIsEnumerable(propertyName)用于检查给定的属性是否能够用for-in来枚举。
5.toString()——返回对象的字符串
6.valueOf()——返回对象的字符串、数值或者布尔值
我们常用的Object类型有:Array、Date、RegExp、Function等。另外javascript种还内置了两个对象:Global和Math ,这两个对象已经实例化了,我们不需要显示的实例化这两个对象。
二、操作符
javascript中的大部分操作符都与其他语言的保持一致,下面说一组与其他语言不一致的操作符:相等和不等、全等和不全等。
这两组操作符的主要区别在于:
alert(“55”==55); //true alert(“55”===55); //false alert(“55”!=55); //false alert(“55”!==55); //true
三、函数
javascript的函数有以下的一些特点:
因为可以不用签名,那么javascript中还存在一种特殊的函数:匿名函数。
function(obj1,obj2){ ...... }
使用匿名函数一般有一下三种形式:
1.函数作为参数传入另外一个函数
2.将函数作为一个值赋值给某一个声明的变量
3.一个函数中返回一个函数
function demo(propertyName){ return function(object1,object2){ return object1[propertyName]>object2[propertyName]?object1[propertyName]: object2[propertyName]; }; }
另一个值得注意的函数是:递归函数。
递归函数在调用自身的时候,使用arguments.callee比使用函数名直接调用要更加保险。如下代码所示:
function Sum(num){ if(num<=1) return 0; return arguments.callee(num-2) + arguments.callee(num-1); }
假如使用函数名调用的话,可能会引起函数错误。如下所示:
var fun=Sum; Sum=null; fun(10); //函数报错
虽然这种几率很小,但我们还是要尽量避免。
四、闭包
说完了函数,顺便说一下闭包。如何在javascript中将声明的变量私有化?——闭包可以帮我们做到。还是看代码:
function demo(){ var name=""; function getName(){ alert(name); }; }
闭包与匿名函数有点相似,只要在一个函数内构建了其他函数,就构成了闭包。
要访问闭包内的函数,需要构建特权方法,还是以上面的代码为例:
function demo(){ var name=""; //特权方法 this.getName=function(){ alert(name); };
过度使用闭包会造成内存泄漏,所以闭包不能滥用,变量使用完后尽量销毁。
五、Javascript面向对象的程序设计
面向对象语言的一个标志,那就是它们都有类的概念,通过类可以创建多个具有相同属性和方法的对象。javascript中没有类,所以与其他基于类的面向对象的语言有所不同,javascript中的对象实际上就是一堆无序属性的集合。首先看一下,在javascript中我们如何创建一个具有多个属性的对象。
var person=new Object(); person.Name=””; person.Age=””; person.Sex=””; person.sayName=function(){ alert(this.Name); }; person.sayName();//调用
创建自定义对象最简单的方式就是先创建一个Object实例,然后直接给这个实例添加属性和方法。OK,对象是创建出来了,但是使用同一个接口创建多个对象,会产生大量的重复代码。为了解决这个问题,人们创建一个广为人知的模式。
a.工厂模式
function createPerson (name,age,sex){ var o=new Object(); o.Name=name; o.Age=age; o.Sex=sex; o.sayName=function(){ alert(this.Name); }; return o; } //创建对象 var person1=createPerson(“Nick”,27,”男”); var person2=createPerson(“Lili”,30,”女”); //对象调用 person1.sayName(); person2.sayName();
虽然工厂模式解决了创建多个相似对象的问题,但是无法解决对象识别的问题,即无法识别创建的对象类型。于是,另一种新的模式出现了。
b.构造函数模式
function Person (name,age,sex){ this.Name=name; this.Age=age; this.Sex=sex; this.sayName=function(){ alert(this.Name); }; } //创建对象 var person1=new Person (“Nick”,27,”男”); var person2=new Person (“Lili”,30,”女”); //对象调用 person1.sayName(); person2.sayName();
构造函数与其他的函数唯一的区别在于调用方式的不同。
//当做构造函数使用 var person=new Person(“Grey”,”27”,”男”); person.sayName(); //作为普通函数调用 Person(“Grey”,”27”,”男”); window.sayName(); //再另外一个对象的作用域中调用 Var o=new Object(); Person.call(o, “Grey”,”27”,”男”); o.sayName();
使用构造函数,最主要的问题就是:每个方法都要在每个实例上重新创建一遍,即:在使用new Person()创建person对象的时候,sayName()方法被创建了两遍。
下面的两种写法在逻辑上是等价的。
this.sayName=function(){ alert(this.Name); } this.sayName=new function(“alert(this.name)”);
OK,继续上面的话题,既然重复创建了方法,那么我们把方法转移到函数外部,就能解决这个问题了。代码如下:
function Person (name,age,sex){ this.Name=name; this.Age=age; this.Sex=sex; this.sayName= sayName; } function sayName(){ alert(this.Name); } //实例方法 var person1=new Person (“Nick”,27,”男”); var person2=new Person (“Lili”,30,”女”); //方法调用 person1.sayName(); person2.sayName();
最然这样解决了创建一个实例的时候一个方法被重复创建的问题,但是全局函数sayName只能对某个对象调用,而且当对象有很多方法时就要定义多个全局函数。这个问题,我们可以通过原型模型来解决。
c.原型模型
我们还是先看代码:
function Person(){} Person.prototype.Name=”Nick”; Person.prototype.Age=27; Person.prototype.Sex=”男”; Person.prototype.sayName=function(){ alert(this.Name); }; var person1=new Person (); var person2=new Person (); person1.Name=”Grag”; person1.sayName(); //”Grag” person2.sayName(); //”Nick” alert(person1.sayName==person2.sayName); //true delete person1.Name; person1.sayName(); //” Nick”
原型模型我们使用了prototype(原型)属性,每一个创建的函数对象都有prototype属性,这样我们不必在函数内部定义对象,可以直接将对象添加到原型对象中。现在构造函数只是为我们创建了一个对象,但是新对象的属性和方法,是跟所有的实例共享的。
为了减少不必要的代码和代码的整洁,我们可以对原型进行封装。
function Person(){} Person.prototype={ Name:”Nick”, Age=27, Sex=”男”; sayName=function(){ alert(this.Name); }; }; var person=new Person (); person.sayName(); //”Nick”
可以随时为原型添加属性和方法,但是不能重写整个原型对象。
function Person(){} var person=new Person(); person.prototype.sayHi=function(){ alert(“hello word”); }; person.sayHi(); //hello word function Person(){}; var person=new Person(); person.prototype={ name:”Nick”, sayHi:function(){ alert("hello word"); } }; person.sayHi(); //error
同样的,原型对象也存在缺点:
1. 所有实例在默认情况下都取得相同的属性值
2. 原型中所有的属性是被很多实例共享的,所以当属性是引用对象的时候,其中一个实例修改属性值时就可能会导致其他实例的属性值被改变。
另外,顺便提一下,利用prototype我们可以实现对象的继承。
function SuperFunction(){ this.property="Super"; } SuperFunction.prototype.getSuperValue=function(){ return this. property; }; function SubFunction(){ this. subproperty="Sub"; }; SubFunction.prototype=new SuperFunction();// SubFunction继承了SuperFunction //给SubFunction添加新方法 SubFunction.prototype.getSubValue=function(){ return this. subproperty; }; //重写超类的方法 SubFunction.prototype.getSuperValue=function(){ return "Override"; }; var instance=new SubFunction(); instance. getSuperValue(); //"Override" instance. getSubValue(); //"Sub"
利用prototype实现继承,也存在一些问题:
1.如果有方法或者属性包含引用类型的值,那么当值发生变化的时候,所有引用对象的值都会变化;
2.创建子类的实例,不能向超类型传递参数
OK,对于上面的问题,我们可以借用构造函数实现继承,代码如下:
function SuperFunction(name){ this.name=name; } function SubFunction(name) { //继承SuperFunction,同时传递参数 SuperFunction.call(this,name); } var instance=new SubFunction("Nick"); alert(instance.name); //"Nick"
仅仅是借用构造函数,就产生了和构造函数一样的问题:函数无法复用。所以我们还要再进行改进,继续看代码:
function SuperFunction(name){ this.name=name; } SuperFunction.prototype.sayName=function(){ alert(this.name); } function SubFunction(name){ SuperFunction.call(this,name);//继承属性 } SubFunction.prototype=new SuperFunction();//继承方法 var instance=new SubFunction("Nick"); instance.sayName(); //"Nick"
d.组合使用构造函数和原型模式
因为原型模式的缺陷,所以在Javascript中,我们很少使用单一的原型模式,创建自定义类型最为常见的方式就是组合使用构造函数和原型模式,这样每个实力都可以共享方法,又可以创建实例的时候有一份相同属性和方法的副本,而且最大限度的节省了内存的开销。代码如下:
function Person(name,age,sex){ this.Name=name; this.Age=age; this.sex=sex; this.Friends=[“Shely”,”Court”]; } Person.prototype={ sayFriends=function(){ alert(this. Friends); }; }; //实例化对象 var person1=new Person(“Nick”,27,”男”); var person2=new Person(“Alen”,27,”女”); //函数调用 person1.Friends.push(“Van”); person1. sayFriends(); //“Shely”,”Court”, “Van” person2. sayFriends(); //“Shely”,”Court”
好了,这次的暂时先到这里,文章所有的内容根据《javascript高级程序设计》整理而来,只是作为一种阅读笔记,如有谬误,请指正。