JavaScript(7)--- 继承

概念 首先继承是一种关系,类(class)与类之间的关系,JS中没有类,但是可以通过构造函数模拟类,然后通过原型来实现继承,继承也是为了数据共享。

之间有讲过js中的原型和原型链:JavaScript(6)--- 原型链

原型链就是继承的一种,子类 没有的东西到 父类 去寻找, 父类 如果还是没有就会到 爷爷 那去寻找,直到顶层,也就是到 Object 这一层如果还是没有就真没有了。

当然除了原型链,还有其它继承方式,下面都会一一说明。

1、原型链继承
2、借用构造函数(经典继承)
3、组合继承
4、原型式继承
5、寄生式继承
6、寄生组合式继承

一、原型链继承

1、示例

      //人类构造函数
function Person(name, age, sex) {
this.name = name;
this.sex = sex;
this.age = age;
}
//人的原型方法
Person.prototype.eat = function () {
console.log("人可以吃东西");
};
//学生构造函数
function Student(score) {
this.score = score;
}
//改变学生的原型的指向即可===>学生和人已经发生关系
Student.prototype = new Person("小明", 10, "男");
//添加学生原型方法
Student.prototype.study = function () {
console.log("学生需要学习");
};
var stu = new Student(100);
//父类 所拥有的特性 被继承来的
console.log(stu.name);
console.log(stu.age);
console.log(stu.sex);
stu.eat();
//学生自有的特性
console.log("---------------");
console.log(stu.score);
stu.study();

运行结果

关键点 让新实例的原型指向父类的实例,这样就可以继承父类的属性和方法,实现数据共享。

缺点 1、因为改变原型指向的同时实现继承,直接初始化了属性,继承过来的属性的值都是一样的了

   2、引用类型的属性被所有实例共享

二、借用构造函数继承

1、示例

function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.say = function () {
console.log("父类方法");
};
function Student(name,age,sex,score) {
//借用构造函数 -> 构造函数名字.call(当前对象,属性,属性,属性....);
Person.call(this,name,age,sex);
this.score = score;
}
var stu1 = new Student("张三",10,"男","100");
console.log(stu1.name, stu1.age, stu1.sex, stu1.score); var stu2 = new Student("李四",20,"女","120");
console.log(stu2.name, stu2.age, stu2.sex, stu2.score); var stu3 = new Student("王五",30,"妖","130");
console.log(stu3.name, stu3.age, stu3.sex, stu3.score);

运行结果

重点.call() 将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))

解决:解决了属性继承,并且值不重复的问题

缺陷

1、只继承了父类构造函数的属性,没有继承父类原型的属性。

2、无法实现构造函数的复用。(每次用每次都要重新调用)

3、每个新实例都有父类构造函数的副本,臃肿。

三、组合继承

概念:原型链继承+借用构造函数继承 = 组合继承

1、示例

//父类构造函数
function Person(name,age,sex) {
this.name=name;
this.age=age;
this.sex=sex;
}
//父类原型
Person.prototype.say=function () {
console.log("父类方法");
};
function Student(name,age,sex,score) {
//借用构造函数:属性值重复的问题
Person.call(this,name,age,sex);
this.score=score;
}
//改变原型指向----继承
Student.prototype=new Person();//不传值
//子类原型
Student.prototype.eat=function () {
console.log("子类方法");
};
//创建子类对象
var stu=new Student("张三",20,"男","100分");
console.log(stu.name,stu.age,stu.sex,stu.score);
stu.say();
stu.eat();
//创建子类对象
var stu2=new Student("李四",200,"女","90分");
console.log(stu2.name,stu2.age,stu2.sex,stu2.score);
stu2.say();
stu2.eat();

运行结果

解决 解决了 只继承了父类构造函数的属性,没有继承父类原型的属性 的缺陷。

缺陷 调用了两次父类构造函数(耗内存),(一次是在子类型构造函数内部,另一次是在创建子类型原型的时候)

四、原型式继承

1、示例
    function CreateObj(o) { //传递一个字面量函数
function F() {} //创建一个构造函数
F.prototype = o; //把字面量函数赋值给构造函数的原型
return new F(); //最终返回出实例化的构造函数
}
var person = { //字面量对象
name: '小小',
friend: ['香蕉', '小鳄鱼', '青蛙']
};
var person1 = CreateObj(person); //传递
console.log(person1.name);
person1.name = "中中"
console.log(person1.name);
var person2 = CreateObj(person); //传递
console.log( person2.name);

运行结果

重点 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。

缺点 包含引用类型的属性值始终都会共享相应的值, 这点跟原型链继承一样。

注意 这里修改了person1.name的值,person2.name的值并未改变,是因为person1.name='person1'是给person1添加了name值,并非修改了原型上的name值。

因为我们找对象上的属性时,总是先找实例上对象,没有找到的话再去原型对象上的属性。实例对象和原型对象上如果有同名属性,总是先取实例对象上的值

五、寄生式继承

概念 它的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某张方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

function createAnother(original){
var clone = Object.create(original); //这个就是上面的原型函数
clone.sayHi = function(){ //只是通过这个函数 可以新增加属性和方法
console.log('2020.1.18号');
}
return clone;
}

我理解了一下,就是又创建了一个函数,然后在函数内部定义一个对象来实现原型式继承方式,然后再给这个对象添加方法或属性值呀,最后再将这个对象返回,

这就成为了寄生式继承方式。让我们来调用这个函数吧!

var person = {
name:"Nick",
friends:["xiaowang","xiaochen"]
};
var person1 = createAnother(person); //新增加sayHi方法
person1.sayHi();

重点 就是给原型式继承外面套了个壳子。

优点 没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。

缺点 没用到原型,无法复用。

六、寄生组合式继承(常用)

上面的组合继承最大的缺点就是:组合继承(构造函数和原型的组合)会调用两次父类构造函数的代码。

因此引入寄生组合式继承,寄生组合继承 是寄生继承跟组合继承的结合版。

即通过借用构造函数来继承属性,通过原型链的方式来继承方法,而不需要为子类指定原型而调用父类的构造函数,我们需要拿到的仅仅是父类原型的一个副本。

因此可以通过传入子类和父类的构造函数作为参数,首先创建父类原型的一个复本,并为其添加constrcutor,最后赋给子类的原型。这样避免了调用两次父类的

构造函数,为其创建多余的属性。

1、代码示例

   // 父类构造函数
function Parent(name){
this.name = name;
this.colors = ['red', 'blue', 'green'];
} //父类原型
Parent.prototype.sayName = function(){
console.log(this.name);
} //子类构造函数
function Child(name,age){
//拷贝父类构造函数
Parent.call(this,name);
this.age = age;
} //原型式继承
function CreateObj(o){
function F(){};
F.prototype = o;
return new F();
} // Child.prototype = new Parent(); // 这里换成下面
function prototype(child,parent){
var prototype = CreateObj(parent.prototype); //创建对象
prototype.constructor = child; //增强对象
child.prototype = prototype; //指定对象
}
prototype(Child,Parent); var child1 = new Child('小小', 18);
console.log(child1);

运行结果

优点 这种方式的高效率体现它只调用了一次Parent构造函数,并且因此避免了再Parent.prototype上面创建不必要的,多余的属性。普遍认为寄生组合式继承是引用类型

最理想的继承方式

七、总结

这么多种继承方式,本质上其实就两种

1.通过原型链,即子类的原型指向父类的实例从而实现原型共享。
2.借用构造函数,即通过js的apply、call实现子类调用父类的属性、方法;

原型链方式: 可以实现所有属性方法共享,但无法做到属性、方法独享(例如Sub1修改了父类的函数,其他所有的子类Sub2、Sub3...想调用旧的函数就无法实现了);

借用构造函数: 除了能独享属性、方法外还能在子类构造函数中传递参数,但代码无法复用。总体而言就是可以实现所有属性方法独享,但无法做到属性、方法共享

(例如,Sub1新增了一个函数,然后想让Sub2、Sub3...都可以用的话就无法实现了,只能Sub2、Sub3...各自在构造函数中新增)。

组合继承: 就是把以上两种继承方式一起使用,把共享的属性、方法用原型链继承实现,独享的属性、方法用借用构造函数实现。

参考

1、如何理解javascript中寄生组合式继承?

2、JS继承的多种方式

3、JS继承的几种方式

别人骂我胖,我会生气,因为我心里承认了我胖。别人说我矮,我就会觉得好笑,因为我心里知道我不可能矮。这就是我们为什么会对别人的攻击生气。
攻我盾者,乃我内心之矛(4)。

JavaScript(7)--- 继承的更多相关文章

  1. Javascript模拟继承(赠送.net吐槽一段)

    首先吐槽一句,今年的就业形势很不乐观啊,特别是搞.net的(相对java),特特别是还没出校门没有正式工作经验的,找个实习很难,前些天接了个面试电话,上来就质疑我“你一个在校大学生怎么可能做了那么多项 ...

  2. 详解Javascript的继承实现(二)

    上文<详解Javascript的继承实现>介绍了一个通用的继承库,基于该库,可以快速构建带继承关系和静态成员的javascript类,好使用也好理解,额外的好处是,如果所有类都用这种库来构 ...

  3. JavaScript之继承(原型链)

    JavaScript之继承(原型链) 我们知道继承是oo语言中不可缺少的一部分,对于JavaScript也是如此.一般的继承有两种方式:其一,接口继承,只继承方法的签名:其二,实现继承,继承实际的方法 ...

  4. javascript深度克隆与javascript的继承实现

    1.javascript深度克隆: //注意这里的对象包括object和array function cloneObject(obj){ var o = obj.constructor === Arr ...

  5. 再谈javascript原型继承

    Javascript原型继承是一个被说烂掉了的话题,但是自己对于这个问题一直没有彻底理解,今天花了点时间又看了一遍<Javascript模式>中关于原型实现继承的几种方法,下面来一一说明下 ...

  6. TDD测试驱动的javascript开发(3) ------ javascript的继承

    说起面向对象,人们就会想到继承,常见的继承分为2种:接口继承和实现继承.接口继承只继承方法签名,实现继承则继承实际的方法. 由于函数没有签名,在ECMAScript中无法实现接口继承,只支持实现继承. ...

  7. 彻底理解Javascript原型继承

    彻底理解Javascript原型继承 之前写过一篇Javascript继承主题的文章,这篇文章作为一篇读书笔记,分析的不够深入. 本文试图进一步思考,争取彻底理解Javascript继承原理 实例成员 ...

  8. Javascript原型继承容易忽略的错误

    编写Javascript的开发者都知道,JS虽然没有类(ES6添加了class语法),但是可以模拟出OOP语言的类和面向对象的概念,比如我们都知道的一句话,Javascript中处处是对象,而面向对象 ...

  9. JavaScript类继承, 用什么方法好

    JavaScript类继承, 用什么方法好 一个实例: 基类Car: function Car(color, year) { this.name = "car"; this.col ...

  10. [转]Javascript原型继承

    真正意义上来说Javascript并不是一门面向对象的语言,没有提供传统的继承方式,但是它提供了一种原型继承的方式,利用自身提供的原型属性来实现继承.Javascript原型继承是一个被说烂掉了的话题 ...

随机推荐

  1. Mybatis-Generator相关配置demo

    generatorConfig.xml配置信息 首先在resource中配置好datasource.propertise文件,包括数据库信息和mysql-connector的jar包位置. <? ...

  2. VMware虚拟机里安装CentOS 6.3图文教程

    著名服务器版本 CentOS 6.3 已经发布 http://www.centoscn.com/CentosSoft/iso/2013/0720/370.html CentOS 6.3 网易镜像下载: ...

  3. 查漏补缺:Linux进程与线程的区别

    1.概念的区别 进程:是具有独立功能的程序在一个数据集合上运行的过程,是系统进行资源分配的基本单位,也是调度运行的基本单位.一个进程中可以包含多个线程. 线程:是进程的一个实体,是CPU调度和分派的基 ...

  4. WTF is The BlockChain?

    最近区块链大热,走到哪儿都有人在讨论区块链和比特币,甚至于一些对密码学完全没有概念的人都开始大肆吹捧,不免让人嗤之以鼻.相信很多技术和非技术的朋友都希望能够更深层次地去了解它是如何工作的.本文将用不到 ...

  5. 使用python模拟登录网易邮箱网站

    环境要求 python 3.6 chromedriver.exe 文件 人工登录 人工登录某个网站,首先需要用浏览器打开登录页面,然后在输入框中输入对应的账号和密码,最后点击登录,以下使用代码模拟以上 ...

  6. C# InputStream获取后乱码处理

    Post推送过来的数据流获取后部分中文出现乱码,晚上找了好多办法,不如朋友鼎力相助,哈哈哈~不说废话了上代码把 旧代码基本是网上普遍写法,字段不长用起来不会有乱码情况,但是传送字段一旦过长,超过byt ...

  7. PHP文件上传 (以上传txt文件为例)

    1.前端代码 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <ti ...

  8. CollectionUtils工具类

    CollectionUtils工具类 这篇讲的CollectionUtils工具类是在apache下的,可以使代码更加简洁和安全. 使用前需导入依赖 <dependency> <gr ...

  9. [面试专题]前端需要知道的web安全知识

    前端需要知道的web安全知识 标签(空格分隔): 未分类 安全 [Doc] Crypto (加密) [Doc] TLS/SSL [Doc] HTTPS [Point] XSS [Point] CSRF ...

  10. Vue请求第三方接口跨域最终解决办法!2020最终版!

    废话少说,再百度的近三个小时尝试了近10种方法无解后,终于皇天不负有心人! 这个vue axios 跨域问题被我解决了! 需求:请求客户端ip地址获取客户ip,再根据ip获取用户位置 工具:Vue,a ...