javascript面向对象三大特征之继承实例详解


Posted in Javascript onJuly 24, 2019

本文实例讲述了javascript面向对象三大特征之继承。分享给大家供大家参考,具体如下:

继承

在JavaScript中的继承的实质就是子代可以拥有父代公开的一些属性和方法,在js编程时,我们一般将相同的属性放到父类中,然后在子类定义自己独特的属性,这样的好处是减少代码重复。继承是面向对象的基础,是代码重用的一种重要机制。

——此文整理自 《jQuery 开发从入门到精通》 ,这是本好书,讲的很详细,建议购买阅读。

继承的作用

实现继承的主要作用是:

① 子类实例可以共享超类属性和方法。
② 子类可以覆盖和扩展超类属性和方法。

继承的分类

在JavaScript中是不支持类的概念,使用构造器机制来实现类的特性。
JavaScript中类的继承不止一种,主要包括:类继承(构造函数继承),原型继承,实例继承,复制继承,克隆继承,混合继承,多重继承等。

类继承

类继承也叫构造函数继承,其表现形式是在子类中执行父类的构造函数。实现本质:比如把一个构造函数A的方法赋值为另一个构造函数B,然后调用该方法,使构造函数A在构造函数B内部执行,这是构造函数B就拥有了构造函数A中定义的属性和方法。这就是B类继承A类。示例如下:

function A(x){
  this.x = x;
  this.say = function() {
   console.log(this.x + ' say');
  }
}
function B(x,y) {
  this.m = A; // 把构造函数A作为一个普通函数引给临时方法m()
  this.m(x); // 把当前参数作为值传给构造函数A,并且执行
  delete this.m; // 清除临时方法
  this.y = y;
  this.call = function(){
   console.log(this.y);
  }
}
// 测试类继承
var a = new A(1);
var b = new B(2,3);
a.say(); // 1 say
b.say(); // 2 say
b.call(); // 3

上面的实现方式很巧妙对吧,但是这种设计方式太随意,缺乏严密性。严禁的设计模式应该考虑到各种可能存在的情况和类继承关系中的互相耦合性。所以一般我们尽可能把子类自身的方法放在原型里去实现。下面这种创建方式会更好:

function A(x){
 this.x = x;
 this.say = function() {
  console.log(this.x + ' say');
 }
}
function B(x,y){
 this.y = y;
 A.call(this,x); // 在构造函数B内调用超类A,实现绑定,用call来实现借用
}
B.prototype = new A(); // 设置原型链,建立继承关系
B.prototype.constructor = B; // 恢复B的原型对象的构造函数为B,如果不操作就会指向A
B.prototype.gety = function(){ // 给类B添加新的方法
 return this.y;
}
// 测试 类继承
var a = new A('A');
var b = new B('Bx','By');
a.say(); // A say
b.say(); // Bx say
console.log(b.gety()); // By
console.log(B.prototype.__proto__ === A.prototype); // true;
console.log(b.constructor === B); // true 这里也可以把B中的 B.prototype.constructor = B; 去除,结果为false

在js中实现类继承,需要设置3点:

① 在子类构造函数结构体内,使用函数call()调用父类构造函数,把子类的参数传递给调用函数如上面的例子:A.call(this,x) 这样子类可以继承父类的所有属性和方法。
② 在子类和父类之间建立原型链,如上例:B.prototype = new A() 为了实现类的继承必须保证他们原型链上的上下级关系。即设置子类的prototype 属性指向父类的一个实例即可。
③ 恢复子类原型对象的构造函数, 如上例:B.prototype.constructor = B

在类继承中,call() 和 apply() 方法被频繁使用,它们之间的功能和用法都是相同的,唯一区别就是第2个参数类型不同。如果深入,请看前面的文章:https://3water.com/article/166093.htm

此处需要提一下,就是类的构造函数中的成员,一般称之为本地成员,而类的原型成员就是类的原型中的成员,此处我们只考虑原型中的成员继承。本地成员继承可以用call 和 apply。下面我们来看下类继承的原型成员的继承封装函数:

在函数体内,首先定义一个空函数F(),用来实现功能中转,把它的原型指向父类的原型,把空函数的实例传递给子类的原型,这样就避免了直接实例化父类引发的内存问题,因为实际开发中,父类可能很大,实例化后,会占用很大一部分内存。

// 定义一个继承函数
function extend(Sub,Sup){ // 有两个入口参数,Sub是子类,Sup是父类
 var F = function(){}; // 建立一个临时的构造函数
 F.prototype = Sup.prototype; // 把临时构造函数的原型指向父类的原型
 Sub.prototype = new F(); // 实例化临时类,此处相当于把子类的原型指向父类的实例
 Sub.prototype.constructor = Sub; // 恢复子类的构造函数
 Sub.sup = Sup.prototype; // 在子类中定义一个本地属性存储超类原型,可避免子类和超类耦合
 if(Sup.prototype.constructor === Object.prototype.constructor) { // 检测超类构造器是否为Object构造器
  Sup.prototype.constructor = Sup;
 }
}
// 下面定义两个类用来测试上面的继承函数
function A(x,y) {
 this.x = x;
 this.y = y;
}
A.prototype.add = function() {
 return (this.x-0) + (this.y-0); // 此处-0 的目的是确保字符串类型可转成数值型
}
A.prototype.minus = function() {
 return this.x - this.y;
}
function B(x,y) {
 A.apply(this,[x,y]);
}
// 开始实现类继承中的原型成员继承
extend(B,A);
// 为了不与A类中的代码耦合可以单独为B定义一个同名的add
B.prototype.add = function() {
 return B.sup.add.call(this); // 避免代码耦合
}
// 测试继承
var b = new B(1,2);
console.log(b.minus()); // -1
console.log(b.add()); // 3

原型继承

原型继承是js中最通用的继承方式,不用实例化对象,通过直接定义对象,并被其他对象引用,这样形成的一种继承关系,其中引用对象被称为原型对象。

function A(){
 this.color = 'red';
}
function B(){}
function C(){}
B.prototype = new A();
C.prototype = new B();
// 测试原型继承
var c = new C();
console.log(c.color); // red

原型继承显得很简单,不需要每次构造都调用父类的构造函数,也不需要通过复制属性的方式就能快速实现继承。但它也存在一些缺点:

① 每个类型只有一个原型,所以不支持多重继承
② 不能很好的支持多参数或动态参数的父类,显得不够灵活。
③ 占用内存多,每次继承都需要实例化一个父类,这样会存在内存占用过多的问题。

实例继承

实例化类可创建新的实例对象,这个实例对象将继承类的所有特征。

function Arr() {
 var a = new Array();
 return a;
}
var arr = new Arr();
arr[0] = 1;
arr[1] = 2;
console.log(arr); // [1,2]
console.log(Array.isArray(arr)); // true
console.log(arr instanceof Array); // true
console.log(arr instanceof Arr); // false

通过构造函数中完成对类的实例化操作,然后返回实例对象,这就是实例继承的由来。实例继承可实现对所有对象的继承,包括自定义类,核心对象和DOM对象。但是也有一些缺点

① 实例继承无法传递动态参数,它是封闭在函数体内试下你,不能通过call和apply来实现动态传参。
② 实例继承只返回一个对象,不支持多重继承
③ 实例继承对象它仍然保持与原对象的实例关系,无法实现继承对象是封装类的实例。如:console.log(arr instanceof Arr); // false

复制继承

复制继承就是利用for in 遍历对象成员,逐一复制给另一个对象。通过这种方式来实现继承。

function A(){
 this.color = 'red';
}
A.prototype.say = function() {
 console.log(this.color);
}
var a = new A();
var b = {};
// 开始拷贝
for(var item in a) {
 b[item] = a[item];
}
// 开始测试
console.log(b.color); // red
b.say(); // red.

我们把它封装一下:

Function.prototype.extend = function(obj){
 for(item in obj){
  this.constructor.prototype[item] = obj[item];
 }
}
function A(){
 this.color = 'green';
}
A.prototype.say = function(){
 console.log(this.color);
}
// 测试
var b = function(){};
b.extend(new A());
b.say(); // green

复制继承实际上是通过反射机制复制类对象中的可枚举属性和方法来模拟继承。这种可以实现多继承。但也有缺点:

① 由于是反射机制,不能继承非枚举类型的属性和方法。对于系统核心对象的只读方法和属性也无法继承。
② 执行效率差,这样的结构越庞大,低效就越明显。
③ 如果当前类型包含同名成员,这些成员会被父类的动态复制给覆盖。
④ 多重继承中,复制继承不能清晰描述父类和子类的相关性。
⑤ 在实例化后才能遍历成员,不够灵活,也不支持动态参数
⑥ 复制继承仅仅是简单的引用赋值,如果父类成员包含引用类型,那么也会带来很多副作用,如不安全,容易遭受污染等。

克隆继承

通过对象克隆方式继承,可以避免赋值对象成员带来的低效。
为Function对象扩展一个clone方法。该方法可把参数对象赋值给一个空的构造函数的原型对象,然后返回实例化后的对象,这样该对象就拥有构造哈数包含的所有成员了。

Function.prototype.clone = function(obj){
 function Temp(){};
 Temp.prototype = obj;
 return new Temp();
}
function A(){
 this.color = 'purple';
}
var o = Function.clone(new A());
console.log(o.color); // purple

混合继承

混合继承是把多种继承方式一起使用,发挥各个优势,来实现各种复杂的应用。最常见的就是把类继承和原型继承一起使用。

function A(x,y){
 this.x = x;
 this.y = y;
}
A.prototype.add = function(){
 return (this.x-0) + (this.y-0);
}
function B(x,y){
 A.call(this,x,y);
}
B.prototype = new A();
// 测试
var b = new B(2,1);
console.log(b.x); // 2
console.log(b.add()); // 3

多重继承

继承一般包括单向继承和多向继承,单向继承模式较为简单,每个子类有且仅有一个超类,多重继承是一个比较复杂的继承模式。一个子类可拥有多个超类。JavaScript原型继承不支持多重继承,但可通过混合模式来实现多重继承。下面让类C来继承类A和类B:

function A(x){
 this.x = x;
}
A.prototype.hi = function(){
 console.log('hi');
}
function B(y){
 this.y = y;
}
B.prototype.hello = function(){
 console.log('hello');
}
// 给Function增加extend方法
Function.prototype.extend = function(obj) {
 for(var item in obj) {
  this.constructor.prototype[item] = obj[item];
 }
}
// 在类C内部实现继承
function C(x,y){
 A.call(this,x);
 B.call(this,y);
};
C.extend(new A(1));
C.extend(new B(2));
// 通过复制继承后,C变成了一个对象,不再是构造函数了,可以直接调用
C.hi(); // hi
C.hello(); // hello
console.log(C.x); // 1
console.log(C.y); // 2

一个类继承另一个类会导致他们之间产生耦合,在js中提供多种途径来避免耦合的发生如 掺元类

掺元类是一种比较特殊的类,一般不会被实例化或者调用,定义掺元类的目的只是向其他类提供通用的方法。

// 定义一个掺元类
function F(x,y){
 this.x = x;
 this.y = y;
}
F.prototype = {
 getx:function(){
  return this.x;
 },
 gety:function(){
  return this.y;
 }
}
// 原型拷贝函数
function extend(Sub,Sup){ // Sub 子类 , Sup 掺元类
 for(var item in Sup.prototype){
  if(!Sub.prototype[item]){ // 如果子类没有存在同名成员
   Sub.prototype[item] = Sup.prototype[item]; // 那么复制掺元类成员到子类原型对象中
  }
 }
}
// 定义两个子类 A,B
function A(x,y){
 F.call(this,x,y);
}
function B(x,y){
 F.call(this,x,y);
}
// 调用extend函数实现原型属性,方法的拷贝
extend(A,F);
extend(B,F);
console.log(A.prototype);
// 测试继承结果
var a = new A(2,3);
console.log(a.x); // 2
console.log(a.getx()); // 2
console.log(a.gety()); // 3
var b = new B(1,2);
console.log(b.x); // 1
console.log(b.getx()); // 1
console.log(b.gety()); // 2

通过此种方式把多个子类合并到一个子类中,就实现了多重继承。

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun测试上述代码运行效果。

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

Javascript 相关文章推荐
查看源码的工具 学习jQuery源码不错的工具
Dec 26 Javascript
使用jQuery内容过滤选择器选择元素实例讲解
Apr 18 Javascript
js中的getAttribute方法使用示例
Aug 01 Javascript
使用JavaScript制作一个简单的计数器的方法
Jul 07 Javascript
微信小程序之MaterialDesign--input组件详解
Feb 15 Javascript
Javascript中click与blur事件的顺序详析
Apr 25 Javascript
深入了解javascript 数组的sort方法
Jun 01 Javascript
解决vue中使用Axios调用接口时出现的ie数据处理问题
Aug 13 Javascript
Spring boot 和Vue开发中CORS跨域问题解决
Sep 05 Javascript
Vue.js的复用组件开发流程完整记录
Nov 29 Javascript
Node.js中Koa2在控制台输出请求日志的方法示例
May 02 Javascript
iview实现动态表单和自定义验证时间段重叠
Jan 10 Javascript
Vue.js组件实现选项卡以及切换特效
Jul 24 #Javascript
javascript中call,apply,callee,caller用法实例分析
Jul 24 #Javascript
javascript关于“时间”的一次探索
Jul 24 #Javascript
javascript面向对象三大特征之封装实例详解
Jul 24 #Javascript
解决vue-cli webpack打包开启Gzip 报错问题
Jul 24 #Javascript
Vue  webpack 项目自动打包压缩成zip文件的方法
Jul 24 #Javascript
JavaScript面向对象中接口实现方法详解
Jul 24 #Javascript
You might like
PHP.MVC的模板标签系统(二)
2006/09/05 PHP
php 数组的一个悲剧?
2011/05/11 PHP
一个不易被发现的PHP后门代码解析
2014/07/05 PHP
Zend Framework教程之模型Model基本规则和使用方法
2016/03/04 PHP
Windows2003下php5.4安装配置教程(IIS)
2016/06/30 PHP
event对象的方法 兼容多浏览器
2009/06/27 Javascript
javascript cookies操作集合
2010/04/12 Javascript
javascript与cookie 的问题详解
2013/11/11 Javascript
jquery ajax请求方式与提示用户正在处理请稍等
2014/09/01 Javascript
JavaScript使用Max函数返回两个数字中较大数的方法
2015/04/06 Javascript
JQuery显示、隐藏div的几种方法简明总结
2015/04/16 Javascript
基于canvas实现的钟摆效果完整实例
2016/01/26 Javascript
浅析Bootstrip的select控件绑定数据的问题
2016/05/10 Javascript
angularjs中$http异步上传Excel文件方法
2018/02/23 Javascript
angularjs数组判断是否含有某个元素的实例
2018/02/27 Javascript
对vue中v-on绑定自定事件的实例讲解
2018/09/06 Javascript
vue移动端弹框组件的实例
2018/09/25 Javascript
webpack4.0 入门实践教程
2018/10/08 Javascript
JS代码优化的8点建议
2020/02/04 Javascript
js实现自定义右键菜单
2020/05/18 Javascript
聊聊vue 中的v-on参数问题
2021/01/29 Vue.js
python求列表交集的方法汇总
2014/11/10 Python
Python的socket模块源码中的一些实现要点分析
2016/06/06 Python
python中使用print输出中文的方法
2018/07/16 Python
使用python搭建服务器并实现Android端与之通信的方法
2019/06/28 Python
详解Django中views数据查询使用locals()函数进行优化
2020/08/24 Python
Python3+Flask安装使用教程详解
2021/02/16 Python
娇韵诗香港官网:Clarins香港
2020/08/13 全球购物
公务员更新知识培训实施方案
2014/03/31 职场文书
通信工程专业求职信
2014/06/04 职场文书
保密工作承诺书
2014/08/29 职场文书
颂军魂爱军营演讲稿
2014/09/13 职场文书
2014年共青团工作总结
2014/12/10 职场文书
交通安全温馨提示语
2015/07/14 职场文书
2019同学聚会主持词
2019/05/06 职场文书
52条SQL语句教你性能优化
2021/05/25 MySQL