热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

js深入理解原型、原型链、继承

一、函数函数原型函数实例函数是function关键字声明的函数,函数原型有个原型链的概念,每个构造函数都是保存在原型中prototype,prototype是函数的

一、函数 函数原型 函数实例

函数是function关键字声明的函数,函数原型有个原型链的概念,每个构造函数都是保存在原型中prototype,prototype是函数的原型,原型中有个constructor,函数前面加new 关键字,就是函数的实例化,生成的就是函数实例。


1.函数

函数是function关键字声明的函数,也就是fun函数本身,也叫做构造函数。在创建函数fun的时候,也会自动为它创建一个prototype属性,这个属性的作用就是用来指向函数原型(原型对象)。prototype可以理解为fun函数的一个属性,保存着原型函数的引用。

function fun(){

}

2.函数实例

函数前面加new就是函数的实例化,生成的就是函数实例。f就是通过new fun()得到的函数实例。f的内部会有一个包含函数原型的指针[[prototype]],这时候f可以调用函数原型的属性和方法。但是[[prototype]]是内部属性,无法直接访问。

var f = new fun();

那要什么解决这个问题呢?当然有方法可以访问内部属性。以下提供了两种方法

//_proto_:部分浏览器提供了此属性去访问[[prototype]]属性的值
//通过Object.getPrototypeOf去获取

3函数原型对象

函数原型对象有一个constructor的属性,这个属性指向包含一个指向prototype属性的所在函数的指针(f函数)

attention:



  • 每一个构造函数都有一个原型对象prototype,原型对象上包含着构造函数的指针constructor,而函数实例都包含着一个指向原型对象的的内部指针_ proto _。实例可以通过内部指针访问到原型对象,原型对象可以通过constructor找到构造函数。



  • 对象没有prototype属性,只有方法才有prototype。



  • 任何对象都有一个原型对象,任何对象都有一个constructor属性,指向创建此对象的构造函数,{}对象,它的构造函数是function Object(){};



在这里插入图片描述


二、class继承



  • ES6之前

function Student(name){
this.name = name;
}
//给student新增一个方法
Student.prototype.hello = function(){
alter('hello');
}


  • ES6引入class关键字,直接定义一个类(属性、方法)

class Student{
constructor(name){
this.name = name;
}
hello(){
alter('hello');
}
}

三、原型链

引用对象属性:首先在对象(instance)的内部寻找该属性,找不到再去该对象的原型(instance.prototype)里去找这个属性

如果让原型对象指向另一个类型的实例 constructor1.prototype = instance2

试图引用constructor1构造的实例instance1的某个属性p1:

(1)在instance内部属性寻找一次

(2)在instance1.proto(construct1.prototype)中找一遍,但是constructor1.prototype = instance2,也就是说在instance2中找属性p1

(3)如果在instance2中没有找到,它会继续在instance2.proto(constructor2.prototype)中寻找…直至Object的原型对象

因为搜索的轨迹像一条长链:搜索轨迹: instance1–> instance2 –> constructor2.prototype…–>Object.prototype

于是把这种实例与原型的链条叫做原型链

//instance实例通过原型链找到了Father原型中的getFatherValue方法
function Father(){
this.property = true;
}
father.prototype.getFatherValue = function(){
return this.property;
}
function Son(){
this.sonProperty = false;
}
//继承Father
Son.prototype = new Father();//Son.prototype被重写,导致Son.prototype.constructor也一同被重写
Son.prototype.getSonValue = function(){
return this.sonProperty;
}
var instance = new Son();//instance.constructor指向的是Father,因为Son.prototype中的constructor被重写。
alert(instance.getFatherValue());//true

在这里插入图片描述



  • 对象分为普通对象和函数对象,Function、Object是js自带的对象。通过new Function()创建的都是函数对象,其他都是普通对象。普通对象没有prototype,但有__proto__属性。



  • 确定原型和实例的关系



(1)通过操作符 instanceof

在这里插入图片描述

测试结果表明,instance是Object Father Son 中任何一个类型的实例,所以三个构造函数都返回了true

(2)isPrototypeOf()方法,只要原型链中出现过的原型,isPrototypeOf()就会返回true

在这里插入图片描述



  • 原型链的问题

(1)当原型链中包含引用类型值的原型时,该引用类型值会被所有实例所共享。

(2)在创建子类型(Son)的时候,不能向超类型(Father)的构造函数传递参数


怎么解决问题??继承!!!

四、继承


1.借用构造函数(constructor stealing)也叫做经典继承

基本思想:在子类构造函数中调用父类构造函数,可以在子类构造函数中使用call()apply()方法

<script>
function Father(){
this.colors = ["red","blue","green"];
}
function Son(){
Father.call(this);//继承Father,且向父类传递参数
}
//引用类型值独立
var instance1 = new Son();
console.log(instance1.colors);
var instance2 = new Son();
console.log(instance2.colors);
script>

在这里插入图片描述

优点:

(1)可以在子类构造函数中向父类传递参数

(2)父类的引用属性不会被所有实例共享,保证原型链中引用类型值的独立

缺点:

(1)方法都在构造函数中定义,函数复用不可用,超类型(Father)类中定义的方法对子类型而言也是不可见的。因此很少用


2.原型链继承

基本思路:将父类的实例作为子类的原型

<script>
function Father(){
this.value = true;
this.table = {
name:"gaby",
age: 21,
};
}
Father.prototype.getTable = function(){
console.log(this.table);
console.log(this.value);
}
function Son(){};
Son.prototype = new Father();//父类的实例作为子类的原型
let Son1 = new Son();
Son1.table.gender = "man";
Son1.getTable();
let Son2 = new Son();
Son2.getTable();
Son2.value = false;
console.log(Son2.value);
script>

在这里插入图片描述

优点:

(1)父类的方法可以复用

缺点:

(1)父类的属性会被所有的子类所共享,更改一个子类的引用属性,其他子类也会受影响。

(2)子类型实例不能给父类构造函数传参


3.组合继承(伪经典继承)

将原型链和构造函数的技术组合到一起

基本思路:使用原型链实现对原型方法和属性的继承,用构造函数实现对实例的继承

<script>
function Father(name){
this.name = name;
this.colors = ["red","blue","green"];
}
Father.prototype.getName = function(){
alert(this.name);
}
function Son(name,age){
Father.call(this,name);//继承实例属性,1调用Father()
this.age = age;
}
Son.prototype = new Father();//2调用Father()
Son.prototype.getAge = function(){
alert(this.age);
}
var instance1 = new Son("gaby",20);
console.log(instance1.colors);
instance1.getName();
instance1.getAge();
var instance1 = new Son("prada",21);
console.log(instance1.colors);
instance1.getName;
instance1.getAge;
script>

在这里插入图片描述

优点:

(1)js常用的继承模式,instanceof()和isPrototypeOf()也能用于识别基于组合继承创建的对象。

(2)父类的方法可以复用

(3)可以在instance构造函数中向Father构造函数传递参数

(4)父类构造函数中的引用属性不会被共享

缺点:

(1)调用两次父类,造成不必要的内耗


4.原型式继承

借助原型基于已有的对象创建新的对象,同时还不必因此创建自定义类型。对参数对象的一种浅复制

基本思路:在object()函数内部,先构建一个临时性的函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。

<script>
function objectCopy(obj){
function f(){};
f.prototype = obj;
return new f();
}
let person = {
name:"gaby",
age:20,
friends:["mike","amy","linda"],
setName:function(){
console.log(this.name);
}
}
let person1 = objectCopy(person);
person1.name = "xiaoming";
person1.friends.push("adam");
person1.setName();
let person2 = objectCopy(person);
person2.name = "xiaohong";
person2.friends.push("lily");
person2.setName();
console.log(person.friends);
script>

在这里插入图片描述

优点:

(1)父类的方法可以复用

缺点:

(1)父类的引用会被子类所共享

(2)子类不能向父类传参

ES5新增object.create() 方法规范化了上面的原型式继承.

object.create() 接受两个参数,一是用作新对象原型的对象,二是(可选的)一个为新对象定义额外属性的对象


5.寄生式继承

基本思路:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象。使用原型式继承对一个目标对象进行浅复制,增强这个浅复制的能力。

<script>
function objectCopy(obj){
function f(){};
f.prototype = obj;
return new f();
}
function createPerson(original){
let clone = objectCopy(original);//通过调用objectCopy函数创建一个新对象
clone.getName = function(){
console.log(this.name);//以某种方式增强新对象
}
return clone;
}
let person = {
name:"gaby",
friends:["lily","tom","mike"]
}
let person1 = createPerson(person);
person1.friends.push("brone");
console.log(person1.friends);
person1.getName();
let person2 = createPerson(person);
console.log(person2.friends);
script>

在这里插入图片描述

缺点:函数不能复用


6.寄生式组合继承

组合继承缺点:无论什么情况都会调用两次父类构造函数,一次是在创建子类原型的时候,另一次是在子类构造函数内部

寄生式组合继承出现就是为了解决这个问题,减低调用父类构造函数。

基本思路:不必为了指定子类型函数的原型而去调用超类型的构造函数

<script>
function objectCopy(obj){
function f(){};
f.prototype = obj;
return new f();
}
function inheritPrototype(Son,Father){
let prototype = objectCopy(Father.prototype);//创建对象
prototype.constructor = Son;//增强对象
Son.prototype = prototype;//赋值对象
}
function Father(name){
this.name = name;
this.friends = ["lily","tom","mike"]
}
Father.prototype.sayName = function(){
console.log(this,name);
}
inheritPrototype(Son,Father);
Son.prototype.sayAge = function(){
console.log(this.age);
}
let Son1 = new Son("gaby",20);
Son1.sayName();
Son1.sayAge();
Son1.friends.push("brone");
console.log(Son1.friends);
let Son2 = new Son("gaby",20);
Son2.sayName();
Son2.sayAge();
Son2.friends.push("brone");
console.log(Son2.friends);
script>

在这里插入图片描述


五、碎碎念

这一部分的内容是前端javascript面试高频考点,要好好理解一下,向下扎根。



推荐阅读
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
author-avatar
饰间人爱642_370
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有