intro
在ES6中,引入了class
关键字用于快速地定义“类”。
在JS中,“类”的本质是function
可以将其看做一个语法糖,让对象原型的写法更简洁清晰,更像面向对象编程的语法。
涉及关键字:
class 声明类模板
constructor 构造方法
get getter方法
set setter方法
static 静态成员
extends 继承
super 在子类中表示父类对象
类定义
- 常规
类名不能重复定义。
类class
的本质是函数function
。
class Person {
constructor(a) {
this.a = a;
}
}
console.log(typeof Person); // function
// class Person {} // 报错:不能重复定义
// Uncaught SyntaxError: Identifier 'Person' has already been declared
- 类表达式
将类定义部分作为一个表达式,赋值(其地址的值)给一个变量。
类表达式中的类名 可省略, 可重复。
创建对象时,使用的是接受类表达式(地址)的变量名 充当类名。
如果使用类表达式中的类名创建对象,会报错。
var p = class Person {}; // 有名类表达式
new p(); // Person {}
// new Person(); // Uncaught ReferenceError: Person is not defined
var p = class {}; // 匿名类表达式
var a = class AAA {};
var b = class AAA {}; // 不报错
console.log(a == b); // false, a和b是两个不同的类(类声明)。
-
类定义不会提升(hoisting)
使用class
简洁地定义类,应该先定义,后使用。否则报错。 -
类中的方法
-
方法不能使用
function
关键字。 -
方法之间使用分隔符
;
,可省略(会自动补全)。 -
原型变量之间的分隔符也是
;
。
-
静态成员和原型成员
ES6中,虽然新增了class
关键字用于类定义,但其实质还是function
,只不过是为了方便书写,随后经过了某种源代码编译过程。
prototype
仍然存在。在类体中定义的方法还是定义在prototype
上。
class Person {}
类 :Person
原型:Person.prototype
-
原型成员
无static
关键字的成员。
this.xxx
类名.prototype.xxx
class Person { // xxx name = "无"; eat() { // 原型方法 推荐写法 console.log("eat func"); }; // this.xxx 打印对象时会打印出来。 constructor(a) { this.age = a; // 原型属性 推荐写法 this.drink = function() { console.log("drink func"); }; }; } // 类名.prototype.xxx 打印对象时不会打印。但可以访问到。 Person.prototype.gender = "男"; // 不推荐 Person.prototype.foo = function() { // 可以 console.log("foo func"); }; var a = new Person(22); a; // Person {name: "无", age: 22, drink: ƒ} Person.prototype; // {gender: "男", foo: ƒ, constructor: ƒ, eat: ƒ}
推荐:
- 原型属性就在
constructor
中用this.xxx
即可。 - 原型方法推荐写在类体中,也可以手动添加到
类名.prototype
对象中。这两种都行。 - 统一规则,尽量避免出现其他写法。
- 原型属性就在
-
静态成员
带关键字static
类名.xxx
ES6中规定,class内部只有静态方法,没有静态属性。
但是在新提案中,有静态属性。
现阶段:- 构造方法
constructor()
中不能调用静态成员(方法,属性) - 静态方法中不能调用静态属性(不论类体内,还是外部)。
- 否则报错:
xxx is not defined
class Person { stativ version = "1.0"; // 不推荐,并非ES6中实现的标准 static sum(a, b) {return a + b;}; // 静态方法 推荐写法 constructor() {} } Person.desc = "人类"; // 静态属性 推荐写法 Person.sub = function(a, b) {return a - b;};
因为版本实现问题,推荐写法:
- 静态属性,一定在类体外用
类名.xxx = value;
定义。 - 静态方法写在类体内部用
static
声明,或写在外部也可以(两种都行)。
NOTE - 构造方法中不得访问静态成员。
- 静态成员之间不得互相调用(静态方法内访问静态属性,静态方法互相调用)
- 构造方法
-
向对象添加成员(成员方法,成员属性)
向类名
添加成员,就相当于静态成员。
向类名.prototype
添加成员,就相当于原型成员。
有两种方法:
obj.key = value;
Object.assign(target, ...sources);
常见方法
constructor
constructor
方法是类的默认构造方法,创建类对象的时候被调用。
可以在constructor
方法中返回指定的类型对象(可与本类不相同)。
class One {
constructor() {
console.log("One constructor()");
// return this; // 默认返回this实例对象
}
}
class Two {
constructor() {
return new One();
}
}
var a = new Two(); // One constructor()
console.log(a instanceof Two); // false
console.log(a instanceof One); // true
可见,使用new
操作符实例化出的对象,实质是什么类型,取决于其构造方法constructor()
的返回值。
getters, setters
- 关键字
set
和get
,置于方法名前(如get name() {...}
和set name() {...}
)。 setter, getter
中访问成员属性的格式:this._xxx
而非this.xxx
。- 任何读操作都会调用
get
方法 - 任何写操作都会调用
set
方法- 构造方法内部
this.prop = value
; - 类体外部
obj.prop = value
;
- 构造方法内部
getter, setter
不可单独出现- 只有
setter
,不能通过obj.xxx
读取xxx
的值。但可以通过打印obj
查看xxx
的值。 - 只有
getter
,- 如果
constructor()
中有对应的this.xxx=value;
的语句,则 因为没有setter,所以实例化时会报错。 - 如果
constructor()
中没有this.xxx=value;
的语句。则实例化时不报错。
但是,没有意义(这个属性不set
值,用不了,只能get
到undefined
的值)。
- 如果
- 只有
getter, setter
必须同级出现(继承关系中)。
- 关键字
class Person {
constructor(age) {
this.age = age;
};
get age() {
console.log("get age()");
return this._age; // 注意是this._age
};
set age(age) {
console.log("set age()");
this._age = age; // 注意是this._age
}
}
var p = new Person(22); // set age()
console.log(p); // Person {_age: 22}
console.log(p.age); // 22
console.log(p._age); // 22
在getter, setter
中调用原型属性:this._xxx
,
而非this.xxx
,否则会在实例化对象(调用构造方法时)报错RangeError
。
decorator
decorator
是一个函数,用来修改类的行为,在代码编译时产生作用。
decorator
方法不是ES6标准,是ES7的新提案之一。
后期再深入。
extends
关键字:extends, super
-
super
super
表示父类。super(...argsList)
表示父类的构造方法super.xxx
可以访问父类的成员属性super.xxx()
可以访问父类的成员方法
-
在子类的构造犯法中访问
this
或返回返回值之前,必须先调用父类的构造方法super()
。
Must call super constructor in derived class before accessing 'this' or returning from derived constructor
。
class Father {
constructor(firstName) {
console.log("father...");
this.firstName = firstName;
}
}
class Child extends Father {
constructor(firstName, lastName) {
super(firstName);
console.log("child...");
this.lastName = lastName;
}
}
Father.prototype.isPrototypeOf(Child.prototype); // true
var a = new Child("J", "DD");
// father...
// child...
// Child {firstName: "J", lastName: "DD"}
- 子类中调用父类的方法
- 原型方法:在子类的原型方法中
super.xxx()
,其中xxx
为原型方法名。 - 静态方法:在子类的静态方法中
super.xxx()
或父类名.xxx()
,其中xxx
为静态方法名。
- 原型方法:在子类的原型方法中
class Father {
foo() {console.log("foo...");}
static bar() {console.log("bar...")}
}
class Child extends Father {
f() {
super.foo(); // 在子类的原型方法中调用父类的原型方法
}
static b() {
super.bar(); // 在子类的静态方法中调用父类的原型方法
Father.bar();
}
}
Child.b() // 用类名调用静态方法
// bar...
// bar...
(new Child()).f() // 用对象调用原型方法
// foo...
即:
父类的构造方法可以在子类的构造方法中被调用。
父类的原型方法可以在子类的原型方法中被调用。
父类的静态方法可以再子类的静态方法中被调用。
否则出错。
- 不可以继承对象字面量
var obj = {name:"JT", showName:function() {console.log(this.name)}};
如果想定义一个类Child
,也拥有这个对象的成员。
- 直接继承对象字面量 - 不可以,报错。
Uncaught TypeError: Class extends value #<Object> is not a constructor or null
var Father = {name:"JT"};
class Child extends Father {};
- 操作原型链
var Father = {name:"JT"};
class Child {};
Object.setPrototypeOf(Child.prototype, Father);
(new Child()).name; // "JT"