今天要用js实现一些客户端功能,考虑到业务逻辑,使用OO的开发方式会很方便,于是认真查看了相关的几篇文章,有一些心得体会。
首先是定义类。js中定义类是使用function,实例化使用new操作符:
function class1() {
this.a = 'class1';
this.m1 = function() {
alert('class1.m1');
}
}
class1.prototype.m2 = function() {
alert('class1.m2');
}
var c1 = new class1();
在js中,Function和Object是两个最基础的类,js中的任何对象、实例、函数都同时是Function和Object的实例,这可以用instanceof来验证,这一点有特殊的作用,后面的一些魔法全靠这一点支撑。
function有两种用法,一种如上,是常用的形式;另一种是运行时动态创建,如
var add = new Function("x", "y", "return (x+y)");
创建的结果是一个Function对象。这可以解释我之前看到的“奇怪”的用法。如上,this.m1 = function() {...}的用法中,实际上后面的function句自动产生了一个匿名的function对象,然后赋值给m1属性,随即class1就具有了m1方法。之后又用class1.prototype.m2 = function(){...}句定义了m2方法,实际上也是得到了匿名的function对象,然后赋值给m2属性。当然,这两种定义方法的形式是由区别的,后面会讲到。既然得到的是一个对象,可以使用变量指代,当然就可以作为函数调用的参数了,也就可以轻松而自然的实现函数回调了。这比C#之类的语言实现回调更方便自然。
这里再多啰嗦一句,js类在定义的时候可以定义属性,如示例中的a属性。除此之外,实例的属性是可以动态添加的。如果使用c1.b = 'b',则c1自动具有了属性b。这种动态添加的属性是不会扩散到其他实例的,即如果有另一个实例c2,则c2仍然只有属性a而无属性b。
如上示例,任何类(class1)的函数有两种实现方式,一种的内联的方式,即在定义function的时候,将方法写在body中;另一种是使用prototype,可以在任何地方定义。对于类的实例(class1的实例c1),同时拥有这两种方式定义的方法。因为内联定义的优先级高,在实例调用方法时,首先查找内联定义,然后转到prototype的定义中查找。因此,prototype定义的方法会被内联定义覆盖掉。如果真的发生了方法覆盖,要想使用prototype定义的方法,至少有两种方法(以下假设class1中使用同时使用了上述两种形式定义了方法m1)。
第一种是使用delete,首先delete掉m1方法,根据优先级,自然delete掉的是内联的m1方法。什么,不明白为什么还能delete掉方法?首先。js是动态语言;其次,记住方法也只是一个function实例,在class1中只是一个特殊属性而已。
var c1 = new class1();
delete c1.m1; //去掉内联的m1方法
c1.m1(); //调用prototype的m1方法
第二种是使用apply方法。至于apply的使用方法,可以查看js手册。
var c1 = new class1();
class1.prototype.m.apply(c1); //使用prototype中定义的m方法代替c1的m方法
用new实例化的对象仍然同时是Function对象和Object对象,包括这两者的所有实例属性和方法。这里不是很好理解,想想看,我们定义的类同时是Function对象和Object对象的实例,然后实例化后,实例也同时是Function对象和Object对象的实例,有没有一点像父亲和儿子的爸爸是同一个人?呵呵,有点恶心了,还希望高手出来详细剖析一下Function对象和Object对象以及js里面的类型关系。
这里把js和ruby做个比较。js和ruby都有一个默认的全局对象,js中是Global对象,所用定义的全局变量和函数都是Global的成员。从这一点看,js也有一点oo的感觉(至少是形式上):-)。ruby中,同时存在class变量和实例变量,js也是。这种设计是脚本语言的优势,可以在运行时随时改变class的定义,非常灵活和强大。同时两种脚本中的所有类型都不是封闭的,而是开放的,用户可以给语言内置的所有类型添加额外的属性和方法。
另外闲扯一下有趣的话题:js的反射。js中任何对象(function和object)都可以使用如下方式遍历所有成员:
var info = typeof(obj)+'\n';
for(p in obj) {
info += p + '\n';
}
alert(info);
同样使用反射,可以这样调用方法和属性:
alert(c1['a']);
c1['m1']();