JS原型、原型链和7种继承方法【白话文讲解】

article/2025/9/13 1:20:33

前言

        在学习JS原型、原型链和继承之前,我们必须先弄懂三个W,也就是我们常说的“学习三问”

            学习三问:

                1.它是什么?(What)

                2. 为什么用它?(Why)

                3. 什么时候用它?(When)       

        带着这三个问题去思考下面将要所学的知识,相信你一定会明白今后所学的每个知识点!!

友情提醒:学会某个知识点后一定要反复多敲几遍相关知识的代码,看懂和敲懂真的是两回事!

正文

小编先在这里简单概述一下构造函数

构造函数

                function Person(name,age){this.name=name;this.age=age;this.sex='男';return this;//每个函数都会有个默认return返回值属性,这里不写return意思就是默认返回值是this}var person = new Person('xiaoming'18);//创建一个实例对象var person2= new Person('xiaohong',16)//创建多个实例对象

大写字母开头一般都是构造函数:function Person(){}

new的过程:

        1.var person = new Person('a',123)=>将参数传进去,函数中的 this 会变成空对象

        2.this.name = name;this.age = age;this.sex= '男' 为赋值;return this 为实际的运行机制

        3.return 之后赋值给 person,person具备了 person.name = xiaoming、person.age = 18、person.sex= '男'

        4. 继续往下执行person2其属性值也是一样根据实参传递 

new总结: 

         1. 在构造函数中创建一个空对象

         2. 函数的this指向空对象,并且将这个对象的隐式原型(__proto__)指向构造函数的原型(prototype)

         3. 逐行执行代码

         4. 隐式返回这个对象

扩展知识:

        var o ={}=>var o = new Object()的语法糖    因为o的构造函数就是Object函数,在JS底层其实已经帮我们自动new(实例化)了一遍

        数组以及自定义函数也是一样的

        注意:

  •         所有原始数据类型(Number、String....)以及引用数据类型最终都是由Function衍生而来的,而Function是由JS引擎自带产生的
  •         而所有的构造函数原型的隐式原型都来源于Object构造函数原型,所以这就是我们常说的万物皆对象的原因

        

1.原型prototype

        (1)原型的定义:每个函数都有一个属性——prototype,在默认情况下,prototype的值就是一个普通的object对象(属性的集合),它有一个默认叫做constructor的属性,而constructor是用来指向这个函数本身(可以理解为函数的来源)。

        (2)原型的使用:当一个函数被用作构造函数来创建实例时,这个函数的prototype属性值会被作为原型赋值给所有对象实例(也就是设置 实例的__proto__属性),也就是说,所有实例的原型引用的是函数的prototype属性

        注意:

                1.prototype是函数(构造函数)的属性

                2.__proto__是对象的属性

Person.prototype.name = "man";//在Person构造函数的原型上创建一个name属性并赋值
Person.prototype.say = function(){//在Person构造函数的原型上创建一个say方法console.log("hello");
}
function Person(){}//创建一个构造函数
var person = new Person();//创建一个实例对象=>设置实例对象的__proto__属性(实例对象的原型链也就产生了)
console.log(person.name);// man
person.say();// hello

        (3)consturctor 构造函数

                定义:此属性只有原型对象才有,它默认指回prototype属性所在的构造函数

2.隐式原型 __proto__

       定义:所有对象上都有的一个属性,叫做隐式原型,__proto__,它指向创建该对象的构造函数的原型(prototype) ,即fn._proto_ === Fn.prototype      

        

   用法: 

  •       当访问一个对象的成员是,如果该对象中存在这个属性,直接使用
  •       如果该属性不存在对象中,沿着该对象的隐式原型去查询
var obj = {a:1,b:2
}
obj.__proto__.c = 123;//往obj隐式原型直接添加c属性并赋值
console.log(obj.c)//123

注意:

       1. 每个构造函数原型的隐式原型都指向Object.prototype,但是Object.prototype一个特例——它的__proto__指向的是null,切记切记!!!(null其实就是JS给的一个出口值)

       2.Function.__proto__指向自身Function.prototype,因为上文提到所有构造函数都是由大的Function创建的,既然Function是函数,当然指向的是其本身了

下面我将上文总结一张图,便于知识的整体梳理:(图片来源于掘金网技术社区)

f:代表自定义函数        

 3.原型链     

             定义:原型链即是隐式原型 。

              为什么说原型链就是隐式原型,相信看完上图结构你已经有所明白了!

              因为每个对象和原型都有隐式原型,对象的隐式原型指向创建该对象的构造函数的原型对象,而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链。

其实原型链的介绍就是隐式原型的介绍,下面我将附上几张图供大家作为原型链总结的参照

 1.函数是通过Function创建的

2.每个函数都有原型对象

 3.普通对象都是通过new 函数创建的

4.隐式原型的指向 

5. 原型中的constructor指向函数本身

 6.一条原型链的全貌

扩展知识:

  1. JavaScript中非常重要的两条链

  2. 原型链的查找属性是从下往上的过程,作用域链的查找是从上往下的过程

  3. 作用域链中找不到出现报错,原型链找不到不会报错,出现undefined

4.原型的应用

        1.基础方法:w3c不推荐直接使用系统属性                

1.Object.getPrototypeOf(对象) 获取对象的隐式原型                

        注意:

               ○  对象.__proto__   

               ○  [[scopes]] 、 __proto__ 系统属性不能被直接调用

2.Object.prototype.isPrototypeOf(指定对象) 判断当前对象是否在指定的对象的原型链上

3.对象 instanceof 函数:用来判断函数的原型在不在对象的原型链上 

4.Object.Create(对象) :创建一个空对象,并且将对象的隐式原型修改为指定的参数

        注意:在创建一个对象时,可以修改隐式原型,但是不要修改为null

5.Object.prototype.hasOwnProperty(属性名):判断某个对象属性名是不是对象自身的

       2.将伪数组转换为真数组

                ○  Array.isArray(伪数组)
                ○  Array.prototype.slice.call(伪数组)

        3.判断一个数组是不是真数组的常用方法

1.用 getPrototypeOf 方法获取对象的隐式原型 

var obj={};
var arr=[];
console.log(Object.getPrototypeOf(obj)===Array.prototype)//false
console.log(Object.getPrototypeOf(arr)===Array.prototype)//true

2.用 constructor 方法通过对象的构造函数判断

var obj = {};
var arr = [];
console.log(obj.constructor === Array)//false
console.log(arr.constructor === Array)//true

3.用 instanceof 操作符方法

 var obj = {};var arr = [];
console.log(obj instanceof Array); // false
console.log(arr instanceof Array); // true

4.用 Object.prototype.toString 方法来检测对象数据类型

var str = 123456;
//注意:原始类型是没有属性和方法的
//所有的数据在调用toString()时,调用的都是自身构造函数原型上的toString()
str.toString(); //'123456'//所以必须去调用Object.prototype.toString()
console.log(Object.prototype.toString.call(123));//"[object Number]"console.log(Object.prototype.toString.call(function(){}));//"[object Function]"console.log(Object.prototype.toString.call(new Date()));//"[object Date]"console.log(Object.prototype.toString.call([1,2,3]) === '[object Array]');//true

4.继承

        定义:不严格的说继承的本质就是复制,即重新创建一个原型对象,而新类型的实例继承了父原型里的所有属性

在学习继承之前,其实还有一个原型链的问题没讲,而我们即将要学的继承就是去弥补原型链产生的问题的

原型链的问题       

问题一: 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例对象所共享;

问题二: 在创建子类型(例如创建Son的实例)时,不能向超类型(例如Father)的构造函数中传递参数.

总结:所以在我们真正的实践中会很少单独使用原型链,都是运用以下几种继承方法来弥补原型链产生的不足之处。

1.原型链继承 

function A(){this.nums= ["1", "2", "3"];
}
function B(){}//关键点在于此行代码,创建一个A的实例对象,并将该实例对象赋值给B.prototype
//也就是被创建的A对象将继承B原型上所有的属性
B.prototype = new A();
var instance1 = new B();
instance1.nums.push("4");
console.log((instance1.nums)); //"1,2,3,4"var instance2 = new B(); 
console.log((instance2.nums)); //"1,2,3,4"

原型链继承存在的问题:      

问题一:当子类实现继承后,会继承超类型原型上所有的属性,也就是说超类型原型的引用类型属性会被所有的子类访问到,这样继承原型引用类型属性的子类之间就不再具有自己的私有属性了。

问题二:在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数。
 

2. 借用构造函数继承

        为解决原型链中上述两个问题, 我们开始使用一种叫做借用构造函数。它是用来增强子类实例对象,等同于复制一遍超类(父类)的实例给子类。

function Father(){this.colors = ["red","blue","green"];
}
function Son(){Father.call(this);//继承了Father,且向超类型传递参数//扩展知识:如果有函数形参时可以用apply方法//Father.apply(this,arguments); arguments作为一个伪数组存储函数的每一个实参
}
var instance1 = new Son();
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"var instance2 = new Son();
console.log(instance2.colors);//"red,blue,green" 可见引用类型值是独立的

核心代码是Father.call(this),创建子类实例时调用Father构造函数,于是Son的每个实例都会将Father中的属性复制一份。

缺点:      

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法因为超类型的原型中定义的方法对于子类型是独立不可访问的
  • 因此每个方法都必须在构造函数中定义,所以无法实现函数复用,极度影响性能。

 3.组合继承

        也叫伪经典继承,将原型链和借用构造函数的技术组合到一块。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。  

function Father(name){this.name=name;this.colors=['red','yellow','pink'];
}
Father.prototype.sayName=function(){alert(this.name);
};
function Son(name,age){Father.apply(this.arguments);//继承Father原型的实例属性,第一次调用Father()this.age=age;
};
Son.prototype=new Father();//继承父类原型的实例方法,第二次调用Father()
Son.prototype.sayAge = function(){alert(this.age);
};
var instance1 = new Son("xm",3);
instance1.colors.push("black");
console.log(instance1.colors);//"red,yellow,pink,black"
instance1.sayName();//xm
instance1.sayAge();//3var instance1 = new Son("xh",10);
console.log(instance1.colors);//"red,yellow,pink"
instance1.sayName();//xh
instance1.sayAge();//10

    组合继承避免了原型链继承和借用构造函数继承的缺点,且融合了它们的优点,也是JS中最常用的继承模式。而组合继承创建的对象也能够被instanceof和isPrototype()检测

虽然组合继承是JS中最常用的继承方式,但是它也存在了一个缺点,就是在使用子类创建实例对象时无论什么情况下都会调用两次超类型的构造函数,并且创建的每个实例中都要屏蔽超类型对象的所有实例属性,造成了不必要的内存消耗。

下面即将要讲的圣杯式继承就有效的解决了此问题,成为了JS最理想的继承模式

 4.传统继承——原型继承

        在object()函数内部, 先创建一个临时性的构造函数, 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。(该方法这是由道格拉斯·克罗克福德所提出来的)

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

function object(o){function F(){}F.prototype = o;return new F();
}

  而此方法返回值是一个引用传入对象的新实例,所以也就存在了某些数据被共享的问题

var Person = {colors : ["red","pink","blue"]
};
var PersonScd = Object(Person);//创建一个PersonScd实例对象,将Person原型上的所有属性赋值给它
PersonScd.colors.push("black");//往PersonScd的colors属性上添加一个值
var PersonTrd = Object(Person);
alert(Person.colors );//"red,pink,blue,black"

缺点:

  • 原型链继承多个实例的引用类型属性会被各个实例之间共享,且存在修改的可能。
  • 无法传递参数

另外,ES5中新增了Object.create()的方法,它能够有效代替传统继承方法。

5.寄生式继承 

        其实就是在原型式继承得到对象的基础上,在内部再以某种方式来增强对象,然后返回构造函数。

function createPerson(original){var clone = Object(original); // 通过调用 object() 函数创建一个新对象clone.sayHi = function(){  // 以某种方式来增强对象alert("hi");};return clone; // 返回这个对象
}

函数的主要作用是为构造函数新增属性和方法,以增强函数

寄生式继承的缺点跟原型式继承一样

6. 寄生组合式继承(圣杯式继承)       

        结合借用构造函数传递参数和寄生模式实现继承,因此圣杯模式成为了JS最理想的继承式方法

function inheritPrototype(subType,superType){var prototype = Object.create(superType.prototype);//创建对象,创建父类原型的一个副本prototype.constructor=subType;//增强对象,弥补因重写原型而失去的默认的constructor 属性subType.prototype=prototype;//指定对象,将新创建的对象赋值给子类的原型
}// 父类初始化实例属性和原型属性
function SuperType(name){this.name = name;this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){alert(this.name);
};// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){SuperType.apply(this, arguments);this.age = age;
}// 将父类原型指向子类
//此行代码等同于组合继承写的 SubType.prototype = new SuperType()
inheritPrototype(SubType, SuperType);// 新增子类原型属性
SubType.prototype.sayAge = function(){alert(this.age);
}

总结:圣杯式继承的核心在于只调用了一次SuperType构造函数,因此避免了在SubType.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变; 因此还能正常使用 instanceof isPrototypeOf() 方法。

7. 混入方式继承多个对象  

function MyClass() {SuperClass.call(this);OtherSuperClass.call(this);
}// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;MyClass.prototype.myMethod = function() {// do something
};

总结:Object.assign会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。

此方法摘自掘金技术社区——‘木易杨说’

关于继承还有一个ES6——extends关键字方法,该方法主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法

具体内容介绍请转自ES6 入门教程

全文总结:不管是学习哪个知识点,我们在学它之前都要带着三个W去学每一个内容,且学完记得多敲些相关的例题,相信你一定能够学有所成!!!


http://chatgpt.dhexx.cn/article/rWTWrBhM.shtml

相关文章

原型链与常用继承方法

原型链:当访问一个对象的属性时,如果该对象内部不存在这个属性,就会去该对象的__proto__ 上(也就是它构造函数的prototype)查找 。该构造函数的prototype上也有一个自己的__proto__ 属性,然后继续向上查找,…

如何用原型链的方式实现一个 JS 继承?

大家好,我是前端西瓜哥。今天讲一道经典的原型链面试题。 原型链是什么? JavaScript 中,每当创建一个对象,都会给这个对象提供一个内置对象 [[Prototype]] 。这个对象就是原型对象,[[Prototype]] 的层层嵌套就形成了…

JS学习笔记 原型链和利用原型实现继承

原型链 原型链是一种关系,实例对象和原型对象之间的关系,关系是通过原型(__proto__)来联系的 实例对象中有__proto__,是对象,叫原型,不是标准的属性,浏览器使用,并且有的游览器不支持构造函数中有prototype属性,也是对象,叫原型 注意 原型中的方法是可…

Arduino基本知识(marlin固件配置)

初识arduino,根据mega2560(某宝可以买到)官网的100个管脚具体控制一句传输进行操作。 https://www.arduino.cc/en/Hacking/PinMapping2560 其管脚图如上所示。 首先在官网下载arduino的配套软件 https://www.arduino.cc/ 对于编程&#xf…

3D打印机硬件驱动-马林固件最新版本2.0.X中文注释(3)marlin 2.0.9.2 截至发稿时间2021年12月16日

/** * Marlin 3D Printer Firmware 头描述详见其他两个文件头描述 * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm * * This program is…

Marlin固件配置

文档来源自http://www.geek-workshop.com/thread-33314-1-1.html 1、基本配置基本配置是可选的,主要是给你的固件起个名字,如果你的配置很牛,让大家知道你是谁。据说这个在启动的时候会显示在显示屏中,应为我没有显示屏,所以无法验证。这个修改也很简单,通过搜索找到“S…

MKS MONSTER8 V1.0使用说明书(基于Marlin 2.0.X固件配置Voron 2.4)

广州谦辉信息科技有限公司 (基于Marlin 2.0.x 固件配置 Voron 2.4) 创客基地QQ群:489095605 232237692 邮箱:Huangkaidamakerbase.com.cn 主板购买链接:https://item.taobao.com/item.htm?spma1z10.5-c-s.w4002-23356668283.43.7eec55caLT…

ESP32烧录Marlin固件

安装platformIO 这点很简单,保证你拥有一个能够成功连接外网的环境即可。内网可能不太稳定有可能安装失败。 克隆代码 我这里采用的是fyset_e4的代码,这个代码开源在了https://github.com/FYSETC/FYSETC-E4,作者已经针对marlin固件做了一些配置。 更改…

i3型3D打印机制作详解——Marlin固件中文介绍

关注微信公众号:嵌入式基地 后台回复:3d打印机 获取资料 硬件框架搭建介绍 https://blog.csdn.net/qq_39020934/article/details/80380250 Marlin固件中文介绍 https://download.csdn.net/download/qq_39020934/10401251 …

3D打印机Marlin固件双Z轴设置

3D打印机Marlin固件双Z轴设置 在3D打印机Marlin固件的最新版本2.1.1中,设置双Z轴和老版本有一些改动。记录一下如何在最新版本的Marlin固件中设置双Z轴。 以MKS GEN_L V2.1的主板为例,硬件连接还是和原来一样,第二个Z轴的电动机连接到空闲的…

Marlin 固件配置手动退换料

换料的步骤首先把喷头加热,软化喷嘴里残余的线材,然后反转挤出机,把线材抽出来。最后装入新线材,并挤出余留在喷头里的材料。整个过程用一个命令 M600 就能完成。默认情况下 Marlin 固件并没有开启这个功能,但是可以修…

MKS_SGEN_L V1.0 marlin 固件编译

1.下载 Visual Studio Code 打开Visual Studio Code 安装插件 在应用商店搜索下载安装如下图: 安装完这些还是不行的 还要另外安装python 3.8.8 其他版本python 不行右下角会报错,提示安装python 3.8 python 3.8.8 百度网盘下载链接:https://pan.bai…

3D打印机硬件驱动-马林固件最新版本2.0.X中文注释(1)marlin 2.0.9.2 截至发稿时间2021年12月16日

马林固件最新版本翻译注释 /* Marlin Firmware 马林固件 (c) 2011-2020 MarlinFirmware Portions of Marlin are (c) by their respective authors. 马林部分程序来源于世界各地的开发者 All code complies with GPLv2 and/or GPLv3 所有源码依靠GPLv2 和 GPLv3架构编写…

Marlin固件学习总结(一)

接触过3D打印也有一段时间了,一直没有将学到的知识以文本的形式记录下来。现在也没有太多时间继续玩这个了,因此想慢慢把之前所接触到所学到的知识通过文本的形式记录一下,也分享给那些感兴趣的人。 既然是开篇我们先了解一下marlin固件的结构…

Marlin固件之二:源代码详解与移植

由于需要进行固件定制化,Marlin固件太过于强大和紧凑,我对这个固件进行了裁剪,只剩下主枝干,实现功能的定制和裁剪。以下的代码详解是基于我已经移植在stm32上面的一个程序进行的。

Marlin固件之—:基础入门与测试

一、Marlin的简单介绍 Marlin固件是一个3D打印的开源固件,3D打印固件有许多,Marlin最为健全和强大,当然相对也会复杂一些。使用Gcode控制爱,Gcode是数控机床等工控控制使用范围较广的一种指令协议。在这里介绍一些Marlin的入门经…

Marlin固件介绍

目录 什么是Marlin? 主要特点 Marlin如何工作 打印东西 建模 …

marlin2.0.x 固件相关配置文档说明

主要目的 了解对应参数的作用,以优化3D打印机的打印效果 具体分析 配置文件有两个 Configuration.h 包含硬件核心、语言和控制器的设置,以及最常见的功能和组件的设置,主要配置的地方。 Configuration_adv.h 提供更详细的自定义选项&…

杨辉三角形--2021蓝桥杯Java组

杨辉三角形–2021蓝桥杯Java组 题目描述 下面的图形是著名的杨辉三角形: 如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:1,1,1,1,2,1,1,3,3,1,1,4,6,4,1,⋯ 给定一个正整数 N,请你输出数列中第一次出现…

JAVA杨辉三角形

杨辉三角形 杨辉三角形(java)首先让我们来实现要求一再来看看要求二对于要求三最后一步给三角形前面加上空格 杨辉三角形(java) 首先让我们来看看杨辉三角形的结构: 要求一:有一个数第二层有两个数要求二:每一层第一个跟最后一个数字都是一**要求三:除了数字一以外其他数字等于…