在JS里面,它实际上是没有类的概念的,我们如果想要实现一个面向对象的一个编程,那么我们只能够借助构造函数,但是构造函数有着自身的缺点,首先第一点它的写法不清晰,我们需要继承的话就需要手动的去操作prototype;其次它面向对象的思想也不是很强烈,所以在ES6它封装了一层构造函数的语法糖,就叫Class,封装后有了一个更清晰的写法,而且更加像一个面向对象编程语言。
1:基本概念
构造函数语法糖
- 更加清晰的写法
- 面向对象编程
在ES5中:
在ES6中:// ES5 的构造函数 function Position(x, y) { this.x = x; this.y = y; } const position = new Position(1, 1);
在ES6中,首先有一个class修饰符,class将告诉编辑器声明了一个名叫Position的class。
class是构造函数的语法糖,我们怎么来看呢?我们可以看一下这个构造函数它的prototype的constructor是否是本身,如果是本身那就说明class的行为跟构造函数是保持一致的。class Position { constructor(x, y) { this.x = x; this.y = y; } } Position === Position.prototype.constructor; /* true */ const position = new Position(1, 1);
类的所有方法定义在类的prototype属性
在ES5里面,比如说我们在构造函数里面定义了一些方法,那么我们在实例里面是有这一个方法的,但是ES6不一样。在Class里面,所有的方法全部都定义到了prototype属性上面,也就是除了this.之外的其他都定义在了prototype属性上面去。
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
}
scan() {
console.log(this.x, this.y);
}
}
Position.prototype;
/*
{
constructor: class Position
scan: ƒ scan()
__proto__: Object
}
*/
内部方法不可枚举
内部的方法都到prototype上面去,但是这个prototype的方法却是不可枚举的,也就是说它的枚举属性是false。
如果这个时候我们想要得到这个枚举,想要知道它有哪些方法的话,我们就需要使用到getOwnPropertyNames。
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
}
scan() {
console.log(this.x, this.y);
}
}
Object.keys(Position.prototype);
/* [] */
Object.getOwnPropertyNames(Position.prototype);
/* ["constructor", "scan"] */
this指向 - 默认指向实例本身
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
}
scan() {
console.log(this.x, this.y);
}
}
const position = new Position(1, 1);
// 调用自身扫描
position.scan();
// 1 1
解构出来再去调用this指向就不一致了,会出问题报错:
const { scan } = position;
scan();
/* Cannot read property 'x' of undefined */
对于上面的this在解构后调用出现的指向问题出错我们可以有两种解决办法:
第一种方法,借助于箭头函数:
箭头函数在定义时this就已经指定了,也就是它什么定义的和在哪里定义的,它的this就是什么。
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
}
// 将scan()函数改为一个箭头函数,this永远指向定义时的位置
scan = () => {
console.log(this.x, this.y);
}
}
const position = new Position(1, 1);
const { scan } = position;
scan();
/* 1 1 */
第二种方法,手动的bind:
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
this.scan = this.scan.bind(this);
}
scan() {
console.log(this.x, this.y);
}
}
const position = new Position(1, 1);
const { scan } = position;
scan();
/* 1 1 */
2:方法属性
constructor 构造器,首先它是一个required,也就是它是一个必须的,如果我们没有传入它,那么就会默认生成一个空的一个constructor。
constructor - required - 默认为空函数
- new命令使用时调用一次constructor
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
console.log('constructor');
}
}
const position = new Position(1, 1);
/* constructor */
- 默认返回实例对象
class Position {
constructor(x, y) {
return Object.create({ name: 'Eric' });
}
}
const position = new Position(1, 1);
控制台输入:position
// 得到一个空对象:{}
position instanceof Position;
/* false */
类的实例
- new命令创建
- 实例的属性除this之外全部定义在原型对象上
hasOwnProperty();
getter与setter - 拦截器,拦截某个属性属性的存取行为
get是拦截它的取,set是拦截它的存,getter和setter必须是成对出现的,而且我们设置时需要设置getter和setter应该是哪一个,应该是当前的对象不存在的,否则就会报一个堆栈溢出,因为它会有一个循环调用的问题。
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
}
get z() {
return this.x;
}
set z(value) {
this.x = 2;
}
}
const position = new Position(1, 1);
static - 静态方法 - 不能被实例继承 - this指向类
static字面意思理解就是静态,它不能被实例继承,也就是说在实例里面是不能调用静态方法的,它只能通过类本身来调用,这时这个this指向的是当前class本身。
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
this.z = 100;
}
static scan() {
console.log(this);
}
}
const position = new Position(1, 1);
position.scan();
/* position.scan is not a function */
Position.scan();
/*
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
this.z = 100;
}
static scan() {
console.log(this);
}
}
*/
属性新写法
在之前的代码里面属性 x 和 y 都是定义在constructor内部的,这样实际上是比较清晰的但是有一个提案:把属性全部放在构造函数的最顶层,我们可以直接在构造函数的内部来定义一些变量,当然这个变量是不需要加修饰符的,这时候就默认加到了this的对象上面去。
class Count {
x = 0;
reduce() {
this.x = this.x - 1;
console.log(this.x);
}
increment() {
this.x = this.x + 1;
console.log(this.x);
}
}
const count = new Count();
count.increment();
count.increment();
count.increment();
count.increment();
// 1
2
3
4
3:类的继承
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
scan = () => {
console.log(this.x, this.y);
}
}
// SubPoint 继承了 Point
class SubPoint extends Point {
}
const subPoint = new SubPoint(1, 1);
subPoint;
/*
{
scan: () => { console.log(this.x, this.y); }
x: 1
y: 1
__proto__: Point
}
*/
子类必须调用super方法
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
scan = () => {
console.log(this.x, this.y);
}
}
class SubPoint extends Point {
constructor(...rest) {
// super(...rest)
}
}
const subPoint = new SubPoint(1, 1);
/* Must call super constructor in derived class before accessing 'this' or returning from derived constructor */
Object.getPrototypeOf - 可以在类中获取父类实例
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
scan = () => {
console.log(this.x, this.y);
}
}
class SubPoint extends Point {
constructor(...rest) {
super(...rest)
}
}
const subPoint = new SubPoint(1, 1);
Object.getPrototypeOf(subPoint);
/*
Point {
constructor: class SubPoint
__proto__: Object
}
*/