• 作者:老汪软件技巧
  • 发表时间:2024-09-27 04:00
  • 浏览量:

该系列文章连载于公众号coderwhy和掘金XiaoYu2002中

脉络探索一、什么是ES61.1 TC39技术委员会对应的讨论项目在TC39(Github名称)下的ecma262中

图17-1 ECMAScript 语言规范草案与源代码

图17-2 ECMA2025(ES16)草案

掌握了这个流程顺序,就可以查看所阅读部分的内容处于哪个阶段

图17-3 草案流程顺序(提案)

图17-4 GitHub中issue区的讨论交流

图17-5 GitHub中提出issue并提供解决方案

1.2 Babel二、认识Class定义类2.1 函数的二义性

而使用 class 语法后,构造函数和继承的语义变得非常明确:

2.2 Class类的必要性

那么,如何使用class 来定义一个类呢?

//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 Person{
}
function Person2(name){
  this.name = name
}
var p = new Person()
var p2 = new Person2()

//构造函数原型
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

三、Class类中定义构造方法和实例方法3.1 类的构造函数

//Class类中的构造函数接收参数方式
class Person{
  //类的构造方法(在这里的constructor构造函数就是一种特殊的构造方法)
  constructor(name){
  }
}
var p = new Person('小余')

如果我们希望在创建实例对象的时候给类传递一些参数,这个时候应该如何做呢?

class Person{
  //默认触发的情况下,相当于只有一个壳子,无事发生
  constructor(){
  }
}

3.2 类的实例方法

图17-6 实例方法指向

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类是没有脱离以往的本质的

console.log(Person.prototype === p1.__proto__);//true
//研究实例方法到底放在哪,放在类的显式原型中
console.log(Person.running);//undefined
console.log(Person.prototype.running);//[Function: running]

这和我们的推测达成一致

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构造函数比对

//通过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

//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

//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'

3.4 类和对象的访问器方法编写

//针对对象
//方式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 类中静态方法的定义(类方法)这两者最主要的区别在于调用方法的主体不同,通过名称就可以发现这一点而静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义:通过之前的学习,我们可以很清楚的了解这其中的区别,类的静态方法是定义在类本身也就是构造函数身上,实例方法则是定义在类的原型上

//类方法
class Person {
  static running(){
    console.log('这是一个类方法');
  }
}
Person.running()//这是一个类方法

图17-8 MDN文档中的静态方法部分

3.6 实例方法与静态方法区别

图17-9 实例方法与静态方法区别

这就需要深入理解构造函数本身没有原型链这一句话了

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 到此为止

console.log(Person.prototype.running()); // 调用两次,一次有效一次undefined
console.log(Person.prototype.constructor); // 类本身,无法继续细化,也无法继续调用类本身的任何内容

图17-10 直接打印出来构造函数原型

实例对象调用步骤1到类本身是无法到达的,没有步骤1类本身调用步骤2再调用步骤1可以到达,获得的是类本身(无法继续细化的版本)

图17-11 方法调用流动图

//静态方法
Object.hasOwn(obj, prop)
//实例方法
具体对象.hasOwnProperty('property1')

静态方法与构造函数的紧密程度

图17-12 静态方法与构造函数的紧密程度

表17-1 静态方法与实例方法总结

特性静态方法实例方法

定义位置

定义在构造器或类本身上。例如:Object.keys()

定义在类的原型上。例如:Object.prototype.toString()

调用方式

直接通过类名调用,不需要实例。例如:Object.keys(someObject)

必须通过类的实例调用。例如:someObject.toString()

用途

实现与类的具体实例无关的功能。提供工具函数或执行不依赖对象状态的操作

与对象状态密切相关,通常需要访问或修改对象实例的数据

依赖性

不依赖于对象实例的状态

可能会依赖或修改对象实例的内部状态

后续预告