es6 class 语法
什么是类?
用通俗易懂的话来说,手机就是一个类,而你手的手机就是这个类的对象;电脑是一个类,而你的电脑就是这个类的对象。也就是说类是一个种类,而对象就是这个类中一个具体的个体。类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
类是面向对象语言的一个概念(比如java),js 中生成实例对象的传统方法是通过构造函数,然后通过原型实现继承。es6 提供了 class 语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
使用 class 关键字定义一个类
class Dog {
constructor(name, age, color) {
this.name = name
this.age = age
this.color = color
}
handleDetails() {
return '姓名:'+this.name+', 年龄:'+this.age+'岁, 颜色:'+this.color
}
}
狗是一个类,某一只狗就是这个类的对象。上面代码定义了一个类,constructor() 就是构造方法,this 关键字则代表实例对象,类里面定义了一个方法。
使用 new 关键字实例化一个对象
const dog = new Dog('大黄', 3, '黄色');
const myDog = new Dog('小虎', 5, '棕色');
dog.handleDetails() // "姓名:大黄, 年龄:3岁, 颜色:黄色"
myDog.handleDetails() // "姓名:小虎, 年龄:5岁, 颜色:棕色"
使用 new 关键字创建一个对象,对象可以使用类中的方法。
constructor 方法
constructor() 是类的默认方法,new 一个对象时会调用该方法,一个类必须要有 constructor() 方法,如果不写,则会默认添加此方法。
class B {
}
// 等同于
class B {
constructor() {}
}
实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
//定义类
class Dog {
constructor(name, color) {
this.name = name;
this.color= color;
}
toString() {
return '名字:' + this.name + ', 颜色:' + this.color;
}
}
var dog = new Dog('哮天犬', '黑色');
dog.toString() // '名字:哮天犬, 颜色:黑色'
dog.hasOwnProperty('name') // true
dog.hasOwnProperty('color') // true
dog.hasOwnProperty('toString') // false
dog.__proto__.hasOwnProperty('toString') // true
上面代码中,name 和 color 都是实例对象 dog 自身的属性(因为定义在 this 对象上),而 toString() 是原型对象的属性(因为定义在 Dog 类上)
var dog1 = new Dog('大黄', '黄色')
dog.__proto__ === dog1.__proto__ // true
上面代码中,dog1 和 dog 都是 Dog 的实例,它们的原型都是 Dog.prototype,所以__proto__属性是相等的。
这也意味着,可以通过实例的__proto__属性为“类”添加方法
class 与 es5 构造函数的关系
1. 生成实例对象的不同方式
// es6 class写法
class Dog {
constructor(name, age, color) {
this.name = name
this.age = age
this.color = color
}
handleDetails() {
return '姓名:'+this.name+', 年龄:'+this.age+'岁, 颜色:'+this.color
}
}
// 生成实例对象
const d = new Dog('大黄', 3, '黄色')
d.handleDetails() // "姓名:大黄, 年龄:3岁, 颜色:黄色"
// es5 构造函数写法
function Dog(name, age, color) {
this.name = name
this.age = age
this.color = color
}
Dog.prototype.handleDetails = function() {
return '姓名:'+this.name+', 年龄:'+this.age+'岁, 颜色:'+this.color
}
// 生成实例对象
const d = new Dog('啸天犬', '3000', '黑色')
d.handleDetails() // "姓名:啸天犬, 年龄:3000岁, 颜色:黑色"
从上面代码可以看出,ES6 的类,完全可以看作构造函数的另一种写法。
2.类的 prototype 属性
class B {
constructor() {}
toString() {}
handleValue() {}
}
typeof B // "function"
B === B.prototype.constructor // true
B.prototype = {
constructor() {},
toString() {}
handleValue() {}
}
new b = new B()
b.constructor === B.prototype.constructor // true
- 类的数据类型就是函数
- 构造函数的 prototype 属性,在 ES6 的类上面继续存在
- 类的本身就是构造函数
- 类的所有方法都定义在类的 prototype 属性上面,在类的实例上面调用方法,其实就是调用原型上的方法
- b是B类的实例对象,它的constructor()方法就是B类原型的constructor()方法。
3. 调用类和调用构造函数的区别
// 类
class Foo {
constructor() {
this.name = name
}
}
// 构造函数
function Bar(name) {
this.name = name
}
var foo = Foo('job') // Uncaught TypeError: Class constructor Foo cannot be invoked without 'new' at <anonymous>:1:1
var bar = Bar('job') // undefined
类必须使用 new 调用,否则会报错,构造函数不用 new 也可以执行。
静态方法
类中定义的方法都会被实例(对象)继承,如果在一个方法前加上 static 关键字,那么该方法不会被对象继承(可以被子类继承),而是直接通过类来调用,这就是静态方法。类中的公共方法都可以定义为静态方法。
class Dog {
static getLegs() {
return '狗有四条腿'
}
}
Dog.getLegs() // '狗有四条腿'
var dog = new Dog()
dog.getLegs() // TypeError: dog.getLegs is not a function
静态属性
静态属性与静态方法一样,都是 class 本身的属性,都通过类直接调用
class Dog {
static eyesProp = 2
}
Dog.eyesProp // 2
var dog = new Dog()
dog.eyesProp // undefined
私有属性和私有方法
私有属性和私有方法,只能在类的内部访问,外部不能访问。在属性或者方法名前加上符号 #
就表示这是个私有属性或方法。
class Dog {
#name = '大黄' // 私有属性
#getName() { // 私有方法
return '它的名字叫' + this.#name
}
printName() {
console.log(this.#getName())
}
}
var dog = new Dog()
dog.printName() // 它的名字叫大黄
dog.#name // 报错
dog.#getName() // 报错
Class 的继承
es5 是通过原型链实现继承,Class 通过extends
实现继承
class Dog {
constructor() {
this.eyes = 'two'
}
getLegs() {
return '狗有四条腿'
}
}
class SmallDog extends Dog {
}
var smallDog = new SmallDog()
smallDog.eyes // two
smallDog.getLegs() // '狗有四条腿'
上面代码中,子类继承了父类的属性和方法
继承的时候,必须在子类的constructor
中调用super
方法,否则会报错。因为ES6 的继承机制是先新建父类的this
对象,得到与父类同样的实例属性和方法,然后再对其加工创建子类的this
。而super
表示的就是父类的构造函数,是用来新建父类的this
对象的,如果不调用super
方法,子类就得不到this
对象,如果子类没有写constructor
方法,这个方法会被默认添加。
class SmallDog extends Dog {
}
// 等同于
class SmallDog extends Dog {
constructor() {
super()
}
}
因为子类的this
对象是通过父类的构造函数完成塑造的,所以子类调用this
应该写在super
方法之后
class Dog {
constructor() {
this.eyes = 'two'
}
getLegs() {
return '狗有四条腿'
}
}
class SmallDog extends Dog {
constructor() {
this.color = 'yellow' // ReferenceError
super()
this.color = 'yellow' // 正确
}
getSmallDogLegs() {
return super.getLegs() + ',但是小型犬的腿很短' // '狗有四条腿,但是小型犬的腿很短'
}
}
super
方法应该写在constructor
方法的第一行