JavaScript Class类 | 类的继承 - 类的使用 -原型与原型链

文章目录

  • JavaScript class类
    • 基础概念
      • 属性与方法相关概念
        • 私有字段
        • 类的name属性 返回类的名字
        • 类的访问器方法
        • super关键字
      • new的过程中发生了什么
      • extends继承 重写-重载
    • 语法细节
      • 类声明与类表达式
        • 补充理解:let和const的作用域提升规则
    • 类的继承
      • 原型与隐式原型链
        • 特殊原型链
      • 原型链继承 - 子类的显式原型是父类的实例
      • 构造函数继承 - 子类构造函数中调用父类构造函数
      • 组合继承 原型链继承+构造函数继承
      • 寄生组合继承
        • 补充:Object.create原理
      • extends 继承原理 (同寄生组合继承)

JavaScript class类

基础概念

  1. 每次new一个实例,constructor方法就会调用一次。
  2. 在函数定义时,会自动往函数添加prototype属性,默认是一个空Object对象,这个对象叫做显式原型对象。
  3. 实例对象的__proto__属性,叫做隐式原型属性/隐式原型,实例化对象时自动添加的,实例对象的隐式原型=构造函数的显式原型
  4. 原型对象有一个constructor属性,指向函数对象
  5. class可以看作构造函数的一个语法糖
    class Person{constructor(){}
    }
    console.log(typeof Person);//function
    console.log(Person===Person.prototype.constructor);//true
    

每个类都必须有一个constructor,如果没有显式声明,js 引擎会自动给它添加一个的构造函数。

属性与方法相关概念

概念定义位置使用特点
原型方法constructor外面实例调用定义在构造函数(类)的显式原型prototype
实例方法constructor里面实例调用new一次,调用一次constructorconstructor内部会重新定义实例方法(改变this指到实例),所以实例方法在每个实例上,方法同名但不是同一个。
静态方法static标识符 修饰类来调用方法里的this指向类本身,静态方法可以被子类继承
私有属性/方法#标识符 修饰只能在类的内部访问的方法和属性,外部不能访问私有指的对类私有

ES5相关概念的写法


function A(x){}
A.prototype.show = function(){} // 原型方法// 实例方法
function A(x){this.x = x;  // 实例属性this.show = function(){} // 实例方法
}
私有字段

私有字段包括私有实例字段和私有静态字段

说明
1.私有字段#名称(hash),访问时也需要携带#
2.私有字段在构造器或调用子类的 super() 方法时被添加到类的实例中。

// 可以通过super方法
class ClassWithPrivateField {#privateField;constructor() {this.#privateField = 42; // 添加到类实例中}
}class SubClass extends ClassWithPrivateField {#subPrivateField;constructor() { super(); // 添加到子类的实例中this.#subPrivateField = 23;}
}new SubClass();
// SubClass {#privateField: 42, #subPrivateField: 23}
类的name属性 返回类的名字
class Person {}
Person.name // Person
类的访问器方法

通过gettersetter访问器函数,可以对读写进行拦截操作

class Person {constructor(name) {this._name = name}// 类的访问器方法get name() {return this._name}set name(val) {this._name = val}
}
super关键字

规定:super() 之前不能访问 this

有一种说法解释: 因为构造器是用来对实例初始化的,而子类实例在初始化之前要先初始化它的父类成分。那接下来为什么 JS 语言不可以隐式调用 super,而要交给开发者?因为 JS 是弱类型的,super 调用时的传参没法自动预判,没法代劳。

  • super():在子类构造器中,把super当作一个函数来调用,创建对象并让其执行父类的构造器方法
  • super.propertysuper.method(): 通过super访问超类的原型属性和方法

new的过程中发生了什么

  1. 创建一个空对象,这个空对象就是返回的实例
  2. 类内部的this指向这个空对象
  3. 实例的隐式原型__proto__指向构造函数的显式原型prototype
  4. 执行构造器函数,为实例添加方法或属性
  5. 获取构造器函数执行的结果,如果构造器函数有返回对象,则将其返回。如果没有返回创建的实例。
function myNew(Fn,...args){let obj = {}; //1let obj.__proto__ = Fn.prototype;//2let result = Fn.apply(obj,args);//3return result instanceof Object ? result : obj;//4
}

extends继承 重写-重载

作用:用于创建一个类的子类
说明:父类的.prototype必须是一个 Object 或者 null

重写:同名属性或方法,同名方法就可以了,不需要参数个数

语法细节

类声明与类表达式

  • 类的声明的特点
    • 将类的名称添加到当前作用域中
    • 结尾的大括号不需要加分号
    • 类似letconst的作用域提升规则
  • 类的表达式的特点
    • 不会将类的名称添加到当前作用域中,赋值的结果是一个函数(类的构造函数)
//类的声明
class Class1{}
//类的表达式
let Color = class{};
//类的匿名表达式
let C = class Color2{};
conselo.log(Color2); //Color2 is not defined
console.log(typeof C)//"function"
补充理解:let和const的作用域提升规则

var声明的变量会使声明被提升到顶部,letconst也存在变量提升,只是提升的方式不同。

  • var变量提升:变量的声明提升到顶部,值为undefined
  • letconst变量提升: 变量声明提升到顶部,只不过将该变量标记为尚未初始化
//原代码
function fn(){console.log(answer); //undefinedvar answer=42;
}
//变量提升
function fn(){var answer;//声明提前console.log(answer);  // 值为undefinedanswer=42;
}

let、const的暂时性死区
letconst存在暂时性死区,代码执行过程中的一段时间内,在此期间无法使用标识符,也不能引用外层作用域的变量。

原因是也将声明提升到了顶部,只不过是标记该变量为尚未初始化

let answer;
function fn(){//如果此时没有将变量变量提升到这里,answer应该取外层answer的值// 提升到了这里并标记未尚未初始化console.log(answer); //Uncaught ReferenceError: Cannot access 'answer' before initializationlet answer=42;
}//理解暂时性死区是暂时的与时间相关
function temporalExample(){const f = ()=>{console.log(value)//这里不会报错}let value = 42;f(); //调用时,value已经声明
}

类的继承

继承对于JS来说就是父类拥有的方法和属性、静态方法等,子类也要拥有。

原型与隐式原型链

原型

函数在定义时会自动添加prototype属性显式原型属性,默认指向一个空Object对象,该对象称为显式原型对象。显式原型对象有一个constructor属性,指向构造函数。
实例在创建对象时会自动添加__proto__隐式原型属性,对象隐式原型的值=对应构造函数的显式原型的值

隐式原型链的概念
访问一个对象的属性时
1.现在自身属性中查找,找到返回
2.没有找到,再沿__proto__这条链上找,找到返回
3.最终没找到,返回undefined
原型链的尽头是Object.prototype.__proto__ === null

原型链的作用
1.实现继承
2.数据共享,节约内存空间

特殊原型链

核心:实例的隐式原型指向构造函数显式原型

Function 可以看成是构造函数,也可以看成Function的实例

Function.__proto__ === Function.prototype

Object 可以看成构造函数,也可以看成Function的实例

Object.__ptoto__ === Function.prototype

在这里插入图片描述

原型链继承 - 子类的显式原型是父类的实例

本质:子类的显式原型是父类的实例

function Parent(){this.name = 'parent'this.play = [1,2,3]
}
function Child(){this.name = 'child';
}
Child.prototype = new Parent();//执行Parant构造器
Parent.prototype.id = '1';
let child1 = new Child(); //执行Child    
console.log(child1.name)//child
console.log(child1.id)//1let child2 = new Child();
child1.play[0] = 2;
console.log(child2.play)//[2,2,3]

原型链:子类实例child自身找 -> 子类的__proto__找(这里是父类的实例) -> 子类__proto____proto__找(父类的显式原型)
在这里插入图片描述

  • 优点
    • 共享父类的实例属性/方法和原型上的属性和方法
  • 缺点
    • 父类的引用属性(play)会被所有子类共享,其中一个子类修改,其他子类也会受到影响
    • 子类的实例不能给父类型构造函数传参(Child.prototype = new Parent()这里已经调用了父类构造器方法,全程只调用了一次所以没办法实例化子类的时候动态传参)

构造函数继承 - 子类构造函数中调用父类构造函数

本质:子类构造函数中调用父类构造函数 - 只会继承构造器中的东西

function Parent(name){this.name = namethis.play = [1,2,3]
}
function Child(name){Parent.call(this,name);//执行Parent函数 - 子类的每个实例都会将父类中的属性复制一份。/* this.name = namethis.play = [1,2,3]*/
}
Parent.prototype.id = '1'var child1 = new Child('child'); //---ES5和ES6的区别 先创造子类的实例,执行构造器函数调用 Parent.call(this),继承父类实例的方法console.log(child1.name)//child
console.log(child1.id)//undefined
var child2 = new Child();
child1.play[0] = 2;
console.log(child2.play)//[1,2,3]

Parent.call(this,name) 第一次参数是指定函数Parent里的this 为参数this,name为Parent函数传递的参数

在这里插入图片描述

  • 优点
    • 可以在子类构造器中给父类构造函数传参
    • 父类的引用对象不会共享
  • 缺点
    • 子类访问不了父类显式原型上的方法(从图里看子类和父类比较独立,只能继承构造器里的东西)
    • 子类的实例每实例化一次,父类的构造器都会被调用一次

组合继承 原型链继承+构造函数继承

  1. 继承实例属性:子类的构造函数里调用父类的构造函数,防止父类引用类型被修改
  2. 继承原型上的属性和方法: 将父类的实例作为子类的原型,访问父类原型。
function Parent(name){this.name = namethis.play = [1,2,3]
}
function Child(name){//子类的每个实例都会将父类中的属性复制一份,访问时优先访问自己作用域中的Parent.call(this,name); //调用一次父类构造器
}
//继承原型上的属性和方法
Child.prototype = new Parent();//调用一次父类构造器
Child.prototype.constructor = Child;

这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性
在这里插入图片描述

  • 优点
    • 父类的方法可以复用
    • 可以在子类构造器中向父类构造器传参
    • 父类构造器中的引用属性不会被共享
  • 缺点
    • 调用了两次父类构造器,原型链上会存在两份相同的属性和方法(child实例有,child实例的隐式原型上也有)

寄生组合继承

实例原型链:子类的实例可以拥有父类的方法,通过Son.prototype.__proto__ = Father.prototype
构造器原型链:子类可以拥有父类的静态方法,通过Son.__proto__=Father

/*
1.子类继承父类的属性
*/
function child(name){Person.call(this,name)
}
/*
2.子类可以看见父类的方法
先创建父类的实例
*/
// Object.create创建一个新对象,对象的隐式原型指向参数
Child.prototype = Object.create(Parent.prototype); 
//Object.create创建的是一个新对象,所以需要显式指定constructor属性
Child.prototype.constructor = Child;/*
3.子类可以看见父类的静态方法
*/
Child.__proto__ = Parant;

在这里插入图片描述

补充:Object.create原理

作用:创建一个新对象,新对象的隐式原型指向参数

function create(proto) {function F(){}F.prototype = protoreturn new F()
}

案例

let obj = Object.create({name: 'johan'})

在这里插入图片描述

extends 继承原理 (同寄生组合继承)

核心代码

var Child = function (_Parent) {_inherits(Child, _Parent);function Child(name, age) {// Object.getPrototypeOf(Child) 获取child的隐式原型// Parent.call(this, name, age)var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Child).call(this, name, age));_this.name = name;_this.age = age;return _this;}return Child;
}(Parent);function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

_inherits的逻辑与寄生组合继承相同

  1. 子类可以继承父类原型空间的属性与方法
  2. 子类可以继承父类的静态属性和方法
function _inherits(subClass, superClass) { // 如果有一个不是函数,则抛出报错if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } // 将 subClass.prototype 设置为 superClass.prototype 的实例subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); // Object.setPrototypeOf(obj, ) 指定对象obj的隐式原型为参数2 ,设置subClass的隐式原型为superClassif (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/306987.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++核心编程(一)】

一、内存分区模型 C程序在执行时,将内存大方向划分为4个区域: 代码区:存放函数体的二进制代码,由操作系统进行管理的。 全局区:存放全局变量和静态变量以及常量。 栈区:由编译器自动分配释放,存放函数的…

第52周,第三期技术动态

大家好,才是真的好。 今天周五,我们主要介绍与Domino相关产品新闻,以及互联网或其他IT行业动态等。 一、HCL Domino将重新开发和发布应用市场 为了持续吸引新客户,现有客户以及技术爱好者和专业人士,在2023年的 Col…

6种大模型的使用方式总结,使用领域数据集持续做无监督预训练可能是一个好选择

本文原文来自DataLearnerAI官方网站:6种大模型的使用方式总结,使用领域数据集持续做无监督预训练可能是一个好选择 | 数据学习者官方网站(Datalearner)https://www.datalearner.com/blog/1051703426665726 Sebastian Raschka是LightningAI的首席科学家&…

数字身份验证:跨境电商如何应对账户安全挑战?

在数字化时代,随着跨境电商的蓬勃发展,账户安全问题逐渐成为行业和消费者关注的焦点。随着网络犯罪日益猖獗,用户的数字身份安全面临着更加复杂的威胁。本文将深入探讨数字身份验证在跨境电商中的重要性,并探讨各种创新技术和策略…

前端使用高德api的AMap.Autocomplete无效,使用AMap.Autocomplete报错

今天需要一个坐标拾取器,需要一个输入框输入模糊地址能筛选的功能 查看官方文档,有一个api可以直接满足我们的需求 AMap.Autocomplete 上代码 AMapLoader.load({"key": "你的key", // 申请好的Web端开发者Key,首次调…

XHR与Fetch的功能异同点列表

XHR与Fetch的功能异同点列表

2012年第一届数学建模国际赛小美赛B题大规模灭绝尚未到来解题全过程文档及程序

2012年第一届数学建模国际赛小美赛 B题 大规模灭绝尚未到来 原题再现: 亚马逊是地球上现存最大的雨林,比地球上任何地方都有更多的野生动物。它位于南美洲大陆的北侧,共有9个国家:巴西、玻利维亚、厄瓜多尔、秘鲁、哥伦比亚、委…

哪种猫粮比较好?超能打的5款主食冻干测评

不知道从什么时候开始掀起一股冻干喂养风,各种查资料阅读文献发现冻干喂养是最适合忙碌地打工人的“生骨肉喂养”替代版,是最符合猫咪饮食天性的一种。很多养猫人纷纷开始冻干喂养,但对于主食冻干猫粮的选择就让很多猫奴犯了难在电商平台随便…

Opencascad(C++)-判断直线段与TopoDS_Shape是否相交(干涉)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、根据两个点创建有界直线段3、判断TopoDS_Shape与直线段相交 1、前言 最近在研究Opencascad的开发,有个需求是判断直线与TopoDS_Shpae是否存在…

0开始配置Cartographer建图和导航定位

0开始配置Cartographer 日期:12-19 硬件:激光雷达IMU 小车的tf变换: 建图配置 lua文件配置:my_robot.lua include "map_builder.lua" include "trajectory_builder.lua"options {map_builder MAP_BUILDE…

Pycharm 切换interpreter---python的环境和第三方库问题

这篇回答两个问题: 1.为什么在 pycharm中打开新的project,切换interpreter 之后发现自己之前装的库消失了? 2.为什么 interpreter 切换到python3.8了, terminal 还是在 3.9?? 问题的关键:搞懂什…

概率论相关题型

文章目录 概率论的基本概念放杯子问题条件概率与重要公式的结合独立的运用 随机变量以及分布离散随机变量的分布函数特点连续随机变量的分布函数在某一点的值为0正态分布标准化随机变量函数的分布 多维随机变量以及分布条件概率max 与 min 函数的相关计算二维随机变量二维随机变…