




已阅读5页,还剩7页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
JavaScript继承详解(一)时间:2009-07-08 Tag:继承 面向对象与基于对象几乎每个开发人员都有面向对象语言(比如C+、C#、Java)的开发经验。 在传统面向对象的语言中,有两个非常重要的概念 - 类和实例。 类定义了一类事物公共的行为和方法;而实例则是类的一个具体实现。 我们还知道,面向对象编程有三个重要的概念 - 封装、继承和多态。 但是在JavaScript的世界中,所有的这一切特性似乎都不存在。 因为JavaScript本身不是面向对象的语言,而是基于对象的语言。 这里面就有一些有趣的特性,比如JavaScript中所有事物都是对象, 包括字符串、数组、日期、数字,甚至是函数,比如下面这个例子: / 定义一个函数 - add function add(a, b) add.invokeTimes+; return a + b; / 因为函数本身也是对象,这里为函数add定义一个属性,用来记录此函数被调用的次数 add.invokeTimes = 0; add(1 + 1); add(2 + 3); console.log(add.invokeTimes); / 2 模拟JavaScript中类和继承在面向对象的语言中,我们使用类来创建一个自定义对象。然而JavaScript中所有事物都是对象,那么用什么办法来创建自定义对象呢? 这就需要引入另外一个概念 - 原型(prototype),我们可以简单的把prototype看做是一个模版,新创建的自定义对象都是这个模版(prototype)的一个拷贝 (实际上不是拷贝而是链接,只不过这种链接是不可见,给人们的感觉好像是拷贝)。 让我们看一下通过prototype创建自定义对象的一个例子: / 构造函数 function Person(name, sex) = name; this.sex = sex; / 定义Person的原型,原型中的属性可以被自定义对象引用 Ptotype = getName: function() return ; , getSex: function() return this.sex; 这里我们把函数Person称为构造函数,也就是创建自定义对象的函数。可以看出,JavaScript通过构造函数和原型的方式模拟实现了类的功能。 创建自定义对象(实例化类)的代码: var zhang = new Person(ZhangSan, man); console.log(zhang.getName(); / ZhangSan var chun = new Person(ChunHua, woman); console.log(chun.getName(); / ChunHua 当代码var zhang = new Person(ZhangSan, man)执行时,其实内部做了如下几件事情: 创建一个空白对象(new Object())。 拷贝Ptotype中的属性(键值对)到这个空对象中(我们前面提到,内部实现时不是拷贝而是一个隐藏的链接)。 将这个对象通过this关键字传递到构造函数中并执行构造函数。 将这个对象赋值给变量zhang。 为了证明prototype模版并不是被拷贝到实例化的对象中,而是一种链接的方式,请看如下代码: function Person(name, sex) = name; this.sex = sex; Ptotype.age = 20; var zhang = new Person(ZhangSan, man); console.log(zhang.age); / 20 / 覆盖prototype中的age属性 zhang.age = 19; console.log(zhang.age); / 19 delete zhang.age; / 在删除实例属性age后,此属性值又从prototype中获取 console.log(zhang.age); / 20 这种在JavaScript内部实现的隐藏的prototype链接,是JavaScript赖以生存的温润土壤, 也是模拟实现继承的基础。 如何在JavaScript中实现简单的继承? 下面的例子将创建一个雇员类Employee,它从Person继承了原型prototype中的所有属性。 function Employee(name, sex, employeeID) = name; this.sex = sex; this.employeeID = employeeID; / 将Employee的原型指向Person的一个实例 / 因为Person的实例可以调用Person原型中的方法, 所以Employee的实例也可以调用Person原型中的所有属性。 Etotype = new Person(); Etotype.getEmployeeID = function() return this.employeeID; ; var zhang = new Employee(ZhangSan, man, 1234); console.log(zhang.getName(); / ZhangSan 上面关于继承的实现很粗糙,并且存在很多问题: 在创建Employee构造函数和原型(以后简称类)时,就对Person进行了实例化,这是不合适的。 Employee的构造函数没法调用父类Person的构造函数,导致在Employee构造函数中对name和sex属性的重复赋值。 Employee中的函数会覆盖Person中的同名函数,没有重载的机制(和上一条是一个类型的问题)。 创建JavaScript类的语法过于零散,不如C#/Java中的语法优雅。 实现中有constructor属性的指向错误,这个会在第二篇文章中讨论。 我们会在第三章完善这个例子。 JavaScript继承的实现正因为JavaScript本身没有完整的类和继承的实现,并且我们也看到通过手工实现的方式存在很多问题, 因此对于这个富有挑战性的任务网上已经有很多实现了: Douglas Crockford - Prototypal Inheritance in JavaScript Douglas Crockford - Classical Inheritance in JavaScript John Resig - Simple JavaScript Inheritance Dean Edwards - A Base Class for JavaScript Inheritance Prototype Mootools Extjs 这个系列的文章将会逐一深入分析这些实现,最终达到对JavaScript中如何实现类和继承有一个深入的了解。 下一章我们将会介绍在类实现中的相关知识,比如this、constructor、prototype等。JavaScript继承详解(二)时间:2009-07-08 Tag:继承 这一章我们将会重点介绍JavaScript中几个重要的属性(this、constructor、prototype), 这些属性对于我们理解如何实现JavaScript中的类和继承起着至关重要的作用。 thisthis表示当前对象,如果在全局作用范围内使用this,则指代当前页面对象windows; 如果在函数中使用this,则this指代什么是根据运行时此函数在什么对象上被调用。 我们还可以使用apply和call两个全局方法来改变函数中this的具体指向。 先看一个在全局作用范围内使用this的例子: console.log(this = window); / true console.log(window.alert = this.alert); / true console.log(this.parseInt(021, 10); / 10 函数中的this是在运行时决定的,而不是函数定义时,如下: / 定义一个全局函数 function foo() console.log(this.fruit); / 定义一个全局变量,等价于window.fruit = apple; var fruit = apple; / 此时函数foo中this指向window对象 / 这种调用方式和window.foo();是完全等价的 foo(); / apple / 自定义一个对象,并将此对象的属性foo指向全局函数foo var pack = fruit: orange, foo: foo ; / 此时函数foo中this指向window.pack对象 pack.foo(); / orange 全局函数apply和call可以用来改变函数中this的指向,如下: / 定义一个全局函数 function foo() console.log(this.fruit); / 定义一个全局变量 var fruit = apple; / 自定义一个对象 var pack = fruit: orange ; / 等价于window.foo(); foo.apply(window); / apple / 此时foo中的this = pack foo.apply(pack); / orange 注:apply和call两个函数的作用相同,唯一的区别是两个函数的参数定义不同。因为在JavaScript中函数也是对象,所以我们可以看到如下有趣的例子: / 定义一个全局函数 function foo() if (this = window) console.log(this is window.); / 函数foo也是对象,所以可以定义foo的属性boo为一个函数 foo.boo = function() if (this = foo) console.log(this is foo.); else if (this = window) console.log(this is window.); ; / 等价于window.foo(); foo(); / this is window. / 可以看到函数中this的指向调用函数的对象 foo.boo(); / this is foo. / 使用apply改变函数中this的指向 foo.boo.apply(window); / this is window. prototype我们已经在第一章中使用prototype模拟类和继承的实现。 prototype本质上还是一个JavaScript对象。 并且每个函数都有一个默认的prototype属性。 如果这个函数被用在创建自定义对象的场景中,我们称这个函数为构造函数。 比如下面一个简单的场景: / 构造函数 function Person(name) = name; / 定义Person的原型,原型中的属性可以被自定义对象引用 Ptotype = getName: function() return ; var zhang = new Person(ZhangSan); console.log(zhang.getName(); / ZhangSan 作为类比,我们考虑下JavaScript中的数据类型 - 字符串(String)、数字(Number)、数组(Array)、对象(Object)、日期(Date)等。 我们有理由相信,在JavaScript内部这些类型都是作为构造函数来实现的,比如:/ 定义数组的构造函数,作为JavaScript的一种预定义类型 function Array() / . / 初始化数组的实例 var arr1 = new Array(1, 56, 34, 12); / 但是,我们更倾向于如下的语法定义: var arr2 = 1, 56, 34, 12; 同时对数组操作的很多方法(比如concat、join、push)应该也是在prototype属性中定义的。 实际上,JavaScript所有的固有数据类型都具有只读的prototype属性(这是可以理解的:因为如果修改了这些类型的prototype属性,则哪些预定义的方法就消失了), 但是我们可以向其中添加自己的扩展方法。/ 向JavaScript固有类型Array扩展一个获取最小值的方法 Atotype.min = function() var min = this0; for (var i = 1; i this.length; i+) if (thisi min) min = thisi; return min; ; / 在任意Array的实例上调用min方法 console.log(1, 56, 34, 12.min(); / 1 注意:这里有一个陷阱,向Array的原型中添加扩展方法后,当使用for-in循环数组时,这个扩展方法也会被循环出来。 下面的代码说明这一点(假设已经向Array的原型中扩展了min方法): var arr = 1, 56, 34, 12; var total = 0; for (var i in arr) total += parseInt(arri, 10); console.log(total); / NaN 解决方法也很简单:var arr = 1, 56, 34, 12; var total = 0; for (var i in arr) if (arr.hasOwnProperty(i) total += parseInt(arri, 10); console.log(total); / 103 constructor constructor始终指向创建当前对象的构造函数。比如下面例子: / 等价于 var foo = new Array(1, 56, 34, 12); var arr = 1, 56, 34, 12; console.log(arr.constructor = Array); / true / 等价于 var foo = new Function(); var Foo = function() ; console.log(Foo.constructor = Function); / true / 由构造函数实例化一个obj对象 var obj = new Foo(); console.log(obj.constructor = Foo); / true / 将上面两段代码合起来,就得到下面的结论 console.log(obj.constructor.constructor = Function); / true 但是当constructor遇到prototype时,有趣的事情就发生了。 我们知道每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。如下例所示: function Person(name) = name; ; Ptotype.getName = function() return ; ; var p = new Person(ZhangSan); console.log(p.constructor = Person); / true console.log(Ptotype.constructor = Person); / true / 将上两行代码合并就得到如下结果 console.log(totype.constructor = Person); / true 当时当我们重新定义函数的prototype时(注意:和上例的区别,这里不是修改而是覆盖), constructor的行为就有点奇怪了,如下示例:function Person(name) = name; ; Ptotype = getName: function() return ; ; var p = new Person(ZhangSan); console.log(p.constructor = Person); / false console.log(Ptotype.constructor = Person); / false console.log(totype.constructor = Person); / false 为什么呢? 原来是因为覆盖Ptotype时,等价于进行如下代码操作:Ptotype = new Object( getName: function() return ; ); 而constructor始终指向创建自身的构造函数,所以此时Ptotype.constructor = Object,即是:function Person(name) = name; ; Ptotype = getName: function() return ; ; var p = new Person(ZhangSan); console.log(p.constructor = Object); / true console.log(Ptotype.constructor = Object); / true console.log(totype.constructor = Object); / true 怎么修正这种问题呢?方法也很简单,重新覆盖Ptotype.constructor即可:function Person(name) = name; ; Ptotype = new Object( getName: function() return ; ); Ptotype.constructor = Person; var p = new Person(ZhangSan); console.log(p.constructor = Person); / true console.log(Ptotype.constructor = Person); / true console.log(totype.constructor = Person); / true下一章我们将会对第一章提到的Person-Employee类和继承的实现进行完善。JavaScript中的对象、函数和继承前一段时间在看Extjs的源代码,起初打算从他的Widget开始看起,因为想借鉴一下 并应用到自己的代码中,但是看了一段时间发现很难阅读进去,主要因为对他的整体如何组织对象和事件的方式不是很清楚。所以还是耐下性子从最基础的开始看 起,什么是Extjs的基础,可以说是他的Ext.extend函数,因为之后的各个wdiget的扩展都用它来实现的。但是起初发现他的内容并不是那么 容易就可以看明白的,有的时候觉得自己看明白了,但是再多问自己一个为什么,可能又答不上来了。 这个时候正好碰到一本很不错的讲JS原理书,周爱民的Javascript语 言精粹与编程实践,如获至宝,赶紧买来阅读。在阅读的过程中又在我面前蹭蹭蹭,出现了几本很不错的js书籍,在这里也向大家推荐一下,一本是有 Yahoo的js大牛Douglas Crockeord(后文称之为老道)所著,由淘宝UED的小马和秦歌翻译的 Javascript语言精粹,一本是Javascript设计模式。最早我读的这两本书都是英文版的,所以还被第二本书中的一些观点给误导了, 但是幸好看到第二本书的译者很负责任,在批注中都已经修正过来了。最后一本,其实不是书,而是ECMAScript Language Specification,俗称ECMA-262,有两个值得看的版本,一个是第三版,一个是第五版;现在的大部分js实现都是基于第三版的规范,但 是在有些问题上,第五版描述的更加清晰一些。废话少说,进入正题。 1、 Javascript中的对象 JavaScript可以说是一个基于对象的编程语言,为什么说是基于对象而不是面向对 象,因为JavaScript自身只实现了封装,而没有实现继承和多态。既然他是基于对象的,那么我们就来说说js中的对象。有人说js中所有的都是对 象,这句话不完全正确。正确的一方是他强调了对象在js中的重要性,对象在js中无处不在,包括可以构造对象的函数本身也是对象。但是另一方面,js中也 有一些简单的数据类型,包括数字、字符串和布尔值、null值和undefined值,而这些不是对象。那为什么这些类型的值不是对象呢,毕竟他们也有方 法。那让我们来看一下,JavaScript中对于对象的定义,有两种定义。(1)JavaScript中的对象是可变的键控集合(keyed collections) (此定义来自老道的那本书的第三章)(2)JavaScript 中的对象是无序(unordered)的属性集合,这些属性可以含有简单的数据类型、对象、函数;保存在一个对象属性中的函数也被称为这个对象的方法。 (来自ECMA-262 的4.3.3)(注:这里所说的属性是可以在js脚本中创建和访问的(我们称之为显性属性),不包括系统为对象自动分配的内部属性)那为什么那个简单的数据类型不是对象呢,主要是因为这些数据类型的值中拥有的方法是不可变的,而一个对象的属性是应当可以被改变的。2、 对象中的原型链proto JavaScript中的每个对象创建的时候系统都会自动为其分配一个原型属性 proto,用来连接到他的原型对象。在JavaScript中就是通过每个对象中的proto来实现对象的继承关系的。但是对象的 proto属性在JavaScript是不能访问和修改的,他是作为一个内部的属性存在的,而且是在对象被创建的同时由系统自动设定的。当访问一个对象的某一属性,如果这个属性在此对象中不存在,就在他的proto所指的原型对象的属性中寻找,如果找到则返回,否则继续沿着proto链一直找下去,直到proto的连接为null的时候停止。 3、 函数也是对象 JavaScript中的函数本身就是一个对象(所以我们经常称之为函数对象),而且可以 说他是js中最重要的对象。之所以称之为最重要的对象,一方面他可以扮演像其他语言中的函数同样的角色,可以被调用,可以被传入参数;另一方面他还被作为 对象的构造器(constructor)来使用,可以结合new操作符来创建对象。既然函数就是对象,所以必然含有对象拥有的全部性质,包括对象在创建时设定的原型链proto属性。让我们来看看函数对象和普通对象有什么区别。我们前面说过,对象就是无序的属性集合,那么 函数的属性和普通对象的属性有什么不同呢。根据ECMA-262中的13.2节所述,在函数对象创建时,系统会默认为其创建两个属性call和 constructor,当函数对象被当做一个普通函数调用的时候(例如myFunc()),“()”操作符指明函数对象的call属性 就被执行,当他被当做一个构造器被调用的时候(例如new myConst(),他的constructor属性就被执行,cosntructor的执行过程我们将在下一节中介绍。除此之外,当 一个函数被创建时,系统会默认的再为其创建一个显示属性prototype,并为其赋值为totype = constructor:this 具体内容可以参加老道的那本书的第五章。这个函数对象的prototype属性也是为了 js把函数当做构造器来实现继承是准备的,但是这个属性是可以在js脚本中访问和修改的。在这里要强调的一点是,大家一定要区分对象中的 proto属性和函数对象中的prototype属性,我在刚开始学习的时候就是因为没有很好的区分这两个东西,走了很多的弯路。4、 对象的创建 在js中有两种创建对象的方法,一种是通过字面量来实现,如var Person = “first_name”:liang,last_name:yang另一种方法是通过构造器来创建var my = new Person(liang,yang);其实第一种方式的创建过程相当于调用Object构造器来实现,如下。var Person = new Object();Person.first_name = liang;Person.last_name = yang所以我们可以把js中所有对象的创建都合并到使用构造器来实现,下面我么来详细说明构造器创建对象的过程:第一步,先创建一个空的对象(既没有任何属性),并将这个对象的proto指向这个构造器函数的prototype属性对象第二步,将这个空的对象作为this指针传给构造器函数并执行第三步,如果上面的函数返回一个对象,则返回这个对象,否则返回第一步创建的对象第四步,把函数当做一个类来使用由上面的步骤我们可以看出,一般来说函数对象的prototype指向的是一个普通对象, 而不是一个函数对象,这个普通对象中的属在由此函数构造器创建的对象中也可以访问。由此可以如此设计我们的代码,假设一个函数就可以代表一个类,这个构造 器函数生成的对象就是这个类的实例对象,那么实例对象中应有的属性和方法应该放在这个构造器函数的prototype中,这个类的静态方法就可以直接放到 这个函数作为对象的属性中,最后这个函数体就是我们平时在面向对象语言中所说的构造函数(在这里我们要区分连个词“构造函数”和“构造器函数”,所谓构造 函数是指普通的面向对象语言中的类的构造函数,而构造器函数是指javascript中的一个函数被当做构造器使用)。在第3节我们说过每个函数的prototype对象中总是含有一个constructor 属性,这个属性就是连接到我们的这个函数本身。再加之,有这个函数生成的每个对象的proto属性都是指向构造器函数的prototype对象, 所以通过proto链,每个由构造器函数生成的对象,都有一个construc
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 公司文体活动月策划方案
- 公司着装大赛策划方案
- 公司新年嘉年华活动方案
- 2025年职业健康安全管理师考试试卷及答案
- 2025年新能源与可再生能源知识考核考试卷及答案
- 2025年数字信号处理技术考试卷及答案
- 2025年天文学与空间科学考试题及答案
- 2025年人机交互设计师职业资格考试试题及答案
- 2025年企业管理咨询师职业资格考试试卷及答案
- 2025年交通工程与智能交通管理的专业知识考试试卷及答案
- 国开《学前儿童语言教育活动指导》形考1-4试题及答案
- 海康2023综合安防工程师认证试题答案HCA
- 浊度仪使用说明书
- GB/T 14404-2011剪板机精度
- GB/T 14294-1993组合式空调机组
- GA 1517-2018金银珠宝营业场所安全防范要求
- 提高痰留取成功率PDCA课件
- 组合导航与融合导航解析课件
- 伊金霍洛旗事业编招聘考试《行测》历年真题汇总及答案解析精选V
- 深基坑支护工程验收表
- 颅脑CT影像课件
评论
0/150
提交评论