本文实例讲述了JavaScript中继承原理与用法。分享给大家供大家参考,具体如下:
正统的面相对象的语言都会提供extend之类的方法用于出来类的继承,但Javascript并不提供extend方法,在Javascript中使用继承需要用点技巧。
Javascript中的实例的属性和行为是由构造函数和原型两部分组成的,我们定义两个类:Person和zhangsan,它们在内存中的表现如下图1:
如果想让Zhangsan继承Person,那么我们需要把Person构造函数和原型中的属性和行为全部传给Zhangsan的构造函数和原型,如下图2所示:
Are you Ok?了解了继承的思路后,那么我们一步步完成Person和Zhangsan的继承功能。首先,我们需要定义Person类,如下代码:
[代码1]
// 定义Person类 function Person (name){ this.name = name; this.type = "人"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +",我的名字叫" + this.name); } } //定义Zhangsan类 function Zhangsan (name){ } Zhangsan.prototype={ }
Zhangsan虽然有自己特有的属性和行为,但它大部分属性和行为和Person相同,需要继承自Person类。如前所述,JavaScript中继承是要分别继承构造函数和原型中的属性和行为的。我们先让Zhangsan继承Person的构造函数中的行为和属性,如下代码:
[代码2]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } //定义Zhangsan类 function Zhangsan (name){ this.name = name; this.type = "黄"; } Zhangsan.prototype={ } //实例化Zhangsan对象 var zs = new Zhangsan("张三"); console.info(zs.type); // 黄
运行正常,但我们怎么没看到继承的“味道”呢?我们在Zhangsan的构造函数中将Person的属性和行为复制了一份,与其说是继承不如说是“真巧,这两个类的构造函数除了函数名不同,其他地方都长得一样”。她的缺点很明显:如果Person类的构造函数有任何变动,我们也需要手动的同步修改Zhangsan类的构造函数,同样一份代码,我们复制了一份写在了程序中 的不同地方,这违法了DRY原则,降低了代码的可维护性。
好了,让我们来改进它:
[代码3]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } // 定义Zhangsan类 function Zhangsan (name){ Person(name); } Zhangsan.prototype={ } // 实例化Zhangsan对象 var zs = new Zhangsan("张三"); console.info(zs.type); // undefined
我们在Zhangsan的构造函数里调用Person()函数,希望它内部的ths.xxx可以在Zhangsan类的构造函数里执行一遍,但奇怪的是,出现“console.info(zs.type);”时,输出的是undefined,这是怎么回事呢?
这和Person的调用方式有关。在JavaScript中,function有两种不同的调用方法:
-
作为函数存在,直接用“()”调用,例如“function test(){}; test();”test被用作函数,直接被“()”符号调用。
-
作为类的构造函数存在,使用new调用,例如“function test(){}; new test();”test作为类的构造函数,通过new进行test类的实例化。这两种方法的调用,function内部的this指向会有所不同---作为函数的function,其this指向的是window,而作为构造函数的function,其this指向的实例对象。
上面代码中,Zhangsan类构造函数中的Person是通过函数方式调用的,它内部的this指向的是window对象,起效果等同于如下代码:
[代码4]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } // 定义Zhangsan类 function Zhangsan (name){ window.name = name; window.type = "黄"; } Zhangsan.prototype={ } // 实例化Zhangsan对象 var zs = new Zhangsan("张三"); console.info(zs.type); // undefined console.info(type); // 黄 (window.type可以省略写成type)
如果想达到[代码3]的效果,让Person内部this指向Zhangsan类的实例,可以通过call或apply方法实现,如下:
[代码5]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } // 定义Zhangsan类 function Zhangsan (name){ Person.call(this,name); } Zhangsan.prototype={ } // 实例化Zhangsan对象 var zs = new Zhangsan("张三"); console.info(zs.type); // 黄
构造函数的属性和行为已经成功实现了继承,接下来我们要实现原型中的属性和行为的继承。既然Zhangsan类需要和Person类原型中同样的属性和行为,那么能否将Person类的原型直接传给Zhangsan类的原型,如下代码:
[代码6]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } // 定义Zhangsan类 function Zhangsan (name){ Person.call(this,name); } Zhangsan.prototype = Person.prototype; // 实例化Zhangsan对象 var zs = new Zhangsan("张三"); // 我是一个黄种人,我的名字叫张三 zs.say();
通过Person类的原型传给Zhangsan类的原型,Zhangsan类成功获得了say行为,但事情并不像想象中的那么简单,如果我们要给Zhangsan类添加run行为呢?如下代码:
[代码7:添加run行为]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } // 定义Zhangsan类 function Zhangsan (name){ Person.call(this,name); } Zhangsan.prototype = Person.prototype; Zhangsan.prototype.run = function(){ console.info("我100米短跑只要10秒!"); } // 实例化Zhangsan对象 var zs = new Zhangsan("张三"); zs.say(); // 我是一个黄种人,我的名字叫张三 zs.run(); //我100米短跑只要10秒! var zs2 = new Person("张三2"); zs2.run(); //我100米短跑只要10秒!
我们只想给Zhangsan类添加run行为,为什么Person类也获得了run行为了呢?这涉及传值和传址的两个问题----在JavaScript中,赋值语句会用传值和传地址两种不同的方式进行赋值,如果是数值型、不尔型、字符型等基本数据类型,在进行赋值时会将数据直接赋值一份,将赋值的那一份数据进行赋值,也就是通常所说的传值;如果是数组、hash对象等复杂数据类型,在进行赋值时会直接用内存地址赋值,而不是将数据赋值一份,这就是传址赋值,就是传数据的映射地址。
[代码8:传值与传址]
var a=10; // 基本数据类型 var b=a; // 将变量a保存的值赋值一份,传给变量b,b和a各保存一份数据 var c=[1,2,3]; // 复杂数据类型 var d=c; // 将变量c指向的数据内存地址传给变量d,c和d指向同一份数据 b++; d.push(4); console.info(a); // 10 console.info(b); // 11 变量b保存的数据更改不会影响到变量a console.info(c); // 1,2,3,4 变量c和d指向同一份数据,数据更改会相互影响 console.info(d); // 1,2,3,4
在原生JavaScript中,选择传值还是传地址是根据数据类型来自动判断的,但传地址有时候会给我们带来意想不到的麻烦,所以我们需要对复杂数据类型的赋值进行控制,让复杂数据类型也可以进行传值。
最简单的做法是遍历数组或者Hash对象,将数组或者Hash对象这种复杂的数据拆分成一个个简单数据,然后分别赋值,如下面代码:
[代码9:对复杂数据类型进行传值]
var a = [1, 2, 3] ,b = {name:'张三',sex:'男',tel:'1383838438'}; var c = [] ,d = {}; for(var p in a){ c[p] = a[p]; } for(var p in b){ d[p] = b[p]; } c.push('4'); d.email = 'ibing@outlook.com'; console.info(a); // [1, 2, 3] console.info(c); // [1, 2, 3, "4"] console.info(b.email); // undefined console.info(d.email); // ibing@outlook.com
值得一提的是,对于数组的传值还可以使用数组类的slice或者concat方法实现,如下面代码:
[代码10:数组传值的简单方法]
var a = [1, 2, 3]; var b = a.slice(), c = a.concat(); b.pop(); c.push(4); console.info(a); // [1, 2, 3] console.info(b); // [1, 2] console.info(c); // [1, 2, 3, 4]
prototype本质上也是一个hash对象,所以直接用它赋值时会进行传址,这也是为什么[代码7:添加润行为]中,zs2居然会run的原因。我们可以用for in来遍历prototype,从而实现prototype的传值。但因为prototype和function(用做类的function)的关系,我们还有另外一种方法实现prototype的传值----new SomeFunction(),如下面代码:
[代码11]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } // 定义Zhangsan类 function Zhangsan (name){ Person.call(this,name); } Zhangsan.prototype = new Person(); Zhangsan.prototype.constructor = Person; Zhangsan.prototype.run = function(){ console.info("我100米短跑只要10秒!"); } // 实例化Zhangsan对象 var zs = new Zhangsan("张三"); zs.say(); // 我是一个黄种人,我的名字叫张三 zs.run(); // 我100米短跑只要10秒! var zs2 = new Person("张三2"); zs2.run(); // TypeError: zs2.run is not a function
您是否注意到上面这句Zhangsan.prototype.constructor = Person;,这是因为Zhangsan.prototype = new Person();时,Zhangsan.prototype.constructor指向了Person,我们需要将它纠正,重新指向Zhangsan。
感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]