Javascript中并没有类的概念,只有对象, 而对象实际是一个哈希表,每个哈希表项的内容,可以是基本类型,函数或者另外一个对象。特别的,函数也被认为是一个对象(你也可以认为函数就是函数)。Javascript并没有内置的面向对象的机制,但是可以通过各种方法模仿出一个面向对象的效果,因此为了达到某个目的往往有不同的方法。
一、对象的创建
创建类的过程,实际是创建一个函数。
function User(id,name,name,age){
this.id=id;
this.name=name;
this.age = age;
function showName(){
alert(this.name);
}
}
上面的代码创建了一个User类, 它包含3个属性和一个方法。这种方法称为构造函数法。其中,this指向(将要)被创建的类的实例。这种方法类似于C#的语法,易于理解,但是缺点是showName这个函数是不共享的(因为函数也是对象)。如果我们随后创建了User1和User2两个实例,尽管定义是完全一样的,但是每个实例保存了一份独立的showName函数副本(或者可以说是独立的showName对象)
为了解决这个问题,引入原型法。
function User(id,name,name,age){
this.id=id;
this.name=name;
this.age = age;
}
User.prototype.showName(){
alert(this.name);
}
这种方式稍显有些别扭的是函数的定义是在类的外面,与传统的OOP不太相同,解决方案是动态原型法。
function User(id,name,name,age){
this.id=id;
this.name=name;
this.age = age;
if (typeof User._initialized == “undefined”) {
User.prototype.showName(){
alert(this.name);
}
User._initialized=true;
}
}
二、继承的实现
继承有2种基本实现方式:对象冒充(Object masquerading)和原型链(Prototype Chain)。
对象冒充的原理是在子类的构造函数中调用父类的构造函数。
function User(id,name ,age){
this.id=id;
this.name=name;
this.age = age;
}
User.prototype.showName(){
alert(this.name);
}
function SuperUser(id,name,age,category){
User.call(this,id,name,age);
this.category=category;
}
如果我们把User视为对象,那么var oUser=new User(id,name,age), 构造函数中的this就是正在被创建的对象;如果我们把User视为普通的函数,那么this就可以是其它任何的对象。在SuperUser的构造函数中,User.call方法是调用User函数的另外一种方法,其传递的第一个参数this是正在创建的SuperUser对象,这样在User的构造函数中的this就是一个冒名的SuperUser对象,于是SuperUser对象就有了User的所有属性。
但这种方法的缺点是只有父类中直接定义的属性和方法才能为子类访问到,在父类的prototype中定义的方法在子类中无法访问到,因为只有把函数视为对象并用new方式生成对象实例的时候,才会自动生成prototype,如果把它视为函数则不行。
解决的方法是原型链。每个类的构造函数都包含一个prototype对象,而prototype对象又包括一个指向构造函数的指针,同时,每个类的实例包含一个指向prototype的指针。因此,如果把子类的prototype指向父类的一个实例,那么子类就拥有了父类的全部属性和方法。
function User(){
this.id=new Object();
this.name=new Object();
this.age = new Object();
}
User.prototype.showName(){
alert(this.name);
}
function SuperUser(){
this.specialpermission=new Object();
}
SuperUser.prototype=new User();
SuperUser.prototype.showName(){
alert(this.name + this.age);
}
SuperUser.prototype.newMethod()_{
alert(“this is new method”);
}
但是原型链的问题在于把子类的prototype设为了父类的一个实例,这样所有的子类的实例都共享这一个实例,那么如果某个实例修改了父类中的某个属性,在另外的一个实例中这个属性也会跟着一起改。另外一个问题是父类的构造函数必须是无参数的。
解决的方法是组合式继承:属性的定义用对象冒充,方法的定义用原型链。组合式继承是最常用的继承方式。
function Super(name){
this.name=name;
}
Super.prototype.sayName=function{
alert(this.name)
}
function Sub(name,age){
Super.call(this,name);
this.age=age;
}
Sub.prototype= new Super();
Sub.prototype.constructor= Sub;
Sub.prototype.sayAge=function{
alert(this.age);
}
除了以上这2种,还有一种原型式继承
function object(o) {
function F() { }
F.prototype = o;
return new F();
}
此种方式是以一个基类的实例为基础,生成的派生对象的原型指向基础类实例,因此就拥有了基类的所有属性以及方法。这种方式与原型链类似,但是是以类的实例而不是类的方式实现的,其结果相当于对原有的实例做了一个浅复制
在原型式继承的基础之上,发展出一种寄生式继承。
function CreateNew(original){
var clone=object(original);
clone.newfunction=function{};
return clone;
}
组合式继承是最常用的,但问题在与父类的构造函数必须调用2次,一次是指定子类的原型时,一次是在子类的构造函数中,特别是在第一次调用时,子类已经拥有了父类中的所有属性(在子类的原型中),在第二次调用时再次拥有了父类的所有属性(在子类的实例中),实例中的属性会屏蔽掉原型中同名属性。
最终的解决方案是寄生组合式继承,即把下面的语句
Sub.prototype= new Super();
Sub.prototype.constructor= Sub;
替换成
extend(Sub,Super)
其中extend函数定义如下
function extend(subType, superType) {
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
三、匿名函数,闭包和模块
匿名函数就是没有名字的函数,例如
function (){
}
由于没有名字所以也就无法调用,因此通常情况下匿名函数总是位于普通函数的内部,例如
function Function1(){
return function(){
}
}
每个函数都有它的调用上下文(也就是在函数内可以访问到的对象的范围),如果是普通函数,它的上下文就是全局的对象已经函数内自己定义的对象,如果是函数内的函数,那么它可以访问到全局对象,外层函数中的对象已经自身定义的对象。即使外层函数已经释放了,匿名函数仍然可以访问外层函数中的对象。例如
function Create(){
var a;
a=1;
return function(x){return a+x;}
}
var obj=Create();
alert(obj(2)); // 3
Create函数返回一个匿名的函数并赋值给变量obj,因为这个函数在Create函数内部,所以尽管obj不能直接访问Create中的变量a,但是却仍然可以间接地调用obj(2)来访问。