《Javascript继承机制原理》由会员分享,可在线阅读,更多相关《Javascript继承机制原理(9页珍藏版)》请在金锄头文库上搜索。
1、Javascript 继承机制继承机制 一、讲个故事吧 澄清在先,Java 和 Javascript 是雷锋和雷峰塔的关系。Javascript 原名 Mocha,当时还叫 做 LiveScript,创造者是 Brendan Eich,现任 Mozilla 公司首席技术官。 1994 年,历史上第一个比较成熟的网络浏览器Navigator0.9 版诞生在网景公司(Netsc ape),极为轰动。 但是,Navigator0.9 只能用来浏览,不具备与访问者交互的能力,比如,用户提交一个数 据表单,如果表单为空,浏览器是无法判断的,只能直接提交给服务器端,再把空值的错误 返回,让用户重新填写,这
2、样显然是低效率和浪费资源的。 这个时候,对于正处于技术革新最前沿的 Netscape,开发一种实用的客户端脚本语言来处 理这些问题变得必要起来,于是,这个任务落到了工程师 Brendan Eich 身上。他觉得吧, 木必要设计得很复杂,只要能搞定一些简单操作就够了,比如判断用户有没有填写表单。 1994 年正是面向对象编程(object-oriented programming)的兴盛时代,C+是最流行的 语言,而 Java 语言的 1.0 版即将于次年推出,Brendan Eich 难免受其影响, 他想将 Java script 里面所有的数据类型看做是对象(object),这一点与 Jav
3、a 非常相似。但是,他马上 就遇到了一个难题,到底要不要设计” 继承”机制呢? 二、继承的演变 1、采用 new 关键字生成实例 处理表单验证这样简单功能脚本语言显然是不需要”继承”机制的, 然而如果 Javascript 里面 都是对象,就需要有一种办法来把所有对象联系起 来。最后,Brendan Eich 还是设计了” 继承”。只是,他并没有引入”类”(class)的概念,因为一旦有了”类”,Javascript 就是一种 完整的面向对象编程语言 了, 这好像有点太正式了,与设计初衷也远了,同时增加了初学者的入门难度。 参照到 C+和 Java 语言都使用 new 命令来生成实例: C+这
4、样写: ClassName *object = new ClassName(param); Java 这样写: Foo foo = new Foo(); 那么,也可以把 new 命令引入了 Javascript,用来从原型对象生成一个实例对象。但是,J avascript 中没有”类”的话,怎样表示原型对象呢? 依然是参照 C+和 Java 使用 new 命令时,都会调用”类”的构造函数(constructor)。Bre ndan Eich 简化了设计,在 Javascript 语言中,new 命令后面跟的是构造函数,不再是类。 我们举个例子来说,现在有一个叫做 WD 构造函数,表示前端开发(
5、web-developper)对 象的原型。 function WD(skill) this.skill = skill; 对这个构造函数使用 new 关键字,就会生成一个前端开发对象的实例。 var WD1 = new WD(html); console.log(WD1.skill); / html 复制代码 在构造函数中的 this 关键字,它其实代表的是新创建的实例对象。 2、new 出来对象的缺陷 采用 new 关键字,用构造函数生成实例对象无法共享属性和方法。 比如,在 WD 对象的构造函数中,设置一个实例对象的共有属性 skill。 function WD(skill) this.
6、skill = skill; this.sex = 男; 然后,生成两个实例对象: var WD1 = new WD(html); var WD2 = new WD(css); 这两个对象的 skill 属性是独立的,修改其中一个,不会影响到另一个。 WD1.skill= Javascript; console.log(WD2.skill);/“css”,不受 WD1 的影响 每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资 源浪费。 3、引入、引入 prototype 属性属性 为了实现属性和方法的共享,Brendan Eich 决定为构造函数设置一个 pro
7、totype 属性。 这个属性包含一个对象(以下简称”prototype 对象”),所有实例对象需要共享的属性和方 法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。 实例对象一旦创建,将自动引用 prototype 对象的属性和方法。也就是说,实例对象的属性 和方法,分成两种,一种是本地的,另一种是引用的。 还是以 WD 构造函数为例,现在用 prototype 属性进行改写: function WD(skill) this.skill = skill; WD.prototype = sex : 男 ; var WD1 = new WD(html); var WD2 =
8、 new WD(css); console.log(WD1.sex); / 男 console.log(WD2.sex); / 男 现在,sex 属性放在 prototype 对象里,是两个实例对象共享的。只要修改了 prototype 对 象,就会同时影响到两个实例对象。 WD.prototype.sex = 女; console.log(WD1.sex); /女 console.log(WD2.sex); / 女 由于所有的实例对象共享同一个 prototype 对象,那么从外界看起来,prototype 对象就好 像是实例对象的原型,而实例对象则好像”继承”了 prototype 对象一
9、样。这就是 Javascript 继承机制的设计思想。 三、构造函数如何实现继承 现在有一个”MED”对象的构造函数(MED:Marketing Experience Design,营销体验设计) function MED() this.aim = “营销体验设计“; 依然是”WD”对象的构造函数, function WD(skill,sex) this.skill = skill; this.sex = sex; 怎样才能使”WD”继承”MED”呢? 1. apply 绑定构造函数实现 最简单的方法,大概就是使用 call 或 apply 方法,将父对象的构造函数绑定在子对象上,也 就是在子
10、对象构造函数中加一行: function WD(skill,sex) MED.apply(this, arguments); this.skill = skill; this.sex = sex; var WD1 = new WD(“Html“,“男“); console.log(WD1.aim); / “营销体验设计“ 2. prototype 模式实现 我们通常的做法是使用 prototype 属性。 如果”WD”的 prototype 对象, 指向一个 MED 的实 例,那么所有”WD”的实例,就能继承 MED 了。 WD.prototype = new MED();/我们将 WD 的
11、prototype 对象指向一个 MED 的实例。 WD.prototype.constructor = WD; var WD1 = new WD(“Html“,“男“); console.log(WD1.aim); / 营销体验设计 这句 WD.prototype = new MED(); 相当于完全删除了 prototype 对象原先的值, 然后赋予一个新值。 那么第二行又是什么意思 呢? WD.prototype.constructor = WD; 原来, 任何一个 prototype 对象都有一个 constructor 属性, 指向它的构造函数。 也就是说, WD.prototype
12、 这个对象的 constructor 属性,是指向 WD 的。 我们在前一步已经删除了这个 prototype 对象原来的值,所以新的 prototype 对象没有 con structor 属性, 需要我们手动加上去, 否则后面的”继承链”会出问题。 这就是第二行的意思。 注意,这是很重要的一点,编程时务必要遵守,下文都遵循这一点,即如果替换了 prototy pe 对象, o.prototype = ; 那么,下一步必然是为新的 prototype 对象加上 constructor 属性,并将这个属性指回原来 的构造函数。 o.prototype.constructor = o; 3. 从
13、 prototype 直接继承实现 由于 MED 对象中,不变的属性都可以直接写入 MED.prototype。所以,我们也可以让 WD ()跳过 MED(),直接继承 MED.prototype。 现在,我们先将 MED 对象改写: function MED() MED.prototype.skill = “MED“; 然后,将 WD 的 prototype 对象指向 MED 的 prototype 对象,这样就完成了继承。 WD.prototype = MED.prototype; WD.prototype.constructor = WD; var WD1 = new WD(“Html“
14、,“男“); console.log(WD1.skill); / MED 与前一种方法相比,这样做的优点是效率比较高(不用执行和建立 MED 的实例了),比较 省内存。缺点是 WD.prototype 和 MED.prototype 现在指向了同一个对象,那么任何对 W D.prototype 的修改,都会反映到 MED.prototype。 所以,上面这一段代码其实是有问题的。请看第二行 WD.prototype.constructor = WD; 这一句实际上把 MED.prototype 对象的 constructor 属性也改掉了! console.log(MED.prototype.
15、constructor); / WD 4. 利用一个空对象作为中介来实现 由于”直接继承 prototype”存在上述的缺点,所以可以利用一个空对象作为中介。 var F = function(); F.prototype = MED.prototype; WD.prototype = new F(); WD.prototype.constructor = WD; F 是空对象,所以几乎不占内存。这时,修改 WD 的 prototype 对象,就不会影响到 MED 的 prototype 对象。 console.log(MED.prototype.constructor); / MED 5.利
16、用 prototype 模式的封装函数 我们将上面的方法,封装成一个函数,便于使用。 function extend(Child, Parent) var F = function(); F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; 使用的时候,方法如下 extend(WD,MED); var WD1 = new WD(“Html“,“男“); console.log(WD1.aim); / 营销体验设计 这个 extend 函数就是 YUI 库如何实现继承的方法。 6. 拷贝继承实现 上面是采用 prototype 方式来实现继承。其实既然子对象会拥有父对象的属性和方法,我们 直接采用”拷贝”方法也可以达到效果。简单说,如果把父对象的所有属性和方法,拷贝进子 对象,不也能够实