一、传统构造器函数遇到的问题
-
属性和原型方法定义隔离,降低了可读性
比如以下代码;
javascript
function User (name,age) {
this.name = name;
this.age = age;
}
//定义实例,原型方法
User.prototype.getUserInfo = function () {
console.log(this.name,this.age)
}
const user = new User()
user.getUserInfo()
上面代码需要定义示例方法,但是需要在后面原型上去定义,这样的写法隔离,影响可读性
-
原型成员可以被枚举
使用for...in...原型成员也被枚举出来了
javascript
function User (name,age) {
this.name = name;
this.age = age;
}
//定义实例,原型方法
User.prototype.getUserInfo = function () {
console.log(this.name,this.age)
}
const user = new User()
for(const key in user){
console.log(key)
}
// name
// age
// getUserInfo
大多数情况是不需要遍历出原型上的属性的
- 默认情况下,构造函数仍然可以被当作普通函数使用
以上的这些问题都是需要在es6中解决的,因此es6做了很大的改进
二、class基本语法糖
2.1 语法
上面案例可以使用class语法糖改进
kotlin
class User {
constructor (name,age) {
this.name = name;
this.age = age;
} //constructor等于上面的构造函数User
getUserInfo(){
console.log(this.name,this.age) //添加原型方法
}
}
2.2 类的特点
- 类声明不会被提升,与let和const一样,存在暂时性死区
- 类中的所有代码均在严格模式下执行
- 类的所有方法都是不可枚举的 赋值的方法除外,因为使用=赋值后的属性挂载到了属性本身上而不是属性原型上
kotlin
class User {
constructor (name,age) {
this.name = name;
this.age = age;
} //constructor等于上面的构造函数User
getUserInfo=()=>{
console.log(this.name,this.age) //添加原型方法
}
}
const user = new User()
for(const key in user){
console.log(key)
}
- 类所有的方法都无法被当作构造函数来使用
依旧是赋值的方法除外
ini
class User {
constructor (name,age) {
this.name = name;
this.age = age;
} //constructor等于上面的构造函数User
getUserInfo= function(b){
this.b = b
}
}
const user = new User()
const a = new user.getUserInfo(123)
console.log(a)
- 类的构造器必须使用 new 来调用
三、类的其他书写方式
3.1 可计算的属性名
kotlin
const name = "getUserInfo"
class User {
constructor (name,age) {
this.name = name;
this.age = age;
} //constructor等于上面的构造函数User
[name](){
console.log(this.name,this.age) //添加原型方法
}
}
//调用
const user = new User()
user[name]()
3.2 getter和setter
在es5的中使用 Object.defineProperty 定义一个属性的读取和设置(改变属性为存储器属性),现在可以使用这两个语法糖处理
csharp
const name = "getUserInfo"
class User {
constructor (name,age) {
this.name = name;
this.age = age;
} //constructor等于上面的构造函数User
//给age赋值运行这个方法
set age(){}
//获取age的时候运行这个方法
get age(){}
}
//调用
const user = new User()
user[name]()
3.3 静态成员
什么是静态成员:构造函数本身的成员,例如
csharp
function User (){
}
User.test = "test11"
//或者
class User {
}
User.test = "test111"
name就是静态成员
一张图表示清楚
graph TD
A[构造函数,构造函数本身的成员称为静态成员] --> |prototype|B[构造函数的原型,这上面的属性也称为实例成员]
A[构造函数] -->|new|C[通过new对象,此时里面的属性为实例成员]
C -->|__proto__|B
由上图可知,静态成员只能通过构造函数访问。
那么静态成员什么时候会使用到呢?
如下有个例子,比如现在生产一辆车,车的名字叫宝马,获取车的名字,则需要先生产车再获取名字
javascript
class Car {
constructor(){
this.name = "baoma"
}
}
const car = new Car()
car.name
显示是不合理的,因此静态成员就可以让这些环节变得简单
javascript
class Car {
static name = "baoma"
constructor(){
}
}
Car.name
不用生产车就知道车的名字了
注意不仅仅是属性可以变成静态成员,方法也可以是静态成员
3.4 属性初始化(es2016提供)
当class创建时,进行初始化赋值不再像下面这样赋值
kotlin
class Car {
constructor(){
this.name = "baoma"
}
}
可以直接=赋值
ini
class Car {
name = "baoma"
}
3.5 类表达式
arduino
const Test = class {} //匿名表达式
3.6 装饰器(es2016)、Decorator
使用场景
- 当某些方法过时
javascript
class Test {
print(){} // 方法过时了,需要给个提示
}
语法 @装饰器名称
+ 装饰器名称函数
javascript
class Test {
@Obsolete
print(){} // 方法过时了,需要给个提示
}
function Obsolete(target,methodName,descriptor){
console.log(target,methodName,descriptor)
//function Test , print ,{value:function print(){}}
const oldFunc = descriptor.value
descriptor.value = functiong (...args){
console.log(`${methodName}方法已经过时了`)
oldFunc.apply(this,args)
}
}
目前浏览器暂时还不支持,但是后续typescript和babel可以让编译后让它支持
四、类的继承
怎么判断是否需要继承
如果A和B两个类,可以描述为B是A,则A和B形成继承关系,A就是B的父类,B会自动拥有A中的所有实例成员。
scala
class Animal {
constructor(type,name,age,sex){
this.type = type;
this.name = name;
this.age = age;
this.sex = sex;
}
}
class Dog extends Animal {
constructor(name,age,sex){
super("dog",name,age,sex)
}
}
注意es6要求,如果定义了constructor,并且该类是子类,则必须在constructor的第一行手动调用父类构造函数super()
如果子类不写constructor,则会有默认的构造器,该构造器需要的参数和父类一致,并且自动调用父类构造器super()
关键字
- extends:继承,用于类的定义
- supper
- 直接当作函数调用,表示父类构造函数
- 如果直接当作对象,则表示父类的原型(用于子类需要调用父类原型上的方法,或者获取原型上的值)