JavaScript中继承原理与用法实例入门


Posted in Javascript onMay 09, 2020

本文实例讲述了JavaScript中继承原理与用法。分享给大家供大家参考,具体如下:

正统的面相对象的语言都会提供extend之类的方法用于出来类的继承,但Javascript并不提供extend方法,在Javascript中使用继承需要用点技巧。

Javascript中的实例的属性和行为是由构造函数和原型两部分组成的,我们定义两个类:Person和zhangsan,它们在内存中的表现如下图1:
JavaScript中继承原理与用法实例入门
如果想让Zhangsan继承Person,那么我们需要把Person构造函数和原型中的属性和行为全部传给Zhangsan的构造函数和原型,如下图2所示:
JavaScript中继承原理与用法实例入门

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有两种不同的调用方法:

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

  2. 作为类的构造函数存在,使用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.3water.com/code/HtmlJsRun测试上述代码运行效果。

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
js获取控件位置以及不同浏览器中的差别介绍
Aug 08 Javascript
JavaScript实现常用二级省市级联下拉列表的方法
Mar 25 Javascript
基于javascript实现彩票随机数生成(升级版)
Apr 17 Javascript
JS动态改变浏览器标题的方法
Apr 06 Javascript
JS代码实现根据时间变换页面背景效果
Jun 16 Javascript
Spring MVC中Ajax实现二级联动的简单实例
Jul 06 Javascript
JavaScript重定向URL参数的两种方法小结
Oct 19 Javascript
js 获取html5的data属性实现方法
Jul 28 Javascript
jQuery实现的两种简单弹窗效果示例
Apr 18 jQuery
vue2中,根据list的id进入对应的详情页并修改title方法
Aug 24 Javascript
阿望教你用vue写扫雷小游戏
Jan 20 Javascript
全面解析js中的原型,原型对象,原型链
Jan 25 Javascript
jQuery三组基本动画与自定义动画操作实例总结
May 09 #jQuery
JavaScript进阶(四)原型与原型链用法实例分析
May 09 #Javascript
JavaScript进阶(三)闭包原理与用法详解
May 09 #Javascript
JavaScript进阶(二)词法作用域与作用域链实例分析
May 09 #Javascript
JavaScript进阶(一)变量声明提升实例分析
May 09 #Javascript
JavaScript面向对象核心知识与概念归纳整理
May 09 #Javascript
Vue列表如何实现滚动到指定位置样式改变效果
May 09 #Javascript
You might like
如何解决phpmyadmin导入数据库文件最大限制2048KB
2015/10/09 PHP
php设计模式之组合模式实例详解【星际争霸游戏案例】
2020/03/27 PHP
JS去除字符串的空格增强版(可以去除中间的空格)
2009/08/26 Javascript
jQuery MD5加密实现代码
2010/03/15 Javascript
js用Date对象处理时间实现思路及代码
2013/01/31 Javascript
jquery 文本上下无缝滚动,鼠标放上去就停止 小例子
2013/06/05 Javascript
用js读、写、删除Cookie代码分享及详细注释说明
2014/06/05 Javascript
javascript限制用户只能输汉字中文的方法
2014/11/20 Javascript
后台获取ZTREE选中节点的方法
2015/02/12 Javascript
js Canvas实现圆形时钟教程
2016/09/19 Javascript
JavaScript实现打印星型金字塔功能实例分析
2017/09/27 Javascript
用Webpack构建Vue项目的实践
2017/11/07 Javascript
mongoose设置unique不生效问题的解决及如何移除unique的限制
2017/11/07 Javascript
Vue代码分割懒加载的实现方法
2017/11/23 Javascript
vue-cli构建项目下使用微信分享功能
2018/05/28 Javascript
微信小程序出现wx.getLocation再次授权问题的解决方法分析
2019/01/16 Javascript
vue router导航守卫(router.beforeEach())的使用详解
2019/04/19 Javascript
layui动态加载多表头的实例
2019/09/05 Javascript
vue改变循环遍历后的数据实例
2019/11/07 Javascript
解决vue一个页面中复用同一个echarts组件的问题
2020/07/19 Javascript
[09:37]DOTA2卡尔工作室 英雄介绍圣堂刺客篇
2013/06/13 DOTA
Python调用C++程序的方法详解
2017/01/24 Python
jupyter notebook引用from pyecharts.charts import Bar运行报错
2020/04/23 Python
Python 实现遥感影像波段组合的示例代码
2019/08/04 Python
Jupyter Notebook的连接密码 token查询方式
2020/04/21 Python
如何基于Python Matplotlib实现网格动画
2020/07/20 Python
python import 上级目录的导入
2020/11/03 Python
实例教程 HTML5 Canvas 超炫酷烟花绽放动画实现代码
2014/11/05 HTML / CSS
html5实现滑块功能之type="range"属性
2020/02/18 HTML / CSS
水果超市创业计划书
2014/01/27 职场文书
彩色的翅膀教学反思
2014/04/25 职场文书
一年级小学生评语大全
2014/12/25 职场文书
上课迟到检讨书范文
2015/05/06 职场文书
单位更名证明
2015/06/18 职场文书
Python面向对象编程之类的概念
2021/11/01 Python
Python必备技巧之函数的使用详解
2022/04/04 Python