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注入技巧
Jun 22 Javascript
JavaScript中valueOf函数与toString方法深入理解
Dec 02 Javascript
用js来获取上传的文件名纯粹是为了美化而用
Oct 23 Javascript
深入探讨JavaScript String对象
Mar 09 Javascript
JS+CSS实现的漂亮渐变背景特效代码(6个渐变效果)
Mar 25 Javascript
AngularJS中run方法的巧妙运用
Jan 04 Javascript
jQuery居中元素scrollleft计算方法示例
Jan 16 Javascript
VUE element-ui 写个复用Table组件的示例代码
Nov 18 Javascript
把vue-router和express项目部署到服务器的方法
Feb 21 Javascript
了解前端理论:rscss和rsjs
May 23 Javascript
js回调函数原理与用法案例分析
Mar 04 Javascript
一小时迅速入门Mybatis之bind与多数据源支持 Java API
Sep 15 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
Trying to clone an uncloneable object of class Imagic的解决方法
2012/01/11 PHP
LotusPhp笔记之:基于ObjectUtil组件的使用分析
2013/05/06 PHP
php利用腾讯ip分享计划获取地理位置示例分享
2014/01/20 PHP
学习php设计模式 php实现观察者模式(Observer)
2015/12/09 PHP
浅析php静态方法与非静态方法的用法区别
2016/05/17 PHP
动态表单验证的操作方法和TP框架里面的ajax表单验证
2017/07/19 PHP
PHP实现微信提现功能
2018/09/30 PHP
php微信公众号开发之音乐信息
2018/10/20 PHP
javascript JSON操作入门实例
2010/04/16 Javascript
使用Json比用string返回数据更友好,也更面向对象一些
2011/09/13 Javascript
JS刷新当前页面的几种方法总结
2013/12/24 Javascript
javaScript使用EL表达式的几种方式
2014/05/27 Javascript
JavaScript跨浏览器获取页面中相同class节点的方法
2015/03/03 Javascript
JavaScript程序中的流程控制语句用法总结
2016/05/23 Javascript
JS实现课堂随机点名和顺序点名
2017/03/09 Javascript
jQuery zTree树插件动态加载实例代码
2017/05/11 jQuery
微信小程序自定义多选事件的实现代码
2018/05/17 Javascript
VueCli3构建TS项目的方法步骤
2018/11/07 Javascript
微信小程序生成分享海报方法(附带二维码生成)
2019/03/29 Javascript
微信sdk实现禁止微信分享(使用原生php实现)
2019/11/15 Javascript
python BeautifulSoup设置页面编码的方法
2015/04/03 Python
Django基础三之视图函数的使用方法
2019/07/18 Python
处理Selenium3+python3定位鼠标悬停才显示的元素
2019/07/31 Python
Python Django 页面上展示固定的页码数实现代码
2019/08/21 Python
python将数组n等分的实例
2019/12/02 Python
python继承threading.Thread实现有返回值的子类实例
2020/05/02 Python
纯CSS实现菜单、导航栏的3D翻转动画效果
2014/04/23 HTML / CSS
html5的canvas实现3d雪花飘舞效果
2013/12/27 HTML / CSS
爱尔兰电子产品购物网站:Komplett.ie
2018/04/04 全球购物
轻松制作精彩视频:Animoto
2018/09/19 全球购物
五十岁生日宴会答谢词
2014/01/15 职场文书
社区安全检查制度
2014/02/03 职场文书
产品委托授权书范本
2014/09/16 职场文书
党的群众路线教育实践活动方案
2014/10/31 职场文书
3招让你摆脱即兴讲话冷场尴尬
2019/08/08 职场文书
52条SQL语句教你性能优化
2021/05/25 MySQL