最近在学习html5,玩了下canvas,发现js中很多的东西都不太记得了。翻了下笔记后发现还是去图书馆逛逛把,到借阅区找了我一直想看的《javascript design patterns》好好研读了个下午,读罢,顿时有种醍醐顿开的感觉(夸张了..),发现之前对javascript OO方面的认识真的很浅,读了前几章关于OO的介绍后感觉思路清晰很多了,对于js一些基本概念的认识也加深了很多。同时也感概到程序员的想象力之丰富,可以将js模仿如此想传统的OO语言。当然这也更适合我们这些用惯了后台服务器语言开发的人来使用js。
废话了一段,算是这个笔记的开始把,接下来就开始了。首先还是笔记下js如何模拟继承的。
(一)js中的继承分为2种,一种是类式继承,另一种是原型式继承。
1.关于原型链接
关于继承,不得不先讲下js的原型链,因为这是js实现继承的基础。
借用下《High Performance JavaScript》的一段代码和UML图,个人觉得这个是理解原型链比较好的例子。
2 this .title = title;
3 this .publisher = publisher;
4 }
5 Book.prototype.sayTitle = function (){
6 alert( this .title);
7 };
8 var book1 = new Book( " High Performance JavaScript " , " Yahoo! Press " );
9 var book2 = new Book( " JavaScript: The Good Parts " , " Yahoo! Press " );
10 book1.sayTitle();
11 book2.sayTitle();
UML图如下:
代码很简单,从UML图我们可以看到,这正是理解原型链的关键,这里有2个名词要着重提到,一个是原型对象, 一个是prototype对象。在js中每个对象都有原型对象的,原型对象是其构造函数的prototype属性所指向的那个对象,这里又有个名词要注意,由于js中处处是对象(当然除了3种原始类型,不过在需要的情况下,他们也可以被包装成对象的),为了区别,我们把构造函数称为函数对象。并且只有函数对象才拥有prototype属性,这一点我们可以从UML图中看出。
从例子可以看到,每个实例化对象都有个_proto_这个内部属性,这个属性是指向其构造函数的prototype属性所指向的那个对象。这有什么用呢,我们看下这句代码:
2 alert( this .title);
3 };
但我们调用:
的时候,由于book1里面没有sayTitle()这个方法,那么怎么办呢?这个时候原型链就有用了,他会沿着book1的原型对象去找sayTitle()这个方法,找到了就调用。
理解了这一些,理解两种继承就很容易了。
2.先看下类式继承的例子:
2 // Person类
3 function Person(name){
4 this .name = name;
5
6 }
7 Person.prototype.getName = function(){
8 alert( this .name);
9 }
10
11 // BOY类,继承Person类
12 function Boy(name,age){
13 Person.call( this ,name);
14 this .age = age;
15 }
16 // 将Person加到Boy的原型链中
17 Boy.prototype = new Person();
18 Boy.prototype.constructor = Boy;
19
20 Boy.prototype.getAge = function(){
21 alert( this .age);
22 }
23
24 var boy = new Boy( " james " , " 23 " );
25 boy.getName();
26 boy.getAge();
用惯了java或C#等传统面向对象语言的人来看这些应该会有种亲切感把~,这种继承很符合我们关于传统继承的思想,先写个父类,再让子类来继承,可能16到18行的会有点不和谐对于不熟悉js的人来说,那么我们可以用一个extend方法把这个模拟继承的过程包装起来。
2 function extend(subClass,superClass){
3 function f(){};
4 f.prototype = superClass.prototype;
5 subClass.prototype = new f();
6 subClass.prototype.constrcuctor = subClass;
7
8 }
代替掉16到18行,这样基本就和传统的很接近了。
还有一点要特别注意:就是prototype属性指向的是一个实例化了的对象
3.接下来介绍原型式继承
还是先看代码:
2 // clone方法,主要是将object对象加到f的原型链上
3 function clone(object){
4 function f(){};
5 f.prototype = object;
6 return new f();
7
8 }
9
10 // 直接定义一个js对象
11 var Person = {
12 name : " james " ,
13 getName : function (){
14 alert( this .name);
15 }
16
17 }
18
19
20 var boy = clone(Person);
21 // 还没改变前读取的是原来Person的默认值;
22 boy.getName(); //输出James,即原来默认那个值
23 boy.age = " 23 " ;
24 boy.getAge = function (){
25 alert( this .age);
26 }
27
28 boy.name = " kobe " ; //改变了name的值
29 boy.getName(); //输出kobe,说明把原来的值覆盖了
30 boy.getAge();
31
32
33 var girl = clone(Person);
34 girl.getName(); //输出james,即默认值
35 girl.name = "tracy"; //改变了name
36 girl.getName(); //输出tracy,说明把原来值覆盖了
37
从第10行看起,首先我们定义了一个json对象Person,注意我们没用function,就是说这是一个对象了而不是函数对象,然后我们用一个clone方法将其赋给了boy变量,由开头可以知道clone方法其实就是把Person对象加到空对象f的原型链上,然后返回f给boy,原型链已经创建成功即继承已完成。
但是,这种原型式继承有个比较特别的地方:读写的差异性。即刚开始的时候所有实例化对象都是指向同一份拷贝的,直到他们改变原来的值,这个从21到36行的实验我们可以看出他的差别。很直观,就不多说了。
4.下面讨论下两种继承的优劣处:
各有各的优缺点,这里我主要从习惯和性能两点来讨论。
从习惯来说,类式继承对于用惯了C#等OO语言的人来说真是太舒服了,可以用原来的习惯来开发前台代码,好处自然是大大的。从这点来说,原型式继承就相对来说比较晦涩一点点了,后台人员还要转变思维来习惯,同时对他的读写差异性上理解也容易产生一些偏差。
从性能来说,原型继承就会好一点,因为实例化对象的时候,在没改变值之前他们都是共享同一份拷贝的,这样可以节约内存开销。
所以,选择哪一种还是要根据具体的项目需求来决定。