js--如何实现继承?
前言
学习过 java 的同学应该都知道,常见的继承有接口继承和实现继承,接口继承只需要继承父类的方法签名,实现继承则继承父类的实际的方法,js 中主要依靠原型链来实现继承,无法做接口继承。
学习 js 继承之前,我们需要了解原型这一 概念,我们知道 js 中创建对象通过构造函数来创建,而每一个构造函数都有对应的 prototype 的属性,该属性对应的值为一个对象,这个对象也就是所有通过该构造函数创建出来的实例所共享的属性和方法,而创建出来的每一个实例对象都有一个指针指向这些共享的属性和方法,这个指针就是所说的 __proto__(注意这里是双下划线),因此就产生了三种来获取原型的方法,分别是 p.__proto__,p.constructor.prototype,Object.getPrototypeOf( p ),这就是我对原型的了解。
当我们在访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会在它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是这样一层一层向上找下去,也就产生了原型链的概念。原型链的尽头就是 object.prototype ,所以我们每创建的一个对象都有 toString(),valueOf() 等方法的原因。
有了上面的基础常识作为铺垫,我们来看下 js 中具体怎么来实现继承。
正文
js 中实现继承的方法有6种,具体实现如下:
(1)原型链实现继承
//定义父类
function superFun(){
this.superProperty = "super"//给父类构造函数添加参数属性
}
superFun.prototype.getSuperValue = function(){//给父类构造函数添加原型方法
return this.superProperty
}
//定义子类
function subFun(){
this.subProperty = "sub"
}
subFun.prototype = new superFun()//继承了superFun父类 ,这一点最主要
subFun.prototype.getSubValue = function(){//在继承父类之后,在原型上添加新的方法或者重写父类的方法
return this.subProperty
}
var sub = new subFun()//实例化一个子类对象
console.log(sub.superProperty);//super--判断继承父类的属性
console.log(sub.subProperty);//sub--子类的实例的属性
console.log(sub.getSuperValue());//super--判断继承父类的方法
console.log(sub.getSubValue());//sub----子类实例的方法
console.log(sub instanceof superFun);//true----原型链判断
console.log(sub instanceof subFun);//true----原型判断
上面的代码需要注意必须在继承父类语句之后才能在其原型上添加新的方法或者重写父类的方法,同时添加新的方法的时候不能使用字面量的形式添加。
所有的函数的默认原型都是 object,默认原型都会包含一个内部指针指向 object.prototype ,因此所有自定义的对象都有 toString()方法和 valueOf() 方法。
确定原型和实例的关系的方法可以使用:instanceof 和 isPrototypeOf。
优缺点:上面的方法让新实例的原型等于父类的实例实现了原型链的继承,子类的实例能够继承构造函数的属性,构造函数的方法,父类的构造函数的属性以及父类原型上的方法,但是新实例无法向构造函数传参,继承单一,所有的新实例都会共享父类构造函数的属性,因此在父类构造函数种定义一个引用数据类型的时候,每个字类的实例都有拥有该引用类型的属性,当其中一个实例对该属性做了修改,别的实例也会收到影响。例子如下:
//定义父类
function superFun(){
this.superProperty = {name:"xiaoming",age:20}//给父类构造函数添加参数属性
}
superFun.prototype.getSuperValue = function(){//给父类构造函数添加原型方法
return this.superProperty
}
//定义字类
function subFun(){
this.subProperty = "sub"
}
subFun.prototype = new superFun()//继承了superFun父类 ,这一点最主要
subFun.prototype.getSubValue = function(){//在继承父类之后,在原型上添加新的方法或者重写父类的方法
return this.subProperty
}
var sub1 = new subFun()
var sub2 = new subFun()
console.log(sub2.superProperty.name);//xiaoming
sub1.superProperty.name = "xiaohong"
console.log(sub2.superProperty.name);//xiaohong
(2)借用构造函数实现继承
//定义父类
function superFun(superProperty) {
this.superProperty = superProperty; //给父类构造函数添加参数属性
}
superFun.prototype.getSuperValue = function () {
//给父类构造函数添加原型方法
return this.superProperty;
};
function subFun() {
superFun.call(this, "super");
this.subProperty = "sub";
}
subFun.prototype.getSubValue = function () {
return this.subProperty;
};
var sub = new subFun();
console.log(sub.superProperty); //super--判断继承父类的属性
console.log(sub.subProperty); //sub--子类的实例的属性
//console.log(sub.getSuperValue()); //报错sub.getSuperValue is not a function--判断继承父类的方法 不能继承
console.log(sub.getSubValue()); //sub----子类实例的方法
console.log(sub instanceof superFun); //false----原型链判断
console.log(sub instanceof subFun); //true----原型判断
上面的方法借用构造函数实现继承,主要是用 call() 或者apply() 在子类的构造函数内部调用父类的构造函数,就相当于在子类构造函数内部做了父类函数的复制并且自执行。
优缺点:通过构造函数实现继承,只能继承父类构造函数的属性,不能继承父类原型上面的方法,无法实现构造函数的复用,每次用每次都要重新调用,相当于每个新实例都有父类构造函数的副本,造成臃肿,但是这种方法能够解决原型链不能传参的问题,对父类构造函数种属性为引用数据类型的问题,以及通过多个 call 解决单一继承问题等。
(3)原型链和构造函数组合实现继承(常用)
//定义父类
function superFun(superProperty) {
this.superProperty = superProperty;
this.superPropertyList = ["red", "blue", "green"];
}
superFun.prototype.getSuperValue = function () {
return this.superProperty;
};
function subFun(property1, property2) {
superFun.call(this, property1); //继承属性
this.subProperty = property2;
}
subFun.prototype = new superFun(); //继承方法
subFun.prototype.constructor = superFun;
//添加字类新方法
subFun.prototype.getSubValue = function () {
return this.subProperty;
};
var sub1 = new subFun("sub1Tosuper", "sub1Property");
var sub2 = new subFun("sub2Tosuper", "sub2Property");
console.log(sub2.superPropertyList); //["red", "blue", "green"]
sub1.superPropertyList.push("black");
console.log(sub2.superPropertyList); //["red", "blue", "green"] 父类引用类型数据两者互不干扰
console.log(sub1.superProperty); //sub1Tosuper--继承父类的属性
console.log(sub1.getSuperValue()); //sub1Tosuper--继承父类方法
console.log(sub1.subProperty); //sub1Property--子类的属性
console.log(sub1.getSubValue()); //sub1Property--子类方法
上面的代码使用原型链和构造函数组合实现了继承,其中通过原型链实现对原型的属性和方法的继承,通过借用构造函数来实现对实例属性的继承,这样即保证了函数的调用,有实现了每个实例都有自己的属性,解决了实例中属性干扰的问题。
优缺点:这种方法结合了前两种模式的优点,达到了传参和复用的效果,可以继承父类原型的属性和方法,可以传参,可以复用,同时每个新实例引入的构造函数的属性都是私有的,但是实现需要调用两次父类构造函数,这样就存在内存消耗问题,子类的构造函数会代替原型上的那个父类构造函数。
(4)原型式实现继承
//定义父类
function superFun(superProperty) {
this.superProperty = superProperty;
}
superFun.prototype.getSuperValue = function () {
return this.superProperty;
};
function subFun(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var super1 = new superFun("super1");
var sub = subFun(super1);
console.log(sub.superProperty);//super1
console.log(sub.getSuperValue());//super1
上面的代码重点在于在 subFun() 函数内部创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例,相当于用一个函数包装了一个对象,然后返回这个函数的的调用,这个函数会就编程了可以随意增添属性的实例或者对象, object.create() 就是这个原理。es5中object.create() 接受两个参数,一个参数作为新对象原型的对象,另一个可选参数作为新对象定义额外属性的对象,当两个参数都存在的时候,任何属性都会覆盖原型对象上的同名属性。
优缺点:这种方法类似于复制一个对象,用函数来包装,其实就是哪一个对象作为继承,然后传入另一个对象,本质就是对传入的对象进行一次浅拷贝,但是所有实例都会继承原型上的属性,且无法实现复用,若包含引用数据类型始终会共享相应的值。
(5)寄生式实现继承
//定义父类
function superFun(superProperty) {
this.superProperty = superProperty;
}
superFun.prototype.getSuperValue = function () {
return this.superProperty;
};
function subFun(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var super1 = new superFun("super1");
function subObject(obj){
var sub=subFun(obj)
sub.subPorperty="subPorperty"
return sub
}
var sub = subObject(super1);
console.log(sub.superProperty); //super1
console.log(sub.subPorperty);//subPorperty
console.log(sub.getSuperValue()); //super1
上面的代码对比原型式继承,其实就是在原型式继承的基础上套了一层壳子,创建了一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回一个对象。
优缺点:这种方法没有创建自定义类型,因为只是给返回的对象添加了一层壳子,实现了创建的新对象,但是这种方法没有用到原型,无法实现复用。
(5)寄生组合式实现实现继承(常用)
针对组合实现继承存在的问题进行了优化,前面说到组合继承要调用两次父类构造函数,第一次是在创建子类原型的时候,第二次是在子类构造函数内部 call 调用。对于这两次调用,第一次调用父类是可以避免的,不必为了指定子类型的原型而调用夫类型的构造函数,我们无非是需要一个父类型原型的一个副本而已。
//定义父类属性
function superFun(superProperty) {
this.superProperty = superProperty;
this.superPropertyList = ["red", "blue", "green"];
}
//定义父类原型上的方法
superFun.prototype.getSuperValue = function () {
return this.superProperty;
};
//使用寄生
function object(obj) {
function F() {}
F.prototype = obj;
return new F();
}
function inheritObject(subFun, superFun) {
var _prototype = object(superFun.prototype); //创建对象
_prototype.constructor = subFun; //增强对象
subFun.prototype = _prototype; //指定对象
}
//使用组合
function subFun(tosuperProperty,subProperty){
superFun.call(this,tosuperProperty)
this.subProperty=subProperty
}
//子类继承父类
inheritObject(subFun,superFun)
//子类原型的方法
subFun.prototype.getSubValue=function(){
return this.subProperty
}
var sub=new subFun("super","sub")
console.log(sub.superProperty);//super
console.log(sub.subProperty);//sub
console.log(sub.getSuperValue());//super
console.log(sub.getSubValue());//sub
console.log(sub instanceof superFun);//true
console.log(sub instanceof subFun);//true
上面的方法是 js 中实现继承最常见方法,它完美解决了组合式继承的中两次调用父类原型的bug,通过寄生,在函数内部返回对象然后调用,使用组合,使得函数的原型等于另一个实例,在函数中调用 call 引入另一个构造函数,实现了可以传参的功能,避免了在父类原型上创建不必要的属性,成为最理想的实现继承的方法。需要注意 inheritObject() 函数接受两个参数,分别式子类和父类的两个构造函数。
优缺点:使用寄生式继承实现了继承父类的原型,然后再将结果指定给子类型的原型。使用组合继承得到传参复用等效果。
总结
以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。
js--如何实现继承?的更多相关文章
- 浅谈JS中的继承
前言 JS 是没有继承的,不过可以曲线救国,利用构造函数.原型等方法实现继承的功能. var o=new Object(); 其实用构造函数实例化一个对象,就是继承,这里可以使用Object中的所有属 ...
- JS创建对象、继承原型、ES6中class继承
面向对象编程:java中对象的两个基本概念:1.类:类是对象的模板,比如说Leader 这个是泛称领导,并不特指谁.2:实例:实例是根据类创建的对象,根据类Leader可以创建出很多实例:liyi,y ...
- js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。
js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } Class ...
- js模拟实现继承功能
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...
- js中实现继承的几种方式
首先我们了解,js中的继承是主要是由原型链实现的.那么什么是原型链呢? 由于每个实例中都有一个指向原型对象的指针,如果一个对象的原型对象,是另一个构造函数的实例,这个对象的原型对象就会指向另一个对象的 ...
- js怎么实现继承?
3. js怎么实现继承? 1. 使用原型prototype 这个问题其实之前总结过了……但是面试时候有点忘……主要思想是记得的,但是不会写,还是基础太不牢靠,写的太少了.一开始因为不知道怎么能继承父类 ...
- [js]js原型链继承小结
这是之前总结的, 发现有很多的毛病,就是重点不突出,重新翻看的时候还是得耗费很长时间去理解这玩意. js中的继承 js中什么是类 1,类是函数数据类型 2.每个类有一个自带prototype属性 pr ...
- js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。
js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } ClassA ...
- 【学习笔记】六:面向对象的程序设计——理解JS中的对象属性、创建对象、JS中的继承
ES中没有类的概念,这也使其对象和其他语言中的对象有所不同,ES中定义对象为:“无序属性的集合,其属性包含基本值.对象或者函数”.现在常用的创建单个对象的方法为对象字面量形式.在常见多个对象时,使用工 ...
- JS中的继承(上)
JS中的继承(上) 学过java或者c#之类语言的同学,应该会对js的继承感到很困惑--不要问我怎么知道的,js的继承主要是基于原型(prototype)的,对js的原型感兴趣的同学,可以了解一下我之 ...
随机推荐
- how to make one you own free online tutorials in minutes
how to make one you own free online tutorials in minutes educative.io https://www.educative.io/colle ...
- ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await
ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await co ...
- js group objects in an array
js group objects in an array js group objects in an array var groupBy = function(xs, key) { return x ...
- JavaScript数据类型判断的四种方法
码文不易啊,转载请带上本文链接呀,感谢感谢 https://www.cnblogs.com/echoyya/p/14416375.html 本文分享了JavaScript类型判断的四种方法:typeo ...
- 14_MySQL条件查询
本节所涉及的sql语句: -- 去除结果集中的重复记录 SELECT job FROM t_emp; SELECT DISTINCT job FROM t_emp; SELECT DISTINCT j ...
- npm与package.json快速入门
本文转载自npm与package.json快速入门 导语 npm 是前端开发广泛使用的包管理工具,之前使用 Weex 时看了阮一峰前辈的文章了解了一些,这次结合官方文章总结一下,加深下理解吧! 读完本 ...
- Mybatis【20】-- Mybatis延迟加载怎么处理?
注:代码已托管在GitHub上,地址是:https://github.com/Damaer/Mybatis-Learning ,项目是mybatis-16-lazyload,需要自取,需要配置mave ...
- kubernetes和docker----2.学习Pod资源
Pod--k8s最基础的资源 我们想要的是单个容器只运行一个进程 然而有时我们需要多个进程协同工作,所以我们需要另外一种更加高级的结构将容器组合在一起---pod Pod 我们来看一个最基本的pod ...
- (原创)用.NET Core实现微信自动回复工具(上篇)
全文 没有视频的介绍显得尤为空白仓促.所以,如果你不赶时间,看看视频先 → → http://wexin.fuyue.xyz/Resource/Video/wechatTool.mp4 ← ← 功能列 ...
- css中a元素放长英文字母或者数字自动换行的解决
在做链接分享页面的时候遇到a元素中的下载链接长英文溢出不换行的问题 在给他以及他父元素设置宽度依然没有解决这个问题 最后解决办法给元素加上word-wrap:break-word 解释:使用break ...