JS面向对象编程基础篇(三) 继承操作实例详解


Posted in Javascript onMarch 03, 2020

本文实例讲述了JS面向对象编程继承操作。分享给大家供大家参考,具体如下:

构造函数的继承

上一篇JS面向对象编程封装操作,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例。

今天要介绍的是,对象之间的"继承"的五种方法

比如,现在有一个"动物"对象的构造函数。

function Animal(){
 this.species = "动物";
    this.action="吃"
}

还有一个"猫"对象的构造函数。

function Cat(name,color){
this.name = name;

this.color = color;
}

怎样才能使"猫"继承"动物"呢? 

一、 构造函数绑定

第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

function Cat(name,color){
Animal.apply(this, arguments);

this.name = name;

this.color = color;
}
var cat1 = new Cat("大毛","黄色");
console.log(cat1.species); // 动物
console.log(cat1.action); // 吃

 

二、 prototype模式

第二种方法更常见,使用prototype属性。

如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。

Cat.prototype = new Animal();


Cat.prototype.constructor = Cat;


var cat1 = new Cat("大毛","黄色");


console.log(cat1.species); // 动物
       console.log(cat1.action); // 吃

代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。

Cat.prototype = new Animal();

它相当于完全删除了prototype 对象原先的值,然后赋予一个新值。但是,第二行又是什么意思呢?

Cat.prototype.constructor = Cat;

原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。

console.log(Cat.prototype.constructor == Animal); //true

更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。

console.log(cat1.constructor == Cat.prototype.constructor); // true

因此,在运行"Cat.prototype = new Animal();"这一行之后,cat1.constructor也指向Animal!

console.log(cat1.constructor == Animal); // true

这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的),因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。这就是第二行的意思。

这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象,

o.prototype = {};

那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。

o.prototype.constructor = o;

三、 直接继承prototype(它是存在缺点的,也就是浅拷贝问题)

第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。

现在,我们先将Animal对象改写:

function Animal(){ }


Animal.prototype.species = "动物";

然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。

Cat.prototype = Animal.prototype;


Cat.prototype.constructor = Cat;


var cat1 = new Cat("大毛","黄色");


console.log(cat1.species); // 动物

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。(浅拷贝)

所以,上面这一段代码其实是有问题的。请看第二行

Cat.prototype.constructor = Cat;

这一句实际上把Animal.prototype对象的constructor属性也改掉了!

console.log(Animal.prototype.constructor); // Cat

四、 利用空对象作为中介(解决方式三的浅拷贝问题)

由于"直接继承prototype"存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。

var F = function(){};


F.prototype = Animal.prototype;


Cat.prototype = new F();


Cat.prototype.constructor = Cat;

F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。

console.log(Animal.prototype.constructor); // Animal

我们将上面的方法,封装成一个函数,便于使用。

function extend(Child, Parent) {



var F = function(){};



F.prototype = Parent.prototype;



Child.prototype = new F();



Child.prototype.constructor = Child;



Child.uber = Parent.prototype;


}

使用的时候,方法如下

extend(Cat,Animal);


var cat1 = new Cat("大毛","黄色");


console.log(cat1.species); // 动物

这个extend函数,就是YUI库如何实现继承的方法。

另外,说明一点,函数体最后一行

Child.uber = Parent.prototype;

意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层"。)这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。

五、 拷贝继承(解决方式三的浅拷贝问题)

上面是采用prototype对象,实现继承。我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?这样我们就有了第五种方法。

首先,还是把Animal的所有不变属性,都放到它的prototype对象上。

function Animal(){}


Animal.prototype.species = "动物";

然后,再写一个函数,实现属性拷贝的目的。

function extend2(Child, Parent) {



var p = Parent.prototype;



var c = Child.prototype;



for (var i in p) {




c[i] = p[i];




}



c.uber = p;


}

这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。

使用的时候,这样写:

extend2(Cat, Animal);


var cat1 = new Cat("大毛","黄色");


console.log(cat1.species); // 动物

非构造函数继承

一、什么是"非构造函数"的继承?

比如,现在有一个对象,叫做"中国人"。

var Chinese = {


nation:'中国'

};

还有一个对象,叫做"医生"。

var Doctor ={


career:'医生'

}

请问怎样才能让"医生"去继承"中国人",也就是说,我怎样才能生成一个"中国医生"的对象?

这里要注意,这两个对象都是普通对象,不是构造函数,无法使用构造函数方法实现"继承"。

二、object()方法

json格式的发明人Douglas Crockford,提出了一个object()函数,可以做到这一点。

function object(o) {



function F() {}



F.prototype = o;



return new F();


}

这个object()函数,其实只做一件事,就是把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起。

使用的时候,第一步先在父对象的基础上,生成子对象:

var Doctor = object(Chinese);

然后,再加上子对象本身的属性:

Doctor.career = '医生';

这时,子对象已经继承了父对象的属性了。

console.log(Doctor.nation); //中国

三、浅拷贝

除了使用"prototype链"以外,还有另一种思路:把父对象的属性,全部拷贝给子对象,也能实现继承。

下面这个函数,就是在做拷贝:

function extendCopy(p) {



var c = {};



for (var i in p) { 



c[i] = p[i];


}



c.uber = p;



return c;

}

使用的时候,这样写:

var Doctor = extendCopy(Chinese);


Doctor.career = '医生';


console.log(Doctor.nation); // 中国

但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。

请看,现在给Chinese添加一个"出生地"属性,它的值是一个数组。

Chinese.birthPlaces = ['北京','上海','香港'];

通过extendCopy()函数,Doctor继承了Chinese。

var Doctor = extendCopy(Chinese);

然后,我们为Doctor的"出生地"添加一个城市:

Doctor.birthPlaces.push('厦门');

发生了什么事?Chinese的"出生地"也被改掉了!

console.log(Doctor.birthPlaces); //北京, 上海, 香港, 厦门


console.log(Chinese.birthPlaces); //北京, 上海, 香港, 厦门

所以,extendCopy()只是拷贝基本类型的数据,我们把这种拷贝叫做"浅拷贝"。这是早期jQuery实现继承的方式。

四、深拷贝

所谓"深拷贝",就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用"浅拷贝"就行了。

function deepCopy(p, c) {



var c = c || {};



for (var i in p) {




if (typeof p[i] === 'object') {





c[i] = (p[i].constructor === Array) ? [] : {};





deepCopy(p[i], c[i]);




} else {





 c[i] = p[i];




}


}



return c;

}

使用的时候这样写:

var Doctor = deepCopy(Chinese);

现在,给父对象加一个属性,值为数组。然后,在子对象上修改这个属性:

Chinese.birthPlaces = ['北京','上海','香港'];


Doctor.birthPlaces.push('厦门');

这时,父对象就不会受到影响了。

console.log(Doctor.birthPlaces); //北京, 上海, 香港, 厦门


console.log(Chinese.birthPlaces); //北京, 上海, 香港

目前,jQuery库使用的就是这种继承方法。

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

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

Javascript 相关文章推荐
自适应图片大小的弹出窗口
Jul 27 Javascript
JS截取url中问号后面参数的值信息
Apr 29 Javascript
详解JavaScript中getFullYear()方法的使用
Jun 10 Javascript
基于jQuery+PHP+Mysql实现在线拍照和在线浏览照片
Sep 06 Javascript
JS遍历JSON数组及获取JSON数组长度操作示例【测试可用】
Dec 12 Javascript
浅谈Vue.js中如何实现自定义下拉菜单指令
Jan 06 Javascript
微信小程序webview 脚手架使用详解
Jul 22 Javascript
vue.js 2.0实现简单分页效果
Jul 29 Javascript
vue实现公共方法抽离
Jul 31 Javascript
基于Vue全局组件与局部组件的区别说明
Aug 11 Javascript
Antd的Table组件嵌套Table以及选择框联动操作
Oct 24 Javascript
JS数据类型分类及常用判断方法
Nov 19 Javascript
小程序接入腾讯位置服务的详细流程
Mar 03 #Javascript
vue.js this.$router.push获取不到params参数问题
Mar 03 #Javascript
JS面向对象编程基础篇(二) 封装操作实例详解
Mar 03 #Javascript
Vue axios与Go Frame后端框架的Options请求跨域问题详解
Mar 03 #Javascript
JS面向对象编程基础篇(一) 对象和构造函数实例详解
Mar 03 #Javascript
vue中改变滚动条样式的方法
Mar 03 #Javascript
vue倒计时刷新页面不会从头开始的解决方法
Mar 03 #Javascript
You might like
ie6 动态缩略图不显示的原因
2009/06/21 PHP
PHP实现数字补零功能的2个函数介绍
2014/05/12 PHP
PHP伪静态Rewrite设置之APACHE篇
2014/07/30 PHP
PHP框架Swoole定时器Timer特性分析
2014/08/19 PHP
Laravel构建即时应用的一种实现方法详解
2017/08/31 PHP
php中关于换行的实例写法
2019/09/26 PHP
如何在一个页面显示多个百度地图
2013/04/07 Javascript
使用js获取图片原始尺寸
2014/12/03 Javascript
JavaScript随机生成信用卡卡号的方法
2015/04/07 Javascript
jquery编写Tab选项卡滚动导航切换特效
2020/07/17 Javascript
jQuery插件cxSelect多级联动下拉菜单实例解析
2016/06/24 Javascript
JS弹出新窗口被拦截的解决方法
2016/08/09 Javascript
通过修改360抢票的刷新频率和突破8车次限制实现方法
2017/01/04 Javascript
Cocos2d实现刮刮卡效果
2018/12/20 Javascript
babel7.x和webpack4.x配置vue项目的方法步骤
2019/05/12 Javascript
javascript获取select值的方法完整实例
2019/06/20 Javascript
Vue 的 v-model用法实例
2020/11/23 Vue.js
vant时间控件使用方法详解
2020/12/24 Javascript
Python 的 with 语句详解
2014/06/13 Python
mac系统安装Python3初体验
2018/01/02 Python
PyQt5每天必学之布局管理
2018/04/19 Python
用python编写第一个IDA插件的实例
2018/05/29 Python
windows下安装Python虚拟环境virtualenvwrapper-win
2019/06/14 Python
如何使用Python脚本实现文件拷贝
2019/11/20 Python
解决Python图形界面中设置尺寸的问题
2020/03/05 Python
基于python实现判断字符串是否数字算法
2020/07/10 Python
html5 canvas 实现光线沿不规则路径运动
2020/04/20 HTML / CSS
最好的商品表达自己:Cafepress
2019/09/04 全球购物
岗位职责定义及内容
2013/11/08 职场文书
学校庆元旦歌咏比赛主持词
2014/03/18 职场文书
同学聚会策划方案
2014/06/06 职场文书
2014年大学生职业规划书:未来不是梦,只要勇敢冲!
2014/09/22 职场文书
先进集体申报材料
2014/12/25 职场文书
2015秋季开学演讲稿范文
2015/07/16 职场文书
幼师自荐信范文(2016推荐篇)
2016/01/28 职场文书
Axios取消重复请求的方法实例详解
2021/06/15 Javascript