1、Javascript 中的对象、函数和继承前一段时间在看 Extjs 的源代码,起初打算从他的 Widget 开始看起,因为想借鉴一下并应用到自己的代码中,但是看了一段时间发现很难阅读进去,主要因为对他的整体如何组织对象和事件的方式不是很清楚。所以还是耐下性子从最基础的开始看起,什么是 Extjs的基础,可以说是他的 Ext.extend 函数,因为之后的各个 wdiget 的扩展都用它来实现的。但是起初发现他的内容并不是那么容易就可以看明白的,有的时候觉得自己看明白了,但是再多问自己一个为什么,可能又答不上来了。这个时候正好碰到一本很不错的讲 JS 原理书,周爱民的Javascript 语言
2、精粹与编程实践 ,如获至宝,赶紧买来阅读。在阅读的过程中又在我面前蹭蹭蹭,出现了几本很不错的 js 书籍,在这里也向大家推荐一下,一本是有 Yahoo 的 js 大牛 Douglas Crockeord(后文称之为老道)所著,由淘宝 UED 的小马和秦歌翻译的 Javascript 语言精粹 ,一本是Javascript 设计模式 。最早我读的这两本书都是英文版的,所以还被第二本书中的一些观点给误导了,但是幸好看到第二本书的译者很负责任,在批注中都已经修正过来了。最后一本,其实不是书,而是ECMAScript Language Specification ,俗称 ECMA-262,有两个值得看
3、的版本,一个是第三版,一个是第五版;现在的大部分 js 实现都是基于第三版的规范,但是在有些问题上,第五版描述的更加清晰一些。废话少说,进入正题。1、 Javascript 中的对象JavaScript 可以说是一个基于对象的编程语言,为什么说是基于对象而不是面向对象,因为 JavaScript 自身只实现了封装,而没有实现继承和多态。既然他是基于对象的,那么我们就来说说 js 中的对象。有人说 js 中所有的都是对象,这句话不完全正确。正确的一方是他强调了对象在 js 中的重要性,对象在 js 中无处不在,包括可以构造对象的函数本身也是对象。但是另一方面,js 中也有一些简单的数据类型,包括
4、数字、字符串和布尔值、null值和 undefined 值,而这些不是对象。那为什么这些类型的值不是对象呢,毕竟他们也有方法。那让我们来看一下,JavaScript 中对于对象的定义,有两种定义。(1)JavaScript 中的对象是可变的键控集合(keyed collections) (此定义来自老道的那本书的第三章)(2)JavaScript 中的对象是无序(unordered)的属性集合,这些属性可以含有简单的数据类型、对象、函数;保存在一个对象属性中的函数也被称为这个对象的方法。 (来自ECMA-262 的 4.3.3)( 注:这里所说的属性是可以在 js 脚本中创建和访问的(我们称之
5、为显性属性) ,不包括系统为对象自动分配的内部属性)那为什么那个简单的数据类型不是对象呢,主要是因为这些数据类型的值中拥有的方法是不可变的,而一个对象的属性是应当可以被改变的。2、 对象中的原型链protoJavaScript 中的每个对象创建的时候系统都会自动为其分配一个原型属性proto,用来连接到他的原型对象。在 JavaScript 中就是通过每个对象中的proto来实现对象的继承关系的。但是对象的proto属性在 JavaScript 是不能访问和修改的,他是作为一个内部的属性存在的,而且是在对象被创建的同时由系统自动设定的。当访问一个对象的某一属性,如果这个属性在此对象中不存在,就
6、在他的proto所指的原型对象的属性中寻找,如果找到则返回,否则继续沿着proto链一直找下去,直到proto的连接为 null 的时候停止。3、 函数也是对象JavaScript 中的函数本身就是一个对象(所以我们经常称之为函数对象) ,而且可以说他是 js 中最重要的对象。之所以称之为最重要的对象,一方面他可以扮演像其他语言中的函数同样的角色,可以被调用,可以被传入参数;另一方面他还被作为对象的构造器(constructor)来使用,可以结合 new 操作符来创建对象。既然函数就是对象,所以必然含有对象拥有的全部性质,包括对象在创建时设定的原型链proto属性。让我们来看看函数对象和普通对
7、象有什么区别。我们前面说过,对象就是无序的属性集合,那么函数的属性和普通对象的属性有什么不同呢。根据 ECMA-262 中的 13.2 节所述,在函数对象创建时,系统会默认为其创建两个属性call和constructor,当函数对象被当做一个普通函数调用的时候(例如 myFunc()) , “() ”操作符指明函数对象的call属性就被执行,当他被当做一个构造器被调用的时候(例如 new myConst(),他的constructor属性就被执行,cosntructor 的执行过程我们将在下一节中介绍。除此之外,当一个函数被创建时,系统会默认的再为其创建一个显示属性 prototype,并为其
8、赋值为this.prototype = constructor:this具体内容可以参加老道的那本书的第五章。这个函数对象的 prototype 属性也是为了 js 把函数当做构造器来实现继承是准备的,但是这个属性是可以在 js 脚本中访问和修改的。在这里要强调的一点是,大家一定要区分对象中的proto属性和函数对象中的 prototype 属性,我在刚开始学习的时候就是因为没有很好的区分这两个东西,走了很多的弯路:(4、 对象的创建在 js 中有两种创建对象的方法,一种是通过字面量来实现,如var Person = “first_name”:liang,last_name:yang另一种方法
9、是通过构造器来创建var my = new Person(liang,yang);其实第一种方式的创建过程相当于调用 Object 构造器来实现,如下。var Person = new Object();Person.first_name = liang;Person.last_name = yang所以我们可以把 js 中所有对象的创建都合并到使用构造器来实现,下面我么来详细说明构造器创建对象的过程:第一步,先创建一个空的对象(既没有任何属性) ,并将这个对象的proto指向这个构造器函数的 prototype 属性对象第二步,将这个空的对象作为 this 指针传给构造器函数并执行第三步,如
10、果上面的函数返回一个对象,则返回这个对象,否则返回第一步创建的对象第四步,把函数当做一个类来使用由上面的步骤我们可以看出,一般来说函数对象的 prototype 指向的是一个普通对象,而不是一个函数对象,这个普通对象中的属在由此函数构造器创建的对象中也可以访问。由此可以如此设计我们的代码,假设一个函数就可以代表一个类,这个构造器函数生成的对象就是这个类的实例对象,那么实例对象中应有的属性和方法应该放在这个构造器函数的 prototype 中,这个类的静态方法就可以直接放到这个函数作为对象的属性中,最后这个函数体就是我们平时在面向对象语言中所说的构造函数(在这里我们要区分连个词“构造函数”和“构
11、造器函数” ,所谓构造函数是指普通的面向对象语言中的类的构造函数,而构造器函数是指 javascript 中的一个函数被当做构造器使用) 。在第 3 节我们说过每个函数的 prototype 对象中总是含有一个 constructor 属性,这个属性就是连接到我们的这个函数本身。再加之,有这个函数生成的每个对象的proto属性都是指向构造器函数的 prototype 对象,所以通过proto 链,每个由构造器函数生成的对象,都有一个 constructor 属性,指向生成他的构造器函数,因此我们可以通过这个属性来判断这个对象是有哪个构造器函数生成的。5、 函数继承(类继承)说了这么多,终于到了
12、我们可以在 javascript 中讨论继承的时候了,我们先来考虑一下要实现类的继承我们都要做些什么,假设我们要从 superClass 继承到子类 subClass为了使得由 subClass 生成的对象中能够访问 superClass 生成的对象中的属性,那么可以使subClass.prototype 为一个 superClass 构造函数生成的对象。subclass.prototye = new superClass();但是问题来了,根据我们在第 4 节说的 new superClass()不仅复制了 superClass.prototype 中的所有方法,而且还运行了 superCl
13、ass()这个函数,这个函数起到的作用是类中的构造函数。我们知道应该在子类的构造函数中调用父类的构造函数来实现初始化。为此我们可以创建一个构造函数为空的,但是原型和 superClass 原型一致的函数,并使 subClass.prototype 指向这个函数生成的对象。var F = function() ;F.prototype = superClass.prototype;subClass.protptype = new F();这样我们就可以再不调用构造函数的同时完成属性复制的工作。但是还有一个问题,那就是我们修改了 subClass 的 prototype 属性,从而也就删除了其中的
14、 constructor 属性,这样我们就无法知道他是哪个构造器函数生成的对象了。我们可以再次给他赋值subClass.prototype.constructor = subClass;这样复制属性的问题就迎刃而解了。但是新的问题又出现了,在 subClass 中我们无法知道他的父类是哪个构造器函数,所以就无法在构造函数中调用父类的构造函数,为此我们可以为 subClass 添加一个属性,指明他的父类subClass.superClass = superClass.prototype;这样我么就可以在子类的构造函数中使用 subClass.superClass.constructor 来访问父类的构造函数了。最后我们把以上的思路写成一个函数myPro.extend = function (subClass,superClass) var F = function() ;F.prototype = superClass.prototype;subClass.protptype = new F();subClass.prototype.constructor = subClass;subClass.superClass = superClass.prototype;superClass.prototype.constructor = superClass;大功告成。