JavaScript:类(class)
在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
中有赋值,则暂存这个值; - 如果构造器没有,则暂存类定义中的值;
- 不管父类及其原型链上同名的属性在中间进行过几次赋值,最终都会重新覆盖为最开始就暂存好的值;
JavaScript:类(class)的更多相关文章
- JavaScript 类式继承与原型继承
交叉着写Java和Javascript都有2年多了,今天来总结下自己所了解的Javascript类与继承. Javascript本身没有类似Java的面向对象的类与继承术语,但其基于原型对象的思想却可 ...
- 第九章:Javascript类和模块
(过年了,祝大家新年好!) 第6章详细介绍了javascript对象,每个javascript对象都是一个属性集合,相互之间没有任何联系.在javascript中也可以定义对象的类,让每个对象都共享某 ...
- 一种JavaScript 类的设计模式
一种JavaScript 类的设计模式尽管前面介绍了如何定义一个类,如何初始化一个类的实例,但既可以在function定义的函数体中添加成员,又可以用prototype 定义类的成员,代码显的很混乱, ...
- JavaScript “类”定义 继承 闭包 封装
一.Javascript “类”: 类:在面向对象编程中,类(class)是对象(object)的模板,定义了同一组对象(又称"实例")共有的属性和方法. Javascript是一 ...
- JavaScript类继承, 用什么方法好
JavaScript类继承, 用什么方法好 一个实例: 基类Car: function Car(color, year) { this.name = "car"; this.col ...
- javascript类式继承最优版
直接看实例代码: <!doctype html> <html lang="en"> <head> <meta charset=" ...
- JavaScript学习总结(十三)——极简主义法编写JavaScript类
前两天在网上无意中发现了一篇使用极简主义法定义JavaScript类的文章,原文链接,这个所谓的"极简主义法"我还是第一次听说,是荷兰程序员Gabor de Mooij提出来的,这 ...
- JavaScript类数组转换为数组 面试题
1.JavaScript类数组转换为数组 (1)方法一:借用slice (2)方法二:Array.from 2.代码 <!DOCTYPE html> <html lang=" ...
- 13.JavaScript 类
JavaScript 类 JavaScript 是面向对象的语言,但 JavaScript 不使用类. 在 JavaScript 中,不会创建类,也不会通过类来创建对象(就像在其他面向对象的语言中那样 ...
- javascript类数组
一.类数组定义: 而对于一个普通的对象来说,如果它的所有property名均为正整数,同时也有相应的length属性,那么虽然该对象并不是由Array构造函数所创建的,它依然呈现出数组的行为,在这种情 ...
随机推荐
- NSIS V3.08 简体中文增强版
说明: 该3.08版本属本人业余时间集成修改制作,首发博客园,欢迎反馈安装与使用中出现的BUG,转载请注明出处! 本版本母版源自NSIS(Nullsoft Scriptable Install Sys ...
- 驱动开发:通过ReadFile与内核层通信
驱动与应用程序的通信是非常有必要的,内核中执行代码后需要将其动态显示给应用层,但驱动程序与应用层毕竟不在一个地址空间内,为了实现内核与应用层数据交互则必须有通信的方法,微软为我们提供了三种通信方式,如 ...
- Spring的自动装配和注解
Bean的自动装配 自动装配说明 自动装配是使用spring满足bean依赖的一种方法 spring会在应用上下文中为某个bean寻找其依赖的bean. Spring的自动装配需要从两个角度来实现,或 ...
- JavaScript基础&实战(4)js中的对象、函数、全局作用域和局部作用域
文章目录 1.对象的简介 2.对象的基本操作 2.1 代码 2.2 测试结果 3.属性和属性值 3.1 代码 3.2 测试结果 4.对象的方法 4.1 代码 4.2 测试结果 5.对象字面量 5.1 ...
- go-zero docker-compose搭建课件服务(四):生成Dockerfile
0.转载 go-zero docker-compose 搭建课件服务(四):生成Dockerfile并在docker-compose中启动 0.1源码地址 https://github.com/liu ...
- VS2022连接Oracle数据库所需包和连接字符串
VS连接ORACLE数据库 l VS2022连接ORACLE数据库时,需要引入Oracle.ManagedDataAccess.Core包. l 引入方式:打开VS2022==>项目==&g ...
- Linux网络管理命令
Linux网络管理命令 ifconfig 用于配置网卡ip地址信息等网络参数或显示网络接口状态,类似于windows的ipconfig命令. 可以用这个工具来临时性的配置网卡的IP地址.掩码.广播地址 ...
- Sublime Text4(Build 4126) 安装备忘
Sublime Text4(Build 4126) 安装备忘 sublime text 4126 PJ已测可用 打开浏览器进入网站https://hexed.it 打开sublime text4安装目 ...
- 支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<支持JDK19虚拟线程的web ...
- curl 下载地址中有特殊字符解决方案
curl 下载地址中有特殊字符解决方案 情况 使用 curl 下载 地址中带有 特殊字符的时候 比如下面这个地址.实际访问地址不正确,参数丢失问题 curl -o kspf.jpeg https:// ...