1、继承

应注意区分继承和实例化,实例化是生成一个对象,这个对象具有构造函数的属性和方法;继承指的应该是利用父类生成一个新的子类构造函数,通过这个子类构造函数实例化的对象,具有子类的属性和方法,同时也具有父类的属性和方法。

2、原型链继承

2.1、实现方法

实现原型链继承的方法是通过重写子类的原型对象(比如 Student.prototype )的值为父类(比如Person) 的一个实例,由此可以实现继承(Student 继承了 Person ) 。

Son.prototype = new Parent();

代码示例:

//父类:人
function Person() {
this.head = "脑袋瓜子";
}
//子类:学生,继承了“人”这个类
function Student(studentID) {
this.studentID = studentID;
}
Student.prototype = new Person(); var stu1 = new Student(1001);
console.log(stu1.head); //脑袋瓜子 stu1.head = "聪明的脑袋瓜子";
console.log(stu1.head); //聪明的脑袋瓜子 var stu2 = new Student(1002);
console.log(stu2.head); //脑袋瓜子

上面代码中,student 继承了 person,所以 stu1 能访问到父类 Person 上定义的 head 属性。

2.2、原型链继承的弊端(误修改原型对象上的引用类型值)

原型链继承的缺点有:

(1)实例可能会误修改原型对象上的引用类型的值,导致其他实例也受到影响

(2)在实现继承时,子类无法向父类传参数

在上面的代码中,所有的 Student 实例都共享着原型对象上的属性。那为什么修改了 stu1 的 head 值以后,重新实例化的对象 stu2 的 head 值仍旧不变呢?

这是因为,当实例对象中存在和原型对象上同名的属性时,会自动屏蔽原型对象上的同名属性。stu1.head = "聪明的脑袋瓜子" 实际上只是给 stu1 添加了一个本地属性 head 并设置了相关值。所以当我们打印 stu1.head 时,访问的是该实例的本地属性,而不是其原型对象上的 head 属性(它因和本地属性名同名已经被屏蔽了)。

原型对象上基本类型的值,都不会被实例所重写/覆盖。在实例上设置与原型对象上同名属性的值,只会在实例上创建一个同名的本地属性。但是,原型对象上引用类型的值可以通过实例进行修改,致使所有实例共享着的该引用类型的值也会随之改变,这正是原型链继承的最大缺点。

//父类:人
function Person () {
this.head = '脑袋瓜子';
this.emotion = ['喜'];
this.say = function () {
console.log('hi');
}
}
//子类:学生,继承了“人”这个类
function Student(studentID) {
this.studentID = studentID;
}
Student.prototype = new Person(); var stu1 = new Student(1001);
console.log(stu1.emotion); //['喜'] stu1.emotion.push('怒');
console.log(stu1.emotion); //["喜", "怒"] var stu2 = new Student(1002);
console.log(stu2.emotion); //["喜", "怒"] console.log(stu1.say === stu2.say); //true 证明子类的实例对象共享引用类型的值

我们在 Person 类中添加了一个 emotion 属性,它是一个引用类型的值。可以看到,此时如果一个实例不小心修改了原型对象上引用类型的值,会导致其它实例也跟着受影响。从上面的代码可知,在实现继承时,子类并不能向父类传递参数。

参考:https://www.cnblogs.com/sarahwang/p/6879161.html

3、构造函数实现继承

借用构造函数实现的继承可以避免原型链继承会导致误修改原型对象上引用类型值的缺点。

3.1、实现方法

构造函数实现继承的方法是在子类的构造函数中,通过 apply ( ) 或 call ( )的形式来调用父类的构造函数,以实现继承。

function Son(){
Parent.call(this);
}

代码示例:

//父类:人
function Person (headMsg) {
this.head = headMsg;
this.emotion = ['喜', '怒'];
this.say = function () {
console.log('hi');
}
}
//子类:学生,继承了“人”这个类
function Student(studentID) {
this.studentID = studentID;
Person.call(this, '脑袋瓜子'); //构造函数在实现继承时可以传递参数
} var stu1 = new Student(1001);
console.log(stu1.emotion); //['喜', '怒'] stu1.emotion.push('哀');
console.log(stu1.emotion); //["喜", "怒", "哀"] var stu2 = new Student(1002);
console.log(stu2.emotion); //["喜", "怒"] console.log(stu1.say === stu2.say); //false 证明与原型链继承不同,引用类型的值并不共享

构造函数继承相对于原型链继承来说只是去掉了之前通过 prototype 继承的方法,而采用了 Person.call (this) 的形式实现继承,通过 call 来指定父类构造函数的作用域。

 this 指向解析:

可以简单理解为:谁调用它,它就指向谁。在 stu1 = new Student ( ) 构造函数时,是 stu1 调用 Student 方法,所以其内部 this 的值指向的是 stu1,所以 Person.call ( this ) 就相当于Person.call ( stu1 ),就相当于 stu1.Person( )。最后,stu1 去调用 Person 方法时,Person 内部的 this 指向就指向了 stu1。那么Person 内部this 上的所有属性和方法,都被拷贝到了 stu1 上。stu2 也是同理,所以其实是,每个实例都具有自己的 emotion 属性副本,它们互不影响。

所以,通过构造函数来实现继承,每个示例都会具有属性及方法的副本,互相不影响,由此也避免了原型链继承的弊端。

参考:https://www.cnblogs.com/sarahwang/p/6879161.html

3.2、构造函数继承的弊端(占用内存)

这种形式的继承,每个子类实例都会拷贝一份父类构造函数中的方法,作为实例自己的方法,这样做会有以下的缺点:

(1)每个实例都拷贝一份,占用内存大,尤其是方法过多的时候。(函数复用又无从谈起了,本来我们用 prototype 就是解决复用问题的)

(2)方法都作为了实例自己的方法,当需求改变,要改动其中的一个方法时,之前所有的实例,他们的该方法都不能及时作出更新。只有后面的实例才能访问到新方法。

所以说,无论是单独使用原型链继承还是借用构造函数继承都有很大的缺点,最好的办法是,将两者结合一起使用,这就是下面介绍的组合式继承。

4、组合式继承

4.1、实现方法

组合式继承实现方式是将需要共享的方法写在父类的原型对象上,而需要每个实例都拷贝一份的属性则写在父类的构造函数上,由此可以需要共享的方法能实现实例间共享,需要自己维护的属性能实现每个实例都有具有自己的副本而不会导致误修改,避免了原型链继承和构造函数继承的弊端。

//父类:人
function Person () {
this.head = '脑袋瓜子';
this.emotion = ['喜']; //人都有喜怒哀乐
}
//将 Person 类中需共享的方法放到 prototype 中,实现复用
Person.prototype.say = function () {
console.log('hi');
}
//子类:学生,继承了“人”这个类
function Student(studentID) {
this.studentID = studentID;
Person.call(this); //构造函数继承的方法
} Student.prototype = new Person(); //原型链继承的方法 此时 Student.prototype 中的 constructor 被重写了,会导致 stu1.constructor === Person
Student.prototype.constructor = Student; //将 Student 原型对象的 constructor 指针重新指向 Student 本身 var stu1 = new Student(1001);
console.log(stu1.emotion); //['喜'] stu1.emotion.push('怒');
console.log(stu1.emotion); //["喜", "怒"] var stu2 = new Student(1002);
console.log(stu2.emotion); //["喜"] 需要实例各自维护一份的属性不会被误修改 stu1.say(); //hi
console.log(stu1.say === stu2.say) //true 证明函数实现了共享
console.log(stu1.constructor); //Student 实例的构造函数仍然是子类构造函数 Student,而不是父类 Person

将 Person 类中需要复用的方法提取到 Person.prototype 中,然后设置 Student 的原型对象为 Person 类的一个实例,这样 stu1 就能访问到 Person 原型对象上的属性和方法了。其次,为保证 stu1 和 stu2 拥有各自的父类属性副本,我们在 Student 构造函数中,还是使用了 Person.call ( this ) 方法。如此,结合原型链继承和借用构造函数继承,就完美地解决了之前这二者各自表现出来的缺点。

参考:https://www.cnblogs.com/sarahwang/p/9098044.html

5、类实现继承(class、extends)

在 ES6 中,可以通过 class 和 extends 关键字来实现继承。ES6 中类实现继承可以看做是组合式继承的语法糖(简单理解),但两者的继承机制还是不太一样的。

class Animal {
constructor(age) {
this.age = age;
}
say() {
console.log("hi");
}
}
// extends 实现继承
class Dog extends Animal {
constructor(age) {
super(age); //ES6 要求,子类的构造函数必须执行一次 super() 函数。
}
}
// extends 实现继承
class Cat extends Animal {
constructor(age) {
super(age);
}
say() {
super.say();
console.log("miao miao!!");
}
} var cat = new Cat(11);
var dog = new Dog(22); console.log(cat.age, dog.age); // 输出11 22 继承了父类的属性
cat.say(); // 输出 hi
dog.say(); // 输出 miao miao!!, hi

JS中的继承(原型链、构造函数、组合式、class类)的更多相关文章

  1. 一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends

    说实在话,以前我只需要知道"寄生组合继承"是最好的,有个祖传代码模版用就行.最近因为一些事情,几个星期以来一直心心念念想整理出来.本文以<JavaScript高级程序设计&g ...

  2. JS 面向对象之继承 -- 原型链

    ECMAScript只支持实现继承,其实现继承主要是靠原型链来实现. 原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法. 简单回顾下构造函数.原型和实例的关系: 每个构造函数都有 ...

  3. 面试题常考&必考之--js中的难点!!!原型链,原型(__proto__),原型对象(prototype)结合例子更易懂

    1>首先,我们先将函数对象认识清楚: 补充snow的另一种写法: var snow =function(){}; 2>其次:就是原型对象 每当我们定义一个函数对象的时候,这个对象中就会包含 ...

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

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

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

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

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

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

  7. 浅谈JS中的继承

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

  8. Javascript 组合继承 原型链继承 寄生继承

    Javascript继承通常有三种方式. 第一种:组合式继承: function SuperType(name) { this.name = name; this.colors = ["re ...

  9. JS中的继承(上)

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

  10. JS中的继承(下)

    JS中的继承(下) 在上一篇 JS中的继承(上) 我们介绍了3种比较常用的js继承方法,如果你没看过,那么建议你先看一下,因为接下来要写的内容, 是建立在此基础上的.另外本文作为我个人的读书笔记,才疏 ...

随机推荐

  1. (转) intellij idea部署web项目时的位置(Tomcat)

    这篇文章说的比较好: 原文地址:https://blog.csdn.net/zmx729618/article/details/78340566 1.当你项目启动的时候console能看到项目运行的位 ...

  2. java.lang.NoClassDefFoundError: com/opensymphony/xwork2/util/finder/DefaultClassFinder$InfoBuildingV 解决方法

    问题:严重: Unable to read class [com.spml.action.AddUserAction]java.lang.NoClassDefFoundError: com/opens ...

  3. log4j.properties 输出指定类日志

    比如,我只要众多日志中,红色框的日志,则可以指定类:com.dangdang.ddframe.rdb.sharding.parser.SQLParserFactory 修改配置文件: 再次输出结果为:

  4. Java ——String 类 StringBuffer 和 StringBuilder类 随机字符

    本节重点思维导图 String 类 创建字符串 String str = "I love ai"; 在代码中遇到字符串常量时,"I love ai",编译器会使 ...

  5. VMware下Linux构建仅主机模式的局域网网络配置方案

    最近使用Linux,进行网络配置,以前都是桥接直连,然后直接组网.由于一些原因现在虚拟机做内网使用,不用上网,只能使用仅主机模式.在仅主机模式下进行虚拟机组网. 仅主机模式下各个虚拟机只能和主机通信, ...

  6. Linux-SSH免密登陆原理

  7. linux复杂命令

    1,查看包含zypper且不包含ar的进程信息的2,3,8,9列信息 ps -ef|grep zypper|grep -v ar|awk '{print $2,$3,$8,$9}' eg:查看包含zy ...

  8. 不用找了,300 分钟帮你搞定 Spring Cloud!

    最近几年,微服务架构一跃成为 IT 领域炙手可热的话题,大量一线互联网公司因为庞大的业务体量和业务需求,纷纷投入了微服务架构的建设中,像阿里巴巴.百度.美团等大厂,很早就已经开始了微服务的实践和应用. ...

  9. C++之匿名对象与析构函数的关系

    #include <iostream> using namespace std; class Location{ public: Location(, ){ X = xx; Y = yy; ...

  10. 谈一下你对 uWSGI 和 nginx 的理解??

    1.uWSGI 是一个 Web 服务器,它实现了 WSGI 协议.uwsgi.http 等协议.Nginx 中HttpUwsgiModule 的作用是与 uWSGI 服务器进行交换.WSGI 是一种 ...