热门标签 | 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面试高频考点,要好好理解一下,向下扎根。



推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
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社区 版权所有