继承是OOP中大家最喜欢谈论的内容之一,一般来说,继承都两种方式:接口继承和实现继承而JavaScript中没有接口继承需要的方法,因此只能依靠实现继承。
在讲继承的实现之前,我们首先来回顾一下什么是继承?继承的作用是什么?

它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”。
被继承的类称为“基类”、“父类”或“超类”。
在清楚继承的作用之后,下面我们来探讨一下JS中的几种继承实现的方式:
//混入式继承(拷贝)//obj2继承到obj1中的成员,可以直接将obj1中的成员拷贝到obj2中即可var obj1 = {name:"zs",age:10};var obj2 = {};// 将obj1中的成员拷贝到obj2中for (var key in obj1) {
obj2[key] = obj1[key];
}console.log(obj1);console.log(obj2);
最终得到的obj2中的成员和obj1中的成员完全一致,当然,我们需要清除的是,此时的obj1和obj2是两个不同的对象。
混入式继承方式看似很简单,但是存在共享数据安全的问题。
var obj1 = {name:"zs",age:10,car:{name:"mini"}};var obj2 = {};// 将obj1中的成员拷贝到obj2中for (var key in obj1) {
obj2[key] = obj1[key];
}//修改obj1对象中的car属性
obj1.car.name = "bus";
console.log(obj1);console.log(obj2);
当我们需要修改某些对象中的引用类型对象的时候,会造成其他相关的对象也被修改,这是我们不希望看到的。
原型式继承的实现
回想一下,当我们在访问一个对象中的成员的时候,首先是在当前对象中查找,如果找不到,就往上,在原型链中依次查找,如果在整个原型链中也不存在该成员,那么就返回undefined。
所以,我们想要在A对象中访问到B对象中的成员,除了将B中的成员添加到A中,如:混入式,我们也可以考虑将B中成员添加到A的原型链中,实现对象成员的共享。
function Animal() {
}
Animal.prototype.name="animal";function Person() {
}//修改Person的原型对象
Person.prototype= Animal.prototype;

Person.prototype.useTool = function () {
console.log("use fire");
}var p = new Person();console.log(p);var ani = new Animal();console.log(ani);
画图分析:

  1. 最初,Animal和Person的两个对象没有任何关系,所以各自只能访问各自的成员
  2. 现在,Person对象如果想要继承Animal对象,只需要将Person的原型对象修改为Animal的原型对象即可

    这种方式实现的继承称之为原型继承,实现也是比较方便的,当时和混入式一样,存在数据共享的问题。
    原型链继承的实现
    原型式继承
    在前面的课程中,我们讲了原型式的继承,这种继承方式是修改子类对象原型指向父类对象的原型,如前面的MyArray执行的是Array的原型对象。
    这种方式存在问题是,只能继承父类对象原型上的成员,但无法继承父类对象中的成员。
    function Animal() {
    this.color="red";
    }
    Animal.prototype.weight=100;function Person() {
    }
    Person.prototype = Animal.prototype;var p = new Person();console.log(p);
    Person.prototype = Animal.prototype;原型式的继承
    此时创建出来的Person对象p可以访问Animal中的weight属性,但是无法访问color属性。看图理解

    那么,如果我们既要继承原型对象中的成员,也要继承实例对象中的成员,就应该使用这一节中的原型链继承
    function Animal() {
    this.color="red";
    }
    Animal.prototype.weight=100;function Person() {
    }
    Person.prototype = new Animal();var p = new Person();console.log(p.color);//red


使用原型链继承方式能够继承到更多的成员。但是依然存在问题:

  1. 共享的问题
  2. 无法向构造器传参
    复杂的原型链继承示例---
    Object-Animal-Person-Boy
    Object.create方法的基本使用
    在Object的构造函数上存在一个create方法,该方法的作用是用来创建对象的。
    该方法可以接收的参数有一下两种
  3. null

  4. 创建一个空对象,这个空对象中连最基本的原型对象都没有的

  5. 对象

  6. 创建传递进来的对象,并设置该对象的原型对象为当前的参数


  7. 该方法的使用率比较低,要求大家知道存在这样的一种创建对象的方式即可。
    call方法和apply方法的基本使用
    call和apply方法作用:

  8. 方法借用2. 设置方法中this的指向
    var obj1 = {
    name:"Neld",
    age:10,
    showInfo:function () {
    console.log("name:"+this.name,"age:"+this.age);
    }
    }var obj2 = {
    name:"Lily",
    age:12
    }
    obj1.showInfo();//name:Neld age:10
    obj2.showInfo();//obj2.showInfo is not a function
    obj1对象中有showInfo方法,而obj2对象中没有,所以如果直接使用obj2调用showInfo方法的时候抛出错误信息。
    如果我们临时需要使用obj2调用showInfo方法来打印出name和age属性的值,此时可以使用这里的call或者apply方法来实现。
    obj1.showInfo.call(obj2);//name:Lily age:12
    obj1.showInfo.apply(obj2);//name:Lily age:12
    这就是把obj1中的showInfo方法借用给obj2使用。
    同时我们观察到,在showInfo方法中使用到了this关键字,在obj2借用该方法的时候,其中的this已经指向了obj2对象,这就要达到修改方法中this关键字的指向的目的。
    call和apply方法的作用是完全一样的,那么他们的区别是什么呢?继续往下分析。
    var obj1 = {
    name:"Neld",
    age:10,
    add : function (a, b) {
    return a + b;
    }
    }var obj2 = {
    name:"Lily",
    age:12
    }console.log(obj1.add.call(obj2, 1, 2));//3//console.log(obj1.add.apply(obj2, 1, 2));//CreateListFromArrayLike called on non-objectconsole.log(obj1.add.apply(obj2, [2, 2]));//4
    在obj1中定义一个带有两个参数的方法,obj2中没有,问题一样,obj2也要使用到obj1中的add方法,此时使用call或者apply借用即可。
    此时新的问题是,在调用add方法的时候参数应该如何传递?
    call方法:
    ​ 将this指向的对象作为第一个参数,其他参数依次传递即可
    apply方法:
    ​ 将this指向的对象作为第一个参数,其他参数封装到数组中传递
    ​ 如果没有使用数组,程序报错。
    以上就是call和apply的基本使用,这两个方法在后续的课程中会大量的使用到,所以必须引起重视。
    借用构造函数继承说明
    所谓借用构造函数,就是在子构造函数中调用父构造函数,达到继承并向父构造函数传参的目的。
    function SuperClass(name,age) {
    this.name = name;
    this.age = age;
    }
    SuperClass.prototype.fun1 = function () {
    console.log("name:",this.name,"age:",this.age);
    }function SubClass(color) {
    this.color = color;
    }
    SubClass.prototype = new SuperClass("xxx",10);//继承父构造函数并设置name和age的值
    SubClass.prototype.fun2 = function () {
    console.log("color:",this.color);
    }var sub = new SubClass("red", "Neld", 10);var sub2 = new SubClass("red", "Lily", 12);console.log(sub);console.log(sub2);
    上面是原型链的继承,这种方式存在一个问题是,在创建不同对象的时候,无法为其继承过来的成员赋值。
    这里的sub和sub2两个对象的name和age属性值都是“xxx”和10,很明显是不满足我们需求的。
    那么我们来看看借用构造函数是否能解决这个问题呢?
    function SuperClass(name,age) {
    this.name = name;
    this.age = age;
    }
    SuperClass.prototype.fun1 = function () {
    console.log("name:",this.name,"age:",this.age);
    }function SubClass(color, name, age) {
    this.newMethod = SuperClass;//①
    this.newMethod(name, age);//②
    this.color = color;
    delete this.newMethod;//③
    }//SubClass.prototype = new SuperClass();
    SubClass.prototype.fun2 = function () {
    console.log("color:",this.color);
    }var sub = new SubClass("red", "Neld", 10);var sub2 = new SubClass("red", "Lily", 12);console.log(sub);console.log(sub2);
    ①、②、③处代码是实现借用构造函数的关键。下面一一作出解释:
    ①:将父对象的构造函数设置为子对象的成员
    ②:调用这个方法,想过类似于将父构造函数的代码放在子构造函数中来执行
    function SubClass(color, name, age) {
    this.newMethod = SuperClass;
    this.name = name;//父构造函数中的代码
    this.age = age;//父构造函数中的代码
    this.color = color;
    delete this.newMethod;
    }
    这样看应该更直观一点,执行之后就是在为当前创建出来的对象封装name和age属性。
    ③:在子构造函数中,newMethod仅仅为了在②调用父构造函数使用,用完之后也就没了价值,所以,直接删除该方法即可

    可以看到,借用构造函数继承方式解决了原型链及继承的问题。
    下面再看看另外一种借用构造函数的实现方式(使用call或apply):
    function SubClass(color, name, age) {
    //SuperClass.call(this,name,age);
    SuperClass.apply(this,[name,age]);
    this.color = color;
    }
    我们可以使用call或apply更简单快捷的实现和上面一样的效果。
    以上就是借用构造函数继承(也要对象冒充)的两种实现方式。当然,这种继承方式都存在下面两个问题:
  9. 如果父子构造函数存在相同的成员,那么子构造函数会覆盖父构造函数中的成员
  10. 不能继承原型链中的成员
    组合继承的基本结构
    基于原型链继承和借用构造函数继承两种方式存在的问题,我们提出一个解决方案---组合继承,就是两种一起使用。
    function SubClass(color, name, age) {
    //SuperClass.call(this,name,age);
    SuperClass.apply(this,[name,age]);//继承构造函数中的成员
    this.color = color;
    }

SubClass.prototype = new SuperClass();//继承原型链上的成员
总结: ECMAScript 实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。
绘制完整的原型链结构图
这一节重点探讨函数对象的原型链结构。完整的结构图如下:

说了这么多,其实核心只有一个:属性共享和独立的控制,当你的对象实例需要独立的属性,所有做法的本质都是在对象实例里面创建属性。若不考虑太多,你大可以在Person里面直接定义你所需要独立的属性来覆盖掉原型的属性。总之,使用原型继承的时候,要对于原型中的属性要特别注意,因为他们都是牵一发而动全身的存在。

一文看懂JS继承的更多相关文章

  1. 一文看懂js中元素的滚动大小(scrollWidth,scrollHeight,scrollTop,scrollLeft)

    滚动大小(scroll dimension) 滚动大小指的是包含滚动内容元素的大小. 以下是与元素滚动内容大小相关的属性: 1. scrollWidth:在没有滚动条的情况下,元素内容的总宽度. 2. ...

  2. 一文看懂js中的clientX,clientY,pageX,pageY,screenX,screenY

    一. 客户区坐标位置(clientX,clientY) 鼠标事件都是在浏览器视口中的特定位置发生的.这个位置信息保存在事件对象的clientX和clientY属性中,所有浏览器都支持这两个属性. 我们 ...

  3. 一文看懂js中元素的客户区大小(clientWidth,clientHeight)

    元素的客户区 元素的客户区大小,指的是元素内容及其内边距所占据的空间大小. 相关属性如下: 1. clientWidth:元素内容区宽度+元素左右内边距 2. clientHeight:元素内容区高度 ...

  4. 一文看懂js中元素偏移量(offsetLeft,offsetTop,offsetWidth,offsetHeight)

    偏移量(offset dimension) 偏移量:包括元素在屏幕上占用的所有可见空间,元素的可见大小有其高度,宽度决定,包括所有内边距,滚动条和边框大小(注意,不包括外边距). 以下4个属性可以获取 ...

  5. 一篇文章看懂JS闭包,都要2020年了,你怎么能还不懂闭包?

     壹 ❀ 引 我觉得每一位JavaScript工作者都无法避免与闭包打交道,就算在实际开发中不使用但面试中被问及也是常态了.就我而言对于闭包的理解仅止步于一些概念,看到相关代码我知道这是个闭包,但闭包 ...

  6. 一文看懂web服务器、应用服务器、web容器、反向代理服务器区别与联系

    我们知道,不同肤色的人外貌差别很大,而双胞胎的辨识很难.有意思的是Web服务器/Web容器/Web应用程序服务器/反向代理有点像四胞胎,在网络上经常一起出现.本文将带读者对这四个相似概念如何区分. 1 ...

  7. 一文看懂https如何保证数据传输的安全性的【转载、收藏】

    一文看懂https如何保证数据传输的安全性的   一文看懂https如何保证数据传输的安全性的 大家都知道,在客户端与服务器数据传输的过程中,http协议的传输是不安全的,也就是一般情况下http是明 ...

  8. [转帖]一文看懂web服务器、应用服务器、web容器、反向代理服务器区别与联系

    一文看懂web服务器.应用服务器.web容器.反向代理服务器区别与联系 https://www.cnblogs.com/vipyoumay/p/7455431.html 我们知道,不同肤色的人外貌差别 ...

  9. [转帖] 一文看懂:"边缘计算"究竟是什么?为何潜力无限?

    一文看懂:"边缘计算"究竟是什么?为何潜力无限? 转载cnbeta   云计算 雾计算 边缘计算...   知名创投调研机构CB Insights撰文详述了边缘计算的发展和应用前景 ...

随机推荐

  1. 谷歌浏览器打开不了Axure生成的html文件

    1.首先要进行翻墙.https://www.google.com 搜索Axure chrome软件 2. 3.安装axure插件即可. 4.管理扩展程序,允许访问文件网址.

  2. c语言的#和##的用法

    #include <stdio.h> #define ADD(A,B) printf(#A " + " #B " = %d\n",((A)+(B)) ...

  3. jdk是什么

    jdk是对java基础环境和相应开发平台标准和工具包的封装(zip) 开发平台 j2se j2ee j2me; 基础环境: 虚拟机.运行环境 JDK是整个JAVA的核心,包括了Java运行环境JRE( ...

  4. Async/await promise实现

    An async function can contain an await expression that pauses the execution of the async function an ...

  5. MongoDB 企业版4.2.2安装

    一.下载企业版MongoDB安装RPM包 https://www.mongodb.com/download-center/enterprise 二.安装MogoDB4.2.2企业版 1.安装依赖包 n ...

  6. 文件描述符fd,struct files_struct

    程序可以理解为硬盘上的普通二进制文件:进程是加载到内存中的二进制文件,除了加载到内存中的二进制文件外,还附有所有对于该二进制文件描述信息的结构体,描述该进程的结构体叫PCB(进程控制块),在这就不在讨 ...

  7. 关于读取Com口数据时,Com口的名字的两种读法。

    1,直接调用SerialPort类中的GetPortNames()方法. string[] portList = System.IO.Ports.SerialPort.GetPortNames(); ...

  8. 【LG3582】[POI2015]KIN

    [LG3582][POI2015]KIN 题面 洛谷 题解 这题维护区间的信息有点像最大子段和,我们往最大子段和上面靠. 对于一个颜色,我们有一个直观的想法就是将它一次出现的权值设为正,二次出现就设为 ...

  9. Java程序设计学习知识点总结

    Java程序设计学习知识点总结 Java语言简单,面向对象,分布式,解释性,健壮,安全与系统无关,可移植,高性能,多线程,动态语言. 什么是框架 可以认为是某种应用的半成品,就是一组组件用来完善自己的 ...

  10. K8s无状态控制器原理介绍

    Pod控制器: ReplicationController:早期K8s只有这一个控制器,但后来发现让这一个来完成所有任务,太复杂.因此被废弃. ReplicaSet: 它用于帮助用户创建指定数量的Po ...