0

0

浅析JavaScript的继承和原型链

WBOY

WBOY

发布时间:2022-02-15 17:44:33

|

2261人浏览过

|

来源于CSDN

转载

本篇文章给大家带来了javascript中继承和原型链的相关知识,其中包括构造函数、原型以及class语法糖的相关问题,希望对大家有帮助。

浅析JavaScript的继承和原型链

浅析JavaScript的继承和原型链

一、前言

JavaScript的继承和原型链是我在学习前端过程中遇到的较为少有的难以理解的部分,这里便将我所有知道和了解到的东西记录了下来,希望能够给还在这里面苦苦挣扎的兄弟萌一点点小的帮助,也欢迎各位大佬批评指正。

二、构造函数

2.1 构造函数的实例成员和静态成员

构造函数由实例成员和静态成员二者组成,其中实例成员是在函数内部通过this关键字添加的成员;只能通过实例化对象以后通过实例化对象进行访问;而静态成员是函数本身上添加的成员,只能通过构造函数来访问。

//创造一个构造函数let Father = function(name,age){
    //实例成员
    this.name = name;
    this.age = age;
    this.method = "我是一个实例成员";}
    //静态成员Father.like = "mother";
    //检验实例对象是否能够被构造函数直接访问console.log(Father.method);
    //undefinedconsole.log(Father.like);
    //mother
    //实例化一个对象let father = new Father("小王",27);
    //检验静态对象是否能够被实例化对象访问console.log(father.name);
    //小王console.log(father.age);
    //27console.log(father.like);
    //undefined

2.2 实例化对象的过程

通过new关键字可以通过构造函数实现一个实例化对象,那么在具体实例化的过程中发生了什么呢?大致可以划分为以下几个步骤:

立即学习Java免费学习笔记(深入)”;

(1) 创建一个空对象 son {}

(2) 为 son 准备原型链连接 son.__proto__ = Father.prototype

(3) 重新绑定this,使构造函数的this指向新对象 Father.call(this)

(4) 为新对象属性赋值 son.name

(5) 返回this return this,此时的新对象就拥有了构造函数的方法和属性了

一个小问题:所有实例化对象的方法都是共享的吗?

构造函数的方法分为两种,第一种为在函数内部直接定义的方法,第二种为通过原型添加的方法;

//函数内部直接定义的方法let Father = function(){
    this.read = function(){
        console.log("我是内部定义的read方法!");
    }}//通过原型链添加的方法Father.prototype.look = function(){
    console.log("我是通过原型链定义的look方法!");}
    //实例化对象进行检验let father1 = new Father();let father2 = new Father();
    father1.read();
    //我是内部定义的read方法!father2.read();
    //我是内部定义的read方法!console.log(father1.read === father2.read);
    //falsefather1.look();
    //我是通过原型链定义的look方法!father2.look();
    //我是通过原型链定义的look方法!console.log(father1.look === father2.look);
    /true

可以发现,函数内部直接定义的方法在每实例化一个新的对象以后,都会给这个方法分配一个新的内存空间,而通过原型添加的方法便会共享一个空间。

一个小问题:所有实例化对象的属性都是共享的吗?

不存在内存空间的问题,判断时看其值是否相同;

let Father = function(name){
    this.name = name;}let father1 = new Father("小王");
    let father2 = new Father("小红");
    console.log(father1.name === father2.name);
    //falselet father1 = new Father("小王");
    let father2 = new Father("小王");
    console.log(father1.name === father2.name);
    //true

因此我们可以总结一下定义构造函数的基本规则,即公共属性定义到构造函数里面,公共方法我们放到原型对象身上。

三、原型

3.1 什么是原型

Father.prototype 就是原型,它是一个对象,也可以称为原型对象。

3.2 原型的作用是什么

原型的作用,就是共享方法。

我们通过 Father.prototype.method 可以共享方法,不会反应开辟空间存储方法。

3.3 原型中的this指向哪儿

原型中this的指向是实例。

Shakespeare
Shakespeare

一款人工智能文案软件,能够创建几乎任何类型的文案。

下载

四、原型链

原型链本人感觉是一个对于初学者或者说是部分前端菜鸡(例如本人)来说特别难以理解的东西,为了让下面的部分更容易理解,这里强行先记住以下几点:

  1. __proto__是每个对象都有的属性,prototype是每个函数特有的方法;
  2. 每个对象的__proto__属性都会指向自身构造函数的prototype;
  3. constructor属性始终指向创建当前对象的构造函数;
  4. Function.__proto__ === Function.prototype;
  5. Object.prototype.__proto__ === null 也就是原型链的终点;

4.1 什么是原型链

原型与原型层层相链接的过程即为原型链。

4.2 原型链的应用

对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在每个对象都有__proto__原型的存在

let Father = function(name){
    this.name = name;}let father = new Father("老王");console.log(father.__proto__ === Father.prototype);
    //true
    //验证上述说法中的第二条

4.3 原型链图

原型链

结合写在最前面的几点,理解上图应该问题不大了,图中圈起来的部分就是骇人听闻的原型链。

4.4 原型链的查找方式

function Star(name) {
	this.name = name;
	//(1)首先看obj对象身上是否有dance方法,如果有,则执行对象身上的方法
	this.dance = function () {
		console.log(this.name + '1');
	}}//(2)如果没有dance方法,就去构造函数原型对象prototype身上去查找dance这个方法。Star.prototype.dance = function () {
	console.log(this.name + '2');};
	//(3)如果再没有dance方法,就去Object原型对象prototype身上去查找dance这个方法。Object.prototype.dance = function () {
	console.log(this.name + '3');};
	//(4)如果再没有,则会报错。let obj = new Star('小红');obj.dance();

(1)首先看obj对象身上是否有dance方法,如果有,则执行对象身上的方法。

(2)如果没有dance方法,就去构造函数原型对象prototype身上去查找dance这个方法。

(3)如果再没有dance方法,就去Object原型对象prototype身上去查找dance这个方法。

(4)如果再没有,则会报错。

一个小问题:在原型上添加方法需要注意的地方

有两种添加方法,第一种为上面的写法,直接通过 构造函数.prototype.方法名 进行添加;第二种为重定义构造函数的prototype,但是此种情况会丢失掉原有的constructor构造器,所以一定要再连接回去,例子如下:

function Star(name) {
	this.name = name;}Star.prototype = {
    dance:function(){
    	console.log("重定义prototype");
	}}Star.prototype.constructor = Star;

另外,类似于Array、String这些内置的类是不能这么处理的。

五、继承

这里就长话短说,首先我们要明确继承需要继承哪些东西,在前文中我们提到了定义构造函数的基本规则,即**公共属性定义到构造函数里面,公共方法我们放到原型对象身上。**我们所需要继承的东西也不外乎就这二者,公共属性的继承可以通过call()或者apply()进行this的指向定义,而公共方法可以通过原型对象的赋值进行处理,因此我们很容易想到如下的方法:

//定义一个父类function Father(name) {
	this.name = name;}Father.prototype.dance = function () {
	console.log('I am dancing');};//定义一个子类function Son(name, age) {
	Father.call(this, name);
	this.age = age;}//通过赋值的方法连接Son.prototype = Father.prototype;//为子类添加方法Son.prototype.sing = function () {
	console.log('I am singing');};
	let son = new Son('小红', 100);
	//此时父类也被影响了console.log(Father.prototype) 
	//{dance: ƒ, sing: ƒ, constructor: ƒ}

很显然,当我们只想修改子类里面的方法时,显然上述方法不太合适;因此 我们可以尝试new一个新的父类出来,代码如下:

function Father(name) {
	this.name = name;}Father.prototype.dance = function () {
	console.log('I am dancing');};function Son(name, age) {
	Father.call(this, name);
	this.age = age;}Son.prototype = new Father();Son.prototype.sing = function () {
	console.log('I am singing');};let son = new Son('小红', 100);console.log(Father.prototype) 
	//{dance: ƒ, constructor: ƒ}

六、class语法糖

对于以前了解过面向对象编程的程序员来讲,上述关于继承的写法属实让人有些难以接受,因此在es6里面新增了一个语法糖来更方便更便捷地书写继承,这里就直接上代码了;

class Father {
	constructor(name) {
		this.name = name;
	}
	dance() {
		console.log("我是" + this.name + ",我今年" + this.age + "岁," + "我在跳舞");
	}}class Son extends Father {
	constructor(name, age) {
		super(name);
		this.age = age;
	}
	sing() {
		console.log("我是" + this.name + ",我今年" + this.age + "岁," + "我在唱歌");
	}}let obj = new Son('小红', 19);
	obj.sing();obj.dance();

分析一下上面代码,首先一个类(构造函数)里面依旧为两部分,即公共属性和公共方法,constructor() 里面存放了该构造函数的公共属性,后面接着的便是公共方法,extends 关键字表示继承的是哪个类,super() 便是将里面父类里面相应的公共属性拿出来,这样看下来便可以将代码规整许多。

相关推荐:javascript学习教程

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

553

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

374

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

731

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

477

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

394

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

990

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

656

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

551

2023.09.20

Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

4

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.6万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.2万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号