javascript面向对象程序设计

面向对象语言有一个标志,就是都有类的概念,通过类可以创建任意多个具有相同属性和方法的对象。但是在javascript中是没有类这个概念的,所以它与其他基于类的面向对象语言也存在差异。在讲javascript的面向对象程序设计之前,我们还是先来温习一下javascript的一些基础知识。相当多的人都在项目中用javascript,但也有很多的人与我之前一样,敲了相当长时间的js代码,却不知道javascript中有哪些类型。言归正传,咱们从数据类型开始吧。

一、数据类型

Javascript中有五种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number和String;一种复杂数据类型:Object。

—Undefined类型只有一个值,即特殊的undefined。每个未被初始化的变量的默认值都为undefined。
var message;
alert(message);      // "undefined"
—
Null类型也只有一个值,这个特殊的值是null。实际上undefined是派生自null的,但是不完全相等undefined。
var code=null;
alter(typeof code);   //Object
alter(null==undefined);  //true
alter(null===undefined); //false
—
Boolean 类型有两个值:true和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中的大部分操作符都与其他语言的保持一致,下面说一组与其他语言不一致的操作符:相等和不等、全等和不全等。

这两组操作符的主要区别在于:

—1.相等和不等——先转换,再比较
2.—全等和不全等——仅比较,不转换
alert(“55”==55);  //true
alert(“55”===55);  //false
alert(“55”!=55); //false
alert(“55”!==55); //true

 

三、函数

javascript的函数有以下的一些特点:

—1.无须指定函数的返回值,可以在任何时候返回任何值。
—2.函数的参数是以一个数组形式传递的(包括0个或多个值),所以函数可以传递任意数量的参数。
—3.没有传递值的命名参数被初始化为undefined。
—4.可以通过arguments对象来访问这些参数。 arguments对象与数组类似,通过下标访问。
5.—函数中没有签名的概念,所以不支持重载。

因为可以不用签名,那么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高级程序设计》整理而来,只是作为一种阅读笔记,如有谬误,请指正。

posted @ 2012-11-26 18:09  起跑线  阅读(1643)  评论(3编辑  收藏  举报