IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 2021-10-07 -> 正文阅读

[JavaScript知识库]2021-10-07

JS的6种继承方案

01-原型链的继承方案

function Person(){
    this.name="mjt";
}
function Student(){}
var p1=new Person();
Student.prototype=p1;
var student1=new Student();
console.log(student1); // Person{}
console.log(student1.name); // mjt

这是最简单的一种方案,同时也是弊端最多的方案,我们来分析下他的弊端

(1).如果直接打印Student的实例对象,打印出来是这样
在这里插入图片描述
为啥不是打印出来的Student呢,我们先得了解一个东西,打印出来的这个名称是由constructor决定的
我们直接将Person的实例对象赋值给Student的prototype,Student原来的prototype就被覆盖了
而Person的实例对象的隐式原型是指向Person的prototype的
Person的prototype中是有constructor的,constructor是指向Person本身的
你们可以这么理解,Person.prototype,那么constructor就是指向Person,Object.prototype中的constructor就是指向Object
所以,Student1没找到自己的constructor,沿着原型链往上找,找到了Person的constructor,所以就是Person
(2).通过这种方式继承过来,打印学生的实例对象,继承过来的属性是看不见的
(3).如果在父类Person上添加引用类型的数据,如果我们创建多个学生对象实例,修改了引用类型的数据,那么父类中的数据也是会被改变的,那么实例的对象,相互之间就不是独立的,可以看下下面这段代码

function Person(){
    this.friends=[];
}
function Student(){}
var p1=new Person();
Student.prototype=p1;
var student1=new Student();
var student2=new Student();
student1.friends.push("mjt");
console.log(student2.friends); // ["mjt"]

(4).无法传递参数
细心的人可能就会发现,我这样写是无法传递参数的.那以后我们要传递参数的话,就会相当麻烦
所以,接下来我们看第二种方式

02-借用构造函数实现继承

这个方案的核心就是 call
function Person(name, age) {
    this.name = name;
    this.age = age;
}


// 第一个弊端: Person执行了两次
// 第一次是new出来的,第二次是call执行的
// 第二个弊端是 在new的时候,会往student上加上一些不必要的属性,会把继承的属性加到了Student上面,这是没必要的
Student.prototype = new Person();
Student.prototype.studying = function () {
    console.log(this.name + "再学习");
}
function Student(name, age, sno) {
    Person.call(this, name, age);
    this.sno = sno;
}

let stu1 = new Student("mjt", 18, 1801305712);
stu1.studying();
console.log(stu1);

这个方案解决了第一种方案的(2),(3),(4)弊端
(1).这两个放的顺序一定不能放错了

Student.prototype = new Person();
Student.prototype.studying = function () {
    console.log(this.name + "再学习");
}

顺序放错的话 studying那个函数加在了Student原来的原型上,而后又把Person的实例对象覆盖了Student的原型,那么在我们调用的话,就找不到了
(2).我们来看下这一段

function Student(name, age, sno) {
    Person.call(this, name, age);
    this.sno = sno;
}

因为在我们内直接使用call调用了Person并执行了,那么Person的属性也都可以在Student里面使用,这也能使的我们可以传递属性,可以复用父类中定义的属性.并且在我们打印的时候,也能把属性全部打印出来,大家可以自己试试

(3).这个方案说实话已经很不错了,解决了大部分问题,如果简单使用也是可以的,但是这种方案就没有弊端吗?
答案是有的,我们现在来看下它的弊端
①第一个弊端: Person执行了两次
②第一次是new出来的,第二次是call执行的
③第二个弊端是 在new的时候,会往student上加上一些不必要的属性,会把继承的属性加到了Student上面,这是没必要的,我们只需要在call的时候,调用Person,并且把属性放到Student上面去就行了
④第一种方案的第一个弊端也没有解决
我们现在来看下一种方案

03-直接继承父类的原型

function Person(name) {
    this.name = name;
}
Person.prototype.running = function () {
    console.log(this.name + " is running");
}

function Teacher(name, career) {
    Person.call(this, name);
    this.career = career;
}
function Student(name, age) {
    Person.call(this, name);;
    this.age = age;
}

// 这种方法可不可行呢?
// 答案是不行
// 我们看下面的例子
// 我明明是给学生单独加的属性,但是teacher也可以共用它,这是不合理的
// 我们需要的是学生和老师自己的原型上是属于自己独一无二的属性
// 这样子可复用性才会高
Student.prototype = Person.prototype;
Teacher.prototype = Person.prototype;
Student.prototype.studying = function () {
    console.log(this.name + " is learning");
}

var s1 = new Student("mjt", 18);
var t1 = new Teacher("teacher", "math");
console.log(s1);
s1.running()
s1.studying();
t1.studying();

这种方案是基于第二种方案的一个改版,但是我们看下这种方案可不可行
看完代码,大家也知道不行了,我在代码上也有详细的注释
(1).我明明是给学生单独加的属性,但是teacher也可以共用它,这是不合理的,这是因为我们加的方法都是直接加在了父类的原型上
(2).我们需要的是学生和老师自己的原型上是属于自己独一无二的属性
(3).这样子可复用性才会高
接下来会介绍基于对象来实现继承的两种方案,这两种方案对于最后一种方案的出现有很大的帮助

04-原型式继承

这个方案是由道格拉斯·克罗克福德(Douglas Crockford)所提出来的,是对我们前端届贡献特别大的一个人物
这个方案是专门针对对象的

我们先看下他当时是怎么实现的

// o是对象
function createObject(o){
    function fn(){};
    //将对象赋值给fn的原型
    fn.prototype=o;
    //将实例化的fn赋值给新对象,这样新对象的隐式原型就会指向我们传递进来的obj
    var newObj=new fn();
    return newObj;
}

大家可以浏览下我上面写的代码,这是道格拉斯当时的想法

现在随着js的逐渐变化,我们写这种方案也变得比较简单了,我们来看下面代码

function createObject(o){
    var newObj={};
    Obejct.setPrototypeof(newObj,o);
    return newObj;
}

Object.setPrototypeof()可以给对象设置一个新的原型,相比之前更加方便了
基于最新的ECMA标准的化,我们还可以这么写

var newObj=Object.create(o);

直接创建一个以o为原型的对象
接下来是针对这一种方案做的一个完善版

var personObj = {
  running: function() {
    console.log("running")
  }
}
function createStudent(name) {
  var stu = Object.create(personObj)
  stu.name = name
  stu.studying = function() {
    console.log("studying~")
  }
  return stu
}
var stuObj = createStudent("why")
var stuObj1 = createStudent("kobe")
var stuObj2 = createStudent("james")

这种方案是基于第四种方案进行改编的

寄生式继承的思路是结合原型类继承和工厂模式的一种方式;

即创建一个封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再将这个对象返回;

06-寄生组合式继承

//因为继承是一个可能很常用的方法,所以我们把继承的方法提炼出来了,作为一个工具函数
//这样我们只需要传递父类和子类,函数就能自动帮助我们实现继承
function inherit(faObj, chiObj) {
    //因为父类和子类的原型都是对象,所以可以用这个方法
    //创建一个以父类原型对象为原型的对象,并且将它赋值给子类原型
    //这帮助我们解决了第二种方案的父类执行了两次的问题,不需要实例化父类对象
    chiObj.prototype = Object.create(faObj.prototype);
    //这是为了解决前面方案都没有解决的constructor的方法
    Object.defineProperty(chiObj.prototype, "constructor", {
        value: chiObj,
        enumerable: false,
        writable: false,
        configurable: false,
    })
}

function Person(name, age, height) {
    this.name = name;
    this.age = age;
    this.height = height;
}
//下面是我们讲的第二种方案 借用构造函数实现继承
function Student(name, age, height, sno) {
    Person.call(this, name, age, height);
    this.sno = sno;
}
function Teacher(name, age, height, pro) {
    Person.call(this, name, age, height);
    this.pro = pro;
}

//在这里使用我们自己定义的继承函数
inherit(Person, Student);
inherit(Person, Teacher);
//注意:上下的顺序不能错,我在第二种方案的时候有讲过
Person.prototype.playing = function () {
    console.log(this.name + " is playing");
}
Student.prototype.studying = function () {
    console.log(this.name + " is studying");
}
Teacher.prototype.teaching = function () {
    console.log(this.name + " is teaching");
}
var s1 = new Student("mjt", 18, 1.80, 1801305712);
var t1 = new Teacher("Mr.ma", 21, 1.80, "computer");

//可以打印出所有的属性 并且打印的是对应的类,而不是父类了
console.log(s1);  // Student{}
console.log(t1); // Teacher{}
s1.studying();
t1.teaching();
s1.playing();
t1.playing();
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-08 11:42:45  更:2021-10-08 11:43:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 22:48:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码