class总览:
- ES6 中的 class 可以看做是一个语法糖,它的功能用ES5 也可以实现,只是写法更加清晰明了(因为更像面向对象编程的语法);
- ES6 中类和模块内部都是默认使用严格模式,无需再加上”use strict”;
- class 的数据类型是”function”,因为类本身其实就是个构造函数;
上实例:
class Test {
constructor (x, y) { this.x= x; this.y = y; }
mix () { return '('+ this.x+ '-'+ this.y+ ')'; }
toStr () {} //方法间不需要用逗号隔开
toNum () {}
}
typeof Test //"function" 说明类的数据类型是函数
Test === Test.prototype.constructor //true 说明类本身就指向构造函数
let test = new Test(1,3); //必须用new 来调用,否则报错
//在类的实例上调用方法,实际上就是调用原型上的方法:
test.mix(); //(1-3)
test.constructor === Test.prototype.constructor
//true 说明实例的constructor 方法就是Test 类原型的constructor 方法
另外,可以用Object.assign
方法一次向类的原型上添加多个方法
class Test {
constructor (x, y) {
this.x= x;
this.y = y;
}
Object.assign (Test.prototype, {
toStr () {}, //需要加上逗号隔开各个方法
toNum () {}
});
}
1、constructor 方法
如果不加constructor 方法,引擎会帮你加一个空的constructor 方法;
这个constructor 方法默认返回实例对象(也就是this),如果手动去返回另一个对象,会导致实例对象不是 这个类的实例;
class Test {
constructor () {
return Object.create(null);
}
}
new Test() instanceof Test // false
let test = new Test(); //必须用new 来调用,否则报错
2、类的实例对象
上面已经说过,生成实例必须要用 new,如果像调用函数一样就会报错;
实例对象的属性除非是显式定义在this 对象上(也就是定义在constructor 方法内),否则都是在类的原型上;
class Test {
constructor (x, y) {
this.x= x;
this.y = y;
}
Object.assign (Test.prototype, {
toStr () {
return '('+ this.x+ '-'+ this.y+ ')';
},
toNum () {}
});
}
let test = new Test(3,2);
test.toStr(); //(3-2)
test.hasOwnProperty('x'); //true
test.hasOwnProperty('y'); //true
test.hasOwnProperty('toStr'); //false
test._proto_.hasOwnProperty('toStr'); //true
//类的某些方法是定义在类的原型上,而原型链式共享的,所以通过 实例的 _proto_ 属性可以改写的类的方法,不过要慎用
3、class的表达式形式
声明类的时候,可以用类的表达式形式:
let ClassTest = class Test {
getClass () {
return Test.name; //类的 name 属性:总是返回class 后面的类名,跟构造函数一样
}
};
let test = new ClassTest();
test.getClass(); //Test
Test.name; //ReferenceError:Test is not defined
由上面的例子可以知道,Test只有在类的内部才存在,在外部上不存在的;
如果内部用不到,可以这样写:
let ClassTest = class {};
如果要马上执行类,可以这样写:
let test = new class {};
4、类的特性探究
(1)类不存在变量提升
也就是说,必须要先声明类,才能去生成实例,否则会报错;
let test = new Test();
class Test {}
(2)私有方法
原生ES6 不提供私有方法,只能自己想办法来实现:
①可以将私有方法移出类的内部,达到方法的私有化
class Widget {
foo (baz) {
bar.call(this, baz);
}
}
function bar(baz) {
return this.snaf = baz;
}
上面例子中,foo是公有方法,内部调用了bar.call(this, baz)
。这使得bar实际上成为了当前模块的私有方法。
②利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz) {
this[bar](baz);
}
// 私有方法
[bar](baz) {
return this[snaf] = baz;
}
};
上面代码中,bar和snaf都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。
③利用私有属性来实现:
class Foo {
#a;
#b;
#sum() { return #a + #b; } //私有方法
printSum() { console.log(#sum()); } //共有方法
constructor(a, b) { #a = a; #b = b; }
}
(3)私有属性
原生ES6 也不支持,但是现在有个提案,在属性名字之前加井号来表示私有属性;
class Point {
#x;
constructor(x = 0) {
#x = +x; // 写成 this.#x 亦可
}
get x() { return #x } // 私立属性和实例属性允许同名
set x(value) { #x = +value }
}
(4)this 指向问题
尤其需要注意,类的内部的this 默认指向实例对象,所以如果脱离实例来操作有this 的属性方法会报错,除非修改this 对象的指向;
class Logger {
printName(name = 'Troyes') {
this.print(`Hello ${name}`); // this 指向实例对象,下面调用这个方法的时候,this 指向 windows 所以报错
}
}
const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
解决这个问题,可以把这个属性方法写在构造方法 constructor 里面:
class Logger {
constructor () {
this.printName = this.printName.bind(this); // 把这个方法绑定到实例对象身上
}
}
// 或者利用箭头函数(箭头函数默认 this 就是绑定到声明的环境中)
class Logger {
constructor () {
this.printName = (name = 'Troyes') => {
this.print(`Hello ${name}`);
}
}
}
(5)class 的存取值函数
类的存值函数(getter)和取值函数(setter)都是定义在属性的Descriptor 对象上;
Test class {
constructor () {
}
get name () {
return 'getter:Troyes'
}
set name (val) {
return 'setter: val'
}
};
let test = new Test();
test.name; // getter:Troyes
test.name = 'Teemo'; //setter: Teemo
let descriptor = Object.getOwnPropertyDescriptor(Test.prototype, 'name');
"get" in descriptor; //true
"set" in descriptor; //true
(6)class 的静态方法
在定义类的时候,在一个方法前加上static关键字,那么该方法不会被实例继承,而是直接通过类来调用,这就是“静态方法”;
class Test {
constructor () { // do something}
static say () {
console.log('Hello World');
}
static sayHello () {
this.say(); //静态方法中,this 指向的是类,而不是实例
}
sayHello () {
console.log('Hello Troyes');
} // 静态方法可以和共有方法同名
}
let test = new Test();
Test.sayHello(); //Hello World
test.sayHello(); //Hello Troyes
test.say(); //TypeError: test.say is not a function
后面将会谈到类的继承问题,我们可以发现,子类可以继承父类的静态方法,同时静态方法还可以在super 对象上调用;
class NewTest extends Test {
static sayWel () {
return super.say() + 'again';
}
}
NewTest.sayHello(); //Hello World
NewTest.sayWel(); //Hello World again
5、类的继承
类的继承通过 extends 关键字来实现,语法如下:
class Person {
constructor (age, gender) {
this.age = age;
this.gender = gender;
}
printMsg() {
console.log(this.age, this.gender);
}
}
class NewPerson extends Person {
constructor (age, gender, job) {
super(age, gender); // 子类必须通过super() 方法才能构建出父类的 this 对象
this.job = job; // 子类 this 对象特有的部分,必须要写在 super() 方法后面,因为不调用super方法根本就构建不出this对象
}
printOtherMsg () {
console.log(this.job);
}
}
let person = new NewPerson (23, 'male', 'webDev');
person.printMsg(); // 23, male
person.printOtherMsg(); // webDev
(1)类的 prototype
属性和_proto_
属性
类作为构造函数的语法糖,理所当然同时有着这两个属性,也就是有着两条继承链:
prototype
属性指向对应的构造函数;
_proto_
属性是每个对象都有的;
- 子类的__proto__
属性表示构造函数的继承,总是指向父类。
- 子类prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。
NewPerson._proto_ === Person // true
NewPerson.prototype._proto_ === Person.prototype // true