1. 为什么要面向对象
如果你只是要用javascript做个用户名校验,那么基于事件驱动的面向过程的编程方法已经足够了,再复杂一点,如果是一个有十几个字段的表单校验,或者你个滑动banner,用上面的方法也还可以;但是如果你要搞一个类似Gmail的东西,哪怕只有其功能的十分之一的的复杂前端应用,面向对象也是必须的了。否则,代码的复杂度将是难以想象的,即使勉强开发完成了,以后的维护也将是个噩梦。

本文将主要介绍javascript对象、类和继承的基础知识,最后讲述了使用“this”的一些经验。
2. 关于javascript对象
1). js对象,即为一组属性和方法的集合,可以动态的添加或删除属性/方法
ex:
//通过对象直接量创建对象
var o = {a:123, b:'abc', f:function(){...}};
//添加属性
o.c = [1,2,3];
//添加方法
o.g = function(){...};
//删除属性/方法
delete o.a;
oc = undefinde; |
以上,通过“.”访问的属性/方法,均可以通过‘[]’访问
比如我们可以这么调用jquery对象的hide()方法:
$(‘#xxx’)['hide']();
我们甚至可以这么使用:
var xh = $(‘#xxx’)['hide'];
然后将xh作为参数传给其他方法,然后再需要的时候使用 xh() 来执行这个方法
2). 获取对象的类型
typeof
任何对象使用typeof操作符返回均是“object”(Date和数组的typeof也是“object”)
那么我们如何知道对象的类型哪?
constructor属性
每一个对象都有一个constructor属性用类记录当前对象的构造函数
ex:
var a = [];
var b = typeof a; //b = "object"
var c = a.constructor === Array; //true 注意,这里的Array不是字符串 |
instanceof操作符
每个对象都会有一个内部的属性_proto_,这个属性对js开发人员不可见,只在虚拟机内部使用。每当创建一个对象的时候,这个对象的_proto_就会被赋值为这个对象的构造函数的prototype,这样对象的_proto_属性和构造函数的prototype引用相同的对象,并且一旦对象创建完成,_proto_属性就不会改变。 这样通过对象的_proto_属性,以及_proto_所引用的对象的_proto_属性,就构成了一个_proto_链,及鼎鼎大名的“原型链”。
如:o instanceof c;
在上面的语句执行过程中,虚拟机会把c.prototype和o的_proto_链上的节点逐个进行比较,如果找到相等的节点,则返回true,否则返回false。
ex:
var o = new C();//假设C继承自A
o instanceof C;//true
a instanceof A;//true
a instanceof Object;//true |
3). 对象的属性
每当javascript读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向类原型的prototype属性,在prototype中查找具有给定名字的属性。如果在prototype中找到了这个属性,则返回该属性的值。虽然可以通过对象实例读取保存在原型中的值,但却不能通过对象实例重写prototype中的值。如果在实例中添加一个与prototype中属性同名的属性,则该属性会屏蔽原型中的那个属性。添加的同名属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性设置为null,也只会在实例中设置这个属性,而不会恢复其指向prototype的链接。不过,使用delete操作符则可以完全删除实例属性,从而让我们可以重新重新访问prototype中的属性。
3. 类
JavaScritpt没有专门的Class机制,但是我们可以function传创建构造函数,继而通过构造函数创建对象
ex1:
function O1(){
//添加属性
this.a = 123;
//添加方法
this.f1 = function(){
...
};
}
var ot = new O1();
alert(ot.a);//123 |
这里,我们就得到了O1的构造函数,另外,我们也可以利用prototype扩展类属性和方法
ex2:
function O2(){
}
O2.prototype.a = 1234;
O2.prototype.f1 = function(){
alert(this.a);
};
var ot = new O2();
ot.f1();//1234 |
前面已经提到,prototype是类原型的属性对象,O2的实例化对象ot,在访问ot.f1是,会首先从ot的属性查找f1的定义,如果找不到,就会到O2的prototype内查找。
在实际使用效果上,ex1和ex2基本是相同的,它们的区别是:每new一个O1,就同时创建一份属性和方法,内存中就会多一份O1的属性和方法;而通过prototype扩展的O2的属性和方法,无论O2被实例化几次,在内存中只会有一份,所以,我们推荐使用ex2.的方法来创建类。
但是,通过ex2.的方法创建类,代码有些散,另外,通常,我们希望某些类属性是在各个实现里是不同的
ex3:
function O3(){
this.a = 234;
}
O3.prototype = {
f1 : function(){
alert(this.a);
},
//使用这种方式完全覆盖了prototype,对象的constructor属性会变成Object,这一步,是将其修改为O3
constructor : O3
};
var ot = new O3();
ot.f1();//234 |
通过ex3.定义的类,在类方法里面通过this参数访问类属性/方法,但这些属性在类方法的外面,通过ot.xx都可以访问,那么,如何来定义私有变量和方法哪?
另外,这种ex3.的定义方式,构造函数和prototype仍然被分成了两部分,还可以进一步优化吗?
ex4:
var O4 = function(){
//构造函数
funciton O(){
//public
this.a = 567;
}
//private
var b = 234;
function pf(){
return b;
}
//public
O.prototype = {
f1 : function(){
alert(b);
},
f2 : function(){
return b;
},
constructor : O4
};
//返回类O给变量O4
return O;
}();//定义一个立即执行的方法
var ot = new O4();
ot.f1();//234 |
到这里,就和java书写class的方式差不多了吧,让我们来总结一下:
a. 在构造函数中添加类属性,在prototype添加类方法
b. 将类代码放在一个立即执行的function中,形成一个单独的命名空间,只对完暴露构造函数,可以在里面模拟private的属性和方法
c. 记得重新定义prototype.constructor,否则用到的时候可能会出错(其实大部分时候用不着它)
此外,我们还可以通过下面这种方式书写prototype来封装私有属性和方法
ex5:
var O5 = function(){
//构造函数
funciton O(){
//public
this.a = 567;
}
//通过立即执行的function返回prototype对象
O.prototype = function(){
//属性和方法
var b = 234;
var c = 2345;
function f1(){
//...
}
function f2(){
//...
}
//通过return返回给prototype便是public,其他的private
return {
c : c,
f1 : f1
}
}();
return O;
}(); |
如本节刚开时所述,“ JavaScritpt没有专门的Class机制”,我们只能使用构造函数和原型链来模拟类的实现,有众多的方法可以写出一个类,我们需要根据实际的需要来确定类的写法。

~~~未完待续