博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

谈谈C#程序员从本质上掌握javascript的面向对象特性

Posted on 2011-04-06 14:53  栖山  阅读(2126)  评论(12编辑  收藏  举报
javascirpt是和C#有着相当不同的语言,C#是面向类的静态编程语言(这里暂不考虑C# 4.0添加的动态特性),而javascript是面向对象的函数式编程语言。
虽然C#和javascript差别很大, 但是我们还有可能利用已经掌握的技能来加速对新技能的掌握。

 一、javascirpt面向对象特性。
 C#是面象类的。它通过“2步法”来获得一个可以操做的对象。首先通过class关键字来定义一个类,然后通过new关键字获得一个对象。这包含了以下几层意思。
1,首先我们需要定义一个对象的结构,这就是类
2,我们通过这个结构,赋予状态来获得一个对象(或者叫实例对象)
3,在使用new关键字的时候,系统会调用一个构造函数,来给对象赋予一个初始状态。
总结: C#需要先决定对象的结构,然后再决定对象的状态, 并且在决定了对象的状态后,不允许再改变对象的结构。所谓的“2步法”就是先结构,后状态。且有了状态后,结构不允许再发生变动。

 javascript可以采用“1步法”,也就是说决定结构和状态的操作在同一个步骤内完成。
  例如 cat.color="while", 这个操作在给对象添加了一个叫color(颜色)的结构的同时,赋予了"white"(白色)这个属性。而上面这个操作是在程序的任何地方使用的。
 由于使用“1步法",类的存在就没有了意义。只要我们有了一个对象,我们就可以随时随地给对象同时添加结构和状态,或者叫属性和赋值。(如果硬要套上类的概念的话,那么javasciprt里面的类是不停地在变化的)
 在C#中new关键字是产生一个对象唯一手段,在javascript中new关键字只是产生一个对象的2个方式中的一种。
第一种产生一个对象的方式是使用“{}"操作符。
 baby={};
 singer={name:"张学友“,sex: "男“}
第1个对象没有什么结构,第2个对象有名有性。以后我们还可以添加其他属性。
到这里你也许会问,有了那么方便的产生对象的“{}"操作符,为什么还需要 new关键字呢?
利用new关键字,可以调用一个函数(即构造函数,或者叫constructor)来完成构建对象的动作,从而获得“2步法“的效果。
比如
function Person(name,sex) {
  this.name=name;
  this.sex =sex;
}
 singer=new Person("张学友",”男")

这里虽然看上去和c#的写法很象,似乎采用了“2步法“,Person函数定义了结构,new语句赋予了状态。但是这个“2步法“和C#的“2步法“有所区别,因为它的"2步“不是很严格的“2步”。

如果我们把构造里面的语句注释掉,换成:
function Person(name,sex) {
 // this.name=name;
 // this.sex =sex;
}
singer=new Persion()
singer.name="张学有"
singer.sex="男“

最后得到的singer对象和前例得到的singer对象完全相同。
我们可以自由的把定义结构的第一步,移出构造函数,仍然使用“1步法“来获得等价的效果。
由于javascript可以随时通过“1步法”同时添加结构和状态, 因此使用“{}"还是使用“new"来产生新对象,可以按照实际需求来自由选择了(很free吧)。

二,javascript的继承。
由于javascript的对象构建采用了“1步法“,同时添加结构和状态, 所以javascript的继承也是“同步”的,同时继承结构和状态。
在C#中,是通过类来继承,并且只继承结构,不继承状态,这个继承通过父类来实现。而在javascript中需要同时继承结构和状态,因此是通过对象来继承了。
因此C#是面向类的语言,javascript是面向对象的语言(同时继承结构和状态)。
一个比较标准的说法是javascript通过prototype来实现继承。其实所谓prototype就是父对象(和C#中的父类相对应)。子对象继承父对象的所有结构和状态(前提是自己对应的结构和状态不存在)
这里有一个学习上的难点,就是一个对象并不直接通过prototype属性连接到它的父对象,而是通过constructor属性的prototype对象,间接的连接到它的父对象。
代码表示如下:
function Monkey() {
}
function Person() {
}
singer=new Person();
Person.prototype=new Monkey()

Person.prototype.constructor= Person

//  assert_true singer.constructor == Person   也就是说singer对象有一个constructor属性指向了Person,而Person再通过prototype属性指向 new Money()对象

喜欢刨根问底的人一定会问,为什么一个子对象不能直接有一个prototype属性来直接指向父对象呢?
其实这是可以的,例如在Firefox中,每一个对象都有一个__proto__属性指向它的父对象。(但是这个属性是不建议用户使用的)
那为什么其他浏览器不这么做呢?而要通过constructor(构造函数)来间接的连接到父对象呢。其实这是有充分理由的,是一个精心的设计。
如果每个对象可以直接连接到父亲对象,那么会出现一个非常混乱的情况,如果我们随时在父对象通过“1步法”同时添加属性和状态,随时会被子对象继承。如果出错了,那么我们需要检查整个程序来找到造成麻烦的那句话。这显然不是个好的设计。

那么该怎么办呢?
通过上面的例子,我们知道通过构造函数,我们可以获得2步法的效果,因此我们把父对象连接到子对象的构造函数的prototype属性上,并且定一个“君子协定”,凡是需要添加属性,都放到构造函数中,这样我们只要检查构造函数,就可以知道子对象继承了哪些结构(注意,状态的继承不受限制,因为构造函数是2步法中的第一步,决定结构)。

由于要给prototype赋值一定要借助于构造函数,因此就“不动声色的,诱导性的“,让你很“自然地“使用“2步法“来完成继承工作,让代码组织性更好。但是由于是“君子协定”,你如果在构造函数外添加结构,也完全做得到,不过也许会在某些情况下造成一些大小麻烦。
有朋友也许会问,本来都是对象很好理解,扯出来函数,反而迷糊了。其实函数就是对象。如果对这个事实感觉很难理解。我们可以试试采用下面的说法。
世上本来没有函数,都是对象。不过有一种对象比较特殊,它有一个非常特殊的属性“()”,使用这个属性,需要省略“."这个操作符,例如是“Person()"而不是“Person.()",这个属性不象 singer.name属性直接输出一个状态,比如“张学友“,而是能够通过它内部隐藏的一段代码,完成一个计算,并且在“()”中可以添加一些参数,从而对它内部代码的运行施加影响。由于它是一个对象,所以其他对象能够出现的地方,它也能出现。最后我们把这种对象称为函数。而它也让函数式编程成为了可能。

至此,你有没有发觉javascript的面向对象是那么的优雅,灵活和简洁呢?那个初看起来很难懂的prototype的设计,其实蕴含了设计者的良苦用心。
而javascript的函数式编程更将这种美发挥到了极致,简直是惊为天人。(这个另文再说)。

另外通过学习javascript,既可以对C#的静态特性有更好的理解。也有助于对c# 4.0添加的动态特性的进一步学习(虽然我对c#的动态特性并不看好,主要原因是同时包含静态和动态特性反而让程序员难以取舍。这也是Java对动态特性一直比较抗拒的原因)。