前言

  学习过 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--如何实现继承?的更多相关文章

  1. 浅谈JS中的继承

    前言 JS 是没有继承的,不过可以曲线救国,利用构造函数.原型等方法实现继承的功能. var o=new Object(); 其实用构造函数实例化一个对象,就是继承,这里可以使用Object中的所有属 ...

  2. JS创建对象、继承原型、ES6中class继承

    面向对象编程:java中对象的两个基本概念:1.类:类是对象的模板,比如说Leader 这个是泛称领导,并不特指谁.2:实例:实例是根据类创建的对象,根据类Leader可以创建出很多实例:liyi,y ...

  3. js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。

    js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } Class ...

  4. js模拟实现继承功能

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  5. js中实现继承的几种方式

    首先我们了解,js中的继承是主要是由原型链实现的.那么什么是原型链呢? 由于每个实例中都有一个指向原型对象的指针,如果一个对象的原型对象,是另一个构造函数的实例,这个对象的原型对象就会指向另一个对象的 ...

  6. js怎么实现继承?

    3. js怎么实现继承? 1. 使用原型prototype 这个问题其实之前总结过了……但是面试时候有点忘……主要思想是记得的,但是不会写,还是基础太不牢靠,写的太少了.一开始因为不知道怎么能继承父类 ...

  7. [js]js原型链继承小结

    这是之前总结的, 发现有很多的毛病,就是重点不突出,重新翻看的时候还是得耗费很长时间去理解这玩意. js中的继承 js中什么是类 1,类是函数数据类型 2.每个类有一个自带prototype属性 pr ...

  8. js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。

    js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } ClassA ...

  9. 【学习笔记】六:面向对象的程序设计——理解JS中的对象属性、创建对象、JS中的继承

    ES中没有类的概念,这也使其对象和其他语言中的对象有所不同,ES中定义对象为:“无序属性的集合,其属性包含基本值.对象或者函数”.现在常用的创建单个对象的方法为对象字面量形式.在常见多个对象时,使用工 ...

  10. JS中的继承(上)

    JS中的继承(上) 学过java或者c#之类语言的同学,应该会对js的继承感到很困惑--不要问我怎么知道的,js的继承主要是基于原型(prototype)的,对js的原型感兴趣的同学,可以了解一下我之 ...

随机推荐

  1. Visual Studio Online & Web 版 VS Code

    Visual Studio Online & Web 版 VS Code https://online.visualstudio.com https://devblogs.microsoft. ...

  2. k8s部署mysql数据持久化

    在这里我部署mysql的目的是为了后面将上一篇博客docker打包的el-admin镜像部署到k8s上,所以本文主要是部署mysql并实现持久化. 1.将我们的应用都部署到 el-admin 这个命名 ...

  3. 加州金融专访NGK,就NGK DeFi+展开讨论

    近日,加利福尼亚金融日报联合数家知名媒体就DeFi+行业专访了NGK团队代表特德惠斯基. 首先,加利福尼亚金融日报专栏记者迈尔斯表示,目前区块链领域,去中心化金融(DeFi+)的概念是目前市场上面最火 ...

  4. Typescript快速入门

    目录 什么是Typescript 为什么学习Typescript 快速搭建开发环境 1.安装node.js 2.使用node自带的npm安装Typescript编译器 3.配置vscode编辑环境 4 ...

  5. Error Code: 1366. Incorrect DECIMAL value: '0' for column '' at row -1 0.266 sec;

    Reference: https://stackoverflow.com/questions/35037288/incorrect-decimal-integer-value-mysql     Er ...

  6. JAVA基础(二)—— 常用的类与方法

    JAVA基础(二)-- 常用的类与方法 1 Math类 abs ceil floor 绝对值 大于等于该浮点数的最小整数 小于等于该浮点数的最大整数 max min round 两参数中较大的 两参数 ...

  7. 基于solarflare的openonload技术以TCPDirect方法加速epoll

    [前言]基于solarflare的onload模式加速,官方文档给出TCPDirect模式可以实现从300ns到30ns的延迟缩减.我们需要测试在我们的交易模型框架中他的延时,有人给出了tcpdire ...

  8. System.IO.IOException:“找不到资源“window1.xaml”。” 解决方法

    报错:找不到资源"window1.xaml 原因:在编译时使用的是en-US选项进行编译并生成了en-US为名的文件夹,里面包含了可本地化的内容:但是你的本地系统使用的是zh-CN,在你运行 ...

  9. java list集合遍历时删除元素

    转: java list集合遍历时删除元素 大家可能都遇到过,在vector或arraylist的迭代遍历过程中同时进行修改,会抛出异常java.util.ConcurrentModification ...

  10. Linux速通07 硬盘分区、格式化及文件系统管理

    硬件设备与文件名的对应关系 # 在Linux系统中,每个设备都被当作一个文件来对待 # 各种设备在Linux中的文件名 设备 设备在Linux内的文件名 IDE硬盘 /dev/hd[a-d] SCSI ...