首页IT科技javascript中的基本数据类型(JavaScript:类(class))

javascript中的基本数据类型(JavaScript:类(class))

时间2025-08-04 15:27:49分类IT科技浏览4285
导读:在JS中,类是后来才出的概念,早期创造对象的方式是new Function( 调用构造函数创建函数对象;...

在JS中               ,类是后来才出的概念                    ,早期创造对象的方式是new Function()调用构造函数创建函数对象;

而现在        ,可以使用new className()构造方法来创建类对象了;

所以在很多方面            ,类的使用方式                    ,很像函数的使用方式:

但是类跟函数            ,还是有本质区别的        ,这在原型那里已经说过                    ,不再赘述;

如何定义一个类

如下所示去定义一个类:

class className { // 属性properties property1 = 1; property2 = []; peoperty3 = {}; property4 = function() {}; property5 = () => {}; // 构造器 constructor(...args) { super(); // code here }; // 方法methods method1() { // code here }; method2(...args) { //code here }; }

可以定义成员属性和成员方法以及构造器               ,他们之间都有封号;隔开;

在通过new className()创建对象obj的时候    ,会立即执行构造器方法;

属性会成为obj的属性                     ,句式为赋值语句                  ,就算等号右边是函数,它也依然是一个属性                  ,注意与方法声明语句区别开;

方法会成为obj的原型里的方法                     ,即放在className.prototype属性里;

像使用function一样使用class关键字

正如函数表达式一样    ,类也有类表达式:

还可以像传递一个函数一样               ,去传递一个类:

这在Java中是不可想象的                    ,但是在JS中        ,就是这么灵活;

静态属性和静态方法

静态属性和静态方法            ,不会成为对象的属性和方法                    ,永远都属于类本身            ,只能通过类去调用;

定义语法

// 直接在类中        ,通过static关键字定义 class className { static property = ...; static methoed() {}; } // 通过类直接添加属性和方法                    ,即为静态的 class className {}; className.property = ...; className.method = function() {};

调用语法

类似于对象调用属性和方法               ,直接通过类名去调用

className.property; className.method();

静态属性/方法    ,可以和普通属性/方法同名                     ,这不会被弄混                  ,因为他们的调用者不一样,前者是类                  ,后者是类对象;

私有属性和私有方法

JS新增的私有特性                     ,在属性和方法之前添加#号    ,使其只在类中可见               ,对象无法调用                    ,只能通过类提供的普通方法去间接访问;

定义和调用语法

class className { // 定义        ,添加#号 #property = ...; #method() {}; // 只能在类中可见            ,调用也需要加#号 getProperty() { return this.#property; } set property(value) { this.#property = value; } }

注意                    ,#property是一个总体作为属性名            ,与property是不同的        ,#method同理;

在这个私有特性之前                    ,JS采用人为约定的方式               ,去间接实现私有;

在属性和方法之前添加下划线_    ,约定这样的属性和方法                     ,只能在类中可见                  ,只能靠人为遵守这样的约定;

类检查instanceof

我们知道,可以用typeof关键字来获取一个变量是什么数据类型;

现在可以用instanceof关键字                  ,来判断一个对象是什么类的实例;

语法obj instanceof className                     ,会返回一个布尔值:

如果className是obj原型链上的类    ,返回true; 否则               ,返回false;

它是怎么去判断的呢?假设现在有如下几个类:

class A {}; class B extends A {}; class C extends B {}; let c = new C();

c的原型是C.prototype;

C.prototype的原型是B.prototype;

B.prototype的原型是A.prototype;

A.prototype的原型是Object.prototype;

Object.prototype的原型是null;

原型链如上所示;

当我们执行c instanceof A的时候                    ,它是这样的过程:

c.__proto__ === A.prototype?否        ,则继续;

c.__proto__.__proto__ === A.prototype?否            ,则继续;

c.__proto__.__proto__.__proto__ === A.prototype?是                    ,返回true;

如果一直否的话            ,这个过程会持续下去        ,直到将c的原型链溯源到null                    ,全都不等于A.prototype               ,则返回false;

也就是说    ,instanceof关键字                     ,比较的是对象的原型链上的原型和目标类的prototype是否相等(原型和prototype里有constructor                  ,但是instanceof不会比较构造器是否相等,只会比较隐藏属性[[Prototype]]);

静态方法Symbol.hasInstance

大多数类是没有实现静态方法[Symbol.hasInstance]的                  ,如果有一个类实现了这个静态方法                     ,那么instanceof关键字会直接调用这个静态方法;

如果类没有实现这个静态方法    ,那么则会按照上述说的流程去检查;

class className { static [Symbol.hasInstance]() {}; }

objA.isPrototypeOf(objB)

isPrototypeOf()方法               ,会判断objA的原型是否处在objB的原型链中                    ,如果在则返回true        ,否则返回false;

objA.isPrototypeOf(objB)就相当于objB instanceof classA;

反过来            ,objB instanceof classA就相当于classA.prototype.isPrototypeOf(objB);

继承

我们知道                    ,JS的继承            ,是通过原型来实现的        ,现在结合原型来说一下类的继承相关内容              。

关键字extends

JS中表示继承的关键字是extends                    ,如果classA extends classB               ,则说明classA继承classB    ,classA是子类                     ,classB是父类;

原型高于extends

时刻记住                  ,JS的继承,是依靠原型来实现的;

关键字extends虽然确立了两个类的父子关系                  ,但是这只是一开始确立子类的父原型;

但是父原型是可以中途被修改的                     ,此时子类调用方法    ,是沿着原型链去寻找的               ,而不是沿着子类父类的关键字声明去寻找的                    ,这和Java是不一样的:

如图所示        ,C extends A确立了C一开始的父原型是A.prototype            ,c.show()调用的也是父类A的方法;

但是后面修改c的父原型为B.prototype                    ,c.show调用的就不是父类A的方法            ,而是父原型的方法;

也就是说        ,原型才是核心                    ,高于extends关键字;

基类和派生类

class classA {}; class classB extends classA {};

像classA这样没有继承任何类(实际上父原型是Object.prototype)的类称为基类;

像classB这样继承classB的类               ,称为classB的派生类;

为什么要分的这么细    ,是因为在创建类时                     ,他们两个的行为不同                  ,后面会说到;

类的原型

类本身也是有原型的,就像类对象有原型一样;

可以看到                  ,B的原型就是其父类A                     ,而A作为基类    ,基类的原型是本地方法;

正因如此               ,B可以通过原型去调用A的静态方法/属性;

也就是说                    ,静态方法/属性        ,也是可以继承的            ,通过类的原型去继承;

类对象的原型和类的prototype属性

在创建类对象的时候                    ,会将类的prototype属性值复制给类对象的原型;

所以说            ,类对象的原型等于类的prototype属性值;

而类的prototype属性        ,默认就有两个属性:

构造器constructor:指向类本身; 原型[[Prototype]]:指向父类的prototype属性;

以及

类的普通方法;

从上图中可以看出                    ,A的prototype属性里               ,除构造器和原型以外    ,就只有一个普通方法show();

这说明                     ,只有类的普通方法                  ,会自动进入类的prototype属性参与继承;

也就是说,一个类对象的数据结构                  ,如下:

普通属性 (原型)prototype属性 构造器 父类的prototype属性(父原型) 方法

另外                     ,类的prototype属性是不可写的    ,但是类对象的原型则是可以修改的;

继承了哪些东西

当子类去继承父类的时候               ,到底继承到了父类的哪些东西                    ,也即子类可以用父类的哪些内容;

从结果上来看        ,我们可以确定如下:

子类继承父类的静态属性/方法(基于类的原型); 子类对象继承父类的普通方法和构造器(基于类的prototype); 子类直接将父类的普通属性作为自己的普通属性(普通属性不参与继承);

由于原型链的存在            ,这些继承会一路沿着原型链回溯                    ,继承到所有祖宗类;

同名属性的覆盖

由于继承的机制            ,势必子类和父类可能会有同名属性的存在:

从结果上可以看到        ,虽然子类直接将父类的普通属性作为自己的普通属性                    ,但是当出现同名属性               ,属性值会进行覆盖    ,最终的值采用子类自己定义的值;

同名方法的重写

与属性一样                     ,子类和父类也可能会出现同名方法;

当然大多数情况下                  ,是我们自己要拓展方法功能而故意同名,从而重写父类的方法;

如上所示                  ,我们重写了父类的静态方法和普通方法;

如果是重写构造器的话                     ,分两种情况:

// 基类重写构造器 class A { constructor() { code... } } // 派生类重写构造器 class B extends A() { constructor() { // 一定要先写super() super(); code... } }

子类的调用顺序

从上图还可以看出来    ,子类调用方法的顺序:

先从自己的方法里调用               ,发现没有可调用的方法时; 再沿着原型链                    ,先从父类开始寻找方法        ,一直往上溯源            ,直到找到可调用的方法                    ,或者没有而出错;

super关键字

类的方法里            ,有一个特殊的              、专门用于super关键字的特殊属性[[HomeObject]]        ,这个属性绑定super语句所在的类的对象                    ,不会改变;

而super关键字               ,则指向[[HomeObject]]绑定的对象的类的父类的prototype;

这要求    ,super关键字用于派生类类的方法里                     ,基类是不可以使用super的                  ,因为没有父类;

当我们使用super关键字时,借助于[[HomeObject]]                  ,总是能够正确重用父类方法;

如上                     ,super语句所在的类为B    ,其对象为b               ,即[[HomeObject]]绑定b;

而super则指向b的类的父原型                    ,即A的prototype属性;

而super.show()就类似于A.prototype.show()        ,故而最终结果如上所示;

可以简单理解成            ,super指向子类对象的父类的prototype

构造器constructor

终于说到构造器了                    ,理解了构造器的具体创建对象的过程            ,我们就能理解关于继承的很多内容了;

先来看一下基类的构造器创建对象的过程:

执行let a = new A()时        ,大致流程如下:

首先调用A.prototype的特性[[Prototype]]创建一个字面量对象                    ,同时this指针指向这个字面量对象; 然后执行类A()的定义               ,A定义的普通属性成为字面量对象的属性并初始化    ,A.prototype的value值复制给字面量对象的隐藏属性[[Prototype]]; 然后再执行constructor构造器                     ,没有构造器就算了; 返回this指针给变量a                  ,即a此时引用该字面量对象了;

从结果上看,在执行构造器时                  ,字面量对象就已经有原型了                     ,以及属性name    ,且值初始化为tomA;

然后才对属性name重新赋值为jerryA;

然而               ,构造器中对属性的重新赋值                    ,从一开始就决定好了        ,只是在执行到这句赋值语句之前            ,暂存在字面量对象中;

现在再来看一下派生类创建对象的过程;

执行let b = new B()的大致流程如下:

首先调用B.prototype的特性[[Prototype]]创建一个字面量对象                    ,同时this指针指向这个字面量对象; 然后执行类B()的定义            ,B定义的普通属性成为字面量对象的属性并初始化        ,B.prototype的value值复制给字面量对象的隐藏属性[[Prototype]]; 然后再执行constructor构造器(没有显式定义构造器会提供默认构造器)                    ,第一句super()               ,开始进入类A()的定义; 暂存B的属性值    ,转而赋值为A定义的值                     ,A.prototype的value值复制给B.__proto__的隐藏属性[[Prototype]]; 然后执行constructor构造器(基类没有构造器就算了); 返回this指针; 丢弃A赋值的属性值                  ,重新使用暂存的B的属性值; 继续执行constructor构造器剩下的语句; 返回this指针给变量b,即b引用该字面量对象了;

通过基类和派生类创建对象的流程对比                  ,可以发现主要区别在于类的属性的赋值上;

属性值从一开始就已经暂存好:

如果构造器constructor中有赋值                     ,则暂存这个值; 如果构造器没有    ,则暂存类定义中的值; 不管父类及其原型链上同名的属性在中间进行过几次赋值               ,最终都会重新覆盖为最开始就暂存好的值;

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
mtu 1500(MTU是什么?如何利用ping命令拥有最佳MTU?) 香港服务器租用哪个好(租用香港服务器如何找好的服务商)