Javascript面向对象编程基本原理

讲解Javascript面向对象的基础原理。

李青原(liqingyuan1986@aliyun.com)创作于博客园个人博客(http://www.cnblogs.com/liqingyuan/),转载请标明出处。

1.Javascript中的对象:

A.对象类型和JSON格式的关系:

JSON(Javascript object natation)原本是JS内部对Object数据类型的描述方式,也就是字面值(literal)语法。

只是JSON这种语法设计的太成功,人们认识到了JSON的优势,才把这种原本javascript独有的数据语法拿出来,作为了网络环境中常用的一种通用数据格式。

所以对于javascript而言,以下两种写法其实是等同的:

1 var a = new Object();
2 
3 var a = {};

B.对象类型的内置[[prototype]]属性:

在javascript的对象模型中,存在一个特殊概念:原型链。javascript的不同对象之间的继承关系,正是依靠原型链这个设计模型来实现的。

原型链的原理如下:

每个Object类型的变量都内置了一个私有的[[prototype]]属性,IE下该属性无法访问,但是Firefox和Chrome封装了公共属性__proto__来访问它。

(注:为了便于区别函数的prototype属性,下文中使用__proto__属性来指代[[prototype]]属性)

__proto__属性通常指定的是另一个Object类型的变量。

当JS引擎查找某个Object类型变量的属性时,如果在变量本身找不到该属性,就会去__proto__属性指向的__proto__对象上去寻找;如果还是找不到,会继续在__proto__对象的__proto__属性指向的下一级__proto__对象上寻找,以此类推。

因此,如果某个Object类型变量A的__proto__属性指向了对象B,A就会拥有B的所有属性和方法,也就模拟了继承关系。

这种经典的“顺藤摸瓜”式的调用方式,被形象的称为原型链。原型链的终端是javascript引擎内置的一个对象,该对象的__proto__属性会是undefined,JS引擎查找到此就会结束。

2.javascript的函数:

A.函数对象的prototype属性:

在javascript中,函数(Function)是一种特殊的Object类型。

当在Object基本类型的属性方法之外,javascript会给所有函数对象指定一个共有属性:prototype属性。 (函数类型的prototype属性不等于Object类型内置的__proto__属性,此属性为函数特有的公共属性)

如前文所述,__proto__属性虽然在FF下提供了公共访问方式,但是javascript默认该属性是不可访问的,为了实现面向对象编程,函数对象和它的prototype属性将成为关键。

3.利用函数和原型链实现javascript的面向对象:

A.利用函数+关键字new实现对象创建:

 1 function Human(){
 2 
 3     this.name = "liqingyuan";
 4 
 5     this.gender = "male";
 6 
 7 }
 8 
 9 var human = new Human();//行1
10 
11 alert(human.name +"@@"+human.gender);//liqingyuan@@male
12 
13 alert(human.constructor);

前文所述,javascript函数的prototype属性指向一个由javascript引擎创建的对象,该对象还会具有一个默认的属性constructor。

constructor属性起到了构造方法的作用,此属性默认指向函数体本身,从而让JS能够利用函数来替代构造方法,模拟对象的创建过程。

因此可以分析在行1的对象创建过程:

javascript首先创建一个空的Object对象,交给指针human。

而new关键字,起到的作用就是让新建的这个object执行Human函数的prottotype对象的constructor属性(默认即Human函数体本身)。

而javascript中,函数中的this默认指代函数的调用者,所以此时Human函数体内的this指代的是新建的空object对象,也就是指针human。所以走完整个Human函数体后,human也就拥有了name和gender属性。

与此同时,new出来的新对象的内置__proto__属性会被指引到Human函数的prototype对象上,也就是说新对象不仅拥有了函数体内部通过this指定的属性,同时拥有了函数的prototype对象的属性。这是最关键的一步,导致javascript继承体系的成立。

根据上面分析的构造函数原理,上面代码其实还可以写成:

function Human(){
 
     this.name = "liqingyuan";
 
     this.gender = "male";
 
}
 
var human = new Human.prototype.constructor();//行1
 
alert(human.name +"@@"+human.gender);
 
alert(human.__proto__ == Human.prototype);//行2

行1中不再使用类似JAVA的方式,而是直接调用构造函数,同样能够获得新的human对象。

Firefox和Chrome下,行2中则会弹出true,显示了上面分析过程最后一步的正确性。 (必须在Firefox和Chrome,属性为双下划线)

B.prototype对象的属性共享关系:

通过前文的分析过程,我们可以知道同一个函数new出来的对象,其内置__proto__属性都指向了函数本身的prototype对象,因此它们必然都能访问prototype对象的属性。此时,函数体内部通过this指定的属性都是每个新对象独有的,而通过prototype对象获得的属性则是共有的。

因此通常对prototype对象的修改,都会反映到新对象上。

但是也有例外:

javascript在处理原型链时,也遵循类似JAVA的规则,优先在对象中查找属性,找不到则去对象的prototype对象中找,找不到再去prototype对象的prototype对象中找,以此类推,直到prototype属性指向undefined。

 1 function Human(name,gender){
 2 
 3     this.name = name;
 4 
 5     this.gender = gender;
 6 
 7 }
 8 
 9 Human.prototype.age = 25;
10 
11  
12 
13 var liqingyuan = new Human("liqingyuan","male");
14 
15 var zhangjun = new Human("zhangjun","male");
16 
17 alert(liqingyuan.age +"@@"+ zhangjun.age);//行1,25  25
18 
19  
20 
21 liqingyuan.age = 28;
22 
23 alert(liqingyuan.age +"@@"+ zhangjun.age);//行2,28  25
24 
25  
26 
27 Human.prototype.age = 30;
28 
29 alert(liqingyuan.age +"@@"+ zhangjun.age);//行3,28  30

行1中,由于Human对象的prototype被指定了age属性,所以2个Human对象的age都为25;

行2中,liqingyuan被单独指定了age属性,所以JS引擎优先查找它自己的age属性28,;

行3中,由于liqingyuan自己有了age属性,所以虽然重新指定了prototype的属性age为30,JS引擎依然优先查找liqingyuan自己的属性28,而非30。

C.指定对象间的继承关系:

根据前文分析,完全可以通过指定prototype对象,来获得类似继承的效果。

 1 function NewObject(){
 2     this.test = 123;
 3 }
 4 
 5 function SubObject(){
 6 
 7 }
 8 
 9 SubObject.prototype = new NewObject();//行1指定继承关系
10 
11 
12 
13 var object = new SubObject();
14 
15 alert(object.test);//子类对象拥有父类的属性

行1中,通过将NewObject对象指定为SubObject的prototype属性,使得2者联系到一条原型链上,成为了事实上的父子关系。因此,新建的子类对象才能使用父类属性。