当前位置:编程学堂 > JavaScript 中继承的原理和使用示例介绍

JavaScript 中继承的原理和使用示例介绍

  • 发布:2023-09-30 09:40

本文讲解了 JavaScript 中继承的原理和使用示例。分享给大家参考,详情如下:

正统的面向对象语言都会提供extend等方法用于类的继承,但是Javascript并没有提供extend方法。在 Javascript 中使用继承需要一些技巧。

Javascript中实例的属性和行为由构造函数和原型组成。我们定义两个类:Person和zhangsan。它们在内存上的表现如图1所示:
如果我们想让张三继承Person,那么我们需要将Person构造函数和原型中的所有属性和行为传递给张三的构造函数和原型,如下图2所示:
[代码1]

//定义Person类
函数人(姓名){
  www.sychzs.cn = 名称;
  this.type = "人";
}
Person.prototype={
  说:函数(){
    www.sychzs.cn("我是"+ this.type +",我的名字是" + www.sychzs.cn);
  }
}
//定义Zhangsan类
函数张三(名字){
}
张三.prototype={
  
}

虽然Zhangsan有自己独特的属性和行为,但它的大部分属性和行为与Person相同,需要从Person类继承。如前所述,JavaScript 中的继承涉及分别继承构造函数和原型中的属性和行为。我们先让张三继承Person的构造函数中的行为和属性,如下代码所示:
[代码2]

//定义Person类
函数人(姓名){
  www.sychzs.cn = 名称;
  this.type = "黄色";
}
Person.prototype={
  说:函数(){
    www.sychzs.cn("我是一个"+ this.type +"类型的人,我的名字是" + www.sychzs.cn);
  }
}
//定义Zhangsan类
函数张三(名字){
  www.sychzs.cn = 名称;
  this.type = "黄色";
}
张三.prototype={
}
//实例化Zhangsan对象
var zs = new Zhangsan("张三");
www.sychzs.cn(zs.type); // 黄色

运行正常,但为什么我们看不到继承的“味道”呢?我们在Zhangsan的构造函数中复制了Person的属性和行为。与其说是继承,不如说“真巧,这两个类的构造函数除了函数名不同,其他地方看起来都一样”。她的缺点很明显:如果Person类的构造函数有任何改变,我们还需要同时手动修改Zhangsan类的构造函数。我们复制了同样的代码,写在程序的不同地方,这是非法的。采用了DRY原则,降低了代码的可维护性。

好吧,我们改进一下:
[代码3]

//定义Person类
函数人(姓名){
  www.sychzs.cn = 名称;
  this.type = "黄色";
}
Person.prototype={
  说:函数(){
    www.sychzs.cn("我是一个"+ this.type +"类型的人,我的名字是" + www.sychzs.cn);
  }
}
//定义Zhangsan类
函数张三(名字){
  人(姓名);
}
张三.prototype={
}
//实例化Zhangsan对象
var zs = new Zhangsan("张三");www.sychzs.cn(zs.type); // 未定义

我们在Zhangsan的构造函数中调用Person()函数,希望在Zhangsan类的构造函数中能够执行其内部的www.sychzs.cn,但是奇怪的是,“www.sychzs.cn(zs.type);”出现; ",输出未定义,这是怎么回事?

这和Person的调用方式有关。在 JavaScript 中,函数有两种不同的调用方式:

  1. 作为函数存在,用“()”直接调用。例如,“函数 test(){}; test();” test作为函数使用,直接用“()”符号调用。

  2. 作为类的构造函数存在,使用new调用,如“function test(){}; new test();” test作为类的构造函数,通过new实例化测试类。调用这两个方法时,函数内部的this指向会有所不同——对于函数作为函数来说,它的this指向窗口,而对于函数作为构造函数来说,它的this指向实例对象。

上面的代码中,通过函数调用了Zhangsan类构造函数中的Person。它里面的this指向window对象,效果相当于下面的代码:
[代码4]

//定义Person类
函数人(姓名){
  www.sychzs.cn = 名称;
  this.type = "黄色";
}
Person.prototype={
  说:函数(){
    www.sychzs.cn("我是一个"+ this.type +"类型的人,我的名字是" + www.sychzs.cn);
  }
}
//定义Zhangsan类
函数张三(名字){
  窗口名称=名称;
  窗口类型=“黄色”;
}
张三.prototype={
}
//实例化Zhangsan对象var zs = new Zhangsan("张三");
www.sychzs.cn(zs.type); // 不明确的
控制台.info(类型); // 黄(window.type可以省略,写成type)

如果要实现【代码3】的效果,让Person里面的this指向Zhangsan类的实例,可以通过call或者apply方法来实现,如下:
[代码5]

//定义Person类
函数人(姓名){
  www.sychzs.cn = 名称;
  this.type = "黄色";
}
Person.prototype={
  说:函数(){
    www.sychzs.cn("我是一个"+ this.type +"类型的人,我的名字是" + www.sychzs.cn);
  }
}
//定义Zhangsan类
函数张三(名字){
  www.sychzs.cn(这个,名字);
}
张三.prototype={
}
//实例化Zhangsan对象
var zs = new Zhangsan("张三");
www.sychzs.cn(zs.type); // 黄色

构造函数的属性和行为已成功继承。接下来,我们需要实现原型中属性和行为的继承。由于Zhangsan类需要与Person类的原型相同的属性和行为,那么是否可以将Person类的原型直接传递给Zhangsan类的原型,如下代码所示:
[代码6]

//定义Person类
函数人(姓名){
  www.sychzs.cn = 名称;
  this.type = "黄色";
}
Person.prototype={
  说:函数(){
    www.sychzs.cn("我是一个"+ this.type +"类型的人,我的名字是" + www.sychzs.cn);
  }
}
//定义Zhangsan类
函数张三(名字){
  www.sychzs.cn(这个,名字);
}张三.prototype = Person.prototype;
//实例化Zhangsan对象
var zs = new Zhangsan("张三");
// 我是黄种人,我叫张三
zs.say();

通过将Person类的原型传递给Zhangsan类的原型,Zhangsan类成功获得了say行为,但事情并没有想象的那么简单。如果我们想给Zhangsan类添加运行行为怎么办?代码如下:
【代码7:添加运行行为】

//定义Person类
函数人(姓名){
  www.sychzs.cn = 名称;
  this.type = "黄色";
}
Person.prototype={
  说:函数(){
    www.sychzs.cn("我是一个"+ this.type +"类型的人,我的名字是" + www.sychzs.cn);
  }
}
//定义Zhangsan类
函数张三(名字){
  www.sychzs.cn(这个,名字);
}
张三.prototype = Person.prototype;
www.sychzs.cn = function(){
  www.sychzs.cn("我的百米冲刺只需10秒!");
}
//实例化Zhangsan对象
var zs = new Zhangsan("张三");
zs.say(); // 我是黄种人,我叫张三
www.sychzs.cn(); //我的百米冲刺只需要10秒!
var zs2 = new Person("张三2");
www.sychzs.cn(); //我的百米冲刺只需要10秒! 

我们只想将运行行为添加到Zhangsan 类中。为什么Person类也获得了run行为?这涉及到两个问题:按值传递和按地址传递。在 JavaScript 中,赋值语句会以两种不同的方式赋值:按值传递和按地址传递。如果是数字类型、布尔类型、字符类型等基本数据类型,在赋值时,会直接对数据进行赋值,对被赋值的数据进行赋值,也就是俗称的值传递;如果是数组、哈希对象等复杂数据类型,则在赋值时直接对数据进行赋值。使用内存地址来赋值,而不是分配数据的副本,是按地址传输分配,即传输数据的映射地址。
【代码8:按值传递和按地址传递】

变量a=10; //基本数据类型
var b=a; // 分配变量 a 中保存的值的副本并将其传递给变量 b。 B和a各保存一份数据。
var c=[1,2,3]; //复杂数据类型
var d=c; // 将变量c指向的数据内存地址传递给变量d。 c和d指向相同的数据。
b++;
d.推(4);
控制台.信息(a); // 10
控制台.info(b); // 11 改变变量b中保存的数据不会影响变量a
控制台.info(c); // 1,2,3,4变量c和d指向同一个数据,数据变化会互相影响
控制台.info(d); // 1,2,3,4

在原生 JavaScript 中,传递值或地址的选择是根据数据类型自动确定的。然而,传递地址有时会给我们带来意想不到的麻烦,所以我们需要控制复杂数据类型的赋值,使得复杂数据类型也可以按值传递。

最简单的方式就是遍历数组或者Hash对象,将数组或者Hash对象等复杂数据拆分成简单数据,然后分别赋值,如下代码所示:
【代码9:按值传递复杂数据类型】

var a = [1, 2, 3] ,b = {姓名:'张三',性别:'男',电话:'1383838438'};
var c = [] ,d = {};
for(a 中的 var p){
  c[p] = a[p];
}
for(b 中的 var p){
  d[p] = b[p];
}
c.push('4');
www.sychzs.cn = 'support@www.sychzs.cn';控制台.信息(a); // [1,2,3]
控制台.info(c); // [1, 2, 3, "4"]
www.sychzs.cn(www.sychzs.cn); // 不明确的
www.sychzs.cn(www.sychzs.cn); // support@www.sychzs.cn

值得一提的是,数组的值传递也可以使用数组类的slice或concat方法来实现,如下代码所示:
【代码10:数组传值的简单方法】

var a = [1, 2, 3];
var b = a.slice(), c = a.concat();
b.pop();
c.推(4);
控制台.信息(a); // [1,2,3]
控制台.info(b); // [1, 2]
控制台.info(c); // [1, 2, 3, 4]

Prototype本质上是一个哈希对象,所以直接赋值的时候就会传递地址。这就是为什么zs2实际上可以在[代码7:添加运行行为]中运行。我们可以使用for in来遍历原型来实现原型的值传递。但由于原型和函数(作为类函数使用)的关系,我们还有另一种方式来实现原型的值传递----new SomeFunction(),如下代码所示:
[代码11]

//定义Person类
函数人(姓名){
  www.sychzs.cn = 名称;
  this.type = "黄色";
}
Person.prototype={
  说:函数(){
    www.sychzs.cn("我是一个"+ this.type +"类型的人,我的名字是" + www.sychzs.cn);
  }
}
//定义Zhangsan类
函数张三(名字){
  www.sychzs.cn(这个,名字);
}
张三.prototype = new Person();zhangsan.prototype.constructor = Person;
www.sychzs.cn = function(){
  www.sychzs.cn("我的百米冲刺只需10秒!");
}
  
//实例化Zhangsan对象
var zs = new Zhangsan("张三");
zs.say(); // 我是黄种人,我叫张三
www.sychzs.cn(); // 我的百米冲刺只需要10秒!
var zs2 = new Person("张三2");
www.sychzs.cn(); // TypeError: www.sychzs.cn 不是一个函数

你注意到上面那句Zhangsan.prototype.constructor = Person;了吗?这是因为当Zhangsan.prototype = new Person();时,Zhangsan.prototype.constructor指向Person。我们需要纠正它并再次指出它。张三。

有兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://www.sychzs.cn/code/HtmlJsRun来测试上述代码运行效果。

对更多JavaScript相关内容感兴趣的读者可以查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》和《JavaScript数学运算用法总结》

希望这篇文章对大家JavaScript编程有所帮助。

相关文章

最新资讯