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 相关文章推荐
JavaScript高级程序设计阅读笔记(六) ECMAScript中的运算符(二)
Feb 27 Javascript
Node.js实战 建立简单的Web服务器
Mar 08 Javascript
JQuery实现鼠标移动到图片上显示边框效果
Jan 09 Javascript
javascript的数组和常用函数详解
May 09 Javascript
js图片处理示例代码
May 12 Javascript
使用jquery+CSS实现控制打印样式
Dec 31 Javascript
javascript实现图片上传前台页面
Aug 18 Javascript
js实现拉幕效果的广告代码
Sep 02 Javascript
js改变透明度实现轮播图的算法
Aug 24 Javascript
浅析location.href跨窗口调用函数
Nov 22 Javascript
关于vue.js v-bind 的一些理解和思考
Jun 06 Javascript
微信小程序如何获取地址
Dec 24 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利用iframe实现无刷新文件上传功能的代码
2011/09/29 PHP
PHP中使用imagick生成PSD文件缩略图教程
2015/01/26 PHP
PHP实现搜索相似图片
2015/09/22 PHP
WordPress中注册菜单与调用菜单的方法详解
2015/12/18 PHP
PHP数组实例详解
2016/06/26 PHP
php微信浏览器分享设置以及回调详解
2016/08/01 PHP
php输出含有“#”字符串的方法
2017/01/18 PHP
JQuery循环滚动图片代码
2011/12/08 Javascript
JavaScript中的私有/静态属性介绍
2012/07/26 Javascript
html页面显示年月日时分秒和星期几的两种方式
2013/08/20 Javascript
javascript firefox 自动加载iframe 自动调整高宽示例
2013/08/27 Javascript
DOM基础教程之事件对象
2015/01/20 Javascript
Angularjs编写KindEditor,UEidtor,jQuery指令
2015/01/28 Javascript
jquery图片倾斜层叠切换特效代码分享
2015/08/27 Javascript
基于bootstrap插件实现autocomplete自动完成表单
2016/05/07 Javascript
JS简单随机数生成方法
2016/09/05 Javascript
微信小程序 触控事件详细介绍
2016/10/17 Javascript
jquery 删除节点 添加节点 找兄弟节点的简单实现
2016/12/07 Javascript
JS实战篇之收缩菜单表单布局
2016/12/10 Javascript
JavaScript控制输入框中只能输入中文、数字和英文的方法【基于正则实现】
2017/03/03 Javascript
vue项目上传Github预览的实现示例
2018/11/06 Javascript
Vue核心概念Getter的使用方法
2019/01/18 Javascript
微信小程序访问豆瓣电影api的实现方法
2019/03/31 Javascript
vue-cli3中vue.config.js配置教程详解
2019/05/29 Javascript
探索node之事件循环的实现
2020/10/30 Javascript
Python进行数据科学工作的简单入门教程
2015/04/01 Python
python3 破解 geetest(极验)的滑块验证码功能
2018/02/24 Python
详解基于django实现的webssh简单例子
2018/07/17 Python
Python学习笔记基本数据结构之序列类型list tuple range用法分析
2019/06/08 Python
python实现简单俄罗斯方块
2020/03/13 Python
添柏岚英国官方网站:Timberland英国
2019/11/28 全球购物
2014年个人工作总结范文
2014/11/07 职场文书
三好学生主要事迹材料
2015/11/03 职场文书
小学生六年级作文之关于感恩
2019/08/16 职场文书
Pytorch可视化的几种实现方法
2021/06/10 Python
ubuntu下常用apt命令介绍
2022/06/05 Servers