前提概要
上一篇编写的是ES6中的Object,链接:juejin.cn/post/700464… ,这次写的是ES6中Class的基本语法和继承,可能不是很全。如有不对的或者不准确的地方,欢迎大家提出?,我也积极修改。下边开始正文:
声明类
如何声明一个类?在ES6之前是这么做的:
// 首字母大写表示声明一个类
function Person (name, age) {
this.name = name
this.age = age
this.showName = function () { // new Function()
console.log('我的名字是' + this.name)
}
}
let p1 = new Person('xs', 20)
console.log(p1) // Person {name: 'xs', age: 20, showName: ƒ}
p1.showName() // 我的名字是xs
复制代码
在上述代码中,我们定义了一个叫 Person 的类,类中声明了两个属性 name,age、一个方法 showName;然后通过new Person
这个类生成实例,完成了类的定义和实例化。当然也可以这样写:
function Person (name, age) {
this.name = name
this.age = age
}
// 实例方法
Person.prototype.showName = function () {
console.log('我的名字是' + this.name)
}
let p1 = new Person('xs', 20)
console.log(p1) // Person {name: 'xs', age: 20}
p1.showName() // 我的名字是xs
复制代码
在 ES6 中引入了Class(类)
这个概念,不在用 function 的方式了,代码如下:
class Person {
constructor(name,age) {
this.name = name
this.age = age
}
showName() {
console.log('我的名字是' + this.name)
}
}
let p1 = new Person('xs', 20)
console.log(p1) // Person {name: 'xs', age: 20}
p1.showName() // 我的名字是xs
复制代码
新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法。
那么 ES6 增加了新的数据类型 class 吗?console.log(typeof Person) //function
可以发现 class 的类型还是 function。
那么class中定义方法存在哪里?并且和prototype对象的关系是什么?
console.log(Person.prototype)
// { constructor: ƒ, showName: ƒ }
// constructor: class Person
// showName: ƒ showName()
// [[Prototype]]: Object
// constructor: ƒ Object()
// hasOwnProperty: ƒ hasOwnProperty()
// isPrototypeOf: ƒ isPrototypeOf()
// propertyIsEnumerable: ƒ propertyIsEnumerable()
// toLocaleString: ƒ toLocaleString()
// toString: ƒ toString()
// valueOf: ƒ valueOf()
// __defineGetter__: ƒ __defineGetter__()
// __defineSetter__: ƒ __defineSetter__()
// __lookupGetter__: ƒ __lookupGetter__()
// __lookupSetter__: ƒ __lookupSetter__()
// __proto__: Object
// get __proto__: ƒ __proto__()
// set __proto__: ƒ __proto__()
复制代码
可以看出在 Person.prototype
对象上有两个方法,一个是构造函数(constructor)
、一个是自定义的方法(showName)
。这和 ES5 的第二种写法一样。
在 ES5 中可以通过hasOwnProperty
可以判断对象的自有属性
console.log(p1.hasOwnProperty('name')) //true
复制代码
这个表现也和 ES5 中直接使用 function 定义类的方式相同,所以得出一个结论:class 的方式是 function 方式的语法糖
。
静态属性和静态方法
静态属性和静态方法是面向对象最常用的功能,在 ES5 中利用 function 实现的类是这样实现静态属性和静态方法的。
function Person ( name, age) {
// 实例属性
this.name = name
this.age = age
Person.count++
}
// 静态属性
Person.count = 0
// 静态方法
Person.getCount = function () {
console.log(this.age) // undefined
console.log('当前执行了' + Person.count + '次') // 当前执行了1次
}
let p1 = new Person('xs',20)
Person.getCount()
console.log(Person.count) // 1
复制代码
上述代码中的console.log(this.age)
为undefined
的原因是,由于声明的是静态方法,所以当前的this指向构造函数,并不是生成的实例。并且静态方法的调用必须是构造函数自身调用。
注意: 如果静态方法包含
this
关键字,这个this
指的是类,而不是实例。
在 ES6 中使用 static 的标记静态属性和静态方法,代码如下:
class Person {
// 静态属性
static count = 0
constructor( name, age) {
this.name = name
this.age = age
}
showName() {
console.log('我的名字是' + this.name)
}
// 静态方法
static getCount() {
return 123
}
}
let p1 = new Person('xs', 20)
console.log(p1)
console.log(Person.getCount()) // 123
console.log(Person.count) // 0
复制代码
注: 实例属性是定义在构造函数当中的,静态属性是定义在类当中的。
Setters & Getters
对于类中的属性,可以直接在constructor
中通过 this 直接定义,还可以直接在类的顶层来定义:
class Person {
constructor(name, age) {
this.name = name
this.age = age
this._sex = -1 // 设置为一个初始值
}
// 需求:当我们给sex赋值为1或者是0,来获取得sex的值为'male'和'female',该如何实现?
get sex () { // 属性
if (this._sex === 1) {
return 'male'
} else if (this._sex === 0) {
return 'female'
} else {
return 'error'
}
}
set sex (val) { // 1:male 0: female
if (val === 1 || val === 0) {
this._sex = val
}
}
showName () {
console.log('名字是:' + this.name, '年龄是:' + this.age)
}
}
let p1 = new Person('xs', 20)
console.log(p1) // Person {name: 'xs', age: 20, _sex: -1}
p1.sex = 1
console.log(p1.sex) // 'male'
p1.showName() // 名字是:xs 年龄是:20
复制代码
通过上述代码可以发现,我们在 constructor
内我们声明了一个 _sex
,如果我们在 set
方法内这么赋值 this.sex = val
,将会报错: Maximum call stack size exceeded
意思是超出当前最大调用栈,也就是成了死循环。原因是因为当我们给 sex
设置值的时候就会调用 set
方法,当调用 set
方法又会给 sex
设置值,所以就会循环反复,成了死循环。所以,这时我们需要引入一个新的属性,在 constructor
内设置一个新的属性 _sex
,当在 set
方法内设置值的时候我们可以 this._sex = val
,在 get
方法内获取值的时候我们可以 return this._sex
。
继承
面向对象只所以可以应对复杂的项目实现,很大程度上要归功于继承。在 ES5 中怎么实现继承呢?代码如下:
// 定义父类
function Animal (name) {
this.name = name
}
// 定义方法
Animal.prototype.showName = function () {
console.log('名字是:' + this.name)
}
// 子类 使用的是组合式继承:构造函数继承 + 原型继承
function Dog (name, color) {
// 初始化父类使用的是构造函数继承
// 构造函数的继承只能继承父类的属性不能继承父类的方法
Animal.call(this, name)
this.color = color
}
// 继承父类的方法: 原型继承
// 1、把Dog的原型指向Animal的实例化对象
Dog.prototype = new Animal()
// 2、再把Dog的原型重新指回到Dog上
Dog.prototype.constuctor = Dog
let d1 = new Dog('wangcai', 'yellow')
console.log(d1) // Dog {name: 'wangcai', color: 'yellow'}
d1.showName() // 名字是:wangcai
复制代码
再看看 ES6 是怎么解决这些问题的:
class Animal {
constructor(name) {
this.name = name
}
showName () {
console.log('名字是:' + this.name)
}
}
class Dog extends Animal {
constructor(name,color) {
// 通过super来继承当前父类的属性
super(name)
this.color = color
}
showColor () {
console.log(this.name + '的颜色是:' + this.color)
}
}
let d1 = new Dog('wangcai', 'yellow')
console.log(d1) // Dog {name: 'wangcai', color: 'yellow'}
d1.showName() // 名字是:wangcai
d1.showColor() // wangcai的颜色是:yellow
复制代码
class实现继承的注意事项
1、class 声明不会提升。Foo 进入暂时性死区,类似于 let、const 声明变量
const bar = new Bar() // it's ok
function Bar () {
this.bar = 18
}
const foo = new Foo() // Uncaught TypeError:Foo is not a constructor
class Foo {
constructor() {
this.bar = 18
}
}
复制代码
2、class 声明内部会启用严格模式(在浏览器的控制台中输入)
// 引用一个未声明的变量
function Bar () {
baz = 18
}
const bar = new Bar()
console.log(bar) // Bar {}
class Foo {
constructor() {
fol = 18
}
}
const foo = new Foo()
console.log(foo) // Uncaught TypeError:fol is not defined
复制代码
3、this的指向
class Logger {
printName(name = 'world') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
logger.printName() // Hello world
printName(); // Uncaught TypeError: Cannot read properties of undefined (reading 'print')
复制代码
上面代码中,printName
方法中的this
,默认指向Logger
类的实例。但是,如果将这个方法提取出来单独使用,this
会指向该方法运行时所在的环境(由于 class 内部是严格模式
,所以 this
实际指向的是undefined
),从而导致找不到print
方法而报错。
4、class 的所有方法(包括静态方法和实例方法)都是不可枚举的
// 引用一个未声明的变量
function Bar () {
this.bar = 18
}
Bar.answer = function () {
return 19
}
Bar.prototype.print = function () {
console.log(this.bar)
}
console.log(Object.keys(Bar)) // ['answer']
console.log(Object.keys(Bar.prototype)) // ['print']
class Foo {
constructor() {
this.foo = 18
}
static answer () {
return 19
}
print () {
console.log(this.foo)
}
}
console.log(Object.keys(Foo)) // []
console.log(Object.keys(Foo.prototype)) // []
复制代码
5、class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有 construct(构造属性),不能使用 new 来调用(在浏览器的控制台中运行)
function Bar () {
this.bar = 18
}
Bar.prototype.print = function () {
console.log(this.bar) // undefined
}
const bar = new Bar()
console.log(bar) // Bar {bar: 18}
const barPrint = new bar.print()
console.log(barPrint) // Bar.print {}
class Foo {
constructor() {
this.foo = 18
}
print () {
console.log(this.foo)
}
}
const foo = new Foo()
console.log(foo) // Foo {foo: 18}
const fooPrint = new foo.print() //Uncaught TypeError: foo.print is not a constructor
复制代码
6、必须使用 new 调用 class (在浏览器的控制台中运行)
function Bar () {
this.baz = 18
}
const bar = Bar() // it's ok
class Foo {
constructor() {
this.fol = 18
}
}
const foo = Foo() // Uncaught TypeError: Class constructor Foo cannot be invoked with
复制代码
7、class 内部无法重写类名
function Bar () {
Bar = 'Baz' // it's ok
this.bar = 18
}
const bar = new Bar()
console.log(Bar) // Baz
console.log(bar) // Bar {bar: 18}
class Foo {
constructor() {
this.foo = 18
// Foo = 'Fol' // Uncaught TypeError: Assignment to constant variable
}
}
const foo = new Foo()
Foo = 'Fol' // it's ok
console.log(foo) // Foo {foo: 18}
console.log(Foo) // Fol
复制代码