class的基本写法:
class Person {} // √
class Person() {} // ×
class Person {}; // ×
实例化时会自动执行构造方法中的所有代码,所以必须有构造方法(constructor(){}),但可以不写出来,不写时候相当于浏览器自动给我们加上了,但推荐大家还是写上。
我们的class不能直接通过像函数调用那样进行调用,必须通过new实例化的方法进行调用。
class Person{
constructor(){
console.log("实例化时执行!");
}
}
const p = new Person();
在实例化的时候constructor里面的东西会执行,本案例是:实例化时执行!
给constructor里面的this添加属性就是给class实例化后的对象添加属性:
class Person{
constructor(name,age){
console.log("实例化时执行!");
this.name = name;
this.age = age;
}
}
const p = new Person('ZS',19);
console.log(p.name); // ZS
console.log(p.age); // 19
一般在构造方法中(constructor里面)定义属性,方法不在构造方法中定义,因为定义到constructor里面的方法不共用:见代码:
class Person{
constructor(name,age){
console.log("实例化时执行!");
this.name = name;
this.age = age;
this.speak = () =>{};
}
}
const zs = new Person('ZS',19);
console.log(zs.name); // ZS
console.log(zs.age); // 19
const ls = new Person('LiSi',99);
console.log(ls.name); // LiSi
console.log(ls.age); // 99
console.log(zs.speak === ls.speak); // false
这个案例中的false说明在constructor中的方法,会根据不同实例化而出现不同的引用,那么如果实例化很多次,那么就会占据大量的空间,所以在开发中我们一般不希望这样做,所以一般将方法写到constructor的外边。见代码:
一般在constructor里面写属性,在外边写方法,一般方法是可以公用的。
class Person{
constructor(name,age){
console.log("实例化时执行!");
this.name = name;
this.age = age;
}
// 一般在构造方法中定义属性,方法不在构造方法中定义
// 各实例共享的方法
speak() {
console.log("speak");
}
}
const zs = new Person('ZS',19);
zs.speak(); // speak
const ls = new Person('LiSi',99);
ls.speak(); // speak
console.log(zs.speak === ls.speak); // true
// 方法这样写的时候都能调用到!
class与构造函数:
function Person(name, age) {
this.name = name;
this.age = age;
}
// 构造函数的添加方法的例子:在原型链上添加:
Person.prototype.speak = function () {};
但是class的出现就大大方便了许多,但class的底层也是构造函数:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log('speak');
}
}
Person.prototype.run = function(){}; // 相当于在class中写了run方法
console.log(typeof Person);
console.log(Person.prototype.speak); // 也可以调用到class中的speak方法
上面的代码只是为了让大家明白class的实质是构造函数,但极度不推荐大家像上面代码的方式去写啊!大家知道class的实质是构造函数就行了。
class的两种定义形式:
一、声明的形式(重点关注):
class Person {
constructor() {}
speak() {}
}
二、表达式的形式(知道就行):
const Person = class {
constructor() {
console.log('constructor');
}
speak() {}
};
new Person();
在构造函数中我们知道有立即执行的匿名函数,所以也有立即执行的匿名类:
new (class {
constructor() {
console.log('constructor');
}
})(); // constructor
实例属性、静态方法和静态属性:
一、实例属性(重点关注——会用会写):
class Person {
age = 0; // 在与构造方法同级的地方设置属性时直接写就行,不用用const let var等进行声明,也不许要写this
sex = 'male';
constructor() {
}
speak() {
this.age = 18; // 在方法中添加属性时要带上age
}
}
一般在与构造函数同级的地方设置默认的属性值其实是用来当默认值的。见代码:
class Person {
age = 0; // 默认值
sex = 'male'; // 默认值
constructor(name, sex) {
this.name = name;
this.sex = sex;
}
speak() {
this.age = 18;
}
}
还有一个实例方法,方法就是值为函数的特殊属性。见代码:
class Person {
age = 0;
sex = 'male';
// 实例方法:方法就是值为函数的特殊属性
getSex = function () {
return this.sex;
};
constructor(name, sex) {
this.name = name;
this.sex = sex;
}
speak() {
this.age = 18;
}
}
二、静态方法:
静态方法说穿了就是类的方法,就是不需要实例化类就能直接调用:
静态方法用关键字:static
class Person {
constructor(name) {
this.name = name;
}
speak() {
console.log('speak');
// this指向实例化的对象
console.log(this);
}
static speak() {
console.log('人类可以说话');
// this 指向类
console.log(this);
}
}
const p = new Person('Alex');
p.speak(); // speak
Person.speak(); // 人类可以说话
静态方法也可以这样写:
class Person {
constructor(name) {
this.name = name;
}
speak() {
console.log('speak');
console.log(this);
}
}
// 也可以通过这种方式给类添加静态方法:
Person.speak = function () {
console.log('人类可以说话');
console.log(this);
};
const p = new Person('Alex');
p.speak(); // speak
Person.speak(); // 人类可以说话
当然既然能写里面就不要写在外面了,因为类一般相当于一种封装,所以尽量写里面,尽管写外面不会报错,但还是推荐写里面。
三、静态属性:
静态属性就是类的属性:
class Person {
constructor(name) {
this.name = name;
}
static version = '1.0';
}
const p = new Person('Alex');
console.log(p.name);
console.log(Person.version);
学过了静态方法,大家可能用上面那种方式给类添加静态属性,但是不要像上面那种方式写静态属性,因为目前那种写法只是提案,存在兼容性的问题。
所以到底该怎样写呢:见代码:
1、可以在外面写:
class Person {
constructor(name) {
this.name = name;
}
}
Person.version = '1.0';
console.log(Person.version); // 1.0
但这种写法在外面也不是很好,违反了封装的原则。
2、用变通的方法进行书写:将属性值当作函数的返回值进行书写。
class Person {
constructor(name) {
this.name = name;
}
static getVersion() {
return '1.0';
}
}
console.log(Person.getVersion()); // 1.0
对上面学习的做个检测吧:
答案:
class Person {
constructor(name,age){
this.name = name;
this.age = age;
}
say(){
console.log('say');
alert(p.name,p.age);
}
static intro() {
return 'this is a Person class'
}
static show() {
console.log('show');
}
}
const p = new Person('Alex',10);
上面如果调用say方法时的alert只会弹出第一个值,即p.name的值,这点注意!
总结:只要给Person加方法的只要直接Person后跟方法名子的,那就是给类添加静态方法,如果Person名字后面跟prototype再跟方法名就是添加实例方法,还有就是实例方法一般写到构造函数(constructor)的外边(原因是:方法一般是公用的),以及静态属性通过变通的方法进行书写(写成一个静态方法形式,返回值return就是静态属性值)。
extends:
1、子类继承父类:
class Person {
constructor(name, sex) {
this.name = name;
this.sex = sex;
this.say = function () {
console.log('say');
};
}
speak() {
console.log('speak');
}
static speak() {
console.log('static speak');
}
}
Person.version = '1.0';
// 通过关键字 extends 进行父类的继承:
class Programmer extends Person {
constructor(name, sex) {
super(name, sex); // 通过关键字super进行调用父类,必写的一部
}
}
const zs = new Programmer('zs', '男');
console.log(zs.name); // zs
console.log(zs.sex); // 男
zs.say(); // say
zs.speak(); // speak
Programmer.speak(); // static speak
console.log(Programmer.version); // 1.0
2、改写继承的属性或方法:
(1)同名覆盖——只要子类与父类的同名,子类就会把父类的覆盖掉。
class Person {
constructor(name, sex) {
this.name = name;
this.sex = sex;
this.say = function () {
console.log('say');
};
}
speak() {
console.log('speak');
}
static speak() {
console.log('static speak');
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name,sex);
}
speak() {
console.log('Programmer speak');
}
static speak() {
console.log('Programmer static speak');
}
}
const zs = new Programmer('zs', '男');
zs.speak() // Programmer speak
Programmer.speak() // Programmer static speak
在这个例子中子类的 speak() 和 static speak() 覆盖了父类的 speak() 和 static speak(),所以打印出来:Programmer speak 和 Programmer static speak。
添加父类没有的属性:
class Person {
constructor(name, sex) {
this.name = name;
this.sex = sex;
this.say = function () {
console.log('say');
};
}
speak() {
console.log('speak');
}
static speak() {
console.log('static speak');
}
}
class Programmer extends Person {
constructor(name, sex,feature) {
super(name,sex);
this.feature = feature; // 添加父类没有的属性
// 这里有个细节就是 this 操作不能写在 super 的前面,只能写在super的后面!这个大家注意!
}
hi() { // 添加子类的新方法时直接添加就完事了!
console.log('hi');
}
speak() {
console.log('Programmer speak');
}
static speak() {
console.log('Programmer static speak');
}
}
const zs = new Programmer('zs', '男','秃头');
zs.speak() // Programmer speak
Programmer.speak() // Programmer static speak
zs.hi() // hi
在给自己添加属性的时候的this操作不能写在super的前面,只能写在super的后面,写前面会报错,这个大家注意!
super:
1、作为函数去调用:
代表父类的构造方法,只能用在子类的构造方法(constructor)中,用在其他地方就会报错。
还有super 虽然代表了父类的构造方法,但是内部的 this 指向子类的实例(指向子类实例化的对象):
// super 虽然代表了父类的构造方法,但是内部的 this 指向子类的实例:
class Person {
constructor(name) {
this.name = name;
console.log(this);
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex);
}
}
new Programmer(); // Programmer {name: undefined}
2、作为对象来使用:
(1)在构造方法中使用:
super 代表父类的原型对象 Person.prototype。
但定义在父类实例上的方法(在构造函数里面的this的方法)或属性,是无法通过 super 调用的。
class Person {
constructor(name) {
this.name = name;
}
speak() {
console.log('speak');
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex);
console.log(super.name); // 因为 super 代表父类的原型对象 Person.prototype,又因为 super.name 不在父类的 prototype 上,所以打印出来的是 undefined ,即:定义在父类实例上的方法或属性,是无法通过 super 调用的
super.speak(); // 因为 super 代表父类的原型对象 Person.prototype,又因为speak方法在父类的 prototype 上,所以可以找到,因此打印出来 speak
}
}
new Programmer();
通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例:
class Person {
constructor(name) {
this.name = name;
}
speak() {
console.log('speak');
console.log(this); // 指向当前的子类实例
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex);
console.log(super.name); // undefined
super.speak(); // speak
}
}
// Programmer {name: undefined} 指向的是当前子类的实例
(2)在一般方法中使用:
super 代表父类的原型对象 Person.prototype。
但定义在父类实例上的方法(在构造函数里面的this的方法)或属性,是无法通过 super 调用的。
通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例:
class Person {
constructor(name) {
this.name = name;
}
speak() {
console.log('speak');
console.log(this);
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex);
}
speak() {
super.speak();
console.log('Programmer speak');
}
}
const p = new Programmer();
p.speak();
// speak
// Programmer {name: undefined}
// Programmer speak
(3)在静态方法中的使用:
之前有个同名覆盖,就是子类会将父类同名的东西覆盖掉,但在实际中一般不用同名覆盖,那怎么办呢?这里有妙招:
在静态方法中super指向父类,而不是父类的原型对象。
class Person {
constructor(name) {
this.name = name;
console.log(this);
}
speak() {
console.log('speak');
}
static speak() {
console.log('Person speak');
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex);
}
static speak() {
// 指向父类,而不是父类的原型
super.speak();
console.log('Programmer speak');
}
}
Programmer.speak();
// Person speak
// Programmer speak
通过 super 调用父类的静态方法时,方法内部的 this 指向当前的子类,而不是子类的实例。
class Person {
constructor(name) {
this.name = name;
console.log(this);
}
speak() {
console.log('speak');
}
static speak() {
console.log('Person speak');
console.log(this); // 指向当前的子类,而不是子类的实例
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex);
}
static speak() {
// 指向父类,而不是父类的原型
super.speak();
console.log('Programmer speak');
}
}
Programmer.speak();
这个例子的答案请大家自行演示,this指向的是子类。
3、注意事项:
使用 super 的时候,必须显式指定是作为函数还是作为对象使用,否则会报错。
class Person {
constructor(name) {
this.name = name;
}
speak() {
console.log('speak');
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex);
// console.log(super); // 直接写是会报错的,因为浏览器不知道super是作为对象还是作为函数使用的
// console.log(super()); // 不会报错,很明显super是作为函数用的
// console.log(super.speak); // 不会报错,明显super是作为对象用的
}
}