手写js——继承
创始人
2024-04-21 18:40:52
0

原型链继承

所谓 函数 也就是 函数 Father其本身,也叫作构造函数 ,当一个函数被创建的同时,也会为其创建一个 prototype 属性,而这个属性,就是用来指向 函数原型,的我们可以把 prototype 理解为 Father的一个属性,保存着函数原型的引用。

  • 当访问一个对象的属性(包括方法时),首先查找这个对象自身有没有该属性。  
  • 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
  •  如果还没有就查找原型对象的原型(Object的原型对象)。依此类推一直找到为null为止。
  • __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
     
//一、原型链继承
function Father(){}
Father.prototype.name="father"
// Father.prototype.sex="man"
function Son(){}
Son.prototype.name="son"
Son.prototype.sex="woman"
var f=new Father()
Son.prototype=new Father()
Son.prototype.constructor=f;
var s=new Son();
console.log(s.name,"=============>son's name") //undefined
console.log(s.sex)

运行上述代码 会发现 s.name 结果为undefined 原因是我们在后面重新 改变了 Father.prototype的指向  所以找不到之前定义的name,所以在原型链定义属性要在 继承之后定义属性

function Father(){}
Father.prototype.name="father"
// Father.prototype.sex="man"
function Son(){}var f=new Father()
Son.prototype=new Father()
Son.prototype.constructor=f;
var s=new Son();
Son.prototype.name="son"
Son.prototype.sex="woman"
console.log(s.name,"=============>son's name") //undefined
console.log(s.sex)

 

优点:

父类方法可以复用

缺点 :

  • 父类的引用属性会被所有子类实例共享

  • 子类的构建实例不能像父类构造函数传参 

构造函数继承

 实现 new函数

当使用new关键字调用构造函数生成一个对象实例时,js做出的关键处理如下:

  • 1.创建一个新的对象,将构造函数内this指针指向新建对象。
  • 2.将新建对象的__proto__属性设置成构造函数的prototype属性,确保新建对象是构造函数实例。
  • 3.运行一遍构造函数,如果构造函数本身需要返回一个object或者array对象,则舍去新创建对象,返回构造函数需要返回的对象;否则返回新建对象

/*** @bug 这里假设new出来实例的都是object,不是array对象* @description 传入构造函数,以及构造函数参数,模拟构造函数返回一个新建实例* @param {function} constructor 需要传入的构造函数* @param {any} params 跟在constructor参数后面的所有参数,用来模拟传入constructor的参数* @returns 返回一个新的对象
*/
function newFn(constructor){var obj={}obj.__proto__=constructor.prototypevar params=Array.prototype.splice.call(arguments,1);constructor.apply(obj,params);return obj
}function Person(name,age){this.name=name;this.age=age
}
let Cat={name:"",age:""
}
let person=newFn(Person,"Florenza",20)
console.log(person)
console.log(person instanceof Person)
// console.log(cat)

基本思想

构造函数继承的基本思想就是利用call或者apply把父类中通过this指定的属性和方法复制(借用)到子类创建的实例中。因为this对象是在运行时基于函数的执行环境绑定的。在全局中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。call 、apply方法可以用来代替另一个对象调用一个方法。call、apply 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
new对象的时候(注意,new操作符与直接调用是不同的,以函数的方式直接调用的时候,this指向window,new创建的时候,this指向创建的这个实例),创建了一个新的实例对象,并且执行子类里面的代码,而子类里面用父类.call改变this指向,也就是说把this指向改成了指向新的实例,所以就会把SuperType里面的this相关属性和方法赋值到新的实例上,而不是赋值到SupType上面。所有实例中就拥有了父类定义的这些this的属性和方法。 

//二 构造函数继承
function Animal(){this.sex="male"
}
function Cat(){Animal.call(this)
}
console.log(new Cat().sex)

 

 优点:和原型链继承完全反过来

  • 父类的引用属性不会被共享

  • 子类构建实例时可以向父类传递参数

缺点:父类的方法不能复用,子类实例的方法每次都是单独创建的。

组合式继承

基本思想

组合继承有时候也叫伪经典继承,指的是将原型链和借用构造函数技术组合到一块,从而发挥二者之长的一种继承模式,其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它的自己的属性。

// 组合式继承
console.log("=============组合式继承=============")
function Animal2(){this.name="animal"this.sex="male"
}function Dog(){this.name="dog"Animal2.call(this);
}
Dog.prototype=new Animal2();
console.log(new Dog().sex,"dog sex")

优点:

  • 父类的方法可以被复用
  • 父类的引用属性不会被共享
  • 子类构建实例时可以向父类传递参数

缺点:

调用了两次父类的构造函数,第一次给子类的原型添加了父类的name, arr属性,第二次又给子类的构造函数添加了父类的name, arr属性,从而覆盖了子类原型中的同名参数。这种被覆盖的情况造成了性能上的浪费。

原型式继承

主要思想

原型式继承并没有使用严格意义上的构造函数,是通过借助原型基于已有的对象创建新对象,同时还不必创建自定义类型。使用原型式继承的主要思路:

核心:原型式继承的object方法本质上是对参数对象的一个浅复制

优点:

父类方法可以复用。

缺点:

  • 父类的引用属性会被所有子类实例共享

  • 子类构建实例时不能向父类传递参数

console.log("===========原型式继承===========")
var people={name:"peopel",friends:["tiger"]
}
function createObject(o){
function F(){}
F.prototype=o;
return new F();
}
let elephant=createObject(people);
elephant.friends.push("mouse")console.log(elephant.name)
console.log(elephant.friends)

 ECMAScript5新增Object.create( )方法规范了原型式继承。该方法接收了两个参数:一个用作新对象原型的对象和(可选)一个为新对象定义额外属性的对象。在传入一个参数的情况下Object.create( )和object( )方法的行为相同。如果传入两个参数,则Object.create( )的第二个参数与Object.defineProperties( )方法的第二个参数的格式相同:每个属性都是通过自己的描述符定义的。

 寄生式继承

接上面原型式继承,只在原型继承方法后面进行方法增强

即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,后再像真的是它做了所有工作一样返回对象。

console.log("===========寄生式继承==========")
//原型式继承方法
var people2={name:"peopel2",friends:["tiger"]
}
function createObject(o){
function F(){}F.prototype=o;return new F();
}
function createAnother(original){ var fn=createObject(original)fn.say=function(){console.log("another")}return fn
} 
var man=createAnother(people2)
man.say()

优缺点:

仅提供一种思路,没什么优点。

组合式寄生式继承

即使用 组合式继承( xxx.call的方式)也使用 原型式 继承

了解了组合继承和寄生继承之后就是寄生式组合继承了,它是通过借用构造函数来继承属性,通过原型链形式来继承方法,会解决2次调用父类函数以及复用率的问题。

console.log("============组合式寄生式继承===========")
function Animal3(){this.name="animal3"this.age=10;this.friends=["cat"]
}
Animal3.prototype.getSay=function(){console.log("animal-3")
}
function Pig(){Animal3.call(this)
}
function createObj(o){function fn(){}fn.prototype=o;return new fn();
}function inhertCreate(parent,child){let parentProto=createObj(parent.prototype);parentProto.constructor=child;child.prototype=parentProto;
}
// 核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费
inhertCreate(Animal3,Pig)
var pig=new Pig();
console.log(pig)
pig.getSay()

 完美方式实现

ES6继承

主要思想

核心: ES6继承的结果和寄生组合继承相似,本质上,ES6继承是一种语法糖。但是,寄生组合继承是先创建子类实例this对象,然后再对其增强;而ES6先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

class A {}class B extends A {constructor() {super();}}ES6实现继承的具体原理:class A {}class B {}Object.setPrototypeOf = function (obj, proto) {obj.__proto__ = proto;return obj;}// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);

相关内容

热门资讯

【MySQL】锁 锁 文章目录锁全局锁表级锁表锁元数据锁(MDL)意向锁AUTO-INC锁...
【内网安全】 隧道搭建穿透上线... 文章目录内网穿透-Ngrok-入门-上线1、服务端配置:2、客户端连接服务端ÿ...
GCN的几种模型复现笔记 引言 本篇笔记紧接上文,主要是上一篇看写了快2w字,再去接入代码感觉有点...
数据分页展示逻辑 import java.util.Arrays;import java.util.List;impo...
Redis为什么选择单线程?R... 目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程?三、R...
【已解决】ERROR: Cou... 正确指令: pip install pyyaml
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
Lock 接口解读 前置知识点Synchronized synchronized 是 Java 中的关键字,...
Win7 专业版安装中文包、汉... 参考资料:http://www.metsky.com/archives/350.htm...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
大模型未来趋势 大模型是人工智能领域的重要发展趋势之一,未来有着广阔的应用前景和发展空间。以下是大模型未来的趋势和展...
python实战应用讲解-【n... 目录 如何在Python中计算残余的平方和 方法1:使用其Base公式 方法2:使用statsmod...
学习u-boot 需要了解的m... 一、常用函数 1. origin 函数 origin 函数的返回值就是变量来源。使用格式如下...
常用python爬虫库介绍与简... 通用 urllib -网络库(stdlib)。 requests -网络库。 grab – 网络库&...
药品批准文号查询|药融云-中国... 药品批文是国家食品药品监督管理局(NMPA)对药品的审评和批准的证明文件...
【2023-03-22】SRS... 【2023-03-22】SRS推流搭配FFmpeg实现目标检测 说明: 外侧测试使用SRS播放器测...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
初级算法-哈希表 主要记录算法和数据结构学习笔记,新的一年更上一层楼! 初级算法-哈希表...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
【Docker】P3 Dock... Docker数据卷、宿主机与挂载数据卷的概念及作用挂载宿主机配置数据卷挂载操作示例一个容器挂载多个目...