该系列文章连载于公众号coderwhy和掘金XiaoYu2002中
- 对该系列知识感兴趣和想要一起交流的可以添加wx:XiaoYu2002-AI,拉你进群参与共学计划,一起成长进步
- 课程对照进度:JavaScript高级系列84-87集(coderwhy)
- 后续JavaScript高级知识技术会持续更新,如果喜欢我们的文章,欢迎关注、点赞、转发、评论,大家的支持是我们最大的动力
脉络探索
- 在结束对原型链的学习之后,我们就要跨步来到ES6-ES15语法系列的学习当中了
- 在本章节中,会先行学习ES6当中的Class(类),因为这能马上和上一章节中的原型链进行无缝的衔接,有更好的学习效果
- 并且会思考Class类的必要性和特点,会探索类和构造函数的比对,实例方法、静态方法在类中的区别在哪
一、什么是ES6
- JavaScript自1995年由Netscape的Brendan Eich发明以来,迅速成为了Web开发的基石。随着时间的推移,使用场景从简单的客户端脚本扩展到复杂的前端应用程序和服务器端开发。然而,随着需求的增长,JavaScript的早期版本开始显得力不从心,特别是在模块化、结构和异步编程方面
- ECMAScript 6(ES6),正式称为 ECMAScript 2015。就是为改变JS早期版本所薄弱和缺陷之处
- 自从上一个版本 ECMAScript 5(ES5)发布以来,间隔了近六年的时间(ES5 发布于 2009 年,ES6 于 2015 年发布)。在这期间,Web 技术和应用需求发生了巨大变化,积累了大量的需求和提案(还有其他语言带来的竞争压力),也是导致ES6的内容特别多的原因,自此之后,ES6及以后的语法通常都统称为
ES6语法
,因为这个阶段的内容最多,最具备代表性 - 从ES6语法问世的2015年开始,就做到了往后每年更新一次,每次的命名变化都以后缀数字自增1来进行,这也是ES6-ES15的命名由来
- 自从上一个版本 ECMAScript 5(ES5)发布以来,间隔了近六年的时间(ES5 发布于 2009 年,ES6 于 2015 年发布)。在这期间,Web 技术和应用需求发生了巨大变化,积累了大量的需求和提案(还有其他语言带来的竞争压力),也是导致ES6的内容特别多的原因,自此之后,ES6及以后的语法通常都统称为
1.1 TC39技术委员会
- 在这里,我们必须要提一个组织,也就是TC39 ,在一定程度上,这就是JS语言的官方,他们是负责ECMAScript(JavaScript 的官方名称)语言规范的标准化组织
- TC39 的成员包括来自主要浏览器厂商(如 Mozilla、Google、Microsoft、Apple 等)的代表,以及大型技术公司和社区的活跃成员
- 了解这个组织的原因来自他们许多讨论和会议是公开的,提案和相关文档通常在 GitHub 上公开讨论和维护,允许社区成员和其他利益相关者参与进来。当然,也包括了我们,通过阅读顶级开发者的文档和解决方式去理解他们的思路,会给我们带来巨大的提升
- 对应的讨论项目在TC39(Github名称)下的ecma262中
- ECMA-262 是 ECMAScript 语言规范的官方文档编号,这一套文档统一了JS在全世界中的一致性和互操作性
- 一个统一的标准对于保证在不同的浏览器和宿主环境中,JavaScript 代码能够一致地执行有多大的帮助,是可以预料得到的,其实就是JS版本的
车同轨,书同文
图17-1 ECMAScript 语言规范草案与源代码
- 这个项目既可以将代码直接下载在
本地阅读
,也可以选择在线阅读
:ECMAScript® 2025 Language Specification (tc39.es)- ECMA2024,也是就ES15已经发布,所以目前最新的是ECMA2025,也就是ES16的草案
图17-2 ECMA2025(ES16)草案
- 说到草案,就需要涉及到每一年的语法发布,都需要经过哪几个流程(一共四个)
- Stage 0 - Strawperson: 初始讨论阶段,用于收集提案和反馈
- Stage 1 - Proposal: 正式的提案阶段,必须有一个负责人和形式上的规范草案
- Stage 2 - Draft: 初稿阶段,提案需要具体描述语法和语义,由TC39成员进行审查,并开始编写测试
- Stage 3 - Candidate: 候选阶段,规范文本需要完成,并通过两个独立实现来验证提案
- Stage 4 - Finished: 完成阶段,提案已经准备好被纳入下一个ECMAScript版本,此时已经在JavaScript环境中广泛实现
- 掌握了这个流程顺序,就可以查看所阅读部分的内容处于哪个阶段
图17-3 草案流程顺序(提案)
- 在拥有自己的想法之后,可以在该项目的issue区和其他人进行互动交流
图17-4 GitHub中issue区的讨论交流
- 通常在这个板块中所讨论的内容是非常前沿的部分,如果觉得自身能力还没有达到,可以先在MDN中进行讨论
- MDN是我们经常使用的语法文档,记录的是已经应用于实践的语法部分,能够学到很多有用的内容
- 在GitHub中的项目mdn中也可以搜索对应的issue区,提出自己对该文档的建议或者观看其他开发者的发言和讨论,让社区发展得更好
图17-5 GitHub中提出issue并提供解决方案
1.2 Babel
- ES6语法其实是具备兼容性问题的,不是所有浏览器都完全支持ES6的所有新特性,这意味着直接在这些浏览器上运行ES6代码可能会导致脚本错误或不执行
- 为了解决这些兼容性问题,Babel工具应运而生。Babel是一个广泛使用的JavaScript编译器,可以将ES6+代码转换成向后兼容的JavaScript版本(如ES5),从而在旧浏览器和环境中运行无误
- 我们接下来要学习的Class类,在编写结束,想要上线项目,就必须转化为ES5代码,因为我们无法确定用户在哪个浏览器中打开从而导致脚本错误之类的问题
- 如何进行转化的,我们就留到讲完Class类后再来进行实践对比。而Babel作为工具而言,我们在JS高级中并不去进一步深入探讨它的实现原理
二、认识Class定义类
- 我们会发现,按照之前的构造函数形式创建类 ,不仅仅和编写普通的函数过于相似,而且代码并不容易理解。
- 在ES6中新的标准使用了
class 关键字
来直接定义类 - 但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已
- 所以学好了前面的构造函数、原型链更有利于我们理解类的概念和继承关系
- 在ES6中新的标准使用了
2.1 函数的二义性
-
在ES6语法后的一些语法糖是用来解决一些模糊不清的界限的,比如语法或结构上的模糊性,使得代码的意图不明确,容易引起误解,此种误解通常被称为二义性,一种代码两种甚至多种含义,让人分不清
-
在ES6之前实现类似
"类"
效果的构造函数与继承时,其中的界限就很模糊,需要仔细分辨才能区分,造成心智上的负担,而这种情况并不止一种- JS使用构造函数来创建对象的新实例。构造函数本身是一个普通函数,但当使用
new
关键字调用时,它的行为就像是在创建一个类的实例 - JS使用原型链来实现继承。每个对象都有一个原型对象,对象从其原型继承属性和方法。但是,直接操作原型(如
StudentObject.prototype = PersonObject
)对于新手来说可能不那么直观
- JS使用构造函数来创建对象的新实例。构造函数本身是一个普通函数,但当使用
-
而使用
class
语法后,构造函数和继承的语义变得非常明确:- 构造函数 :
constructor
方法明确指定了对象的初始化过程 - 继承 :
extends
和super
关键字清晰地定义了类之间的继承关系
- 构造函数 :
2.2 Class类的必要性
-
曾经使用Class最多的就是React框架,但自从React18乃至React19版本的发布之后,已经全面转向于函数式组件编程
- 这可能会让我们对于学习Class有所顾虑,但这是没有必要的
- 首先依旧有大量的旧项目(React18以前的版本)在维护,这种项目一般情况下不会重构(成本很高)
- 其次还有很多对应的第三方库是使用Class类来进行编写的,我们想要使用这些第三方库,也必须掌握Class用法
-
那么,如何使用class 来定义一个类呢?
- 可以使用两种方式来声明类:
类声明
和类表达式
,我们以类声明为主
- 可以使用两种方式来声明类:
js
//ES6中定义类
//大括号的3种用法
//{key:value} -> 对象的用法
//{表达式} -> 代码块
//{} -> 类的结构
//类声明
class Person{
}
//创建实例对象
var p1 = new Person()
var p2 = new Person()
//另外一种定义方式(不常用):类表达式
var Student = class {//不同之处在这里
}
//类表达式和函数表达式有异曲同工之妙
var foo = function(){
}
//创建实例对象
var stu1 = new Student()
console.log(stu1);
2.3 研究Class类的特点
- 我们在前面说,Class类只是一个语法糖,它的本质依旧是我们熟悉的构造函数,对此我们可以来验证其中的原型、类型等等
js
//对比
class Person{
}
function Person2(name){
this.name = name
}
var p = new Person()
var p2 = new Person2()
- 在最简单的Demo下,我们和ES6以前的写法进行一个对比
- 在原型链的表现上可以说是近乎一模一样
- 唯一一个不同之处在于ES6写法Class在原型返回的构造函数是
class 类名称
,而不是Function 类名称
- 这是因为在
class
语法中,类被设计为特殊的函数,在打印时有特殊的表示([class ClassName]
),以区别于普通函数([Function: FunctionName]
。这种现象是class语法的特殊处理,目的就是为了让语义清晰
,也就是我们前面所说的避免二义性 - 在我们上一章节实现各种继承时,借用构造函数继承等方式会在该返回Student类型时返回了Person类型,这种造成来源误会的事情是应当去避免的,class语法在这方面也做出了对应处理,进一步强调了当前使用的是类,而非普通函数
js
//构造函数原型
console.log(Person.prototype);//{}
console.log(Person2.prototype);//{}
//指向Object原型的__proto__,原型链的重点null
console.log(Person.prototype.__proto__);//[Object: null prototype] {}
console.log(Person2.prototype.__proto__);//[Object: null prototype] {}
//指向构造函数本身
console.log(Person.prototype.constructor);//[class Person]
console.log(Person2.prototype.constructor);//[Function: Person2]
//判断原型链关系
console.log(p.__proto__ === Person.prototype);//true
console.log(p2.__proto__ === Person2.prototype);//true
console.log(typeof Person);//function
console.log(typeof Person2);//function
- 而在typeof返回类型的时候,为什么Person已经设置为ES6类了,还是返回了function?
- 首先我们清楚在JS当中,类 (
class
) 实际上还是函数 (function
) 的一种特殊形式,JS引擎看到的是类背后的函数实现 - JS语言的设计初衷是保持向后兼容。因此,即使引入了新的
class
语法,它仍然需要在现有的 JavaScript 引擎中无缝工作,而这些引擎原本就是使用函数来实现对象构造和原型继承的。为了不破坏已有代码和库,以及确保旧代码可以与新标准一起运行,保持class
为function
类型是必要的 - 虽然
typeof
返回function
可能在一些情况下使类和普通函数之间的区别不那么明显,但这种设计是出于保持语言的一致性和简洁性考虑。对于需要精确区分类和函数的场景,我们最好使用其他方式(如检查特定的原型属性或使用instanceof
操作符)来确定一个对象是否为类的实例
- 首先我们清楚在JS当中,类 (
三、Class类中定义构造方法和实例方法
3.1 类的构造函数
- 在Class中,我们主要的功能是传递参数和对应的方法(构造方法和实例方法)
- 在之前,我们传递参数是通过函数的形参与实参进行传递的,方法的定义通过继承有多种方式
- 在Class中,这些内容得到了统一的明确,也就是前面所说的
构造函数
与继承
js
//Class类中的构造函数接收参数方式
class Person{
//类的构造方法(在这里的constructor构造函数就是一种特殊的构造方法)
constructor(name){
}
}
var p = new Person('小余')
- 我们通过constructor进行接收参数,在这里constructor是一个特殊的函数,对应到了
Person.prototype.constructor
身上- 这个特殊的函数只要我们有new调用对应的类就会触发,就算没有编写constructor函数也会触发默认的情况
- 如果我们希望在创建实例对象的时候给类传递一些参数,这个时候应该如何做呢?
- 每个类都可以有一个自己的构造函数(方法),这个方法的名称是固定的constructor;
- 当我们通过new操作符,操作一个类的时候会调用这个类的构造函数constructor
- 一个类只能有一个构造函数(constructor特殊函数),具备唯一性,因为这是和对应原型互相唯一引用的一个状态,只能有一个。如果包含多个构造函数,那么会抛出异常
js
class Person{
//默认触发的情况下,相当于只有一个壳子,无事发生
constructor(){
}
}
3.2 类的实例方法
- 相对于一开始就定义好的构造方法,还有一个实例方法
- 该实例方法在以前的写法中是放到构造函数的原型当中的
- 在这种情况下p实例是可以访问到对应的方法的
图17-6 实例方法指向
- 而在ES6中的类的实例方法是定义在类的原型上的方法,这些方法可以被类的所有实例访问和执行
- 在意思的表达上其实是一样的,只不过换了一个名字而已
js
class Person{
constructor(name){
this.name = name
}
running(){
console.log( `${this.name}向你敬礼`);
}
}
var p = new Person('小余')
var p2 = new Person('coderwhy')
p.running()//小余向你敬礼
p2.running()//coderwhy向你敬礼
图17-7 实例对象共享实例方法
- class类通过这样调用实现了对应的方法
- 在
p实例
和p2实例
都可以调用到running方法
,这依赖于他们指向的位置是一样的,也就是Person原型,该图需要联系到上图进行比对 - 同时在调用实例对应的方法时,由于new调用改变了this的指向,实现了获取我们真正想要的name
- 在
- 我们是知道class类是没有脱离以往的本质的
- 所以这些实例方法存储的位置就在构造函数的显式原型上,只不过在ES6语法中,定义会更加明确
js
console.log(Person.prototype === p1.__proto__);//true
//研究实例方法到底放在哪,放在类的显式原型中
console.log(Person.running);//undefined
console.log(Person.prototype.running);//[Function: running]
-
通过
Object.getOwnPropertyDescriptor
方法,就能够获取给定对象上特定属性(即直接存在于对象上而不在对象的原型链中的属性)的配置- 在Person原型上,我们可以拿到对应的方法
- 在Person身上,我们可以拿到对应的name属性等等
这和我们的推测达成一致
js
class Person{
constructor(name){
this.name = name
}
running(){
console.log( `${this.name}向你敬礼`);
}
}
console.log(Object.getOwnPropertyDescriptors(Person.prototype));
// {
// constructor: {
// value: [class Person],
// writable: true,
// enumerable: false,
// configurable: true
// },
// running: {
// value: [Function: running],
// writable: true,
// enumerable: false,
// configurable: true
// }
// }
3.3 class类和function构造函数比对
- 不管是class类还是function构造函数,他们所代表的含义都是相通的
- 不同之处在于书写方式,我们来进行对比一下
js
//通过function来定义类
function Person1(name,age){
this.age = age
this.name = name
}
Person1.prototype.running = function(){
}
Person1.prototype.eating = function(){
}
var p1 = new Person1("coderwhy",35)
console.log(p1.__proto__ === Person1.prototype);//true
js
//class来定义类,这种写法只是上面的语法糖
class Person2{
constructor(name,age){
this.name = name
this.age = age
}
running(){
}
eating(){
}
}
var p2 = new Person2("小余",20)
console.log(p2.__proto__ === Person2.prototype);//true
console.log(Person2.prototype.constructor);//指回类本身,跟上面Person1是一样的
console.log(typeof Person1,typeof Person2);//都是function
- 不同之处在于class的边界更加清晰明确
- function模拟出来的类只是普通的函数,而普通的函数是可以做到随便调用的
- 但class类不行,class类只能通过new调用,在这里划定了非常清晰的调用界限,因为我们知道只有通过new调用的函数才是构造函数,才是我们的类,ES6之后的这点特性得到了强化
js
//function的写法是能够作为普通函数去调用的
function Person1(name,age){
//这两个需要写,如果不写,name,age打印不出来。如果name能够打印出来的话,那是之前有用过,存到window里面去了,因为Person1函数的外面一层就是window,这个时候就算把Person1("coderwhy",35)的coderwhy修改掉,控制台也是无法修改掉的,因为函数内部的this.name是去window中读取了
this.name = name
this.age = age
console.log(this.name+"今年已经"+this.age);
}
Person1("coderwhy",35)//coderwhy今年已经35
//class定义的类,不能作为一个普通的函数进行调用
//class的写法则是不行,结果就是报错。不使用 new 的时候是不能够调用的
class Person2{
constructor(name,age){
this.name = name
this.age = age
}
}
Person2("小余",20)//Class constructor Person2 cannot be invoked without 'new'
- 在明确class的使用关键在于
new调用
,那当new调用产生后会发生什么?- 当我们通过new关键字操作类的时候,会调用这个constructor函数,并且执行如下操作:
- 在内存中创建一个新的对象(空对象)
- 这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性
- 构造函数内部的this,会指向创建出来的新对象
- 执行构造函数的内部代码(函数体代码)
- 如果构造函数没有返回非空对象,则返回创建出来的新对象
- 而这也就是我们前面所说的通过new显式调用后,内容传递不会混淆的原因
- 当我们通过new关键字操作类的时候,会调用这个constructor函数,并且执行如下操作:
3.4 类和对象的访问器方法编写
- 在类当中也是可以定义访问器的,只要我们想的话
- 访问器(Accessor)是一种使用
get
和set
关键字定义的特殊类型的方法,允许我们对对象的属性进行更加细致和控制的读取和修改操作。这些方法通常被称为访问器属性,包括一个获取器(getter)和一个设置器(setter) - 虽然封装性更好,但前端用的情况不是特别的多,因为应用场景主要在于
属性变化监听
,而在正式的项目中,通常都由框架封装好了。如果想前端向后端过渡,可以多注意这方面的内容 - 这些方式在之前我们都有实现过,需要强调的注意事项在于对于
私有属性
的规范,我们暂时还是以_下划线
来区分,但在ES6以后的语法特性中,有对此进行更明确的操作规范,我们之后会学习到的
- 访问器(Accessor)是一种使用
js
//针对对象
//方式1:描述符
var obj = {
}
Object.defineProperty(obj,"name",{
configurable:true,
enumerable:false,
get:function(){
},
set:function(){
}
})
//方法2:直接在对象定义访问器(少见) 但这种对象上的监听只能实现监听一个的,如果我想要监听多个如何实现
var obj = {
_name:"小余",
//setter方法(私有属性),这就是个名词
set name(value){
this._name = value
},
//getter方法
get name(){
return this._name
}
}
3.5 类中静态方法的定义(类方法)
- 类方法是指放在类中的方法,但通常也叫做类中的静态方法,他们其实是同一个东西
- 和实例方法有什么区别呢?
- 首先实例方法通常都是基于实例对象去进行调用的,也可以说通过创建出来的对象去进行一个访问
- 而类的静态方法,是通过类名去访问的,所以调用静态方法不需要通过实例对象,自然也就不需要new调用
- 这两者最主要的区别在于调用方法的主体不同,通过名称就可以发现这一点
类方法(静态方法)
:通过类调用的方法实例方法
:通过实例对象调用的方法
- 而静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义:
- 静态方法通常是指
被定义在类上而非类的实例上的方法
,也是我们所说的类方法,之所以这样称呼是因为我们的类方法是通过static关键字来进行定义的,而static本身就有"静态"的意思,所以静态方法
在这里可以和static方法
、类方法
划等号,他们都是一个意思,只是叫法上的不同。但大部分情况下,都是称呼为静态方法
- 而将静态方法(类方法)写在class里面,内聚性会更好,不会过于松散
- 理解这个可以让我们在阅读MDN等文档时可以进行有效的区分
- 静态方法通常是指
- 通过之前的学习,我们可以很清楚的了解这其中的区别,类的静态方法是定义在类本身也就是构造函数身上,实例方法则是定义在类的原型上
- 所以通过实例对象进行调用,只能调用实例方法。因为实例对象本身就可以顺着原型链找到类的原型上(每个实例对象的内部属性
[[Prototype]]
指向类的原型对象)。但实例对象是沿着原型链查找,构造函数不属于原型链的一部分,所以实例对象
没办法调用来自构造函数的静态方法 - 而类方法也就是静态方法只能够由类本身来调用,实例对象是调用不了的
- 所以通过实例对象进行调用,只能调用实例方法。因为实例对象本身就可以顺着原型链找到类的原型上(每个实例对象的内部属性
js
//类方法
class Person {
static running(){
console.log('这是一个类方法');
}
}
Person.running()//这是一个类方法
图17-8 MDN文档中的静态方法部分
3.6 实例方法与静态方法区别
- 在这里我想要详细剖析一下实例方法与静态方法之间更多的差异化
- 在前面,我们已经知道实例对象只能调用实例方法而无法调用静态方法的原因了
- 但为什么类本身也只能调用静态方法而无法调用实例方法?在这里有一条非常清晰的界限
图17-9 实例方法与静态方法区别
- 这是因为构造函数本身确实有一个原型,但它
不具备像实例对象那样的原型链
,这是什么意思?值得我们深入探讨一下- 首先每个构造函数都有一个
prototype
属性,这个属性指向一个对象,称为构造函数的原型对象 - 构造函数的原型对象 (
Constructor.prototype
) 是一个普通对象,用于存储实例方法。这也是让我们疑惑的点,为什么构造函数没办法调用实例方法,只需要沿着原型去找就能够找到
- 首先每个构造函数都有一个
- 这就需要深入理解
构造函数本身没有原型链
这一句话了- 构造函数的
prototype
对象形成了实例对象的原型链的一部分,但构造函数自身并不具有原型链 - 实例对象的
[[Prototype]]
指向构造函数的prototype
对象,但构造函数的prototype
对象并不会有一个进一步的[[Prototype]]
链,指向其他构造函数
- 构造函数的
js
const p = new Person('Alice');
//实例对象原型 等于 构造函数原型
console.log(p.__proto__ === Person.prototype); // true
//构造函数的 `prototype` 对象并不会有一个进一步的 `[[Prototype]]` 链
//Person构造函数是否可以从实例对象的原型的构造函数获取,答案是不可以,没有constructor了
console.log(p.prototype.constructor);//没有constructor
console.log(Person === p.prototype.constructor); // TypeError: Cannot read properties of undefined (reading 'constructor') // false 到此为止
- 如果直接打印出来构造函数原型的构造属性,会直接是类本身,但这个本身是完整的内容,无法继续细化的那种,如图17-
- 这种形式的内容是无法进行调用的,且类本身和类并不等同
js
console.log(Person.prototype.running()); // 调用两次,一次有效一次undefined
console.log(Person.prototype.constructor); // 类本身,无法继续细化,也无法继续调用类本身的任何内容
图17-10 直接打印出来构造函数原型
-
这也是造成实例方法和静态方法最大区别的原因
-
类虽然可以通过步骤2从原型中强制调用到实例方法,但会出现调用两次的问题(第一次有正确内容,第二次为undefined),该缺陷原因来自之前讲解的继承实现原理。有缺陷所以不推荐使用,而类本身是没有实例方法,也没有原型链,所以类本身调用实例方法会报错(不会自主找到原型上)
-
实例对象p可以调用到Person原型的实例方法,但如果想要通过步骤1去调用类本身的静态方法就是想多了,在这里被进行处理了
- 实例对象调用步骤1到类本身是无法到达的,没有步骤1
- 类本身调用步骤2再调用步骤1可以到达,获得的是类本身(无法继续细化的版本)
-
图17-11 方法调用流动图
- 那在具备实例方法后,为什么还需要静态方法?
- 静态方法和实例方法的目的是不相同的
- 静态方法属于类本身而非类的实例,可以在不创建类实例的情况下调用静态方法,用来执行不依赖于对象状态的功能
- 上面这句话怎么理解?不依赖于对象状态的功能,如果大家有仔细阅读MDN的API文档,会注意到一点,那就是静态方法的调用方式和实例方法的调用方式不同
- 静态方法直接从类本身进行调用,而实例方法必须从具体的对象身上进行调用。需要具体对象实例,这就是依赖于对象状态
js
//静态方法
Object.hasOwn(obj, prop)
//实例方法
具体对象.hasOwnProperty('property1')
- 并且在MDN文档中,可以看到,想要有静态方法,则必须有对应的构造函数,这是必须点
图17-12 静态方法与构造函数的紧密程度
- 在用于各种工具函数时,往往需要各种静态方法,因为无需实例化,更适合执行通用操作而不修改任何对象状态
- 可以被类的所有实例共享,也可以在没有任何类实例的情况下使用。是实现与类功能相关但不依赖具体实例的方法的理想选择
- 且调用静态方法不需要创建和维护类的实例,做到了减少内存使用,并提高程序的运行效率
表17-1 静态方法与实例方法总结
特性 | 静态方法 | 实例方法 |
---|---|---|
定义位置 | 定义在构造器或类本身上。例如:Object.keys() |
定义在类的原型上。例如:Object.prototype.toString() |
调用方式 | 直接通过类名调用,不需要实例。例如:Object.keys(someObject) |
必须通过类的实例调用。例如:someObject.toString() |
用途 | 实现与类的具体实例无关的功能。提供工具函数或执行不依赖对象状态的操作 | 与对象状态密切相关,通常需要访问或修改对象实例的数据 |
依赖性 | 不依赖于对象实例的状态 | 可能会依赖或修改对象实例的内部状态 |
后续预告
- 在下一章节中,我们会继续学习Class类,探究类是如何实现的继承以及在类中的关键字super
- 还有像Babel是如何将ES6中的class类代码转化为ES5代码
- 以及对应的源码阅读技巧,都是我们所能够学习到的部分