一. Class的基本用法
1. 构造函数和对象: ES5和ES6
1.1 ES5里构造函数和对象是怎么使用的?
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.say = function(words){
console.log(`${this.name} is saying ${words}.`);
}
let p = new Person('半个头像的女同学', 22);
p.say('hello World'); // 半个头像的女同学 is saying hello World
1.2 ES6里构造函数的写法
let methodName = 'say';
class Person {
// 写成这个样子的函数都是在prototype上的。
constructor(name, age){
this.name = name;
this.age = age;
}
[methodName](words){
console.log(`${this.name} is saying ${words}.`);
}
}
let p = new Person('半个头像的女同学', 25);
p.say('hello world');
let p1 = new Person('半个头像的女同学', 27);
1.3 ES6写法与ES5写法的相同点
let methodName = 'say';
class Person {
// 写成这个样子的函数都是在prototype上的。
constructor(name, age){
this.name = name;
this.age = age;
}
[methodName](words){
console.log(`${this.name} is saying ${words}.`);
}
}
let p = new Person('半个头像的女同学', 25);
p.say('hello world');
let p1 = new Person('半个头像的女同学', 27);
// 1.3 ES6写法与ES5写法的相同点
console.log(Person === Person.prototype.constructor); // true
console.log(p.constructor === Person.prototype.constructor); // true
console.log(p.say === Person.prototype.say); // true
1.4 ES6写法与ES5写法的不同点
1.4.1 用class方式实现的函数,是不可以枚举的。
let methodName = 'say';
class Person {
// 写成这个样子的函数都是在prototype上的。
constructor(name, age){
this.name = name;
this.age = age;
}
[methodName](words){
console.log(`${this.name} is saying ${words}.`);
}
}
let p = new Person('半个头像的女同学', 25);
p.say('hello world');
let p1 = new Person('半个头像的女同学', 27);
console.log(Object.keys(Person.prototype)); // []
但是我们看ES5的情况,是可以枚举的
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.say = function(words){
console.log(`${this.name} is saying ${words}.`);
}
let p = new Person('半个头像的女同学', 22);
p.say('hello World');
console.log(Object.keys(Person.prototype)); // ["say"]
1.4.2 用class方式实现的构造函数,调用时必须使用new,否则报错
let methodName = 'say';
class Person {
// 写成这个样子的函数都是在prototype上的。
constructor(name, age){
this.name = name;
this.age = age;
}
[methodName](words){
console.log(`${this.name} is saying ${words}.`);
}
}
let p = new Person('半个头像的女同学', 25);
p.say('hello world');
let p1 = new Person('半个头像的女同学', 27);
Person('aaa', 25); // 这里会报错,没有new
报错
但是我们看ES5可以
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.say = function(words){
console.log(`${this.name} is saying ${words}.`);
}
let p = new Person('半个头像的女同学', 22);
p.say('hello World');
Person('aaa', 25); // 半个头像的女同学 is saying hello World.
2. class 表达式
let Person = class {
constructor(name, age){
this.name = name;
this.age = age;
}
say(words){
console.log(`${this.name} is saying ${words}.`);
}
}
2.1 表达式可以写成立即执行函数的形式
let person = new class Person{ // 注意:这里有个new
constructor(name, age){
this.name = name;
this.age = age;
}
say(words){
console.log(`${this.name} is saying ${words}.`);
}
}('半个头像的女同学', '18');
person.say('hello'); // 半个头像的女同学 is saying hello.
2.2 Person 可以省略(class表达式可以没有class名字)
let person = new class { // 看:这里的Person可以省略
constructor(name, age){
this.name = name;
this.age = age;
}
say(words){
console.log(`${this.name} is saying ${words}.`);
}
}('半个头像的女同学', '18');
person.say('hello'); // 半个头像的女同学 is saying hello.
3. function 会提升变量; 但是class不会
所有的class,必须先定义后使用
// 用function不会报错
console.log(Person);
function Person(){
}
// 用class会报错
console.log(Person); // Person is not defined
class Person {
constructor(){
}
}
// 用class写在后面就不会报错
class Person {
constructor(){
}
}
console.log(Person);
4. this的指向
我们先看一下下面的代码有什么问题
class Person {
constructor(name, age){
this.name = name;
this.age = age;
}
say(words){
console.log(`${this.name} is saying ${words}.`); // a
}
}
let p = new Person('半个头像的女同学', 22);
let f = p.say;
f('Hello');
结果会报错:Cannot read property 'name' of undefined
我们经常会遇到把对象里的函数给到另一个变量,后面再使用的情况,但是函数里的this不能正确获取。
上a处获取不到变量,因为this是在调用的时候确定的,而调用f的是windows,window里没有name,所以报错,解决办法:
4.1 用箭头函数
箭头函数里的this是定义的时候确定的,所以name已经确定了用Person对象里的,所以即使在调用f的是windows,但是name还是用Person对象里的name,最后能正常输出。
class Person {
constructor(name, age){
this.name = name;
this.age = age;
// 箭头函数
this.say = words => {
console.log(`${this.name} is saying ${words}.`);
}
}
}
let p = new Person('半个头像的女同学', 22);
let f = p.say;
f('Hello');
4.2 用bind绑定
class Person {
constructor(name, age){
this.name = name;
this.age = age;
this.say = this.say.bind(this); // 这里
}
say(words){
console.log(`${this.name} is saying ${words}.`); // a
}
}
let p = new Person('半个头像的女同学', 22);
let f = p.say;
f('Hello'); // 半个头像的女同学 is saying Hello.
5. getter/setter, 属性
let obj = {
_a: 0,
get a(){
return this._a;
},
set a(x){
this._a = x;
}
}
console.log(obj.a); // 0 调用get方法
obj.a = 3; // 调用set方法
console.log(obj.a); // 3
6. 静态属性和静态方法
你们看下面的代码有什么错误
class Person {
// 静态属性
static staticNumber = 5;
// 静态方法
static staticMethod(words){
console.log(`static say ${words}`);
}
constructor(){
}
}
let p = new Person();
p.staticMethod('hello!');
结果报错:p.staticMethod is not a function
因为静态属性和静态方法不同通过new的方式调用。
正确写法如下:
class Person {
// 静态属性
static staticNumber = 5;
// 静态方法
static staticMethod(words){
console.log(`static say ${words}`);
}
constructor(){
}
}
Person.staticMethod('hello!'); // static say hello!
console.log(Person.staticNumber); // 5
等价于这个写法
class Person {
constructor(){
}
}
Person.staticNumber = 5;
Person.staticMethod = words => {
console.log(`static say ${words}`);
}
Person.staticMethod('hello!');
console.log(Person.staticNumber);
二. Class的继承
1. 继承怎么用?
- 通过调用基类构造函数继承
- super()代表基类构造函数
- 2.1 super()必须在构造函数中调用,ES5中,先有this,后调用基类构造函数;ES6中,this是super初始化的。
- 2.2 在派生类构造函数完成以前,必须调用基类构造函数
- 2.3 在调用super以前,不能使用this
class Person {
constructor(name, age){
this.name = name;
this.age = age;
this.say = this.say.bind(this);
}
say(words){
console.log(`${this.name} is saying ${words}.`);
}
}
class Student extends Person{ // 派生类 派生 自基类,任何派生类的实例都是基类的实例
constructor(studentId, ...args){
// 2.1和2.2 必须通过super调用基类构造函数
super(...args);
console.log(1, args);
// 2.3 在调用super以前,不能使用this
this.studentId = studentId;
this.study = this.study.bind(this);
}
study(knowledge){
console.log(`Student ${this.studentId} is studying ${knowledge}`);
}
}
let xiaohong = new Student(1, '小红', 13);
xiaohong.say('hello!');
console.log(xiaohong.name);
xiaohong.study('物理');
console.log(xiaohong.studentId);
他们的继承关系是这样的
继承关系 Student.prototype是Person构造出来的
console.log(Object.getPrototypeOf(Student) === Person); // true
console.log(Student.prototype.__proto__ === Person.prototype); // true
2.把supper拿下
2.1 super.属性
情况1:在读取值的时候,充当基类的原型对象
// 基类
class Person {
constructor(name, age){
this.name = name;
this.age = age;
}
}
// 派生类
class Student extends Person{
constructor(studentId, ...args){
super(...args);
this.studentId = studentId;
}
test(){
console.log(super.name, this.name); // undefined "半个头像的女同学"
}
}
let student = new Student(1,'半个头像的女同学',22);
student.test();
派生类里,super.name读取不到基类里的name属性,需要通过this.name才能读取到。
情况2:在赋值的时候,充当派生类this使用
// 基类
class Person {
constructor(name, age){
this.name = name;
this.age = age;
}
}
// 派生类
class Student extends Person{
constructor(studentId, ...args){
super(...args);
this.studentId = studentId;
}
test(){
super.name = '女同学';
console.log(super.name, this.name); // 这里输出:undefined "女同学"
}
}
let student = new Student(1,'半个头像的女同学',22);
student.test();
派生类里,如果用super.name进行赋值,可以当成this.name进行使用。
情况3:如果super后面是方法,调用的是基类原型的方法,但是函数中绑定的this是派生类的
// 基类
class Person {
constructor(name, age){
this.name = name;
this.age = age;
}
f(){
console.log('in Person');
}
}
// 派生类
class Student extends Person{
constructor(studentId, ...args){
super(...args);
this.studentId = studentId;
}
test(){
console.log(super.f, this.f); // 这里输出:in Person in Student
}
f(){
console.log('in Student');
}
}
let student = new Student(1,'半个头像的女同学',22);
student.test();
super.属性是读取不到基类的属性,但是super.方法是可以读取得到基类的方法,但是this.方法是派生类的方法。